diff --git a/app/src/gen3/infos_g3.py b/app/src/gen3/infos_g3.py index b8dd908..964a78f 100644 --- a/app/src/gen3/infos_g3.py +++ b/app/src/gen3/infos_g3.py @@ -1,17 +1,94 @@ import struct -import json import logging from typing import Generator if __name__ == "app.src.gen3.infos_g3": - from app.src.infos import Infos + from app.src.infos import Infos, Register else: # pragma: no cover - from infos import Infos + from infos import Infos, Register + + +class RegisterMap: + map = { + 0x00092ba8: Register.COLLECTOR_FW_VERSION, + 0x000927c0: Register.CHIP_TYPE, + 0x00092f90: Register.CHIP_MODEL, + 0x00095a88: Register.TRACE_URL, + 0x00095aec: Register.LOGGER_URL, + 0x0000000a: Register.PRODUCT_NAME, + 0x00000014: Register.MANUFACTURER, + 0x0000001e: Register.VERSION, + 0x00000028: Register.SERIAL_NUMBER, + 0x00000032: Register.EQUIPMENT_MODEL, + 0x00013880: Register.NO_INPUTS, + 0xffffff00: Register.INVERTER_CNT, + 0xffffff01: Register.UNKNOWN_SNR, + 0xffffff02: Register.UNKNOWN_MSG, + 0xffffff03: Register.INVALID_DATA_TYPE, + 0xffffff04: Register.INTERNAL_ERROR, + 0xffffff05: Register.UNKNOWN_CTRL, + 0xffffff06: Register.OTA_START_MSG, + 0xffffff07: Register.SW_EXCEPTION, + 0xfffffffe: Register.TEST_REG1, + 0xffffffff: Register.TEST_REG2, + 0x00000640: Register.OUTPUT_POWER, + 0x000005dc: Register.RATED_POWER, + 0x00000514: Register.INVERTER_TEMP, + 0x000006a4: Register.PV1_VOLTAGE, + 0x00000708: Register.PV1_CURRENT, + 0x0000076c: Register.PV1_POWER, + 0x000007d0: Register.PV2_VOLTAGE, + 0x00000834: Register.PV2_CURRENT, + 0x00000898: Register.PV2_POWER, + 0x000008fc: Register.PV3_VOLTAGE, + 0x00000960: Register.PV3_CURRENT, + 0x000009c4: Register.PV3_POWER, + 0x00000a28: Register.PV4_VOLTAGE, + 0x00000a8c: Register.PV4_CURRENT, + 0x00000af0: Register.PV4_POWER, + 0x00000c1c: Register.PV1_DAILY_GENERATION, + 0x00000c80: Register.PV1_TOTAL_GENERATION, + 0x00000ce4: Register.PV2_DAILY_GENERATION, + 0x00000d48: Register.PV2_TOTAL_GENERATION, + 0x00000dac: Register.PV3_DAILY_GENERATION, + 0x00000e10: Register.PV3_TOTAL_GENERATION, + 0x00000e74: Register.PV4_DAILY_GENERATION, + 0x00000ed8: Register.PV4_TOTAL_GENERATION, + 0x00000b54: Register.DAILY_GENERATION, + 0x00000bb8: Register.TOTAL_GENERATION, + 0x000003e8: Register.GRID_VOLTAGE, + 0x0000044c: Register.GRID_CURRENT, + 0x000004b0: Register.GRID_FREQUENCY, + 0x000cfc38: Register.CONNECT_COUNT, + 0x000c3500: Register.SIGNAL_STRENGTH, + 0x000c96a8: Register.POWER_ON_TIME, + 0x000d0020: Register.COLLECT_INTERVAL, + 0x000cf850: Register.DATA_UP_INTERVAL, + 0x000c7f38: Register.COMMUNICATION_TYPE, + 0x00000191: Register.EVENT_401, + 0x00000192: Register.EVENT_402, + 0x00000193: Register.EVENT_403, + 0x00000194: Register.EVENT_404, + 0x00000195: Register.EVENT_405, + 0x00000196: Register.EVENT_406, + 0x00000197: Register.EVENT_407, + 0x00000198: Register.EVENT_408, + 0x00000199: Register.EVENT_409, + 0x0000019a: Register.EVENT_410, + 0x0000019b: Register.EVENT_411, + 0x0000019c: Register.EVENT_412, + 0x0000019d: Register.EVENT_413, + 0x0000019e: Register.EVENT_414, + 0x0000019f: Register.EVENT_415, + 0x000001a0: Register.EVENT_416, + } class InfosG3(Infos): - def ha_confs(self, ha_prfx, node_id, snr, singleton: bool, sug_area='') \ + + def ha_confs(self, ha_prfx: str, node_id: str, snr: str, + sug_area: str = '') \ -> Generator[tuple[dict, str], None, None]: '''Generator function yields a json register struct for home-assistant auto configuration and a unique entity string @@ -21,119 +98,11 @@ class InfosG3(Infos): snr:str ==> serial number of the inverter, used to build unique entity strings sug_area:str ==> suggested area string from the config file''' - tab = self.info_defs - for key in tab: - row = tab[key] - if 'singleton' in row: - if singleton != row['singleton']: - continue - elif singleton: - continue - prfx = ha_prfx + node_id - - # check if we have details for home assistant - if 'ha' in row: - ha = row['ha'] - if 'comp' in ha: - component = ha['comp'] - else: - component = 'sensor' - attr = {} - if 'name' in ha: - attr['name'] = ha['name'] - else: - attr['name'] = row['name'][-1] - - attr['stat_t'] = prfx + row['name'][0] - attr['dev_cla'] = ha['dev_cla'] - attr['stat_cla'] = ha['stat_cla'] - attr['uniq_id'] = ha['id']+snr - if 'val_tpl' in ha: - attr['val_tpl'] = ha['val_tpl'] - elif 'fmt' in ha: - attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }} # noqa: E501 - else: - self.inc_counter('Internal_Error') - logging.error(f"Infos._info_defs: the row for {key} do" - " not have a 'val_tpl' nor a 'fmt' value") - - # add unit_of_meas only, if status_class isn't none. If - # status_cla is None we want a number format and not line - # graph in home assistant. A unit will change the number - # format to a line graph - if 'unit' in row and attr['stat_cla'] is not None: - attr['unit_of_meas'] = row['unit'] # 'unit_of_meas' - if 'icon' in ha: - attr['ic'] = ha['icon'] # icon for the entity - if 'nat_prc' in ha: - attr['sug_dsp_prc'] = ha['nat_prc'] # precison of floats - if 'ent_cat' in ha: - attr['ent_cat'] = ha['ent_cat'] # diagnostic, config - - # enabled_by_default is deactivated, since it avoid the via - # setup of the devices. It seems, that there is a bug in home - # assistant. tested with 'Home Assistant 2023.10.4' - # if 'en' in ha: # enabled_by_default - # attr['en'] = ha['en'] - - if 'dev' in ha: - device = self.info_devs[ha['dev']] - - if 'dep' in device and self.ignore_this_device(device['dep']): # noqa: E501 - continue - - dev = {} - - # the same name for 'name' and 'suggested area', so we get - # dedicated devices in home assistant with short value - # name and headline - if (sug_area == '' or - ('singleton' in device and device['singleton'])): - dev['name'] = device['name'] - dev['sa'] = device['name'] - else: - dev['name'] = device['name']+' - '+sug_area - dev['sa'] = device['name']+' - '+sug_area - - if 'via' in device: # add the link to the parent device - via = device['via'] - if via in self.info_devs: - via_dev = self.info_devs[via] - if 'singleton' in via_dev and via_dev['singleton']: - dev['via_device'] = via - else: - dev['via_device'] = f"{via}_{snr}" - else: - self.inc_counter('Internal_Error') - logging.error(f"Infos._info_defs: the row for " - f"{key} has an invalid via value: " - f"{via}") - - for key in ('mdl', 'mf', 'sw', 'hw'): # add optional - # values fpr 'modell', 'manufacturer', 'sw version' and - # 'hw version' - if key in device: - data = self.dev_value(device[key]) - if data is not None: - dev[key] = data - - if 'singleton' in device and device['singleton']: - dev['ids'] = [f"{ha['dev']}"] - else: - dev['ids'] = [f"{ha['dev']}_{snr}"] - - attr['dev'] = dev - - origin = {} - origin['name'] = self.app_name - origin['sw'] = self.version - attr['o'] = origin - else: - self.inc_counter('Internal_Error') - logging.error(f"Infos._info_defs: the row for {key} " - "missing 'dev' value for ha register") - - yield json.dumps(attr), component, node_id, attr['uniq_id'] + # 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 + if res: + yield res def parse(self, buf, ind=0) -> Generator[tuple[str, bool], None, None]: '''parse a data sequence received from the inverter and @@ -146,10 +115,13 @@ class InfosG3(Infos): ind += 4 while i < elms: result = struct.unpack_from('!lB', buf, ind) - info_id = result[0] + addr = result[0] + if addr not in RegisterMap.map: + info_id = -1 + else: + 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] @@ -159,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 @@ -188,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/gen3/inverter_g3.py b/app/src/gen3/inverter_g3.py index b3e33d4..e9db0f4 100644 --- a/app/src/gen3/inverter_g3.py +++ b/app/src/gen3/inverter_g3.py @@ -110,7 +110,7 @@ class InverterG3(Inverter, ConnectionG3): '''register all our topics at home assistant''' for data_json, component, node_id, id in self.db.ha_confs( self.entity_prfx, self.node_id, self.unique_id, - False, self.sug_area): + self.sug_area): logger_mqtt.debug(f"MQTT Register: cmp:'{component}'" f" node_id:'{node_id}' {data_json}") await self.mqtt.publish(f"{self.discovery_prfx}{component}" diff --git a/app/src/gen3plus/infos_g3p.py b/app/src/gen3plus/infos_g3p.py index fff6efa..831d323 100644 --- a/app/src/gen3plus/infos_g3p.py +++ b/app/src/gen3plus/infos_g3p.py @@ -1,17 +1,80 @@ import struct -import json -import logging from typing import Generator if __name__ == "app.src.gen3plus.infos_g3p": - from app.src.infos import Infos + from app.src.infos import Infos, Register else: # pragma: no cover - from infos import Infos + from infos import Infos, Register + + +class RegisterMap: + # make the class read/only by using __slots__ + + __slots__ = () + map = { + # 0x41020007: {'reg': Register.DEVICE_SNR, 'fmt': ' Generator[tuple[dict, str], None, None]: '''Generator function yields a json register struct for home-assistant auto configuration and a unique entity string @@ -21,204 +84,49 @@ class InfosG3P(Infos): snr:str ==> serial number of the inverter, used to build unique entity strings sug_area:str ==> suggested area string from the config file''' - tab = self.info_defs - for key in tab: - row = tab[key] - if 'singleton' in row: - if singleton != row['singleton']: - continue - elif singleton: - continue - prfx = ha_prfx + node_id + # iterate over RegisterMap.map and get the register values + 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 - # check if we have details for home assistant - if 'ha' in row: - ha = row['ha'] - if 'comp' in ha: - component = ha['comp'] - else: - component = 'sensor' - attr = {} - if 'name' in ha: - attr['name'] = ha['name'] - else: - attr['name'] = row['name'][-1] - - attr['stat_t'] = prfx + row['name'][0] - attr['dev_cla'] = ha['dev_cla'] - attr['stat_cla'] = ha['stat_cla'] - attr['uniq_id'] = ha['id']+snr - if 'val_tpl' in ha: - attr['val_tpl'] = ha['val_tpl'] - elif 'fmt' in ha: - attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }} # noqa: E501 - else: - self.inc_counter('Internal_Error') - logging.error(f"Infos._info_defs: the row for {key} do" - " not have a 'val_tpl' nor a 'fmt' value") - - # add unit_of_meas only, if status_class isn't none. If - # status_cla is None we want a number format and not line - # graph in home assistant. A unit will change the number - # format to a line graph - if 'unit' in row and attr['stat_cla'] is not None: - attr['unit_of_meas'] = row['unit'] # 'unit_of_meas' - if 'icon' in ha: - attr['ic'] = ha['icon'] # icon for the entity - if 'nat_prc' in ha: - attr['sug_dsp_prc'] = ha['nat_prc'] # precison of floats - if 'ent_cat' in ha: - attr['ent_cat'] = ha['ent_cat'] # diagnostic, config - - # enabled_by_default is deactivated, since it avoid the via - # setup of the devices. It seems, that there is a bug in home - # assistant. tested with 'Home Assistant 2023.10.4' - # if 'en' in ha: # enabled_by_default - # attr['en'] = ha['en'] - - if 'dev' in ha: - device = self.info_devs[ha['dev']] - - if 'dep' in device and self.ignore_this_device(device['dep']): # noqa: E501 - continue - - dev = {} - - # the same name for 'name' and 'suggested area', so we get - # dedicated devices in home assistant with short value - # name and headline - if (sug_area == '' or - ('singleton' in device and device['singleton'])): - dev['name'] = device['name'] - dev['sa'] = device['name'] - else: - dev['name'] = device['name']+' - '+sug_area - dev['sa'] = device['name']+' - '+sug_area - - if 'via' in device: # add the link to the parent device - via = device['via'] - if via in self.info_devs: - via_dev = self.info_devs[via] - if 'singleton' in via_dev and via_dev['singleton']: - dev['via_device'] = via - else: - dev['via_device'] = f"{via}_{snr}" - else: - self.inc_counter('Internal_Error') - logging.error(f"Infos._info_defs: the row for " - f"{key} has an invalid via value: " - f"{via}") - - for key in ('mdl', 'mf', 'sw', 'hw'): # add optional - # values fpr 'modell', 'manufacturer', 'sw version' and - # 'hw version' - if key in device: - data = self.dev_value(device[key]) - if data is not None: - dev[key] = data - - if 'singleton' in device and device['singleton']: - dev['ids'] = [f"{ha['dev']}"] - else: - dev['ids'] = [f"{ha['dev']}_{snr}"] - - attr['dev'] = dev - - origin = {} - origin['name'] = self.app_name - origin['sw'] = self.version - attr['o'] = origin - else: - self.inc_counter('Internal_Error') - logging.error(f"Infos._info_defs: the row for {key} " - "missing 'dev' value for ha register") - - yield json.dumps(attr), component, node_id, attr['uniq_id'] - - def parse(self, buf, ind=0) -> Generator[tuple[str, bool], None, None]: + def parse(self, buf, msg_type: int, rcv_ftype: int) \ + -> 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''' - 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 idx, row in RegisterMap.map.items(): + addr = idx & 0xffff + ftype = (idx >> 16) & 0xff + mtype = (idx >> 24) & 0xff + if ftype != rcv_ftype or mtype != msg_type: + continue + if isinstance(row, dict): + info_id = row['reg'] + fmt = row['fmt'] + res = struct.unpack_from(fmt, buf, addr) + result = res[0] + if isinstance(result, (bytearray, bytes)): + result = result.decode('utf-8') + if 'eval' in row: + result = eval(row['eval']) + if 'ratio' in row: + result = round(result * row['ratio'], 2) + 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/inverter_g3p.py b/app/src/gen3plus/inverter_g3p.py index e8f979a..70e8d5c 100644 --- a/app/src/gen3plus/inverter_g3p.py +++ b/app/src/gen3plus/inverter_g3p.py @@ -110,7 +110,7 @@ class InverterG3P(Inverter, ConnectionG3P): '''register all our topics at home assistant''' for data_json, component, node_id, id in self.db.ha_confs( self.entity_prfx, self.node_id, self.unique_id, - False, self.sug_area): + self.sug_area): logger_mqtt.debug(f"MQTT Register: cmp:'{component}'" f" node_id:'{node_id}' {data_json}") await self.mqtt.publish(f"{self.discovery_prfx}{component}" diff --git a/app/src/gen3plus/solarman_v5.py b/app/src/gen3plus/solarman_v5.py index 6a371ff..9d5ef58 100644 --- a/app/src/gen3plus/solarman_v5.py +++ b/app/src/gen3plus/solarman_v5.py @@ -236,26 +236,12 @@ class SolarmanV5(Message): total = result[1] tim = result[2] res = result[3] # always zero - logger.info(f'frame type:{ftype} total:{total}s' + logger.info(f'frame type:{ftype:02x} total:{total}s' f' timer:{tim:08x}s null:{res}') dt = datetime.fromtimestamp(total) logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}') - if (ftype == 2): - result = struct.unpack_from('!BBBBBB40s', data, 13) - upload_period = result[0] - data_acq_period = result[1] - heart_beat = result[2] - res = result[3] - wifi = result[4] - ver = result[6] - # res2 = result[5] - logger.info(f'upload:{upload_period}min ' - f'data collect:{data_acq_period}s ' - f'heartbeat:{heart_beat}s ' - f'wifi:{wifi}%') - logger.info(f'ver:{ver}') - + self.__process_data(ftype) self.forward(self._recv_buffer, self.header_len+self.data_len+2) def msg_dev_rsp(self): @@ -270,36 +256,21 @@ class SolarmanV5(Message): offset = result[3] unkn = result[4] cnt = result[5] - logger.info(f'ftype:{ftype} total:{total}s' + logger.info(f'ftype:{ftype:02x} total:{total}s' f' timer:{tim:08x}s ofs:{offset}' f' ??: {unkn:08x} cnt:{cnt}') dt = datetime.fromtimestamp(total) logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}') - if (ftype == 1): - result = struct.unpack_from('!HH', data, 0xdc) - rated = result[0]/1 - actual = result[1]/10 - logger.info(f'Rated Power:{rated}W, Actual Power:{actual}W') - - result = struct.unpack_from('!HLHLHLHLHL', data, 0xf8) - daily = result[0]/100 - total = result[1]/100 - pv1_daily = result[2]/100 - pv1_total = result[3]/100 - pv2_daily = result[4]/100 - pv2_total = result[5]/100 - pv3_daily = result[6]/100 - pv3_total = result[7]/100 - pv4_daily = result[8]/100 - pv4_total = result[9]/100 - logger.info(f'daily:{daily}kWh ' - f'tolal:{total}kWh\n' - f'pv daily:{pv1_daily}kWh, {pv2_daily}kWh, {pv3_daily}kWh, {pv4_daily}kWh\n' # noqa: E501 - f'pv total:{pv1_total}kWh, {pv2_total}kWh, {pv3_total}kWh, {pv4_total}kWh') # noqa: E501 - + self.__process_data(ftype & 0x7f) self.forward(self._recv_buffer, self.header_len+self.data_len+2) + def __process_data(self, ftype): + msg_type = self.control >> 8 + for key, update in self.db.parse(self._recv_buffer, msg_type, ftype): + 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 a0dab8e..f3b40d5 100644 --- a/app/src/infos.py +++ b/app/src/infos.py @@ -1,5 +1,92 @@ import logging +import json import os +from enum import Enum +from typing import Generator + + +class Register(Enum): + 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 + INVERTER_CNT = 50 + UNKNOWN_SNR = 51 + UNKNOWN_MSG = 52 + INVALID_DATA_TYPE = 53 + INTERNAL_ERROR = 54 + UNKNOWN_CTRL = 55 + OTA_START_MSG = 56 + SW_EXCEPTION = 57 + OUTPUT_POWER = 83 + RATED_POWER = 84 + INVERTER_TEMP = 85 + PV1_VOLTAGE = 100 + PV1_CURRENT = 101 + PV1_POWER = 102 + PV2_VOLTAGE = 110 + PV2_CURRENT = 111 + PV2_POWER = 112 + PV3_VOLTAGE = 120 + PV3_CURRENT = 121 + PV3_POWER = 122 + PV4_VOLTAGE = 130 + PV4_CURRENT = 131 + PV4_POWER = 132 + PV5_VOLTAGE = 140 + PV5_CURRENT = 141 + PV5_POWER = 142 + PV6_VOLTAGE = 150 + PV6_CURRENT = 151 + PV6_POWER = 152 + PV1_DAILY_GENERATION = 200 + PV1_TOTAL_GENERATION = 201 + PV2_DAILY_GENERATION = 210 + PV2_TOTAL_GENERATION = 211 + PV3_DAILY_GENERATION = 220 + PV3_TOTAL_GENERATION = 221 + PV4_DAILY_GENERATION = 230 + PV4_TOTAL_GENERATION = 231 + PV5_DAILY_GENERATION = 240 + PV5_TOTAL_GENERATION = 241 + PV6_DAILY_GENERATION = 250 + PV6_TOTAL_GENERATION = 251 + GRID_VOLTAGE = 300 + GRID_CURRENT = 301 + GRID_FREQUENCY = 302 + DAILY_GENERATION = 303 + TOTAL_GENERATION = 304 + COMMUNICATION_TYPE = 400 + SIGNAL_STRENGTH = 401 + POWER_ON_TIME = 402 + COLLECT_INTERVAL = 403 + DATA_UP_INTERVAL = 404 + CONNECT_COUNT = 405 + EVENT_401 = 500 + EVENT_402 = 501 + EVENT_403 = 502 + EVENT_404 = 503 + EVENT_405 = 504 + EVENT_406 = 505 + EVENT_407 = 506 + EVENT_408 = 507 + EVENT_409 = 508 + EVENT_410 = 509 + EVENT_411 = 510 + EVENT_412 = 511 + EVENT_413 = 512 + EVENT_414 = 513 + EVENT_415 = 514 + EVENT_416 = 515 + TEST_REG1 = 10000 + TEST_REG2 = 10001 class Infos: @@ -14,13 +101,13 @@ class Infos: logging.info('Initialize proxy statistics') # init proxy counter in the class.stat dictionary cls.stat['proxy'] = {} - for key in cls._info_defs: - name = cls._info_defs[key]['name'] + for key in cls.__info_defs: + name = cls.__info_defs[key]['name'] if name[0] == 'proxy': cls.stat['proxy'][name[1]] = 0 # add values from the environment to the device definition table - prxy = cls._info_devs['proxy'] + prxy = cls.__info_devs['proxy'] prxy['sw'] = cls.version prxy['mdl'] = cls.app_name @@ -28,122 +115,122 @@ class Infos: self.db = {} self.tracer = logging.getLogger('data') - _info_devs = { + __info_devs = { 'proxy': {'singleton': True, 'name': 'Proxy', 'mf': 'Stefan Allius'}, # noqa: E501 - 'controller': {'via': 'proxy', 'name': 'Controller', 'mdl': 0x00092f90, 'mf': 0x000927c0, 'sw': 0x00092ba8}, # noqa: E501 - 'inverter': {'via': 'controller', 'name': 'Micro Inverter', 'mdl': 0x00000032, 'mf': 0x00000014, 'sw': 0x0000001e}, # noqa: E501 + 'controller': {'via': 'proxy', 'name': 'Controller', 'mdl': Register.CHIP_MODEL, 'mf': Register.CHIP_TYPE, 'sw': Register.COLLECTOR_FW_VERSION}, # noqa: E501 + 'inverter': {'via': 'controller', 'name': 'Micro Inverter', 'mdl': Register.EQUIPMENT_MODEL, 'mf': Register.MANUFACTURER, 'sw': Register.VERSION}, # noqa: E501 'input_pv1': {'via': 'inverter', 'name': 'Module PV1'}, - 'input_pv2': {'via': 'inverter', 'name': 'Module PV2', 'dep': {'reg': 0x00013880, 'gte': 2}}, # noqa: E501 - 'input_pv3': {'via': 'inverter', 'name': 'Module PV3', 'dep': {'reg': 0x00013880, 'gte': 3}}, # noqa: E501 - 'input_pv4': {'via': 'inverter', 'name': 'Module PV4', 'dep': {'reg': 0x00013880, 'gte': 4}}, # noqa: E501 + 'input_pv2': {'via': 'inverter', 'name': 'Module PV2', 'dep': {'reg': Register.NO_INPUTS, 'gte': 2}}, # noqa: E501 + 'input_pv3': {'via': 'inverter', 'name': 'Module PV3', 'dep': {'reg': Register.NO_INPUTS, 'gte': 3}}, # noqa: E501 + 'input_pv4': {'via': 'inverter', 'name': 'Module PV4', 'dep': {'reg': Register.NO_INPUTS, 'gte': 4}}, # noqa: E501 } __comm_type_val_tpl = "{%set com_types = ['n/a','Wi-Fi', 'G4', 'G5', 'GPRS'] %}{{com_types[value_json['Communication_Type']|int(0)]|default(value_json['Communication_Type'])}}" # noqa: E501 - _info_defs = { + __info_defs = { # collector values used for device registration: - 0x00092ba8: {'name': ['collector', 'Collector_Fw_Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501 - 0x000927c0: {'name': ['collector', 'Chip_Type'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00092f90: {'name': ['collector', 'Chip_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00095a88: {'name': ['collector', 'Trace_URL'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00095aec: {'name': ['collector', 'Logger_URL'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.COLLECTOR_FW_VERSION: {'name': ['collector', 'Collector_Fw_Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501 + Register.CHIP_TYPE: {'name': ['collector', 'Chip_Type'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.CHIP_MODEL: {'name': ['collector', 'Chip_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.TRACE_URL: {'name': ['collector', 'Trace_URL'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.LOGGER_URL: {'name': ['collector', 'Logger_URL'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 # inverter values used for device registration: - 0x0000000a: {'name': ['inverter', 'Product_Name'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000014: {'name': ['inverter', 'Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x0000001e: {'name': ['inverter', 'Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501 - 0x00000028: {'name': ['inverter', 'Serial_Number'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000032: {'name': ['inverter', 'Equipment_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00013880: {'name': ['inverter', 'No_Inputs'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.PRODUCT_NAME: {'name': ['inverter', 'Product_Name'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.MANUFACTURER: {'name': ['inverter', 'Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.VERSION: {'name': ['inverter', 'Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501 + Register.SERIAL_NUMBER: {'name': ['inverter', 'Serial_Number'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EQUIPMENT_MODEL: {'name': ['inverter', 'Equipment_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.NO_INPUTS: {'name': ['inverter', 'No_Inputs'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 # proxy: - 0xffffff00: {'name': ['proxy', 'Inverter_Cnt'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_count_', 'fmt': '| int', 'name': 'Active Inverter Connections', 'icon': 'mdi:counter'}}, # noqa: E501 - 0xffffff01: {'name': ['proxy', 'Unknown_SNR'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_snr_', 'fmt': '| int', 'name': 'Unknown Serial No', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0xffffff02: {'name': ['proxy', 'Unknown_Msg'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_msg_', 'fmt': '| int', 'name': 'Unknown Msg Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0xffffff03: {'name': ['proxy', 'Invalid_Data_Type'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_data_type_', 'fmt': '| int', 'name': 'Invalid Data Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0xffffff04: {'name': ['proxy', 'Internal_Error'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'intern_err_', 'fmt': '| int', 'name': 'Internal Error', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic', 'en': False}}, # noqa: E501 - 0xffffff05: {'name': ['proxy', 'Unknown_Ctrl'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_ctrl_', 'fmt': '| int', 'name': 'Unknown Control Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0xffffff06: {'name': ['proxy', 'OTA_Start_Msg'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'ota_start_cmd_', 'fmt': '| int', 'name': 'OTA Start Cmd', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0xffffff07: {'name': ['proxy', 'SW_Exception'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'sw_exception_', 'fmt': '| int', 'name': 'Internal SW Exception', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.INVERTER_CNT: {'name': ['proxy', 'Inverter_Cnt'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_count_', 'fmt': '| int', 'name': 'Active Inverter Connections', 'icon': 'mdi:counter'}}, # noqa: E501 + Register.UNKNOWN_SNR: {'name': ['proxy', 'Unknown_SNR'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_snr_', 'fmt': '| int', 'name': 'Unknown Serial No', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.UNKNOWN_MSG: {'name': ['proxy', 'Unknown_Msg'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_msg_', 'fmt': '| int', 'name': 'Unknown Msg Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.INVALID_DATA_TYPE: {'name': ['proxy', 'Invalid_Data_Type'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_data_type_', 'fmt': '| int', 'name': 'Invalid Data Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.INTERNAL_ERROR: {'name': ['proxy', 'Internal_Error'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'intern_err_', 'fmt': '| int', 'name': 'Internal Error', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic', 'en': False}}, # noqa: E501 + Register.UNKNOWN_CTRL: {'name': ['proxy', 'Unknown_Ctrl'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_ctrl_', 'fmt': '| int', 'name': 'Unknown Control Type', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.OTA_START_MSG: {'name': ['proxy', 'OTA_Start_Msg'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'ota_start_cmd_', 'fmt': '| int', 'name': 'OTA Start Cmd', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.SW_EXCEPTION: {'name': ['proxy', 'SW_Exception'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'sw_exception_', 'fmt': '| int', 'name': 'Internal SW Exception', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501 # 0xffffff03: {'name':['proxy', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'proxy', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'proxy_volt_', 'fmt':'| float','name': 'Grid Voltage'}}, # noqa: E501 # events - 0x00000191: {'name': ['events', '401_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000192: {'name': ['events', '402_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000193: {'name': ['events', '403_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000194: {'name': ['events', '404_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000195: {'name': ['events', '405_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000196: {'name': ['events', '406_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000197: {'name': ['events', '407_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000198: {'name': ['events', '408_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x00000199: {'name': ['events', '409_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x0000019a: {'name': ['events', '410_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x0000019b: {'name': ['events', '411_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x0000019c: {'name': ['events', '412_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x0000019d: {'name': ['events', '413_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x0000019e: {'name': ['events', '414_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x0000019f: {'name': ['events', '415_GridFreqOverRating'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 - 0x000001a0: {'name': ['events', '416_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_401: {'name': ['events', '401_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_402: {'name': ['events', '402_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_403: {'name': ['events', '403_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_404: {'name': ['events', '404_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_405: {'name': ['events', '405_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_406: {'name': ['events', '406_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_407: {'name': ['events', '407_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_408: {'name': ['events', '408_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_409: {'name': ['events', '409_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_410: {'name': ['events', '410_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_411: {'name': ['events', '411_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_412: {'name': ['events', '412_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_413: {'name': ['events', '413_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_414: {'name': ['events', '414_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_415: {'name': ['events', '415_GridFreqOverRating'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 + Register.EVENT_416: {'name': ['events', '416_'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 # grid measures: - 0x000003e8: {'name': ['grid', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'inverter', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'out_volt_', 'fmt': '| float', 'name': 'Grid Voltage', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x0000044c: {'name': ['grid', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'inverter', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'out_cur_', 'fmt': '| float', 'name': 'Grid Current', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x000004b0: {'name': ['grid', 'Frequency'], 'level': logging.DEBUG, 'unit': 'Hz', 'ha': {'dev': 'inverter', 'dev_cla': 'frequency', 'stat_cla': 'measurement', 'id': 'out_freq_', 'fmt': '| float', 'name': 'Grid Frequency', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x00000640: {'name': ['grid', 'Output_Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'out_power_', 'fmt': '| float', 'name': 'Power'}}, # noqa: E501 - 0x000005dc: {'name': ['env', 'Rated_Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'rated_power_', 'fmt': '| string + " W"', 'name': 'Rated Power', 'icon': 'mdi:lightning-bolt', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x00000514: {'name': ['env', 'Inverter_Temp'], 'level': logging.DEBUG, 'unit': '°C', 'ha': {'dev': 'inverter', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_', 'fmt': '| int', 'name': 'Temperature'}}, # noqa: E501 + Register.GRID_VOLTAGE: {'name': ['grid', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'inverter', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'out_volt_', 'fmt': '| float', 'name': 'Grid Voltage', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.GRID_CURRENT: {'name': ['grid', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'inverter', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'out_cur_', 'fmt': '| float', 'name': 'Grid Current', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.GRID_FREQUENCY: {'name': ['grid', 'Frequency'], 'level': logging.DEBUG, 'unit': 'Hz', 'ha': {'dev': 'inverter', 'dev_cla': 'frequency', 'stat_cla': 'measurement', 'id': 'out_freq_', 'fmt': '| float', 'name': 'Grid Frequency', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.OUTPUT_POWER: {'name': ['grid', 'Output_Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'out_power_', 'fmt': '| float', 'name': 'Power'}}, # noqa: E501 + Register.RATED_POWER: {'name': ['env', 'Rated_Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'rated_power_', 'fmt': '| string + " W"', 'name': 'Rated Power', 'icon': 'mdi:lightning-bolt', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.INVERTER_TEMP: {'name': ['env', 'Inverter_Temp'], 'level': logging.DEBUG, 'unit': '°C', 'ha': {'dev': 'inverter', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_', 'fmt': '| int', 'name': 'Temperature'}}, # noqa: E501 # input measures: - 0x000006a4: {'name': ['input', 'pv1', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv1', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv1_', 'val_tpl': "{{ (value_json['pv1']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x00000708: {'name': ['input', 'pv1', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv1', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv1_', 'val_tpl': "{{ (value_json['pv1']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x0000076c: {'name': ['input', 'pv1', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'input_pv1', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv1_', 'val_tpl': "{{ (value_json['pv1']['Power'] | float)}}"}}, # noqa: E501 - 0x000007d0: {'name': ['input', 'pv2', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv2', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv2_', 'val_tpl': "{{ (value_json['pv2']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x00000834: {'name': ['input', 'pv2', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv2', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv2_', 'val_tpl': "{{ (value_json['pv2']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x00000898: {'name': ['input', 'pv2', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'input_pv2', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv2_', 'val_tpl': "{{ (value_json['pv2']['Power'] | float)}}"}}, # noqa: E501 - 0x000008fc: {'name': ['input', 'pv3', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv3', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv3_', 'val_tpl': "{{ (value_json['pv3']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x00000960: {'name': ['input', 'pv3', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv3', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv3_', 'val_tpl': "{{ (value_json['pv3']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x000009c4: {'name': ['input', 'pv3', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'input_pv3', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv3_', 'val_tpl': "{{ (value_json['pv3']['Power'] | float)}}"}}, # noqa: E501 - 0x00000a28: {'name': ['input', 'pv4', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv4', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv4_', 'val_tpl': "{{ (value_json['pv4']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x00000a8c: {'name': ['input', 'pv4', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv4', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv4_', 'val_tpl': "{{ (value_json['pv4']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x00000af0: {'name': ['input', 'pv4', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'input_pv4', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv4_', 'val_tpl': "{{ (value_json['pv4']['Power'] | float)}}"}}, # noqa: E501 - 0x00000c1c: {'name': ['input', 'pv1', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv1', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv1_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv1']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 - 0x00000c80: {'name': ['input', 'pv1', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv1', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv1_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv1']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 - 0x00000ce4: {'name': ['input', 'pv2', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv2', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv2_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv2']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 - 0x00000d48: {'name': ['input', 'pv2', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv2', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv2_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv2']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 - 0x00000dac: {'name': ['input', 'pv3', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv3', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv3_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv3']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 - 0x00000e10: {'name': ['input', 'pv3', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv3', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv3_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv3']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 - 0x00000e74: {'name': ['input', 'pv4', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv4', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv4_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv4']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 - 0x00000ed8: {'name': ['input', 'pv4', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv4', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv4_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv4']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 + Register.PV1_VOLTAGE: {'name': ['input', 'pv1', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv1', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv1_', 'val_tpl': "{{ (value_json['pv1']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.PV1_CURRENT: {'name': ['input', 'pv1', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv1', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv1_', 'val_tpl': "{{ (value_json['pv1']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.PV1_POWER: {'name': ['input', 'pv1', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'input_pv1', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv1_', 'val_tpl': "{{ (value_json['pv1']['Power'] | float)}}"}}, # noqa: E501 + Register.PV2_VOLTAGE: {'name': ['input', 'pv2', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv2', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv2_', 'val_tpl': "{{ (value_json['pv2']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.PV2_CURRENT: {'name': ['input', 'pv2', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv2', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv2_', 'val_tpl': "{{ (value_json['pv2']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.PV2_POWER: {'name': ['input', 'pv2', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'input_pv2', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv2_', 'val_tpl': "{{ (value_json['pv2']['Power'] | float)}}"}}, # noqa: E501 + Register.PV3_VOLTAGE: {'name': ['input', 'pv3', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv3', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv3_', 'val_tpl': "{{ (value_json['pv3']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.PV3_CURRENT: {'name': ['input', 'pv3', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv3', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv3_', 'val_tpl': "{{ (value_json['pv3']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.PV3_POWER: {'name': ['input', 'pv3', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'input_pv3', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv3_', 'val_tpl': "{{ (value_json['pv3']['Power'] | float)}}"}}, # noqa: E501 + Register.PV4_VOLTAGE: {'name': ['input', 'pv4', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha': {'dev': 'input_pv4', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv4_', 'val_tpl': "{{ (value_json['pv4']['Voltage'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.PV4_CURRENT: {'name': ['input', 'pv4', 'Current'], 'level': logging.DEBUG, 'unit': 'A', 'ha': {'dev': 'input_pv4', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv4_', 'val_tpl': "{{ (value_json['pv4']['Current'] | float)}}", 'icon': 'mdi:gauge', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.PV4_POWER: {'name': ['input', 'pv4', 'Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'input_pv4', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_pv4_', 'val_tpl': "{{ (value_json['pv4']['Power'] | float)}}"}}, # noqa: E501 + Register.PV1_DAILY_GENERATION: {'name': ['input', 'pv1', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv1', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv1_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv1']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 + Register.PV1_TOTAL_GENERATION: {'name': ['input', 'pv1', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv1', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv1_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv1']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 + Register.PV2_DAILY_GENERATION: {'name': ['input', 'pv2', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv2', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv2_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv2']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 + Register.PV2_TOTAL_GENERATION: {'name': ['input', 'pv2', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv2', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv2_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv2']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 + Register.PV3_DAILY_GENERATION: {'name': ['input', 'pv3', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv3', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv3_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv3']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 + Register.PV3_TOTAL_GENERATION: {'name': ['input', 'pv3', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv3', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv3_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv3']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 + Register.PV4_DAILY_GENERATION: {'name': ['input', 'pv4', 'Daily_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv4', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_pv4_', 'name': 'Daily Generation', 'val_tpl': "{{ (value_json['pv4']['Daily_Generation'] | float)}}", 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 + Register.PV4_TOTAL_GENERATION: {'name': ['input', 'pv4', 'Total_Generation'], 'level': logging.DEBUG, 'unit': 'kWh', 'ha': {'dev': 'input_pv4', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_pv4_', 'name': 'Total Generation', 'val_tpl': "{{ (value_json['pv4']['Total_Generation'] | float)}}", 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 # total: - 0x00000b54: {'name': ['total', 'Daily_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'inverter', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_', 'fmt': '| float', 'name': 'Daily Generation', 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 - 0x00000bb8: {'name': ['total', 'Total_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'inverter', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_', 'fmt': '| float', 'name': 'Total Generation', 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 + Register.DAILY_GENERATION: {'name': ['total', 'Daily_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'inverter', 'dev_cla': 'energy', 'stat_cla': 'total_increasing', 'id': 'daily_gen_', 'fmt': '| float', 'name': 'Daily Generation', 'icon': 'mdi:solar-power-variant', 'must_incr': True}}, # noqa: E501 + Register.TOTAL_GENERATION: {'name': ['total', 'Total_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'inverter', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_', 'fmt': '| float', 'name': 'Total Generation', 'icon': 'mdi:solar-power', 'must_incr': True}}, # noqa: E501 # controller: - 0x000c3500: {'name': ['controller', 'Signal_Strength'], 'level': logging.DEBUG, 'unit': '%', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'signal_', 'fmt': '| int', 'name': 'Signal Strength', 'icon': 'mdi:wifi'}}, # noqa: E501 - 0x000c96a8: {'name': ['controller', 'Power_On_Time'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id': 'power_on_time_', 'fmt': '| float', 'name': 'Power on Time', 'nat_prc': '3', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x000d0020: {'name': ['controller', 'Collect_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_collect_intval_', 'fmt': '| string + " s"', 'name': 'Data Collect Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x000cfc38: {'name': ['controller', 'Connect_Count'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'connect_count_', 'fmt': '| int', 'name': 'Connect Count', 'icon': 'mdi:counter', 'comp': 'sensor', 'ent_cat': 'diagnostic'}}, # noqa: E501 - 0x000c7f38: {'name': ['controller', 'Communication_Type'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'comm_type_', 'name': 'Communication Type', 'val_tpl': __comm_type_val_tpl, 'comp': 'sensor', 'icon': 'mdi:wifi'}}, # noqa: E501 - # 0x000c7f38: {'name': ['controller', 'Communication_Type'], 'level': logging.DEBUG, 'unit': 's', 'new_value': 5}, # noqa: E501 - 0x000cf850: {'name': ['controller', 'Data_Up_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_up_intval_', 'fmt': '| string + " s"', 'name': 'Data Up Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501 - + Register.SIGNAL_STRENGTH: {'name': ['controller', 'Signal_Strength'], 'level': logging.DEBUG, 'unit': '%', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'signal_', 'fmt': '| int', 'name': 'Signal Strength', 'icon': 'mdi:wifi'}}, # noqa: E501 + Register.POWER_ON_TIME: {'name': ['controller', 'Power_On_Time'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id': 'power_on_time_', 'fmt': '| float', 'name': 'Power on Time', 'nat_prc': '3', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.COLLECT_INTERVAL: {'name': ['controller', 'Collect_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_collect_intval_', 'fmt': '| string + " s"', 'name': 'Data Collect Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.CONNECT_COUNT: {'name': ['controller', 'Connect_Count'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'connect_count_', 'fmt': '| int', 'name': 'Connect Count', 'icon': 'mdi:counter', 'comp': 'sensor', 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.COMMUNICATION_TYPE: {'name': ['controller', 'Communication_Type'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'comm_type_', 'name': 'Communication Type', 'val_tpl': __comm_type_val_tpl, 'comp': 'sensor', 'icon': 'mdi:wifi'}}, # noqa: E501 + Register.DATA_UP_INTERVAL: {'name': ['controller', 'Data_Up_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_up_intval_', 'fmt': '| string + " s"', 'name': 'Data Up Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501 } @property def info_devs(self) -> dict: - return self._info_devs - - @info_devs.setter - def info_devs(self, value: dict) -> None: - self._info_devs = value + return self.__info_devs @property def info_defs(self) -> dict: - return self._info_defs + return self.__info_defs + ''' + if __name__ == "app.src.messages": + @info_defs.setter + def info_defs(self, value: dict) -> None: + self.__info_defs = value - @info_defs.setter - def info_defs(self, value: dict) -> None: - self._info_defs = value + @info_devs.setter + def info_devs(self, value: dict) -> None: + self.__info_devs = value + ''' def dev_value(self, idx: str | int) -> str | int | float | None: '''returns the stored device value from our database @@ -155,8 +242,8 @@ class Infos: ''' if type(idx) is str: return idx # return idx as a fixed value - elif idx in self._info_defs: - row = self._info_defs[idx] + elif idx in self.info_defs: + row = self.info_defs[idx] if 'singleton' in row and row['singleton']: dict = self.stat else: @@ -170,7 +257,7 @@ class Infos: dict = dict[key] return dict # value of the reqeusted entry - return None # unknwon idx, not in __info_defs + return None # unknwon idx, not in info_defs def inc_counter(self, counter: str) -> None: '''inc proxy statistic counter''' @@ -182,9 +269,132 @@ class Infos: dict = self.stat['proxy'] dict[counter] -= 1 + def ha_proxy_confs(self, ha_prfx: str, node_id: str, snr: str) \ + -> Generator[tuple[dict, str], None, None]: + '''Generator function yields json register struct for home-assistant + auto configuration and the unique entity string, for all proxy + registers + + arguments: + ha_prfx:str ==> MQTT prefix for the home assistant 'stat_t string + node_id:str ==> node id of the inverter, used to build unique entity + snr:str ==> serial number of the inverter, used to build unique + entity strings + ''' + # iterate over RegisterMap.map and get the register values for entries + # with Singleton=True, which means that this is a proxy register + for reg in self.info_defs.keys(): + res = self.ha_conf(reg, ha_prfx, node_id, snr, True) # noqa: E501 + if res: + yield res + + def ha_conf(self, key, ha_prfx, node_id, snr, singleton: bool, sug_area: str = '') -> tuple[str, str, str, str]: # noqa: E501 + if key not in self.info_defs: + return None + row = self.info_defs[key] + + if 'singleton' in row: + if singleton != row['singleton']: + return None + elif singleton: + return None + prfx = ha_prfx + node_id + + # check if we have details for home assistant + if 'ha' in row: + ha = row['ha'] + if 'comp' in ha: + component = ha['comp'] + else: + component = 'sensor' + attr = {} + if 'name' in ha: + attr['name'] = ha['name'] + else: + attr['name'] = row['name'][-1] + attr['stat_t'] = prfx + row['name'][0] + attr['dev_cla'] = ha['dev_cla'] + attr['stat_cla'] = ha['stat_cla'] + attr['uniq_id'] = ha['id']+snr + if 'val_tpl' in ha: + attr['val_tpl'] = ha['val_tpl'] + elif 'fmt' in ha: + attr['val_tpl'] = '{{value_json' + f"['{row['name'][-1]}'] {ha['fmt']}" + '}}' # eg. 'val_tpl': "{{ value_json['Output_Power']|float }} # noqa: E501 + else: + self.inc_counter('Internal_Error') + logging.error(f"Infos.info_defs: the row for {key} do" + " not have a 'val_tpl' nor a 'fmt' value") + # add unit_of_meas only, if status_class isn't none. If + # status_cla is None we want a number format and not line + # graph in home assistant. A unit will change the number + # format to a line graph + if 'unit' in row and attr['stat_cla'] is not None: + attr['unit_of_meas'] = row['unit'] # 'unit_of_meas' + if 'icon' in ha: + attr['ic'] = ha['icon'] # icon for the entity + if 'nat_prc' in ha: + attr['sug_dsp_prc'] = ha['nat_prc'] # precison of floats + if 'ent_cat' in ha: + attr['ent_cat'] = ha['ent_cat'] # diagnostic, config + # enabled_by_default is deactivated, since it avoid the via + # setup of the devices. It seems, that there is a bug in home + # assistant. tested with 'Home Assistant 2023.10.4' + # if 'en' in ha: # enabled_by_default + # attr['en'] = ha['en'] + if 'dev' in ha: + device = self.info_devs[ha['dev']] + if 'dep' in device and self.ignore_this_device(device['dep']): # noqa: E501 + return None + dev = {} + # the same name for 'name' and 'suggested area', so we get + # dedicated devices in home assistant with short value + # name and headline + if (sug_area == '' or + ('singleton' in device and device['singleton'])): + dev['name'] = device['name'] + dev['sa'] = device['name'] + else: + dev['name'] = device['name']+' - '+sug_area + dev['sa'] = device['name']+' - '+sug_area + if 'via' in device: # add the link to the parent device + via = device['via'] + if via in self.info_devs: + via_dev = self.info_devs[via] + if 'singleton' in via_dev and via_dev['singleton']: + dev['via_device'] = via + else: + dev['via_device'] = f"{via}_{snr}" + else: + self.inc_counter('Internal_Error') + logging.error(f"Infos.info_defs: the row for " + f"{key} has an invalid via value: " + f"{via}") + for key in ('mdl', 'mf', 'sw', 'hw'): # add optional + # values fpr 'modell', 'manufacturer', 'sw version' and + # 'hw version' + if key in device: + data = self.dev_value(device[key]) + if data is not None: + dev[key] = data + if 'singleton' in device and device['singleton']: + dev['ids'] = [f"{ha['dev']}"] + else: + dev['ids'] = [f"{ha['dev']}_{snr}"] + attr['dev'] = dev + origin = {} + origin['name'] = self.app_name + origin['sw'] = self.version + attr['o'] = origin + else: + self.inc_counter('Internal_Error') + logging.error(f"Infos.info_defs: the row for {key} " + "missing 'dev' value for ha register") + return json.dumps(attr), component, node_id, attr['uniq_id'] + return None + def _key_obj(self, id) -> list: - d = self._info_defs.get(id, {'name': None, 'level': logging.DEBUG, - 'unit': ''}) + d = self.info_defs.get(id, {'name': None, 'level': logging.DEBUG, + 'unit': ''}) if 'ha' in d and 'must_incr' in d['ha']: must_incr = d['ha']['must_incr'] else: @@ -194,3 +404,30 @@ 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 + + def set_db_def_value(self, id, value): + '''set default value''' + row = self.info_defs[id] + if isinstance(row, dict): + keys = row['name'] + self.update_db(keys, False, value) diff --git a/app/src/inverter.py b/app/src/inverter.py index 1053bd5..41eb39c 100644 --- a/app/src/inverter.py +++ b/app/src/inverter.py @@ -40,9 +40,8 @@ class Inverter(): @classmethod async def _register_proxy_stat_home_assistant(cls) -> None: '''register all our topics at home assistant''' - for data_json, component, node_id, id in cls.db_stat.ha_confs( - cls.entity_prfx, cls.proxy_node_id, - cls.proxy_unique_id, True): + for data_json, component, node_id, id in cls.db_stat.ha_proxy_confs( + cls.entity_prfx, cls.proxy_node_id, cls.proxy_unique_id): logger_mqtt.debug(f"MQTT Register: cmp:'{component}' node_id:'{node_id}' {data_json}") # noqa: E501 await cls.mqtt.publish(f'{cls.discovery_prfx}{component}/{node_id}{id}/config', data_json) # noqa: E501 diff --git a/app/tests/test_infos.py b/app/tests/test_infos.py index 4a23400..aedfa61 100644 --- a/app/tests/test_infos.py +++ b/app/tests/test_infos.py @@ -1,5 +1,6 @@ # test_with_pytest.py import pytest, json +from app.src.infos import Register from app.src.gen3.infos_g3 import InfosG3 @pytest.fixture @@ -218,7 +219,7 @@ def test_build_ha_conf1(ContrDataSeq): i.static_init() # initialize counter tests = 0 - for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', singleton=False): + for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123'): if id == 'out_power_123': assert comp == 'sensor' @@ -249,7 +250,7 @@ def test_build_ha_conf1(ContrDataSeq): assert tests==4 - for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True): + for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'): if id == 'out_power_123': assert False @@ -279,7 +280,7 @@ def test_build_ha_conf2(ContrDataSeq, InvDataSeq, InvDataSeq2): pass tests = 0 - for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', singleton=False, sug_area = 'roof'): + for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', sug_area = 'roof'): if id == 'out_power_123': assert comp == 'sensor' @@ -405,22 +406,22 @@ def test_statistic_counter(): val = i.dev_value(0xffffffff) # invalid addr assert val == None - val = i.dev_value(0xffffff00) # valid addr but not initiliazed + val = i.dev_value(Register.INVERTER_CNT) # valid addr but not initiliazed assert val == None or val == 0 i.static_init() # initialize counter assert json.dumps(i.stat) == json.dumps({"proxy": {"Inverter_Cnt": 0, "Unknown_SNR": 0, "Unknown_Msg": 0, "Invalid_Data_Type": 0, "Internal_Error": 0,"Unknown_Ctrl": 0, "OTA_Start_Msg": 0, "SW_Exception": 0}}) - val = i.dev_value(0xffffff00) # valid and initiliazed addr + val = i.dev_value(Register.INVERTER_CNT) # valid and initiliazed addr assert val == 0 i.inc_counter('Inverter_Cnt') assert json.dumps(i.stat) == json.dumps({"proxy": {"Inverter_Cnt": 1, "Unknown_SNR": 0, "Unknown_Msg": 0, "Invalid_Data_Type": 0, "Internal_Error": 0,"Unknown_Ctrl": 0, "OTA_Start_Msg": 0, "SW_Exception": 0}}) - val = i.dev_value(0xffffff00) + val = i.dev_value(Register.INVERTER_CNT) assert val == 1 i.dec_counter('Inverter_Cnt') - val = i.dev_value(0xffffff00) + val = i.dev_value(Register.INVERTER_CNT) assert val == 0 def test_dep_rules(): @@ -434,66 +435,66 @@ def test_dep_rules(): assert res == True i.inc_counter('Inverter_Cnt') # is 1 - val = i.dev_value(0xffffff00) + val = i.dev_value(Register.INVERTER_CNT) assert val == 1 - res = i.ignore_this_device({'reg':0xffffff00}) + res = i.ignore_this_device({'reg': Register.INVERTER_CNT}) assert res == True - res = i.ignore_this_device({'reg':0xffffff00, 'less_eq': 2}) + res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'less_eq': 2}) assert res == False - res = i.ignore_this_device({'reg':0xffffff00, 'gte': 2}) + res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'gte': 2}) assert res == True i.inc_counter('Inverter_Cnt') # is 2 - res = i.ignore_this_device({'reg':0xffffff00, 'less_eq': 2}) + res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'less_eq': 2}) assert res == False - res = i.ignore_this_device({'reg':0xffffff00, 'gte': 2}) + res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'gte': 2}) assert res == False i.inc_counter('Inverter_Cnt') # is 3 - res = i.ignore_this_device({'reg':0xffffff00, 'less_eq': 2}) + res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'less_eq': 2}) assert res == True - res = i.ignore_this_device({'reg':0xffffff00, 'gte': 2}) + res = i.ignore_this_device({'reg': Register.INVERTER_CNT, 'gte': 2}) assert res == False def test_table_definition(): i = InfosG3() i.static_init() # initialize counter - val = i.dev_value(0xffffff04) # check internal error counter + val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter assert val == 0 - for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', singleton=False, sug_area = 'roof'): + for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', sug_area = 'roof'): pass - for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True, sug_area = 'roof'): + for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'): pass - val = i.dev_value(0xffffff04) # check internal error counter + val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter assert val == 0 # test missing 'fmt' value - i.info_defs[0xfffffffe] = {'name':['proxy', 'Internal_Test1'], 'singleton': True, 'ha':{'dev':'proxy', 'dev_cla': None, 'stat_cla': None, 'id':'intern_test1_'}} + i.info_defs[Register.TEST_REG1] = {'name':['proxy', 'Internal_Test1'], 'singleton': True, 'ha':{'dev':'proxy', 'dev_cla': None, 'stat_cla': None, 'id':'intern_test1_'}} tests = 0 - for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True, sug_area = 'roof'): + for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'): if id == 'intern_test1_456': tests +=1 assert tests == 1 - val = i.dev_value(0xffffff04) # check internal error counter + val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter assert val == 1 # test missing 'dev' value - i.info_defs[0xfffffffe] = {'name':['proxy', 'Internal_Test2'], 'singleton': True, 'ha':{'dev_cla': None, 'stat_cla': None, 'id':'intern_test2_', 'fmt':'| int'}} + i.info_defs[Register.TEST_REG1] = {'name':['proxy', 'Internal_Test2'], 'singleton': True, 'ha':{'dev_cla': None, 'stat_cla': None, 'id':'intern_test2_', 'fmt':'| int'}} tests = 0 - for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True, sug_area = 'roof'): + for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'): if id == 'intern_test2_456': tests +=1 assert tests == 1 - val = i.dev_value(0xffffff04) # check internal error counter + val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter assert val == 2 @@ -501,15 +502,15 @@ def test_table_definition(): # test invalid 'via' value i.info_devs['test_dev'] = {'via':'xyz', 'name':'Module PV1'} - i.info_defs[0xfffffffe] = {'name':['proxy', 'Internal_Test2'], 'singleton': True, 'ha':{'dev':'test_dev', 'dev_cla': None, 'stat_cla': None, 'id':'intern_test2_', 'fmt':'| int'}} + i.info_defs[Register.TEST_REG1] = {'name':['proxy', 'Internal_Test2'], 'singleton': True, 'ha':{'dev':'test_dev', 'dev_cla': None, 'stat_cla': None, 'id':'intern_test2_', 'fmt':'| int'}} tests = 0 - for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456', singleton=True, sug_area = 'roof'): + for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'): if id == 'intern_test2_456': tests +=1 assert tests == 1 - val = i.dev_value(0xffffff04) # check internal error counter + val = i.dev_value(Register.INTERNAL_ERROR) # check internal error counter assert val == 3 @@ -517,7 +518,7 @@ def test_invalid_data_type(InvalidDataSeq): i = InfosG3() i.static_init() # initialize counter - val = i.dev_value(0xffffff03) # check invalid data type counter + val = i.dev_value(Register.INVALID_DATA_TYPE) # check invalid data type counter assert val == 0 @@ -525,6 +526,6 @@ def test_invalid_data_type(InvalidDataSeq): pass assert json.dumps(i.db) == json.dumps({"inverter": {"Product_Name": "Microinv"}}) - val = i.dev_value(0xffffff03) # check invalid data type counter + val = i.dev_value(Register.INVALID_DATA_TYPE) # check invalid data type counter assert val == 1