Compare commits
10 Commits
24ab8a96e4
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 30c785221e | |||
| e0393a6353 | |||
| ef88dd5f8c | |||
| fb5d47f25c | |||
| 3921bf4689 | |||
| 9b40e23293 | |||
| 92bb2a27dc | |||
| 00300e70e3 | |||
| eaf1087a0e | |||
| f6ceba6605 |
102
README.md
102
README.md
@ -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
|
||||
|
||||
@ -50,14 +50,14 @@ sudo service domoticz.sh restart
|
||||
|
||||
## 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:
|
||||
|
||||
- Enter a `name` for the counter.
|
||||
- Select `DS238-2 D/ZN ModbusTCP` from the `type` dropdown list.
|
||||
- Enter the IP address of the PW21 in the `Inverter IP Address` field.
|
||||
- Enter the port number (default: 502) of the inverter in the `Inverter Port Number` field.
|
||||
- Select `DS238-2 ZN/S ModbusTCP` from the `type` dropdown list.
|
||||
- Enter the IP address of the PW21 in the `IP Address` 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!
|
||||
- `Add` the counter.
|
||||
|
||||
@ -77,4 +77,94 @@ Once that is done, restart domoticz:
|
||||
``` shell
|
||||
sudo service domoticz.sh restart
|
||||
```
|
||||
## ScreenShots
|
||||
|
||||
Hardware page showing a configured PW21 to get data from a DS238-2 ZN/S
|
||||
|
||||

|
||||
|
||||
The devices page show all Domoticz devices that were created for it.
|
||||
|
||||

|
||||
|
||||
The voltage graphs.
|
||||
|
||||

|
||||
|
||||
Total power meter with Return energy (usefull for PV/Wind turbine generators)
|
||||
|
||||

|
||||
|
||||
## 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/
|
||||
|
||||
|
||||
186
plugin.py
186
plugin.py
@ -7,7 +7,7 @@ Requirements:
|
||||
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>
|
||||
<param field="Address" label="IP Address" width="150px" required="true" />
|
||||
<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.8/dist-packages')
|
||||
sys.path.append('/usr/local/lib/python3.9/dist-packages')
|
||||
sys.path.append('/usr/local/lib/python3.10/dist-packages')
|
||||
|
||||
import pymodbus
|
||||
|
||||
@ -100,8 +101,19 @@ class Maximum:
|
||||
class BasePlugin:
|
||||
#enabled = False
|
||||
def __init__(self):
|
||||
# Default hearbeat is 10 seconds, then 30 samples for the 5 minutes
|
||||
self.max_samples = 30
|
||||
# Voltage for last 5 minutes
|
||||
self.voltage=Average()
|
||||
# Current for last 5 minutes
|
||||
self.current=Average()
|
||||
# Active power for last 5 minutes
|
||||
self.active_power=Average()
|
||||
# Reactive power for last 5 minutes
|
||||
self.reactive_power=Average()
|
||||
# Power factor for last 5 minutes
|
||||
self.power_factor=Average()
|
||||
# Frequency for last 5 minutes
|
||||
self.frequency=Average()
|
||||
|
||||
return
|
||||
|
||||
def onStart(self):
|
||||
@ -133,7 +145,7 @@ class BasePlugin:
|
||||
Domoticz.Debug("Query IP " + self.IPAddress + ":" + str(self.IPPort) +" on device : "+str(self.MBAddr))
|
||||
|
||||
# Create the devices if they does not exists
|
||||
# TODO: refactor this.
|
||||
# TODO: refactor this.
|
||||
if 1 not in Devices:
|
||||
Domoticz.Device(Name="Total Energy", Unit=1, Type=0xfa, Subtype=0x01, Used=0).Create()
|
||||
if 2 not in Devices:
|
||||
@ -163,7 +175,7 @@ class BasePlugin:
|
||||
|
||||
|
||||
def onStop(self):
|
||||
Domoticz.Log("onStop called")
|
||||
Domoticz.Debugging(0)
|
||||
|
||||
def onConnect(self, Connection, Status, Description):
|
||||
Domoticz.Log("onConnect called")
|
||||
@ -208,80 +220,30 @@ class BasePlugin:
|
||||
power = "0"
|
||||
|
||||
# Total Energy
|
||||
data = client.read_holding_registers(0, 2)
|
||||
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()
|
||||
# Scale factor / 100
|
||||
#value = str ( round (value / 100, 3))
|
||||
#Domoticz.Debug("Value after conversion : "+str(value))
|
||||
total_e = str(value)
|
||||
total_e = str(getmodbus32(0, client))
|
||||
|
||||
# Export Energy
|
||||
data = client.read_holding_registers(0x8, 2)
|
||||
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()
|
||||
# Scale factor / 100
|
||||
#value = str ( round (value / 100, 3))
|
||||
#Domoticz.Debug("Value after conversion : "+str(value))
|
||||
export_e = str(value)
|
||||
export_e = str(getmodbus32(0x8, client))
|
||||
|
||||
# Import Energy
|
||||
data = client.read_holding_registers(0xA, 2)
|
||||
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()
|
||||
# Scale factor / 100
|
||||
#value = str ( round (value / 100, 3))
|
||||
#Domoticz.Debug("Value after conversion : "+str(value))
|
||||
import_e = str(value)
|
||||
import_e = str(getmodbus32(0xA, client))
|
||||
|
||||
# Voltage
|
||||
data = client.read_holding_registers(0xC, 1)
|
||||
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 = str ( round (value / 10, 3))
|
||||
#Domoticz.Debug("Value after conversion : "+str(value))
|
||||
Devices[4].Update(1, value)
|
||||
value = round (getmodbus16(0xC,client) / 10, 3)
|
||||
self.voltage.update(value)
|
||||
value = self.voltage.get()
|
||||
Devices[4].Update(1, str(value))
|
||||
|
||||
# Current
|
||||
data = client.read_holding_registers(0xD, 1)
|
||||
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 = str ( round (value / 100, 3))
|
||||
#Domoticz.Debug("Value after conversion : "+str(value))
|
||||
Devices[5].Update(1, value)
|
||||
value = round (getmodbus16(0xD,client) / 100, 3)
|
||||
self.current.update(value)
|
||||
value = self.current.get()
|
||||
Devices[5].Update(1, str(value))
|
||||
|
||||
# Active Power
|
||||
data = client.read_holding_registers(0xE, 1)
|
||||
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()
|
||||
# Scale factor / 100
|
||||
#value = str ( round (value / 100, 3))
|
||||
Domoticz.Debug("Value after conversion : "+str(value))
|
||||
Domoticz.Debug("-> Calculating average")
|
||||
m = Average()
|
||||
m.update(value)
|
||||
value = m.get()
|
||||
Domotice.Debug(" = {}".format(value))
|
||||
value = getmodbus16(0xE, client)
|
||||
self.active_power.update(value)
|
||||
value = self.active_power.get()
|
||||
Devices[6].Update(1, str(value))
|
||||
if value > 0.0:
|
||||
import_w = value
|
||||
@ -290,41 +252,22 @@ class BasePlugin:
|
||||
power = str(abs(value))
|
||||
|
||||
# Reactive Power
|
||||
data = client.read_holding_registers(0xF, 1)
|
||||
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()
|
||||
# Scale factor / 100
|
||||
#value = str ( round (value / 100, 3))
|
||||
#Domoticz.Debug("Value after conversion : "+str(value))
|
||||
value = getmodbus16(0xF, client)
|
||||
self.reactive_power.update(value)
|
||||
value = self.reactive_power.get()
|
||||
Devices[7].Update(1, str(value))
|
||||
|
||||
# Power Factor
|
||||
data = client.read_holding_registers(0x10, 1)
|
||||
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 = str ( round (value / 1000, 3))
|
||||
#Domoticz.Debug("Value after conversion : "+str(value))
|
||||
Devices[8].Update(1, value)
|
||||
value = round (getmodbus16(0x10,client) / 1000, 3)
|
||||
self.power_factor.update(value)
|
||||
value = self.power_factor.get()
|
||||
Devices[8].Update(1, str(value))
|
||||
|
||||
# Frequency
|
||||
data = client.read_holding_registers(0x11, 1)
|
||||
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 = str ( round (value / 100, 3))
|
||||
#Domoticz.Debug("Value after conversion : "+str(value))
|
||||
Devices[9].Update(1, value)
|
||||
|
||||
value = round (getmodbus16(0x11, client) / 100, 3)
|
||||
self.frequency.update(value)
|
||||
value = self.frequency.get()
|
||||
Devices[9].Update(1, str(value))
|
||||
|
||||
# Do insert data on counters
|
||||
Devices[1].Update(1, sValue=total_e+"0;0;0;0;"+power+";0")
|
||||
@ -382,3 +325,48 @@ def DumpConfigToLog():
|
||||
Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'")
|
||||
Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel))
|
||||
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
BIN
screenshots/Devices.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 316 KiB |
BIN
screenshots/Hardware.png
Normal file
BIN
screenshots/Hardware.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 182 KiB |
BIN
screenshots/TotalPower.png
Normal file
BIN
screenshots/TotalPower.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 204 KiB |
BIN
screenshots/Voltage.png
Normal file
BIN
screenshots/Voltage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
Reference in New Issue
Block a user