Compare commits
60 Commits
v0.12.1
...
s-allius/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60d142ea54 | ||
|
|
5d09c09d8b | ||
|
|
911eb8d44e | ||
|
|
506076ee59 | ||
|
|
4e96f077db | ||
|
|
36f7c1ac1f | ||
|
|
83e0f192ef | ||
|
|
56b9153373 | ||
|
|
d9384a6118 | ||
|
|
9ec111759a | ||
|
|
8d2dcb7212 | ||
|
|
32d7711ab7 | ||
|
|
dff8934b82 | ||
|
|
3eb6a24dcb | ||
|
|
da383c7794 | ||
|
|
f9be171865 | ||
|
|
45abc69ffb | ||
|
|
96c35ed263 | ||
|
|
795a52e172 | ||
|
|
5d1ee60baf | ||
|
|
7cf9e98c7f | ||
|
|
e0777dca8e | ||
|
|
955657fd87 | ||
|
|
ecd21e46fb | ||
|
|
3489e8997d | ||
|
|
88cb01f613 | ||
|
|
be60f9ea1e | ||
|
|
10b4a84701 | ||
|
|
06ceb02f0d | ||
|
|
8a2ca3ab9a | ||
|
|
3f3ed1b14f | ||
|
|
036dd6d1dc | ||
|
|
1f0ac97368 | ||
|
|
5faf242d6c | ||
|
|
ec3af69e62 | ||
|
|
113a41ebfe | ||
|
|
13e6adc5c0 | ||
|
|
f9256099c7 | ||
|
|
204bc76153 | ||
|
|
58c7f51266 | ||
|
|
1eaabb97a2 | ||
|
|
7a6e6f73a5 | ||
|
|
39495d3e9e | ||
|
|
a257f09d4c | ||
|
|
5f0a35d55b | ||
|
|
4df36e2672 | ||
|
|
48a9696df2 | ||
|
|
24567eaf5f | ||
|
|
42fe33bacf | ||
|
|
cfdd65606d | ||
|
|
2e3ed8f162 | ||
|
|
66a875c291 | ||
|
|
46043e7754 | ||
|
|
01ad8eff6b | ||
|
|
53c76e72a2 | ||
|
|
f5d760e2f0 | ||
|
|
3234e87b55 | ||
|
|
412013f626 | ||
|
|
1b3833989e | ||
|
|
1e160f3b0f |
6
.github/workflows/python-app.yml
vendored
6
.github/workflows/python-app.yml
vendored
@@ -37,10 +37,10 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Set up Python 3.12
|
||||
- name: Set up Python 3.13
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.13"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
coverage report
|
||||
- name: Analyze with SonarCloud
|
||||
if: ${{ env.SONAR_TOKEN != 0 }}
|
||||
uses: SonarSource/sonarqube-scan-action@v4
|
||||
uses: SonarSource/sonarqube-scan-action@v5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.13.1
|
||||
3.13.2
|
||||
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [unreleased]
|
||||
|
||||
- update dependency python to 3.13
|
||||
- add initial support for TSUN MS-3000
|
||||
- add initial apparmor support [#293](https://github.com/s-allius/tsun-gen3-proxy/issues/293)
|
||||
- add Modbus polling mode for DCU1000 [#292](https://github.com/s-allius/tsun-gen3-proxy/issues/292)
|
||||
- add Modbus scanning mode
|
||||
- allow `R47`serial numbers for GEN3 inverters
|
||||
- add watchdog for Add-ons
|
||||
- add first costumer apparmor definition
|
||||
- Respect logging.ini file, if LOG_ENV isn't set well [#288](https://github.com/s-allius/tsun-gen3-proxy/issues/288)
|
||||
- Remove trailing apostrophe in the log output [#288](https://github.com/s-allius/tsun-gen3-proxy/issues/288)
|
||||
- update AddOn base docker image to version 17.2.1
|
||||
- addon: add date and time to dev container version
|
||||
- Update AddOn python3 to 3.12.9-r0
|
||||
- add initial DCU support
|
||||
- update aiohttp to version 3.11.12
|
||||
- fix the path handling for logging.ini and default_config.toml [#180](https://github.com/s-allius/tsun-gen3-proxy/issues/180)
|
||||
|
||||
## [0.12.1] - 2025-01-13
|
||||
|
||||
- addon: bump base image version to v17.1.0
|
||||
- addon: add syntax check to config parameters
|
||||
- addon: bump base image version to v17.0.2
|
||||
|
||||
## [0.12.0] - 2024-12-22
|
||||
|
||||
- add hadolint configuration
|
||||
|
||||
105
README.md
105
README.md
@@ -1,12 +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-3120/"><img alt="Supported Python versions" src="https://img.shields.io/badge/python-3.12-blue.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://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>
|
||||
@@ -20,11 +21,11 @@
|
||||
|
||||
# Overview
|
||||
|
||||
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!
|
||||
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!
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -32,12 +33,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 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 and storage systems 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 inverter and TSUN Cloud.
|
||||
between the device 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
|
||||
@@ -49,7 +50,8 @@ 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 inverters: TSOL-MS800, MS700, MS600, MS400, MS350 and MS300
|
||||
- Supports TSUN GEN3 PLUS batteries: TSOL-DC1000 (from version 0.13)
|
||||
- Supports TSUN GEN3 inverters: TSOL-MS3000, MS800, MS700, MS600, MS400, MS350 and MS300
|
||||
- `MQTT` support
|
||||
- `Home-Assistant` auto-discovery support
|
||||
- `MODBUS` support via MQTT topics
|
||||
@@ -68,15 +70,15 @@ Here are some screenshots of how the inverter is displayed in the Home Assistant
|
||||
|
||||
## Requirements
|
||||
|
||||
### for Docker Installation
|
||||
### Requirements for Docker Installation
|
||||
|
||||
- A running Docker engine to host the container
|
||||
- Ability to loop the proxy into the connection between the inverter and the TSUN cloud
|
||||
- Ability to loop the proxy into the connection between the device and the TSUN cloud
|
||||
|
||||
### for Home Assistant Add-on Installation
|
||||
### Requirements 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 inverter and the TSUN cloud
|
||||
- Ability to loop the proxy into the connection between the device and the TSUN cloud
|
||||
|
||||
# Getting Started
|
||||
|
||||
@@ -117,19 +119,20 @@ 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 inverter to the proxy must be set up, which is done differently depending on the inverter generation
|
||||
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
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
1. [Container Setup](#container-setup)
|
||||
2. [Proxy Configuration](#proxy-configuration)
|
||||
3. [Inverter Configuration](#inverter-configuration) (only GEN3PLUS)
|
||||
3. [Inverter and Batterie Configuration](#inverter-and-batterie-configuration) (only GEN3PLUS)
|
||||
4. [DNS Settings](#dns-settings) (Mandatory for GEN3)
|
||||
|
||||
## Container Setup
|
||||
@@ -309,6 +312,33 @@ 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
|
||||
@@ -330,19 +360,19 @@ mqtt.block = []
|
||||
|
||||
</details>
|
||||
|
||||
## Inverter Configuration
|
||||
## Inverter and Batterie Configuration
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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`.
|
||||
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`.
|
||||
|
||||
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 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
|
||||
❗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
|
||||
require a complete reset. Use the configuration in client mode instead.
|
||||
```
|
||||
|
||||
@@ -350,23 +380,23 @@ If access to the web interface does not work, it can also be redirected via DNS
|
||||
|
||||
## 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`.
|
||||
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`.
|
||||
|
||||
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
|
||||
- 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
|
||||
|
||||
## DNS Settings
|
||||
|
||||
### Loop the proxy into the connection
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
The new GEN3 PLUS inverters use a different URL. Here, *iot.talent-monitoring.com* must be redirected.
|
||||
The new GEN3 PLUS devices 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.
|
||||
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.
|
||||
|
||||
### DNS Rebind Protection
|
||||
|
||||
@@ -382,7 +412,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 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.
|
||||
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.
|
||||
|
||||
It must be ensured that this address is not mapped to the proxy!
|
||||
|
||||
@@ -395,10 +425,13 @@ 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 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>
|
||||
<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</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>
|
||||
</table>
|
||||
|
||||
```txt
|
||||
@@ -409,7 +442,7 @@ Legend
|
||||
🚧: Proxy support in preparation
|
||||
```
|
||||
|
||||
❗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`
|
||||
❗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'.
|
||||
|
||||
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 +1 @@
|
||||
0.12.0
|
||||
0.13.0
|
||||
@@ -30,7 +30,7 @@ ARG SERVICE_NAME
|
||||
ARG VERSION
|
||||
ARG UID
|
||||
ARG GID
|
||||
ARG LOG_LVL
|
||||
ARG LOG_LVL=INFO
|
||||
ARG environment
|
||||
|
||||
ENV SERVICE_NAME=$SERVICE_NAME
|
||||
@@ -59,7 +59,6 @@ RUN python -m pip install --no-cache-dir --no-cache --no-index /root/wheels/* &&
|
||||
|
||||
# copy the content of the local src and config directory to the working directory
|
||||
COPY --chmod=0700 entrypoint.sh /root/entrypoint.sh
|
||||
COPY config .
|
||||
COPY src .
|
||||
RUN echo ${VERSION} > /proxy-version.txt \
|
||||
&& date > /build-date.txt
|
||||
|
||||
33
app/Makefile
33
app/Makefile
@@ -7,23 +7,6 @@ IMAGE = tsun-gen3-proxy
|
||||
|
||||
# Folders
|
||||
SRC=.
|
||||
SRC_PROXY=$(SRC)/src
|
||||
CNF_PROXY=$(SRC)/config
|
||||
|
||||
DST=rootfs
|
||||
DST_PROXY=$(DST)/home/proxy
|
||||
|
||||
# collect source files
|
||||
SRC_FILES := $(wildcard $(SRC_PROXY)/*.py)\
|
||||
$(wildcard $(SRC_PROXY)/*.ini)\
|
||||
$(wildcard $(SRC_PROXY)/cnf/*.py)\
|
||||
$(wildcard $(SRC_PROXY)/gen3/*.py)\
|
||||
$(wildcard $(SRC_PROXY)/gen3plus/*.py)
|
||||
CNF_FILES := $(wildcard $(CNF_PROXY)/*.toml)
|
||||
|
||||
# determine destination files
|
||||
TARGET_FILES = $(SRC_FILES:$(SRC_PROXY)/%=$(DST_PROXY)/%)
|
||||
CONFIG_FILES = $(CNF_FILES:$(CNF_PROXY)/%=$(DST_PROXY)/%)
|
||||
|
||||
export BUILD_DATE := ${shell date -Iminutes}
|
||||
VERSION := $(shell cat $(SRC)/.version)
|
||||
@@ -48,20 +31,4 @@ preview rc rel:
|
||||
docker buildx bake -f docker-bake.hcl $@
|
||||
|
||||
|
||||
|
||||
.PHONY: debug dev preview rc rel
|
||||
|
||||
|
||||
$(CONFIG_FILES): $(DST_PROXY)/% : $(CNF_PROXY)/%
|
||||
@echo Copy $< to $@
|
||||
@mkdir -p $(@D)
|
||||
@cp $< $@
|
||||
|
||||
$(TARGET_FILES): $(DST_PROXY)/% : $(SRC_PROXY)/%
|
||||
@echo Copy $< to $@
|
||||
@mkdir -p $(@D)
|
||||
@cp $< $@
|
||||
|
||||
$(DST)/requirements.txt : $(SRC)/requirements.txt
|
||||
@echo Copy $< to $@
|
||||
@cp $< $@
|
||||
|
||||
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
@@ -4,45 +4,45 @@
|
||||
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||
-->
|
||||
<!-- Title: G Pages: 1 -->
|
||||
<svg width="539pt" height="2000pt"
|
||||
viewBox="0.00 0.00 538.57 2000.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1996)">
|
||||
<svg width="539pt" height="2036pt"
|
||||
viewBox="0.00 0.00 538.57 2036.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 2032)">
|
||||
<title>G</title>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1996 534.566,-1996 534.566,4 -4,4"/>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-2032 534.566,-2032 534.566,4 -4,4"/>
|
||||
<!-- A0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>A0</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="98.1981,-1972 -.0661,-1972 -.0661,-1928 104.1981,-1928 104.1981,-1966 98.1981,-1972"/>
|
||||
<polyline fill="none" stroke="#000000" points="98.1981,-1972 98.1981,-1966 "/>
|
||||
<polyline fill="none" stroke="#000000" points="104.1981,-1966 98.1981,-1966 "/>
|
||||
<text text-anchor="middle" x="52.066" y="-1959" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Example of</text>
|
||||
<text text-anchor="middle" x="52.066" y="-1947" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">instantiation for a</text>
|
||||
<text text-anchor="middle" x="52.066" y="-1935" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">GEN3 inverter!</text>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="98.1981,-2008 -.0661,-2008 -.0661,-1964 104.1981,-1964 104.1981,-2002 98.1981,-2008"/>
|
||||
<polyline fill="none" stroke="#000000" points="98.1981,-2008 98.1981,-2002 "/>
|
||||
<polyline fill="none" stroke="#000000" points="104.1981,-2002 98.1981,-2002 "/>
|
||||
<text text-anchor="middle" x="52.066" y="-1995" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Example of</text>
|
||||
<text text-anchor="middle" x="52.066" y="-1983" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">instantiation for a</text>
|
||||
<text text-anchor="middle" x="52.066" y="-1971" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">GEN3 inverter!</text>
|
||||
</g>
|
||||
<!-- A1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>A1</title>
|
||||
<polygon fill="none" stroke="#000000" points="122.066,-1960 122.066,-1992 238.066,-1992 238.066,-1960 122.066,-1960"/>
|
||||
<text text-anchor="start" x="131.715" y="-1973" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<AbstractIterMeta>></text>
|
||||
<polygon fill="none" stroke="#000000" points="122.066,-1940 122.066,-1960 238.066,-1960 238.066,-1940 122.066,-1940"/>
|
||||
<polygon fill="none" stroke="#000000" points="122.066,-1908 122.066,-1940 238.066,-1940 238.066,-1908 122.066,-1908"/>
|
||||
<text text-anchor="start" x="158.676" y="-1921" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__()</text>
|
||||
<polygon fill="none" stroke="#000000" points="122.066,-1996 122.066,-2028 238.066,-2028 238.066,-1996 122.066,-1996"/>
|
||||
<text text-anchor="start" x="131.715" y="-2009" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<AbstractIterMeta>></text>
|
||||
<polygon fill="none" stroke="#000000" points="122.066,-1976 122.066,-1996 238.066,-1996 238.066,-1976 122.066,-1976"/>
|
||||
<polygon fill="none" stroke="#000000" points="122.066,-1944 122.066,-1976 238.066,-1976 238.066,-1944 122.066,-1944"/>
|
||||
<text text-anchor="start" x="158.676" y="-1957" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__()</text>
|
||||
</g>
|
||||
<!-- A14 -->
|
||||
<g id="node15" class="node">
|
||||
<title>A14</title>
|
||||
<polygon fill="none" stroke="#000000" points="135.066,-1748 135.066,-1780 225.066,-1780 225.066,-1748 135.066,-1748"/>
|
||||
<text text-anchor="start" x="144.7725" y="-1761" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<ProtocolIfc>></text>
|
||||
<polygon fill="none" stroke="#000000" points="135.066,-1716 135.066,-1748 225.066,-1748 225.066,-1716 135.066,-1716"/>
|
||||
<text text-anchor="start" x="160.8995" y="-1729" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_registry</text>
|
||||
<polygon fill="none" stroke="#000000" points="135.066,-1684 135.066,-1716 225.066,-1716 225.066,-1684 135.066,-1684"/>
|
||||
<text text-anchor="start" x="165.0685" y="-1697" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
<polygon fill="none" stroke="#000000" points="135.066,-1784 135.066,-1816 225.066,-1816 225.066,-1784 135.066,-1784"/>
|
||||
<text text-anchor="start" x="144.7725" y="-1797" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<ProtocolIfc>></text>
|
||||
<polygon fill="none" stroke="#000000" points="135.066,-1752 135.066,-1784 225.066,-1784 225.066,-1752 135.066,-1752"/>
|
||||
<text text-anchor="start" x="160.8995" y="-1765" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_registry</text>
|
||||
<polygon fill="none" stroke="#000000" points="135.066,-1720 135.066,-1752 225.066,-1752 225.066,-1720 135.066,-1720"/>
|
||||
<text text-anchor="start" x="165.0685" y="-1733" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A1->A14 -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>A1->A14</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M180.066,-1897.756C180.066,-1862.0883 180.066,-1815.1755 180.066,-1780.3644"/>
|
||||
<polygon fill="none" stroke="#000000" points="176.5661,-1897.9674 180.066,-1907.9674 183.5661,-1897.9674 176.5661,-1897.9674"/>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M180.066,-1933.756C180.066,-1898.0883 180.066,-1851.1755 180.066,-1816.3644"/>
|
||||
<polygon fill="none" stroke="#000000" points="176.5661,-1933.9674 180.066,-1943.9674 183.5661,-1933.9674 176.5661,-1933.9674"/>
|
||||
</g>
|
||||
<!-- A2 -->
|
||||
<g id="node3" class="node">
|
||||
@@ -289,31 +289,34 @@
|
||||
<!-- A13 -->
|
||||
<g id="node14" class="node">
|
||||
<title>A13</title>
|
||||
<polygon fill="none" stroke="#000000" points="156.066,-1524 156.066,-1556 305.066,-1556 305.066,-1524 156.066,-1524"/>
|
||||
<text text-anchor="start" x="210.2835" y="-1537" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text>
|
||||
<polygon fill="none" stroke="#000000" points="156.066,-1300 156.066,-1524 305.066,-1524 305.066,-1300 156.066,-1300"/>
|
||||
<text text-anchor="start" x="193.8925" y="-1505" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">server_side:bool</text>
|
||||
<text text-anchor="start" x="204.45" y="-1493" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
|
||||
<text text-anchor="start" x="205.2845" y="-1481" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
|
||||
<text text-anchor="start" x="212.7795" y="-1469" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
|
||||
<text text-anchor="start" x="191.109" y="-1457" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_valid:bool</text>
|
||||
<text text-anchor="start" x="205.556" y="-1445" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_len</text>
|
||||
<text text-anchor="start" x="211.39" y="-1433" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">data_len</text>
|
||||
<text text-anchor="start" x="208.8905" y="-1421" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">unique_id</text>
|
||||
<text text-anchor="start" x="202.781" y="-1409" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">sug_area:str</text>
|
||||
<text text-anchor="start" x="199.722" y="-1397" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_data:dict</text>
|
||||
<text text-anchor="start" x="206.666" y="-1385" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">state:State</text>
|
||||
<text text-anchor="start" x="180.2705" y="-1373" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">shutdown_started:bool</text>
|
||||
<text text-anchor="start" x="199.4505" y="-1361" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">modbus_elms</text>
|
||||
<text text-anchor="start" x="195.573" y="-1349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_timer:Timer</text>
|
||||
<text text-anchor="start" x="204.451" y="-1337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_timeout</text>
|
||||
<text text-anchor="start" x="193.6185" y="-1325" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_first_timeout</text>
|
||||
<text text-anchor="start" x="184.72" y="-1313" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">modbus_polling:bool</text>
|
||||
<polygon fill="none" stroke="#000000" points="156.066,-1196 156.066,-1300 305.066,-1300 305.066,-1196 156.066,-1196"/>
|
||||
<text text-anchor="start" x="179.4505" y="-1281" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_set_mqtt_timestamp()</text>
|
||||
<text text-anchor="start" x="208.066" y="-1269" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_timeout()</text>
|
||||
<text text-anchor="start" x="180.8335" y="-1257" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_send_modbus_cmd()</text>
|
||||
<text text-anchor="start" x="165.8255" y="-1245" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async> end_modbus_cmd()</text>
|
||||
<polygon fill="none" stroke="#000000" points="155.066,-1560 155.066,-1592 306.066,-1592 306.066,-1560 155.066,-1560"/>
|
||||
<text text-anchor="start" x="210.2835" y="-1573" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text>
|
||||
<polygon fill="none" stroke="#000000" points="155.066,-1336 155.066,-1560 306.066,-1560 306.066,-1336 155.066,-1336"/>
|
||||
<text text-anchor="start" x="193.8925" y="-1541" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">server_side:bool</text>
|
||||
<text text-anchor="start" x="204.45" y="-1529" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
|
||||
<text text-anchor="start" x="205.2845" y="-1517" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
|
||||
<text text-anchor="start" x="212.7795" y="-1505" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
|
||||
<text text-anchor="start" x="191.109" y="-1493" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_valid:bool</text>
|
||||
<text text-anchor="start" x="205.556" y="-1481" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_len</text>
|
||||
<text text-anchor="start" x="211.39" y="-1469" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">data_len</text>
|
||||
<text text-anchor="start" x="208.8905" y="-1457" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">unique_id</text>
|
||||
<text text-anchor="start" x="202.781" y="-1445" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">sug_area:str</text>
|
||||
<text text-anchor="start" x="199.722" y="-1433" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_data:dict</text>
|
||||
<text text-anchor="start" x="206.666" y="-1421" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">state:State</text>
|
||||
<text text-anchor="start" x="180.2705" y="-1409" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">shutdown_started:bool</text>
|
||||
<text text-anchor="start" x="199.4505" y="-1397" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">modbus_elms</text>
|
||||
<text text-anchor="start" x="195.573" y="-1385" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_timer:Timer</text>
|
||||
<text text-anchor="start" x="204.451" y="-1373" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_timeout</text>
|
||||
<text text-anchor="start" x="193.6185" y="-1361" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_first_timeout</text>
|
||||
<text text-anchor="start" x="184.72" y="-1349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">modbus_polling:bool</text>
|
||||
<polygon fill="none" stroke="#000000" points="155.066,-1196 155.066,-1336 306.066,-1336 306.066,-1196 155.066,-1196"/>
|
||||
<text text-anchor="start" x="169.447" y="-1317" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_set_config_parms(inv:dict)</text>
|
||||
<text text-anchor="start" x="165.0025" y="-1305" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_set_mqtt_timestamp(key, ts)</text>
|
||||
<text text-anchor="start" x="208.066" y="-1293" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_timeout()</text>
|
||||
<text text-anchor="start" x="180.8335" y="-1281" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_send_modbus_cmd()</text>
|
||||
<text text-anchor="start" x="164.715" y="-1269" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>send_modbus_cmd()</text>
|
||||
<text text-anchor="start" x="179.7185" y="-1257" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_send_modbus_scan()</text>
|
||||
<text text-anchor="start" x="178.054" y="-1245" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_dump_modbus_scan()</text>
|
||||
<text text-anchor="start" x="215.5685" y="-1233" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
<text text-anchor="start" x="201.3965" y="-1221" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
|
||||
<text text-anchor="start" x="199.7265" y="-1209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
|
||||
@@ -321,51 +324,51 @@
|
||||
<!-- A13->A5 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>A13->A5</title>
|
||||
<path fill="none" stroke="#000000" d="M210.2965,-1195.7758C208.8462,-1182.5547 207.3854,-1169.2373 205.9406,-1156.0662"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="204.8393,-1146.0268 210.403,-1155.4764 205.3846,-1150.997 205.9298,-1155.9672 205.9298,-1155.9672 205.9298,-1155.9672 205.3846,-1150.997 201.4567,-1156.4579 204.8393,-1146.0268 204.8393,-1146.0268"/>
|
||||
<text text-anchor="middle" x="199.9181" y="-1175.6794" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
|
||||
<path fill="none" stroke="#000000" d="M209.2902,-1195.9056C207.8967,-1182.6185 206.4991,-1169.2927 205.1209,-1156.1518"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="204.0712,-1146.1426 209.5898,-1155.6186 204.5928,-1151.1153 205.1143,-1156.0881 205.1143,-1156.0881 205.1143,-1156.0881 204.5928,-1151.1153 200.6389,-1156.5575 204.0712,-1146.1426 204.0712,-1146.1426"/>
|
||||
<text text-anchor="middle" x="198.9933" y="-1175.7599" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
|
||||
</g>
|
||||
<!-- A13->A10 -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>A13->A10</title>
|
||||
<path fill="none" stroke="#000000" d="M260.8183,-1185.9405C281.556,-1057.7747 308.5382,-891.0162 327.7708,-772.1524"/>
|
||||
<polygon fill="none" stroke="#000000" points="257.3528,-1185.4467 259.2105,-1195.8774 264.2629,-1186.5648 257.3528,-1185.4467"/>
|
||||
<path fill="none" stroke="#000000" d="M262.9659,-1185.8135C283.5931,-1055.2874 309.7059,-890.0494 328.3192,-772.2668"/>
|
||||
<polygon fill="none" stroke="#000000" points="259.4691,-1185.519 261.3652,-1195.9428 266.3833,-1186.6118 259.4691,-1185.519"/>
|
||||
</g>
|
||||
<!-- A14->A13 -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>A14->A13</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M188.2401,-1673.8004C192.8037,-1641.3079 198.7631,-1598.8764 204.747,-1556.2713"/>
|
||||
<polygon fill="none" stroke="#000000" points="184.7342,-1673.5986 186.8092,-1683.9883 191.6661,-1674.5723 184.7342,-1673.5986"/>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M187.8521,-1709.7603C192.164,-1677.5066 197.804,-1635.3194 203.5512,-1592.3307"/>
|
||||
<polygon fill="none" stroke="#000000" points="184.3563,-1709.4961 186.5003,-1719.8717 191.2946,-1710.4237 184.3563,-1709.4961"/>
|
||||
</g>
|
||||
<!-- A15 -->
|
||||
<g id="node16" class="node">
|
||||
<title>A15</title>
|
||||
<polygon fill="none" stroke="#000000" points="244.066,-1826 244.066,-1858 319.066,-1858 319.066,-1826 244.066,-1826"/>
|
||||
<text text-anchor="start" x="263.7835" y="-1839" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text>
|
||||
<polygon fill="none" stroke="#000000" points="244.066,-1674 244.066,-1826 319.066,-1826 319.066,-1674 244.066,-1674"/>
|
||||
<text text-anchor="start" x="273.2275" y="-1807" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text>
|
||||
<text text-anchor="start" x="254.056" y="-1783" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
|
||||
<text text-anchor="start" x="255.171" y="-1771" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text>
|
||||
<text text-anchor="start" x="265.1745" y="-1759" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text>
|
||||
<text text-anchor="start" x="255.4555" y="-1747" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text>
|
||||
<text text-anchor="start" x="263.508" y="-1735" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
|
||||
<text text-anchor="start" x="275.4575" y="-1723" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
|
||||
<text text-anchor="start" x="262.1195" y="-1711" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
|
||||
<text text-anchor="start" x="260.445" y="-1699" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
|
||||
<text text-anchor="start" x="274.9025" y="-1687" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
|
||||
<polygon fill="none" stroke="#000000" points="244.066,-1606 244.066,-1674 319.066,-1674 319.066,-1606 244.066,-1606"/>
|
||||
<text text-anchor="start" x="255.456" y="-1655" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
|
||||
<text text-anchor="start" x="258.79" y="-1643" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
|
||||
<text text-anchor="start" x="256.29" y="-1631" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
|
||||
<text text-anchor="start" x="266.5685" y="-1619" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
<polygon fill="none" stroke="#000000" points="244.066,-1862 244.066,-1894 319.066,-1894 319.066,-1862 244.066,-1862"/>
|
||||
<text text-anchor="start" x="263.7835" y="-1875" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text>
|
||||
<polygon fill="none" stroke="#000000" points="244.066,-1710 244.066,-1862 319.066,-1862 319.066,-1710 244.066,-1710"/>
|
||||
<text text-anchor="start" x="273.2275" y="-1843" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text>
|
||||
<text text-anchor="start" x="254.056" y="-1819" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
|
||||
<text text-anchor="start" x="255.171" y="-1807" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text>
|
||||
<text text-anchor="start" x="265.1745" y="-1795" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text>
|
||||
<text text-anchor="start" x="255.4555" y="-1783" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text>
|
||||
<text text-anchor="start" x="263.508" y="-1771" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
|
||||
<text text-anchor="start" x="275.4575" y="-1759" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
|
||||
<text text-anchor="start" x="262.1195" y="-1747" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
|
||||
<text text-anchor="start" x="260.445" y="-1735" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
|
||||
<text text-anchor="start" x="274.9025" y="-1723" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
|
||||
<polygon fill="none" stroke="#000000" points="244.066,-1642 244.066,-1710 319.066,-1710 319.066,-1642 244.066,-1642"/>
|
||||
<text text-anchor="start" x="255.456" y="-1691" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
|
||||
<text text-anchor="start" x="258.79" y="-1679" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
|
||||
<text text-anchor="start" x="256.29" y="-1667" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
|
||||
<text text-anchor="start" x="266.5685" y="-1655" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A15->A13 -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>A15->A13</title>
|
||||
<path fill="none" stroke="#000000" d="M261.5887,-1596.041C259.7128,-1582.9463 257.7908,-1569.5297 255.8664,-1556.0971"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="263.0135,-1605.9867 257.1408,-1596.726 262.3044,-1601.0373 261.5953,-1596.0878 261.5953,-1596.0878 261.5953,-1596.0878 262.3044,-1601.0373 266.0499,-1595.4496 263.0135,-1605.9867 263.0135,-1605.9867"/>
|
||||
<text text-anchor="middle" x="266.8039" y="-1569.8414" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
|
||||
<text text-anchor="middle" x="252.0761" y="-1586.2424" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">0..1</text>
|
||||
<path fill="none" stroke="#000000" d="M262.4684,-1631.6179C260.7062,-1618.6948 258.8964,-1605.4228 257.0763,-1592.0754"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="263.8708,-1641.9021 258.0609,-1632.6018 263.1952,-1636.9479 262.5196,-1631.9938 262.5196,-1631.9938 262.5196,-1631.9938 263.1952,-1636.9479 266.9783,-1631.3857 263.8708,-1641.9021 263.8708,-1641.9021"/>
|
||||
<text text-anchor="middle" x="267.9002" y="-1605.8933" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
|
||||
<text text-anchor="middle" x="253.0469" y="-1622.0841" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">0..1</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
@@ -31,7 +31,7 @@
|
||||
|
||||
[Talent]->[InfosG3]
|
||||
|
||||
[Message|server_side:bool;mb:Modbus;ifc:AsyncIfc;node_id;header_valid:bool;header_len;data_len;unique_id;sug_area:str;new_data:dict;state:State;shutdown_started:bool;modbus_elms;mb_timer:Timer;mb_timeout;mb_first_timeout;modbus_polling:bool|_set_mqtt_timestamp();_timeout();_send_modbus_cmd();<async> end_modbus_cmd();close();inc_counter();dec_counter()]
|
||||
[Message|server_side:bool;mb:Modbus;ifc:AsyncIfc;node_id;header_valid:bool;header_len;data_len;unique_id;sug_area:str;new_data:dict;state:State;shutdown_started:bool;modbus_elms;mb_timer:Timer;mb_timeout;mb_first_timeout;modbus_polling:bool|_set_config_parms(inv:dict);_set_mqtt_timestamp(key, ts);_timeout();_send_modbus_cmd();<async>send_modbus_cmd();_send_modbus_scan();_dump_modbus_scan();close();inc_counter();dec_counter()]
|
||||
[Message]use->[<<AsyncIfc>>]
|
||||
|
||||
[<<ProtocolIfc>>|_registry|close()]
|
||||
@@ -4,45 +4,45 @@
|
||||
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
|
||||
-->
|
||||
<!-- Title: G Pages: 1 -->
|
||||
<svg width="539pt" height="1940pt"
|
||||
viewBox="0.00 0.00 538.62 1940.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1936)">
|
||||
<svg width="539pt" height="1976pt"
|
||||
viewBox="0.00 0.00 538.62 1976.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1972)">
|
||||
<title>G</title>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1936 534.6165,-1936 534.6165,4 -4,4"/>
|
||||
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1972 534.6165,-1972 534.6165,4 -4,4"/>
|
||||
<!-- A0 -->
|
||||
<g id="node1" class="node">
|
||||
<title>A0</title>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="114.3497,-1912 -.1167,-1912 -.1167,-1868 120.3497,-1868 120.3497,-1906 114.3497,-1912"/>
|
||||
<polyline fill="none" stroke="#000000" points="114.3497,-1912 114.3497,-1906 "/>
|
||||
<polyline fill="none" stroke="#000000" points="120.3497,-1906 114.3497,-1906 "/>
|
||||
<text text-anchor="middle" x="60.1165" y="-1899" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Example of</text>
|
||||
<text text-anchor="middle" x="60.1165" y="-1887" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">instantiation for a</text>
|
||||
<text text-anchor="middle" x="60.1165" y="-1875" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">GEN3PLUS inverter!</text>
|
||||
<polygon fill="#fff8dc" stroke="#000000" points="114.3497,-1948 -.1167,-1948 -.1167,-1904 120.3497,-1904 120.3497,-1942 114.3497,-1948"/>
|
||||
<polyline fill="none" stroke="#000000" points="114.3497,-1948 114.3497,-1942 "/>
|
||||
<polyline fill="none" stroke="#000000" points="120.3497,-1942 114.3497,-1942 "/>
|
||||
<text text-anchor="middle" x="60.1165" y="-1935" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Example of</text>
|
||||
<text text-anchor="middle" x="60.1165" y="-1923" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">instantiation for a</text>
|
||||
<text text-anchor="middle" x="60.1165" y="-1911" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">GEN3PLUS inverter!</text>
|
||||
</g>
|
||||
<!-- A1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>A1</title>
|
||||
<polygon fill="none" stroke="#000000" points="138.1165,-1900 138.1165,-1932 254.1165,-1932 254.1165,-1900 138.1165,-1900"/>
|
||||
<text text-anchor="start" x="147.7655" y="-1913" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<AbstractIterMeta>></text>
|
||||
<polygon fill="none" stroke="#000000" points="138.1165,-1880 138.1165,-1900 254.1165,-1900 254.1165,-1880 138.1165,-1880"/>
|
||||
<polygon fill="none" stroke="#000000" points="138.1165,-1848 138.1165,-1880 254.1165,-1880 254.1165,-1848 138.1165,-1848"/>
|
||||
<text text-anchor="start" x="174.7265" y="-1861" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__()</text>
|
||||
<polygon fill="none" stroke="#000000" points="138.1165,-1936 138.1165,-1968 254.1165,-1968 254.1165,-1936 138.1165,-1936"/>
|
||||
<text text-anchor="start" x="147.7655" y="-1949" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<AbstractIterMeta>></text>
|
||||
<polygon fill="none" stroke="#000000" points="138.1165,-1916 138.1165,-1936 254.1165,-1936 254.1165,-1916 138.1165,-1916"/>
|
||||
<polygon fill="none" stroke="#000000" points="138.1165,-1884 138.1165,-1916 254.1165,-1916 254.1165,-1884 138.1165,-1884"/>
|
||||
<text text-anchor="start" x="174.7265" y="-1897" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">__iter__()</text>
|
||||
</g>
|
||||
<!-- A14 -->
|
||||
<g id="node15" class="node">
|
||||
<title>A14</title>
|
||||
<polygon fill="none" stroke="#000000" points="151.1165,-1688 151.1165,-1720 241.1165,-1720 241.1165,-1688 151.1165,-1688"/>
|
||||
<text text-anchor="start" x="160.823" y="-1701" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<ProtocolIfc>></text>
|
||||
<polygon fill="none" stroke="#000000" points="151.1165,-1656 151.1165,-1688 241.1165,-1688 241.1165,-1656 151.1165,-1656"/>
|
||||
<text text-anchor="start" x="176.95" y="-1669" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_registry</text>
|
||||
<polygon fill="none" stroke="#000000" points="151.1165,-1624 151.1165,-1656 241.1165,-1656 241.1165,-1624 151.1165,-1624"/>
|
||||
<text text-anchor="start" x="181.119" y="-1637" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
<polygon fill="none" stroke="#000000" points="151.1165,-1724 151.1165,-1756 241.1165,-1756 241.1165,-1724 151.1165,-1724"/>
|
||||
<text text-anchor="start" x="160.823" y="-1737" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><<ProtocolIfc>></text>
|
||||
<polygon fill="none" stroke="#000000" points="151.1165,-1692 151.1165,-1724 241.1165,-1724 241.1165,-1692 151.1165,-1692"/>
|
||||
<text text-anchor="start" x="176.95" y="-1705" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_registry</text>
|
||||
<polygon fill="none" stroke="#000000" points="151.1165,-1660 151.1165,-1692 241.1165,-1692 241.1165,-1660 151.1165,-1660"/>
|
||||
<text text-anchor="start" x="181.119" y="-1673" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A1->A14 -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>A1->A14</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M196.1165,-1837.756C196.1165,-1802.0883 196.1165,-1755.1755 196.1165,-1720.3644"/>
|
||||
<polygon fill="none" stroke="#000000" points="192.6166,-1837.9674 196.1165,-1847.9674 199.6166,-1837.9674 192.6166,-1837.9674"/>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M196.1165,-1873.756C196.1165,-1838.0883 196.1165,-1791.1755 196.1165,-1756.3644"/>
|
||||
<polygon fill="none" stroke="#000000" points="192.6166,-1873.9674 196.1165,-1883.9674 199.6166,-1873.9674 192.6166,-1873.9674"/>
|
||||
</g>
|
||||
<!-- A2 -->
|
||||
<g id="node3" class="node">
|
||||
@@ -282,31 +282,34 @@
|
||||
<!-- A13 -->
|
||||
<g id="node14" class="node">
|
||||
<title>A13</title>
|
||||
<polygon fill="none" stroke="#000000" points="172.1165,-1464 172.1165,-1496 321.1165,-1496 321.1165,-1464 172.1165,-1464"/>
|
||||
<text text-anchor="start" x="226.334" y="-1477" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text>
|
||||
<polygon fill="none" stroke="#000000" points="172.1165,-1240 172.1165,-1464 321.1165,-1464 321.1165,-1240 172.1165,-1240"/>
|
||||
<text text-anchor="start" x="209.943" y="-1445" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">server_side:bool</text>
|
||||
<text text-anchor="start" x="220.5005" y="-1433" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
|
||||
<text text-anchor="start" x="221.335" y="-1421" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
|
||||
<text text-anchor="start" x="228.83" y="-1409" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
|
||||
<text text-anchor="start" x="207.1595" y="-1397" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_valid:bool</text>
|
||||
<text text-anchor="start" x="221.6065" y="-1385" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_len</text>
|
||||
<text text-anchor="start" x="227.4405" y="-1373" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">data_len</text>
|
||||
<text text-anchor="start" x="224.941" y="-1361" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">unique_id</text>
|
||||
<text text-anchor="start" x="218.8315" y="-1349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">sug_area:str</text>
|
||||
<text text-anchor="start" x="215.7725" y="-1337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_data:dict</text>
|
||||
<text text-anchor="start" x="222.7165" y="-1325" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">state:State</text>
|
||||
<text text-anchor="start" x="196.321" y="-1313" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">shutdown_started:bool</text>
|
||||
<text text-anchor="start" x="215.501" y="-1301" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">modbus_elms</text>
|
||||
<text text-anchor="start" x="211.6235" y="-1289" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_timer:Timer</text>
|
||||
<text text-anchor="start" x="220.5015" y="-1277" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_timeout</text>
|
||||
<text text-anchor="start" x="209.669" y="-1265" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_first_timeout</text>
|
||||
<text text-anchor="start" x="200.7705" y="-1253" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">modbus_polling:bool</text>
|
||||
<polygon fill="none" stroke="#000000" points="172.1165,-1136 172.1165,-1240 321.1165,-1240 321.1165,-1136 172.1165,-1136"/>
|
||||
<text text-anchor="start" x="195.501" y="-1221" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_set_mqtt_timestamp()</text>
|
||||
<text text-anchor="start" x="224.1165" y="-1209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_timeout()</text>
|
||||
<text text-anchor="start" x="196.884" y="-1197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_send_modbus_cmd()</text>
|
||||
<text text-anchor="start" x="181.876" y="-1185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async> end_modbus_cmd()</text>
|
||||
<polygon fill="none" stroke="#000000" points="171.1165,-1500 171.1165,-1532 322.1165,-1532 322.1165,-1500 171.1165,-1500"/>
|
||||
<text text-anchor="start" x="226.334" y="-1513" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Message</text>
|
||||
<polygon fill="none" stroke="#000000" points="171.1165,-1276 171.1165,-1500 322.1165,-1500 322.1165,-1276 171.1165,-1276"/>
|
||||
<text text-anchor="start" x="209.943" y="-1481" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">server_side:bool</text>
|
||||
<text text-anchor="start" x="220.5005" y="-1469" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb:Modbus</text>
|
||||
<text text-anchor="start" x="221.335" y="-1457" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">ifc:AsyncIfc</text>
|
||||
<text text-anchor="start" x="228.83" y="-1445" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">node_id</text>
|
||||
<text text-anchor="start" x="207.1595" y="-1433" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_valid:bool</text>
|
||||
<text text-anchor="start" x="221.6065" y="-1421" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">header_len</text>
|
||||
<text text-anchor="start" x="227.4405" y="-1409" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">data_len</text>
|
||||
<text text-anchor="start" x="224.941" y="-1397" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">unique_id</text>
|
||||
<text text-anchor="start" x="218.8315" y="-1385" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">sug_area:str</text>
|
||||
<text text-anchor="start" x="215.7725" y="-1373" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">new_data:dict</text>
|
||||
<text text-anchor="start" x="222.7165" y="-1361" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">state:State</text>
|
||||
<text text-anchor="start" x="196.321" y="-1349" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">shutdown_started:bool</text>
|
||||
<text text-anchor="start" x="215.501" y="-1337" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">modbus_elms</text>
|
||||
<text text-anchor="start" x="211.6235" y="-1325" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_timer:Timer</text>
|
||||
<text text-anchor="start" x="220.5015" y="-1313" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_timeout</text>
|
||||
<text text-anchor="start" x="209.669" y="-1301" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">mb_first_timeout</text>
|
||||
<text text-anchor="start" x="200.7705" y="-1289" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">modbus_polling:bool</text>
|
||||
<polygon fill="none" stroke="#000000" points="171.1165,-1136 171.1165,-1276 322.1165,-1276 322.1165,-1136 171.1165,-1136"/>
|
||||
<text text-anchor="start" x="185.4975" y="-1257" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_set_config_parms(inv:dict)</text>
|
||||
<text text-anchor="start" x="181.053" y="-1245" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_set_mqtt_timestamp(key, ts)</text>
|
||||
<text text-anchor="start" x="224.1165" y="-1233" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_timeout()</text>
|
||||
<text text-anchor="start" x="196.884" y="-1221" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_send_modbus_cmd()</text>
|
||||
<text text-anchor="start" x="180.7655" y="-1209" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"><async>send_modbus_cmd()</text>
|
||||
<text text-anchor="start" x="195.769" y="-1197" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_send_modbus_scan()</text>
|
||||
<text text-anchor="start" x="194.1045" y="-1185" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">_dump_modbus_scan()</text>
|
||||
<text text-anchor="start" x="231.619" y="-1173" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
<text text-anchor="start" x="217.447" y="-1161" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">inc_counter()</text>
|
||||
<text text-anchor="start" x="215.777" y="-1149" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">dec_counter()</text>
|
||||
@@ -314,51 +317,51 @@
|
||||
<!-- A13->A5 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>A13->A5</title>
|
||||
<path fill="none" stroke="#000000" d="M226.347,-1135.7758C224.8967,-1122.5547 223.4359,-1109.2373 221.9911,-1096.0662"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="220.8898,-1086.0268 226.4535,-1095.4764 221.4351,-1090.997 221.9803,-1095.9672 221.9803,-1095.9672 221.9803,-1095.9672 221.4351,-1090.997 217.5072,-1096.4579 220.8898,-1086.0268 220.8898,-1086.0268"/>
|
||||
<text text-anchor="middle" x="215.9686" y="-1115.6794" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
|
||||
<path fill="none" stroke="#000000" d="M225.3407,-1135.9056C223.9472,-1122.6185 222.5496,-1109.2927 221.1714,-1096.1518"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="220.1217,-1086.1426 225.6403,-1095.6186 220.6433,-1091.1153 221.1648,-1096.0881 221.1648,-1096.0881 221.1648,-1096.0881 220.6433,-1091.1153 216.6894,-1096.5575 220.1217,-1086.1426 220.1217,-1086.1426"/>
|
||||
<text text-anchor="middle" x="215.0438" y="-1115.7599" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">use</text>
|
||||
</g>
|
||||
<!-- A13->A10 -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>A13->A10</title>
|
||||
<path fill="none" stroke="#000000" d="M277.1595,-1125.5329C299.2708,-989.8666 328.1962,-812.3923 346.4719,-700.2604"/>
|
||||
<polygon fill="none" stroke="#000000" points="273.6668,-1125.205 275.5125,-1135.6378 280.5757,-1126.3311 273.6668,-1125.205"/>
|
||||
<path fill="none" stroke="#000000" d="M279.2341,-1125.7521C301.232,-987.426 329.217,-811.4525 346.9052,-700.227"/>
|
||||
<polygon fill="none" stroke="#000000" points="275.7749,-1125.2195 277.6608,-1135.6451 282.688,-1126.319 275.7749,-1125.2195"/>
|
||||
</g>
|
||||
<!-- A14->A13 -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>A14->A13</title>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M204.2906,-1613.8004C208.8542,-1581.3079 214.8136,-1538.8764 220.7975,-1496.2713"/>
|
||||
<polygon fill="none" stroke="#000000" points="200.7847,-1613.5986 202.8597,-1623.9883 207.7166,-1614.5723 200.7847,-1613.5986"/>
|
||||
<path fill="none" stroke="#000000" stroke-dasharray="5,2" d="M203.9026,-1649.7603C208.2145,-1617.5066 213.8545,-1575.3194 219.6017,-1532.3307"/>
|
||||
<polygon fill="none" stroke="#000000" points="200.4068,-1649.4961 202.5508,-1659.8717 207.3451,-1650.4237 200.4068,-1649.4961"/>
|
||||
</g>
|
||||
<!-- A15 -->
|
||||
<g id="node16" class="node">
|
||||
<title>A15</title>
|
||||
<polygon fill="none" stroke="#000000" points="260.1165,-1766 260.1165,-1798 335.1165,-1798 335.1165,-1766 260.1165,-1766"/>
|
||||
<text text-anchor="start" x="279.834" y="-1779" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text>
|
||||
<polygon fill="none" stroke="#000000" points="260.1165,-1614 260.1165,-1766 335.1165,-1766 335.1165,-1614 260.1165,-1614"/>
|
||||
<text text-anchor="start" x="289.278" y="-1747" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text>
|
||||
<text text-anchor="start" x="270.1065" y="-1723" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
|
||||
<text text-anchor="start" x="271.2215" y="-1711" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text>
|
||||
<text text-anchor="start" x="281.225" y="-1699" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text>
|
||||
<text text-anchor="start" x="271.506" y="-1687" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text>
|
||||
<text text-anchor="start" x="279.5585" y="-1675" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
|
||||
<text text-anchor="start" x="291.508" y="-1663" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
|
||||
<text text-anchor="start" x="278.17" y="-1651" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
|
||||
<text text-anchor="start" x="276.4955" y="-1639" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
|
||||
<text text-anchor="start" x="290.953" y="-1627" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
|
||||
<polygon fill="none" stroke="#000000" points="260.1165,-1546 260.1165,-1614 335.1165,-1614 335.1165,-1546 260.1165,-1546"/>
|
||||
<text text-anchor="start" x="271.5065" y="-1595" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
|
||||
<text text-anchor="start" x="274.8405" y="-1583" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
|
||||
<text text-anchor="start" x="272.3405" y="-1571" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
|
||||
<text text-anchor="start" x="282.619" y="-1559" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
<polygon fill="none" stroke="#000000" points="260.1165,-1802 260.1165,-1834 335.1165,-1834 335.1165,-1802 260.1165,-1802"/>
|
||||
<text text-anchor="start" x="279.834" y="-1815" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">Modbus</text>
|
||||
<polygon fill="none" stroke="#000000" points="260.1165,-1650 260.1165,-1802 335.1165,-1802 335.1165,-1650 260.1165,-1650"/>
|
||||
<text text-anchor="start" x="289.278" y="-1783" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">que</text>
|
||||
<text text-anchor="start" x="270.1065" y="-1759" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">snd_handler</text>
|
||||
<text text-anchor="start" x="271.2215" y="-1747" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">rsp_handler</text>
|
||||
<text text-anchor="start" x="281.225" y="-1735" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">timeout</text>
|
||||
<text text-anchor="start" x="271.506" y="-1723" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">max_retires</text>
|
||||
<text text-anchor="start" x="279.5585" y="-1711" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">last_xxx</text>
|
||||
<text text-anchor="start" x="291.508" y="-1699" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">err</text>
|
||||
<text text-anchor="start" x="278.17" y="-1687" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">retry_cnt</text>
|
||||
<text text-anchor="start" x="276.4955" y="-1675" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">req_pend</text>
|
||||
<text text-anchor="start" x="290.953" y="-1663" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">tim</text>
|
||||
<polygon fill="none" stroke="#000000" points="260.1165,-1582 260.1165,-1650 335.1165,-1650 335.1165,-1582 260.1165,-1582"/>
|
||||
<text text-anchor="start" x="271.5065" y="-1631" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">build_msg()</text>
|
||||
<text text-anchor="start" x="274.8405" y="-1619" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_req()</text>
|
||||
<text text-anchor="start" x="272.3405" y="-1607" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">recv_resp()</text>
|
||||
<text text-anchor="start" x="282.619" y="-1595" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">close()</text>
|
||||
</g>
|
||||
<!-- A15->A13 -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>A15->A13</title>
|
||||
<path fill="none" stroke="#000000" d="M277.6392,-1536.041C275.7633,-1522.9463 273.8413,-1509.5297 271.9169,-1496.0971"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="279.064,-1545.9867 273.1913,-1536.726 278.3549,-1541.0373 277.6458,-1536.0878 277.6458,-1536.0878 277.6458,-1536.0878 278.3549,-1541.0373 282.1004,-1535.4496 279.064,-1545.9867 279.064,-1545.9867"/>
|
||||
<text text-anchor="middle" x="282.8544" y="-1509.8414" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
|
||||
<text text-anchor="middle" x="268.1266" y="-1526.2424" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">0..1</text>
|
||||
<path fill="none" stroke="#000000" d="M278.5189,-1571.6179C276.7567,-1558.6948 274.9469,-1545.4228 273.1268,-1532.0754"/>
|
||||
<polygon fill="#000000" stroke="#000000" points="279.9213,-1581.9021 274.1114,-1572.6018 279.2457,-1576.9479 278.5701,-1571.9938 278.5701,-1571.9938 278.5701,-1571.9938 279.2457,-1576.9479 283.0288,-1571.3857 279.9213,-1581.9021 279.9213,-1581.9021"/>
|
||||
<text text-anchor="middle" x="283.9507" y="-1545.8933" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">has</text>
|
||||
<text text-anchor="middle" x="269.0974" y="-1562.0841" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">0..1</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@@ -30,7 +30,7 @@
|
||||
|
||||
[SolarmanV5]->[InfosG3P]
|
||||
|
||||
[Message|server_side:bool;mb:Modbus;ifc:AsyncIfc;node_id;header_valid:bool;header_len;data_len;unique_id;sug_area:str;new_data:dict;state:State;shutdown_started:bool;modbus_elms;mb_timer:Timer;mb_timeout;mb_first_timeout;modbus_polling:bool|_set_mqtt_timestamp();_timeout();_send_modbus_cmd();<async> end_modbus_cmd();close();inc_counter();dec_counter()]
|
||||
[Message|server_side:bool;mb:Modbus;ifc:AsyncIfc;node_id;header_valid:bool;header_len;data_len;unique_id;sug_area:str;new_data:dict;state:State;shutdown_started:bool;modbus_elms;mb_timer:Timer;mb_timeout;mb_first_timeout;modbus_polling:bool|_set_config_parms(inv:dict);_set_mqtt_timestamp(key, ts);_timeout();_send_modbus_cmd();<async>send_modbus_cmd();_send_modbus_scan();_dump_modbus_scan();close();inc_counter();dec_counter()]
|
||||
[Message]use->[<<AsyncIfc>>]
|
||||
|
||||
[<<ProtocolIfc>>|_registry|close()]
|
||||
@@ -1,4 +1,4 @@
|
||||
aiomqtt==2.3.0
|
||||
schema==0.7.7
|
||||
aiocron==1.8
|
||||
aiohttp==3.11.11
|
||||
aiocron==2.1
|
||||
aiohttp==3.11.14
|
||||
@@ -78,7 +78,8 @@ class Config():
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': Use(bool), And(Use(str), lambda s: len(s) == 16): {
|
||||
'allow_all': Use(bool),
|
||||
And(Use(str), lambda s: len(s) == 16): {
|
||||
Optional('monitor_sn', default=0): Use(int),
|
||||
Optional('node_id', default=""): And(Use(str),
|
||||
Use(lambda s: s + '/'
|
||||
@@ -92,8 +93,13 @@ class Config():
|
||||
Optional('forward', default=False): Use(bool),
|
||||
},
|
||||
Optional('modbus_polling', default=True): Use(bool),
|
||||
Optional('modbus_scanning'): {
|
||||
'start': Use(int),
|
||||
Optional('step', default=0x400): Use(int),
|
||||
Optional('bytes', default=0x10): Use(int),
|
||||
},
|
||||
Optional('suggested_area', default=""): Use(str),
|
||||
Optional('sensor_list', default=0x2b0): Use(int),
|
||||
Optional('sensor_list', default=0): Use(int),
|
||||
Optional('pv1'): {
|
||||
Optional('type'): Use(str),
|
||||
Optional('manufacturer'): Use(str),
|
||||
@@ -119,6 +125,38 @@ class Config():
|
||||
Optional('manufacturer'): Use(str),
|
||||
}
|
||||
}
|
||||
},
|
||||
'batteries': {
|
||||
And(Use(str), lambda s: len(s) == 16): {
|
||||
Optional('monitor_sn', default=0): Use(int),
|
||||
Optional('node_id', default=""): And(Use(str),
|
||||
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('forward', default=False): Use(bool),
|
||||
},
|
||||
Optional('modbus_polling', default=True): Use(bool),
|
||||
Optional('modbus_scanning'): {
|
||||
'start': Use(int),
|
||||
Optional('step', default=0x400): Use(int),
|
||||
Optional('bytes', default=0x10): Use(int),
|
||||
},
|
||||
Optional('suggested_area', default=""): Use(str),
|
||||
Optional('sensor_list', default=0): Use(int),
|
||||
Optional('pv1'): {
|
||||
Optional('type'): Use(str),
|
||||
Optional('manufacturer'): Use(str),
|
||||
},
|
||||
Optional('pv2'): {
|
||||
Optional('type'): Use(str),
|
||||
Optional('manufacturer'): Use(str),
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ignore_extra_keys=True
|
||||
)
|
||||
@@ -178,7 +216,7 @@ here. The default config reader is handled in the Config.init method'''
|
||||
rd_config = reader.get_config()
|
||||
config = cls.act_config.copy()
|
||||
for key in ['tsun', 'solarman', 'mqtt', 'ha', 'inverters',
|
||||
'gen3plus']:
|
||||
'gen3plus', 'batteries']:
|
||||
if key in rd_config:
|
||||
config[key] = config[key] | rd_config[key]
|
||||
|
||||
|
||||
@@ -31,7 +31,8 @@ class ConfigReadJson(ConfigIfc):
|
||||
def convert_to_obj(self, data):
|
||||
conf = {}
|
||||
for key, val in data.items():
|
||||
if key == 'inverters' and isinstance(val, list):
|
||||
if (key == 'inverters' or key == 'batteries') and \
|
||||
isinstance(val, list):
|
||||
self.convert_inv_arr(conf, key, val)
|
||||
else:
|
||||
self._extend_key(conf, key, val)
|
||||
|
||||
@@ -113,7 +113,7 @@ inverters.allow_all = false # only allow known inverters
|
||||
##
|
||||
## For each GEN3 inverter, the serial number of the inverter must be mapped to an MQTT
|
||||
## definition. To do this, the corresponding configuration block is started with
|
||||
## `[Inverter.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## `[inverters.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## to this inverter. Further inverter-specific parameters (e.g. polling mode) can be set
|
||||
## in the configuration block
|
||||
##
|
||||
@@ -132,7 +132,7 @@ pv2 = {type = 'RSM40-8-395M', manufacturer = 'Risen'} # Optional, PV module de
|
||||
##
|
||||
## For each GEN3PLUS inverter, the serial number of the inverter must be mapped to an MQTT
|
||||
## definition. To do this, the corresponding configuration block is started with
|
||||
## `[Inverter.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## `[inverters.“<16-digit serial number>”]` so that all subsequent parameters are assigned
|
||||
## to this inverter. Further inverter-specific parameters (e.g. polling mode, client mode)
|
||||
## can be set in the configuration block
|
||||
##
|
||||
@@ -157,6 +157,33 @@ 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 = '' # MQTT replacement for devices serial number
|
||||
suggested_area = '' # 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
|
||||
@@ -2,26 +2,14 @@
|
||||
import struct
|
||||
import logging
|
||||
from typing import Generator
|
||||
from itertools import chain
|
||||
|
||||
from infos import Infos, Register
|
||||
|
||||
|
||||
class RegisterMap:
|
||||
__slots__ = ()
|
||||
|
||||
map = {
|
||||
0x00092ba8: {'reg': Register.COLLECTOR_FW_VERSION},
|
||||
0x000927c0: {'reg': Register.CHIP_TYPE},
|
||||
0x00092f90: {'reg': Register.CHIP_MODEL},
|
||||
0x00094ae8: {'reg': Register.MAC_ADDR},
|
||||
0x00095a88: {'reg': Register.TRACE_URL},
|
||||
0x00095aec: {'reg': Register.LOGGER_URL},
|
||||
0x0000000a: {'reg': Register.PRODUCT_NAME},
|
||||
0x00000014: {'reg': Register.MANUFACTURER},
|
||||
0x0000001e: {'reg': Register.VERSION},
|
||||
0x00000028: {'reg': Register.SERIAL_NUMBER},
|
||||
0x00000032: {'reg': Register.EQUIPMENT_MODEL},
|
||||
0x00013880: {'reg': Register.NO_INPUTS},
|
||||
0xffffff00: {'reg': Register.INVERTER_CNT},
|
||||
0xffffff01: {'reg': Register.UNKNOWN_SNR},
|
||||
0xffffff02: {'reg': Register.UNKNOWN_MSG},
|
||||
@@ -33,6 +21,100 @@ class RegisterMap:
|
||||
0xffffff08: {'reg': Register.POLLING_INTERVAL},
|
||||
0xfffffffe: {'reg': Register.TEST_REG1},
|
||||
0xffffffff: {'reg': Register.TEST_REG2},
|
||||
}
|
||||
map_0e100000 = {
|
||||
0x00092ba8: {'reg': Register.COLLECTOR_FW_VERSION},
|
||||
0x000927c0: {'reg': Register.CHIP_TYPE},
|
||||
0x00092f90: {'reg': Register.CHIP_MODEL},
|
||||
0x00094ae8: {'reg': Register.MAC_ADDR},
|
||||
0x00095a88: {'reg': Register.TRACE_URL},
|
||||
0x00095aec: {'reg': Register.LOGGER_URL},
|
||||
0x000cfc38: {'reg': Register.CONNECT_COUNT},
|
||||
0x000c3500: {'reg': Register.SIGNAL_STRENGTH},
|
||||
0x000c96a8: {'reg': Register.POWER_ON_TIME},
|
||||
0x000d0020: {'reg': Register.COLLECT_INTERVAL},
|
||||
0x000cf850: {'reg': Register.DATA_UP_INTERVAL},
|
||||
0x000c7f38: {'reg': Register.COMMUNICATION_TYPE},
|
||||
}
|
||||
map_01900000 = {
|
||||
0x0000000a: {'reg': Register.PRODUCT_NAME},
|
||||
0x00000014: {'reg': Register.MANUFACTURER},
|
||||
0x0000001e: {'reg': Register.VERSION},
|
||||
0x00000046: {'reg': Register.SERIAL_NUMBER},
|
||||
0x0000005A: {'reg': Register.EQUIPMENT_MODEL},
|
||||
0x00000064: {'reg': Register.INVERTER_STATUS},
|
||||
0x00000190: {'reg': Register.EVENT_ALARM},
|
||||
0x000001f4: {'reg': Register.EVENT_FAULT},
|
||||
0x00000258: {'reg': Register.EVENT_BF1},
|
||||
0x000002bc: {'reg': Register.EVENT_BF2},
|
||||
0x00000320: {'reg': Register.TEST_IVAL_1},
|
||||
0x000003e8: {'reg': Register.TEST_VAL_0},
|
||||
0x0000044c: {'reg': Register.TEST_VAL_1}, # DC 1 Inpput Voltage *10
|
||||
0x000004b0: {'reg': Register.TEST_VAL_2},
|
||||
0x00000514: {'reg': Register.GRID_VOLTAGE}, # Grid Voltage
|
||||
0x00000578: {'reg': Register.GRID_CURRENT}, # Grid Current
|
||||
0x000005dc: {'reg': Register.TEST_VAL_3},
|
||||
0x00000640: {'reg': Register.GRID_FREQUENCY},
|
||||
0x000006a4: {'reg': Register.TEST_IVAL_2},
|
||||
0x00000708: {'reg': Register.TEST_IVAL_3},
|
||||
0x0000076c: {'reg': Register.TEST_IVAL_4},
|
||||
0x000007d0: {'reg': Register.TEST_VAL_4}, # DC 2 Input Voltage *10
|
||||
0x00000834: {'reg': Register.MAX_DESIGNED_POWER},
|
||||
0x00000898: {'reg': Register.OUTPUT_POWER}, # Grid Power
|
||||
0x000008fc: {'reg': Register.DAILY_GENERATION}, # Daily Generation
|
||||
0x00000960: {'reg': Register.TOTAL_GENERATION}, # Total Genration
|
||||
0x000009c4: {'reg': Register.TEST_IVAL_5},
|
||||
0x00000a28: {'reg': Register.TEST_VAL_10}, # Isolationsimpedanz Rx
|
||||
0x00000a8c: {'reg': Register.TEST_VAL_11}, # Isolationsimpedanz Ry
|
||||
0x00000af0: {'reg': Register.TEST_IVAL_6},
|
||||
0x000001324: {'reg': Register.PV1_VOLTAGE}, # PV1 Voltage
|
||||
0x000001388: {'reg': Register.PV1_CURRENT}, # PV1 Current
|
||||
0x0000013ec: {'reg': Register.PV1_POWER}, # PV1 Power
|
||||
0x000001450: {'reg': Register.TEST_VAL_5},
|
||||
0x0000015e0: {'reg': Register.PV2_VOLTAGE}, # PV2 Voltage
|
||||
0x000001644: {'reg': Register.PV2_CURRENT}, # PV2 Current
|
||||
0x0000016a8: {'reg': Register.PV2_POWER}, # PV2 Power
|
||||
0x00000170c: {'reg': Register.TEST_VAL_6},
|
||||
0x00000189c: {'reg': Register.PV3_VOLTAGE},
|
||||
0x000001900: {'reg': Register.PV3_CURRENT},
|
||||
0x000001964: {'reg': Register.PV3_POWER},
|
||||
0x0000019c8: {'reg': Register.TEST_VAL_7},
|
||||
0x000001c20: {'reg': Register.TEST_VAL_14},
|
||||
0x000001c84: {'reg': Register.TEST_VAL_15},
|
||||
0x000001ce8: {'reg': Register.TEST_VAL_16}, # DC 1 Voltage
|
||||
0x000001d4c: {'reg': Register.TEST_VAL_17},
|
||||
0x000001db0: {'reg': Register.TEST_VAL_18},
|
||||
0x000001e14: {'reg': Register.TEST_IVAL_8},
|
||||
0x000001e78: {'reg': Register.PV4_VOLTAGE},
|
||||
0x000001edc: {'reg': Register.PV4_CURRENT},
|
||||
0x000001f40: {'reg': Register.PV4_POWER},
|
||||
0x000001fa4: {'reg': Register.TEST_VAL_8},
|
||||
0x0000020c9: {'reg': Register.TEST_IVAL_9},
|
||||
0x0000020db: {'reg': Register.TEST_IVAL_10},
|
||||
|
||||
0x000002134: {'reg': Register.PV5_VOLTAGE},
|
||||
0x000002198: {'reg': Register.PV5_CURRENT},
|
||||
0x0000021fc: {'reg': Register.PV5_POWER},
|
||||
# 0x000002260: {'reg': Register.TEST_VAL_13},
|
||||
0x0000023f0: {'reg': Register.PV6_VOLTAGE},
|
||||
0x000002454: {'reg': Register.PV6_CURRENT},
|
||||
0x0000024b8: {'reg': Register.PV6_POWER},
|
||||
# 0x00000251c: {'reg': Register.TEST_VAL_14},
|
||||
0x000002774: {'reg': Register.TEST_VAL_24},
|
||||
0x0000027d8: {'reg': Register.TEST_VAL_25},
|
||||
0x00000283c: {'reg': Register.TEST_VAL_26}, # DC 2 Voltage
|
||||
0x0000028a0: {'reg': Register.TEST_VAL_27},
|
||||
0x000002904: {'reg': Register.TEST_VAL_28},
|
||||
0x000002968: {'reg': Register.TEST_IVAL_11},
|
||||
0x0000029cc: {'reg': Register.TEST_IVAL_12},
|
||||
}
|
||||
map_01900001 = {
|
||||
0x0000000a: {'reg': Register.PRODUCT_NAME},
|
||||
0x00000014: {'reg': Register.MANUFACTURER},
|
||||
0x0000001e: {'reg': Register.VERSION},
|
||||
0x00000028: {'reg': Register.SERIAL_NUMBER},
|
||||
0x00000032: {'reg': Register.EQUIPMENT_MODEL},
|
||||
0x00013880: {'reg': Register.NO_INPUTS},
|
||||
0x00000640: {'reg': Register.OUTPUT_POWER},
|
||||
0x000005dc: {'reg': Register.RATED_POWER},
|
||||
0x00000514: {'reg': Register.INVERTER_TEMP},
|
||||
@@ -61,12 +143,7 @@ class RegisterMap:
|
||||
0x000003e8: {'reg': Register.GRID_VOLTAGE},
|
||||
0x0000044c: {'reg': Register.GRID_CURRENT},
|
||||
0x000004b0: {'reg': Register.GRID_FREQUENCY},
|
||||
0x000cfc38: {'reg': Register.CONNECT_COUNT},
|
||||
0x000c3500: {'reg': Register.SIGNAL_STRENGTH},
|
||||
0x000c96a8: {'reg': Register.POWER_ON_TIME},
|
||||
0x000d0020: {'reg': Register.COLLECT_INTERVAL},
|
||||
0x000cf850: {'reg': Register.DATA_UP_INTERVAL},
|
||||
0x000c7f38: {'reg': Register.COMMUNICATION_TYPE},
|
||||
|
||||
0x00000190: {'reg': Register.EVENT_ALARM},
|
||||
0x000001f4: {'reg': Register.EVENT_FAULT},
|
||||
0x00000258: {'reg': Register.EVENT_BF1},
|
||||
@@ -86,6 +163,18 @@ class RegisterMap:
|
||||
}
|
||||
|
||||
|
||||
class RegisterSel:
|
||||
__sensor_map = {
|
||||
0x0e100000: RegisterMap.map_0e100000,
|
||||
0x01900000: RegisterMap.map_01900000,
|
||||
0x01900001: RegisterMap.map_01900001,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get(cls, sensor: int):
|
||||
return cls.__sensor_map.get(sensor, RegisterMap.map)
|
||||
|
||||
|
||||
class InfosG3(Infos):
|
||||
__slots__ = ()
|
||||
|
||||
@@ -101,18 +190,27 @@ class InfosG3(Infos):
|
||||
entity strings
|
||||
sug_area:str ==> suggested area string from the config file'''
|
||||
# iterate over RegisterMap.map and get the register values
|
||||
for row in RegisterMap.map.values():
|
||||
sensor = self.get_db_value(Register.SENSOR_LIST)
|
||||
if "01900000" == sensor:
|
||||
items = RegisterMap.map_01900000.items()
|
||||
elif "01900001" == sensor:
|
||||
items = RegisterMap.map_01900001.items()
|
||||
else:
|
||||
items = {}
|
||||
|
||||
for _, row in chain(RegisterMap.map_0e100000.items(), items):
|
||||
reg = row['reg']
|
||||
res = self.ha_conf(reg, ha_prfx, node_id, snr, False, sug_area) # noqa: E501
|
||||
if res:
|
||||
yield res
|
||||
|
||||
def parse(self, buf, ind=0, node_id: str = '') -> \
|
||||
def parse(self, buf, ind=0, sensor: int = 0, node_id: str = '') -> \
|
||||
Generator[tuple[str, bool], None, None]:
|
||||
'''parse a data sequence received from the inverter and
|
||||
stores the values in Infos.db
|
||||
|
||||
buf: buffer of the sequence to parse'''
|
||||
reg_map = RegisterSel.get(sensor)
|
||||
result = struct.unpack_from('!l', buf, ind)
|
||||
elms = result[0]
|
||||
i = 0
|
||||
@@ -120,11 +218,11 @@ class InfosG3(Infos):
|
||||
while i < elms:
|
||||
result = struct.unpack_from('!lB', buf, ind)
|
||||
addr = result[0]
|
||||
if addr not in RegisterMap.map:
|
||||
if addr not in reg_map:
|
||||
row = None
|
||||
info_id = -1
|
||||
else:
|
||||
row = RegisterMap.map[addr]
|
||||
row = reg_map[addr]
|
||||
info_id = row['reg']
|
||||
data_type = result[1]
|
||||
ind += 5
|
||||
@@ -192,3 +290,6 @@ class InfosG3(Infos):
|
||||
if update:
|
||||
self.tracer.log(level, f'[{node_id}] GEN3: {name} :'
|
||||
f' {result}{unit}')
|
||||
|
||||
logging.log(level, f'[{node_id}] GEN3: {name} :'
|
||||
f' {result}{unit}')
|
||||
|
||||
@@ -75,6 +75,7 @@ class Talent(Message):
|
||||
0x87: self.get_modbus_log_lvl,
|
||||
0x04: logging.INFO,
|
||||
}
|
||||
self.sensor_list = 0
|
||||
|
||||
'''
|
||||
Our puplic methods
|
||||
@@ -98,13 +99,9 @@ class Talent(Message):
|
||||
|
||||
if serial_no in inverters:
|
||||
inv = inverters[serial_no]
|
||||
self.node_id = inv['node_id']
|
||||
self.sug_area = inv['suggested_area']
|
||||
self.modbus_polling = inv['modbus_polling']
|
||||
logger.debug(f'SerialNo {serial_no} allowed! area:{self.sug_area}') # noqa: E501
|
||||
self._set_config_parms(inv)
|
||||
self.db.set_pv_module_details(inv)
|
||||
if self.mb:
|
||||
self.mb.set_node_id(self.node_id)
|
||||
logger.debug(f'SerialNo {serial_no} allowed! area:{self.sug_area}') # noqa: E501
|
||||
else:
|
||||
self.node_id = ''
|
||||
self.sug_area = ''
|
||||
@@ -175,12 +172,17 @@ class Talent(Message):
|
||||
|
||||
def mb_timout_cb(self, exp_cnt):
|
||||
self.mb_timer.start(self.mb_timeout)
|
||||
if self.mb_scan:
|
||||
self._send_modbus_scan()
|
||||
return
|
||||
|
||||
if 2 == (exp_cnt % 30):
|
||||
# logging.info("Regular Modbus Status request")
|
||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x2000, 96, logging.DEBUG)
|
||||
self._send_modbus_cmd(Modbus.INV_ADDR, Modbus.READ_REGS, 0x2000,
|
||||
96, logging.DEBUG)
|
||||
else:
|
||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x3000, 48, logging.DEBUG)
|
||||
self._send_modbus_cmd(Modbus.INV_ADDR, Modbus.READ_REGS, 0x3000,
|
||||
48, logging.DEBUG)
|
||||
|
||||
def _init_new_client_conn(self) -> bool:
|
||||
contact_name = self.contact_name
|
||||
@@ -442,7 +444,7 @@ class Talent(Message):
|
||||
logger.debug(f'time: {timestamp:08x}')
|
||||
# logger.info(f'time: {datetime.utcfromtimestamp(result[2]).strftime(
|
||||
# "%Y-%m-%d %H:%M:%S")}')
|
||||
return msg_hdr_len, timestamp
|
||||
return msg_hdr_len, data_id, timestamp
|
||||
|
||||
def msg_collector_data(self):
|
||||
if self.ctrl.is_ind():
|
||||
@@ -479,21 +481,51 @@ class Talent(Message):
|
||||
|
||||
self.forward()
|
||||
|
||||
def __process_data(self, ignore_replay: bool):
|
||||
msg_hdr_len, ts = self.parse_msg_header()
|
||||
if ignore_replay:
|
||||
def __build_model_name(self):
|
||||
db = self.db
|
||||
model = db.get_db_value(Register.EQUIPMENT_MODEL, None)
|
||||
if model:
|
||||
return
|
||||
max_pow = db.get_db_value(Register.MAX_DESIGNED_POWER, 0)
|
||||
if max_pow == 3000:
|
||||
model = f'TSOL-MS{max_pow}'
|
||||
self.db.set_db_def_value(Register.EQUIPMENT_MODEL, model)
|
||||
self.db.set_db_def_value(Register.MANUFACTURER, 'TSUN')
|
||||
self.db.set_db_def_value(Register.NO_INPUTS, 4)
|
||||
|
||||
def __process_data(self, inv_data: bool):
|
||||
msg_hdr_len, data_id, ts = self.parse_msg_header()
|
||||
if inv_data:
|
||||
# handle register mapping
|
||||
if 0 == self.sensor_list:
|
||||
self.sensor_list = data_id
|
||||
self.db.set_db_def_value(Register.SENSOR_LIST,
|
||||
f"{self.sensor_list:08x}")
|
||||
logging.debug(f"Use sensor-list: {self.sensor_list:#08x}"
|
||||
f" for '{self.unique_id}'")
|
||||
if data_id != self.sensor_list:
|
||||
logging.warning(f'Unexpected Sensor-List:{data_id:08x}'
|
||||
f' (!={self.sensor_list:08x})')
|
||||
# ignore replays for inverter data
|
||||
age = self._utc() - self._utcfromts(ts)
|
||||
age = age/(3600*24)
|
||||
logger.debug(f"Age: {age} days")
|
||||
if age > 1:
|
||||
if age > 1: # is a replay?
|
||||
return
|
||||
|
||||
inv_update = False
|
||||
|
||||
for key, update in self.db.parse(self.ifc.rx_peek(), self.header_len
|
||||
+ msg_hdr_len, self.node_id):
|
||||
+ msg_hdr_len, data_id, self.node_id):
|
||||
if update:
|
||||
if key == 'inverter':
|
||||
inv_update = True
|
||||
self._set_mqtt_timestamp(key, self._utcfromts(ts))
|
||||
self.new_data[key] = True
|
||||
|
||||
if inv_update:
|
||||
self.__build_model_name()
|
||||
|
||||
def msg_ota_update(self):
|
||||
if self.ctrl.is_req():
|
||||
self.inc_counter('OTA_Start_Msg')
|
||||
@@ -554,6 +586,9 @@ class Talent(Message):
|
||||
logger.warning('Unknown Message')
|
||||
self.inc_counter('Unknown_Msg')
|
||||
return
|
||||
if (self.mb_scan):
|
||||
modbus_msg_len = self.data_len - hdr_len
|
||||
self._dump_modbus_scan(data, hdr_len, modbus_msg_len)
|
||||
|
||||
for key, update, _ in self.mb.recv_resp(self.db, data[
|
||||
hdr_len:]):
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
|
||||
from typing import Generator
|
||||
from itertools import chain
|
||||
|
||||
from infos import Infos, Register, ProxyMode, Fmt
|
||||
|
||||
|
||||
class RegisterFunc:
|
||||
@staticmethod
|
||||
def prod_sum(info: Infos, arr: dict) -> None | int:
|
||||
result = 0
|
||||
for sum in arr:
|
||||
prod = 1
|
||||
for factor in sum:
|
||||
val = info.get_db_value(factor)
|
||||
if val is None:
|
||||
return None
|
||||
prod = prod * val
|
||||
result += prod
|
||||
return result
|
||||
|
||||
|
||||
class RegisterMap:
|
||||
# make the class read/only by using __slots__
|
||||
__slots__ = ()
|
||||
@@ -32,7 +48,8 @@ class RegisterMap:
|
||||
0x4102008e: {'reg': None, 'fmt': '<B'}, # noqa: E501 Encryption Certificate File Status
|
||||
0x4102008f: {'reg': None, 'fmt': '!40s'}, # noqa: E501
|
||||
0x410200b7: {'reg': Register.SSID, 'fmt': '!40s'}, # noqa: E501
|
||||
|
||||
}
|
||||
map_02b0 = {
|
||||
0x4201000c: {'reg': Register.SENSOR_LIST, 'fmt': '<H', 'func': Fmt.hex4}, # noqa: E501
|
||||
0x4201001c: {'reg': Register.POWER_ON_TIME, 'fmt': '<H', 'ratio': 1, 'dep': ProxyMode.SERVER}, # noqa: E501, or packet number
|
||||
0x42010020: {'reg': Register.SERIAL_NUMBER, 'fmt': '!16s'}, # noqa: E501
|
||||
@@ -110,6 +127,69 @@ class RegisterMap:
|
||||
0xffffff02: {'reg': Register.POLLING_INTERVAL},
|
||||
# 0x4281001c: {'reg': Register.POWER_ON_TIME, 'fmt': '<H', 'ratio': 1}, # noqa: E501
|
||||
}
|
||||
map_3026 = {
|
||||
0x4201000c: {'reg': Register.SENSOR_LIST, 'fmt': '<H', 'func': Fmt.hex4}, # noqa: E501
|
||||
0x4201001c: {'reg': Register.POWER_ON_TIME, 'fmt': '<H', 'ratio': 1, 'dep': ProxyMode.SERVER}, # noqa: E501, or packet number
|
||||
0x42010020: {'reg': Register.SERIAL_NUMBER, 'fmt': '!16s'}, # noqa: E501
|
||||
0x42010030: {'reg': Register.BATT_PV1_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV1 voltage
|
||||
0x42010032: {'reg': Register.BATT_PV1_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV1 current
|
||||
0x42010034: {'reg': Register.BATT_PV2_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV2 voltage
|
||||
0x42010036: {'reg': Register.BATT_PV2_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV2 current
|
||||
0x42010038: {'reg': Register.BATT_38, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201003a: {'reg': Register.BATT_TOTAL_GEN, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x4201003c: {'reg': Register.BATT_STATUS_1, 'fmt': '!h'}, # noqa: E501 MPTT-1 Status?
|
||||
0x4201003e: {'reg': Register.BATT_STATUS_2, 'fmt': '!h'}, # noqa: E501 MPTT-2 Status?
|
||||
0x42010040: {'reg': Register.BATT_VOLT, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x42010042: {'reg': Register.BATT_CUR, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x42010044: {'reg': Register.BATT_SOC, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, state of charge (SOC) in percent
|
||||
0x42010046: {'reg': Register.BATT_CELL1_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x42010048: {'reg': Register.BATT_CELL2_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x4201004a: {'reg': Register.BATT_CELL3_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x4201004c: {'reg': Register.BATT_CELL4_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x4201004e: {'reg': Register.BATT_CELL5_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x42010050: {'reg': Register.BATT_CELL6_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x42010052: {'reg': Register.BATT_CELL7_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x42010054: {'reg': Register.BATT_CELL8_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x42010056: {'reg': Register.BATT_CELL9_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x42010058: {'reg': Register.BATT_CELL10_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x4201005a: {'reg': Register.BATT_CELL11_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x4201005c: {'reg': Register.BATT_CELL12_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x4201005e: {'reg': Register.BATT_CELL13_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x42010060: {'reg': Register.BATT_CELL14_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x42010062: {'reg': Register.BATT_CELL15_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x42010064: {'reg': Register.BATT_CELL16_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
|
||||
0x42010066: {'reg': Register.BATT_TEMP_1, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010068: {'reg': Register.BATT_TEMP_2, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201006a: {'reg': Register.BATT_TEMP_3, 'fmt': '!h'}, # noqa: E501
|
||||
0x4201006c: {'reg': Register.BATT_OUT_VOLT, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x4201006e: {'reg': Register.BATT_OUT_CUR, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x42010070: {'reg': Register.BATT_OUT_STATUS, 'fmt': '!h'}, # noqa: E501, state of output value 0 or 1
|
||||
0x42010072: {'reg': Register.BATT_TEMP_4, 'fmt': '!h'}, # noqa: E501 controller temp
|
||||
0x42010074: {'reg': Register.BATT_74, 'fmt': '!h'}, # noqa: E501, control input 0..2048
|
||||
0x42010076: {'reg': Register.BATT_76, 'fmt': '!h'}, # noqa: E501
|
||||
0x42010078: {'reg': Register.BATT_78, 'fmt': '!h'}, # noqa: E501
|
||||
'calc': {
|
||||
1: {'reg': Register.BATT_PV_PWR, 'func': RegisterFunc.prod_sum, # noqa: E501
|
||||
'params': [[Register.BATT_PV1_VOLT, Register.BATT_PV1_CUR],
|
||||
[Register.BATT_PV2_VOLT, Register.BATT_PV2_CUR]]},
|
||||
2: {'reg': Register.BATT_PWR, 'func': RegisterFunc.prod_sum, # noqa: E501
|
||||
'params': [[Register.BATT_VOLT, Register.BATT_CUR]]},
|
||||
3: {'reg': Register.BATT_OUT_PWR, 'func': RegisterFunc.prod_sum, # noqa: E501
|
||||
'params': [[Register.BATT_OUT_VOLT, Register.BATT_OUT_CUR]]},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RegisterSel:
|
||||
__sensor_map = {
|
||||
0x02b0: RegisterMap.map_02b0,
|
||||
0x3026: RegisterMap.map_3026,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get(cls, sensor: int):
|
||||
return cls.__sensor_map.get(sensor, RegisterMap.map)
|
||||
|
||||
|
||||
class InfosG3P(Infos):
|
||||
@@ -144,7 +224,22 @@ class InfosG3P(Infos):
|
||||
entity strings
|
||||
sug_area:str ==> suggested area string from the config file'''
|
||||
# iterate over RegisterMap.map and get the register values
|
||||
for row in RegisterMap.map.values():
|
||||
sensor = self.get_db_value(Register.SENSOR_LIST)
|
||||
if "3026" == sensor:
|
||||
reg_map = RegisterMap.map_3026
|
||||
elif "02b0" == sensor:
|
||||
reg_map = RegisterMap.map_02b0
|
||||
else:
|
||||
reg_map = {}
|
||||
items = reg_map.items()
|
||||
if 'calc' in reg_map:
|
||||
virt = reg_map['calc'].items()
|
||||
else:
|
||||
virt = {}
|
||||
|
||||
for idx, row in chain(RegisterMap.map.items(), items, virt):
|
||||
if 'calc' == idx:
|
||||
continue
|
||||
info_id = row['reg']
|
||||
if self.__hide_topic(row):
|
||||
res = self.ha_remove(info_id, node_id, snr) # noqa: E501
|
||||
@@ -153,13 +248,17 @@ class InfosG3P(Infos):
|
||||
if res:
|
||||
yield res
|
||||
|
||||
def parse(self, buf, msg_type: int, rcv_ftype: int, node_id: str = '') \
|
||||
def parse(self, buf, msg_type: int, rcv_ftype: int,
|
||||
sensor: int = 0, node_id: str = '') \
|
||||
-> Generator[tuple[str, bool], None, None]:
|
||||
'''parse a data sequence received from the inverter and
|
||||
stores the values in Infos.db
|
||||
|
||||
buf: buffer of the sequence to parse'''
|
||||
for idx, row in RegisterMap.map.items():
|
||||
reg_map = RegisterSel.get(sensor)
|
||||
for idx, row in reg_map.items():
|
||||
if 'calc' == idx:
|
||||
continue
|
||||
addr = idx & 0xffff
|
||||
ftype = (idx >> 16) & 0xff
|
||||
mtype = (idx >> 24) & 0xff
|
||||
@@ -169,23 +268,36 @@ class InfosG3P(Infos):
|
||||
continue
|
||||
info_id = row['reg']
|
||||
result = Fmt.get_value(buf, addr, row)
|
||||
yield from self.__update_val(node_id, "GEN3PLUS", info_id, result)
|
||||
yield from self.calc(sensor, node_id)
|
||||
|
||||
keys, level, unit, must_incr = self._key_obj(info_id)
|
||||
def calc(self, sensor: int = 0, node_id: str = '') \
|
||||
-> Generator[tuple[str, bool], None, None]:
|
||||
'''calculate meta values from the
|
||||
stored values in Infos.db
|
||||
|
||||
if keys:
|
||||
name, update = self.update_db(keys, must_incr, result)
|
||||
yield keys[0], update
|
||||
else:
|
||||
name = str(f'info-id.0x{addr:x}')
|
||||
update = False
|
||||
sensor: sensor_list number
|
||||
node_id: id-string for the node'''
|
||||
|
||||
reg_map = RegisterSel.get(sensor)
|
||||
if 'calc' in reg_map:
|
||||
for row in reg_map['calc'].values():
|
||||
info_id = row['reg']
|
||||
result = row['func'](self, row['params'])
|
||||
yield from self.__update_val(node_id, "CALC", info_id, result)
|
||||
|
||||
def __update_val(self, node_id, source: str, info_id, result):
|
||||
keys, level, unit, must_incr = self._key_obj(info_id)
|
||||
if keys:
|
||||
name, update = self.update_db(keys, must_incr, result)
|
||||
yield keys[0], update
|
||||
if update:
|
||||
self.tracer.log(level, f'[{node_id}] GEN3PLUS: {name}'
|
||||
self.tracer.log(level, f'[{node_id}] {source}: {name}'
|
||||
f' : {result}{unit}')
|
||||
|
||||
def build(self, len, msg_type: int, rcv_ftype: int):
|
||||
def build(self, len, msg_type: int, rcv_ftype: int, sensor: int = 0):
|
||||
buf = bytearray(len)
|
||||
for idx, row in RegisterMap.map.items():
|
||||
for idx, row in RegisterSel.get(sensor).items():
|
||||
addr = idx & 0xffff
|
||||
ftype = (idx >> 16) & 0xff
|
||||
mtype = (idx >> 24) & 0xff
|
||||
|
||||
@@ -103,7 +103,7 @@ class SolarmanEmu(SolarmanBase):
|
||||
self.data_timer.start(self.data_up_inv)
|
||||
_len = 420
|
||||
ftype = 1
|
||||
build_msg = self.db.build(_len, 0x42, ftype)
|
||||
build_msg = self.db.build(_len, 0x42, ftype, 0x02b0)
|
||||
|
||||
self._build_header(0x4210)
|
||||
self.ifc.tx_add(
|
||||
|
||||
@@ -2,8 +2,10 @@ import struct
|
||||
import logging
|
||||
import time
|
||||
import asyncio
|
||||
from itertools import chain
|
||||
from datetime import datetime
|
||||
|
||||
from proxy import Proxy
|
||||
from async_ifc import AsyncIfc
|
||||
from messages import hex_dump_memory, Message, State
|
||||
from cnf.config import Config
|
||||
@@ -245,6 +247,7 @@ class SolarmanBase(Message):
|
||||
class SolarmanV5(SolarmanBase):
|
||||
AT_CMD = 1
|
||||
MB_RTU_CMD = 2
|
||||
AT_CMD_RSP = 8
|
||||
MB_CLIENT_DATA_UP = 30
|
||||
'''Data up time in client mode'''
|
||||
HDR_FMT = '<BLLL'
|
||||
@@ -290,7 +293,7 @@ class SolarmanV5(SolarmanBase):
|
||||
# MODbus or AT cmd
|
||||
0x4510: self.msg_command_req, # from server
|
||||
0x1510: self.msg_command_rsp, # from inverter
|
||||
# 0x0510: self.msg_command_rsp, # from inverter
|
||||
0x0510: self.msg_command_rsp, # from inverter
|
||||
}
|
||||
|
||||
self.log_lvl = {
|
||||
@@ -321,6 +324,8 @@ class SolarmanV5(SolarmanBase):
|
||||
self.at_acl = g3p_cnf['at_acl']
|
||||
|
||||
self.sensor_list = 0
|
||||
self.mb_regs = [{'addr': 0x3000, 'len': 48},
|
||||
{'addr': 0x2000, 'len': 96}]
|
||||
|
||||
'''
|
||||
Our puplic methods
|
||||
@@ -356,7 +361,16 @@ class SolarmanV5(SolarmanBase):
|
||||
self.new_data['controller'] = True
|
||||
|
||||
self.state = State.up
|
||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x3000, 48, logging.DEBUG)
|
||||
|
||||
if self.mb_scan:
|
||||
self._send_modbus_cmd(self.mb_inv_no, Modbus.READ_REGS,
|
||||
self.mb_start_reg, self.mb_bytes,
|
||||
logging.INFO)
|
||||
else:
|
||||
self._send_modbus_cmd(Modbus.INV_ADDR, Modbus.READ_REGS,
|
||||
self.mb_regs[0]['addr'],
|
||||
self.mb_regs[0]['len'], logging.DEBUG)
|
||||
|
||||
self.mb_timer.start(self.mb_timeout)
|
||||
|
||||
def new_state_up(self):
|
||||
@@ -376,14 +390,25 @@ class SolarmanV5(SolarmanBase):
|
||||
self.ifc.fwd_add(build_msg)
|
||||
self.ifc.fwd_add(struct.pack('<BB', 0, 0x15)) # crc & stop
|
||||
|
||||
def __set_config_parms(self, inv: dict):
|
||||
def _set_config_parms(self, inv: dict, serial_no: str = ""):
|
||||
'''init connection with params from the configuration'''
|
||||
self.node_id = inv['node_id']
|
||||
self.sug_area = inv['suggested_area']
|
||||
self.modbus_polling = inv['modbus_polling']
|
||||
super()._set_config_parms(inv)
|
||||
snr = serial_no[:3]
|
||||
if '410' == snr:
|
||||
self.db.set_db_def_value(Register.EQUIPMENT_MODEL,
|
||||
'TSOL-DC1000')
|
||||
|
||||
self.sensor_list = inv['sensor_list']
|
||||
if self.mb:
|
||||
self.mb.set_node_id(self.node_id)
|
||||
if 0 == self.sensor_list:
|
||||
if '410' == snr:
|
||||
self.sensor_list = 0x3026
|
||||
self.mb_regs = [{'addr': 0x0000, 'len': 45}]
|
||||
else:
|
||||
self.sensor_list = 0x02b0
|
||||
self.db.set_db_def_value(Register.SENSOR_LIST,
|
||||
f"{self.sensor_list:04x}")
|
||||
logging.debug(f"Use sensor-list: {self.sensor_list:#04x}"
|
||||
f" for '{serial_no}'")
|
||||
|
||||
def _set_serial_no(self, snr: int):
|
||||
'''check the serial number and configure the inverter connection'''
|
||||
@@ -392,13 +417,14 @@ class SolarmanV5(SolarmanBase):
|
||||
logger.debug(f'SerialNo: {serial_no}')
|
||||
else:
|
||||
inverters = Config.get('inverters')
|
||||
batteries = Config.get('batteries')
|
||||
# logger.debug(f'Inverters: {inverters}')
|
||||
|
||||
for key, inv in inverters.items():
|
||||
for key, inv in chain(inverters.items(), batteries.items()):
|
||||
# logger.debug(f'key: {key} -> {inv}')
|
||||
if (type(inv) is dict and 'monitor_sn' in inv
|
||||
and inv['monitor_sn'] == snr):
|
||||
self.__set_config_parms(inv)
|
||||
self._set_config_parms(inv, key)
|
||||
self.db.set_pv_module_details(inv)
|
||||
logger.debug(f'SerialNo {serial_no} allowed! area:{self.sug_area}') # noqa: E501
|
||||
|
||||
@@ -411,9 +437,11 @@ class SolarmanV5(SolarmanBase):
|
||||
if 'allow_all' not in inverters or not inverters['allow_all']:
|
||||
self.inc_counter('Unknown_SNR')
|
||||
self.unique_id = None
|
||||
logger.warning(f'ignore message from unknow inverter! (SerialNo: {serial_no})') # noqa: E501
|
||||
logging.error(f"Ignore message from unknow inverter with Monitoring-SN: {serial_no})!\n" # noqa: E501
|
||||
" !!Check the 'monitor_sn' setting in your configuration!!") # noqa: E501
|
||||
return
|
||||
logger.warning(f'SerialNo {serial_no} not known but accepted!')
|
||||
logging.warning(f"Monitoring-SN: {serial_no} not configured but accepted!" # noqa: E501
|
||||
" !!Check the 'monitor_sn' setting in your configuration!!") # noqa: E501
|
||||
|
||||
self.unique_id = serial_no
|
||||
|
||||
@@ -459,12 +487,18 @@ class SolarmanV5(SolarmanBase):
|
||||
|
||||
def mb_timout_cb(self, exp_cnt):
|
||||
self.mb_timer.start(self.mb_timeout)
|
||||
if self.mb_scan:
|
||||
self._send_modbus_scan()
|
||||
else:
|
||||
self._send_modbus_cmd(Modbus.INV_ADDR, Modbus.READ_REGS,
|
||||
self.mb_regs[0]['addr'],
|
||||
self.mb_regs[0]['len'], logging.INFO)
|
||||
|
||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x3000, 48, logging.DEBUG)
|
||||
|
||||
if 1 == (exp_cnt % 30):
|
||||
# logging.info("Regular Modbus Status request")
|
||||
self._send_modbus_cmd(Modbus.READ_REGS, 0x2000, 96, logging.DEBUG)
|
||||
if 1 == (exp_cnt % 30) and len(self.mb_regs) > 1:
|
||||
# logging.info("Regular Modbus Status request")
|
||||
self._send_modbus_cmd(Modbus.INV_ADDR, Modbus.READ_REGS,
|
||||
self.mb_regs[1]['addr'],
|
||||
self.mb_regs[1]['len'], logging.INFO)
|
||||
|
||||
def at_cmd_forbidden(self, cmd: str, connection: str) -> bool:
|
||||
return not cmd.startswith(tuple(self.at_acl[connection]['allow'])) or \
|
||||
@@ -482,7 +516,7 @@ class SolarmanV5(SolarmanBase):
|
||||
node_id = self.node_id
|
||||
key = 'at_resp'
|
||||
logger.info(f'{key}: {data_json}')
|
||||
await self.mqtt.publish(f'{self.entity_prfx}{node_id}{key}', data_json) # noqa: E501
|
||||
await Proxy.mqtt.publish(f'{Proxy.entity_prfx}{node_id}{key}', data_json) # noqa: E501
|
||||
return
|
||||
|
||||
self.forward_at_cmd_resp = False
|
||||
@@ -516,11 +550,11 @@ class SolarmanV5(SolarmanBase):
|
||||
logger.info(f'Model: {model}')
|
||||
self.db.set_db_def_value(Register.EQUIPMENT_MODEL, model)
|
||||
|
||||
def __process_data(self, ftype, ts):
|
||||
def __process_data(self, ftype, ts, sensor=0):
|
||||
inv_update = False
|
||||
msg_type = self.control >> 8
|
||||
for key, update in self.db.parse(self.ifc.rx_peek(), msg_type, ftype,
|
||||
self.node_id):
|
||||
for key, update in self.db.parse(self.ifc.rx_peek(), msg_type,
|
||||
ftype, sensor, self.node_id):
|
||||
if update:
|
||||
if key == 'inverter':
|
||||
inv_update = True
|
||||
@@ -581,7 +615,7 @@ class SolarmanV5(SolarmanBase):
|
||||
else:
|
||||
ts = None
|
||||
|
||||
self.__process_data(ftype, ts)
|
||||
self.__process_data(ftype, ts, sensor)
|
||||
self.__forward_msg()
|
||||
self.__send_ack_rsp(0x1210, ftype)
|
||||
self.new_state_up()
|
||||
@@ -626,7 +660,7 @@ class SolarmanV5(SolarmanBase):
|
||||
|
||||
def publish_mqtt(self, key, data): # pragma: no cover
|
||||
asyncio.ensure_future(
|
||||
self.mqtt.publish(key, data))
|
||||
Proxy.mqtt.publish(key, data))
|
||||
|
||||
def get_cmd_rsp_log_lvl(self) -> int:
|
||||
ftype = self.ifc.rx_peek()[self.header_len]
|
||||
@@ -644,29 +678,39 @@ class SolarmanV5(SolarmanBase):
|
||||
data = self.ifc.rx_peek()[self.header_len:
|
||||
self.header_len+self.data_len]
|
||||
ftype = data[0]
|
||||
if ftype == self.AT_CMD:
|
||||
if ftype == self.AT_CMD or \
|
||||
ftype == self.AT_CMD_RSP:
|
||||
if not self.forward_at_cmd_resp:
|
||||
data_json = data[14:].decode("utf-8")
|
||||
node_id = self.node_id
|
||||
key = 'at_resp'
|
||||
logger.info(f'{key}: {data_json}')
|
||||
self.publish_mqtt(f'{self.entity_prfx}{node_id}{key}', data_json) # noqa: E501
|
||||
self.publish_mqtt(f'{Proxy.entity_prfx}{node_id}{key}', data_json) # noqa: E501
|
||||
return
|
||||
elif ftype == self.MB_RTU_CMD:
|
||||
self.__modbus_command_rsp(data)
|
||||
return
|
||||
self.__forward_msg()
|
||||
|
||||
def __parse_modbus_rsp(self, data):
|
||||
def __parse_modbus_rsp(self, data, modbus_msg_len):
|
||||
inv_update = False
|
||||
self.modbus_elms = 0
|
||||
if (self.mb_scan):
|
||||
self._dump_modbus_scan(data, 14, modbus_msg_len)
|
||||
|
||||
ts = self._timestamp()
|
||||
for key, update, _ in self.mb.recv_resp(self.db, data[14:]):
|
||||
self.modbus_elms += 1
|
||||
if update:
|
||||
if key == 'inverter':
|
||||
inv_update = True
|
||||
self._set_mqtt_timestamp(key, self._timestamp())
|
||||
self._set_mqtt_timestamp(key, ts)
|
||||
self.new_data[key] = True
|
||||
for key, update in self.db.calc(self.sensor_list, self.node_id):
|
||||
if update:
|
||||
self._set_mqtt_timestamp(key, ts)
|
||||
self.new_data[key] = True
|
||||
|
||||
return inv_update
|
||||
|
||||
def __modbus_command_rsp(self, data):
|
||||
@@ -676,7 +720,7 @@ class SolarmanV5(SolarmanBase):
|
||||
# logger.debug(f'modbus_len:{modbus_msg_len} accepted:{valid}')
|
||||
if valid == 1 and modbus_msg_len > 4:
|
||||
# logger.info(f'first byte modbus:{data[14]}')
|
||||
inv_update = self.__parse_modbus_rsp(data)
|
||||
inv_update = self.__parse_modbus_rsp(data, modbus_msg_len)
|
||||
if inv_update:
|
||||
self.__build_model_name()
|
||||
|
||||
|
||||
211
app/src/infos.py
211
app/src/infos.py
@@ -121,6 +121,94 @@ class Register(Enum):
|
||||
TS_INPUT = 600
|
||||
TS_GRID = 601
|
||||
TS_TOTAL = 602
|
||||
BATT_PV1_VOLT = 1000
|
||||
BATT_PV1_CUR = 1001
|
||||
BATT_PV2_VOLT = 1002
|
||||
BATT_PV2_CUR = 1003
|
||||
BATT_38 = 1004
|
||||
BATT_TOTAL_GEN = 1005
|
||||
BATT_STATUS_1 = 1006
|
||||
BATT_STATUS_2 = 1007
|
||||
BATT_VOLT = 1010
|
||||
BATT_CUR = 1011
|
||||
BATT_SOC = 1012
|
||||
BATT_CELL1_VOLT = 1013
|
||||
BATT_CELL2_VOLT = 1014
|
||||
BATT_CELL3_VOLT = 1015
|
||||
BATT_CELL4_VOLT = 1016
|
||||
BATT_CELL5_VOLT = 1017
|
||||
BATT_CELL6_VOLT = 1018
|
||||
BATT_CELL7_VOLT = 1019
|
||||
BATT_CELL8_VOLT = 1020
|
||||
BATT_CELL9_VOLT = 1021
|
||||
BATT_CELL10_VOLT = 1022
|
||||
BATT_CELL11_VOLT = 1023
|
||||
BATT_CELL12_VOLT = 1024
|
||||
BATT_CELL13_VOLT = 1025
|
||||
BATT_CELL14_VOLT = 1026
|
||||
BATT_CELL15_VOLT = 1027
|
||||
BATT_CELL16_VOLT = 1028
|
||||
BATT_TEMP_1 = 1029
|
||||
BATT_TEMP_2 = 1030
|
||||
BATT_TEMP_3 = 1031
|
||||
BATT_OUT_VOLT = 1032
|
||||
BATT_OUT_CUR = 1033
|
||||
BATT_OUT_STATUS = 1034
|
||||
BATT_TEMP_4 = 1035
|
||||
BATT_74 = 1036
|
||||
BATT_76 = 1037
|
||||
BATT_78 = 1038
|
||||
BATT_PV_PWR = 1040
|
||||
BATT_PWR = 1041
|
||||
BATT_OUT_PWR = 1042
|
||||
|
||||
TEST_VAL_0 = 2000
|
||||
TEST_VAL_1 = 2001
|
||||
TEST_VAL_2 = 2002
|
||||
TEST_VAL_3 = 2003
|
||||
TEST_VAL_4 = 2004
|
||||
TEST_VAL_5 = 2005
|
||||
TEST_VAL_6 = 2006
|
||||
TEST_VAL_7 = 2007
|
||||
TEST_VAL_8 = 2008
|
||||
TEST_VAL_9 = 2009
|
||||
TEST_VAL_10 = 2010
|
||||
TEST_VAL_11 = 2011
|
||||
TEST_VAL_12 = 2012
|
||||
TEST_VAL_13 = 2013
|
||||
TEST_VAL_14 = 2014
|
||||
TEST_VAL_15 = 2015
|
||||
TEST_VAL_16 = 2016
|
||||
TEST_VAL_17 = 2017
|
||||
TEST_VAL_18 = 2018
|
||||
TEST_VAL_19 = 2019
|
||||
TEST_VAL_20 = 2020
|
||||
TEST_VAL_21 = 2021
|
||||
TEST_VAL_22 = 2022
|
||||
TEST_VAL_23 = 2023
|
||||
TEST_VAL_24 = 2024
|
||||
TEST_VAL_25 = 2025
|
||||
TEST_VAL_26 = 2026
|
||||
TEST_VAL_27 = 2027
|
||||
TEST_VAL_28 = 2028
|
||||
TEST_VAL_29 = 2029
|
||||
TEST_VAL_30 = 2030
|
||||
TEST_VAL_31 = 2031
|
||||
TEST_VAL_32 = 2032
|
||||
|
||||
TEST_IVAL_1 = 2041
|
||||
TEST_IVAL_2 = 2042
|
||||
TEST_IVAL_3 = 2043
|
||||
TEST_IVAL_4 = 2044
|
||||
TEST_IVAL_5 = 2045
|
||||
TEST_IVAL_6 = 2046
|
||||
TEST_IVAL_7 = 2047
|
||||
TEST_IVAL_8 = 2048
|
||||
TEST_IVAL_9 = 2049
|
||||
TEST_IVAL_10 = 2050
|
||||
TEST_IVAL_11 = 2051
|
||||
TEST_IVAL_12 = 2052
|
||||
|
||||
VALUE_1 = 9000
|
||||
TEST_REG1 = 10000
|
||||
TEST_REG2 = 10001
|
||||
@@ -131,7 +219,10 @@ class Fmt:
|
||||
def get_value(buf: bytes, idx: int, row: dict):
|
||||
'''Get a value from buf and interpret as in row defined'''
|
||||
fmt = row['fmt']
|
||||
res = struct.unpack_from(fmt, buf, idx)
|
||||
try:
|
||||
res = struct.unpack_from(fmt, buf, idx)
|
||||
except Exception:
|
||||
return None
|
||||
result = res[0]
|
||||
if isinstance(result, (bytearray, bytes)):
|
||||
result = result.decode().split('\x00')[0]
|
||||
@@ -230,6 +321,7 @@ class Infos:
|
||||
LIGHTNING = 'mdi:lightning-bolt'
|
||||
COUNTER = 'mdi:counter'
|
||||
GAUGE = 'mdi:gauge'
|
||||
POWER = 'mdi:power'
|
||||
SOLAR_POWER_VAR = 'mdi:solar-power-variant'
|
||||
SOLAR_POWER = 'mdi:solar-power'
|
||||
WIFI = 'mdi:wifi'
|
||||
@@ -266,6 +358,7 @@ class Infos:
|
||||
__info_devs = {
|
||||
'proxy': {'singleton': True, 'name': 'Proxy', 'mf': 'Stefan Allius'}, # noqa: E501
|
||||
'controller': {'via': 'proxy', 'name': 'Controller', 'mdl': Register.CHIP_MODEL, 'mf': Register.CHIP_TYPE, 'sw': Register.COLLECTOR_FW_VERSION, 'mac': Register.MAC_ADDR, 'sn': Register.COLLECTOR_SNR}, # noqa: E501
|
||||
|
||||
'inverter': {'via': 'controller', 'name': 'Micro Inverter', 'mdl': Register.EQUIPMENT_MODEL, 'mf': Register.MANUFACTURER, 'sw': Register.VERSION, 'sn': Register.SERIAL_NUMBER}, # noqa: E501
|
||||
'input_pv1': {'via': 'inverter', 'name': 'Module PV1', 'mdl': Register.PV1_MODEL, 'mf': Register.PV1_MANUFACTURER}, # noqa: E501
|
||||
'input_pv2': {'via': 'inverter', 'name': 'Module PV2', 'mdl': Register.PV2_MODEL, 'mf': Register.PV2_MANUFACTURER, 'dep': {'reg': Register.NO_INPUTS, 'gte': 2}}, # noqa: E501
|
||||
@@ -273,11 +366,18 @@ class Infos:
|
||||
'input_pv4': {'via': 'inverter', 'name': 'Module PV4', 'mdl': Register.PV4_MODEL, 'mf': Register.PV4_MANUFACTURER, 'dep': {'reg': Register.NO_INPUTS, 'gte': 4}}, # noqa: E501
|
||||
'input_pv5': {'via': 'inverter', 'name': 'Module PV5', 'mdl': Register.PV5_MODEL, 'mf': Register.PV5_MANUFACTURER, 'dep': {'reg': Register.NO_INPUTS, 'gte': 5}}, # noqa: E501
|
||||
'input_pv6': {'via': 'inverter', 'name': 'Module PV6', 'mdl': Register.PV6_MODEL, 'mf': Register.PV6_MANUFACTURER, 'dep': {'reg': Register.NO_INPUTS, 'gte': 6}}, # noqa: E501
|
||||
|
||||
'batterie': {'via': 'controller', 'name': 'Batterie', 'mdl': Register.EQUIPMENT_MODEL, 'mf': Register.MANUFACTURER, 'sw': Register.VERSION, 'sn': Register.SERIAL_NUMBER}, # noqa: E501
|
||||
'bat_inp_pv1': {'via': 'batterie', 'name': 'Module PV1', 'mdl': Register.PV1_MODEL, 'mf': Register.PV1_MANUFACTURER}, # noqa: E501
|
||||
'bat_inp_pv2': {'via': 'batterie', 'name': 'Module PV2', 'mdl': Register.PV2_MODEL, 'mf': Register.PV2_MANUFACTURER}, # noqa: E501
|
||||
}
|
||||
|
||||
__comm_type_val_tpl = "{%set com_types = ['n/a','Wi-Fi', 'G4', 'G5', 'GPRS'] %}{{com_types[value_json['Communication_Type']|int(0)]|default(value_json['Communication_Type'])}}" # noqa: E501
|
||||
__work_mode_val_tpl = "{%set mode = ['Normal-Mode', 'Aging-Mode', 'ATE-Mode', 'Shielding GFDI', 'DTU-Mode'] %}{{mode[value_json['Work_Mode']|int(0)]|default(value_json['Work_Mode'])}}" # noqa: E501
|
||||
__status_type_val_tpl = "{%set inv_status = ['Off-line', 'On-grid', 'Off-grid'] %}{{inv_status[value_json['Inverter_Status']|int(0)]|default(value_json['Inverter_Status'])}}" # noqa: E501
|
||||
__mppt1_status_type_val_tpl = "{%set mppt_status = ['Locked', 'On', 'Off'] %}{{mppt_status[value_json['Status_1']|int(0)]|default(value_json['Status_1'])}}" # noqa: E501
|
||||
__mppt2_status_type_val_tpl = "{%set mppt_status = ['Locked', 'On', 'Off'] %}{{mppt_status[value_json['Status_2']|int(0)]|default(value_json['Status_2'])}}" # noqa: E501
|
||||
__out_status_type_val_tpl = "{%set out_status = ['Off', 'On'] %}{{out_status[value_json['out']['Out_Status']|int(0)]|default(value_json['out']['Out_Status'])}}" # noqa: E501
|
||||
__rated_power_val_tpl = "{% if 'Rated_Power' in value_json and value_json['Rated_Power'] != None %}{{value_json['Rated_Power']|string() +' W'}}{% else %}{{ this.state }}{% endif %}" # noqa: E501
|
||||
__designed_power_val_tpl = '''
|
||||
{% if 'Max_Designed_Power' in value_json and
|
||||
@@ -428,7 +528,7 @@ class Infos:
|
||||
Register.NO_INPUTS: {'name': ['inverter', 'No_Inputs'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
|
||||
Register.MAX_DESIGNED_POWER: {'name': ['inverter', 'Max_Designed_Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'designed_power_', 'val_tpl': __designed_power_val_tpl, 'name': 'Max Designed Power', 'icon': LIGHTNING, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.RATED_POWER: {'name': ['inverter', 'Rated_Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'rated_power_', 'val_tpl': __rated_power_val_tpl, 'name': 'Rated Power', 'icon': LIGHTNING, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.WORK_MODE: {'name': ['inverter', 'Work_Mode'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'inverter', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'work_mode_', 'name': 'Work Mode', 'val_tpl': __work_mode_val_tpl, 'icon': 'mdi:power', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.WORK_MODE: {'name': ['inverter', 'Work_Mode'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'inverter', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'work_mode_', 'name': 'Work Mode', 'val_tpl': __work_mode_val_tpl, 'icon': POWER, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.INPUT_COEFFICIENT: {'name': ['inverter', 'Input_Coefficient'], 'level': logging.DEBUG, 'unit': '%', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'input_coef_', 'val_tpl': __input_coef_val_tpl, 'name': 'Input Coefficient', 'icon': LIGHTNING, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.OUTPUT_COEFFICIENT: {'name': ['inverter', 'Output_Coefficient'], 'level': logging.INFO, 'unit': '%', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'output_coef_', 'val_tpl': __output_coef_val_tpl, 'name': 'Output Coefficient', 'icon': LIGHTNING, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.PV1_MANUFACTURER: {'name': ['inverter', 'PV1_Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
|
||||
@@ -476,7 +576,7 @@ class Infos:
|
||||
Register.GRID_FREQUENCY: {'name': ['grid', 'Frequency'], 'level': logging.DEBUG, 'unit': 'Hz', 'ha': {'dev': 'inverter', 'dev_cla': 'frequency', 'stat_cla': 'measurement', 'id': 'out_freq_', 'fmt': FMT_FLOAT, 'name': 'Grid Frequency', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.OUTPUT_POWER: {'name': ['grid', 'Output_Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'out_power_', 'fmt': FMT_FLOAT, 'name': 'Power'}}, # noqa: E501
|
||||
Register.INVERTER_TEMP: {'name': ['env', 'Inverter_Temp'], 'level': logging.DEBUG, 'unit': '°C', 'ha': {'dev': 'inverter', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_', 'fmt': FMT_INT, 'name': 'Temperature'}}, # noqa: E501
|
||||
Register.INVERTER_STATUS: {'name': ['env', 'Inverter_Status'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_status_', 'name': 'Inverter Status', 'val_tpl': __status_type_val_tpl, 'icon': 'mdi:power'}}, # noqa: E501
|
||||
Register.INVERTER_STATUS: {'name': ['env', 'Inverter_Status'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_status_', 'name': 'Inverter Status', 'val_tpl': __status_type_val_tpl, 'icon': POWER}}, # noqa: E501
|
||||
Register.DETECT_STATUS_1: {'name': ['env', 'Detect_Status_1'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
|
||||
Register.DETECT_STATUS_2: {'name': ['env', 'Detect_Status_2'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
|
||||
|
||||
@@ -518,15 +618,15 @@ class Infos:
|
||||
Register.TOTAL_GENERATION: {'name': ['total', 'Total_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'inverter', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_', 'fmt': FMT_FLOAT, 'name': TOTAL_GEN, 'icon': SOLAR_POWER, 'must_incr': True}}, # noqa: E501
|
||||
|
||||
# controller:
|
||||
Register.SIGNAL_STRENGTH: {'name': ['controller', 'Signal_Strength'], 'level': logging.DEBUG, 'unit': '%', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'signal_', 'fmt': FMT_INT, 'name': 'Signal Strength', 'icon': WIFI}}, # noqa: E501
|
||||
Register.POWER_ON_TIME: {'name': ['controller', 'Power_On_Time'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id': 'power_on_time_', 'fmt': FMT_INT, 'name': 'Power on Time', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.COLLECT_INTERVAL: {'name': ['controller', 'Collect_Interval'], 'level': logging.DEBUG, 'unit': 'min', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_collect_intval_', 'fmt': '| string + " min"', 'name': 'Data Collect Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.CONNECT_COUNT: {'name': ['controller', 'Connect_Count'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'connect_count_', 'fmt': FMT_INT, 'name': 'Connect Count', 'icon': COUNTER, 'comp': 'sensor', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.COMMUNICATION_TYPE: {'name': ['controller', 'Communication_Type'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'comm_type_', 'name': 'Communication Type', 'val_tpl': __comm_type_val_tpl, 'comp': 'sensor', 'icon': WIFI}}, # noqa: E501
|
||||
Register.DATA_UP_INTERVAL: {'name': ['controller', 'Data_Up_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_up_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Data Up Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.HEARTBEAT_INTERVAL: {'name': ['controller', 'Heartbeat_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'heartbeat_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Heartbeat Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.IP_ADDRESS: {'name': ['controller', 'IP_Address'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'ip_address_', 'fmt': '| string', 'name': 'IP Address', 'icon': WIFI, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.POLLING_INTERVAL: {'name': ['controller', 'Polling_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'polling_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Polling Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.SIGNAL_STRENGTH: {'name': ['controller', 'Signal_Strength'], 'level': logging.INFO, 'unit': '%', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'signal_', 'fmt': FMT_INT, 'name': 'Signal Strength', 'icon': WIFI}}, # noqa: E501
|
||||
Register.POWER_ON_TIME: {'name': ['controller', 'Power_On_Time'], 'level': logging.INFO, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id': 'power_on_time_', 'fmt': FMT_INT, 'name': 'Power on Time', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.COLLECT_INTERVAL: {'name': ['controller', 'Collect_Interval'], 'level': logging.INFO, 'unit': 'min', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_collect_intval_', 'fmt': '| string + " min"', 'name': 'Data Collect Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.CONNECT_COUNT: {'name': ['controller', 'Connect_Count'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'connect_count_', 'fmt': FMT_INT, 'name': 'Connect Count', 'icon': COUNTER, 'comp': 'sensor', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.COMMUNICATION_TYPE: {'name': ['controller', 'Communication_Type'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'comm_type_', 'name': 'Communication Type', 'val_tpl': __comm_type_val_tpl, 'comp': 'sensor', 'icon': WIFI}}, # noqa: E501
|
||||
Register.DATA_UP_INTERVAL: {'name': ['controller', 'Data_Up_Interval'], 'level': logging.INFO, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'data_up_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Data Up Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.HEARTBEAT_INTERVAL: {'name': ['controller', 'Heartbeat_Interval'], 'level': logging.INFO, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'heartbeat_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Heartbeat Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.IP_ADDRESS: {'name': ['controller', 'IP_Address'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'ip_address_', 'fmt': '| string', 'name': 'IP Address', 'icon': WIFI, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.POLLING_INTERVAL: {'name': ['controller', 'Polling_Interval'], 'level': logging.INFO, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': None, 'id': 'polling_intval_', 'fmt': FMT_STRING_SEC, 'name': 'Polling Interval', 'icon': UPDATE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.SENSOR_LIST: {'name': ['controller', 'Sensor_List'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
|
||||
Register.SSID: {'name': ['controller', 'WiFi_SSID'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
|
||||
|
||||
@@ -536,6 +636,93 @@ class Infos:
|
||||
Register.PROD_COMPL_TYPE: {'name': ['other', 'Prod_Compliance_Type'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
|
||||
Register.INV_UNKNOWN_1: {'name': ['inv_unknown', 'Unknown_1'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
|
||||
|
||||
Register.BATT_PV1_VOLT: {'name': ['batterie', 'pv1', 'Voltage'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'bat_inp_pv1', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv1_', 'val_tpl': "{{ (value_json['pv1']['Voltage'] | float)}}", 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_PV1_CUR: {'name': ['batterie', 'pv1', 'Current'], 'level': logging.INFO, 'unit': 'A', 'ha': {'dev': 'bat_inp_pv1', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv1_', 'val_tpl': "{{ (value_json['pv1']['Current'] | float)}}", 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_PV2_VOLT: {'name': ['batterie', 'pv2', 'Voltage'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'bat_inp_pv2', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_pv2_', 'val_tpl': "{{ (value_json['pv2']['Voltage'] | float)}}", 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_PV2_CUR: {'name': ['batterie', 'pv2', 'Current'], 'level': logging.INFO, 'unit': 'A', 'ha': {'dev': 'bat_inp_pv2', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_pv2_', 'val_tpl': "{{ (value_json['pv2']['Current'] | float)}}", 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_38: {'name': ['batterie', 'Reg_38'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_38_', 'fmt': FMT_FLOAT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_TOTAL_GEN: {'name': ['batterie', 'Total_Generation'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'batterie', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_gen_', 'fmt': FMT_FLOAT, 'name': TOTAL_GEN, 'icon': SOLAR_POWER, 'must_incr': True}}, # noqa: E501
|
||||
Register.BATT_STATUS_1: {'name': ['batterie', 'Status_1'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'status1_', 'name': 'MPPT-1 Status', 'val_tpl': __mppt1_status_type_val_tpl, 'icon': POWER}}, # noqa: E501
|
||||
Register.BATT_STATUS_2: {'name': ['batterie', 'Status_2'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'status2_', 'name': 'MPPT-2 Status', 'val_tpl': __mppt2_status_type_val_tpl, 'icon': POWER}}, # noqa: E501
|
||||
Register.BATT_VOLT: {'name': ['batterie', 'Voltage'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_bat_', 'fmt': FMT_FLOAT, 'name': 'Batterie Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CUR: {'name': ['batterie', 'Current'], 'level': logging.INFO, 'unit': 'A', 'ha': {'dev': 'batterie', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_bat_', 'fmt': FMT_FLOAT, 'name': 'Batterie Current', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_SOC: {'name': ['batterie', 'SOC'], 'level': logging.INFO, 'unit': '%', 'ha': {'dev': 'batterie', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'soc_', 'fmt': FMT_FLOAT, 'name': 'State of Charge (SOC)', 'icon': 'mdi:battery-90'}}, # noqa: E501
|
||||
Register.BATT_CELL1_VOLT: {'name': ['batterie', 'Cell', 'Volt1'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell1_', 'val_tpl': "{{ (value_json['Cell']['Volt1'] | float)}}", 'name': 'Cell-01 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL3_VOLT: {'name': ['batterie', 'Cell', 'Volt3'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell3_', 'val_tpl': "{{ (value_json['Cell']['Volt2'] | float)}}", 'name': 'Cell-03 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL4_VOLT: {'name': ['batterie', 'Cell', 'Volt4'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell4_', 'val_tpl': "{{ (value_json['Cell']['Volt3'] | float)}}", 'name': 'Cell-04 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL2_VOLT: {'name': ['batterie', 'Cell', 'Volt2'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell2_', 'val_tpl': "{{ (value_json['Cell']['Volt4'] | float)}}", 'name': 'Cell-02 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL5_VOLT: {'name': ['batterie', 'Cell', 'Volt5'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell5_', 'val_tpl': "{{ (value_json['Cell']['Volt5'] | float)}}", 'name': 'Cell-05 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL6_VOLT: {'name': ['batterie', 'Cell', 'Volt6'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell6_', 'val_tpl': "{{ (value_json['Cell']['Volt6'] | float)}}", 'name': 'Cell-06 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL7_VOLT: {'name': ['batterie', 'Cell', 'Volt7'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell7_', 'val_tpl': "{{ (value_json['Cell']['Volt7'] | float)}}", 'name': 'Cell-07 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL8_VOLT: {'name': ['batterie', 'Cell', 'Volt8'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell8_', 'val_tpl': "{{ (value_json['Cell']['Volt8'] | float)}}", 'name': 'Cell-08 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL9_VOLT: {'name': ['batterie', 'Cell', 'Volt9'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell9_', 'val_tpl': "{{ (value_json['Cell']['Volt9'] | float)}}", 'name': 'Cell-09 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL10_VOLT: {'name': ['batterie', 'Cell', 'Volt10'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell10_', 'val_tpl': "{{ (value_json['Cell']['Volt10'] | float)}}", 'name': 'Cell-10 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL11_VOLT: {'name': ['batterie', 'Cell', 'Volt11'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell11_', 'val_tpl': "{{ (value_json['Cell']['Volt11'] | float)}}", 'name': 'Cell-11 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL12_VOLT: {'name': ['batterie', 'Cell', 'Volt12'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell12_', 'val_tpl': "{{ (value_json['Cell']['Volt12'] | float)}}", 'name': 'Cell-12 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL13_VOLT: {'name': ['batterie', 'Cell', 'Volt13'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell13_', 'val_tpl': "{{ (value_json['Cell']['Volt13'] | float)}}", 'name': 'Cell-13 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL14_VOLT: {'name': ['batterie', 'Cell', 'Volt14'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell14_', 'val_tpl': "{{ (value_json['Cell']['Volt14'] | float)}}", 'name': 'Cell-14 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL15_VOLT: {'name': ['batterie', 'Cell', 'Volt15'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell15_', 'val_tpl': "{{ (value_json['Cell']['Volt15'] | float)}}", 'name': 'Cell-15 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_CELL16_VOLT: {'name': ['batterie', 'Cell', 'Volt16'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_cell16_', 'val_tpl': "{{ (value_json['Cell']['Volt16'] | float)}}", 'name': 'Cell-16 Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_TEMP_1: {'name': ['batterie', 'Temp_1'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_1_', 'fmt': FMT_INT, 'name': 'Batterie Temp-1', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_TEMP_2: {'name': ['batterie', 'Temp_2'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_2_', 'fmt': FMT_INT, 'name': 'Batterie Temp-2', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_TEMP_3: {'name': ['batterie', 'Temp_3'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_3_', 'fmt': FMT_INT, 'name': 'Batterie Temp-3', 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_OUT_VOLT: {'name': ['batterie', 'out', 'Voltage'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'out_volt_', 'val_tpl': "{{ (value_json['out']['Voltage'] | float)}}", 'name': 'Output Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_OUT_CUR: {'name': ['batterie', 'out', 'Current'], 'level': logging.INFO, 'unit': 'A', 'ha': {'dev': 'batterie', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'out_cur_', 'val_tpl': "{{ (value_json['out']['Current'] | float)}}", 'name': 'Output Current', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_OUT_STATUS: {'name': ['batterie', 'out', 'Out_Status'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'out_status_', 'name': 'Output Status', 'val_tpl': __out_status_type_val_tpl, 'icon': POWER}}, # noqa: E501
|
||||
Register.BATT_TEMP_4: {'name': ['batterie', 'Controller_Temp'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_4_', 'fmt': FMT_INT, 'name': 'Ctrl Temperature'}}, # noqa: E501
|
||||
Register.BATT_74: {'name': ['batterie', 'Reg_74'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_74_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_76: {'name': ['batterie', 'Reg_76'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_76_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_78: {'name': ['batterie', 'Reg_78'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'batt_78_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.BATT_PV_PWR: {'name': ['batterie', 'PV_Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'pv_power_', 'fmt': FMT_INT, 'name': 'PV Power'}}, # noqa: E501
|
||||
Register.BATT_PWR: {'name': ['batterie', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_', 'fmt': FMT_INT, 'name': 'Batterie Power'}}, # noqa: E501
|
||||
Register.BATT_OUT_PWR: {'name': ['batterie', 'out', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'out_power_', 'val_tpl': "{{ (value_json['out']['Power'] | int)}}", 'name': 'Output Power'}}, # noqa: E501
|
||||
|
||||
Register.TEST_VAL_0: {'name': ['input', 'Val_0'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_0_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_1: {'name': ['input', 'Val_1'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_1_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_2: {'name': ['input', 'Val_2'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_2_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_3: {'name': ['input', 'Val_3'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_3_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_4: {'name': ['input', 'Val_4'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_4_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_5: {'name': ['input', 'Val_5'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_5_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_6: {'name': ['input', 'Val_6'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_6_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_7: {'name': ['input', 'Val_7'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_7_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_8: {'name': ['input', 'Val_8'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_8_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_9: {'name': ['input', 'Val_9'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_9_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_10: {'name': ['input', 'Val_10'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_10_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_11: {'name': ['input', 'Val_11'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_11_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_12: {'name': ['input', 'Val_12'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_12_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_13: {'name': ['input', 'Val_13'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_13_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_14: {'name': ['input', 'Val_14'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_14_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_15: {'name': ['input', 'Val_15'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_15_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_16: {'name': ['input', 'Val_16'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_16_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_17: {'name': ['input', 'Val_17'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_17_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_18: {'name': ['input', 'Val_18'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_18_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_19: {'name': ['input', 'Val_19'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_19_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_20: {'name': ['input', 'Val_20'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_20_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_21: {'name': ['input', 'Val_21'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_21_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_22: {'name': ['input', 'Val_22'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_22_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_23: {'name': ['input', 'Val_23'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_23_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_24: {'name': ['input', 'Val_24'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_24_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_25: {'name': ['input', 'Val_25'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_25_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_26: {'name': ['input', 'Val_26'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_26_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_27: {'name': ['input', 'Val_27'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_27_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_28: {'name': ['input', 'Val_28'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_28_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_29: {'name': ['input', 'Val_29'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_29_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_30: {'name': ['input', 'Val_30'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_30_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_31: {'name': ['input', 'Val_31'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_31_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_VAL_32: {'name': ['input', 'Val_32'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'val_32_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
|
||||
Register.TEST_IVAL_1: {'name': ['input', 'iVal_1'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_1_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_2: {'name': ['input', 'iVal_2'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_2_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_3: {'name': ['input', 'iVal_3'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_3_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_4: {'name': ['input', 'iVal_4'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_4_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_5: {'name': ['input', 'iVal_5'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_5_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_6: {'name': ['input', 'iVal_6'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_6_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_7: {'name': ['input', 'iVal_7'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_7_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_8: {'name': ['input', 'iVal_8'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_8_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_9: {'name': ['input', 'iVal_9'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_9_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_10: {'name': ['input', 'iVal_10'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_10_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_11: {'name': ['input', 'iVal_11'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_11_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
Register.TEST_IVAL_12: {'name': ['input', 'iVal_12'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'ival_12_', 'fmt': FMT_INT, 'ent_cat': 'diagnostic'}}, # noqa: E501
|
||||
}
|
||||
|
||||
@property
|
||||
|
||||
@@ -151,6 +151,8 @@ class InverterBase(InverterIfc, Proxy):
|
||||
# home assistant has changed the status back to online
|
||||
try:
|
||||
if (('inverter' in stream.new_data and stream.new_data['inverter'])
|
||||
or ('batterie' in stream.new_data and
|
||||
stream.new_data['batterie'])
|
||||
or ('collector' in stream.new_data and
|
||||
stream.new_data['collector'])
|
||||
or self.mqtt.ha_restarts != self.__ha_restarts):
|
||||
|
||||
@@ -67,10 +67,10 @@ formatter=file_formatter
|
||||
args=(handlers.log_path + 'trace.log', when:='midnight', backupCount:=handlers.log_backups)
|
||||
|
||||
[formatter_console_formatter]
|
||||
format=%(asctime)s %(levelname)5s | %(name)4s | %(message)s'
|
||||
datefmt='%Y-%m-%d %H:%M:%S
|
||||
format=%(asctime)s %(levelname)5s | %(name)4s | %(message)s
|
||||
datefmt=%Y-%m-%d %H:%M:%S
|
||||
|
||||
[formatter_file_formatter]
|
||||
format=%(asctime)s %(levelname)5s | %(name)4s | %(message)s'
|
||||
datefmt='%Y-%m-%d %H:%M:%S
|
||||
format=%(asctime)s %(levelname)5s | %(name)4s | %(message)s
|
||||
datefmt=%Y-%m-%d %H:%M:%S
|
||||
|
||||
|
||||
@@ -117,6 +117,11 @@ class Message(ProtocolIfc):
|
||||
self.mb_first_timeout = self.MB_START_TIMEOUT
|
||||
'''timer value for next Modbus polling request'''
|
||||
self.modbus_polling = False
|
||||
self.mb_start_reg = 0
|
||||
self.mb_step = 0
|
||||
self.mb_bytes = 0
|
||||
self.mb_inv_no = 1
|
||||
self.mb_scan = False
|
||||
|
||||
@property
|
||||
def node_id(self):
|
||||
@@ -135,6 +140,25 @@ class Message(ProtocolIfc):
|
||||
# to our _recv_buffer
|
||||
return # pragma: no cover
|
||||
|
||||
def _set_config_parms(self, inv: dict):
|
||||
'''init connection with params from the configuration'''
|
||||
self.node_id = inv['node_id']
|
||||
self.sug_area = inv['suggested_area']
|
||||
self.modbus_polling = inv['modbus_polling']
|
||||
if 'modbus_scanning' in inv:
|
||||
scan = inv['modbus_scanning']
|
||||
self.mb_scan = True
|
||||
self.mb_start_reg = scan['start']
|
||||
self.mb_step = scan['step']
|
||||
self.mb_bytes = scan['bytes']
|
||||
if 'client_mode' in inv:
|
||||
self.mb_start_reg = scan['start']
|
||||
else:
|
||||
self.mb_start_reg = scan['start'] - scan['step']
|
||||
self.mb_start_reg &= 0xffff
|
||||
if self.mb:
|
||||
self.mb.set_node_id(self.node_id)
|
||||
|
||||
def _set_mqtt_timestamp(self, key, ts: float | None):
|
||||
if key not in self.new_data or \
|
||||
not self.new_data[key]:
|
||||
@@ -160,15 +184,39 @@ class Message(ProtocolIfc):
|
||||
to = self.MAX_DEF_IDLE_TIME
|
||||
return to
|
||||
|
||||
def _send_modbus_cmd(self, func, addr, val, log_lvl) -> None:
|
||||
def _send_modbus_cmd(self, dev_id, func, addr, val, log_lvl) -> None:
|
||||
if self.state != State.up:
|
||||
logger.log(log_lvl, f'[{self.node_id}] ignore MODBUS cmd,'
|
||||
' as the state is not UP')
|
||||
return
|
||||
self.mb.build_msg(Modbus.INV_ADDR, func, addr, val, log_lvl)
|
||||
self.mb.build_msg(dev_id, func, addr, val, log_lvl)
|
||||
|
||||
async def send_modbus_cmd(self, func, addr, val, log_lvl) -> None:
|
||||
self._send_modbus_cmd(func, addr, val, log_lvl)
|
||||
self._send_modbus_cmd(Modbus.INV_ADDR, func, addr, val, log_lvl)
|
||||
|
||||
def _send_modbus_scan(self):
|
||||
self.mb_start_reg += self.mb_step
|
||||
if self.mb_start_reg > 0xffff:
|
||||
self.mb_start_reg = self.mb_start_reg & 0xffff
|
||||
self.mb_inv_no += 1
|
||||
logging.info(f"Next Round: inv:{self.mb_inv_no}"
|
||||
f" reg:{self.mb_start_reg:04x}")
|
||||
if (self.mb_start_reg & 0xfffc) % 0x80 == 0:
|
||||
logging.info(f"[{self.node_id}] Scan info: "
|
||||
f"inv:{self.mb_inv_no}"
|
||||
f" reg:{self.mb_start_reg:04x}")
|
||||
self._send_modbus_cmd(self.mb_inv_no, Modbus.READ_REGS,
|
||||
self.mb_start_reg, self.mb_bytes,
|
||||
logging.INFO)
|
||||
|
||||
def _dump_modbus_scan(self, data, hdr_len, modbus_msg_len):
|
||||
if (data[hdr_len] == self.mb_inv_no and
|
||||
data[hdr_len+1] == Modbus.READ_REGS):
|
||||
logging.info(f'[{self.node_id}] Valid MODBUS data '
|
||||
f'(reg: 0x{self.mb.last_reg:04x}):')
|
||||
hex_dump_memory(logging.INFO, 'Valid MODBUS data '
|
||||
f'(reg: 0x{self.mb.last_reg:04x}):',
|
||||
data[hdr_len:], modbus_msg_len)
|
||||
|
||||
'''
|
||||
Our puplic methods
|
||||
|
||||
@@ -37,6 +37,45 @@ class Modbus():
|
||||
|
||||
__crc_tab = []
|
||||
mb_reg_mapping = {
|
||||
0x0000: {'reg': Register.SERIAL_NUMBER, 'fmt': '!16s'}, # noqa: E501
|
||||
0x0008: {'reg': Register.BATT_PV1_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV1 voltage
|
||||
0x0009: {'reg': Register.BATT_PV1_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV1 current
|
||||
0x000a: {'reg': Register.BATT_PV2_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV2 voltage
|
||||
0x000b: {'reg': Register.BATT_PV2_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV2 current
|
||||
0x000c: {'reg': Register.BATT_38, 'fmt': '!h'}, # noqa: E501
|
||||
0x000d: {'reg': Register.BATT_TOTAL_GEN, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x000e: {'reg': Register.BATT_STATUS_1, 'fmt': '!h'}, # noqa: E501
|
||||
0x000f: {'reg': Register.BATT_STATUS_2, 'fmt': '!h'}, # noqa: E501
|
||||
0x0010: {'reg': Register.BATT_VOLT, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x0011: {'reg': Register.BATT_CUR, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x0012: {'reg': Register.BATT_SOC, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, state of charge (SOC) in percent
|
||||
0x0013: {'reg': Register.BATT_CELL1_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0014: {'reg': Register.BATT_CELL2_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0015: {'reg': Register.BATT_CELL3_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0016: {'reg': Register.BATT_CELL4_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0017: {'reg': Register.BATT_CELL5_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0018: {'reg': Register.BATT_CELL6_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0019: {'reg': Register.BATT_CELL7_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x001a: {'reg': Register.BATT_CELL8_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x001b: {'reg': Register.BATT_CELL9_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x001c: {'reg': Register.BATT_CELL10_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x001d: {'reg': Register.BATT_CELL11_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x001e: {'reg': Register.BATT_CELL12_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x001f: {'reg': Register.BATT_CELL13_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0020: {'reg': Register.BATT_CELL14_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0021: {'reg': Register.BATT_CELL15_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0022: {'reg': Register.BATT_CELL16_VOLT, 'fmt': '!h', 'ratio': 0.001}, # noqa: E501
|
||||
0x0023: {'reg': Register.BATT_TEMP_1, 'fmt': '!h'}, # noqa: E501
|
||||
0x0024: {'reg': Register.BATT_TEMP_2, 'fmt': '!h'}, # noqa: E501
|
||||
0x0025: {'reg': Register.BATT_TEMP_3, 'fmt': '!h'}, # noqa: E501
|
||||
0x0026: {'reg': Register.BATT_OUT_VOLT, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x0027: {'reg': Register.BATT_OUT_CUR, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
|
||||
0x0028: {'reg': Register.BATT_OUT_STATUS, 'fmt': '!h'}, # noqa: E501
|
||||
0x0029: {'reg': Register.BATT_TEMP_4, 'fmt': '!h'}, # noqa: E501
|
||||
0x002a: {'reg': Register.BATT_74, 'fmt': '!h'}, # noqa: E501
|
||||
0x002b: {'reg': Register.BATT_76, 'fmt': '!h'}, # noqa: E501
|
||||
0x002c: {'reg': Register.BATT_78, 'fmt': '!h'}, # noqa: E501
|
||||
|
||||
0x2000: {'reg': Register.BOOT_STATUS, 'fmt': '!H'}, # noqa: E501
|
||||
0x2001: {'reg': Register.DSP_STATUS, 'fmt': '!H'}, # noqa: E501
|
||||
0x2003: {'reg': Register.WORK_MODE, 'fmt': '!H'},
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import traceback
|
||||
import asyncio
|
||||
from itertools import chain
|
||||
|
||||
from cnf.config import Config
|
||||
from gen3plus.inverter_g3p import InverterG3P
|
||||
@@ -42,14 +43,15 @@ class ModbusTcp():
|
||||
self.tim_restart = tim_restart
|
||||
|
||||
inverters = Config.get('inverters')
|
||||
batteries = Config.get('batteries')
|
||||
# logging.info(f'Inverters: {inverters}')
|
||||
|
||||
for inv in inverters.values():
|
||||
for _, inv in chain(inverters.items(), batteries.items()):
|
||||
if (type(inv) is dict
|
||||
and 'monitor_sn' in inv
|
||||
and 'client_mode' in inv):
|
||||
client = inv['client_mode']
|
||||
logger.info(f"'client_mode' for snr: {inv['monitor_sn']} host: {client['host']}:{client['port']}, forward: {client['forward']}") # noqa: E501
|
||||
logger.info(f"'client_mode' for Monitoring-SN: {inv['monitor_sn']} host: {client['host']}:{client['port']}, forward: {client['forward']}") # noqa: E501
|
||||
loop.create_task(self.modbus_loop(client['host'],
|
||||
client['port'],
|
||||
inv['monitor_sn'],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import json
|
||||
from itertools import chain
|
||||
|
||||
from cnf.config import Config
|
||||
from mqtt import Mqtt
|
||||
@@ -56,8 +57,9 @@ class Proxy():
|
||||
# reset at midnight when you restart the proxy just before
|
||||
# midnight!
|
||||
inverters = Config.get('inverters')
|
||||
batteries = Config.get('batteries')
|
||||
# logger.debug(f'Proxys: {inverters}')
|
||||
for inv in inverters.values():
|
||||
for _, inv in chain(inverters.items(), batteries.items()):
|
||||
if (type(inv) is dict):
|
||||
node_id = inv['node_id']
|
||||
cls.db_stat.reg_clr_at_midnight(f'{cls.entity_prfx}{node_id}',
|
||||
|
||||
@@ -117,19 +117,19 @@ async def handle_shutdown(loop, web_task):
|
||||
loop.stop()
|
||||
|
||||
|
||||
def get_log_level() -> int:
|
||||
def get_log_level() -> int | None:
|
||||
'''checks if LOG_LVL is set in the environment and returns the
|
||||
corresponding logging.LOG_LEVEL'''
|
||||
log_level = os.getenv('LOG_LVL', 'INFO')
|
||||
switch = {
|
||||
'DEBUG': logging.DEBUG,
|
||||
'WARN': logging.WARNING,
|
||||
'INFO': logging.INFO,
|
||||
'ERROR': logging.ERROR,
|
||||
}
|
||||
log_level = os.getenv('LOG_LVL', None)
|
||||
logging.info(f"LOG_LVL : {log_level}")
|
||||
|
||||
if log_level == 'DEBUG':
|
||||
log_level = logging.DEBUG
|
||||
elif log_level == 'WARN':
|
||||
log_level = logging.WARNING
|
||||
else:
|
||||
log_level = logging.INFO
|
||||
return log_level
|
||||
return switch.get(log_level, None)
|
||||
|
||||
|
||||
def main(): # pragma: no cover
|
||||
@@ -156,8 +156,10 @@ def main(): # pragma: no cover
|
||||
|
||||
setattr(logging.handlers, "log_path", args.log_path)
|
||||
setattr(logging.handlers, "log_backups", args.log_backups)
|
||||
os.makedirs(args.log_path, exist_ok=True)
|
||||
|
||||
logging.config.fileConfig('logging.ini')
|
||||
src_dir = os.path.dirname(__file__) + '/'
|
||||
logging.config.fileConfig(src_dir + 'logging.ini')
|
||||
logging.info(f'Server "{serv_name} - {version}" will be started')
|
||||
logging.info(f'current dir: {os.getcwd()}')
|
||||
logging.info(f"config_path: {args.config_path}")
|
||||
@@ -170,21 +172,21 @@ def main(): # pragma: no cover
|
||||
logging.info(f"log_backups: {args.log_backups} days")
|
||||
log_level = get_log_level()
|
||||
logging.info('******')
|
||||
|
||||
# set lowest-severity for 'root', 'msg', 'conn' and 'data' logger
|
||||
logging.getLogger().setLevel(log_level)
|
||||
logging.getLogger('msg').setLevel(log_level)
|
||||
logging.getLogger('conn').setLevel(log_level)
|
||||
logging.getLogger('data').setLevel(log_level)
|
||||
logging.getLogger('tracer').setLevel(log_level)
|
||||
logging.getLogger('asyncio').setLevel(log_level)
|
||||
# logging.getLogger('mqtt').setLevel(log_level)
|
||||
if log_level:
|
||||
# set lowest-severity for 'root', 'msg', 'conn' and 'data' logger
|
||||
logging.getLogger().setLevel(log_level)
|
||||
logging.getLogger('msg').setLevel(log_level)
|
||||
logging.getLogger('conn').setLevel(log_level)
|
||||
logging.getLogger('data').setLevel(log_level)
|
||||
logging.getLogger('tracer').setLevel(log_level)
|
||||
logging.getLogger('asyncio').setLevel(log_level)
|
||||
# logging.getLogger('mqtt').setLevel(log_level)
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
# read config file
|
||||
Config.init(ConfigReadToml("default_config.toml"))
|
||||
Config.init(ConfigReadToml(src_dir + "cnf/default_config.toml"))
|
||||
ConfigReadEnv()
|
||||
ConfigReadJson(args.config_path + "config.json")
|
||||
ConfigReadToml(args.config_path + "config.toml")
|
||||
|
||||
@@ -76,7 +76,7 @@ def ConfigDefault():
|
||||
'type': 'RSM40-8-395M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-395M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
@@ -91,8 +91,21 @@ def ConfigDefault():
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
}
|
||||
},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'suggested_area': '',
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,14 +152,49 @@ def ConfigComplete():
|
||||
'pv4': {'manufacturer': 'man4',
|
||||
'type': 'type4'},
|
||||
'suggested_area': 'Garage2',
|
||||
'sensor_list': 688},
|
||||
'Y170000000000002': {'modbus_polling': False,
|
||||
'modbus_scanning': {
|
||||
'bytes': 16,
|
||||
'start': 2048,
|
||||
'step': 1024
|
||||
},
|
||||
'monitor_sn': 2000000001,
|
||||
'node_id': 'PV-Garage3/',
|
||||
'suggested_area': 'Garage3',
|
||||
'sensor_list': 688}
|
||||
},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'suggested_area': 'Garage3',
|
||||
'node_id': 'Bat-Garage3/',
|
||||
'pv1': {'manufacturer': 'man5',
|
||||
'type': 'type5'},
|
||||
'pv2': {'manufacturer': 'man6',
|
||||
'type': 'type6'},
|
||||
'sensor_list': 12326}
|
||||
}
|
||||
}
|
||||
|
||||
def test_default_config():
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
validated = Config.def_config
|
||||
assert validated == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 10000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
'suggested_area': ''
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': False,
|
||||
'R170000000000001': {
|
||||
@@ -158,7 +206,7 @@ def test_default_config():
|
||||
'modbus_polling': False,
|
||||
'monitor_sn': 0,
|
||||
'suggested_area': '',
|
||||
'sensor_list': 688},
|
||||
'sensor_list': 0},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 2000000000,
|
||||
@@ -172,7 +220,7 @@ def test_default_config():
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'suggested_area': '',
|
||||
'sensor_list': 688}}}
|
||||
'sensor_list': 0}}}
|
||||
|
||||
def test_full_config(ConfigComplete):
|
||||
cnf = {'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005},
|
||||
@@ -181,9 +229,15 @@ def test_full_config(ConfigComplete):
|
||||
'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 10000},
|
||||
'mqtt': {'host': 'mqtt', 'port': 1883, 'user': '', 'passwd': ''},
|
||||
'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {'modbus_polling': True, 'monitor_sn': 3000000000, 'node_id': 'Bat-Garage3/', 'sensor_list': 0x3026, 'suggested_area': 'Garage3', 'pv1': {'type': 'type5', 'manufacturer': 'man5'}, 'pv2': {'type': 'type6', 'manufacturer': 'man6'}}
|
||||
},
|
||||
'inverters': {'allow_all': False,
|
||||
'R170000000000001': {'modbus_polling': False, 'node_id': 'PV-Garage/', 'sensor_list': 0x02B0, 'suggested_area': 'Garage', 'pv1': {'type': 'type1', 'manufacturer': 'man1'}, 'pv2': {'type': 'type2', 'manufacturer': 'man2'}},
|
||||
'Y170000000000001': {'modbus_polling': True, 'monitor_sn': 2000000000, 'node_id': 'PV-Garage2/', 'sensor_list': 0x02B0, 'suggested_area': 'Garage2', 'pv1': {'type': 'type1', 'manufacturer': 'man1'}, 'pv2': {'type': 'type2', 'manufacturer': 'man2'}, 'pv3': {'type': 'type3', 'manufacturer': 'man3'}, 'pv4': {'type': 'type4', 'manufacturer': 'man4'}}}}
|
||||
'Y170000000000001': {'modbus_polling': True, 'monitor_sn': 2000000000, 'node_id': 'PV-Garage2/', 'sensor_list': 0x02B0, 'suggested_area': 'Garage2', 'pv1': {'type': 'type1', 'manufacturer': 'man1'}, 'pv2': {'type': 'type2', 'manufacturer': 'man2'}, 'pv3': {'type': 'type3', 'manufacturer': 'man3'}, 'pv4': {'type': 'type4', 'manufacturer': 'man4'}},
|
||||
'Y170000000000002': {'modbus_polling': False, 'monitor_sn': 2000000001, 'node_id': 'PV-Garage3/', 'sensor_list': 0x02B0, 'suggested_area': 'Garage3', 'modbus_scanning': {'start': 2048, 'step': 1024, 'bytes': 16}}
|
||||
}
|
||||
}
|
||||
try:
|
||||
validated = Config.conf_schema.validate(cnf)
|
||||
except Exception:
|
||||
@@ -193,7 +247,7 @@ def test_full_config(ConfigComplete):
|
||||
def test_read_empty(ConfigDefault):
|
||||
test_buffer.rd = ""
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadToml("config/config.toml")
|
||||
err = Config.get_error()
|
||||
@@ -216,14 +270,14 @@ def test_no_file():
|
||||
assert defcnf == None
|
||||
|
||||
def test_no_file2():
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
assert Config.err == None
|
||||
ConfigReadToml("_no__file__no_")
|
||||
err = Config.get_error()
|
||||
assert err == None
|
||||
|
||||
def test_invalid_filename():
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
assert Config.err == None
|
||||
ConfigReadToml(None)
|
||||
err = Config.get_error()
|
||||
@@ -232,7 +286,7 @@ def test_invalid_filename():
|
||||
def test_read_cnf1():
|
||||
test_buffer.rd = "solarman.enabled = false"
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadToml("config/config.toml")
|
||||
err = Config.get_error()
|
||||
@@ -240,6 +294,19 @@ def test_read_cnf1():
|
||||
assert err == None
|
||||
cnf = Config.get()
|
||||
assert cnf == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': False, 'host': 'iot.talent-monitoring.com', 'port': 10000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
'suggested_area': ''
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': False,
|
||||
'R170000000000001': {
|
||||
@@ -251,7 +318,7 @@ def test_read_cnf1():
|
||||
'type': 'RSM40-8-395M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-395M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
@@ -266,7 +333,7 @@ def test_read_cnf1():
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,7 +346,7 @@ def test_read_cnf1():
|
||||
def test_read_cnf2():
|
||||
test_buffer.rd = "solarman.enabled = 'FALSE'"
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadToml("config/config.toml")
|
||||
err = Config.get_error()
|
||||
@@ -287,6 +354,19 @@ def test_read_cnf2():
|
||||
assert err == None
|
||||
cnf = Config.get()
|
||||
assert cnf == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 10000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
'suggested_area': ''
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': False,
|
||||
'R170000000000001': {
|
||||
@@ -298,7 +378,7 @@ def test_read_cnf2():
|
||||
'type': 'RSM40-8-395M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-395M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
@@ -313,7 +393,7 @@ def test_read_cnf2():
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +402,7 @@ def test_read_cnf2():
|
||||
def test_read_cnf3(ConfigDefault):
|
||||
test_buffer.rd = "solarman.port = 'FALSE'"
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadToml("config/config.toml")
|
||||
err = Config.get_error()
|
||||
@@ -334,7 +414,7 @@ def test_read_cnf3(ConfigDefault):
|
||||
def test_read_cnf4():
|
||||
test_buffer.rd = "solarman.port = 5000"
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadToml("config/config.toml")
|
||||
err = Config.get_error()
|
||||
@@ -342,6 +422,19 @@ def test_read_cnf4():
|
||||
assert err == None
|
||||
cnf = Config.get()
|
||||
assert cnf == {'gen3plus': {'at_acl': {'mqtt': {'allow': ['AT+'], 'block': []}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE'], 'block': []}}}, 'tsun': {'enabled': True, 'host': 'logger.talent-monitoring.com', 'port': 5005}, 'solarman': {'enabled': True, 'host': 'iot.talent-monitoring.com', 'port': 5000}, 'mqtt': {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}, 'ha': {'auto_conf_prefix': 'homeassistant', 'discovery_prefix': 'homeassistant', 'entity_prefix': 'tsun', 'proxy_node_id': 'proxy', 'proxy_unique_id': 'P170000000000001'},
|
||||
'batteries': {
|
||||
'4100000000000001': {
|
||||
'modbus_polling': True,
|
||||
'monitor_sn': 3000000000,
|
||||
'node_id': '',
|
||||
'pv1': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 0,
|
||||
'suggested_area': ''
|
||||
}
|
||||
},
|
||||
'inverters': {
|
||||
'allow_all': False,
|
||||
'R170000000000001': {
|
||||
@@ -353,7 +446,7 @@ def test_read_cnf4():
|
||||
'type': 'RSM40-8-395M'},
|
||||
'pv2': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-395M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
},
|
||||
'Y170000000000001': {
|
||||
'modbus_polling': True,
|
||||
@@ -368,7 +461,7 @@ def test_read_cnf4():
|
||||
'type': 'RSM40-8-410M'},
|
||||
'pv4': {'manufacturer': 'Risen',
|
||||
'type': 'RSM40-8-410M'},
|
||||
'sensor_list': 688
|
||||
'sensor_list': 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,7 +470,7 @@ def test_read_cnf4():
|
||||
def test_read_cnf5():
|
||||
test_buffer.rd = "solarman.port = 1023"
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadToml("config/config.toml")
|
||||
err = Config.get_error()
|
||||
@@ -386,7 +479,7 @@ def test_read_cnf5():
|
||||
def test_read_cnf6():
|
||||
test_buffer.rd = "solarman.port = 65536"
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadToml("config/config.toml")
|
||||
err = Config.get_error()
|
||||
|
||||
@@ -44,7 +44,7 @@ def test_extend_key():
|
||||
assert conf == {'': 'testuser'}
|
||||
|
||||
def test_read_env_config():
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
assert Config.get('mqtt') == {'host': 'mqtt', 'port': 1883, 'user': None, 'passwd': None}
|
||||
for _ in patch_getenv():
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ def ConfigTomlEmpty():
|
||||
def test_no_config(ConfigDefault):
|
||||
test_buffer.rd = "" # empty buffer, no json
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadJson()
|
||||
err = Config.get_error()
|
||||
@@ -96,7 +96,7 @@ def test_no_config(ConfigDefault):
|
||||
def test_no_file(ConfigDefault):
|
||||
test_buffer.rd = "" # empty buffer, no json
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadJson("_no__file__no_")
|
||||
err = Config.get_error()
|
||||
@@ -108,7 +108,7 @@ def test_no_file(ConfigDefault):
|
||||
def test_invalid_filename(ConfigDefault):
|
||||
test_buffer.rd = "" # empty buffer, no json
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadJson(None)
|
||||
err = Config.get_error()
|
||||
@@ -340,7 +340,7 @@ def test_cnv6():
|
||||
def test_empty_config(ConfigDefault):
|
||||
test_buffer.rd = "{}" # empty json
|
||||
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadJson()
|
||||
err = Config.get_error()
|
||||
@@ -380,6 +380,29 @@ def test_full_config(ConfigComplete):
|
||||
"pv4.manufacturer": "man4",
|
||||
"pv4.type": "type4",
|
||||
"sensor_list": 688
|
||||
},
|
||||
{
|
||||
"serial": "Y170000000000002",
|
||||
"monitor_sn": 2000000001,
|
||||
"modbus_polling": false,
|
||||
"modbus_scanning.start": 2048,
|
||||
"node_id": "PV-Garage3",
|
||||
"suggested_area": "Garage3",
|
||||
"sensor_list": 688
|
||||
}
|
||||
],
|
||||
"batteries": [
|
||||
{
|
||||
"serial": "4100000000000001",
|
||||
"modbus_polling": true,
|
||||
"monitor_sn": 3000000000,
|
||||
"node_id": "Bat-Garage3",
|
||||
"suggested_area": "Garage3",
|
||||
"pv1.manufacturer": "man5",
|
||||
"pv1.type": "type5",
|
||||
"pv2.manufacturer": "man6",
|
||||
"pv2.type": "type6",
|
||||
"sensor_list": 12326
|
||||
}
|
||||
],
|
||||
"tsun.enabled": true,
|
||||
@@ -401,7 +424,7 @@ def test_full_config(ConfigComplete):
|
||||
]
|
||||
}
|
||||
"""
|
||||
Config.init(ConfigReadToml("app/config/default_config.toml"))
|
||||
Config.init(ConfigReadToml("app/src/cnf/default_config.toml"))
|
||||
for _ in patch_open():
|
||||
ConfigReadJson()
|
||||
err = Config.get_error()
|
||||
|
||||
@@ -93,6 +93,86 @@ def contr2_data_seq(): # Get Time Request message
|
||||
msg += b'\x00\x00\x00'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def contr3_data_seq(): # Get Time Request message
|
||||
msg = b'\x00\x00\x00\x39\x00\x09\x2b\xa8\x54\x10\x52' # | ..^.....9..+.T.R
|
||||
msg += b'\x53\x57\x5f\x34\x30\x30\x5f\x56\x32\x2e\x30\x31\x2e\x31\x33\x00' # | SW_400_V2.01.13.
|
||||
msg += b'\x09\x27\xc0\x54\x06\x52\x61\x79\x6d\x6f\x6e\x00\x09\x2f\x90\x54' # | .'.T.Raymon../.T
|
||||
msg += b'\x0b\x52\x53\x57\x2d\x31\x2d\x31\x30\x30\x30\x31\x00\x09\x5a\x88' # | .RSW-1-10001..Z.
|
||||
msg += b'\x54\x0f\x74\x2e\x72\x61\x79\x6d\x6f\x6e\x69\x6f\x74\x2e\x63\x6f' # | T.t.raymoniot.co
|
||||
msg += b'\x6d\x00\x09\x5a\xec\x54\x1c\x6c\x6f\x67\x67\x65\x72\x2e\x74\x61' # | m..Z.T.logger.ta
|
||||
msg += b'\x6c\x65\x6e\x74\x2d\x6d\x6f\x6e\x69\x74\x6f\x72\x69\x6e\x67\x2e' # | lent-monitoring.
|
||||
msg += b'\x63\x6f\x6d\x00\x0d\x2f\x00\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | com../.T........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x32\xe8\x54\x10\xff' # | ...........2.T..
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
|
||||
msg += b'\x0d\x36\xd0\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .6.T............
|
||||
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x3a\xb8\x54\x10\xff\xff\xff\xff\xff' # | .......:.T......
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x3e\xa0\x54' # | .............>.T
|
||||
msg += b'\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\x00\x0d\x42\x88\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ...B.T..........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x46\x70\x54\x10\xff\xff\xff' # | .........FpT....
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x4a' # | ...............J
|
||||
msg += b'\x58\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | XT..............
|
||||
msg += b'\xff\xff\xff\x00\x0d\x4e\x40\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .....N@T........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x52\x28\x54\x10\xff' # | ...........R(T..
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
|
||||
msg += b'\x0d\x56\x10\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .V.T............
|
||||
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x59\xf8\x54\x10\xff\xff\xff\xff\xff' # | .......Y.T......
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x5d\xe0\x54' # | .............].T
|
||||
msg += b'\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\x00\x0d\x61\xc8\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ...a.T..........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x65\xb0\x54\x10\xff\xff\xff' # | .........e.T....
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x69' # | ...............i
|
||||
msg += b'\x98\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .T..............
|
||||
msg += b'\xff\xff\xff\x00\x0d\x6d\x80\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .....m.T........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x71\x68\x54\x10\xff' # | ...........qhT..
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
|
||||
msg += b'\x0d\x75\x50\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .uPT............
|
||||
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x79\x38\x54\x10\xff\xff\xff\xff\xff' # | .......y8T......
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x7d\x20\x54' # | .............} T
|
||||
msg += b'\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\x00\x0d\x81\x08\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .....T..........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x84\xf0\x54\x10\xff\xff\xff' # | ...........T....
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x88' # | ................
|
||||
msg += b'\xd8\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .T..............
|
||||
msg += b'\xff\xff\xff\x00\x0d\x8c\xc0\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .......T........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x90\xa8\x54\x10\xff' # | .............T..
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
|
||||
msg += b'\x0d\x94\x90\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ...T............
|
||||
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x98\x78\x54\x10\xff\xff\xff\xff\xff' # | ........xT......
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x9c\x60\x54' # | ..............`T
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\x00\x0d\x00\x20\x49\x00\x00\x00\x01\x00\x0c\x35\x00\x49\x00\x00' # | ... I......5.I..
|
||||
msg += b'\x00\x62\x00\x0c\x96\xa8\x49\x00\x00\x01\x4f\x00\x0c\x7f\x38\x49' # | .b....I...O...8I
|
||||
msg += b'\x00\x00\x00\x01\x00\x0c\xfc\x38\x49\x00\x00\x00\x01\x00\x0c\xf8' # | .......8I.......
|
||||
msg += b'\x50\x49\x00\x00\x01\x2c\x00\x0c\x63\xe0\x49\x00\x00\x00\x00\x00' # | PI...,..c.I.....
|
||||
msg += b'\x0c\x67\xc8\x49\x00\x00\x00\x00\x00\x0c\x50\x58\x49\x00\x00\x00' # | .g.I......PXI...
|
||||
msg += b'\x01\x00\x09\x5e\x70\x49\x00\x00\x13\x8d\x00\x09\x5e\xd4\x49\x00' # | ...^pI......^.I.
|
||||
msg += b'\x00\x13\x8d\x00\x09\x5b\x50\x49\x00\x00\x00\x02\x00\x0d\x04\x08' # | .....[PI........
|
||||
msg += b'\x49\x00\x00\x00\x00\x00\x07\xa1\x84\x49\x00\x00\x00\x01\x00\x0c' # | I........I......
|
||||
msg += b'\x50\x59\x49\x00\x00\x00\x2d\x00\x0d\x1f\x60\x49\x00\x00\x00\x00' # | PYI...-...`I....
|
||||
msg += b'\x00\x0d\x23\x48\x49\xff\xff\xff\xff\x00\x0d\x27\x30\x49\xff\xff' # | ..#HI......'0I..
|
||||
msg += b'\xff\xff\x00\x0d\x2b\x18\x4c\x00\x00\x00\x00\xff\xff\xff\xff\x00' # | ....+.L.........
|
||||
msg += b'\x0c\xa2\x60\x49\x00\x00\x00\x00\x00\x0d\xa0\x48\x49\x00\x00\x00' # | ..`I.......HI...
|
||||
msg += b'\x00\x00\x0d\xa4\x30\x49\x00\x00\x00\xff\x00\x0d\xa8\x18\x49\x00' # | ....0I........I.
|
||||
msg += b'\x00\x00\xff'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def inv_data_seq(): # Data indication from the controller
|
||||
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'
|
||||
@@ -140,6 +220,153 @@ def inv_data_seq2(): # Data indication from the controller
|
||||
msg += b'\x53\x00\x00'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def inv_data_seq3(): # Inverter indication from MS-2000
|
||||
|
||||
msg = b'\x00\x00\x01\x2c\x00\x00\x00\x64\x53\x00\x00' # | ..^.....,...dS..
|
||||
msg += b'\x00\x00\x00\xc8\x53\x44\x00\x00\x00\x01\x2c\x53\x00\x00\x00\x00' # | ....SD....,S....
|
||||
msg += b'\x01\x90\x49\x00\x00\x00\x00\x00\x00\x01\x91\x53\x00\x00\x00\x00' # | ..I........S....
|
||||
msg += b'\x01\x92\x53\x00\x00\x00\x00\x01\x93\x53\x00\x00\x00\x00\x01\x94' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x01\x95\x53\x00\x00\x00\x00\x01\x96\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x01\x97\x53\x00\x00\x00\x00\x01\x98\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x01\x99\x53\x00\x00\x00\x00\x01\x9a\x53\x00\x00\x00\x00\x01' # | ...S......S.....
|
||||
msg += b'\x9b\x53\x00\x00\x00\x00\x01\x9c\x53\x00\x00\x00\x00\x01\x9d\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x01\x9e\x53\x00\x00\x00\x00\x01\x9f\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x01\xa0\x53\x00\x00\x00\x00\x01\xf4\x49\x00\x00\x00\x00' # | ....S......I....
|
||||
msg += b'\x00\x00\x01\xf5\x53\x00\x00\x00\x00\x01\xf6\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x01\xf7\x53\x00\x00\x00\x00\x01\xf8\x53\x00\x00\x00\x00\x01\xf9' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x01\xfa\x53\x00\x00\x00\x00\x01\xfb\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x01\xfc\x53\x00\x00\x00\x00\x01\xfd\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x01\xfe\x53\x00\x00\x00\x00\x01\xff\x53\x00\x00\x00\x00\x02' # | ...S......S.....
|
||||
msg += b'\x00\x53\x00\x00\x00\x00\x02\x01\x53\x00\x00\x00\x00\x02\x02\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x02\x03\x53\x00\x00\x00\x00\x02\x04\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x02\x58\x49\x00\x00\x00\x00\x00\x00\x02\x59\x53\x00\x00' # | ...XI.......YS..
|
||||
msg += b'\x00\x00\x02\x5a\x53\x00\x00\x00\x00\x02\x5b\x53\x00\x00\x00\x00' # | ...ZS.....[S....
|
||||
msg += b'\x02\x5c\x53\x00\x00\x00\x00\x02\x5d\x53\x00\x00\x00\x00\x02\x5e' # | .\S.....]S.....^
|
||||
msg += b'\x53\x00\x00\x00\x00\x02\x5f\x53\x00\x00\x00\x00\x02\x60\x53\x00' # | S....._S.....`S.
|
||||
msg += b'\x00\x00\x00\x02\x61\x53\x00\x00\x00\x00\x02\x62\x53\x00\x00\x00' # | ....aS.....bS...
|
||||
msg += b'\x00\x02\x63\x53\x00\x00\x00\x00\x02\x64\x53\x00\x00\x00\x00\x02' # | ..cS.....dS.....
|
||||
msg += b'\x65\x53\x00\x00\x00\x00\x02\x66\x53\x00\x00\x00\x00\x02\x67\x53' # | eS.....fS.....gS
|
||||
msg += b'\x00\x00\x00\x00\x02\x68\x53\x00\x00\x00\x00\x02\xbc\x49\x00\x00' # | .....hS......I..
|
||||
msg += b'\x00\x00\x00\x00\x02\xbd\x53\x00\x00\x00\x00\x02\xbe\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x02\xbf\x53\x00\x00\x00\x00\x02\xc0\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x02\xc1\x53\x00\x00\x00\x00\x02\xc2\x53\x00\x00\x00\x00\x02\xc3' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x02\xc4\x53\x00\x00\x00\x00\x02\xc5\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x02\xc6\x53\x00\x00\x00\x00\x02\xc7\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x02\xc8\x53\x00\x00\x00\x00\x02\xc9\x53\x00\x00\x00\x00\x02' # | ...S......S.....
|
||||
msg += b'\xca\x53\x00\x00\x00\x00\x02\xcb\x53\x00\x00\x00\x00\x02\xcc\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x03\x20\x53\x00\x01\x00\x00\x03\x84\x53\x11\x68' # | ..... S......S.h
|
||||
msg += b'\x00\x00\x03\xe8\x46\x44\x23\xd1\xec\x00\x00\x04\x4c\x46\x43\xa3' # | ....FD#.....LFC.
|
||||
msg += b'\xb3\x33\x00\x00\x04\xb0\x46\x00\x00\x00\x00\x00\x00\x05\x14\x46' # | .3....F........F
|
||||
msg += b'\x43\x6e\x80\x00\x00\x00\x05\x78\x46\x3d\x4c\xcc\xcd\x00\x00\x05' # | Cn.....xF=L.....
|
||||
msg += b'\xdc\x46\x00\x00\x00\x00\x00\x00\x06\x40\x46\x42\x48\x00\x00\x00' # | .F.......@FBH...
|
||||
msg += b'\x00\x06\xa4\x53\x00\x03\x00\x00\x07\x08\x53\x00\x0c\x00\x00\x07' # | ...S......S.....
|
||||
msg += b'\x6c\x53\x00\x50\x00\x00\x07\xd0\x46\x43\xa3\xb3\x33\x00\x00\x08' # | lS.P....FC..3...
|
||||
msg += b'\x34\x53\x0b\xb8\x00\x00\x08\x98\x46\x00\x00\x00\x00\x00\x00\x08' # | 4S......F.......
|
||||
msg += b'\xfc\x46\x00\x00\x00\x00\x00\x00\x09\x60\x46\x41\xee\xe1\x48\x00' # | .F.......`FA..H.
|
||||
msg += b'\x00\x09\xc4\x53\x00\x00\x00\x00\x0a\x28\x46\x41\xf2\x00\x00\x00' # | ...S.....(FA....
|
||||
msg += b'\x00\x0a\x8c\x46\x3f\xac\x28\xf6\x00\x00\x0a\xf0\x53\x00\x0c\x00' # | ...F?.(.....S...
|
||||
msg += b'\x00\x0b\x54\x53\x00\x00\x00\x00\x0b\xb8\x53\x00\x00\x00\x00\x0c' # | ..TS......S.....
|
||||
msg += b'\x1c\x53\x00\x00\x00\x00\x0c\x80\x53\x00\x00\x00\x00\x0c\xe4\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x0d\x48\x53\x00\x00\x00\x00\x0d\xac\x53\x00\x00' # | .....HS......S..
|
||||
msg += b'\x00\x00\x0e\x10\x53\x00\x00\x00\x00\x0e\x74\x53\x00\x00\x00\x00' # | ....S.....tS....
|
||||
msg += b'\x0e\xd8\x53\x00\x00\x00\x00\x0f\x3c\x53\x00\x00\x00\x00\x0f\xa0' # | ..S.....<S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x10\x04\x53\x00\x00\x00\x00\x10\x68\x53\x00' # | S......S.....hS.
|
||||
msg += b'\x00\x00\x00\x10\xcc\x53\x00\x00\x00\x00\x11\x30\x53\x00\x00\x00' # | .....S.....0S...
|
||||
msg += b'\x00\x11\x94\x53\x00\x00\x00\x00\x11\xf8\x53\x00\x00\x00\x00\x12' # | ...S......S.....
|
||||
msg += b'\x5c\x53\x00\x00\x00\x00\x12\xc0\x53\x00\x00\x00\x00\x13\x24\x46' # | \S......S.....$F
|
||||
msg += b'\x42\x9d\x33\x33\x00\x00\x13\x88\x46\x00\x00\x00\x00\x00\x00\x13' # | B.33....F.......
|
||||
msg += b'\xec\x46\x00\x00\x00\x00\x00\x00\x14\x50\x46\x42\xdc\x00\x00\x00' # | .F.......PFB....
|
||||
msg += b'\x00\x14\xb4\x53\x00\x00\x00\x00\x15\x18\x53\x00\x00\x00\x00\x15' # | ...S......S.....
|
||||
msg += b'\x7c\x53\x00\x00\x00\x00\x15\x7d\x53\x00\x00\x00\x00\x15\x7e\x53' # | |S.....}S.....~S
|
||||
msg += b'\x00\x00\x00\x00\x15\x7f\x53\x00\x00\x00\x00\x15\x80\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x15\x81\x53\x00\x00\x00\x00\x15\x82\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x15\x83\x53\x00\x00\x00\x00\x15\x84\x53\x00\x00\x00\x00\x15\x85' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x15\x86\x53\x00\x00\x00\x00\x15\x87\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x15\x88\x53\x00\x00\x00\x00\x15\x89\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x15\x8a\x53\x00\x00\x00\x00\x15\x8b\x53\x00\x00\x00\x00\x15' # | ...S......S.....
|
||||
msg += b'\x8c\x53\x00\x00\x00\x00\x15\xe0\x46\x42\x68\x66\x66\x00\x00\x16' # | .S......FBhff...
|
||||
msg += b'\x44\x46\x00\x00\x00\x00\x00\x00\x16\xa8\x46\x00\x00\x00\x00\x00' # | DF........F.....
|
||||
msg += b'\x00\x17\x0c\x46\x42\xdc\x00\x00\x00\x00\x17\x70\x53\x00\x00\x00' # | ...FB......pS...
|
||||
msg += b'\x00\x17\xd4\x53\x00\x00\x00\x00\x18\x38\x53\x00\x00\x00\x00\x18' # | ...S.....8S.....
|
||||
msg += b'\x39\x53\x00\x00\x00\x00\x18\x3a\x53\x00\x00\x00\x00\x18\x3b\x53' # | 9S.....:S.....;S
|
||||
msg += b'\x00\x00\x00\x00\x18\x3c\x53\x00\x00\x00\x00\x18\x3d\x53\x00\x00' # | .....<S.....=S..
|
||||
msg += b'\x00\x00\x18\x3e\x53\x00\x00\x00\x00\x18\x3f\x53\x00\x00\x00\x00' # | ...>S.....?S....
|
||||
msg += b'\x18\x40\x53\x00\x00\x00\x00\x18\x41\x53\x00\x00\x00\x00\x18\x42' # | .@S.....AS.....B
|
||||
msg += b'\x53\x00\x00\x00\x00\x18\x43\x53\x00\x00\x00\x00\x18\x44\x53\x00' # | S.....CS.....DS.
|
||||
msg += b'\x00\x00\x00\x18\x45\x53\x00\x00\x00\x00\x18\x46\x53\x00\x00\x00' # | ....ES.....FS...
|
||||
msg += b'\x00\x18\x47\x53\x00\x00\x00\x00\x18\x48\x53\x00\x00\x00\x00\x18' # | ..GS.....HS.....
|
||||
msg += b'\x9c\x46\x42\x6b\x33\x33\x00\x00\x19\x00\x46\x00\x00\x00\x00\x00' # | .FBk33....F.....
|
||||
msg += b'\x00\x19\x64\x46\x00\x00\x00\x00\x00\x00\x19\xc8\x46\x42\xdc\x00' # | ..dF........FB..
|
||||
msg += b'\x00\x00\x00\x1a\x2c\x53\x00\x00\x00\x00\x1a\x90\x53\x00\x00\x00' # | ....,S......S...
|
||||
msg += b'\x00\x1a\xf4\x53\x00\x00\x00\x00\x1a\xf5\x53\x00\x00\x00\x00\x1a' # | ...S......S.....
|
||||
msg += b'\xf6\x53\x00\x00\x00\x00\x1a\xf7\x53\x00\x00\x00\x00\x1a\xf8\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x1a\xf9\x53\x00\x00\x00\x00\x1a\xfa\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x1a\xfb\x53\x00\x00\x00\x00\x1a\xfc\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x1a\xfd\x53\x00\x00\x00\x00\x1a\xfe\x53\x00\x00\x00\x00\x1a\xff' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x1b\x00\x53\x00\x00\x00\x00\x1b\x01\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x1b\x02\x53\x00\x00\x00\x00\x1b\x03\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x1b\x04\x53\x00\x00\x00\x00\x1b\x58\x53\x00\x00\x00\x00\x1b' # | ...S.....XS.....
|
||||
msg += b'\xbc\x53\x11\x3d\x00\x00\x1c\x20\x46\x3c\x23\xd7\x0a\x00\x00\x1c' # | .S.=... F<#.....
|
||||
msg += b'\x84\x46\x00\x00\x00\x00\x00\x00\x1c\xe8\x46\x42\x04\x00\x00\x00' # | .F........FB....
|
||||
msg += b'\x00\x1d\x4c\x46\x00\x00\x00\x00\x00\x00\x1d\xb0\x46\x00\x00\x00' # | ..LF........F...
|
||||
msg += b'\x00\x00\x00\x1e\x14\x53\x00\x02\x00\x00\x1e\x78\x46\x41\x8b\x33' # | .....S.....xFA.3
|
||||
msg += b'\x33\x00\x00\x1e\xdc\x46\x3c\xa3\xd7\x0a\x00\x00\x1f\x40\x46\x3e' # | 3....F<......@F>
|
||||
msg += b'\x99\x99\x9a\x00\x00\x1f\xa4\x46\x40\x99\x99\x9a\x00\x00\x20\x08' # | .......F@..... .
|
||||
msg += b'\x53\x00\x00\x00\x00\x20\x6c\x53\x00\x00\x00\x00\x20\xd0\x53\x05' # | S.... lS.... .S.
|
||||
msg += b'\x00\x00\x00\x20\xd1\x53\x00\x00\x00\x00\x20\xd2\x53\x00\x00\x00' # | ... .S.... .S...
|
||||
msg += b'\x00\x20\xd3\x53\x00\x00\x00\x00\x20\xd4\x53\x00\x00\x00\x00\x20' # | . .S.... .S....
|
||||
msg += b'\xd5\x53\x00\x00\x00\x00\x20\xd6\x53\x00\x00\x00\x00\x20\xd7\x53' # | .S.... .S.... .S
|
||||
msg += b'\x00\x00\x00\x00\x20\xd8\x53\x00\x00\x00\x00\x20\xd9\x53\x00\x01' # | .... .S.... .S..
|
||||
msg += b'\x00\x00\x20\xda\x53\x00\x00\x00\x00\x20\xdb\x53\x00\x01\x00\x00' # | .. .S.... .S....
|
||||
msg += b'\x20\xdc\x53\x00\x00\x00\x00\x20\xdd\x53\x00\x00\x00\x00\x20\xde' # | .S.... .S.... .
|
||||
msg += b'\x53\x00\x00\x00\x00\x20\xdf\x53\x00\x00\x00\x00\x20\xe0\x53\x00' # | S.... .S.... .S.
|
||||
msg += b'\x00\x00\x00\x21\x34\x46\x00\x00\x00\x00\x00\x00\x21\x98\x46\x00' # | ...!4F......!.F.
|
||||
msg += b'\x00\x00\x00\x00\x00\x21\xfc\x46\x00\x00\x00\x00\x00\x00\x22\x60' # | .....!.F......"`
|
||||
msg += b'\x46\x00\x00\x00\x00\x00\x00\x22\xc4\x53\x00\x00\x00\x00\x23\x28' # | F......".S....#(
|
||||
msg += b'\x53\x00\x00\x00\x00\x23\x8c\x53\x00\x00\x00\x00\x23\x8d\x53\x00' # | S....#.S....#.S.
|
||||
msg += b'\x00\x00\x00\x23\x8e\x53\x00\x00\x00\x00\x23\x8f\x53\x00\x00\x00' # | ...#.S....#.S...
|
||||
msg += b'\x00\x23\x90\x53\x00\x00\x00\x00\x23\x91\x53\x00\x00\x00\x00\x23' # | .#.S....#.S....#
|
||||
msg += b'\x92\x53\x00\x00\x00\x00\x23\x93\x53\x00\x00\x00\x00\x23\x94\x53' # | .S....#.S....#.S
|
||||
msg += b'\x00\x00\x00\x00\x23\x95\x53\x00\x00\x00\x00\x23\x96\x53\x00\x00' # | ....#.S....#.S..
|
||||
msg += b'\x00\x00\x23\x97\x53\x00\x00\x00\x00\x23\x98\x53\x00\x00\x00\x00' # | ..#.S....#.S....
|
||||
msg += b'\x23\x99\x53\x00\x00\x00\x00\x23\x9a\x53\x00\x00\x00\x00\x23\x9b' # | #.S....#.S....#.
|
||||
msg += b'\x53\x00\x00\x00\x00\x23\x9c\x53\x00\x00\x00\x00\x23\xf0\x46\x00' # | S....#.S....#.F.
|
||||
msg += b'\x00\x00\x00\x00\x00\x24\x54\x46\x00\x00\x00\x00\x00\x00\x24\xb8' # | .....$TF......$.
|
||||
msg += b'\x46\x00\x00\x00\x00\x00\x00\x25\x1c\x46\x00\x00\x00\x00\x00\x00' # | F......%.F......
|
||||
msg += b'\x25\x80\x53\x00\x00\x00\x00\x25\xe4\x53\x00\x00\x00\x00\x26\x48' # | %.S....%.S....&H
|
||||
msg += b'\x53\x00\x00\x00\x00\x26\x49\x53\x00\x00\x00\x00\x26\x4a\x53\x00' # | S....&IS....&JS.
|
||||
msg += b'\x00\x00\x00\x26\x4b\x53\x00\x00\x00\x00\x26\x4c\x53\x00\x00\x00' # | ...&KS....&LS...
|
||||
msg += b'\x00\x26\x4d\x53\x00\x00\x00\x00\x26\x4e\x53\x00\x00\x00\x00\x26' # | .&MS....&NS....&
|
||||
msg += b'\x4f\x53\x00\x00\x00\x00\x26\x50\x53\x00\x00\x00\x00\x26\x51\x53' # | OS....&PS....&QS
|
||||
msg += b'\x00\x00\x00\x00\x26\x52\x53\x00\x00\x00\x00\x26\x53\x53\x00\x00' # | ....&RS....&SS..
|
||||
msg += b'\x00\x00\x26\x54\x53\x00\x00\x00\x00\x26\x55\x53\x00\x00\x00\x00' # | ..&TS....&US....
|
||||
msg += b'\x26\x56\x53\x00\x00\x00\x00\x26\x57\x53\x00\x00\x00\x00\x26\x58' # | &VS....&WS....&X
|
||||
msg += b'\x53\x00\x00\x00\x00\x26\xac\x53\x00\x00\x00\x00\x27\x10\x53\x11' # | S....&.S....'.S.
|
||||
msg += b'\x3d\x00\x00\x27\x74\x46\x00\x00\x00\x00\x00\x00\x27\xd8\x46\x00' # | =..'tF......'.F.
|
||||
msg += b'\x00\x00\x00\x00\x00\x28\x3c\x46\x42\x03\xf5\xc3\x00\x00\x28\xa0' # | .....(<FB.....(.
|
||||
msg += b'\x46\x00\x00\x00\x00\x00\x00\x29\x04\x46\x00\x00\x00\x00\x00\x00' # | F......).F......
|
||||
msg += b'\x29\x68\x53\x00\x02\x00\x00\x29\xcc\x53\x00\x03\x00\x00\x2a\x30' # | )hS....).S....*0
|
||||
msg += b'\x46\x42\x20\x00\x00\x00\x00\x2a\x94\x46\x42\x20\x00\x00\x00\x00' # | FB ....*.FB ....
|
||||
msg += b'\x2a\xf8\x46\x44\x20\x00\x00\x00\x00\x2b\x5c\x46\x43\x7b\x00\x00' # | *.FD ....+\FC{..
|
||||
msg += b'\x00\x00\x2b\xc0\x46\x43\x50\x00\x00\x00\x00\x2c\x24\x46\x42\x48' # | ..+.FCP....,$FBH
|
||||
msg += b'\x5c\x29\x00\x00\x2c\x88\x46\x42\x47\xa3\xd7\x00\x00\x2c\xec\x53' # | \)..,.FBG....,.S
|
||||
msg += b'\x00\x00\x00\x00\x2d\x50\x46\x43\x42\x00\x00\x00\x00\x2d\xb4\x46' # | ....-PFCB....-.F
|
||||
msg += b'\x42\xbc\x00\x00\x00\x00\x2e\x18\x46\x3f\xe6\x66\x66\x00\x00\x2e' # | B.......F?.ff...
|
||||
msg += b'\x7c\x46\x3f\xe6\x66\x66\x00\x00\x2e\xe0\x46\x43\x7e\x00\x00\x00' # | |F?.ff....FC~...
|
||||
msg += b'\x00\x2f\x44\x46\x43\x83\xf3\x33\x00\x00\x2f\xa8\x46\x3f\xe6\x66' # | ./DFC..3../.F?.f
|
||||
msg += b'\x66\x00\x00\x30\x0c\x46\x3f\xe6\x66\x66\x00\x00\x30\x70\x46\x43' # | f..0.F?.ff..0pFC
|
||||
msg += b'\x7e\x00\x00\x00\x00\x30\xd4\x46\x42\x3f\xeb\x85\x00\x00\x31\x38' # | ~....0.FB?....18
|
||||
msg += b'\x46\x42\x3d\xeb\x85\x00\x00\x31\x9c\x46\x3e\x4c\xcc\xcd\x00\x00' # | FB=....1.F>L....
|
||||
msg += b'\x32\x00\x46\x3e\x4c\xcc\xcd\x00\x00\x32\x64\x46\x42\x4c\x14\x7b' # | 2.F>L....2dFBL.{
|
||||
msg += b'\x00\x00\x32\xc8\x46\x42\x4d\xeb\x85\x00\x00\x33\x2c\x46\x3e\x4c' # | ..2.FBM....3,F>L
|
||||
msg += b'\xcc\xcd\x00\x00\x33\x90\x46\x3e\x4c\xcc\xcd\x00\x00\x33\xf4\x53' # | ....3.F>L....3.S
|
||||
msg += b'\x00\x00\x00\x00\x34\x58\x53\x00\x00\x00\x00\x34\xbc\x53\x04\x00' # | ....4XS....4.S..
|
||||
msg += b'\x00\x00\x35\x20\x53\x00\x01\x00\x00\x35\x84\x53\x13\x9c\x00\x00' # | ..5 S....5.S....
|
||||
msg += b'\x35\xe8\x53\x0f\xa0\x00\x00\x36\x4c\x53\x00\x00\x00\x00\x36\xb0' # | 5.S....6LS....6.
|
||||
msg += b'\x53\x00\x66' # | S.f'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def inv_data_new(): # Data indication from DSP V5.0.17
|
||||
msg = b'\x00\x00\x00\xa3\x00\x00\x00\x00\x53\x00\x00'
|
||||
@@ -254,7 +481,7 @@ def inv_data_seq2_zero(): # Data indication from the controller
|
||||
|
||||
def test_parse_control(contr_data_seq):
|
||||
i = InfosG3()
|
||||
for key, result in i.parse (contr_data_seq):
|
||||
for key, result in i.parse (contr_data_seq, sensor=0x0e100000):
|
||||
pass # side effect in calling i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps(
|
||||
@@ -262,15 +489,23 @@ def test_parse_control(contr_data_seq):
|
||||
|
||||
def test_parse_control2(contr2_data_seq):
|
||||
i = InfosG3()
|
||||
for key, result in i.parse (contr2_data_seq):
|
||||
for key, result in i.parse (contr2_data_seq, sensor=0x0e100000):
|
||||
pass # side effect in calling i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps(
|
||||
{"collector": {"Collector_Fw_Version": "RSW_400_V1.00.20", "Chip_Type": "Raymon", "Chip_Model": "RSW-1-10001", "Trace_URL": "t.raymoniot.com", "Logger_URL": "logger.talent-monitoring.com"}, "controller": {"Collect_Interval": 1, "Signal_Strength": 16, "Power_On_Time": 334, "Communication_Type": 1, "Connect_Count": 1, "Data_Up_Interval": 300}})
|
||||
|
||||
def test_parse_control3(contr3_data_seq):
|
||||
i = InfosG3()
|
||||
for key, result in i.parse (contr3_data_seq, sensor=0x0e100000):
|
||||
pass # side effect in calling i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps(
|
||||
{"collector": {"Collector_Fw_Version": "RSW_400_V2.01.13", "Chip_Type": "Raymon", "Chip_Model": "RSW-1-10001", "Trace_URL": "t.raymoniot.com", "Logger_URL": "logger.talent-monitoring.com"}, "controller": {"Collect_Interval": 1, "Signal_Strength": 98, "Power_On_Time": 335, "Communication_Type": 1, "Connect_Count": 1, "Data_Up_Interval": 300}})
|
||||
|
||||
def test_parse_inverter(inv_data_seq):
|
||||
i = InfosG3()
|
||||
for key, result in i.parse (inv_data_seq):
|
||||
for key, result in i.parse (inv_data_seq, sensor=0x01900001):
|
||||
pass # side effect in calling i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps(
|
||||
@@ -278,10 +513,10 @@ def test_parse_inverter(inv_data_seq):
|
||||
|
||||
def test_parse_cont_and_invert(contr_data_seq, inv_data_seq):
|
||||
i = InfosG3()
|
||||
for key, result in i.parse (contr_data_seq):
|
||||
for key, result in i.parse (contr_data_seq, sensor=0x0e100000):
|
||||
pass # side effect in calling i.parse()
|
||||
|
||||
for key, result in i.parse (inv_data_seq):
|
||||
for key, result in i.parse (inv_data_seq, sensor=0x01900001):
|
||||
pass # side effect in calling i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps(
|
||||
@@ -289,10 +524,29 @@ def test_parse_cont_and_invert(contr_data_seq, inv_data_seq):
|
||||
"collector": {"Collector_Fw_Version": "RSW_400_V1.00.06", "Chip_Type": "Raymon", "Chip_Model": "RSW-1-10001", "Trace_URL": "t.raymoniot.com", "Logger_URL": "logger.talent-monitoring.com"}, "controller": {"Collect_Interval": 1, "Signal_Strength": 100, "Power_On_Time": 29, "Communication_Type": 1, "Connect_Count": 1, "Data_Up_Interval": 300},
|
||||
"inverter": {"Product_Name": "Microinv", "Manufacturer": "TSUN", "Version": "V5.0.11", "Serial_Number": "T170000000000001", "Equipment_Model": "TSOL-MS600"}})
|
||||
|
||||
def test_parse_cont_and_invert2(contr3_data_seq, inv_data_seq3):
|
||||
i = InfosG3()
|
||||
for key, result in i.parse (contr3_data_seq, sensor=0x0e100000):
|
||||
pass # side effect in calling i.parse()
|
||||
|
||||
for key, result in i.parse (inv_data_seq3, sensor=0x01900000):
|
||||
pass # side effect in calling i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps(
|
||||
{
|
||||
"collector": {"Collector_Fw_Version": "RSW_400_V2.01.13", "Chip_Type": "Raymon", "Chip_Model": "RSW-1-10001", "Trace_URL": "t.raymoniot.com", "Logger_URL": "logger.talent-monitoring.com"}, "controller": {"Collect_Interval": 1, "Signal_Strength": 98, "Power_On_Time": 335, "Communication_Type": 1, "Connect_Count": 1, "Data_Up_Interval": 300},
|
||||
"env": {"Inverter_Status": 0},
|
||||
"events": {"Inverter_Alarm": 0, "Inverter_Fault": 0, "Inverter_Bitfield_1": 0, "Inverter_bitfield_2": 0},
|
||||
"input": {"iVal_1": 1, "Val_0": 655.28, "Val_1": 327.4, "Val_2": 0.0, "Val_3": 0.0, "iVal_2": 3, "iVal_3": 12, "iVal_4": 80, "Val_4": 327.4, "iVal_5": 0, "Val_10": 30.25, "Val_11": 1.35, "iVal_6": 12, "pv1": {"Voltage": 78.6, "Current": 0.0, "Power": 0.0}, "Val_5": 110.0, "pv2": {"Voltage": 58.1, "Current": 0.0, "Power": 0.0}, "Val_6": 110.0, "pv3": {"Voltage": 58.8, "Current": 0.0, "Power": 0.0}, "Val_7": 110.0, "Val_14": 0.01, "Val_15": 0.0, "Val_16": 33.0, "Val_17": 0.0, "Val_18": 0.0, "iVal_8": 2, "pv4": {"Voltage": 17.4, "Current": 0.02, "Power": 0.3}, "Val_8": 4.8, "iVal_10": 1, "pv5": {"Voltage": 0.0, "Current": 0.0, "Power": 0.0}, "pv6": {"Voltage": 0.0, "Current": 0.0, "Power": 0.0}, "Val_24": 0.0, "Val_25": 0.0, "Val_26": 32.99, "Val_27": 0.0, "Val_28": 0.0, "iVal_11": 2, "iVal_12": 3},
|
||||
"grid": {"Voltage": 238.5, "Current": 0.05, "Frequency": 50.0, "Output_Power": 0.0},
|
||||
"inverter": {"Max_Designed_Power": 3000},
|
||||
"total": {"Total_Generation": 29.86}
|
||||
})
|
||||
|
||||
def test_build_ha_conf1(contr_data_seq):
|
||||
i = InfosG3()
|
||||
i.static_init() # initialize counter
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "01900001")
|
||||
|
||||
tests = 0
|
||||
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123'):
|
||||
@@ -328,6 +582,7 @@ def test_build_ha_conf1(contr_data_seq):
|
||||
def test_build_ha_conf2(contr_data_seq):
|
||||
i = InfosG3()
|
||||
i.static_init() # initialize counter
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "01900001")
|
||||
|
||||
tests = 0
|
||||
for d_json, comp, node_id, id in i.ha_proxy_confs(ha_prfx="tsun/", node_id = 'proxy/', snr = '456'):
|
||||
@@ -352,11 +607,12 @@ def test_build_ha_conf2(contr_data_seq):
|
||||
|
||||
def test_build_ha_conf3(contr_data_seq, inv_data_seq, inv_data_seq2):
|
||||
i = InfosG3()
|
||||
for key, result in i.parse (contr_data_seq):
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "01900001")
|
||||
for key, result in i.parse (contr_data_seq, sensor=0x0e100000):
|
||||
pass # side effect in calling i.parse()
|
||||
for key, result in i.parse (inv_data_seq):
|
||||
for key, result in i.parse (inv_data_seq, sensor=0x01900001):
|
||||
pass # side effect in calling i.parse()
|
||||
for key, result in i.parse (inv_data_seq2):
|
||||
for key, result in i.parse (inv_data_seq2, sensor=0x01900001):
|
||||
pass # side effect in calling i.parse()
|
||||
|
||||
tests = 0
|
||||
@@ -390,9 +646,10 @@ def test_build_ha_conf3(contr_data_seq, inv_data_seq, inv_data_seq2):
|
||||
|
||||
def test_build_ha_conf4(contr_data_seq, inv_data_seq):
|
||||
i = InfosG3()
|
||||
for key, result in i.parse (contr_data_seq):
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "01900001")
|
||||
for key, result in i.parse (contr_data_seq, sensor=0x0e100000):
|
||||
pass # side effect in calling i.parse()
|
||||
for key, result in i.parse (inv_data_seq):
|
||||
for key, result in i.parse (inv_data_seq, sensor=0x01900001):
|
||||
pass # side effect in calling i.parse()
|
||||
i.set_db_def_value(Register.MAC_ADDR, "00a057123456")
|
||||
|
||||
@@ -414,10 +671,37 @@ def test_build_ha_conf4(contr_data_seq, inv_data_seq):
|
||||
tests +=1
|
||||
assert tests==1
|
||||
|
||||
def test_build_ha_conf5(contr3_data_seq, inv_data_seq3):
|
||||
i = InfosG3()
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "01900000")
|
||||
for key, result in i.parse (contr3_data_seq, sensor=0x0e100000):
|
||||
pass # side effect in calling i.parse()
|
||||
for key, result in i.parse (inv_data_seq3, sensor=0x01900000):
|
||||
pass # side effect in calling i.parse()
|
||||
i.set_db_def_value(Register.MAC_ADDR, "00a057123456")
|
||||
|
||||
tests = 0
|
||||
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', sug_area = 'roof'):
|
||||
if id == 'signal_123':
|
||||
assert comp == 'sensor'
|
||||
assert d_json == json.dumps({"name": "Signal Strength", "stat_t": "tsun/garagendach/controller", "dev_cla": None, "stat_cla": "measurement", "uniq_id": "signal_123", "val_tpl": "{{value_json[\'Signal_Strength\'] | int}}", "unit_of_meas": "%", "ic": "mdi:wifi", "dev": {"name": "Controller - roof", "sa": "Controller - roof", "via_device": "proxy", "mdl": "RSW-1-10001", "mf": "Raymon", "sw": "RSW_400_V2.01.13", "ids": ["controller_123"], "cns": [["mac", "00:a0:57:12:34:56"]]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||
tests +=1
|
||||
assert tests==1
|
||||
|
||||
i.set_db_def_value(Register.MAC_ADDR, "00:a0:57:12:34:57")
|
||||
|
||||
tests = 0
|
||||
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123', sug_area = 'roof'):
|
||||
if id == 'signal_123':
|
||||
assert comp == 'sensor'
|
||||
assert d_json == json.dumps({"name": "Signal Strength", "stat_t": "tsun/garagendach/controller", "dev_cla": None, "stat_cla": "measurement", "uniq_id": "signal_123", "val_tpl": "{{value_json[\'Signal_Strength\'] | int}}", "unit_of_meas": "%", "ic": "mdi:wifi", "dev": {"name": "Controller - roof", "sa": "Controller - roof", "via_device": "proxy", "mdl": "RSW-1-10001", "mf": "Raymon", "sw": "RSW_400_V2.01.13", "ids": ["controller_123"], "cns": [["mac", "00:a0:57:12:34:57"]]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||
tests +=1
|
||||
assert tests==1
|
||||
|
||||
def test_must_incr_total(inv_data_seq2, inv_data_seq2_zero):
|
||||
i = InfosG3()
|
||||
tests = 0
|
||||
for key, update in i.parse (inv_data_seq2):
|
||||
for key, update in i.parse (inv_data_seq2, sensor=0x01900001):
|
||||
if key == 'total' or key == 'inverter' or key == 'env':
|
||||
assert update == True
|
||||
tests +=1
|
||||
@@ -426,7 +710,7 @@ def test_must_incr_total(inv_data_seq2, inv_data_seq2_zero):
|
||||
assert json.dumps(i.db['input']) == json.dumps({"pv1": {"Voltage": 33.6, "Current": 1.91, "Power": 64.5, "Daily_Generation": 1.08, "Total_Generation": 9.74}, "pv2": {"Voltage": 33.5, "Current": 1.36, "Power": 45.7, "Daily_Generation": 0.62, "Total_Generation": 7.62}, "pv3": {"Voltage": 0.0, "Current": 0.0, "Power": 0.0}, "pv4": {"Voltage": 0.0, "Current": 0.0, "Power": 0.0}})
|
||||
assert json.dumps(i.db['env']) == json.dumps({"Inverter_Status": 1, "Inverter_Temp": 23})
|
||||
tests = 0
|
||||
for key, update in i.parse (inv_data_seq2):
|
||||
for key, update in i.parse (inv_data_seq2, sensor=0x01900001):
|
||||
if key == 'total' or key == 'env':
|
||||
assert update == False
|
||||
tests +=1
|
||||
@@ -438,7 +722,7 @@ def test_must_incr_total(inv_data_seq2, inv_data_seq2_zero):
|
||||
assert json.dumps(i.db['inverter']) == json.dumps({"Rated_Power": 600, "BOOT_STATUS": 0, "DSP_STATUS": 21930, "Work_Mode": 0, "Max_Designed_Power": -1, "Input_Coefficient": -0.1, "Output_Coefficient": 100.0, "No_Inputs": 2})
|
||||
|
||||
tests = 0
|
||||
for key, update in i.parse (inv_data_seq2_zero):
|
||||
for key, update in i.parse (inv_data_seq2_zero, sensor=0x01900001):
|
||||
if key == 'total':
|
||||
assert update == False
|
||||
tests +=1
|
||||
@@ -453,7 +737,7 @@ def test_must_incr_total(inv_data_seq2, inv_data_seq2_zero):
|
||||
def test_must_incr_total2(inv_data_seq2, inv_data_seq2_zero):
|
||||
i = InfosG3()
|
||||
tests = 0
|
||||
for key, update in i.parse (inv_data_seq2_zero):
|
||||
for key, update in i.parse (inv_data_seq2_zero, sensor=0x01900001):
|
||||
if key == 'total':
|
||||
assert update == False
|
||||
tests +=1
|
||||
@@ -467,7 +751,7 @@ def test_must_incr_total2(inv_data_seq2, inv_data_seq2_zero):
|
||||
assert json.dumps(i.db['env']) == json.dumps({"Inverter_Status": 1, "Inverter_Temp": 0})
|
||||
|
||||
tests = 0
|
||||
for key, update in i.parse (inv_data_seq2_zero):
|
||||
for key, update in i.parse (inv_data_seq2_zero, sensor=0x01900001):
|
||||
if key == 'total' or key == 'env':
|
||||
assert update == False
|
||||
tests +=1
|
||||
@@ -478,7 +762,7 @@ def test_must_incr_total2(inv_data_seq2, inv_data_seq2_zero):
|
||||
assert json.dumps(i.db['env']) == json.dumps({"Inverter_Status": 1, "Inverter_Temp": 0})
|
||||
|
||||
tests = 0
|
||||
for key, update in i.parse (inv_data_seq2):
|
||||
for key, update in i.parse (inv_data_seq2, sensor=0x01900001):
|
||||
if key == 'total' or key == 'env':
|
||||
tests +=1
|
||||
|
||||
@@ -489,7 +773,7 @@ def test_must_incr_total2(inv_data_seq2, inv_data_seq2_zero):
|
||||
def test_new_data_types(inv_data_new):
|
||||
i = InfosG3()
|
||||
tests = 0
|
||||
for key, update in i.parse (inv_data_new):
|
||||
for key, update in i.parse (inv_data_new, sensor=0x01900001):
|
||||
if key == 'events':
|
||||
tests +=1
|
||||
elif key == 'inverter':
|
||||
@@ -514,7 +798,7 @@ def test_invalid_data_type(invalid_data_seq):
|
||||
assert val == 0
|
||||
|
||||
|
||||
for key, result in i.parse (invalid_data_seq):
|
||||
for key, result in i.parse (invalid_data_seq, sensor=0x01900001):
|
||||
pass # side effect in calling i.parse()
|
||||
assert json.dumps(i.db) == json.dumps({"inverter": {"Product_Name": "Microinv"}})
|
||||
|
||||
|
||||
@@ -70,6 +70,28 @@ def inverter_data(): # 0x4210 ftype: 0x01
|
||||
msg += b'\x00\x00\x00\x00'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def batterie_data(): # 0x4210 ftype: 0x01
|
||||
msg = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x26\x30\xc7\xde'
|
||||
msg += b'\x2d\x32\x28\x00\x00\x00\x84\x17\x79\x35\x01\x00\x4c\x12\x00\x00'
|
||||
msg += b'\x34\x31\x30\x31\x32\x34\x30\x37\x30\x31\x34\x39\x30\x33\x31\x34'
|
||||
msg += b'\x0d\x3a\x00\x70\x0d\x2c\x00\x00\x00\x00\x08\x20\x00\x00\x00\x00'
|
||||
msg += b'\x14\x0e\xff\xfe\x03\xe8\x0c\x89\x0c\x89\x0c\x89\x0c\x8a\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x8a\x0c\x89\x0c\x89\x0c\x8a\x0c\x8a\x0c\x89\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x89\x0c\x88\x00\x0f\x00\x0f\x00\x0f\x00\x0e\x00\x00'
|
||||
msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def batterie_data2(): # 0x4210 ftype: 0x01
|
||||
msg = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x26\x30\xc7\xde'
|
||||
msg += b'\x2d\x32\x28\x00\x00\x00\x84\x17\x79\x35\x01\x00\x4c\x12\x00\x00'
|
||||
msg += b'\x34\x31\x30\x31\x32\x34\x30\x37\x30\x31\x34\x39\x30\x33\x31\x34'
|
||||
msg += b'\x0d\x3a\x00\x70\x0d\x2c\x00\x00\x00\x00\x08\x20\x00\x00\x00\x00'
|
||||
msg += b'\x14\x0e\xff\xfe\x03\xe8\x0c\x89\x0c\x89\x0c\x89\x0c\x8a\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x8a\x0c\x89\x0c\x89\x0c\x8a\x0c\x8a\x0c\x89\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x89\x0c\x88\x00\x0f\x00\x0f\x00\x0f\x00\x0e'
|
||||
return msg
|
||||
|
||||
def test_default_db():
|
||||
i = InfosG3P(client_mode=False)
|
||||
@@ -101,11 +123,11 @@ def test_build_4110(str_test_ip, device_data: bytes):
|
||||
build_msg[i] = device_data[i]
|
||||
assert device_data == build_msg
|
||||
|
||||
def test_parse_4210(inverter_data: bytes):
|
||||
def test_parse_4210_02b0(inverter_data: bytes):
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps({
|
||||
@@ -123,14 +145,56 @@ def test_parse_4210(inverter_data: bytes):
|
||||
"other": {"Output_Shutdown": 65535, "Rated_Level": 3, "Grid_Volt_Cal_Coef": 1024, "Prod_Compliance_Type": 6}
|
||||
})
|
||||
|
||||
def test_parse_4210_3026(batterie_data: bytes):
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (batterie_data, 0x42, 1, 0x3026):
|
||||
pass # side effect is calling generator i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps({
|
||||
"controller": {"Sensor_List": "3026", "Power_On_Time": 4684},
|
||||
"inverter": {"Serial_Number": "4101240701490314"},
|
||||
"batterie": {"pv1": {"Voltage": 33.86, "Current": 1.12},
|
||||
"pv2": {"Voltage": 33.72, "Current": 0.0},
|
||||
"Reg_38": 0, "Total_Generation": 20.8, "Status_1": 0, "Status_2": 0,
|
||||
"Voltage": 51.34, "Current": -0.02, "SOC": 10.0,
|
||||
"Cell": {"Volt1": 3.21, "Volt2": 3.21, "Volt3": 3.21, "Volt4": 3.21, "Volt5": 3.21, "Volt6": 3.21, "Volt7": 3.21, "Volt8": 3.21, "Volt9": 3.21, "Volt10": 3.21, "Volt11": 3.21, "Volt12": 3.21, "Volt13": 3.21, "Volt14": 3.21, "Volt15": 3.21, "Volt16": 3.21},
|
||||
"Temp_1": 15, "Temp_2": 15, "Temp_3": 15,
|
||||
"out": {"Voltage": 0.14, "Current": 0.0, "Out_Status": 0, "Power": 0.0},
|
||||
"Controller_Temp": 15, "Reg_74": 0, "Reg_76": 517, "Reg_78": 513,
|
||||
"PV_Power": 37.9232, "Power": -1.0268000000000002},
|
||||
})
|
||||
|
||||
def test_parse_4210_3026_incomplete(batterie_data2: bytes):
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (batterie_data2, 0x42, 1, 0x3026):
|
||||
pass # side effect is calling generator i.parse()
|
||||
|
||||
assert json.dumps(i.db) == json.dumps({
|
||||
"controller": {"Sensor_List": "3026", "Power_On_Time": 4684},
|
||||
"inverter": {"Serial_Number": "4101240701490314"},
|
||||
"batterie": {"pv1": {"Voltage": 33.86, "Current": 1.12},
|
||||
"pv2": {"Voltage": 33.72, "Current": 0.0},
|
||||
"Reg_38": 0, "Total_Generation": 20.8, "Status_1": 0, "Status_2": 0,
|
||||
"Voltage": 51.34, "Current": -0.02, "SOC": 10.0,
|
||||
"Cell": {"Volt1": 3.21, "Volt2": 3.21, "Volt3": 3.21, "Volt4": 3.21, "Volt5": 3.21, "Volt6": 3.21, "Volt7": 3.21, "Volt8": 3.21, "Volt9": 3.21, "Volt10": 3.21, "Volt11": 3.21, "Volt12": 3.21, "Volt13": 3.21, "Volt14": 3.21, "Volt15": 3.21, "Volt16": 3.21},
|
||||
"Temp_1": 15, "Temp_2": 15, "Temp_3": 15,
|
||||
"out": {"Voltage": 0.14, "Current": None, "Out_Status": None, "Power": None},
|
||||
"Controller_Temp": None, "Reg_74": None, "Reg_76": None, "Reg_78": None,
|
||||
"PV_Power": 37.9232, "Power": -1.0268000000000002},
|
||||
})
|
||||
|
||||
def test_build_4210(inverter_data: bytes):
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1)
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0)
|
||||
for i in range(11, 31):
|
||||
build_msg[i] = inverter_data[i]
|
||||
assert inverter_data == build_msg
|
||||
@@ -138,6 +202,7 @@ def test_build_4210(inverter_data: bytes):
|
||||
def test_build_ha_conf1():
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.static_init() # initialize counter
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "02b0")
|
||||
|
||||
tests = 0
|
||||
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123'):
|
||||
@@ -212,6 +277,7 @@ def test_build_ha_conf2():
|
||||
def test_build_ha_conf3():
|
||||
i = InfosG3P(client_mode=True)
|
||||
i.static_init() # initialize counter
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "02b0")
|
||||
|
||||
tests = 0
|
||||
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123'):
|
||||
@@ -283,56 +349,89 @@ def test_build_ha_conf4():
|
||||
|
||||
assert tests==1
|
||||
|
||||
def test_build_ha_conf5():
|
||||
i = InfosG3P(client_mode=True)
|
||||
i.static_init() # initialize counter
|
||||
i.set_db_def_value(Register.SENSOR_LIST, "3026")
|
||||
|
||||
tests = 0
|
||||
for d_json, comp, node_id, id in i.ha_confs(ha_prfx="tsun/", node_id="garagendach/", snr='123'):
|
||||
|
||||
if id == 'out_power_123':
|
||||
assert comp == 'sensor'
|
||||
assert d_json == json.dumps({"name": "Output Power", "stat_t": "tsun/garagendach/batterie", "dev_cla": "power", "stat_cla": "measurement", "uniq_id": "out_power_123", "val_tpl": "{{ (value_json['out']['Power'] | int)}}", "unit_of_meas": "W", "dev": {"name": "Batterie", "sa": "Batterie", "via_device": "controller_123", "mdl": "TSOL-MSxx00", "mf": "TSUN", "ids": ["batterie_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||
tests +=1
|
||||
elif id == 'daily_gen_123':
|
||||
assert False
|
||||
elif id == 'volt_pv1_123':
|
||||
assert comp == 'sensor'
|
||||
assert d_json == json.dumps({"name": "Voltage", "stat_t": "tsun/garagendach/batterie", "dev_cla": "voltage", "stat_cla": "measurement", "uniq_id": "volt_pv1_123", "val_tpl": "{{ (value_json['pv1']['Voltage'] | float)}}", "unit_of_meas": "V", "ic": "mdi:gauge", "ent_cat": "diagnostic", "dev": {"name": "Module PV1", "sa": "Module PV1", "via_device": "batterie_123", "ids": ["bat_inp_pv1_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||
tests +=1
|
||||
elif id == 'volt_pv2_123':
|
||||
assert comp == 'sensor'
|
||||
assert d_json == json.dumps({"name": "Voltage", "stat_t": "tsun/garagendach/batterie", "dev_cla": "voltage", "stat_cla": "measurement", "uniq_id": "volt_pv2_123", "val_tpl": "{{ (value_json['pv2']['Voltage'] | float)}}", "unit_of_meas": "V", "ic": "mdi:gauge", "ent_cat": "diagnostic", "dev": {"name": "Module PV2", "sa": "Module PV2", "via_device": "batterie_123", "ids": ["bat_inp_pv2_123"]}, "o": {"name": "proxy", "sw": "unknown"}})
|
||||
tests +=1
|
||||
elif id == 'signal_123':
|
||||
assert comp == 'sensor'
|
||||
assert d_json == json.dumps({})
|
||||
tests +=1
|
||||
elif id == 'inv_count_456':
|
||||
assert False
|
||||
else:
|
||||
print(id)
|
||||
|
||||
assert tests==4
|
||||
|
||||
def test_exception_and_calc(inverter_data: bytes):
|
||||
|
||||
# patch table to convert temperature from °F to °C
|
||||
ofs = RegisterMap.map[0x420100d8]['offset']
|
||||
RegisterMap.map[0x420100d8]['quotient'] = 1.8
|
||||
RegisterMap.map[0x420100d8]['offset'] = -32/1.8
|
||||
ofs = RegisterMap.map_02b0[0x420100d8]['offset']
|
||||
RegisterMap.map_02b0[0x420100d8]['quotient'] = 1.8
|
||||
RegisterMap.map_02b0[0x420100d8]['offset'] = -32/1.8
|
||||
# map PV1_VOLTAGE to invalid register
|
||||
RegisterMap.map[0x420100e0]['reg'] = Register.TEST_REG2
|
||||
RegisterMap.map_02b0[0x420100e0]['reg'] = Register.TEST_REG2
|
||||
# set invalid maping entry for OUTPUT_POWER (string instead of dict type)
|
||||
backup = RegisterMap.map[0x420100de]
|
||||
RegisterMap.map[0x420100de] = 'invalid_entry'
|
||||
backup = RegisterMap.map_02b0[0x420100de]
|
||||
RegisterMap.map_02b0[0x420100de] = 'invalid_entry'
|
||||
|
||||
i = InfosG3P(client_mode=False)
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
assert math.isclose(12.2222, round (i.get_db_value(Register.INVERTER_TEMP, 0),4), rel_tol=1e-09, abs_tol=1e-09)
|
||||
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1)
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0)
|
||||
assert build_msg[32:0xde] == inverter_data[32:0xde]
|
||||
assert build_msg[0xde:0xe2] == b'\x00\x00\x00\x00'
|
||||
assert build_msg[0xe2:-1] == inverter_data[0xe2:-1]
|
||||
|
||||
|
||||
# remove a table entry and test parsing and building
|
||||
del RegisterMap.map[0x420100d8]['quotient']
|
||||
del RegisterMap.map[0x420100d8]['offset']
|
||||
del RegisterMap.map_02b0[0x420100d8]['quotient']
|
||||
del RegisterMap.map_02b0[0x420100d8]['offset']
|
||||
|
||||
i.db.clear()
|
||||
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
assert 54 == i.get_db_value(Register.INVERTER_TEMP, 0)
|
||||
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1)
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0)
|
||||
assert build_msg[32:0xd8] == inverter_data[32:0xd8]
|
||||
assert build_msg[0xd8:0xe2] == b'\x006\x00\x00\x02X\x00\x00\x00\x00'
|
||||
assert build_msg[0xe2:-1] == inverter_data[0xe2:-1]
|
||||
|
||||
# test restore table
|
||||
RegisterMap.map[0x420100d8]['offset'] = ofs
|
||||
RegisterMap.map[0x420100e0]['reg'] = Register.PV1_VOLTAGE # reset mapping
|
||||
RegisterMap.map[0x420100de] = backup # reset mapping
|
||||
RegisterMap.map_02b0[0x420100d8]['offset'] = ofs
|
||||
RegisterMap.map_02b0[0x420100e0]['reg'] = Register.PV1_VOLTAGE # reset mapping
|
||||
RegisterMap.map_02b0[0x420100de] = backup # reset mapping
|
||||
|
||||
# test orginial table
|
||||
i.db.clear()
|
||||
for key, update in i.parse (inverter_data, 0x42, 1):
|
||||
for key, update in i.parse (inverter_data, 0x42, 1, 0x02b0):
|
||||
pass # side effect is calling generator i.parse()
|
||||
assert 14 == i.get_db_value(Register.INVERTER_TEMP, 0)
|
||||
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1)
|
||||
build_msg = i.build(len(inverter_data), 0x42, 1, 0x02b0)
|
||||
assert build_msg[32:-1] == inverter_data[32:-1]
|
||||
|
||||
@@ -7,18 +7,26 @@ from server import get_log_level
|
||||
|
||||
def test_get_log_level():
|
||||
|
||||
with patch.dict(os.environ, {'LOG_LVL': ''}):
|
||||
with patch.dict(os.environ, {}):
|
||||
log_lvl = get_log_level()
|
||||
assert log_lvl == logging.INFO
|
||||
assert log_lvl == None
|
||||
|
||||
with patch.dict(os.environ, {'LOG_LVL': 'DEBUG'}):
|
||||
log_lvl = get_log_level()
|
||||
assert log_lvl == logging.DEBUG
|
||||
|
||||
with patch.dict(os.environ, {'LOG_LVL': 'INFO'}):
|
||||
log_lvl = get_log_level()
|
||||
assert log_lvl == logging.INFO
|
||||
|
||||
with patch.dict(os.environ, {'LOG_LVL': 'WARN'}):
|
||||
log_lvl = get_log_level()
|
||||
assert log_lvl == logging.WARNING
|
||||
|
||||
with patch.dict(os.environ, {'LOG_LVL': 'ERROR'}):
|
||||
log_lvl = get_log_level()
|
||||
assert log_lvl == logging.ERROR
|
||||
|
||||
with patch.dict(os.environ, {'LOG_LVL': 'UNKNOWN'}):
|
||||
log_lvl = get_log_level()
|
||||
assert log_lvl == logging.INFO
|
||||
assert log_lvl == None
|
||||
|
||||
@@ -11,6 +11,7 @@ from cnf.config import Config
|
||||
from infos import Infos, Register
|
||||
from modbus import Modbus
|
||||
from messages import State, Message
|
||||
from proxy import Proxy
|
||||
|
||||
|
||||
pytest_plugins = ('pytest_asyncio',)
|
||||
@@ -24,6 +25,8 @@ heartbeat = 60
|
||||
|
||||
class Mqtt():
|
||||
def __init__(self):
|
||||
self.clear()
|
||||
def clear(self):
|
||||
self.key = ''
|
||||
self.data = ''
|
||||
|
||||
@@ -50,7 +53,6 @@ class MemoryStream(SolarmanV5):
|
||||
self.mb_timeout = 0.5
|
||||
self.sent_pdu = b''
|
||||
self.ifc.tx_fifo.reg_trigger(self.write_cb)
|
||||
self.mqtt = Mqtt()
|
||||
self.__msg = msg
|
||||
self.__msg_len = len(msg)
|
||||
self.__chunks = chunks
|
||||
@@ -62,7 +64,6 @@ class MemoryStream(SolarmanV5):
|
||||
self.db.stat['proxy']['AT_Command'] = 0
|
||||
self.db.stat['proxy']['AT_Command_Blocked'] = 0
|
||||
self.test_exception_async_write = False
|
||||
self.entity_prfx = ''
|
||||
self.at_acl = {'mqtt': {'allow': ['AT+'], 'block': ['AT+WEBU']}, 'tsun': {'allow': ['AT+Z', 'AT+UPURL', 'AT+SUPDATE', 'AT+TIME'], 'block': ['AT+WEBU']}}
|
||||
self.key = ''
|
||||
self.data = ''
|
||||
@@ -85,8 +86,8 @@ class MemoryStream(SolarmanV5):
|
||||
self.__chunk_idx = 0
|
||||
|
||||
def publish_mqtt(self, key, data):
|
||||
self.key = key
|
||||
self.data = data
|
||||
Proxy.mqtt.key = key
|
||||
Proxy.mqtt.data = data
|
||||
|
||||
def _read(self) -> int:
|
||||
copied_bytes = 0
|
||||
@@ -130,6 +131,12 @@ def get_sn() -> bytes:
|
||||
def get_sn_int() -> int:
|
||||
return 2070233889
|
||||
|
||||
def get_dcu_sn() -> bytes:
|
||||
return b'\x20\x43\x65\x7b'
|
||||
|
||||
def get_dcu_sn_int() -> int:
|
||||
return 2070233888
|
||||
|
||||
def get_inv_no() -> bytes:
|
||||
return b'T170000000000001'
|
||||
|
||||
@@ -560,6 +567,17 @@ def at_command_rsp_msg(): # 0x1510
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def at_command_interim_rsp_msg(): # 0x0510
|
||||
msg = b'\xa5\x25\x00\x10\x05\x03\x03' +get_sn() +b'\x08\x01'
|
||||
msg += total()
|
||||
msg += hb()
|
||||
msg += b'\x00\x00\x00\x00+ok=10\x2c'
|
||||
msg += b'start download\x0d\x0a'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def heartbeat_ind_msg(): # 0x4710
|
||||
msg = b'\xa5\x01\x00\x10\x47\x10\x84' +get_sn()
|
||||
@@ -615,6 +633,15 @@ def msg_modbus_cmd_fwd():
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_modbus_cmd_seq():
|
||||
msg = b'\xa5\x17\x00\x10\x45\x03\x02' +get_sn() +b'\x05\x26\x30\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x01\x06\x01\x00\x01'
|
||||
msg += b'\x03\xe8'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_modbus_cmd_crc_err():
|
||||
msg = b'\xa5\x17\x00\x10\x45\x03\x02' +get_sn() +b'\x02\xb0\x02'
|
||||
@@ -637,6 +664,32 @@ def msg_modbus_rsp(): # 0x1510
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_modbus_interim_rsp(): # 0x0510
|
||||
msg = b'\xa5\x3b\x00\x10\x15\x03\x03' +get_sn() +b'\x02\x01'
|
||||
msg += total()
|
||||
msg += hb()
|
||||
msg += b'\x0a\xe2\xfa\x33\x01\x03\x28\x40\x10\x08\xd8'
|
||||
msg += b'\x00\x00\x13\x87\x00\x31\x00\x68\x02\x58\x00\x00\x01\x53\x00\x02'
|
||||
msg += b'\x00\x00\x01\x52\x00\x02\x00\x00\x01\x53\x00\x03\x00\x00\x00\x04'
|
||||
msg += b'\x00\x01\x00\x00\x6c\x68'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_modbus_rsp_inv_id2(): # 0x1510
|
||||
msg = b'\xa5\x3b\x00\x10\x15\x03\x03' +get_sn() +b'\x02\x01'
|
||||
msg += total()
|
||||
msg += hb()
|
||||
msg += b'\x0a\xe2\xfa\x33\x02\x03\x28\x40\x10\x08\xd8'
|
||||
msg += b'\x00\x00\x13\x87\x00\x31\x00\x68\x02\x58\x00\x00\x01\x53\x00\x02'
|
||||
msg += b'\x00\x00\x01\x52\x00\x02\x00\x00\x01\x53\x00\x03\x00\x00\x00\x04'
|
||||
msg += b'\x00\x01\x00\x00\x2a\xaa'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_modbus_invalid(): # 0x1510
|
||||
msg = b'\xa5\x3b\x00\x10\x15\x03\x03' +get_sn() +b'\x02\x00'
|
||||
@@ -672,9 +725,94 @@ def msg_unknown_cmd_rsp(): # 0x1510
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_modbus_rsp(): # 0x1510
|
||||
msg = b'\xa5\x6d\x00\x10\x15\x03\x03' +get_dcu_sn() +b'\x02\x01'
|
||||
msg += total()
|
||||
msg += hb()
|
||||
msg += b'\x4d\x0d\x84\x34\x01\x03\x5a\x34\x31\x30\x31'
|
||||
msg += b'\x32\x34\x30\x37\x30\x31\x34\x39\x30\x33\x31\x34\x00\x32\x00\x00'
|
||||
msg += b'\x00\x32\x00\x00\x00\x00\x10\x7b\x00\x02\x00\x02\x14\x9b\xfe\xfd'
|
||||
msg += b'\x25\x28\x0c\xe1\x0c\xde\x0c\xe1\x0c\xe1\x0c\xe0\x0c\xe1\x0c\xe3'
|
||||
msg += b'\x0c\xdf\x0c\xe0\x0c\xe2\x0c\xe1\x0c\xe1\x0c\xe2\x0c\xe2\x0c\xe3'
|
||||
msg += b'\x0c\xdf\x00\x14\x00\x14\x00\x13\x0f\x94\x01\x4a\x00\x01\x00\x15'
|
||||
msg += b'\x00\x00\x02\x05\x02\x01\x14\xab'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_dev_ind_msg(): # 0x4110
|
||||
msg = b'\xa5\x3a\x01\x10\x41\x91\x01' +get_dcu_sn() +b'\x02\xc6\xde\x2d\x32'
|
||||
msg += b'\x27\x00\x00\x00\x00\x00\x00\x00\x05\x3c\x78\x01\x5c\x01\x4c\x53'
|
||||
msg += b'\x57\x35\x5f\x30\x31\x5f\x33\x30\x32\x36\x5f\x4e\x53\x5f\x30\x35'
|
||||
msg += b'\x5f\x30\x31\x2e\x30\x30\x2e\x30\x30\x2e\x30\x30\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\xd4\x27\x87\x12\xad\xc0\x31\x39\x32\x2e'
|
||||
msg += b'\x31\x36\x38\x2e\x39\x2e\x31\x34\x00\x00\x00\x00\x01\x00\x01\x26'
|
||||
msg += b'\x30\x0f\x00\xff\x56\x31\x2e\x31\x2e\x30\x30\x2e\x30\x42\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\x00\x00'
|
||||
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\x7a\x75\x68\x61\x75\x73\x65\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\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\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\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x08\x01\x01\x01\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x01'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_dev_rsp_msg(): # 0x1110
|
||||
msg = b'\xa5\x0a\x00\x10\x11\x92\x01' +get_dcu_sn() +b'\x02\x01'
|
||||
msg += total()
|
||||
msg += hb()
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_data_ind_msg(): # 0x4210
|
||||
msg = b'\xa5\x6f\x00\x10\x42\x92\x02' +get_dcu_sn() +b'\x01\x26\x30\xc7\xde'
|
||||
msg += b'\x2d\x32\x28\x00\x00\x00\x84\x17\x79\x35\x01\x00\x4c\x12\x00\x00'
|
||||
msg += b'\x34\x31\x30\x31\x32\x34\x30\x37\x30\x31\x34\x39\x30\x33\x31\x34'
|
||||
msg += b'\x0d\x3a\x00\x00\x0d\x2c\x00\x00\x00\x00\x08\x20\x00\x00\x00\x00'
|
||||
msg += b'\x14\x0e\xff\xfe\x03\xe8\x0c\x89\x0c\x89\x0c\x89\x0c\x8a\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x8a\x0c\x89\x0c\x89\x0c\x8a\x0c\x8a\x0c\x89\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x89\x0c\x88\x00\x0f\x00\x0f\x00\x0f\x00\x0e\x00\x00'
|
||||
msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_data_rsp_msg(): # 0x1210
|
||||
msg = b'\xa5\x0a\x00\x10\x12\x93\x02' +get_dcu_sn() +b'\x01\x01'
|
||||
msg += total()
|
||||
msg += hb()
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def config_tsun_allow_all():
|
||||
Config.act_config = {'solarman':{'enabled': True}, 'inverters':{'allow_all':True}}
|
||||
Config.act_config = {
|
||||
'ha':{
|
||||
'auto_conf_prefix': 'homeassistant',
|
||||
'discovery_prefix': 'homeassistant',
|
||||
'entity_prefix': 'tsun',
|
||||
'proxy_node_id': 'test_1',
|
||||
'proxy_unique_id': ''
|
||||
},
|
||||
'solarman':{'enabled': True}, 'inverters':{'allow_all':True}}
|
||||
Proxy.class_init()
|
||||
Proxy.mqtt = Mqtt() # set dummy mqtt instance
|
||||
|
||||
@pytest.fixture
|
||||
def config_no_tsun_inv1():
|
||||
@@ -682,7 +820,29 @@ def config_no_tsun_inv1():
|
||||
|
||||
@pytest.fixture
|
||||
def config_tsun_inv1():
|
||||
Config.act_config = {'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 688}}}
|
||||
Config.act_config = {
|
||||
'ha':{
|
||||
'auto_conf_prefix': 'homeassistant',
|
||||
'discovery_prefix': 'homeassistant',
|
||||
'entity_prefix': 'tsun',
|
||||
'proxy_node_id': 'test_1',
|
||||
'proxy_unique_id': ''
|
||||
},
|
||||
'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 0}}}
|
||||
Proxy.class_init()
|
||||
Proxy.mqtt = Mqtt()
|
||||
|
||||
@pytest.fixture
|
||||
def config_tsun_scan():
|
||||
Config.act_config = {'solarman':{'enabled': True},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'modbus_scanning': {'start': 0xffc0, 'step': 0x40, 'bytes':20}, 'suggested_area':'roof', 'sensor_list': 0}}}
|
||||
|
||||
@pytest.fixture
|
||||
def config_tsun_scan_dcu():
|
||||
Config.act_config = {'solarman':{'enabled': True},'inverters':{'4100000000000001':{'monitor_sn': 2070233888, 'node_id':'inv1', 'modbus_polling': True, 'modbus_scanning': {'start': 0x0000, 'step': 0x100, 'bytes':0x2d}, 'client_mode': {'host': '192.168.1.1.'}, 'suggested_area':'roof', 'sensor_list': 0}}}
|
||||
|
||||
@pytest.fixture
|
||||
def config_tsun_dcu1():
|
||||
Config.act_config = {'solarman':{'enabled': True},'batteries':{'4100000000000001':{'monitor_sn': 2070233888, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 0}}}
|
||||
|
||||
def test_read_message(device_ind_msg):
|
||||
Config.act_config = {'solarman':{'enabled': True}}
|
||||
@@ -963,6 +1123,34 @@ def test_read_two_messages3(config_tsun_allow_all, device_ind_msg2, device_rsp_m
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
m.close()
|
||||
|
||||
def test_read_two_messages4(config_tsun_dcu1, dcu_dev_ind_msg, dcu_dev_rsp_msg, dcu_data_ind_msg, dcu_data_rsp_msg):
|
||||
_ = config_tsun_dcu1
|
||||
m = MemoryStream(dcu_dev_ind_msg, (0,))
|
||||
m.append_msg(dcu_data_ind_msg)
|
||||
assert 0 == m.sensor_list
|
||||
m._init_new_client_conn()
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 2
|
||||
assert m.header_len==11
|
||||
assert m.snr == 2070233888
|
||||
assert m.unique_id == '2070233888'
|
||||
assert m.msg_recvd[0]['control']==0x4110
|
||||
assert m.msg_recvd[0]['seq']=='01:92'
|
||||
assert m.msg_recvd[0]['data_len']==314
|
||||
assert m.msg_recvd[1]['control']==0x4210
|
||||
assert m.msg_recvd[1]['seq']=='02:93'
|
||||
assert m.msg_recvd[1]['data_len']==111
|
||||
assert '3026' == m.db.get_db_value(Register.SENSOR_LIST, None)
|
||||
assert 0x3026 == m.sensor_list
|
||||
assert m.ifc.fwd_fifo.get()==dcu_dev_ind_msg+dcu_data_ind_msg
|
||||
assert m.ifc.tx_fifo.get()==dcu_dev_rsp_msg+dcu_data_rsp_msg
|
||||
|
||||
m._init_new_client_conn()
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
m.close()
|
||||
|
||||
def test_unkown_frame_code(config_tsun_inv1, inverter_ind_msg_81, inverter_rsp_msg_81):
|
||||
_ = config_tsun_inv1
|
||||
m = MemoryStream(inverter_ind_msg_81, (0,))
|
||||
@@ -1331,8 +1519,8 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.sent_pdu == b''
|
||||
assert str(m.seq) == '01:01'
|
||||
assert m.mqtt.key == ''
|
||||
assert m.mqtt.data == ""
|
||||
assert Proxy.mqtt.key == ''
|
||||
assert Proxy.mqtt.data == ""
|
||||
|
||||
m.append_msg(inverter_ind_msg)
|
||||
m.read() # read inverter ind
|
||||
@@ -1348,8 +1536,8 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
|
||||
m.sent_pdu = bytearray()
|
||||
|
||||
assert str(m.seq) == '02:03'
|
||||
assert m.mqtt.key == ''
|
||||
assert m.mqtt.data == ""
|
||||
assert Proxy.mqtt.key == ''
|
||||
assert Proxy.mqtt.data == ""
|
||||
|
||||
m.append_msg(at_command_rsp_msg)
|
||||
m.read() # read at resp
|
||||
@@ -1358,8 +1546,9 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
|
||||
assert m.ifc.rx_get()==b''
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert m.key == 'at_resp'
|
||||
assert m.data == "+ok"
|
||||
assert Proxy.mqtt.key == 'tsun/at_resp'
|
||||
assert Proxy.mqtt.data == "+ok"
|
||||
Proxy.mqtt.clear() # clear last test result
|
||||
|
||||
m.sent_pdu = bytearray()
|
||||
m.test_exception_async_write = True
|
||||
@@ -1371,8 +1560,8 @@ async def test_at_cmd(config_tsun_allow_all, device_ind_msg, device_rsp_msg, inv
|
||||
assert m.sent_pdu == b''
|
||||
assert str(m.seq) == '03:04'
|
||||
assert m.forward_at_cmd_resp == False
|
||||
assert m.mqtt.key == ''
|
||||
assert m.mqtt.data == ""
|
||||
assert Proxy.mqtt.key == ''
|
||||
assert Proxy.mqtt.data == ""
|
||||
m.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -1389,8 +1578,8 @@ async def test_at_cmd_blocked(config_tsun_allow_all, device_ind_msg, device_rsp_
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert str(m.seq) == '01:01'
|
||||
assert m.mqtt.key == ''
|
||||
assert m.mqtt.data == ""
|
||||
assert Proxy.mqtt.key == ''
|
||||
assert Proxy.mqtt.data == ""
|
||||
|
||||
m.append_msg(inverter_ind_msg)
|
||||
m.read()
|
||||
@@ -1406,8 +1595,8 @@ async def test_at_cmd_blocked(config_tsun_allow_all, device_ind_msg, device_rsp_
|
||||
assert m.ifc.fwd_fifo.get()==b''
|
||||
assert str(m.seq) == '02:02'
|
||||
assert m.forward_at_cmd_resp == False
|
||||
assert m.mqtt.key == 'at_resp'
|
||||
assert m.mqtt.data == "'AT+WEBU' is forbidden"
|
||||
assert Proxy.mqtt.key == 'tsun/at_resp'
|
||||
assert Proxy.mqtt.data == "'AT+WEBU' is forbidden"
|
||||
m.close()
|
||||
|
||||
def test_at_cmd_ind(config_tsun_inv1, at_command_ind_msg):
|
||||
@@ -1496,6 +1685,29 @@ def test_msg_at_command_rsp2(config_tsun_inv1, at_command_rsp_msg):
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
m.close()
|
||||
|
||||
def test_msg_at_command_rsp3(config_tsun_inv1, at_command_interim_rsp_msg):
|
||||
_ = config_tsun_inv1
|
||||
m = MemoryStream(at_command_interim_rsp_msg)
|
||||
m.db.stat['proxy']['Unknown_Ctrl'] = 0
|
||||
m.db.stat['proxy']['Modbus_Command'] = 0
|
||||
m.db.stat['proxy']['Invalid_Msg_Format'] = 0
|
||||
m.db.stat['proxy']['Unknown_Msg'] = 0
|
||||
m.forward_at_cmd_resp = True
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.control == 0x0510
|
||||
assert str(m.seq) == '03:03'
|
||||
assert m.header_len==11
|
||||
assert m.data_len==37
|
||||
assert m.ifc.fwd_fifo.get()==at_command_interim_rsp_msg
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
assert m.db.stat['proxy']['Unknown_Msg'] == 0
|
||||
m.close()
|
||||
|
||||
def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd, msg_modbus_cmd_fwd):
|
||||
_ = config_tsun_inv1
|
||||
m = MemoryStream(b'')
|
||||
@@ -1524,6 +1736,34 @@ def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd, msg_modbus_cmd_fwd):
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
def test_msg_modbus_req_seq(config_tsun_inv1, msg_modbus_cmd_seq):
|
||||
_ = config_tsun_inv1
|
||||
m = MemoryStream(b'')
|
||||
m.snr = get_sn_int()
|
||||
m.sensor_list = 0x2b0
|
||||
m.state = State.up
|
||||
c = m.createClientStream(msg_modbus_cmd_seq)
|
||||
|
||||
m.db.stat['proxy']['Unknown_Ctrl'] = 0
|
||||
m.db.stat['proxy']['AT_Command'] = 0
|
||||
m.db.stat['proxy']['Modbus_Command'] = 0
|
||||
m.db.stat['proxy']['Invalid_Msg_Format'] = 0
|
||||
c.read() # read complete msg, and dispatch msg
|
||||
assert not c.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert c.msg_count == 1
|
||||
assert c.control == 0x4510
|
||||
assert str(c.seq) == '03:02'
|
||||
assert c.header_len==11
|
||||
assert c.data_len==23
|
||||
assert c.ifc.fwd_fifo.get()==msg_modbus_cmd_seq
|
||||
assert c.ifc.tx_fifo.get()==b''
|
||||
assert m.sent_pdu == b''
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['AT_Command'] == 0
|
||||
assert m.db.stat['proxy']['Modbus_Command'] == 0
|
||||
assert m.db.stat['proxy']['Invalid_Msg_Format'] == 0
|
||||
m.close()
|
||||
|
||||
def test_msg_modbus_req2(config_tsun_inv1, msg_modbus_cmd_crc_err):
|
||||
_ = config_tsun_inv1
|
||||
m = MemoryStream(b'')
|
||||
@@ -1762,6 +2002,79 @@ async def test_modbus_polling(config_tsun_inv1, heartbeat_ind_msg, heartbeat_rsp
|
||||
assert next(m.mb_timer.exp_count) == 4
|
||||
m.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_modbus_scaning(config_tsun_scan, heartbeat_ind_msg, heartbeat_rsp_msg, msg_modbus_rsp, msg_modbus_rsp_inv_id2):
|
||||
_ = config_tsun_scan
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
m = MemoryStream(heartbeat_ind_msg, (0x15,0x56,0))
|
||||
m.append_msg(msg_modbus_rsp)
|
||||
m.append_msg(msg_modbus_rsp_inv_id2)
|
||||
assert m.mb_scan == False
|
||||
assert asyncio.get_running_loop() == m.mb_timer.loop
|
||||
m.db.stat['proxy']['Unknown_Ctrl'] = 0
|
||||
assert m.mb_timer.tim == None
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert m.mb_scan == True
|
||||
assert m.mb_start_reg == 0xff80
|
||||
assert m.mb_step == 0x40
|
||||
assert m.mb_bytes == 0x14
|
||||
assert asyncio.get_running_loop() == m.mb_timer.loop
|
||||
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.snr == 2070233889
|
||||
assert m.control == 0x4710
|
||||
|
||||
assert m.msg_recvd[0]['control']==0x4710
|
||||
assert m.msg_recvd[0]['seq']=='84:11'
|
||||
assert m.msg_recvd[0]['data_len']==0x1
|
||||
|
||||
assert m.ifc.tx_fifo.get()==heartbeat_rsp_msg
|
||||
assert m.ifc.fwd_fifo.get()==heartbeat_ind_msg
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
|
||||
m.ifc.tx_clear() # clear send buffer for next test
|
||||
assert isclose(m.mb_timeout, 0.5)
|
||||
assert next(m.mb_timer.exp_count) == 0
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.sent_pdu==b'\xa5\x17\x00\x10E\x12\x84!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00' \
|
||||
b'\x00\x00\x00\x00\x00\x00\x01\x03\xff\xc0\x00\x14\x75\xed\x33\x15'
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 2
|
||||
assert m.msg_recvd[1]['control']==0x1510
|
||||
assert m.msg_recvd[1]['seq']=='03:03'
|
||||
assert m.msg_recvd[1]['data_len']==0x3b
|
||||
assert m.mb.last_addr == 1
|
||||
assert m.mb.last_fcode == 3
|
||||
assert m.mb.last_reg == 0xffc0 # mb_start_reg + mb_step
|
||||
assert m.mb.last_len == 20
|
||||
assert m.mb.err == 0
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.sent_pdu==b'\xa5\x17\x00\x10E\x04\x03!Ce{\x02\xb0\x02\x00\x00\x00\x00\x00\x00' \
|
||||
b'\x00\x00\x00\x00\x00\x00\x02\x03\x00\x00\x00\x14\x45\xf6\xbf\x15'
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 3
|
||||
assert m.msg_recvd[2]['control']==0x1510
|
||||
assert m.msg_recvd[2]['seq']=='03:03'
|
||||
assert m.msg_recvd[2]['data_len']==0x3b
|
||||
assert m.mb.last_addr == 2
|
||||
assert m.mb.last_fcode == 3
|
||||
assert m.mb.last_reg == 0x0000 # mb_start_reg + mb_step
|
||||
assert m.mb.last_len == 20
|
||||
assert m.mb.err == 0
|
||||
|
||||
assert next(m.mb_timer.exp_count) == 3
|
||||
m.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_client_mode(config_tsun_inv1, str_test_ip):
|
||||
_ = config_tsun_inv1
|
||||
@@ -1794,6 +2107,79 @@ async def test_start_client_mode(config_tsun_inv1, str_test_ip):
|
||||
assert next(m.mb_timer.exp_count) == 3
|
||||
m.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_start_client_mode_scan(config_tsun_scan_dcu, str_test_ip, dcu_modbus_rsp):
|
||||
_ = config_tsun_scan_dcu
|
||||
assert asyncio.get_running_loop()
|
||||
m = MemoryStream(dcu_modbus_rsp, (131,0,))
|
||||
m.append_msg(dcu_modbus_rsp)
|
||||
assert m.state == State.init
|
||||
assert m.no_forwarding == False
|
||||
assert m.mb_timer.tim == None
|
||||
assert asyncio.get_running_loop() == m.mb_timer.loop
|
||||
await m.send_start_cmd(get_dcu_sn_int(), str_test_ip, False, m.mb_first_timeout)
|
||||
assert m.mb_start_reg == 0x0000
|
||||
assert m.mb_step == 0x100
|
||||
assert m.mb_bytes == 0x2d
|
||||
|
||||
assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x01\x00 Ce{\x02&0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00-\x85\xd7\x95\x15')
|
||||
assert m.mb_scan == True
|
||||
m.mb_step = 0
|
||||
assert m.db.get_db_value(Register.IP_ADDRESS) == str_test_ip
|
||||
assert isclose(m.db.get_db_value(Register.POLLING_INTERVAL), 0.5)
|
||||
assert m.db.get_db_value(Register.HEARTBEAT_INTERVAL) == 120
|
||||
|
||||
assert m.state == State.up
|
||||
assert m.no_forwarding == True
|
||||
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
assert isclose(m.mb_timeout, 0.5)
|
||||
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.msg_recvd[0]['control']==0x1510
|
||||
assert m.msg_recvd[0]['seq']=='03:03'
|
||||
assert m.msg_recvd[0]['data_len']==109
|
||||
assert m.mb.last_addr == 1
|
||||
assert m.mb.last_fcode == 3
|
||||
assert m.mb.last_reg == 0x0000 # mb_start_reg + mb_step
|
||||
assert m.mb.last_len == 45
|
||||
assert m.mb.err == 0
|
||||
|
||||
assert isclose(m.db.get_db_value(Register.BATT_PWR, None), -136.6225)
|
||||
assert isclose(m.db.get_db_value(Register.BATT_OUT_PWR, None), 131.604)
|
||||
assert isclose(m.db.get_db_value(Register.BATT_PV_PWR, None), 0.0)
|
||||
assert m.new_data['batterie'] == True
|
||||
m.new_data['batterie'] = False
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.sent_pdu==bytearray(b'\xa5\x17\x00\x10E\x04\x03 Ce{\x02&0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x03\x00\x00\x00-\x85\xd7\x9b\x15')
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 2
|
||||
assert m.msg_recvd[1]['control']==0x1510
|
||||
assert m.msg_recvd[1]['seq']=='03:03'
|
||||
assert m.msg_recvd[1]['data_len']==109
|
||||
assert m.mb.last_addr == 1
|
||||
assert m.mb.last_fcode == 3
|
||||
assert m.mb.last_reg == 0x0000 # mb_start_reg + mb_step
|
||||
assert m.mb.last_len == 45
|
||||
assert m.mb.err == 0
|
||||
|
||||
assert isclose(m.db.get_db_value(Register.BATT_PWR, None), -136.6225)
|
||||
assert isclose(m.db.get_db_value(Register.BATT_OUT_PWR, None), 131.604)
|
||||
assert isclose(m.db.get_db_value(Register.BATT_PV_PWR, None), 0.0)
|
||||
assert m.new_data['batterie'] == False
|
||||
|
||||
assert next(m.mb_timer.exp_count) == 1
|
||||
|
||||
m.close()
|
||||
|
||||
def test_timeout(config_tsun_inv1):
|
||||
_ = config_tsun_inv1
|
||||
m = MemoryStream(b'')
|
||||
|
||||
@@ -7,6 +7,7 @@ from cnf.config import Config
|
||||
from infos import Infos, Register
|
||||
from modbus import Modbus
|
||||
from messages import State
|
||||
from mock import patch
|
||||
|
||||
|
||||
pytest_plugins = ('pytest_asyncio',)
|
||||
@@ -468,15 +469,15 @@ def config_tsun_allow_all():
|
||||
|
||||
@pytest.fixture
|
||||
def config_no_tsun_inv1():
|
||||
Config.act_config = {'tsun':{'enabled': False},'inverters':{'R170000000000001':{'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof'}}}
|
||||
Config.act_config = {'tsun':{'enabled': False},'inverters':{'R170000000000001':{'node_id':'inv1', 'sensor_list': 0, 'modbus_polling': True, 'suggested_area':'roof'}}}
|
||||
|
||||
@pytest.fixture
|
||||
def config_tsun_inv1():
|
||||
Config.act_config = {'tsun':{'enabled': True},'inverters':{'R170000000000001':{'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof'}}}
|
||||
Config.act_config = {'tsun':{'enabled': True},'inverters':{'R170000000000001':{'node_id':'inv1', 'sensor_list': 0x01900001, 'modbus_polling': True, 'suggested_area':'roof'}}}
|
||||
|
||||
@pytest.fixture
|
||||
def config_no_modbus_poll():
|
||||
Config.act_config = {'tsun':{'enabled': True},'inverters':{'R170000000000001':{'node_id':'inv1', 'modbus_polling': False, 'suggested_area':'roof'}}}
|
||||
Config.act_config = {'tsun':{'enabled': True},'inverters':{'R170000000000001':{'node_id':'inv1', 'sensor_list': 0, 'modbus_polling': False, 'suggested_area':'roof'}}}
|
||||
|
||||
@pytest.fixture
|
||||
def msg_ota_req(): # Over the air update request from tsun cloud
|
||||
@@ -817,6 +818,236 @@ def multiple_recv_buf(): # There are three message in the buffer, but the second
|
||||
msg += b'\x30\x00\x00\x00\x3c\x54\x05\x41\x2c\x42\x2c\x43' # | 0...<T.A,B,C'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_controller_ms3000_ind(): # Data indication from the controller
|
||||
msg = b'\x00\x00\x04\xdf\x10R170000000000001\x91\x71\x0e\x10\x00\x00\x10R170000000000001'
|
||||
msg += b'\x01\x00\x00\x01'
|
||||
msg += b'\x95\x18\x5e\x19\x98\x00\x00\x00\x39\x00\x09\x2b\xa8\x54\x10\x52' # | ..^.....9..+.T.R
|
||||
msg += b'\x53\x57\x5f\x34\x30\x30\x5f\x56\x32\x2e\x30\x31\x2e\x31\x33\x00' # | SW_400_V2.01.13.
|
||||
msg += b'\x09\x27\xc0\x54\x06\x52\x61\x79\x6d\x6f\x6e\x00\x09\x2f\x90\x54' # | .'.T.Raymon../.T
|
||||
msg += b'\x0b\x52\x53\x57\x2d\x31\x2d\x31\x30\x30\x30\x31\x00\x09\x5a\x88' # | .RSW-1-10001..Z.
|
||||
msg += b'\x54\x0f\x74\x2e\x72\x61\x79\x6d\x6f\x6e\x69\x6f\x74\x2e\x63\x6f' # | T.t.raymoniot.co
|
||||
msg += b'\x6d\x00\x09\x5a\xec\x54\x1c\x6c\x6f\x67\x67\x65\x72\x2e\x74\x61' # | m..Z.T.logger.ta
|
||||
msg += b'\x6c\x65\x6e\x74\x2d\x6d\x6f\x6e\x69\x74\x6f\x72\x69\x6e\x67\x2e' # | lent-monitoring.
|
||||
msg += b'\x63\x6f\x6d\x00\x0d\x2f\x00\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | com../.T........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x32\xe8\x54\x10\xff' # | ...........2.T..
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
|
||||
msg += b'\x0d\x36\xd0\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .6.T............
|
||||
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x3a\xb8\x54\x10\xff\xff\xff\xff\xff' # | .......:.T......
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x3e\xa0\x54' # | .............>.T
|
||||
msg += b'\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\x00\x0d\x42\x88\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ...B.T..........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x46\x70\x54\x10\xff\xff\xff' # | .........FpT....
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x4a' # | ...............J
|
||||
msg += b'\x58\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | XT..............
|
||||
msg += b'\xff\xff\xff\x00\x0d\x4e\x40\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .....N@T........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x52\x28\x54\x10\xff' # | ...........R(T..
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
|
||||
msg += b'\x0d\x56\x10\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .V.T............
|
||||
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x59\xf8\x54\x10\xff\xff\xff\xff\xff' # | .......Y.T......
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x5d\xe0\x54' # | .............].T
|
||||
msg += b'\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\x00\x0d\x61\xc8\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ...a.T..........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x65\xb0\x54\x10\xff\xff\xff' # | .........e.T....
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x69' # | ...............i
|
||||
msg += b'\x98\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .T..............
|
||||
msg += b'\xff\xff\xff\x00\x0d\x6d\x80\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .....m.T........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x71\x68\x54\x10\xff' # | ...........qhT..
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
|
||||
msg += b'\x0d\x75\x50\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .uPT............
|
||||
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x79\x38\x54\x10\xff\xff\xff\xff\xff' # | .......y8T......
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x7d\x20\x54' # | .............} T
|
||||
msg += b'\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\x00\x0d\x81\x08\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .....T..........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x84\xf0\x54\x10\xff\xff\xff' # | ...........T....
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x88' # | ................
|
||||
msg += b'\xd8\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | .T..............
|
||||
msg += b'\xff\xff\xff\x00\x0d\x8c\xc0\x54\x10\xff\xff\xff\xff\xff\xff\xff' # | .......T........
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x90\xa8\x54\x10\xff' # | .............T..
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00' # | ................
|
||||
msg += b'\x0d\x94\x90\x54\x10\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ...T............
|
||||
msg += b'\xff\xff\xff\xff\xff\x00\x0d\x98\x78\x54\x10\xff\xff\xff\xff\xff' # | ........xT......
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x0d\x9c\x60\x54' # | ..............`T
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff' # | ................
|
||||
msg += b'\x00\x0d\x00\x20\x49\x00\x00\x00\x01\x00\x0c\x35\x00\x49\x00\x00' # | ... I......5.I..
|
||||
msg += b'\x00\x62\x00\x0c\x96\xa8\x49\x00\x00\x01\x4f\x00\x0c\x7f\x38\x49' # | .b....I...O...8I
|
||||
msg += b'\x00\x00\x00\x01\x00\x0c\xfc\x38\x49\x00\x00\x00\x01\x00\x0c\xf8' # | .......8I.......
|
||||
msg += b'\x50\x49\x00\x00\x01\x2c\x00\x0c\x63\xe0\x49\x00\x00\x00\x00\x00' # | PI...,..c.I.....
|
||||
msg += b'\x0c\x67\xc8\x49\x00\x00\x00\x00\x00\x0c\x50\x58\x49\x00\x00\x00' # | .g.I......PXI...
|
||||
msg += b'\x01\x00\x09\x5e\x70\x49\x00\x00\x13\x8d\x00\x09\x5e\xd4\x49\x00' # | ...^pI......^.I.
|
||||
msg += b'\x00\x13\x8d\x00\x09\x5b\x50\x49\x00\x00\x00\x02\x00\x0d\x04\x08' # | .....[PI........
|
||||
msg += b'\x49\x00\x00\x00\x00\x00\x07\xa1\x84\x49\x00\x00\x00\x01\x00\x0c' # | I........I......
|
||||
msg += b'\x50\x59\x49\x00\x00\x00\x2d\x00\x0d\x1f\x60\x49\x00\x00\x00\x00' # | PYI...-...`I....
|
||||
msg += b'\x00\x0d\x23\x48\x49\xff\xff\xff\xff\x00\x0d\x27\x30\x49\xff\xff' # | ..#HI......'0I..
|
||||
msg += b'\xff\xff\x00\x0d\x2b\x18\x4c\x00\x00\x00\x00\xff\xff\xff\xff\x00' # | ....+.L.........
|
||||
msg += b'\x0c\xa2\x60\x49\x00\x00\x00\x00\x00\x0d\xa0\x48\x49\x00\x00\x00' # | ..`I.......HI...
|
||||
msg += b'\x00\x00\x0d\xa4\x30\x49\x00\x00\x00\xff\x00\x0d\xa8\x18\x49\x00' # | ....0I........I.
|
||||
msg += b'\x00\x00\xff'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_inverter_ms3000_ind(): # Data indication from the controller
|
||||
msg = b'\x00\x00\x08\xff\x10R170000000000001\x91\x04\x01\x90\x00\x00\x10R170000000000001'
|
||||
msg += b'\x01\x00\x00\x01'
|
||||
msg += b'\x95\x18\x5e\x1d\x80\x00\x00\x01\x2c\x00\x00\x00\x64\x53\x00\x00' # | ..^.....,...dS..
|
||||
msg += b'\x00\x00\x00\xc8\x53\x44\x00\x00\x00\x01\x2c\x53\x00\x00\x00\x00' # | ....SD....,S....
|
||||
msg += b'\x01\x90\x49\x00\x00\x00\x00\x00\x00\x01\x91\x53\x00\x00\x00\x00' # | ..I........S....
|
||||
msg += b'\x01\x92\x53\x00\x00\x00\x00\x01\x93\x53\x00\x00\x00\x00\x01\x94' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x01\x95\x53\x00\x00\x00\x00\x01\x96\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x01\x97\x53\x00\x00\x00\x00\x01\x98\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x01\x99\x53\x00\x00\x00\x00\x01\x9a\x53\x00\x00\x00\x00\x01' # | ...S......S.....
|
||||
msg += b'\x9b\x53\x00\x00\x00\x00\x01\x9c\x53\x00\x00\x00\x00\x01\x9d\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x01\x9e\x53\x00\x00\x00\x00\x01\x9f\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x01\xa0\x53\x00\x00\x00\x00\x01\xf4\x49\x00\x00\x00\x00' # | ....S......I....
|
||||
msg += b'\x00\x00\x01\xf5\x53\x00\x00\x00\x00\x01\xf6\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x01\xf7\x53\x00\x00\x00\x00\x01\xf8\x53\x00\x00\x00\x00\x01\xf9' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x01\xfa\x53\x00\x00\x00\x00\x01\xfb\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x01\xfc\x53\x00\x00\x00\x00\x01\xfd\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x01\xfe\x53\x00\x00\x00\x00\x01\xff\x53\x00\x00\x00\x00\x02' # | ...S......S.....
|
||||
msg += b'\x00\x53\x00\x00\x00\x00\x02\x01\x53\x00\x00\x00\x00\x02\x02\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x02\x03\x53\x00\x00\x00\x00\x02\x04\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x02\x58\x49\x00\x00\x00\x00\x00\x00\x02\x59\x53\x00\x00' # | ...XI.......YS..
|
||||
msg += b'\x00\x00\x02\x5a\x53\x00\x00\x00\x00\x02\x5b\x53\x00\x00\x00\x00' # | ...ZS.....[S....
|
||||
msg += b'\x02\x5c\x53\x00\x00\x00\x00\x02\x5d\x53\x00\x00\x00\x00\x02\x5e' # | .\S.....]S.....^
|
||||
msg += b'\x53\x00\x00\x00\x00\x02\x5f\x53\x00\x00\x00\x00\x02\x60\x53\x00' # | S....._S.....`S.
|
||||
msg += b'\x00\x00\x00\x02\x61\x53\x00\x00\x00\x00\x02\x62\x53\x00\x00\x00' # | ....aS.....bS...
|
||||
msg += b'\x00\x02\x63\x53\x00\x00\x00\x00\x02\x64\x53\x00\x00\x00\x00\x02' # | ..cS.....dS.....
|
||||
msg += b'\x65\x53\x00\x00\x00\x00\x02\x66\x53\x00\x00\x00\x00\x02\x67\x53' # | eS.....fS.....gS
|
||||
msg += b'\x00\x00\x00\x00\x02\x68\x53\x00\x00\x00\x00\x02\xbc\x49\x00\x00' # | .....hS......I..
|
||||
msg += b'\x00\x00\x00\x00\x02\xbd\x53\x00\x00\x00\x00\x02\xbe\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x02\xbf\x53\x00\x00\x00\x00\x02\xc0\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x02\xc1\x53\x00\x00\x00\x00\x02\xc2\x53\x00\x00\x00\x00\x02\xc3' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x02\xc4\x53\x00\x00\x00\x00\x02\xc5\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x02\xc6\x53\x00\x00\x00\x00\x02\xc7\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x02\xc8\x53\x00\x00\x00\x00\x02\xc9\x53\x00\x00\x00\x00\x02' # | ...S......S.....
|
||||
msg += b'\xca\x53\x00\x00\x00\x00\x02\xcb\x53\x00\x00\x00\x00\x02\xcc\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x03\x20\x53\x00\x01\x00\x00\x03\x84\x53\x11\x68' # | ..... S......S.h
|
||||
msg += b'\x00\x00\x03\xe8\x46\x44\x23\xd1\xec\x00\x00\x04\x4c\x46\x43\xa3' # | ....FD#.....LFC.
|
||||
msg += b'\xb3\x33\x00\x00\x04\xb0\x46\x00\x00\x00\x00\x00\x00\x05\x14\x46' # | .3....F........F
|
||||
msg += b'\x43\x6e\x80\x00\x00\x00\x05\x78\x46\x3d\x4c\xcc\xcd\x00\x00\x05' # | Cn.....xF=L.....
|
||||
msg += b'\xdc\x46\x00\x00\x00\x00\x00\x00\x06\x40\x46\x42\x48\x00\x00\x00' # | .F.......@FBH...
|
||||
msg += b'\x00\x06\xa4\x53\x00\x03\x00\x00\x07\x08\x53\x00\x0c\x00\x00\x07' # | ...S......S.....
|
||||
msg += b'\x6c\x53\x00\x50\x00\x00\x07\xd0\x46\x43\xa3\xb3\x33\x00\x00\x08' # | lS.P....FC..3...
|
||||
msg += b'\x34\x53\x0b\xb8\x00\x00\x08\x98\x46\x00\x00\x00\x00\x00\x00\x08' # | 4S......F.......
|
||||
msg += b'\xfc\x46\x00\x00\x00\x00\x00\x00\x09\x60\x46\x41\xee\xe1\x48\x00' # | .F.......`FA..H.
|
||||
msg += b'\x00\x09\xc4\x53\x00\x00\x00\x00\x0a\x28\x46\x41\xf2\x00\x00\x00' # | ...S.....(FA....
|
||||
msg += b'\x00\x0a\x8c\x46\x3f\xac\x28\xf6\x00\x00\x0a\xf0\x53\x00\x0c\x00' # | ...F?.(.....S...
|
||||
msg += b'\x00\x0b\x54\x53\x00\x00\x00\x00\x0b\xb8\x53\x00\x00\x00\x00\x0c' # | ..TS......S.....
|
||||
msg += b'\x1c\x53\x00\x00\x00\x00\x0c\x80\x53\x00\x00\x00\x00\x0c\xe4\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x0d\x48\x53\x00\x00\x00\x00\x0d\xac\x53\x00\x00' # | .....HS......S..
|
||||
msg += b'\x00\x00\x0e\x10\x53\x00\x00\x00\x00\x0e\x74\x53\x00\x00\x00\x00' # | ....S.....tS....
|
||||
msg += b'\x0e\xd8\x53\x00\x00\x00\x00\x0f\x3c\x53\x00\x00\x00\x00\x0f\xa0' # | ..S.....<S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x10\x04\x53\x00\x00\x00\x00\x10\x68\x53\x00' # | S......S.....hS.
|
||||
msg += b'\x00\x00\x00\x10\xcc\x53\x00\x00\x00\x00\x11\x30\x53\x00\x00\x00' # | .....S.....0S...
|
||||
msg += b'\x00\x11\x94\x53\x00\x00\x00\x00\x11\xf8\x53\x00\x00\x00\x00\x12' # | ...S......S.....
|
||||
msg += b'\x5c\x53\x00\x00\x00\x00\x12\xc0\x53\x00\x00\x00\x00\x13\x24\x46' # | \S......S.....$F
|
||||
msg += b'\x42\x9d\x33\x33\x00\x00\x13\x88\x46\x00\x00\x00\x00\x00\x00\x13' # | B.33....F.......
|
||||
msg += b'\xec\x46\x00\x00\x00\x00\x00\x00\x14\x50\x46\x42\xdc\x00\x00\x00' # | .F.......PFB....
|
||||
msg += b'\x00\x14\xb4\x53\x00\x00\x00\x00\x15\x18\x53\x00\x00\x00\x00\x15' # | ...S......S.....
|
||||
msg += b'\x7c\x53\x00\x00\x00\x00\x15\x7d\x53\x00\x00\x00\x00\x15\x7e\x53' # | |S.....}S.....~S
|
||||
msg += b'\x00\x00\x00\x00\x15\x7f\x53\x00\x00\x00\x00\x15\x80\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x15\x81\x53\x00\x00\x00\x00\x15\x82\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x15\x83\x53\x00\x00\x00\x00\x15\x84\x53\x00\x00\x00\x00\x15\x85' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x15\x86\x53\x00\x00\x00\x00\x15\x87\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x15\x88\x53\x00\x00\x00\x00\x15\x89\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x15\x8a\x53\x00\x00\x00\x00\x15\x8b\x53\x00\x00\x00\x00\x15' # | ...S......S.....
|
||||
msg += b'\x8c\x53\x00\x00\x00\x00\x15\xe0\x46\x42\x68\x66\x66\x00\x00\x16' # | .S......FBhff...
|
||||
msg += b'\x44\x46\x00\x00\x00\x00\x00\x00\x16\xa8\x46\x00\x00\x00\x00\x00' # | DF........F.....
|
||||
msg += b'\x00\x17\x0c\x46\x42\xdc\x00\x00\x00\x00\x17\x70\x53\x00\x00\x00' # | ...FB......pS...
|
||||
msg += b'\x00\x17\xd4\x53\x00\x00\x00\x00\x18\x38\x53\x00\x00\x00\x00\x18' # | ...S.....8S.....
|
||||
msg += b'\x39\x53\x00\x00\x00\x00\x18\x3a\x53\x00\x00\x00\x00\x18\x3b\x53' # | 9S.....:S.....;S
|
||||
msg += b'\x00\x00\x00\x00\x18\x3c\x53\x00\x00\x00\x00\x18\x3d\x53\x00\x00' # | .....<S.....=S..
|
||||
msg += b'\x00\x00\x18\x3e\x53\x00\x00\x00\x00\x18\x3f\x53\x00\x00\x00\x00' # | ...>S.....?S....
|
||||
msg += b'\x18\x40\x53\x00\x00\x00\x00\x18\x41\x53\x00\x00\x00\x00\x18\x42' # | .@S.....AS.....B
|
||||
msg += b'\x53\x00\x00\x00\x00\x18\x43\x53\x00\x00\x00\x00\x18\x44\x53\x00' # | S.....CS.....DS.
|
||||
msg += b'\x00\x00\x00\x18\x45\x53\x00\x00\x00\x00\x18\x46\x53\x00\x00\x00' # | ....ES.....FS...
|
||||
msg += b'\x00\x18\x47\x53\x00\x00\x00\x00\x18\x48\x53\x00\x00\x00\x00\x18' # | ..GS.....HS.....
|
||||
msg += b'\x9c\x46\x42\x6b\x33\x33\x00\x00\x19\x00\x46\x00\x00\x00\x00\x00' # | .FBk33....F.....
|
||||
msg += b'\x00\x19\x64\x46\x00\x00\x00\x00\x00\x00\x19\xc8\x46\x42\xdc\x00' # | ..dF........FB..
|
||||
msg += b'\x00\x00\x00\x1a\x2c\x53\x00\x00\x00\x00\x1a\x90\x53\x00\x00\x00' # | ....,S......S...
|
||||
msg += b'\x00\x1a\xf4\x53\x00\x00\x00\x00\x1a\xf5\x53\x00\x00\x00\x00\x1a' # | ...S......S.....
|
||||
msg += b'\xf6\x53\x00\x00\x00\x00\x1a\xf7\x53\x00\x00\x00\x00\x1a\xf8\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x1a\xf9\x53\x00\x00\x00\x00\x1a\xfa\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x1a\xfb\x53\x00\x00\x00\x00\x1a\xfc\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x1a\xfd\x53\x00\x00\x00\x00\x1a\xfe\x53\x00\x00\x00\x00\x1a\xff' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x1b\x00\x53\x00\x00\x00\x00\x1b\x01\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x1b\x02\x53\x00\x00\x00\x00\x1b\x03\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x1b\x04\x53\x00\x00\x00\x00\x1b\x58\x53\x00\x00\x00\x00\x1b' # | ...S.....XS.....
|
||||
msg += b'\xbc\x53\x11\x3d\x00\x00\x1c\x20\x46\x3c\x23\xd7\x0a\x00\x00\x1c' # | .S.=... F<#.....
|
||||
msg += b'\x84\x46\x00\x00\x00\x00\x00\x00\x1c\xe8\x46\x42\x04\x00\x00\x00' # | .F........FB....
|
||||
msg += b'\x00\x1d\x4c\x46\x00\x00\x00\x00\x00\x00\x1d\xb0\x46\x00\x00\x00' # | ..LF........F...
|
||||
msg += b'\x00\x00\x00\x1e\x14\x53\x00\x02\x00\x00\x1e\x78\x46\x41\x8b\x33' # | .....S.....xFA.3
|
||||
msg += b'\x33\x00\x00\x1e\xdc\x46\x3c\xa3\xd7\x0a\x00\x00\x1f\x40\x46\x3e' # | 3....F<......@F>
|
||||
msg += b'\x99\x99\x9a\x00\x00\x1f\xa4\x46\x40\x99\x99\x9a\x00\x00\x20\x08' # | .......F@..... .
|
||||
msg += b'\x53\x00\x00\x00\x00\x20\x6c\x53\x00\x00\x00\x00\x20\xd0\x53\x05' # | S.... lS.... .S.
|
||||
msg += b'\x00\x00\x00\x20\xd1\x53\x00\x00\x00\x00\x20\xd2\x53\x00\x00\x00' # | ... .S.... .S...
|
||||
msg += b'\x00\x20\xd3\x53\x00\x00\x00\x00\x20\xd4\x53\x00\x00\x00\x00\x20' # | . .S.... .S....
|
||||
msg += b'\xd5\x53\x00\x00\x00\x00\x20\xd6\x53\x00\x00\x00\x00\x20\xd7\x53' # | .S.... .S.... .S
|
||||
msg += b'\x00\x00\x00\x00\x20\xd8\x53\x00\x00\x00\x00\x20\xd9\x53\x00\x01' # | .... .S.... .S..
|
||||
msg += b'\x00\x00\x20\xda\x53\x00\x00\x00\x00\x20\xdb\x53\x00\x01\x00\x00' # | .. .S.... .S....
|
||||
msg += b'\x20\xdc\x53\x00\x00\x00\x00\x20\xdd\x53\x00\x00\x00\x00\x20\xde' # | .S.... .S.... .
|
||||
msg += b'\x53\x00\x00\x00\x00\x20\xdf\x53\x00\x00\x00\x00\x20\xe0\x53\x00' # | S.... .S.... .S.
|
||||
msg += b'\x00\x00\x00\x21\x34\x46\x00\x00\x00\x00\x00\x00\x21\x98\x46\x00' # | ...!4F......!.F.
|
||||
msg += b'\x00\x00\x00\x00\x00\x21\xfc\x46\x00\x00\x00\x00\x00\x00\x22\x60' # | .....!.F......"`
|
||||
msg += b'\x46\x00\x00\x00\x00\x00\x00\x22\xc4\x53\x00\x00\x00\x00\x23\x28' # | F......".S....#(
|
||||
msg += b'\x53\x00\x00\x00\x00\x23\x8c\x53\x00\x00\x00\x00\x23\x8d\x53\x00' # | S....#.S....#.S.
|
||||
msg += b'\x00\x00\x00\x23\x8e\x53\x00\x00\x00\x00\x23\x8f\x53\x00\x00\x00' # | ...#.S....#.S...
|
||||
msg += b'\x00\x23\x90\x53\x00\x00\x00\x00\x23\x91\x53\x00\x00\x00\x00\x23' # | .#.S....#.S....#
|
||||
msg += b'\x92\x53\x00\x00\x00\x00\x23\x93\x53\x00\x00\x00\x00\x23\x94\x53' # | .S....#.S....#.S
|
||||
msg += b'\x00\x00\x00\x00\x23\x95\x53\x00\x00\x00\x00\x23\x96\x53\x00\x00' # | ....#.S....#.S..
|
||||
msg += b'\x00\x00\x23\x97\x53\x00\x00\x00\x00\x23\x98\x53\x00\x00\x00\x00' # | ..#.S....#.S....
|
||||
msg += b'\x23\x99\x53\x00\x00\x00\x00\x23\x9a\x53\x00\x00\x00\x00\x23\x9b' # | #.S....#.S....#.
|
||||
msg += b'\x53\x00\x00\x00\x00\x23\x9c\x53\x00\x00\x00\x00\x23\xf0\x46\x00' # | S....#.S....#.F.
|
||||
msg += b'\x00\x00\x00\x00\x00\x24\x54\x46\x00\x00\x00\x00\x00\x00\x24\xb8' # | .....$TF......$.
|
||||
msg += b'\x46\x00\x00\x00\x00\x00\x00\x25\x1c\x46\x00\x00\x00\x00\x00\x00' # | F......%.F......
|
||||
msg += b'\x25\x80\x53\x00\x00\x00\x00\x25\xe4\x53\x00\x00\x00\x00\x26\x48' # | %.S....%.S....&H
|
||||
msg += b'\x53\x00\x00\x00\x00\x26\x49\x53\x00\x00\x00\x00\x26\x4a\x53\x00' # | S....&IS....&JS.
|
||||
msg += b'\x00\x00\x00\x26\x4b\x53\x00\x00\x00\x00\x26\x4c\x53\x00\x00\x00' # | ...&KS....&LS...
|
||||
msg += b'\x00\x26\x4d\x53\x00\x00\x00\x00\x26\x4e\x53\x00\x00\x00\x00\x26' # | .&MS....&NS....&
|
||||
msg += b'\x4f\x53\x00\x00\x00\x00\x26\x50\x53\x00\x00\x00\x00\x26\x51\x53' # | OS....&PS....&QS
|
||||
msg += b'\x00\x00\x00\x00\x26\x52\x53\x00\x00\x00\x00\x26\x53\x53\x00\x00' # | ....&RS....&SS..
|
||||
msg += b'\x00\x00\x26\x54\x53\x00\x00\x00\x00\x26\x55\x53\x00\x00\x00\x00' # | ..&TS....&US....
|
||||
msg += b'\x26\x56\x53\x00\x00\x00\x00\x26\x57\x53\x00\x00\x00\x00\x26\x58' # | &VS....&WS....&X
|
||||
msg += b'\x53\x00\x00\x00\x00\x26\xac\x53\x00\x00\x00\x00\x27\x10\x53\x11' # | S....&.S....'.S.
|
||||
msg += b'\x3d\x00\x00\x27\x74\x46\x00\x00\x00\x00\x00\x00\x27\xd8\x46\x00' # | =..'tF......'.F.
|
||||
msg += b'\x00\x00\x00\x00\x00\x28\x3c\x46\x42\x03\xf5\xc3\x00\x00\x28\xa0' # | .....(<FB.....(.
|
||||
msg += b'\x46\x00\x00\x00\x00\x00\x00\x29\x04\x46\x00\x00\x00\x00\x00\x00' # | F......).F......
|
||||
msg += b'\x29\x68\x53\x00\x02\x00\x00\x29\xcc\x53\x00\x03\x00\x00\x2a\x30' # | )hS....).S....*0
|
||||
msg += b'\x46\x42\x20\x00\x00\x00\x00\x2a\x94\x46\x42\x20\x00\x00\x00\x00' # | FB ....*.FB ....
|
||||
msg += b'\x2a\xf8\x46\x44\x20\x00\x00\x00\x00\x2b\x5c\x46\x43\x7b\x00\x00' # | *.FD ....+\FC{..
|
||||
msg += b'\x00\x00\x2b\xc0\x46\x43\x50\x00\x00\x00\x00\x2c\x24\x46\x42\x48' # | ..+.FCP....,$FBH
|
||||
msg += b'\x5c\x29\x00\x00\x2c\x88\x46\x42\x47\xa3\xd7\x00\x00\x2c\xec\x53' # | \)..,.FBG....,.S
|
||||
msg += b'\x00\x00\x00\x00\x2d\x50\x46\x43\x42\x00\x00\x00\x00\x2d\xb4\x46' # | ....-PFCB....-.F
|
||||
msg += b'\x42\xbc\x00\x00\x00\x00\x2e\x18\x46\x3f\xe6\x66\x66\x00\x00\x2e' # | B.......F?.ff...
|
||||
msg += b'\x7c\x46\x3f\xe6\x66\x66\x00\x00\x2e\xe0\x46\x43\x7e\x00\x00\x00' # | |F?.ff....FC~...
|
||||
msg += b'\x00\x2f\x44\x46\x43\x83\xf3\x33\x00\x00\x2f\xa8\x46\x3f\xe6\x66' # | ./DFC..3../.F?.f
|
||||
msg += b'\x66\x00\x00\x30\x0c\x46\x3f\xe6\x66\x66\x00\x00\x30\x70\x46\x43' # | f..0.F?.ff..0pFC
|
||||
msg += b'\x7e\x00\x00\x00\x00\x30\xd4\x46\x42\x3f\xeb\x85\x00\x00\x31\x38' # | ~....0.FB?....18
|
||||
msg += b'\x46\x42\x3d\xeb\x85\x00\x00\x31\x9c\x46\x3e\x4c\xcc\xcd\x00\x00' # | FB=....1.F>L....
|
||||
msg += b'\x32\x00\x46\x3e\x4c\xcc\xcd\x00\x00\x32\x64\x46\x42\x4c\x14\x7b' # | 2.F>L....2dFBL.{
|
||||
msg += b'\x00\x00\x32\xc8\x46\x42\x4d\xeb\x85\x00\x00\x33\x2c\x46\x3e\x4c' # | ..2.FBM....3,F>L
|
||||
msg += b'\xcc\xcd\x00\x00\x33\x90\x46\x3e\x4c\xcc\xcd\x00\x00\x33\xf4\x53' # | ....3.F>L....3.S
|
||||
msg += b'\x00\x00\x00\x00\x34\x58\x53\x00\x00\x00\x00\x34\xbc\x53\x04\x00' # | ....4XS....4.S..
|
||||
msg += b'\x00\x00\x35\x20\x53\x00\x01\x00\x00\x35\x84\x53\x13\x9c\x00\x00' # | ..5 S....5.S....
|
||||
msg += b'\x35\xe8\x53\x0f\xa0\x00\x00\x36\x4c\x53\x00\x00\x00\x00\x36\xb0' # | 5.S....6LS....6.
|
||||
msg += b'\x53\x00\x66' # | S.f'
|
||||
return msg
|
||||
|
||||
def test_read_message(msg_contact_info):
|
||||
Config.act_config = {'tsun':{'enabled': True}}
|
||||
m = MemoryStream(msg_contact_info, (0,))
|
||||
@@ -1570,6 +1801,64 @@ def test_msg_inv_ind3(config_tsun_inv1, msg_inverter_ind_0w, msg_inverter_ack):
|
||||
m.close()
|
||||
assert m.db.get_db_value(Register.INVERTER_STATUS) == 0
|
||||
|
||||
def test_msg_inv_ind4(config_tsun_inv1, msg_inverter_ms3000_ind, msg_inverter_ack):
|
||||
'''Check sonar_lists of MS-3000 inverter'''
|
||||
_ = config_tsun_inv1
|
||||
tracer.setLevel(logging.DEBUG)
|
||||
with patch.object(logging, 'warning') as spy:
|
||||
m = MemoryStream(msg_inverter_ms3000_ind, (0,))
|
||||
m.db.stat['proxy']['Unknown_Ctrl'] = 0
|
||||
m.db.stat['proxy']['Invalid_Data_Type'] = 0
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
spy.assert_not_called()
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Invalid_Data_Type'] == 0
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m.unique_id == 'R170000000000001'
|
||||
assert int(m.ctrl)==145
|
||||
assert m.msg_id==4
|
||||
assert m.header_len==23
|
||||
assert m.data_len==2284
|
||||
m.ts_offset = 0
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_inverter_ms3000_ind
|
||||
assert m.ifc.tx_fifo.get()==msg_inverter_ack
|
||||
assert m.db.get_db_value(Register.INVERTER_STATUS) == 0
|
||||
assert m.db.get_db_value(Register.TS_GRID) == 1739866976
|
||||
m.db.db['grid'] = {'Output_Power': 100}
|
||||
m.close()
|
||||
|
||||
def test_msg_inv_ind5(config_tsun_inv1, msg_inverter_ms3000_ind, msg_inverter_ack):
|
||||
'''Check that unexpected sonar_lists will log a warning'''
|
||||
_ = config_tsun_inv1
|
||||
tracer.setLevel(logging.DEBUG)
|
||||
with patch.object(logging, 'warning') as spy:
|
||||
m = MemoryStream(msg_inverter_ms3000_ind, (0,))
|
||||
m.sensor_list = 0x01900002 # change the expected sensor_list
|
||||
m.db.stat['proxy']['Unknown_Ctrl'] = 0
|
||||
m.db.stat['proxy']['Invalid_Data_Type'] = 0
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
spy.assert_called()
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
assert m.db.stat['proxy']['Invalid_Data_Type'] == 0
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m.unique_id == 'R170000000000001'
|
||||
assert int(m.ctrl)==145
|
||||
assert m.msg_id==4
|
||||
assert m.header_len==23
|
||||
assert m.data_len==2284
|
||||
m.ts_offset = 0
|
||||
m._update_header(m.ifc.fwd_fifo.peek())
|
||||
assert m.ifc.fwd_fifo.get()==msg_inverter_ms3000_ind
|
||||
assert m.ifc.tx_fifo.get()==msg_inverter_ack
|
||||
assert m.db.get_db_value(Register.INVERTER_STATUS) == 0
|
||||
assert m.db.get_db_value(Register.TS_GRID) == 1739866976
|
||||
m.db.db['grid'] = {'Output_Power': 100}
|
||||
m.close()
|
||||
|
||||
def test_msg_inv_ack(config_tsun_inv1, msg_inverter_ack):
|
||||
_ = config_tsun_inv1
|
||||
@@ -1614,6 +1903,18 @@ def test_msg_inv_invalid(config_tsun_inv1, msg_inverter_invalid):
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 1
|
||||
m.close()
|
||||
|
||||
def test_build_modell_3000(config_tsun_allow_all, msg_inverter_ms3000_ind):
|
||||
_ = config_tsun_allow_all
|
||||
m = MemoryStream(msg_inverter_ms3000_ind, (0,))
|
||||
assert 0 == m.db.get_db_value(Register.MAX_DESIGNED_POWER, 0)
|
||||
assert None == m.db.get_db_value(Register.RATED_POWER, None)
|
||||
assert None == m.db.get_db_value(Register.INVERTER_TEMP, None)
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert 3000 == m.db.get_db_value(Register.MAX_DESIGNED_POWER, 0)
|
||||
assert 0 == m.db.get_db_value(Register.RATED_POWER, 0)
|
||||
assert 'TSOL-MS3000' == m.db.get_db_value(Register.EQUIPMENT_MODEL, 0)
|
||||
m.close()
|
||||
|
||||
def test_msg_ota_req(config_tsun_inv1, msg_ota_req):
|
||||
_ = config_tsun_inv1
|
||||
m = MemoryStream(msg_ota_req, (0,), False)
|
||||
@@ -2184,6 +2485,56 @@ async def test_modbus_polling(config_tsun_inv1, msg_inverter_ind):
|
||||
assert next(m.mb_timer.exp_count) == 4
|
||||
m.close()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_modbus_scaning(config_tsun_inv1, msg_inverter_ind, msg_modbus_rsp21):
|
||||
_ = config_tsun_inv1
|
||||
assert asyncio.get_running_loop()
|
||||
|
||||
m = MemoryStream(msg_inverter_ind, (0x8f,0))
|
||||
m.append_msg(msg_modbus_rsp21)
|
||||
m.mb_scan = True
|
||||
m.mb_start_reg = 0x4560
|
||||
m.mb_bytes = 0x14
|
||||
assert asyncio.get_running_loop() == m.mb_timer.loop
|
||||
m.db.stat['proxy']['Unknown_Ctrl'] = 0
|
||||
assert m.mb_timer.tim == None
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 1
|
||||
assert m.id_str == b"R170000000000001"
|
||||
assert m.unique_id == 'R170000000000001'
|
||||
assert m.msg_recvd[0]['ctrl']==145
|
||||
assert m.msg_recvd[0]['msg_id']==4
|
||||
assert m.msg_recvd[0]['header_len']==23
|
||||
assert m.msg_recvd[0]['data_len']==120
|
||||
assert m.ifc.fwd_fifo.get()==msg_inverter_ind
|
||||
assert m.ifc.tx_fifo.get()==b'\x00\x00\x00\x14\x10R170000000000001\x99\x04\x01'
|
||||
assert m.db.stat['proxy']['Unknown_Ctrl'] == 0
|
||||
|
||||
m.ifc.tx_clear() # clear send buffer for next test
|
||||
assert isclose(m.mb_timeout, 0.5)
|
||||
assert next(m.mb_timer.exp_count) == 0
|
||||
|
||||
await asyncio.sleep(0.5)
|
||||
assert m.sent_pdu==b'\x00\x00\x00 \x10R170000000000001pw\x00\x01\xa3(\x08\x01\x03\x45\x60\x00\x14\x50\xd7'
|
||||
assert m.ifc.tx_fifo.get()==b''
|
||||
|
||||
m.read() # read complete msg, and dispatch msg
|
||||
assert not m.header_valid # must be invalid, since msg was handled and buffer flushed
|
||||
assert m.msg_count == 2
|
||||
assert m.msg_recvd[1]['ctrl']==145
|
||||
assert m.msg_recvd[1]['msg_id']==119
|
||||
assert m.msg_recvd[1]['header_len']==23
|
||||
assert m.msg_recvd[1]['data_len']==50
|
||||
assert m.mb.last_addr == 1
|
||||
assert m.mb.last_fcode == 3
|
||||
assert m.mb.last_reg == 0x4560
|
||||
assert m.mb.last_len == 20
|
||||
assert m.mb.err == 0
|
||||
|
||||
assert next(m.mb_timer.exp_count) == 2
|
||||
m.close()
|
||||
|
||||
def test_broken_recv_buf(config_tsun_allow_all, broken_recv_buf):
|
||||
_ = config_tsun_allow_all
|
||||
m = MemoryStream(broken_recv_buf, (0,))
|
||||
|
||||
3
ha_addons/.gitignore
vendored
3
ha_addons/.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.data.json
|
||||
config.yaml
|
||||
config.yaml
|
||||
apparmor.txt
|
||||
@@ -8,23 +8,71 @@ JINJA = jinja2
|
||||
IMAGE = tsun-gen3-addon
|
||||
|
||||
|
||||
# Folders
|
||||
# Source folders for building the local add-on
|
||||
SRC=../app
|
||||
SRC_PROXY=$(SRC)/src
|
||||
CNF_PROXY=$(SRC)/config
|
||||
|
||||
# Target folders for building the local add-on and the docker container
|
||||
ADDON_PATH = ha_addon
|
||||
DST=$(ADDON_PATH)/rootfs
|
||||
DST_PROXY=$(DST)/home/proxy
|
||||
|
||||
INST_BASE=../../ha-addons/ha-addons
|
||||
# base director of the add-on repro for installing the add-on git repros
|
||||
INST_BASE=../../ha-addons
|
||||
|
||||
# Template folder for build the config.yaml variants
|
||||
TEMPL=templates
|
||||
|
||||
# help variable STAGE determine the target to build
|
||||
STAGE=dev
|
||||
debug : STAGE=debug
|
||||
rc : STAGE=rc
|
||||
rel : STAGE=rel
|
||||
|
||||
|
||||
export BUILD_DATE := ${shell date -Iminutes}
|
||||
BUILD_ID := ${shell date +'%y%m%d%H%M'}
|
||||
VERSION := $(shell cat $(SRC)/.version)
|
||||
export MAJOR := $(shell echo $(VERSION) | cut -f1 -d.)
|
||||
|
||||
PUBLIC_URL := $(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f1 -d/)
|
||||
PUBLIC_USER :=$(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f2 -d/)
|
||||
|
||||
build: local_add_on
|
||||
|
||||
dev debug: local_add_on
|
||||
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PRIVAT_CONTAINER_REGISTRY)$(IMAGE)
|
||||
export VERSION=$(VERSION)-$@-$(BUILD_ID) && \
|
||||
export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \
|
||||
docker buildx bake -f docker-bake.hcl $@
|
||||
|
||||
rc rel: local_add_on
|
||||
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PUBLIC_CONTAINER_REGISTRY)$(IMAGE)
|
||||
@echo login at $(PUBLIC_URL) as $(PUBLIC_USER)
|
||||
@DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)"
|
||||
export VERSION=$(VERSION)-$@ && \
|
||||
export IMAGE=$(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) && \
|
||||
docker buildx bake -f docker-bake.hcl $@
|
||||
|
||||
clean:
|
||||
rm -r -f $(DST_PROXY)
|
||||
rm -f $(DST)/requirements.txt
|
||||
rm -f $(ADDON_PATH)/config.yaml
|
||||
rm -f $(TEMPL)/.data.json
|
||||
docker logout ghcr.io
|
||||
|
||||
#############
|
||||
# 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
|
||||
|
||||
# collect source files
|
||||
SRC_FILES := $(wildcard $(SRC_PROXY)/*.py)\
|
||||
$(wildcard $(SRC_PROXY)/*.ini)\
|
||||
$(wildcard $(SRC_PROXY)/cnf/*.py)\
|
||||
$(wildcard $(SRC_PROXY)/cnf/*.toml)\
|
||||
$(wildcard $(SRC_PROXY)/gen3/*.py)\
|
||||
$(wildcard $(SRC_PROXY)/gen3plus/*.py)
|
||||
CNF_FILES := $(wildcard $(CNF_PROXY)/*.toml)
|
||||
@@ -33,49 +81,8 @@ CNF_FILES := $(wildcard $(CNF_PROXY)/*.toml)
|
||||
TARGET_FILES = $(SRC_FILES:$(SRC_PROXY)/%=$(DST_PROXY)/%)
|
||||
CONFIG_FILES = $(CNF_FILES:$(CNF_PROXY)/%=$(DST_PROXY)/%)
|
||||
|
||||
export BUILD_DATE := ${shell date -Iminutes}
|
||||
VERSION := $(shell cat $(SRC)/.version)
|
||||
export MAJOR := $(shell echo $(VERSION) | cut -f1 -d.)
|
||||
|
||||
PUBLIC_URL := $(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f1 -d/)
|
||||
PUBLIC_USER :=$(shell echo $(PUBLIC_CONTAINER_REGISTRY) | cut -f2 -d/)
|
||||
|
||||
|
||||
dev debug: build
|
||||
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PRIVAT_CONTAINER_REGISTRY)$(IMAGE)
|
||||
export VERSION=$(VERSION)-$@ && \
|
||||
export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \
|
||||
docker buildx bake -f docker-bake.hcl $@
|
||||
|
||||
rc rel: build
|
||||
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PUBLIC_CONTAINER_REGISTRY)$(IMAGE)
|
||||
@echo login at $(PUBLIC_URL) as $(PUBLIC_USER)
|
||||
@DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)"
|
||||
export VERSION=$(VERSION)-$@ && \
|
||||
export IMAGE=$(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) && \
|
||||
docker buildx bake -f docker-bake.hcl $@
|
||||
|
||||
|
||||
build: rootfs $(ADDON_PATH)/config.yaml repro
|
||||
|
||||
clean:
|
||||
rm -r -f $(DST_PROXY)
|
||||
rm -f $(DST)/requirements.txt
|
||||
rm -f $(ADDON_PATH)/config.yaml
|
||||
rm -f $(TEMPL)/.data.json
|
||||
|
||||
#
|
||||
# Build rootfs and config.yaml as local add-on
|
||||
# The rootfs is needed to build the add-on Dockercontainers
|
||||
#
|
||||
|
||||
rootfs: $(TARGET_FILES) $(CONFIG_FILES) $(DST)/requirements.txt
|
||||
|
||||
STAGE=dev
|
||||
debug : STAGE=debug
|
||||
rc : STAGE=rc
|
||||
rel : STAGE=rel
|
||||
|
||||
$(CONFIG_FILES): $(DST_PROXY)/% : $(CNF_PROXY)/%
|
||||
@echo Copy $< to $@
|
||||
@mkdir -p $(@D)
|
||||
@@ -91,46 +98,84 @@ $(DST)/requirements.txt : $(SRC)/requirements.txt
|
||||
@cp $< $@
|
||||
|
||||
$(ADDON_PATH)/%.yaml: $(TEMPL)/%.jinja $(TEMPL)/.data.json
|
||||
$(JINJA) --strict -D AppVersion=$(VERSION) --format=json $^ -o $@
|
||||
$(JINJA) --strict -D AppVersion=$(VERSION) -D BuildID=$(BUILD_ID) --format=json $^ -o $@
|
||||
|
||||
$(ADDON_PATH)/%.txt: $(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
|
||||
rsync --checksum $(TEMPL)/$(STAGE)_data.json $@
|
||||
|
||||
FORCE : ;
|
||||
|
||||
|
||||
#
|
||||
# Build repository for Home Assistant Add-On
|
||||
#############
|
||||
# Build repository for Home Assistant Add-Onx
|
||||
#
|
||||
|
||||
INST=$(INST_BASE)/ha_addon_dev
|
||||
repro_files = DOCS.md icon.png logo.png translations/de.yaml translations/en.yaml rootfs/run.sh
|
||||
repro_root = CHANGELOG.md
|
||||
repro_root = CHANGELOG.md LICENSE.md
|
||||
repro_templates = config.yaml
|
||||
repro_apparmor = apparmor.txt
|
||||
repro_subdirs = translations rootfs
|
||||
repro_vers = debug dev rel
|
||||
repro_vers = debug dev rc rel
|
||||
|
||||
repro_all_files := $(foreach dir,$(repro_vers), $(foreach file,$(repro_files),$(INST_BASE)/ha_addon_$(dir)/$(file)))
|
||||
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_subdirs := $(foreach dir,$(repro_vers), $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_$(dir)/$(file)))
|
||||
|
||||
repro: $(repro_all_subdirs) $(repro_all_templates) $(repro_all_files) $(repro_root_files)
|
||||
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_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_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_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_files),$(INST_BASE)/ha_addon_rel/$(file)) \
|
||||
$(foreach file,$(repro_root),$(INST_BASE)/ha_addon_rel/$(file))
|
||||
|
||||
$(repro_all_subdirs) :
|
||||
mkdir -p $@
|
||||
|
||||
$(repro_all_templates) : $(INST_BASE)/ha_addon_%/config.yaml: $(TEMPL)/config.jinja $(TEMPL)/%_data.json $(SRC)/.version
|
||||
$(JINJA) --strict -D AppVersion=$(VERSION)-$* $< $(filter %.json,$^) -o $@
|
||||
$(repro_all_templates) : $(INST_BASE)/ha_addon_%/config.yaml: $(TEMPL)/config.jinja $(TEMPL)/%_data.json $(SRC)/.version FORCE
|
||||
$(JINJA) --strict -D AppVersion=$(VERSION)-$* -D BuildID=$(BUILD_ID) $< $(filter %.json,$^) -o $@
|
||||
|
||||
$(repro_root_files) : %/CHANGELOG.md : ../CHANGELOG.md
|
||||
$(repro_all_apparmor) : $(INST_BASE)/ha_addon_%/apparmor.txt: $(TEMPL)/apparmor.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/% : ../%
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_rc/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_rc/% : ../%
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_rel/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_rel/% : ../%
|
||||
cp $< $@
|
||||
|
||||
|
||||
$(filter $(INST_BASE)/ha_addon_debug/%,$(repro_all_files)) : $(INST_BASE)/ha_addon_debug/% : ha_addon/%
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_dev/%,$(repro_all_files)) : $(INST_BASE)/ha_addon_dev/% : ha_addon/%
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_rc/%,$(repro_all_files)) : $(INST_BASE)/ha_addon_rc/% : ha_addon/%
|
||||
cp $< $@
|
||||
$(filter $(INST_BASE)/ha_addon_rel/%,$(repro_all_files)) : $(INST_BASE)/ha_addon_rel/% : ha_addon/%
|
||||
cp $< $@
|
||||
|
||||
|
||||
|
||||
@@ -74,12 +74,12 @@ target "_prod" {
|
||||
}
|
||||
target "debug" {
|
||||
inherits = ["_common", "_debug"]
|
||||
tags = ["${IMAGE}:debug"]
|
||||
tags = ["${IMAGE}:debug", "${IMAGE}:${VERSION}"]
|
||||
}
|
||||
|
||||
target "dev" {
|
||||
inherits = ["_common"]
|
||||
tags = ["${IMAGE}:dev"]
|
||||
tags = ["${IMAGE}:dev", "${IMAGE}:${VERSION}"]
|
||||
}
|
||||
|
||||
target "preview" {
|
||||
|
||||
@@ -68,8 +68,8 @@ Example add-on configuration for GEN3PLUS inverters:
|
||||
inverters:
|
||||
- serial: Y17000000000000
|
||||
monitor_sn: 2000000000
|
||||
node_id: PV-Garage
|
||||
suggested_area: Garage
|
||||
node_id: inv_1
|
||||
suggested_area: Roof
|
||||
modbus_polling: true
|
||||
client_mode.host: 192.168.x.x
|
||||
client_mode.port: 8899
|
||||
@@ -84,6 +84,21 @@ inverters:
|
||||
pv4.type: SF-M18/144550
|
||||
```
|
||||
|
||||
Example add-on configuration for GEN3PLUS energie storages:
|
||||
|
||||
```yaml
|
||||
batteries:
|
||||
- serial: 4100000000000000
|
||||
monitor_sn: 2300000000
|
||||
node_id: bat_1
|
||||
suggested_area: Garage
|
||||
modbus_polling: false
|
||||
pv1.manufacturer: Shinefar
|
||||
pv1.type: SF-M18/144550
|
||||
pv2.manufacturer: Shinefar
|
||||
pv2.type: SF-M18/144550
|
||||
```
|
||||
|
||||
**Note**: _This is just an example, you need to replace the values with your own!_
|
||||
|
||||
more information about the configuration can be found in the [configuration details page][configdetails].
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
# 1 Build Base Image #
|
||||
######################
|
||||
|
||||
ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.1.0"
|
||||
ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.2"
|
||||
# hadolint ignore=DL3006
|
||||
FROM $BUILD_FROM AS base
|
||||
|
||||
# Installiere Python, pip und virtuelle Umgebungstools
|
||||
RUN apk add --no-cache python3=3.12.8-r1 py3-pip=24.3.1-r0 && \
|
||||
RUN apk add --no-cache python3=3.12.9-r0 py3-pip=24.3.1-r0 && \
|
||||
python -m venv /opt/venv && \
|
||||
. /opt/venv/bin/activate
|
||||
|
||||
@@ -47,6 +47,8 @@ FROM base AS runtime
|
||||
|
||||
ARG SERVICE_NAME
|
||||
ARG VERSION
|
||||
ARG LOG_LVL=INFO
|
||||
ENV LOG_LVL=$LOG_LVL
|
||||
ENV SERVICE_NAME=${SERVICE_NAME}
|
||||
|
||||
|
||||
|
||||
@@ -10,8 +10,21 @@ configuration:
|
||||
Weitere wechselrichterspezifische Parameter (z.B. Polling Mode) können im
|
||||
Konfigurationsblock gesetzt werden.
|
||||
|
||||
Die Seriennummer der GEN3 Wechselrichter beginnen mit `R17` und die der GEN3PLUS
|
||||
Wechselrichter mir `Y17`oder `47`!
|
||||
Die Seriennummer der GEN3 Wechselrichter beginnen mit `R17` oder `R47` und die der GEN3PLUS
|
||||
Wechselrichter mit `Y17`oder `Y47`!
|
||||
|
||||
Siehe Beispielkonfiguration im Dokumentations-Tab
|
||||
batteries:
|
||||
name: Batterien
|
||||
description: >+
|
||||
Für jeden Energiespeicher muss die Seriennummer des Speichers einer MQTT
|
||||
Definition zugeordnet werden. Dazu wird der entsprechende Konfigurationsblock mit der
|
||||
16-stellige Seriennummer gestartet, so dass alle nachfolgenden Parameter diesem
|
||||
Speicher zugeordnet sind.
|
||||
Weitere speicherspezifische Parameter (z.B. Polling Mode) können im
|
||||
Konfigurationsblock gesetzt werden.
|
||||
|
||||
Die Seriennummer der GEN3PLUS Batteriespeicher beginnen mit `410`!
|
||||
|
||||
Siehe Beispielkonfiguration im Dokumentations-Tab
|
||||
|
||||
@@ -25,14 +38,14 @@ configuration:
|
||||
ein => normaler Proxy-Betrieb.
|
||||
aus => Der Wechselrichter wird vom Internet isoliert.
|
||||
solarman.enabled:
|
||||
name: Verbindung zur Solarman Cloud - nur für GEN3PLUS Wechselrichter
|
||||
name: Verbindung zur Solarman/TSUN Cloud - nur für GEN3PLUS Wechselrichter
|
||||
description: >+
|
||||
Schaltet die Verbindung zur Solarman Cloud ein/aus.
|
||||
Diese Verbindung ist erforderlich, wenn Sie Daten an die Solarman Cloud senden möchten,
|
||||
z.B. um die Solarman Apps zu nutzen oder Firmware-Updates zu erhalten.
|
||||
Schaltet die Verbindung zur Solarman oder TSUN Cloud ein/aus.
|
||||
Diese Verbindung ist erforderlich, wenn Sie Daten an die Cloud senden möchten,
|
||||
z.B. um die Solarman App oder TSUN Smart App zu nutzen oder Firmware-Updates zu erhalten.
|
||||
|
||||
ein => normaler Proxy-Betrieb.
|
||||
aus => Der Wechselrichter wird vom Internet isoliert.
|
||||
aus => Die GEN3PLUS Geräte werden vom Internet isoliert.
|
||||
inverters.allow_all:
|
||||
name: Erlaube Verbindungen von sämtlichen Wechselrichtern
|
||||
description: >-
|
||||
|
||||
@@ -7,13 +7,27 @@ configuration:
|
||||
definition. To do this, the corresponding configuration block is started with
|
||||
16-digit serial number so that all subsequent parameters are assigned
|
||||
to this inverter. Further inverter-specific parameters (e.g. polling mode) can be set
|
||||
in the configuration block
|
||||
in the configuration block.
|
||||
|
||||
The serial numbers of all GEN3 inverters start with `R17` and that of the GEN3PLUS
|
||||
inverters with ‘Y17’ or ‘47’!
|
||||
The serial numbers of all GEN3 inverters start with `R17` or `R47` and that of the GEN3PLUS
|
||||
inverters with ‘Y17’ or ‘Y47’!
|
||||
|
||||
For reference see example configuration in Documentation Tab
|
||||
|
||||
batteries:
|
||||
name: Energy Storages
|
||||
description: >+
|
||||
For each energy storage device, the serial number of the storage device must be
|
||||
assigned to an MQTT definition. To do this, the corresponding configuration block
|
||||
is started with the 16-digit serial number so that all subsequent parameters are
|
||||
assigned to this energy storage. Further inverter-specific parameters (e.g. polling
|
||||
mode) can be set in the configuration block.
|
||||
|
||||
The serial numbers of all GEN3PLUS energy storages start with ‘410’!
|
||||
|
||||
For reference see example configuration in Documentation Tab
|
||||
|
||||
|
||||
tsun.enabled:
|
||||
name: Connection to TSUN Cloud - for GEN3 inverter only
|
||||
description: >+
|
||||
@@ -24,14 +38,14 @@ configuration:
|
||||
on => normal proxy operation.
|
||||
off => The Inverter become isolated from Internet.
|
||||
solarman.enabled:
|
||||
name: Connection to Solarman Cloud - for GEN3PLUS inverter only
|
||||
name: Connection to Solarman/TSUN Cloud - for GEN3PLUS inverter only
|
||||
description: >+
|
||||
switch on/off connection to the Solarman cloud.
|
||||
This connection is only required if you want send data to the Solarman cloud
|
||||
eg. to use the Solarman APPs or receive firmware updates.
|
||||
switch on/off connection to the Solarman or TSUN cloud.
|
||||
This connection is only required if you want send data to the cloud
|
||||
eg. to use the Solarman APP, the TSUN Smart APP or receive firmware updates.
|
||||
|
||||
on => normal proxy operation.
|
||||
off => The Inverter become isolated from Internet
|
||||
off => The GEN3PLUS devices become isolated from Internet
|
||||
inverters.allow_all:
|
||||
name: Allow all connections from all inverters
|
||||
description: >-
|
||||
|
||||
52
ha_addons/templates/apparmor.jinja
Normal file
52
ha_addons/templates/apparmor.jinja
Normal file
@@ -0,0 +1,52 @@
|
||||
#include <tunables/global>
|
||||
|
||||
profile {{slug}} flags=(attach_disconnected,mediate_deleted) {
|
||||
#include <abstractions/base>
|
||||
|
||||
# Capabilities
|
||||
file,
|
||||
signal (send) set=(kill,term,int,hup,cont),
|
||||
|
||||
# S6-Overlay
|
||||
/init ix,
|
||||
/bin/** ix,
|
||||
/usr/bin/** ix,
|
||||
/run/{s6,s6-rc*,service}/** ix,
|
||||
/package/** ix,
|
||||
/command/** ix,
|
||||
/etc/services.d/** rwix,
|
||||
/etc/cont-init.d/** rwix,
|
||||
/etc/cont-finish.d/** rwix,
|
||||
/run/{,**} rwk,
|
||||
/dev/tty rw,
|
||||
|
||||
# Bashio
|
||||
/usr/lib/bashio/** ix,
|
||||
/tmp/** rwk,
|
||||
|
||||
# Access to options.json and other files within your addon
|
||||
/data/** rw,
|
||||
|
||||
# Start new profile for service
|
||||
/usr/bin/myprogram cx -> myprogram,
|
||||
|
||||
profile myprogram flags=(attach_disconnected,mediate_deleted) {
|
||||
#include <abstractions/base>
|
||||
|
||||
# Receive signals from S6-Overlay
|
||||
signal (receive) peer=*_{{slug}},
|
||||
|
||||
# Access to options.json and other files within your addon
|
||||
/data/** rw,
|
||||
|
||||
# Access to mapped volumes specified in config.json
|
||||
/share/** rw,
|
||||
|
||||
# Access required for service functionality
|
||||
/usr/bin/myprogram r,
|
||||
/bin/bash rix,
|
||||
/bin/echo ix,
|
||||
/etc/passwd r,
|
||||
/dev/tty rw,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
name: {{name}}
|
||||
description: {{description}}
|
||||
version: {% if version is defined and version|length %} {{version}} {% else %} {{AppVersion}} {% endif %}
|
||||
version: {% if version is defined and version|length %} {{version}} {% elif BuildID is defined and BuildID|length %} {{AppVersion}}-{{BuildID}} {% else %} {{AppVersion}} {% endif %}
|
||||
image: {{image}}
|
||||
url: https://github.com/s-allius/tsun-gen3-proxy
|
||||
slug: {{slug}}
|
||||
@@ -24,29 +24,25 @@ ports:
|
||||
5005/tcp: 5005
|
||||
10000/tcp: 10000
|
||||
|
||||
# FIXME: we disabled the watchdog due to exceptions in the ha supervisor. See: https://github.com/s-allius/tsun-gen3-proxy/issues/249
|
||||
# watchdog: "http://[HOST]:[PORT:8127]/-/healthy"
|
||||
watchdog: "http://[HOST]:[PORT:8127]/-/healthy"
|
||||
|
||||
# Definition of parameters in the configuration tab of the addon
|
||||
# parameters are available within the container as /data/options.json
|
||||
# and should become picked up by the proxy - current workaround as a transfer script
|
||||
# TODO: check again for multi hierarchie parameters
|
||||
|
||||
schema:
|
||||
inverters:
|
||||
- serial: match(^(R17|Y17|Y47).{13}$)
|
||||
- serial: match(^(R17|R47|Y17|Y47).{13}$)
|
||||
monitor_sn: int?
|
||||
node_id: str
|
||||
suggested_area: str
|
||||
modbus_polling: bool
|
||||
client_mode.host: match(\b((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b)
|
||||
client_mode.host: match(\b((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b)?
|
||||
client_mode.port: port?
|
||||
client_mode.forward: bool?
|
||||
#strings: # leider funktioniert es nicht die folgenden 3 parameter im schema aufzulisten. möglicherweise wird die verschachtelung nicht unterstützt.
|
||||
# - string: str
|
||||
# type: str
|
||||
# manufacturer: str
|
||||
# daher diese variante
|
||||
modbus_scanning.start: int(0,65535)?
|
||||
modbus_scanning.step: int(0,65535)?
|
||||
modbus_scanning.bytes: int(1,80)?
|
||||
pv1.manufacturer: str?
|
||||
pv1.type: str?
|
||||
pv2.manufacturer: str?
|
||||
@@ -62,6 +58,19 @@ schema:
|
||||
tsun.enabled: bool
|
||||
solarman.enabled: bool
|
||||
inverters.allow_all: bool
|
||||
batteries:
|
||||
- serial: match(^(410).{13}$)
|
||||
monitor_sn: int
|
||||
node_id: str
|
||||
suggested_area: str
|
||||
modbus_polling: bool
|
||||
client_mode.host: match(\b((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\b)?
|
||||
client_mode.port: port?
|
||||
client_mode.forward: bool?
|
||||
pv1.manufacturer: str?
|
||||
pv1.type: str?
|
||||
pv2.manufacturer: str?
|
||||
pv2.type: str?
|
||||
|
||||
# optionale parameter
|
||||
|
||||
@@ -90,17 +99,21 @@ schema:
|
||||
# If any default value is given, the option becomes a required value.
|
||||
options:
|
||||
inverters:
|
||||
- serial: R17E760702080400
|
||||
node_id: PV-Garage
|
||||
- serial: R17E000000000000
|
||||
monitor_sn: 0
|
||||
node_id: inv_1
|
||||
suggested_area: Roof
|
||||
modbus_polling: false
|
||||
pv1.manufacturer: Shinefar
|
||||
pv1.type: SF-M18/144550
|
||||
pv2.manufacturer: Shinefar
|
||||
pv2.type: SF-M18/144550
|
||||
batteries:
|
||||
- serial: 4100000000000000
|
||||
monitor_sn: 0
|
||||
node_id: bat_1
|
||||
suggested_area: Garage
|
||||
modbus_polling: false
|
||||
# strings:
|
||||
# - string: PV1
|
||||
# type: SF-M18/144550
|
||||
# manufacturer: Shinefar
|
||||
# - string: PV2
|
||||
# type: SF-M18/144550
|
||||
# manufacturer: Shinefar
|
||||
pv1.manufacturer: Shinefar
|
||||
pv1.type: SF-M18/144550
|
||||
pv2.manufacturer: Shinefar
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
{
|
||||
"name": "TSUN-Proxy (Debug)",
|
||||
"description": "MQTT Proxy for TSUN Photovoltaic Inverters with Debug Logging",
|
||||
"version": "debug",
|
||||
"image": "docker.io/sallius/tsun-gen3-addon",
|
||||
"slug": "tsun-proxy-debug",
|
||||
"advanced": true,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
{
|
||||
"name": "TSUN-Proxy (Dev)",
|
||||
"description": "MQTT Proxy for TSUN Photovoltaic Inverters",
|
||||
"version": "dev",
|
||||
"image": "docker.io/sallius/tsun-gen3-addon",
|
||||
"slug": "tsun-proxy-dev",
|
||||
"advanced": false,
|
||||
|
||||
10
ha_addons/templates/rc_data.json
Normal file
10
ha_addons/templates/rc_data.json
Normal file
@@ -0,0 +1,10 @@
|
||||
|
||||
{
|
||||
"name": "TSUN-Proxy (Release Candidate)",
|
||||
"description": "MQTT Proxy for TSUN Photovoltaic Inverters",
|
||||
"version": "rc",
|
||||
"image": "ghcr.io/s-allius/tsun-gen3-addon",
|
||||
"slug": "tsun-proxy-rc",
|
||||
"advanced": true,
|
||||
"stage": "experimental"
|
||||
}
|
||||
@@ -15,6 +15,10 @@ sonar.sources=app/src/
|
||||
sonar.python.version=3.12
|
||||
sonar.tests=system_tests/,app/tests/
|
||||
sonar.exclusions=**/.vscode/**/*
|
||||
|
||||
# disable code dupication check for config grammar
|
||||
sonar.cpd.exclusions=app/src/cnf/config.py
|
||||
|
||||
# Name your criteria
|
||||
sonar.issue.ignore.multicriteria=e1,e2
|
||||
|
||||
|
||||
@@ -93,6 +93,155 @@ def msg_inverter_ind(): # Data indication from the inverter
|
||||
msg += b'\x53\x00\x00'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def msg_inverter_ind2(): # Data indication from the inverter
|
||||
msg = b'\x00\x00\x08\xff\x10'+ get_sn() + b'\x91\x04\x01\x90\x00\x00\x10'+get_inv_no()
|
||||
msg += b'\x01\x00\x00\x01'
|
||||
msg += b'\x95\x18\x5e\x1d\x80\x00\x00\x01\x2c\x00\x00\x00\x64\x53\x00\x00' # | ..^.....,...dS..
|
||||
msg += b'\x00\x00\x00\xc8\x53\x44\x00\x00\x00\x01\x2c\x53\x00\x00\x00\x00' # | ....SD....,S....
|
||||
msg += b'\x01\x90\x49\x00\x00\x00\x00\x00\x00\x01\x91\x53\x00\x00\x00\x00' # | ..I........S....
|
||||
msg += b'\x01\x92\x53\x00\x00\x00\x00\x01\x93\x53\x00\x00\x00\x00\x01\x94' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x01\x95\x53\x00\x00\x00\x00\x01\x96\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x01\x97\x53\x00\x00\x00\x00\x01\x98\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x01\x99\x53\x00\x00\x00\x00\x01\x9a\x53\x00\x00\x00\x00\x01' # | ...S......S.....
|
||||
msg += b'\x9b\x53\x00\x00\x00\x00\x01\x9c\x53\x00\x00\x00\x00\x01\x9d\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x01\x9e\x53\x00\x00\x00\x00\x01\x9f\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x01\xa0\x53\x00\x00\x00\x00\x01\xf4\x49\x00\x00\x00\x00' # | ....S......I....
|
||||
msg += b'\x00\x00\x01\xf5\x53\x00\x00\x00\x00\x01\xf6\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x01\xf7\x53\x00\x00\x00\x00\x01\xf8\x53\x00\x00\x00\x00\x01\xf9' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x01\xfa\x53\x00\x00\x00\x00\x01\xfb\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x01\xfc\x53\x00\x00\x00\x00\x01\xfd\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x01\xfe\x53\x00\x00\x00\x00\x01\xff\x53\x00\x00\x00\x00\x02' # | ...S......S.....
|
||||
msg += b'\x00\x53\x00\x00\x00\x00\x02\x01\x53\x00\x00\x00\x00\x02\x02\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x02\x03\x53\x00\x00\x00\x00\x02\x04\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x02\x58\x49\x00\x00\x00\x00\x00\x00\x02\x59\x53\x00\x00' # | ...XI.......YS..
|
||||
msg += b'\x00\x00\x02\x5a\x53\x00\x00\x00\x00\x02\x5b\x53\x00\x00\x00\x00' # | ...ZS.....[S....
|
||||
msg += b'\x02\x5c\x53\x00\x00\x00\x00\x02\x5d\x53\x00\x00\x00\x00\x02\x5e' # | .\S.....]S.....^
|
||||
msg += b'\x53\x00\x00\x00\x00\x02\x5f\x53\x00\x00\x00\x00\x02\x60\x53\x00' # | S....._S.....`S.
|
||||
msg += b'\x00\x00\x00\x02\x61\x53\x00\x00\x00\x00\x02\x62\x53\x00\x00\x00' # | ....aS.....bS...
|
||||
msg += b'\x00\x02\x63\x53\x00\x00\x00\x00\x02\x64\x53\x00\x00\x00\x00\x02' # | ..cS.....dS.....
|
||||
msg += b'\x65\x53\x00\x00\x00\x00\x02\x66\x53\x00\x00\x00\x00\x02\x67\x53' # | eS.....fS.....gS
|
||||
msg += b'\x00\x00\x00\x00\x02\x68\x53\x00\x00\x00\x00\x02\xbc\x49\x00\x00' # | .....hS......I..
|
||||
msg += b'\x00\x00\x00\x00\x02\xbd\x53\x00\x00\x00\x00\x02\xbe\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x02\xbf\x53\x00\x00\x00\x00\x02\xc0\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x02\xc1\x53\x00\x00\x00\x00\x02\xc2\x53\x00\x00\x00\x00\x02\xc3' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x02\xc4\x53\x00\x00\x00\x00\x02\xc5\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x02\xc6\x53\x00\x00\x00\x00\x02\xc7\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x02\xc8\x53\x00\x00\x00\x00\x02\xc9\x53\x00\x00\x00\x00\x02' # | ...S......S.....
|
||||
msg += b'\xca\x53\x00\x00\x00\x00\x02\xcb\x53\x00\x00\x00\x00\x02\xcc\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x03\x20\x53\x00\x01\x00\x00\x03\x84\x53\x11\x68' # | ..... S......S.h
|
||||
msg += b'\x00\x00\x03\xe8\x46\x44\x23\xd1\xec\x00\x00\x04\x4c\x46\x43\xa3' # | ....FD#.....LFC.
|
||||
msg += b'\xb3\x33\x00\x00\x04\xb0\x46\x00\x00\x00\x00\x00\x00\x05\x14\x46' # | .3....F........F
|
||||
msg += b'\x43\x6e\x80\x00\x00\x00\x05\x78\x46\x3d\x4c\xcc\xcd\x00\x00\x05' # | Cn.....xF=L.....
|
||||
msg += b'\xdc\x46\x00\x00\x00\x00\x00\x00\x06\x40\x46\x42\x48\x00\x00\x00' # | .F.......@FBH...
|
||||
msg += b'\x00\x06\xa4\x53\x00\x03\x00\x00\x07\x08\x53\x00\x0c\x00\x00\x07' # | ...S......S.....
|
||||
msg += b'\x6c\x53\x00\x50\x00\x00\x07\xd0\x46\x43\xa3\xb3\x33\x00\x00\x08' # | lS.P....FC..3...
|
||||
msg += b'\x34\x53\x0b\xb8\x00\x00\x08\x98\x46\x00\x00\x00\x00\x00\x00\x08' # | 4S......F.......
|
||||
msg += b'\xfc\x46\x00\x00\x00\x00\x00\x00\x09\x60\x46\x41\xee\xe1\x48\x00' # | .F.......`FA..H.
|
||||
msg += b'\x00\x09\xc4\x53\x00\x00\x00\x00\x0a\x28\x46\x41\xf2\x00\x00\x00' # | ...S.....(FA....
|
||||
msg += b'\x00\x0a\x8c\x46\x3f\xac\x28\xf6\x00\x00\x0a\xf0\x53\x00\x0c\x00' # | ...F?.(.....S...
|
||||
msg += b'\x00\x0b\x54\x53\x00\x00\x00\x00\x0b\xb8\x53\x00\x00\x00\x00\x0c' # | ..TS......S.....
|
||||
msg += b'\x1c\x53\x00\x00\x00\x00\x0c\x80\x53\x00\x00\x00\x00\x0c\xe4\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x0d\x48\x53\x00\x00\x00\x00\x0d\xac\x53\x00\x00' # | .....HS......S..
|
||||
msg += b'\x00\x00\x0e\x10\x53\x00\x00\x00\x00\x0e\x74\x53\x00\x00\x00\x00' # | ....S.....tS....
|
||||
msg += b'\x0e\xd8\x53\x00\x00\x00\x00\x0f\x3c\x53\x00\x00\x00\x00\x0f\xa0' # | ..S.....<S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x10\x04\x53\x00\x00\x00\x00\x10\x68\x53\x00' # | S......S.....hS.
|
||||
msg += b'\x00\x00\x00\x10\xcc\x53\x00\x00\x00\x00\x11\x30\x53\x00\x00\x00' # | .....S.....0S...
|
||||
msg += b'\x00\x11\x94\x53\x00\x00\x00\x00\x11\xf8\x53\x00\x00\x00\x00\x12' # | ...S......S.....
|
||||
msg += b'\x5c\x53\x00\x00\x00\x00\x12\xc0\x53\x00\x00\x00\x00\x13\x24\x46' # | \S......S.....$F
|
||||
msg += b'\x42\x9d\x33\x33\x00\x00\x13\x88\x46\x00\x00\x00\x00\x00\x00\x13' # | B.33....F.......
|
||||
msg += b'\xec\x46\x00\x00\x00\x00\x00\x00\x14\x50\x46\x42\xdc\x00\x00\x00' # | .F.......PFB....
|
||||
msg += b'\x00\x14\xb4\x53\x00\x00\x00\x00\x15\x18\x53\x00\x00\x00\x00\x15' # | ...S......S.....
|
||||
msg += b'\x7c\x53\x00\x00\x00\x00\x15\x7d\x53\x00\x00\x00\x00\x15\x7e\x53' # | |S.....}S.....~S
|
||||
msg += b'\x00\x00\x00\x00\x15\x7f\x53\x00\x00\x00\x00\x15\x80\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x15\x81\x53\x00\x00\x00\x00\x15\x82\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x15\x83\x53\x00\x00\x00\x00\x15\x84\x53\x00\x00\x00\x00\x15\x85' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x15\x86\x53\x00\x00\x00\x00\x15\x87\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x15\x88\x53\x00\x00\x00\x00\x15\x89\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x15\x8a\x53\x00\x00\x00\x00\x15\x8b\x53\x00\x00\x00\x00\x15' # | ...S......S.....
|
||||
msg += b'\x8c\x53\x00\x00\x00\x00\x15\xe0\x46\x42\x68\x66\x66\x00\x00\x16' # | .S......FBhff...
|
||||
msg += b'\x44\x46\x00\x00\x00\x00\x00\x00\x16\xa8\x46\x00\x00\x00\x00\x00' # | DF........F.....
|
||||
msg += b'\x00\x17\x0c\x46\x42\xdc\x00\x00\x00\x00\x17\x70\x53\x00\x00\x00' # | ...FB......pS...
|
||||
msg += b'\x00\x17\xd4\x53\x00\x00\x00\x00\x18\x38\x53\x00\x00\x00\x00\x18' # | ...S.....8S.....
|
||||
msg += b'\x39\x53\x00\x00\x00\x00\x18\x3a\x53\x00\x00\x00\x00\x18\x3b\x53' # | 9S.....:S.....;S
|
||||
msg += b'\x00\x00\x00\x00\x18\x3c\x53\x00\x00\x00\x00\x18\x3d\x53\x00\x00' # | .....<S.....=S..
|
||||
msg += b'\x00\x00\x18\x3e\x53\x00\x00\x00\x00\x18\x3f\x53\x00\x00\x00\x00' # | ...>S.....?S....
|
||||
msg += b'\x18\x40\x53\x00\x00\x00\x00\x18\x41\x53\x00\x00\x00\x00\x18\x42' # | .@S.....AS.....B
|
||||
msg += b'\x53\x00\x00\x00\x00\x18\x43\x53\x00\x00\x00\x00\x18\x44\x53\x00' # | S.....CS.....DS.
|
||||
msg += b'\x00\x00\x00\x18\x45\x53\x00\x00\x00\x00\x18\x46\x53\x00\x00\x00' # | ....ES.....FS...
|
||||
msg += b'\x00\x18\x47\x53\x00\x00\x00\x00\x18\x48\x53\x00\x00\x00\x00\x18' # | ..GS.....HS.....
|
||||
msg += b'\x9c\x46\x42\x6b\x33\x33\x00\x00\x19\x00\x46\x00\x00\x00\x00\x00' # | .FBk33....F.....
|
||||
msg += b'\x00\x19\x64\x46\x00\x00\x00\x00\x00\x00\x19\xc8\x46\x42\xdc\x00' # | ..dF........FB..
|
||||
msg += b'\x00\x00\x00\x1a\x2c\x53\x00\x00\x00\x00\x1a\x90\x53\x00\x00\x00' # | ....,S......S...
|
||||
msg += b'\x00\x1a\xf4\x53\x00\x00\x00\x00\x1a\xf5\x53\x00\x00\x00\x00\x1a' # | ...S......S.....
|
||||
msg += b'\xf6\x53\x00\x00\x00\x00\x1a\xf7\x53\x00\x00\x00\x00\x1a\xf8\x53' # | .S......S......S
|
||||
msg += b'\x00\x00\x00\x00\x1a\xf9\x53\x00\x00\x00\x00\x1a\xfa\x53\x00\x00' # | ......S......S..
|
||||
msg += b'\x00\x00\x1a\xfb\x53\x00\x00\x00\x00\x1a\xfc\x53\x00\x00\x00\x00' # | ....S......S....
|
||||
msg += b'\x1a\xfd\x53\x00\x00\x00\x00\x1a\xfe\x53\x00\x00\x00\x00\x1a\xff' # | ..S......S......
|
||||
msg += b'\x53\x00\x00\x00\x00\x1b\x00\x53\x00\x00\x00\x00\x1b\x01\x53\x00' # | S......S......S.
|
||||
msg += b'\x00\x00\x00\x1b\x02\x53\x00\x00\x00\x00\x1b\x03\x53\x00\x00\x00' # | .....S......S...
|
||||
msg += b'\x00\x1b\x04\x53\x00\x00\x00\x00\x1b\x58\x53\x00\x00\x00\x00\x1b' # | ...S.....XS.....
|
||||
msg += b'\xbc\x53\x11\x3d\x00\x00\x1c\x20\x46\x3c\x23\xd7\x0a\x00\x00\x1c' # | .S.=... F<#.....
|
||||
msg += b'\x84\x46\x00\x00\x00\x00\x00\x00\x1c\xe8\x46\x42\x04\x00\x00\x00' # | .F........FB....
|
||||
msg += b'\x00\x1d\x4c\x46\x00\x00\x00\x00\x00\x00\x1d\xb0\x46\x00\x00\x00' # | ..LF........F...
|
||||
msg += b'\x00\x00\x00\x1e\x14\x53\x00\x02\x00\x00\x1e\x78\x46\x41\x8b\x33' # | .....S.....xFA.3
|
||||
msg += b'\x33\x00\x00\x1e\xdc\x46\x3c\xa3\xd7\x0a\x00\x00\x1f\x40\x46\x3e' # | 3....F<......@F>
|
||||
msg += b'\x99\x99\x9a\x00\x00\x1f\xa4\x46\x40\x99\x99\x9a\x00\x00\x20\x08' # | .......F@..... .
|
||||
msg += b'\x53\x00\x00\x00\x00\x20\x6c\x53\x00\x00\x00\x00\x20\xd0\x53\x05' # | S.... lS.... .S.
|
||||
msg += b'\x00\x00\x00\x20\xd1\x53\x00\x00\x00\x00\x20\xd2\x53\x00\x00\x00' # | ... .S.... .S...
|
||||
msg += b'\x00\x20\xd3\x53\x00\x00\x00\x00\x20\xd4\x53\x00\x00\x00\x00\x20' # | . .S.... .S....
|
||||
msg += b'\xd5\x53\x00\x00\x00\x00\x20\xd6\x53\x00\x00\x00\x00\x20\xd7\x53' # | .S.... .S.... .S
|
||||
msg += b'\x00\x00\x00\x00\x20\xd8\x53\x00\x00\x00\x00\x20\xd9\x53\x00\x01' # | .... .S.... .S..
|
||||
msg += b'\x00\x00\x20\xda\x53\x00\x00\x00\x00\x20\xdb\x53\x00\x01\x00\x00' # | .. .S.... .S....
|
||||
msg += b'\x20\xdc\x53\x00\x00\x00\x00\x20\xdd\x53\x00\x00\x00\x00\x20\xde' # | .S.... .S.... .
|
||||
msg += b'\x53\x00\x00\x00\x00\x20\xdf\x53\x00\x00\x00\x00\x20\xe0\x53\x00' # | S.... .S.... .S.
|
||||
msg += b'\x00\x00\x00\x21\x34\x46\x00\x00\x00\x00\x00\x00\x21\x98\x46\x00' # | ...!4F......!.F.
|
||||
msg += b'\x00\x00\x00\x00\x00\x21\xfc\x46\x00\x00\x00\x00\x00\x00\x22\x60' # | .....!.F......"`
|
||||
msg += b'\x46\x00\x00\x00\x00\x00\x00\x22\xc4\x53\x00\x00\x00\x00\x23\x28' # | F......".S....#(
|
||||
msg += b'\x53\x00\x00\x00\x00\x23\x8c\x53\x00\x00\x00\x00\x23\x8d\x53\x00' # | S....#.S....#.S.
|
||||
msg += b'\x00\x00\x00\x23\x8e\x53\x00\x00\x00\x00\x23\x8f\x53\x00\x00\x00' # | ...#.S....#.S...
|
||||
msg += b'\x00\x23\x90\x53\x00\x00\x00\x00\x23\x91\x53\x00\x00\x00\x00\x23' # | .#.S....#.S....#
|
||||
msg += b'\x92\x53\x00\x00\x00\x00\x23\x93\x53\x00\x00\x00\x00\x23\x94\x53' # | .S....#.S....#.S
|
||||
msg += b'\x00\x00\x00\x00\x23\x95\x53\x00\x00\x00\x00\x23\x96\x53\x00\x00' # | ....#.S....#.S..
|
||||
msg += b'\x00\x00\x23\x97\x53\x00\x00\x00\x00\x23\x98\x53\x00\x00\x00\x00' # | ..#.S....#.S....
|
||||
msg += b'\x23\x99\x53\x00\x00\x00\x00\x23\x9a\x53\x00\x00\x00\x00\x23\x9b' # | #.S....#.S....#.
|
||||
msg += b'\x53\x00\x00\x00\x00\x23\x9c\x53\x00\x00\x00\x00\x23\xf0\x46\x00' # | S....#.S....#.F.
|
||||
msg += b'\x00\x00\x00\x00\x00\x24\x54\x46\x00\x00\x00\x00\x00\x00\x24\xb8' # | .....$TF......$.
|
||||
msg += b'\x46\x00\x00\x00\x00\x00\x00\x25\x1c\x46\x00\x00\x00\x00\x00\x00' # | F......%.F......
|
||||
msg += b'\x25\x80\x53\x00\x00\x00\x00\x25\xe4\x53\x00\x00\x00\x00\x26\x48' # | %.S....%.S....&H
|
||||
msg += b'\x53\x00\x00\x00\x00\x26\x49\x53\x00\x00\x00\x00\x26\x4a\x53\x00' # | S....&IS....&JS.
|
||||
msg += b'\x00\x00\x00\x26\x4b\x53\x00\x00\x00\x00\x26\x4c\x53\x00\x00\x00' # | ...&KS....&LS...
|
||||
msg += b'\x00\x26\x4d\x53\x00\x00\x00\x00\x26\x4e\x53\x00\x00\x00\x00\x26' # | .&MS....&NS....&
|
||||
msg += b'\x4f\x53\x00\x00\x00\x00\x26\x50\x53\x00\x00\x00\x00\x26\x51\x53' # | OS....&PS....&QS
|
||||
msg += b'\x00\x00\x00\x00\x26\x52\x53\x00\x00\x00\x00\x26\x53\x53\x00\x00' # | ....&RS....&SS..
|
||||
msg += b'\x00\x00\x26\x54\x53\x00\x00\x00\x00\x26\x55\x53\x00\x00\x00\x00' # | ..&TS....&US....
|
||||
msg += b'\x26\x56\x53\x00\x00\x00\x00\x26\x57\x53\x00\x00\x00\x00\x26\x58' # | &VS....&WS....&X
|
||||
msg += b'\x53\x00\x00\x00\x00\x26\xac\x53\x00\x00\x00\x00\x27\x10\x53\x11' # | S....&.S....'.S.
|
||||
msg += b'\x3d\x00\x00\x27\x74\x46\x00\x00\x00\x00\x00\x00\x27\xd8\x46\x00' # | =..'tF......'.F.
|
||||
msg += b'\x00\x00\x00\x00\x00\x28\x3c\x46\x42\x03\xf5\xc3\x00\x00\x28\xa0' # | .....(<FB.....(.
|
||||
msg += b'\x46\x00\x00\x00\x00\x00\x00\x29\x04\x46\x00\x00\x00\x00\x00\x00' # | F......).F......
|
||||
msg += b'\x29\x68\x53\x00\x02\x00\x00\x29\xcc\x53\x00\x03\x00\x00\x2a\x30' # | )hS....).S....*0
|
||||
msg += b'\x46\x42\x20\x00\x00\x00\x00\x2a\x94\x46\x42\x20\x00\x00\x00\x00' # | FB ....*.FB ....
|
||||
msg += b'\x2a\xf8\x46\x44\x20\x00\x00\x00\x00\x2b\x5c\x46\x43\x7b\x00\x00' # | *.FD ....+\FC{..
|
||||
msg += b'\x00\x00\x2b\xc0\x46\x43\x50\x00\x00\x00\x00\x2c\x24\x46\x42\x48' # | ..+.FCP....,$FBH
|
||||
msg += b'\x5c\x29\x00\x00\x2c\x88\x46\x42\x47\xa3\xd7\x00\x00\x2c\xec\x53' # | \)..,.FBG....,.S
|
||||
msg += b'\x00\x00\x00\x00\x2d\x50\x46\x43\x42\x00\x00\x00\x00\x2d\xb4\x46' # | ....-PFCB....-.F
|
||||
msg += b'\x42\xbc\x00\x00\x00\x00\x2e\x18\x46\x3f\xe6\x66\x66\x00\x00\x2e' # | B.......F?.ff...
|
||||
msg += b'\x7c\x46\x3f\xe6\x66\x66\x00\x00\x2e\xe0\x46\x43\x7e\x00\x00\x00' # | |F?.ff....FC~...
|
||||
msg += b'\x00\x2f\x44\x46\x43\x83\xf3\x33\x00\x00\x2f\xa8\x46\x3f\xe6\x66' # | ./DFC..3../.F?.f
|
||||
msg += b'\x66\x00\x00\x30\x0c\x46\x3f\xe6\x66\x66\x00\x00\x30\x70\x46\x43' # | f..0.F?.ff..0pFC
|
||||
msg += b'\x7e\x00\x00\x00\x00\x30\xd4\x46\x42\x3f\xeb\x85\x00\x00\x31\x38' # | ~....0.FB?....18
|
||||
msg += b'\x46\x42\x3d\xeb\x85\x00\x00\x31\x9c\x46\x3e\x4c\xcc\xcd\x00\x00' # | FB=....1.F>L....
|
||||
msg += b'\x32\x00\x46\x3e\x4c\xcc\xcd\x00\x00\x32\x64\x46\x42\x4c\x14\x7b' # | 2.F>L....2dFBL.{
|
||||
msg += b'\x00\x00\x32\xc8\x46\x42\x4d\xeb\x85\x00\x00\x33\x2c\x46\x3e\x4c' # | ..2.FBM....3,F>L
|
||||
msg += b'\xcc\xcd\x00\x00\x33\x90\x46\x3e\x4c\xcc\xcd\x00\x00\x33\xf4\x53' # | ....3.F>L....3.S
|
||||
msg += b'\x00\x00\x00\x00\x34\x58\x53\x00\x00\x00\x00\x34\xbc\x53\x04\x00' # | ....4XS....4.S..
|
||||
msg += b'\x00\x00\x35\x20\x53\x00\x01\x00\x00\x35\x84\x53\x13\x9c\x00\x00' # | ..5 S....5.S....
|
||||
msg += b'\x35\xe8\x53\x0f\xa0\x00\x00\x36\x4c\x53\x00\x00\x00\x00\x36\xb0' # | 5.S....6LS....6.
|
||||
msg += b'\x53\x00\x66' # | S.f'
|
||||
return msg
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
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'
|
||||
@@ -234,3 +383,18 @@ def test_ota_req(client_connection, msg_ota_update_req):
|
||||
_ = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
|
||||
def test_send_inv_data2(client_connection, msg_timestamp_req, msg_timestamp_resp, msg_inverter_ind2):
|
||||
s = client_connection
|
||||
try:
|
||||
s.sendall(msg_timestamp_req)
|
||||
_ = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(32.5)
|
||||
# assert data == msg_timestamp_resp
|
||||
try:
|
||||
s.sendall(msg_inverter_ind2)
|
||||
_ = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
|
||||
@@ -10,6 +10,12 @@ SOLARMAN_SNR = os.getenv('SOLARMAN_SNR', '00000080')
|
||||
def get_sn() -> bytes:
|
||||
return bytes.fromhex(SOLARMAN_SNR)
|
||||
|
||||
def get_dcu_sn() -> bytes:
|
||||
return b'\x20\x43\x65\x7b'
|
||||
|
||||
def get_dcu_no() -> bytes:
|
||||
return b'4100000000000001'
|
||||
|
||||
def get_inv_no() -> bytes:
|
||||
return b'T170000000000001'
|
||||
|
||||
@@ -105,6 +111,62 @@ def MsgInvalidInfo(): # Contact Info message wrong start byte
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_dev_ind_msg(): # 0x4110
|
||||
msg = b'\xa5\x3a\x01\x10\x41\x00\x01' +get_dcu_sn() +b'\x02\xc6\xde\x2d\x32'
|
||||
msg += b'\x27\x00\x00\x00\x00\x00\x00\x00\x05\x3c\x78\x01\x5c\x01\x4c\x53'
|
||||
msg += b'\x57\x35\x5f\x30\x31\x5f\x33\x30\x32\x36\x5f\x4e\x53\x5f\x30\x35'
|
||||
msg += b'\x5f\x30\x31\x2e\x30\x30\x2e\x30\x30\x2e\x30\x30\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\xd4\x27\x87\x12\xad\xc0\x31\x39\x32\x2e'
|
||||
msg += b'\x31\x36\x38\x2e\x39\x2e\x31\x34\x00\x00\x00\x00\x01\x00\x01\x26'
|
||||
msg += b'\x30\x0f\x00\xff\x56\x31\x2e\x31\x2e\x30\x30\x2e\x30\x42\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xfe\xfe\x00\x00'
|
||||
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\x7a\x75\x68\x61\x75\x73\x65\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\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\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\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x00\x00\x00\x00\x08\x01\x01\x01\x00\x00\x00\x00'
|
||||
msg += b'\x00\x00\x00\x00\x01'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_dev_rsp_msg(): # 0x1110
|
||||
msg = b'\xa5\x0a\x00\x10\x11\x92\x01' +get_dcu_sn() +b'\x02\x01\x4a\xf6\xa6'
|
||||
msg += b'\x67\x3c\x00\x00\x00'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_data_ind_msg(): # 0x4210
|
||||
msg = b'\xa5\x6f\x00\x10\x42\x92\x02' +get_dcu_sn() +b'\x01\x26\x30\xc7\xde'
|
||||
msg += b'\x2d\x32\x28\x00\x00\x00\x84\x17\x79\x35\x01\x00\x4c\x12\x00\x00'
|
||||
msg += get_dcu_no()
|
||||
msg += b'\x0d\x3a\x00\x0a\x0d\x2c\x00\x00\x00\x00\x08\x20\x00\x00\x00\x00'
|
||||
msg += b'\x14\x0e\xff\xfe\x03\xe8\x0c\x89\x0c\x89\x0c\x89\x0c\x8a\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x8a\x0c\x89\x0c\x89\x0c\x8a\x0c\x8a\x0c\x89\x0c\x89'
|
||||
msg += b'\x0c\x89\x0c\x89\x0c\x88\x00\x0f\x00\x0f\x00\x0f\x00\x0e\x00\x00'
|
||||
msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture
|
||||
def dcu_data_rsp_msg(): # 0x1210
|
||||
msg = b'\xa5\x0a\x00\x10\x12\x93\x02' +get_dcu_sn() +b'\x01\x01\xd1\x96\x04'
|
||||
msg += b'\x66\x3c\x00\x00\x00'
|
||||
msg += correct_checksum(msg)
|
||||
msg += b'\x15'
|
||||
return msg
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ClientConnection():
|
||||
@@ -181,4 +243,24 @@ def test_inavlid_msg(ClientConnection,MsgInvalidInfo,MsgContactInfo, MsgContactR
|
||||
# time.sleep(2.5)
|
||||
checkResponse(data, MsgContactResp)
|
||||
|
||||
|
||||
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)
|
||||
data = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
checkResponse(data, dcu_dev_rsp_msg)
|
||||
|
||||
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)
|
||||
data = s.recv(1024)
|
||||
except TimeoutError:
|
||||
pass
|
||||
# time.sleep(2.5)
|
||||
checkResponse(data, dcu_data_rsp_msg)
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
"path": "."
|
||||
},
|
||||
{
|
||||
"path": "../wiki"
|
||||
"path": "../tsun-gen3-proxy.wiki"
|
||||
},
|
||||
{
|
||||
"name": "ha-addons",
|
||||
"path": "../ha-addons/ha-addons"
|
||||
"path": "../ha-addons"
|
||||
}
|
||||
],
|
||||
"settings": {}
|
||||
|
||||
Reference in New Issue
Block a user