From e3fdeecf82868d853a5551a3a5cd16efbb0ed938 Mon Sep 17 00:00:00 2001 From: Stefan Allius Date: Sat, 30 Mar 2024 01:15:07 +0100 Subject: [PATCH] parse gen3plus inverter data --- app/src/gen3/infos_g3.py | 34 +------- app/src/gen3plus/infos_g3p.py | 139 +++++++++++++++----------------- app/src/gen3plus/solarman_v5.py | 7 ++ app/src/infos.py | 20 +++++ 4 files changed, 97 insertions(+), 103 deletions(-) diff --git a/app/src/gen3/infos_g3.py b/app/src/gen3/infos_g3.py index 4fdfb29..964a78f 100644 --- a/app/src/gen3/infos_g3.py +++ b/app/src/gen3/infos_g3.py @@ -122,7 +122,6 @@ class InfosG3(Infos): info_id = RegisterMap.map[addr] data_type = result[1] ind += 5 - keys, level, unit, must_incr, new_val = self._key_obj(info_id) if data_type == 0x54: # 'T' -> Pascal-String str_len = buf[ind] @@ -132,26 +131,18 @@ class InfosG3(Infos): ind += str_len+1 elif data_type == 0x49: # 'I' -> int32 - # if new_val: - # struct.pack_into('!l', buf, ind, new_val) result = struct.unpack_from('!l', buf, ind)[0] ind += 4 elif data_type == 0x53: # 'S' -> short - # if new_val: - # struct.pack_into('!h', buf, ind, new_val) result = struct.unpack_from('!h', buf, ind)[0] ind += 2 elif data_type == 0x46: # 'F' -> float32 - # if new_val: - # struct.pack_into('!f', buf, ind, new_val) result = round(struct.unpack_from('!f', buf, ind)[0], 2) ind += 4 elif data_type == 0x4c: # 'L' -> int64 - # if new_val: - # struct.pack_into('!q', buf, ind, new_val) result = struct.unpack_from('!q', buf, ind)[0] ind += 8 @@ -161,31 +152,14 @@ class InfosG3(Infos): " not supported") return + keys, level, unit, must_incr, new_val = self._key_obj(info_id) + if keys: - dict = self.db - name = '' - - for key in keys[:-1]: - if key not in dict: - dict[key] = {} - dict = dict[key] - name += key + '.' - - if keys[-1] not in dict: - update = (not must_incr or result > 0) - else: - if must_incr: - update = dict[keys[-1]] < result - else: - update = dict[keys[-1]] != result - - if update: - dict[keys[-1]] = result - name += keys[-1] + name, update = self.update_db(keys, must_incr, result) yield keys[0], update else: update = False - name = str(f'info-id.0x{info_id:x}') + name = str(f'info-id.0x{addr:x}') self.tracer.log(level, f'{name} : {result}{unit}' f' update: {update}') diff --git a/app/src/gen3plus/infos_g3p.py b/app/src/gen3plus/infos_g3p.py index c6b9a1a..97c8155 100644 --- a/app/src/gen3plus/infos_g3p.py +++ b/app/src/gen3plus/infos_g3p.py @@ -1,6 +1,5 @@ import struct -import logging from typing import Generator if __name__ == "app.src.gen3plus.infos_g3p": @@ -10,9 +9,59 @@ else: # pragma: no cover class RegisterMap: + # make the class read/only by using __slots__ + + # __slots__ = () map = { - 0x00092ba8: Register.COLLECTOR_FW_VERSION, + 0x00d2: {'reg': Register.GRID_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00d4: {'reg': Register.GRID_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x00d6: {'reg': Register.GRID_FREQUENCY, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x00d8: {'reg': Register.INVERTER_TEMP, 'fmt': '!H', 'ratio': 1}, # noqa: E501 + 0x00dc: {'reg': Register.RATED_POWER, 'fmt': '!H', 'ratio': 1}, # noqa: E501 + 0x00de: {'reg': Register.OUTPUT_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00e0: {'reg': Register.PV1_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00e2: {'reg': Register.PV1_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x00e4: {'reg': Register.PV1_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00e6: {'reg': Register.PV2_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00e8: {'reg': Register.PV2_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x00ea: {'reg': Register.PV2_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00ec: {'reg': Register.PV3_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00ee: {'reg': Register.PV3_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x00f0: {'reg': Register.PV3_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00f2: {'reg': Register.PV4_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00f4: {'reg': Register.PV4_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x00f6: {'reg': Register.PV4_POWER, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501 + 0x00f8: {'reg': Register.DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x00fa: {'reg': Register.TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 + 0x00fe: {'reg': Register.PV1_DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x0100: {'reg': Register.PV1_TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 + 0x0104: {'reg': Register.PV2_DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x0106: {'reg': Register.PV2_TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 + 0x010a: {'reg': Register.PV3_DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x010c: {'reg': Register.PV3_TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 + 0x0110: {'reg': Register.PV4_DAILY_GENERATION, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 + 0x0112: {'reg': Register.PV4_TOTAL_GENERATION, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 } + ''' + COMMUNICATION_TYPE = 400 + SIGNAL_STRENGTH = 401 + POWER_ON_TIME = 402 + COLLECT_INTERVAL = 403 + DATA_UP_INTERVAL = 404 + CONNECT_COUNT = 405 + + COLLECTOR_FW_VERSION = 1 + CHIP_TYPE = 2 + CHIP_MODEL = 3 + TRACE_URL = 4 + LOGGER_URL = 5 + PRODUCT_NAME = 20 + MANUFACTURER = 21 + VERSION = 22 + SERIAL_NUMBER = 23 + EQUIPMENT_MODEL = 24 + NO_INPUTS = 25 + ''' class InfosG3P(Infos): @@ -28,8 +77,9 @@ class InfosG3P(Infos): entity strings sug_area:str ==> suggested area string from the config file''' # iterate over RegisterMap.map and get the register values - for reg in RegisterMap.map.values(): - res = self.ha_conf(reg, ha_prfx, node_id, snr, False, sug_area) # noqa: E501 + for row in RegisterMap.map.values(): + info_id = row['reg'] + res = self.ha_conf(info_id, ha_prfx, node_id, snr, False, sug_area) # noqa: E501 if res: yield res @@ -38,85 +88,28 @@ class InfosG3P(Infos): stores the values in Infos.db buf: buffer of the sequence to parse''' - result = struct.unpack_from('!l', buf, ind) - elms = result[0] - i = 0 - ind += 4 - while i < elms: - result = struct.unpack_from('!lB', buf, ind) - info_id = result[0] - data_type = result[1] - ind += 5 + for addr, row in RegisterMap.map.items(): + if isinstance(row, dict): + info_id = row['reg'] + fmt = row['fmt'] + res = struct.unpack_from(fmt, buf, addr) + result = res[0] + if 'ratio' in row: + ratio = row['ratio'] + result *= ratio + keys, level, unit, must_incr, new_val = self._key_obj(info_id) - if data_type == 0x54: # 'T' -> Pascal-String - str_len = buf[ind] - result = struct.unpack_from(f'!{str_len+1}p', buf, - ind)[0].decode(encoding='ascii', - errors='replace') - ind += str_len+1 - - elif data_type == 0x49: # 'I' -> int32 - # if new_val: - # struct.pack_into('!l', buf, ind, new_val) - result = struct.unpack_from('!l', buf, ind)[0] - ind += 4 - - elif data_type == 0x53: # 'S' -> short - # if new_val: - # struct.pack_into('!h', buf, ind, new_val) - result = struct.unpack_from('!h', buf, ind)[0] - ind += 2 - - elif data_type == 0x46: # 'F' -> float32 - # if new_val: - # struct.pack_into('!f', buf, ind, new_val) - result = round(struct.unpack_from('!f', buf, ind)[0], 2) - ind += 4 - - elif data_type == 0x4c: # 'L' -> int64 - # if new_val: - # struct.pack_into('!q', buf, ind, new_val) - result = struct.unpack_from('!q', buf, ind)[0] - ind += 8 - - else: - self.inc_counter('Invalid_Data_Type') - logging.error(f"Infos.parse: data_type: {data_type}" - " not supported") - return - if keys: - dict = self.db - name = '' - - for key in keys[:-1]: - if key not in dict: - dict[key] = {} - dict = dict[key] - name += key + '.' - - if keys[-1] not in dict: - update = (not must_incr or result > 0) - else: - if must_incr: - update = dict[keys[-1]] < result - else: - update = dict[keys[-1]] != result - - if update: - dict[keys[-1]] = result - name += keys[-1] + name, update = self.update_db(keys, must_incr, result) yield keys[0], update else: + name = str(f'info-id.0x{addr:x}') update = False - name = str(f'info-id.0x{info_id:x}') self.tracer.log(level, f'{name} : {result}{unit}' f' update: {update}') - i += 1 - def ignore_this_device(self, dep: dict) -> bool: '''Checks the equation in the dep dict diff --git a/app/src/gen3plus/solarman_v5.py b/app/src/gen3plus/solarman_v5.py index 6a371ff..ac01f9b 100644 --- a/app/src/gen3plus/solarman_v5.py +++ b/app/src/gen3plus/solarman_v5.py @@ -277,6 +277,8 @@ class SolarmanV5(Message): logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}') if (ftype == 1): + self.__process_data() + result = struct.unpack_from('!HH', data, 0xdc) rated = result[0]/1 actual = result[1]/10 @@ -300,6 +302,11 @@ class SolarmanV5(Message): self.forward(self._recv_buffer, self.header_len+self.data_len+2) + def __process_data(self): + for key, update in self.db.parse(self._recv_buffer, 0): + if update: + self.new_data[key] = True + def msg_data_rsp(self): self.msg_response() diff --git a/app/src/infos.py b/app/src/infos.py index 965299c..eb81470 100644 --- a/app/src/infos.py +++ b/app/src/infos.py @@ -402,3 +402,23 @@ class Infos: # new_val = d['new_value'] return d['name'], d['level'], d['unit'], must_incr, new_val + + def update_db(self, keys, must_incr, result): + name = '' + dict = self.db + for key in keys[:-1]: + if key not in dict: + dict[key] = {} + dict = dict[key] + name += key + '.' + if keys[-1] not in dict: + update = (not must_incr or result > 0) + else: + if must_incr: + update = dict[keys[-1]] < result + else: + update = dict[keys[-1]] != result + if update: + dict[keys[-1]] = result + name += keys[-1] + return name, update