Files
brewpi/components/astirrer.py
T
jensandClaude Sonnet 5 f20e617d81 feat: add connect/disconnect status for real heater/stirrer hardware
Heater and Stirrer can be real serial hardware (hendi, Pololu1376), but
there was no connection concept at all - the constructors opened the
port and crashed the whole server if the device was missing, with no
way to see connection status or firmware version and no way to
reconnect without a restart.

Adds an observable Connectable mixin (components/connectable.py) shared
by AHeater/AStirrer; real devices defer opening the serial port to an
explicit connect(), auto-connect on server startup, and surface
Connected/FirmwareVersion/Simulated plus manual Connect/Disconnect over
the web GUI. Heating/stirring and Sud Start are all gated on connection
state, and a disconnect mid-brew force-stops the run via the same path
as a manual Stop.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YaPLuRPpyjWcwhMvCvpHCL
2026-07-03 19:18:59 +02:00

77 lines
1.4 KiB
Python

import abc
from components.connectable import Connectable
from contextlib import contextmanager
class AStirrer(Connectable):
def __init__(self, dt, simulated=True):
Connectable.__init__(self, simulated)
self.dt = dt
self.speed = 0
self.cycleTime = 1
self.dutyCycle = 1
self.cycleCounter = 0
self.isOn = 1
def process(self):
isOn = self.isOn
self.cycleCounter += self.dt
if self.cycleCounter >= self.cycleTime:
self.cycleCounter -= self.cycleTime
isOn = 0
if self.cycleCounter >= (self.cycleTime * (1-self.dutyCycle)):
if not isOn:
isOn = 1
if self.isOn != isOn:
if isOn:
self._on_set_speed(self.speed)
else:
self._on_set_speed(0)
self.isOn = isOn
self._on_process()
def set_speed(self, speed):
self.speed = speed
if self.isOn or self.dutyCycle == 1:
self._on_set_speed(speed)
def set_cycle_time(self, time):
self.cycleTime = time
def set_duty_cycle(self, dutyCycle):
self.dutyCycle = dutyCycle
@abc.abstractmethod
def get_speed(self):
return None
@abc.abstractmethod
def _on_set_speed(self, speed):
pass
@abc.abstractmethod
def _on_process(self):
pass
@contextmanager
def open(self):
try:
self.activate(True)
yield None
finally:
self.activate(False)
return None
@abc.abstractmethod
def activate(self, enable):
pass
@abc.abstractmethod
def is_activated(self):
pass