server: graceful Ctrl-C shutdown; HendiCtrl closes connection cleanly

On KeyboardInterrupt, brewpi.py cancels all asyncio tasks (letting
HeaterTask's `with device.open()` finally block run), then calls
heater.close() as belt-and-suspenders before closing the event loop.

On shutdown HeaterHendi.activate(False) now sets the switch to 0 instead
of disabling remote control, so the Hendi stays in remote mode at 0 W
rather than dropping back to its local panel state.  HendiCtrl.close()
does the same (setSwitch(0)) then closes the serial port.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 18:26:54 +02:00
co-authored by Claude Sonnet 4.6
parent 29dd5267a9
commit 5751d26873
4 changed files with 33 additions and 2 deletions
+4 -1
View File
@@ -24,7 +24,7 @@ class HeaterHendi(AHeater):
if enable: if enable:
self.hendi.remoteEnable(1) self.hendi.remoteEnable(1)
else: else:
self.hendi.remoteEnable(0) self.hendi.setSwitch(0)
def is_activated(self): def is_activated(self):
s = self.hendi.isRemoteEnable() s = self.hendi.isRemoteEnable()
@@ -44,3 +44,6 @@ class HeaterHendi(AHeater):
def get_power(self): def get_power(self):
return self.power_eff return self.power_eff
def close(self):
self.hendi.close()
+7
View File
@@ -183,6 +183,13 @@ class HendiCtrl:
def getPowerWatts(self): def getPowerWatts(self):
return int(self.toWatts(self.getPowerDigits()) + 0.5) return int(self.toWatts(self.getPowerDigits()) + 0.5)
def close(self):
try:
self.setSwitch(0)
except Exception:
pass
self.ser.close()
if __name__ == '__main__': if __name__ == '__main__':
+3
View File
@@ -20,6 +20,9 @@ class AHeater(AttributeChange):
def get_powers(self): def get_powers(self):
pass pass
def close(self):
pass
@contextmanager @contextmanager
def open(self): def open(self):
try: try:
+19 -1
View File
@@ -190,4 +190,22 @@ if __name__ == '__main__':
threading.Thread(target=http_server.serve_forever, daemon=True).start() threading.Thread(target=http_server.serve_forever, daemon=True).start()
print("Serving {} on http://0.0.0.0:{}".format(web_dir, args.http_port)) print("Serving {} on http://0.0.0.0:{}".format(web_dir, args.http_port))
server.run_forever() loop = asyncio.get_event_loop()
try:
loop.run_forever()
except KeyboardInterrupt:
print("\nShutting down...")
finally:
# Cancel all tasks so their finally blocks (e.g. HeaterTask's
# `with device.open()`) get a chance to run before we close the port.
pending = asyncio.all_tasks(loop)
for task in pending:
task.cancel()
if pending:
loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True))
# Belt-and-suspenders: explicitly close the heater connection even if
# the task cleanup above failed to reach HeaterHendi.activate(False).
heater.close()
http_server.shutdown()
loop.close()
print("Server stopped.")