From 577596d9f30cd6116a5a3221f8c9ebeb8b5fde14 Mon Sep 17 00:00:00 2001 From: Norbert Date: Fri, 12 Jul 2024 12:13:55 +0200 Subject: [PATCH] update to 1.0.2 --- .DS_Store | Bin 6148 -> 6148 bytes debug.py | 6 +- solax.py | 14 +- source/.DS_Store | Bin 6148 -> 6148 bytes source/README.md | 6 +- source/UNKNOWN.egg-info/PKG-INFO | 11 - source/UNKNOWN.egg-info/SOURCES.txt | 8 - source/UNKNOWN.egg-info/top_level.txt | 1 - source/database/collect_stats.py | 108 - source/database/data_source_db.py | 19 + source/database/mysql_data_source.py | 89 + source/database/read_and_save.py | 93 + source/database/schema.sql | 59 +- source/dist/solaxx3-1.0.2-py3-none-any.whl | Bin 0 -> 14473 bytes source/dist/solaxx3-1.0.2.tar.gz | Bin 0 -> 17349 bytes source/example.py | 13 +- source/pyproject.toml | 7 +- source/src/.DS_Store | Bin 6148 -> 6148 bytes source/src/solaxx3.egg-info/PKG-INFO | 82 + source/src/solaxx3.egg-info/SOURCES.txt | 16 + .../solaxx3.egg-info}/dependency_links.txt | 0 source/src/solaxx3.egg-info/requires.txt | 1 + source/src/solaxx3.egg-info/top_level.txt | 1 + source/src/solaxx3/__init__.py | 5 +- source/src/solaxx3/registers.py | 1619 -------- source/src/solaxx3/rs485.py | 168 - source/src/solaxx3/solax_registers_info.py | 3570 +++++++++++++++++ source/src/solaxx3/solaxx3.py | 198 + source/src/solaxx3/utils.py | 18 + source/tests/final_result.py | 361 ++ source/tests/mock_packages/mysql/__init__.py | 0 .../tests/mock_packages/mysql/connection.py | 51 + source/tests/mock_packages/mysql/connector.py | 10 + source/tests/mock_packages/mysql/cursor.py | 56 + .../mock_packages/mysql/cursor_config.json | 4 + source/tests/mock_packages/mysql/error.py | 5 + .../mock_packages/mysql/open_connections.txt | 1 + .../tests/mock_packages/pymodbus/__init__.py | 0 source/tests/mock_packages/pymodbus/client.py | 30 + .../pymodbus/registers_output.py | 806 ++++ source/tests/mock_packages/pymodbus/utils.py | 11 + source/tests/test_mysql_connector.py | 68 + source/tests/test_register_data.py | 254 ++ source/tests/test_solaxx3.py | 48 + 44 files changed, 5860 insertions(+), 1957 deletions(-) delete mode 100644 source/UNKNOWN.egg-info/PKG-INFO delete mode 100644 source/UNKNOWN.egg-info/SOURCES.txt delete mode 100644 source/UNKNOWN.egg-info/top_level.txt delete mode 100644 source/database/collect_stats.py create mode 100644 source/database/data_source_db.py create mode 100644 source/database/mysql_data_source.py create mode 100644 source/database/read_and_save.py create mode 100644 source/dist/solaxx3-1.0.2-py3-none-any.whl create mode 100644 source/dist/solaxx3-1.0.2.tar.gz create mode 100644 source/src/solaxx3.egg-info/PKG-INFO create mode 100644 source/src/solaxx3.egg-info/SOURCES.txt rename source/{UNKNOWN.egg-info => src/solaxx3.egg-info}/dependency_links.txt (100%) create mode 100644 source/src/solaxx3.egg-info/requires.txt create mode 100644 source/src/solaxx3.egg-info/top_level.txt delete mode 100644 source/src/solaxx3/registers.py delete mode 100644 source/src/solaxx3/rs485.py create mode 100644 source/src/solaxx3/solax_registers_info.py create mode 100644 source/src/solaxx3/solaxx3.py create mode 100644 source/src/solaxx3/utils.py create mode 100644 source/tests/final_result.py create mode 100644 source/tests/mock_packages/mysql/__init__.py create mode 100644 source/tests/mock_packages/mysql/connection.py create mode 100644 source/tests/mock_packages/mysql/connector.py create mode 100644 source/tests/mock_packages/mysql/cursor.py create mode 100644 source/tests/mock_packages/mysql/cursor_config.json create mode 100644 source/tests/mock_packages/mysql/error.py create mode 100644 source/tests/mock_packages/mysql/open_connections.txt create mode 100644 source/tests/mock_packages/pymodbus/__init__.py create mode 100644 source/tests/mock_packages/pymodbus/client.py create mode 100644 source/tests/mock_packages/pymodbus/registers_output.py create mode 100644 source/tests/mock_packages/pymodbus/utils.py create mode 100644 source/tests/test_mysql_connector.py create mode 100644 source/tests/test_register_data.py create mode 100644 source/tests/test_solaxx3.py diff --git a/.DS_Store b/.DS_Store index e37503dac365f36929e71ec8f1676e3903088001..8881bdd43990aae8c1b85f76a2af4fb054b2b416 100644 GIT binary patch delta 14 VcmZoMXffEJ!pO+5S(Wj=7yuv>1PuTH delta 14 VcmZoMXffEJ!pO+DS(Wj=7yuv{1P%ZI diff --git a/debug.py b/debug.py index 22708ff..31e14dd 100755 --- a/debug.py +++ b/debug.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 -from solaxx3.rs485 import SolaxX3 +#from solaxx3.rs485 import SolaxX3 +from solaxx3.solaxx3 import SolaxX3 + solax_items=[ "power_dc1", "power_dc2", @@ -88,7 +90,7 @@ if s.connect(): echarge_today = s.read("echarge_today")[0] energy_from_grid = s.read("energy_from_grid_meter")[0] energy_to_grid = s.read("energy_to_grid_meter")[0] - power_to_ev = s.read("power_to_ev")[0] +# power_to_ev = s.read("power_to_ev")[0] feed_in_power = s.read("feed_in_power")[0] output_energy_charge = s.read("output_energy_charge")[0] output_energy_today = s.read("output_energy_charge_today")[0] diff --git a/solax.py b/solax.py index 446e4fa..4bdc49b 100755 --- a/solax.py +++ b/solax.py @@ -1,4 +1,6 @@ -from solaxx3.rs485 import SolaxX3 +#from solaxx3.rs485 import SolaxX3 +from solaxx3.solaxx3 import SolaxX3 + from influxdb_client import InfluxDBClient, Point, WritePrecision from influxdb_client.client.write_api import SYNCHRONOUS token = "Wt0uDqp1gLr7Qe6qoUAohD8ciBva0kCRajHEFtRGDhSY5-TI2ASPJmqlQTUrcK-61rUAVedwiiLu5Iy-G41ByQ==" @@ -44,8 +46,9 @@ solax_items=[ "grid_voltage_r_meter", "grid_voltage_s_meter", "grid_voltage_t_meter", -"firmware_version_dsp", -"firmware_version_arm" +"firmware_version_dsp_minor", +"firmware_version_arm_minor", +"work_mode" ] @@ -83,7 +86,8 @@ openhab_items=[ "Solax_GridVoltage_2_meter", "Solax_GridVoltage_3_meter", "Solax_FW_DSP", -"Solax_FW_ARM" +"Solax_FW_ARM", +"Solax_WorkMode" ] # adjust the serial port and baud rate as necessary @@ -109,7 +113,7 @@ if s.connect(): echarge_today = s.read("echarge_today")[0] energy_from_grid = s.read("energy_from_grid_meter")[0] energy_to_grid = s.read("energy_to_grid_meter")[0] - power_to_ev = s.read("power_to_ev")[0] + #power_to_ev = s.read("power_to_ev")[0] feed_in_power = s.read("feed_in_power")[0] output_energy_charge = s.read("output_energy_charge")[0] output_energy_today = s.read("output_energy_charge_today")[0] diff --git a/source/.DS_Store b/source/.DS_Store index 9a874b5768f336915163bb88cd434575b859f936..58f97072d1a2b2d8032846f954106a3d9e6ac8eb 100644 GIT binary patch delta 185 zcmZoMXfc=|&e%RNQH+&?fq{WzVxovN6OaJ{%s|Y@z#zcDkdjiIoRgHFpEEI0GaMw$ z%8PNQS8f2?;QSGWaog1F=6tIFQt1NM%R|LS3NlJccxee3;^m qiK^_A4MbQrb93-;Ft%?@{LVa?Uqn$5B?yt|g`b delta 109 zcmZoMXfc=|&Zs)EP}qc#fq{XUp_rkFAvvWuIVUMUKL;cP224;IBml$$3{d%vAH~=w jF;3dd&LP0TsJijvcjn3bB8q|_WeG@H8a6wM9A*XphTas$ diff --git a/source/README.md b/source/README.md index 1732c15..c8c5941 100644 --- a/source/README.md +++ b/source/README.md @@ -1,3 +1,7 @@ +![Build badge](https://github.com/mkfam7/solaxx3/actions/workflows/python-package.yml/badge.svg) + + + # solax-x3 #### Read in real-time all parameters provided by Solax X3 solar inverter via its Modbus S-485 serial interface. @@ -21,7 +25,7 @@ pip install solaxx3 ``` -from solaxx3.rs485 import SolaxX3 +from solaxx3.solaxx3 import SolaxX3 # adjust the serial port and baud rate as necessary s = SolaxX3(port="/dev/ttyUSB0", baudrate=115200) diff --git a/source/UNKNOWN.egg-info/PKG-INFO b/source/UNKNOWN.egg-info/PKG-INFO deleted file mode 100644 index a89f0fc..0000000 --- a/source/UNKNOWN.egg-info/PKG-INFO +++ /dev/null @@ -1,11 +0,0 @@ -Metadata-Version: 2.1 -Name: UNKNOWN -Version: 0.0.0 -Summary: UNKNOWN -Home-page: UNKNOWN -License: UNKNOWN -Platform: UNKNOWN -License-File: LICENSE - -UNKNOWN - diff --git a/source/UNKNOWN.egg-info/SOURCES.txt b/source/UNKNOWN.egg-info/SOURCES.txt deleted file mode 100644 index bc91d28..0000000 --- a/source/UNKNOWN.egg-info/SOURCES.txt +++ /dev/null @@ -1,8 +0,0 @@ -LICENSE -README.md -pyproject.toml -setup.py -UNKNOWN.egg-info/PKG-INFO -UNKNOWN.egg-info/SOURCES.txt -UNKNOWN.egg-info/dependency_links.txt -UNKNOWN.egg-info/top_level.txt \ No newline at end of file diff --git a/source/UNKNOWN.egg-info/top_level.txt b/source/UNKNOWN.egg-info/top_level.txt deleted file mode 100644 index 8b13789..0000000 --- a/source/UNKNOWN.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/source/database/collect_stats.py b/source/database/collect_stats.py deleted file mode 100644 index 40009e5..0000000 --- a/source/database/collect_stats.py +++ /dev/null @@ -1,108 +0,0 @@ -from datetime import datetime, timedelta -from pymodbus.client import ModbusSerialClient -from solaxx3.rs485 import SolaxX3 -import mysql.connector - -s = SolaxX3(port="/dev/ttyUSB0", baudrate=115200) - -database_ip = "172.17.7.77" - -if s.connect(): - s.read_all_registers() - - # read the stats from the inverter - battery_capacity = s.read("battery_capacity")[0] - feed_in_today = s.read("feed_in_energy_today")[0] - consumtion_today = s.read("consumption_energy_today")[0] - battery_charging = s.read("battery_power_charge1")[0] - grid_voltage_r = s.read("grid_voltage_r")[0] - grid_voltage_s = s.read("grid_voltage_s")[0] - grid_voltage_t = s.read("grid_voltage_t")[0] - run_mode = s.read("run_mode")[0] - time_count_down = s.read("time_count_down")[0] - inverter_ac_power = s.read("grid_power")[0] - etoday_togrid = s.read("energy_to_grid_today")[0] - solar_energy_today = s.read("solar_energy_today")[0] - echarge_today = s.read("echarge_today")[0] - energy_from_grid = s.read("energy_from_grid_meter")[0] - energy_to_grid = s.read("energy_to_grid_meter")[0] - power_to_ev = s.read("power_to_ev")[0] - feed_in_power = s.read("feed_in_power")[0] - output_energy_charge = s.read("output_energy_charge")[0] - output_energy_today = s.read("output_energy_charge_today")[0] - input_energy_today = s.read("input_energy_charge_today")[0] - power_dc1 = s.read("power_dc1")[0] - power_dc2 = s.read("power_dc2")[0] - total_power = power_dc1 + power_dc2 - uploadTime = s.read("rtc_datetime")[0] - uploadDate = uploadTime.date() - - timezone_difference_from_utc = 2 - uploadTime = uploadTime - timedelta(hours=timezone_difference_from_utc, minutes=0) - - # store the stats in the database - mydb = mysql.connector.connect( - host=database_ip, user="root", passwd="rootroot", database="solax" - ) - mycursor = mydb.cursor() - - try: - - # create the sql statement - sql = """REPLACE INTO solax_local ( - uploadTime, - inverter_status, - dc_solar_power, - grid_voltage_r, - grid_voltage_s, - grid_voltage_t, - battery_capacity, - battery_power, - feed_in_power, - time_count_down, - inverter_ac_power, - consumeenergy, - feedinenergy, - power_dc1, - power_dc2 - ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) - """ - - values = ( - uploadTime, - run_mode, - total_power, - grid_voltage_r, - grid_voltage_s, - grid_voltage_t, - battery_capacity, - battery_charging, - feed_in_power, - time_count_down, - inverter_ac_power, - energy_from_grid, - energy_to_grid, - power_dc1, - power_dc2, - ) - - mycursor.execute(sql, values) - mydb.commit() - - # update daily values - sql = """REPLACE INTO solax_daily ( - uploadDate, - feed_in, - total_yield - ) VALUES (%s, %s, %s) - """ - values = (uploadDate, feed_in_today, etoday_togrid) - mycursor.execute(sql, values) - mydb.commit() - - except mysql.connector.Error as error: - print("parameterized query failed {}".format(error)) - -else: - print("Cannot connect to the Modbus Server/Slave") - exit() diff --git a/source/database/data_source_db.py b/source/database/data_source_db.py new file mode 100644 index 0000000..5c30083 --- /dev/null +++ b/source/database/data_source_db.py @@ -0,0 +1,19 @@ +"""Module containing an abstract class.""" + +from abc import ABC, abstractmethod + + +class DataSourceDb(ABC): + """Abstract class representing a data source.""" + + @abstractmethod + def save_record(self): + """Transfer some data.""" + + raise NotImplementedError + + @abstractmethod + def bulk_save(self): + """Transfer multiple records of data.""" + + raise NotImplementedError diff --git a/source/database/mysql_data_source.py b/source/database/mysql_data_source.py new file mode 100644 index 0000000..60397f3 --- /dev/null +++ b/source/database/mysql_data_source.py @@ -0,0 +1,89 @@ +from typing import Any, Dict, List, Tuple + +import mysql.connector + +from .data_source_db import DataSourceDb + + +class MySQLDataSource(DataSourceDb): + def __init__(self, mysql_connection_info: Dict[str, str]) -> None: + self.user = mysql_connection_info["user"] + self.host = mysql_connection_info["host"] + self.password = mysql_connection_info["password"] + + def save_record( + self, + database: str, + tablename: str, + data: Dict[str, Any], + use_obj_connection: bool = False, + close_obj_connection: bool = True, + ): + query, values = self.create_query(tablename, data) + + if use_obj_connection: + try: + self.cursor.execute(query, values) + + if close_obj_connection: + self.db.commit() + self.db.close() + + except mysql.connector.Error: + self.db.commit() + self.db.close() + raise + + else: + db = mysql.connector.connect( + user=self.user, + host=self.host, + password=self.password, + database=database, + ) + try: + cursor = db.cursor() + cursor.execute(query, values) + db.commit() + db.close() + except mysql.connector.Error: + db.close() + raise + + def bulk_save(self, export_data: List[Dict[str, Any]]) -> None: + for index, unit in enumerate(export_data): + database, table_name, data = unit.values() + + if ( + not hasattr(self, "db") + or not hasattr(self, "cursor") + or not self.db.is_connected() + ): + self.db = mysql.connector.connect( + user=self.user, + host=self.host, + password=self.password, + database=database, + ) + try: + self.cursor = self.db.cursor() + except: + self.db.close() + raise + + self.save_record( + database, table_name, data, True, index + 1 == len(export_data) + ) + + def create_query(self, table_name: str, data: dict) -> Tuple[str, list]: + columns = list(data.keys()) + values = list(data.values()) + + query = ( + f"REPLACE INTO {table_name} (" + + ", ".join(columns) + + ") VALUES (" + + ", ".join(["%s"] * len(columns)) + + ")" + ) + return (query, values) diff --git a/source/database/read_and_save.py b/source/database/read_and_save.py new file mode 100644 index 0000000..76e19a0 --- /dev/null +++ b/source/database/read_and_save.py @@ -0,0 +1,93 @@ +"""Example of reading and saving some inverter registers.""" + +import sys +from datetime import datetime, timedelta +from os import environ + +from solaxx3.solaxx3 import SolaxX3 + +from .mysql_data_source import MySQLDataSource + + +def _get_datetime(inverter_time: datetime) -> datetime: + return inverter_time - timedelta(hours=TIMEZONE_OFFSET) + + +MYSQL_CONNECTION_INFO = { + "user": environ["MYSQL_DB_USERNAME"], + "host": environ["MYSQL_DB_HOST_IP"], + "password": environ["MYSQL_DB_PASSWORD"], +} +DATABASE = environ["MYSQL_DB_DATABASE"] +TIMEZONE_OFFSET = 2 + +s = SolaxX3(port="/dev/ttyUSB0", baudrate=115200) +if not s.connect(): + print("Could not connect to inverter") + sys.exit(1) + +s.read_all_registers() + +mysql_export_data = [ + { + "database": DATABASE, + "table": "solax_local", + "data": { + "uploadTime": _get_datetime(s.read("rtc_datetime")[0]), + "inverter_status": s.read("run_mode")[0], + "dc_solar_power": s.read("power_dc1")[0] + s.read("power_dc2")[0], + "grid_voltage_r": s.read("grid_voltage_r")[0], + "grid_voltage_s": s.read("grid_voltage_s")[0], + "grid_voltage_t": s.read("grid_voltage_t")[0], + "battery_capacity": s.read("battery_capacity")[0], + "battery_power": s.read("battery_power_charge1")[0], + "feed_in_power": s.read("feed_in_power")[0], + "time_count_down": s.read("time_count_down")[0], + "inverter_ac_power": s.read("grid_power")[0], + "consumeenergy": s.read("energy_from_grid_meter")[0], + "feedinenergy": s.read("energy_to_grid_meter")[0], + "power_dc1": s.read("power_dc1")[0], + "power_dc2": s.read("power_dc2")[0], + "inv_volt_r": s.read("inv_volt_r")[0], + "inv_volt_s": s.read("inv_volt_s")[0], + "inv_volt_t": s.read("inv_volt_t")[0], + "off_grid_power_active_r": s.read("off_grid_power_active_r")[0], + "off_grid_power_active_s": s.read("off_grid_power_active_s")[0], + "off_grid_power_active_t": s.read("off_grid_power_active_t")[0], + "grid_power_r": s.read("grid_power_r")[0], + "grid_power_s": s.read("grid_power_s")[0], + "grid_power_t": s.read("grid_power_t")[0], + }, + }, + { + "database": DATABASE, + "table": "solax_daily", + "data": { + "uploadDate": _get_datetime(s.read("rtc_datetime")[0]).date(), + "feed_in": s.read("feed_in_energy_today")[0], + "total_yield": s.read("energy_to_grid_today")[0], + }, + }, +] +mysql_data_source = MySQLDataSource(MYSQL_CONNECTION_INFO) + + +def bulk_save(): + """Save collected data.""" + + mysql_data_source.bulk_save(mysql_export_data) + + +def _transfer(index: int, **extras) -> None: + """Save a record of collected data.""" + + data_record = mysql_export_data[index] + database, tablename = data_record["database"], data_record["table"] + data = data_record["data"] + mysql_data_source.save_record(database, tablename, data, **extras) + + +save = bulk_save + +if __name__ == "__main__": + save() diff --git a/source/database/schema.sql b/source/database/schema.sql index 27ec77d..8c42b5f 100644 --- a/source/database/schema.sql +++ b/source/database/schema.sql @@ -1,25 +1,36 @@ -CREATE TABLE `solax_daily` ( - `uploadDate` date NOT NULL, - `feed_in` float(6,1) DEFAULT NULL, - `total_yield` float(6,1) DEFAULT NULL, - PRIMARY KEY (`uploadDate`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +CREATE TABLE solax_daily ( + uploadDate DATE NOT NULL, + feed_in FLOAT NULL, + total_yield FLOAT NULL +) +ENGINE=InnoDB +DEFAULT CHARSET=latin1; -CREATE TABLE `solax_local` ( - `uploadTime` datetime NOT NULL, - `inverter_status` tinyint(4) DEFAULT NULL, - `dc_solar_power` smallint(6) DEFAULT NULL, - `grid_voltage_r` smallint(6) DEFAULT NULL, - `grid_voltage_s` smallint(6) DEFAULT NULL, - `grid_voltage_t` smallint(6) DEFAULT NULL, - `battery_capacity` tinyint(4) DEFAULT NULL, - `battery_power` smallint(6) DEFAULT NULL, - `feed_in_power` smallint(6) DEFAULT NULL, - `time_count_down` smallint(6) DEFAULT NULL, - `inverter_ac_power` smallint(6) DEFAULT NULL, - `consumeenergy` float(7,1) DEFAULT NULL, - `feedinenergy` float(7,1) DEFAULT NULL, - `power_dc1` smallint(6) DEFAULT NULL, - `power_dc2` smallint(6) DEFAULT NULL, - PRIMARY KEY (`uploadTime`) -) ENGINE=InnoDB DEFAULT CHARSET=latin1; +CREATE TABLE solax_local ( + uploadTime DATETIME NOT NULL, + inverter_status TINYINT NULL, + dc_solar_power SMALLINT NULL, + grid_voltage_r SMALLINT NULL, + grid_voltage_s SMALLINT NULL, + grid_voltage_t SMALLINT NULL, + battery_capacity TINYINT NULL, + battery_power SMALLINT NULL, + feed_in_power SMALLINT NULL, + time_count_down SMALLINT NULL, + inverter_ac_power SMALLINT NULL, + consumeenergy FLOAT NULL, + feedinenergy FLOAT NULL, + power_dc1 SMALLINT NULL, + power_dc2 SMALLINT NULL, + inv_volt_r SMALLINT NULL, + inv_volt_s SMALLINT NULL, + inv_volt_t SMALLINT NULL, + off_grid_power_active_r INTEGER NULL, + off_grid_power_active_s INTEGER NULL, + off_grid_power_active_t INTEGER NULL, + grid_power_r INTEGER NULL, + grid_power_s INTEGER NULL, + grid_power_t INTEGER NULL +) +ENGINE=InnoDB +DEFAULT CHARSET=latin1; \ No newline at end of file diff --git a/source/dist/solaxx3-1.0.2-py3-none-any.whl b/source/dist/solaxx3-1.0.2-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..f70402ed2e3259b78a7f89bdc5defe473a186579 GIT binary patch literal 14473 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 iyYu)bCE!0${wM@xpQJy|=aBuct^WfiG26NT literal 0 HcmV?d00001 diff --git a/source/dist/solaxx3-1.0.2.tar.gz b/source/dist/solaxx3-1.0.2.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..57e26cd863d546b70064c1cc03cd693ddbbaec75 GIT binary patch literal 17349 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 literal 0 HcmV?d00001 diff --git a/source/example.py b/source/example.py index 66ef8d3..51d6506 100644 --- a/source/example.py +++ b/source/example.py @@ -1,22 +1,21 @@ +"""Sample program for reading and saving some inverter register values.""" + +#from solaxx3.solaxx3 import SolaxX3 from solaxx3.rs485 import SolaxX3 # adjust the serial port and baud rate as necessary -s = SolaxX3(port="/dev/ttyUSB0", baudrate=115200) +s = SolaxX3(port="/dev/ttyUSB0", baudrate=19200) if s.connect(): s.read_all_registers() - print(s._input_registers_values_list) - exit() - available_stats = s.list_register_names() for stat in available_stats: print(stat, f" {s.read(stat)}") battery_temperature = s.read("temperature_battery") print(f"\n\nBattery temperature: {s.read('temperature_battery')}") - - + work_mode = s.read("work_mode") + print(work_mode) else: print("Cannot connect to the Modbus Server/Slave") - exit() \ No newline at end of file diff --git a/source/pyproject.toml b/source/pyproject.toml index 7513639..9fed4bc 100644 --- a/source/pyproject.toml +++ b/source/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "solaxx3" -version = "0.1.0" +version = "1.0.2" description = "Read Solax X3 inverter registers via modbus interface (RS-485)" readme = "README.md" authors = [{ name = "Flavius Moldovan", email = "mkfam@protonmail.com" }] @@ -15,6 +15,11 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python", "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ] keywords = ["Solax", "solax-x3", "solaxx3", "solar inverter", "RTU", "MODBUS"] dependencies = [ diff --git a/source/src/.DS_Store b/source/src/.DS_Store index f4228550edf4c925a854709919316426e956b351..8b497991d13a5de3c94389d7bdb4964012425f0f 100644 GIT binary patch delta 18 acmZoMXfc?uY~#i-_K6Mro4GlD@&f=#kOxcv delta 20 ccmZoMXfc?ujFEBU#xVAY4P2YqIsWnk08YaPNdN!< diff --git a/source/src/solaxx3.egg-info/PKG-INFO b/source/src/solaxx3.egg-info/PKG-INFO new file mode 100644 index 0000000..e21fe61 --- /dev/null +++ b/source/src/solaxx3.egg-info/PKG-INFO @@ -0,0 +1,82 @@ +Metadata-Version: 2.1 +Name: solaxx3 +Version: 1.0.2 +Summary: Read Solax X3 inverter registers via modbus interface (RS-485) +Author-email: Flavius Moldovan +License: The MIT License (MIT) + Copyright © 2022 + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +Project-URL: Homepage, https://github.com/mkfam7/solaxx3 +Keywords: Solax,solax-x3,solaxx3,solar inverter,RTU,MODBUS +Classifier: License :: OSI Approved :: MIT License +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Requires-Python: >=3.8 +Description-Content-Type: text/markdown +License-File: LICENSE +Requires-Dist: pymodbus[serial]>=3.0.0 + +![Build badge](https://github.com/mkfam7/solaxx3/actions/workflows/python-package.yml/badge.svg) + + + +# solax-x3 +#### Read in real-time all parameters provided by Solax X3 solar inverter via its Modbus S-485 serial interface. + +
+ +## Prerequisites + +* Solax X3 inverter +* Modbus RS-485 serial adapter/interface +* [Modbus cable](https://github.com/mkfam7/solaxx3/blob/main/diagrams/rs485_cable.png) +* python version >= 3.8 +* This python module + +## Installation + +``` +pip install solaxx3 +``` + +## Usage + + +``` +from solaxx3.solaxx3 import SolaxX3 + +# adjust the serial port and baud rate as necessary +s = SolaxX3(port="/dev/ttyUSB0", baudrate=115200) + +if s.connect(): + s.read_all_registers() + + available_stats = s.list_register_names() + for stat in available_stats: + print(stat) + + battery_temperature = s.read("temperature_battery") + print(f"\n\nBattery temperature: {s.read('temperature_battery')}") + + +else: + print("Cannot connect to the Modbus Server/Slave") + exit() + + +``` + +Project Link: [https://github.com/mkfam7/solaxx3](https://github.com/mkfam7/solaxx3) + + + diff --git a/source/src/solaxx3.egg-info/SOURCES.txt b/source/src/solaxx3.egg-info/SOURCES.txt new file mode 100644 index 0000000..73387c6 --- /dev/null +++ b/source/src/solaxx3.egg-info/SOURCES.txt @@ -0,0 +1,16 @@ +LICENSE +README.md +pyproject.toml +setup.py +src/solaxx3/__init__.py +src/solaxx3/solax_registers_info.py +src/solaxx3/solaxx3.py +src/solaxx3/utils.py +src/solaxx3.egg-info/PKG-INFO +src/solaxx3.egg-info/SOURCES.txt +src/solaxx3.egg-info/dependency_links.txt +src/solaxx3.egg-info/requires.txt +src/solaxx3.egg-info/top_level.txt +tests/test_mysql_connector.py +tests/test_register_data.py +tests/test_solaxx3.py \ No newline at end of file diff --git a/source/UNKNOWN.egg-info/dependency_links.txt b/source/src/solaxx3.egg-info/dependency_links.txt similarity index 100% rename from source/UNKNOWN.egg-info/dependency_links.txt rename to source/src/solaxx3.egg-info/dependency_links.txt diff --git a/source/src/solaxx3.egg-info/requires.txt b/source/src/solaxx3.egg-info/requires.txt new file mode 100644 index 0000000..6fb8063 --- /dev/null +++ b/source/src/solaxx3.egg-info/requires.txt @@ -0,0 +1 @@ +pymodbus[serial]>=3.0.0 diff --git a/source/src/solaxx3.egg-info/top_level.txt b/source/src/solaxx3.egg-info/top_level.txt new file mode 100644 index 0000000..8de2371 --- /dev/null +++ b/source/src/solaxx3.egg-info/top_level.txt @@ -0,0 +1 @@ +solaxx3 diff --git a/source/src/solaxx3/__init__.py b/source/src/solaxx3/__init__.py index 5935f36..49c9fdb 100644 --- a/source/src/solaxx3/__init__.py +++ b/source/src/solaxx3/__init__.py @@ -1,2 +1,3 @@ -# Version of the Solax RTU package -__version__ = "0.0.6" +"""Version of the Solax RTU package""" + +__version__ = "1.0.2" diff --git a/source/src/solaxx3/registers.py b/source/src/solaxx3/registers.py deleted file mode 100644 index 98c27fe..0000000 --- a/source/src/solaxx3/registers.py +++ /dev/null @@ -1,1619 +0,0 @@ -class SolaxRegistersInfo: - __registers = { - # input registers - "grid_voltage": { - "address": 0x0000, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Grid voltage", - }, - "grid_current": { - "address": 0x0001, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "A", - "data_length": 1, - "description": "Grid current", - }, - "grid_power": { - "address": 0x0002, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 1, - "description": "Grid power", - }, - "pv_voltage_1": { - "address": 0x0003, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "PV Voltage 1", - }, - "pv_voltage_2": { - "address": 0x0004, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "PV Voltage 2", - }, - "pv_current_1": { - "address": 0x0005, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "A", - "data_length": 1, - "description": "PV Current 1", - }, - "pv_current_2": { - "address": 0x0006, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "A", - "data_length": 1, - "description": "PV Current 2", - }, - "grid_frequency": { - "address": 0x0007, - "register_type": "input", - "data_format": "uint16", - "si_adj": 100, - "signed": False, - "data_unit": "Hz", - "data_length": 1, - "description": "Grid frequency", - }, - "radiator_temperature": { - "address": 0x0008, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "C", - "data_length": 1, - "description": "Radiator temperature", - }, - "run_mode": { - "address": 0x0009, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "", - "data_length": 1, - "description": "Inverter status", - }, - "power_dc1": { - "address": 0x000A, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "W", - "data_length": 1, - "description": "DC Power String 1", - }, - "power_dc2": { - "address": 0x000B, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "W", - "data_length": 1, - "description": "DC Power String 2", - }, - "temperature_fault_value": { - "address": 0x000C, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "C", - "data_length": 1, - "description": "Temperature fault value", - }, - "pv1_volt_fault_value": { - "address": 0x000D, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "PV1 volt fault value", - }, - "pv2_volt_fault_value": { - "address": 0x000E, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "PV2 volt fault value", - }, - "gfci_fault_value": { - "address": 0x000F, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1000, - "signed": False, - "data_unit": "A", - "data_length": 1, - "description": "GFCI fault value", - }, - "grid_fault_value": { - "address": 0x0010, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Grid fault value", - }, - "grid_freq__fault__value_t": { - "address": 0x0011, - "register_type": "input", - "data_format": "uint16", - "si_adj": 100, - "signed": False, - "data_unit": "Hz", - "data_length": 1, - "description": "Grid frequency fault value t", - }, - "dci_fault_value": { - "address": 0x0012, - "register_type": "input", - "data_format": "uint16", - "si_adj": 100, - "signed": False, - "data_unit": "A", - "data_length": 1, - "description": "DCI fault value", - }, - "time_count_down": { - "address": 0x0013, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1000, - "signed": False, - "data_unit": "seconds", - "data_length": 1, - "description": "Time remaining until the current operation finishes (e.g. checking)", - }, - "battery_voltage_charge_1": { - "address": 0x0014, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "V", - "data_length": 1, - "description": "Battery voltage charge 1", - }, - "battery_current_charge_1": { - "address": 0x0015, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "V", - "data_length": 1, - "description": "Battery current charge 1", - }, - "battery_power_charge1": { - "address": 0x0016, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 1, - "description": "Battery power charge 1", - }, - "bms_connect_state": { - "address": 0x0017, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "", - "data_length": 1, - "description": "BMS connect state", - }, - "temperature_battery": { - "address": 0x0018, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "C", - "data_length": 1, - "description": "Temperature of the battery", - }, - "bdc_status": { - "address": 0x0019, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "BDC Status", - }, - "grid_status": { - "address": 0x001A, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "Grid Status: 0-OnGrid, 1-OffGrid", - }, - "mppt_count": { - "address": 0x001B, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "MPPT Count", - }, - "battery_capacity": { - "address": 0x001C, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "%", - "data_length": 1, - "description": "Battery charge percentage", - }, - "output_energy_charge": { - "address": 0x001D, - "register_type": "input", - "data_format": "uint32", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Output energy charge", - }, - "output_energy_charge_today": { - "address": 0x0020, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 1, - "description": "Output energy charge today", - }, - "input_energy_charge": { - "address": 0x0021, - "register_type": "input", - "data_format": "uint32", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Input energy charge", - }, - "input_energy_charge_today": { - "address": 0x0023, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 1, - "description": "Input energy charge today", - }, - "bms_charge_max_current": { - "address": 0x0024, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "A", - "data_length": 1, - "description": "BMS charge max current", - }, - "bms_discharge_max_current": { - "address": 0x0025, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "A", - "data_length": 1, - "description": "BMS discharge max current", - }, - "power_to_ev": { - "address": 0x0026, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Power to EV", - }, - "ref_power_to_ev": { - "address": 0x0028, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": False, - "data_unit": "W", - "data_length": 1, - "description": "ref_power_to_ev", - }, - "pcs_major_fault": { - "address": 0x003E, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "PCS Major Fault", - }, - "battery_major_fault": { - "address": 0x003F, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "Battery Major Fault", - }, - "inv_fault_message": { - "address": 0x0040, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 2, - "description": "Inverter Fault Message: X1:Table 2-4, X2:Table 2-3", - }, - "mgr_fault_message": { - "address": 0x0043, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "Manager Fault Message: Table 2-5", - }, - "bat_bms_fault_message": { - "address": 0x0044, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 2, - "description": "Battery BMS Fault Message - Table 2-6", - }, - "feed_in_power": { - "address": 0x0046, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W ", - "data_length": 2, - "description": "Feed-in power obtained from Meter or CT", - }, - "energy_to_grid_meter": { - "address": 0x0048, - "register_type": "input", - "data_format": "uint32", - "si_adj": 1, - "signed": False, - "data_unit": "KWh", - "si_adj": 100, - "data_length": 2, - "description": "Energy to grid meter value", - }, - "energy_from_grid_meter": { - "address": 0x004A, - "register_type": "input", - "data_format": "uint32", - "si_adj": 100, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Energy from grid meter value", - }, - "off_grid_voltage": { - "address": 0x004C, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Off-Grid Voltage X1", - }, - "off_grid_current": { - "address": 0x004D, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "A", - "data_length": 1, - "description": "Off-Grid Current X1", - }, - "off_grid_power": { - "address": 0x004E, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "VA", - "data_length": 1, - "description": "Off Grid Power X1", - }, - "off_grid_frequency": { - "address": 0x004F, - "register_type": "input", - "data_format": "uint16", - "si_adj": 100, - "signed": False, - "data_unit": "Hz", - "data_length": 1, - "description": "Off-Grid Frequency", - }, - "energy_to_grid_today": { - "address": 0x0050, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 1, - "description": "The quantity of energy sent to grid today", - }, - "energy_to_grid_total": { - "address": 0x0052, - "register_type": "input", - "data_format": "uint32", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Total energy sent to grid", - }, - "lock_state": { - "address": 0x0054, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "Lock State", - }, - "bus_volt": { - "address": 0x0066, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Bus Volt", - }, - "w_dcv_fault_val": { - "address": 0x0067, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "W DCV Fault Value", - }, - "w_overload_fault_val": { - "address": 0x0068, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "W", - "data_length": 1, - "description": "W Overload Fault Value", - }, - "w_battery_volt_fault_val": { - "address": 0x0069, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "W Battery Volt Fault Value", - }, - "grid_voltage_r": { - "address": 0x006A, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Grid voltage for R phase", - }, - "grid_current_r": { - "address": 0x006B, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "A", - "data_length": 1, - "description": "Grid Current R Phase", - }, - "grid_power_r": { - "address": 0x006C, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 1, - "description": "Grid Power R Phase", - }, - "grid_frequency_r": { - "address": 0x006D, - "register_type": "input", - "data_format": "uint16", - "si_adj": 100, - "signed": False, - "data_unit": "Hz", - "data_length": 1, - "description": "Grid Frequency R Phase", - }, - "grid_voltage_s": { - "address": 0x006E, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Grid voltage for S phase", - }, - "grid_current_s": { - "address": 0x006F, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "A", - "data_length": 1, - "description": "Grid Current S Phase", - }, - "grid_power_s": { - "address": 0x0070, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 1, - "description": "Grid Power S Phase", - }, - "grid_frequency_s": { - "address": 0x0071, - "register_type": "input", - "data_format": "uint16", - "si_adj": 100, - "signed": False, - "data_unit": "Hz", - "data_length": 1, - "description": "Grid Frequency S", - }, - "grid_voltage_t": { - "address": 0x0072, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Grid voltage for T phase", - }, - "grid_current_t": { - "address": 0x0073, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "A", - "data_length": 1, - "description": "Grid Current T Phase", - }, - "grid_power_t": { - "address": 0x0074, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 1, - "description": "Grid Power T Phase", - }, - "grid_frequency_t": { - "address": 0x0075, - "register_type": "input", - "data_format": "uint16", - "si_adj": 100, - "signed": False, - "data_unit": "Hz", - "data_length": 1, - "description": "Grid Frequency T Phase", - }, - "off_grid_volt_r": { - "address": 0x0076, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Off-Grid Volt R Phase", - }, - "off_grid_current_r": { - "address": 0x0077, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "A", - "data_length": 1, - "description": "Off-Grid Current R Phase", - }, - "off_grid_power_active_r": { - "address": 0x0078, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 1, - "description": "Off-Grid Active Power R Phase", - }, - "off_grid_power_s_r": { - "address": 0x0079, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "VA", - "data_length": 1, - "description": "Off-Grid S Power R Phase", - }, - "off-grid-volt-s": { - "address": 0x007A, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Off-Grid Volt S Phase", - }, - "off_grid_current_s": { - "address": 0x007B, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "A", - "data_length": 1, - "description": "Off-Grid Current S Phase", - }, - "off_grid_power_active_s": { - "address": 0x007C, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 1, - "description": "Off-Grid Active Power S Phase", - }, - "off_grid_power_s_s": { - "address": 0x007D, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "VA", - "data_length": 1, - "description": "Off-Grid S Power S Phase", - }, - "off_grid_volt_t": { - "address": 0x007E, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Off-Grid Volt T Phase", - }, - "off_grid_current_t": { - "address": 0x007F, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": True, - "data_unit": "A", - "data_length": 1, - "description": "Off-Grid Current T Phase", - }, - "off_grid_power_active_t": { - "address": 0x0080, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 1, - "description": "Off-Grid Active Power T Phase", - }, - "off_grid_power_s_t": { - "address": 0x0081, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "VA", - "data_length": 1, - "description": "Off-Grid S Power T Phase", - }, - "feedin_power_r_phase": { - "address": 0x0082, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Feedin Power R Phase", - }, - "feedin_power_s_phase": { - "address": 0x0084, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Feedin Power S Phase", - }, - "feedin_power_t_phase": { - "address": 0x0086, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Feedin Power T Phase", - }, - "ongrid_run_time": { - "address": 0x0088, - "register_type": "input", - "data_format": "int32", - "si_adj": 10, - "signed": True, - "data_unit": "hour", - "data_length": 2, - "description": "On-Grid Run Time", - }, - "offgrid_run_time": { - "address": 0x008A, - "register_type": "input", - "data_format": "int32", - "si_adj": 10, - "signed": True, - "data_unit": "hour", - "data_length": 2, - "description": "Off-Grid Run Time", - }, - "offgrid_yield_total": { - "address": 0x008E, - "register_type": "input", - "data_format": "uint32", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Off-Grid Yield Total", - }, - "offgrid_yield_today": { - "address": 0x0090, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 1, - "description": "Off-Grid Yield Today", - }, - "echarge_today": { - "address": 0x0091, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 1, - "description": "Echarge energy today", - }, - "echarge_total": { - "address": 0x0092, - "register_type": "input", - "data_format": "uint32", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Echarge Total", - }, - "solar_energy_total": { - "address": 0x0094, - "register_type": "input", - "data_format": "uint32", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Total solar energy", - }, - "solar_energy_today": { - "address": 0x0096, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "KWh", - "data_length": 1, - "description": "Solar energy today", - }, - "feed_in_energy_today": { - "address": 0x0098, - "register_type": "input", - "data_format": "uint32", - "si_adj": 100, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Energy to grid meter for today", - }, - "consumption_energy_today": { - "address": 0x009A, - "register_type": "input", - "data_format": "uint32", - "si_adj": 100, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Energy from grid meter for today", - }, - "inv_volt_r": { - "address": 0x009C, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Inverter Volt R Phase", - }, - "inv_volt_s": { - "address": 0x009D, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Inverter Volt S Phase", - }, - "inv_volt_t": { - "address": 0x009E, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Inverter Volt T Phase", - }, - "feedin_power_meter_2": { - "address": 0x00A8, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Power to Grid Meter 2", - }, - "feedin_energy_total_meter_2": { - "address": 0x00AA, - "register_type": "input", - "data_format": "uint32", - "si_adj": 100, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Energy to the grid", - }, - "consum_energy_total_meter_2": { - "address": 0x00AC, - "register_type": "input", - "data_format": "uint32", - "si_adj": 100, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Energy from grid", - }, - "feedin_energy_today_meter_2": { - "address": 0x00AE, - "register_type": "input", - "data_format": "uint32", - "si_adj": 100, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Energy to grid", - }, - "consum_energy_today_meter_2": { - "address": 0x00B0, - "register_type": "input", - "data_format": "uint32", - "si_adj": 100, - "signed": False, - "data_unit": "KWh", - "data_length": 2, - "description": "Energy from grid", - }, - "feedin_power_r_phase_meter_2": { - "address": 0x00B2, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Feedin Power R Phase Meter 2", - }, - "feedin_power_s_phase_meter_2": { - "address": 0x00B4, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Feedin Power S Phase Meter 2", - }, - "feedin_power_t_phase_meter_2": { - "address": 0x00B6, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Feedin Power T Phase Meter 2", - }, - "meter_1_communication_state": { - "address": 0x00B8, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "Meter 1 Communication State: 0:Com Error, 1:Normal", - }, - "meter_2_communication_state": { - "address": 0x00B9, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "Meter 2 Communication State: 0:Com Error, 1:Normal", - }, - "battery_tem_high": { - "address": 0x00BA, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "C", - "data_length": 1, - "description": "Battery Temperature High", - }, - "battery_tem_low": { - "address": 0x00BB, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "C", - "data_length": 1, - "description": "Battery Temperature Low", - }, - "cell_voltage_high": { - "address": 0x00BC, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1000, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Cell Voltage High", - }, - "cell_voltage_low": { - "address": 0x00BD, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1000, - "signed": True, - "data_unit": "V", - "data_length": 1, - "description": "Cell Voltage Low", - }, - "bms_user_soc": { - "address": 0x00BE, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "%", - "data_length": 1, - "description": "BMS User SOC", - }, - "bms_user_soh": { - "address": 0x00BF, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "%", - "data_length": 1, - "description": "BMS User SOH", - }, - "grid_reactive_power_total_meter": { - "address": 0x00C0, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "Var", - "data_length": 1, - "description": "Grid Reactive Power Total Meter", - }, - "grid_reactive_power_r_meter": { - "address": 0x00C1, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "Var", - "data_length": 1, - "description": "Grid Reactive Power Phase R Meter", - }, - "grid_reactive_power_s_meter": { - "address": 0x00C2, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "Var", - "data_length": 1, - "description": "Grid Reactive Power Phase S Meter", - }, - "grid_reactive_power_t_meter": { - "address": 0x00C3, - "register_type": "input", - "data_format": "int16", - "si_adj": 1, - "signed": True, - "data_unit": "Var", - "data_length": 1, - "description": "Grid Reactive Power Phase T Meter", - }, - "grid_power_factor_total_meter": { - "address": 0x00C4, - "register_type": "input", - "data_format": "int16", - "si_adj": 100, - "signed": True, - "data_unit": "N/A", - "data_length": 1, - "description": "Grid Power Factor Total Meter", - }, - "grid_power_factor_r_meter": { - "address": 0x00C5, - "register_type": "input", - "data_format": "int16", - "si_adj": 100, - "signed": True, - "data_unit": "N/A", - "data_length": 1, - "description": "Grid Power Factor Phase R Meter", - }, - "grid_power_factor_s_meter": { - "address": 0x00C6, - "register_type": "input", - "data_format": "int16", - "si_adj": 100, - "signed": True, - "data_unit": "N/A", - "data_length": 1, - "description": "Grid Power Factor Phase S Meter", - }, - "grid_power_factor_t_meter": { - "address": 0x00C7, - "register_type": "input", - "data_format": "int16", - "si_adj": 100, - "signed": True, - "data_unit": "N/A", - "data_length": 1, - "description": "Grid Power Factor Phase T Meter", - }, - "grid_frequency_meter": { - "address": 0x00C8, - "register_type": "input", - "data_format": "uint16", - "si_adj": 100, - "signed": False, - "data_unit": "Hz", - "data_length": 1, - "description": "Grid Frequency Meter", - }, - "grid_voltage_total_meter": { - "address": 0x00C9, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Grid Voltage Total Meter", - }, - "grid_voltage_r_meter": { - "address": 0x00CA, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Grid Voltage R Meter", - }, - "grid_voltage_s_meter": { - "address": 0x00CB, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Grid Voltage S Meter", - }, - "grid_voltage_t_meter": { - "address": 0x00CC, - "register_type": "input", - "data_format": "uint16", - "si_adj": 10, - "signed": False, - "data_unit": "V", - "data_length": 1, - "description": "Grid Voltage T Meter", - }, - "grid_current_total_meter": { - "address": 0x00CD, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "A", - "data_length": 1, - "description": "Grid Current Total Meter", - }, - "grid_current_r_meter": { - "address": 0x00CE, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "A", - "data_length": 1, - "description": "Grid Current R Meter", - }, - "grid_current_s_meter": { - "address": 0x00CF, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "A", - "data_length": 1, - "description": "Grid Current S Meter", - }, - "grid_current_t_meter": { - "address": 0x00D0, - "register_type": "input", - "data_format": "int16", - "si_adj": 10, - "signed": True, - "data_unit": "A", - "data_length": 1, - "description": "Grid Current T Meter", - }, - "modbus_power_control": { - "address": 0x0100, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "Modbus Power Control: 0:disable remote contro, 1:enable power control, 2:enable electric quality control, 3:enable SOC target control", - }, - "target_finish_tag": { - "address": 0x0101, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "Target Finish Tag: 0:unfinished, 1:finished", - }, - "active_power_target": { - "address": 0x0102, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Active Power Target", - }, - "w_reactive_power_target": { - "address": 0x0104, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "Var", - "data_length": 2, - "description": "W Reactive Power Target", - }, - "w_active_power_real": { - "address": 0x0106, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "W Active Power Real", - }, - "w_reactive_power_real": { - "address": 0x0108, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "Var", - "data_length": 2, - "description": "W Reactive Power Real", - }, - "w_active_power_upper": { - "address": 0x010A, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "W Active Power Upper", - }, - "w_active_power_lower": { - "address": 0x010C, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "W Active Power Lower", - }, - "w_reactive_power_upper": { - "address": 0x010E, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "Var", - "data_length": 2, - "description": "W Reactive Power Upper", - }, - "w_reactive_power_lower": { - "address": 0x0110, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "Var", - "data_length": 2, - "description": "W Reactive Power Lower", - }, - "target_energy": { - "address": 0x0112, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "Wh", - "data_length": 2, - "description": "Target Energy", - }, - "charge_discharge_power": { - "address": 0x0114, - "register_type": "input", - "data_format": "int32", - "si_adj": 1, - "signed": True, - "data_unit": "W", - "data_length": 2, - "description": "Charge Discharge Power", - }, - "chargeable_electric_capacity": { - "address": 0x0116, - "register_type": "input", - "data_format": "uint32", - "si_adj": 1, - "signed": False, - "data_unit": "Wh", - "data_length": 2, - "description": "Chargeable Electric Capacity", - }, - "dischargeable_electric_capacity": { - "address": 0x0118, - "register_type": "input", - "data_format": "uint32", - "si_adj": 1, - "signed": False, - "data_unit": "Wh", - "data_length": 2, - "description": "Dischargeable Electric Capacity", - }, - "time_of_duration": { - "address": 0x011A, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "second", - "data_length": 1, - "description": "time_of_duration", - }, - "target_soc": { - "address": 0x011B, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "%", - "data_length": 1, - "description": "Target SOC", - }, - "soc_upper": { - "address": 0x011C, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "%", - "data_length": 1, - "description": "SOC Upper", - }, - "soc_lower": { - "address": 0x011D, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "%", - "data_length": 1, - "description": "SOC Lower", - }, - "remote_control_timeout": { - "address": 0x011E, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "second", - "data_length": 1, - "description": "Remote Control Timeout: 4-65535", - }, - "w_battery_force_charge_flag": { - "address": 0x011F, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "W Battery Force Charge Flag: 0:No Action, 1:Force Charge", - }, - "w_bms_relay_state": { - "address": 0x0120, - "register_type": "input", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_unit": "N/A", - "data_length": 1, - "description": "W BMS Relay State: 0:OFF, 1:ON", - }, - # holding registers - "serial_number": { - "address": 0x0000, - "register_type": "holding", - "data_format": "varchar", - "signed": False, - "data_length": 7, - "description": "Inverter' serial number", - }, - "firmware_version_dsp": { - "address": 0x007D, - "register_type": "holding", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_length": 1, - "description": "Firmware version DSP", - }, - "hardware_version_dsp": { - "address": 0x007E, - "register_type": "holding", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_length": 1, - "description": "Hardware version DSP", - }, - "firmware_version_modbus_rtu": { - "address": 0x0082, - "register_type": "holding", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_length": 1, - "description": "Firmware version MODBUS RTU", - }, - "firmware_version_arm": { - "address": 0x0083, - "register_type": "holding", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_length": 1, - "description": "Firmware version ARM", - }, - "firmware_version_arm_bootloader": { - "address": 0x0084, - "register_type": "holding", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_length": 1, - "description": "Firmware version ARM Bootloader", - }, - "rtc_datetime": { - "address": 0x0085, - "register_type": "holding", - "data_format": "datetime", - "si_adj": 1, - "signed": False, - "data_length": 6, - "description": "RTC datetime", - }, - "registration_code": { - "address": 0x00AA, - "register_type": "holding", - "data_format": "varchar", - "si_adj": 1, - "signed": False, - "data_length": 6, - "description": "Registration code for external module", - }, - "inverter_power_type": { - "address": 0x00BA, - "register_type": "holding", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_length": 1, - "description": "Inverter power type", - }, - "user_password": { - "address": 0x00E0, - "register_type": "holding", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_length": 1, - "description": "User password", - }, - "advanced_password": { - "address": 0x00E1, - "register_type": "holding", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_length": 1, - "description": "Advanced password", - }, - "machine_type": { - "address": 0x0105, - "register_type": "holding", - "data_format": "uint16", - "si_adj": 1, - "signed": False, - "data_length": 1, - "description": "Machine type: 1:X1, 3:X3", - }, - } - - def get_register_info(self, name: str): - return self.__registers[name] - - def list_register_names(self): - return list(self.__registers.keys()) \ No newline at end of file diff --git a/source/src/solaxx3/rs485.py b/source/src/solaxx3/rs485.py deleted file mode 100644 index 9bbb6f6..0000000 --- a/source/src/solaxx3/rs485.py +++ /dev/null @@ -1,168 +0,0 @@ -from typing import Any - -from pymodbus.client import ModbusSerialClient -from datetime import date, datetime, timedelta -from struct import * -from solaxx3.registers import SolaxRegistersInfo -from time import sleep, perf_counter - - -class SolaxX3: - connected: bool = False - - def __init__( - self, - method="rtu", - port="/dev/ttyUSB0", - baudrate=115200, - timeout=3, - parity="N", - stopbits=1, - bytesize=8, - ) -> None: - self._input_registers_values_list = [] - self._holding_registers_values_list = [] - - self.client = ModbusSerialClient( - method=method, - port=port, - baudrate=baudrate, - timeout=timeout, - parity=parity, - stopbits=stopbits, - bytesize=bytesize, - ) - - def connect(self) -> bool: - self.connected = self.client.connect() - return self.connected - - def _join_msb_lsb(self, msb: int, lsb: int) -> int: - return (msb << 16) | lsb - - def _unsigned16(self, type: str, addr: int, count: int = 1, unit: int = 1) -> int: - - self._input_registers_values_list - if type == "input": - return self._input_registers_values_list[addr] - elif type == "holding": - return self._holding_registers_values_list[addr] - - def _readRegisterRange( - self, type: str, addr: int, count: int = 1, unit: int = 1 - ) -> list: - if type == "input": - return self._input_registers_values_list[addr : addr + count] - elif type == "holding": - return self._holding_registers_values_list[addr : addr + count] - - def _twos_complement(self, number: int, bits: int) -> int: - """ - Compute the 2's complement of the int value val - """ - - # if sign bit is set e.g., 8bit: 128-255 - if (number & (1 << (bits - 1))) != 0: - - # compute negative value - number = number - (1 << bits) - - return number - - def _read_register(self, register_type: str, register_info: dict) -> Any: - """Read the values from a register based on length and sign - - Parameters: - - register_info:dict - dictionary with register definition fields - """ - - if "int" in register_info["data_format"]: - if register_info["data_length"] == 1: - val = self._unsigned16(register_type, register_info["address"]) - - if register_info["data_length"] == 2: - val = self._join_msb_lsb( - self._unsigned16(register_type, register_info["address"] + 1), - self._unsigned16(register_type, register_info["address"]), - ) - - if register_info["signed"]: - val = self._twos_complement(val, register_info["data_length"] * 16) - - val = val / register_info["si_adj"] - - elif "varchar" in register_info["data_format"]: - block = self._readRegisterRange( - register_type, register_info["address"], register_info["data_length"] - ) - sn = [] - for i in range(register_info["data_length"]): - first_byte, second_byte = unpack( - "BB", int.to_bytes(block[i], 2, "little") - ) - if not second_byte == 0x0: - sn.append(chr(second_byte)) - if not first_byte == 0x0: - sn.append(chr(first_byte)) - val = "".join(sn) - - elif "datetime" in register_info["data_format"]: - sec, min, hr, day, mon, year = self._readRegisterRange( - register_type, register_info["address"], register_info["data_length"] - ) - - inverter_datetime = ( - f"{(year+2000):02}-{mon:02}-{day:02} {hr:02}:{min:02}:{sec:02}" - ) - val = datetime.strptime(inverter_datetime, "%Y-%m-%d %H:%M:%S") - - return val - - def read_register(self, register_info: dict) -> tuple: - """Read the values from a register based on length and sign - - Parameters: - - register_info:dict - dictionary with register definition fields - """ - - val = self._read_register(register_info["register_type"], register_info) - - if not "data_unit" in register_info: - return (val, "N/A") - - return (val, register_info["data_unit"]) - - def read(self, name: str): - """Retrieve the value for the register with the provided name""" - - r = SolaxRegistersInfo() - - register_info = r.get_register_info(name) - value = self.read_register(register_info) - - return value - - def list_register_names(self): - r = SolaxRegistersInfo() - return r.list_register_names() - - def read_all_registers(self) -> None: - self._input_registers_values_list = [] - self._holding_registers_values_list = [] - - read_block_length = 100 - for i in range(3): - address = i * read_block_length - values_list = self.client.read_input_registers( - address=address, count=read_block_length, slave=1 - ).registers - self._input_registers_values_list.extend(values_list) - - for i in range(3): - address = i * read_block_length - values_list = self.client.read_holding_registers( - address=address, count=read_block_length, slave=1 - ).registers - self._holding_registers_values_list.extend(values_list) \ No newline at end of file diff --git a/source/src/solaxx3/solax_registers_info.py b/source/src/solaxx3/solax_registers_info.py new file mode 100644 index 0000000..41cdd16 --- /dev/null +++ b/source/src/solaxx3/solax_registers_info.py @@ -0,0 +1,3570 @@ +"""Module holding information about inverter's registers.""" + +from typing import Dict, Union, Literal + +FIELDS = Literal[ + "address", + "register_type", + "data_format", + "si_adj", + "signed", + "data_unit", + "data_length", + "description", +] +FIELD_VALUES = Union[int, str] + + +class SolaxRegistersInfo: + """Class holding information about inverter's registers.""" + + _registers: Dict[str, Dict[FIELDS, FIELD_VALUES]] = { + # input registers + "grid_voltage": { + "address": 0x0000, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid voltage", + }, + "grid_current": { + "address": 0x0001, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Grid current", + }, + "grid_power": { + "address": 0x0002, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Grid power", + }, + "pv_voltage_1": { + "address": 0x0003, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PV Voltage 1", + }, + "pv_voltage_2": { + "address": 0x0004, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PV Voltage 2", + }, + "pv_current_1": { + "address": 0x0005, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "PV Current 1", + }, + "pv_current_2": { + "address": 0x0006, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "PV Current 2", + }, + "grid_frequency": { + "address": 0x0007, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid frequency", + }, + "radiator_temperature": { + "address": 0x0008, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "C", + "data_length": 1, + "description": "Radiator temperature", + }, + "run_mode": { + "address": 0x0009, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Inverter status", + }, + "power_dc1": { + "address": 0x000A, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "W", + "data_length": 1, + "description": "DC Power String 1", + }, + "power_dc2": { + "address": 0x000B, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "W", + "data_length": 1, + "description": "DC Power String 2", + }, + "temperature_fault_value": { + "address": 0x000C, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "C", + "data_length": 1, + "description": "Temperature fault value", + }, + "pv1_volt_fault_value": { + "address": 0x000D, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PV1 volt fault value", + }, + "pv2_volt_fault_value": { + "address": 0x000E, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PV2 volt fault value", + }, + "gfci_fault_value": { + "address": 0x000F, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "GFCI fault value", + }, + "grid_fault_value": { + "address": 0x0010, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid fault value", + }, + "grid_freq_fault_value_t": { + "address": 0x0011, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid frequency fault value t", + }, + "dci_fault_value": { + "address": 0x0012, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "DCI fault value", + }, + "time_count_down": { + "address": 0x0013, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_unit": "second", + "data_length": 1, + "description": "Time remaining until the current operation finishes (e.g. checking)", + }, + "battery_voltage_charge_1": { + "address": 0x0014, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "V", + "data_length": 1, + "description": "Battery voltage charge 1", + }, + "battery_current_charge_1": { + "address": 0x0015, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Battery current charge 1", + }, + "battery_power_charge1": { + "address": 0x0016, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Battery power charge 1", + }, + "bms_connect_state": { + "address": 0x0017, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "BMS connect state", + }, + "temperature_battery": { + "address": 0x0018, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "C", + "data_length": 1, + "description": "Temperature of the battery", + }, + "bdc_status": { + "address": 0x0019, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "BDC Status", + }, + "grid_status": { + "address": 0x001A, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Grid Status: 0-OnGrid, 1-OffGrid", + }, + "mppt_count": { + "address": 0x001B, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "MPPT Count", + }, + "battery_capacity": { + "address": 0x001C, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "Battery charge percentage", + }, + "output_energy_charge": { + "address": 0x001D, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Output energy charge", + }, + "output_energy_charge_today": { + "address": 0x0020, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "Output energy charge today", + }, + "input_energy_charge": { + "address": 0x0021, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Input energy charge", + }, + "input_energy_charge_today": { + "address": 0x0023, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "Input energy charge today", + }, + "bms_charge_max_current": { + "address": 0x0024, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "BMS charge max current", + }, + "bms_discharge_max_current": { + "address": 0x0025, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "BMS discharge max current", + }, + "bms_battery_capacity": { + "address": 0x0026, + "register_type": "input", + "data_format": "uint32", + "si_adj": 1, + "signed": False, + "data_unit": "Wh", + "data_length": 2, + "description": "BMS Battery Capacity", + }, + "pcs_major_fault": { + "address": 0x003E, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "PCS Major Fault", + }, + "battery_major_fault": { + "address": 0x003F, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Battery Major Fault", + }, + "inv_fault_message": { + "address": 0x0040, + "register_type": "input", + "data_format": "uint32", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 2, + "description": "Inverter Fault Message: X1:Table 2-4, X2:Table 2-3", + }, + "mgr_fault_message": { + "address": 0x0043, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Manager Fault Message: Table 2-5", + }, + "bat_bms_fault_message": { + "address": 0x0044, + "register_type": "input", + "data_format": "uint32", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 2, + "description": "Battery BMS Fault Message - Table 2-6", + }, + "feed_in_power": { + "address": 0x0046, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Feed-in power obtained from Meter or CT", + }, + "energy_to_grid_meter": { + "address": 0x0048, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy to grid meter value", + }, + "energy_from_grid_meter": { + "address": 0x004A, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy from grid meter value", + }, + "off_grid_voltage": { + "address": 0x004C, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Off-Grid Voltage X1", + }, + "off_grid_current": { + "address": 0x004D, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "Off-Grid Current X1", + }, + "off_grid_power": { + "address": 0x004E, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "VA", + "data_length": 1, + "description": "Off Grid Power X1", + }, + "off_grid_frequency_input_reg": { + "address": 0x004F, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Off-Grid Frequency", + }, + "energy_to_grid_today": { + "address": 0x0050, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "The quantity of energy sent to grid today", + }, + "energy_to_grid_total": { + "address": 0x0052, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Total energy sent to grid", + }, + "lock_state": { + "address": 0x0054, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Lock State", + }, + "bus_volt": { + "address": 0x0066, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Bus Volt", + }, + "w_dcv_fault_val": { + "address": 0x0067, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "W DCV Fault Value", + }, + "w_overload_fault_val": { + "address": 0x0068, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "W", + "data_length": 1, + "description": "W Overload Fault Value", + }, + "w_battery_volt_fault_val": { + "address": 0x0069, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "W Battery Volt Fault Value", + }, + "grid_voltage_r": { + "address": 0x006A, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid voltage for R phase", + }, + "grid_current_r": { + "address": 0x006B, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Grid Current R Phase", + }, + "grid_power_r": { + "address": 0x006C, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Grid Power R Phase", + }, + "grid_frequency_r": { + "address": 0x006D, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid Frequency R Phase", + }, + "grid_voltage_s": { + "address": 0x006E, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid voltage for S phase", + }, + "grid_current_s": { + "address": 0x006F, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Grid Current S Phase", + }, + "grid_power_s": { + "address": 0x0070, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Grid Power S Phase", + }, + "grid_frequency_s": { + "address": 0x0071, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid Frequency S", + }, + "grid_voltage_t": { + "address": 0x0072, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid voltage for T phase", + }, + "grid_current_t": { + "address": 0x0073, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Grid Current T Phase", + }, + "grid_power_t": { + "address": 0x0074, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Grid Power T Phase", + }, + "grid_frequency_t": { + "address": 0x0075, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid Frequency T Phase", + }, + "off_grid_volt_r": { + "address": 0x0076, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Off-Grid Volt R Phase", + }, + "off_grid_current_r": { + "address": 0x0077, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "Off-Grid Current R Phase", + }, + "off_grid_power_active_r": { + "address": 0x0078, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Off-Grid Active Power R Phase", + }, + "off_grid_power_s_r": { + "address": 0x0079, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "VA", + "data_length": 1, + "description": "Off-Grid S Power R Phase", + }, + "off_grid_volt_s": { + "address": 0x007A, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Off-Grid Volt S Phase", + }, + "off_grid_current_s": { + "address": 0x007B, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "Off-Grid Current S Phase", + }, + "off_grid_power_active_s": { + "address": 0x007C, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Off-Grid Active Power S Phase", + }, + "off_grid_power_s_s": { + "address": 0x007D, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "VA", + "data_length": 1, + "description": "Off-Grid S Power S Phase", + }, + "off_grid_volt_t": { + "address": 0x007E, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Off-Grid Volt T Phase", + }, + "off_grid_current_t": { + "address": 0x007F, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "Off-Grid Current T Phase", + }, + "off_grid_power_active_t": { + "address": 0x0080, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Off-Grid Active Power T Phase", + }, + "off_grid_power_s_t": { + "address": 0x0081, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "VA", + "data_length": 1, + "description": "Off-Grid S Power T Phase", + }, + "feedin_power_r_phase": { + "address": 0x0082, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Feedin Power R Phase", + }, + "feedin_power_s_phase": { + "address": 0x0084, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Feedin Power S Phase", + }, + "feedin_power_t_phase": { + "address": 0x0086, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Feedin Power T Phase", + }, + "ongrid_run_time": { + "address": 0x0088, + "register_type": "input", + "data_format": "int32", + "si_adj": 10, + "signed": True, + "data_unit": "hour", + "data_length": 2, + "description": "On-Grid Run Time", + }, + "offgrid_run_time": { + "address": 0x008A, + "register_type": "input", + "data_format": "int32", + "si_adj": 10, + "signed": True, + "data_unit": "hour", + "data_length": 2, + "description": "Off-Grid Run Time", + }, + "offgrid_yield_total": { + "address": 0x008E, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Off-Grid Yield Total", + }, + "offgrid_yield_today": { + "address": 0x0090, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "Off-Grid Yield Today", + }, + "echarge_today": { + "address": 0x0091, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "Echarge energy today", + }, + "echarge_total": { + "address": 0x0092, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Echarge Total", + }, + "solar_energy_total": { + "address": 0x0094, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Total solar energy", + }, + "solar_energy_today": { + "address": 0x0096, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "Solar energy today", + }, + "feed_in_energy_today": { + "address": 0x0098, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy to grid meter for today", + }, + "consumption_energy_today": { + "address": 0x009A, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy from grid meter for today", + }, + "inv_volt_r": { + "address": 0x009C, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Inverter Volt R Phase", + }, + "inv_volt_s": { + "address": 0x009D, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Inverter Volt S Phase", + }, + "inv_volt_t": { + "address": 0x009E, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Inverter Volt T Phase", + }, + "feedin_power_meter_2": { + "address": 0x00A8, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Power to Grid Meter 2", + }, + "feedin_energy_total_meter_2": { + "address": 0x00AA, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy to the grid", + }, + "consum_energy_total_meter_2": { + "address": 0x00AC, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy from grid", + }, + "feedin_energy_today_meter_2": { + "address": 0x00AE, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy to grid", + }, + "consum_energy_today_meter_2": { + "address": 0x00B0, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy from grid", + }, + "feedin_power_r_phase_meter_2": { + "address": 0x00B2, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Feedin Power R Phase Meter 2", + }, + "feedin_power_s_phase_meter_2": { + "address": 0x00B4, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Feedin Power S Phase Meter 2", + }, + "feedin_power_t_phase_meter_2": { + "address": 0x00B6, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Feedin Power T Phase Meter 2", + }, + "meter_1_communication_state": { + "address": 0x00B8, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Meter 1 Communication State: 0:Com Error, 1:Normal", + }, + "meter_2_communication_state": { + "address": 0x00B9, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Meter 2 Communication State: 0:Com Error, 1:Normal", + }, + "battery_tem_high": { + "address": 0x00BA, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "C", + "data_length": 1, + "description": "Battery Temperature High", + }, + "battery_tem_low": { + "address": 0x00BB, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "C", + "data_length": 1, + "description": "Battery Temperature Low", + }, + "cell_voltage_high": { + "address": 0x00BC, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Cell Voltage High", + }, + "cell_voltage_low": { + "address": 0x00BD, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Cell Voltage Low", + }, + "bms_user_soc": { + "address": 0x00BE, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "BMS User SOC", + }, + "bms_user_soh": { + "address": 0x00BF, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "BMS User SOH", + }, + "grid_reactive_power_total_meter": { + "address": 0x00C0, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 1, + "description": "Grid Reactive Power Total Meter", + }, + "grid_reactive_power_r_meter": { + "address": 0x00C1, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 1, + "description": "Grid Reactive Power Phase R Meter", + }, + "grid_reactive_power_s_meter": { + "address": 0x00C2, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 1, + "description": "Grid Reactive Power Phase S Meter", + }, + "grid_reactive_power_t_meter": { + "address": 0x00C3, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 1, + "description": "Grid Reactive Power Phase T Meter", + }, + "grid_power_factor_total_meter": { + "address": 0x00C4, + "register_type": "input", + "data_format": "int16", + "si_adj": 100, + "signed": True, + "data_unit": "N/A", + "data_length": 1, + "description": "Grid Power Factor Total Meter", + }, + "grid_power_factor_r_meter": { + "address": 0x00C5, + "register_type": "input", + "data_format": "int16", + "si_adj": 100, + "signed": True, + "data_unit": "N/A", + "data_length": 1, + "description": "Grid Power Factor Phase R Meter", + }, + "grid_power_factor_s_meter": { + "address": 0x00C6, + "register_type": "input", + "data_format": "int16", + "si_adj": 100, + "signed": True, + "data_unit": "N/A", + "data_length": 1, + "description": "Grid Power Factor Phase S Meter", + }, + "grid_power_factor_t_meter": { + "address": 0x00C7, + "register_type": "input", + "data_format": "int16", + "si_adj": 100, + "signed": True, + "data_unit": "N/A", + "data_length": 1, + "description": "Grid Power Factor Phase T Meter", + }, + "grid_frequency_meter": { + "address": 0x00C8, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid Frequency Meter", + }, + "grid_voltage_total_meter": { + "address": 0x00C9, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid Voltage Total Meter", + }, + "grid_voltage_r_meter": { + "address": 0x00CA, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid Voltage R Meter", + }, + "grid_voltage_s_meter": { + "address": 0x00CB, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid Voltage S Meter", + }, + "grid_voltage_t_meter": { + "address": 0x00CC, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid Voltage T Meter", + }, + "grid_current_total_meter": { + "address": 0x00CD, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Grid Current Total Meter", + }, + "grid_current_r_meter": { + "address": 0x00CE, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Grid Current R Meter", + }, + "grid_current_s_meter": { + "address": 0x00CF, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Grid Current S Meter", + }, + "grid_current_t_meter": { + "address": 0x00D0, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Grid Current T Meter", + }, + "modbus_power_control": { + "address": 0x0100, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": """Modbus Power Control + 0:disable remote contro + 1:enable power control + 2:enable electric quality control + 3:enable SOC target control""", + }, + "target_finish_tag": { + "address": 0x0101, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Target Finish Tag: 0:unfinished, 1:finished", + }, + "active_power_target": { + "address": 0x0102, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Active Power Target", + }, + "w_reactive_power_target": { + "address": 0x0104, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 2, + "description": "W Reactive Power Target", + }, + "w_active_power_real": { + "address": 0x0106, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "W Active Power Real", + }, + "w_reactive_power_real": { + "address": 0x0108, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 2, + "description": "W Reactive Power Real", + }, + "w_active_power_upper": { + "address": 0x010A, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "W Active Power Upper", + }, + "w_active_power_lower": { + "address": 0x010C, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "W Active Power Lower", + }, + "w_reactive_power_upper": { + "address": 0x010E, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 2, + "description": "W Reactive Power Upper", + }, + "w_reactive_power_lower": { + "address": 0x0110, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 2, + "description": "W Reactive Power Lower", + }, + "target_energy": { + "address": 0x0112, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "Wh", + "data_length": 2, + "description": "Target Energy", + }, + "charge_discharge_power": { + "address": 0x0114, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Charge Discharge Power", + }, + "chargeable_electric_capacity": { + "address": 0x0116, + "register_type": "input", + "data_format": "uint32", + "si_adj": 1, + "signed": False, + "data_unit": "Wh", + "data_length": 2, + "description": "Chargeable Electric Capacity", + }, + "dischargeable_electric_capacity": { + "address": 0x0118, + "register_type": "input", + "data_format": "uint32", + "si_adj": 1, + "signed": False, + "data_unit": "Wh", + "data_length": 2, + "description": "Dischargeable Electric Capacity", + }, + "time_of_duration": { + "address": 0x011A, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "second", + "data_length": 1, + "description": "time_of_duration", + }, + "target_soc": { + "address": 0x011B, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "Target SOC", + }, + "soc_upper": { + "address": 0x011C, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "SOC Upper", + }, + "soc_lower": { + "address": 0x011D, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "SOC Lower", + }, + "remote_control_timeout": { + "address": 0x011E, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "second", + "data_length": 1, + "description": "Remote Control Timeout: 4-65535", + }, + "w_battery_force_charge_flag": { + "address": 0x011F, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "W Battery Force Charge Flag: 0:No Action, 1:Force Charge", + }, + "w_bms_relay_state": { + "address": 0x0120, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "W BMS Relay State: 0:OFF, 1:ON", + }, + # holding registers + "serial_number": { + "address": 0x0000, + "register_type": "holding", + "data_format": "varchar", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 7, + "description": "Inverter' serial number", + }, + "factory_name": { + "address": 0x0007, + "register_type": "holding", + "data_format": "varchar", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 7, + "description": "Factory name", + }, + "module_name": { + "address": 0x000E, + "register_type": "holding", + "data_format": "varchar", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 7, + "description": "Module name", + }, + "start_time": { + "address": 0x0016, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "second", + "data_length": 1, + "description": "Launch wait time", + }, + "reconnection_time": { + "address": 0x0017, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "second", + "data_length": 1, + "description": "Reconnection time", + }, + "checking_time": { + "address": 0x0018, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "second", + "data_length": 1, + "description": "Checking time", + }, + "vac_min_protect": { + "address": 0x0019, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Allowed minimum grid voltage", + }, + "vac_max_protect": { + "address": 0x001A, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Allowed maximum grid voltage", + }, + "fac_min_protect": { + "address": 0x001B, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Allowed minimum grid frequency", + }, + "fac_max_protect": { + "address": 0x001C, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Allowed maximum grid frequency", + }, + "safety_code": { + "address": 0x001D, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Safety type", + }, + "mate_box_enable": { + "address": 0x001E, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "0:Disable 1:Enable", + }, + "grid_10_min_avg_protect": { + "address": 0x001F, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "10minutes over voltage protect", + }, + "vac_min_slow_protect": { + "address": 0x0020, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid undervoltage protect value", + }, + "vac_max_slow_protect": { + "address": 0x0021, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid overvoltage protect value", + }, + "fac_min_slow_protect": { + "address": 0x0022, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid underfrequency protect value", + }, + "fac_max_slow_protect": { + "address": 0x0023, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid overfrequency protect value", + }, + "power_limits_percent": { + "address": 0x0025, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "Output power limits precent", + }, + "powerfactor_mode": { + "address": 0x0026, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "0:Off 1:Over Excited 2:Under Excited 3:Curve 4:Qu 5:Fix Q Power", + }, + "powerfactor_data": { + "address": 0x0027, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "Power factor data", + }, + "power_factor_curve_pf1": { + "address": 0x0028, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "PowerFactor_Curve_PF1", + }, + "power_factor_curve_pf2": { + "address": 0x0029, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "PowerFactor_Curve_PF2", + }, + "power_factor_curve_pf3": { + "address": 0x002A, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "PowerFactor_Curve_PF3", + }, + "power_factor_curve_pf4": { + "address": 0x002B, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "PowerFactor_Curve_PF4", + }, + "power_factor_curve_power1": { + "address": 0x002C, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "PowerFactor_Curve_Power1", + }, + "power_factor_curve_power2": { + "address": 0x002D, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "PowerFactor_Curve_Power2", + }, + "power_factor_curve_power3": { + "address": 0x002E, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "PowerFactor_Curve_Power3", + }, + "power_factor_curve_power4": { + "address": 0x002F, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "PowerFactor_Curve_Power4", + }, + "power_factor_curve_pf_lock_in_point": { + "address": 0x0030, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "PowerFactor_Curve_PfLockInPoint", + }, + "power_factor_curve_pf_lock_out_point": { + "address": 0x0031, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "PowerFactor_Curve_PfLockOutPoint", + }, + "power_factor_curve_3_tau": { + "address": 0x0032, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "second", + "data_length": 1, + "description": "PowerFactor_Curve_3Tau", + }, + "power_factor_qu_volt_ratio_1": { + "address": 0x0033, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "PowerFactor_Qu_VoltRatio1", + }, + "power_factor_qu_volt_ratio_4": { + "address": 0x0034, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "PowerFactor_Qu_VoltRatio4", + }, + "power_factor_qu_qu_response_v1": { + "address": 0x0035, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PowerFactor_Qu_QuResponseV1", + }, + "power_factor_qu_qu_response_v2": { + "address": 0x0036, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PowerFactor_Qu_QuResponseV2", + }, + "power_factor_qu_qu_response_v3": { + "address": 0x0037, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PowerFactor_Qu_QuResponseV3", + }, + "power_factor_qu_qu_response_v4": { + "address": 0x0038, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PowerFactor_Qu_QuResponseV4", + }, + "power_factor_qu_k": { + "address": 0x0039, + "register_type": "holding", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "N/A", + "data_length": 1, + "description": "PowerFactor_Qu_K", + }, + "power_factor_qu_3_tau": { + "address": 0x003A, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "second", + "data_length": 1, + "description": "PowerFactor_Qu_3Tau", + }, + "power_factor_qu_qu_delay_timer": { + "address": 0x003B, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "second", + "data_length": 1, + "description": "PowerFactor_Qu_QuDelayTimer", + }, + "power_factor_qu_qu_lock_enable": { + "address": 0x003C, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "N/A", + "data_length": 1, + "description": "PowerFactor_Qu_QuLockEn; 0:Disable 1:Enable", + }, + "power_factor_qu_qu_lock_in": { + "address": 0x003D, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "PowerFactor_Qu_QuLockIn", + }, + "power_factor_qu_qu_lock_out": { + "address": 0x003E, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "PowerFactor_Qu_QuLockOut", + }, + "power_factor_fix_q_power": { + "address": 0x003F, + "register_type": "holding", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 1, + "description": "PowerFactor_FixQPower", + }, + "power_factor_fix_q_power_max": { + "address": 0x0040, + "register_type": "holding", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 1, + "description": "PowerFactor_FixQPower_Max", + }, + "power_factor_fix_q_power_min": { + "address": 0x0041, + "register_type": "holding", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "Var", + "data_length": 1, + "description": "PowerFactor_FixQPower_Min", + }, + "w_connection_fl": { + "address": 0x0042, + "register_type": "holding", + "data_format": "int16", + "si_adj": 100, + "signed": True, + "data_unit": "Hz", + "data_length": 1, + "description": "Connection Low frequency", + }, + "w_connection_fh": { + "address": 0x0043, + "register_type": "holding", + "data_format": "int16", + "si_adj": 100, + "signed": True, + "data_unit": "Hz", + "data_length": 1, + "description": "Connection High frequency", + }, + "w_connection_vl": { + "address": 0x0044, + "register_type": "holding", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "V", + "data_length": 1, + "description": "Connection Low voltage", + }, + "w_connection_vh": { + "address": 0x0045, + "register_type": "holding", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "V", + "data_length": 1, + "description": "Connection High voltage", + }, + "w_connection_observe_t": { + "address": 0x0046, + "register_type": "holding", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "second", + "data_length": 1, + "description": "Connection Observation time", + }, + "w_connection_gradient_enable": { + "address": 0x0047, + "register_type": "holding", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "N/A", + "data_length": 1, + "description": "Connection Gradient Select", + }, + "w_reconnection_fl": { + "address": 0x0048, + "register_type": "holding", + "data_format": "int16", + "si_adj": 100, + "signed": True, + "data_unit": "Hz", + "data_length": 1, + "description": "Reconnection Low frequency", + }, + "w_reconnection_fh": { + "address": 0x0049, + "register_type": "holding", + "data_format": "int16", + "si_adj": 100, + "signed": True, + "data_unit": "Hz", + "data_length": 1, + "description": "Reconnection High frequency", + }, + "w_reconnection_vl": { + "address": 0x004A, + "register_type": "holding", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "V", + "data_length": 1, + "description": "Reconnection Low voltage", + }, + "w_reconnection_vh": { + "address": 0x004B, + "register_type": "holding", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "V", + "data_length": 1, + "description": "Reconnection High voltage", + }, + "w_reconnection_observe_t": { + "address": 0x004C, + "register_type": "holding", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "second", + "data_length": 1, + "description": "Reconnection Observation time", + }, + "w_reconnection_gradient_enable": { + "address": 0x004D, + "register_type": "holding", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "N/A", + "data_length": 1, + "description": "Reconnection Gradient Select", + }, + "w_reconnection_gradient": { + "address": 0x004E, + "register_type": "holding", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "%", + "data_length": 1, + "description": "Reconnection Gradient", + }, + "firmware_version_dsp_minor": { + "address": 0x007D, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Firmware version DSP Minor", + }, + "hardware_version_dsp": { + "address": 0x007E, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Hardware version DSP", + }, + "firmware_version_dsp_major": { + "address": 0x007F, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Firmware version DSP major", + }, + "firmware_version_arm_major": { + "address": 0x0080, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Firmware version ARM major", + }, + "firmware_version_modbus_rtu": { + "address": 0x0082, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Firmware version MODBUS RTU", + }, + "firmware_version_arm_minor": { + "address": 0x0083, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Firmware version ARM minor", + }, + "firmware_version_arm_bootloader": { + "address": 0x0084, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Firmware version ARM Bootloader", + }, + "rtc_datetime": { + "address": 0x0085, + "register_type": "holding", + "data_format": "datetime", + "si_adj": 1, + "signed": False, + "data_length": 6, + "data_unit": "N/A", + "description": "RTC datetime", + }, + "solar_charger_use_mode": { + "address": 0x008B, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Solar Charger Use Mode", + }, + "manual_mode": { + "address": 0x008C, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Manual Mode", + }, + "w_battery1_type": { + "address": 0x008D, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "wBattery1 Type; 0:Lead Acid 1:Lithium", + }, + "charge_float_volt": { + "address": 0x008E, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "Lead-acid battery charge_float voltage", + }, + "battery_discharge_cut_voltage": { + "address": 0x008F, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "Lead-acid battery discharge cut-off voltage", + }, + "battery_charge_max_current": { + "address": 0x0090, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "A", + "description": "Lead-acid battery charge maximum current", + }, + "battery_discharge_max_current": { + "address": 0x0091, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "A", + "description": "Lead-acid battery discharge maximum Current", + }, + "absorpt_voltage": { + "address": 0x0092, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "Lead-acid battery absorpt voltage", + }, + "self_use_night_charge_upper_soc": { + "address": 0x0094, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "This value will be enabled if SelfUse_NightCharge_Enable is 1.", + }, + "set_charge_and_discharge_period_2_enable": { + "address": 0x009B, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Whether to use period 2.", + }, + "eps_restart_soc": { + "address": 0x00A0, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Eps restart soc; 10-100", + }, + "hot_standby_enable": { + "address": 0x00A1, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Hot Standby EN; 0:Disable 1:Enable", + }, + "extend_bms_setting": { + "address": 0x00A2, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Extend Bms Setting; 0:Disable 1:Enable", + }, + "battery_heating_enable": { + "address": 0x00A3, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Battery Heating En; 0:Disable 1:Enable", + }, + "w_battery_discharge_backup_voltage": { + "address": 0x00A8, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "wBattery Discharge Backup Voltage", + }, + "match_resistance_set": { + "address": 0x00A9, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "(X3) match resistance set; 0:Disable 1:Enable", + }, + "registration_code": { + "address": 0x00AA, + "register_type": "holding", + "data_format": "varchar", + "si_adj": 1, + "signed": False, + "data_length": 5, + "data_unit": "N/A", + "description": "Registration code for external module", + }, + "modbus_rtu_address": { + "address": 0x00AF, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Modbus RTU Address", + }, + "modbus_rtu_braud_rate": { + "address": 0x00B0, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Modbus RTU braud rate; 0:115200 1:57600 2:56000 3:38400 4:19200 5:14400 6:9600", + }, + "eps_bat_low_auto_recover_voltage": { + "address": 0x00B1, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "Eps Bat Low Auto Recover Voltage", + }, + "p_grid_bias": { + "address": 0x00B2, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "P grid Bias: 0:Disable 1:Grid 2:INV", + }, + "factory_limit": { + "address": 0x00B5, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "W", + "description": "Factory limit", + }, + "export_control_user_limit": { + "address": 0x00B6, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "W", + "description": "export control user limit", + }, + "off_grid_mute": { + "address": 0x00B7, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Off-grid mute: 0(off)/1(on)", + }, + "off_grid_min_soc": { + "address": 0x00B8, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Off-grid Min SoC", + }, + "off_grid_frequency_holding_reg": { + "address": 0x00B9, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Off-grid Frequency", + }, + "inverter_power_type": { + "address": 0x00BA, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "W", + "description": "Inverter power type", + }, + "language": { + "address": 0x00BB, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Language", + }, + "enable_mppt": { + "address": 0x00BC, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Enable MPPT", + }, + "wtuvp_l2": { + "address": 0x00BD, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "wTuvp L2", + }, + "wtovp_l2": { + "address": 0x00BE, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "wTovp L2", + }, + "wtufp_l2": { + "address": 0x00BF, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "wTufp L2", + }, + "wtofp_l2": { + "address": 0x00C0, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "wTofp L2", + }, + "wtuvp_l1": { + "address": 0x00C1, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "wTuvp L1", + }, + "wtovp_l1": { + "address": 0x00C2, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "wTovp L1", + }, + "wtufp_l1": { + "address": 0x00C3, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "wTufp L1", + }, + "wtofp_l1": { + "address": 0x00C4, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "wTofp L1", + }, + "test_step": { + "address": 0x00C5, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Test Step", + }, + "ovp_value_ovp_59_s2": { + "address": 0x00C6, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "OvpValue(Ovp(59.S2))", + }, + "ovp_time_ovp_59_s2": { + "address": 0x00C7, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "OvpTime(Ovp(59.S2))", + }, + "uvp_value_uvp_27_s1": { + "address": 0x00C8, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "UvpValue(Uvp(27.S1))", + }, + "uvp_time_uvp_27_s1": { + "address": 0x00C9, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "UvpTime(Uvp(27.S1))", + }, + "ofp_value_ofp_81_s1": { + "address": 0x00CA, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "OfpValue(Ofp(81>.S1))", + }, + "ofp_time_ofp_81_s1": { + "address": 0x00CB, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "OfpTime(Ofp(81>.S1))", + }, + "ufp_value_ufp_81_s1": { + "address": 0x00CC, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "UfpValue(Ufp(81<.S1))", + }, + "ufp_time_ufp_81_s1": { + "address": 0x00CD, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "UfpTime(Ufp(81<.S1))", + }, + "self_test_ovp_10m_avg_val": { + "address": 0x00CE, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "Self Test Ovp 10m Avg Val (Ovp_10(59.S1))", + }, + "self_test_ovp_10m_avg_time": { + "address": 0x00CF, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "Self Test Ovp 10m Avg Time (Ovp_10(59.S1))", + }, + "self_test_ofp_val_restrictive": { + "address": 0x00D0, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "Self Test Ofp Val Restrictive (Ofp2(81>.S2))", + }, + "self_test_ofp_time_restrictive": { + "address": 0x00D1, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "Self Test Ofp Time Restrictive (Ofp2(81>.S2))", + }, + "self_test_ufp_val_restrictive": { + "address": 0x00D2, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "Self Test Ufp Val Restrictive (Ufp2(81<.S2))", + }, + "self_test_ufp_time_restrictive": { + "address": 0x00D3, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "Self Test Ufp Time Restrictive (Ufp2(81<.S2))", + }, + "self_test_uvp_restrictive_val": { + "address": 0x00D4, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "Self Test Uvp Restrictive Val (Uvp(27.S2))", + }, + "self_test_uvp_restrictive_time": { + "address": 0x00D5, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "Self Test Uvp Restrictive Time (Uvp(27.S2))", + }, + "self_test_time": { + "address": 0x00D6, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "Self Test Time", + }, + "main_breaker_current_limit": { + "address": 0x00D7, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "A", + "description": "Main Breaker Current Limit", + }, + "w_inverter_output_switch": { + "address": 0x00DA, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "w Inverter OutPut Switch", + }, + "ofpl_point": { + "address": 0x00DB, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "Overfrequency load reduction point", + }, + "ofpl_set_rate": { + "address": 0x00DC, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Overfrequency load reduction rate", + }, + "ofpl_delay_time": { + "address": 0x00DD, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "Overfrequency load reduction delay time", + }, + "ofpl_fstop_disch": { + "address": 0x00DE, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "OFPL fstop disch", + }, + "ofpl_fpmin": { + "address": 0x00DF, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "OFPL_fPmin", + }, + "user_password": { + "address": 0x00E0, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "User password", + }, + "advanced_password": { + "address": 0x00E1, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Advanced password", + }, + "ufpl_point": { + "address": 0x00E2, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "Underfrequency load increase point", + }, + "ufpl_set_rate": { + "address": 0x00E3, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Underfrequency load increase rate", + }, + "ufpl_delay_time": { + "address": 0x00E4, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "Underfrequency load increase delay time", + }, + "ofpl_curve_type": { + "address": 0x00E5, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Overfrequency load reduction curve type selection.", + }, + "ofpl_tstop": { + "address": 0x00E6, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "Overfrequency load reduction asymmetry curve stop time", + }, + "ofpl_remove_point": { + "address": 0x00E7, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "Overfrequency load reduction frequency remove point", + }, + "ufpl_remove_point": { + "address": 0x00E8, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "Underfrequency load increase frequency remove point", + }, + "export_soft_limit_enable": { + "address": 0x00E9, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Export Soft Limit En", + }, + "export_hard_limit_enable": { + "address": 0x00EA, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Export Hard Limit En", + }, + "general_soft_limit_enable": { + "address": 0x00EB, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "General Soft Limit En", + }, + "general_hard_limit_enable": { + "address": 0x00EC, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "General Hard Limit En", + }, + "w_ac_power_limit": { + "address": 0x00ED, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "VA", + "description": "w Ac Power Limit", + }, + "connect_slop": { + "address": 0x00EE, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Connect slop", + }, + "reconnect_slop": { + "address": 0x00EF, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Reconnect slop", + }, + "hard_export_power": { + "address": 0x00F0, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "W", + "description": "Hard Export Power", + }, + "hard_ac_power_t_limit": { + "address": 0x00F1, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "VA", + "description": "Hard Ac Power t Limit", + }, + "setpoint_timeout": { + "address": 0x00F2, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_length": 1, + "data_unit": "second", + "description": "Setpoint Timeout", + }, + "w_power_limit_gra": { + "address": 0x00F3, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10000, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "w Power Limit Gra", + }, + "pu_func_volt_response_v2": { + "address": 0x00F4, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "PuFunction Voltage", + }, + "pu_func_volt_response_v3": { + "address": 0x00F5, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "PuFunction Voltage", + }, + "pu_func_volt_response_v4": { + "address": 0x00F6, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "Pu Function Voltage", + }, + "pu_func_volt_response_v1": { + "address": 0x00F7, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "Pu Function Voltage", + }, + "pu_func_3tau": { + "address": 0x00F8, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Pu Func 3Tau", + }, + "pu_func_enable": { + "address": 0x00F9, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "PU Func Enable", + }, + "set_pu_power1": { + "address": 0x00FA, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Set Pu Power1", + }, + "set_pu_power2": { + "address": 0x00FB, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Set Pu Power2", + }, + "set_pu_power3": { + "address": 0x00FC, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Set Pu Power3", + }, + "set_pu_power4": { + "address": 0x00FD, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "Set Pu Power4", + }, + "pu_tpye": { + "address": 0x00FF, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Pu Tpye", + }, + "ufpl_fstop_ch": { + "address": 0x0100, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "UFPL fstop ch", + }, + "ufpl_fpmax": { + "address": 0x0101, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_length": 1, + "data_unit": "Hz", + "description": "UFPL fPmax", + }, + "drm_function_enable": { + "address": 0x0102, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "DRM Function Enable", + }, + "ct_type": { + "address": 0x0103, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Ct Type", + }, + "w_shadow_fix_func_enable": { + "address": 0x0104, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "w Shadow Fix Func Enable", + }, + "machine_type": { + "address": 0x0105, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Machine type: 1:X1, 3:X3", + }, + "phase_power_balance": { + "address": 0x0106, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Phase Power Balance", + }, + "w_machine_style": { + "address": 0x0107, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "w Machine Style", + }, + "meter_function": { + "address": 0x0108, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Meter Function", + }, + "meter1_id": { + "address": 0x0109, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Meter1 ID", + }, + "meter2_id": { + "address": 0x010A, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Meter2 ID", + }, + "direction_meter_ct_1": { + "address": 0x010B, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Direction Meter CT 1", + }, + "direction_meter_2": { + "address": 0x010C, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Direction Meter 2", + }, + "external_inv": { + "address": 0x010D, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "External Inv", + }, + "input_di_1": { + "address": 0x0110, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Input DI 1; 0:Low-level or 1:High-level", + }, + "disch_cut_off_point_different_enable": { + "address": 0x0111, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Whether Lead-acid Battery discharge cut-off voltage point is enable.", + }, + "disch_cut_off_voltage_grid_mode": { + "address": 0x0113, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "Lead-acid Battery discharge cut-off voltage in on-grid mode", + }, + "shadow_fix_func_enable_2": { + "address": 0x0114, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Shadow Fix Func Enable 2", + }, + "meter_ct_select": { + "address": 0x0115, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Meter/CT select", + }, + "fvrt_function": { + "address": 0x0116, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "FVRT Function", + }, + "fvrt_vac_upper": { + "address": 0x0117, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "If FVRT_Function is enable, FVRT Vac upper limit is available.", + }, + "fvrt_vac_lower": { + "address": 0x0118, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "V", + "description": "If FVRT_Function is enable, FVRT Vac lower limit is available.", + }, + "bpv_connection_mode": { + "address": 0x011B, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "PV connection", + }, + "shut_down": { + "address": 0x011C, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Shut Down", + }, + "micro_grid": { + "address": 0x011D, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Micro Grid", + }, + "self_use_mode_backup_enable": { + "address": 0x011E, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Selfuse Mode Backup En", + }, + "b_self_use_backup_soc": { + "address": 0x011F, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "b Self Use Backup Soc", + }, + "b_lease_mode_enable": { + "address": 0x0120, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "b Lease Mode Enable", + }, + "b_device_lock_flag": { + "address": 0x0121, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "b Device Lock Flag", + }, + "manual_mode_control": { + "address": 0x0122, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Manual Mode Control", + }, + "feedin_on_power": { + "address": 0x0123, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "W", + "description": "Grid connected pull in power point", + }, + "b_switch_on_soc": { + "address": 0x0124, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "SOC trigger point of pull in action", + }, + "consume_off_power": { + "address": 0x0125, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "W", + "description": "Power consumption off trigger point", + }, + "b_switch_off_soc": { + "address": 0x0126, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "%", + "description": "SOC trigger point of breaking action", + }, + "minimum_per_on_signal": { + "address": 0x0127, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "min", + "description": "Minimum duration of single pull in", + }, + "maximum_per_day_on": { + "address": 0x0128, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Maximum cumulative pickup time of the day", + }, + "b_schedule_enable": { + "address": 0x0129, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "b Schedule Enable", + }, + "work_mode": { + "address": 0x012E, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Work Mode", + }, + "dry_contact_mode": { + "address": 0x012F, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Dry Contact Mode", + }, + "parallel_setting": { + "address": 0x0130, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "Parallel Setting", + }, + "external_gen_enable": { + "address": 0x0131, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "External Gen En", + }, + "external_gen_max_charge": { + "address": 0x0132, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_length": 1, + "data_unit": "W", + "description": "External Gen Max Charge", + }, + "485_comm_fun_select": { + "address": 0x013E, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "data_unit": "N/A", + "description": "485 Comm Fun Select", + }, + } + + def get_register_info(self, name: str) -> dict: + """Get the information about a register.""" + + return self._registers[name] + + def list_register_names(self) -> list: + """Return all registers defined in this class.""" + + return list(self._registers.keys()) + + def list_holding_registers(self) -> list: + """Return all holding registers defined in this class.""" + + f = lambda register: self._registers[register]["register_type"] == "holding" + return list(filter(f, self._registers)) + + def list_input_registers(self) -> list: + """Return all input registers defined in this class.""" + + f = lambda register: self._registers[register]["register_type"] == "input" + return list(filter(f, self._registers)) diff --git a/source/src/solaxx3/solaxx3.py b/source/src/solaxx3/solaxx3.py new file mode 100644 index 0000000..15ec4ce --- /dev/null +++ b/source/src/solaxx3/solaxx3.py @@ -0,0 +1,198 @@ +"""Class loading and storing data from the inverter.""" + +from datetime import datetime +from struct import unpack +from typing import Dict, Iterable, List, Literal, Tuple, Union + +from pymodbus.client import ModbusSerialClient + +from .solax_registers_info import FIELD_VALUES, FIELDS, SolaxRegistersInfo +from .utils import join_msb_lsb, twos_complement + +REGISTER_VALUE = Union[float, str, datetime] +REGISTER_INFO = Dict[FIELDS, FIELD_VALUES] + + +class SolaxX3: + """ + Class interacting with values from the inverter. + Initialization parameters: + - method (default: rtu) + - port (default: /dev/ttyUSB0 ) + - baudrate: Bits per second (default: 115,200 ) + - timeout: Timeout for a request, in seconds. (default: 3) + - parity: 'E'ven, 'O'dd, 'N'one (default: N) + - stopbits: Number of stop bits 0-2 (default: 1) + - bytesize: Number of bits per byte (7 or 8) (default: 8) + """ + + connected: bool = False + READ_BLOCK_LENGTH = 100 + + def __init__( + self, + method: str = "rtu", + port: str = "/dev/ttyUSB0", + baudrate: int = 115200, + timeout: int = 3, + parity: Literal["E", "O", "N"] = "N", + stopbits: Literal[0, 1, 2] = 1, + bytesize: Literal[7, 8] = 8, + ) -> None: + self._input_registers_values: List[int] = [] + self._holding_registers_values: List[int] = [] + + self.client: ModbusSerialClient = ModbusSerialClient( + method=method, + port=port, + baudrate=baudrate, + timeout=timeout, + parity=parity, + stopbits=stopbits, + bytesize=bytesize, + ) + + def connect(self) -> bool: + """Connect to the inverter and return if it was successful.""" + + self.connected: bool = self.client.connect() + return self.connected + + def _get_unsigned_16(self, value_type: str, address: int) -> int: + if value_type == "input": + return self._input_registers_values[address] + return self._holding_registers_values[address] + + def _read_register_range( + self, value_type: str, address: int, count: int = 1 + ) -> list: + if value_type == "input": + return self._input_registers_values[address : address + count] + return self._holding_registers_values[address : address + count] + + def _read_format_register_value( + self, register_info: REGISTER_INFO + ) -> REGISTER_VALUE: + """Read the values from a register based on length and sign + + Parameters: + register_info:dict - dictionary with register definition fields + """ + + if self._is_register_type_integer(register_info): + value = self._get_integer_value(register_info) + value = value / register_info["si_adj"] + + elif self._is_register_type_string(register_info): + value = self._get_string_value(register_info) + + else: + value = self._get_datetime_value(register_info) + + return value + + def _get_datetime_value(self, register_info: REGISTER_INFO) -> datetime: + register_type = register_info["register_type"] + + sec, minute, hr, day, mon, year = self._read_register_range( + register_type, register_info["address"], register_info["data_length"] + ) + inverter_datetime = f"{year:02}-{mon:02}-{day:02} {hr:02}:{minute:02}:{sec:02}" + value = datetime.strptime(inverter_datetime, "%y-%m-%d %H:%M:%S") + return value + + def _is_register_type_datetime(self, register_info: REGISTER_INFO) -> bool: + return "datetime" in register_info["data_format"] + + def _get_string_value(self, register_info: REGISTER_INFO) -> str: + characters: List[str] = [] + register_type: Literal["input", "holding"] = register_info["register_type"] + block = self._read_register_range( + register_type, register_info["address"], register_info["data_length"] + ) + + for i in range(register_info["data_length"]): + first_byte, second_byte = unpack("BB", int.to_bytes(block[i], 2, "little")) + if not second_byte == 0x0: + characters.append(chr(second_byte)) + if not first_byte == 0x0: + characters.append(chr(first_byte)) + + return "".join(characters) + + def _is_register_type_string(self, register_info: REGISTER_INFO) -> bool: + return "varchar" in register_info["data_format"] + + def _get_integer_value(self, register_info: REGISTER_INFO) -> int: + register_type = register_info["register_type"] + val = 0 + + if register_info["data_length"] == 1: + val = self._get_unsigned_16(register_type, register_info["address"]) + + if register_info["data_length"] == 2: + val = join_msb_lsb( + self._get_unsigned_16(register_type, register_info["address"] + 1), + self._get_unsigned_16(register_type, register_info["address"]), + ) + + if register_info["signed"]: + val = twos_complement(val, register_info["data_length"] * 16) + return val + + def _is_register_type_integer(self, register_info: REGISTER_INFO) -> bool: + return "int" in register_info["data_format"] + + def _read_register_value( + self, register_info: REGISTER_INFO + ) -> Tuple[REGISTER_VALUE, str]: + """Read the values from a register based on length and sign. + + Parameters: + :param register_info: dictionary with register definition fields + :return: Tuple containing the value and data unit + """ + + val = self._read_format_register_value(register_info) + return (val, register_info["data_unit"]) + + def read(self, name: str) -> Tuple[REGISTER_VALUE, str]: + """Retrieve the value for the register with the provided name""" + + registers = SolaxRegistersInfo() + register_info = registers.get_register_info(name) + value_data_unit = self._read_register_value(register_info) + return value_data_unit + + def list_register_names(self) -> list: + """Return all registers defined in register info.""" + + r = SolaxRegistersInfo() + return r.list_register_names() + + def read_all_registers(self) -> None: + """Read all register values from inverter.""" + + self._input_registers_values: List[int] = [] + self._holding_registers_values: List[int] = [] + + self._read_input_registers() + self._read_holding_registers() + + def _read_holding_registers(self): + for count in range(4): + address: int = count * self.READ_BLOCK_LENGTH + + values: Iterable = self.client.read_holding_registers( + address=address, count=self.READ_BLOCK_LENGTH, slave=1 + ).registers + self._holding_registers_values.extend(values) + + def _read_input_registers(self): + for count in range(4): + address: int = count * self.READ_BLOCK_LENGTH + + values: Iterable = self.client.read_input_registers( + address=address, count=self.READ_BLOCK_LENGTH, slave=1 + ).registers + self._input_registers_values.extend(values) diff --git a/source/src/solaxx3/utils.py b/source/src/solaxx3/utils.py new file mode 100644 index 0000000..288521a --- /dev/null +++ b/source/src/solaxx3/utils.py @@ -0,0 +1,18 @@ +"""SolaxX3 utils module performing binary operations.""" + + +def join_msb_lsb(msb: int, lsb: int) -> int: + """Join two 16-bit registers into a 32-bit register.""" + + return (msb << 16) | lsb + + +def twos_complement(number: int, bits: int) -> int: + """Compute the 2's complement of the provided integer.""" + + # if sign bit is set e.g., 8bit: 128-255 + if (number & (1 << (bits - 1))) != 0: + # compute negative value + number = number - (1 << bits) + + return number diff --git a/source/tests/final_result.py b/source/tests/final_result.py new file mode 100644 index 0000000..b2325e3 --- /dev/null +++ b/source/tests/final_result.py @@ -0,0 +1,361 @@ +"""The formatted register values.""" + +import datetime + + +altered_input_register_values = [ + 236.4, + 4.0, + 1243.0, + 629.5, + 737.1, + 1.4, + 0.6, + 49.96, + 44.0, + 2.0, + 940.0, + 484.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 428.1, + 0.2, + 128.0, + 1.0, + 29.0, + 1.0, + 0.0, + 2.0, + 98.0, + 3914.1, + 0.4, + 4535.7, + 8.2, + 5.2, + 30.0, + 12288.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 20792.6, + 3776.9, + 0.0, + 0.0, + 0.0, + 0.0, + 56.9, + 33161.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 235.9, + 4.0, + 937.0, + 49.97, + 232.3, + 1.3, + 45.0, + 49.98, + 235.4, + 1.5, + 261.0, + 49.98, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2.0, + 2.0, + 13476.8, + 107.5, + 69.1, + 0.0, + 0.0, + 0.2, + 35229.0, + 68.0, + 41.3, + 1.75, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 21.3, + 19.0, + 3.344, + 3.329, + 0.0, + 94.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1247.0, + -1.0, + 2338.0, + -9973.0, + 5000.0, + -5000.0, + 0.0, + 0.0, + 245.0, + 12042.0, + 0.0, + 0.0, + 100.0, + 40.0, + 4.0, + 0.0, + 1.0, +] +altered_holding_register_values = [ + "H34T15H9022043", + "Solax ", + " ", + 900.0, + 15.0, + 900.0, + 195.5, + 264.5, + 49.5, + 50.5, + 1.0, + 0.0, + 259.9, + 195.5, + 264.5, + 47.5, + 51.5, + 100.0, + 0.0, + 1.0, + 1.0, + 1.0, + 0.94, + 0.9, + 20.0, + 50.0, + 80.0, + 100.0, + 1.05, + 1.0, + 6.0, + 0.0, + 0.0, + 213.9, + 223.1, + 236.9, + 246.1, + 0.0, + 10.0, + 0.0, + 0.0, + 20.0, + 5.0, + 0.0, + 7500.0, + -7500.0, + 47.55, + 50.05, + 195.5, + 253.0, + 0.0, + 0.0, + 47.55, + 50.05, + 195.5, + 253.0, + 0.0, + 1.0, + 900.0, + 29.0, + 0.0, + 1.0, + 1.0, + 6.0, + 27.0, + 8.0, + datetime.datetime(2024, 3, 19, 14, 58, 43), + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 20.0, + 21.0, + 0.0, + 30.0, + 0.0, + 30.0, + 0.0, + 0.0, + 0.0, + 245.0, + 0.0, + "SPNXLAQHWX", + 1.0, + 0.0, + 0.0, + 0.0, + 60000.0, + 1500.0, + 0.0, + 20.0, + 0.0, + 15000.0, + 0.0, + 0.0, + 0.3, + 0.1, + 0.1, + 0.1, + 0.3, + 0.01, + 0.01, + 0.01, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 32.0, + 1.0, + 50.2, + 5.0, + 0.0, + 0.0, + 0.0, + 0.0, + 2014.0, + 49.8, + 2.0, + 0.005, + 0.0, + 30.0, + 50.2, + 49.8, + 0.0, + 0.0, + 0.0, + 0.0, + 3000.0, + 900.0, + 900.0, + 3000.0, + 3000.0, + 1.0, + 0.09, + 220.0, + 253.0, + 257.6, + 207.0, + 0.1, + 0.0, + 100.0, + 100.0, + 100.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 3.0, + 1.0, + 0.0, + 1.0, + 1.0, + 2.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 266.8, + 181.8, + 0.0, + 0.0, + 0.0, + 0.0, + 30.0, + 0.0, + 0.0, + 0.0, + 2000.0, + 80.0, + 1000.0, + 40.0, + 30.0, + 800.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 50.0, + 0.0, +] diff --git a/source/tests/mock_packages/mysql/__init__.py b/source/tests/mock_packages/mysql/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/tests/mock_packages/mysql/connection.py b/source/tests/mock_packages/mysql/connection.py new file mode 100644 index 0000000..f5d67da --- /dev/null +++ b/source/tests/mock_packages/mysql/connection.py @@ -0,0 +1,51 @@ +"""Module containing a connection class.""" + +from pathlib import Path + +from .cursor import Cursor +from .error import Error + +open_connections_file = Path(__file__).parent / "open_connections.txt" + + +class Connection: + """Class mocking a connection to a MySQL server.""" + + def __init__(self, *args, consider_open=True, **kwargs): + self.open_connections += consider_open + self.open: bool = consider_open + + def cursor(self) -> Cursor: + """Return the cursor for the connection.""" + + self._cursor = Cursor() + return self._cursor + + def close(self): + """Close connection.""" + + if not self.open: + raise Error("Connection already closed") + + self.open = False + self.open_connections -= 1 + + def commit(self): + """Save all changes to the database.""" + + def is_connected(self) -> bool: + """Return if the connection is open.""" + + return self.open + + @property + def open_connections(self): + with open(open_connections_file, "r", encoding="utf-8") as f: + contents = f.read() + return int(contents) + + @open_connections.setter + def open_connections(self, value: int): + value = value if value > 0 else 0 + with open(open_connections_file, "w", encoding="utf-8") as f: + f.write(str(value)) diff --git a/source/tests/mock_packages/mysql/connector.py b/source/tests/mock_packages/mysql/connector.py new file mode 100644 index 0000000..e2c9c4e --- /dev/null +++ b/source/tests/mock_packages/mysql/connector.py @@ -0,0 +1,10 @@ +"""Module with mock implementation of interacting with a MySQL server.""" + +from .connection import Connection +from .error import Error + + +def connect(*args, **kwargs) -> Connection: + """Mock connect to a MySQL server.""" + + return Connection() diff --git a/source/tests/mock_packages/mysql/cursor.py b/source/tests/mock_packages/mysql/cursor.py new file mode 100644 index 0000000..f9f9205 --- /dev/null +++ b/source/tests/mock_packages/mysql/cursor.py @@ -0,0 +1,56 @@ +"""Module with cursor implementation.""" + +import json +from pathlib import Path +from .error import Error + + +config_file = Path(__file__).parent / "cursor_config.json" + + +class Cursor: + """Cursor of a MySQL connection.""" + + def __init__(self, raise_err: bool = True): + if self._raise_error_in_init and raise_err: + raise Error + + def execute(self, *args, **kwargs) -> None: + """Mock execute a query.""" + + if self._raise_error: + raise Error + + def executemany(self, *args, **kwargs) -> None: + """Mock execute a query multiple times with different data.""" + + if self._raise_error: + raise Error + + @property + def _raise_error(self): + return self._read_configs()["raise_error"] + + @property + def _raise_error_in_init(self): + return self._read_configs()["raise_error_in_init"] + + @_raise_error.setter + def _raise_error(self, value: int): + data = self._read_configs() + data["raise_error"] = value + self._save_configs(data) + + @_raise_error_in_init.setter + def _raise_error_in_init(self, value: int): + data = self._read_configs() + data["raise_error_in_init"] = value + self._save_configs(data) + + def _read_configs(self): + with open(config_file, "r", encoding="utf-8") as f: + return json.loads(f.read()) + + def _save_configs(self, data): + with open(config_file, "w", encoding="utf-8") as f: + f.write(json.dumps(data, indent=4)) diff --git a/source/tests/mock_packages/mysql/cursor_config.json b/source/tests/mock_packages/mysql/cursor_config.json new file mode 100644 index 0000000..9f6c80c --- /dev/null +++ b/source/tests/mock_packages/mysql/cursor_config.json @@ -0,0 +1,4 @@ +{ + "raise_error_in_init": false, + "raise_error": false +} \ No newline at end of file diff --git a/source/tests/mock_packages/mysql/error.py b/source/tests/mock_packages/mysql/error.py new file mode 100644 index 0000000..9e28c0f --- /dev/null +++ b/source/tests/mock_packages/mysql/error.py @@ -0,0 +1,5 @@ +"""Module containing the basic error class of this package.""" + + +class Error(Exception): + """Basic error class of this package.""" diff --git a/source/tests/mock_packages/mysql/open_connections.txt b/source/tests/mock_packages/mysql/open_connections.txt new file mode 100644 index 0000000..c227083 --- /dev/null +++ b/source/tests/mock_packages/mysql/open_connections.txt @@ -0,0 +1 @@ +0 \ No newline at end of file diff --git a/source/tests/mock_packages/pymodbus/__init__.py b/source/tests/mock_packages/pymodbus/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/source/tests/mock_packages/pymodbus/client.py b/source/tests/mock_packages/pymodbus/client.py new file mode 100644 index 0000000..1c1f25a --- /dev/null +++ b/source/tests/mock_packages/pymodbus/client.py @@ -0,0 +1,30 @@ +"""Class to mimick `pymodbus.client`. It is used when running the tests.""" + +from collections import namedtuple + +from .registers_output import raw_holding_register_values, raw_input_register_values +from .utils import getnext + +Registers = namedtuple("Registers", ["registers"]) + + +class ModbusSerialClient: + """Class mimicking `pymodbus.client.ModbusSerialClient`.""" + + def __init__(self, *_, **__) -> None: + self._holding_registers_gen = getnext(raw_holding_register_values) + self._input_registers_gen = getnext(raw_input_register_values) + + def connect(self): + """Mimick connecting the inverter and return the success code.""" + + return True + + def read_holding_registers(self, count, *_, **__): + """Read holding register values from inverter.""" + + return Registers([next(self._holding_registers_gen) for _ in range(count)]) + + def read_input_registers(self, count, *_, **__): + """Read input register values from inverter.""" + return Registers([next(self._input_registers_gen) for _ in range(count)]) diff --git a/source/tests/mock_packages/pymodbus/registers_output.py b/source/tests/mock_packages/pymodbus/registers_output.py new file mode 100644 index 0000000..3e5e100 --- /dev/null +++ b/source/tests/mock_packages/pymodbus/registers_output.py @@ -0,0 +1,806 @@ +"""Raw register values, just read from the inverter.""" + +raw_input_register_values = [ + 2364, + 40, + 1243, + 6295, + 7371, + 14, + 6, + 4996, + 44, + 2, + 940, + 484, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4281, + 2, + 128, + 1, + 29, + 1, + 0, + 2, + 98, + 39141, + 0, + 0, + 4, + 45357, + 0, + 82, + 52, + 300, + 12288, + 0, + 1, + 0, + 52, + 10, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 47644, + 31, + 50010, + 5, + 0, + 0, + 0, + 0, + 569, + 0, + 3930, + 5, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2359, + 40, + 937, + 4997, + 2323, + 13, + 45, + 4998, + 2354, + 15, + 261, + 4998, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 2, + 0, + 3696, + 2, + 1075, + 0, + 0, + 0, + 691, + 0, + 0, + 0, + 2, + 0, + 24610, + 5, + 680, + 0, + 4130, + 0, + 175, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 213, + 190, + 3344, + 3329, + 0, + 94, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 64289, + 65535, + 65535, + 65535, + 2338, + 0, + 55563, + 65535, + 5000, + 0, + 60536, + 65535, + 0, + 0, + 0, + 0, + 245, + 0, + 12042, + 0, + 0, + 0, + 100, + 40, + 4, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +] +raw_holding_register_values = [ + 18483, + 13396, + 12597, + 18489, + 12338, + 12848, + 13363, + 21359, + 27745, + 30752, + 8224, + 8224, + 8224, + 8224, + 8224, + 8224, + 8224, + 8224, + 8224, + 8224, + 8224, + 1800, + 900, + 15, + 900, + 1955, + 2645, + 4950, + 5050, + 1, + 0, + 2599, + 1955, + 2645, + 4750, + 5150, + 275, + 100, + 0, + 100, + 100, + 100, + 94, + 90, + 20, + 50, + 80, + 100, + 105, + 100, + 6, + 0, + 0, + 2139, + 2231, + 2369, + 2461, + 0, + 10, + 0, + 0, + 20, + 5, + 0, + 7500, + 58036, + 4755, + 5005, + 1955, + 2530, + 0, + 0, + 4755, + 5005, + 1955, + 2530, + 0, + 1, + 900, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 29, + 0, + 1, + 1, + 0, + 6, + 27, + 8, + 43, + 58, + 14, + 19, + 3, + 24, + 0, + 0, + 1, + 0, + 0, + 200, + 210, + 0, + 10240, + 30, + 25610, + 12830, + 0, + 0, + 0, + 15127, + 0, + 0, + 0, + 0, + 0, + 30, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2450, + 0, + 21328, + 20056, + 19521, + 20808, + 22360, + 1, + 0, + 0, + 0, + 0, + 0, + 60000, + 1500, + 0, + 20, + 0, + 15000, + 0, + 0, + 300, + 100, + 100, + 100, + 300, + 10, + 10, + 10, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 32, + 0, + 0, + 1, + 5020, + 5, + 0, + 0, + 0, + 0, + 2014, + 4980, + 2, + 5, + 0, + 30, + 5020, + 4980, + 0, + 0, + 0, + 0, + 30000, + 900, + 900, + 30000, + 30000, + 1000, + 900, + 2200, + 2530, + 2576, + 2070, + 10, + 0, + 100, + 100, + 100, + 0, + 10, + 1, + 0, + 0, + 0, + 0, + 0, + 3, + 1, + 0, + 1, + 1, + 2, + 0, + 0, + 0, + 100, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 2668, + 1818, + 0, + 0, + 0, + 0, + 0, + 0, + 30, + 0, + 0, + 0, + 2000, + 80, + 1000, + 40, + 30, + 800, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 500, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 20, + 95, + 1000, + 30, + 60, + 0, + 15127, + 0, + 2000, + 0, + 0, + 2000, + 30, + 80, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +] diff --git a/source/tests/mock_packages/pymodbus/utils.py b/source/tests/mock_packages/pymodbus/utils.py new file mode 100644 index 0000000..47c24fe --- /dev/null +++ b/source/tests/mock_packages/pymodbus/utils.py @@ -0,0 +1,11 @@ +"""Mock `pymodbus` utils.""" + +from typing import Any + + +def getnext(iterable: list, default: Any = 0): + """Yield one item at a time and yield `default` when iterable is exhausted.""" + + yield from iterable + while True: + yield default diff --git a/source/tests/test_mysql_connector.py b/source/tests/test_mysql_connector.py new file mode 100644 index 0000000..27258e3 --- /dev/null +++ b/source/tests/test_mysql_connector.py @@ -0,0 +1,68 @@ +import sys +from os import environ +from pathlib import Path +from unittest import TestCase + +from tests.mock_packages.mysql.connection import Connection +from tests.mock_packages.mysql.cursor import Cursor + +environ["MYSQL_DB_USERNAME"] = "" +environ["MYSQL_DB_HOST_IP"] = "" +environ["MYSQL_DB_PASSWORD"] = "" +environ["MYSQL_DB_DATABASE"] = "" +current: Path = Path().resolve() +paths_to_extend = [current / "tests" / "mock_packages", current / "src"] +for path in paths_to_extend: + path_as_string = str(path) + if path_as_string not in sys.path: + sys.path.append(path_as_string) + +from database import read_and_save + + +class MysqlConnectionLeakageTests(TestCase): + """Tests to see if the class leaves any connections open after its processes.""" + + def _open_connections(self): + return Connection(consider_open=False).open_connections + + def setUp(self) -> None: + """Set up the data after each test case.""" + + Cursor(raise_err=False)._raise_error = False + Cursor(raise_err=False)._raise_error_in_init = False + Connection().open_connections = 0 + + def test_bulk_transfer_without_error(self): + """Test if `bulk_transfer` leaves any hanging connections in normal operations.""" + + for _ in range(3): + read_and_save.bulk_save() + + self.assertEqual(self._open_connections(), 0) + + def test_bulk_transfer_with_error_in_cursor_init(self): + """Test if `bulk_transfer` leaves any hanging connections with errors when instantiating + cursor.""" + + Cursor()._raise_error_in_init = True + for _ in range(3): + try: + read_and_save.bulk_save() + except: + pass + + self.assertEqual(self._open_connections(), 0) + + def test_bulk_transfer_with_error_in_cursor(self): + """Test if `bulk_transfer` leaves any hanging connections with errors when executing + a query in the cursor.""" + + Cursor()._raise_error = True + for _ in range(3): + try: + read_and_save.bulk_save() + except: + pass + + self.assertEqual(self._open_connections(), 0) diff --git a/source/tests/test_register_data.py b/source/tests/test_register_data.py new file mode 100644 index 0000000..18f7bbd --- /dev/null +++ b/source/tests/test_register_data.py @@ -0,0 +1,254 @@ +""" +Test scenarios: + Report any of the following anomalies: + 1. Not valid dictionary syntax [done] + 2. Dictionary key duplicates [done] + 3. "address" duplicates + 4. The value field has different keys than: [done] + * address + * register_type + * data_format + * si_adj + * signed + * data_unit + * data_length + * description + 5. "dataformat" starts with 'uint' but "signed" is different than "False" + 6. "dataformat" starts with 'int' but "signed" is different than "True" + 7. If "data_format" contains "int" in the name, + "data_length" != / 16 or 1 if no number is found + in "data_length" + 8. "data_unit" is not one of following: + {%, A, C, Hx, KWh, N/A, V, VA, Var, W, Wh, hour, second} + Note: values are case sensitive + 9. "register_type" is different than {input, holding} + Note: no values are case sensitive + 10. the key name contains "volt" but the "data_unit" is different than "V" + 11. the key name contains "current" but the "data_unit" is different than "A" + 12. "address" overlapping +""" + +import re +import sys +import unittest +from itertools import filterfalse +from pathlib import Path + +from src.solaxx3.solax_registers_info import SolaxRegistersInfo + +import_path = Path().resolve() / "tests" / "mock_packages" +if str(import_path) not in sys.path: + sys.path.append(str(import_path)) + +REGISTER_FILE = "src/solaxx3/solax_registers_info.py" + + +class RegisterTests(unittest.TestCase): + """Tests for SolaxRegistersInfo.""" + + registers = SolaxRegistersInfo._registers + + def test_tc1_valid_dictionary(self): + """Prints an error if `registers` is a dictionary.""" + + self.assertIsInstance(self.registers, dict) + + def test_tc2_duplicate_keys(self): + """Prints an error if 'registers' has any duplicate keys.""" + + PATTERN = r""" + "(?P.*?)" # The key of the dictionary + \s*: # colon + \s*{ # start of value + """ + regex = re.compile(PATTERN, re.VERBOSE) + + with open(REGISTER_FILE, "r", encoding="utf-8") as dictionary: + list_keys = [] + + for line_number, line in filterfalse( + lambda x: x[1].lstrip().startswith("#"), enumerate(dictionary, start=1) + ): + re_match = regex.search(line) + + if re_match is not None: + key = re_match.group("key") + self.assertNotIn(key, list_keys, f"line={line_number}") + list_keys.append(key) + + def test_tc4_correct_value_field(self): + """Prints an error if the keys do not have the following keys: + - address, register_type, data_format, si_adj, signed, data_unit, data_length, + description""" + + VALID_SUBKEYS = [ + "address", + "data_format", + "data_length", + "data_unit", + "description", + "register_type", + "si_adj", + "signed", + ] + + for register, register_value in self.registers.items(): + self.assertListEqual( + sorted(list(register_value.keys())), VALID_SUBKEYS, f"dict={register!r}" + ) + + def test_tc5_correctly_unsigned(self): + """Prints an error if subkey `data_format` starts with `uint` but `signed` is + different than False.""" + + for register, register_value in self.registers.items(): + self.assertFalse( + register_value["data_format"].startswith("uint") + and register_value["signed"] is not False, + f"not correctly signed: dict='{register}'", + ) + + def test_tc6_correctly_signed(self): + """Prints an error if subkey `data_format` starts with 'int' but `signed` + is different than True.""" + + for register, register_value in self.registers.items(): + self.assertFalse( + register_value["data_format"].startswith("int") + and register_value["signed"] is not True, + f"not correctly signed: dict='{register}'", + ) + + def test_tc7_correct_data_length(self): + """Prints an error if 'data_format' does not match 'data_length'.""" + + PATTERN = r"int(?P\d+)" + regex = re.compile(PATTERN) + + for register, register_value in self.registers.items(): + match = regex.search(register_value["data_format"]) + + if match: + bits = int(match.group("bits")) + self.assertEqual( + bits / 16, register_value["data_length"], f"dict={register!r}" + ) + + def test_tc8_correct_data_unit_value(self): + """Prints an error if `data_unit` is not %, C, Hz, KWh, N/A, V, VA, Var, + W, Wh, hour, second.""" + + POSSIBLE_DATA_UNITS = ( + "min", + "bps", + "%", + "A", + "C", + "Hz", + "KWh", + "N/A", + "V", + "VA", + "Var", + "W", + "Wh", + "hour", + "second", + ) + + for register, register_value in self.registers.items(): + self.assertGreater( + len(register_value["data_unit"]), + 0, + f"empty 'data_unit': dict={register!r}", + ) + self.assertIn( + register_value["data_unit"], + POSSIBLE_DATA_UNITS, + f"invalid data_unit: '{register_value['data_unit']}'; 'dict='{register}'", + ) + + def test_tc9_correct_register_type(self): + """Prints an error if `register_type` is not 'input' or 'holding'.""" + + POSSIBLE_REGISTER_TYPES = ("input", "holding") + + for register, register_value in self.registers.items(): + self.assertIn( + register_value["register_type"], + POSSIBLE_REGISTER_TYPES, + f"dict='{register}'", + ) + + def test_tc10_correct_data_unit_with_volt(self): + """Prints an error if the key contains 'volt' but `data_unit` is differrent + than 'V'.""" + + for register, register_value in self.registers.items(): + self.assertFalse( + "volt" in register + and register_value["data_unit"] != "V" + and "percent" not in register + and "ratio" not in register, + f"'data_unit' does not match name: dict={register!r}", + ) + + def test_tc11_correct_data_unit_with_current(self): + """Prints an error if the key contains 'current' but `data_unit` is + differrent than 'A'.""" + + for register, register_value in self.registers.items(): + self.assertFalse( + "current" in register and register_value["data_unit"] != "A", + f"'data_unit' does not match name: dict='{register}'", + ) + + def test_tc12_correct_addresses(self): + """Prints an error if there is any address overlapping.""" + + holding_addresses: list = [] + input_addresses: list = [] + + for register, register_value in self.registers.items(): + addresses = ( + holding_addresses + if register_value["register_type"] == "holding" + else input_addresses + ) + data_length = register_value["data_length"] + + for x in range(data_length): + address = register_value["address"] + x + self.assertNotIn(address, addresses, f"dict={register!r}") + addresses.append(address) + + def test_tc13_subkey_duplicates(self): + """Prints an error if there are any subkey duplicates.""" + + PATTERN = r""" + "(?P.*?)" # dictionary subkey + \s*: # colon + \s*?[^[]+$ # value different than a dict + """ + + regex = re.compile(PATTERN, re.VERBOSE) + + with open(REGISTER_FILE, "r", encoding="utf-8") as dictionary: + subkeys = [] + + for line_number, line in filterfalse( + lambda x: x[1].lstrip().startswith("#"), enumerate(dictionary, start=1) + ): + match = regex.search(line) + + if match: + subkey = match.group("subkey") + + self.assertNotIn(subkey, subkeys, f"line={line_number}") + subkeys.append(subkey) + else: + subkeys.clear() + + +if __name__ == "__main__": + unittest.main() diff --git a/source/tests/test_solaxx3.py b/source/tests/test_solaxx3.py new file mode 100644 index 0000000..ab476d1 --- /dev/null +++ b/source/tests/test_solaxx3.py @@ -0,0 +1,48 @@ +"""Module to test solaxx3.""" + +import sys +import unittest +from pathlib import Path + +from src.solaxx3 import solaxx3 +from tests.final_result import ( + altered_holding_register_values, + altered_input_register_values, +) + +import_path = Path().resolve() / "tests" / "mock_packages" +if str(import_path) not in sys.path: + sys.path.append(str(import_path)) + + +class Solaxx3Tests(unittest.TestCase): + """Tests for SolaxX3.""" + + def test_connection(self): + """Test if SolaxX3 connected.""" + + s = solaxx3.SolaxX3() + s.connect() + self.assertEqual(s.connected, True) + + def test_format_registers(self): + """Test if the module correctly formats raw register values.""" + + result = [] + s = solaxx3.SolaxX3() + s.read_all_registers() + + regs = solaxx3.SolaxRegistersInfo().list_register_names() + for reg in regs: + result.append(s.read(reg)[0]) + + self.assertEqual( + result, altered_input_register_values + altered_holding_register_values + ) + + def test_are_registers_identical(self): + """Test if holding registers' values are not equal to input registers' values.""" + + s = solaxx3.SolaxX3() + s.read_all_registers() + self.assertFalse(s._holding_registers_values == s._input_registers_values)