Files
brewpi/components/actor/stirrerpololu1376.py
T
jensandClaude Sonnet 5 a19d2504fb stirrer: decode and report Pololu SMC status errors instead of guessing
Adds StatusError flags for the STATUS register and get_status_errors();
go() now reports if a fail-safe (safe-start violation or other error)
persists after GO instead of failing silently. _on_process uses the
same decoding to log any active error and only auto-recovers on a
safe-start violation, rather than the old narrow status & limit_status
heuristic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EU3NTwLygh8jkrnqPoHNXF
2026-07-07 19:36:11 +02:00

132 lines
3.1 KiB
Python

from contextlib import contextmanager
from components import AStirrer
from components.actor.pololu1376 import *
import time
import serial
class StirrerPololu1376(AStirrer):
def name(self):
return "Pololu1376"
def __init__(self, dt, params):
AStirrer.__init__(self, dt, simulated=False)
# Deferred-open pyserial idiom - see Pololu1376's own constructor
# comment: connect() is what actually opens params['port'] (and
# sets output flow control, which itself requires an open port).
ser = serial.Serial()
ser.port = params['port']
ser.baudrate = params['speed']
self.drv = Pololu1376(ser)
def connect(self):
ok = self.drv.connect()
self.connected = ok
return ok
def disconnect(self):
self.drv.disconnect()
self.connected = False
@contextmanager
def remote_open(self):
try:
self.activate(True)
yield None
finally:
self.activate(False)
def get_speed(self):
if self.isOn and self.is_activated():
return self.speed
return 0
def activate(self, enable):
if not self.connected:
return
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
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
try:
errors = self.drv.get_status_errors()
if errors:
print(f"StirrerPololu1376: motor controller reports errors: {errors!r}")
if StatusError.SAFE_START_VIOLATION in errors:
print("Recover after safe start violation!")
self.drv.go()
except Exception as e:
print(f"StirrerPololu1376: comm error in _on_process: {e}")
self.disconnect()
if __name__ == '__main__':
s = StirrerPololu1376(dt=1.0, params={'port': "/dev/ttyACM0", 'speed': 115200})
s.connect()
with s.remote_open():
print("Set some speeds")
s.set_speed(50)
time.sleep(2)
s.set_speed(20)
time.sleep(2)
s.set_speed(50)
time.sleep(2)
print("Pulsed operation, duty cycle = 50%")
s.set_duty_cycle(0.5)
s.set_cycle_time(10)
for i in range(1, 30):
s.process()
time.sleep(1)
print("Pulsed operation, duty cycle = 20%")
s.set_duty_cycle(0.2)
s.set_cycle_time(10)
for i in range(1, 30):
s.process()
time.sleep(1)
print("Continious operation")
s.set_duty_cycle(1.0)
s.set_speed(50)
time.sleep(3.0)
print("Continious operation increasing speed")
for speed in range (25, 75, 5):
s.set_duty_cycle(1.0)
s.set_speed(speed)
for d in range(0,3,1):
s.process()
time.sleep(1.0)
print("Leaving context")