Compare commits
4 Commits
s-allius/i
...
s-allius/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0580764c79 | ||
|
|
56cc875417 | ||
|
|
908c13398c | ||
|
|
a060d3ba8e |
@@ -6,9 +6,4 @@ PRIVAT_CONTAINER_REGISTRY=docker.io/<user>/
|
||||
|
||||
# registry for official container (preview, rc, rel)
|
||||
PUBLIC_CONTAINER_REGISTRY=ghcr.io/<user>/
|
||||
PUBLIC_CR_KEY=
|
||||
|
||||
# define serial number of GEN3PLUS devices for systemtests
|
||||
# the serialnumber are coded as 4-byte hex-strings
|
||||
SOLARMAN_INV_SNR='00000000'
|
||||
SOLARMAN_DCU_SNR='00000000'
|
||||
PUBLIC_CR_KEY=
|
||||
2
.github/workflows/python-app.yml
vendored
2
.github/workflows/python-app.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
- name: Lint with flake8
|
||||
run: |
|
||||
# stop the build if there are Python syntax errors or undefined names
|
||||
flake8 . --count --select=E9,F63,F7,F82 --ignore=F821 --show-source --statistics
|
||||
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
||||
flake8 --exit-zero --ignore=C901,E121,E123,E126,E133,E226,E241,E242,E704,W503,W504,W505 --format=pylint --output-file=output_flake.txt --exclude=*.pyc app/src/
|
||||
- name: Test with pytest
|
||||
|
||||
109
README.md
109
README.md
@@ -1,14 +1,13 @@
|
||||
<h1 align="center">TSUN-Gen3-Proxy</h1>
|
||||
<p align="center">A proxy for</p>
|
||||
<h3 align="center">TSUN Gen 3 Micro-Inverters</h3>
|
||||
<h3 align="center">and Batteries</h3>
|
||||
<p align="center">for easy</p>
|
||||
<h3 align="center">MQTT/Home-Assistant</h3>
|
||||
<p align="center">integration</p>
|
||||
<p align="center">
|
||||
<a href="https://opensource.org/licenses/BSD-3-Clause"><img alt="License: BSD-3-Clause" src="https://img.shields.io/badge/License-BSD_3--Clause-green.svg"></a>
|
||||
<a href="https://www.python.org/downloads/release/python-3130/"><img alt="Supported Python versions" src="https://img.shields.io/badge/python-3.13-blue.svg"></a>
|
||||
<a href="https://aiomqtt.bo3hm.com/introduction.html"><img alt="Supported aiomqtt versions" src="https://img.shields.io/badge/aiomqtt-2.3.1-lightblue.svg"></a>
|
||||
<a href="https://sbtinstruments.github.io/aiomqtt/introduction.html"><img alt="Supported aiomqtt versions" src="https://img.shields.io/badge/aiomqtt-2.3.0-lightblue.svg"></a>
|
||||
<a href="https://libraries.io/pypi/aiocron"><img alt="Supported aiocron versions" src="https://img.shields.io/badge/aiocron-1.8-lightblue.svg"></a>
|
||||
<a href="https://toml.io/en/v1.0.0"><img alt="Supported toml versions" src="https://img.shields.io/badge/toml-1.0.0-lightblue.svg"></a>
|
||||
<br>
|
||||
@@ -21,11 +20,11 @@
|
||||
|
||||
# Overview
|
||||
|
||||
This proxy enables a reliable connection between TSUN third generation devices and an MQTT broker. With the proxy, you can easily retrieve real-time values such as power, current and daily energy from inverters and energy storage systems and integrate them into typical home automations. This works even without an internet connection. The optional connection to the TSUN Cloud can be disabled!
|
||||
This proxy enables a reliable connection between TSUN third generation inverters and an MQTT broker. With the proxy, you can easily retrieve real-time values such as power, current and daily energy and integrate the inverter into typical home automations. This works even without an internet connection. The optional connection to the TSUN Cloud can be disabled!
|
||||
|
||||
In detail, the device establishes a TCP connection to the TSUN cloud to transmit current measured values every 300 seconds. To be able to forward the measurement data to an MQTT broker, the proxy must be looped into this TCP connection.
|
||||
In detail, the inverter establishes a TCP connection to the TSUN cloud to transmit current measured values every 300 seconds. To be able to forward the measurement data to an MQTT broker, the proxy must be looped into this TCP connection.
|
||||
|
||||
Through this, the device then establishes a connection to the proxy and the proxy establishes another connection to the TSUN Cloud. The transmitted data is interpreted by the proxy and then passed on to both the TSUN Cloud and the MQTT broker. The connection to the TSUN Cloud is optional and can be switched off in the configuration (default is on). Then no more data is sent to the Internet, but no more remote updates of firmware and operating parameters (e.g. rated power, grid parameters) are possible.
|
||||
Through this, the inverter then establishes a connection to the proxy and the proxy establishes another connection to the TSUN Cloud. The transmitted data is interpreted by the proxy and then passed on to both the TSUN Cloud and the MQTT broker. The connection to the TSUN Cloud is optional and can be switched off in the configuration (default is on). Then no more data is sent to the Internet, but no more remote updates of firmware and operating parameters (e.g. rated power, grid parameters) are possible.
|
||||
|
||||
By means of `docker` a simple installation and operation is possible. By using `docker-composer`, a complete stack of proxy, `MQTT-brocker` and `home-assistant` can be started easily.
|
||||
|
||||
@@ -33,12 +32,12 @@ Alternatively you can run the TSUN-Proxy as a Home Assistant Add-on. The install
|
||||
Follow the Instructions mentioned in the add-on subdirectory `ha_addons`.
|
||||
|
||||
<br>
|
||||
ℹ️ This project is not related to the company TSUN. It is a private initiative that aims to connect TSUN inverters and storage systems with an MQTT broker. There is no support and no warranty from TSUN.
|
||||
ℹ️ This project is not related to the company TSUN. It is a private initiative that aims to connect TSUN inverters with an MQTT broker. There is no support and no warranty from TSUN.
|
||||
<br><br>
|
||||
|
||||
```txt
|
||||
❗An essential requirement is that the proxy can be looped into the connection
|
||||
between the device and TSUN Cloud.
|
||||
between the inverter and TSUN Cloud.
|
||||
|
||||
There are various ways to do this, for example via an DNS host entry or via firewall
|
||||
rules (iptables) in your router. However, depending on the circumstances, not all
|
||||
@@ -50,8 +49,7 @@ If you use a Pi-hole, you can also store the host entry in the Pi-hole.
|
||||
## Features
|
||||
|
||||
- Supports TSUN GEN3 PLUS inverters: TSOL-MS2000, MS1800 and MS1600
|
||||
- Supports TSUN GEN3 PLUS batteries: TSOL-DC1000 (from version 0.13)
|
||||
- Supports TSUN GEN3 inverters: TSOL-MS3000, MS800, MS700, MS600, MS400, MS350 and MS300
|
||||
- Supports TSUN GEN3 inverters: TSOL-MS800, MS700, MS600, MS400, MS350 and MS300
|
||||
- `MQTT` support
|
||||
- `Home-Assistant` auto-discovery support
|
||||
- `MODBUS` support via MQTT topics
|
||||
@@ -70,15 +68,15 @@ Here are some screenshots of how the inverter is displayed in the Home Assistant
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirements for Docker Installation
|
||||
### for Docker Installation
|
||||
|
||||
- A running Docker engine to host the container
|
||||
- Ability to loop the proxy into the connection between the device and the TSUN cloud
|
||||
- Ability to loop the proxy into the connection between the inverter and the TSUN cloud
|
||||
|
||||
### Requirements for Home Assistant Add-on Installation
|
||||
### for Home Assistant Add-on Installation
|
||||
|
||||
- Running Home Assistant on Home Assistant OS or Supervised. Container and Core installations doesn't support add-ons.
|
||||
- Ability to loop the proxy into the connection between the device and the TSUN cloud
|
||||
- Ability to loop the proxy into the connection between the inverter and the TSUN cloud
|
||||
|
||||
# Getting Started
|
||||
|
||||
@@ -119,20 +117,19 @@ docker run --dns '8.8.8.8' --env 'UID=1050' -p '5005:5005' -p '10000:10000' -v
|
||||
# Configuration
|
||||
|
||||
```txt
|
||||
❗The following description applies to the Docker installation. When installing the Home
|
||||
Assistant add-on, the configuration is carried out via the Home Assistant UI. Some of the
|
||||
options described below are not required for this. Additionally, creating a config.toml
|
||||
file is not necessary. However, for a general understanding of the configuration and
|
||||
functionality, it is helpful to read the following description.
|
||||
❗The following description applies to the Docker installation. When installing the Home Assistant add-on, the
|
||||
configuration is carried out via the Home Assistant UI. Some of the options described below are not required for
|
||||
this. Additionally, creating a config.toml file is not necessary. However, for a general understanding of the
|
||||
configuration and functionality, it is helpful to read the following description.
|
||||
```
|
||||
|
||||
The configuration consists of several parts. First, the container and the proxy itself must be configured, and then the connection of the device to the proxy must be set up, which is done differently depending on the device generation
|
||||
The configuration consists of several parts. First, the container and the proxy itself must be configured, and then the connection of the inverter to the proxy must be set up, which is done differently depending on the inverter generation
|
||||
|
||||
For GEN3PLUS devices, this can be done easily via the web interface of the devices. The GEN3 inverters do not have a web interface, so the proxy is integrated via a modified DNS resolution.
|
||||
For GEN3PLUS inverters, this can be done easily via the web interface of the inverter. The GEN3 inverters do not have a web interface, so the proxy is integrated via a modified DNS resolution.
|
||||
|
||||
1. [Container Setup](#container-setup)
|
||||
2. [Proxy Configuration](#proxy-configuration)
|
||||
3. [Inverter and Batterie Configuration](#inverter-and-batterie-configuration) (only GEN3PLUS)
|
||||
3. [Inverter Configuration](#inverter-configuration) (only GEN3PLUS)
|
||||
4. [DNS Settings](#dns-settings) (Mandatory for GEN3)
|
||||
|
||||
## Container Setup
|
||||
@@ -312,33 +309,6 @@ pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module de
|
||||
pv4 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
||||
|
||||
|
||||
##########################################################################################
|
||||
##
|
||||
## For each GEN3PLUS energy storage system, the serial number must be mapped to an MQTT
|
||||
## definition. To do this, the corresponding configuration block is started with
|
||||
## `[batteries.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## to this energy storage system. Further device-specific parameters (e.g. polling mode,
|
||||
## client mode) can be set in the configuration block
|
||||
##
|
||||
## The serial numbers of all GEN3PLUS energy storage systems/batteries start with `410`!
|
||||
## Each GEN3PLUS device is supplied with a “Monitoring SN:”. This can be found on a
|
||||
## sticker enclosed with the inverter.
|
||||
##
|
||||
|
||||
[batteries."4100000000000001"]
|
||||
monitor_sn = 3000000000 # The GEN3PLUS "Monitoring SN:"
|
||||
node_id = 'bat_1' # MQTT replacement for devices serial number
|
||||
suggested_area = ''garage' # suggested installation place for home-assistant
|
||||
modbus_polling = true # Enable optional MODBUS polling
|
||||
|
||||
# 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, forward = true}
|
||||
|
||||
pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
||||
pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
|
||||
|
||||
|
||||
##########################################################################################
|
||||
###
|
||||
### If the proxy mode is configured, commands from TSUN can be sent to the inverter via
|
||||
@@ -360,19 +330,19 @@ mqtt.block = []
|
||||
|
||||
</details>
|
||||
|
||||
## Inverter and Batterie Configuration
|
||||
## Inverter Configuration
|
||||
|
||||
GEN3PLUS devices (inverter, batteries, ...) offer a web interface that can be used to configure it. This is very practical for sending the data directly to the proxy. On the one hand, the device broadcasts its own SSID on 2.4GHz. This can be recognized because it is broadcast with `AP_<Montoring SN>`. You will find the `Monitor SN` and the password for the WLAN connection on a small sticker enclosed with the device.
|
||||
GEN3PLUS inverters offer a web interface that can be used to configure the inverter. This is very practical for sending the data directly to the proxy. On the one hand, the inverter broadcasts its own SSID on 2.4GHz. This can be recognized because it is broadcast with `AP_<Montoring SN>`. You will find the `Monitor SN` and the password for the WLAN connection on a small sticker enclosed with the inverter.
|
||||
|
||||
If you have already connected the device to the cloud via the TSUN app, you can also address the device directly via WiFi. In the first case, the device uses the fixed IP address `10.10.100.254`, in the second case you have to look up the IP address in your router.
|
||||
If you have already connected the inverter to the cloud via the TSUN app, you can also address the inverter directly via WiFi. In the first case, the inverter uses the fixed IP address `10.10.100.254`, in the second case you have to look up the IP address in your router.
|
||||
|
||||
The standard web interface of the device can be accessed at `http://<ip-adress>/index_cn.html`. Here you can set up the WLAN connection or change the password. The default user and password is `admin`/`admin`.
|
||||
The standard web interface of the inverter can be accessed at `http://<ip-adress>/index_cn.html`. Here you can set up the WLAN connection or change the password. The default user and password is `admin`/`admin`.
|
||||
|
||||
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.
|
||||
|
||||
```txt
|
||||
❗If the IP port is set to 10443 in the device configuration, you probably have a firmware with SSL support.
|
||||
In this case, you MUST NOT change the port or the host address, as this may cause the device to hang and
|
||||
❗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 NOT change the port or the host address, as this may cause the inverter to hang and
|
||||
require a complete reset. Use the configuration in client mode instead.
|
||||
```
|
||||
|
||||
@@ -380,23 +350,23 @@ If access to the web interface does not work, it can also be redirected via DNS
|
||||
|
||||
## Client Mode (GEN3PLUS only)
|
||||
|
||||
Newer GEN3PLUS inverters, batteries and smart meter 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 device don't allow this. You can configure the proxy in client-mode to establish an unencrypted connection to the inverter. For this porpuse the device listen on port `8899`.
|
||||
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 device should have a fixed IP
|
||||
- the proxy must be able to reach the device. You must configure a corresponding route in your router if the device and the proxy are in different IP networks
|
||||
- add a 'client_mode' line to your config.toml file, to specify the device's ip address
|
||||
- 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
|
||||
|
||||
To include the proxy in the connection between the device and the TSUN Cloud, you must adapt the DNS record of *logger.talent-monitoring.com* within the network that your deivce uses. You need a mapping from logger.talent-monitoring.com to the IP address of the host running the Docker engine.
|
||||
To include the proxy in the connection between the inverter and the TSUN Cloud, you must adapt the DNS record of *logger.talent-monitoring.com* within the network that your inverter uses. You need a mapping from logger.talent-monitoring.com to the IP address of the host running the Docker engine.
|
||||
|
||||
The new GEN3 PLUS devices use a different URL. Here, *iot.talent-monitoring.com* must be redirected.
|
||||
The new GEN3 PLUS inverters use a different URL. Here, *iot.talent-monitoring.com* must be redirected.
|
||||
|
||||
This can be done, for example, by adding a local DNS record to the Pi-hole if you are using it. User of the Home Assistant Add-on should use the AdGuard Add-on for this.
|
||||
This can be done, for example, by adding a local DNS record to the Pi-hole if you are using it.
|
||||
|
||||
### DNS Rebind Protection
|
||||
|
||||
@@ -412,7 +382,7 @@ As described above, set a DNS sever in the Docker command or Docker compose file
|
||||
|
||||
### Over The Air (OTA) firmware update
|
||||
|
||||
Even if the proxy is connected between the device and the TSUN Cloud, an OTA update is supported. To do this, the device must be able to reach the website <http://www.talent-monitoring.com:9002/> in order to download images from there.
|
||||
Even if the proxy is connected between the inverter and the TSUN Cloud, an OTA update is supported. To do this, the inverter must be able to reach the website <http://www.talent-monitoring.com:9002/> in order to download images from there.
|
||||
|
||||
It must be ensured that this address is not mapped to the proxy!
|
||||
|
||||
@@ -425,24 +395,21 @@ A combination with a red question mark should work, but I have not checked it in
|
||||
|
||||
<table align="center">
|
||||
<tr><th align="center">Micro Inverter Model</th><th align="center">Fw. 1.00.06</th><th align="center">Fw. 1.00.17</th><th align="center">Fw. 1.00.20</th><th align="center">Fw. 4.0.10</th><th align="center">Fw. 4.0.20</th></tr>
|
||||
<tr><td>GEN3 micro inverters (single MPPT):<br>MS300, MS350, MS400<br>MS400-D</td><td align="center">✔️</td><td align="center">✔️</td><td align="center">✔️</td><td align="center">➖</td><td align="center">➖</td></tr>
|
||||
<tr><td>GEN3 micro inverters (single MPPT):<br>MS300, MS350, MS400<br>MS400-D</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td><td align="center">➖</td><td align="center">➖</td></tr>
|
||||
<tr><td>GEN3 micro inverters (dual MPPT):<br>MS600, MS700, MS800<br>MS600-D, MS800-D</td><td align="center">✔️</td><td align="center">✔️</td><td align="center">✔️</td><td align="center">➖</td><td align="center">➖</td></tr>
|
||||
<tr><td>GEN3 micro inverters (quad MPPT):<br>MS3000</td><td align="center">✔️</td><td align="center">✔️</td><td align="center">✔️</td><td align="center">➖</td><td align="center">➖</td></tr>
|
||||
<tr><td>GEN3 PLUS micro inverters:<br>MS1600, MS1800, MS2000<br>MS2000-D, MS800</td><td align="center">➖</td><td align="center">➖</td><td align="center">➖</td><td align="center">✔️</td><td align="center">✔️</td></tr>
|
||||
<tr><td>GEN3 PLUS storage systems:<br>DC1000</td><td align="center">➖</td><td align="center">➖</td><td align="center">➖</td><td align="center">✔️</td><td align="center">✔️</td></tr>
|
||||
<tr><td>GEN3 PLUS smart meter:<br>TSOL-MG3-MS, DDZY422-D2</td><td align="center">➖</td><td align="center">➖</td><td align="center">➖</td><td align="center">❓</td><td align="center">❓</td></tr>
|
||||
</<tr><td>TITAN micro inverters:<br>TSOL-MP3000, MP2250</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td></tr>
|
||||
<tr><td>GEN3 PLUS micro inverters:<br>MS1600, MS1800, MS2000<br>MS2000-D</td><td align="center">➖</td><td align="center">➖</td><td align="center">➖</td><td align="center">✔️</td><td align="center">✔️</td></tr>
|
||||
<tr><td>TITAN micro inverters:<br>TSOL-MP3000, MP2250, MS3000</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td><td align="center">❓</td></tr>
|
||||
</table>
|
||||
|
||||
```txt
|
||||
Legend
|
||||
➖: Firmware not available for this devices
|
||||
✔️: Proxy support testet
|
||||
❓: Proxy support unknown. There is an open port, but all known protocols do not work.
|
||||
✔️: proxy support testet
|
||||
❓: proxy support possible but not testet
|
||||
🚧: Proxy support in preparation
|
||||
```
|
||||
|
||||
❗GEN3 Plus generation devices (e.g. MS-2000, DC-1000) can be recognized by their serial number. This starts with 'Y17' or 'Y47' for inverters and '410' for the DC-1000 battery storage system. In contrast, the serial number of GEN3 inverters begins with 'R17' or 'R47'.
|
||||
❗The new inverters of the GEN3 Plus generation (e.g. MS-2000) use a completely different protocol for data transmission to the TSUN server. These inverters are supported from proxy version 0.6. The serial numbers of these inverters start with `Y17E` or `Y47E` instead of `R17E`
|
||||
|
||||
If you have one of these combinations with a red question mark, it would be very nice if you could send me a proxy trace so that I can carry out the detailed checks and adjust the device and system tests. [Ask here how to send a trace](https://github.com/s-allius/tsun-gen3-proxy/discussions/categories/traces-for-compatibility-check)
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
flake8==7.2.0
|
||||
flake8==7.1.2
|
||||
pytest==8.3.5
|
||||
pytest-asyncio==0.26.0
|
||||
pytest-cov==6.1.0
|
||||
python-dotenv==1.1.0
|
||||
pytest-asyncio==0.25.3
|
||||
pytest-cov==6.0.0
|
||||
python-dotenv==1.0.1
|
||||
mock==5.2.0
|
||||
coverage==7.8.0
|
||||
coverage==7.7.1
|
||||
jinja2-cli==0.8.2
|
||||
@@ -1,4 +1,4 @@
|
||||
aiomqtt==2.3.1
|
||||
aiomqtt==2.3.0
|
||||
schema==0.7.7
|
||||
aiocron==2.1
|
||||
aiohttp==3.11.16
|
||||
aiohttp==3.11.14
|
||||
@@ -159,7 +159,7 @@ pv4 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module de
|
||||
|
||||
##########################################################################################
|
||||
##
|
||||
## For each GEN3PLUS energy storage system, the serial number must be mapped to an MQTT
|
||||
## For each GEN3PLUS enrgy storage system, the serial number must be mapped to an MQTT
|
||||
## definition. To do this, the corresponding configuration block is started with
|
||||
## `[batteries.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## to this energy storage system. Further device-specific parameters (e.g. polling mode,
|
||||
|
||||
@@ -84,10 +84,7 @@ async def test_close_cb():
|
||||
return 0.1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
# The callback will be called after the AsyncStreamServer
|
||||
# constructer has finished and so ifc must be defined in the
|
||||
# upper scope
|
||||
assert "ifc" in locals()
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
|
||||
@@ -116,6 +113,7 @@ async def test_close_cb():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_read():
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_13_BYTES
|
||||
@@ -126,13 +124,11 @@ async def test_read():
|
||||
return 1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
# The callback will be called after the AsyncStreamServer
|
||||
# constructer has finished and so ifc must be defined in the
|
||||
# upper scope
|
||||
assert "ifc" in locals()
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
def app_read():
|
||||
nonlocal ifc
|
||||
ifc.proc_start -= 3
|
||||
return 0.01 # async wait of 0.01
|
||||
cnt = 0
|
||||
@@ -155,6 +151,7 @@ async def test_read():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_write():
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_13_BYTES
|
||||
@@ -165,13 +162,11 @@ async def test_write():
|
||||
return 1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
# The callback will be called after the AsyncStreamServer
|
||||
# constructer has finished and so ifc must be defined in the
|
||||
# upper scope
|
||||
assert "ifc" in locals()
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
def app_read():
|
||||
nonlocal ifc
|
||||
ifc.proc_start -= 3
|
||||
return 0.01 # async wait of 0.01
|
||||
|
||||
@@ -208,6 +203,7 @@ async def test_publ_mqtt_cb():
|
||||
return 0.1
|
||||
async def publ_mqtt():
|
||||
nonlocal cnt
|
||||
nonlocal ifc
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
@@ -237,10 +233,7 @@ async def test_create_remote_cb():
|
||||
return 0.1
|
||||
async def create_remote():
|
||||
nonlocal cnt
|
||||
# The callback will be called after the AsyncStreamServer
|
||||
# constructer has finished and so ifc must be defined in the
|
||||
# upper scope
|
||||
assert "ifc" in locals()
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
|
||||
@@ -262,6 +255,7 @@ async def test_create_remote_cb():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sw_exception():
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_SW_EXCEPT
|
||||
@@ -272,10 +266,7 @@ async def test_sw_exception():
|
||||
return 1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
# The callback will be called after the AsyncStreamServer
|
||||
# constructer has finished and so ifc must be defined in the
|
||||
# upper scope
|
||||
assert "ifc" in locals()
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
cnt = 0
|
||||
@@ -294,6 +285,7 @@ async def test_sw_exception():
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_os_error():
|
||||
global test
|
||||
assert asyncio.get_running_loop()
|
||||
reader = FakeReader()
|
||||
reader.test = FakeReader.RD_TEST_OS_ERROR
|
||||
@@ -301,11 +293,12 @@ async def test_os_error():
|
||||
reader.on_recv.set()
|
||||
writer = FakeWriter()
|
||||
cnt = 0
|
||||
|
||||
def timeout():
|
||||
return 1
|
||||
def closed():
|
||||
nonlocal cnt
|
||||
nonlocal ifc
|
||||
ifc.close() # clears the closed callback
|
||||
cnt += 1
|
||||
cnt = 0
|
||||
ifc = AsyncStreamClient(reader, writer, None, closed)
|
||||
@@ -368,13 +361,10 @@ async def test_forward():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote, ifc
|
||||
create_remote(remote, TestType.FWD_NO_EXCPT)
|
||||
# The callback will be called after the AsyncStreamServer
|
||||
# constructer has finished and so ifc must be defined in the
|
||||
# upper scope
|
||||
assert "ifc" in locals()
|
||||
ifc.fwd_add(b'test-forward_msg2 ')
|
||||
cnt += 1
|
||||
|
||||
@@ -392,7 +382,7 @@ async def test_forward_with_conn():
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote, ifc
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
@@ -427,7 +417,7 @@ async def test_forward_sw_except():
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_SW_EXCPT)
|
||||
cnt += 1
|
||||
|
||||
@@ -445,7 +435,7 @@ async def test_forward_os_error():
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_OS_ERROR)
|
||||
cnt += 1
|
||||
|
||||
@@ -463,7 +453,7 @@ async def test_forward_os_error2():
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_OS_ERROR, True)
|
||||
cnt += 1
|
||||
|
||||
@@ -481,7 +471,7 @@ async def test_forward_os_error3():
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_OS_ERROR_NO_STREAM)
|
||||
cnt += 1
|
||||
|
||||
@@ -499,7 +489,7 @@ async def test_forward_runtime_error():
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_RUNTIME_ERROR)
|
||||
cnt += 1
|
||||
|
||||
@@ -517,7 +507,7 @@ async def test_forward_runtime_error2():
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_RUNTIME_ERROR, True)
|
||||
cnt += 1
|
||||
|
||||
@@ -535,7 +525,7 @@ async def test_forward_runtime_error3():
|
||||
cnt = 0
|
||||
|
||||
async def _create_remote():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote
|
||||
create_remote(remote, TestType.FWD_RUNTIME_ERROR_NO_STREAM, True)
|
||||
cnt += 1
|
||||
|
||||
@@ -553,7 +543,7 @@ async def test_forward_resp():
|
||||
cnt = 0
|
||||
|
||||
def _close_cb():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote, ifc
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
@@ -569,8 +559,9 @@ async def test_forward_resp2():
|
||||
assert asyncio.get_running_loop()
|
||||
remote = StreamPtr(None)
|
||||
cnt = 0
|
||||
|
||||
def _close_cb():
|
||||
nonlocal cnt
|
||||
nonlocal cnt, remote, ifc
|
||||
cnt += 1
|
||||
|
||||
cnt = 0
|
||||
@@ -580,4 +571,3 @@ async def test_forward_resp2():
|
||||
await ifc.client_loop('')
|
||||
assert cnt == 1
|
||||
del ifc
|
||||
|
||||
@@ -85,6 +85,7 @@ def patch_open_connection():
|
||||
return FakeReader(), FakeWriter()
|
||||
|
||||
def new_open(host: str, port: int):
|
||||
global test
|
||||
if test == MockType.RD_TEST_TIMEOUT:
|
||||
raise ConnectionRefusedError
|
||||
elif test == MockType.RD_TEST_EXCEPT:
|
||||
@@ -317,7 +318,7 @@ async def test_remote_conn_to_loopback(config_conn, patch_open_connection):
|
||||
assert cnt == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_remote_conn_to_none(config_conn, patch_open_connection):
|
||||
async def test_remote_conn_to_None(config_conn, patch_open_connection):
|
||||
'''check if get_extra_info() return None in case of an error'''
|
||||
_ = config_conn
|
||||
_ = patch_open_connection
|
||||
|
||||
@@ -85,6 +85,7 @@ def patch_open_connection():
|
||||
return FakeReader(), FakeWriter()
|
||||
|
||||
def new_open(host: str, port: int):
|
||||
global test
|
||||
if test == MockType.RD_TEST_TIMEOUT:
|
||||
raise ConnectionRefusedError
|
||||
elif test == MockType.RD_TEST_EXCEPT:
|
||||
|
||||
@@ -84,6 +84,7 @@ def patch_open_connection():
|
||||
return FakeReader(), FakeWriter()
|
||||
|
||||
def new_open(host: str, port: int):
|
||||
global test
|
||||
if test == MockType.RD_TEST_TIMEOUT:
|
||||
raise ConnectionRefusedError
|
||||
elif test == MockType.RD_TEST_EXCEPT:
|
||||
|
||||
@@ -96,6 +96,7 @@ def test_native_client(test_hostname, test_port):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mqtt_connection(config_mqtt_conn):
|
||||
global NO_MOSQUITTO_TEST
|
||||
if NO_MOSQUITTO_TEST:
|
||||
pytest.skip('skipping, since Mosquitto is not reliable at the moment')
|
||||
|
||||
@@ -121,6 +122,7 @@ async def test_mqtt_connection(config_mqtt_conn):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ha_reconnect(config_mqtt_conn):
|
||||
global NO_MOSQUITTO_TEST
|
||||
if NO_MOSQUITTO_TEST:
|
||||
pytest.skip('skipping, since Mosquitto is not reliable at the moment')
|
||||
|
||||
|
||||
3
ha_addons/.gitignore
vendored
3
ha_addons/.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
.data.json
|
||||
config.yaml
|
||||
apparmor.txt
|
||||
README.md
|
||||
apparmor.txt
|
||||
@@ -66,7 +66,7 @@ clean:
|
||||
# Build the local add-on with a rootfs and config.yaml
|
||||
# The rootfs is needed to build the add-on Docker container
|
||||
#
|
||||
local_add_on: rootfs $(ADDON_PATH)/config.yaml $(ADDON_PATH)/apparmor.txt $(ADDON_PATH)/README.md
|
||||
local_add_on: rootfs $(ADDON_PATH)/config.yaml $(ADDON_PATH)/apparmor.txt
|
||||
|
||||
# collect source files
|
||||
SRC_FILES := $(wildcard $(SRC_PROXY)/*.py)\
|
||||
@@ -103,9 +103,6 @@ $(ADDON_PATH)/%.yaml: $(TEMPL)/%.jinja $(TEMPL)/.data.json
|
||||
$(ADDON_PATH)/%.txt: $(TEMPL)/%.jinja $(TEMPL)/.data.json
|
||||
$(JINJA) --strict --format=json $^ -o $@
|
||||
|
||||
$(ADDON_PATH)/%.md: $(TEMPL)/%.jinja $(TEMPL)/.data.json
|
||||
$(JINJA) --strict --format=json $^ -o $@
|
||||
|
||||
# build a common data.json file from STAGE depending source files
|
||||
# don't touch the destination if the checksum of src and dst is equal
|
||||
$(TEMPL)/.data.json: FORCE
|
||||
@@ -122,7 +119,6 @@ repro_files = DOCS.md icon.png logo.png translations/de.yaml translations/en.yam
|
||||
repro_root = CHANGELOG.md LICENSE.md
|
||||
repro_templates = config.yaml
|
||||
repro_apparmor = apparmor.txt
|
||||
repro_readme = README.md
|
||||
repro_subdirs = translations rootfs
|
||||
repro_vers = debug dev rc rel
|
||||
|
||||
@@ -130,34 +126,29 @@ repro_all_files := $(foreach dir,$(repro_vers), $(foreach file,$(repro_files),$(
|
||||
repro_root_files := $(foreach dir,$(repro_vers), $(foreach file,$(repro_root),$(INST_BASE)/ha_addon_$(dir)/$(file)))
|
||||
repro_all_templates := $(foreach dir,$(repro_vers), $(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_$(dir)/$(file)))
|
||||
repro_all_apparmor := $(foreach dir,$(repro_vers), $(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_$(dir)/$(file)))
|
||||
repro_all_readme := $(foreach dir,$(repro_vers), $(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_$(dir)/$(file)))
|
||||
repro_all_subdirs := $(foreach dir,$(repro_vers), $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_$(dir)/$(file)))
|
||||
|
||||
debug: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_debug/$(file)) \
|
||||
$(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_debug/$(file)) \
|
||||
$(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_debug/$(file)) \
|
||||
$(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_debug/$(file)) \
|
||||
$(foreach file,$(repro_files),$(INST_BASE)/ha_addon_debug/$(file)) \
|
||||
$(foreach file,$(repro_root),$(INST_BASE)/ha_addon_debug/$(file))
|
||||
|
||||
dev: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_dev/$(file)) \
|
||||
$(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_dev/$(file)) \
|
||||
$(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_dev/$(file)) \
|
||||
$(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_dev/$(file)) \
|
||||
$(foreach file,$(repro_files),$(INST_BASE)/ha_addon_dev/$(file)) \
|
||||
$(foreach file,$(repro_root),$(INST_BASE)/ha_addon_dev/$(file))
|
||||
|
||||
rc: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_rc/$(file)) \
|
||||
$(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_rc/$(file)) \
|
||||
$(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_rc/$(file)) \
|
||||
$(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_rc/$(file)) \
|
||||
$(foreach file,$(repro_files),$(INST_BASE)/ha_addon_rc/$(file)) \
|
||||
$(foreach file,$(repro_root),$(INST_BASE)/ha_addon_rc/$(file))
|
||||
|
||||
rel: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_rel/$(file)) \
|
||||
$(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_rel/$(file)) \
|
||||
$(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_rel/$(file)) \
|
||||
$(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_rel/$(file)) \
|
||||
$(foreach file,$(repro_files),$(INST_BASE)/ha_addon_rel/$(file)) \
|
||||
$(foreach file,$(repro_root),$(INST_BASE)/ha_addon_rel/$(file))
|
||||
|
||||
@@ -170,9 +161,6 @@ $(repro_all_templates) : $(INST_BASE)/ha_addon_%/config.yaml: $(TEMPL)/config.ji
|
||||
$(repro_all_apparmor) : $(INST_BASE)/ha_addon_%/apparmor.txt: $(TEMPL)/apparmor.jinja $(TEMPL)/%_data.json
|
||||
$(JINJA) --strict $< $(filter %.json,$^) -o $@
|
||||
|
||||
$(repro_all_readme) : $(INST_BASE)/ha_addon_%/README.md: $(TEMPL)/README.jinja $(TEMPL)/%_data.json
|
||||
$(JINJA) --strict $< $(filter %.json,$^) -o $@
|
||||
|
||||
$(filter $(INST_BASE)/ha_addon_debug/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_debug/% : ../%
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_dev/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_dev/% : ../%
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# 1 Build Base Image #
|
||||
######################
|
||||
|
||||
ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.3"
|
||||
ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.2"
|
||||
# hadolint ignore=DL3006
|
||||
FROM $BUILD_FROM AS base
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
# Home Assistant Add-on: {{name}}
|
||||
|
||||
{{readme_descr}}
|
||||
|
||||
## Features
|
||||
|
||||
- Supports TSUN GEN3 PLUS inverters: TSOL-MS2000, MS1800 and MS1600
|
||||
- Supports TSUN GEN3 PLUS batteries: TSOL-DC1000 (from version 0.13)
|
||||
- Supports TSUN GEN3 inverters: TSOL-MS3000, MS800, MS700, MS600, MS400, MS350 and MS300
|
||||
- `Home-Assistant` auto-discovery support
|
||||
- `MODBUS` support via MQTT topics
|
||||
- `AT-Command` support via MQTT topics (GEN3PLUS only)
|
||||
- Faster DataUp interval sends measurement data to the MQTT broker every minute
|
||||
- Self-sufficient island operation without internet
|
||||
- Security-Features:
|
||||
- control access via `AT-commands`
|
||||
|
||||
## About
|
||||
|
||||
This Add-on and the TSUN Proxy is not related to the company TSUN. It is a private initiative that aims to connect TSUN inverters and storage systems with an MQTT broker. There is no support and no warranty from TSUN.
|
||||
{{readme_links}}
|
||||
@@ -5,7 +5,5 @@
|
||||
"image": "docker.io/sallius/tsun-gen3-addon",
|
||||
"slug": "tsun-proxy-debug",
|
||||
"advanced": true,
|
||||
"stage": "experimental",
|
||||
"readme_descr": "This is a bleeding-edge version of the `TSUN Proxy` Add-On with debuging enabled by default.\n\nThe versions may be based on different feature branches and therefore the range of functions may change.\n\nIt is intended to be used to simulate special situations/problems and should only be used in consultation with the maintainer.\n\nFor production please use the stable version `TSUN Proxy`. If you are interested in a bleeding edge version, we offer the `TSUN Proxy (dev)` version.",
|
||||
"readme_links": ""
|
||||
"stage": "experimental"
|
||||
}
|
||||
@@ -5,7 +5,5 @@
|
||||
"image": "docker.io/sallius/tsun-gen3-addon",
|
||||
"slug": "tsun-proxy-dev",
|
||||
"advanced": false,
|
||||
"stage": "experimental",
|
||||
"readme_descr": "This is a bleeding-edge version of the `TSUN Proxy` Add-On.\n\nThe versions may be based on different feature branches and therefore the range of functions may change.\n\nIt is intended for testing new functions or testing new devices that are to be supported with the next release.\nFor production, please use the stable version 'TSUN Proxy'.",
|
||||
"readme_links": ""
|
||||
"stage": "experimental"
|
||||
}
|
||||
@@ -6,8 +6,5 @@
|
||||
"image": "ghcr.io/s-allius/tsun-gen3-addon",
|
||||
"slug": "tsun-proxy-rc",
|
||||
"advanced": true,
|
||||
"stage": "experimental",
|
||||
"readme_descr": "This is a release candidate of the `TSUN Proxy` Add-On.\n\nIt is intended for testing the next release.\nFor production, please use the stable version 'TSUN Proxy'.",
|
||||
"readme_links": ""
|
||||
|
||||
"stage": "experimental"
|
||||
}
|
||||
@@ -5,7 +5,5 @@
|
||||
"image": "ghcr.io/s-allius/tsun-gen3-addon",
|
||||
"slug": "tsun-proxy",
|
||||
"advanced": false,
|
||||
"stage": "stable",
|
||||
"readme_dsecr": "Integrates TSUN inverters (e.g. TSOL MS800, MS2000, MS3000) and batteries (TSOL DC1000) into Home Assistant.\n\nIt is based on the [TSUN Proxy][tsunproxy] and enables a reliable connection between TSUN devices and an MQTT broker.\n\nWith the Add-on, you can easily retrieve real-time values such as power, current and daily energy and integrate the inverter into Home Assistant.\nThis works even without an internet connection.\n\nThe optional connection to the TSUN Cloud can be disabled!",
|
||||
"readme_links": "\n[tsunproxy]: https://github.com/s-allius/tsun-gen3-proxy\n"
|
||||
"stage": "stable"
|
||||
}
|
||||
@@ -5,14 +5,13 @@ from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
SOLARMAN_INV_SNR = os.getenv('SOLARMAN_INV_SNR', '00000080')
|
||||
SOLARMAN_DCU_SNR = os.getenv('SOLARMAN_INV_SNR', '00000080')
|
||||
SOLARMAN_SNR = os.getenv('SOLARMAN_SNR', '00000080')
|
||||
|
||||
def get_sn() -> bytes:
|
||||
return bytes.fromhex(SOLARMAN_INV_SNR)
|
||||
return bytes.fromhex(SOLARMAN_SNR)
|
||||
|
||||
def get_dcu_sn() -> bytes:
|
||||
return bytes.fromhex(SOLARMAN_DCU_SNR)
|
||||
return b'\x20\x43\x65\x7b'
|
||||
|
||||
def get_dcu_no() -> bytes:
|
||||
return b'4100000000000001'
|
||||
@@ -28,7 +27,7 @@ def correct_checksum(buf):
|
||||
return checksum.to_bytes(length=1)
|
||||
|
||||
@pytest.fixture
|
||||
def msg_contact_info(): # Contact Info message
|
||||
def MsgContactInfo(): # Contact Info message
|
||||
msg = b'\xa5\xd4\x00\x10\x41\x00\x01' +get_sn() +b'\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'
|
||||
@@ -47,13 +46,13 @@ def msg_contact_info(): # Contact Info message
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_contact_resp(): # Contact Response message
|
||||
def MsgContactResp(): # Contact Response message
|
||||
msg = b'\xa5\x0a\x00\x10\x11\x01\x01' +get_sn() +b'\x02\x01\x6a\xfd\x8f'
|
||||
msg += b'\x65\x3c\x00\x00\x00\x75\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_data_ind():
|
||||
def MsgDataInd():
|
||||
msg = b'\xa5\x99\x01\x10\x42\x59\x84' +get_sn() +b'\x01\xb0\x02\x2c\x87'
|
||||
msg += b'\x22\x32\xb7\x29\x00\x00\xd6\xcf\xe1\x33\x01\x00\x0c\x05\x00\x00'
|
||||
msg += b'\x59\x31\x37\x45\x37\x41\x30\x46\x30\x31\x30\x42\x30\x31\x33\x45'
|
||||
@@ -87,14 +86,14 @@ def msg_data_ind():
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_data_rsp(): # Contact Response message
|
||||
def MsgDataResp(): # Contact Response message
|
||||
msg = b'\xa5\x0a\x00\x10\x12\x80\x84' +get_sn() +b'\x01\x01\xd1\x96\x04'
|
||||
msg += b'\x66\x3c\x00\x00\x00\xed\x15'
|
||||
return msg
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def msg_invalid_info(): # Contact Info message wrong start byte
|
||||
def MsgInvalidInfo(): # Contact Info message wrong start byte
|
||||
msg = b'\x47\xd4\x00\x10\x41\x00\x01' +get_sn() +b'\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'
|
||||
@@ -170,7 +169,7 @@ def dcu_data_rsp_msg(): # 0x1210
|
||||
return msg
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def client_connection():
|
||||
def ClientConnection():
|
||||
host = 'logger.talent-monitoring.com'
|
||||
port = 10000
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
@@ -179,15 +178,15 @@ def client_connection():
|
||||
yield s
|
||||
s.close()
|
||||
|
||||
def check_response(data, msg):
|
||||
def checkResponse(data, Msg):
|
||||
check = bytearray(data)
|
||||
check[5]= msg[5] # ignore seq
|
||||
check[13:18]= msg[13:18] # ignore timestamp + first byte of repeat time
|
||||
check[21]= msg[21] # ignore crc
|
||||
assert check == msg
|
||||
check[5]= Msg[5] # ignore seq
|
||||
check[13:18]= Msg[13:18] # ignore timestamp + first byte of repeat time
|
||||
check[21]= Msg[21] # ignore crc
|
||||
assert check == Msg
|
||||
|
||||
|
||||
def tempclient_connection():
|
||||
def tempClientConnection():
|
||||
host = 'logger.talent-monitoring.com'
|
||||
port = 10000
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
@@ -199,53 +198,53 @@ def tempclient_connection():
|
||||
|
||||
def test_open_close():
|
||||
try:
|
||||
for _ in tempclient_connection():
|
||||
pass # test generator tempclient_connection()
|
||||
except TimeoutError:
|
||||
for _ in tempClientConnection():
|
||||
pass # test generator tempClientConnection()
|
||||
except:
|
||||
assert False
|
||||
|
||||
def test_conn_msg(client_connection,msg_contact_info, msg_contact_resp):
|
||||
s = client_connection
|
||||
def test_conn_msg(ClientConnection,MsgContactInfo, MsgContactResp):
|
||||
s = ClientConnection
|
||||
try:
|
||||
s.sendall(msg_contact_info)
|
||||
time.sleep(2.5)
|
||||
data = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
check_response(data, msg_contact_resp)
|
||||
|
||||
def test_data_ind(client_connection,msg_data_ind, msg_data_rsp):
|
||||
s = client_connection
|
||||
try:
|
||||
s.sendall(msg_data_ind)
|
||||
s.sendall(MsgContactInfo)
|
||||
# time.sleep(2.5)
|
||||
data = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
check_response(data, msg_data_rsp)
|
||||
checkResponse(data, MsgContactResp)
|
||||
|
||||
def test_inavlid_msg(client_connection,msg_invalid_info,msg_contact_info, msg_contact_resp):
|
||||
s = client_connection
|
||||
def test_data_ind(ClientConnection,MsgDataInd, MsgDataResp):
|
||||
s = ClientConnection
|
||||
try:
|
||||
s.sendall(msg_invalid_info)
|
||||
s.sendall(MsgDataInd)
|
||||
# time.sleep(2.5)
|
||||
data = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
checkResponse(data, MsgDataResp)
|
||||
|
||||
def test_inavlid_msg(ClientConnection,MsgInvalidInfo,MsgContactInfo, MsgContactResp):
|
||||
s = ClientConnection
|
||||
try:
|
||||
s.sendall(MsgInvalidInfo)
|
||||
# time.sleep(2.5)
|
||||
data = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
try:
|
||||
s.sendall(msg_contact_info)
|
||||
s.sendall(MsgContactInfo)
|
||||
# time.sleep(2.5)
|
||||
data = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
check_response(data, msg_contact_resp)
|
||||
checkResponse(data, MsgContactResp)
|
||||
|
||||
def test_dcu_dev(client_connection,dcu_dev_ind_msg, dcu_dev_rsp_msg):
|
||||
s = client_connection
|
||||
def test_dcu_dev(ClientConnection,dcu_dev_ind_msg, dcu_dev_rsp_msg):
|
||||
s = ClientConnection
|
||||
try:
|
||||
s.sendall(dcu_dev_ind_msg)
|
||||
# time.sleep(2.5)
|
||||
@@ -253,10 +252,10 @@ def test_dcu_dev(client_connection,dcu_dev_ind_msg, dcu_dev_rsp_msg):
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
check_response(data, dcu_dev_rsp_msg)
|
||||
checkResponse(data, dcu_dev_rsp_msg)
|
||||
|
||||
def test_dcu_ind(client_connection,dcu_data_ind_msg, dcu_data_rsp_msg):
|
||||
s = client_connection
|
||||
def test_dcu_ind(ClientConnection,dcu_data_ind_msg, dcu_data_rsp_msg):
|
||||
s = ClientConnection
|
||||
try:
|
||||
s.sendall(dcu_data_ind_msg)
|
||||
# time.sleep(2.5)
|
||||
@@ -264,4 +263,4 @@ def test_dcu_ind(client_connection,dcu_data_ind_msg, dcu_data_rsp_msg):
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
check_response(data, dcu_data_rsp_msg)
|
||||
checkResponse(data, dcu_data_rsp_msg)
|
||||
|
||||
Reference in New Issue
Block a user