Same bug class as the earlier heater fix: TempSensorTask.on_process()
called self.sensor.temperature() every tick with no exception handling
at all, and the constructor even called it once synchronously at
startup to prime the value. A read failure there would either crash
the whole server before the event loop started, or permanently kill
TempSensorTask's coroutine mid-run - nothing restarts a dead ATask, so
the sensor would never be read again for the rest of the process's
life.
TempSensor_max31865.temperature() now catches read failures and calls
a new reopen() (closes/reopens the spidev handle - the closest
equivalent to unplug/replug for a bus peripheral) instead of raising,
holding the last-known-good reading meanwhile. reopen() itself never
raises either, learned from HendiCtrl.disconnect() previously letting
a failure escape the same way. Belt-and-suspenders guards added at the
TempSensorTask level too, matching HeaterTask/StirrerTask.
Verified against a fake spidev: a read failure no longer raises,
reopen() is attempted automatically, and readings resume once the bus
responds again - even when reopen() itself also fails.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YaPLuRPpyjWcwhMvCvpHCL
TempSensorSim/TempSensor_max31865's temperature() now stores its
reading on self.temp, so ATemperatureSensor's inherited AttributeChange
(previously never triggered by anything) actually fires. TempSensorTask
no longer keeps its own shadow copy of the reading - it registers its
websocket-push callback on self.sensor directly and just drives the
read each tick; server/brewpi.py's TC-feeding registration moved from
sensor_task to sensor for the same reason.
Priming read happens before registering the callback (not after) since
all tasks are built synchronously at module level, before the asyncio
event loop starts - registering first would fire on_temp_changed's
asyncio.create_task() with no running loop.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpePKZiEZWbGo9HrfuML6U
Callbacks were registered inside the async on_process() coroutine,
meaning they weren't active until the event loop started ticking.
Moving them to __init__ ensures they're wired up at construction time.
Also removes remaining debug prints from the affected task files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tqxrk8uj4M3w3d3eXm3xK8