Files
brewpi/scripts/hendi_ctrl_app.py
T
jensandClaude Sonnet 5 d079db8b37 feat: add --reset and --trigger-error-state to hendi_ctrl_app.py
--trigger-error-state deliberately reproduces the Hendi's
ungraceful-disconnect lockout (remoteEnable(True) without a matching
disable) so it can be tested on demand. --reset toggles the reset
line, but per hardware testing it does NOT clear that lockout - only
the device's physical knob/power cycle does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpePKZiEZWbGo9HrfuML6U
2026-07-02 17:35:05 +02:00

102 lines
3.3 KiB
Python

#!/usr/bin/env python3
"""CLI tool for talking to a Hendi induction plate over serial - see
components/actor/hendiCtrl.py for the protocol/HendiCtrl class this wraps."""
import argparse as ap
import time
from components.actor.hendiCtrl import HendiCtrl
DEFAULT_PORT = '/dev/ttyUSB0'
DEFAULT_BAUDRATE = 115200
def print_version(hendi):
print(f"{hendi.getSoftwareIdentifier()}, F/W-Version: {hendi.getSoftwareVersion()}")
def print_capabilities(hendi):
print("Capabilities:", hendi.getCapabilties())
def test_converter(hendi):
print("Test converters")
for pwr in range(500, 3600, 100):
digits = hendi.toDigits(pwr)
power = hendi.toWatts(digits)
print(f"pwr={pwr} digits={digits} power={power}")
def test_heater(hendi):
print("Test Hendi")
with hendi.remote_open():
hendi.setSwitch(1)
time.sleep(1.0)
for pwr in range(500, 3600, 100):
hendi.setPowerWatts(pwr)
pw = hendi.getPowerWatts()
print("power_w", pw)
time.sleep(1.0)
hendi.setSwitch(0)
def trigger_error_state(hendi):
"""Enables remote control and deliberately exits without disabling it
again - reproduces the Hendi's "not closed gracefully" lockout (its
built-in security feature that fails subsequent connect attempts)
on demand, instead of waiting for an actual crash to trigger it."""
print("Triggering error state: enabling remote control, exiting without releasing it")
hendi.remoteEnable(True)
def reset(hendi):
"""Toggles the reset line. Confirmed on hardware that this does NOT
clear the ungraceful-disconnect lockout (see trigger_error_state()) -
only the device's physical knob/power cycle does that."""
print("Resetting Hendi (toggling DTR/RTS)")
hendi.reset()
time.sleep(1.0)
def update_firmware(hendi, filename):
hendi.firmware_update(filename)
def main():
parser = ap.ArgumentParser(description=__doc__)
parser.add_argument("--serial-port", default=DEFAULT_PORT)
parser.add_argument("--baudrate", type=int, default=DEFAULT_BAUDRATE)
parser.add_argument("--reset", action="store_true", help="toggle the reset line (does not clear the ungraceful-disconnect lockout - use the device's knob for that)")
parser.add_argument("--version", action="store_true", help="print software identifier/version")
parser.add_argument("--capabilities", action="store_true", help="print power/digit capability info")
parser.add_argument("--test-converter", action="store_true", help="round-trip the watts<->digits polynomials")
parser.add_argument("--test-heater", action="store_true", help="drive the heater through its power range")
parser.add_argument("--trigger-error-state", action="store_true",
help="enable remote control and exit without disabling it, to deliberately reproduce the ungraceful-disconnect lockout")
parser.add_argument("--update-firmware", metavar="FILE", help="flash the given firmware file")
args = parser.parse_args()
hendi = HendiCtrl(args.serial_port, args.baudrate)
if args.reset:
reset(hendi)
if args.version:
print_version(hendi)
if args.capabilities:
print_capabilities(hendi)
if args.test_converter:
test_converter(hendi)
if args.test_heater:
test_heater(hendi)
if args.trigger_error_state:
trigger_error_state(hendi)
if args.update_firmware:
update_firmware(hendi, args.update_firmware)
print("End of program.")
if __name__ == '__main__':
main()