diff --git a/README.md b/README.md index e4dd6cb..98a13d1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # pyFroniusReg -python library for interacting with Fronius inverters via modbus/TCP. Includes some example utilities +The aim of this python library is to provide some nice to use abstractions to interact with Fronius solar inverters and storage systems over ModBus. It is tested on a Gen24 Primo 5kW system with an attached BYD battery storage system using 'float' mode of the sunspec ModBus over TCP protocol. -FroniusReg.py: the library + + + +# Examples force_charge.py: Use the library to force my system to charge at 2.5kW diff --git a/examples/read_regs.py b/examples/read_regs.py index 77bc649..ee2a962 100755 --- a/examples/read_regs.py +++ b/examples/read_regs.py @@ -1,32 +1,44 @@ #!/usr/bin/env python3 from pyfroniusreg import gen24_registers as froniusreg +from pyfroniusreg.froniusreg import registerReadError from pymodbus.client.tcp import ModbusTcpClient fronius1 = ModbusTcpClient("172.19.107.211", port=502, timeout=10) fronius1.connect() +print(froniusreg.ID.getValue(fronius1)) +print(froniusreg.L.getValue(fronius1)) +# Some strings + +print(froniusreg.Mn.getValue(fronius1)) +print(froniusreg.Md.getValue(fronius1)) +print(froniusreg.Vr.getValue(fronius1)) +print(froniusreg.SN.getValue(fronius1)) +print(froniusreg.DA.getValue(fronius1)) + + soc = froniusreg.scaledStateOfCharge.getValue(fronius1) print(" SOC: %s%%" % soc) discharge = froniusreg.scaledOutWRte.getValue(fronius1) -print("Pre DRate: %d%%" % discharge) +print(" DRate: %d%%" % discharge) charge = froniusreg.scaledInWRte.getValue(fronius1) -print("Pre CRate: %d%%" % charge) +print(" CRate: %d%%" % charge) mode = froniusreg.StorCtl_Mode.getValue(fronius1) -print("Pre Mode: %d" % mode) +print(" Mode: %d" % mode) reserve = froniusreg.scaledReserve.getValue(fronius1) -print("Pre Res: %d" % reserve) +print(" Res: %d" % reserve) rate = froniusreg.scaledMaxChaRte.getValue(fronius1) -print("Pre rate: %d" % rate) +print(" rate: %d" % rate) rate = froniusreg.scaledMaxWChaGra.getValue(fronius1) -print("Pre WGra rate: %d" % rate) +print(" WGra rate: %d" % rate) revert = froniusreg.InOutWRte_RvrtTms.getValue(fronius1) print("Timer: %d" % revert) diff --git a/src/pyfroniusreg/froniusreg.py b/src/pyfroniusreg/froniusreg.py index d4638f6..c0d995b 100644 --- a/src/pyfroniusreg/froniusreg.py +++ b/src/pyfroniusreg/froniusreg.py @@ -29,16 +29,25 @@ class DataType(): # helper functions for DataType constructors -def decode_string(decoder, value): - return str(decoder.decode_string(16).decode('utf-8')) +def decode_string8(decoder): + return str(decoder.decode_string(8).decode('utf-8')) + +def decode_string16(decoder): + try: + return str(decoder.decode_string(16).decode('utf-8')) + except UnicodeDecodeError: + return str(decoder.decode_string(16)) + +def decode_string32(decoder): + return str(decoder.decode_string(32).decode('utf-8')) def encode_16bit_int(encoder, value): return encoder.add_16bit_int(int(value)) # The various data types that the fronius inverters use -string8 = DataType(4, decode_string, BinaryPayloadBuilder.add_string) -string16 = DataType(8, decode_string, BinaryPayloadBuilder.add_string) -string32 = DataType(16, decode_string, BinaryPayloadBuilder.add_string) +string8 = DataType(4, decode_string8, BinaryPayloadBuilder.add_string) +string16 = DataType(8, decode_string16, BinaryPayloadBuilder.add_string) +string32 = DataType(16, decode_string32, BinaryPayloadBuilder.add_string) int16 = DataType(1, BinaryPayloadDecoder.decode_16bit_int, encode_16bit_int) uint16 = DataType(1, BinaryPayloadDecoder.decode_16bit_uint, encode_16bit_int) int32 = DataType(2, BinaryPayloadDecoder.decode_32bit_int, BinaryPayloadBuilder.add_32bit_int) @@ -80,7 +89,7 @@ class FroniusReg: self.datatype.width, slave=self.unit) if(modbusValue.isError()): - raise registerReadError("Unable to read from Fronius Register: %d, %s" % (self.id, self.description)) + raise registerReadError("Unable to read from Fronius Register: %d, %s\n%s" % (self.address, self.description, modbusValue)) if(modbusValue is None): raise registerReadError("It's NONE!") return self.datatype.decode_from_register(modbusValue.registers) diff --git a/src/pyfroniusreg/gen24_registers.py b/src/pyfroniusreg/gen24_registers.py index 92f94af..e31bb42 100644 --- a/src/pyfroniusreg/gen24_registers.py +++ b/src/pyfroniusreg/gen24_registers.py @@ -8,6 +8,14 @@ storageStateOfCharge = froniusreg.FroniusReg(40362, froniusreg.uint16, 1, "Stora storageStateOfChargeSF = froniusreg.FroniusReg(40376, froniusreg.int16, 1, "Storage State of Charge Scaling Factor") scaledStateOfCharge = froniusreg.ScaledFroniusReg(storageStateOfCharge, storageStateOfChargeSF) +ID = froniusreg.FroniusReg(40003, froniusreg.uint16, 1, "Well-known value. Uniquely identifies this as a sunspec model 'common' (1)") +L = froniusreg.FroniusReg(40004, froniusreg.uint16, 1, "Sunspec model commen register count") +Mn = froniusreg.FroniusReg(40005, froniusreg.string16, 1, "Manufacturer") +Md = froniusreg.FroniusReg(40021, froniusreg.string16, 1, "Device Model") +Vr = froniusreg.FroniusReg(40045, froniusreg.string8, 1, "SW version") +SN = froniusreg.FroniusReg(40068, froniusreg.string16, 1, "Serial Number") +DA = froniusreg.FroniusReg(40069, froniusreg.uint16, 1, "Modbus Device Address") + OutWRte = froniusreg.FroniusReg(40366, froniusreg.int16, 1, "DischargeRate") InWRte = froniusreg.FroniusReg(40367, froniusreg.int16, 1, "ChargeRate") WRteSF = froniusreg.FroniusReg(40379, froniusreg.int16, 1, "ScalingFactor for storage Watts") diff --git a/tests/test_regs.py b/tests/test_regs.py index 16d65b2..ae7a46a 100644 --- a/tests/test_regs.py +++ b/tests/test_regs.py @@ -18,6 +18,23 @@ class TestRead(unittest.TestCase): dr = gen24_registers.OutWRte.getValue(fronius1) assert isinstance(dr, int) + def test_read_string16(self): + Mn = gen24_registers.Mn.getValue(fronius1) + assert Mn[0:7] == "Fronius" + + def test_read_string8(self): + # this isn't a good test, this value changes regularly + Vr = gen24_registers.Vr.getValue(fronius1) + assert Vr == "1.33.7-1" + + def test_read_model(self): + Md = gen24_registers.Md.getValue(fronius1) + assert Md == "Primo GEN24 5.0\x00" + + def test_read_sn(self): + SN = gen24_registers.SN.getValue(fronius1) + assert SN == "12345567" + def test_write_direct(self): current = gen24_registers.OutWRte.getValue(fronius1) retval = gen24_registers.OutWRte.setValue(fronius1, current)