diff --git a/README.md b/README.md index 8819339..1434454 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ This is a gateway between Modbus and MQTT. It was originally developed to read r can read from one or more Modbus TCP gateways (e.g. the Waveshare RS485 to Ethernet) simultaneously. The state of the coils can be published as a json message and their state can also be changed with a json message sent to the rpc topic. +## Changes from the upstream version +1) Read input register +2) Basic support of big and little endian registers +3) Read multi-byte value in big and little endian order +4) Fix the default value of length to 1 ( as in the readme) + ## Configuration Create a configuration file (see examples/). Here is the basic syntax: @@ -42,6 +48,8 @@ schema: substract: 0 # <--- (optional) Value to substract before publishing (default is 0) signed: 1 # <--- (optional) True/false for signed/unsigned value (default is 0) divide: 1 # <--- (optional) Value to divide by before publishing (default is 1) + typereg: holding # <--- (optional) Read holding or input register (default is holding) + littleendian: 0 # <--- (optional) Byte order (default is 0) # Coils @@ -176,11 +184,11 @@ sources: Result: -![MQQT Explorer Screenshot](examples/Deye-SG04LP3-EU/deye-modbus2mqtt-example.jpg) +![MQTT Explorer Screenshot](examples/Deye-SG04LP3-EU/deye-modbus2mqtt-example.jpg) Home Assistant device sensors: -![MQQT Explorer Screenshot](examples/Deye-SG04LP3-EU/ha-mqtt-deye-data.jpg) +![MQTT Explorer Screenshot](examples/Deye-SG04LP3-EU/ha-mqtt-deye-data.jpg) ## Known Issues diff --git a/examples/config_solax_x1_ess_g44.yaml b/examples/config_solax_x1_ess_g44.yaml new file mode 100644 index 0000000..e79b4bb --- /dev/null +++ b/examples/config_solax_x1_ess_g44.yaml @@ -0,0 +1,275 @@ +mqtt: + host: "192.168.0.147" + port: 1883 + tls: false + username: "jeedom" + password: "0Ta9jUaIdFO8R1V" + topic_prefix: "pv" + +# Modbus register example for Solax ESS X1 G4 +schema: +- name: solax_x1_g4 + readings: + - name: "System State" + topic: "system/state" + register: 9 + length: 1 + signed: 0 + substract: 0 + divide: 1 + typereg: "Input" + - name: "System Use Mode" + topic: "system/usemode" + register: 139 + length: 1 + signed: 0 + substract: 0 + divide: 1 + typereg: "holding" + - name: "System Temperature" + topic: "system/temperature" + register: 8 + length: 1 + signed: 1 + substract: 0 + divide: 1 + typereg: "Input" + - name: "To grid energytotal" + topic: "system/totals/grid_feedin" + register: 72 + length: 2 + lindean: 1 + substract: 0 + decimals: 1 + divide: 100 + typereg: "Input" + - name: "From grid energy total" + topic: "system/totals/grid_energy" + register: 74 + length: 2 + substract: 0 + decimals: 1 + lindean: 1 + divide: 100 + typereg: "Input" + - name: "Yield total" + topic: "system/totals/gridyield" + register: 82 + length: 2 + lindean: 1 + substract: 0 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "Grid yield Today" + topic: "grid/today/yield" + register: 80 + length: 2 + lindean: 1 + substract: 0 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "OffGrid Yield total" + topic: "system/totals/epsyield" + register: 142 + length: 2 + lindean: 1 + substract: 0 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "OffGrid yield Today" + topic: "eps/today/yield" + register: 144 + length: 1 + substract: 0 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "Grid Sold Today" + topic: "grid/today/sold" + register: 152 + length: 2 + lindean: 1 + substract: 0 + decimals: 1 + divide: 100 + typereg: "Input" + - name: "Grid Bought Today" + topic: "grid/today/bought" + register: 154 + length: 2 + lindean: 1 + substract: 0 + decimals: 1 + divide: 100 + typereg: "Input" + - name: "Grid Frequency" + topic: "grid/frequency" + register: 7 + length: 1 + substract: 0 + decimals: 0 + divide: 100 + typereg: "Input" + - name: "Grid L1 Power" + topic: "grid/now/l1/power" + register: 70 + length: 1 + substract: 0 + decimals: 0 + signed: 1 + divide: 1 + typereg: "Input" + - name: "Grid L1 Volt" + topic: "grid/now/l1/volt" + register: 0 + length: 1 + substract: 0 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "Grid L1 Current" + topic: "grid/now/l1/amp" + register: 70 + length: 1 + signed: 1 + substract: 0 + decimals: 1 + divide: 230 + typereg: "Input" + - name: "EPS L1 Power" + topic: "eps/now/l1/power" + register: 78 + length: 1 + substract: 0 + decimals: 0 + typereg: "Input" + - name: "EPS L1 Volt" + topic: "eps/now/l1/volt" + register: 76 + length: 1 + substract: 0 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "EPS L1 Current" + topic: "eps/now/l1/amp" + register: 77 + length: 1 + substract: 0 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "EPS L1 frequency" + topic: "eps/frequency" + register: 79 + length: 1 + substract: 0 + decimals: 1 + divide: 100 + typereg: "Input" + - name: "Load L1 Current" + topic: "load/now/l1/amp" + register: 1 + length: 1 + substract: 0 + signed: 1 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "Grid Power L1" + topic: "load/now/l1/power" + register: 2 + length: 1 + substract: 0 + decimals: 0 + signed: 1 + divide: 1 + typereg: "Input" + - name: "PV Production Today" + topic: "pv/today" + register: 150 + length: 1 + substract: 0 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "PV String 1 Power" + topic: "pv/string/1/power" + register: 10 + length: 1 + substract: 0 + divide: 1 + typereg: "Input" + - name: "PV String 2 Power" + topic: "pv/string/2/power" + register: 11 + length: 1 + substract: 0 + divide: 1 + typereg: "Input" + - name: "Battery SOC" + topic: "battery/soc" + register: 28 + length: 1 + substract: 0 + divide: 1 + typereg: "Input" + - name: "Battery Charged Today" + topic: "battery/today/charged" + register: 35 + length: 1 + substract: 0 + decimals: 1 + divide: 10 + typereg: "Input" + - name: "Battery Discharged Today" + topic: "battery/today/discharged" + register: 32 + length: 1 + substract: 0 + divide: 10 + typereg: "Input" + - name: "Battery Temperature" + topic: "battery/temperature" + register: 24 + length: 1 + substract: 0 + decimals: 1 + divide: 1 + typereg: "Input" + - name: "Battery Voltage" + topic: "battery/volt" + register: 20 + length: 1 + substract: 0 + decimals: 2 + divide: 10 + typereg: "Input" + - name: "Battery Output Power" + topic: "battery/power" + register: 22 + length: 1 + signed: 1 + substract: 0 + decimals: 0 + divide: 1 + typereg: "Input" + - name: "Battery Output Current" + topic: "battery/current" + register: 21 + length: 1 + signed: 1 + substract: 0 + decimals: 2 + divide: 10 + typereg: "Input" + +sources: +- name: "Solax X1" + host: "192.168.0.87" + port: 502 + schema: solax_x1_g4 + topic_prefix: "x1" diff --git a/modbus2mqtt.py b/modbus2mqtt.py index b609e33..c5e43b9 100755 --- a/modbus2mqtt.py +++ b/modbus2mqtt.py @@ -61,7 +61,7 @@ def on_publish(self, client, userdata, result): def on_connect(self, client, userdata, flags, rc): if rc == 0: - log.info("Connected dto broker %s", self.host) + log.info("Connected to broker %s", self.host) self.is_connected = True else: log.error("Connection to broker %s failed.", self.host) @@ -232,8 +232,11 @@ def set_value(self, src, params): class HoldingRegister(Register): + # Keep the function name but can read holding and input registers. + # pass the parameter "typereg" with the value "holding" or "input" to define the type of register to read. + # pass the parameter "littleendian" with the value False or true (little endian) to define the endianness of the register to read. (Solax use little endian) - def __init__(self, name: str, topic: str, register: int, length: int = 2, + def __init__(self, name: str, topic: str, register: int, typereg: str = "holding", littleendian: bool = False, length: int = 1, mode: str = "r", substract: float = 0, divide: float = 1, decimals: int = 0, signed: bool = False, unitid: int = None, **kvargs): super().__init__(name, topic, register, length, mode, unitid=unitid) @@ -241,13 +244,17 @@ def __init__(self, name: str, topic: str, register: int, length: int = 2, self.decimals = decimals self.substract = substract self.signed = signed + self.typereg = typereg + self.littleendian = littleendian def get_value(self, src): unitid = self.unitid if unitid is None: unitid = src.unitid - - rr = src.client.read_holding_registers(self.start, self.length, slave=unitid) + if (self.typereg == "holding"): + rr = src.client.read_holding_registers(self.start, self.length, slave=unitid) + else: + rr = src.client.read_input_registers(self.start, self.length, slave=unitid) if not rr: raise ModbusException("Received empty modbus respone.") if rr.isError(): @@ -255,7 +262,21 @@ def get_value(self, src): if isinstance(rr, ExceptionResponse): raise ModbusException(f"Received Modbus library exception ({rr}).") - val = rr.registers[0] + if ((self.littleendian)): + # Read multiple bytes in little endian mode + h = "" + for i in range(0, self.length): + h = hex(rr.registers[i]).split('x')[-1].zfill(4) + h + log.debug(f"Got Value {h} from {self.typereg} register {self.start} with length {self.length} from unit {unitid} in little endian mode.") + val = int(h, 16) + else: + # Read multiple bytes in big endian mode + h = "" + for i in range(0, self.length): + h = h + hex(rr.registers[i]).split('x')[-1].zfill(4) + log.debug(f"Got Value {h} from {self.typereg} register {self.start} with length {self.length} from unit {unitid} in big endian mode.") + val = int(h, 16) + if self.signed and int(val) >= 32768: val = int(val) - 65535 if self.decimals > 0: