fix: guard HeaterHendi/StirrerPololu1376 activate() against comm errors

Live-testing on the brewpi Pi found that a connected-but-unresponsive
device (e.g. the hendi sitting in its ungraceful-disconnect lockout, see
docs/hendi_lockout_findings.md) crashed the whole server: activate() -
called both from HeaterTask's `with device.open():` and from a client's
Connect/Disconnect command - raised uncaught, escaping the gathered task
and killing the entire asyncio.gather in TaskManager.start().

activate()/is_activated()/process() on both real actors now catch comm
exceptions and self-heal via disconnect() (the same path a genuine
unplug already takes), instead of letting the exception propagate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YaPLuRPpyjWcwhMvCvpHCL
This commit is contained in:
2026-07-03 19:39:07 +02:00
co-authored by Claude Sonnet 5
parent 1db0e66ab7
commit 0877e7754b
2 changed files with 65 additions and 26 deletions
+31 -13
View File
@@ -47,27 +47,45 @@ class StirrerPololu1376(AStirrer):
def activate(self, enable):
if not self.connected:
return
print("activate {}".format(enable))
if enable:
self.drv.go()
else:
self.drv.stop()
try:
print("activate {}".format(enable))
if enable:
self.drv.go()
else:
self.drv.stop()
except Exception as e:
# See HeaterHendi.activate()'s own comment: a device that was
# connected can still fail a live command (cable pulled
# mid-run, ...) - this is called both from StirrerTask's tick
# loop (with self.device.open(): activate(True)/(False)) and
# from a client's Connect/Disconnect command, neither of which
# expect activate() itself to ever raise.
print(f"StirrerPololu1376: comm error in activate({enable}): {e}")
self.disconnect()
def _on_set_speed(self, speed):
if not self.connected:
return
self.drv.motor_forward(speed)
print("Set speed to {} %".format(speed))
try:
self.drv.motor_forward(speed)
print("Set speed to {} %".format(speed))
except Exception as e:
print(f"StirrerPololu1376: comm error in _on_set_speed: {e}")
self.disconnect()
def _on_process(self):
if not self.connected:
return
status = self.drv.get_variable(Varid.STATUS)
limit_status = self.drv.get_variable(Varid.STATUS_LIMIT_STATUS)
if status & 0x01 == 0x01:
if limit_status & 0x01 == 0x01:
print("Recover after motor error!")
self.drv.go()
try:
status = self.drv.get_variable(Varid.STATUS)
limit_status = self.drv.get_variable(Varid.STATUS_LIMIT_STATUS)
if status & 0x01 == 0x01:
if limit_status & 0x01 == 0x01:
print("Recover after motor error!")
self.drv.go()
except Exception as e:
print(f"StirrerPololu1376: comm error in _on_process: {e}")
self.disconnect()
if __name__ == '__main__':