Files
brewpi/components/actor/heater_hendi.py
T
jensandClaude Sonnet 5 0877e7754b 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
2026-07-03 19:39:07 +02:00

89 lines
2.3 KiB
Python
Executable File

from components.aheater import AHeater
from components.actor.hendiCtrl import HendiCtrl
class HeaterHendi(AHeater):
def __init__(self, params):
AHeater.__init__(self, simulated=False)
self.hendi = HendiCtrl(params['port'], params['speed'])
self.power_set = 0
self.power_eff = 0
def get_power_min(self):
return 0
def get_power_max(self):
caps = self.hendi.getCapabilties()
return caps['pwr_watts_max']
def get_powers(self):
caps = self.hendi.getCapabilties()
return [0] + caps['pwr_list']
def connect(self):
ok = self.hendi.connect()
self.connected = ok
self.firmware_version = self.hendi.sw_ver if ok else None
return ok
def disconnect(self):
self.hendi.disconnect()
self.connected = False
self.firmware_version = None
def activate(self, enable):
if not self.connected:
return
try:
if enable:
self.hendi.remoteEnable(1)
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):
if not self.connected:
return False
try:
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):
if not self.connected:
self.power_eff = 0
return
try:
power = self.power_set
if power == 0:
self.hendi.setSwitch(0)
else:
self.hendi.setSwitch(1)
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):
self.power_set = min(self.get_power_max(), power)
def get_power(self):
return self.power_eff
def close(self):
self.disconnect()