Compare commits

..

8 Commits

Author SHA1 Message Date
30c785221e Fix for pymodbus 3.5.x 2023-09-23 16:28:04 +02:00
e0393a6353 Better way to make this working 2022-10-17 21:22:06 +02:00
ef88dd5f8c Remove "old" way to get data
Added 2 attempts to get data. Sometime the data is borken on decoding it,
so try to fetch again the data twice
2022-10-17 21:09:28 +02:00
fb5d47f25c Catch errors 2022-10-17 18:16:23 +02:00
3921bf4689 Credits. 2021-05-20 11:32:10 +02:00
9b40e23293 Added Screen shots 2021-05-20 11:12:22 +02:00
92bb2a27dc Added registers 2021-05-20 11:02:47 +02:00
00300e70e3 Fix README 2021-05-20 10:56:58 +02:00
6 changed files with 152 additions and 92 deletions

102
README.md
View File

@ -1,6 +1,6 @@
# Domoticz DS238-2 ModbusTCP plugin # Domoticz DS238-2 ZN/S ModbusTCP plugin
A Domoticz plugin to collect data from Hiking DS238-2 D/ZN power meter ModbusTCP. A Domoticz plugin to collect data from Hiking DS238-2 ZN/S power meter ModbusTCP.
## Requirements ## Requirements
@ -50,14 +50,14 @@ sudo service domoticz.sh restart
## Configuration in Domoticz ## Configuration in Domoticz
Once the plugin is installed, a new hardware type will be available: `DS238-2 D/ZN ModbusTCP`. Once the plugin is installed, a new hardware type will be available: `DS238-2 ZN/S ModbusTCP`.
To add the inverter, go to `Setup` -> `Hardware` and add the counter: To add the inverter, go to `Setup` -> `Hardware` and add the counter:
- Enter a `name` for the counter. - Enter a `name` for the counter.
- Select `DS238-2 D/ZN ModbusTCP` from the `type` dropdown list. - Select `DS238-2 ZN/S ModbusTCP` from the `type` dropdown list.
- Enter the IP address of the PW21 in the `Inverter IP Address` field. - Enter the IP address of the PW21 in the `IP Address` field.
- Enter the port number (default: 502) of the inverter in the `Inverter Port Number` field. - Enter the port number (default: 502) of the PW21 in the `Port Number` field.
- Optionally turn on `Debug`; be aware: this will generate a lot of entries in the Domoticz log! - Optionally turn on `Debug`; be aware: this will generate a lot of entries in the Domoticz log!
- `Add` the counter. - `Add` the counter.
@ -77,4 +77,94 @@ Once that is done, restart domoticz:
``` shell ``` shell
sudo service domoticz.sh restart sudo service domoticz.sh restart
``` ```
## ScreenShots
Hardware page showing a configured PW21 to get data from a DS238-2 ZN/S
![](screenshots/Hardware.png)
The devices page show all Domoticz devices that were created for it.
![](screenshots/Devices.png)
The voltage graphs.
![](screenshots/Voltage.png)
Total power meter with Return energy (usefull for PV/Wind turbine generators)
![](screenshots/TotalPower.png)
## Documentation about the registers used.
This is a copy of : https://gist.github.com/alphp/95e1efe916c0dd6df7156f43dd521d53
### Modbus holding registers:
| Register(s) | Meaning | Scale Unit | Data format | R/W |
|-------------|-----------------|------------|----------------|:---:|
| 0000h-0001h | total energy | 1/100 kWh | unsigned dword | R<> |
| 0002h-0003h | reserved | | unsigned dword | |
| 0004h-0005h | reserved | | unsigned dword | |
| 0006h-0007h | reserved | | unsigned dword | |
| 0008h-0009h | export energy | 1/100 kWh | unsigned dword | R<> |
| 000Ah-000Bh | import energy | 1/100 kWh | unsigned dword | R<> |
| 000Ch | voltage | 1/10 V | unsigned word | R |
| 000Dh | current | 1/100 A | unsigned word | R |
| 000Eh | active power | 1 W | signed word | R |
| 000Fh | reactive power | 1 VAr | unsigned word | R |
| 0010h | power factor | 1/1000 | unsigned word | R |
| 0011h | frequency | 1/100 Hz | unsigned word | R |
| 0012h | reserved | | unsigned word | |
| 0013h | reserved | | unsigned word | |
| 0014h | reserved | | unsigned word | |
| 0015h:high | station address | 1-247 | unsigned char | R/W |
| 0015h:low | baud rate | 1-4<> | unsigned char | R/W |
| 001Ah | relay<61> | | unsigned word | R/W |
#### Notes:
##### Note 1:
Total, export and import energy counters can erased writing 0 in total energy
registers.
##### Note 2:
Value mapping, default 1.
| Value | Baud rate |
|:-----:|:---------:|
| 1 | 9600 Bd |
| 2 | 4800 Bd |
| 3 | 2400 Bd |
| 4 | 1200 Bd |
##### Note 3:
In DDS238-2 ZN/SR model the relay can be switched by 0x001A register.
| Value | Relay |
|:-----:|:-----:|
| 0 | Off |
| 1 | On |
##### Data formats
| Data format | Lenght | Byte order |
|-------------|--------:|------------|
| char | 8 bits | |
| word | 16 bits | Big endian |
| dword | 32 bits | Big endian |
### Writing registers
The meter does not understand the 'write sigle register' function code (06h),
only the 'write multiple registers' function code (10h).
## Credits
Some part of the code has been taken from SolarEgde Modbus TCP Plugin. Thanks !
https://github.com/addiejanssen/domoticz-solaredge-modbustcp-plugin/

142
plugin.py
View File

@ -7,7 +7,7 @@ Requirements:
2. pymodbus AND pymodbusTCP 2. pymodbus AND pymodbusTCP
""" """
""" """
<plugin key="DS238_ModbusTCP" name="DS238-2 ZN/S ModbusTCP" author="Xavier Beaudouin" version="0.0.1" externallink="https://github.com/xbeaudouin/domoticz-ds238-modbus-tcp"> <plugin key="DS238_ModbusTCP" name="DS238-2 ZN/S ModbusTCP" author="Xavier Beaudouin" version="0.0.3" externallink="https://github.com/xbeaudouin/domoticz-ds238-modbus-tcp">
<params> <params>
<param field="Address" label="IP Address" width="150px" required="true" /> <param field="Address" label="IP Address" width="150px" required="true" />
<param field="Port" label="Port Number" width="100px" required="true" default="502" /> <param field="Port" label="Port Number" width="100px" required="true" default="502" />
@ -31,6 +31,7 @@ sys.path.append('/usr/local/lib/python3.6/dist-packages')
sys.path.append('/usr/local/lib/python3.7/dist-packages') sys.path.append('/usr/local/lib/python3.7/dist-packages')
sys.path.append('/usr/local/lib/python3.8/dist-packages') sys.path.append('/usr/local/lib/python3.8/dist-packages')
sys.path.append('/usr/local/lib/python3.9/dist-packages') sys.path.append('/usr/local/lib/python3.9/dist-packages')
sys.path.append('/usr/local/lib/python3.10/dist-packages')
import pymodbus import pymodbus
@ -210,7 +211,6 @@ class BasePlugin:
Devices[9].Update(1, "0") Devices[9].Update(1, "0")
Devices[10].Update(1, "0") Devices[10].Update(1, "0")
# TODO: catch errors
# 3 counters # 3 counters
total_e = "0" total_e = "0"
export_e = "0" export_e = "0"
@ -220,76 +220,30 @@ class BasePlugin:
power = "0" power = "0"
# Total Energy # Total Energy
data = client.read_holding_registers(0, 2) total_e = str(getmodbus32(0, client))
Domoticz.Debug("Data from register 0: "+str(data))
# Unsigned 32
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_32bit_int()
total_e = str(value)
# Export Energy # Export Energy
data = client.read_holding_registers(0x8, 2) export_e = str(getmodbus32(0x8, client))
Domoticz.Debug("Data from register 0x8: "+str(data))
# Unsigned 32
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_32bit_int()
export_e = str(value)
# Import Energy # Import Energy
data = client.read_holding_registers(0xA, 2) import_e = str(getmodbus32(0xA, client))
Domoticz.Debug("Data from register 0xA: "+str(data))
# Unsigned 32
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_32bit_int()
import_e = str(value)
# Voltage # Voltage
data = client.read_holding_registers(0xC, 1) value = round (getmodbus16(0xC,client) / 10, 3)
Domoticz.Debug("Data from register 0xC: "+str(data))
# Unsigned 16
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_16bit_int()
# Scale factor / 10
value = round (value / 10, 3)
Domoticz.Debug("Value after conversion : "+str(value))
Domoticz.Debug("-> Calculating average")
self.voltage.update(value) self.voltage.update(value)
value = self.voltage.get() value = self.voltage.get()
Domoticz.Debug(" = {}".format(value))
Devices[4].Update(1, str(value)) Devices[4].Update(1, str(value))
# Current # Current
data = client.read_holding_registers(0xD, 1) value = round (getmodbus16(0xD,client) / 100, 3)
Domoticz.Debug("Data from register 0xD: "+str(data))
# Unsigned 16
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_16bit_int()
# Scale factor / 100
value = round (value / 100, 3)
Domoticz.Debug("Value after conversion : "+str(value))
Domoticz.Debug("-> Calculating average")
self.current.update(value) self.current.update(value)
value = self.current.get() value = self.current.get()
Domoticz.Debug(" = {}".format(value))
Devices[5].Update(1, str(value)) Devices[5].Update(1, str(value))
# Active Power # Active Power
data = client.read_holding_registers(0xE, 1) value = getmodbus16(0xE, client)
Domoticz.Debug("Data from register 0xE: "+str(data))
# Unsigned 16
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_16bit_int()
Domoticz.Debug("Value after conversion : "+str(value))
Domoticz.Debug("-> Calculating average")
self.active_power.update(value) self.active_power.update(value)
value = self.active_power.get() value = self.active_power.get()
Domoticz.Debug(" = {}".format(value))
Devices[6].Update(1, str(value)) Devices[6].Update(1, str(value))
if value > 0.0: if value > 0.0:
import_w = value import_w = value
@ -298,52 +252,23 @@ class BasePlugin:
power = str(abs(value)) power = str(abs(value))
# Reactive Power # Reactive Power
data = client.read_holding_registers(0xF, 1) value = getmodbus16(0xF, client)
Domoticz.Debug("Data from register 0xF: "+str(data))
# Unsigned 16
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_16bit_int()
Domoticz.Debug("Value after conversion : "+str(value))
Domoticz.Debug("-> Calculating average")
self.reactive_power.update(value) self.reactive_power.update(value)
value = self.reactive_power.get() value = self.reactive_power.get()
Domoticz.Debug(" = {}".format(value))
Devices[7].Update(1, str(value)) Devices[7].Update(1, str(value))
# Power Factor # Power Factor
data = client.read_holding_registers(0x10, 1) value = round (getmodbus16(0x10,client) / 1000, 3)
Domoticz.Debug("Data from register 0x10: "+str(data))
# Unsigned 16
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_16bit_int()
# Scale factor / 1000
value = round (value / 1000, 3)
Domoticz.Debug("Value after conversion : "+str(value))
Domoticz.Debug("-> Calculating average")
self.power_factor.update(value) self.power_factor.update(value)
value = self.power_factor.get() value = self.power_factor.get()
Domoticz.Debug(" = {}".format(value))
Devices[8].Update(1, str(value)) Devices[8].Update(1, str(value))
# Frequency # Frequency
data = client.read_holding_registers(0x11, 1) value = round (getmodbus16(0x11, client) / 100, 3)
Domoticz.Debug("Data from register 0x11: "+str(data))
# Unsigned 16
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_16bit_int()
# Scale factor / 100
value = round (value / 100, 3)
Domoticz.Debug("Value after conversion : "+str(value))
Domoticz.Debug("-> Calculating average")
self.frequency.update(value) self.frequency.update(value)
value = self.frequency.get() value = self.frequency.get()
Domoticz.Debug(" = {}".format(value))
Devices[9].Update(1, str(value)) Devices[9].Update(1, str(value))
# Do insert data on counters # Do insert data on counters
Devices[1].Update(1, sValue=total_e+"0;0;0;0;"+power+";0") Devices[1].Update(1, sValue=total_e+"0;0;0;0;"+power+";0")
Devices[2].Update(1, sValue=export_e+"0;0;0;0;"+str(abs(export_w))+";0") Devices[2].Update(1, sValue=export_e+"0;0;0;0;"+str(abs(export_w))+";0")
@ -400,3 +325,48 @@ def DumpConfigToLog():
Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'") Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'")
Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel)) Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel))
return return
# get Modbus 32 bits values
def getmodbus32(register, client):
value = 0
try:
data = client.read_holding_registers(register, 2)
Domoticz.Debug("Data from register "+str(register)+": "+str(data))
#decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.BIG, wordorder=Endian.BIG)
value = decoder.decode_32bit_int()
except:
Domoticz.Error("Error getting data from "+str(register) + ", try 1")
try:
data = client.read_holding_registers(register, 2)
Domoticz.Debug("Data from register "+str(register)+": "+str(data))
#decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.BIG, wordorder=Endian.BIG)
value = decoder.decode_32bit_int()
except:
Domoticz.Error("Error getting data from "+str(register) + ", try 2")
return value
# get Modbug 16 bits values
def getmodbus16(register, client):
value = 0
try:
data = client.read_holding_registers(register, 1)
Domoticz.Debug("Data from register "+str(register)+": "+str(data))
#decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.BIG, wordorder=Endian.BIG)
value = decoder.decode_16bit_int()
except:
Domoticz.Error("Error getting data from "+str(register) + ", try 1")
try:
data = client.read_holding_registers(register, 1)
Domoticz.Debug("Data from register "+str(register)+": "+str(data))
#decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.BIG, wordorder=Endian.BIG)
value = decoder.decode_16bit_int()
except:
Domoticz.Error("Error getting data from "+str(register) + ", try 2")
return value

BIN
screenshots/Devices.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
screenshots/Hardware.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

BIN
screenshots/TotalPower.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

BIN
screenshots/Voltage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB