Dev 0.11 (#175)
* 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:
@@ -62,18 +62,10 @@ RUN python -m pip install --no-cache --no-index /root/wheels/* && \
|
|||||||
COPY --chmod=0700 entrypoint.sh /root/entrypoint.sh
|
COPY --chmod=0700 entrypoint.sh /root/entrypoint.sh
|
||||||
COPY config .
|
COPY config .
|
||||||
COPY src .
|
COPY src .
|
||||||
RUN echo ${VERSION} > /proxy-version.txt
|
RUN echo ${VERSION} > /proxy-version.txt \
|
||||||
RUN date > /build-date.txt
|
&& date > /build-date.txt
|
||||||
EXPOSE 5005 8127 10000
|
EXPOSE 5005 8127 10000
|
||||||
|
|
||||||
# command to run on container start
|
# command to run on container start
|
||||||
ENTRYPOINT ["/root/entrypoint.sh"]
|
ENTRYPOINT ["/root/entrypoint.sh"]
|
||||||
CMD [ "python3", "./server.py" ]
|
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"
|
|
||||||
|
|||||||
39
app/build.sh
39
app/build.sh
@@ -17,6 +17,7 @@ VERSION="${VERSION:1}"
|
|||||||
arr=(${VERSION//./ })
|
arr=(${VERSION//./ })
|
||||||
MAJOR=${arr[0]}
|
MAJOR=${arr[0]}
|
||||||
IMAGE=tsun-gen3-proxy
|
IMAGE=tsun-gen3-proxy
|
||||||
|
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
BLUE='\033[0;34m'
|
BLUE='\033[0;34m'
|
||||||
NC='\033[0m'
|
NC='\033[0m'
|
||||||
@@ -26,44 +27,22 @@ IMAGE=docker.io/sallius/${IMAGE}
|
|||||||
VERSION=${VERSION}+$1
|
VERSION=${VERSION}+$1
|
||||||
elif [[ $1 == rc ]] || [[ $1 == rel ]] || [[ $1 == preview ]] ;then
|
elif [[ $1 == rc ]] || [[ $1 == rel ]] || [[ $1 == preview ]] ;then
|
||||||
IMAGE=ghcr.io/s-allius/${IMAGE}
|
IMAGE=ghcr.io/s-allius/${IMAGE}
|
||||||
|
echo 'login to ghcr.io'
|
||||||
|
echo $GHCR_TOKEN | docker login ghcr.io -u s-allius --password-stdin
|
||||||
else
|
else
|
||||||
echo argument missing!
|
echo argument missing!
|
||||||
echo try: $0 '[debug|dev|preview|rc|rel]'
|
echo try: $0 '[debug|dev|preview|rc|rel]'
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ $1 == debug ]] ;then
|
export IMAGE
|
||||||
BUILD_ENV="dev"
|
export VERSION
|
||||||
else
|
export BUILD_DATE
|
||||||
BUILD_ENV="production"
|
export BRANCH
|
||||||
fi
|
export MAJOR
|
||||||
|
|
||||||
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}"
|
|
||||||
|
|
||||||
echo version: $VERSION build-date: $BUILD_DATE image: $IMAGE
|
echo version: $VERSION build-date: $BUILD_DATE image: $IMAGE
|
||||||
if [[ $1 == debug ]];then
|
docker buildx bake -f app/docker-bake.hcl $1
|
||||||
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
|
|
||||||
|
|
||||||
echo -e "${BLUE} => checking docker-compose.yaml file${NC}"
|
echo -e "${BLUE} => checking docker-compose.yaml file${NC}"
|
||||||
docker-compose config -q
|
docker-compose config -q
|
||||||
|
|||||||
93
app/docker-bake.hcl
Normal file
93
app/docker-bake.hcl
Normal 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
|
||||||
|
}
|
||||||
@@ -3,10 +3,15 @@ import logging
|
|||||||
import traceback
|
import traceback
|
||||||
import time
|
import time
|
||||||
from asyncio import StreamReader, StreamWriter
|
from asyncio import StreamReader, StreamWriter
|
||||||
from messages import hex_dump_memory, State
|
|
||||||
from typing import Self
|
from typing import Self
|
||||||
from itertools import count
|
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
|
import gc
|
||||||
logger = logging.getLogger('conn')
|
logger = logging.getLogger('conn')
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import logging
|
import logging
|
||||||
from asyncio import StreamReader, StreamWriter
|
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')
|
logger = logging.getLogger('conn')
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,18 @@ import traceback
|
|||||||
import json
|
import json
|
||||||
import asyncio
|
import asyncio
|
||||||
from asyncio import StreamReader, StreamWriter
|
from asyncio import StreamReader, StreamWriter
|
||||||
from config import Config
|
|
||||||
from inverter import Inverter
|
|
||||||
from gen3plus.connection_g3p import ConnectionG3P
|
|
||||||
from aiomqtt import MqttCodeError
|
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')
|
logger_mqtt = logging.getLogger('mqtt')
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class SolarmanV5(Message):
|
|||||||
self.db.set_db_def_value(Register.POLLING_INTERVAL,
|
self.db.set_db_def_value(Register.POLLING_INTERVAL,
|
||||||
self.mb_timeout)
|
self.mb_timeout)
|
||||||
self.db.set_db_def_value(Register.HEARTBEAT_INTERVAL,
|
self.db.set_db_def_value(Register.HEARTBEAT_INTERVAL,
|
||||||
120) # fixme
|
120)
|
||||||
self.new_data['controller'] = True
|
self.new_data['controller'] = True
|
||||||
|
|
||||||
self.state = State.up
|
self.state = State.up
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from config import Config
|
if __name__ == "app.src.inverter":
|
||||||
from mqtt import Mqtt
|
from app.src.config import Config
|
||||||
from infos import Infos
|
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')
|
logger_mqtt = logging.getLogger('mqtt')
|
||||||
|
|
||||||
|
|
||||||
@@ -72,7 +76,7 @@ class Inverter():
|
|||||||
Infos.new_stat_data[key] = False
|
Infos.new_stat_data[key] = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def class_close(cls, loop) -> None:
|
def class_close(cls, loop) -> None: # pragma: no cover
|
||||||
logging.debug('Inverter.class_close')
|
logging.debug('Inverter.class_close')
|
||||||
logging.info('Close MQTT Task')
|
logging.info('Close MQTT Task')
|
||||||
loop.run_until_complete(cls.mqtt.close())
|
loop.run_until_complete(cls.mqtt.close())
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import logging
|
import logging
|
||||||
import traceback
|
import traceback
|
||||||
import asyncio
|
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')
|
logger = logging.getLogger('conn')
|
||||||
|
|
||||||
|
|||||||
@@ -325,7 +325,11 @@ def test_build_ha_conf1(contr_data_seq):
|
|||||||
|
|
||||||
assert tests==4
|
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'):
|
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':
|
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"}})
|
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
|
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()
|
i = InfosG3()
|
||||||
for key, result in i.parse (contr_data_seq):
|
for key, result in i.parse (contr_data_seq):
|
||||||
pass # side effect in calling i.parse()
|
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})
|
assert json.dumps(i.db['env']) == json.dumps({"Inverter_Temp": 23})
|
||||||
tests = 0
|
tests = 0
|
||||||
for key, update in i.parse (inv_data_seq2):
|
for key, update in i.parse (inv_data_seq2):
|
||||||
if key == 'total':
|
if key == 'total' or key == 'env':
|
||||||
assert update == False
|
|
||||||
tests +=1
|
|
||||||
elif key == 'env':
|
|
||||||
assert update == False
|
assert update == False
|
||||||
tests +=1
|
tests +=1
|
||||||
|
|
||||||
@@ -442,10 +443,7 @@ def test_must_incr_total2(inv_data_seq2, inv_data_seq2_zero):
|
|||||||
|
|
||||||
tests = 0
|
tests = 0
|
||||||
for key, update in i.parse (inv_data_seq2_zero):
|
for key, update in i.parse (inv_data_seq2_zero):
|
||||||
if key == 'total':
|
if key == 'total' or key == 'env':
|
||||||
assert update == False
|
|
||||||
tests +=1
|
|
||||||
elif key == 'env':
|
|
||||||
assert update == False
|
assert update == False
|
||||||
tests +=1
|
tests +=1
|
||||||
|
|
||||||
@@ -456,10 +454,7 @@ def test_must_incr_total2(inv_data_seq2, inv_data_seq2_zero):
|
|||||||
|
|
||||||
tests = 0
|
tests = 0
|
||||||
for key, update in i.parse (inv_data_seq2):
|
for key, update in i.parse (inv_data_seq2):
|
||||||
if key == 'total':
|
if key == 'total' or key == 'env':
|
||||||
assert update == True
|
|
||||||
tests +=1
|
|
||||||
elif key == 'env':
|
|
||||||
assert update == True
|
assert update == True
|
||||||
tests +=1
|
tests +=1
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,33 @@
|
|||||||
|
|
||||||
# test_with_pytest.py
|
# test_with_pytest.py
|
||||||
import pytest, json, math
|
import pytest, json, math, random
|
||||||
from app.src.infos import Register
|
from app.src.infos import Register
|
||||||
from app.src.gen3plus.infos_g3p import InfosG3P
|
from app.src.gen3plus.infos_g3p import InfosG3P
|
||||||
from app.src.gen3plus.infos_g3p import RegisterMap
|
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
|
@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'\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'\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'\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'\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'\x00\x00\x00\x00\x00\x00\x40\x2a\x8f\x4f\x51\x54' + bytes_test_ip
|
||||||
msg += b'\x31\x36\x38\x2e\x38\x30\x2e\x34\x39\x00\x00\x00\x0f\x00\x01\xb0'
|
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'\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\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\xfe\xfe\x00\x00'
|
||||||
@@ -63,14 +78,14 @@ def test_default_db():
|
|||||||
"collector": {"Chip_Type": "IGEN TECH"},
|
"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 = InfosG3P(client_mode=False)
|
||||||
i.db.clear()
|
i.db.clear()
|
||||||
for key, update in i.parse (device_data, 0x41, 2):
|
for key, update in i.parse (device_data, 0x41, 2):
|
||||||
pass # side effect is calling generator i.parse()
|
pass # side effect is calling generator i.parse()
|
||||||
|
|
||||||
assert json.dumps(i.db) == json.dumps({
|
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"},
|
'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
|
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'):
|
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':
|
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"}})
|
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
|
tests +=1
|
||||||
|
|
||||||
assert tests==8
|
assert tests==1
|
||||||
|
|
||||||
def test_build_ha_conf2():
|
def test_build_ha_conf3():
|
||||||
i = InfosG3P(client_mode=True)
|
i = InfosG3P(client_mode=True)
|
||||||
i.static_init() # initialize counter
|
i.static_init() # initialize counter
|
||||||
|
|
||||||
@@ -209,7 +228,11 @@ def test_build_ha_conf2():
|
|||||||
|
|
||||||
assert tests==7
|
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'):
|
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':
|
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"}})
|
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
|
tests +=1
|
||||||
|
|
||||||
assert tests==8
|
assert tests==1
|
||||||
|
|
||||||
def test_exception_and_eval(inverter_data: bytes):
|
def test_exception_and_eval(inverter_data: bytes):
|
||||||
|
|
||||||
|
|||||||
90
app/tests/test_inverter.py
Normal file
90
app/tests/test_inverter.py
Normal 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()
|
||||||
78
app/tests/test_modbus_tcp.py
Normal file
78
app/tests/test_modbus_tcp.py
Normal 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
|
||||||
@@ -5,6 +5,7 @@ import aiomqtt
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mock import patch, Mock
|
from mock import patch, Mock
|
||||||
|
from app.src.singleton import Singleton
|
||||||
from app.src.mqtt import Mqtt
|
from app.src.mqtt import Mqtt
|
||||||
from app.src.modbus import Modbus
|
from app.src.modbus import Modbus
|
||||||
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
||||||
@@ -13,7 +14,10 @@ from app.src.config import Config
|
|||||||
|
|
||||||
pytest_plugins = ('pytest_asyncio',)
|
pytest_plugins = ('pytest_asyncio',)
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module", autouse=True)
|
||||||
|
def module_init():
|
||||||
|
Singleton._instances.clear()
|
||||||
|
yield
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def test_port():
|
def test_port():
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class Test(metaclass=Singleton):
|
|||||||
pass # is a dummy test class
|
pass # is a dummy test class
|
||||||
|
|
||||||
def test_singleton_metaclass():
|
def test_singleton_metaclass():
|
||||||
|
Singleton._instances.clear()
|
||||||
a = Test()
|
a = Test()
|
||||||
assert 1 == len(Singleton._instances)
|
assert 1 == len(Singleton._instances)
|
||||||
b = Test()
|
b = Test()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import struct
|
|||||||
import time
|
import time
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
import random
|
||||||
from math import isclose
|
from math import isclose
|
||||||
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
from app.src.gen3plus.solarman_v5 import SolarmanV5
|
||||||
from app.src.config import Config
|
from app.src.config import Config
|
||||||
@@ -148,6 +149,12 @@ def incorrect_checksum(buf):
|
|||||||
checksum = (sum(buf[1:])+1) & 0xff
|
checksum = (sum(buf[1:])+1) & 0xff
|
||||||
return checksum.to_bytes(length=1)
|
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
|
@pytest.fixture
|
||||||
def device_ind_msg(): # 0x4110
|
def device_ind_msg(): # 0x4110
|
||||||
msg = b'\xa5\xd4\x00\x10\x41\x00\x01' +get_sn() +b'\x02\xba\xd2\x00\x00'
|
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()
|
m.close()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@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
|
_ = config_tsun_inv1
|
||||||
assert asyncio.get_running_loop()
|
assert asyncio.get_running_loop()
|
||||||
m = MemoryStream(b'')
|
m = MemoryStream(b'')
|
||||||
@@ -1700,9 +1707,9 @@ async def test_start_client_mode(config_tsun_inv1):
|
|||||||
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(), '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.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 isclose(m.db.get_db_value(Register.POLLING_INTERVAL), 0.5)
|
||||||
assert m.db.get_db_value(Register.HEARTBEAT_INTERVAL) == 120
|
assert m.db.get_db_value(Register.HEARTBEAT_INTERVAL) == 120
|
||||||
|
|
||||||
|
|||||||
@@ -1150,7 +1150,7 @@ def test_msg_ota_invalid(config_tsun_inv1, msg_ota_invalid):
|
|||||||
m.close()
|
m.close()
|
||||||
|
|
||||||
def test_msg_unknown(config_tsun_inv1, msg_unknown):
|
def test_msg_unknown(config_tsun_inv1, msg_unknown):
|
||||||
config_tsun_inv1
|
_ = config_tsun_inv1
|
||||||
m = MemoryStream(msg_unknown, (0,), False)
|
m = MemoryStream(msg_unknown, (0,), False)
|
||||||
m.db.stat['proxy']['Unknown_Msg'] = 0
|
m.db.stat['proxy']['Unknown_Msg'] = 0
|
||||||
m.read() # read complete msg, and dispatch msg
|
m.read() # read complete msg, and dispatch msg
|
||||||
|
|||||||
@@ -13,31 +13,31 @@ def get_invalid_sn():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@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'
|
return b'\x00\x00\x00\x2c\x10'+get_sn()+b'\x91\x00\x08solarhub\x0fsolarhub\x40123456'
|
||||||
|
|
||||||
@pytest.fixture
|
@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'
|
return b'\x00\x00\x00\x14\x10'+get_sn()+b'\x91\x00\x01'
|
||||||
|
|
||||||
@pytest.fixture
|
@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'
|
return b'\x00\x00\x00\x2c\x10'+get_invalid_sn()+b'\x91\x00\x08solarhub\x0fsolarhub\x40123456'
|
||||||
|
|
||||||
@pytest.fixture
|
@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'
|
return b'\x00\x00\x00\x14\x10'+get_invalid_sn()+b'\x91\x00\x01'
|
||||||
|
|
||||||
@pytest.fixture
|
@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'
|
return b'\x00\x00\x00\x13\x10'+get_sn()+b'\x91\x22'
|
||||||
|
|
||||||
@pytest.fixture
|
@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'
|
return b'\x00\x00\x00\x1b\x10'+get_sn()+b'\x99\x22\x00\x00\x01\x89\xc6\x63\x4d\x80'
|
||||||
|
|
||||||
@pytest.fixture
|
@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'\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'\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'
|
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
|
return msg
|
||||||
|
|
||||||
@pytest.fixture
|
@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'\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'\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'
|
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
|
return msg
|
||||||
|
|
||||||
@pytest.fixture
|
@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'\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'\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'
|
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
|
return msg
|
||||||
|
|
||||||
@pytest.fixture
|
@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'\x00\x00\x01\x16\x10'+ get_sn() + b'\x70\x13\x01\x02\x76\x35'
|
||||||
msg += b'\x70\x68\x74\x74\x70'
|
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'
|
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")
|
@pytest.fixture(scope="session")
|
||||||
def ClientConnection():
|
def client_connection():
|
||||||
host = 'logger.talent-monitoring.com'
|
host = 'logger.talent-monitoring.com'
|
||||||
port = 5005
|
port = 5005
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
@@ -127,7 +127,7 @@ def ClientConnection():
|
|||||||
time.sleep(2.5)
|
time.sleep(2.5)
|
||||||
s.close()
|
s.close()
|
||||||
|
|
||||||
def tempClientConnection():
|
def tempclient_connection():
|
||||||
host = 'logger.talent-monitoring.com'
|
host = 'logger.talent-monitoring.com'
|
||||||
port = 5005
|
port = 5005
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||||
@@ -138,25 +138,25 @@ def tempClientConnection():
|
|||||||
|
|
||||||
def test_open_close():
|
def test_open_close():
|
||||||
try:
|
try:
|
||||||
for s in tempClientConnection():
|
for _ in tempclient_connection():
|
||||||
pass
|
pass # test side effect of generator
|
||||||
except:
|
except Exception:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
def test_send_contact_info1(ClientConnection, MsgContactInfo, MsgContactResp):
|
def test_send_contact_info1(client_connection, msg_contact_info, msg_contact_resp):
|
||||||
s = ClientConnection
|
s = client_connection
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgContactInfo)
|
s.sendall(msg_contact_info)
|
||||||
data = s.recv(1024)
|
data = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
assert data == MsgContactResp
|
assert data == msg_contact_resp
|
||||||
|
|
||||||
|
|
||||||
def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, MsgContactResp):
|
def test_send_contact_info2(client_connection, msg_contact_info2, msg_contact_info, msg_contact_resp):
|
||||||
s = ClientConnection
|
s = client_connection
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgContactInfo2)
|
s.sendall(msg_contact_info2)
|
||||||
data = s.recv(1024)
|
data = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
@@ -164,73 +164,73 @@ def test_send_contact_info2(ClientConnection, MsgContactInfo2, MsgContactInfo, M
|
|||||||
assert False
|
assert False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgContactInfo)
|
s.sendall(msg_contact_info)
|
||||||
data = s.recv(1024)
|
data = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
assert data == MsgContactResp
|
assert data == msg_contact_resp
|
||||||
|
|
||||||
def test_send_contact_info3(ClientConnection, MsgContactInfo, MsgContactResp, MsgTimeStampReq):
|
def test_send_contact_info3(client_connection, msg_contact_info, msg_contact_resp, msg_timestamp_req):
|
||||||
s = ClientConnection
|
s = client_connection
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgContactInfo)
|
s.sendall(msg_contact_info)
|
||||||
data = s.recv(1024)
|
data = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
assert data == MsgContactResp
|
assert data == msg_contact_resp
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgTimeStampReq)
|
s.sendall(msg_timestamp_req)
|
||||||
data = s.recv(1024)
|
data = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def test_send_contact_resp(ClientConnection, MsgContactResp):
|
def test_send_contact_resp(client_connection, msg_contact_resp):
|
||||||
s = ClientConnection
|
s = client_connection
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgContactResp)
|
s.sendall(msg_contact_resp)
|
||||||
data = s.recv(1024)
|
data = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
assert data == b''
|
assert data == b''
|
||||||
|
|
||||||
def test_send_ctrl_data(ClientConnection, MsgTimeStampReq, MsgTimeStampResp, MsgContollerInd):
|
def test_send_ctrl_data(client_connection, msg_timestamp_req, msg_timestamp_resp, msg_controller_ind):
|
||||||
s = ClientConnection
|
s = client_connection
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgTimeStampReq)
|
s.sendall(msg_timestamp_req)
|
||||||
data = s.recv(1024)
|
_ = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
# time.sleep(2.5)
|
# time.sleep(2.5)
|
||||||
# assert data == MsgTimeStampResp
|
# assert data == msg_timestamp_resp
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgContollerInd)
|
s.sendall(msg_controller_ind)
|
||||||
data = s.recv(1024)
|
_ = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_send_inv_data(ClientConnection, MsgTimeStampReq, MsgTimeStampResp, MsgInvData, MsgInverterInd):
|
def test_send_inv_data(client_connection, msg_timestamp_req, msg_timestamp_resp, msg_inv_data, msg_inverter_ind):
|
||||||
s = ClientConnection
|
s = client_connection
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgTimeStampReq)
|
s.sendall(msg_timestamp_req)
|
||||||
data = s.recv(1024)
|
_ = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
# time.sleep(32.5)
|
# time.sleep(32.5)
|
||||||
# assert data == MsgTimeStampResp
|
# assert data == msg_timestamp_resp
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgInvData)
|
s.sendall(msg_inv_data)
|
||||||
data = s.recv(1024)
|
_ = s.recv(1024)
|
||||||
s.sendall(MsgInverterInd)
|
s.sendall(msg_inverter_ind)
|
||||||
data = s.recv(1024)
|
_ = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_ota_req(ClientConnection, MsgOtaUpdateReq):
|
def test_ota_req(client_connection, msg_ota_update_req):
|
||||||
s = ClientConnection
|
s = client_connection
|
||||||
try:
|
try:
|
||||||
s.sendall(MsgOtaUpdateReq)
|
s.sendall(msg_ota_update_req)
|
||||||
data = s.recv(1024)
|
_ = s.recv(1024)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
Reference in New Issue
Block a user