Skip to content

Commit 39bd92f

Browse files
KMX415javastraat
andcommitted
Fix firmware flash stream crashes and port contention.
Expose MeshCore USB source.connected for the release gate, always stop a matched source before esptool (reconnect loops still hold the port), and wait longer after flash before reconnecting. Co-Authored-By: Albert Einstein <javastraat@hotmail.com>
1 parent 9a0425f commit 39bd92f

4 files changed

Lines changed: 34 additions & 5 deletions

File tree

src/api/routes/meshcore_firmware_routes.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -312,12 +312,17 @@ async def body() -> AsyncIterator[bytes]:
312312
"text": f"Using MeshCore {fw['tag']} ({fw['flavor']}).",
313313
})
314314

315-
released = source is not None and source.connected
315+
# Always stop a matched source before esptool — even when
316+
# handshake failed, background reconnect still races the port.
317+
released = source is not None
316318
if released:
317319
yield _ndjson({
318320
"type": "line",
319321
"stream": "stdout",
320-
"text": f"Releasing {port} ({source.name} was connected)…",
322+
"text": (
323+
f"Releasing {port} ({source.name}"
324+
f"{'' if source.connected else ' reconnect loop'})…"
325+
),
321326
})
322327
await source.stop()
323328

@@ -350,7 +355,8 @@ async def body() -> AsyncIterator[bytes]:
350355
"stream": "stdout",
351356
"text": "Waiting for the board to finish rebooting…",
352357
})
353-
await asyncio.sleep(3.0)
358+
# ESP32-S3 USB companions often need >3s after flash.
359+
await asyncio.sleep(10.0)
354360
await source.start()
355361
yield _ndjson({
356362
"type": "line",

src/api/routes/meshtastic_firmware_routes.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,12 +330,17 @@ async def body() -> AsyncIterator[bytes]:
330330
"text": f"Using Meshtastic {fw['version']} ({fw['mcu']}).",
331331
})
332332

333-
released = source is not None and source.connected
333+
# Always stop a matched source before esptool — reconnect /
334+
# half-open serial still races the port when connected=False.
335+
released = source is not None
334336
if released:
335337
yield _ndjson({
336338
"type": "line",
337339
"stream": "stdout",
338-
"text": f"Releasing {port} ({source.name} was connected)…",
340+
"text": (
341+
f"Releasing {port} ({source.name}"
342+
f"{'' if source.connected else ' reconnect loop'})…"
343+
),
339344
})
340345
await source.stop()
341346

src/capture/meshcore_usb_source.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ def __init__(
7171
def name(self) -> str:
7272
return "meshcore_usb"
7373

74+
@property
75+
def connected(self) -> bool:
76+
"""True while the MeshCore companion serial session is live."""
77+
return self._connected
78+
7479
@property
7580
def is_running(self) -> bool:
7681
return self._running

tests/test_firmware_flash_routes.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,5 +149,18 @@ def test_erase_flag_omitted_from_cmd_when_false(self):
149149
self.assertIn("--erase-all", args)
150150

151151

152+
class TestMeshcoreSourceConnectedProperty(unittest.TestCase):
153+
"""Flash stream uses source.connected; SerialSource already exposes it."""
154+
155+
def test_meshcore_usb_exposes_connected(self):
156+
from src.capture.meshcore_usb_source import MeshcoreUsbCaptureSource
157+
from src.config import MeshcoreUsbConfig
158+
159+
source = MeshcoreUsbCaptureSource(MeshcoreUsbConfig())
160+
self.assertFalse(source.connected)
161+
source._connected = True
162+
self.assertTrue(source.connected)
163+
164+
152165
if __name__ == "__main__":
153166
unittest.main()

0 commit comments

Comments
 (0)