fix: heater task permanently died after a mid-brew unplug, never recovered
Root cause (from the brewpi Pi's log): HendiCtrl.disconnect() only caught HendiException (a protocol-level NAK), but a genuine unplug fails deep inside pyserial itself - flushInput()/readline() raise termios.error/OSError on the now-dead fd. That escaped disconnect() uncaught (skipping self.ser.close() too), then kept propagating up through every caller that invoked disconnect() from its own except handler (HeaterHendi.process()/activate(), AHeater.open()'s context-manager exit), all the way out of HeaterTask.on_process()'s coroutine entirely. Nothing restarts a dead ATask, so the heater's process()/duty-cycle loop was gone for the rest of the process's life - reconnecting afterward changed `connected` back to True but nothing was left running to ever call process() again. Fixes: - HendiCtrl.disconnect() now catches broadly and always closes the port, matching Pololu1376.disconnect()'s already-correct pattern. - HeaterTask/StirrerTask.on_process() gain an outer retry loop as a safety net: even an unanticipated exception now logs, marks disconnected, and re-enters rather than permanently killing the task. Verified by reproducing the exact failure (comm error raising a raw OSError, not HendiException) against a fake device - the task now survives and resumes process()ing once reconnected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YaPLuRPpyjWcwhMvCvpHCL
This commit is contained in:
+18
-5
@@ -66,11 +66,24 @@ class StirrerTask(ATask):
|
||||
# observer.
|
||||
await self.send({'Connected': self.device.connected})
|
||||
|
||||
with self.device.open():
|
||||
while True:
|
||||
# Outer retry loop - see HeaterTask.on_process()'s own comment for
|
||||
# why: nothing restarts a dead ATask, so an exception escaping this
|
||||
# coroutine entirely would permanently stop this device from ever
|
||||
# being process()'d again, even after a later successful reconnect.
|
||||
while True:
|
||||
try:
|
||||
with self.device.open():
|
||||
while True:
|
||||
try:
|
||||
self.device.process()
|
||||
except Exception as e:
|
||||
print(f"StirrerTask: comm error, marking disconnected: {e}")
|
||||
self.device.disconnect()
|
||||
await asyncio.sleep(self.interval)
|
||||
except Exception as e:
|
||||
print(f"StirrerTask: unexpected error, recovering: {e}")
|
||||
try:
|
||||
self.device.process()
|
||||
except Exception as e:
|
||||
print(f"StirrerTask: comm error, marking disconnected: {e}")
|
||||
self.device.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
await asyncio.sleep(self.interval)
|
||||
|
||||
Reference in New Issue
Block a user