S allius/issue281 (#282)
* accept DCU serial number starting with '410' * determine sensor-list by serial number * adapt unit test for DCU support * send first batterie measurements to home assistant * add test case for sensor-list==3036 * add more registers for batteries * improve error logging (Monitoring SN) * update the add-on repro only for one stage * add configuration for energie storages * add License and Readme file to the add-on * addon: add date and time to dev and debug docker container tag * disable duplicate code check for config.py * cleanup unit test, remove trailing whitespaces * update changelog * fix example config for batteries * cleanup config.jinja template * fix comments * improve help texts
This commit is contained in:
@@ -9,9 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Respect logging.ini file, if LOG_ENV isn't set well [#288](https://github.com/s-allius/tsun-gen3-proxy/issues/288)
|
||||
- Remove trailing apostrophe in the log output [#288](https://github.com/s-allius/tsun-gen3-proxy/issues/288)
|
||||
- Update AddOn base docker image to version 17.1.3 and python3 to 3.12.9-r0
|
||||
- update AddOn base docker image to version 17.2.1
|
||||
- addon: add date and time to dev container version
|
||||
- Update AddOn python3 to 3.12.9-r0
|
||||
- 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)
|
||||
|
||||
|
||||
@@ -78,7 +78,8 @@ class Config():
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': Use(bool), And(Use(str), lambda s: len(s) == 16): {
|
||||
'allow_all': Use(bool),
|
||||
And(Use(str), lambda s: len(s) == 16): {
|
||||
Optional('monitor_sn', default=0): Use(int),
|
||||
Optional('node_id', default=""): And(Use(str),
|
||||
Use(lambda s: s + '/'
|
||||
@@ -93,7 +94,7 @@ class Config():
|
||||
},
|
||||
Optional('modbus_polling', default=True): Use(bool),
|
||||
Optional('suggested_area', default=""): Use(str),
|
||||
Optional('sensor_list', default=0x2b0): Use(int),
|
||||
Optional('sensor_list', default=0): Use(int),
|
||||
Optional('pv1'): {
|
||||
Optional('type'): Use(str),
|
||||
Optional('manufacturer'): Use(str),
|
||||
@@ -119,6 +120,33 @@ class Config():
|
||||
Optional('manufacturer'): Use(str),
|
||||
}
|
||||
}
|
||||
},
|
||||
'batteries': {
|
||||
And(Use(str), lambda s: len(s) == 16): {
|
||||
Optional('monitor_sn', default=0): Use(int),
|
||||
Optional('node_id', default=""): And(Use(str),
|
||||
Use(lambda s: s + '/'
|
||||
if len(s) > 0
|
||||
and s[-1] != '/'
|
||||
else s)),
|
||||
Optional('client_mode'): {
|
||||
'host': Use(str),
|
||||
Optional('port', default=8899):
|
||||
And(Use(int), lambda n: 1024 <= n <= 65535),
|
||||
Optional('forward', default=False): Use(bool),
|
||||
},
|
||||
Optional('modbus_polling', default=True): Use(bool),
|
||||
Optional('suggested_area', default=""): Use(str),
|
||||
Optional('sensor_list', default=0): Use(int),
|
||||
Optional('pv1'): {
|
||||
Optional('type'): Use(str),
|
||||
Optional('manufacturer'): Use(str),
|
||||
},
|
||||
Optional('pv2'): {
|
||||
Optional('type'): Use(str),
|
||||
Optional('manufacturer'): Use(str),
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ignore_extra_keys=True
|
||||
)
|
||||
@@ -178,7 +206,7 @@ here. The default config reader is handled in the Config.init method'''
|
||||
rd_config = reader.get_config()
|
||||
config = cls.act_config.copy()
|
||||
for key in ['tsun', 'solarman', 'mqtt', 'ha', 'inverters',
|
||||
'gen3plus']:
|
||||
'gen3plus', 'batteries']:
|
||||
if key in rd_config:
|
||||
config[key] = config[key] | rd_config[key]
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ class ConfigReadJson(ConfigIfc):
|
||||
def convert_to_obj(self, data):
|
||||
conf = {}
|
||||
for key, val in data.items():
|
||||
if key == 'inverters' and isinstance(val, list):
|
||||
if (key == 'inverters' or key == 'batteries') and \
|
||||
isinstance(val, list):
|
||||
self.convert_inv_arr(conf, key, val)
|
||||
else:
|
||||
self._extend_key(conf, key, val)
|
||||
|
||||
@@ -113,7 +113,7 @@ inverters.allow_all = false # only allow known inverters
|
||||
##
|
||||
## For each GEN3 inverter, the serial number of the inverter must be mapped to an MQTT
|
||||
## definition. To do this, the corresponding configuration block is started with
|
||||
## `[Inverter.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## `[inverters.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## to this inverter. Further inverter-specific parameters (e.g. polling mode) can be set
|
||||
## in the configuration block
|
||||
##
|
||||
@@ -132,7 +132,7 @@ pv2 = {type = 'RSM40-8-395M', manufacturer = 'Risen'} # Optional, PV module de
|
||||
##
|
||||
## For each GEN3PLUS inverter, the serial number of the inverter must be mapped to an MQTT
|
||||
## definition. To do this, the corresponding configuration block is started with
|
||||
## `[Inverter.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## `[inverters.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## to this inverter. Further inverter-specific parameters (e.g. polling mode, client mode)
|
||||
## can be set in the configuration block
|
||||
##
|
||||
@@ -157,6 +157,33 @@ pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module de
|
||||
pv4 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
||||
|
||||
|
||||
##########################################################################################
|
||||
##
|
||||
## For each GEN3PLUS enrgy storage system, the serial number must be mapped to an MQTT
|
||||
## definition. To do this, the corresponding configuration block is started with
|
||||
## `[batteries.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## to this energy storage system. Further device-specific parameters (e.g. polling mode,
|
||||
## client mode) can be set in the configuration block
|
||||
##
|
||||
## The serial numbers of all GEN3PLUS energy storage systems/batteries start with `410`!
|
||||
## Each GEN3PLUS device is supplied with a “Monitoring SN:”. This can be found on a
|
||||
## sticker enclosed with the inverter.
|
||||
##
|
||||
|
||||
[batteries."4100000000000001"]
|
||||
monitor_sn = 3000000000 # The GEN3PLUS "Monitoring SN:"
|
||||
node_id = '' # MQTT replacement for devices serial number
|
||||
suggested_area = '' # suggested installation place for home-assistant
|
||||
modbus_polling = true # Enable optional MODBUS polling
|
||||
|
||||
# if your inverter supports SSL connections you must use the client_mode. Pls, uncomment
|
||||
# the next line and configure the fixed IP of your inverter
|
||||
#client_mode = {host = '192.168.0.1', port = 8899, forward = true}
|
||||
|
||||
pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
||||
pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
||||
|
||||
|
||||
##########################################################################################
|
||||
###
|
||||
### If the proxy mode is configured, commands from TSUN can be sent to the inverter via
|
||||
|
||||
@@ -116,6 +116,33 @@ class RegisterMap:
|
||||
0x4201000c: {'reg': Register.SENSOR_LIST, 'fmt': '<H', 'func': Fmt.hex4}, # noqa: E501
|
||||
0x4201001c: {'reg': Register.POWER_ON_TIME, 'fmt': '<H', 'ratio': 1, 'dep': ProxyMode.SERVER}, # noqa: E501, or packet number
|
||||
0x42010020: {'reg': Register.SERIAL_NUMBER, 'fmt': '!16s'}, # noqa: E501
|
||||
0x42010030: {'reg': Register.BATT_PV1_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV1 voltage
|
||||
0x42010032: {'reg': Register.BATT_PV1_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV1 current
|
||||
0x42010034: {'reg': Register.BATT_PV2_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV2 voltage
|
||||
0x42010036: {'reg': Register.BATT_PV2_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV2 current
|
||||
0x42010038: {'reg': Register.BATT_38, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201003a: {'reg': Register.BATT_3a, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201003c: {'reg': Register.BATT_3c, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201003e: {'reg': Register.BATT_3e, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010040: {'reg': Register.BATT_40, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x42010042: {'reg': Register.BATT_42, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x42010044: {'reg': Register.BATT_SOC, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, state of charge (SOC) in percent
|
||||
0x42010046: {'reg': Register.BATT_46, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010048: {'reg': Register.BATT_48, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201004a: {'reg': Register.BATT_4a, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201004c: {'reg': Register.BATT_4c, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201004e: {'reg': Register.BATT_4e, 'fmt': '!h'}, # noqa: E501
|
||||
|
||||
0x42010066: {'reg': Register.BATT_66, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010068: {'reg': Register.BATT_68, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201006a: {'reg': Register.BATT_6a, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201006c: {'reg': Register.BATT_6c, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201006e: {'reg': Register.BATT_6e, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010070: {'reg': Register.BATT_70, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010072: {'reg': Register.BATT_72, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010074: {'reg': Register.BATT_74, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010076: {'reg': Register.BATT_76, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010078: {'reg': Register.BATT_78, 'fmt': '!h'}, # noqa: E501
|
||||
}
|
||||
|
||||
|
||||
@@ -162,9 +189,15 @@ 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 _, row in chain(RegisterMap.map.items(),
|
||||
RegisterMap.map_02b0.items(),
|
||||
RegisterMap.map_3026.items()):
|
||||
sensor = self.get_db_value(Register.SENSOR_LIST)
|
||||
if "3026" == sensor:
|
||||
items = RegisterMap.map_3026.items()
|
||||
elif "02b0" == sensor:
|
||||
items = RegisterMap.map_02b0.items()
|
||||
else:
|
||||
items = {}
|
||||
|
||||
for _, row in chain(RegisterMap.map.items(), items):
|
||||
info_id = row['reg']
|
||||
if self.__hide_topic(row):
|
||||
res = self.ha_remove(info_id, node_id, snr) # noqa: E501
|
||||
|
||||
@@ -2,6 +2,7 @@ import struct
|
||||
import logging
|
||||
import time
|
||||
import asyncio
|
||||
from itertools import chain
|
||||
from datetime import datetime
|
||||
|
||||
from async_ifc import AsyncIfc
|
||||
@@ -376,12 +377,23 @@ class SolarmanV5(SolarmanBase):
|
||||
self.ifc.fwd_add(build_msg)
|
||||
self.ifc.fwd_add(struct.pack('<BB', 0, 0x15)) # crc & stop
|
||||
|
||||
def __set_config_parms(self, inv: dict):
|
||||
def __set_config_parms(self, inv: dict, serial_no: str):
|
||||
'''init connection with params from the configuration'''
|
||||
self.node_id = inv['node_id']
|
||||
self.sug_area = inv['suggested_area']
|
||||
self.modbus_polling = inv['modbus_polling']
|
||||
self.sensor_list = inv['sensor_list']
|
||||
if 0 == self.sensor_list:
|
||||
snr = serial_no[:3]
|
||||
if '410' == snr:
|
||||
self.sensor_list = 0x3026
|
||||
else:
|
||||
self.sensor_list = 0x02b0
|
||||
self.db.set_db_def_value(Register.SENSOR_LIST,
|
||||
f"{self.sensor_list:04x}")
|
||||
logging.debug(f"Use sensor-list: {self.sensor_list:#04x}"
|
||||
f" for '{serial_no}'")
|
||||
|
||||
if self.mb:
|
||||
self.mb.set_node_id(self.node_id)
|
||||
|
||||
@@ -392,13 +404,14 @@ class SolarmanV5(SolarmanBase):
|
||||
logger.debug(f'SerialNo: {serial_no}')
|
||||
else:
|
||||
inverters = Config.get('inverters')
|
||||
batteries = Config.get('batteries')
|
||||
# logger.debug(f'Inverters: {inverters}')
|
||||
|
||||
for key, inv in inverters.items():
|
||||
for key, inv in chain(inverters.items(), batteries.items()):
|
||||
# logger.debug(f'key: {key} -> {inv}')
|
||||
if (type(inv) is dict and 'monitor_sn' in inv
|
||||
and inv['monitor_sn'] == snr):
|
||||
self.__set_config_parms(inv)
|
||||
self.__set_config_parms(inv, key)
|
||||
self.db.set_pv_module_details(inv)
|
||||
logger.debug(f'SerialNo {serial_no} allowed! area:{self.sug_area}') # noqa: E501
|
||||
|
||||
@@ -411,9 +424,11 @@ class SolarmanV5(SolarmanBase):
|
||||
if 'allow_all' not in inverters or not inverters['allow_all']:
|
||||
self.inc_counter('Unknown_SNR')
|
||||
self.unique_id = None
|
||||
logger.warning(f'ignore message from unknow inverter! (SerialNo: {serial_no})') # noqa: E501
|
||||
logging.error(f"Ignore message from unknow inverter with Monitoring-SN: {serial_no})!\n" # noqa: E501
|
||||
" !!Check the 'monitor_sn' setting in your configuration!!") # noqa: E501
|
||||
return
|
||||
logger.warning(f'SerialNo {serial_no} not known but accepted!')
|
||||
logging.warning(f"Monitoring-SN: {serial_no} not configured but accepted!" # noqa: E501
|
||||
" !!Check the 'monitor_sn' setting in your configuration!!") # noqa: E501
|
||||
|
||||
self.unique_id = serial_no
|
||||
|
||||
@@ -460,11 +475,11 @@ class SolarmanV5(SolarmanBase):
|
||||
def mb_timout_cb(self, exp_cnt):
|
||||
self.mb_timer.start(self.mb_timeout)
|
||||
|
||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x3000, 48, logging.DEBUG)
|
||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x3000, 48, logging.INFO)
|
||||
|
||||
if 1 == (exp_cnt % 30):
|
||||
# logging.info("Regular Modbus Status request")
|
||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x2000, 96, logging.DEBUG)
|
||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x2000, 96, logging.INFO)
|
||||
|
||||
def at_cmd_forbidden(self, cmd: str, connection: str) -> bool:
|
||||
return not cmd.startswith(tuple(self.at_acl[connection]['allow'])) or \
|
||||
|
||||
@@ -121,6 +121,32 @@ class Register(Enum):
|
||||
TS_INPUT = 600
|
||||
TS_GRID = 601
|
||||
TS_TOTAL = 602
|
||||
BATT_PV1_VOLT = 1000
|
||||
BATT_PV1_CUR = 1001
|
||||
BATT_PV2_VOLT = 1002
|
||||
BATT_PV2_CUR = 1003
|
||||
BATT_38 = 1004
|
||||
BATT_3a = 1005
|
||||
BATT_3c = 1006
|
||||
BATT_3e = 1007
|
||||
BATT_40 = 1010
|
||||
BATT_42 = 1011
|
||||
BATT_SOC = 1012
|
||||
BATT_46 = 1013
|
||||
BATT_48 = 1014
|
||||
BATT_4a = 1015
|
||||
BATT_4c = 1016
|
||||
BATT_4e = 1017
|
||||
BATT_66 = 1018
|
||||
BATT_68 = 1019
|
||||
BATT_6a = 1020
|
||||
BATT_6c = 1021
|
||||
BATT_6e = 1022
|
||||
BATT_70 = 1023
|
||||
BATT_72 = 1024
|
||||
BATT_74 = 1025
|
||||
BATT_76 = 1026
|
||||
BATT_78 = 1027
|
||||
VALUE_1 = 9000
|
||||
TEST_REG1 = 10000
|
||||
TEST_REG2 = 10001
|
||||
@@ -266,6 +292,7 @@ class Infos:
|
||||
__info_devs = {
|
||||
'proxy': {'singleton': True, 'name': 'Proxy', 'mf': 'Stefan Allius'}, # noqa: E501
|
||||
'controller': {'via': 'proxy', 'name': 'Controller', 'mdl': Register.CHIP_MODEL, 'mf': Register.CHIP_TYPE, 'sw': Register.COLLECTOR_FW_VERSION, 'mac': Register.MAC_ADDR, 'sn': Register.COLLECTOR_SNR}, # noqa: E501
|
||||
|
||||
'inverter': {'via': 'controller', 'name': 'Micro Inverter', 'mdl': Register.EQUIPMENT_MODEL, 'mf': Register.MANUFACTURER, 'sw': Register.VERSION, 'sn': Register.SERIAL_NUMBER}, # noqa: E501
|
||||
'input_pv1': {'via': 'inverter', 'name': 'Module PV1', 'mdl': Register.PV1_MODEL, 'mf': Register.PV1_MANUFACTURER}, # noqa: E501
|
||||
'input_pv2': {'via': 'inverter', 'name': 'Module PV2', 'mdl': Register.PV2_MODEL, 'mf': Register.PV2_MANUFACTURER, 'dep': {'reg': Register.NO_INPUTS, 'gte': 2}}, # noqa: E501
|
||||
@@ -273,6 +300,10 @@ class Infos:
|
||||
'input_pv4': {'via': 'inverter', 'name': 'Module PV4', 'mdl': Register.PV4_MODEL, 'mf': Register.PV4_MANUFACTURER, 'dep': {'reg': Register.NO_INPUTS, 'gte': 4}}, # noqa: E501
|
||||
'input_pv5': {'via': 'inverter', 'name': 'Module PV5', 'mdl': Register.PV5_MODEL, 'mf': Register.PV5_MANUFACTURER, 'dep': {'reg': Register.NO_INPUTS, 'gte': 5}}, # noqa: E501
|
||||
'input_pv6': {'via': 'inverter', 'name': 'Module PV6', 'mdl': Register.PV6_MODEL, 'mf': Register.PV6_MANUFACTURER, 'dep': {'reg': Register.NO_INPUTS, 'gte': 6}}, # noqa: E501
|
||||
|
||||
'batterie': {'via': 'controller', 'name': 'Batterie', 'mdl': Register.EQUIPMENT_MODEL, 'mf': Register.MANUFACTURER, 'sw': Register.VERSION, 'sn': Register.SERIAL_NUMBER}, # noqa: E501
|
||||
'bat_inp_pv1': {'via': 'batterie', 'name': 'Module PV1', 'mdl': Register.PV1_MODEL, 'mf': Register.PV1_MANUFACTURER}, # noqa: E501
|
||||
'bat_inp_pv2': {'via': 'batterie', 'name': 'Module PV2', 'mdl': Register.PV2_MODEL, 'mf': Register.PV2_MANUFACTURER}, # 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
|
||||
@@ -518,15 +549,15 @@ class Infos:
|
||||
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': FMT_FLOAT, 'name': TOTAL_GEN, 'icon': SOLAR_POWER, 'must_incr': True}}, # noqa: E501
|
||||
|
||||
# controller:
|
||||
Register.SIGNAL_STRENGTH: {'name': ['controller', 'Signal_Strength'], 'level': logging.DEBUG, 'unit': '%', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'signal_', 'fmt': FMT_INT, 'name': 'Signal Strength', 'icon': 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': FMT_INT, 'name': 'Power on Time', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.COLLECT_INTERVAL: {'name': ['controller', 'Collect_Interval'], 'level': logging.DEBUG, 'unit': 'min', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_collect_intval_', 'fmt': '| string + " min"', 'name': 'Data Collect Interval', 'icon': 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': FMT_INT, 'name': 'Connect Count', 'icon': 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': 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': FMT_STRING_SEC, 'name': 'Data Up Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.HEARTBEAT_INTERVAL: {'name': ['controller', 'Heartbeat_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'heartbeat_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Heartbeat Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.IP_ADDRESS: {'name': ['controller', 'IP_Address'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'ip_address_', 'fmt': '| string', 'name': 'IP Address', 'icon': WIFI, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.POLLING_INTERVAL: {'name': ['controller', 'Polling_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'polling_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Polling Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.SIGNAL_STRENGTH: {'name': ['controller', 'Signal_Strength'], 'level': logging.INFO, 'unit': '%', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'signal_', 'fmt': FMT_INT, 'name': 'Signal Strength', 'icon': WIFI}}, # noqa: E501
|
||||
Register.POWER_ON_TIME: {'name': ['controller', 'Power_On_Time'], 'level': logging.INFO, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id': 'power_on_time_', 'fmt': FMT_INT, 'name': 'Power on Time', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.COLLECT_INTERVAL: {'name': ['controller', 'Collect_Interval'], 'level': logging.INFO, 'unit': 'min', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_collect_intval_', 'fmt': '| string + " min"', 'name': 'Data Collect Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.CONNECT_COUNT: {'name': ['controller', 'Connect_Count'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'connect_count_', 'fmt': FMT_INT, 'name': 'Connect Count', 'icon': COUNTER, 'comp': 'sensor', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.COMMUNICATION_TYPE: {'name': ['controller', 'Communication_Type'], 'level': logging.INFO, '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': WIFI}}, # noqa: E501
|
||||
Register.DATA_UP_INTERVAL: {'name': ['controller', 'Data_Up_Interval'], 'level': logging.INFO, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_up_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Data Up Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.HEARTBEAT_INTERVAL: {'name': ['controller', 'Heartbeat_Interval'], 'level': logging.INFO, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'heartbeat_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Heartbeat Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.IP_ADDRESS: {'name': ['controller', 'IP_Address'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'ip_address_', 'fmt': '| string', 'name': 'IP Address', 'icon': WIFI, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.POLLING_INTERVAL: {'name': ['controller', 'Polling_Interval'], 'level': logging.INFO, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'polling_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Polling Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.SENSOR_LIST: {'name': ['controller', 'Sensor_List'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
|
||||
Register.SSID: {'name': ['controller', 'WiFi_SSID'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
|
||||
|
||||
@@ -536,6 +567,33 @@ class Infos:
|
||||
Register.PROD_COMPL_TYPE: {'name': ['other', 'Prod_Compliance_Type'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
|
||||
Register.INV_UNKNOWN_1: {'name': ['inv_unknown', 'Unknown_1'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
|
||||
|
||||
Register.BATT_PV1_VOLT: {'name': ['batterie', 'pv1', 'Voltage'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'bat_inp_pv1', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv1_', 'val_tpl': "{{ (value_json['pv1']['Voltage'] | float)}}", 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_PV1_CUR: {'name': ['batterie', 'pv1', 'Current'], 'level': logging.INFO, 'unit': 'A', 'ha': {'dev': 'bat_inp_pv1', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv1_', 'val_tpl': "{{ (value_json['pv1']['Current'] | float)}}", 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_PV2_VOLT: {'name': ['batterie', 'pv2', 'Voltage'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'bat_inp_pv2', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv2_', 'val_tpl': "{{ (value_json['pv2']['Voltage'] | float)}}", 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_PV2_CUR: {'name': ['batterie', 'pv2', 'Current'], 'level': logging.INFO, 'unit': 'A', 'ha': {'dev': 'bat_inp_pv2', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv2_', 'val_tpl': "{{ (value_json['pv2']['Current'] | float)}}", 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_38: {'name': ['batterie', 'Reg_38'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_38_', 'fmt': FMT_FLOAT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_3a: {'name': ['batterie', 'Reg_3a'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_3a_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_3c: {'name': ['batterie', 'Reg_3c'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_3c_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_3e: {'name': ['batterie', 'Reg_3e'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_3e_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_40: {'name': ['batterie', 'Reg_40'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_40_', 'fmt': FMT_FLOAT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_42: {'name': ['batterie', 'Reg_42'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_42_', 'fmt': FMT_FLOAT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_SOC: {'name': ['batterie', 'SOC'], 'level': logging.INFO, 'unit': '%', 'ha': {'dev': 'batterie', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'soc_', 'fmt': FMT_FLOAT, 'name': 'State of Charge', 'icon': 'mdi:battery-90'}}, # noqa: E501
|
||||
# Register.BATT_46: {'name': ['batterie', 'Reg_46'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_46_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
# Register.BATT_48: {'name': ['batterie', 'Reg_48'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_48_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
# Register.BATT_4a: {'name': ['batterie', 'Reg_4a'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_4a_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
# Register.BATT_4c: {'name': ['batterie', 'Reg_4c'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_4c_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
# Register.BATT_4e: {'name': ['batterie', 'Reg_4e'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_4e_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
|
||||
Register.BATT_66: {'name': ['batterie', 'Reg_66'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_66_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_68: {'name': ['batterie', 'Reg_68'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_68_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_6a: {'name': ['batterie', 'Reg_6a'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_6a_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_6c: {'name': ['batterie', 'Reg_6c'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_6c_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_6e: {'name': ['batterie', 'Reg_6e'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_6e_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_70: {'name': ['batterie', 'Reg_70'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_70_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_72: {'name': ['batterie', 'Reg_72'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_72_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_74: {'name': ['batterie', 'Reg_74'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_74_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_76: {'name': ['batterie', 'Reg_76'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_76_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_78: {'name': ['batterie', 'Reg_78'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_78_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
}
|
||||
|
||||
@property
|
||||
|
||||
@@ -151,6 +151,8 @@ class InverterBase(InverterIfc, Proxy):
|
||||
# home assistant has changed the status back to online
|
||||
try:
|
||||
if (('inverter' in stream.new_data and stream.new_data['inverter'])
|
||||
or ('batterie' in stream.new_data and
|
||||
stream.new_data['batterie'])
|
||||
or ('collector' in stream.new_data and
|
||||
stream.new_data['collector'])
|
||||
or self.mqtt.ha_restarts != self.__ha_restarts):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import traceback
|
||||
import asyncio
|
||||
from itertools import chain
|
||||
|
||||
from cnf.config import Config
|
||||
from gen3plus.inverter_g3p import InverterG3P
|
||||
@@ -42,14 +43,15 @@ class ModbusTcp():
|
||||
self.tim_restart = tim_restart
|
||||
|
||||
inverters = Config.get('inverters')
|
||||
batteries = Config.get('batteries')
|
||||
# logging.info(f'Inverters: {inverters}')
|
||||
|
||||
for inv in inverters.values():
|
||||
for _, inv in chain(inverters.items(), batteries.items()):
|
||||
if (type(inv) is dict
|
||||
and 'monitor_sn' in inv
|
||||
and 'client_mode' in inv):
|
||||
client = inv['client_mode']
|
||||
logger.info(f"'client_mode' for snr: {inv['monitor_sn']} host: {client['host']}:{client['port']}, forward: {client['forward']}") # noqa: E501
|
||||
logger.info(f"'client_mode' for Monitoring-SN: {inv['monitor_sn']} host: {client['host']}:{client['port']}, forward: {client['forward']}") # noqa: E501
|
||||
loop.create_task(self.modbus_loop(client['host'],
|
||||
client['port'],
|
||||
inv['monitor_sn'],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
from itertools import chain
|
||||
|
||||
from cnf.config import Config
|
||||
from mqtt import Mqtt
|
||||
@@ -56,8 +57,9 @@ class Proxy():
|
||||
# reset at midnight when you restart the proxy just before
|
||||
# midnight!
|
||||
inverters = Config.get('inverters')
|
||||
batteries = Config.get('batteries')
|
||||
# logger.debug(f'Proxys: {inverters}')
|
||||
for inv in inverters.values():
|
||||
for _, inv in chain(inverters.items(), batteries.items()):
|
||||
if (type(inv) is dict):
|
||||
node_id = inv['node_id']
|
||||
cls.db_stat.reg_clr_at_midnight(f'{cls.entity_prfx}{node_id}',
|
||||
|
||||
@@ -76,7 +76,7 @@ def ConfigDefault():
|
||||
'type': 'RSM40-8-395M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-395M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
@@ -91,8 +91,21 @@ def ConfigDefault():
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
}
|
||||
},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'suggested_area': '',
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +153,18 @@ def ConfigComplete():
|
||||
'type': 'type4'},
|
||||
'suggested_area': 'Garage2',
|
||||
'sensor_list': 688}
|
||||
},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'suggested_area': 'Garage3',
|
||||
'node_id': 'Bat-Garage3/',
|
||||
'pv1': {'manufacturer': 'man5',
|
||||
'type': 'type5'},
|
||||
'pv2': {'manufacturer': 'man6',
|
||||
'type': 'type6'},
|
||||
'sensor_list': 12326}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +172,19 @@ def test_default_config():
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
validated = Config.def_config
|
||||
assert validated == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 10000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
'suggested_area': ''
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': False,
|
||||
'R170000000000001': {
|
||||
@@ -158,7 +196,7 @@ def test_default_config():
|
||||
'modbus_polling': False,
|
||||
'monitor_sn': 0,
|
||||
'suggested_area': '',
|
||||
'sensor_list': 688},
|
||||
'sensor_list': 0},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 2000000000,
|
||||
@@ -172,7 +210,7 @@ def test_default_config():
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'suggested_area': '',
|
||||
'sensor_list': 688}}}
|
||||
'sensor_list': 0}}}
|
||||
|
||||
def test_full_config(ConfigComplete):
|
||||
cnf = {'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005},
|
||||
@@ -181,9 +219,14 @@ def test_full_config(ConfigComplete):
|
||||
'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 10000},
|
||||
'mqtt': {'host': 'mqtt', 'port': 1883, 'user': '', 'passwd': ''},
|
||||
'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {'modbus_polling': True, 'monitor_sn': 3000000000, 'node_id': 'Bat-Garage3/', 'sensor_list': 0x3026, 'suggested_area': 'Garage3', 'pv1': {'type': 'type5', 'manufacturer': 'man5'}, 'pv2': {'type': 'type6', 'manufacturer': 'man6'}}
|
||||
},
|
||||
'inverters': {'allow_all': False,
|
||||
'R170000000000001': {'modbus_polling': False, 'node_id': 'PV-Garage/', 'sensor_list': 0x02B0, 'suggested_area': 'Garage', 'pv1': {'type': 'type1', 'manufacturer': 'man1'}, 'pv2': {'type': 'type2', 'manufacturer': 'man2'}},
|
||||
'Y170000000000001': {'modbus_polling': True, 'monitor_sn': 2000000000, 'node_id': 'PV-Garage2/', 'sensor_list': 0x02B0, 'suggested_area': 'Garage2', 'pv1': {'type': 'type1', 'manufacturer': 'man1'}, 'pv2': {'type': 'type2', 'manufacturer': 'man2'}, 'pv3': {'type': 'type3', 'manufacturer': 'man3'}, 'pv4': {'type': 'type4', 'manufacturer': 'man4'}}}}
|
||||
'Y170000000000001': {'modbus_polling': True, 'monitor_sn': 2000000000, 'node_id': 'PV-Garage2/', 'sensor_list': 0x02B0, 'suggested_area': 'Garage2', 'pv1': {'type': 'type1', 'manufacturer': 'man1'}, 'pv2': {'type': 'type2', 'manufacturer': 'man2'}, 'pv3': {'type': 'type3', 'manufacturer': 'man3'}, 'pv4': {'type': 'type4', 'manufacturer': 'man4'}}
|
||||
}
|
||||
}
|
||||
try:
|
||||
validated = Config.conf_schema.validate(cnf)
|
||||
except Exception:
|
||||
@@ -240,6 +283,19 @@ def test_read_cnf1():
|
||||
assert err == None
|
||||
cnf = Config.get()
|
||||
assert cnf == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': False, 'host': 'iot.talent-monitoring.com', 'port': 10000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
'suggested_area': ''
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': False,
|
||||
'R170000000000001': {
|
||||
@@ -251,7 +307,7 @@ def test_read_cnf1():
|
||||
'type': 'RSM40-8-395M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-395M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
@@ -266,7 +322,7 @@ def test_read_cnf1():
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,6 +343,19 @@ def test_read_cnf2():
|
||||
assert err == None
|
||||
cnf = Config.get()
|
||||
assert cnf == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 10000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
'suggested_area': ''
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': False,
|
||||
'R170000000000001': {
|
||||
@@ -298,7 +367,7 @@ def test_read_cnf2():
|
||||
'type': 'RSM40-8-395M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-395M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
@@ -313,7 +382,7 @@ def test_read_cnf2():
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,6 +411,19 @@ def test_read_cnf4():
|
||||
assert err == None
|
||||
cnf = Config.get()
|
||||
assert cnf == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 5000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
'suggested_area': ''
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': False,
|
||||
'R170000000000001': {
|
||||
@@ -353,7 +435,7 @@ def test_read_cnf4():
|
||||
'type': 'RSM40-8-395M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-395M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
@@ -368,7 +450,7 @@ def test_read_cnf4():
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -382,6 +382,20 @@ def test_full_config(ConfigComplete):
|
||||
"sensor_list": 688
|
||||
}
|
||||
],
|
||||
"batteries": [
|
||||
{
|
||||
"serial": "4100000000000001",
|
||||
"modbus_polling": true,
|
||||
"monitor_sn": 3000000000,
|
||||
"node_id": "Bat-Garage3",
|
||||
"suggested_area": "Garage3",
|
||||
"pv1.manufacturer": "man5",
|
||||
"pv1.type": "type5",
|
||||
"pv2.manufacturer": "man6",
|
||||
"pv2.type": "type6",
|
||||
"sensor_list": 12326
|
||||
}
|
||||
],
|
||||
"tsun.enabled": true,
|
||||
"solarman.enabled": true,
|
||||
"inverters.allow_all": false,
|
||||
|
||||
@@ -138,6 +138,7 @@ def test_build_4210(inverter_data: bytes):
|
||||
def test_build_ha_conf1():
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.static_init() # initialize counter
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "02b0")
|
||||
|
||||
tests = 0
|
||||
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123'):
|
||||
@@ -212,6 +213,7 @@ def test_build_ha_conf2():
|
||||
def test_build_ha_conf3():
|
||||
i = InfosG3P(client_mode=True)
|
||||
i.static_init() # initialize counter
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "02b0")
|
||||
|
||||
tests = 0
|
||||
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123'):
|
||||
@@ -283,6 +285,35 @@ def test_build_ha_conf4():
|
||||
|
||||
assert tests==1
|
||||
|
||||
def test_build_ha_conf5():
|
||||
i = InfosG3P(client_mode=True)
|
||||
i.static_init() # initialize counter
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "3026")
|
||||
|
||||
tests = 0
|
||||
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 False
|
||||
elif id == 'daily_gen_123':
|
||||
assert False
|
||||
elif id == 'power_pv1_123':
|
||||
assert False
|
||||
elif id == 'power_pv2_123':
|
||||
assert False
|
||||
elif id == 'power_pv3_123':
|
||||
assert False
|
||||
elif id == 'power_pv4_123':
|
||||
assert False
|
||||
elif id == 'signal_123':
|
||||
assert comp == 'sensor'
|
||||
assert d_json == json.dumps({})
|
||||
tests +=1
|
||||
elif id == 'inv_count_456':
|
||||
assert False
|
||||
|
||||
assert tests==1
|
||||
|
||||
def test_exception_and_calc(inverter_data: bytes):
|
||||
|
||||
# patch table to convert temperature from °F to °C
|
||||
|
||||
@@ -130,6 +130,12 @@ def get_sn() -> bytes:
|
||||
def get_sn_int() -> int:
|
||||
return 2070233889
|
||||
|
||||
def get_dcu_sn() -> bytes:
|
||||
return b'\x20\x43\x65\x7b'
|
||||
|
||||
def get_dcu_sn_int() -> int:
|
||||
return 2070233888
|
||||
|
||||
def get_inv_no() -> bytes:
|
||||
return b'T170000000000001'
|
||||
|
||||
@@ -672,6 +678,65 @@ def msg_unknown_cmd_rsp(): # 0x1510
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_dev_ind_msg(): # 0x4110
|
||||
msg = b'\xa5\x3a\x01\x10\x41\x91\x01' +get_dcu_sn() +b'\x02\xc6\xde\x2d\x32'
|
||||
msg += b'\x27\x00\x00\x00\x00\x00\x00\x00\x05\x3c\x78\x01\x5c\x01\x4c\x53'
|
||||
msg += b'\x57\x35\x5f\x30\x31\x5f\x33\x30\x32\x36\x5f\x4e\x53\x5f\x30\x35'
|
||||
msg += b'\x5f\x30\x31\x2e\x30\x30\x2e\x30\x30\x2e\x30\x30\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\xd4\x27\x87\x12\xad\xc0\x31\x39\x32\x2e'
|
||||
msg += b'\x31\x36\x38\x2e\x39\x2e\x31\x34\x00\x00\x00\x00\x01\x00\x01\x26'
|
||||
msg += b'\x30\x0f\x00\xff\x56\x31\x2e\x31\x2e\x30\x30\x2e\x30\x42\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x7a\x75\x68\x61\x75\x73\x65\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x08\x01\x01\x01\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x01'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_dev_rsp_msg(): # 0x1110
|
||||
msg = b'\xa5\x0a\x00\x10\x11\x92\x01' +get_dcu_sn() +b'\x02\x01'
|
||||
msg += total()
|
||||
msg += hb()
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_data_ind_msg(): # 0x4210
|
||||
msg = b'\xa5\x6f\x00\x10\x42\x92\x02' +get_dcu_sn() +b'\x01\x26\x30\xc7\xde'
|
||||
msg += b'\x2d\x32\x28\x00\x00\x00\x84\x17\x79\x35\x01\x00\x4c\x12\x00\x00'
|
||||
msg += b'\x34\x31\x30\x31\x32\x34\x30\x37\x30\x31\x34\x39\x30\x33\x31\x34'
|
||||
msg += b'\x0d\x3a\x00\x00\x0d\x2c\x00\x00\x00\x00\x08\x20\x00\x00\x00\x00'
|
||||
msg += b'\x14\x0e\xff\xfe\x03\xe8\x0c\x89\x0c\x89\x0c\x89\x0c\x8a\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x8a\x0c\x89\x0c\x89\x0c\x8a\x0c\x8a\x0c\x89\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x89\x0c\x88\x00\x0f\x00\x0f\x00\x0f\x00\x0e\x00\x00'
|
||||
msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_data_rsp_msg(): # 0x1210
|
||||
msg = b'\xa5\x0a\x00\x10\x12\x93\x02' +get_dcu_sn() +b'\x01\x01'
|
||||
msg += total()
|
||||
msg += hb()
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def config_tsun_allow_all():
|
||||
Config.act_config = {'solarman':{'enabled': True}, 'inverters':{'allow_all':True}}
|
||||
@@ -682,7 +747,11 @@ def config_no_tsun_inv1():
|
||||
|
||||
@pytest.fixture
|
||||
def config_tsun_inv1():
|
||||
Config.act_config = {'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 688}}}
|
||||
Config.act_config = {'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 0}}}
|
||||
|
||||
@pytest.fixture
|
||||
def config_tsun_dcu1():
|
||||
Config.act_config = {'solarman':{'enabled': True},'batteries':{'4100000000000001':{'monitor_sn': 2070233888, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 0}}}
|
||||
|
||||
def test_read_message(device_ind_msg):
|
||||
Config.act_config = {'solarman':{'enabled': True}}
|
||||
@@ -963,6 +1032,34 @@ def test_read_two_messages3(config_tsun_allow_all, device_ind_msg2, device_rsp_m
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
m.close()
|
||||
|
||||
def test_read_two_messages4(config_tsun_dcu1, dcu_dev_ind_msg, dcu_dev_rsp_msg, dcu_data_ind_msg, dcu_data_rsp_msg):
|
||||
_ = config_tsun_dcu1
|
||||
m = MemoryStream(dcu_dev_ind_msg, (0,))
|
||||
m.append_msg(dcu_data_ind_msg)
|
||||
assert 0 == m.sensor_list
|
||||
m._init_new_client_conn()
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 2
|
||||
assert m.header_len==11
|
||||
assert m.snr == 2070233888
|
||||
assert m.unique_id == '2070233888'
|
||||
assert m.msg_recvd[0]['control']==0x4110
|
||||
assert m.msg_recvd[0]['seq']=='01:92'
|
||||
assert m.msg_recvd[0]['data_len']==314
|
||||
assert m.msg_recvd[1]['control']==0x4210
|
||||
assert m.msg_recvd[1]['seq']=='02:93'
|
||||
assert m.msg_recvd[1]['data_len']==111
|
||||
assert '3026' == m.db.get_db_value(Register.SENSOR_LIST, None)
|
||||
assert 0x3026 == m.sensor_list
|
||||
assert m.ifc.fwd_fifo.get()==dcu_dev_ind_msg+dcu_data_ind_msg
|
||||
assert m.ifc.tx_fifo.get()==dcu_dev_rsp_msg+dcu_data_rsp_msg
|
||||
|
||||
m._init_new_client_conn()
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
m.close()
|
||||
|
||||
def test_unkown_frame_code(config_tsun_inv1, inverter_ind_msg_81, inverter_rsp_msg_81):
|
||||
_ = config_tsun_inv1
|
||||
m = MemoryStream(inverter_ind_msg_81, (0,))
|
||||
|
||||
@@ -8,19 +8,65 @@ JINJA = jinja2
|
||||
IMAGE = tsun-gen3-addon
|
||||
|
||||
|
||||
# Folders
|
||||
# Source folders for building the local add-on
|
||||
SRC=../app
|
||||
SRC_PROXY=$(SRC)/src
|
||||
CNF_PROXY=$(SRC)/config
|
||||
|
||||
# Target folders for building the local add-on and the docker container
|
||||
ADDON_PATH = ha_addon
|
||||
DST=$(ADDON_PATH)/rootfs
|
||||
DST_PROXY=$(DST)/home/proxy
|
||||
|
||||
# base director of the add-on repro for installing the add-on git repros
|
||||
INST_BASE=../../ha-addons/ha-addons
|
||||
|
||||
# Template folder for build the config.yaml variants
|
||||
TEMPL=templates
|
||||
|
||||
# help variable STAGE determine the target to build
|
||||
dev: STAGE=dev
|
||||
debug : STAGE=debug
|
||||
rc : STAGE=rc
|
||||
rel : STAGE=rel
|
||||
|
||||
|
||||
export BUILD_DATE := ${shell date -Iminutes}
|
||||
BUILD_ID := ${shell date +'%y%m%d%H%M'}
|
||||
VERSION := $(shell cat $(SRC)/.version)
|
||||
export MAJOR := $(shell echo $(VERSION) | cut -f1 -d.)
|
||||
|
||||
PUBLIC_URL := $(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f1 -d/)
|
||||
PUBLIC_USER :=$(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f2 -d/)
|
||||
|
||||
|
||||
dev debug: local_add_on
|
||||
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PRIVAT_CONTAINER_REGISTRY)$(IMAGE)
|
||||
export VERSION=$(VERSION)-$@-$(BUILD_ID) && \
|
||||
export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \
|
||||
docker buildx bake -f docker-bake.hcl $@
|
||||
|
||||
rc rel: local_add_on
|
||||
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PUBLIC_CONTAINER_REGISTRY)$(IMAGE)
|
||||
@echo login at $(PUBLIC_URL) as $(PUBLIC_USER)
|
||||
@DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)"
|
||||
export VERSION=$(VERSION)-$@ && \
|
||||
export IMAGE=$(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) && \
|
||||
docker buildx bake -f docker-bake.hcl $@
|
||||
|
||||
clean:
|
||||
rm -r -f $(DST_PROXY)
|
||||
rm -f $(DST)/requirements.txt
|
||||
rm -f $(ADDON_PATH)/config.yaml
|
||||
rm -f $(TEMPL)/.data.json
|
||||
docker logout ghcr.io
|
||||
|
||||
#############
|
||||
# Build the local add-on with a rootfs and config.yaml
|
||||
# The rootfs is needed to build the add-on Docker container
|
||||
#
|
||||
local_add_on: rootfs $(ADDON_PATH)/config.yaml
|
||||
|
||||
# collect source files
|
||||
SRC_FILES := $(wildcard $(SRC_PROXY)/*.py)\
|
||||
$(wildcard $(SRC_PROXY)/*.ini)\
|
||||
@@ -34,50 +80,8 @@ CNF_FILES := $(wildcard $(CNF_PROXY)/*.toml)
|
||||
TARGET_FILES = $(SRC_FILES:$(SRC_PROXY)/%=$(DST_PROXY)/%)
|
||||
CONFIG_FILES = $(CNF_FILES:$(CNF_PROXY)/%=$(DST_PROXY)/%)
|
||||
|
||||
export BUILD_DATE := ${shell date -Iminutes}
|
||||
VERSION := $(shell cat $(SRC)/.version)
|
||||
export MAJOR := $(shell echo $(VERSION) | cut -f1 -d.)
|
||||
|
||||
PUBLIC_URL := $(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f1 -d/)
|
||||
PUBLIC_USER :=$(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f2 -d/)
|
||||
|
||||
|
||||
dev debug: build
|
||||
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PRIVAT_CONTAINER_REGISTRY)$(IMAGE)
|
||||
export VERSION=$(VERSION)-$@ && \
|
||||
export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \
|
||||
docker buildx bake -f docker-bake.hcl $@
|
||||
|
||||
rc rel: build
|
||||
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PUBLIC_CONTAINER_REGISTRY)$(IMAGE)
|
||||
@echo login at $(PUBLIC_URL) as $(PUBLIC_USER)
|
||||
@DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)"
|
||||
export VERSION=$(VERSION)-$@ && \
|
||||
export IMAGE=$(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) && \
|
||||
docker buildx bake -f docker-bake.hcl $@
|
||||
|
||||
|
||||
build: rootfs $(ADDON_PATH)/config.yaml repro
|
||||
|
||||
clean:
|
||||
rm -r -f $(DST_PROXY)
|
||||
rm -f $(DST)/requirements.txt
|
||||
rm -f $(ADDON_PATH)/config.yaml
|
||||
rm -f $(TEMPL)/.data.json
|
||||
docker logout ghcr.io
|
||||
|
||||
#
|
||||
# Build rootfs and config.yaml as local add-on
|
||||
# The rootfs is needed to build the add-on Dockercontainers
|
||||
#
|
||||
|
||||
rootfs: $(TARGET_FILES) $(CONFIG_FILES) $(DST)/requirements.txt
|
||||
|
||||
STAGE=dev
|
||||
debug : STAGE=debug
|
||||
rc : STAGE=rc
|
||||
rel : STAGE=rel
|
||||
|
||||
$(CONFIG_FILES): $(DST_PROXY)/% : $(CNF_PROXY)/%
|
||||
@echo Copy $< to $@
|
||||
@mkdir -p $(@D)
|
||||
@@ -93,21 +97,22 @@ $(DST)/requirements.txt : $(SRC)/requirements.txt
|
||||
@cp $< $@
|
||||
|
||||
$(ADDON_PATH)/%.yaml: $(TEMPL)/%.jinja $(TEMPL)/.data.json
|
||||
$(JINJA) --strict -D AppVersion=$(VERSION) --format=json $^ -o $@
|
||||
$(JINJA) --strict -D AppVersion=$(VERSION) -D BuildID=$(BUILD_ID) --format=json $^ -o $@
|
||||
|
||||
# build a common data.json file from STAGE depending source files
|
||||
# don't touch the destination if the checksum of src and dst is equal
|
||||
$(TEMPL)/.data.json: FORCE
|
||||
rsync --checksum $(TEMPL)/$(STAGE)_data.json $@
|
||||
|
||||
FORCE : ;
|
||||
|
||||
|
||||
#
|
||||
# Build repository for Home Assistant Add-On
|
||||
#############
|
||||
# Build repository for Home Assistant Add-Onx
|
||||
#
|
||||
|
||||
INST=$(INST_BASE)/ha_addon_dev
|
||||
repro_files = DOCS.md icon.png logo.png translations/de.yaml translations/en.yaml rootfs/run.sh
|
||||
repro_root = CHANGELOG.md
|
||||
repro_root = CHANGELOG.md LICENSE.md
|
||||
repro_templates = config.yaml
|
||||
repro_subdirs = translations rootfs
|
||||
repro_vers = debug dev rc rel
|
||||
@@ -117,16 +122,42 @@ repro_root_files := $(foreach dir,$(repro_vers), $(foreach file,$(repro_root),$(
|
||||
repro_all_templates := $(foreach dir,$(repro_vers), $(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_$(dir)/$(file)))
|
||||
repro_all_subdirs := $(foreach dir,$(repro_vers), $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_$(dir)/$(file)))
|
||||
|
||||
repro: $(repro_all_subdirs) $(repro_all_templates) $(repro_all_files) $(repro_root_files)
|
||||
debug: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_debug/$(file)) \
|
||||
$(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_debug/$(file)) \
|
||||
$(foreach file,$(repro_files),$(INST_BASE)/ha_addon_debug/$(file)) \
|
||||
$(foreach file,$(repro_root),$(INST_BASE)/ha_addon_debug/$(file))
|
||||
|
||||
dev: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_dev/$(file)) \
|
||||
$(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_dev/$(file)) \
|
||||
$(foreach file,$(repro_files),$(INST_BASE)/ha_addon_dev/$(file)) \
|
||||
$(foreach file,$(repro_root),$(INST_BASE)/ha_addon_dev/$(file))
|
||||
|
||||
rc: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_rc/$(file)) \
|
||||
$(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_rc/$(file)) \
|
||||
$(foreach file,$(repro_files),$(INST_BASE)/ha_addon_rc/$(file)) \
|
||||
$(foreach file,$(repro_root),$(INST_BASE)/ha_addon_rc/$(file))
|
||||
|
||||
rel: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_rel/$(file)) \
|
||||
$(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_rel/$(file)) \
|
||||
$(foreach file,$(repro_files),$(INST_BASE)/ha_addon_rel/$(file)) \
|
||||
$(foreach file,$(repro_root),$(INST_BASE)/ha_addon_rel/$(file))
|
||||
|
||||
$(repro_all_subdirs) :
|
||||
mkdir -p $@
|
||||
|
||||
$(repro_all_templates) : $(INST_BASE)/ha_addon_%/config.yaml: $(TEMPL)/config.jinja $(TEMPL)/%_data.json $(SRC)/.version
|
||||
$(JINJA) --strict -D AppVersion=$(VERSION)-$* $< $(filter %.json,$^) -o $@
|
||||
$(repro_all_templates) : $(INST_BASE)/ha_addon_%/config.yaml: $(TEMPL)/config.jinja $(TEMPL)/%_data.json $(SRC)/.version FORCE
|
||||
$(JINJA) --strict -D AppVersion=$(VERSION)-$* -D BuildID=$(BUILD_ID) $< $(filter %.json,$^) -o $@
|
||||
|
||||
$(repro_root_files) : %/CHANGELOG.md : ../CHANGELOG.md
|
||||
|
||||
$(filter $(INST_BASE)/ha_addon_debug/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_debug/% : ../%
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_dev/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_dev/% : ../%
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_rc/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_rc/% : ../%
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_rel/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_rel/% : ../%
|
||||
cp $< $@
|
||||
|
||||
|
||||
$(filter $(INST_BASE)/ha_addon_debug/%,$(repro_all_files)) : $(INST_BASE)/ha_addon_debug/% : ha_addon/%
|
||||
cp $< $@
|
||||
@@ -136,5 +167,3 @@ $(filter $(INST_BASE)/ha_addon_rc/%,$(repro_all_files)) : $(INST_BASE)/ha_addon_
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_rel/%,$(repro_all_files)) : $(INST_BASE)/ha_addon_rel/% : ha_addon/%
|
||||
cp $< $@
|
||||
|
||||
|
||||
|
||||
@@ -74,12 +74,12 @@ target "_prod" {
|
||||
}
|
||||
target "debug" {
|
||||
inherits = ["_common", "_debug"]
|
||||
tags = ["${IMAGE}:debug"]
|
||||
tags = ["${IMAGE}:debug", "${IMAGE}:${VERSION}"]
|
||||
}
|
||||
|
||||
target "dev" {
|
||||
inherits = ["_common"]
|
||||
tags = ["${IMAGE}:dev"]
|
||||
tags = ["${IMAGE}:dev", "${IMAGE}:${VERSION}"]
|
||||
}
|
||||
|
||||
target "preview" {
|
||||
|
||||
@@ -68,8 +68,8 @@ Example add-on configuration for GEN3PLUS inverters:
|
||||
inverters:
|
||||
- serial: Y17000000000000
|
||||
monitor_sn: 2000000000
|
||||
node_id: PV-Garage
|
||||
suggested_area: Garage
|
||||
node_id: inv_1
|
||||
suggested_area: Roof
|
||||
modbus_polling: true
|
||||
client_mode.host: 192.168.x.x
|
||||
client_mode.port: 8899
|
||||
@@ -84,6 +84,21 @@ inverters:
|
||||
pv4.type: SF-M18/144550
|
||||
```
|
||||
|
||||
Example add-on configuration for GEN3PLUS energie storages:
|
||||
|
||||
```yaml
|
||||
batteries:
|
||||
- serial: 4100000000000000
|
||||
monitor_sn: 2300000000
|
||||
node_id: bat_1
|
||||
suggested_area: Garage
|
||||
modbus_polling: false
|
||||
pv1.manufacturer: Shinefar
|
||||
pv1.type: SF-M18/144550
|
||||
pv2.manufacturer: Shinefar
|
||||
pv2.type: SF-M18/144550
|
||||
```
|
||||
|
||||
**Note**: _This is just an example, you need to replace the values with your own!_
|
||||
|
||||
more information about the configuration can be found in the [configuration details page][configdetails].
|
||||
|
||||
@@ -11,7 +11,20 @@ configuration:
|
||||
Konfigurationsblock gesetzt werden.
|
||||
|
||||
Die Seriennummer der GEN3 Wechselrichter beginnen mit `R17` und die der GEN3PLUS
|
||||
Wechselrichter mir `Y17`oder `47`!
|
||||
Wechselrichter mit `Y17`oder `Y47`!
|
||||
|
||||
Siehe Beispielkonfiguration im Dokumentations-Tab
|
||||
batteries:
|
||||
name: Batterien
|
||||
description: >+
|
||||
Für jeden Energiespeicher muss die Seriennummer des Speichers einer MQTT
|
||||
Definition zugeordnet werden. Dazu wird der entsprechende Konfigurationsblock mit der
|
||||
16-stellige Seriennummer gestartet, so dass alle nachfolgenden Parameter diesem
|
||||
Speicher zugeordnet sind.
|
||||
Weitere speicherspezifische Parameter (z.B. Polling Mode) können im
|
||||
Konfigurationsblock gesetzt werden.
|
||||
|
||||
Die Seriennummer der GEN3PLUS Batteriespeicher beginnen mit `410`!
|
||||
|
||||
Siehe Beispielkonfiguration im Dokumentations-Tab
|
||||
|
||||
@@ -25,14 +38,14 @@ configuration:
|
||||
ein => normaler Proxy-Betrieb.
|
||||
aus => Der Wechselrichter wird vom Internet isoliert.
|
||||
solarman.enabled:
|
||||
name: Verbindung zur Solarman Cloud - nur für GEN3PLUS Wechselrichter
|
||||
name: Verbindung zur Solarman/TSUN Cloud - nur für GEN3PLUS Wechselrichter
|
||||
description: >+
|
||||
Schaltet die Verbindung zur Solarman Cloud ein/aus.
|
||||
Diese Verbindung ist erforderlich, wenn Sie Daten an die Solarman Cloud senden möchten,
|
||||
z.B. um die Solarman Apps zu nutzen oder Firmware-Updates zu erhalten.
|
||||
Schaltet die Verbindung zur Solarman oder TSUN Cloud ein/aus.
|
||||
Diese Verbindung ist erforderlich, wenn Sie Daten an die Cloud senden möchten,
|
||||
z.B. um die Solarman App oder TSUN Smart App zu nutzen oder Firmware-Updates zu erhalten.
|
||||
|
||||
ein => normaler Proxy-Betrieb.
|
||||
aus => Der Wechselrichter wird vom Internet isoliert.
|
||||
aus => Die GEN3PLUS Geräte werden vom Internet isoliert.
|
||||
inverters.allow_all:
|
||||
name: Erlaube Verbindungen von sämtlichen Wechselrichtern
|
||||
description: >-
|
||||
|
||||
@@ -7,13 +7,27 @@ configuration:
|
||||
definition. To do this, the corresponding configuration block is started with
|
||||
16-digit serial number so that all subsequent parameters are assigned
|
||||
to this inverter. Further inverter-specific parameters (e.g. polling mode) can be set
|
||||
in the configuration block
|
||||
in the configuration block.
|
||||
|
||||
The serial numbers of all GEN3 inverters start with `R17` and that of the GEN3PLUS
|
||||
inverters with ‘Y17’ or ‘47’!
|
||||
inverters with ‘Y17’ or ‘Y47’!
|
||||
|
||||
For reference see example configuration in Documentation Tab
|
||||
|
||||
batteries:
|
||||
name: Energy Storages
|
||||
description: >+
|
||||
For each energy storage device, the serial number of the storage device must be
|
||||
assigned to an MQTT definition. To do this, the corresponding configuration block
|
||||
is started with the 16-digit serial number so that all subsequent parameters are
|
||||
assigned to this energy storage. Further inverter-specific parameters (e.g. polling
|
||||
mode) can be set in the configuration block.
|
||||
|
||||
The serial numbers of all GEN3PLUS energy storages start with ‘410’!
|
||||
|
||||
For reference see example configuration in Documentation Tab
|
||||
|
||||
|
||||
tsun.enabled:
|
||||
name: Connection to TSUN Cloud - for GEN3 inverter only
|
||||
description: >+
|
||||
@@ -24,14 +38,14 @@ configuration:
|
||||
on => normal proxy operation.
|
||||
off => The Inverter become isolated from Internet.
|
||||
solarman.enabled:
|
||||
name: Connection to Solarman Cloud - for GEN3PLUS inverter only
|
||||
name: Connection to Solarman/TSUN Cloud - for GEN3PLUS inverter only
|
||||
description: >+
|
||||
switch on/off connection to the Solarman cloud.
|
||||
This connection is only required if you want send data to the Solarman cloud
|
||||
eg. to use the Solarman APPs or receive firmware updates.
|
||||
switch on/off connection to the Solarman or TSUN cloud.
|
||||
This connection is only required if you want send data to the cloud
|
||||
eg. to use the Solarman APP, the TSUN Smart APP or receive firmware updates.
|
||||
|
||||
on => normal proxy operation.
|
||||
off => The Inverter become isolated from Internet
|
||||
off => The GEN3PLUS devices become isolated from Internet
|
||||
inverters.allow_all:
|
||||
name: Allow all connections from all inverters
|
||||
description: >-
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: {{name}}
|
||||
description: {{description}}
|
||||
version: {% if version is defined and version|length %} {{version}} {% else %} {{AppVersion}} {% endif %}
|
||||
version: {% if version is defined and version|length %} {{version}} {% elif BuildID is defined and BuildID|length %} {{AppVersion}}-{{BuildID}} {% else %} {{AppVersion}} {% endif %}
|
||||
image: {{image}}
|
||||
url: https://github.com/s-allius/tsun-gen3-proxy
|
||||
slug: {{slug}}
|
||||
@@ -30,7 +30,6 @@ ports:
|
||||
# Definition of parameters in the configuration tab of the addon
|
||||
# parameters are available within the container as /data/options.json
|
||||
# and should become picked up by the proxy - current workaround as a transfer script
|
||||
# TODO: check again for multi hierarchie parameters
|
||||
|
||||
schema:
|
||||
inverters:
|
||||
@@ -42,11 +41,6 @@ schema:
|
||||
client_mode.host: match(\b((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b)?
|
||||
client_mode.port: port?
|
||||
client_mode.forward: bool?
|
||||
#strings: # leider funktioniert es nicht die folgenden 3 parameter im schema aufzulisten. möglicherweise wird die verschachtelung nicht unterstützt.
|
||||
# - string: str
|
||||
# type: str
|
||||
# manufacturer: str
|
||||
# daher diese variante
|
||||
pv1.manufacturer: str?
|
||||
pv1.type: str?
|
||||
pv2.manufacturer: str?
|
||||
@@ -62,6 +56,19 @@ schema:
|
||||
tsun.enabled: bool
|
||||
solarman.enabled: bool
|
||||
inverters.allow_all: bool
|
||||
batteries:
|
||||
- serial: match(^(410).{13}$)
|
||||
monitor_sn: int
|
||||
node_id: str
|
||||
suggested_area: str
|
||||
modbus_polling: bool
|
||||
client_mode.host: match(\b((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b)?
|
||||
client_mode.port: port?
|
||||
client_mode.forward: bool?
|
||||
pv1.manufacturer: str?
|
||||
pv1.type: str?
|
||||
pv2.manufacturer: str?
|
||||
pv2.type: str?
|
||||
|
||||
# optionale parameter
|
||||
|
||||
@@ -90,17 +97,21 @@ schema:
|
||||
# If any default value is given, the option becomes a required value.
|
||||
options:
|
||||
inverters:
|
||||
- serial: R17E760702080400
|
||||
node_id: PV-Garage
|
||||
- serial: R17E000000000000
|
||||
monitor_sn: 0
|
||||
node_id: inv_1
|
||||
suggested_area: Roof
|
||||
modbus_polling: false
|
||||
pv1.manufacturer: Shinefar
|
||||
pv1.type: SF-M18/144550
|
||||
pv2.manufacturer: Shinefar
|
||||
pv2.type: SF-M18/144550
|
||||
batteries:
|
||||
- serial: 4100000000000000
|
||||
monitor_sn: 0
|
||||
node_id: bat_1
|
||||
suggested_area: Garage
|
||||
modbus_polling: false
|
||||
# strings:
|
||||
# - string: PV1
|
||||
# type: SF-M18/144550
|
||||
# manufacturer: Shinefar
|
||||
# - string: PV2
|
||||
# type: SF-M18/144550
|
||||
# manufacturer: Shinefar
|
||||
pv1.manufacturer: Shinefar
|
||||
pv1.type: SF-M18/144550
|
||||
pv2.manufacturer: Shinefar
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
{
|
||||
"name": "TSUN-Proxy (Debug)",
|
||||
"description": "MQTT Proxy for TSUN Photovoltaic Inverters with Debug Logging",
|
||||
"version": "debug",
|
||||
"image": "docker.io/sallius/tsun-gen3-addon",
|
||||
"slug": "tsun-proxy-debug",
|
||||
"advanced": true,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
{
|
||||
"name": "TSUN-Proxy (Dev)",
|
||||
"description": "MQTT Proxy for TSUN Photovoltaic Inverters",
|
||||
"version": "dev",
|
||||
"image": "docker.io/sallius/tsun-gen3-addon",
|
||||
"slug": "tsun-proxy-dev",
|
||||
"advanced": false,
|
||||
|
||||
@@ -15,6 +15,10 @@ sonar.sources=app/src/
|
||||
sonar.python.version=3.12
|
||||
sonar.tests=system_tests/,app/tests/
|
||||
sonar.exclusions=**/.vscode/**/*
|
||||
|
||||
# disable code dupication check for config grammar
|
||||
sonar.cpd.exclusions=app/src/cnf/config.py
|
||||
|
||||
# Name your criteria
|
||||
sonar.issue.ignore.multicriteria=e1,e2
|
||||
|
||||
|
||||
@@ -10,6 +10,12 @@ SOLARMAN_SNR = os.getenv('SOLARMAN_SNR', '00000080')
|
||||
def get_sn() -> bytes:
|
||||
return bytes.fromhex(SOLARMAN_SNR)
|
||||
|
||||
def get_dcu_sn() -> bytes:
|
||||
return b'\x20\x43\x65\x7b'
|
||||
|
||||
def get_dcu_no() -> bytes:
|
||||
return b'4100000000000001'
|
||||
|
||||
def get_inv_no() -> bytes:
|
||||
return b'T170000000000001'
|
||||
|
||||
@@ -105,6 +111,62 @@ def MsgInvalidInfo(): # Contact Info message wrong start byte
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_dev_ind_msg(): # 0x4110
|
||||
msg = b'\xa5\x3a\x01\x10\x41\x00\x01' +get_dcu_sn() +b'\x02\xc6\xde\x2d\x32'
|
||||
msg += b'\x27\x00\x00\x00\x00\x00\x00\x00\x05\x3c\x78\x01\x5c\x01\x4c\x53'
|
||||
msg += b'\x57\x35\x5f\x30\x31\x5f\x33\x30\x32\x36\x5f\x4e\x53\x5f\x30\x35'
|
||||
msg += b'\x5f\x30\x31\x2e\x30\x30\x2e\x30\x30\x2e\x30\x30\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\xd4\x27\x87\x12\xad\xc0\x31\x39\x32\x2e'
|
||||
msg += b'\x31\x36\x38\x2e\x39\x2e\x31\x34\x00\x00\x00\x00\x01\x00\x01\x26'
|
||||
msg += b'\x30\x0f\x00\xff\x56\x31\x2e\x31\x2e\x30\x30\x2e\x30\x42\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x7a\x75\x68\x61\x75\x73\x65\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x08\x01\x01\x01\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x01'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_dev_rsp_msg(): # 0x1110
|
||||
msg = b'\xa5\x0a\x00\x10\x11\x92\x01' +get_dcu_sn() +b'\x02\x01\x4a\xf6\xa6'
|
||||
msg += b'\x67\x3c\x00\x00\x00'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_data_ind_msg(): # 0x4210
|
||||
msg = b'\xa5\x6f\x00\x10\x42\x92\x02' +get_dcu_sn() +b'\x01\x26\x30\xc7\xde'
|
||||
msg += b'\x2d\x32\x28\x00\x00\x00\x84\x17\x79\x35\x01\x00\x4c\x12\x00\x00'
|
||||
msg += get_dcu_no()
|
||||
msg += b'\x0d\x3a\x00\x00\x0d\x2c\x00\x00\x00\x00\x08\x20\x00\x00\x00\x00'
|
||||
msg += b'\x14\x0e\xff\xfe\x03\xe8\x0c\x89\x0c\x89\x0c\x89\x0c\x8a\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x8a\x0c\x89\x0c\x89\x0c\x8a\x0c\x8a\x0c\x89\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x89\x0c\x88\x00\x0f\x00\x0f\x00\x0f\x00\x0e\x00\x00'
|
||||
msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_data_rsp_msg(): # 0x1210
|
||||
msg = b'\xa5\x0a\x00\x10\x12\x93\x02' +get_dcu_sn() +b'\x01\x01\xd1\x96\x04'
|
||||
msg += b'\x66\x3c\x00\x00\x00'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ClientConnection():
|
||||
@@ -181,4 +243,24 @@ def test_inavlid_msg(ClientConnection,MsgInvalidInfo,MsgContactInfo, MsgContactR
|
||||
# time.sleep(2.5)
|
||||
checkResponse(data, MsgContactResp)
|
||||
|
||||
|
||||
def test_dcu_dev(ClientConnection,dcu_dev_ind_msg, dcu_dev_rsp_msg):
|
||||
s = ClientConnection
|
||||
try:
|
||||
s.sendall(dcu_dev_ind_msg)
|
||||
# time.sleep(2.5)
|
||||
data = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
checkResponse(data, dcu_dev_rsp_msg)
|
||||
|
||||
def test_dcu_ind(ClientConnection,dcu_data_ind_msg, dcu_data_rsp_msg):
|
||||
s = ClientConnection
|
||||
try:
|
||||
s.sendall(dcu_data_ind_msg)
|
||||
# time.sleep(2.5)
|
||||
data = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
checkResponse(data, dcu_data_rsp_msg)
|
||||
|
||||
Reference in New Issue
Block a user