From 321c66838df9c9e0d140f8e4c71ae2db1f41abc0 Mon Sep 17 00:00:00 2001 From: Stefan Allius <122395479+s-allius@users.noreply.github.com> Date: Sat, 24 May 2025 23:12:55 +0200 Subject: [PATCH] set no of pv modules for MS800 GEN3PLUS inverters (#424) * set no of pv modules for MS800 GEN3PLUS inverters * fix unit test * increase test coverage * change the PV module handling - in default we set the number of modules now to two. So with the first data from the inverter we only register two modules. After we determine the inverter module, the number can increase to four and more PV modules will be registered. With the default value of 4, we register always 4 modules and can't reduce the number of areas when we detect that the inverter only supoorts two PV modules --- CHANGELOG.md | 1 + app/src/gen3plus/infos_g3p.py | 2 +- app/src/gen3plus/solarman_v5.py | 5 ++++ app/tests/test_infos_g3p.py | 6 ++-- app/tests/test_solarman.py | 53 +++++++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 116ef15..a77022d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +- set no of pv modules for MS800 GEN3PLUS inverters - fix the paths to copy the config.example.toml file during proxy start - add MQTT topic `dcu_power` for setting output power on DCUs - Update ghcr.io/hassio-addons/base Docker tag to v17.2.5 diff --git a/app/src/gen3plus/infos_g3p.py b/app/src/gen3plus/infos_g3p.py index 470c695..086dcd3 100644 --- a/app/src/gen3plus/infos_g3p.py +++ b/app/src/gen3plus/infos_g3p.py @@ -216,7 +216,7 @@ class InfosG3P(Infos): self.set_db_def_value(Register.MANUFACTURER, 'TSUN') self.set_db_def_value(Register.EQUIPMENT_MODEL, 'TSOL-MSxx00') self.set_db_def_value(Register.CHIP_TYPE, 'IGEN TECH') - self.set_db_def_value(Register.NO_INPUTS, 4) + self.set_db_def_value(Register.NO_INPUTS, 2) def __hide_topic(self, row: dict) -> bool: if 'dep' in row: diff --git a/app/src/gen3plus/solarman_v5.py b/app/src/gen3plus/solarman_v5.py index 2cf7d16..355890b 100755 --- a/app/src/gen3plus/solarman_v5.py +++ b/app/src/gen3plus/solarman_v5.py @@ -562,12 +562,17 @@ class SolarmanV5(SolarmanBase): rated = db.get_db_value(Register.RATED_POWER, 0) model = None if max_pow == 2000: + db.set_db_def_value(Register.NO_INPUTS, 4) if rated == 800 or rated == 600: model = f'TSOL-MS{max_pow}({rated})' else: model = f'TSOL-MS{max_pow}' elif max_pow == 1800 or max_pow == 1600: + db.set_db_def_value(Register.NO_INPUTS, 4) model = f'TSOL-MS{max_pow}' + elif max_pow <= 800: + model = f'TSOL-MS{max_pow}' + if model: logger.info(f'Model: {model}') self.db.set_db_def_value(Register.EQUIPMENT_MODEL, model) diff --git a/app/tests/test_infos_g3p.py b/app/tests/test_infos_g3p.py index 0596044..95e24a8 100644 --- a/app/tests/test_infos_g3p.py +++ b/app/tests/test_infos_g3p.py @@ -109,7 +109,7 @@ def test_default_db(): i = InfosG3P(client_mode=False) assert json.dumps(i.db) == json.dumps({ - "inverter": {"Manufacturer": "TSUN", "Equipment_Model": "TSOL-MSxx00", "No_Inputs": 4}, + "inverter": {"Manufacturer": "TSUN", "Equipment_Model": "TSOL-MSxx00", "No_Inputs": 2}, "collector": {"Chip_Type": "IGEN TECH"}, }) @@ -271,7 +271,7 @@ def test_build_ha_conf1(): elif id == 'inv_count_456': assert False - assert tests==7 + assert tests==5 def test_build_ha_conf2(): i = InfosG3P(client_mode=False) @@ -346,7 +346,7 @@ def test_build_ha_conf3(): elif id == 'inv_count_456': assert False - assert tests==7 + assert tests==5 def test_build_ha_conf4(): i = InfosG3P(client_mode=True) diff --git a/app/tests/test_solarman.py b/app/tests/test_solarman.py index 77866cf..8cb36b1 100755 --- a/app/tests/test_solarman.py +++ b/app/tests/test_solarman.py @@ -462,6 +462,39 @@ def inverter_ind_msg800(): # 0x4210 rated Power 800W msg += b'\x15' return msg +@pytest.fixture +def inverter_ind_msg900(): # 0x4210 rated Power 900W + msg = b'\xa5\x99\x01\x10\x42\xe6\x9e' +get_sn() +b'\x01\xb0\x02\xbc\xc8' + msg += b'\x24\x32\x6c\x1f\x00\x00\xa0\x47\xe4\x33\x01\x00\x03\x08\x00\x00' + msg += b'\x59\x31\x37\x45\x37\x41\x30\x46\x30\x31\x30\x42\x30\x31\x33\x45' + msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x01\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x40\x10\x08\xc8\x00\x49\x13\x8d\x00\x36\x00\x00\x03\x84\x06\x7a' + msg += b'\x01\x61\x00\xa8\x02\x54\x01\x5a\x00\x8a\x01\xe4\x01\x5a\x00\xbd' + msg += b'\x02\x8f\x00\x11\x00\x01\x00\x00\x00\x0b\x00\x00\x27\x98\x00\x04' + msg += b'\x00\x00\x0c\x04\x00\x03\x00\x00\x0a\xe7\x00\x05\x00\x00\x0c\x75' + msg += b'\x00\x00\x00\x00\x06\x16\x02\x00\x00\x00\x55\xaa\x00\x01\x00\x00' + msg += b'\x00\x00\x00\x00\xff\xff\x03\x84\x00\x03\x04\x00\x04\x00\x04\x00' + msg += b'\x04\x00\x00\x01\xff\xff\x00\x01\x00\x06\x00\x68\x00\x68\x05\x00' + msg += b'\x09\xcd\x07\xb6\x13\x9c\x13\x24\x00\x01\x07\xae\x04\x0f\x00\x41' + msg += b'\x00\x0f\x0a\x64\x0a\x64\x00\x06\x00\x06\x09\xf6\x12\x8c\x12\x8c' + msg += b'\x00\x10\x00\x10\x14\x52\x14\x52\x00\x10\x00\x10\x01\x51\x00\x05' + msg += b'\x04\x00\x00\x01\x13\x9c\x0f\xa0\x00\x4e\x00\x66\x03\xe8\x04\x00' + msg += b'\x09\xce\x07\xa8\x13\x9c\x13\x26\x00\x00\x00\x00\x00\x00\x00\x00' + msg += b'\x00\x00\x00\x00\x04\x00\x04\x00\x00\x00\x00\x00\xff\xff\x00\x00' + msg += b'\x00\x00\x00\x00' + msg += correct_checksum(msg) + msg += b'\x15' + return msg + @pytest.fixture def inverter_ind_msg_81(): # 0x4210 fcode 0x81 msg = b'\xa5\x99\x01\x10\x42\x02\x03' +get_sn() +b'\x81\xb0\x02\xbc\xc8' @@ -1435,6 +1468,7 @@ async def test_build_modell_600(my_loop, config_tsun_allow_all, inverter_ind_msg m.read() # read complete msg, and dispatch msg assert 2000 == m.db.get_db_value(Register.MAX_DESIGNED_POWER, 0) assert 600 == m.db.get_db_value(Register.RATED_POWER, 0) + assert 4 == m.db.get_db_value(Register.NO_INPUTS, 0) assert 'TSOL-MS2000(600)' == m.db.get_db_value(Register.EQUIPMENT_MODEL, 0) assert '02b0' == m.db.get_db_value(Register.SENSOR_LIST, None) assert 0 == m.sensor_list # must not been set by an inverter data ind @@ -1454,6 +1488,7 @@ async def test_build_modell_1600(my_loop, config_tsun_allow_all, inverter_ind_ms m.read() # read complete msg, and dispatch msg assert 1600 == m.db.get_db_value(Register.MAX_DESIGNED_POWER, 0) assert 1600 == m.db.get_db_value(Register.RATED_POWER, 0) + assert 4 == m.db.get_db_value(Register.NO_INPUTS, 0) assert 'TSOL-MS1600' == m.db.get_db_value(Register.EQUIPMENT_MODEL, 0) m.close() @@ -1467,6 +1502,7 @@ async def test_build_modell_1800(my_loop, config_tsun_allow_all, inverter_ind_ms m.read() # read complete msg, and dispatch msg assert 1800 == m.db.get_db_value(Register.MAX_DESIGNED_POWER, 0) assert 1800 == m.db.get_db_value(Register.RATED_POWER, 0) + assert 4 == m.db.get_db_value(Register.NO_INPUTS, 0) assert 'TSOL-MS1800' == m.db.get_db_value(Register.EQUIPMENT_MODEL, 0) m.close() @@ -1480,6 +1516,7 @@ async def test_build_modell_2000(my_loop, config_tsun_allow_all, inverter_ind_ms m.read() # read complete msg, and dispatch msg assert 2000 == m.db.get_db_value(Register.MAX_DESIGNED_POWER, 0) assert 2000 == m.db.get_db_value(Register.RATED_POWER, 0) + assert 4 == m.db.get_db_value(Register.NO_INPUTS, 0) assert 'TSOL-MS2000' == m.db.get_db_value(Register.EQUIPMENT_MODEL, 0) m.close() @@ -1493,6 +1530,21 @@ async def test_build_modell_800(my_loop, config_tsun_allow_all, inverter_ind_msg m.read() # read complete msg, and dispatch msg assert 800 == m.db.get_db_value(Register.MAX_DESIGNED_POWER, 0) assert 800 == m.db.get_db_value(Register.RATED_POWER, 0) + assert 2 == m.db.get_db_value(Register.NO_INPUTS, 0) + assert 'TSOL-MS800' == m.db.get_db_value(Register.EQUIPMENT_MODEL, 0) + m.close() + +@pytest.mark.asyncio +async def test_build_modell_900(my_loop, config_tsun_allow_all, inverter_ind_msg900): + _ = config_tsun_allow_all + m = MemoryStream(inverter_ind_msg900, (0,)) + assert 0 == m.db.get_db_value(Register.MAX_DESIGNED_POWER, 0) + assert None == m.db.get_db_value(Register.RATED_POWER, None) + assert None == m.db.get_db_value(Register.INVERTER_TEMP, None) + m.read() # read complete msg, and dispatch msg + assert 900 == m.db.get_db_value(Register.MAX_DESIGNED_POWER, 0) + assert 900 == m.db.get_db_value(Register.RATED_POWER, 0) + assert 2 == m.db.get_db_value(Register.NO_INPUTS, 0) assert 'TSOL-MSxx00' == m.db.get_db_value(Register.EQUIPMENT_MODEL, 0) m.close() @@ -2542,6 +2594,7 @@ async def test_proxy_dcu_cmd(my_loop, config_tsun_dcu1, patch_open_connection, d assert l.db.stat['proxy']['AT_Command'] == 0 assert l.db.stat['proxy']['AT_Command_Blocked'] == 0 assert l.db.stat['proxy']['Modbus_Command'] == 0 + assert 2 == l.db.get_db_value(Register.NO_INPUTS, 0) l.append_msg(dcu_command_rsp_msg) l.read() # read at resp