Files
tsun-gen3-proxy/app/src/gen3/infos_g3.py
Stefan Allius 6da5d2cef6 define __slots__ for class ByteFifo (#202)
* define __slots__ for class ByteFifo

* disable set-timezone action

* set set-timezone to UTC

* try MathRobin/timezone-action@v1.1

* set TZ to "Europe/Berlin"

* define __slots__
2024-10-15 22:16:22 +02:00

204 lines
8.1 KiB
Python

import struct
import logging
from typing import Generator
if __name__ == "app.src.gen3.infos_g3":
from app.src.infos import Infos, Register
else: # pragma: no cover
from infos import Infos, Register
class RegisterMap:
__slots__ = ()
map = {
0x00092ba8: {'reg': Register.COLLECTOR_FW_VERSION},
0x000927c0: {'reg': Register.CHIP_TYPE},
0x00092f90: {'reg': Register.CHIP_MODEL},
0x00094ae8: {'reg': Register.MAC_ADDR},
0x00095a88: {'reg': Register.TRACE_URL},
0x00095aec: {'reg': Register.LOGGER_URL},
0x0000000a: {'reg': Register.PRODUCT_NAME},
0x00000014: {'reg': Register.MANUFACTURER},
0x0000001e: {'reg': Register.VERSION},
0x00000028: {'reg': Register.SERIAL_NUMBER},
0x00000032: {'reg': Register.EQUIPMENT_MODEL},
0x00013880: {'reg': Register.NO_INPUTS},
0xffffff00: {'reg': Register.INVERTER_CNT},
0xffffff01: {'reg': Register.UNKNOWN_SNR},
0xffffff02: {'reg': Register.UNKNOWN_MSG},
0xffffff03: {'reg': Register.INVALID_DATA_TYPE},
0xffffff04: {'reg': Register.INTERNAL_ERROR},
0xffffff05: {'reg': Register.UNKNOWN_CTRL},
0xffffff06: {'reg': Register.OTA_START_MSG},
0xffffff07: {'reg': Register.SW_EXCEPTION},
0xffffff08: {'reg': Register.POLLING_INTERVAL},
0xfffffffe: {'reg': Register.TEST_REG1},
0xffffffff: {'reg': Register.TEST_REG2},
0x00000640: {'reg': Register.OUTPUT_POWER},
0x000005dc: {'reg': Register.RATED_POWER},
0x00000514: {'reg': Register.INVERTER_TEMP},
0x000006a4: {'reg': Register.PV1_VOLTAGE},
0x00000708: {'reg': Register.PV1_CURRENT},
0x0000076c: {'reg': Register.PV1_POWER},
0x000007d0: {'reg': Register.PV2_VOLTAGE},
0x00000834: {'reg': Register.PV2_CURRENT},
0x00000898: {'reg': Register.PV2_POWER},
0x000008fc: {'reg': Register.PV3_VOLTAGE},
0x00000960: {'reg': Register.PV3_CURRENT},
0x000009c4: {'reg': Register.PV3_POWER},
0x00000a28: {'reg': Register.PV4_VOLTAGE},
0x00000a8c: {'reg': Register.PV4_CURRENT},
0x00000af0: {'reg': Register.PV4_POWER},
0x00000c1c: {'reg': Register.PV1_DAILY_GENERATION},
0x00000c80: {'reg': Register.PV1_TOTAL_GENERATION},
0x00000ce4: {'reg': Register.PV2_DAILY_GENERATION},
0x00000d48: {'reg': Register.PV2_TOTAL_GENERATION},
0x00000dac: {'reg': Register.PV3_DAILY_GENERATION},
0x00000e10: {'reg': Register.PV3_TOTAL_GENERATION},
0x00000e74: {'reg': Register.PV4_DAILY_GENERATION},
0x00000ed8: {'reg': Register.PV4_TOTAL_GENERATION},
0x00000b54: {'reg': Register.DAILY_GENERATION},
0x00000bb8: {'reg': Register.TOTAL_GENERATION},
0x000003e8: {'reg': Register.GRID_VOLTAGE},
0x0000044c: {'reg': Register.GRID_CURRENT},
0x000004b0: {'reg': Register.GRID_FREQUENCY},
0x000cfc38: {'reg': Register.CONNECT_COUNT},
0x000c3500: {'reg': Register.SIGNAL_STRENGTH},
0x000c96a8: {'reg': Register.POWER_ON_TIME},
0x000d0020: {'reg': Register.COLLECT_INTERVAL},
0x000cf850: {'reg': Register.DATA_UP_INTERVAL},
0x000c7f38: {'reg': Register.COMMUNICATION_TYPE},
0x00000191: {'reg': Register.EVENT_401},
0x00000192: {'reg': Register.EVENT_402},
0x00000193: {'reg': Register.EVENT_403},
0x00000194: {'reg': Register.EVENT_404},
0x00000195: {'reg': Register.EVENT_405},
0x00000196: {'reg': Register.EVENT_406},
0x00000197: {'reg': Register.EVENT_407},
0x00000198: {'reg': Register.EVENT_408},
0x00000199: {'reg': Register.EVENT_409},
0x0000019a: {'reg': Register.EVENT_410},
0x0000019b: {'reg': Register.EVENT_411},
0x0000019c: {'reg': Register.EVENT_412},
0x0000019d: {'reg': Register.EVENT_413},
0x0000019e: {'reg': Register.EVENT_414},
0x0000019f: {'reg': Register.EVENT_415},
0x000001a0: {'reg': Register.EVENT_416},
0x00000064: {'reg': Register.INVERTER_STATUS},
0x0000125c: {'reg': Register.MAX_DESIGNED_POWER},
0x00003200: {'reg': Register.OUTPUT_COEFFICIENT, 'ratio': 100/1024},
}
class InfosG3(Infos):
__slots__ = ()
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
arguments:
prfx:str ==> MQTT prefix for the home assistant 'stat_t string
snr:str ==> serial number of the inverter, used to build unique
entity strings
sug_area:str ==> suggested area string from the config file'''
# iterate over RegisterMap.map and get the register values
for row in RegisterMap.map.values():
reg = row['reg']
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, 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'''
result = struct.unpack_from('!l', buf, ind)
elms = result[0]
i = 0
ind += 4
while i < elms:
result = struct.unpack_from('!lB', buf, ind)
addr = result[0]
if addr not in RegisterMap.map:
row = None
info_id = -1
else:
row = RegisterMap.map[addr]
info_id = row['reg']
data_type = result[1]
ind += 5
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 == 0x00: # 'Nul' -> end
i = elms # abort the loop
elif data_type == 0x41: # 'A' -> Nop ??
ind += 0
i += 1
continue
elif data_type == 0x42: # 'B' -> byte, int8
result = struct.unpack_from('!B', buf, ind)[0]
ind += 1
elif data_type == 0x49: # 'I' -> int32
result = struct.unpack_from('!l', buf, ind)[0]
ind += 4
elif data_type == 0x53: # 'S' -> short, int16
result = struct.unpack_from('!h', buf, ind)[0]
ind += 2
elif data_type == 0x46: # 'F' -> float32
result = round(struct.unpack_from('!f', buf, ind)[0], 2)
ind += 4
elif data_type == 0x4c: # 'L' -> long, int64
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}"
f" @0x{addr:04x} No:{i}"
" not supported")
return
result = self.__modify_val(row, result)
yield from self.__store_result(addr, result, info_id, node_id)
i += 1
def __modify_val(self, row, result):
if row:
if 'eval' in row:
result = eval(row['eval'])
if 'ratio' in row:
result = round(result * row['ratio'], 2)
return result
def __store_result(self, addr, result, info_id, node_id):
keys, level, unit, must_incr = self._key_obj(info_id)
if keys:
name, update = self.update_db(keys, must_incr, result)
yield keys[0], update
else:
update = False
name = str(f'info-id.0x{addr:x}')
if update:
self.tracer.log(level, f'[{node_id}] GEN3: {name} :'
f' {result}{unit}')