"""pymodbus-based helpers for the ZLAC8015D V4 RS485 (Modbus RTU) protocol. Register reference (from "ZLAC8015D V4 Series RS485 Communication" manual): 0x2001 RS485 Custom Drive Node Number (RW/S, range 1-127, default 1) 0x2002 RS485 custom communication baud rate (RW/S) 0x2010 Save all RW attribute parameters to EEPROM (write 1 to save) Requires: pip install pymodbus pyserial """ from pymodbus.client import ModbusSerialClient REG_NODE_ID = 0x2001 REG_SAVE_EEPROM = 0x2010 # pymodbus renamed the "slave id" keyword across major versions # (unit -> slave -> device_id). Try each so this works regardless of # which pymodbus version is installed (e.g. on the Ubuntu test box). _ID_KWARGS = ("device_id", "slave", "unit") def _call(fn, *args, node_id, **kwargs): last_err = None for kw in _ID_KWARGS: try: return fn(*args, **{kw: node_id}, **kwargs) except TypeError as exc: last_err = exc continue raise TypeError(f"pymodbus client does not accept any of {_ID_KWARGS}: {last_err}") def make_client(port: str, baud: int = 115200, timeout: float = 0.3) -> ModbusSerialClient: return ModbusSerialClient( port, baudrate=baud, bytesize=8, parity="N", stopbits=1, timeout=timeout, ) def read_register(client: ModbusSerialClient, node_id: int, address: int, count: int = 1): """Return list of register values, or None on error/no response.""" result = _call(client.read_holding_registers, address, node_id=node_id, count=count) if result is None or result.isError(): return None return result.registers def write_register(client: ModbusSerialClient, node_id: int, address: int, value: int) -> bool: result = _call(client.write_register, address, value, node_id=node_id) if result is None or result.isError(): return False return True