Auto-detect upload port via ID? serial query (issue #11)
- Add ID? command to firmware: server responds 'SylDa_Server', client 'SylDa_Client' - Add tools/pio_upload_port.py POST-script that queries connected ESP32s - Remove hardcoded upload_port from platformio.ini - Script logic: 1 ESP32 → use directly, 2+ → ID? query, no match → prompt user - Requires pyserial for serial queries (graceful fallback if missing)
This commit is contained in:
parent
f8d4e96241
commit
bf6619aa54
4 changed files with 128 additions and 4 deletions
|
|
@ -11,11 +11,12 @@ build_flags =
|
||||||
lib_deps =
|
lib_deps =
|
||||||
fastled/FastLED@^3
|
fastled/FastLED@^3
|
||||||
h2zero/NimBLE-Arduino@^2
|
h2zero/NimBLE-Arduino@^2
|
||||||
upload_port = /dev/serial/by-id/usb-Espressif_USB_JTAG_serial_debug_unit_44:1B:F6:F4:CF:14-if00
|
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
upload_flags = --no-stub
|
upload_flags = --no-stub
|
||||||
build_src_filter = +<main.cpp> -<client.cpp>
|
build_src_filter = +<main.cpp> -<client.cpp>
|
||||||
extra_scripts = pre:tools/pio_version_pre.py
|
extra_scripts =
|
||||||
|
pre:tools/pio_version_pre.py
|
||||||
|
post:tools/pio_upload_port.py
|
||||||
|
|
||||||
[env:s3lite-client]
|
[env:s3lite-client]
|
||||||
platform = espressif32@6.7.0
|
platform = espressif32@6.7.0
|
||||||
|
|
@ -31,8 +32,9 @@ build_flags =
|
||||||
lib_deps =
|
lib_deps =
|
||||||
fastled/FastLED@^3
|
fastled/FastLED@^3
|
||||||
h2zero/NimBLE-Arduino@^2
|
h2zero/NimBLE-Arduino@^2
|
||||||
upload_port = /dev/ttyACM2
|
|
||||||
upload_speed = 115200
|
upload_speed = 115200
|
||||||
upload_flags = --no-stub
|
upload_flags = --no-stub
|
||||||
build_src_filter = +<client.cpp> -<main.cpp>
|
build_src_filter = +<client.cpp> -<main.cpp>
|
||||||
extra_scripts = pre:tools/pio_version_pre.py
|
extra_scripts =
|
||||||
|
pre:tools/pio_version_pre.py
|
||||||
|
post:tools/pio_upload_port.py
|
||||||
|
|
|
||||||
|
|
@ -263,6 +263,8 @@ void loop() {
|
||||||
} else {
|
} else {
|
||||||
Serial.println("NO_KEY");
|
Serial.println("NO_KEY");
|
||||||
}
|
}
|
||||||
|
} else if (serialBuf == "ID?") {
|
||||||
|
Serial.println("SylDa_Client");
|
||||||
} else if (serialBuf == "VERSION?") {
|
} else if (serialBuf == "VERSION?") {
|
||||||
Serial.print("BUILD_NR:");
|
Serial.print("BUILD_NR:");
|
||||||
Serial.println(FIRMWARE_VERSION_CLIENT);
|
Serial.println(FIRMWARE_VERSION_CLIENT);
|
||||||
|
|
|
||||||
|
|
@ -485,6 +485,8 @@ void loop() {
|
||||||
} else {
|
} else {
|
||||||
Serial.println("NO_KEY");
|
Serial.println("NO_KEY");
|
||||||
}
|
}
|
||||||
|
} else if (serialBuf == "ID?") {
|
||||||
|
Serial.println("SylDa_Server");
|
||||||
} else if (serialBuf == "VERSION?") {
|
} else if (serialBuf == "VERSION?") {
|
||||||
Serial.print("BUILD_NR:");
|
Serial.print("BUILD_NR:");
|
||||||
Serial.println(FIRMWARE_VERSION);
|
Serial.println(FIRMWARE_VERSION);
|
||||||
|
|
|
||||||
118
tools/pio_upload_port.py
Normal file
118
tools/pio_upload_port.py
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""POST-script: auto-detect upload_port by querying connected ESP32s.
|
||||||
|
|
||||||
|
Each ESP32 running SylDa firmware responds to the serial command "ID?"
|
||||||
|
with either "SylDa_Server" or "SylDa_Client". This script scans
|
||||||
|
/dev/serial/by-id/ for Espressif devices, queries each one, and sets
|
||||||
|
UPLOAD_PORT for the matching environment.
|
||||||
|
|
||||||
|
If no device responds (e.g. first flash), the user is prompted to
|
||||||
|
select a port manually.
|
||||||
|
"""
|
||||||
|
Import("env")
|
||||||
|
|
||||||
|
if env.IsIntegrationDump():
|
||||||
|
Return()
|
||||||
|
|
||||||
|
ENV_TO_ID = {
|
||||||
|
"s3lite-server": "SylDa_Server",
|
||||||
|
"s3lite-client": "SylDa_Client",
|
||||||
|
}
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
try:
|
||||||
|
import serial
|
||||||
|
except ImportError:
|
||||||
|
print(" WARNING: pyserial not installed, skipping auto-detect")
|
||||||
|
print(" Install: pip install pyserial")
|
||||||
|
Return()
|
||||||
|
|
||||||
|
|
||||||
|
def find_esp32_ports():
|
||||||
|
by_id = "/dev/serial/by-id"
|
||||||
|
if not os.path.isdir(by_id):
|
||||||
|
return []
|
||||||
|
ports = []
|
||||||
|
for entry in sorted(os.listdir(by_id)):
|
||||||
|
if "Espressif" in entry or "espressif" in entry:
|
||||||
|
ports.append(os.path.join(by_id, entry))
|
||||||
|
return ports
|
||||||
|
|
||||||
|
|
||||||
|
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 line:
|
||||||
|
ser.close()
|
||||||
|
return line
|
||||||
|
ser.close()
|
||||||
|
except (serial.SerialException, OSError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def detect_port(source, target, env):
|
||||||
|
env_name = env.subst("$PIOENV")
|
||||||
|
expected_id = ENV_TO_ID.get(env_name)
|
||||||
|
|
||||||
|
if not expected_id:
|
||||||
|
print(f" Unknown environment '{env_name}', skipping auto-detect")
|
||||||
|
return
|
||||||
|
|
||||||
|
ports = find_esp32_ports()
|
||||||
|
|
||||||
|
if not ports:
|
||||||
|
print(f" No ESP32 serial ports found in /dev/serial/by-id/")
|
||||||
|
print(f" Connect an ESP32 and try again")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if len(ports) == 1:
|
||||||
|
port = ports[0]
|
||||||
|
resp = query_id(port, timeout=5)
|
||||||
|
if resp and expected_id in resp:
|
||||||
|
print(f" Auto-detected {expected_id} on {port}")
|
||||||
|
else:
|
||||||
|
if resp:
|
||||||
|
print(f" Found '{resp}' on {port} (expected {expected_id})")
|
||||||
|
print(f" Only one ESP32 connected, using {port}")
|
||||||
|
env["UPLOAD_PORT"] = port
|
||||||
|
return
|
||||||
|
|
||||||
|
for port in ports:
|
||||||
|
resp = query_id(port, timeout=5)
|
||||||
|
if resp and expected_id in resp:
|
||||||
|
print(f" Found {expected_id} on {port}")
|
||||||
|
env["UPLOAD_PORT"] = port
|
||||||
|
return
|
||||||
|
|
||||||
|
print(f"\n Cannot auto-detect {expected_id} (no ID? response matched)")
|
||||||
|
print(f" Available ESP32 ports:")
|
||||||
|
for i, port in enumerate(ports, 1):
|
||||||
|
resp = query_id(port, timeout=3)
|
||||||
|
label = f" ({resp})" if resp else ""
|
||||||
|
print(f" {i}: {port}{label}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
choice = input(f" Select port for {env_name} [{expected_id}] (1-{len(ports)}): ")
|
||||||
|
idx = int(choice) - 1
|
||||||
|
if 0 <= idx < len(ports):
|
||||||
|
env["UPLOAD_PORT"] = ports[idx]
|
||||||
|
return
|
||||||
|
except (ValueError, EOFError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
print(" Invalid choice, aborting upload")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
env.AddPreAction("upload", detect_port)
|
||||||
Loading…
Reference in a new issue