Compare commits

..

81 Commits

Author SHA1 Message Date
Stefan Allius
1357b0f665 cleanup shutdown
- stop webserver on shutdown
- enable asyncio debug mode for debug versions
2024-06-29 20:07:06 +02:00
Stefan Allius
d9b7b9e858 add asyncio log 2024-06-29 20:05:08 +02:00
Stefan Allius
7c48ee4065 rename python to debugpy 2024-06-27 21:01:32 +02:00
Stefan Allius
4e89abd2c9 fix timout calculation 2024-06-27 21:00:50 +02:00
Stefan Allius
f304aa009e add quit flag to docker push 2024-06-27 21:00:17 +02:00
Stefan Allius
9e218fdf41 fix Config.class_init()
- return error string or None
- release Schema structure after building thr config
2024-06-25 23:28:34 +02:00
Stefan Allius
18f6332784 fix timer cleanup 2024-06-25 23:13:59 +02:00
Stefan Allius
26aebbcab8 fix buildx warnings 2024-06-23 23:56:37 +02:00
Stefan Allius
a9c7ea386e S allius/issue111 (#112)
Synchronize regular MODBUS commands with the status of the inverter to prevent the inverter from crashing due to unexpected packets.

* inital checkin

* remove crontab entry for regular MODBUS cmds

* add timer for regular MODBUS polling

* fix Stop method call for already stopped timer

* optimize MB_START_TIMEOUT value

* cleanup

* update changelog
2024-06-23 22:23:48 +02:00
Stefan Allius
6332976c4a S allius/issue102 (#110)
* hotfix: don't send two MODBUS commands together

* fix unit tests

* remove read loop

* optional sleep between msg read and sending rsp

* wait after read 0.5s before sending a response

* add pending state

* fix state definitions

* determine the connection timeout by the conn state

* avoid sending MODBUS cmds in the inverter's reporting phase

* update changelog
2024-06-23 15:06:43 +02:00
Stefan Allius
cc233dcb17 S allius/issue108 (#109)
* add more data types

* adapt unittests

* improve test coverage

* fix linter warning

* update changelog
2024-06-23 00:52:42 +02:00
Stefan Allius
9a9cf79aac fix unittests 2024-06-21 23:38:07 +02:00
Stefan Allius
3ce29d4a96 fix merge conflict 2024-06-21 19:26:27 +02:00
Stefan Allius
a09d489c94 Merge branch 'main' of https://github.com/s-allius/tsun-gen3-proxy into dev-0.9 2024-06-21 19:25:37 +02:00
Stefan Allius
2d4679a361 S allius/issue100 (#101)
* detect dead connections

- disconnect connection on Msg receive timeout
- improve connection trace (add connection id)

* update changelog
2024-06-17 23:10:54 +02:00
Stefan Allius
9ff1453922 Merge pull request #99 from s-allius/health_check
Health check
2024-06-16 23:08:12 +02:00
Stefan Allius
5b36efc5e9 Merge branch 'dev-0.9.0' into health_check 2024-06-16 23:07:47 +02:00
Stefan Allius
c71994c839 update changelog 2024-06-16 22:58:04 +02:00
Stefan Allius
7d058e74fe log healthcheck infos with DEBUG level 2024-06-16 22:54:56 +02:00
Stefan Allius
373916bead add healthy method 2024-06-16 22:47:45 +02:00
Stefan Allius
f4b434cfef set new state State.received 2024-06-16 22:45:13 +02:00
Stefan Allius
d14cbe87a2 add docstrings to state enum 2024-06-16 22:43:59 +02:00
Stefan Allius
8aa1ef59ce updat changelog 2024-06-16 19:35:38 +02:00
Stefan Allius
3d55ac57a8 Update CHANGELOG.md 2024-06-16 19:17:11 +02:00
Stefan Allius
8088e6ab3c cleanup 2024-06-16 18:13:07 +02:00
Stefan Allius
4372e49a1e add HTTP server for healthcheck 2024-06-16 17:51:51 +02:00
Stefan Allius
da832232bb calc processing time for healthcheck 2024-06-16 17:51:14 +02:00
Stefan Allius
e0568291f6 use Enum class for State 2024-06-16 17:50:09 +02:00
Stefan Allius
f5e7aa4292 add aiohttp 2024-06-16 17:48:17 +02:00
Stefan Allius
5e360e1139 add wget for healtcheck 2024-06-16 17:47:46 +02:00
Stefan Allius
94f7f5faa2 complete exposed port list 2024-06-16 17:47:13 +02:00
Stefan Allius
4600fc9577 add healtcheck 2024-06-16 17:46:51 +02:00
Stefan Allius
fa7bfe9e16 log unrelease references 2024-06-16 13:29:43 +02:00
Stefan Allius
3cebab40c8 add heaithy handler 2024-06-16 13:26:05 +02:00
Stefan Allius
4649beb075 Merge pull request #97 from s-allius/s-allius/issue93
S allius/issue93
2024-06-16 13:09:16 +02:00
Stefan Allius
9138affdb9 update changelog 2024-06-16 13:05:20 +02:00
Stefan Allius
80183598ca cleanup 2024-06-16 13:03:33 +02:00
Stefan Allius
b688d04836 isolate Modbus fix 2024-06-16 13:00:02 +02:00
Stefan Allius
377c09bc66 Merge branch 'dev-0.9.0' of https://github.com/s-allius/tsun-gen3-proxy into s-allius/issue93 2024-06-16 12:39:56 +02:00
Stefan Allius
abb9e7c280 Merge pull request #96 from s-allius/s-allius/issue94
S allius/issue94
2024-06-16 12:35:12 +02:00
Stefan Allius
d78e32dd12 update changelog 2024-06-16 12:26:55 +02:00
Stefan Allius
30a6f75430 Merge branch 'dev-0.9.0' of https://github.com/s-allius/tsun-gen3-proxy into s-allius/issue94 2024-06-16 12:23:57 +02:00
Stefan Allius
e22ad78dcd add exception handling for forward handler 2024-06-16 12:23:13 +02:00
Stefan Allius
453d8b2aa2 call modbus close hanlder on a close call 2024-06-16 11:57:51 +02:00
Stefan Allius
f9b02f3486 add a close handler to release internal resources 2024-06-16 11:56:03 +02:00
Stefan Allius
b053c7e576 Update async_stream.py
- check if processing time is < 5 sec
2024-06-16 02:08:15 +02:00
Stefan Allius
10346e888f log ConfigErr with DEBUG level 2024-06-16 01:52:34 +02:00
Stefan Allius
f629246dbd fix typo 2024-06-16 01:18:06 +02:00
Stefan Allius
dbff66affd add healthy check methods 2024-06-15 23:36:59 +02:00
Stefan Allius
ac534c20ed calculate msg prossesing time 2024-06-15 23:34:11 +02:00
Stefan Allius
ff3ed83b49 add http server for healthcheck 2024-06-15 23:29:27 +02:00
Stefan Allius
ae94cd62fc use config validation for healthcheck 2024-06-15 23:23:57 +02:00
Stefan Allius
a16a19cc2c add aiohttp 2024-06-15 23:21:15 +02:00
Stefan Allius
dd351176bd add wget for healthcheck 2024-06-15 23:20:38 +02:00
Stefan Allius
cc8674d108 add exposed ports and healthcheck 2024-06-15 23:19:10 +02:00
Stefan Allius
d7767cb5ea update changelog 2024-06-14 20:11:17 +02:00
Stefan Allius
1e3bb31ef8 Merge pull request #90 from s-allius/s-allius/issue56
S allius/issue56
2024-06-14 00:05:48 +02:00
Stefan Allius
d6a44d9173 update changelog 2024-06-13 23:52:13 +02:00
Stefan Allius
43a2ef5712 add systemtest with invalid start byte 2024-06-13 23:45:22 +02:00
Stefan Allius
3209ebabde fix warnings 2024-06-13 23:44:57 +02:00
Stefan Allius
aac6cfd629 dump droped packages 2024-06-13 23:43:05 +02:00
Stefan Allius
e8d32b45a5 label debug images with debug 2024-06-13 23:41:30 +02:00
Stefan Allius
06b63f554d addapt unit test 2024-06-09 11:41:29 +02:00
Stefan Allius
53f6a5447d cleanup msg_get_time handler 2024-06-09 11:41:01 +02:00
Stefan Allius
d6093e6b11 fix pytest collect warning 2024-06-09 11:40:08 +02:00
Stefan Allius
c8113e2f60 update changelog 2024-06-09 11:29:43 +02:00
Stefan Allius
57d6785f15 print image build time during proxy start 2024-06-09 11:22:23 +02:00
Stefan Allius
ff8adb5632 fix solarman unit tests
- fake Mqtt class
2024-06-09 11:02:43 +02:00
Stefan Allius
1deab4be6a fix imports 2024-06-09 11:01:04 +02:00
Stefan Allius
730229cfb0 don't mark all test as async 2024-06-09 01:26:21 +02:00
Stefan Allius
7b9550773d don't use depricated varn anymore 2024-06-09 01:25:06 +02:00
Stefan Allius
3bc2b262b5 add more type annotations 2024-06-08 23:59:13 +02:00
Stefan Allius
37c2246132 fix names of issue branches 2024-06-08 23:57:46 +02:00
Stefan Allius
d0bd599420 fix Generator annotation for ha_proxy_confs 2024-06-08 23:54:52 +02:00
Stefan Allius
661f699444 Merge branch 'main' of https://github.com/s-allius/tsun-gen3-proxy into s-allius/issue56 2024-06-08 23:35:18 +02:00
Stefan Allius
a499c5e6b0 add more type annotations 2024-06-08 23:33:25 +02:00
Stefan Allius
9985917ad2 add more type annotations 2024-06-08 23:15:38 +02:00
Stefan Allius
851bd54d8f Merge branch 'dev-0.9.0' of https://github.com/s-allius/tsun-gen3-proxy into s-allius/issue56 2024-06-08 00:08:54 +02:00
Stefan Allius
81d551e47f initial version 2024-04-30 11:49:59 +02:00
Stefan Allius
63547bb51f adapt tests for stateless timestamp handling 2024-04-29 22:51:31 +02:00
Stefan Allius
6eebd0c852 make timestamp handling stateless 2024-04-29 22:48:41 +02:00
12 changed files with 20 additions and 182 deletions

View File

@@ -7,19 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased]
- cleanup shutdown
- add preview build
- MODBUS: the last digit of the inverter version is a hexadecimal number [#119](https://github.com/s-allius/tsun-gen3-proxy/issues/119)
- GEN3PLUS: add client_mode connection on port 8899 [#117](https://github.com/s-allius/tsun-gen3-proxy/issues/117)
## [0.9.0] - 2024-07-01
- fix exception in MODBUS timeout callback
## [0.9.0-RC1] - 2024-06-29
- add asyncio log and debug mode
- stop the HTTP server on shutdown gracefully
- Synchronize regular MODBUS commands with the status of the inverter to prevent the inverter from crashing due to
unexpected packets. [#111](https://github.com/s-allius/tsun-gen3-proxy/issues/111)
- GEN3: avoid sending MODBUS commands to the inverter during the inverter's reporting phase

View File

@@ -165,9 +165,6 @@ pv2 = {type = 'RSM40-8-405M', manufacturer = 'Risen'} # Optional, PV module de
monitor_sn = 2000000000 # The "Monitoring SN:" can be found on a sticker enclosed with the inverter
node_id = 'inv_3' # MQTT replacement for inverters serial number
suggested_area = 'garage' # suggested installation place for home-assistant
# if your inverter supports SSL connections you must use the client_mode. Pls, uncomment
# the next line and configure the fixed IP of your inverter
#client_mode = {host = '192.168.0.1', port = 8899}
pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
@@ -191,19 +188,8 @@ The standard web interface of the inverter can be accessed at `http://<ip-adress
For our purpose, the hidden URL `http://<ip-adress>/config_hide.html` should be called. There you can see and modify the parameters for accessing the cloud. Here we enter the IP address of our proxy and the IP port `10000` for the `Server A Setting` and for `Optional Server Setting`. The second entry is used as a backup in the event of connection problems.
❗If the IP port is set to 10443 in the inverter configuration, you probably have a firmware with SSL support. In this case, you must use the client-mode configuration.
If access to the web interface does not work, it can also be redirected via DNS redirection, as is necessary for the GEN3 inverters.
## Client Mode (GEN3PLUS only)
Newer GEN3PLUS inverters support SSL encrypted connections over port 10443 to the TSUN cloud. In this case you can't loop the proxy into this connection, since the certicate verification of the inverter don't allow this. You can configure the proxy in client-mode to establish an unencrypted connection to the inverter. For this porpuse the inverter listen on port `8899`.
There are some requirements to be met:
- the inverter should have a fixed IP
- the proxy must be able to reach the inverter. You must configure a corresponding route in your router if the inverter and the proxy are in different IP networks
- add a 'client_mode' line to your config.toml file, to specify the inverter's ip address
## DNS Settings
### Loop the proxy into the connection

View File

@@ -21,11 +21,11 @@ IMAGE=tsun-gen3-proxy
if [[ $1 == debug ]] || [[ $1 == dev ]] ;then
IMAGE=docker.io/sallius/${IMAGE}
VERSION=${VERSION}-$1
elif [[ $1 == rc ]] || [[ $1 == rel ]] || [[ $1 == preview ]] ;then
elif [[ $1 == rc ]] || [[ $1 == rel ]];then
IMAGE=ghcr.io/s-allius/${IMAGE}
else
echo argument missing!
echo try: $0 '[debug|dev|preview|rc|rel]'
echo try: $0 '[debug|dev|rc|rel]'
exit 1
fi
@@ -35,13 +35,6 @@ docker build --build-arg "VERSION=${VERSION}" --build-arg environment=dev --buil
elif [[ $1 == dev ]];then
docker build --build-arg "VERSION=${VERSION}" --build-arg environment=production --label "org.opencontainers.image.created=${BUILD_DATE}" --label "org.opencontainers.image.version=${VERSION}" --label "org.opencontainers.image.revision=${BRANCH}" -t ${IMAGE}:dev app
elif [[ $1 == preview ]];then
docker build --build-arg "VERSION=${VERSION}" --build-arg environment=production --label "org.opencontainers.image.created=${BUILD_DATE}" --label "org.opencontainers.image.version=${VERSION}" --label "org.opencontainers.image.revision=${BRANCH}" -t ${IMAGE}:preview -t ${IMAGE}:${VERSION} app
echo 'login to ghcr.io'
echo $GHCR_TOKEN | docker login ghcr.io -u s-allius --password-stdin
docker push -q ghcr.io/s-allius/tsun-gen3-proxy:preview
docker push -q ghcr.io/s-allius/tsun-gen3-proxy:${VERSION}
elif [[ $1 == rc ]];then
docker build --build-arg "VERSION=${VERSION}" --build-arg environment=production --label "org.opencontainers.image.created=${BUILD_DATE}" --label "org.opencontainers.image.version=${VERSION}" --label "org.opencontainers.image.revision=${BRANCH}" -t ${IMAGE}:rc -t ${IMAGE}:${VERSION} app
echo 'login to ghcr.io'

View File

@@ -44,11 +44,6 @@ inverters.allow_all = true # allow inverters, even if we have no inverter mapp
monitor_sn = 2000000000 # The "Monitoring SN:" can be found on a sticker enclosed with the inverter
#node_id = '' # Optional, MQTT replacement for inverters serial number
#suggested_area = '' # Optional, suggested installation place for home-assistant
# if your inverter supports SSL connections you must use the client_mode. Pls, uncomment
# the next line and configure the fixed IP of your inverter
#client_mode = {host = '192.168.0.1', port = 8899}
#pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
#pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
#pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr

View File

@@ -53,11 +53,7 @@ class Config():
Use(lambda s: s + '/'
if len(s) > 0 and
s[-1] != '/' else s)),
Optional('client_mode'): {
'host': Use(str),
Optional('port', default=8899):
And(Use(int), lambda n: 1024 <= n <= 65535)
},
Optional('suggested_area', default=""): Use(str),
Optional('pv1'): {
Optional('type'): Use(str),

View File

@@ -66,8 +66,6 @@ class SolarmanV5(Message):
self.db = InfosG3P()
self.time_ofs = 0
self.forward_at_cmd_resp = False
self.no_forwarding = False
'''not allowed to connect to TSUN cloud by connection type'''
self.switch = {
0x4210: self.msg_data_ind, # real time data
@@ -145,19 +143,6 @@ class SolarmanV5(Message):
self.mb_timer.close()
super().close()
async def send_start_cmd(self, snr: int):
self.no_forwarding = True
self.snr = snr
self.__set_serial_no(snr)
self.__send_ack_rsp(0x1710, ftype=0)
await self.async_write('Send Start Command:')
self._send_buffer = bytearray(0)
self.state = State.up
self._send_modbus_cmd(Modbus.READ_REGS, 0x2000, 64, logging.INFO)
self.mb_timer.start(self.MB_START_TIMEOUT)
def __set_serial_no(self, snr: int):
serial_no = str(snr)
if self.unique_id == serial_no:
@@ -213,8 +198,6 @@ class SolarmanV5(Message):
return 0 # wait 0s before sending a response
def forward(self, buffer, buflen) -> None:
if self.no_forwarding:
return
tsun = Config.get('solarman')
if tsun['enabled']:
self._forward_buffer = buffer[:buflen]
@@ -397,11 +380,11 @@ class SolarmanV5(Message):
def mb_timout_cb(self, exp_cnt):
self.mb_timer.start(self.MB_REGULAR_TIMEOUT)
self._send_modbus_cmd(Modbus.READ_REGS, 0x3000, 48, logging.DEBUG)
self._send_modbus_cmd(Modbus.READ_REGS, 0x3008, 21, logging.DEBUG)
if 0 == (exp_cnt % 30):
# logging.info("Regular Modbus Status request")
self._send_modbus_cmd(Modbus.READ_REGS, 0x2000, 64, logging.DEBUG)
self._send_modbus_cmd(Modbus.READ_REGS, 0x2007, 2, logging.DEBUG)
def at_cmd_forbidden(self, cmd: str, connection: str) -> bool:
return not cmd.startswith(tuple(self.at_acl[connection]['allow'])) or \

View File

@@ -91,7 +91,6 @@ class Message(metaclass=IterRegistry):
self._forward_buffer = bytearray(0)
self.new_data = {}
self.state = State.init
self.shutdown_started = False
'''
Empty methods, that have to be implemented in any child class which

View File

@@ -41,10 +41,8 @@ class Modbus():
__crc_tab = []
map = {
0x2007: {'reg': Register.MAX_DESIGNED_POWER, 'fmt': '!H', 'ratio': 1}, # noqa: E501
0x203e: {'reg': Register.NO_INPUTS, 'fmt': '!H', 'ratio': 1/256}, # noqa: E501
0x3000: {'reg': Register.INVERTER_STATUS, 'fmt': '!H'}, # noqa: E501
0x3008: {'reg': Register.VERSION, 'fmt': '!H', 'eval': "f'V{(result>>12)}.{(result>>8)&0xf}.{(result>>4)&0xf}{result&0xf:1X}'"}, # noqa: E501
# 0x????: {'reg': Register.INVERTER_STATUS, 'fmt': '!H'}, # noqa: E501
0x3008: {'reg': Register.VERSION, 'fmt': '!H', 'eval': "f'V{(result>>12)}.{(result>>8)&0xf}.{(result>>4)&0xf}{result&0xf}'"}, # noqa: E501
0x3009: {'reg': Register.GRID_VOLTAGE, 'fmt': '!H', 'ratio': 0.1}, # noqa: E501
0x300a: {'reg': Register.GRID_CURRENT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
0x300b: {'reg': Register.GRID_FREQUENCY, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501
@@ -110,7 +108,6 @@ class Modbus():
def close(self):
"""free the queue and erase the callback handlers"""
logging.debug('Modbus close:')
self.__stop_timer()
self.rsp_handler = None
self.snd_handler = None
while not self.que.empty:
@@ -255,7 +252,6 @@ class Modbus():
# logging.debug(f'Modbus stop timer {self}')
if self.tim:
self.tim.cancel()
self.tim = None
def __timeout_cb(self) -> None:
'''Rsponse timeout handler retransmit pdu or send next pdu'''

View File

@@ -1,73 +0,0 @@
import logging
import traceback
import asyncio
from config import Config
# import gc
from gen3plus.inverter_g3p import InverterG3P
logger = logging.getLogger('conn')
class ModbusConn():
def __init__(self, host, port):
self.host = host
self.port = port
self.addr = (host, port)
self.stream = None
async def __aenter__(self) -> 'InverterG3P':
'''Establish a client connection to the TSUN cloud'''
connection = asyncio.open_connection(self.host, self.port)
reader, writer = await connection
self.stream = InverterG3P(reader, writer, self.addr)
logging.info(f'[{self.stream.node_id}:{self.stream.conn_no}] '
f'Connected to {self.addr}')
self.stream.inc_counter('Inverter_Cnt')
return self.stream
async def __aexit__(self, exc_type, exc, tb):
self.stream.dec_counter('Inverter_Cnt')
class ModbusTcp():
def __init__(self, loop) -> None:
inverters = Config.get('inverters')
# logging.info(f'Inverters: {inverters}')
for inv in inverters.values():
if (type(inv) is dict
and 'monitor_sn' in inv
and 'client_mode' in inv):
client = inv['client_mode']
# logging.info(f"SerialNo:{inv['monitor_sn']} host:{client['host']} port:{client['port']}") # noqa: E501
loop.create_task(self.modbus_loop(client['host'],
client['port'],
inv['monitor_sn']))
async def modbus_loop(self, host, port, snr: int) -> None:
'''Loop for receiving messages from the TSUN cloud (client-side)'''
while True:
try:
async with ModbusConn(host, port) as stream:
await stream.send_start_cmd(snr)
await stream.loop()
logger.info(f'[{stream.node_id}:{stream.conn_no}] '
f'Connection closed - Shutdown: '
f'{stream.shutdown_started}')
if stream.shutdown_started:
return
except (ConnectionRefusedError, TimeoutError) as error:
logging.debug(f'Inv-conn:{error}')
except OSError as error:
logging.info(f'os-error: {error}')
except Exception:
logging.error(
f"ModbusTcpCreate: Exception for {(host,port)}:\n"
f"{traceback.format_exc()}")
await asyncio.sleep(10)

View File

@@ -11,7 +11,6 @@ from gen3.inverter_g3 import InverterG3
from gen3plus.inverter_g3p import InverterG3P
from scheduler import Schedule
from config import Config
from modbus_tcp import ModbusTcp
routes = web.RouteTableDef()
proxy_is_up = False
@@ -95,7 +94,6 @@ async def handle_shutdown(web_task):
# first, disc all open TCP connections gracefully
#
for stream in Message:
stream.shutdown_started = True
try:
await asyncio.wait_for(stream.disc(), 2)
except Exception:
@@ -117,13 +115,6 @@ async def handle_shutdown(web_task):
web_task.cancel()
await web_task
#
# now cancel all remaining (pending) tasks
#
pending = asyncio.all_tasks()
for task in pending:
task.cancel()
#
# at last, start a coro for stopping the loop
#
@@ -173,7 +164,6 @@ if __name__ == "__main__":
logging.info(f'ConfigErr: {ConfigErr}')
Inverter.class_init()
Schedule.start()
mb_tcp = ModbusTcp(loop)
#
# Create tasks for our listening servers. These must be tasks! If we call

View File

@@ -32,12 +32,7 @@ def test_modbus_crc():
assert mb._Modbus__check_crc(b'\x01\x06\x20\x08\x00\x00\x03\xc8')
assert 0x5c75 == mb._Modbus__calc_crc(b'\x01\x03\x08\x01\x2c\x00\x2c\x02\x2c\x2c\x46')
msg = b'\x01\x03\x28\x51'
msg += b'\x0e\x08\xd3\x00\x29\x13\x87\x00\x3e\x00\x00\x01\x2c\x03\xb4\x00'
msg += b'\x08\x00\x00\x00\x00\x01\x59\x01\x21\x03\xe6\x00\x00\x00\x00\x00'
msg += b'\x00\x00\x00\x00\x00\x00\x00\xe6\xef'
assert 0 == mb._Modbus__calc_crc(msg)
def test_build_modbus_pdu():
'''Check building and sending a MODBUS RTU'''
mb = ModbusTestHelper()
@@ -178,7 +173,7 @@ def test_parse_resp():
assert mb.req_pend
call = 0
exp_result = ['V0.0.2C', 4.4, 0.7, 0.7, 30]
exp_result = ['V0.0.212', 4.4, 0.7, 0.7, 30]
for key, update, val in mb.recv_resp(mb.db, b'\x01\x03\x0c\x01\x2c\x00\x2c\x00\x2c\x00\x46\x00\x46\x00\x46\x32\xc8', 'test'):
if key == 'grid':
assert update == True
@@ -227,7 +222,7 @@ def test_queue2():
assert mb.send_calls == 1
assert mb.pdu == b'\x01\x030\x07\x00\x06{\t'
call = 0
exp_result = ['V0.0.2C', 4.4, 0.7, 0.7, 30]
exp_result = ['V0.0.212', 4.4, 0.7, 0.7, 30]
for key, update, val in mb.recv_resp(mb.db, b'\x01\x03\x0c\x01\x2c\x00\x2c\x00\x2c\x00\x46\x00\x46\x00\x46\x32\xc8', 'test'):
if key == 'grid':
assert update == True
@@ -277,7 +272,7 @@ def test_queue3():
assert mb.recv_responses == 0
call = 0
exp_result = ['V0.0.2C', 4.4, 0.7, 0.7, 30]
exp_result = ['V0.0.212', 4.4, 0.7, 0.7, 30]
for key, update, val in mb.recv_resp(mb.db, b'\x01\x03\x0c\x01\x2c\x00\x2c\x00\x2c\x00\x46\x00\x46\x00\x46\x32\xc8', 'test'):
if key == 'grid':
assert update == True

View File

@@ -339,15 +339,6 @@ def MsgModbusResp20():
msg += b'\x00\x00\x00\x00\x00\x00\x00\xdb\x6b'
return msg
@pytest.fixture
def MsgModbusResp21():
msg = b'\x00\x00\x00\x45\x10R170000000000001'
msg += b'\x91\x77\x17\x18\x19\x1a\x2d\x01\x03\x28\x51'
msg += b'\x0e\x08\xd3\x00\x29\x13\x87\x00\x3e\x00\x00\x01\x2c\x03\xb4\x00'
msg += b'\x08\x00\x00\x00\x00\x01\x59\x01\x21\x03\xe6\x00\x00\x00\x00\x00'
msg += b'\x00\x00\x00\x00\x00\x00\x00\xe6\xef'
return msg
def test_read_message(MsgContactInfo):
m = MemoryStream(MsgContactInfo, (0,))
m.read() # read complete msg, and dispatch msg
@@ -1235,11 +1226,11 @@ def test_msg_modbus_rsp2(ConfigTsunInv1, MsgModbusResp20):
m.close()
def test_msg_modbus_rsp3(ConfigTsunInv1, MsgModbusResp21):
def test_msg_modbus_rsp3(ConfigTsunInv1, MsgModbusResp20):
'''Modbus response with a valid Modbus request must be forwarded'''
ConfigTsunInv1
m = MemoryStream(MsgModbusResp21)
m.append_msg(MsgModbusResp21)
m = MemoryStream(MsgModbusResp20)
m.append_msg(MsgModbusResp20)
m.mb.rsp_handler = m.msg_forward
m.mb.last_addr = 1
@@ -1256,10 +1247,10 @@ def test_msg_modbus_rsp3(ConfigTsunInv1, MsgModbusResp21):
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.mb.err == 0
assert m.msg_count == 1
assert m._forward_buffer==MsgModbusResp21
assert m._forward_buffer==MsgModbusResp20
assert m._send_buffer==b''
assert m.db.db == {'inverter': {'Version': 'V5.1.0E', 'Rated_Power': 300}, 'grid': {'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}}
assert m.db.get_db_value(Register.VERSION) == 'V5.1.0E'
assert m.db.db == {'inverter': {'Version': 'V5.1.09', 'Rated_Power': 300}, 'grid': {'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}}
assert m.db.get_db_value(Register.VERSION) == 'V5.1.09'
assert m.new_data['inverter'] == True
m.new_data['inverter'] = False
assert m.mb.req_pend == False
@@ -1268,10 +1259,10 @@ def test_msg_modbus_rsp3(ConfigTsunInv1, MsgModbusResp21):
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
assert m.mb.err == 5
assert m.msg_count == 2
assert m._forward_buffer==MsgModbusResp21
assert m._forward_buffer==MsgModbusResp20
assert m._send_buffer==b''
assert m.db.db == {'inverter': {'Version': 'V5.1.0E', 'Rated_Power': 300}, 'grid': {'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}}
assert m.db.get_db_value(Register.VERSION) == 'V5.1.0E'
assert m.db.db == {'inverter': {'Version': 'V5.1.09', 'Rated_Power': 300}, 'grid': {'Voltage': 225.9, 'Current': 0.41, 'Frequency': 49.99, 'Output_Power': 94.8}, 'env': {'Inverter_Temp': 22}, 'input': {'pv1': {'Voltage': 0.8, 'Current': 0.0, 'Power': 0.0}, 'pv2': {'Voltage': 34.5, 'Current': 2.89, 'Power': 99.8}, 'pv3': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}, 'pv4': {'Voltage': 0.0, 'Current': 0.0, 'Power': 0.0}}}
assert m.db.get_db_value(Register.VERSION) == 'V5.1.09'
assert m.new_data['inverter'] == False
m.close()