Compare commits

..

8 Commits

Author SHA1 Message Date
Stefan Allius
2a8c120325 workaround for github runner 2025-06-22 21:35:26 +02:00
Stefan Allius
a8a73af7a4 fix git runner localisation 2025-06-22 21:24:29 +02:00
Stefan Allius
6fab284506 extend languages tests 2025-06-22 16:44:59 +02:00
Stefan Allius
276bf483d6 update changelog 2025-06-22 16:04:37 +02:00
Stefan Allius
ad78d97f8f improve unit-tests for the web-UI 2025-06-22 16:03:58 +02:00
Stefan Allius
20b6beba49 set app.testing to get exceptions during test 2025-06-22 11:59:27 +02:00
Stefan Allius
af1340af03 Add translations 2025-06-22 11:58:17 +02:00
Stefan Allius
f6d08d38e1 add links to add-on urls 2025-06-21 18:05:20 +02:00
17 changed files with 51 additions and 103 deletions

View File

@@ -7,12 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased] ## [unreleased]
- Update dependency pytest-asyncio to v1.1.0
- save task references, to avoid a task disappearing mid-execution
- catch socket.gaierror exception and log this with info level
- Update dependency coverage to v7.9.2
- add-on: bump base-image to version 18.0.3
- add-on: remove armhf and armv7 support
- add-on: add links to config and log-file to the web-UI - add-on: add links to config and log-file to the web-UI
- fix some SonarQube warnings - fix some SonarQube warnings
- remove unused 32-bit architectures - remove unused 32-bit architectures

View File

@@ -29,17 +29,17 @@ target "_common" {
"type =sbom,generator=docker/scout-sbom-indexer:latest" "type =sbom,generator=docker/scout-sbom-indexer:latest"
] ]
annotations = [ annotations = [
"index,manifest-descriptor:org.opencontainers.image.title=TSUN-Proxy", "index:org.opencontainers.image.title=TSUN Gen3 Proxy",
"index,manifest-descriptor:org.opencontainers.image.authors=Stefan Allius", "index:org.opencontainers.image.authors=Stefan Allius",
"index,manifest-descriptor:org.opencontainers.image.created=${BUILD_DATE}", "index:org.opencontainers.image.created=${BUILD_DATE}",
"index,manifest-descriptor:org.opencontainers.image.version=${VERSION}", "index:org.opencontainers.image.version=${VERSION}",
"index,manifest-descriptor:org.opencontainers.image.revision=${BRANCH}", "index:org.opencontainers.image.revision=${BRANCH}",
"index,manifest-descriptor:org.opencontainers.image.description=${DESCRIPTION}", "index:org.opencontainers.image.description=${DESCRIPTION}",
"index:org.opencontainers.image.licenses=BSD-3-Clause", "index:org.opencontainers.image.licenses=BSD-3-Clause",
"index:org.opencontainers.image.source=https://github.com/s-allius/tsun-gen3-proxy" "index:org.opencontainers.image.source=https://github.com/s-allius/tsun-gen3-proxy"
] ]
labels = { labels = {
"org.opencontainers.image.title" = "TSUN-Proxy" "org.opencontainers.image.title" = "TSUN Gen3 Proxy"
"org.opencontainers.image.authors" = "Stefan Allius" "org.opencontainers.image.authors" = "Stefan Allius"
"org.opencontainers.image.created" = "${BUILD_DATE}" "org.opencontainers.image.created" = "${BUILD_DATE}"
"org.opencontainers.image.version" = "${VERSION}" "org.opencontainers.image.version" = "${VERSION}"

View File

@@ -1,8 +1,8 @@
flake8==7.3.0 flake8==7.3.0
pytest==8.4.1 pytest==8.4.1
pytest-asyncio==1.1.0 pytest-asyncio==1.0.0
pytest-cov==6.2.1 pytest-cov==6.2.1
python-dotenv==1.1.1 python-dotenv==1.1.0
mock==5.2.0 mock==5.2.0
coverage==7.9.2 coverage==7.9.1
jinja2-cli==0.8.2 jinja2-cli==0.8.2

View File

@@ -102,7 +102,3 @@ class AsyncIfc(ABC):
@abstractmethod @abstractmethod
def prot_set_update_header_cb(self, callback): def prot_set_update_header_cb(self, callback):
pass # pragma: no cover pass # pragma: no cover
@abstractmethod
def prot_set_disc_cb(self, callback):
pass # pragma: no cover

View File

@@ -29,7 +29,6 @@ class AsyncIfcImpl(AsyncIfc):
self.timeout_cb = None self.timeout_cb = None
self.init_new_client_conn_cb = None self.init_new_client_conn_cb = None
self.update_header_cb = None self.update_header_cb = None
self.inv_disc_cb = None
def close(self): def close(self):
self.timeout_cb = None self.timeout_cb = None
@@ -107,9 +106,6 @@ class AsyncIfcImpl(AsyncIfc):
def prot_set_update_header_cb(self, callback): def prot_set_update_header_cb(self, callback):
self.update_header_cb = callback self.update_header_cb = callback
def prot_set_disc_cb(self, callback):
self.inv_disc_cb = callback
class StreamPtr(): class StreamPtr():
'''Descr StreamPtr''' '''Descr StreamPtr'''
@@ -334,8 +330,6 @@ class AsyncStreamServer(AsyncStream):
Infos.inc_counter('ServerMode_Cnt') Infos.inc_counter('ServerMode_Cnt')
await self.publish_outstanding_mqtt() await self.publish_outstanding_mqtt()
await self.loop() await self.loop()
if self.inv_disc_cb:
self.inv_disc_cb()
Infos.dec_counter('ServerMode_Cnt') Infos.dec_counter('ServerMode_Cnt')
Infos.dec_counter('Inverter_Cnt') Infos.dec_counter('Inverter_Cnt')
await self.publish_outstanding_mqtt() await self.publish_outstanding_mqtt()
@@ -392,8 +386,6 @@ class AsyncStreamClient(AsyncStream):
Infos.inc_counter('ProxyMode_Cnt') Infos.inc_counter('ProxyMode_Cnt')
await self.publish_outstanding_mqtt() await self.publish_outstanding_mqtt()
await self.loop() await self.loop()
if self.inv_disc_cb:
self.inv_disc_cb()
if self.emu_mode: if self.emu_mode:
Infos.dec_counter('EmuMode_Cnt') Infos.dec_counter('EmuMode_Cnt')
else: else:

View File

@@ -36,7 +36,6 @@ class Talent(Message):
def __init__(self, inverter, addr, ifc: "AsyncIfc", server_side: bool, def __init__(self, inverter, addr, ifc: "AsyncIfc", server_side: bool,
client_mode: bool = False, id_str=b''): client_mode: bool = False, id_str=b''):
self.db = InfosG3()
super().__init__('G3', ifc, server_side, self.send_modbus_cb, super().__init__('G3', ifc, server_side, self.send_modbus_cb,
mb_timeout=15) mb_timeout=15)
_ = inverter _ = inverter
@@ -52,6 +51,7 @@ class Talent(Message):
self.contact_name = b'' self.contact_name = b''
self.contact_mail = b'' self.contact_mail = b''
self.ts_offset = 0 # time offset between tsun cloud and local self.ts_offset = 0 # time offset between tsun cloud and local
self.db = InfosG3()
self.switch = { self.switch = {
0x00: self.msg_contact_info, 0x00: self.msg_contact_info,
0x13: self.msg_ota_update, 0x13: self.msg_ota_update,

View File

@@ -256,11 +256,11 @@ class SolarmanV5(SolarmanBase):
def __init__(self, inverter, addr, ifc: "AsyncIfc", def __init__(self, inverter, addr, ifc: "AsyncIfc",
server_side: bool, client_mode: bool): server_side: bool, client_mode: bool):
self.db = InfosG3P(client_mode)
super().__init__(addr, ifc, server_side, self.send_modbus_cb, super().__init__(addr, ifc, server_side, self.send_modbus_cb,
mb_timeout=8) mb_timeout=8)
self.inverter = inverter self.inverter = inverter
self.db = InfosG3P(client_mode)
self.no_forwarding = False self.no_forwarding = False
'''not allowed to connect to TSUN cloud by connection type''' '''not allowed to connect to TSUN cloud by connection type'''
self.establish_inv_emu = False self.establish_inv_emu = False
@@ -327,7 +327,6 @@ class SolarmanV5(SolarmanBase):
self.sensor_list = 0 self.sensor_list = 0
self.mb_regs = [{'addr': 0x3000, 'len': 48}, self.mb_regs = [{'addr': 0x3000, 'len': 48},
{'addr': 0x2000, 'len': 96}] {'addr': 0x2000, 'len': 96}]
self.background_tasks = set()
''' '''
Our puplic methods Our puplic methods
@@ -340,7 +339,6 @@ class SolarmanV5(SolarmanBase):
self.inverter = None self.inverter = None
self.switch.clear() self.switch.clear()
self.log_lvl.clear() self.log_lvl.clear()
self.background_tasks.clear()
super().close() super().close()
def send_start_cmd(self, snr: int, host: str, def send_start_cmd(self, snr: int, host: str,
@@ -692,10 +690,8 @@ class SolarmanV5(SolarmanBase):
self.__forward_msg() self.__forward_msg()
def publish_mqtt(self, key, data): # pragma: no cover def publish_mqtt(self, key, data): # pragma: no cover
task = asyncio.ensure_future( asyncio.ensure_future(
Proxy.mqtt.publish(key, data)) Proxy.mqtt.publish(key, data))
self.background_tasks.add(task)
task.add_done_callback(self.background_tasks.discard)
def get_cmd_rsp_log_lvl(self) -> int: def get_cmd_rsp_log_lvl(self) -> int:
ftype = self.ifc.rx_peek()[self.header_len] ftype = self.ifc.rx_peek()[self.header_len]

View File

@@ -31,7 +31,6 @@ class Register(Enum):
GRID_VOLT_CAL_COEF = 29 GRID_VOLT_CAL_COEF = 29
OUTPUT_COEFFICIENT = 30 OUTPUT_COEFFICIENT = 30
PROD_COMPL_TYPE = 31 PROD_COMPL_TYPE = 31
AVAIL_STATUS = 32
INVERTER_CNT = 50 INVERTER_CNT = 50
UNKNOWN_SNR = 51 UNKNOWN_SNR = 51
UNKNOWN_MSG = 52 UNKNOWN_MSG = 52
@@ -578,7 +577,6 @@ class Infos:
__output_coef_val_tpl = "{% if 'Output_Coefficient' in value_json and value_json['Output_Coefficient'] != None %}{{value_json['Output_Coefficient']|string() +' %'}}{% else %}{{ this.state }}{% endif %}" # noqa: E501 __output_coef_val_tpl = "{% if 'Output_Coefficient' in value_json and value_json['Output_Coefficient'] != None %}{{value_json['Output_Coefficient']|string() +' %'}}{% else %}{{ this.state }}{% endif %}" # noqa: E501
__info_defs = { __info_defs = {
Register.AVAIL_STATUS: {'name': ['status', 'status']},
# collector values used for device registration: # collector values used for device registration:
Register.COLLECTOR_FW_VERSION: {'name': ['collector', 'Collector_Fw_Version'], 'level': logging.INFO, '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'], 'singleton': False, 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 Register.CHIP_TYPE: {'name': ['collector', 'Chip_Type'], 'singleton': False, 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
@@ -948,9 +946,6 @@ class Infos:
attr['dev_cla'] = ha['dev_cla'] attr['dev_cla'] = ha['dev_cla']
attr['stat_cla'] = ha['stat_cla'] attr['stat_cla'] = ha['stat_cla']
attr['uniq_id'] = ha['id']+snr attr['uniq_id'] = ha['id']+snr
# attr['availability_topic'] = prfx + "status"
# attr['payload_available'] = "online"
# attr['payload_not_available'] = "offline"
if 'val_tpl' in ha: if 'val_tpl' in ha:
attr['val_tpl'] = ha['val_tpl'] attr['val_tpl'] = ha['val_tpl']
elif 'fmt' in ha: elif 'fmt' in ha:

View File

@@ -4,7 +4,6 @@ import logging
import traceback import traceback
import json import json
import gc import gc
import socket
from aiomqtt import MqttCodeError from aiomqtt import MqttCodeError
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter
from ipaddress import ip_address from ipaddress import ip_address
@@ -39,7 +38,6 @@ class InverterBase(InverterIfc, Proxy):
self.use_emulation = False self.use_emulation = False
self.__ha_restarts = -1 self.__ha_restarts = -1
self.remote = StreamPtr(None) self.remote = StreamPtr(None)
self.background_tasks = set()
ifc = AsyncStreamServer(reader, writer, ifc = AsyncStreamServer(reader, writer,
self.async_publ_mqtt, self.async_publ_mqtt,
self.create_remote, self.create_remote,
@@ -74,7 +72,6 @@ class InverterBase(InverterIfc, Proxy):
if self.remote.ifc: if self.remote.ifc:
self.remote.ifc.close() self.remote.ifc.close()
self.remote.ifc = None self.remote.ifc = None
self.background_tasks.clear()
async def disc(self, shutdown_started=False) -> None: async def disc(self, shutdown_started=False) -> None:
if self.remote.stream: if self.remote.stream:
@@ -139,14 +136,9 @@ class InverterBase(InverterIfc, Proxy):
logging.info(f'[{self.remote.stream.node_id}:' logging.info(f'[{self.remote.stream.node_id}:'
f'{self.remote.stream.conn_no}] ' f'{self.remote.stream.conn_no}] '
f'Connected to {addr}') f'Connected to {addr}')
task = asyncio.create_task( asyncio.create_task(self.remote.ifc.client_loop(addr))
self.remote.ifc.client_loop(addr))
self.background_tasks.add(task)
task.add_done_callback(self.background_tasks.discard)
except (ConnectionRefusedError, except (ConnectionRefusedError, TimeoutError) as error:
TimeoutError,
socket.gaierror) as error:
logging.info(f'{error}') logging.info(f'{error}')
except Exception: except Exception:
Infos.inc_counter('SW_Exception') Infos.inc_counter('SW_Exception')
@@ -167,8 +159,6 @@ class InverterBase(InverterIfc, Proxy):
stream.new_data['batterie']) stream.new_data['batterie'])
or ('collector' in stream.new_data and or ('collector' in stream.new_data and
stream.new_data['collector']) stream.new_data['collector'])
or ('status' in stream.new_data and
stream.new_data['status'])
or self.mqtt.ha_restarts != self.__ha_restarts): or self.mqtt.ha_restarts != self.__ha_restarts):
await self._register_proxy_stat_home_assistant() await self._register_proxy_stat_home_assistant()
await self.__register_home_assistant(stream) await self.__register_home_assistant(stream)

View File

@@ -98,11 +98,7 @@ class Message(ProtocolIfc):
self.server_side = server_side self.server_side = server_side
self.ifc = ifc self.ifc = ifc
self.node_id = node_id self.node_id = node_id
self.new_data = {}
if server_side: if server_side:
ifc.prot_set_disc_cb(self._inv_disc)
self.db.set_db_def_value(Register.AVAIL_STATUS, "on")
self.new_data['status'] = True
self.mb = Modbus(send_modbus_cb, mb_timeout) self.mb = Modbus(send_modbus_cb, mb_timeout)
self.mb_timer = Timer(self.mb_timout_cb, self.node_id) self.mb_timer = Timer(self.mb_timout_cb, self.node_id)
else: else:
@@ -114,6 +110,7 @@ class Message(ProtocolIfc):
self.unique_id = 0 self.unique_id = 0
self.inv_serial = '' self.inv_serial = ''
self.sug_area = '' self.sug_area = ''
self.new_data = {}
self.state = State.init self.state = State.init
self.shutdown_started = False self.shutdown_started = False
self.modbus_elms = 0 # for unit tests self.modbus_elms = 0 # for unit tests
@@ -223,11 +220,6 @@ class Message(ProtocolIfc):
f'(reg: 0x{self.mb.last_reg:04x}):', f'(reg: 0x{self.mb.last_reg:04x}):',
data[hdr_len:], modbus_msg_len) data[hdr_len:], modbus_msg_len)
def _inv_disc(self):
logging.warning(f"Un-Available: [{self.node_id}]")
self.db.set_db_def_value(Register.AVAIL_STATUS, "off")
self.new_data['status'] = True
''' '''
Our puplic methods Our puplic methods
''' '''
@@ -245,7 +237,6 @@ class Message(ProtocolIfc):
self.ifc.prot_set_timeout_cb(None) self.ifc.prot_set_timeout_cb(None)
self.ifc.prot_set_init_new_client_conn_cb(None) self.ifc.prot_set_init_new_client_conn_cb(None)
self.ifc.prot_set_update_header_cb(None) self.ifc.prot_set_update_header_cb(None)
self.ifc.prot_set_disc_cb(None)
self.ifc = None self.ifc = None
if self.mb: if self.mb:

View File

@@ -35,8 +35,6 @@ class ModbusConn():
async def __aexit__(self, exc_type, exc, tb): async def __aexit__(self, exc_type, exc, tb):
Infos.dec_counter('ClientMode_Cnt') Infos.dec_counter('ClientMode_Cnt')
Infos.dec_counter('Inverter_Cnt') Infos.dec_counter('Inverter_Cnt')
if self.inverter.local.ifc.inv_disc_cb:
self.inverter.local.ifc.inv_disc_cb()
await self.inverter.local.ifc.publish_outstanding_mqtt() await self.inverter.local.ifc.publish_outstanding_mqtt()
self.inverter.__exit__(exc_type, exc, tb) self.inverter.__exit__(exc_type, exc, tb)
@@ -45,7 +43,6 @@ class ModbusTcp():
def __init__(self, loop, tim_restart=10) -> None: def __init__(self, loop, tim_restart=10) -> None:
self.tim_restart = tim_restart self.tim_restart = tim_restart
self.background_tasks = set()
inverters = Config.get('inverters') inverters = Config.get('inverters')
batteries = Config.get('batteries') batteries = Config.get('batteries')
@@ -57,13 +54,10 @@ class ModbusTcp():
and 'client_mode' in inv): and 'client_mode' in inv):
client = inv['client_mode'] client = inv['client_mode']
logger.info(f"'client_mode' for Monitoring-SN: {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
task = loop.create_task( loop.create_task(self.modbus_loop(client['host'],
self.modbus_loop(client['host'],
client['port'], client['port'],
inv['monitor_sn'], inv['monitor_sn'],
client['forward'])) client['forward']))
self.background_tasks.add(task)
task.add_done_callback(self.background_tasks.discard)
async def modbus_loop(self, host, port, async def modbus_loop(self, host, port,
snr: int, forward: bool) -> None: snr: int, forward: bool) -> None:

View File

@@ -218,7 +218,6 @@ app = Quart(__name__,
static_folder='web/static') static_folder='web/static')
app.secret_key = 'JKLdks.dajlKKKdladkflKwolafallsdfl' app.secret_key = 'JKLdks.dajlKKKdladkflKwolafallsdfl'
app.jinja_env.globals.update(url_for=url_for) app.jinja_env.globals.update(url_for=url_for)
app.background_tasks = set()
server = Server(app, __name__ == "__main__") server = Server(app, __name__ == "__main__")
Web(app, server.trans_path, server.rel_urls) Web(app, server.trans_path, server.rel_urls)
@@ -269,13 +268,9 @@ async def startup_app(): # pragma: no cover
for inv_class, port in [(InverterG3, 5005), (InverterG3P, 10000)]: for inv_class, port in [(InverterG3, 5005), (InverterG3P, 10000)]:
logging.info(f'listen on port: {port} for inverters') logging.info(f'listen on port: {port} for inverters')
task = loop.create_task( loop.create_task(asyncio.start_server(lambda r, w, i=inv_class:
asyncio.start_server(lambda r, w, i=inv_class:
handle_client(r, w, i), handle_client(r, w, i),
'0.0.0.0', port)) '0.0.0.0', port))
app.background_tasks.add(task)
task.add_done_callback(app.background_tasks.discard)
ProxyState.set_up(True) ProxyState.set_up(True)
@@ -299,7 +294,6 @@ async def handle_shutdown(): # pragma: no cover
await inverter.disc(True) await inverter.disc(True)
logging.info('Proxy disconnecting done') logging.info('Proxy disconnecting done')
app.background_tasks.clear()
await Proxy.class_close(loop) await Proxy.class_close(loop)

View File

@@ -1598,18 +1598,18 @@ async def test_msg_iterator(my_loop, config_tsun_inv1):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_proxy_counter(my_loop, config_tsun_inv1): async def test_proxy_counter(my_loop, config_tsun_inv1):
m = SolarmanV5(None, ('test.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) m = SolarmanV5(None, ('test.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False)
assert m.new_data == {'status': True} assert m.new_data == {}
m.db.stat['proxy']['Unknown_Msg'] = 0 m.db.stat['proxy']['Unknown_Msg'] = 0
Infos.new_stat_data['proxy'] = False Infos.new_stat_data['proxy'] = False
m.inc_counter('Unknown_Msg') m.inc_counter('Unknown_Msg')
assert m.new_data == {'status': True} assert m.new_data == {}
assert Infos.new_stat_data == {'proxy': True} assert Infos.new_stat_data == {'proxy': True}
assert 1 == m.db.stat['proxy']['Unknown_Msg'] assert 1 == m.db.stat['proxy']['Unknown_Msg']
Infos.new_stat_data['proxy'] = False Infos.new_stat_data['proxy'] = False
m.dec_counter('Unknown_Msg') m.dec_counter('Unknown_Msg')
assert m.new_data == {'status': True} assert m.new_data == {}
assert Infos.new_stat_data == {'proxy': True} assert Infos.new_stat_data == {'proxy': True}
assert 0 == m.db.stat['proxy']['Unknown_Msg'] assert 0 == m.db.stat['proxy']['Unknown_Msg']
m.close() m.close()

View File

@@ -2070,7 +2070,7 @@ def test_proxy_counter():
m.id_str = b"R170000000000001" m.id_str = b"R170000000000001"
c = m.createClientStream(b'') c = m.createClientStream(b'')
assert m.new_data == {'status': True} assert m.new_data == {}
m.db.stat['proxy']['Unknown_Msg'] = 0 m.db.stat['proxy']['Unknown_Msg'] = 0
c.db.stat['proxy']['Unknown_Msg'] = 0 c.db.stat['proxy']['Unknown_Msg'] = 0
Infos.new_stat_data['proxy'] = False Infos.new_stat_data['proxy'] = False
@@ -2079,7 +2079,7 @@ def test_proxy_counter():
m.close() m.close()
m = MemoryStream(b'') m = MemoryStream(b'')
assert m.new_data == {'status': True} assert m.new_data == {}
assert Infos.new_stat_data == {'proxy': True} assert Infos.new_stat_data == {'proxy': True}
assert m.db.new_stat_data == {'proxy': True} assert m.db.new_stat_data == {'proxy': True}
assert c.db.new_stat_data == {'proxy': True} assert c.db.new_stat_data == {'proxy': True}
@@ -2088,7 +2088,7 @@ def test_proxy_counter():
Infos.new_stat_data['proxy'] = False Infos.new_stat_data['proxy'] = False
c.inc_counter('Unknown_Msg') c.inc_counter('Unknown_Msg')
assert m.new_data == {'status': True} assert m.new_data == {}
assert Infos.new_stat_data == {'proxy': True} assert Infos.new_stat_data == {'proxy': True}
assert m.db.new_stat_data == {'proxy': True} assert m.db.new_stat_data == {'proxy': True}
assert c.db.new_stat_data == {'proxy': True} assert c.db.new_stat_data == {'proxy': True}
@@ -2097,7 +2097,7 @@ def test_proxy_counter():
Infos.new_stat_data['proxy'] = False Infos.new_stat_data['proxy'] = False
c.inc_counter('Modbus_Command') c.inc_counter('Modbus_Command')
assert m.new_data == {'status': True} assert m.new_data == {}
assert Infos.new_stat_data == {'proxy': True} assert Infos.new_stat_data == {'proxy': True}
assert m.db.new_stat_data == {'proxy': True} assert m.db.new_stat_data == {'proxy': True}
assert c.db.new_stat_data == {'proxy': True} assert c.db.new_stat_data == {'proxy': True}
@@ -2106,7 +2106,7 @@ def test_proxy_counter():
Infos.new_stat_data['proxy'] = False Infos.new_stat_data['proxy'] = False
m.dec_counter('Unknown_Msg') m.dec_counter('Unknown_Msg')
assert m.new_data == {'status': True} assert m.new_data == {}
assert Infos.new_stat_data == {'proxy': True} assert Infos.new_stat_data == {'proxy': True}
assert 1 == m.db.stat['proxy']['Unknown_Msg'] assert 1 == m.db.stat['proxy']['Unknown_Msg']
m.close() m.close()
@@ -2258,7 +2258,7 @@ def test_msg_modbus_rsp2(config_tsun_inv1, msg_modbus_rsp20):
m.mb.req_pend = True m.mb.req_pend = True
m.mb.err = 0 m.mb.err = 0
assert m.db.db == {'status': {'status': 'on'}} assert m.db.db == {}
m.new_data['inverter'] = False m.new_data['inverter'] = False
m.read() # read complete msg, and dispatch msg m.read() # read complete msg, and dispatch msg
@@ -2267,7 +2267,7 @@ def test_msg_modbus_rsp2(config_tsun_inv1, msg_modbus_rsp20):
assert m.msg_count == 2 assert m.msg_count == 2
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp20 assert m.ifc.fwd_fifo.get()==msg_modbus_rsp20
assert m.ifc.tx_fifo.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.db == {'status': {'status': 'on'}, 'collector': {'Serial_Number': 'R170000000000001'}, 'inverter': {'Version': 'V5.1.09', 'Rated_Power': 300}, 'grid': {'Timestamp': m._utc(), 'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'Timestamp': m._utc(), 'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}} assert m.db.db == {'collector': {'Serial_Number': 'R170000000000001'}, 'inverter': {'Version': 'V5.1.09', 'Rated_Power': 300}, 'grid': {'Timestamp': m._utc(), 'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'Timestamp': m._utc(), 'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}}
assert m.db.get_db_value(Register.VERSION) == 'V5.1.09' assert m.db.get_db_value(Register.VERSION) == 'V5.1.09'
assert m.db.get_db_value(Register.TS_GRID) == m._utc() assert m.db.get_db_value(Register.TS_GRID) == m._utc()
assert m.new_data['inverter'] == True assert m.new_data['inverter'] == True
@@ -2288,7 +2288,7 @@ def test_msg_modbus_rsp3(config_tsun_inv1, msg_modbus_rsp21):
m.mb.req_pend = True m.mb.req_pend = True
m.mb.err = 0 m.mb.err = 0
assert m.db.db == {'status': {'status': 'on'}} assert m.db.db == {}
m.new_data['inverter'] = False m.new_data['inverter'] = False
m.read() # read complete msg, and dispatch msg m.read() # read complete msg, and dispatch msg
@@ -2297,7 +2297,7 @@ def test_msg_modbus_rsp3(config_tsun_inv1, msg_modbus_rsp21):
assert m.msg_count == 2 assert m.msg_count == 2
assert m.ifc.fwd_fifo.get()==msg_modbus_rsp21 assert m.ifc.fwd_fifo.get()==msg_modbus_rsp21
assert m.ifc.tx_fifo.get()==b'' assert m.ifc.tx_fifo.get()==b''
assert m.db.db == {'status': {'status': 'on'}, 'collector': {'Serial_Number': 'R170000000000001'}, 'inverter': {'Version': 'V5.1.0E', 'Rated_Power': 300}, 'grid': {'Timestamp': m._utc(), 'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'Timestamp': m._utc(), 'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}} assert m.db.db == {'collector': {'Serial_Number': 'R170000000000001'}, 'inverter': {'Version': 'V5.1.0E', 'Rated_Power': 300}, 'grid': {'Timestamp': m._utc(), 'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'Timestamp': m._utc(), 'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}}
assert m.db.get_db_value(Register.VERSION) == 'V5.1.0E' assert m.db.get_db_value(Register.VERSION) == 'V5.1.0E'
assert m.db.get_db_value(Register.TS_GRID) == m._utc() assert m.db.get_db_value(Register.TS_GRID) == m._utc()
assert m.new_data['inverter'] == True assert m.new_data['inverter'] == True

View File

@@ -29,23 +29,27 @@ target "_common" {
"type =sbom,generator=docker/scout-sbom-indexer:latest" "type =sbom,generator=docker/scout-sbom-indexer:latest"
] ]
annotations = [ annotations = [
"index:io.hass.version=${VERSION}",
"index:io.hass.type=addon", "index:io.hass.type=addon",
"index:io.hass.arch=aarch64|amd64", "index:io.hass.arch=armhf|aarch64|i386|amd64",
"index,manifest-descriptor:org.opencontainers.image.title=TSUN-Proxy", "index:org.opencontainers.image.title=TSUN-Proxy",
"index,manifest-descriptor:org.opencontainers.image.authors=Stefan Allius", "index:org.opencontainers.image.authors=Stefan Allius",
"index,manifest-descriptor:org.opencontainers.image.created=${BUILD_DATE}", "index:org.opencontainers.image.created=${BUILD_DATE}",
"index,manifest-descriptor:org.opencontainers.image.version=${VERSION}", "index:org.opencontainers.image.version=${VERSION}",
"index,manifest-descriptor:org.opencontainers.image.description=${DESCRIPTION}", "index:org.opencontainers.image.revision=${BRANCH}",
"index:org.opencontainers.image.description=${DESCRIPTION}",
"index:org.opencontainers.image.licenses=BSD-3-Clause", "index:org.opencontainers.image.licenses=BSD-3-Clause",
"index:org.opencontainers.image.source=https://github.com/s-allius/tsun-gen3-proxy/ha_addons/ha_addon", "index:org.opencontainers.image.source=https://github.com/s-allius/tsun-gen3-proxy/ha_addons/ha_addon"
] ]
labels = { labels = {
"io.hass.version" = "${VERSION}"
"io.hass.type" = "addon" "io.hass.type" = "addon"
"io.hass.arch" = "aarch64|amd64" "io.hass.arch" = "armhf|aarch64|i386|amd64"
"org.opencontainers.image.title" = "TSUN-Proxy" "org.opencontainers.image.title" = "TSUN-Proxy"
"org.opencontainers.image.authors" = "Stefan Allius" "org.opencontainers.image.authors" = "Stefan Allius"
"org.opencontainers.image.created" = "${BUILD_DATE}" "org.opencontainers.image.created" = "${BUILD_DATE}"
"org.opencontainers.image.version" = "${VERSION}" "org.opencontainers.image.version" = "${VERSION}"
"org.opencontainers.image.revision" = "${BRANCH}"
"org.opencontainers.image.description" = "${DESCRIPTION}" "org.opencontainers.image.description" = "${DESCRIPTION}"
"org.opencontainers.image.licenses" = "BSD-3-Clause" "org.opencontainers.image.licenses" = "BSD-3-Clause"
"org.opencontainers.image.source" = "https://github.com/s-allius/tsun-gen3-proxy/ha_addonsha_addon" "org.opencontainers.image.source" = "https://github.com/s-allius/tsun-gen3-proxy/ha_addonsha_addon"

View File

@@ -13,12 +13,12 @@
# 1 Build Base Image # # 1 Build Base Image #
###################### ######################
ARG BUILD_FROM="ghcr.io/hassio-addons/base:18.0.3" ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.5"
# hadolint ignore=DL3006 # hadolint ignore=DL3006
FROM $BUILD_FROM AS base FROM $BUILD_FROM AS base
# Installiere Python, pip und virtuelle Umgebungstools # Installiere Python, pip und virtuelle Umgebungstools
RUN apk add --no-cache python3=3.12.11-r0 py3-pip=25.1.1-r0 && \ RUN apk add --no-cache python3=3.12.10-r1 py3-pip=24.3.1-r0 && \
python -m venv /opt/venv && \ python -m venv /opt/venv && \
. /opt/venv/bin/activate . /opt/venv/bin/activate

View File

@@ -10,6 +10,8 @@ init: false
arch: arch:
- aarch64 - aarch64
- amd64 - amd64
- armhf
- armv7
startup: services startup: services
homeassistant_api: true homeassistant_api: true
map: map: