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
This commit is contained in:
2026-07-07 19:36:11 +02:00
co-authored by Claude Sonnet 5
parent d19f1066d1
commit a19d2504fb
2 changed files with 31 additions and 6 deletions
+26 -1
View File
@@ -48,6 +48,24 @@ class Varid(enum.Enum):
MOTOR_CURRENT_LIMIT_OCCURRENCE_COUNT = 45 MOTOR_CURRENT_LIMIT_OCCURRENCE_COUNT = 45
# Bit flags of the STATUS variable (Varid.STATUS). A non-zero
# SAFE_START_VIOLATION is the "fail-safe" state: the controller accepted
# the GO command but still refuses to spin the motor. The other bits are
# the SMC's other error conditions, which also stop the motor and can
# make GO fail to bring it back.
class StatusError(enum.IntFlag):
SAFE_START_VIOLATION = 0x0001
REQUIRED_CHANNEL_INVALID = 0x0002
SERIAL_ERROR = 0x0004
COMMAND_TIMEOUT = 0x0008
LIMIT_KILL_SWITCH = 0x0010
LOW_VIN = 0x0020
HIGH_VIN = 0x0040
OVER_TEMPERATURE = 0x0080
MOTOR_DRIVER_ERROR = 0x0100
ERR_LINE_HIGH = 0x0200
class Pololu1376: class Pololu1376:
def __init__(self, serial_port): def __init__(self, serial_port):
# Deferred-open pyserial idiom: serial_port is constructed with no # Deferred-open pyserial idiom: serial_port is constructed with no
@@ -117,8 +135,15 @@ class Pololu1376:
return rsp[1:] return rsp[1:]
def get_status_errors(self):
return StatusError(self.get_variable(Varid.STATUS))
def go(self): def go(self):
return self.cmd("GO") rsp = self.cmd("GO")
errors = self.get_status_errors()
if errors:
print(f"Pololu1376: ERROR - still in fail-safe after GO, active errors: {errors!r}")
return rsp
def stop(self): def stop(self):
return self.cmd("X") return self.cmd("X")
+5 -5
View File
@@ -75,11 +75,11 @@ class StirrerPololu1376(AStirrer):
if not self.connected: if not self.connected:
return return
try: try:
status = self.drv.get_variable(Varid.STATUS) errors = self.drv.get_status_errors()
limit_status = self.drv.get_variable(Varid.STATUS_LIMIT_STATUS) if errors:
if status & 0x01 == 0x01: print(f"StirrerPololu1376: motor controller reports errors: {errors!r}")
if limit_status & 0x01 == 0x01: if StatusError.SAFE_START_VIOLATION in errors:
print("Recover after motor error!") print("Recover after safe start violation!")
self.drv.go() self.drv.go()
except Exception as e: except Exception as e:
print(f"StirrerPololu1376: comm error in _on_process: {e}") print(f"StirrerPololu1376: comm error in _on_process: {e}")