First code

This commit is contained in:
2022-10-23 15:59:44 +02:00
parent 675322145a
commit 834bb8474b

298
plugin.py
View File

@ -7,7 +7,7 @@ Requirements:
2. pymodbus AND pymodbusTCP 2. pymodbus AND pymodbusTCP
""" """
""" """
<plugin key="DS238_ModbusTCP" name="SDM120c ModbusTCP" author="Xavier Beaudouin" version="0.0.1" externallink="https://github.com/xbeaudouin/domoticz-sdm120c-modbus-tcp"> <plugin key="SDM120c_ModbusTCP" name="SDM120c ModbusTCP" author="Xavier Beaudouin" version="0.0.1" externallink="https://github.com/xbeaudouin/domoticz-sdm120c-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,8 +31,13 @@ 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 sdm_modbus import pymodbus
from pyModbusTCP.client import ModbusClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder
# #
# Domoticz shows graphs with intervals of 5 minutes. # Domoticz shows graphs with intervals of 5 minutes.
@ -63,55 +68,36 @@ class Average:
def get(self): def get(self):
return sum(self.samples) / len(self.samples) return sum(self.samples) / len(self.samples)
# def strget(self):
# Domoticz shows graphs with intervals of 5 minutes. return str(sum(self.samples) / len(self.samples))
# When collecting information from the inverter more frequently than that, then it makes no sense to only show the last value.
#
# The Maximum class can be used to calculate the highest value based on a sliding window of samples.
# The number of samples stored depends on the interval used to collect the value from the inverter itself.
#
class Maximum:
def __init__(self):
self.samples = []
self.max_samples = 30
def set_max_samples(self, max):
self.max_samples = max
if self.max_samples < 1:
self.max_samples = 1
def update(self, new_value, scale = 0):
self.samples.append(new_value * (10 ** scale))
while (len(self.samples) > self.max_samples):
del self.samples[0]
Domoticz.Debug("Maximum: {} - {} values".format(self.get(), len(self.samples)))
def get(self):
return max(self.samples)
# Plugin itself # Plugin itself
class BasePlugin: class BasePlugin:
#enabled = False
def __init__(self): def __init__(self):
# Voltage for last 5 minutes # Voltage for last 5 minutes
self.voltage=Average() self.voltage=Average()
# Current for last 5 minutes # Current for last 5 minutes
self.current=Average() self.current=Average()
# Apparent Power for last 5 minutes
self.apparent_power=Average()
# Active power for last 5 minutes # Active power for last 5 minutes
self.active_power=Average() self.active_power=Average()
# Apparent Power for last 5 minutes
self.apparent_power=Average()
# Reactive power for last 5 minutes # Reactive power for last 5 minutes
self.reactive_power=Average() self.reactive_power=Average()
# Power factor for last 5 minutes # Power factor for last 5 minutes
self.power_factor=Average() self.power_factor=Average()
# Phase Angle for last 5 minutes # Phase Angle for last 5 minutes
self.phase_angle=Average() self.phase_angle=Average()
# Frequency for last 5 minutes # Frequency for last 5 minutes
self.frequency=Average() self.frequency=Average()
# Total demand power for last 5 minutes
self.total_demand_power=Average()
# Import demand power for last 5 minutes
self.import_demand_power=Average()
# Export demand power for last 5 minutes
self.export_demand_power=Average()
# Total Demand Current for last 5 minutes
self.total_demand_current=Average()
return return
@ -140,51 +126,50 @@ class BasePlugin:
Domoticz.Debug("Query IP " + self.IPAddress + ":" + str(self.IPPort) +" on device : "+str(self.MBAddr)) Domoticz.Debug("Query IP " + self.IPAddress + ":" + str(self.IPPort) +" on device : "+str(self.MBAddr))
# Create the devices if they does not exists # Create the devices if they does not exists
# TODO: refactor this. if 1 not in Devices:
if 1 not in Devices:
Domoticz.Device(Name="Voltage", Unit=1, TypeName="Voltage", Used=0).Create() Domoticz.Device(Name="Voltage", Unit=1, TypeName="Voltage", Used=0).Create()
if 2 not in Devices: if 2 not in Devices:
Domoticz.Device(Name="Current", Unit=2, TypeName="Current (Single)", Used=0).Create() Domoticz.Device(Name="Current", Unit=2, TypeName="Current (Single)", Used=0).Create()
if 3 not in Devices: if 3 not in Devices:
Options = { "Custom": "1;W" } Options = { "Custom": "1;W" }
Domoticz.Device(Name="Active Power", Unit=3, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Active Power", Unit=3, TypeName="Custom", Used=0, Options=Options).Create()
if 4 not in Devices: if 4 not in Devices:
Options = { "Custom": "1;VA" } Options = { "Custom": "1;VA" }
Domoticz.Device(Name="Apparent Power", Unit=4, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Apparent Power", Unit=4, TypeName="Custom", Used=0, Options=Options).Create()
if 5 not in Devices: if 5 not in Devices:
Options = { "Custom": "1;VAr" } Options = { "Custom": "1;VAr" }
Domoticz.Device(Name="Reactive Power", Unit=5, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Reactive Power", Unit=5, TypeName="Custom", Used=0, Options=Options).Create()
if 6 not in Devices: if 6 not in Devices:
Options = { "Custom": "1;PF" } Options = { "Custom": "1;PF" }
Domoticz.Device(Name="Power Factor", Unit=6, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Power Factor", Unit=6, TypeName="Custom", Used=0, Options=Options).Create()
if 7 not in Devices: if 7 not in Devices:
Options = { "Custom": "1;Deg" } Options = { "Custom": "1;Deg" }
Domoticz.Device(Name="Phase Angle", Unit=7, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Phase Angle", Unit=7, TypeName="Custom", Used=0, Options=Options).Create()
if 8 not in Devices: if 8 not in Devices:
Options = { "Custom": "1;Hz" } Options = { "Custom": "1;Hz" }
Domoticz.Device(Name="Frequency", Unit=8, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Frequency", Unit=8, TypeName="Custom", Used=0, Options=Options).Create()
if 9 not in Devices: if 9 not in Devices:
Domoticz.Device(Name="Import Energy", Unit=9, Type=0xfa, Subtype=0x01, Used=0).Create() Domoticz.Device(Name="Import Energy", Unit=9, Type=243, Subtype=29, Used=0).Create()
if 10 not in Devices: if 10 not in Devices:
Domoticz.Device(Name="Export Energy", Unit=10, Type=0xfa, Subtype=0x01, Used=0).Create() Domoticz.Device(Name="Export Energy", Unit=10, Type=243, Subtype=29, Used=0).Create()
# 11 will be not used Import Energy (Reactive) / kVArh # 11 will be not used Import Energy (Reactive) / kVArh
# 12 will be not used Export Energy (Reactive) / kVArh # 12 will be not used Export Energy (Reactive) / kVArh
if 13 not in Devices: if 13 not in Devices:
Options = { "Custom": "1;W" } Options = { "Custom": "1;W" }
Domoticz.Device(Name="Total Demand Power", Unit=13, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Total Demand Power", Unit=13, TypeName="Custom", Used=0, Options=Options).Create()
if 14 not in Devices: if 14 not in Devices:
Options = { "Custom": "1;W" } Options = { "Custom": "1;W" }
Domoticz.Device(Name="Maximum Total Demand Power", Unit=14, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Maximum Total Demand Power", Unit=14, TypeName="Custom", Used=0, Options=Options).Create()
if 15 not in Devices: if 15 not in Devices:
Options = { "Custom": "1;W" } Options = { "Custom": "1;W" }
Domoticz.Device(Name="Import Demand Power", Unit=15, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Import Demand Power", Unit=15, TypeName="Custom", Used=0, Options=Options).Create()
if 16 not in Devices: if 16 not in Devices:
Options = { "Custom": "1;W" } Options = { "Custom": "1;W" }
Domoticz.Device(Name="Maximum Import Demand Power", Unit=16, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Maximum Import Demand Power", Unit=16, TypeName="Custom", Used=0, Options=Options).Create()
if 17 not in Devices: if 17 not in Devices:
Options = { "Custom": "1;W" } Options = { "Custom": "1;W" }
Domoticz.Device(Name="Export Demand Power", Unit=17, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Export Demand Power", Unit=17, TypeName="Custom", Used=0, Options=Options).Create()
if 18 not in Devices: if 18 not in Devices:
Options = { "Custom": "1;W" } Options = { "Custom": "1;W" }
Domoticz.Device(Name="Maximum Export Demand Power", Unit=18, TypeName="Custom", Used=0, Options=Options).Create() Domoticz.Device(Name="Maximum Export Demand Power", Unit=18, TypeName="Custom", Used=0, Options=Options).Create()
if 19 not in Devices: if 19 not in Devices:
@ -192,8 +177,8 @@ class BasePlugin:
if 20 not in Devices: if 20 not in Devices:
Domoticz.Device(Name="Maximum Total Demand Current", Unit=20, TypeName="Current (Single)", Used=0).Create() Domoticz.Device(Name="Maximum Total Demand Current", Unit=20, TypeName="Current (Single)", Used=0).Create()
if 21 not in Devices: if 21 not in Devices:
Domoticz.Device(Name="Total Energy (Active)", Unit=21, Type=0xfa, Subtype=0x01, Used=0).Create() Domoticz.Device(Name="Total Energy (Active)", Unit=21, Type=243, Subtype=29, Used=0).Create()
# 22 will not be used Total Energy (Reactive) # 22 will not be used Total Energy (Reactive)
return return
@ -228,148 +213,65 @@ class BasePlugin:
Devices[20].Update(1, "0") Devices[20].Update(1, "0")
Devices[21].Update(1, "0") Devices[21].Update(1, "0")
# XXX: Finish that. #Domoticz.Log("Voltage : " + str(getmodbus(0x0000, client)) )
self.voltage.update(getmodbus(0x0000, client))
Devices[1].Update(1, self.voltage.strget())
# TODO: catch errors #Domoticz.Log("Current : " + str(getmodbus(0x0006, client)) )
# 3 counters self.current.update(getmodbus(0x0006, client))
total_e = "0" Devices[2].Update(1, self.current.strget())
export_e = "0"
import_e = "0"
export_w = 0
import_w = 0
power = "0"
# Total Energy #Domoticz.Log("Power Active : " + str(getmodbus(0x000c, client)) )
data = client.read_holding_registers(0, 2) self.active_power.update(getmodbus(0x000c, client))
Domoticz.Debug("Data from register 0: "+str(data)) Devices[3].Update(1, self.active_power.strget())
# Unsigned 32
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_32bit_int()
total_e = str(value)
# Export Energy #Domoticz.Log("Power apparent: " + str(getmodbus(0x0012, client)) )
data = client.read_holding_registers(0x8, 2) self.apparent_power.update(getmodbus(0x0012, client))
Domoticz.Debug("Data from register 0x8: "+str(data)) Devices[4].Update(1, self.apparent_power.strget())
# Unsigned 32
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_32bit_int()
export_e = str(value)
# Import Energy #Domoticz.Log("Power reactive: " + str(getmodbus(0x0018, client)) )
data = client.read_holding_registers(0xA, 2) self.reactive_power.update(getmodbus(0x0018, client))
Domoticz.Debug("Data from register 0xA: "+str(data)) Devices[5].Update(1, self.reactive_power.strget())
# Unsigned 32
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
# Value
value = decoder.decode_32bit_int()
import_e = str(value)
# Voltage #Domoticz.Log("Power Factor : " + str(getmodbus(0x001e, client)) )
data = client.read_holding_registers(0xC, 1) self.power_factor.update(getmodbus(0x001e, client))
Domoticz.Debug("Data from register 0xC: "+str(data)) Devices[6].Update(1, self.power_factor.strget())
# 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)
value = self.voltage.get()
Domoticz.Debug(" = {}".format(value))
Devices[4].Update(1, str(value))
# Current #Domoticz.Log("Phase Angle : " + str(getmodbus(0x0024, client)) )
data = client.read_holding_registers(0xD, 1) self.phase_angle.update(getmodbus(0x0024, client))
Domoticz.Debug("Data from register 0xD: "+str(data)) Devices[7].Update(1, self.phase_angle.strget())
# 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)
value = self.current.get()
Domoticz.Debug(" = {}".format(value))
Devices[5].Update(1, str(value))
# Active Power #Domoticz.Log("Frequency : " + str(getmodbus(0x0046, client)) )
data = client.read_holding_registers(0xE, 1) self.frequency.update(getmodbus(0x0046, client))
Domoticz.Debug("Data from register 0xE: "+str(data)) Devices[8].Update(1, self.frequency.strget())
# 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)
value = self.active_power.get()
Domoticz.Debug(" = {}".format(value))
Devices[6].Update(1, str(value))
if value > 0.0:
import_w = value
if value < 0.0:
export_w = value
power = str(abs(value))
# Reactive Power power = self.active_power.get()
data = client.read_holding_registers(0xF, 1) if power >= 0:
Domoticz.Debug("Data from register 0xF: "+str(data)) import_power = power
# Unsigned 16 else:
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big) import_power = 0
# Value
value = decoder.decode_16bit_int()
Domoticz.Debug("Value after conversion : "+str(value))
Domoticz.Debug("-> Calculating average")
self.reactive_power.update(value)
value = self.reactive_power.get()
Domoticz.Debug(" = {}".format(value))
Devices[7].Update(1, str(value))
# Power Factor if power < 0:
data = client.read_holding_registers(0x10, 1) export_power = abs(power)
Domoticz.Debug("Data from register 0x10: "+str(data)) else:
# Unsigned 16 export_power = 0
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)
value = self.power_factor.get()
Domoticz.Debug(" = {}".format(value))
Devices[8].Update(1, str(value))
# Frequency #Domoticz.Log("Import NRJ act : " + str(getmodbus(0x0048, client)) )
data = client.read_holding_registers(0x11, 1) Devices[9].Update(1, sValue=str(import_power)+";"+str(getmodbus(0x0048, client)*1000))
Domoticz.Debug("Data from register 0x11: "+str(data))
# Unsigned 16 #Domoticz.Log("Export NRJ act : " + str(getmodbus(0x004a, client)) )
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big) Devices[10].Update(1, sValue=str(export_power)+";"+str(getmodbus(0x004a, client)*1000))
# 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)
value = self.frequency.get()
Domoticz.Debug(" = {}".format(value))
Devices[9].Update(1, str(value))
# Do insert data on counters
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[3].Update(1, sValue=import_e+"0;0;0;0;"+str(abs(import_w))+";0")
Devices[10].Update(1, sValue=import_e+"0;0;"+export_e+"0;0;"+str(abs(import_w))+";"+str(abs(export_w)))
Domoticz.Log("Total Demand Pwr : " + str(getmodbus(0x0054, client)) )
Domoticz.Log("Max Demand Pwr : " + str(getmodbus(0x0056, client)) )
Domoticz.Log("Input Demand Pwr : " + str(getmodbus(0x0058, client)) )
Domoticz.Log("Max Input Demand Pwr : " + str(getmodbus(0x005a, client)) )
Domoticz.Log("Export Demand Pwr : " + str(getmodbus(0x005c, client)) )
Domoticz.Log("Max Export Demand Pwr : " + str(getmodbus(0x005e, client)) )
Domoticz.Log("Total Demand Cur: " + str(getmodbus(0x0102, client)) )
Domoticz.Log("Max Total Demand Cur: " + str(getmodbus(0x0108, client)) )
Domoticz.Log("Total Energy Act: " + str(getmodbus(0x0156, client)) )
global _plugin global _plugin
_plugin = BasePlugin() _plugin = BasePlugin()
@ -400,3 +302,27 @@ 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 float 32 bits values
def getmodbus(register, client):
value = 0
try:
data = client.read_input_registers(register, 2)
Domoticz.Debug("Data from register "+str(register)+": "+str(data))
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
value = round(decoder.decode_32bit_float(), 3)
except:
Domoticz.Error("Error getting data from "+str(register) + ", try 1")
try:
data = client.read_input_registers(register, 2)
Domoticz.Debug("Data from register "+str(register)+": "+str(data))
decoder = BinaryPayloadDecoder.fromRegisters(data, byteorder=Endian.Big, wordorder=Endian.Big)
value = round(decoder.decode_32bit_float(), 3)
except:
Domoticz.Error("Error getting data from "+str(register) + ", try 2")
return value