From 42fe33bacf04d6337173568385031414f1d53b01 Mon Sep 17 00:00:00 2001 From: Stefan Allius Date: Tue, 11 Feb 2025 00:08:57 +0100 Subject: [PATCH] add initial DCU support --- CHANGELOG.md | 3 +++ app/src/gen3plus/infos_g3p.py | 33 +++++++++++++++++++++----- app/src/gen3plus/solarman_emu.py | 2 +- app/src/gen3plus/solarman_v5.py | 8 +++---- app/tests/test_infos_g3p.py | 40 ++++++++++++++++---------------- 5 files changed, 55 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bb09b5..a52e212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] +- add initial DCU support +- update AddOn base docker image to version 17.1.2 +- update aiohttp to version 3.11.12 - fix the path handling for logging.ini and default_config.toml [#180](https://github.com/s-allius/tsun-gen3-proxy/issues/180) ## [0.12.1] - 2025-01-13 diff --git a/app/src/gen3plus/infos_g3p.py b/app/src/gen3plus/infos_g3p.py index 417487a..0a6264f 100644 --- a/app/src/gen3plus/infos_g3p.py +++ b/app/src/gen3plus/infos_g3p.py @@ -1,5 +1,6 @@ from typing import Generator +from itertools import chain from infos import Infos, Register, ProxyMode, Fmt @@ -32,7 +33,8 @@ class RegisterMap: 0x4102008e: {'reg': None, 'fmt': ' suggested area string from the config file''' # iterate over RegisterMap.map and get the register values - for row in RegisterMap.map.values(): + for _, row in chain(RegisterMap.map.items(), + RegisterMap.map_02b0.items(), + RegisterMap.map_3026.items()): info_id = row['reg'] if self.__hide_topic(row): res = self.ha_remove(info_id, node_id, snr) # noqa: E501 @@ -153,13 +173,14 @@ class InfosG3P(Infos): if res: yield res - def parse(self, buf, msg_type: int, rcv_ftype: int, node_id: str = '') \ + def parse(self, buf, msg_type: int, rcv_ftype: int, + sensor: int = 0, node_id: str = '') \ -> Generator[tuple[str, bool], None, None]: '''parse a data sequence received from the inverter and stores the values in Infos.db buf: buffer of the sequence to parse''' - for idx, row in RegisterMap.map.items(): + for idx, row in RegisterSel.get(sensor).items(): addr = idx & 0xffff ftype = (idx >> 16) & 0xff mtype = (idx >> 24) & 0xff @@ -183,9 +204,9 @@ class InfosG3P(Infos): self.tracer.log(level, f'[{node_id}] GEN3PLUS: {name}' f' : {result}{unit}') - def build(self, len, msg_type: int, rcv_ftype: int): + def build(self, len, msg_type: int, rcv_ftype: int, sensor: int = 0): buf = bytearray(len) - for idx, row in RegisterMap.map.items(): + for idx, row in RegisterSel.get(sensor).items(): addr = idx & 0xffff ftype = (idx >> 16) & 0xff mtype = (idx >> 24) & 0xff diff --git a/app/src/gen3plus/solarman_emu.py b/app/src/gen3plus/solarman_emu.py index 66035bb..7462388 100644 --- a/app/src/gen3plus/solarman_emu.py +++ b/app/src/gen3plus/solarman_emu.py @@ -103,7 +103,7 @@ class SolarmanEmu(SolarmanBase): self.data_timer.start(self.data_up_inv) _len = 420 ftype = 1 - build_msg = self.db.build(_len, 0x42, ftype) + build_msg = self.db.build(_len, 0x42, ftype, 0x02b0) self._build_header(0x4210) self.ifc.tx_add( diff --git a/app/src/gen3plus/solarman_v5.py b/app/src/gen3plus/solarman_v5.py index 463c043..837b78c 100644 --- a/app/src/gen3plus/solarman_v5.py +++ b/app/src/gen3plus/solarman_v5.py @@ -516,11 +516,11 @@ class SolarmanV5(SolarmanBase): logger.info(f'Model: {model}') self.db.set_db_def_value(Register.EQUIPMENT_MODEL, model) - def __process_data(self, ftype, ts): + def __process_data(self, ftype, ts, sensor=0): inv_update = False msg_type = self.control >> 8 - for key, update in self.db.parse(self.ifc.rx_peek(), msg_type, ftype, - self.node_id): + for key, update in self.db.parse(self.ifc.rx_peek(), msg_type, + ftype, sensor, self.node_id): if update: if key == 'inverter': inv_update = True @@ -581,7 +581,7 @@ class SolarmanV5(SolarmanBase): else: ts = None - self.__process_data(ftype, ts) + self.__process_data(ftype, ts, sensor) self.__forward_msg() self.__send_ack_rsp(0x1210, ftype) self.new_state_up() diff --git a/app/tests/test_infos_g3p.py b/app/tests/test_infos_g3p.py index e0cac05..7da5924 100644 --- a/app/tests/test_infos_g3p.py +++ b/app/tests/test_infos_g3p.py @@ -105,7 +105,7 @@ def test_parse_4210(inverter_data: bytes): i = InfosG3P(client_mode=False) i.db.clear() - for key, update in i.parse (inverter_data, 0x42, 1): + for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0): pass # side effect is calling generator i.parse() assert json.dumps(i.db) == json.dumps({ @@ -127,10 +127,10 @@ def test_build_4210(inverter_data: bytes): i = InfosG3P(client_mode=False) i.db.clear() - for key, update in i.parse (inverter_data, 0x42, 1): + for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0): pass # side effect is calling generator i.parse() - build_msg = i.build(len(inverter_data), 0x42, 1) + build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0) for i in range(11, 31): build_msg[i] = inverter_data[i] assert inverter_data == build_msg @@ -286,53 +286,53 @@ def test_build_ha_conf4(): def test_exception_and_calc(inverter_data: bytes): # patch table to convert temperature from °F to °C - ofs = RegisterMap.map[0x420100d8]['offset'] - RegisterMap.map[0x420100d8]['quotient'] = 1.8 - RegisterMap.map[0x420100d8]['offset'] = -32/1.8 + ofs = RegisterMap.map_02b0[0x420100d8]['offset'] + RegisterMap.map_02b0[0x420100d8]['quotient'] = 1.8 + RegisterMap.map_02b0[0x420100d8]['offset'] = -32/1.8 # map PV1_VOLTAGE to invalid register - RegisterMap.map[0x420100e0]['reg'] = Register.TEST_REG2 + RegisterMap.map_02b0[0x420100e0]['reg'] = Register.TEST_REG2 # set invalid maping entry for OUTPUT_POWER (string instead of dict type) - backup = RegisterMap.map[0x420100de] - RegisterMap.map[0x420100de] = 'invalid_entry' + backup = RegisterMap.map_02b0[0x420100de] + RegisterMap.map_02b0[0x420100de] = 'invalid_entry' i = InfosG3P(client_mode=False) i.db.clear() - for key, update in i.parse (inverter_data, 0x42, 1): + for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0): pass # side effect is calling generator i.parse() assert math.isclose(12.2222, round (i.get_db_value(Register.INVERTER_TEMP, 0),4), rel_tol=1e-09, abs_tol=1e-09) - build_msg = i.build(len(inverter_data), 0x42, 1) + build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0) assert build_msg[32:0xde] == inverter_data[32:0xde] assert build_msg[0xde:0xe2] == b'\x00\x00\x00\x00' assert build_msg[0xe2:-1] == inverter_data[0xe2:-1] # remove a table entry and test parsing and building - del RegisterMap.map[0x420100d8]['quotient'] - del RegisterMap.map[0x420100d8]['offset'] + del RegisterMap.map_02b0[0x420100d8]['quotient'] + del RegisterMap.map_02b0[0x420100d8]['offset'] i.db.clear() - for key, update in i.parse (inverter_data, 0x42, 1): + for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0): pass # side effect is calling generator i.parse() assert 54 == i.get_db_value(Register.INVERTER_TEMP, 0) - build_msg = i.build(len(inverter_data), 0x42, 1) + build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0) assert build_msg[32:0xd8] == inverter_data[32:0xd8] assert build_msg[0xd8:0xe2] == b'\x006\x00\x00\x02X\x00\x00\x00\x00' assert build_msg[0xe2:-1] == inverter_data[0xe2:-1] # test restore table - RegisterMap.map[0x420100d8]['offset'] = ofs - RegisterMap.map[0x420100e0]['reg'] = Register.PV1_VOLTAGE # reset mapping - RegisterMap.map[0x420100de] = backup # reset mapping + RegisterMap.map_02b0[0x420100d8]['offset'] = ofs + RegisterMap.map_02b0[0x420100e0]['reg'] = Register.PV1_VOLTAGE # reset mapping + RegisterMap.map_02b0[0x420100de] = backup # reset mapping # test orginial table i.db.clear() - for key, update in i.parse (inverter_data, 0x42, 1): + for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0): pass # side effect is calling generator i.parse() assert 14 == i.get_db_value(Register.INVERTER_TEMP, 0) - build_msg = i.build(len(inverter_data), 0x42, 1) + build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0) assert build_msg[32:-1] == inverter_data[32:-1]