Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Read input registers and basic support of little Indian values #1

Merged
merged 7 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
275 changes: 275 additions & 0 deletions examples/config_solax_x1_ess_g44.yaml
Original file line number Diff line number Diff line change
@@ -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"
31 changes: 26 additions & 5 deletions modbus2mqtt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -232,30 +232,51 @@ 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)
self.divide = divide
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():
raise ModbusException(f"Received Modbus library error({rr}).")
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:
Expand Down
Loading