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:
@@ -34,28 +34,49 @@ class HeaterHendi(AHeater):
|
|||||||
def activate(self, enable):
|
def activate(self, enable):
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
return
|
return
|
||||||
if enable:
|
try:
|
||||||
self.hendi.remoteEnable(1)
|
if enable:
|
||||||
else:
|
self.hendi.remoteEnable(1)
|
||||||
self.hendi.setSwitch(0)
|
else:
|
||||||
|
self.hendi.setSwitch(0)
|
||||||
|
except Exception as e:
|
||||||
|
# A device that was connected can still fail a live command (the
|
||||||
|
# hendi's own idle-timeout auto-suspend, a cable pulled mid-run,
|
||||||
|
# ...) - treat that the same as a disconnect rather than letting
|
||||||
|
# it escape uncaught, since this is called both from HeaterTask'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"HeaterHendi: comm error in activate({enable}): {e}")
|
||||||
|
self.disconnect()
|
||||||
|
|
||||||
def is_activated(self):
|
def is_activated(self):
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
return False
|
return False
|
||||||
s = self.hendi.isRemoteEnable()
|
try:
|
||||||
return '1' in s
|
s = self.hendi.isRemoteEnable()
|
||||||
|
return '1' in s
|
||||||
|
except Exception as e:
|
||||||
|
print(f"HeaterHendi: comm error in is_activated: {e}")
|
||||||
|
self.disconnect()
|
||||||
|
return False
|
||||||
|
|
||||||
def process(self):
|
def process(self):
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
self.power_eff = 0
|
self.power_eff = 0
|
||||||
return
|
return
|
||||||
power = self.power_set
|
try:
|
||||||
if power == 0:
|
power = self.power_set
|
||||||
self.hendi.setSwitch(0)
|
if power == 0:
|
||||||
else:
|
self.hendi.setSwitch(0)
|
||||||
self.hendi.setSwitch(1)
|
else:
|
||||||
self.hendi.setPowerWatts(power)
|
self.hendi.setSwitch(1)
|
||||||
self.power_eff = self.hendi.getPowerWatts() if power > 0 else 0
|
self.hendi.setPowerWatts(power)
|
||||||
|
self.power_eff = self.hendi.getPowerWatts() if power > 0 else 0
|
||||||
|
except Exception as e:
|
||||||
|
print(f"HeaterHendi: comm error in process: {e}")
|
||||||
|
self.disconnect()
|
||||||
|
self.power_eff = 0
|
||||||
|
|
||||||
def set_power(self, power):
|
def set_power(self, power):
|
||||||
self.power_set = min(self.get_power_max(), power)
|
self.power_set = min(self.get_power_max(), power)
|
||||||
|
|||||||
@@ -47,27 +47,45 @@ class StirrerPololu1376(AStirrer):
|
|||||||
def activate(self, enable):
|
def activate(self, enable):
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
return
|
return
|
||||||
print("activate {}".format(enable))
|
try:
|
||||||
if enable:
|
print("activate {}".format(enable))
|
||||||
self.drv.go()
|
if enable:
|
||||||
else:
|
self.drv.go()
|
||||||
self.drv.stop()
|
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):
|
def _on_set_speed(self, speed):
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
return
|
return
|
||||||
self.drv.motor_forward(speed)
|
try:
|
||||||
print("Set speed to {} %".format(speed))
|
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):
|
def _on_process(self):
|
||||||
if not self.connected:
|
if not self.connected:
|
||||||
return
|
return
|
||||||
status = self.drv.get_variable(Varid.STATUS)
|
try:
|
||||||
limit_status = self.drv.get_variable(Varid.STATUS_LIMIT_STATUS)
|
status = self.drv.get_variable(Varid.STATUS)
|
||||||
if status & 0x01 == 0x01:
|
limit_status = self.drv.get_variable(Varid.STATUS_LIMIT_STATUS)
|
||||||
if limit_status & 0x01 == 0x01:
|
if status & 0x01 == 0x01:
|
||||||
print("Recover after motor error!")
|
if limit_status & 0x01 == 0x01:
|
||||||
self.drv.go()
|
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__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
Reference in New Issue
Block a user