Skip to content

Instantly share code, notes, and snippets.

@cardil
Created March 27, 2026 14:40
Show Gist options
  • Select an option

  • Save cardil/ab1e8e243210e36e8066239cf7097211 to your computer and use it in GitHub Desktop.

Select an option

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
#!/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