5.1 KiB
Auto-detect Upload Port Fix
Issue: #16
Date: 2026-08-04
Status: Approved
Problem
The post-script tools/pio_upload_port.py sends ID? to the ESP and reads the first non-empty response to determine device type (server/client). The client responds correctly with SylDa_Client, but during upload there was active log output (ECDH handshake) on the serial port. The query_id() function reads the first log message instead of the ID response, causing auto-detect to fail.
Root cause: query_id() blindly takes the first non-empty line, even if it's log output.
Solution
Two-part fix for maximum robustness:
- Script-side: Filter responses — keep reading until a line containing
SylDa_ServerorSylDa_Clientappears - Firmware-side: Pause all serial output during
ID?response
No protocol changes. Both parts work independently but together provide robustness.
Architecture
┌─────────────────────┐ ID?\n ┌─────────────────────┐
│ pio_upload_port │ ─────────────▶│ Firmware (ESP32) │
│ (Python script) │ │ │
│ │◀─────────────│ serialMuted=true │
│ query_id() reads │ SylDa_* │ Serial.flush() │
│ until match │ │ response │
└─────────────────────┘ │ serialMuted=false │
└─────────────────────┘
Components
1. Script: tools/pio_upload_port.py
Change: query_id() function filters for expected ID instead of returning first non-empty line.
Before:
def query_id(port, timeout=5):
# ...
while time.time() < deadline:
line = ser.readline().decode("utf-8", errors="ignore").strip()
if line:
ser.close()
return line # Returns first non-empty, even if log
# ...
After:
def query_id(port, timeout=5):
try:
ser = serial.Serial(port, 115200, timeout=0.5)
time.sleep(0.1)
ser.reset_input_buffer()
ser.write(b"ID?\n")
deadline = time.time() + timeout
while time.time() < deadline:
line = ser.readline().decode("utf-8", errors="ignore").strip()
if "SylDa_Server" in line or "SylDa_Client" in line:
ser.close()
return line
ser.close()
except (serial.SerialException, OSError):
pass
return None
No other changes needed — the rest of the script already uses expected_id in resp checks.
2. Firmware: src/client.cpp and src/server.cpp
Changes:
- Global
serialMutedflag - Serial mute during
ID?handling - Wrapper macros for existing Serial calls
Implementation:
// Global flag (file scope)
static bool serialMuted = false;
// Wrapper macros
#define SERIAL_PRINTLN(msg) if (!serialMuted) Serial.println(msg)
#define SERIAL_PRINT(msg) if (!serialMuted) Serial.print(msg)
// In serial command handling
} else if (serialBuf == "ID?") {
Serial.flush();
serialMuted = true;
Serial.println("SylDa_Client"); // or SylDa_Server
delay(50); // Ensure response transmitted
serialMuted = false;
}
Existing Serial.println() calls: Replace with SERIAL_PRINTLN() macro.
ESP_LOG macros: Unchanged — these use IDF logging, not Arduino Serial.
3. Server firmware
Identical implementation to client, with SylDa_Server response.
Data Flow
- Script opens serial port, sends
ID?\n - Firmware receives command:
Serial.flush()empties TX bufferserialMuted = trueblocks new outputSerial.println("SylDa_Client")— only outputdelay(50)ensures transmissionserialMuted = falsereleases mute
- Script reads lines until
SylDa_*appears (timeout 5s) - Match → port selected
Error Handling
| Scenario | Behavior |
|---|---|
| No response | Script timeout → manual port selection (existing) |
| Wrong response | Script detects mismatch → manual selection (existing) |
| Multiple ports, both respond | Script matches correct one, selects it |
| Firmware mute stuck | Impossible — mute released immediately after response |
| Buffer overflow | Serial.flush() prevents this |
Testing
- Connect both server and client simultaneously
- Run
pio run -e s3lite-client -t upload— should auto-detect client - Run
pio run -e s3lite-server -t upload— should auto-detect server - Verify log output resumes after
ID?response - Test with active ECDH handshake in progress
Files Changed
tools/pio_upload_port.py—query_id()filteringsrc/client.cpp— serial mute implementationsrc/server.cpp— serial mute implementation
Rollback
If issues arise, revert to manual port selection:
pio run -e s3lite-client -t upload --upload-port /dev/serial/by-id/...