Compare commits

..

14 Commits

Author SHA1 Message Date
Stefan Allius
ccc7c588df update changelog 2025-06-21 12:10:52 +02:00
Stefan Allius
7e9dc5413a make more functions synchronous 2025-06-21 12:03:04 +02:00
Stefan Allius
aed82685b0 make send_start_cmd synchronous
https://sonarcloud.io/project/issues?open=AZeMhhhyyR1Wrs09sNya&id=s-allius_tsun-gen3-proxy
2025-06-21 11:56:20 +02:00
Stefan Allius
2e7222e9bb change send_modbus_cmd into a synchronous function 2025-06-21 11:47:17 +02:00
Stefan Allius
8566d6a0f8 Merge branch 'main' of https://github.com/s-allius/tsun-gen3-proxy into s-allius/issue463 2025-06-21 11:32:45 +02:00
Stefan Allius
6b69adfe83 remove duplicated line 2025-06-21 11:32:21 +02:00
Stefan Allius
9fb93acf08 reraise cancel arror after cleanup
https://sonarcloud.io/project/issues?open=AZeMhhltyR1Wrs09sNyc&id=s-allius_tsun-gen3-proxy
2025-06-21 11:31:41 +02:00
Stefan Allius
0980487daa replace constructor call with a literal
https://sonarcloud.io/project/issues?open=AZeMhhlEyR1Wrs09sNyb&id=s-allius_tsun-gen3-proxy
2025-06-21 11:21:19 +02:00
Stefan Allius
bacebbd649 S allius/issue456 (#462)
* - remove unused 32-bit architectures from the prebuild multiarch containers

* update po file
2025-06-21 10:41:47 +02:00
renovate[bot]
ebbb675e63 Update dependency flake8 to v7.3.0 (#459)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-21 10:27:45 +02:00
Stefan Allius
04fd9ed7f6 S allius/issue460 (#461)
* - Improve Makefile

* - Babel don't build new po file if only the pot creation-date was changed
2025-06-21 10:26:17 +02:00
renovate[bot]
f3c22c9853 Update dependency pytest to v8.4.1 (#458)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-20 10:51:02 +02:00
renovate[bot]
460db31fa6 Update python Docker tag to v3.13.5 (#453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-20 10:46:35 +02:00
renovate[bot]
144c9080cb Update dependency coverage to v7.9.1 (#454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-15 22:43:00 +02:00
18 changed files with 161 additions and 95 deletions

View File

@@ -1 +1 @@
3.13.4 3.13.5

View File

@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased] ## [unreleased]
- fix some SonarQube warnings
- remove unused 32-bit architectures
- Babel don't build new po file if only the pot creation-date was changed
- Improve Makefile
- Update dependency pytest-asyncio to v1 - Update dependency pytest-asyncio to v1
## [0.14.1] - 2025-05-31 ## [0.14.1] - 2025-05-31
@@ -19,7 +23,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- set no of pv modules for MS800 GEN3PLUS inverters - set no of pv modules for MS800 GEN3PLUS inverters
- fix the paths to copy the config.example.toml file during proxy start - fix the paths to copy the config.example.toml file during proxy start
- add MQTT topic `dcu_power` for setting output power on DCUs - add MQTT topic `dcu_power` for setting output power on DCUs
- add MQTT topic `dcu_power` for setting output power on DCUs
- Update ghcr.io/hassio-addons/base Docker tag to v17.2.5 - Update ghcr.io/hassio-addons/base Docker tag to v17.2.5
- fix a lot of pytest-asyncio problems in the unit tests - fix a lot of pytest-asyncio problems in the unit tests
- Cleanup startup code for Quart and the Proxy - Cleanup startup code for Quart and the Proxy

View File

@@ -1,27 +1,37 @@
.PHONY: build babel clean addon-dev addon-debug addon-rc addon-rel debug dev preview rc rel check-docker-compose install .PHONY: help build babel clean addon-dev addon-debug addon-rc addon-rel debug dev preview rc rel check-docker-compose install
babel: help: ## show help message
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[$$()% a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)
babel: ## build language files
$(MAKE) -C app $@ $(MAKE) -C app $@
build: build:
$(MAKE) -C ha_addons $@ $(MAKE) -C ha_addons $@
clean: clean: ## delete all built files
$(MAKE) -C app $@ $(MAKE) -C app $@
$(MAKE) -C ha_addons $@ $(MAKE) -C ha_addons $@
debug dev preview rc rel: debug dev preview rc rel: ## build docker container in <dev|debg|rc|rel> version
$(MAKE) -C app babel $(MAKE) -C app babel
$(MAKE) -C app $@ $(MAKE) -C app $@
addon-dev addon-debug addon-rc addon-rel: addon-dev addon-debug addon-rc addon-rel: ## build HA add-on in <dev|debg|rc|rel> version
$(MAKE) -C app babel $(MAKE) -C app babel
$(MAKE) -C ha_addons $(patsubst addon-%,%,$@) $(MAKE) -C ha_addons $(patsubst addon-%,%,$@)
check-docker-compose: check-docker-compose: ## check the docker-compose file
docker-compose config -q docker-compose config -q
install: PY_VER := $(shell cat .python-version)
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt install: ## install requirements into the pyenv and switch to proper venv
python3 -m pip install -r requirements-test.txt @pyenv local $(PY_VER) || { pyenv install $(PY_VER) && pyenv local $(PY_VER) || exit 1; }
@pyenv exec pip install --upgrade pip
@pyenv exec pip install -r requirements.txt
@pyenv exec pip install -r requirements-test.txt
pyenv exec python --version
run: ## run proxy locally out of the actual venv
pyenv exec python app/src/server.py -c /app/src/cnf

View File

@@ -55,7 +55,7 @@ $(BABEL_TRANSLATIONS)/%.pot : $(SRC)/.babel.cfg $(BABEL_INPUT)
$(BABEL_TRANSLATIONS)/%/LC_MESSAGES/messages.po : $(BABEL_TRANSLATIONS)/messages.pot $(BABEL_TRANSLATIONS)/%/LC_MESSAGES/messages.po : $(BABEL_TRANSLATIONS)/messages.pot
@mkdir -p $(@D) @mkdir -p $(@D)
@pybabel update --init-missing -i $< -d $(BABEL_TRANSLATIONS) -l $* @pybabel update --init-missing --ignore-pot-creation-date -i $< -d $(BABEL_TRANSLATIONS) -l $*
$(BABEL_TRANSLATIONS)/%/LC_MESSAGES/messages.mo : $(BABEL_TRANSLATIONS)/%/LC_MESSAGES/messages.po $(BABEL_TRANSLATIONS)/%/LC_MESSAGES/messages.mo : $(BABEL_TRANSLATIONS)/%/LC_MESSAGES/messages.po
@pybabel compile -d $(BABEL_TRANSLATIONS) -l $* @pybabel compile -d $(BABEL_TRANSLATIONS) -l $*

View File

@@ -53,7 +53,7 @@ target "_common" {
] ]
no-cache = false no-cache = false
platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7"] platforms = ["linux/amd64", "linux/arm64"]
} }
target "_debug" { target "_debug" {

View File

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

View File

@@ -341,9 +341,9 @@ class SolarmanV5(SolarmanBase):
self.log_lvl.clear() self.log_lvl.clear()
super().close() super().close()
async def send_start_cmd(self, snr: int, host: str, def send_start_cmd(self, snr: int, host: str,
forward: bool, forward: bool,
start_timeout=MB_CLIENT_DATA_UP): start_timeout=MB_CLIENT_DATA_UP):
self.no_forwarding = True self.no_forwarding = True
self.establish_inv_emu = forward self.establish_inv_emu = forward
self.snr = snr self.snr = snr

View File

@@ -193,7 +193,7 @@ class Message(ProtocolIfc):
return return
self.mb.build_msg(dev_id, func, addr, val, log_lvl) self.mb.build_msg(dev_id, func, addr, val, log_lvl)
async def send_modbus_cmd(self, func, addr, val, log_lvl) -> None: def send_modbus_cmd(self, func, addr, val, log_lvl) -> None:
self._send_modbus_cmd(Modbus.INV_ADDR, func, addr, val, log_lvl) self._send_modbus_cmd(Modbus.INV_ADDR, func, addr, val, log_lvl)
def _send_modbus_scan(self): def _send_modbus_scan(self):

View File

@@ -66,7 +66,7 @@ class ModbusTcp():
try: try:
async with ModbusConn(host, port) as inverter: async with ModbusConn(host, port) as inverter:
stream = inverter.local.stream stream = inverter.local.stream
await stream.send_start_cmd(snr, host, forward) stream.send_start_cmd(snr, host, forward)
await stream.ifc.loop() await stream.ifc.loop()
logger.info(f'[{stream.node_id}:{stream.conn_no}] ' logger.info(f'[{stream.node_id}:{stream.conn_no}] '
f'Connection closed - Shutdown: ' f'Connection closed - Shutdown: '

View File

@@ -112,7 +112,7 @@ class Mqtt(metaclass=Singleton):
except asyncio.CancelledError: except asyncio.CancelledError:
logger_mqtt.debug("MQTT task cancelled") logger_mqtt.debug("MQTT task cancelled")
self.__client = None self.__client = None
return raise
except Exception: except Exception:
# self.inc_counter('SW_Exception') # fixme # self.inc_counter('SW_Exception') # fixme
self.ctime = None self.ctime = None
@@ -151,7 +151,7 @@ class Mqtt(metaclass=Singleton):
if self.__cb_mqtt_is_up: if self.__cb_mqtt_is_up:
await self.__cb_mqtt_is_up() await self.__cb_mqtt_is_up()
async def _out_coeff(self, message): def _out_coeff(self, message):
payload = message.payload.decode("UTF-8") payload = message.payload.decode("UTF-8")
try: try:
val = round(float(payload) * 1024/100) val = round(float(payload) * 1024/100)
@@ -160,9 +160,9 @@ class Mqtt(metaclass=Singleton):
'the range 0..100,' 'the range 0..100,'
f' got: {payload}') f' got: {payload}')
else: else:
await self._modbus_cmd(message, self._modbus_cmd(message,
Modbus.WRITE_SINGLE_REG, Modbus.WRITE_SINGLE_REG,
0, 0x202c, val) 0, 0x202c, val)
except Exception: except Exception:
pass pass
@@ -182,7 +182,7 @@ class Mqtt(metaclass=Singleton):
else: else:
logger_mqtt.warning(f'Node_id: {node_id} not found') logger_mqtt.warning(f'Node_id: {node_id} not found')
async def _modbus_cmd(self, message, func, params=0, addr=0, val=0): def _modbus_cmd(self, message, func, params=0, addr=0, val=0):
payload = message.payload.decode("UTF-8") payload = message.payload.decode("UTF-8")
for fnc in self.each_inverter(message, "send_modbus_cmd"): for fnc in self.each_inverter(message, "send_modbus_cmd"):
res = payload.split(',') res = payload.split(',')
@@ -195,7 +195,7 @@ class Mqtt(metaclass=Singleton):
elif params == 2: elif params == 2:
addr = int(res[0], base=16) addr = int(res[0], base=16)
val = int(res[1]) # lenght val = int(res[1]) # lenght
await fnc(func, addr, val, logging.INFO) fnc(func, addr, val, logging.INFO)
async def _at_cmd(self, message): async def _at_cmd(self, message):
payload = message.payload.decode("UTF-8") payload = message.payload.decode("UTF-8")

View File

@@ -60,7 +60,7 @@ class Server():
@app.context_processor @app.context_processor
def utility_processor(): def utility_processor():
return dict(version=self.version) return {'version': self.version}
def parse_args(self, arg_list: list[str] | None): def parse_args(self, arg_list: list[str] | None):
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()

View File

@@ -29,9 +29,9 @@ def get_tz():
@web.context_processor @web.context_processor
def utility_processor(): def utility_processor():
return dict(lang=babel_get_locale(), return {'lang': babel_get_locale(),
lang_str=LANGUAGES.get(str(babel_get_locale()), "English"), 'lang_str': LANGUAGES.get(str(babel_get_locale()), "English"),
languages=LANGUAGES) 'languages': LANGUAGES}
@web.route('/language/<language>') @web.route('/language/<language>')

View File

@@ -3,7 +3,8 @@ import pytest
import asyncio import asyncio
import aiomqtt import aiomqtt
import logging import logging
from aiomqtt import MqttError from aiomqtt import MqttError, MessagesIterator
from aiomqtt import Message as AiomqttMessage
from mock import patch, Mock from mock import patch, Mock
from async_stream import AsyncIfcImpl from async_stream import AsyncIfcImpl
@@ -34,6 +35,26 @@ def test_hostname():
# else: # else:
return 'test.mosquitto.org' return 'test.mosquitto.org'
@pytest.fixture(scope="function")
def aiomqtt_mock(monkeypatch):
recv_que = asyncio.Queue()
async def my_aenter(self):
return self
async def my_subscribe(self, *arg):
return
async def my_anext(self):
return await recv_que.get()
async def my_receive(self, topic: str, payload: bytes):
msg = AiomqttMessage(topic, payload,qos=0, retain=False, mid=0, properties=None)
await recv_que.put(msg)
await asyncio.sleep(0) # dispath the msg
monkeypatch.setattr(aiomqtt.Client, "__aenter__", my_aenter)
monkeypatch.setattr(aiomqtt.Client, "subscribe", my_subscribe)
monkeypatch.setattr(MessagesIterator, "__anext__", my_anext)
monkeypatch.setattr(Mqtt, "receive", my_receive, False)
@pytest.fixture @pytest.fixture
def config_mqtt_conn(test_hostname, test_port): def config_mqtt_conn(test_hostname, test_port):
Config.act_config = {'mqtt':{'host': test_hostname, 'port': test_port, 'user': '', 'passwd': ''}, Config.act_config = {'mqtt':{'host': test_hostname, 'port': test_port, 'user': '', 'passwd': ''},
@@ -161,13 +182,17 @@ async def test_ha_reconnect(config_mqtt_conn):
await m.close() await m.close()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_mqtt_no_config(config_no_conn): async def test_mqtt_no_config(config_no_conn, monkeypatch):
_ = config_no_conn _ = config_no_conn
assert asyncio.get_running_loop() assert asyncio.get_running_loop()
on_connect = asyncio.Event() on_connect = asyncio.Event()
async def cb(): async def cb():
on_connect.set() on_connect.set()
async def my_publish(*args):
return
monkeypatch.setattr(aiomqtt.Client, "publish", my_publish)
try: try:
m = Mqtt(cb) m = Mqtt(cb)
@@ -176,9 +201,9 @@ async def test_mqtt_no_config(config_no_conn):
assert not on_connect.is_set() assert not on_connect.is_set()
try: try:
await m.publish('homeassistant/status', 'online') await m.publish('homeassistant/status', 'online')
assert False assert m.published == 1
except Exception: except Exception:
pass assert False
except TimeoutError: except TimeoutError:
assert False assert False
finally: finally:
@@ -250,92 +275,119 @@ async def test_mqtt_except_def_config(config_def_conn, monkeypatch, caplog):
assert 'MQTT is unconfigured; Check your config.toml!' in caplog.text assert 'MQTT is unconfigured; Check your config.toml!' in caplog.text
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_msg_dispatch(config_mqtt_conn, spy_modbus_cmd): async def test_mqtt_dispatch(config_mqtt_conn, aiomqtt_mock, spy_modbus_cmd):
_ = config_mqtt_conn _ = config_mqtt_conn
_ = aiomqtt_mock
spy = spy_modbus_cmd spy = spy_modbus_cmd
try: try:
m = Mqtt(None) m = Mqtt(None)
msg = aiomqtt.Message(topic= 'homeassistant/status', payload= b'online', qos= 0, retain = False, mid= 0, properties= None) assert m.ha_restarts == 0
await m.dispatch_msg(msg) await m.receive('homeassistant/status', b'online') # send the message
assert m.ha_restarts == 1 assert m.ha_restarts == 1
msg = aiomqtt.Message(topic= 'tsun/inv_1/rated_load', payload= b'2', qos= 0, retain = False, mid= 0, properties= None) await m.receive(topic= 'tsun/inv_1/rated_load', payload= b'2')
await m.dispatch_msg(msg) spy.assert_called_once_with(Modbus.WRITE_SINGLE_REG, 0x2008, 2, logging.INFO)
spy.assert_awaited_once_with(Modbus.WRITE_SINGLE_REG, 0x2008, 2, logging.INFO)
spy.reset_mock()
msg = aiomqtt.Message(topic= 'tsun/inv_1/out_coeff', payload= b'100', qos= 0, retain = False, mid= 0, properties= None)
await m.dispatch_msg(msg)
spy.assert_awaited_once_with(Modbus.WRITE_SINGLE_REG, 0x202c, 1024, logging.INFO)
spy.reset_mock()
msg = aiomqtt.Message(topic= 'tsun/inv_1/out_coeff', payload= b'50', qos= 0, retain = False, mid= 0, properties= None)
await m.dispatch_msg(msg)
spy.assert_awaited_once_with(Modbus.WRITE_SINGLE_REG, 0x202c, 512, logging.INFO)
spy.reset_mock()
msg = aiomqtt.Message(topic= 'tsun/inv_1/modbus_read_regs', payload= b'0x3000, 10', qos= 0, retain = False, mid= 0, properties= None)
await m.dispatch_msg(msg)
spy.assert_awaited_once_with(Modbus.READ_REGS, 0x3000, 10, logging.INFO)
spy.reset_mock() spy.reset_mock()
msg = aiomqtt.Message(topic= 'tsun/inv_1/modbus_read_inputs', payload= b'0x3000, 10', qos= 0, retain = False, mid= 0, properties= None) await m.receive(topic= 'tsun/inv_1/out_coeff', payload= b'100')
await m.dispatch_msg(msg) spy.assert_called_once_with(Modbus.WRITE_SINGLE_REG, 0x202c, 1024, logging.INFO)
spy.assert_awaited_once_with(Modbus.READ_INPUTS, 0x3000, 10, logging.INFO)
spy.reset_mock()
await m.receive(topic= 'tsun/inv_1/out_coeff', payload= b'50')
spy.assert_called_once_with(Modbus.WRITE_SINGLE_REG, 0x202c, 512, logging.INFO)
spy.reset_mock()
await m.receive(topic= 'tsun/inv_1/modbus_read_regs', payload= b'0x3000, 10')
spy.assert_called_once_with(Modbus.READ_REGS, 0x3000, 10, logging.INFO)
spy.reset_mock()
await m.receive(topic= 'tsun/inv_1/modbus_read_inputs', payload= b'0x3000, 10')
spy.assert_called_once_with(Modbus.READ_INPUTS, 0x3000, 10, logging.INFO)
# test dispatching with empty mapping table # test dispatching with empty mapping table
m.topic_defs.clear() m.topic_defs.clear()
spy.reset_mock() spy.reset_mock()
msg = aiomqtt.Message(topic= 'tsun/inv_1/modbus_read_inputs', payload= b'0x3000, 10', qos= 0, retain = False, mid= 0, properties= None) await m.receive(topic= 'tsun/inv_1/modbus_read_inputs', payload= b'0x3000, 10')
await m.dispatch_msg(msg)
spy.assert_not_called() spy.assert_not_called()
# test dispatching with incomplete mapping table - invalid fnc defined # test dispatching with incomplete mapping table - invalid fnc defined
m.topic_defs.append( m.topic_defs.append(
{'prefix': 'entity_prefix', 'topic': '/+/modbus_read_inputs', {'prefix': 'entity_prefix', 'topic': '/+/modbus_read_inputs',
'full_topic': 'tsun/+/modbus_read_inputs', 'fnc': 'invalid'} 'full_topic': 'tsun/+/modbus_read_inputs', 'fnc': 'addr'}
) )
spy.reset_mock() spy.reset_mock()
msg = aiomqtt.Message(topic= 'tsun/inv_1/modbus_read_inputs', payload= b'0x3000, 10', qos= 0, retain = False, mid= 0, properties= None) await m.receive(topic= 'tsun/inv_1/modbus_read_inputs', payload= b'0x3000, 10')
await m.dispatch_msg(msg)
spy.assert_not_called() spy.assert_not_called()
except MqttError:
assert False
except Exception:
assert False
finally: finally:
await m.close() await m.close()
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_msg_dispatch_err(config_mqtt_conn, spy_modbus_cmd): async def test_mqtt_dispatch_cb(config_mqtt_conn, aiomqtt_mock):
_ = config_mqtt_conn _ = config_mqtt_conn
_ = aiomqtt_mock
on_connect = asyncio.Event()
async def cb():
on_connect.set()
try:
m = Mqtt(cb)
assert m.ha_restarts == 0
await m.receive('homeassistant/status', b'online') # send the message
assert on_connect.is_set()
assert m.ha_restarts == 1
except MqttError:
assert False
except Exception:
assert False
finally:
await m.close()
@pytest.mark.asyncio
async def test_mqtt_dispatch_err(config_mqtt_conn, aiomqtt_mock, spy_modbus_cmd, caplog):
_ = config_mqtt_conn
_ = aiomqtt_mock
spy = spy_modbus_cmd spy = spy_modbus_cmd
LOGGER = logging.getLogger("mqtt")
LOGGER.propagate = True
LOGGER.setLevel(logging.INFO)
try: try:
m = Mqtt(None) m = Mqtt(None)
# test out of range param # test out of range param
msg = aiomqtt.Message(topic= 'tsun/inv_1/out_coeff', payload= b'-1', qos= 0, retain = False, mid= 0, properties= None) await m.receive(topic= 'tsun/inv_1/out_coeff', payload= b'-1')
await m.dispatch_msg(msg)
spy.assert_not_called() spy.assert_not_called()
# test unknown node_id # test unknown node_id
spy.reset_mock() await m.receive(topic= 'tsun/inv_2/out_coeff', payload= b'2')
msg = aiomqtt.Message(topic= 'tsun/inv_2/out_coeff', payload= b'2', qos= 0, retain = False, mid= 0, properties= None)
await m.dispatch_msg(msg)
spy.assert_not_called() spy.assert_not_called()
# test invalid fload param # test invalid fload param
spy.reset_mock() await m.receive(topic= 'tsun/inv_1/out_coeff', payload= b'2, 3')
msg = aiomqtt.Message(topic= 'tsun/inv_1/out_coeff', payload= b'2, 3', qos= 0, retain = False, mid= 0, properties= None) spy.assert_not_called()
await m.dispatch_msg(msg)
await m.receive(topic= 'tsun/inv_1/modbus_read_regs', payload= b'0x3000, 10, 7')
spy.assert_not_called() spy.assert_not_called()
spy.reset_mock() await m.receive(topic= 'tsun/inv_1/dcu_power', payload= b'100W')
msg = aiomqtt.Message(topic= 'tsun/inv_1/modbus_read_regs', payload= b'0x3000, 10, 7', qos= 0, retain = False, mid= 0, properties= None)
await m.dispatch_msg(msg)
spy.assert_not_called()
spy.reset_mock()
msg = aiomqtt.Message(topic= 'tsun/inv_1/dcu_power', payload= b'100W', qos= 0, retain = False, mid= 0, properties= None)
await m.dispatch_msg(msg)
spy.assert_not_called() spy.assert_not_called()
with caplog.at_level(logging.INFO):
msg = aiomqtt.Message(topic= 'tsun/inv_1/out_coeff', payload= b'2', qos= 0, retain = False, mid= 0, properties= None)
for _ in m.each_inverter(msg, "addr"):
pass # do nothing here
assert 'Cmd not supported by: inv_1/' in caplog.text
except MqttError:
assert False
except Exception:
assert False
finally: finally:
await m.close() await m.close()

View File

@@ -1624,7 +1624,7 @@ async def test_msg_build_modbus_req(my_loop, config_tsun_inv1, device_ind_msg, d
assert m.ifc.tx_fifo.get()==device_rsp_msg assert m.ifc.tx_fifo.get()==device_rsp_msg
assert m.ifc.fwd_fifo.get()==device_ind_msg assert m.ifc.fwd_fifo.get()==device_ind_msg
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG) m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs assert 0 == m.send_msg_ofs
assert m.ifc.fwd_fifo.get() == b'' assert m.ifc.fwd_fifo.get() == b''
assert m.sent_pdu == b'' # modbus command must be ignore, cause connection is still not up assert m.sent_pdu == b'' # modbus command must be ignore, cause connection is still not up
@@ -1642,7 +1642,7 @@ async def test_msg_build_modbus_req(my_loop, config_tsun_inv1, device_ind_msg, d
assert m.ifc.tx_fifo.get()==inverter_rsp_msg assert m.ifc.tx_fifo.get()==inverter_rsp_msg
assert m.ifc.fwd_fifo.get()==inverter_ind_msg assert m.ifc.fwd_fifo.get()==inverter_ind_msg
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG) m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs assert 0 == m.send_msg_ofs
assert m.ifc.fwd_fifo.get() == b'' assert m.ifc.fwd_fifo.get() == b''
assert m.sent_pdu == msg_modbus_cmd assert m.sent_pdu == msg_modbus_cmd
@@ -2318,7 +2318,7 @@ async def test_start_client_mode(my_loop, config_tsun_inv1, str_test_ip):
assert m.no_forwarding == False assert m.no_forwarding == False
assert m.mb_timer.tim == None assert m.mb_timer.tim == None
assert asyncio.get_running_loop() == m.mb_timer.loop assert asyncio.get_running_loop() == m.mb_timer.loop
await m.send_start_cmd(get_sn_int(), str_test_ip, False, m.mb_first_timeout) m.send_start_cmd(get_sn_int(), str_test_ip, False, m.mb_first_timeout)
assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x01\x00!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x030\x00\x000J\xde\xf1\x15') assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x01\x00!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x030\x00\x000J\xde\xf1\x15')
assert m.db.get_db_value(Register.IP_ADDRESS) == str_test_ip assert m.db.get_db_value(Register.IP_ADDRESS) == str_test_ip
assert isclose(m.db.get_db_value(Register.POLLING_INTERVAL), 0.5) assert isclose(m.db.get_db_value(Register.POLLING_INTERVAL), 0.5)
@@ -2351,7 +2351,7 @@ async def test_start_client_mode_scan(config_tsun_scan_dcu, str_test_ip, dcu_mod
assert m.no_forwarding == False assert m.no_forwarding == False
assert m.mb_timer.tim == None assert m.mb_timer.tim == None
assert asyncio.get_running_loop() == m.mb_timer.loop assert asyncio.get_running_loop() == m.mb_timer.loop
await m.send_start_cmd(get_dcu_sn_int(), str_test_ip, False, m.mb_first_timeout) m.send_start_cmd(get_dcu_sn_int(), str_test_ip, False, m.mb_first_timeout)
assert m.mb_start_reg == 0x0000 assert m.mb_start_reg == 0x0000
assert m.mb_step == 0x100 assert m.mb_step == 0x100
assert m.mb_bytes == 0x2d assert m.mb_bytes == 0x2d
@@ -2662,6 +2662,7 @@ async def test_proxy_dcu_cmd(my_loop, config_tsun_dcu1, patch_open_connection, d
assert l.db.stat['proxy']['AT_Command'] == 0 assert l.db.stat['proxy']['AT_Command'] == 0
assert l.db.stat['proxy']['AT_Command_Blocked'] == 0 assert l.db.stat['proxy']['AT_Command_Blocked'] == 0
assert l.db.stat['proxy']['Modbus_Command'] == 0 assert l.db.stat['proxy']['Modbus_Command'] == 0
assert 2 == l.db.get_db_value(Register.NO_INPUTS, 0)
l.append_msg(dcu_command_rsp_msg) l.append_msg(dcu_command_rsp_msg)
l.read() # read at resp l.read() # read at resp

View File

@@ -144,7 +144,7 @@ async def test_emu_start(my_loop, config_tsun_inv1, msg_modbus_rsp, str_test_ip,
inv = InvStream(msg_modbus_rsp) inv = InvStream(msg_modbus_rsp)
assert asyncio.get_running_loop() == inv.mb_timer.loop assert asyncio.get_running_loop() == inv.mb_timer.loop
await inv.send_start_cmd(get_sn_int(), str_test_ip, True, inv.mb_first_timeout) inv.send_start_cmd(get_sn_int(), str_test_ip, True, inv.mb_first_timeout)
inv.read() # read complete msg, and dispatch msg inv.read() # read complete msg, and dispatch msg
assert not inv.header_valid # must be invalid, since msg was handled and buffer flushed assert not inv.header_valid # must be invalid, since msg was handled and buffer flushed
assert inv.msg_count == 1 assert inv.msg_count == 1
@@ -161,7 +161,7 @@ async def test_snd_hb(my_loop, config_tsun_inv1, heartbeat_ind):
inv = InvStream() inv = InvStream()
cld = CldStream(inv) cld = CldStream(inv)
# await inv.send_start_cmd(get_sn_int(), str_test_ip, False, inv.mb_first_timeout) # inv.send_start_cmd(get_sn_int(), str_test_ip, False, inv.mb_first_timeout)
cld.send_heartbeat_cb(0) cld.send_heartbeat_cb(0)
assert cld.ifc.tx_fifo.peek() == heartbeat_ind assert cld.ifc.tx_fifo.peek() == heartbeat_ind
cld.close() cld.close()
@@ -178,7 +178,7 @@ async def test_snd_inv_data(my_loop, config_tsun_inv1, inverter_ind_msg, inverte
inv.db.set_db_def_value(Register.GRID_FREQUENCY, 50.05) inv.db.set_db_def_value(Register.GRID_FREQUENCY, 50.05)
inv.db.set_db_def_value(Register.PROD_COMPL_TYPE, 6) inv.db.set_db_def_value(Register.PROD_COMPL_TYPE, 6)
assert asyncio.get_running_loop() == inv.mb_timer.loop assert asyncio.get_running_loop() == inv.mb_timer.loop
await inv.send_start_cmd(get_sn_int(), str_test_ip, False, inv.mb_first_timeout) inv.send_start_cmd(get_sn_int(), str_test_ip, False, inv.mb_first_timeout)
inv.db.set_db_def_value(Register.DATA_UP_INTERVAL, 17) # set test value inv.db.set_db_def_value(Register.DATA_UP_INTERVAL, 17) # set test value
cld = CldStream(inv) cld = CldStream(inv)
@@ -213,7 +213,7 @@ async def test_rcv_invalid(my_loop, config_tsun_inv1, inverter_ind_msg, inverter
_ = config_tsun_inv1 _ = config_tsun_inv1
inv = InvStream() inv = InvStream()
assert asyncio.get_running_loop() == inv.mb_timer.loop assert asyncio.get_running_loop() == inv.mb_timer.loop
await inv.send_start_cmd(get_sn_int(), str_test_ip, False, inv.mb_first_timeout) inv.send_start_cmd(get_sn_int(), str_test_ip, False, inv.mb_first_timeout)
inv.db.set_db_def_value(Register.DATA_UP_INTERVAL, 17) # set test value inv.db.set_db_def_value(Register.DATA_UP_INTERVAL, 17) # set test value
cld = CldStream(inv) cld = CldStream(inv)

View File

@@ -2411,14 +2411,14 @@ async def test_msg_build_modbus_req(config_tsun_inv1, msg_modbus_cmd):
_ = config_tsun_inv1 _ = config_tsun_inv1
m = MemoryStream(b'', (0,), True) m = MemoryStream(b'', (0,), True)
m.id_str = b"R170000000000001" m.id_str = b"R170000000000001"
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG) m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs assert 0 == m.send_msg_ofs
assert m.ifc.fwd_fifo.get() == b'' assert m.ifc.fwd_fifo.get() == b''
assert m.ifc.tx_fifo.get() == b'' assert m.ifc.tx_fifo.get() == b''
assert m.sent_pdu == b'' assert m.sent_pdu == b''
m.state = State.up m.state = State.up
await m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG) m.send_modbus_cmd(Modbus.WRITE_SINGLE_REG, 0x2008, 0, logging.DEBUG)
assert 0 == m.send_msg_ofs assert 0 == m.send_msg_ofs
assert m.ifc.fwd_fifo.get() == b'' assert m.ifc.fwd_fifo.get() == b''
assert m.ifc.tx_fifo.get() == b'' assert m.ifc.tx_fifo.get() == b''

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: tsun-gen3-proxy 0.14.0\n" "Project-Id-Version: tsun-gen3-proxy 0.14.0\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-05-18 15:30+0200\n" "POT-Creation-Date: 2025-05-13 22:34+0200\n"
"PO-Revision-Date: 2025-04-18 16:24+0200\n" "PO-Revision-Date: 2025-04-18 16:24+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de\n" "Language: de\n"

View File

@@ -59,7 +59,7 @@ target "_common" {
] ]
no-cache = false no-cache = false
platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7"] platforms = ["linux/amd64", "linux/arm64"]
} }
target "_debug" { target "_debug" {