Created
March 27, 2026 14:40
-
-
Save cardil/ab1e8e243210e36e8066239cf7097211 to your computer and use it in GitHub Desktop.
APC Modbus word-order probe test - tests EchterAgo's CANCEL|ON|OFF detection method
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Test EchterAgo's probe idea: write CANCEL|OUTPUT_ON|OUTPUT_OFF to OutletCommand_BF. | |
| On spec-compliant firmware: EMBXILVAL (invalid command combo detected) | |
| On LE-buggy firmware: different error or no error (combo lands in reserved bits) | |
| Must run with NUT driver stopped: systemctl stop nut-driver@apc | |
| """ | |
| import sys | |
| try: | |
| from pymodbus.client import ModbusSerialClient | |
| except ImportError: | |
| from pymodbus.client.sync import ModbusSerialClient | |
| SERIAL_PORT = "/dev/ttyS0" | |
| BAUDRATE = 9600 | |
| SLAVE_ID = 1 | |
| REG_OUTLET_CMD = 1538 # OutletCommand_BF (2 regs) | |
| # Bit definitions from apc_modbus.h | |
| CMD_CANCEL = 1 << 0 # 0x0001 | |
| CMD_OUTPUT_ON = 1 << 1 # 0x0002 | |
| CMD_OUTPUT_OFF = 1 << 2 # 0x0004 | |
| # Invalid combo - multiple commands at once | |
| INVALID_COMBO = CMD_CANCEL | CMD_OUTPUT_ON | CMD_OUTPUT_OFF # 0x0007 | |
| print(f"Probe value: 0x{INVALID_COMBO:04x}") | |
| print() | |
| client = ModbusSerialClient( | |
| port=SERIAL_PORT, | |
| baudrate=BAUDRATE, | |
| bytesize=8, | |
| parity='N', | |
| stopbits=1, | |
| timeout=3 | |
| ) | |
| if not client.connect(): | |
| print("ERROR: Could not connect to serial port") | |
| sys.exit(1) | |
| print("Connected to", SERIAL_PORT) | |
| print() | |
| # Test 1: BE order (NUT's current behavior) | |
| # reg 1538 = 0x0000 (empty), reg 1539 = 0x0007 (combo) | |
| print("=== Test 1: BE order [0x0000, 0x0007] (NUT's current code) ===") | |
| result = client.write_registers(REG_OUTLET_CMD, [0x0000, 0x0007], slave=SLAVE_ID) | |
| if result.isError(): | |
| print(f" Result: ERROR - {result}") | |
| else: | |
| print(f" Result: SUCCESS (no error)") | |
| print() | |
| # Test 2: LE order (the "fix" order) | |
| # reg 1538 = 0x0007 (combo), reg 1539 = 0x0000 (empty) | |
| print("=== Test 2: LE order [0x0007, 0x0000] (reversed) ===") | |
| result = client.write_registers(REG_OUTLET_CMD, [0x0007, 0x0000], slave=SLAVE_ID) | |
| if result.isError(): | |
| print(f" Result: ERROR - {result}") | |
| else: | |
| print(f" Result: SUCCESS (no error)") | |
| print() | |
| client.close() | |
| print("Done. Restart driver: systemctl start nut-driver@apc") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment