* use random IP adresses for unit tests

* Docker: The description ist missing (#171)

Fixes #167

* S allius/issue167 (#172)

* cleanup

* Sonar qube 6 (#174)

* test class ModbusConn
This commit is contained in:
Stefan Allius
2024-08-26 23:49:23 +02:00
committed by GitHub
parent 5db3fbf495
commit 2b8dacb0de
18 changed files with 422 additions and 135 deletions

View File

@@ -62,18 +62,10 @@ RUN python -m pip install --no-cache --no-index /root/wheels/* && \
COPY --chmod=0700 entrypoint.sh /root/entrypoint.sh
COPY config .
COPY src .
RUN echo ${VERSION} > /proxy-version.txt
RUN date > /build-date.txt
RUN echo ${VERSION} > /proxy-version.txt \
&& date > /build-date.txt
EXPOSE 5005 8127 10000
# command to run on container start
ENTRYPOINT ["/root/entrypoint.sh"]
CMD [ "python3", "./server.py" ]
LABEL org.opencontainers.image.title="TSUN Gen3 Proxy"
LABEL org.opencontainers.image.authors="Stefan Allius"
LABEL org.opencontainers.image.source=https://github.com/s-allius/tsun-gen3-proxy
LABEL org.opencontainers.image.description='This proxy enables a reliable connection between TSUN third generation inverters (eg. TSOL MS600, MS800, MS2000) and an MQTT broker to integrate the inverter into typical home automations.'
LABEL org.opencontainers.image.licenses="BSD-3-Clause"
LABEL org.opencontainers.image.vendor="Stefan Allius"

View File

@@ -17,6 +17,7 @@ VERSION="${VERSION:1}"
arr=(${VERSION//./ })
MAJOR=${arr[0]}
IMAGE=tsun-gen3-proxy
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
@@ -26,44 +27,22 @@ IMAGE=docker.io/sallius/${IMAGE}
VERSION=${VERSION}+$1
elif [[ $1 == rc ]] || [[ $1 == rel ]] || [[ $1 == preview ]] ;then
IMAGE=ghcr.io/s-allius/${IMAGE}
echo 'login to ghcr.io'
echo $GHCR_TOKEN | docker login ghcr.io -u s-allius --password-stdin
else
echo argument missing!
echo try: $0 '[debug|dev|preview|rc|rel]'
exit 1
fi
if [[ $1 == debug ]] ;then
BUILD_ENV="dev"
else
BUILD_ENV="production"
fi
BUILD_CMD="buildx build --push --build-arg VERSION=${VERSION} --build-arg environment=${BUILD_ENV} --attest type=provenance,mode=max --attest type=sbom,generator=docker/scout-sbom-indexer:latest"
ARCH="--platform linux/amd64,linux/arm64,linux/arm/v7"
LABELS="--label org.opencontainers.image.created=${BUILD_DATE} --label org.opencontainers.image.version=${VERSION} --label org.opencontainers.image.revision=${BRANCH}"
export IMAGE
export VERSION
export BUILD_DATE
export BRANCH
export MAJOR
echo version: $VERSION build-date: $BUILD_DATE image: $IMAGE
if [[ $1 == debug ]];then
docker ${BUILD_CMD} ${ARCH} ${LABELS} --build-arg "LOG_LVL=DEBUG" -t ${IMAGE}:debug app
elif [[ $1 == dev ]];then
docker ${BUILD_CMD} ${ARCH} ${LABELS} -t ${IMAGE}:dev app
elif [[ $1 == preview ]];then
echo 'login to ghcr.io'
echo $GHCR_TOKEN | docker login ghcr.io -u s-allius --password-stdin
docker ${BUILD_CMD} ${ARCH} ${LABELS} -t ${IMAGE}:preview -t ${IMAGE}:${VERSION} app
elif [[ $1 == rc ]];then
echo 'login to ghcr.io'
echo $GHCR_TOKEN | docker login ghcr.io -u s-allius --password-stdin
docker ${BUILD_CMD} ${ARCH} ${LABELS} -t ${IMAGE}:rc -t ${IMAGE}:${VERSION} app
elif [[ $1 == rel ]];then
echo 'login to ghcr.io'
echo $GHCR_TOKEN | docker login ghcr.io -u s-allius --password-stdin
docker ${BUILD_CMD} ${ARCH} ${LABELS} --no-cache -t ${IMAGE}:latest -t ${IMAGE}:${MAJOR} -t ${IMAGE}:${VERSION} app
fi
docker buildx bake -f app/docker-bake.hcl $1
echo -e "${BLUE} => checking docker-compose.yaml file${NC}"
docker-compose config -q

93
app/docker-bake.hcl Normal file
View File

@@ -0,0 +1,93 @@
variable "IMAGE" {
default = "tsun-gen3-proxy"
}
variable "VERSION" {
default = "0.0.0"
}
variable "MAJOR" {
default = "0"
}
variable "BUILD_DATE" {
default = "dev"
}
variable "BRANCH" {
default = ""
}
variable "DESCRIPTION" {
default = "This proxy enables a reliable connection between TSUN third generation inverters (eg. TSOL MS600, MS800, MS2000) and an MQTT broker to integrate the inverter into typical home automations."
}
target "_common" {
context = "app"
dockerfile = "Dockerfile"
args = {
VERSION = "${VERSION}"
environment = "production"
}
attest = [
"type =provenance,mode=max",
"type =sbom,generator=docker/scout-sbom-indexer:latest"
]
annotations = [
"index:org.opencontainers.image.title=TSUN Gen3 Proxy",
"index:org.opencontainers.image.authors=Stefan Allius",
"index:org.opencontainers.image.created=${BUILD_DATE}",
"index:org.opencontainers.image.version=${VERSION}",
"index:org.opencontainers.image.revision=${BRANCH}",
"index:org.opencontainers.image.description=${DESCRIPTION}",
"index:org.opencontainers.image.licenses=BSD-3-Clause",
"index:org.opencontainers.image.source=https://github.com/s-allius/tsun-gen3-proxy"
]
labels = {
"org.opencontainers.image.title" = "TSUN Gen3 Proxy"
"org.opencontainers.image.authors" = "Stefan Allius"
"org.opencontainers.image.created" = "${BUILD_DATE}"
"org.opencontainers.image.version" = "${VERSION}"
"org.opencontainers.image.revision" = "${BRANCH}"
"org.opencontainers.image.description" = "${DESCRIPTION}"
"org.opencontainers.image.licenses" = "BSD-3-Clause"
"org.opencontainers.image.source" = "https://github.com/s-allius/tsun-gen3-proxy"
}
output = [
"type=image,push=true"
]
no-cache = false
platforms = ["linux/amd64", "linux/arm64", "linux/arm/v7"]
}
target "_debug" {
args = {
LOG_LVL = "DEBUG"
environment = "dev"
}
}
target "_prod" {
args = {
}
}
target "debug" {
inherits = ["_common", "_debug"]
tags = ["${IMAGE}:debug"]
}
target "dev" {
inherits = ["_common"]
tags = ["${IMAGE}:dev"]
}
target "preview" {
inherits = ["_common", "_prod"]
tags = ["${IMAGE}:dev", "${IMAGE}:${VERSION}"]
}
target "rc" {
inherits = ["_common", "_prod"]
tags = ["${IMAGE}:rc", "${IMAGE}:${VERSION}"]
}
target "rel" {
inherits = ["_common", "_prod"]
tags = ["${IMAGE}:latest", "${IMAGE}:${MAJOR}", "${IMAGE}:${VERSION}"]
no-cache = true
}

View File

@@ -3,10 +3,15 @@ import logging
import traceback
import time
from asyncio import StreamReader, StreamWriter
from messages import hex_dump_memory, State
from typing import Self
from itertools import count
if __name__ == "app.src.async_stream":
from app.src.messages import hex_dump_memory, State
else: # pragma: no cover
from messages import hex_dump_memory, State
import gc
logger = logging.getLogger('conn')

View File

@@ -1,7 +1,12 @@
import logging
from asyncio import StreamReader, StreamWriter
from async_stream import AsyncStream
from gen3plus.solarman_v5 import SolarmanV5
if __name__ == "app.src.gen3plus.connection_g3p":
from app.src.async_stream import AsyncStream
from app.src.gen3plus.solarman_v5 import SolarmanV5
else: # pragma: no cover
from async_stream import AsyncStream
from gen3plus.solarman_v5 import SolarmanV5
logger = logging.getLogger('conn')

View File

@@ -3,11 +3,18 @@ import traceback
import json
import asyncio
from asyncio import StreamReader, StreamWriter
from config import Config
from inverter import Inverter
from gen3plus.connection_g3p import ConnectionG3P
from aiomqtt import MqttCodeError
from infos import Infos
if __name__ == "app.src.gen3plus.inverter_g3p":
from app.src.config import Config
from app.src.inverter import Inverter
from app.src.gen3plus.connection_g3p import ConnectionG3P
from app.src.infos import Infos
else: # pragma: no cover
from config import Config
from inverter import Inverter
from gen3plus.connection_g3p import ConnectionG3P
from infos import Infos
logger_mqtt = logging.getLogger('mqtt')

View File

@@ -172,7 +172,7 @@ class SolarmanV5(Message):
self.db.set_db_def_value(Register.POLLING_INTERVAL,
self.mb_timeout)
self.db.set_db_def_value(Register.HEARTBEAT_INTERVAL,
120) # fixme
120)
self.new_data['controller'] = True
self.state = State.up

View File

@@ -1,11 +1,15 @@
import asyncio
import logging
import json
from config import Config
from mqtt import Mqtt
from infos import Infos
if __name__ == "app.src.inverter":
from app.src.config import Config
from app.src.mqtt import Mqtt
from app.src.infos import Infos
else: # pragma: no cover
from config import Config
from mqtt import Mqtt
from infos import Infos
# logger = logging.getLogger('conn')
logger_mqtt = logging.getLogger('mqtt')
@@ -72,7 +76,7 @@ class Inverter():
Infos.new_stat_data[key] = False
@classmethod
def class_close(cls, loop) -> None:
def class_close(cls, loop) -> None: # pragma: no cover
logging.debug('Inverter.class_close')
logging.info('Close MQTT Task')
loop.run_until_complete(cls.mqtt.close())

View File

@@ -1,9 +1,13 @@
import logging
import traceback
import asyncio
from config import Config
from gen3plus.inverter_g3p import InverterG3P
if __name__ == "app.src.modbus_tcp":
from app.src.config import Config
from app.src.gen3plus.inverter_g3p import InverterG3P
else: # pragma: no cover
from config import Config
from gen3plus.inverter_g3p import InverterG3P
logger = logging.getLogger('conn')

View File

@@ -325,7 +325,11 @@ def test_build_ha_conf1(contr_data_seq):
assert tests==4
def test_build_ha_conf2(contr_data_seq):
i = InfosG3()
i.static_init() # initialize counter
tests = 0
for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'):
if id == 'out_power_123':
@@ -344,9 +348,9 @@ def test_build_ha_conf1(contr_data_seq):
assert d_json == json.dumps({"name": "Active Inverter Connections", "stat_t": "tsun/proxy/proxy", "dev_cla": None, "stat_cla": None, "uniq_id": "inv_count_456", "val_tpl": "{{value_json['Inverter_Cnt'] | int}}", "ic": "mdi:counter", "dev": {"name": "Proxy", "sa": "Proxy", "mdl": "proxy", "mf": "Stefan Allius", "sw": "unknown", "ids": ["proxy"]}, "o": {"name": "proxy", "sw": "unknown"}})
tests +=1
assert tests==5
assert tests==1
def test_build_ha_conf2(contr_data_seq, inv_data_seq, inv_data_seq2):
def test_build_ha_conf3(contr_data_seq, inv_data_seq, inv_data_seq2):
i = InfosG3()
for key, result in i.parse (contr_data_seq):
pass # side effect in calling i.parse()
@@ -397,10 +401,7 @@ def test_must_incr_total(inv_data_seq2, inv_data_seq2_zero):
assert json.dumps(i.db['env']) == json.dumps({"Inverter_Temp": 23})
tests = 0
for key, update in i.parse (inv_data_seq2):
if key == 'total':
assert update == False
tests +=1
elif key == 'env':
if key == 'total' or key == 'env':
assert update == False
tests +=1
@@ -442,10 +443,7 @@ def test_must_incr_total2(inv_data_seq2, inv_data_seq2_zero):
tests = 0
for key, update in i.parse (inv_data_seq2_zero):
if key == 'total':
assert update == False
tests +=1
elif key == 'env':
if key == 'total' or key == 'env':
assert update == False
tests +=1
@@ -456,10 +454,7 @@ def test_must_incr_total2(inv_data_seq2, inv_data_seq2_zero):
tests = 0
for key, update in i.parse (inv_data_seq2):
if key == 'total':
assert update == True
tests +=1
elif key == 'env':
if key == 'total' or key == 'env':
assert update == True
tests +=1

View File

@@ -1,18 +1,33 @@
# test_with_pytest.py
import pytest, json, math
import pytest, json, math, random
from app.src.infos import Register
from app.src.gen3plus.infos_g3p import InfosG3P
from app.src.gen3plus.infos_g3p import RegisterMap
@pytest.fixture(scope="session")
def str_test_ip():
ip = ".".join(str(random.randint(1, 254)) for _ in range(4))
print(f'random_ip: {ip}')
return ip
@pytest.fixture(scope="session")
def bytes_test_ip(str_test_ip):
ip = bytes(str.encode(str_test_ip))
l = len(ip)
if l < 16:
ip = ip + bytearray(16-l)
print(f'random_ip: {ip}')
return ip
@pytest.fixture
def device_data(): # 0x4110 ftype: 0x02
def device_data(bytes_test_ip): # 0x4110 ftype: 0x02
msg = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xba\xd2\x00\x00'
msg += b'\x19\x00\x00\x00\x00\x00\x00\x00\x05\x3c\x78\x01\x64\x01\x4c\x53'
msg += b'\x57\x35\x42\x4c\x45\x5f\x31\x37\x5f\x30\x32\x42\x30\x5f\x31\x2e'
msg += b'\x30\x35\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
msg += b'\x00\x00\x00\x00\x00\x00\x40\x2a\x8f\x4f\x51\x54\x31\x39\x32\x2e'
msg += b'\x31\x36\x38\x2e\x38\x30\x2e\x34\x39\x00\x00\x00\x0f\x00\x01\xb0'
msg += b'\x00\x00\x00\x00\x00\x00\x40\x2a\x8f\x4f\x51\x54' + bytes_test_ip
msg += b'\x0f\x00\x01\xb0'
msg += b'\x02\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'
@@ -63,14 +78,14 @@ def test_default_db():
"collector": {"Chip_Type": "IGEN TECH"},
})
def test_parse_4110(device_data: bytes):
def test_parse_4110(str_test_ip, device_data: bytes):
i = InfosG3P(client_mode=False)
i.db.clear()
for key, update in i.parse (device_data, 0x41, 2):
pass # side effect is calling generator i.parse()
assert json.dumps(i.db) == json.dumps({
'controller': {"Data_Up_Interval": 300, "Collect_Interval": 1, "Heartbeat_Interval": 120, "Signal_Strength": 100, "IP_Address": "192.168.80.49", "Sensor_List": "02b0"},
'controller': {"Data_Up_Interval": 300, "Collect_Interval": 1, "Heartbeat_Interval": 120, "Signal_Strength": 100, "IP_Address": str_test_ip, "Sensor_List": "02b0"},
'collector': {"Chip_Model": "LSW5BLE_17_02B0_1.05", "Collector_Fw_Version": "V1.1.00.0B"},
})
@@ -139,7 +154,11 @@ def test_build_ha_conf1():
assert tests==7
def test_build_ha_conf2():
i = InfosG3P(client_mode=False)
i.static_init() # initialize counter
tests = 0
for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'):
if id == 'out_power_123':
@@ -161,9 +180,9 @@ def test_build_ha_conf1():
assert d_json == json.dumps({"name": "Active Inverter Connections", "stat_t": "tsun/proxy/proxy", "dev_cla": None, "stat_cla": None, "uniq_id": "inv_count_456", "val_tpl": "{{value_json['Inverter_Cnt'] | int}}", "ic": "mdi:counter", "dev": {"name": "Proxy", "sa": "Proxy", "mdl": "proxy", "mf": "Stefan Allius", "sw": "unknown", "ids": ["proxy"]}, "o": {"name": "proxy", "sw": "unknown"}})
tests +=1
assert tests==8
assert tests==1
def test_build_ha_conf2():
def test_build_ha_conf3():
i = InfosG3P(client_mode=True)
i.static_init() # initialize counter
@@ -209,7 +228,11 @@ def test_build_ha_conf2():
assert tests==7
def test_build_ha_conf4():
i = InfosG3P(client_mode=True)
i.static_init() # initialize counter
tests = 0
for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'):
if id == 'out_power_123':
@@ -231,7 +254,7 @@ def test_build_ha_conf2():
assert d_json == json.dumps({"name": "Active Inverter Connections", "stat_t": "tsun/proxy/proxy", "dev_cla": None, "stat_cla": None, "uniq_id": "inv_count_456", "val_tpl": "{{value_json['Inverter_Cnt'] | int}}", "ic": "mdi:counter", "dev": {"name": "Proxy", "sa": "Proxy", "mdl": "proxy", "mf": "Stefan Allius", "sw": "unknown", "ids": ["proxy"]}, "o": {"name": "proxy", "sw": "unknown"}})
tests +=1
assert tests==8
assert tests==1
def test_exception_and_eval(inverter_data: bytes):

View File

@@ -0,0 +1,90 @@
# test_with_pytest.py
import pytest
import asyncio
import aiomqtt
import logging
from mock import patch, Mock
from app.src.singleton import Singleton
from app.src.inverter import Inverter
from app.src.mqtt import Mqtt
from app.src.gen3plus.solarman_v5 import SolarmanV5
from app.src.config import Config
pytest_plugins = ('pytest_asyncio',)
@pytest.fixture(scope="module", autouse=True)
def module_init():
def new_init(cls, cb_mqtt_is_up):
cb_mqtt_is_up()
Singleton._instances.clear()
with patch.object(Mqtt, '__init__', new_init):
yield
@pytest.fixture(scope="module")
def test_port():
return 1883
@pytest.fixture(scope="module")
def test_hostname():
# if getenv("GITHUB_ACTIONS") == "true":
# return 'mqtt'
# else:
return 'test.mosquitto.org'
@pytest.fixture
def config_conn(test_hostname, test_port):
Config.act_config = {
'mqtt':{
'host': test_hostname,
'port': test_port,
'user': '',
'passwd': ''
},
'ha':{
'auto_conf_prefix': 'homeassistant',
'discovery_prefix': 'homeassistant',
'entity_prefix': 'tsun',
'proxy_node_id': 'test_1',
'proxy_unique_id': ''
},
'inverters': {
'allow_all': True,
"R170000000000001":{
'node_id': 'inv_1'
}
}
}
@pytest.mark.asyncio
async def test_inverter_cb(config_conn):
_ = config_conn
with patch.object(Inverter, '_cb_mqtt_is_up', wraps=Inverter._cb_mqtt_is_up) as spy:
print('call Inverter.class_init')
Inverter.class_init()
assert 'homeassistant/' == Inverter.discovery_prfx
assert 'tsun/' == Inverter.entity_prfx
assert 'test_1/' == Inverter.proxy_node_id
spy.assert_called_once()
@pytest.mark.asyncio
async def test_mqtt_is_up(config_conn):
_ = config_conn
with patch.object(Mqtt, 'publish') as spy:
Inverter.class_init()
await Inverter._cb_mqtt_is_up()
spy.assert_called()
@pytest.mark.asyncio
async def test_mqtt_proxy_statt_invalid(config_conn):
_ = config_conn
with patch.object(Mqtt, 'publish') as spy:
Inverter.class_init()
await Inverter._async_publ_mqtt_proxy_stat('InValId_kEy')
spy.assert_not_called()

View File

@@ -0,0 +1,78 @@
# test_with_pytest.py
import pytest
import asyncio
from mock import patch
from app.src.singleton import Singleton
from app.src.config import Config
from app.src.infos import Infos
from app.src.modbus_tcp import ModbusConn
pytest_plugins = ('pytest_asyncio',)
# initialize the proxy statistics
Infos.static_init()
@pytest.fixture(scope="module", autouse=True)
def module_init():
Singleton._instances.clear()
yield
@pytest.fixture(scope="module")
def test_port():
return 1883
@pytest.fixture(scope="module")
def test_hostname():
# if getenv("GITHUB_ACTIONS") == "true":
# return 'mqtt'
# else:
return 'test.mosquitto.org'
@pytest.fixture
def config_mqtt_conn(test_hostname, test_port):
Config.act_config = {'mqtt':{'host': test_hostname, 'port': test_port, 'user': '', 'passwd': ''},
'ha':{'auto_conf_prefix': 'homeassistant','discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun'}
}
@pytest.fixture
def config_no_conn(test_port):
Config.act_config = {'mqtt':{'host': "", 'port': test_port, 'user': '', 'passwd': ''},
'ha':{'auto_conf_prefix': 'homeassistant','discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun'}
}
class FakeReader():
pass
class FakeWriter():
pass
@pytest.fixture
def patch_open():
async def new_conn(conn):
await asyncio.sleep(0.01)
return FakeReader(), FakeWriter()
def new_open(host: str, port: int):
return new_conn(None)
with patch.object(asyncio, 'open_connection', new_open) as conn:
yield conn
@pytest.mark.asyncio
async def test_modbus_conn(patch_open):
_ = patch_open
assert Infos.stat['proxy']['Inverter_Cnt'] == 0
async with ModbusConn('test.local', 1234) as stream:
assert stream.node_id == 'G3P'
assert stream.addr == ('test.local', 1234)
assert type(stream.reader) is FakeReader
assert type(stream.writer) is FakeWriter
assert Infos.stat['proxy']['Inverter_Cnt'] == 1
assert Infos.stat['proxy']['Inverter_Cnt'] == 0

View File

@@ -5,6 +5,7 @@ import aiomqtt
import logging
from mock import patch, Mock
from app.src.singleton import Singleton
from app.src.mqtt import Mqtt
from app.src.modbus import Modbus
from app.src.gen3plus.solarman_v5 import SolarmanV5
@@ -13,7 +14,10 @@ from app.src.config import Config
pytest_plugins = ('pytest_asyncio',)
@pytest.fixture(scope="module", autouse=True)
def module_init():
Singleton._instances.clear()
yield
@pytest.fixture(scope="module")
def test_port():

View File

@@ -7,6 +7,7 @@ class Test(metaclass=Singleton):
pass # is a dummy test class
def test_singleton_metaclass():
Singleton._instances.clear()
a = Test()
assert 1 == len(Singleton._instances)
b = Test()

View File

@@ -3,6 +3,7 @@ import struct
import time
import asyncio
import logging
import random
from math import isclose
from app.src.gen3plus.solarman_v5 import SolarmanV5
from app.src.config import Config
@@ -148,6 +149,12 @@ def incorrect_checksum(buf):
checksum = (sum(buf[1:])+1) & 0xff
return checksum.to_bytes(length=1)
@pytest.fixture(scope="session")
def str_test_ip():
ip = ".".join(str(random.randint(1, 254)) for _ in range(4))
print(f'random_ip: {ip}')
return ip
@pytest.fixture
def device_ind_msg(): # 0x4110
msg = b'\xa5\xd4\x00\x10\x41\x00\x01' +get_sn() +b'\x02\xba\xd2\x00\x00'
@@ -1692,7 +1699,7 @@ async def test_modbus_polling(config_tsun_inv1, heartbeat_ind_msg, heartbeat_rsp
m.close()
@pytest.mark.asyncio
async def test_start_client_mode(config_tsun_inv1):
async def test_start_client_mode(config_tsun_inv1, str_test_ip):
_ = config_tsun_inv1
assert asyncio.get_running_loop()
m = MemoryStream(b'')
@@ -1700,9 +1707,9 @@ async def test_start_client_mode(config_tsun_inv1):
assert m.no_forwarding == False
assert m.mb_timer.tim == None
assert asyncio.get_running_loop() == m.mb_timer.loop
await m.send_start_cmd(get_sn_int(), '192.168.1.1', m.mb_first_timeout)
await m.send_start_cmd(get_sn_int(), str_test_ip, m.mb_first_timeout)
assert m.writer.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) == '192.168.1.1'
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 m.db.get_db_value(Register.HEARTBEAT_INTERVAL) == 120

View File

@@ -1150,7 +1150,7 @@ def test_msg_ota_invalid(config_tsun_inv1, msg_ota_invalid):
m.close()
def test_msg_unknown(config_tsun_inv1, msg_unknown):
config_tsun_inv1
_ = config_tsun_inv1
m = MemoryStream(msg_unknown, (0,), False)
m.db.stat['proxy']['Unknown_Msg'] = 0
m.read() # read complete msg, and dispatch msg

View File

@@ -13,31 +13,31 @@ def get_invalid_sn():
@pytest.fixture
def MsgContactInfo(): # Contact Info message
def msg_contact_info(): # Contact Info message
return b'\x00\x00\x00\x2c\x10'+get_sn()+b'\x91\x00\x08solarhub\x0fsolarhub\x40123456'
@pytest.fixture
def MsgContactResp(): # Contact Response message
def msg_contact_resp(): # Contact Response message
return b'\x00\x00\x00\x14\x10'+get_sn()+b'\x91\x00\x01'
@pytest.fixture
def MsgContactInfo2(): # Contact Info message
def msg_contact_info2(): # Contact Info message
return b'\x00\x00\x00\x2c\x10'+get_invalid_sn()+b'\x91\x00\x08solarhub\x0fsolarhub\x40123456'
@pytest.fixture
def MsgContactResp2(): # Contact Response message
def msg_contact_resp2(): # Contact Response message
return b'\x00\x00\x00\x14\x10'+get_invalid_sn()+b'\x91\x00\x01'
@pytest.fixture
def MsgTimeStampReq(): # Get Time Request message
def msg_timestamp_req(): # Get Time Request message
return b'\x00\x00\x00\x13\x10'+get_sn()+b'\x91\x22'
@pytest.fixture
def MsgTimeStampResp(): # Get Time Resonse message
def msg_timestamp_resp(): # Get Time Resonse message
return b'\x00\x00\x00\x1b\x10'+get_sn()+b'\x99\x22\x00\x00\x01\x89\xc6\x63\x4d\x80'
@pytest.fixture
def MsgContollerInd(): # Data indication from the controller
def msg_controller_ind(): # Data indication from the controller
msg = b'\x00\x00\x01\x2f\x10'+ get_sn() + b'\x91\x71\x0e\x10\x00\x00\x10'+get_sn()
msg += b'\x01\x00\x00\x01\x89\xc6\x63\x55\x50'
msg += b'\x00\x00\x00\x15\x00\x09\x2b\xa8\x54\x10\x52\x53\x57\x5f\x34\x30\x30\x5f\x56\x31\x2e\x30\x30\x2e\x30\x36\x00\x09\x27\xc0\x54\x06\x52\x61\x79\x6d\x6f'
@@ -49,7 +49,7 @@ def MsgContollerInd(): # Data indication from the controller
return msg
@pytest.fixture
def MsgInvData(): # Data indication from the controller
def msg_inv_data(): # Data indication from the controller
msg = b'\x00\x00\x00\x8b\x10'+ get_sn() + b'\x91\x04\x01\x90\x00\x01\x10'+get_inv_no()
msg += b'\x01\x00\x00\x01\x89\xc6\x63\x61\x08'
msg += b'\x00\x00\x00\x06\x00\x00\x00\x0a\x54\x08\x4d\x69\x63\x72\x6f\x69\x6e\x76\x00\x00\x00\x14\x54\x04\x54\x53\x55\x4e\x00\x00\x00\x1E\x54\x07\x56\x35\x2e\x30\x2e\x31\x31\x00\x00\x00\x28'
@@ -57,7 +57,7 @@ def MsgInvData(): # Data indication from the controller
return msg
@pytest.fixture
def MsgInverterInd(): # Data indication from the inverter
def msg_inverter_ind(): # Data indication from the inverter
msg = b'\x00\x00\x05\x02\x10'+ get_sn() + b'\x91\x04\x01\x90\x00\x01\x10'+get_inv_no()
msg += b'\x01\x00\x00\x01\x89\xc6\x63\x61\x08'
msg += b'\x00\x00\x00\xa3\x00\x00\x00\x64\x53\x00\x01\x00\x00\x00\xc8\x53\x00\x02\x00\x00\x01\x2c\x53\x00\x00\x00\x00\x01\x90\x49\x00\x00\x00\x00\x00\x00\x01\x91\x53\x00\x00'
@@ -94,7 +94,7 @@ def MsgInverterInd(): # Data indication from the inverter
return msg
@pytest.fixture
def MsgOtaUpdateReq(): # Over the air update request from talent cloud
def msg_ota_update_req(): # Over the air update request from talent cloud
msg = b'\x00\x00\x01\x16\x10'+ get_sn() + b'\x70\x13\x01\x02\x76\x35'
msg += b'\x70\x68\x74\x74\x70'
msg += b'\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x61\x6c\x65\x6e\x74\x2d\x6d\x6f'
@@ -117,7 +117,7 @@ def MsgOtaUpdateReq(): # Over the air update request from talent cloud
@pytest.fixture(scope="session")
def ClientConnection():
def client_connection():
host = 'logger.talent-monitoring.com'
port = 5005
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
@@ -127,7 +127,7 @@ def ClientConnection():
time.sleep(2.5)
s.close()
def tempClientConnection():
def tempclient_connection():
host = 'logger.talent-monitoring.com'
port = 5005
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
@@ -138,25 +138,25 @@ def tempClientConnection():
def test_open_close():
try:
for s in tempClientConnection():
pass
except:
for _ in tempclient_connection():
pass # test side effect of generator
except Exception:
assert False
def test_send_contact_info1(ClientConnection, MsgContactInfo, MsgContactResp):
s = ClientConnection
def test_send_contact_info1(client_connection, msg_contact_info, msg_contact_resp):
s = client_connection
try:
s.sendall(MsgContactInfo)
s.sendall(msg_contact_info)
data = s.recv(1024)
except TimeoutError:
pass
assert data == MsgContactResp
assert data == msg_contact_resp
def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, MsgContactResp):
s = ClientConnection
def test_send_contact_info2(client_connection, msg_contact_info2, msg_contact_info, msg_contact_resp):
s = client_connection
try:
s.sendall(MsgContactInfo2)
s.sendall(msg_contact_info2)
data = s.recv(1024)
except TimeoutError:
pass
@@ -164,73 +164,73 @@ def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, M
assert False
try:
s.sendall(MsgContactInfo)
s.sendall(msg_contact_info)
data = s.recv(1024)
except TimeoutError:
pass
assert data == MsgContactResp
assert data == msg_contact_resp
def test_send_contact_info3(ClientConnection, MsgContactInfo, MsgContactResp, MsgTimeStampReq):
s = ClientConnection
def test_send_contact_info3(client_connection, msg_contact_info, msg_contact_resp, msg_timestamp_req):
s = client_connection
try:
s.sendall(MsgContactInfo)
s.sendall(msg_contact_info)
data = s.recv(1024)
except TimeoutError:
pass
assert data == MsgContactResp
assert data == msg_contact_resp
try:
s.sendall(MsgTimeStampReq)
s.sendall(msg_timestamp_req)
data = s.recv(1024)
except TimeoutError:
pass
def test_send_contact_resp(ClientConnection, MsgContactResp):
s = ClientConnection
def test_send_contact_resp(client_connection, msg_contact_resp):
s = client_connection
try:
s.sendall(MsgContactResp)
s.sendall(msg_contact_resp)
data = s.recv(1024)
except TimeoutError:
pass
else:
assert data == b''
def test_send_ctrl_data(ClientConnection, MsgTimeStampReq, MsgTimeStampResp, MsgContollerInd):
s = ClientConnection
def test_send_ctrl_data(client_connection, msg_timestamp_req, msg_timestamp_resp, msg_controller_ind):
s = client_connection
try:
s.sendall(MsgTimeStampReq)
data = s.recv(1024)
s.sendall(msg_timestamp_req)
_ = s.recv(1024)
except TimeoutError:
pass
# time.sleep(2.5)
# assert data == MsgTimeStampResp
# assert data == msg_timestamp_resp
try:
s.sendall(MsgContollerInd)
data = s.recv(1024)
s.sendall(msg_controller_ind)
_ = s.recv(1024)
except TimeoutError:
pass
def test_send_inv_data(ClientConnection, MsgTimeStampReq, MsgTimeStampResp, MsgInvData, MsgInverterInd):
s = ClientConnection
def test_send_inv_data(client_connection, msg_timestamp_req, msg_timestamp_resp, msg_inv_data, msg_inverter_ind):
s = client_connection
try:
s.sendall(MsgTimeStampReq)
data = s.recv(1024)
s.sendall(msg_timestamp_req)
_ = s.recv(1024)
except TimeoutError:
pass
# time.sleep(32.5)
# assert data == MsgTimeStampResp
# assert data == msg_timestamp_resp
try:
s.sendall(MsgInvData)
data = s.recv(1024)
s.sendall(MsgInverterInd)
data = s.recv(1024)
s.sendall(msg_inv_data)
_ = s.recv(1024)
s.sendall(msg_inverter_ind)
_ = s.recv(1024)
except TimeoutError:
pass
def test_ota_req(ClientConnection, MsgOtaUpdateReq):
s = ClientConnection
def test_ota_req(client_connection, msg_ota_update_req):
s = client_connection
try:
s.sendall(MsgOtaUpdateReq)
data = s.recv(1024)
s.sendall(msg_ota_update_req)
_ = s.recv(1024)
except TimeoutError:
pass