From 17d77ab667a20cb52b931df8f0d4925a29b7acdb Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Wed, 5 Nov 2025 23:00:09 +0100 Subject: [PATCH 01/15] #48: Upgraded to pymodbus>=3.11.0 --- README.md | 7 ++-- pyproject.toml | 2 +- requirements.txt | 2 +- sun2000_modbus/inverter.py | 24 ++++++------- tests/sun2000mock.py | 14 ++++---- tests/test_sun2000_modbus.py | 70 ++++++++++++++++++------------------ 6 files changed, 60 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index d14d971..e112ae6 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ to the device's internal Wifi access point. For information about how to connect ## Requirements - Python >= 3.9 +- pymodbus >= 3.11.0 ## Disclaimer @@ -47,7 +48,7 @@ During instantiation of a Sun2000 object the following parameters are accepted: | port | Port, usually 502, changed to 6607 on newer firmware versions. | | timeout | Connection timeout | | wait | Time to wait after connection before a register read can be performed. Increases stability. | -| slave | Number of inverter unit to be read by default, used in cascading scenarios. Defaults to 0, but some devices need it to be set to other values. | +| device_id | Number of inverter unit to be read by default, used in cascading scenarios. Defaults to 0, but some devices need it to be set to other values. | ### Read metrics @@ -70,13 +71,13 @@ Looking at the [above example](#usage) the different methods would return the fo Furthermore, a method `read_range` exists accepting the address of the register to start reading and either a quantity of registers or the address of the last register to be read. The result is returned as byte-string for further processing. -Each `read*` method accepts a `slave` argument which is used in cascading scenarios to address the desired inverter unit. +Each `read*` method accepts a `device_id` argument which is used in cascading scenarios to address the desired inverter unit. ### Write settings For writing a register the `write` method can be used, taking the register address and the value as arguments. -Furthermore, the `write` method accepts a `slave` argument which is used in cascading scenarios to address the desired inverter unit. +Furthermore, the `write` method accepts a `device_id` argument which is used in cascading scenarios to address the desired inverter unit. ## Registers diff --git a/pyproject.toml b/pyproject.toml index 768d138..ea9d232 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ dynamic = ["version", "description"] requires-python = ">=3.9" keywords = ["sun2000", "modbus", "photovoltaic"] dependencies = [ - "pymodbus>=3.7.4", + "pymodbus>=3.11.0", ] [project.urls] diff --git a/requirements.txt b/requirements.txt index b000634..9329510 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -pymodbus>=3.7.4 +pymodbus>=3.11.0 diff --git a/sun2000_modbus/inverter.py b/sun2000_modbus/inverter.py index 0f02323..5be1605 100644 --- a/sun2000_modbus/inverter.py +++ b/sun2000_modbus/inverter.py @@ -17,9 +17,9 @@ class Sun2000: - def __init__(self, host, port=502, timeout=5, wait=2, slave=0): # some models need slave=1 + def __init__(self, host, port=502, timeout=5, wait=2, device_id=0): # some models need device_id=1 self.wait = wait - self.slave = slave + self.device_id = device_id self.inverter = ModbusTcpClient(host=host, port=port, timeout=timeout) def connect(self): @@ -48,12 +48,12 @@ def isConnected(self): def connected(self): return self.isConnected() - def read_raw_value(self, register, slave=None): + def read_raw_value(self, register, device_id=None): if not self.isConnected(): raise ValueError('Inverter is not connected') try: - register_value = self.inverter.read_holding_registers(address=register.value.address, count=register.value.quantity, slave=self.slave if slave is None else slave) + register_value = self.inverter.read_holding_registers(address=register.value.address, count=register.value.quantity, device_id=self.device_id if device_id is None else device_id) if type(register_value) == ModbusIOException: logger.error('Inverter unit did not respond') raise register_value @@ -63,16 +63,16 @@ def read_raw_value(self, register, slave=None): return datatypes.decode(register_value.encode()[1:], register.value.data_type) - def read(self, register, slave=None): - raw_value = self.read_raw_value(register, slave) + def read(self, register, device_id=None): + raw_value = self.read_raw_value(register, device_id) if register.value.gain is None: return raw_value else: return raw_value / register.value.gain - def read_formatted(self, register, slave=None, use_locale=False): - value = self.read(register, slave) + def read_formatted(self, register, device_id=None, use_locale=False): + value = self.read(register, device_id) if register.value.unit is not None: if use_locale: @@ -84,7 +84,7 @@ def read_formatted(self, register, slave=None, use_locale=False): else: return value - def read_range(self, start_address, quantity=0, end_address=0, slave=None): + def read_range(self, start_address, quantity=0, end_address=0, device_id=None): if quantity == 0 and end_address == 0: raise ValueError('Either parameter quantity or end_address is required and must be greater than 0') if quantity != 0 and end_address != 0: @@ -98,7 +98,7 @@ def read_range(self, start_address, quantity=0, end_address=0, slave=None): if end_address != 0: quantity = end_address - start_address + 1 try: - register_range_value = self.inverter.read_holding_registers(address=start_address, count=quantity, slave=self.slave if slave is None else slave) + register_range_value = self.inverter.read_holding_registers(address=start_address, count=quantity, device_id=self.device_id if device_id is None else device_id) if type(register_range_value) == ModbusIOException: logger.error('Inverter unit did not respond') raise register_range_value @@ -108,7 +108,7 @@ def read_range(self, start_address, quantity=0, end_address=0, slave=None): return datatypes.decode(register_range_value.encode()[1:], datatypes.DataType.MULTIDATA) - def write(self, register, value, slave=None): + def write(self, register, value, device_id=None): if not self.isConnected(): raise ValueError('Inverter is not connected') if not register.value.access_type in [AccessType.RW, AccessType.WO]: @@ -118,7 +118,7 @@ def write(self, register, value, slave=None): chunks = [int.from_bytes(encoded_value[i:i+2], byteorder='big', signed=False) for i in range(0, len(encoded_value), 2)] try: - response = self.inverter.write_registers(address=register.value.address, values=chunks, slave=self.slave if slave is None else slave) + response = self.inverter.write_registers(address=register.value.address, values=chunks, device_id=self.device_id if device_id is None else device_id) if type(response) == ModbusIOException: logger.error('Inverter unit did not respond') raise response diff --git a/tests/sun2000mock.py b/tests/sun2000mock.py index 0c4de52..851a9be 100644 --- a/tests/sun2000mock.py +++ b/tests/sun2000mock.py @@ -34,23 +34,23 @@ def encode(self): } -def mock_read_holding_registers(self, address, count, slave): +def mock_read_holding_registers(self, address, count, device_id): return MockedResponse(address, count) -def mock_read_holding_registers_ModbusIOException(self, address, count, slave): - return ModbusIOException('Requested slave is not available') +def mock_read_holding_registers_ModbusIOException(self, address, count, device_id): + return ModbusIOException('Requested device is not available') -def mock_read_holding_registers_ConnectionException(self, address, count, slave): +def mock_read_holding_registers_ConnectionException(self, address, count, device_id): raise ConnectionException('Connection unexpectedly closed') -def mock_write_registers_ModbusIOException(self, address, values, slave): - return ModbusIOException('Requested slave is not available') +def mock_write_registers_ModbusIOException(self, address, values, device_id): + return ModbusIOException('Requested device is not available') -def mock_write_registers_ConnectionException(self, address, values, slave): +def mock_write_registers_ConnectionException(self, address, values, device_id): raise ConnectionException('Connection unexpectedly closed') diff --git a/tests/test_sun2000_modbus.py b/tests/test_sun2000_modbus.py index 58af96b..9dadda4 100644 --- a/tests/test_sun2000_modbus.py +++ b/tests/test_sun2000_modbus.py @@ -82,14 +82,14 @@ def test_decode_invalid(self): class TestSun2000(unittest.TestCase): def setUp(self) -> None: - self.test_inverter = Sun2000(host='192.168.8.1', port=123, timeout=3, wait=0, slave=1) + self.test_inverter = Sun2000(host='192.168.8.1', port=123, timeout=3, wait=0, device_id=1) def test_init(self): self.assertEqual(self.test_inverter.inverter.comm_params.host, '192.168.8.1') self.assertEqual(self.test_inverter.inverter.comm_params.port, 123) self.assertEqual(self.test_inverter.inverter.comm_params.timeout_connect, 3) self.assertEqual(self.test_inverter.wait, 0) - self.assertEqual(self.test_inverter.slave, 1) + self.assertEqual(self.test_inverter.device_id, 1) self.assertEqual(self.test_inverter.isConnected(), False) @patch( @@ -131,7 +131,7 @@ def test_read_raw_value_string_from_unavailable_unit(self): self.test_inverter.connect() with self.assertRaises(ModbusIOException) as cm: self.test_inverter.read_raw_value(InverterEquipmentRegister.Model) - self.assertEqual(str(cm.exception), 'Modbus Error: [Input/Output] Requested slave is not available') + self.assertEqual(str(cm.exception), 'Modbus Error: [Input/Output] Requested device is not available') @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers_ConnectionException @@ -157,10 +157,10 @@ def test_read_raw_value_string_connection_unexpectedly_closed(self): @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_read_raw_value_without_slave_argument_takes_default(self, mock_read_holding_registers): + def test_read_raw_value_without_device_id_argument_takes_default(self, mock_read_holding_registers): self.test_inverter.connect() self.test_inverter.read_raw_value(InverterEquipmentRegister.Model) - mock_read_holding_registers.assert_called_once_with(address=30000, count=15, slave=1) + mock_read_holding_registers.assert_called_once_with(address=30000, count=15, device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers' @@ -171,10 +171,10 @@ def test_read_raw_value_without_slave_argument_takes_default(self, mock_read_hol @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_read_raw_value_with_slave_argument(self, mock_read_holding_registers): + def test_read_raw_value_with_device_id_argument(self, mock_read_holding_registers): self.test_inverter.connect() - self.test_inverter.read_raw_value(InverterEquipmentRegister.Model, slave=123) - mock_read_holding_registers.assert_called_once_with(address=30000, count=15, slave=123) + self.test_inverter.read_raw_value(InverterEquipmentRegister.Model, device_id=123) + mock_read_holding_registers.assert_called_once_with(address=30000, count=15, device_id=123) @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers @@ -227,10 +227,10 @@ def test_read_raw_value_uint32be(self): @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_read_without_slave_argument_takes_default(self, mock_read_holding_registers): + def test_read_without_device_id_argument_takes_default(self, mock_read_holding_registers): self.test_inverter.connect() self.test_inverter.read(InverterEquipmentRegister.RatedPower) - mock_read_holding_registers.assert_called_once_with(address=30073, count=2, slave=1) + mock_read_holding_registers.assert_called_once_with(address=30073, count=2, device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers' @@ -241,10 +241,10 @@ def test_read_without_slave_argument_takes_default(self, mock_read_holding_regis @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_read_with_slave_argument(self, mock_read_holding_registers): + def test_read_with_device_id_argument(self, mock_read_holding_registers): self.test_inverter.connect() - self.test_inverter.read(InverterEquipmentRegister.RatedPower, slave=123) - mock_read_holding_registers.assert_called_once_with(address=30073, count=2, slave=123) + self.test_inverter.read(InverterEquipmentRegister.RatedPower, device_id=123) + mock_read_holding_registers.assert_called_once_with(address=30073, count=2, device_id=123) @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers @@ -269,10 +269,10 @@ def test_read_uint32be(self): @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_read_formatted_without_slave_argument_takes_default(self, mock_read_holding_registers): + def test_read_formatted_without_device_id_argument_takes_default(self, mock_read_holding_registers): self.test_inverter.connect() self.test_inverter.read_formatted(InverterEquipmentRegister.RatedPower) - mock_read_holding_registers.assert_called_once_with(address=30073, count=2, slave=1) + mock_read_holding_registers.assert_called_once_with(address=30073, count=2, device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers' @@ -283,10 +283,10 @@ def test_read_formatted_without_slave_argument_takes_default(self, mock_read_hol @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_read_formatted_with_slave_argument(self, mock_read_holding_registers): + def test_read_formatted_with_device_id_argument(self, mock_read_holding_registers): self.test_inverter.connect() - self.test_inverter.read_formatted(InverterEquipmentRegister.RatedPower, slave=123) - mock_read_holding_registers.assert_called_once_with(address=30073, count=2, slave=123) + self.test_inverter.read_formatted(InverterEquipmentRegister.RatedPower, device_id=123) + mock_read_holding_registers.assert_called_once_with(address=30073, count=2, device_id=123) @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers @@ -396,10 +396,10 @@ def test_read_returns_float(self): @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_read_range_without_slave_argument_takes_default(self, mock_read_holding_registers): + def test_read_range_without_device_id_argument_takes_default(self, mock_read_holding_registers): self.test_inverter.connect() self.test_inverter.read_range(30000, quantity=35) - mock_read_holding_registers.assert_called_once_with(address=30000, count=35, slave=1) + mock_read_holding_registers.assert_called_once_with(address=30000, count=35, device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers' @@ -410,10 +410,10 @@ def test_read_range_without_slave_argument_takes_default(self, mock_read_holding @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_read_range_with_slave_argument(self, mock_read_holding_registers): + def test_read_range_with_device_id_argument(self, mock_read_holding_registers): self.test_inverter.connect() - self.test_inverter.read_range(30000, quantity=35, slave=123) - mock_read_holding_registers.assert_called_once_with(address=30000, count=35, slave=123) + self.test_inverter.read_range(30000, quantity=35, device_id=123) + mock_read_holding_registers.assert_called_once_with(address=30000, count=35, device_id=123) @patch( 'pymodbus.client.ModbusTcpClient.read_holding_registers', sun2000mock.mock_read_holding_registers @@ -509,7 +509,7 @@ def test_read_range_from_unavailable_unit2(self): self.test_inverter.connect() with self.assertRaises(ModbusIOException) as cm: self.test_inverter.read_range(30000, quantity=35) - self.assertEqual(str(cm.exception), 'Modbus Error: [Input/Output] Requested slave is not available') + self.assertEqual(str(cm.exception), 'Modbus Error: [Input/Output] Requested device is not available') @patch( 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_fail @@ -533,7 +533,7 @@ def test_write_to_unavailable_unit(self): self.test_inverter.connect() with self.assertRaises(ModbusIOException) as cm: self.test_inverter.write(BatteryEquipmentRegister.BackupPowerSOC, 10) - self.assertEqual(str(cm.exception), 'Modbus Error: [Input/Output] Requested slave is not available') + self.assertEqual(str(cm.exception), 'Modbus Error: [Input/Output] Requested device is not available') @patch( 'pymodbus.client.ModbusTcpClient.connect', sun2000mock.connect_success @@ -571,10 +571,10 @@ def test_write_uint16be_connection_unexpectedly_closed(self): @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_write_without_slave_argument_takes_default(self, write_registers_mock): + def test_write_without_device_id_argument_takes_default(self, write_registers_mock): self.test_inverter.connect() self.test_inverter.write(BatteryEquipmentRegister.BackupPowerSOC, 10) - write_registers_mock.assert_called_once_with(address=47102, values=[10], slave=1) + write_registers_mock.assert_called_once_with(address=47102, values=[10], device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.write_registers' @@ -585,10 +585,10 @@ def test_write_without_slave_argument_takes_default(self, write_registers_mock): @patch( 'pymodbus.client.ModbusTcpClient.is_socket_open', sun2000mock.connect_success ) - def test_write_with_slave_argument(self, write_registers_mock): + def test_write_with_device_id_argument(self, write_registers_mock): self.test_inverter.connect() - self.test_inverter.write(BatteryEquipmentRegister.BackupPowerSOC, 10, slave=123) - write_registers_mock.assert_called_once_with(address=47102, values=[10], slave=123) + self.test_inverter.write(BatteryEquipmentRegister.BackupPowerSOC, 10, device_id=123) + write_registers_mock.assert_called_once_with(address=47102, values=[10], device_id=123) @patch( 'pymodbus.client.ModbusTcpClient.write_registers' @@ -602,7 +602,7 @@ def test_write_with_slave_argument(self, write_registers_mock): def test_write_uint16be(self, write_registers_mock): self.test_inverter.connect() self.test_inverter.write(BatteryEquipmentRegister.BackupPowerSOC, 10) - write_registers_mock.assert_called_once_with(address=47102, values=[10], slave=1) + write_registers_mock.assert_called_once_with(address=47102, values=[10], device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.write_registers' @@ -616,7 +616,7 @@ def test_write_uint16be(self, write_registers_mock): def test_write_uint32be(self, write_registers_mock): self.test_inverter.connect() self.test_inverter.write(InverterEquipmentRegister.FixedActivePowerDeratedInW, 10200) - write_registers_mock.assert_called_once_with(address=40126, values=[0, 10200], slave=1) + write_registers_mock.assert_called_once_with(address=40126, values=[0, 10200], device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.write_registers' @@ -630,7 +630,7 @@ def test_write_uint32be(self, write_registers_mock): def test_write_int16be(self, write_registers_mock): self.test_inverter.connect() self.test_inverter.write(BatteryEquipmentRegister.MaximumFeedGridPowerInPercentage, -90) - write_registers_mock.assert_called_once_with(address=47418, values=[65446], slave=1) + write_registers_mock.assert_called_once_with(address=47418, values=[65446], device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.write_registers' @@ -644,7 +644,7 @@ def test_write_int16be(self, write_registers_mock): def test_write_int32be(self, write_registers_mock): self.test_inverter.connect() self.test_inverter.write(BatteryEquipmentRegister.MaximumChargeFromGridPower, -10200) - write_registers_mock.assert_called_once_with(address=47590, values=[65535, 55336], slave=1) + write_registers_mock.assert_called_once_with(address=47590, values=[65535, 55336], device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.write_registers' @@ -658,7 +658,7 @@ def test_write_int32be(self, write_registers_mock): def test_write_multidata(self, write_registers_mock): self.test_inverter.connect() self.test_inverter.write(InverterEquipmentRegister.CosPhiPPnCharacteristicCurve, b'\x01\x02\x03\x04') - write_registers_mock.assert_called_once_with(address=40133, values=[258, 772], slave=1) + write_registers_mock.assert_called_once_with(address=40133, values=[258, 772], device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.write_registers' From c4cb88788acea6fa0d9970e980a8d85cd2662e54 Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Wed, 5 Nov 2025 23:03:03 +0100 Subject: [PATCH 02/15] #48: Fixed table format in README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e112ae6..747c468 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ During instantiation of a Sun2000 object the following parameters are accepted: | port | Port, usually 502, changed to 6607 on newer firmware versions. | | timeout | Connection timeout | | wait | Time to wait after connection before a register read can be performed. Increases stability. | -| device_id | Number of inverter unit to be read by default, used in cascading scenarios. Defaults to 0, but some devices need it to be set to other values. | +| device_id | Number of inverter unit to be read by default, used in cascading scenarios. Defaults to 0, but some devices need it to be set to other values. | ### Read metrics From 9bf035e51b47f9152fc8537e074c864d21c0310b Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Mon, 1 Dec 2025 17:46:12 +0100 Subject: [PATCH 03/15] #48: Added register-parser and updated registers for inverter --- register-parser/.gitignore | 2 + register-parser/register-parser.ipynb | 224 ++++++++++++++++++++++++++ register-parser/requirements.txt | 3 + sun2000_modbus/registers.py | 85 +++++++--- 4 files changed, 291 insertions(+), 23 deletions(-) create mode 100644 register-parser/.gitignore create mode 100644 register-parser/register-parser.ipynb create mode 100644 register-parser/requirements.txt diff --git a/register-parser/.gitignore b/register-parser/.gitignore new file mode 100644 index 0000000..1aafc3b --- /dev/null +++ b/register-parser/.gitignore @@ -0,0 +1,2 @@ +input +output diff --git a/register-parser/register-parser.ipynb b/register-parser/register-parser.ipynb new file mode 100644 index 0000000..f2d0eea --- /dev/null +++ b/register-parser/register-parser.ipynb @@ -0,0 +1,224 @@ +{ + "cells": [ + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "This Jupyter notebook handles the generation of all register enum values based on a \"Modbus Interface Definitions\"-PDF file provided by Huawei and the existing register enum class content for remapping already existing values.\n", + "\n", + "Input parameters:\n", + "\n", + "- `pdf_path`: Path to the \"Modbus Interface Definitions\"-PDF file\n", + "- `register_pages`: Page range where the tables of one register (e.g. \"Inverter Equipment Register\") are located in the PDF\n", + "- `old_registers_file_path`: Path of the text file containing the current register enum values\n", + "- `output_file_path`: Path of the resulting text-file containing the generated register enum values\n", + "\n", + "The process flow consists of the following steps:\n", + "\n", + "1. Parse the \"Modbus Interface Definitions\"-PDF into a pandas DataFrame\n", + " 1. The pages set in `register_pages` of the \"Modbus Interface Definitions\"-PDF file set in `pdf_path` are read by tabula to extract the contained table contents\n", + " 2. All found tables are merged into one, unnecessary columns are dropped, all columns are named accordingly and empty rows are dropped\n", + " 3. Carriage-returns are removed from the _name_-column\n", + " 4. The values in the columns _type_, _unit_ and _gain_ are mapped\n", + " 5. The table is sorted along the _address_ values\n", + " 6. The table is displayed for examination\n", + "2. Parse the current register enum values into a pandas DataFrame\n", + " 1. The file set in `old_registers_file_path` is opened for reading\n", + " 2. Each line is manipulated to represent a CSV-line\n", + " 3. All CSV-lines are read into a pandas DataFrame, all columns are named accordingly\n", + " 4. The table is displayed for examination\n", + "3. DataFrames are joined\n", + " 1. The both DataFrames from step 1 and 2 are left-joined on the _address_-columns\n", + " 2. The table is displayed for examination\n", + "4. Generated register enum values are exported to output file\n", + " 1. Each row in the joined column is converted to the target format for a register enum value\n", + " 2. All rows are exported to the file set in `output_file_path`\n", + "\n", + "The content of the output file can be copied over to the respective register enum class in `registers.py` (e.g. `InverterEquipmentRegister`). Some manual post-processing might be required." + ], + "id": "b7bfe393773b194c" + }, + { + "cell_type": "code", + "id": "initial_id", + "metadata": { + "collapsed": true + }, + "source": [ + "import pandas as pd\n", + "import tabula\n", + "from IPython.display import display\n", + "\n", + "pdf_path = '../docs/modbus.pdf'\n", + "register_pages = '15-33'\n", + "old_registers_file_path = 'input/registers.txt'\n", + "output_file_path = 'output/inverter.txt'\n", + "\n", + "#------------------------------------------------\n", + "datatype_mapping = {\n", + " 'STR': 'STRING',\n", + " 'String': 'STRING',\n", + " 'U16': 'UINT16_BE',\n", + " 'UINT16': 'UINT16_BE',\n", + " 'ENUM16': 'UINT16_BE',\n", + " 'U32': 'UINT32_BE',\n", + " 'UINT32': 'UINT32_BE',\n", + " 'EPOCHTIME': 'UINT32_BE',\n", + " 'I16': 'INT16_BE',\n", + " 'I32': 'INT32_BE',\n", + " 'INT32': 'INT32_BE',\n", + " 'Bitfield16': 'BITFIELD16',\n", + " 'Bitfield32': 'BITFIELD32',\n", + " 'DBitfield32': 'BITFIELD32',\n", + " 'MLD/Bytes': 'MULTIDATA',\n", + " 'BYTES': 'MULTIDATA',\n", + "}\n", + "\n", + "\n", + "unit_mapping = {\n", + " 'N/A': \"None\",\n", + " 'NA': \"None\",\n", + " 'kVar': \"'kvar'\",\n", + " 'MΩ': \"'MOhm'\",\n", + " 'kW': \"'W'\",\n", + "}\n", + "\n", + "\n", + "gain_mapping = {\n", + " 'N/A': \"None\",\n", + " 'NA': \"None\",\n", + "}\n", + "\n", + "\n", + "def process_name_column(merged_table: pd.DataFrame) -> pd.DataFrame:\n", + " merged_table['name'] = merged_table['name'].str.replace('\\r', ' ')\n", + "\n", + " return merged_table\n", + "\n", + "\n", + "def process_type_column(merged_table: pd.DataFrame) -> pd.DataFrame:\n", + " merged_table['type'] = merged_table['type'].str.replace('\\r', '')\n", + " merged_table['parsed_type'] = merged_table['type'].map(datatype_mapping)\n", + "\n", + " return merged_table\n", + "\n", + "\n", + "def process_unit_column(merged_table: pd.DataFrame) -> pd.DataFrame:\n", + " merged_table['parsed_unit'] = merged_table['unit'].apply(lambda x: unit_mapping.get(x, f'\\'{x}\\''))\n", + "\n", + " return merged_table\n", + "\n", + "\n", + "def process_gain_column(merged_table: pd.DataFrame) -> pd.DataFrame:\n", + " merged_table['parsed_gain'] = merged_table.apply(lambda x: 1 if x['unit'] == 'kW' else gain_mapping.get(x['gain'], x['gain']), axis=1)\n", + "\n", + " return merged_table\n", + "\n", + "\n", + "# Display all rows for examination of data\n", + "pd.set_option('display.max_rows', None)\n", + "\n", + "tables = tabula.read_pdf(pdf_path, pages=register_pages, multiple_tables=True, lattice=True, pandas_options={'dtype': str})\n", + "if not tables:\n", + " print('No tables found')\n", + " exit(1)\n", + "else:\n", + " merged_table = pd.concat(objs=tables, ignore_index=True)\n", + " merged_table.drop(columns={merged_table.columns[8], merged_table.columns[9]}, inplace=True)\n", + " column_mapping = {\n", + " merged_table.columns[0]: 'index',\n", + " merged_table.columns[1]: 'name',\n", + " merged_table.columns[2]: 'mode',\n", + " merged_table.columns[3]: 'type',\n", + " merged_table.columns[4]: 'unit',\n", + " merged_table.columns[5]: 'gain',\n", + " merged_table.columns[6]: 'address',\n", + " merged_table.columns[7]: 'quantity'\n", + " }\n", + " merged_table.rename(columns=column_mapping, inplace=True)\n", + " merged_table.dropna(inplace=True)\n", + "\n", + " merged_table = process_name_column(merged_table)\n", + " merged_table = process_type_column(merged_table)\n", + " merged_table = process_unit_column(merged_table)\n", + " merged_table = process_gain_column(merged_table)\n", + "\n", + " merged_table.sort_values(by=['address'], inplace=True)\n", + "\n", + " # Examination step\n", + " display(merged_table)" + ], + "outputs": [], + "execution_count": null + }, + { + "metadata": {}, + "cell_type": "code", + "source": [ + "from io import StringIO\n", + "import pandas\n", + "\n", + "csv_lines = ''\n", + "with open(old_registers_file_path, 'r') as input_file:\n", + " for line in input_file:\n", + " csv_line = line.replace('=', ',').replace('(', ',').replace(')', '').replace(' ', '')\n", + " csv_lines += csv_line\n", + "\n", + "old_registers = pandas.read_csv(StringIO(csv_lines), header=None, names=['name', 'register', 'address', 'quantity', 'type', 'gain', 'unit', 'mode', 'mapping'], dtype=str)\n", + "\n", + "# Examination step\n", + "display(old_registers)" + ], + "id": "73ef97640afc6320", + "outputs": [], + "execution_count": null + }, + { + "metadata": {}, + "cell_type": "code", + "source": [ + "joined = merged_table.merge(old_registers, how='left', left_on='address', right_on='address')\n", + "\n", + "# Examination step\n", + "display(joined)" + ], + "id": "ddfd2e9dbcf9ad9e", + "outputs": [], + "execution_count": null + }, + { + "metadata": {}, + "cell_type": "code", + "source": [ + "import csv\n", + "\n", + "joined['new_register'] = joined.apply(lambda x: f'{'\"' + x['name_x'] + '\"' if pandas.isnull(x['name_y']) else x['name_y']} = Register({x['address']}, {x['quantity_x']}, datatypes.DataType.{x['parsed_type']}, {x['parsed_gain']}, {x['parsed_unit']}, AccessType.{x['mode_x']}, None)', axis=1)\n", + "joined.to_csv(output_file_path, columns=['new_register'], index=False, header=False, quoting=csv.QUOTE_NONE, sep=';')" + ], + "id": "136feb9761e37ec1", + "outputs": [], + "execution_count": null + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 2 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython2", + "version": "2.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/register-parser/requirements.txt b/register-parser/requirements.txt new file mode 100644 index 0000000..d6a152f --- /dev/null +++ b/register-parser/requirements.txt @@ -0,0 +1,3 @@ +tabula-py +pandas +ipython \ No newline at end of file diff --git a/sun2000_modbus/registers.py b/sun2000_modbus/registers.py index 17e54cb..9f514d9 100644 --- a/sun2000_modbus/registers.py +++ b/sun2000_modbus/registers.py @@ -33,10 +33,7 @@ class InverterEquipmentRegister(Enum): Model = Register(30000, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) SN = Register(30015, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) PN = Register(30025, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) - FirmwareVersion = Register(30035, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - SoftwareVersion = Register(30050, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - ProtocolVersion = Register(30068, 2, datatypes.DataType.UINT32_BE, None, None, AccessType.RO, None) - ModelID = Register(30070, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + ModelID = Register(30070, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) NumberOfPVStrings = Register(30071, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) NumberOfMPPTrackers = Register(30072, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) RatedPower = Register(30073, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RO, None) @@ -44,12 +41,24 @@ class InverterEquipmentRegister(Enum): MaximumApparentPower = Register(30077, 2, datatypes.DataType.UINT32_BE, 1000, 'kVA', AccessType.RO, None) MaximumReactivePowerFedToTheGrid = Register(30079, 2, datatypes.DataType.INT32_BE, 1000, 'kvar', AccessType.RO, None) MaximumReactivePowerAbsorbedFromTheGrid = Register(30081, 2, datatypes.DataType.INT32_BE, 1000, 'kvar', AccessType.RO, None) + OfferingNameOfSouthboundDevice1 = Register(30561, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) + OfferingNameOfSouthboundDevice2 = Register(30576, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) + OfferingNameOfSouthboundDevice3 = Register(30591, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) + HardwareVersion = Register(31000, 15, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + MonitoringBoardSN = Register(31015, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) + MonitoringSoftwareVersion = Register(31025, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) + MasterDSPVersion = Register(31040, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) + CPLDVersion = Register(31070, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) + AFCIVersion = Register(31085, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) + DCMBUSVersion = Register(31115, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) + REGKEY = Register(31200, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) State1 = Register(32000, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) State2 = Register(32002, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) State3 = Register(32003, 2, datatypes.DataType.BITFIELD32, None, None, AccessType.RO, None) Alarm1 = Register(32008, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) Alarm2 = Register(32009, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) Alarm3 = Register(32010, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) + ESN = Register(32015, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) PV1Voltage = Register(32016, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) PV1Current = Register(32017, 1, datatypes.DataType.INT16_BE, 100, 'A', AccessType.RO, None) PV2Voltage = Register(32018, 1, datatypes.DataType.INT16_BE, 10, 'V', AccessType.RO, None) @@ -116,43 +125,73 @@ class InverterEquipmentRegister(Enum): Efficiency = Register(32086, 1, datatypes.DataType.UINT16_BE, 100, '%', AccessType.RO, None) InternalTemperature = Register(32087, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) InsulationResistance = Register(32088, 1, datatypes.DataType.UINT16_BE, 1000, 'MOhm', AccessType.RO, None) - DeviceStatus = Register(32089, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.DeviceStatus) - FaultCode = Register(32090, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - StartupTime = Register(32091, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) - ShutdownTime = Register(32093, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) + DeviceStatus = Register(32089, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, mappings.DeviceStatus) + FaultCode = Register(32090, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) + StartupTime = Register(32091, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) + ShutdownTime = Register(32093, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) AccumulatedEnergyYield = Register(32106, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) DailyEnergyYield = Register(32114, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) - ActiveAdjustmentMode = Register(35300, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - ActiveAdjustmentValue = Register(35302, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) - ActiveAdjustmentCommand = Register(35303, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - ReactiveAdjustmentMode = Register(35304, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - ReactiveAdjustmentValue = Register(35305, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) - ReactiveAdjustmentCommand = Register(35307, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + ManagementSystemStatus = Register(35127, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + AuthorizationFunction = Register(35136, 2, datatypes.DataType.BITFIELD32, 1, None, AccessType.RO, None) + LicenseStatus = Register(35138, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) + LicenseExpirationTime = Register(35139, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) + LicenseLoadingTime = Register(35141, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) + LicenseRevocationTime = Register(35143, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) + LicenseSN = Register(35145, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + RevocationCode = Register(35155, 64, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + ModuleStatus4G = Register(35249, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) + IPAddress4G = Register(35250, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) + SubnetMask4G = Register(35252, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) + IMEI4G = Register(35254, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + SignalStrength4G = Register(35264, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + MaximumNumberOfPINAttempts4G = Register(35265, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + PINVerificationStatus4G = Register(35266, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) + OriginalModelName = Register(35268, 15, datatypes.DataType.MULTIDATA, 1, None, AccessType.RO, None) + ActiveAdjustmentMode = Register(35300, 4, datatypes.DataType.MULTIDATA, None, None, AccessType.RO, None) + ReactiveAdjustmentMode = Register(35304, 4, datatypes.DataType.MULTIDATA, None, None, AccessType.RO, None) + ChargeDischargeMode = Register(37006, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) PowerMeterCollectionActivePower = Register(37113, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) TotalNumberOfOptimizers = Register(37200, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) NumberOfOnlineOptimizers = Register(37201, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) FeatureData = Register(37202, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - SystemTime = Register(40000, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RW, None) - QUCharacteristicCurveMode = Register(40037, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) - QUDispatchTriggerPower = Register(40038, 1, datatypes.DataType.UINT16_BE, 1, '%', AccessType.RW, None) - FixedActivePowerDeratedInKW = Register(40120, 1, datatypes.DataType.UINT16_BE, 10, 'kW', AccessType.RW, None) + SystemTime = Register(40000, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RW, None) + QUCharacteristicCurveMode = Register(40037, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + QUDispatchTriggerPower = Register(40038, 1, datatypes.DataType.INT16_BE, 1, '%', AccessType.RW, None) + FixedActivePowerDeratedInKW = Register(40120, 1, datatypes.DataType.UINT16_BE, 1, 'W', AccessType.RW, None) ReactivePowerCompensationInPF = Register(40122, 1, datatypes.DataType.INT16_BE, 1000, None, AccessType.RW, None) ReactivePowerCompensationQS = Register(40123, 1, datatypes.DataType.INT16_BE, 1000, None, AccessType.RW, None) - ActivePowerPercentageDerating = Register(40125, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) + ActivePowerPercentageDerating = Register(40125, 1, datatypes.DataType.INT16_BE, 10, '%', AccessType.RW, None) FixedActivePowerDeratedInW = Register(40126, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) ReactivePowerCompensationAtNight = Register(40129, 2, datatypes.DataType.INT32_BE, 1000, 'kvar', AccessType.RW, None) CosPhiPPnCharacteristicCurve = Register(40133, 21, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) QUCharacteristicCurve = Register(40154, 21, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) PFUCharacteristicCurve = Register(40175, 21, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) ReactivePowerAdjustmentTime = Register(40196, 1, datatypes.DataType.UINT16_BE, 1, 's', AccessType.RW, None) - QUPowerPercentageToExitScheduling = Register(40198, 1, datatypes.DataType.UINT16_BE, 1, '%', AccessType.RW, None) - Startup = Register(40200, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.WO, None) - Shutdown = Register(40201, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.WO, None) - GridCode = Register(42000, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) + QUPowerPercentageToExitScheduling = Register(40198, 1, datatypes.DataType.INT16_BE, 1, '%', AccessType.RW, None) + Startup = Register(40200, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.WO, None) + Shutdown = Register(40201, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.WO, None) + GridCode = Register(42000, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) ReactivePowerChangeGradient = Register(42015, 2, datatypes.DataType.UINT32_BE, 1000, '%/s', AccessType.RW, None) ActivePowerChangeGradient = Register(42017, 2, datatypes.DataType.UINT32_BE, 1000, '%/s', AccessType.RW, None) ScheduleInstructionValidDuration = Register(42019, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RW, None) + AcivePowerLimit = Register(42405, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) TimeZone = Register(43006, 1, datatypes.DataType.INT16_BE, 1, 'min', AccessType.RW, None) + TLSEncryption = Register(43098, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + WLANWakeup = Register(45052, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + FastPowerScheduling = Register(45086, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + BatteryChargingMode = Register(47086, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + BatteryChargeAndDischargePower = Register(47321, 1, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) + RemoteChargeDischargeControlMode = Register(47589, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + RemoteControlForChargingAndDischarging = Register(47589, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + ScheduledTask = Register(47674, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + DefaultMaximumFeedInPower = Register(47675, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) + DefaultActivePowerChangeGradient = Register(47677, 2, datatypes.DataType.UINT32_BE, 1000, '%/s', AccessType.RW, None) + PeakShaving = Register(47954, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + BackupPowerSOCForPeakShaving = Register(47955, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) + PeakPower = Register(47956, 64, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) + AIOpticalStorage = Register(48020, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + BackupBoxModel = Register(48089, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + PhaseToGroundCircuitProtection = Register(48090, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) class BatteryEquipmentRegister(Enum): From f548fc004701793590bae11fdb80a2e4cf0d9b64 Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Mon, 1 Dec 2025 22:34:06 +0100 Subject: [PATCH 04/15] #48: Updated registers for battery and power meter --- README.md | 10 +- register-parser/register-parser.ipynb | 1481 ++++++++++++++++++++++++- sun2000_modbus/registers.py | 138 +-- 3 files changed, 1545 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 747c468..366f999 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,8 @@ Furthermore, the `write` method accepts a `device_id` argument which is used in ## Registers -The following registers are provided by the Sun2000's Modbus interface and can be read accordingly. Documentation can be found -[here](https://javierin.com/wp-content/uploads/sites/2/2021/09/Solar-Inverter-Modbus-Interface-Definitions.pdf). +The following registers are provided by the Sun2000's Modbus interface and can be read and written accordingly. Documentation can be found +[here](https://support.huawei.com/enterprise/en/doc/EDOC1100480291?idPath=258788305|254827209|258792409|22755755§ion=k003). ### InverterEquipmentRegister @@ -91,9 +91,9 @@ The following registers are provided by the Sun2000's Modbus interface and can b | Model | String | | | RO | | SN | String | | | RO | | PN | String | | | RO | -| FirmwareVerion | String | | | RO | -| SoftwareVerion | String | | | RO | -| ProtocolVerion | String | | | RO | +| FirmwareVersion | String | | | RO | +| SoftwareVersion | String | | | RO | +| ProtocolVersion | String | | | RO | | ModelID | Number | 1 | | RO | | NumberOfPVStrings | Number | 1 | | RO | | NumberOfMPPTrackers | Number | 1 | | RO | diff --git a/register-parser/register-parser.ipynb b/register-parser/register-parser.ipynb index f2d0eea..0114a0d 100644 --- a/register-parser/register-parser.ipynb +++ b/register-parser/register-parser.ipynb @@ -42,7 +42,11 @@ "cell_type": "code", "id": "initial_id", "metadata": { - "collapsed": true + "collapsed": true, + "ExecuteTime": { + "end_time": "2025-12-01T21:30:04.823571Z", + "start_time": "2025-12-01T21:30:04.300437Z" + } }, "source": [ "import pandas as pd\n", @@ -50,14 +54,15 @@ "from IPython.display import display\n", "\n", "pdf_path = '../docs/modbus.pdf'\n", - "register_pages = '15-33'\n", + "register_pages = '78-80'\n", "old_registers_file_path = 'input/registers.txt'\n", - "output_file_path = 'output/inverter.txt'\n", + "output_file_path = 'output/powermeter.txt'\n", "\n", "#------------------------------------------------\n", "datatype_mapping = {\n", " 'STR': 'STRING',\n", " 'String': 'STRING',\n", + " 'STRING': 'STRING',\n", " 'U16': 'UINT16_BE',\n", " 'UINT16': 'UINT16_BE',\n", " 'ENUM16': 'UINT16_BE',\n", @@ -65,22 +70,28 @@ " 'UINT32': 'UINT32_BE',\n", " 'EPOCHTIME': 'UINT32_BE',\n", " 'I16': 'INT16_BE',\n", + " 'INT16': 'INT16_BE',\n", " 'I32': 'INT32_BE',\n", " 'INT32': 'INT32_BE',\n", " 'Bitfield16': 'BITFIELD16',\n", " 'Bitfield32': 'BITFIELD32',\n", " 'DBitfield32': 'BITFIELD32',\n", " 'MLD/Bytes': 'MULTIDATA',\n", + " 'MULTIDATA': 'MULTIDATA',\n", " 'BYTES': 'MULTIDATA',\n", + " 'Bytes': 'MULTIDATA',\n", "}\n", "\n", "\n", "unit_mapping = {\n", " 'N/A': \"None\",\n", " 'NA': \"None\",\n", + " 'Var': \"'var'\",\n", " 'kVar': \"'kvar'\",\n", + " 'kVarh': \"'kvarh'\",\n", " 'MΩ': \"'MOhm'\",\n", " 'kW': \"'W'\",\n", + " 'Kw': \"'W'\",\n", "}\n", "\n", "\n", @@ -104,13 +115,14 @@ "\n", "\n", "def process_unit_column(merged_table: pd.DataFrame) -> pd.DataFrame:\n", + " merged_table['unit'] = merged_table['unit'].str.replace('\\r', '')\n", " merged_table['parsed_unit'] = merged_table['unit'].apply(lambda x: unit_mapping.get(x, f'\\'{x}\\''))\n", "\n", " return merged_table\n", "\n", "\n", "def process_gain_column(merged_table: pd.DataFrame) -> pd.DataFrame:\n", - " merged_table['parsed_gain'] = merged_table.apply(lambda x: 1 if x['unit'] == 'kW' else gain_mapping.get(x['gain'], x['gain']), axis=1)\n", + " merged_table['parsed_gain'] = merged_table.apply(lambda x: 1 if x['unit'] in ['kW', 'Kw'] else gain_mapping.get(x['gain'], x['gain']), axis=1)\n", "\n", " return merged_table\n", "\n", @@ -124,7 +136,7 @@ " exit(1)\n", "else:\n", " merged_table = pd.concat(objs=tables, ignore_index=True)\n", - " merged_table.drop(columns={merged_table.columns[8], merged_table.columns[9]}, inplace=True)\n", + " merged_table.drop(columns={merged_table.columns[0], merged_table.columns[9]}, inplace=True)\n", " column_mapping = {\n", " merged_table.columns[0]: 'index',\n", " merged_table.columns[1]: 'name',\n", @@ -148,11 +160,420 @@ " # Examination step\n", " display(merged_table)" ], - "outputs": [], - "execution_count": null + "outputs": [ + { + "data": { + "text/plain": [ + " index name mode type unit gain address \\\n", + "0 1 Meter status RO UINT16 N/A 1 37100 \n", + "1 2 Grid voltage (A phase) RO INT32 V 10 37101 \n", + "2 3 B phase voltage RO INT32 V 10 37103 \n", + "3 4 C phase voltage RO INT32 V 10 37105 \n", + "4 5 Grid current(A phase) RO INT32 A 100 37107 \n", + "5 6 B phase current RO INT32 A 100 37109 \n", + "6 7 C phase current RO INT32 A 100 37111 \n", + "7 8 Active power RO INT32 W 1 37113 \n", + "8 9 Reactive power RO INT32 Var 1 37115 \n", + "9 10 Power factor RO INT16 NA 1000 37117 \n", + "10 11 Grid frequency RO INT16 Hz 100 37118 \n", + "11 12 Positive active electricity RO INT32 kWh 100 37119 \n", + "12 13 Reverse active power RO INT32 kWh 100 37121 \n", + "13 14 Accumulat ed reactive power RO INT32 kVarh 100 37123 \n", + "14 15 Meter type RO UINT16 N/A 1 37125 \n", + "15 16 A-B line voltage RO INT32 V 10 37126 \n", + "16 17 B-C line voltage RO INT32 V 10 37128 \n", + "17 18 CA line voltage RO INT32 V 10 37130 \n", + "18 19 A phase active power RO INT32 W 1 37132 \n", + "19 20 B phase active power RO INT32 W 1 37134 \n", + "20 21 C phase active power RO INT32 W 1 37136 \n", + "21 22 Meter model detection result RO UINT16 N/A 1 37138 \n", + "\n", + " quantity parsed_type parsed_unit parsed_gain \n", + "0 1 UINT16_BE None 1 \n", + "1 2 INT32_BE 'V' 10 \n", + "2 2 INT32_BE 'V' 10 \n", + "3 2 INT32_BE 'V' 10 \n", + "4 2 INT32_BE 'A' 100 \n", + "5 2 INT32_BE 'A' 100 \n", + "6 2 INT32_BE 'A' 100 \n", + "7 2 INT32_BE 'W' 1 \n", + "8 2 INT32_BE 'var' 1 \n", + "9 1 INT16_BE None 1000 \n", + "10 1 INT16_BE 'Hz' 100 \n", + "11 2 INT32_BE 'kWh' 100 \n", + "12 2 INT32_BE 'kWh' 100 \n", + "13 2 INT32_BE 'kvarh' 100 \n", + "14 1 UINT16_BE None 1 \n", + "15 2 INT32_BE 'V' 10 \n", + "16 2 INT32_BE 'V' 10 \n", + "17 2 INT32_BE 'V' 10 \n", + "18 2 INT32_BE 'W' 1 \n", + "19 2 INT32_BE 'W' 1 \n", + "20 2 INT32_BE 'W' 1 \n", + "21 1 UINT16_BE None 1 " + ], + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
indexnamemodetypeunitgainaddressquantityparsed_typeparsed_unitparsed_gain
01Meter statusROUINT16N/A1371001UINT16_BENone1
12Grid voltage (A phase)ROINT32V10371012INT32_BE'V'10
23B phase voltageROINT32V10371032INT32_BE'V'10
34C phase voltageROINT32V10371052INT32_BE'V'10
45Grid current(A phase)ROINT32A100371072INT32_BE'A'100
56B phase currentROINT32A100371092INT32_BE'A'100
67C phase currentROINT32A100371112INT32_BE'A'100
78Active powerROINT32W1371132INT32_BE'W'1
89Reactive powerROINT32Var1371152INT32_BE'var'1
910Power factorROINT16NA1000371171INT16_BENone1000
1011Grid frequencyROINT16Hz100371181INT16_BE'Hz'100
1112Positive active electricityROINT32kWh100371192INT32_BE'kWh'100
1213Reverse active powerROINT32kWh100371212INT32_BE'kWh'100
1314Accumulat ed reactive powerROINT32kVarh100371232INT32_BE'kvarh'100
1415Meter typeROUINT16N/A1371251UINT16_BENone1
1516A-B line voltageROINT32V10371262INT32_BE'V'10
1617B-C line voltageROINT32V10371282INT32_BE'V'10
1718CA line voltageROINT32V10371302INT32_BE'V'10
1819A phase active powerROINT32W1371322INT32_BE'W'1
1920B phase active powerROINT32W1371342INT32_BE'W'1
2021C phase active powerROINT32W1371362INT32_BE'W'1
2122Meter model detection resultROUINT16N/A1371381UINT16_BENone1
\n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 21 }, { - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-01T21:30:17.152949Z", + "start_time": "2025-12-01T21:30:17.145273Z" + } + }, "cell_type": "code", "source": [ "from io import StringIO\n", @@ -170,11 +591,398 @@ "display(old_registers)" ], "id": "73ef97640afc6320", - "outputs": [], - "execution_count": null + "outputs": [ + { + "data": { + "text/plain": [ + " name register address quantity \\\n", + "0 MeterType Register 37125 1 \n", + "1 MeterStatus Register 37100 1 \n", + "2 MeterModelDetectionResult Register 37138 1 \n", + "3 APhaseVoltage Register 37101 2 \n", + "4 BPhaseVoltage Register 37103 2 \n", + "5 CPhaseVoltage Register 37105 2 \n", + "6 APhaseCurrent Register 37107 2 \n", + "7 BPhaseCurrent Register 37109 2 \n", + "8 CPhaseCurrent Register 37111 2 \n", + "9 ActivePower Register 37113 2 \n", + "10 ReactivePower Register 37115 2 \n", + "11 PowerFactor Register 37117 1 \n", + "12 GridFrequency Register 37118 1 \n", + "13 PositiveActiveElectricity Register 37119 2 \n", + "14 ReverseActivePower Register 37121 2 \n", + "15 AccumulatedReactivePower Register 37123 2 \n", + "16 ABLineVoltage Register 37126 2 \n", + "17 BCLineVoltage Register 37128 2 \n", + "18 CALineVoltage Register 37130 2 \n", + "19 APhaseActivePower Register 37132 2 \n", + "20 BPhaseActivePower Register 37134 2 \n", + "21 CPhaseActivePower Register 37136 2 \n", + "\n", + " type gain unit mode \\\n", + "0 datatypes.DataType.UINT16_BE 1 NaN AccessType.RO \n", + "1 datatypes.DataType.UINT16_BE 1 NaN AccessType.RO \n", + "2 datatypes.DataType.UINT16_BE 1 NaN AccessType.RO \n", + "3 datatypes.DataType.INT32_BE 10 'V' AccessType.RO \n", + "4 datatypes.DataType.INT32_BE 10 'V' AccessType.RO \n", + "5 datatypes.DataType.INT32_BE 10 'V' AccessType.RO \n", + "6 datatypes.DataType.INT32_BE 100 'A' AccessType.RO \n", + "7 datatypes.DataType.INT32_BE 100 'A' AccessType.RO \n", + "8 datatypes.DataType.INT32_BE 100 'A' AccessType.RO \n", + "9 datatypes.DataType.INT32_BE 1 'W' AccessType.RO \n", + "10 datatypes.DataType.INT32_BE 1 'var' AccessType.RO \n", + "11 datatypes.DataType.INT16_BE 1000 NaN AccessType.RO \n", + "12 datatypes.DataType.INT16_BE 100 'Hz' AccessType.RO \n", + "13 datatypes.DataType.INT32_BE 100 'kWh' AccessType.RO \n", + "14 datatypes.DataType.INT32_BE 100 'kWh' AccessType.RO \n", + "15 datatypes.DataType.INT32_BE 100 'kvar' AccessType.RO \n", + "16 datatypes.DataType.INT32_BE 10 'V' AccessType.RO \n", + "17 datatypes.DataType.INT32_BE 10 'V' AccessType.RO \n", + "18 datatypes.DataType.INT32_BE 10 'V' AccessType.RO \n", + "19 datatypes.DataType.INT32_BE 1 'W' AccessType.RO \n", + "20 datatypes.DataType.INT32_BE 1 'W' AccessType.RO \n", + "21 datatypes.DataType.INT32_BE 1 'W' AccessType.RO \n", + "\n", + " mapping \n", + "0 mappings.MeterType \n", + "1 mappings.MeterStatus \n", + "2 mappings.MeterModelDetectionResult \n", + "3 NaN \n", + "4 NaN \n", + "5 NaN \n", + "6 NaN \n", + "7 NaN \n", + "8 NaN \n", + "9 NaN \n", + "10 NaN \n", + "11 NaN \n", + "12 NaN \n", + "13 NaN \n", + "14 NaN \n", + "15 NaN \n", + "16 NaN \n", + "17 NaN \n", + "18 NaN \n", + "19 NaN \n", + "20 NaN \n", + "21 NaN " + ], + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
nameregisteraddressquantitytypegainunitmodemapping
0MeterTypeRegister371251datatypes.DataType.UINT16_BE1NaNAccessType.ROmappings.MeterType
1MeterStatusRegister371001datatypes.DataType.UINT16_BE1NaNAccessType.ROmappings.MeterStatus
2MeterModelDetectionResultRegister371381datatypes.DataType.UINT16_BE1NaNAccessType.ROmappings.MeterModelDetectionResult
3APhaseVoltageRegister371012datatypes.DataType.INT32_BE10'V'AccessType.RONaN
4BPhaseVoltageRegister371032datatypes.DataType.INT32_BE10'V'AccessType.RONaN
5CPhaseVoltageRegister371052datatypes.DataType.INT32_BE10'V'AccessType.RONaN
6APhaseCurrentRegister371072datatypes.DataType.INT32_BE100'A'AccessType.RONaN
7BPhaseCurrentRegister371092datatypes.DataType.INT32_BE100'A'AccessType.RONaN
8CPhaseCurrentRegister371112datatypes.DataType.INT32_BE100'A'AccessType.RONaN
9ActivePowerRegister371132datatypes.DataType.INT32_BE1'W'AccessType.RONaN
10ReactivePowerRegister371152datatypes.DataType.INT32_BE1'var'AccessType.RONaN
11PowerFactorRegister371171datatypes.DataType.INT16_BE1000NaNAccessType.RONaN
12GridFrequencyRegister371181datatypes.DataType.INT16_BE100'Hz'AccessType.RONaN
13PositiveActiveElectricityRegister371192datatypes.DataType.INT32_BE100'kWh'AccessType.RONaN
14ReverseActivePowerRegister371212datatypes.DataType.INT32_BE100'kWh'AccessType.RONaN
15AccumulatedReactivePowerRegister371232datatypes.DataType.INT32_BE100'kvar'AccessType.RONaN
16ABLineVoltageRegister371262datatypes.DataType.INT32_BE10'V'AccessType.RONaN
17BCLineVoltageRegister371282datatypes.DataType.INT32_BE10'V'AccessType.RONaN
18CALineVoltageRegister371302datatypes.DataType.INT32_BE10'V'AccessType.RONaN
19APhaseActivePowerRegister371322datatypes.DataType.INT32_BE1'W'AccessType.RONaN
20BPhaseActivePowerRegister371342datatypes.DataType.INT32_BE1'W'AccessType.RONaN
21CPhaseActivePowerRegister371362datatypes.DataType.INT32_BE1'W'AccessType.RONaN
\n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 22 }, { - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-01T21:30:21.914371Z", + "start_time": "2025-12-01T21:30:21.905380Z" + } + }, "cell_type": "code", "source": [ "joined = merged_table.merge(old_registers, how='left', left_on='address', right_on='address')\n", @@ -183,21 +991,662 @@ "display(joined)" ], "id": "ddfd2e9dbcf9ad9e", - "outputs": [], - "execution_count": null + "outputs": [ + { + "data": { + "text/plain": [ + " index name_x mode_x type_x unit_x gain_x address \\\n", + "0 1 Meter status RO UINT16 N/A 1 37100 \n", + "1 2 Grid voltage (A phase) RO INT32 V 10 37101 \n", + "2 3 B phase voltage RO INT32 V 10 37103 \n", + "3 4 C phase voltage RO INT32 V 10 37105 \n", + "4 5 Grid current(A phase) RO INT32 A 100 37107 \n", + "5 6 B phase current RO INT32 A 100 37109 \n", + "6 7 C phase current RO INT32 A 100 37111 \n", + "7 8 Active power RO INT32 W 1 37113 \n", + "8 9 Reactive power RO INT32 Var 1 37115 \n", + "9 10 Power factor RO INT16 NA 1000 37117 \n", + "10 11 Grid frequency RO INT16 Hz 100 37118 \n", + "11 12 Positive active electricity RO INT32 kWh 100 37119 \n", + "12 13 Reverse active power RO INT32 kWh 100 37121 \n", + "13 14 Accumulat ed reactive power RO INT32 kVarh 100 37123 \n", + "14 15 Meter type RO UINT16 N/A 1 37125 \n", + "15 16 A-B line voltage RO INT32 V 10 37126 \n", + "16 17 B-C line voltage RO INT32 V 10 37128 \n", + "17 18 CA line voltage RO INT32 V 10 37130 \n", + "18 19 A phase active power RO INT32 W 1 37132 \n", + "19 20 B phase active power RO INT32 W 1 37134 \n", + "20 21 C phase active power RO INT32 W 1 37136 \n", + "21 22 Meter model detection result RO UINT16 N/A 1 37138 \n", + "\n", + " quantity_x parsed_type parsed_unit parsed_gain name_y \\\n", + "0 1 UINT16_BE None 1 MeterStatus \n", + "1 2 INT32_BE 'V' 10 APhaseVoltage \n", + "2 2 INT32_BE 'V' 10 BPhaseVoltage \n", + "3 2 INT32_BE 'V' 10 CPhaseVoltage \n", + "4 2 INT32_BE 'A' 100 APhaseCurrent \n", + "5 2 INT32_BE 'A' 100 BPhaseCurrent \n", + "6 2 INT32_BE 'A' 100 CPhaseCurrent \n", + "7 2 INT32_BE 'W' 1 ActivePower \n", + "8 2 INT32_BE 'var' 1 ReactivePower \n", + "9 1 INT16_BE None 1000 PowerFactor \n", + "10 1 INT16_BE 'Hz' 100 GridFrequency \n", + "11 2 INT32_BE 'kWh' 100 PositiveActiveElectricity \n", + "12 2 INT32_BE 'kWh' 100 ReverseActivePower \n", + "13 2 INT32_BE 'kvarh' 100 AccumulatedReactivePower \n", + "14 1 UINT16_BE None 1 MeterType \n", + "15 2 INT32_BE 'V' 10 ABLineVoltage \n", + "16 2 INT32_BE 'V' 10 BCLineVoltage \n", + "17 2 INT32_BE 'V' 10 CALineVoltage \n", + "18 2 INT32_BE 'W' 1 APhaseActivePower \n", + "19 2 INT32_BE 'W' 1 BPhaseActivePower \n", + "20 2 INT32_BE 'W' 1 CPhaseActivePower \n", + "21 1 UINT16_BE None 1 MeterModelDetectionResult \n", + "\n", + " register quantity_y type_y gain_y unit_y \\\n", + "0 Register 1 datatypes.DataType.UINT16_BE 1 NaN \n", + "1 Register 2 datatypes.DataType.INT32_BE 10 'V' \n", + "2 Register 2 datatypes.DataType.INT32_BE 10 'V' \n", + "3 Register 2 datatypes.DataType.INT32_BE 10 'V' \n", + "4 Register 2 datatypes.DataType.INT32_BE 100 'A' \n", + "5 Register 2 datatypes.DataType.INT32_BE 100 'A' \n", + "6 Register 2 datatypes.DataType.INT32_BE 100 'A' \n", + "7 Register 2 datatypes.DataType.INT32_BE 1 'W' \n", + "8 Register 2 datatypes.DataType.INT32_BE 1 'var' \n", + "9 Register 1 datatypes.DataType.INT16_BE 1000 NaN \n", + "10 Register 1 datatypes.DataType.INT16_BE 100 'Hz' \n", + "11 Register 2 datatypes.DataType.INT32_BE 100 'kWh' \n", + "12 Register 2 datatypes.DataType.INT32_BE 100 'kWh' \n", + "13 Register 2 datatypes.DataType.INT32_BE 100 'kvar' \n", + "14 Register 1 datatypes.DataType.UINT16_BE 1 NaN \n", + "15 Register 2 datatypes.DataType.INT32_BE 10 'V' \n", + "16 Register 2 datatypes.DataType.INT32_BE 10 'V' \n", + "17 Register 2 datatypes.DataType.INT32_BE 10 'V' \n", + "18 Register 2 datatypes.DataType.INT32_BE 1 'W' \n", + "19 Register 2 datatypes.DataType.INT32_BE 1 'W' \n", + "20 Register 2 datatypes.DataType.INT32_BE 1 'W' \n", + "21 Register 1 datatypes.DataType.UINT16_BE 1 NaN \n", + "\n", + " mode_y mapping \n", + "0 AccessType.RO mappings.MeterStatus \n", + "1 AccessType.RO NaN \n", + "2 AccessType.RO NaN \n", + "3 AccessType.RO NaN \n", + "4 AccessType.RO NaN \n", + "5 AccessType.RO NaN \n", + "6 AccessType.RO NaN \n", + "7 AccessType.RO NaN \n", + "8 AccessType.RO NaN \n", + "9 AccessType.RO NaN \n", + "10 AccessType.RO NaN \n", + "11 AccessType.RO NaN \n", + "12 AccessType.RO NaN \n", + "13 AccessType.RO NaN \n", + "14 AccessType.RO mappings.MeterType \n", + "15 AccessType.RO NaN \n", + "16 AccessType.RO NaN \n", + "17 AccessType.RO NaN \n", + "18 AccessType.RO NaN \n", + "19 AccessType.RO NaN \n", + "20 AccessType.RO NaN \n", + "21 AccessType.RO mappings.MeterModelDetectionResult " + ], + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
indexname_xmode_xtype_xunit_xgain_xaddressquantity_xparsed_typeparsed_unitparsed_gainname_yregisterquantity_ytype_ygain_yunit_ymode_ymapping
01Meter statusROUINT16N/A1371001UINT16_BENone1MeterStatusRegister1datatypes.DataType.UINT16_BE1NaNAccessType.ROmappings.MeterStatus
12Grid voltage (A phase)ROINT32V10371012INT32_BE'V'10APhaseVoltageRegister2datatypes.DataType.INT32_BE10'V'AccessType.RONaN
23B phase voltageROINT32V10371032INT32_BE'V'10BPhaseVoltageRegister2datatypes.DataType.INT32_BE10'V'AccessType.RONaN
34C phase voltageROINT32V10371052INT32_BE'V'10CPhaseVoltageRegister2datatypes.DataType.INT32_BE10'V'AccessType.RONaN
45Grid current(A phase)ROINT32A100371072INT32_BE'A'100APhaseCurrentRegister2datatypes.DataType.INT32_BE100'A'AccessType.RONaN
56B phase currentROINT32A100371092INT32_BE'A'100BPhaseCurrentRegister2datatypes.DataType.INT32_BE100'A'AccessType.RONaN
67C phase currentROINT32A100371112INT32_BE'A'100CPhaseCurrentRegister2datatypes.DataType.INT32_BE100'A'AccessType.RONaN
78Active powerROINT32W1371132INT32_BE'W'1ActivePowerRegister2datatypes.DataType.INT32_BE1'W'AccessType.RONaN
89Reactive powerROINT32Var1371152INT32_BE'var'1ReactivePowerRegister2datatypes.DataType.INT32_BE1'var'AccessType.RONaN
910Power factorROINT16NA1000371171INT16_BENone1000PowerFactorRegister1datatypes.DataType.INT16_BE1000NaNAccessType.RONaN
1011Grid frequencyROINT16Hz100371181INT16_BE'Hz'100GridFrequencyRegister1datatypes.DataType.INT16_BE100'Hz'AccessType.RONaN
1112Positive active electricityROINT32kWh100371192INT32_BE'kWh'100PositiveActiveElectricityRegister2datatypes.DataType.INT32_BE100'kWh'AccessType.RONaN
1213Reverse active powerROINT32kWh100371212INT32_BE'kWh'100ReverseActivePowerRegister2datatypes.DataType.INT32_BE100'kWh'AccessType.RONaN
1314Accumulat ed reactive powerROINT32kVarh100371232INT32_BE'kvarh'100AccumulatedReactivePowerRegister2datatypes.DataType.INT32_BE100'kvar'AccessType.RONaN
1415Meter typeROUINT16N/A1371251UINT16_BENone1MeterTypeRegister1datatypes.DataType.UINT16_BE1NaNAccessType.ROmappings.MeterType
1516A-B line voltageROINT32V10371262INT32_BE'V'10ABLineVoltageRegister2datatypes.DataType.INT32_BE10'V'AccessType.RONaN
1617B-C line voltageROINT32V10371282INT32_BE'V'10BCLineVoltageRegister2datatypes.DataType.INT32_BE10'V'AccessType.RONaN
1718CA line voltageROINT32V10371302INT32_BE'V'10CALineVoltageRegister2datatypes.DataType.INT32_BE10'V'AccessType.RONaN
1819A phase active powerROINT32W1371322INT32_BE'W'1APhaseActivePowerRegister2datatypes.DataType.INT32_BE1'W'AccessType.RONaN
1920B phase active powerROINT32W1371342INT32_BE'W'1BPhaseActivePowerRegister2datatypes.DataType.INT32_BE1'W'AccessType.RONaN
2021C phase active powerROINT32W1371362INT32_BE'W'1CPhaseActivePowerRegister2datatypes.DataType.INT32_BE1'W'AccessType.RONaN
2122Meter model detection resultROUINT16N/A1371381UINT16_BENone1MeterModelDetectionResultRegister1datatypes.DataType.UINT16_BE1NaNAccessType.ROmappings.MeterModelDetectionResult
\n", + "
" + ] + }, + "metadata": {}, + "output_type": "display_data", + "jetTransient": { + "display_id": null + } + } + ], + "execution_count": 23 }, { - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-12-01T21:30:36.216444Z", + "start_time": "2025-12-01T21:30:36.209791Z" + } + }, "cell_type": "code", "source": [ "import csv\n", "\n", - "joined['new_register'] = joined.apply(lambda x: f'{'\"' + x['name_x'] + '\"' if pandas.isnull(x['name_y']) else x['name_y']} = Register({x['address']}, {x['quantity_x']}, datatypes.DataType.{x['parsed_type']}, {x['parsed_gain']}, {x['parsed_unit']}, AccessType.{x['mode_x']}, None)', axis=1)\n", + "joined['new_register'] = joined.apply(lambda x: f'{'\"' + x['name_x'] + '\"' if pandas.isnull(x['name_y']) else x['name_y']} = Register({x['address']}, {x['quantity_x']}, datatypes.DataType.{x['parsed_type']}, {x['parsed_gain']}, {x['parsed_unit']}, AccessType.{x['mode_x']}, {None if pandas.isnull(x['mapping']) else x['mapping']})', axis=1)\n", "joined.to_csv(output_file_path, columns=['new_register'], index=False, header=False, quoting=csv.QUOTE_NONE, sep=';')" ], "id": "136feb9761e37ec1", "outputs": [], - "execution_count": null + "execution_count": 24 } ], "metadata": { diff --git a/sun2000_modbus/registers.py b/sun2000_modbus/registers.py index 9f514d9..61729f6 100644 --- a/sun2000_modbus/registers.py +++ b/sun2000_modbus/registers.py @@ -196,166 +196,178 @@ class InverterEquipmentRegister(Enum): class BatteryEquipmentRegister(Enum): # Overall + ProductModel = Register(47106, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) RunningStatus = Register(37762, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.RunningStatus) WorkingModeSettings = Register(47086, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.WorkingModeSettings) BusVoltage = Register(37763, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) BusCurrent = Register(37764, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) ChargeDischargePower = Register(37765, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - MaximumChargePower = Register(37046, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - MaximumDischargePower = Register(37048, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - RatedCapacity = Register(37758, 2, datatypes.DataType.INT32_BE, 1, 'Wh', AccessType.RO, None) + MaximumChargePower = Register(37046, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RO, None) + MaximumDischargePower = Register(37048, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RO, None) + RatedCapacity = Register(37758, 2, datatypes.DataType.UINT32_BE, 1, 'Wh', AccessType.RO, None) SOC = Register(37760, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) BackupPowerSOC = Register(47102, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) - TotalCharge = Register(37780, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - TotalDischarge = Register(37782, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - CurrentDayChargeCapacity = Register(37784, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - CurrentDayDischargeCapacity = Register(37786, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - TimeOfUseElectricityPricePeriods = Register(47028, 41, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) - MaximumChargingPower = Register(47075, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) - MaximumDischargingPower = Register(47077, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) + TargetSOC = Register(47101, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) + TotalCharge = Register(37780, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + TotalDischarge = Register(37782, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + CurrentDayChargeCapacity = Register(37784, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + CurrentDayDischargeCapacity = Register(37786, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + TimeOfUseElectricityPricePeriods = Register(47028, 41, datatypes.DataType.MULTIDATA, 1, None, AccessType.RW, None) + MaximumChargingPower = Register(47075, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) + MaximumDischargingPower = Register(47077, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) ChargingCutoffCapacity = Register(47081, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) DischargeCutoffCapacity = Register(47082, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) - ForcedChargingAndDischargingPeriod = Register(47083, 1, datatypes.DataType.UINT16_BE, 1, 'minutes', AccessType.RW, None) + ForcedChargingAndDischargingPeriod = Register(47083, 1, datatypes.DataType.UINT16_BE, 1, 'mins', AccessType.RW, None) + ForcedChargingAndDischargingPower = Register(47084, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) ChargeFromGridFunction = Register(47087, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ChargeFromGridFunction) GridChargeCutoffSOC = Register(47088, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) - ForcibleChargeDischarge = Register(47100, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.WO, mappings.ForcibleChargeDischarge) - FixedChargingAndDischargingPeriods = Register(47200, 41, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) - PowerOfChargeFromGrid = Register(47242, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) - MaximumPowerOfChargeFromGrid = Register(47244, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) + ForcibleChargeDischarge = Register(47100, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ForcibleChargeDischarge) + FixedChargingAndDischargingPeriods = Register(47200, 41, datatypes.DataType.MULTIDATA, 1, None, AccessType.RW, None) + PowerOfChargeFromGrid = Register(47242, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) + MaximumPowerOfChargeFromGrid = Register(47244, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) ForcibleChargeDischargeSettingMode = Register(47246, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ForcibleChargeDischargeSettingMode) - ForcibleChargePower = Register(47247, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) - ForcibleDischargePower = Register(47249, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) - TimeOfUseChargingAndDischargingPeriods = Register(47255, 43, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) + ForcibleChargePower = Register(47247, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) + ForcibleDischargePower = Register(47249, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) + TimeOfUseChargingAndDischargingPeriods = Register(47255, 43, datatypes.DataType.MULTIDATA, 1, None, AccessType.RW, None) ExcessPVEnergyUseInTOU = Register(47299, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ExcessPVEnergyUseInTOU) ActivePowerControlMode = Register(47415, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ActivePowerControlMode) - MaximumFeedGridPowerInKW = Register(47416, 2, datatypes.DataType.INT32_BE, 1000, 'kW', AccessType.RW, None) + MaximumFeedGridPowerInKW = Register(47416, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) MaximumFeedGridPowerInPercentage = Register(47418, 1, datatypes.DataType.INT16_BE, 10, '%', AccessType.RW, None) - MaximumChargeFromGridPower = Register(47590, 2, datatypes.DataType.INT32_BE, 0.1, 'W', AccessType.RW, None) + MaximumChargeFromGridPower = Register(47590, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) SwitchToOffGrid = Register(47604, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.SwitchToOffGrid) - VoltageInIndependentOperation = Register(47605, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.VoltageIndependentOperation) + VoltageInIndependentOperation = Register(47605, 1, datatypes.DataType.UINT16_BE, 1, 'V', AccessType.RW, mappings.VoltageIndependentOperation) + SOHCalibrationStatus = Register(37926, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + SOHCalibrationReleaseTheLowerDischargeLimitOfSOC = Register(37927, 1, datatypes.DataType.UINT16_BE, 10, None, AccessType.RO, None) + SOHCalibrationEnableTheBackupPowerSOC = Register(37928, 1, datatypes.DataType.UINT16_BE, 10, None, AccessType.RO, None) # Unit 1 Unit1ProductModel = Register(47000, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ProductModel) - Unit1SN = Register(37052, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) + Unit1SN = Register(37052, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) Unit1No = Register(47107, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) - Unit1SoftwareVersion = Register(37814, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit1DCDCVersion = Register(37026, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit1BMSVersion = Register(37036, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) + Unit1SoftwareVersion = Register(37814, 15, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + Unit1DCDCVersion = Register(37026, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + Unit1BMSVersion = Register(37036, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) Unit1RunningStatus = Register(37000, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.RunningStatus) Unit1WorkingMode = Register(37006, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.WorkingMode) Unit1BusVoltage = Register(37003, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) Unit1BusCurrent = Register(37021, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) Unit1BatterySOC = Register(37004, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) Unit1ChargeAndDischargePower = Register(37001, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit1RemainingChargeDischargeTime = Register(37025, 1, datatypes.DataType.UINT16_BE, 1, 'minutes', AccessType.RO, None) - Unit1RatedChargePower = Register(37007, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit1RatedDischargePower = Register(37009, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit1CurrentDayChargeCapacity = Register(37015, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit1CurrentDayDischargeCapacity = Register(37017, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit1TotalCharge = Register(37066, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit1TotalDischarge = Register(37068, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1RemainingChargeDischargeTime = Register(37025, 1, datatypes.DataType.UINT16_BE, 1, 'mins', AccessType.RO, None) + Unit1RatedChargePower = Register(37007, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RO, None) + Unit1RatedDischargePower = Register(37009, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RO, None) + Unit1CurrentDayChargeCapacity = Register(37015, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1CurrentDayDischargeCapacity = Register(37017, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1TotalCharge = Register(37066, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1TotalDischarge = Register(37068, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) Unit1BatteryTemperature = Register(37022, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) Unit1FaultID = Register(37014, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) # Unit 2 Unit2ProductModel = Register(47089, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ProductModel) - Unit2SN = Register(37700, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) + Unit2SN = Register(37700, 10, datatypes.DataType.MULTIDATA, 1, None, AccessType.RO, None) Unit2No = Register(47108, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) - Unit2SoftwareVersion = Register(37799, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) + Unit2SoftwareVersion = Register(37799, 15, datatypes.DataType.STRING, 1, None, AccessType.RO, None) Unit2RunningStatus = Register(37741, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.RunningStatus) Unit2BusVoltage = Register(37750, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) Unit2BusCurrent = Register(37751, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) Unit2BatterySOC = Register(37738, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) Unit2ChargeAndDischargePower = Register(37743, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit2CurrentDayChargeCapacity = Register(37746, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit2CurrentDayDischargeCapacity = Register(37748, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit2TotalCharge = Register(37753, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit2TotalDischarge = Register(37755, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2CurrentDayChargeCapacity = Register(37746, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2CurrentDayDischargeCapacity = Register(37748, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2TotalCharge = Register(37753, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2TotalDischarge = Register(37755, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) Unit2BatteryTemperature = Register(37752, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) # Unit 1 BatteryPack 1 Unit1BatteryPack1SN = Register(38200, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit1BatteryPack1No = Register(47750, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) + Unit1BatteryPack1No = Register(47750, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) Unit1BatteryPack1FirmwareVersion = Register(38210, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit1BatteryPack1WorkingStatus = Register(38228, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + Unit1BatteryPack1WorkingStatus = Register(38228, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) Unit1BatteryPack1Voltage = Register(38235, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) Unit1BatteryPack1Current = Register(38236, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) Unit1BatteryPack1SOC = Register(38229, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) Unit1BatteryPack1ChargeDischargePower = Register(38233, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit1BatteryPack1TotalCharge = Register(38238, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit1BatteryPack1TotalDischarge = Register(38240, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack1TotalCharge = Register(38238, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack1TotalDischarge = Register(38240, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) Unit1BatteryPack1MinimumTemperature = Register(38453, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) Unit1BatteryPack1MaximumTemperature = Register(38452, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit1BatteryPack1SOHCalibrationStatus = Register(37920, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) # Unit 1 BatteryPack 2 Unit1BatteryPack2SN = Register(38242, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit1BatteryPack2No = Register(47751, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) + Unit1BatteryPack2No = Register(47751, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) Unit1BatteryPack2FirmwareVersion = Register(38252, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit1BatteryPack2WorkingStatus = Register(38270, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + Unit1BatteryPack2WorkingStatus = Register(38270, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) Unit1BatteryPack2Voltage = Register(38277, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) Unit1BatteryPack2Current = Register(38278, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) Unit1BatteryPack2SOC = Register(38271, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) Unit1BatteryPack2ChargeDischargePower = Register(38275, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit1BatteryPack2TotalCharge = Register(38280, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit1BatteryPack2TotalDischarge = Register(38282, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack2TotalCharge = Register(38280, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack2TotalDischarge = Register(38282, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) Unit1BatteryPack2MinimumTemperature = Register(38455, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) Unit1BatteryPack2MaximumTemperature = Register(38454, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit1BatteryPack2SOHCalibrationStatus = Register(37921, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) # Unit 1 BatteryPack 3 Unit1BatteryPack3SN = Register(38284, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit1BatteryPack3No = Register(47752, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) + Unit1BatteryPack3No = Register(47752, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) Unit1BatteryPack3FirmwareVersion = Register(38294, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit1BatteryPack3WorkingStatus = Register(38312, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + Unit1BatteryPack3WorkingStatus = Register(38312, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) Unit1BatteryPack3Voltage = Register(38319, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) Unit1BatteryPack3Current = Register(38320, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) Unit1BatteryPack3SOC = Register(38313, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) Unit1BatteryPack3ChargeDischargePower = Register(38317, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit1BatteryPack3TotalCharge = Register(38322, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit1BatteryPack3TotalDischarge = Register(38324, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack3TotalCharge = Register(38322, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit1BatteryPack3TotalDischarge = Register(38324, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) Unit1BatteryPack3MinimumTemperature = Register(38457, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) Unit1BatteryPack3MaximumTemperature = Register(38456, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit1BatteryPack3SOHCalibrationStatus = Register(37922, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) # Unit 2 BatteryPack 1 Unit2BatteryPack1SN = Register(38326, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit2BatteryPack1No = Register(47753, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) + Unit2BatteryPack1No = Register(47753, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) Unit2BatteryPack1FirmwareVersion = Register(38336, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit2BatteryPack1WorkingStatus = Register(38354, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + Unit2BatteryPack1WorkingStatus = Register(38354, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) Unit2BatteryPack1Voltage = Register(38361, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) Unit2BatteryPack1Current = Register(38362, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) Unit2BatteryPack1SOC = Register(38355, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) Unit2BatteryPack1ChargeDischargePower = Register(38359, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit2BatteryPack1TotalCharge = Register(38364, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit2BatteryPack1TotalDischarge = Register(38366, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack1TotalCharge = Register(38364, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack1TotalDischarge = Register(38366, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) Unit2BatteryPack1MinimumTemperature = Register(38459, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) Unit2BatteryPack1MaximumTemperature = Register(38458, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit2BatteryPack1SOHCalibrationStatus = Register(37923, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) # Unit 2 BatteryPack 2 Unit2BatteryPack2SN = Register(38368, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit2BatteryPack2No = Register(47754, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) + Unit2BatteryPack2No = Register(47754, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) Unit2BatteryPack2FirmwareVersion = Register(38378, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit2BatteryPack2WorkingStatus = Register(38396, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + Unit2BatteryPack2WorkingStatus = Register(38396, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) Unit2BatteryPack2Voltage = Register(38403, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) Unit2BatteryPack2Current = Register(38404, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) Unit2BatteryPack2SOC = Register(38397, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) Unit2BatteryPack2ChargeDischargePower = Register(38401, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit2BatteryPack2TotalCharge = Register(38406, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit2BatteryPack2TotalDischarge = Register(38408, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack2TotalCharge = Register(38406, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack2TotalDischarge = Register(38408, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) Unit2BatteryPack2MinimumTemperature = Register(38461, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) Unit2BatteryPack2MaximumTemperature = Register(38460, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit2BatteryPack2SOHCalibrationStatus = Register(37924, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) # Unit 2 BatteryPack 3 Unit2BatteryPack3SN = Register(38410, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit2BatteryPack3No = Register(47755, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) + Unit2BatteryPack3No = Register(47755, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) Unit2BatteryPack3FirmwareVersion = Register(38420, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - Unit2BatteryPack3WorkingStatus = Register(38438, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) + Unit2BatteryPack3WorkingStatus = Register(38438, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) Unit2BatteryPack3Voltage = Register(38445, 1, datatypes.DataType.UINT16_BE, 10, 'V', AccessType.RO, None) Unit2BatteryPack3Current = Register(38446, 1, datatypes.DataType.INT16_BE, 10, 'A', AccessType.RO, None) Unit2BatteryPack3SOC = Register(38439, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RO, None) Unit2BatteryPack3ChargeDischargePower = Register(38443, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) - Unit2BatteryPack3TotalCharge = Register(38448, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - Unit2BatteryPack3TotalDischarge = Register(38450, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack3TotalCharge = Register(38448, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) + Unit2BatteryPack3TotalDischarge = Register(38450, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) Unit2BatteryPack3MinimumTemperature = Register(38463, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) Unit2BatteryPack3MaximumTemperature = Register(38462, 1, datatypes.DataType.INT16_BE, 10, '°C', AccessType.RO, None) + Unit2BatteryPack3SOHCalibrationStatus = Register(37925, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) class MeterEquipmentRegister(Enum): @@ -374,7 +386,7 @@ class MeterEquipmentRegister(Enum): GridFrequency = Register(37118, 1, datatypes.DataType.INT16_BE, 100, 'Hz', AccessType.RO, None) PositiveActiveElectricity = Register(37119, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) ReverseActivePower = Register(37121, 2, datatypes.DataType.INT32_BE, 100, 'kWh', AccessType.RO, None) - AccumulatedReactivePower = Register(37123, 2, datatypes.DataType.INT32_BE, 100, 'kvar', AccessType.RO, None) + AccumulatedReactivePower = Register(37123, 2, datatypes.DataType.INT32_BE, 100, 'kvarh', AccessType.RO, None) ABLineVoltage = Register(37126, 2, datatypes.DataType.INT32_BE, 10, 'V', AccessType.RO, None) BCLineVoltage = Register(37128, 2, datatypes.DataType.INT32_BE, 10, 'V', AccessType.RO, None) CALineVoltage = Register(37130, 2, datatypes.DataType.INT32_BE, 10, 'V', AccessType.RO, None) From bcafdf2f5ebf8071c73f9cf63054c761acdce14d Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Fri, 5 Dec 2025 22:21:39 +0100 Subject: [PATCH 05/15] #48: Updated mappings for InverterEquipmentRegister --- sun2000_modbus/mappings.py | 146 ++++++++++++++++++++++++++---------- sun2000_modbus/registers.py | 17 ++--- 2 files changed, 115 insertions(+), 48 deletions(-) diff --git a/sun2000_modbus/mappings.py b/sun2000_modbus/mappings.py index f6c8e1b..c8987a7 100644 --- a/sun2000_modbus/mappings.py +++ b/sun2000_modbus/mappings.py @@ -1,35 +1,103 @@ DeviceStatus = { - 0x0000: 'Standby: initializing', - 0x0001: 'Standby: detecting insulation resistance', - 0x0002: 'Standby: detecting irradiation', - 0x0003: 'Standby: Grid detecting', + 0x0000: 'Standby: initialization', + 0x0001: 'Standby: insulation resistance detection', + 0x0002: 'Standby: sunlight detection', + 0x0003: 'Standby: grid detecting', 0x0100: 'Starting', 0x0200: 'On-grid', - 0x0201: 'Grid connection: power limited', - 0x0202: 'Grid connection: self-derating', - 0x0203: 'Off-grid Running', - 0x0300: 'Shutdown: fault', - 0x0301: 'Shutdown: command', - 0x0302: 'Shutdown: OVGR', - 0x0303: 'Shutdown: communication disconnected', - 0x0304: 'Shutdown: power limited', - 0x0305: 'Shutdown: manual startup required', - 0x0306: 'Shutdown: DC switches disconnected', - 0x0307: 'Shutdown: rapid cutoff', - 0x0308: 'Shutdown: input underpowered', + 0x0201: 'Grid connected: power limited', + 0x0202: 'Grid connected: self derating', + 0x0203: 'Off-grid operation', + 0x0300: 'OFF: unexpected shutdown', + 0x0301: 'OFF: instructed shutdown', + 0x0302: 'OFF: OVGR', + 0x0303: 'OFF: communication interrupted', + 0x0304: 'OFF: power limited', + 0x0305: 'OFF: manual startup required', + 0x0307: 'Shutdown: rapid shutdown', + 0x030A: 'Shutdown: commanded rapid shutdown', + 0x030B: 'Shutdown: the backup power system is abnormal', 0x0401: 'Grid scheduling: cosPhi-P curve', 0x0402: 'Grid scheduling: Q-U curve', 0x0403: 'Grid scheduling: PF-U curve', - 0x0404: 'Grid scheduling: dry contact', 0x0405: 'Grid scheduling: Q-P curve', - 0x0500: 'Spot-check ready', - 0x0501: 'Spot-checking', - 0x0600: 'Inspecting', - 0x0700: 'AFCI self check', - 0x0800: 'I-V scanning', - 0x0900: 'DC input detection', - 0x0A00: 'Running: off-grid charging', - 0xA000: 'Standby: no irradiation' + 0x0600: 'Inspecting ongoing', + 0x0700: 'AFCI check', + 0x0800: 'I-V curve scanning', + 0x0A00: 'Running: grid-disconnected and charged', + 0x0A01: 'Standby: the backup power system is abnormal', + 0xA000: 'Standby: no irradiation', +} + +LicenseStatus = { + 0x0000: 'No License', + 0x0001: 'Normal', + 0x0002: 'In grace period', + 0x0003: 'Revoked', + 0x0004: 'SN not match', + 0x0005: 'Expired', +} + +ModuleStatus4G = { + 0x0000: 'Card not found', + 0x0001: 'Card registration failed', + 0x0002: 'No connection', + 0x0003: 'Low signal strength', + 0x0004: 'Medium signal strength', + 0x0005: 'High signal strength', + 0x0006: 'Connected', + 0x0100: 'Card in position', + 0x0101: 'PIN required', + 0x0102: 'PUK required', + 0xFFFF: 'Module not found', +} + +PINVerificationStatus4G = { + 0x0000: 'Initial state', + 0x0001: 'Verifying...', +} + +ChargeDischargeMode = { + 0x0000: 'N/A', + 0x0001: 'Forced charge/discharge', + 0x0002: 'Time-of-use', + 0x0003: 'Fixed charge and discharge', + 0x0004: 'Max. self-consumption', + 0x0005: 'Fully fed to grid', + 0x0006: 'TOU', + 0x0007: 'Remote scheduling - Max. self-consumption', + 0x0008: 'Remote scheduling - Fully fed to grid', + 0x0009: 'Remote scheduling - TOU', + 0x000A: 'AI control', + 0x000B: 'Remote scheduling - AI control', + 0x000C: 'Three-party scheduling', +} + +QUCharacteristicCurveMode = { + 0x0000: 'non-hysteresis', + 0x0001: 'hysteresis', +} + +RemoteChargeDischargeControlMode = { + 0x0000: 'Local control', + 0x0001: 'Remote control - Max. self-consumption', + 0x0002: 'Remote control - Fully fed to grid', + 0x0003: 'Remote control - TOU', + 0x0004: 'Remote control - AI control', + 0x0005: 'Remote control - Three-party scheduling', +} + +PeakShaving = { + 0x0000: 'Disabled', + 0x0001: 'Active power limit', + 0x0002: 'Apparent power limit', +} + +BackupBoxModel = { + 0x0000: 'Backup Box - B0/B1', + 0x0001: 'Compatible Third Party Backup Box', + 0x0002: 'SmartGuard', + 0x0003: 'No Backup Box', } RunningStatus = { @@ -37,7 +105,7 @@ 1: 'standby', 2: 'running', 3: 'fault', - 4: 'sleep mode' + 4: 'sleep mode', } WorkingMode = { @@ -47,13 +115,13 @@ 3: 'Fixed charge/discharge', 4: 'Maximise self consumption', 5: 'Fully fed to grid', - 6: 'Time of Use (LUNA2000)' + 6: 'Time of Use (LUNA2000)', } ProductModel = { 0: 'None', 1: 'LG-RESU', - 2: 'HUAWEI-LUNA2000' + 2: 'HUAWEI-LUNA2000', } WorkingModeSettings = { @@ -62,52 +130,52 @@ 2: 'Maximise self consumption', 3: 'Time of Use (LG)', 4: 'Fully fed to grid', - 5: 'Time of Use (LUNA2000)' + 5: 'Time of Use (LUNA2000)', } ChargeFromGridFunction = { 0: 'Disable', - 1: 'Enable' + 1: 'Enable', } ForcibleChargeDischarge = { 0: 'Stop', 1: 'Charge', - 2: 'Discharge' + 2: 'Discharge', } ForcibleChargeDischargeSettingMode = { - 0: 'Duration' + 0: 'Duration', } ExcessPVEnergyUseInTOU = { 0: 'Fed to grid', - 1: 'Charge' + 1: 'Charge', } SwitchToOffGrid = { - 0: 'Switch from grid-tied to off-grid' + 0: 'Switch from grid-tied to off-grid', } VoltageIndependentOperation = { 0: '101 V', - 1: '202 V' + 1: '202 V', } MeterStatus = { 0: 'offline', - 1: 'online' + 1: 'online', } MeterType = { 0: 'single-phase', - 1: 'three-phase' + 1: 'three-phase', } MeterModelDetectionResult = { 0: 'being identified', 1: 'The selected model is the same as the actual model of the connected meter', - 2: 'The selected model is different from the actual model of the connected meter' + 2: 'The selected model is different from the actual model of the connected meter', } ActivePowerControlMode = { @@ -115,5 +183,5 @@ 1: 'DI active scheduling', 5: 'Zero power grid connection', 6: 'Powerlimited grid connection (kW)', - 7: 'Powerlimited grid connection (%)' + 7: 'Powerlimited grid connection (%)', } diff --git a/sun2000_modbus/registers.py b/sun2000_modbus/registers.py index 61729f6..c687c56 100644 --- a/sun2000_modbus/registers.py +++ b/sun2000_modbus/registers.py @@ -133,29 +133,29 @@ class InverterEquipmentRegister(Enum): DailyEnergyYield = Register(32114, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) ManagementSystemStatus = Register(35127, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) AuthorizationFunction = Register(35136, 2, datatypes.DataType.BITFIELD32, 1, None, AccessType.RO, None) - LicenseStatus = Register(35138, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) + LicenseStatus = Register(35138, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, mappings.LicenseStatus) LicenseExpirationTime = Register(35139, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) LicenseLoadingTime = Register(35141, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) LicenseRevocationTime = Register(35143, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) LicenseSN = Register(35145, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) RevocationCode = Register(35155, 64, datatypes.DataType.STRING, 1, None, AccessType.RO, None) - ModuleStatus4G = Register(35249, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) + ModuleStatus4G = Register(35249, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, mappings.ModuleStatus4G) IPAddress4G = Register(35250, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) SubnetMask4G = Register(35252, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) IMEI4G = Register(35254, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) SignalStrength4G = Register(35264, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) MaximumNumberOfPINAttempts4G = Register(35265, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - PINVerificationStatus4G = Register(35266, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) + PINVerificationStatus4G = Register(35266, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, mappings.PINVerificationStatus4G) OriginalModelName = Register(35268, 15, datatypes.DataType.MULTIDATA, 1, None, AccessType.RO, None) ActiveAdjustmentMode = Register(35300, 4, datatypes.DataType.MULTIDATA, None, None, AccessType.RO, None) ReactiveAdjustmentMode = Register(35304, 4, datatypes.DataType.MULTIDATA, None, None, AccessType.RO, None) - ChargeDischargeMode = Register(37006, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, None) + ChargeDischargeMode = Register(37006, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, mappings.ChargeDischargeMode) PowerMeterCollectionActivePower = Register(37113, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RO, None) TotalNumberOfOptimizers = Register(37200, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) NumberOfOnlineOptimizers = Register(37201, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) FeatureData = Register(37202, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) SystemTime = Register(40000, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RW, None) - QUCharacteristicCurveMode = Register(40037, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + QUCharacteristicCurveMode = Register(40037, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, mappings.QUCharacteristicCurveMode) QUDispatchTriggerPower = Register(40038, 1, datatypes.DataType.INT16_BE, 1, '%', AccessType.RW, None) FixedActivePowerDeratedInKW = Register(40120, 1, datatypes.DataType.UINT16_BE, 1, 'W', AccessType.RW, None) ReactivePowerCompensationInPF = Register(40122, 1, datatypes.DataType.INT16_BE, 1000, None, AccessType.RW, None) @@ -181,16 +181,15 @@ class InverterEquipmentRegister(Enum): FastPowerScheduling = Register(45086, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) BatteryChargingMode = Register(47086, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) BatteryChargeAndDischargePower = Register(47321, 1, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) - RemoteChargeDischargeControlMode = Register(47589, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) - RemoteControlForChargingAndDischarging = Register(47589, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + RemoteChargeDischargeControlMode = Register(47589, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, mappings.RemoteChargeDischargeControlMode) ScheduledTask = Register(47674, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) DefaultMaximumFeedInPower = Register(47675, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) DefaultActivePowerChangeGradient = Register(47677, 2, datatypes.DataType.UINT32_BE, 1000, '%/s', AccessType.RW, None) - PeakShaving = Register(47954, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + PeakShaving = Register(47954, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, mappings.PeakShaving) BackupPowerSOCForPeakShaving = Register(47955, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) PeakPower = Register(47956, 64, datatypes.DataType.MULTIDATA, None, None, AccessType.RW, None) AIOpticalStorage = Register(48020, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) - BackupBoxModel = Register(48089, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) + BackupBoxModel = Register(48089, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, mappings.BackupBoxModel) PhaseToGroundCircuitProtection = Register(48090, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) From 9ca4c9f7301b86ed313afeac9eceec40b304e937 Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Fri, 5 Dec 2025 22:40:51 +0100 Subject: [PATCH 06/15] #48: Updated mappings for BatteryEquipmentRegister and MeterEquipmentRegister --- sun2000_modbus/mappings.py | 111 +++++++++++++++++------------------- sun2000_modbus/registers.py | 6 +- 2 files changed, 54 insertions(+), 63 deletions(-) diff --git a/sun2000_modbus/mappings.py b/sun2000_modbus/mappings.py index c8987a7..373f3f6 100644 --- a/sun2000_modbus/mappings.py +++ b/sun2000_modbus/mappings.py @@ -101,87 +101,78 @@ } RunningStatus = { - 0: 'offline', - 1: 'standby', - 2: 'running', - 3: 'fault', - 4: 'sleep mode', -} - -WorkingMode = { - 0: 'none', - 1: 'Forcible charge/discharge', - 2: 'Time of Use (LG)', - 3: 'Fixed charge/discharge', - 4: 'Maximise self consumption', - 5: 'Fully fed to grid', - 6: 'Time of Use (LUNA2000)', -} - -ProductModel = { - 0: 'None', - 1: 'LG-RESU', - 2: 'HUAWEI-LUNA2000', + 0x0000: 'offline', + 0x0001: 'standby', + 0x0002: 'running', + 0x0003: 'fault', + 0x0004: 'sleep mode', } WorkingModeSettings = { - 0: 'Adaptive', - 1: 'Fixed charge/discharge', - 2: 'Maximise self consumption', - 3: 'Time of Use (LG)', - 4: 'Fully fed to grid', - 5: 'Time of Use (LUNA2000)', -} - -ChargeFromGridFunction = { - 0: 'Disable', - 1: 'Enable', + 0x0000: 'Adaptive', + 0x0001: 'Fixed charge/discharge', + 0x0002: 'Maximise self consumption', + 0x0003: 'Time of Use (LG)', + 0x0004: 'Fully fed to grid', + 0x0005: 'Time of Use (LUNA2000)', } ForcibleChargeDischarge = { - 0: 'Stop', - 1: 'Charge', - 2: 'Discharge', -} - -ForcibleChargeDischargeSettingMode = { - 0: 'Duration', + 0x0000: 'Stop', + 0x0001: 'Charge', + 0x0002: 'Discharge', } ExcessPVEnergyUseInTOU = { - 0: 'Fed to grid', - 1: 'Charge', + 0x0000: 'Fed to grid', + 0x0001: 'Charge', } -SwitchToOffGrid = { - 0: 'Switch from grid-tied to off-grid', +ActivePowerControlMode = { + 0x0000: 'Unlimited', + 0x0001: 'DI active scheduling', + 0x0005: 'Zero power grid connection', + 0x0006: 'Power-limited grid connection (kW)', + 0x0007: 'Power-limited grid connection (%)', } VoltageIndependentOperation = { - 0: '101 V', - 1: '202 V', + 0x0000: '101 V', + 0x0001: '202 V', } -MeterStatus = { - 0: 'offline', - 1: 'online', +ProductModel = { + 0x0000: 'None', + 0x0001: 'LG-RESU', + 0x0002: 'HUAWEI-LUNA2000', +} + +WorkingMode = { + 0x0000: 'none', + 0x0001: 'Forcible charge/discharge', + 0x0002: 'Time of Use (LG)', + 0x0003: 'Fixed charge/discharge', + 0x0004: 'Maximise self consumption', + 0x0005: 'Fully fed to grid', + 0x0006: 'Time of Use (LUNA2000)', + 0x0007: 'Remote scheduling - maximum self-use', + 0x0008: 'Remote scheduling - full internet access', + 0x0009: 'Remote scheduling - TOU', + 0x000A: 'AI energy management and scheduling', } MeterType = { - 0: 'single-phase', - 1: 'three-phase', + 0x0000: 'single-phase', + 0x0001: 'three-phase', } -MeterModelDetectionResult = { - 0: 'being identified', - 1: 'The selected model is the same as the actual model of the connected meter', - 2: 'The selected model is different from the actual model of the connected meter', +MeterStatus = { + 0x0000: 'offline', + 0x0001: 'normal', } -ActivePowerControlMode = { - 0: 'Unlimited', - 1: 'DI active scheduling', - 5: 'Zero power grid connection', - 6: 'Powerlimited grid connection (kW)', - 7: 'Powerlimited grid connection (%)', +MeterModelDetectionResult = { + 0x0000: 'being identified', + 0x0001: 'The selected model is the same as the actual model of the connected meter', + 0x0002: 'The selected model is different from the actual model of the connected meter', } diff --git a/sun2000_modbus/registers.py b/sun2000_modbus/registers.py index c687c56..724d072 100644 --- a/sun2000_modbus/registers.py +++ b/sun2000_modbus/registers.py @@ -218,13 +218,13 @@ class BatteryEquipmentRegister(Enum): DischargeCutoffCapacity = Register(47082, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) ForcedChargingAndDischargingPeriod = Register(47083, 1, datatypes.DataType.UINT16_BE, 1, 'mins', AccessType.RW, None) ForcedChargingAndDischargingPower = Register(47084, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) - ChargeFromGridFunction = Register(47087, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ChargeFromGridFunction) + ChargeFromGridFunction = Register(47087, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) GridChargeCutoffSOC = Register(47088, 1, datatypes.DataType.UINT16_BE, 10, '%', AccessType.RW, None) ForcibleChargeDischarge = Register(47100, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ForcibleChargeDischarge) FixedChargingAndDischargingPeriods = Register(47200, 41, datatypes.DataType.MULTIDATA, 1, None, AccessType.RW, None) PowerOfChargeFromGrid = Register(47242, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) MaximumPowerOfChargeFromGrid = Register(47244, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) - ForcibleChargeDischargeSettingMode = Register(47246, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ForcibleChargeDischargeSettingMode) + ForcibleChargeDischargeSettingMode = Register(47246, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) ForcibleChargePower = Register(47247, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) ForcibleDischargePower = Register(47249, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) TimeOfUseChargingAndDischargingPeriods = Register(47255, 43, datatypes.DataType.MULTIDATA, 1, None, AccessType.RW, None) @@ -233,7 +233,7 @@ class BatteryEquipmentRegister(Enum): MaximumFeedGridPowerInKW = Register(47416, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) MaximumFeedGridPowerInPercentage = Register(47418, 1, datatypes.DataType.INT16_BE, 10, '%', AccessType.RW, None) MaximumChargeFromGridPower = Register(47590, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) - SwitchToOffGrid = Register(47604, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.SwitchToOffGrid) + SwitchToOffGrid = Register(47604, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) VoltageInIndependentOperation = Register(47605, 1, datatypes.DataType.UINT16_BE, 1, 'V', AccessType.RW, mappings.VoltageIndependentOperation) SOHCalibrationStatus = Register(37926, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) SOHCalibrationReleaseTheLowerDischargeLimitOfSOC = Register(37927, 1, datatypes.DataType.UINT16_BE, 10, None, AccessType.RO, None) From 731d7a983d6ee587d23d939da82c6303a95f8423 Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Fri, 5 Dec 2025 22:44:12 +0100 Subject: [PATCH 07/15] #48: Fixed test --- tests/test_sun2000_modbus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_sun2000_modbus.py b/tests/test_sun2000_modbus.py index 9dadda4..ee67d3d 100644 --- a/tests/test_sun2000_modbus.py +++ b/tests/test_sun2000_modbus.py @@ -643,8 +643,8 @@ def test_write_int16be(self, write_registers_mock): ) def test_write_int32be(self, write_registers_mock): self.test_inverter.connect() - self.test_inverter.write(BatteryEquipmentRegister.MaximumChargeFromGridPower, -10200) - write_registers_mock.assert_called_once_with(address=47590, values=[65535, 55336], device_id=1) + self.test_inverter.write(BatteryEquipmentRegister.MaximumFeedGridPowerInKW, -10200) + write_registers_mock.assert_called_once_with(address=47416, values=[65535, 55336], device_id=1) @patch( 'pymodbus.client.ModbusTcpClient.write_registers' From 5ae55089c73cfb84da13a6a5d36f0627833f6625 Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Fri, 5 Dec 2025 23:18:11 +0100 Subject: [PATCH 08/15] #48: Added helper for doc generation, updated docs for registers --- README.md | 432 ++++++++++-------- helpers/doc-generator/.gitignore | 1 + helpers/doc-generator/main.py | 35 ++ .../register-parser}/.gitignore | 0 .../register-parser}/register-parser.ipynb | 2 +- .../register-parser}/requirements.txt | 0 6 files changed, 278 insertions(+), 192 deletions(-) create mode 100644 helpers/doc-generator/.gitignore create mode 100644 helpers/doc-generator/main.py rename {register-parser => helpers/register-parser}/.gitignore (100%) rename {register-parser => helpers/register-parser}/register-parser.ipynb (99%) rename {register-parser => helpers/register-parser}/requirements.txt (100%) diff --git a/README.md b/README.md index 366f999..8978d1e 100644 --- a/README.md +++ b/README.md @@ -87,14 +87,11 @@ The following registers are provided by the Sun2000's Modbus interface and can b ### InverterEquipmentRegister | Name | Type | Gain | Unit | Access Type | -|-----------------------------------------|------------------------|------|------|-------------| +|:----------------------------------------|:-----------------------|:-----|:-----|:------------| | Model | String | | | RO | | SN | String | | | RO | | PN | String | | | RO | -| FirmwareVersion | String | | | RO | -| SoftwareVersion | String | | | RO | -| ProtocolVersion | String | | | RO | -| ModelID | Number | 1 | | RO | +| ModelID | Number | | | RO | | NumberOfPVStrings | Number | 1 | | RO | | NumberOfMPPTrackers | Number | 1 | | RO | | RatedPower | Number | 1 | W | RO | @@ -102,12 +99,24 @@ The following registers are provided by the Sun2000's Modbus interface and can b | MaximumApparentPower | Number | 1000 | kVA | RO | | MaximumReactivePowerFedToTheGrid | Number | 1000 | kvar | RO | | MaximumReactivePowerAbsorbedFromTheGrid | Number | 1000 | kvar | RO | +| OfferingNameOfSouthboundDevice1 | String | | | RO | +| OfferingNameOfSouthboundDevice2 | String | | | RO | +| OfferingNameOfSouthboundDevice3 | String | | | RO | +| HardwareVersion | String | 1 | | RO | +| MonitoringBoardSN | String | | | RO | +| MonitoringSoftwareVersion | String | | | RO | +| MasterDSPVersion | String | | | RO | +| CPLDVersion | String | | | RO | +| AFCIVersion | String | | | RO | +| DCMBUSVersion | String | | | RO | +| REGKEY | String | 1 | | RO | | State1 | Binary String/Bitfield | | | RO | | State2 | Binary String/Bitfield | | | RO | | State3 | Binary String/Bitfield | | | RO | | Alarm1 | Binary String/Bitfield | | | RO | | Alarm2 | Binary String/Bitfield | | | RO | | Alarm3 | Binary String/Bitfield | | | RO | +| ESN | Number | | | RO | | PV1Voltage | Number | 10 | V | RO | | PV1Current | Number | 100 | A | RO | | PV2Voltage | Number | 10 | V | RO | @@ -174,26 +183,39 @@ The following registers are provided by the Sun2000's Modbus interface and can b | Efficiency | Number | 100 | % | RO | | InternalTemperature | Number | 10 | °C | RO | | InsulationResistance | Number | 1000 | MOhm | RO | -| DeviceStatus | Number | 1 | | RO | -| FaultCode | Number | 1 | | RO | -| StartupTime | Number | 1 | | RO | -| ShutdownTime | Number | 1 | | RO | +| DeviceStatus | Number | | | RO | +| FaultCode | Number | | | RO | +| StartupTime | Number | 1 | s | RO | +| ShutdownTime | Number | 1 | s | RO | | AccumulatedEnergyYield | Number | 100 | kWh | RO | | DailyEnergyYield | Number | 100 | kWh | RO | -| ActiveAdjustmentMode | Number | 1 | | RO | -| ActiveAdjustmentValue | Number | 1 | | RO | -| ActiveAdjustmentCommand | Number | 1 | | RO | -| ReactiveAdjustmentMode | Number | 1 | | RO | -| ReactiveAdjustmentValue | Number | 1 | | RO | -| ReactiveAdjustmentCommand | Number | 1 | | RO | +| ManagementSystemStatus | Number | 1 | | RO | +| AuthorizationFunction | Binary String/Bitfield | 1 | | RO | +| LicenseStatus | Number | | | RO | +| LicenseExpirationTime | Number | 1 | s | RO | +| LicenseLoadingTime | Number | 1 | s | RO | +| LicenseRevocationTime | Number | 1 | s | RO | +| LicenseSN | String | 1 | | RO | +| RevocationCode | String | 1 | | RO | +| ModuleStatus4G | Number | | | RO | +| IPAddress4G | Number | 1 | | RO | +| SubnetMask4G | Number | 1 | | RO | +| IMEI4G | String | 1 | | RO | +| SignalStrength4G | Number | 1 | | RO | +| MaximumNumberOfPINAttempts4G | Number | 1 | | RO | +| PINVerificationStatus4G | Number | | | RO | +| OriginalModelName | Bytestring | 1 | | RO | +| ActiveAdjustmentMode | Bytestring | | | RO | +| ReactiveAdjustmentMode | Bytestring | | | RO | +| ChargeDischargeMode | Number | | | RO | | PowerMeterCollectionActivePower | Number | 1 | W | RO | | TotalNumberOfOptimizers | Number | 1 | | RO | | NumberOfOnlineOptimizers | Number | 1 | | RO | | FeatureData | Number | 1 | | RO | -| SystemTime | Number | 1 | | RW | -| QUCharacteristicCurveMode | Number | 1 | | RW | +| SystemTime | Number | 1 | s | RW | +| QUCharacteristicCurveMode | Number | | | RW | | QUDispatchTriggerPower | Number | 1 | % | RW | -| FixedActivePowerDeratedInKW | Number | 10 | kW | RW | +| FixedActivePowerDeratedInKW | Number | 1 | W | RW | | ReactivePowerCompensationInPF | Number | 1000 | | RW | | ReactivePowerCompensationQS | Number | 1000 | | RW | | ActivePowerPercentageDerating | Number | 10 | % | RW | @@ -204,186 +226,214 @@ The following registers are provided by the Sun2000's Modbus interface and can b | PFUCharacteristicCurve | Bytestring | | | RW | | ReactivePowerAdjustmentTime | Number | 1 | s | RW | | QUPowerPercentageToExitScheduling | Number | 1 | % | RW | -| Startup | Number | 1 | | WO | -| Shutdown | Number | 1 | | WO | -| GridCode | Number | 1 | | RW | +| Startup | Number | | | WO | +| Shutdown | Number | | | WO | +| GridCode | Number | | | RW | | ReactivePowerChangeGradient | Number | 1000 | %/s | RW | | ActivePowerChangeGradient | Number | 1000 | %/s | RW | | ScheduleInstructionValidDuration | Number | 1 | s | RW | +| AcivePowerLimit | Number | 1 | W | RW | | TimeZone | Number | 1 | min | RW | +| TLSEncryption | Number | | | RW | +| WLANWakeup | Number | | | RW | +| FastPowerScheduling | Number | | | RW | +| BatteryChargingMode | Number | | | RW | +| BatteryChargeAndDischargePower | Number | 1 | W | RW | +| RemoteChargeDischargeControlMode | Number | | | RW | +| ScheduledTask | Number | | | RW | +| DefaultMaximumFeedInPower | Number | 1 | W | RW | +| DefaultActivePowerChangeGradient | Number | 1000 | %/s | RW | +| PeakShaving | Number | | | RW | +| BackupPowerSOCForPeakShaving | Number | 10 | % | RW | +| PeakPower | Bytestring | | | RW | +| AIOpticalStorage | Number | | | RW | +| BackupBoxModel | Number | | | RW | +| PhaseToGroundCircuitProtection | Number | | | RW | ### BatteryEquipmentRegister -| Name | Type | Gain | Unit | Access Type | -|----------------------------------------|------------|------|---------|-------------| -| RunningStatus | Number | 1 | | RO | -| WorkingModeSettings | Number | 1 | | RW | -| BusVoltage | Number | 10 | V | RO | -| BusCurrent | Number | 10 | A | RO | -| ChargeDischargePower | Number | 1 | W | RO | -| MaximumChargePower | Number | 1 | W | RO | -| MaximumDischargePower | Number | 1 | W | RO | -| RatedCapacity | Number | 1 | Wh | RO | -| SOC | Number | 10 | % | RO | -| BackupPowerSOC | Number | 10 | % | RW | -| TotalCharge | Number | 100 | kWh | RO | -| TotalDischarge | Number | 100 | kWh | RO | -| CurrentDayChargeCapacity | Number | 100 | kWh | RO | -| CurrentDayDischargeCapacity | Number | 100 | kWh | RO | -| TimeOfUseElectricityPricePeriods | Bytestring | | | RW | -| MaximumChargingPower | Number | 1 | W | RW | -| MaximumDischargingPower | Number | 1 | W | RW | -| ChargingCutoffCapacity | Number | 10 | % | RW | -| DischargeCutoffCapacity | Number | 10 | % | RW | -| ForcedChargingAndDischargingPeriod | Number | 1 | minutes | RW | -| ChargeFromGridFunction | Number | 1 | | RW | -| GridChargeCutoffSOC | Number | 10 | % | RW | -| ForcibleChargeDischarge | Number | 1 | | WO | -| FixedChargingAndDischargingPeriods | Bytestring | | | RW | -| PowerOfChargeFromGrid | Number | 0.1 | W | RW | -| MaximumPowerOfChargeFromGrid | Number | 0.1 | W | RW | -| ForcibleChargeDischargeSettingMode | Number | 1 | | RW | -| ForcibleChargePower | Number | 0.1 | W | RW | -| ForcibleDischargePower | Number | 0.1 | W | RW | -| TimeOfUseChargingAndDischargingPeriods | Bytestring | | | RW | -| ExcessPVEnergyUseInTOU | Number | 1 | | RW | -| ActivePowerControlMode | Number | 1 | | RW | -| MaximumFeedGridPowerInKW | Number | 1000 | kW | RW | -| MaximumFeedGridPowerInPercentage | Number | 10 | % | RW | -| MaximumChargeFromGridPower | Number | 0.1 | W | RW | -| SwitchToOffGrid | Number | 1 | | RW | -| VoltageInIndependentOperation | Number | 1 | | RW | -| Unit1ProductModel | Number | 1 | | RW | -| Unit1SN | String | | | RO | -| Unit1No | Number | 1 | | RW | -| Unit1SoftwareVersion | String | | | RO | -| Unit1DCDCVersion | String | | | RO | -| Unit1BMSVersion | String | | | RO | -| Unit1RunningStatus | Number | 1 | | RO | -| Unit1WorkingMode | Number | 1 | | RO | -| Unit1BusVoltage | Number | 10 | V | RO | -| Unit1BusCurrent | Number | 10 | A | RO | -| Unit1BatterySOC | Number | 10 | % | RO | -| Unit1ChargeAndDischargePower | Number | 1 | W | RO | -| Unit1RemainingChargeDischargeTime | Number | 1 | minutes | RO | -| Unit1RatedChargePower | Number | 1 | W | RO | -| Unit1RatedDischargePower | Number | 1 | W | RO | -| Unit1CurrentDayChargeCapacity | Number | 100 | kWh | RO | -| Unit1CurrentDayDischargeCapacity | Number | 100 | kWh | RO | -| Unit1TotalCharge | Number | 100 | kWh | RO | -| Unit1TotalDischarge | Number | 100 | kWh | RO | -| Unit1BatteryTemperature | Number | 10 | °C | RO | -| Unit1FaultID | Number | 1 | | RO | -| Unit2ProductModel | Number | 1 | | RW | -| Unit2SN | String | | | RO | -| Unit2No | Number | 1 | | RW | -| Unit2SoftwareVersion | String | | | RO | -| Unit2RunningStatus | Number | 1 | | RO | -| Unit2BusVoltage | Number | 10 | V | RO | -| Unit2BusCurrent | Number | 10 | A | RO | -| Unit2BatterySOC | Number | 10 | % | RO | -| Unit2ChargeAndDischargePower | Number | 1 | W | RO | -| Unit2CurrentDayChargeCapacity | Number | 100 | kWh | RO | -| Unit2CurrentDayDischargeCapacity | Number | 100 | kWh | RO | -| Unit2TotalCharge | Number | 100 | kWh | RO | -| Unit2TotalDischarge | Number | 100 | kWh | RO | -| Unit2BatteryTemperature | Number | 10 | °C | RO | -| Unit1BatteryPack1SN | String | | | RO | -| Unit1BatteryPack1No | Number | 1 | | RW | -| Unit1BatteryPack1FirmwareVersion | String | | | RO | -| Unit1BatteryPack1WorkingStatus | Number | 1 | | RO | -| Unit1BatteryPack1Voltage | Number | 10 | V | RO | -| Unit1BatteryPack1Current | Number | 10 | A | RO | -| Unit1BatteryPack1SOC | Number | 10 | % | RO | -| Unit1BatteryPack1ChargeDischargePower | Number | 1 | W | RO | -| Unit1BatteryPack1TotalCharge | Number | 100 | kWh | RO | -| Unit1BatteryPack1TotalDischarge | Number | 100 | kWh | RO | -| Unit1BatteryPack1MinimumTemperature | Number | 10 | °C | RO | -| Unit1BatteryPack1MaximumTemperature | Number | 10 | °C | RO | -| Unit1BatteryPack2SN | String | | | RO | -| Unit1BatteryPack2No | Number | 1 | | RW | -| Unit1BatteryPack2FirmwareVersion | String | | | RO | -| Unit1BatteryPack2WorkingStatus | Number | 1 | | RO | -| Unit1BatteryPack2Voltage | Number | 10 | V | RO | -| Unit1BatteryPack2Current | Number | 10 | A | RO | -| Unit1BatteryPack2SOC | Number | 10 | % | RO | -| Unit1BatteryPack2ChargeDischargePower | Number | 1 | W | RO | -| Unit1BatteryPack2TotalCharge | Number | 100 | kWh | RO | -| Unit1BatteryPack2TotalDischarge | Number | 100 | kWh | RO | -| Unit1BatteryPack2MinimumTemperature | Number | 10 | °C | RO | -| Unit1BatteryPack2MaximumTemperature | Number | 10 | °C | RO | -| Unit1BatteryPack3SN | String | | | RO | -| Unit1BatteryPack3No | Number | 1 | | RW | -| Unit1BatteryPack3FirmwareVersion | String | | | RO | -| Unit1BatteryPack3WorkingStatus | Number | 1 | | RO | -| Unit1BatteryPack3Voltage | Number | 10 | V | RO | -| Unit1BatteryPack3Current | Number | 10 | A | RO | -| Unit1BatteryPack3SOC | Number | 10 | % | RO | -| Unit1BatteryPack3ChargeDischargePower | Number | 1 | W | RO | -| Unit1BatteryPack3TotalCharge | Number | 100 | kWh | RO | -| Unit1BatteryPack3TotalDischarge | Number | 100 | kWh | RO | -| Unit1BatteryPack3MinimumTemperature | Number | 10 | °C | RO | -| Unit1BatteryPack3MaximumTemperature | Number | 10 | °C | RO | -| Unit2BatteryPack1SN | String | | | RO | -| Unit2BatteryPack1No | Number | 1 | | RW | -| Unit2BatteryPack1FirmwareVersion | String | | | RO | -| Unit2BatteryPack1WorkingStatus | Number | 1 | | RO | -| Unit2BatteryPack1Voltage | Number | 10 | V | RO | -| Unit2BatteryPack1Current | Number | 10 | A | RO | -| Unit2BatteryPack1SOC | Number | 10 | % | RO | -| Unit2BatteryPack1ChargeDischargePower | Number | 1 | W | RO | -| Unit2BatteryPack1TotalCharge | Number | 100 | kWh | RO | -| Unit2BatteryPack1TotalDischarge | Number | 100 | kWh | RO | -| Unit2BatteryPack1MinimumTemperature | Number | 10 | °C | RO | -| Unit2BatteryPack1MaximumTemperature | Number | 10 | °C | RO | -| Unit2BatteryPack2SN | String | | | RO | -| Unit2BatteryPack2No | Number | 1 | | RW | -| Unit2BatteryPack2FirmwareVersion | String | | | RO | -| Unit2BatteryPack2WorkingStatus | Number | 1 | | RO | -| Unit2BatteryPack2Voltage | Number | 10 | V | RO | -| Unit2BatteryPack2Current | Number | 10 | A | RO | -| Unit2BatteryPack2SOC | Number | 10 | % | RO | -| Unit2BatteryPack2ChargeDischargePower | Number | 1 | W | RO | -| Unit2BatteryPack2TotalCharge | Number | 100 | kWh | RO | -| Unit2BatteryPack2TotalDischarge | Number | 100 | kWh | RO | -| Unit2BatteryPack2MinimumTemperature | Number | 10 | °C | RO | -| Unit2BatteryPack2MaximumTemperature | Number | 10 | °C | RO | -| Unit2BatteryPack3SN | String | | | RO | -| Unit2BatteryPack3No | Number | 1 | | RW | -| Unit2BatteryPack3FirmwareVersion | String | | | RO | -| Unit2BatteryPack3WorkingStatus | Number | 1 | | RO | -| Unit2BatteryPack3Voltage | Number | 10 | V | RO | -| Unit2BatteryPack3Current | Number | 10 | A | RO | -| Unit2BatteryPack3SOC | Number | 10 | % | RO | -| Unit2BatteryPack3ChargeDischargePower | Number | 1 | W | RO | -| Unit2BatteryPack3TotalCharge | Number | 100 | kWh | RO | -| Unit2BatteryPack3TotalDischarge | Number | 100 | kWh | RO | -| Unit2BatteryPack3MinimumTemperature | Number | 10 | °C | RO | -| Unit2BatteryPack3MaximumTemperature | Number | 10 | °C | RO | +| Name | Type | Gain | Unit | Access Type | +|:-------------------------------------------------|:-----------|:-----|:-----|:------------| +| ProductModel | Number | 1 | | RW | +| RunningStatus | Number | 1 | | RO | +| WorkingModeSettings | Number | 1 | | RW | +| BusVoltage | Number | 10 | V | RO | +| BusCurrent | Number | 10 | A | RO | +| ChargeDischargePower | Number | 1 | W | RO | +| MaximumChargePower | Number | 1 | W | RO | +| MaximumDischargePower | Number | 1 | W | RO | +| RatedCapacity | Number | 1 | Wh | RO | +| SOC | Number | 10 | % | RO | +| BackupPowerSOC | Number | 10 | % | RW | +| TargetSOC | Number | 10 | % | RW | +| TotalCharge | Number | 100 | kWh | RO | +| TotalDischarge | Number | 100 | kWh | RO | +| CurrentDayChargeCapacity | Number | 100 | kWh | RO | +| CurrentDayDischargeCapacity | Number | 100 | kWh | RO | +| TimeOfUseElectricityPricePeriods | Bytestring | 1 | | RW | +| MaximumChargingPower | Number | 1 | W | RW | +| MaximumDischargingPower | Number | 1 | W | RW | +| ChargingCutoffCapacity | Number | 10 | % | RW | +| DischargeCutoffCapacity | Number | 10 | % | RW | +| ForcedChargingAndDischargingPeriod | Number | 1 | mins | RW | +| ForcedChargingAndDischargingPower | Number | 1 | W | RW | +| ChargeFromGridFunction | Number | 1 | | RW | +| GridChargeCutoffSOC | Number | 10 | % | RW | +| ForcibleChargeDischarge | Number | 1 | | RW | +| FixedChargingAndDischargingPeriods | Bytestring | 1 | | RW | +| PowerOfChargeFromGrid | Number | 1 | W | RW | +| MaximumPowerOfChargeFromGrid | Number | 1 | W | RW | +| ForcibleChargeDischargeSettingMode | Number | 1 | | RW | +| ForcibleChargePower | Number | 1 | W | RW | +| ForcibleDischargePower | Number | 1 | W | RW | +| TimeOfUseChargingAndDischargingPeriods | Bytestring | 1 | | RW | +| ExcessPVEnergyUseInTOU | Number | 1 | | RW | +| ActivePowerControlMode | Number | 1 | | RW | +| MaximumFeedGridPowerInKW | Number | 1 | W | RW | +| MaximumFeedGridPowerInPercentage | Number | 10 | % | RW | +| MaximumChargeFromGridPower | Number | 1 | W | RW | +| SwitchToOffGrid | Number | 1 | | RW | +| VoltageInIndependentOperation | Number | 1 | V | RW | +| SOHCalibrationStatus | Number | 1 | | RO | +| SOHCalibrationReleaseTheLowerDischargeLimitOfSOC | Number | 10 | | RO | +| SOHCalibrationEnableTheBackupPowerSOC | Number | 10 | | RO | +| Unit1ProductModel | Number | 1 | | RW | +| Unit1SN | String | 1 | | RO | +| Unit1No | Number | 1 | | RW | +| Unit1SoftwareVersion | String | 1 | | RO | +| Unit1DCDCVersion | String | 1 | | RO | +| Unit1BMSVersion | String | 1 | | RO | +| Unit1RunningStatus | Number | 1 | | RO | +| Unit1WorkingMode | Number | 1 | | RO | +| Unit1BusVoltage | Number | 10 | V | RO | +| Unit1BusCurrent | Number | 10 | A | RO | +| Unit1BatterySOC | Number | 10 | % | RO | +| Unit1ChargeAndDischargePower | Number | 1 | W | RO | +| Unit1RemainingChargeDischargeTime | Number | 1 | mins | RO | +| Unit1RatedChargePower | Number | 1 | W | RO | +| Unit1RatedDischargePower | Number | 1 | W | RO | +| Unit1CurrentDayChargeCapacity | Number | 100 | kWh | RO | +| Unit1CurrentDayDischargeCapacity | Number | 100 | kWh | RO | +| Unit1TotalCharge | Number | 100 | kWh | RO | +| Unit1TotalDischarge | Number | 100 | kWh | RO | +| Unit1BatteryTemperature | Number | 10 | °C | RO | +| Unit1FaultID | Number | 1 | | RO | +| Unit2ProductModel | Number | 1 | | RW | +| Unit2SN | Bytestring | 1 | | RO | +| Unit2No | Number | 1 | | RW | +| Unit2SoftwareVersion | String | 1 | | RO | +| Unit2RunningStatus | Number | 1 | | RO | +| Unit2BusVoltage | Number | 10 | V | RO | +| Unit2BusCurrent | Number | 10 | A | RO | +| Unit2BatterySOC | Number | 10 | % | RO | +| Unit2ChargeAndDischargePower | Number | 1 | W | RO | +| Unit2CurrentDayChargeCapacity | Number | 100 | kWh | RO | +| Unit2CurrentDayDischargeCapacity | Number | 100 | kWh | RO | +| Unit2TotalCharge | Number | 100 | kWh | RO | +| Unit2TotalDischarge | Number | 100 | kWh | RO | +| Unit2BatteryTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack1SN | String | | | RO | +| Unit1BatteryPack1No | Number | | | RW | +| Unit1BatteryPack1FirmwareVersion | String | | | RO | +| Unit1BatteryPack1WorkingStatus | Number | | | RO | +| Unit1BatteryPack1Voltage | Number | 10 | V | RO | +| Unit1BatteryPack1Current | Number | 10 | A | RO | +| Unit1BatteryPack1SOC | Number | 10 | % | RO | +| Unit1BatteryPack1ChargeDischargePower | Number | 1 | W | RO | +| Unit1BatteryPack1TotalCharge | Number | 100 | kWh | RO | +| Unit1BatteryPack1TotalDischarge | Number | 100 | kWh | RO | +| Unit1BatteryPack1MinimumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack1MaximumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack1SOHCalibrationStatus | Number | | | RO | +| Unit1BatteryPack2SN | String | | | RO | +| Unit1BatteryPack2No | Number | | | RW | +| Unit1BatteryPack2FirmwareVersion | String | | | RO | +| Unit1BatteryPack2WorkingStatus | Number | | | RO | +| Unit1BatteryPack2Voltage | Number | 10 | V | RO | +| Unit1BatteryPack2Current | Number | 10 | A | RO | +| Unit1BatteryPack2SOC | Number | 10 | % | RO | +| Unit1BatteryPack2ChargeDischargePower | Number | 1 | W | RO | +| Unit1BatteryPack2TotalCharge | Number | 100 | kWh | RO | +| Unit1BatteryPack2TotalDischarge | Number | 100 | kWh | RO | +| Unit1BatteryPack2MinimumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack2MaximumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack2SOHCalibrationStatus | Number | | | RO | +| Unit1BatteryPack3SN | String | | | RO | +| Unit1BatteryPack3No | Number | | | RW | +| Unit1BatteryPack3FirmwareVersion | String | | | RO | +| Unit1BatteryPack3WorkingStatus | Number | | | RO | +| Unit1BatteryPack3Voltage | Number | 10 | V | RO | +| Unit1BatteryPack3Current | Number | 10 | A | RO | +| Unit1BatteryPack3SOC | Number | 10 | % | RO | +| Unit1BatteryPack3ChargeDischargePower | Number | 1 | W | RO | +| Unit1BatteryPack3TotalCharge | Number | 100 | kWh | RO | +| Unit1BatteryPack3TotalDischarge | Number | 100 | kWh | RO | +| Unit1BatteryPack3MinimumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack3MaximumTemperature | Number | 10 | °C | RO | +| Unit1BatteryPack3SOHCalibrationStatus | Number | | | RO | +| Unit2BatteryPack1SN | String | | | RO | +| Unit2BatteryPack1No | Number | | | RW | +| Unit2BatteryPack1FirmwareVersion | String | | | RO | +| Unit2BatteryPack1WorkingStatus | Number | | | RO | +| Unit2BatteryPack1Voltage | Number | 10 | V | RO | +| Unit2BatteryPack1Current | Number | 10 | A | RO | +| Unit2BatteryPack1SOC | Number | 10 | % | RO | +| Unit2BatteryPack1ChargeDischargePower | Number | 1 | W | RO | +| Unit2BatteryPack1TotalCharge | Number | 100 | kWh | RO | +| Unit2BatteryPack1TotalDischarge | Number | 100 | kWh | RO | +| Unit2BatteryPack1MinimumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack1MaximumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack1SOHCalibrationStatus | Number | | | RO | +| Unit2BatteryPack2SN | String | | | RO | +| Unit2BatteryPack2No | Number | | | RW | +| Unit2BatteryPack2FirmwareVersion | String | | | RO | +| Unit2BatteryPack2WorkingStatus | Number | | | RO | +| Unit2BatteryPack2Voltage | Number | 10 | V | RO | +| Unit2BatteryPack2Current | Number | 10 | A | RO | +| Unit2BatteryPack2SOC | Number | 10 | % | RO | +| Unit2BatteryPack2ChargeDischargePower | Number | 1 | W | RO | +| Unit2BatteryPack2TotalCharge | Number | 100 | kWh | RO | +| Unit2BatteryPack2TotalDischarge | Number | 100 | kWh | RO | +| Unit2BatteryPack2MinimumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack2MaximumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack2SOHCalibrationStatus | Number | | | RO | +| Unit2BatteryPack3SN | String | | | RO | +| Unit2BatteryPack3No | Number | | | RW | +| Unit2BatteryPack3FirmwareVersion | String | | | RO | +| Unit2BatteryPack3WorkingStatus | Number | | | RO | +| Unit2BatteryPack3Voltage | Number | 10 | V | RO | +| Unit2BatteryPack3Current | Number | 10 | A | RO | +| Unit2BatteryPack3SOC | Number | 10 | % | RO | +| Unit2BatteryPack3ChargeDischargePower | Number | 1 | W | RO | +| Unit2BatteryPack3TotalCharge | Number | 100 | kWh | RO | +| Unit2BatteryPack3TotalDischarge | Number | 100 | kWh | RO | +| Unit2BatteryPack3MinimumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack3MaximumTemperature | Number | 10 | °C | RO | +| Unit2BatteryPack3SOHCalibrationStatus | Number | | | RO | ### MeterEquipmentRegister -| Name | Type | Gain | Unit | Access Type | -|---------------------------|--------|------|------|-------------| -| MeterStatus | Number | 1 | | RO | -| APhaseVoltage | Number | 10 | V | RO | -| BPhaseVoltage | Number | 10 | V | RO | -| CPhaseVoltage | Number | 10 | V | RO | -| APhaseCurrent | Number | 100 | A | RO | -| BPhaseCurrent | Number | 100 | A | RO | -| CPhaseCurrent | Number | 100 | A | RO | -| ActivePower | Number | 1 | W | RO | -| ReactivePower | Number | 1 | var | RO | -| PowerFactor | Number | 1000 | | RO | -| GridFrequency | Number | 100 | Hz | RO | -| PositiveActiveElectricity | Number | 100 | kWh | RO | -| ReverseActivePower | Number | 100 | kWh | RO | -| AccumulatedReactivePower | Number | 100 | kvar | RO | -| MeterType | Number | 1 | | RO | -| ABLineVoltage | Number | 10 | V | RO | -| BCLineVoltage | Number | 10 | V | RO | -| CALineVoltage | Number | 10 | V | RO | -| APhaseActivePower | Number | 1 | W | RO | -| BPhaseActivePower | Number | 1 | W | RO | -| CPhaseActivePower | Number | 1 | W | RO | -| MeterModelDetectionResult | Number | 1 | | RO | +| Name | Type | Gain | Unit | Access Type | +|:--------------------------|:-------|:-----|:------|:------------| +| MeterType | Number | 1 | | RO | +| MeterStatus | Number | 1 | | RO | +| MeterModelDetectionResult | Number | 1 | | RO | +| APhaseVoltage | Number | 10 | V | RO | +| BPhaseVoltage | Number | 10 | V | RO | +| CPhaseVoltage | Number | 10 | V | RO | +| APhaseCurrent | Number | 100 | A | RO | +| BPhaseCurrent | Number | 100 | A | RO | +| CPhaseCurrent | Number | 100 | A | RO | +| ActivePower | Number | 1 | W | RO | +| ReactivePower | Number | 1 | var | RO | +| PowerFactor | Number | 1000 | | RO | +| GridFrequency | Number | 100 | Hz | RO | +| PositiveActiveElectricity | Number | 100 | kWh | RO | +| ReverseActivePower | Number | 100 | kWh | RO | +| AccumulatedReactivePower | Number | 100 | kvarh | RO | +| ABLineVoltage | Number | 10 | V | RO | +| BCLineVoltage | Number | 10 | V | RO | +| CALineVoltage | Number | 10 | V | RO | +| APhaseActivePower | Number | 1 | W | RO | +| BPhaseActivePower | Number | 1 | W | RO | +| CPhaseActivePower | Number | 1 | W | RO | diff --git a/helpers/doc-generator/.gitignore b/helpers/doc-generator/.gitignore new file mode 100644 index 0000000..53752db --- /dev/null +++ b/helpers/doc-generator/.gitignore @@ -0,0 +1 @@ +output diff --git a/helpers/doc-generator/main.py b/helpers/doc-generator/main.py new file mode 100644 index 0000000..3ef98aa --- /dev/null +++ b/helpers/doc-generator/main.py @@ -0,0 +1,35 @@ +from sun2000_modbus.datatypes import DataType +from sun2000_modbus.registers import InverterEquipmentRegister, BatteryEquipmentRegister, MeterEquipmentRegister + + +datatype_mapping = { + DataType.STRING: 'String', + DataType.UINT16_BE: 'Number', + DataType.UINT32_BE: 'Number', + DataType.INT16_BE: 'Number', + DataType.INT32_BE: 'Number', + DataType.BITFIELD16: 'Binary String/Bitfield', + DataType.BITFIELD32: 'Binary String/Bitfield', + DataType.MULTIDATA: 'Bytestring', +} + + +def omit_none(gain) -> str: + if gain: + return gain + return '' + +def process_register(register, filename) -> None: + with open(f'output/{filename}', 'w') as inverter_docs: + inverter_docs.write('| Name | Type | Gain | Unit | Access Type |\n') + inverter_docs.write('| :- | :- | :- | :- | :- |\n') + for item in register: + inverter_docs.write(f'| {item.name} | {datatype_mapping[item.value.data_type]} | {omit_none(item.value.gain)} | {omit_none(item.value.unit)} | {item.value.access_type.name} |\n') + +def main() -> None: + process_register(InverterEquipmentRegister, 'inverter.md') + process_register(BatteryEquipmentRegister, 'battery.md') + process_register(MeterEquipmentRegister, 'meter.md') + +if __name__ == "__main__": + main() diff --git a/register-parser/.gitignore b/helpers/register-parser/.gitignore similarity index 100% rename from register-parser/.gitignore rename to helpers/register-parser/.gitignore diff --git a/register-parser/register-parser.ipynb b/helpers/register-parser/register-parser.ipynb similarity index 99% rename from register-parser/register-parser.ipynb rename to helpers/register-parser/register-parser.ipynb index 0114a0d..66d042b 100644 --- a/register-parser/register-parser.ipynb +++ b/helpers/register-parser/register-parser.ipynb @@ -53,7 +53,7 @@ "import tabula\n", "from IPython.display import display\n", "\n", - "pdf_path = '../docs/modbus.pdf'\n", + "pdf_path = '../../docs/modbus.pdf'\n", "register_pages = '78-80'\n", "old_registers_file_path = 'input/registers.txt'\n", "output_file_path = 'output/powermeter.txt'\n", diff --git a/register-parser/requirements.txt b/helpers/register-parser/requirements.txt similarity index 100% rename from register-parser/requirements.txt rename to helpers/register-parser/requirements.txt From 57d2e5bdc8df407a6636318e28a5ad458a33abd6 Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Sat, 6 Dec 2025 22:29:08 +0100 Subject: [PATCH 09/15] #48: Small fixes --- README.md | 18 +++++++++--------- helpers/register-parser/requirements.txt | 2 +- sun2000_modbus/registers.py | 22 +++++++++++----------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 8978d1e..dccb3d9 100644 --- a/README.md +++ b/README.md @@ -102,14 +102,14 @@ The following registers are provided by the Sun2000's Modbus interface and can b | OfferingNameOfSouthboundDevice1 | String | | | RO | | OfferingNameOfSouthboundDevice2 | String | | | RO | | OfferingNameOfSouthboundDevice3 | String | | | RO | -| HardwareVersion | String | 1 | | RO | +| HardwareVersion | String | | | RO | | MonitoringBoardSN | String | | | RO | | MonitoringSoftwareVersion | String | | | RO | | MasterDSPVersion | String | | | RO | | CPLDVersion | String | | | RO | | AFCIVersion | String | | | RO | | DCMBUSVersion | String | | | RO | -| REGKEY | String | 1 | | RO | +| REGKEY | String | | | RO | | State1 | Binary String/Bitfield | | | RO | | State2 | Binary String/Bitfield | | | RO | | State3 | Binary String/Bitfield | | | RO | @@ -190,21 +190,21 @@ The following registers are provided by the Sun2000's Modbus interface and can b | AccumulatedEnergyYield | Number | 100 | kWh | RO | | DailyEnergyYield | Number | 100 | kWh | RO | | ManagementSystemStatus | Number | 1 | | RO | -| AuthorizationFunction | Binary String/Bitfield | 1 | | RO | +| AuthorizationFunction | Binary String/Bitfield | | | RO | | LicenseStatus | Number | | | RO | | LicenseExpirationTime | Number | 1 | s | RO | | LicenseLoadingTime | Number | 1 | s | RO | | LicenseRevocationTime | Number | 1 | s | RO | -| LicenseSN | String | 1 | | RO | -| RevocationCode | String | 1 | | RO | +| LicenseSN | String | | | RO | +| RevocationCode | String | | | RO | | ModuleStatus4G | Number | | | RO | | IPAddress4G | Number | 1 | | RO | | SubnetMask4G | Number | 1 | | RO | -| IMEI4G | String | 1 | | RO | +| IMEI4G | String | | | RO | | SignalStrength4G | Number | 1 | | RO | | MaximumNumberOfPINAttempts4G | Number | 1 | | RO | | PINVerificationStatus4G | Number | | | RO | -| OriginalModelName | Bytestring | 1 | | RO | +| OriginalModelName | Bytestring | | | RO | | ActiveAdjustmentMode | Bytestring | | | RO | | ReactiveAdjustmentMode | Bytestring | | | RO | | ChargeDischargeMode | Number | | | RO | @@ -232,7 +232,7 @@ The following registers are provided by the Sun2000's Modbus interface and can b | ReactivePowerChangeGradient | Number | 1000 | %/s | RW | | ActivePowerChangeGradient | Number | 1000 | %/s | RW | | ScheduleInstructionValidDuration | Number | 1 | s | RW | -| AcivePowerLimit | Number | 1 | W | RW | +| ActivePowerLimit | Number | 1 | W | RW | | TimeZone | Number | 1 | min | RW | | TLSEncryption | Number | | | RW | | WLANWakeup | Number | | | RW | @@ -319,7 +319,7 @@ The following registers are provided by the Sun2000's Modbus interface and can b | Unit1BatteryTemperature | Number | 10 | °C | RO | | Unit1FaultID | Number | 1 | | RO | | Unit2ProductModel | Number | 1 | | RW | -| Unit2SN | Bytestring | 1 | | RO | +| Unit2SN | String | 1 | | RO | | Unit2No | Number | 1 | | RW | | Unit2SoftwareVersion | String | 1 | | RO | | Unit2RunningStatus | Number | 1 | | RO | diff --git a/helpers/register-parser/requirements.txt b/helpers/register-parser/requirements.txt index d6a152f..fec379d 100644 --- a/helpers/register-parser/requirements.txt +++ b/helpers/register-parser/requirements.txt @@ -1,3 +1,3 @@ tabula-py pandas -ipython \ No newline at end of file +ipython diff --git a/sun2000_modbus/registers.py b/sun2000_modbus/registers.py index 724d072..8c0d170 100644 --- a/sun2000_modbus/registers.py +++ b/sun2000_modbus/registers.py @@ -44,14 +44,14 @@ class InverterEquipmentRegister(Enum): OfferingNameOfSouthboundDevice1 = Register(30561, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) OfferingNameOfSouthboundDevice2 = Register(30576, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) OfferingNameOfSouthboundDevice3 = Register(30591, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - HardwareVersion = Register(31000, 15, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + HardwareVersion = Register(31000, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) MonitoringBoardSN = Register(31015, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) MonitoringSoftwareVersion = Register(31025, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) MasterDSPVersion = Register(31040, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) CPLDVersion = Register(31070, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) AFCIVersion = Register(31085, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) DCMBUSVersion = Register(31115, 15, datatypes.DataType.STRING, None, None, AccessType.RO, None) - REGKEY = Register(31200, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + REGKEY = Register(31200, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) State1 = Register(32000, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) State2 = Register(32002, 1, datatypes.DataType.BITFIELD16, None, None, AccessType.RO, None) State3 = Register(32003, 2, datatypes.DataType.BITFIELD32, None, None, AccessType.RO, None) @@ -132,21 +132,21 @@ class InverterEquipmentRegister(Enum): AccumulatedEnergyYield = Register(32106, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) DailyEnergyYield = Register(32114, 2, datatypes.DataType.UINT32_BE, 100, 'kWh', AccessType.RO, None) ManagementSystemStatus = Register(35127, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) - AuthorizationFunction = Register(35136, 2, datatypes.DataType.BITFIELD32, 1, None, AccessType.RO, None) + AuthorizationFunction = Register(35136, 2, datatypes.DataType.BITFIELD32, None, None, AccessType.RO, None) LicenseStatus = Register(35138, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, mappings.LicenseStatus) LicenseExpirationTime = Register(35139, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) LicenseLoadingTime = Register(35141, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) LicenseRevocationTime = Register(35143, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RO, None) - LicenseSN = Register(35145, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) - RevocationCode = Register(35155, 64, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + LicenseSN = Register(35145, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) + RevocationCode = Register(35155, 64, datatypes.DataType.STRING, None, None, AccessType.RO, None) ModuleStatus4G = Register(35249, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, mappings.ModuleStatus4G) IPAddress4G = Register(35250, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) SubnetMask4G = Register(35252, 2, datatypes.DataType.UINT32_BE, 1, None, AccessType.RO, None) - IMEI4G = Register(35254, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) + IMEI4G = Register(35254, 10, datatypes.DataType.STRING, None, None, AccessType.RO, None) SignalStrength4G = Register(35264, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) MaximumNumberOfPINAttempts4G = Register(35265, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, None) PINVerificationStatus4G = Register(35266, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, mappings.PINVerificationStatus4G) - OriginalModelName = Register(35268, 15, datatypes.DataType.MULTIDATA, 1, None, AccessType.RO, None) + OriginalModelName = Register(35268, 15, datatypes.DataType.MULTIDATA, None, None, AccessType.RO, None) ActiveAdjustmentMode = Register(35300, 4, datatypes.DataType.MULTIDATA, None, None, AccessType.RO, None) ReactiveAdjustmentMode = Register(35304, 4, datatypes.DataType.MULTIDATA, None, None, AccessType.RO, None) ChargeDischargeMode = Register(37006, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RO, mappings.ChargeDischargeMode) @@ -157,7 +157,7 @@ class InverterEquipmentRegister(Enum): SystemTime = Register(40000, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RW, None) QUCharacteristicCurveMode = Register(40037, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, mappings.QUCharacteristicCurveMode) QUDispatchTriggerPower = Register(40038, 1, datatypes.DataType.INT16_BE, 1, '%', AccessType.RW, None) - FixedActivePowerDeratedInKW = Register(40120, 1, datatypes.DataType.UINT16_BE, 1, 'W', AccessType.RW, None) + FixedActivePowerDeratedInKW = Register(40120, 1, datatypes.DataType.UINT16_BE, 10, 'kW', AccessType.RW, None) ReactivePowerCompensationInPF = Register(40122, 1, datatypes.DataType.INT16_BE, 1000, None, AccessType.RW, None) ReactivePowerCompensationQS = Register(40123, 1, datatypes.DataType.INT16_BE, 1000, None, AccessType.RW, None) ActivePowerPercentageDerating = Register(40125, 1, datatypes.DataType.INT16_BE, 10, '%', AccessType.RW, None) @@ -174,7 +174,7 @@ class InverterEquipmentRegister(Enum): ReactivePowerChangeGradient = Register(42015, 2, datatypes.DataType.UINT32_BE, 1000, '%/s', AccessType.RW, None) ActivePowerChangeGradient = Register(42017, 2, datatypes.DataType.UINT32_BE, 1000, '%/s', AccessType.RW, None) ScheduleInstructionValidDuration = Register(42019, 2, datatypes.DataType.UINT32_BE, 1, 's', AccessType.RW, None) - AcivePowerLimit = Register(42405, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) + ActivePowerLimit = Register(42405, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) TimeZone = Register(43006, 1, datatypes.DataType.INT16_BE, 1, 'min', AccessType.RW, None) TLSEncryption = Register(43098, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) WLANWakeup = Register(45052, 1, datatypes.DataType.UINT16_BE, None, None, AccessType.RW, None) @@ -230,7 +230,7 @@ class BatteryEquipmentRegister(Enum): TimeOfUseChargingAndDischargingPeriods = Register(47255, 43, datatypes.DataType.MULTIDATA, 1, None, AccessType.RW, None) ExcessPVEnergyUseInTOU = Register(47299, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ExcessPVEnergyUseInTOU) ActivePowerControlMode = Register(47415, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ActivePowerControlMode) - MaximumFeedGridPowerInKW = Register(47416, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) + MaximumFeedGridPowerInW = Register(47416, 2, datatypes.DataType.INT32_BE, 1, 'W', AccessType.RW, None) MaximumFeedGridPowerInPercentage = Register(47418, 1, datatypes.DataType.INT16_BE, 10, '%', AccessType.RW, None) MaximumChargeFromGridPower = Register(47590, 2, datatypes.DataType.UINT32_BE, 1, 'W', AccessType.RW, None) SwitchToOffGrid = Register(47604, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) @@ -264,7 +264,7 @@ class BatteryEquipmentRegister(Enum): # Unit 2 Unit2ProductModel = Register(47089, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, mappings.ProductModel) - Unit2SN = Register(37700, 10, datatypes.DataType.MULTIDATA, 1, None, AccessType.RO, None) + Unit2SN = Register(37700, 10, datatypes.DataType.STRING, 1, None, AccessType.RO, None) Unit2No = Register(47108, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RW, None) Unit2SoftwareVersion = Register(37799, 15, datatypes.DataType.STRING, 1, None, AccessType.RO, None) Unit2RunningStatus = Register(37741, 1, datatypes.DataType.UINT16_BE, 1, None, AccessType.RO, mappings.RunningStatus) From be67a9f453a2ec82e4f2ab3b307050d9f778af7d Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Sat, 6 Dec 2025 22:30:38 +0100 Subject: [PATCH 10/15] #48: Small fixes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dccb3d9..8a80d84 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,7 @@ The following registers are provided by the Sun2000's Modbus interface and can b | SystemTime | Number | 1 | s | RW | | QUCharacteristicCurveMode | Number | | | RW | | QUDispatchTriggerPower | Number | 1 | % | RW | -| FixedActivePowerDeratedInKW | Number | 1 | W | RW | +| FixedActivePowerDeratedInKW | Number | 10 | kW | RW | | ReactivePowerCompensationInPF | Number | 1000 | | RW | | ReactivePowerCompensationQS | Number | 1000 | | RW | | ActivePowerPercentageDerating | Number | 10 | % | RW | From 117a04482a20d30f46934b3421494b5024df4a2c Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Sat, 6 Dec 2025 22:32:49 +0100 Subject: [PATCH 11/15] #48: Small fixes --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a80d84..56d3999 100644 --- a/README.md +++ b/README.md @@ -289,7 +289,7 @@ The following registers are provided by the Sun2000's Modbus interface and can b | TimeOfUseChargingAndDischargingPeriods | Bytestring | 1 | | RW | | ExcessPVEnergyUseInTOU | Number | 1 | | RW | | ActivePowerControlMode | Number | 1 | | RW | -| MaximumFeedGridPowerInKW | Number | 1 | W | RW | +| MaximumFeedGridPowerInW | Number | 1 | W | RW | | MaximumFeedGridPowerInPercentage | Number | 10 | % | RW | | MaximumChargeFromGridPower | Number | 1 | W | RW | | SwitchToOffGrid | Number | 1 | | RW | From aa72f01f36b92387568e41f10f53e000f507c54c Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Sat, 6 Dec 2025 22:34:34 +0100 Subject: [PATCH 12/15] #48: Fixed test --- tests/test_sun2000_modbus.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_sun2000_modbus.py b/tests/test_sun2000_modbus.py index ee67d3d..da50b7d 100644 --- a/tests/test_sun2000_modbus.py +++ b/tests/test_sun2000_modbus.py @@ -643,7 +643,7 @@ def test_write_int16be(self, write_registers_mock): ) def test_write_int32be(self, write_registers_mock): self.test_inverter.connect() - self.test_inverter.write(BatteryEquipmentRegister.MaximumFeedGridPowerInKW, -10200) + self.test_inverter.write(BatteryEquipmentRegister.MaximumFeedGridPowerInW, -10200) write_registers_mock.assert_called_once_with(address=47416, values=[65535, 55336], device_id=1) @patch( From ef6e60179c80eec54d342674f21807c5e2b88273 Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Sat, 6 Dec 2025 23:05:35 +0100 Subject: [PATCH 13/15] #48: Increased resilience for string readings, improved error logging --- sun2000_modbus/inverter.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sun2000_modbus/inverter.py b/sun2000_modbus/inverter.py index 5be1605..d8e32bf 100644 --- a/sun2000_modbus/inverter.py +++ b/sun2000_modbus/inverter.py @@ -60,13 +60,16 @@ def read_raw_value(self, register, device_id=None): except ConnectionException: logger.error('A connection error occurred') raise + except Exception: + logger.error(f'An error occurred during reading address {register.value.address}') + raise return datatypes.decode(register_value.encode()[1:], register.value.data_type) def read(self, register, device_id=None): raw_value = self.read_raw_value(register, device_id) - if register.value.gain is None: + if register.value.gain is None or register.value.gain == 1: return raw_value else: return raw_value / register.value.gain @@ -105,6 +108,9 @@ def read_range(self, start_address, quantity=0, end_address=0, device_id=None): except ConnectionException: logger.error('A connection error occurred') raise + except Exception: + logger.error(f'An error occurred during reading address {register.value.address}') + raise return datatypes.decode(register_range_value.encode()[1:], datatypes.DataType.MULTIDATA) From c74c3b388501921526df88294065f29a41825791 Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Sat, 6 Dec 2025 23:15:46 +0100 Subject: [PATCH 14/15] #48: Increased resilience for string readings, improved error logging --- sun2000_modbus/inverter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sun2000_modbus/inverter.py b/sun2000_modbus/inverter.py index d8e32bf..510b864 100644 --- a/sun2000_modbus/inverter.py +++ b/sun2000_modbus/inverter.py @@ -5,6 +5,7 @@ from pymodbus.exceptions import ModbusIOException, ConnectionException from . import datatypes +from .datatypes import DataType from .registers import AccessType logger = logging.getLogger(__name__) @@ -69,7 +70,7 @@ def read_raw_value(self, register, device_id=None): def read(self, register, device_id=None): raw_value = self.read_raw_value(register, device_id) - if register.value.gain is None or register.value.gain == 1: + if register.value.gain is None or register.value.data_type in [DataType.STRING, DataType.BITFIELD16, DataType.BITFIELD32, DataType.MULTIDATA]: return raw_value else: return raw_value / register.value.gain @@ -109,7 +110,7 @@ def read_range(self, start_address, quantity=0, end_address=0, device_id=None): logger.error('A connection error occurred') raise except Exception: - logger.error(f'An error occurred during reading address {register.value.address}') + logger.error(f'An error occurred during reading starting from address {start_address}') raise return datatypes.decode(register_range_value.encode()[1:], datatypes.DataType.MULTIDATA) From 58f020784c90df66dd400f557bbfc4fd8f456124 Mon Sep 17 00:00:00 2001 From: Oliver Gregorius Date: Sun, 7 Dec 2025 21:21:41 +0100 Subject: [PATCH 15/15] #48: Added undocumented 0x030C for DeviceStatus mapping --- sun2000_modbus/mappings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sun2000_modbus/mappings.py b/sun2000_modbus/mappings.py index 373f3f6..130fbc7 100644 --- a/sun2000_modbus/mappings.py +++ b/sun2000_modbus/mappings.py @@ -17,6 +17,8 @@ 0x0307: 'Shutdown: rapid shutdown', 0x030A: 'Shutdown: commanded rapid shutdown', 0x030B: 'Shutdown: the backup power system is abnormal', + # 0x030C is not officially documented, yet, but the Huawei support declared it as a valid status (https://www.photovoltaikforum.com/thread/160098-what-s-up-huawei/?postID=4478596#post4478596) + 0x030C: 'Shutdown: battery end-of-discharge SOC', 0x0401: 'Grid scheduling: cosPhi-P curve', 0x0402: 'Grid scheduling: Q-U curve', 0x0403: 'Grid scheduling: PF-U curve',