Add some string reading bits, SerialNumber test failing

This commit is contained in:
Paul Warren 2024-10-12 17:37:46 +11:00
parent 989323f96d
commit 3929da604d
5 changed files with 63 additions and 14 deletions

View file

@ -1,8 +1,11 @@
# pyFroniusReg # 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 force_charge.py: Use the library to force my system to charge at 2.5kW

View file

@ -1,32 +1,44 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from pyfroniusreg import gen24_registers as froniusreg from pyfroniusreg import gen24_registers as froniusreg
from pyfroniusreg.froniusreg import registerReadError
from pymodbus.client.tcp import ModbusTcpClient from pymodbus.client.tcp import ModbusTcpClient
fronius1 = ModbusTcpClient("172.19.107.211", port=502, timeout=10) fronius1 = ModbusTcpClient("172.19.107.211", port=502, timeout=10)
fronius1.connect() 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) soc = froniusreg.scaledStateOfCharge.getValue(fronius1)
print(" SOC: %s%%" % soc) print(" SOC: %s%%" % soc)
discharge = froniusreg.scaledOutWRte.getValue(fronius1) discharge = froniusreg.scaledOutWRte.getValue(fronius1)
print("Pre DRate: %d%%" % discharge) print(" DRate: %d%%" % discharge)
charge = froniusreg.scaledInWRte.getValue(fronius1) charge = froniusreg.scaledInWRte.getValue(fronius1)
print("Pre CRate: %d%%" % charge) print(" CRate: %d%%" % charge)
mode = froniusreg.StorCtl_Mode.getValue(fronius1) mode = froniusreg.StorCtl_Mode.getValue(fronius1)
print("Pre Mode: %d" % mode) print(" Mode: %d" % mode)
reserve = froniusreg.scaledReserve.getValue(fronius1) reserve = froniusreg.scaledReserve.getValue(fronius1)
print("Pre Res: %d" % reserve) print(" Res: %d" % reserve)
rate = froniusreg.scaledMaxChaRte.getValue(fronius1) rate = froniusreg.scaledMaxChaRte.getValue(fronius1)
print("Pre rate: %d" % rate) print(" rate: %d" % rate)
rate = froniusreg.scaledMaxWChaGra.getValue(fronius1) rate = froniusreg.scaledMaxWChaGra.getValue(fronius1)
print("Pre WGra rate: %d" % rate) print(" WGra rate: %d" % rate)
revert = froniusreg.InOutWRte_RvrtTms.getValue(fronius1) revert = froniusreg.InOutWRte_RvrtTms.getValue(fronius1)
print("Timer: %d" % revert) print("Timer: %d" % revert)

View file

@ -29,16 +29,25 @@ class DataType():
# helper functions for DataType constructors # helper functions for DataType constructors
def decode_string(decoder, value): def decode_string8(decoder):
return str(decoder.decode_string(16).decode('utf-8')) 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): def encode_16bit_int(encoder, value):
return encoder.add_16bit_int(int(value)) return encoder.add_16bit_int(int(value))
# The various data types that the fronius inverters use # The various data types that the fronius inverters use
string8 = DataType(4, decode_string, BinaryPayloadBuilder.add_string) string8 = DataType(4, decode_string8, BinaryPayloadBuilder.add_string)
string16 = DataType(8, decode_string, BinaryPayloadBuilder.add_string) string16 = DataType(8, decode_string16, BinaryPayloadBuilder.add_string)
string32 = DataType(16, decode_string, BinaryPayloadBuilder.add_string) string32 = DataType(16, decode_string32, BinaryPayloadBuilder.add_string)
int16 = DataType(1, BinaryPayloadDecoder.decode_16bit_int, encode_16bit_int) int16 = DataType(1, BinaryPayloadDecoder.decode_16bit_int, encode_16bit_int)
uint16 = DataType(1, BinaryPayloadDecoder.decode_16bit_uint, encode_16bit_int) uint16 = DataType(1, BinaryPayloadDecoder.decode_16bit_uint, encode_16bit_int)
int32 = DataType(2, BinaryPayloadDecoder.decode_32bit_int, BinaryPayloadBuilder.add_32bit_int) int32 = DataType(2, BinaryPayloadDecoder.decode_32bit_int, BinaryPayloadBuilder.add_32bit_int)
@ -80,7 +89,7 @@ class FroniusReg:
self.datatype.width, self.datatype.width,
slave=self.unit) slave=self.unit)
if(modbusValue.isError()): 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): if(modbusValue is None):
raise registerReadError("It's NONE!") raise registerReadError("It's NONE!")
return self.datatype.decode_from_register(modbusValue.registers) return self.datatype.decode_from_register(modbusValue.registers)

View file

@ -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") storageStateOfChargeSF = froniusreg.FroniusReg(40376, froniusreg.int16, 1, "Storage State of Charge Scaling Factor")
scaledStateOfCharge = froniusreg.ScaledFroniusReg(storageStateOfCharge, storageStateOfChargeSF) 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") OutWRte = froniusreg.FroniusReg(40366, froniusreg.int16, 1, "DischargeRate")
InWRte = froniusreg.FroniusReg(40367, froniusreg.int16, 1, "ChargeRate") InWRte = froniusreg.FroniusReg(40367, froniusreg.int16, 1, "ChargeRate")
WRteSF = froniusreg.FroniusReg(40379, froniusreg.int16, 1, "ScalingFactor for storage Watts") WRteSF = froniusreg.FroniusReg(40379, froniusreg.int16, 1, "ScalingFactor for storage Watts")

View file

@ -18,6 +18,23 @@ class TestRead(unittest.TestCase):
dr = gen24_registers.OutWRte.getValue(fronius1) dr = gen24_registers.OutWRte.getValue(fronius1)
assert isinstance(dr, int) 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): def test_write_direct(self):
current = gen24_registers.OutWRte.getValue(fronius1) current = gen24_registers.OutWRte.getValue(fronius1)
retval = gen24_registers.OutWRte.setValue(fronius1, current) retval = gen24_registers.OutWRte.setValue(fronius1, current)