From 834bb8474b12b527d7a25a8beb83c3597d3e0d12 Mon Sep 17 00:00:00 2001 From: Xavier Beaudouin Date: Sun, 23 Oct 2022 15:59:44 +0200 Subject: [PATCH] First code --- plugin.py | 298 ++++++++++++++++++++---------------------------------- 1 file changed, 112 insertions(+), 186 deletions(-) diff --git a/plugin.py b/plugin.py index 26fa802..0f7cf3e 100644 --- a/plugin.py +++ b/plugin.py @@ -7,7 +7,7 @@ Requirements: 2. pymodbus AND pymodbusTCP """ """ - + @@ -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.8/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. @@ -63,55 +68,36 @@ class Average: def get(self): return sum(self.samples) / len(self.samples) -# -# Domoticz shows graphs with intervals of 5 minutes. -# 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) + def strget(self): + return str(sum(self.samples) / len(self.samples)) # Plugin itself class BasePlugin: - #enabled = False def __init__(self): # Voltage for last 5 minutes self.voltage=Average() # Current for last 5 minutes self.current=Average() + # Apparent Power for last 5 minutes + self.apparent_power=Average() # Active power for last 5 minutes self.active_power=Average() - # Apparent Power for last 5 minutes - self.apparent_power=Average() # Reactive power for last 5 minutes self.reactive_power=Average() # Power factor for last 5 minutes self.power_factor=Average() - # Phase Angle for last 5 minutes - self.phase_angle=Average() + # Phase Angle for last 5 minutes + self.phase_angle=Average() # Frequency for last 5 minutes 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 @@ -140,51 +126,50 @@ 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. - if 1 not in Devices: + if 1 not in Devices: 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() - if 3 not in Devices: + if 3 not in Devices: Options = { "Custom": "1;W" } 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" } 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" } 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" } 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" } Domoticz.Device(Name="Phase Angle", Unit=7, TypeName="Custom", Used=0, Options=Options).Create() if 8 not in Devices: Options = { "Custom": "1;Hz" } Domoticz.Device(Name="Frequency", Unit=8, TypeName="Custom", Used=0, Options=Options).Create() 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: - Domoticz.Device(Name="Export Energy", Unit=10, Type=0xfa, Subtype=0x01, Used=0).Create() - # 11 will be not used Import Energy (Reactive) / kVArh - # 12 will be not used Export Energy (Reactive) / kVArh - if 13 not in Devices: + Domoticz.Device(Name="Export Energy", Unit=10, Type=243, Subtype=29, Used=0).Create() + # 11 will be not used Import Energy (Reactive) / kVArh + # 12 will be not used Export Energy (Reactive) / kVArh + if 13 not in Devices: Options = { "Custom": "1;W" } 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" } 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" } 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" } 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" } 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" } Domoticz.Device(Name="Maximum Export Demand Power", Unit=18, TypeName="Custom", Used=0, Options=Options).Create() if 19 not in Devices: @@ -192,8 +177,8 @@ class BasePlugin: if 20 not in Devices: Domoticz.Device(Name="Maximum Total Demand Current", Unit=20, TypeName="Current (Single)", Used=0).Create() if 21 not in Devices: - Domoticz.Device(Name="Total Energy (Active)", Unit=21, Type=0xfa, Subtype=0x01, Used=0).Create() - # 22 will not be used Total Energy (Reactive) + Domoticz.Device(Name="Total Energy (Active)", Unit=21, Type=243, Subtype=29, Used=0).Create() + # 22 will not be used Total Energy (Reactive) return @@ -228,148 +213,65 @@ class BasePlugin: Devices[20].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 - # 3 counters - total_e = "0" - export_e = "0" - import_e = "0" - export_w = 0 - import_w = 0 - power = "0" + #Domoticz.Log("Current : " + str(getmodbus(0x0006, client)) ) + self.current.update(getmodbus(0x0006, client)) + Devices[2].Update(1, self.current.strget()) - # 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() - total_e = str(value) + #Domoticz.Log("Power Active : " + str(getmodbus(0x000c, client)) ) + self.active_power.update(getmodbus(0x000c, client)) + Devices[3].Update(1, self.active_power.strget()) - # 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() - export_e = str(value) + #Domoticz.Log("Power apparent: " + str(getmodbus(0x0012, client)) ) + self.apparent_power.update(getmodbus(0x0012, client)) + Devices[4].Update(1, self.apparent_power.strget()) - # 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() - import_e = str(value) + #Domoticz.Log("Power reactive: " + str(getmodbus(0x0018, client)) ) + self.reactive_power.update(getmodbus(0x0018, client)) + Devices[5].Update(1, self.reactive_power.strget()) - # 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 = 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)) + #Domoticz.Log("Power Factor : " + str(getmodbus(0x001e, client)) ) + self.power_factor.update(getmodbus(0x001e, client)) + Devices[6].Update(1, self.power_factor.strget()) - # 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 = 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)) + #Domoticz.Log("Phase Angle : " + str(getmodbus(0x0024, client)) ) + self.phase_angle.update(getmodbus(0x0024, client)) + Devices[7].Update(1, self.phase_angle.strget()) - # 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() - 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)) + #Domoticz.Log("Frequency : " + str(getmodbus(0x0046, client)) ) + self.frequency.update(getmodbus(0x0046, client)) + Devices[8].Update(1, self.frequency.strget()) - # 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() - 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 = self.active_power.get() + if power >= 0: + import_power = power + else: + import_power = 0 - # 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 = 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)) + if power < 0: + export_power = abs(power) + else: + export_power = 0 - # 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 = 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("Import NRJ act : " + str(getmodbus(0x0048, client)) ) + Devices[9].Update(1, sValue=str(import_power)+";"+str(getmodbus(0x0048, client)*1000)) + + #Domoticz.Log("Export NRJ act : " + str(getmodbus(0x004a, client)) ) + Devices[10].Update(1, sValue=str(export_power)+";"+str(getmodbus(0x004a, client)*1000)) + 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 _plugin = BasePlugin() @@ -400,3 +302,27 @@ def DumpConfigToLog(): Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'") Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel)) 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 + + +