Compare commits

..

No commits in common. "faf2090fa1999fcf361be48cfd20518068db08e5" and "8a925769bba163c263a0c2a5cb63a6943457a216" have entirely different histories.

5 changed files with 59 additions and 121 deletions

View file

@ -1,12 +1,10 @@
import nox import nox
import os import os
@nox.session @nox.session
def lint(session): def lint(session):
session.install("ruff") session.install('ruff')
session.run("ruff", "check", "--exclude", "examples") session.run('ruff', 'check', '--exclude', 'examples')
@nox.session @nox.session
def build_and_check_dists(session): def build_and_check_dists(session):
@ -15,16 +13,14 @@ def build_and_check_dists(session):
session.run("python", "-m", "build") session.run("python", "-m", "build")
session.run("python", "-m", "twine", "check", "dist/*") session.run("python", "-m", "twine", "check", "dist/*")
@nox.session @nox.session
def build(session): def build(session):
lint(session) lint(session)
build_and_check_dists(session) build_and_check_dists(session)
@nox.session @nox.session
def tests(session): def tests(session):
session.install("pytest") session.install('pytest')
build_and_check_dists(session) build_and_check_dists(session)
generated_files = os.listdir("dist/") generated_files = os.listdir("dist/")
@ -32,4 +28,5 @@ def tests(session):
session.install(generated_sdist) session.install(generated_sdist)
session.run("pytest") session.run('pytest')

View file

@ -6,8 +6,8 @@
from pymodbus.constants import Endian from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder from pymodbus.payload import BinaryPayloadDecoder, BinaryPayloadBuilder
class DataType():
class DataType:
def __init__(self, width, decode, add): def __init__(self, width, decode, add):
self._width = width self._width = width
self._decode = decode self._decode = decode
@ -18,14 +18,12 @@ class DataType:
return self._width return self._width
def decode_from_register(self, registers): def decode_from_register(self, registers):
decoder = BinaryPayloadDecoder.fromRegisters( decoder = BinaryPayloadDecoder.fromRegisters(registers, byteorder=Endian.BIG, wordorder=Endian.BIG)
registers, byteorder=Endian.BIG, wordorder=Endian.BIG
)
return self._decode(decoder) return self._decode(decoder)
def encode_to_buffer(self, value): def encode_to_buffer(self, value):
print(type(value))
encoder = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.BIG) encoder = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.BIG)
print(type(value))
self._add(encoder, value) self._add(encoder, value)
return encoder.build() return encoder.build()
@ -33,53 +31,40 @@ class DataType:
# helper functions for DataType constructors # helper functions for DataType constructors
def decode_string8(decoder): def decode_string8(decoder):
try: try:
return str(decoder.decode_string(8).decode("utf-8")) return str(decoder.decode_string(8).decode('utf-8'))
except UnicodeDecodeError: except UnicodeDecodeError:
return decoder.decode_string(8) return decoder.decode_string(8)
def decode_string16(decoder): def decode_string16(decoder):
try: try:
return str(decoder.decode_string(16).decode("utf-8")) return str(decoder.decode_string(16).decode('utf-8'))
except UnicodeDecodeError: except UnicodeDecodeError:
return decoder.decode_string(16) return decoder.decode_string(16)
def decode_string32(decoder): def decode_string32(decoder):
try: try:
return str(decoder.decode_string(32).decode("utf-8")) return str(decoder.decode_string(32).decode('utf-8'))
except UnicodeDecodeError: except UnicodeDecodeError:
return decoder.decode_string(32) return decoder.decode_string(32)
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_string8, BinaryPayloadBuilder.add_string) string8 = DataType(4, decode_string8, BinaryPayloadBuilder.add_string)
string16 = DataType(8, decode_string16, BinaryPayloadBuilder.add_string) string16 = DataType(8, decode_string16, BinaryPayloadBuilder.add_string)
string32 = DataType(16, decode_string32, 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( int32 = DataType(2, BinaryPayloadDecoder.decode_32bit_int, BinaryPayloadBuilder.add_32bit_int)
2, BinaryPayloadDecoder.decode_32bit_int, BinaryPayloadBuilder.add_32bit_int uint32 = DataType(2, BinaryPayloadDecoder.decode_32bit_uint, BinaryPayloadBuilder.add_32bit_uint)
) float32 = DataType(2, BinaryPayloadDecoder.decode_32bit_float, BinaryPayloadBuilder.add_32bit_float)
uint32 = DataType( uint64 = DataType(4, BinaryPayloadDecoder.decode_64bit_uint, BinaryPayloadBuilder.add_64bit_uint)
2, BinaryPayloadDecoder.decode_32bit_uint, BinaryPayloadBuilder.add_32bit_uint
)
float32 = DataType(
2, BinaryPayloadDecoder.decode_32bit_float, BinaryPayloadBuilder.add_32bit_float
)
uint64 = DataType(
4, BinaryPayloadDecoder.decode_64bit_uint, BinaryPayloadBuilder.add_64bit_uint
)
class registerReadError(Exception): class registerReadError(Exception):
pass pass
# Fronius modbus register object. # Fronius modbus register object.
# #
# the 'spreadsheet' refers to the various spreadsheets in the 'gen24-modbus-api-external-docs.zip' # the 'spreadsheet' refers to the various spreadsheets in the 'gen24-modbus-api-external-docs.zip'
@ -87,7 +72,6 @@ class registerReadError(Exception):
# 'gen24 modbus' # 'gen24 modbus'
# #
# Constructor parameters: # Constructor parameters:
# address: address as specified in the spreadsheet # address: address as specified in the spreadsheet
# datatype: One of the above datatypes, as specified for the address in the spreadsheet # datatype: One of the above datatypes, as specified for the address in the spreadsheet
@ -98,7 +82,7 @@ class FroniusReg:
self.address = address self.address = address
self.datatype = datatype self.datatype = datatype
self.unit = unit self.unit = unit
self.description = description self.description=description
def getValue(self, modbusClient): def getValue(self, modbusClient):
return self.__getRegisterValue(modbusClient) return self.__getRegisterValue(modbusClient)
@ -107,39 +91,31 @@ class FroniusReg:
return self.__setRegisterValue(modbusClient, value) return self.__setRegisterValue(modbusClient, value)
def __getRegisterValue(self, modbusClient): def __getRegisterValue(self, modbusClient):
modbusValue = modbusClient.read_holding_registers( modbusValue = modbusClient.read_holding_registers(self.address-1,
self.address - 1, self.datatype.width, slave=self.unit self.datatype.width,
) slave=self.unit)
if modbusValue.isError(): if(modbusValue.isError()):
raise registerReadError( raise registerReadError("Unable to read from Fronius Register: %d, %s\n%s" % (self.address, self.description, modbusValue))
"Unable to read from Fronius Register: %d, %s\n%s" if(modbusValue is None):
% (self.address, self.description, modbusValue)
)
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)
def __setRegisterValue(self, modbusClient, value): def __setRegisterValue(self, modbusClient, value):
modbusValue = modbusClient.write_registers( modbusValue = modbusClient.write_registers(self.address-1,
self.address - 1, self.datatype.encode_to_buffer(value),
self.datatype.encode_to_buffer(value), slave=self.unit,
slave=self.unit, skip_encode=True)
skip_encode=True,
)
return modbusValue return modbusValue
class ScaledFroniusReg: class ScaledFroniusReg:
def __init__(self, valueReg, scaleReg): def __init__(self, valueReg, scaleReg):
self.valueReg = valueReg self.valueReg=valueReg
self.scaleReg = scaleReg self.scaleReg=scaleReg
def getValue(self, modbusClient): def getValue(self, modbusClient):
return self.valueReg.getValue(modbusClient) * 10 ** self.scaleReg.getValue( return self.valueReg.getValue(modbusClient) * 10 ** self.scaleReg.getValue(modbusClient)
modbusClient
)
def setValue(self, modbusClient, value): def setValue(self, modbusClient, value):
return self.valueReg.setValue( return self.valueReg.setValue(modbusClient, value / 10 ** self.scaleReg.getValue(modbusClient))
modbusClient, value / 10 ** self.scaleReg.getValue(modbusClient)
)

View file

@ -4,25 +4,12 @@ MaxChaRte = froniusreg.FroniusReg(40155, froniusreg.uint16, 1, "Max Charge Rate"
MaxChaRte_SF = froniusreg.FroniusReg(40156, froniusreg.int16, 1, "Max Charge Rate SF") MaxChaRte_SF = froniusreg.FroniusReg(40156, froniusreg.int16, 1, "Max Charge Rate SF")
wChaGra = froniusreg.FroniusReg(40357, froniusreg.uint16, 1, "Max Charge Power") wChaGra = froniusreg.FroniusReg(40357, froniusreg.uint16, 1, "Max Charge Power")
storageStateOfCharge = froniusreg.FroniusReg( storageStateOfCharge = froniusreg.FroniusReg(40362, froniusreg.uint16, 1, "Storage State of Charge")
40362, froniusreg.uint16, 1, "Storage State of Charge" storageStateOfChargeSF = froniusreg.FroniusReg(40376, froniusreg.int16, 1, "Storage State of Charge Scaling Factor")
) scaledStateOfCharge = froniusreg.ScaledFroniusReg(storageStateOfCharge, storageStateOfChargeSF)
storageStateOfChargeSF = froniusreg.FroniusReg(
40376, froniusreg.int16, 1, "Storage State of Charge Scaling Factor"
)
scaledStateOfCharge = froniusreg.ScaledFroniusReg(
storageStateOfCharge, storageStateOfChargeSF
)
ID = froniusreg.FroniusReg( ID = froniusreg.FroniusReg(40003, froniusreg.uint16, 1, "Well-known value. Uniquely identifies this as a sunspec model 'common' (1)")
40003, L = froniusreg.FroniusReg(40004, froniusreg.uint16, 1, "Sunspec model commen register count")
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") Mn = froniusreg.FroniusReg(40005, froniusreg.string16, 1, "Manufacturer")
Md = froniusreg.FroniusReg(40021, froniusreg.string16, 1, "Device Model") Md = froniusreg.FroniusReg(40021, froniusreg.string16, 1, "Device Model")
Vr = froniusreg.FroniusReg(40045, froniusreg.string8, 1, "SW version") Vr = froniusreg.FroniusReg(40045, froniusreg.string8, 1, "SW version")
@ -32,43 +19,21 @@ DA = froniusreg.FroniusReg(40069, froniusreg.uint16, 1, "Modbus Device Address")
InputID = froniusreg.FroniusReg(40304, froniusreg.uint16, 1, "Input ID") InputID = froniusreg.FroniusReg(40304, froniusreg.uint16, 1, "Input ID")
InputIDString = froniusreg.FroniusReg(40305, froniusreg.string8, 1, "Input ID String") InputIDString = froniusreg.FroniusReg(40305, froniusreg.string8, 1, "Input ID String")
module3DCW = froniusreg.FroniusReg( module3DCW = froniusreg.FroniusReg(40325, froniusreg.uint16, 1, "When the battery is discharged the data-points of the charge input are set to 0")
40325,
froniusreg.uint16,
1,
"When the battery is discharged the data-points of the charge input are set to 0",
)
module4DCW = froniusreg.FroniusReg( module4DCW = froniusreg.FroniusReg(40345, froniusreg.uint16, 1, "When the battery is charged the data-points of the discharge input are set to 0")
40345,
froniusreg.uint16,
1,
"When the battery is charged the data-points of the discharge input are set to 0",
)
DCW_SF = froniusreg.FroniusReg(40268, froniusreg.int16, 1, "DC Power Scaling factor") DCW_SF = froniusreg.FroniusReg(40268, froniusreg.int16, 1, "DC Power Scaling factor")
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( WRteSF = froniusreg.FroniusReg(40379, froniusreg.int16, 1, "ScalingFactor for storage Watts")
40379, froniusreg.int16, 1, "ScalingFactor for storage Watts" StorCtl_Mode = froniusreg.FroniusReg(40359, froniusreg.uint16, 1, "Hold/Charge/Discharge limit")
)
StorCtl_Mode = froniusreg.FroniusReg(
40359, froniusreg.uint16, 1, "Hold/Charge/Discharge limit"
)
MinRsvPct = froniusreg.FroniusReg(40361, froniusreg.uint16, 1, "Reserve Percentage") MinRsvPct = froniusreg.FroniusReg(40361, froniusreg.uint16, 1, "Reserve Percentage")
InOutWRte_RvrtTms = froniusreg.FroniusReg( InOutWRte_RvrtTms = froniusreg.FroniusReg(40369, froniusreg.uint16, 1, "Revert timer for charge settings")
40369, froniusreg.uint16, 1, "Revert timer for charge settings" ChaGriSet = froniusreg.FroniusReg(40371, froniusreg.uint16, 1, "enum16, 0 = PV only, 1 = Grid enabled")
) WChaDisChaGra_SF = froniusreg.FroniusReg(40373, froniusreg.int16, 1, "Charge/Discharge Power SF")
ChaGriSet = froniusreg.FroniusReg( MinRsvPct_SF = froniusreg.FroniusReg(40375, froniusreg.int16, 1, "Reserve Percentage Scaling")
40371, froniusreg.uint16, 1, "enum16, 0 = PV only, 1 = Grid enabled"
)
WChaDisChaGra_SF = froniusreg.FroniusReg(
40373, froniusreg.int16, 1, "Charge/Discharge Power SF"
)
MinRsvPct_SF = froniusreg.FroniusReg(
40375, froniusreg.int16, 1, "Reserve Percentage Scaling"
)
scaledOutWRte = froniusreg.ScaledFroniusReg(OutWRte, WRteSF) scaledOutWRte = froniusreg.ScaledFroniusReg(OutWRte, WRteSF)
scaledInWRte = froniusreg.ScaledFroniusReg(InWRte, WRteSF) scaledInWRte = froniusreg.ScaledFroniusReg(InWRte, WRteSF)

View file

@ -3,12 +3,13 @@ import unittest
import pyfroniusreg.froniusreg as froniusreg import pyfroniusreg.froniusreg as froniusreg
class TestDataTypes(unittest.TestCase): class TestDataTypes(unittest.TestCase):
def test_int16(self): def test_int16(self):
int16_buffer = froniusreg.int16.encode_to_buffer(1024) int16_buffer = froniusreg.int16.encode_to_buffer(1024)
assert int16_buffer == [b"\x04\x00"] assert int16_buffer == [b'\x04\x00']
if __name__ == "__main__": if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -8,8 +8,8 @@ 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()
class TestRead(unittest.TestCase): class TestRead(unittest.TestCase):
def test_read_scaled(self): def test_read_scaled(self):
soc = gen24_registers.scaledStateOfCharge.getValue(fronius1) soc = gen24_registers.scaledStateOfCharge.getValue(fronius1)
assert isinstance(soc, float) assert isinstance(soc, float)
@ -32,9 +32,9 @@ class TestRead(unittest.TestCase):
assert Md == "Primo GEN24 5.0\x00" assert Md == "Primo GEN24 5.0\x00"
# def test_read_sn(self): # def test_read_sn(self):
# This doesn't seem to return anything useful # This doesn't seem to return anything useful
# SN = gen24_registers.SN.getValue(fronius1) # SN = gen24_registers.SN.getValue(fronius1)
# assert SN == "12345567" # 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)
@ -46,7 +46,6 @@ class TestRead(unittest.TestCase):
retval = gen24_registers.scaledInWRte.setValue(fronius1, current) retval = gen24_registers.scaledInWRte.setValue(fronius1, current)
assert retval is not None assert retval is not None
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main() unittest.main()
fronius1.close() fronius1.close()