Compare commits

..

20 Commits

Author SHA1 Message Date
Stefan Allius
fbca07bd59 use Blueprints 2025-04-16 01:45:10 +02:00
Stefan Allius
e66d2e864b update changelog 2025-04-16 01:05:02 +02:00
Stefan Allius
e19acbc514 add web UI to add-on 2025-04-16 01:02:16 +02:00
Stefan Allius
6798753d84 move home route into web diretory 2025-04-16 01:01:39 +02:00
Stefan Allius
7d5670b6b5 remove aiohttp by quart (#381)
* remove aiohttp by quart

* remove global proxy_is_up

* add unit test for some routes
2025-04-16 00:54:02 +02:00
Stefan Allius
f98273a3eb S allius/issue362 (#379)
* allow serial number starting with `Y00`

* rollback to python version 3.13.2
2025-04-15 17:54:45 +02:00
Stefan Allius
5452b8efc2 Start version 0.14 (#374)
* bump version to 0.14.0

* update changelog

* set BUILD_ID only for dev and debug versions
2025-04-14 00:01:06 +02:00
renovate[bot]
5568a017ec Update python Docker tag to v3.13.3 (#359)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-13 23:39:44 +02:00
renovate[bot]
4a70f366f1 Update dependency aiomqtt to v2.3.2 (#358)
* Update dependency aiomqtt to v2.3.2

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Stefan Allius <stefan.allius@t-online.de>
2025-04-13 23:38:38 +02:00
Stefan Allius
aac89065fd Update rel 0.13.0 (#370)
* bump version
2025-04-13 20:56:10 +02:00
Stefan Allius
fe250d478a Fix rel build (#368)
* fix rel build run

* disable cache for rc build

* bump python version to 3.12.10-r0
2025-04-13 20:35:21 +02:00
Stefan Allius
83a723c959 Update rel 0.13.0 (#367)
* fix link

(cherry picked from commit 3d422f9249)

* update compose help link

(cherry picked from commit 6d4ff0d508)

* fix link

(cherry picked from commit 3d422f9249)

* fix rel build run
2025-04-13 19:59:05 +02:00
Stefan Allius
abe6bfb013 update compose help link (#361) 2025-04-10 23:58:59 +02:00
renovate[bot]
31f4f05bed Update ghcr.io/hassio-addons/base Docker tag to v17.2.4 (#355)
* Update ghcr.io/hassio-addons/base Docker tag to v17.2.3

* Update ghcr.io/hassio-addons/base Docker tag to v17.2.4

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Stefan Allius <stefan.allius@t-online.de>
2025-04-08 20:47:11 +02:00
Stefan Allius
8ca91c2fdd define the value 2 for the out status (#356) 2025-04-07 23:47:29 +02:00
Stefan Allius
ea749dcce6 enforce numbered release candidates (#353) 2025-04-06 22:28:32 +02:00
Stefan Allius
af5604d029 add alarm bitfields (#352)
- fix bitfield of the inverter alarms
- add batterie alarms
2025-04-06 20:07:17 +02:00
renovate[bot]
015b6b8db0 Update dependency pytest-cov to v6.1.1 (#346)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-06 01:28:27 +02:00
Stefan Allius
7782a3cb57 Add two states build from the measurements (#351)
* Add two states build from the measurements
- Batterie Status calculated from the batt current
- Power Supply State calc from the out Power

* improve test coverage
2025-04-06 01:21:41 +02:00
Stefan Allius
3d073acc58 Cleanup MQTT json format for DCU batterie (#349)
* Cleanup MQTT json format for DCU batterie
- add hw and sw version
- rename total generation into total charging energy
- rename cell temperature sensors
- restructure json format
- adapt unit tests

* revert changed test packages
2025-04-05 22:30:57 +02:00
25 changed files with 329 additions and 149 deletions

View File

@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased] ## [unreleased]
- add web UI to add-on
- allow `Y00` serial numbers for GEN3PLUS devices
## [0.13.0] - 2025-04-13
- update dependency python to 3.13 - update dependency python to 3.13
- add initial support for TSUN MS-3000 - add initial support for TSUN MS-3000
- add initial apparmor support [#293](https://github.com/s-allius/tsun-gen3-proxy/issues/293) - add initial apparmor support [#293](https://github.com/s-allius/tsun-gen3-proxy/issues/293)

View File

@@ -141,7 +141,7 @@ No special configuration is required for the Docker container if it is built and
On the host, two directories (for log files and for config files) must be mapped. If necessary, the UID of the proxy process can be adjusted, which is also the owner of the log and configuration files. On the host, two directories (for log files and for config files) must be mapped. If necessary, the UID of the proxy process can be adjusted, which is also the owner of the log and configuration files.
A description of the configuration parameters can be found [here](https://github.com/s-allius/tsun-gen3-proxy/wiki/Configuration-toml#docker-compose-environment-variables). A description of the configuration parameters can be found [here](https://github.com/s-allius/tsun-gen3-proxy/wiki/configuration-env#docker-compose-environment-variables)
## Proxy Configuration ## Proxy Configuration

View File

@@ -1 +1 @@
0.13.0 0.14.0

View File

@@ -22,7 +22,16 @@ dev debug:
export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \ export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \
docker buildx bake -f docker-bake.hcl $@ docker buildx bake -f docker-bake.hcl $@
preview rc rel: rc:
@[ "${RC}" ] || ( echo ">> RC is not set"; exit 1 )
@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)-$@$(RC) && \
export IMAGE=$(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) && \
docker buildx bake -f docker-bake.hcl $@
preview rel:
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) @echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PUBLIC_CONTAINER_REGISTRY)$(IMAGE)
@echo login at $(PUBLIC_URL) as $(PUBLIC_USER) @echo login at $(PUBLIC_URL) as $(PUBLIC_USER)
@DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)" @DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)"

View File

@@ -1,7 +1,7 @@
flake8==7.2.0 flake8==7.2.0
pytest==8.3.5 pytest==8.3.5
pytest-asyncio==0.26.0 pytest-asyncio==0.26.0
pytest-cov==6.1.0 pytest-cov==6.1.1
python-dotenv==1.1.0 python-dotenv==1.1.0
mock==5.2.0 mock==5.2.0
coverage==7.8.0 coverage==7.8.0

View File

@@ -1,4 +1,4 @@
aiomqtt==2.3.1 aiomqtt==2.3.2
schema==0.7.7 schema==0.7.7
aiocron==2.1 aiocron==2.1
aiohttp==3.11.16 quart==0.20

View File

@@ -19,6 +19,19 @@ class RegisterFunc:
result += prod result += prod
return result return result
@staticmethod
def cmp_values(info: Infos, params: map) -> None | int:
try:
val = info.get_db_value(params['reg'])
if val < params['cmp_val']:
return params['res'][0]
if val == params['cmp_val']:
return params['res'][1]
return params['res'][2]
except Exception:
pass
return None
class RegisterMap: class RegisterMap:
# make the class read/only by using __slots__ # make the class read/only by using __slots__
@@ -136,8 +149,8 @@ class RegisterMap:
0x42010034: {'reg': Register.BATT_PV2_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, DC Voltage PV2 0x42010034: {'reg': Register.BATT_PV2_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, DC Voltage PV2
0x42010036: {'reg': Register.BATT_PV2_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, DC Current PV2 0x42010036: {'reg': Register.BATT_PV2_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, DC Current PV2
0x42010038: {'reg': Register.BATT_TOTAL_CHARG, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 0x42010038: {'reg': Register.BATT_TOTAL_CHARG, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501
0x4201003c: {'reg': Register.BATT_STATUS_1, 'fmt': '!H'}, # noqa: E501 MPTT-1 Operating Status: 0(Standby), 1(Work) 0x4201003c: {'reg': Register.BATT_PV1_STATUS, 'fmt': '!H'}, # noqa: E501 MPTT-1 Operating Status: 0(Standby), 1(Work)
0x4201003e: {'reg': Register.BATT_STATUS_2, 'fmt': '!H'}, # noqa: E501 MPTT-2 Operating Status: 0(Standby), 1(Work) 0x4201003e: {'reg': Register.BATT_PV2_STATUS, 'fmt': '!H'}, # noqa: E501 MPTT-2 Operating Status: 0(Standby), 1(Work)
0x42010040: {'reg': Register.BATT_VOLT, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501 0x42010040: {'reg': Register.BATT_VOLT, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501
0x42010042: {'reg': Register.BATT_CUR, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501 => Batterie Status: <0(Discharging), 0(Static), 0>(Loading) 0x42010042: {'reg': Register.BATT_CUR, 'fmt': '!h', 'ratio': 0.01}, # noqa: E501 => Batterie Status: <0(Discharging), 0(Static), 0>(Loading)
0x42010044: {'reg': Register.BATT_SOC, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, state of charge (SOC) in percent 0x42010044: {'reg': Register.BATT_SOC, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, state of charge (SOC) in percent
@@ -164,7 +177,7 @@ class RegisterMap:
0x4201006e: {'reg': Register.BATT_OUT_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 Output Current 0x4201006e: {'reg': Register.BATT_OUT_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 Output Current
0x42010070: {'reg': Register.BATT_OUT_STATUS, 'fmt': '!H'}, # noqa: E501 Output Working Status: 0(Standby), 1(Work) 0x42010070: {'reg': Register.BATT_OUT_STATUS, 'fmt': '!H'}, # noqa: E501 Output Working Status: 0(Standby), 1(Work)
0x42010072: {'reg': Register.BATT_TEMP_4, 'fmt': '!h'}, # noqa: E50, Environment temp 0x42010072: {'reg': Register.BATT_TEMP_4, 'fmt': '!h'}, # noqa: E50, Environment temp
0x42010074: {'reg': Register.BATT_74, 'fmt': '!H'}, # noqa: E501 Warning Alarmcode 1, Bit 0..15 0x42010074: {'reg': Register.BATT_ALARM, 'fmt': '!H'}, # noqa: E501 Warning Alarmcode 1, Bit 0..15
0x42010076: {'reg': Register.BATT_HW_VERS, 'fmt': '!h'}, # noqa: E501 hardware version 0x42010076: {'reg': Register.BATT_HW_VERS, 'fmt': '!h'}, # noqa: E501 hardware version
0x42010078: {'reg': Register.BATT_SW_VERS, 'fmt': '!h'}, # noqa: E501 software main version 0x42010078: {'reg': Register.BATT_SW_VERS, 'fmt': '!h'}, # noqa: E501 software main version
'calc': { 'calc': {
@@ -173,8 +186,12 @@ class RegisterMap:
[Register.BATT_PV2_VOLT, Register.BATT_PV2_CUR]]}, [Register.BATT_PV2_VOLT, Register.BATT_PV2_CUR]]},
2: {'reg': Register.BATT_PWR, 'func': RegisterFunc.prod_sum, # noqa: E501 2: {'reg': Register.BATT_PWR, 'func': RegisterFunc.prod_sum, # noqa: E501
'params': [[Register.BATT_VOLT, Register.BATT_CUR]]}, 'params': [[Register.BATT_VOLT, Register.BATT_CUR]]},
3: {'reg': Register.BATT_OUT_PWR, 'func': RegisterFunc.prod_sum, # noqa: E501 Supply Power => Power Supply State: 0(Idle), 0>(Power Supply) 3: {'reg': Register.BATT_OUT_PWR, 'func': RegisterFunc.prod_sum, # noqa: E501 Supply Power => Power Supply State: 0(Idle), 0>(Power Supply)
'params': [[Register.BATT_OUT_VOLT, Register.BATT_OUT_CUR]]}, 'params': [[Register.BATT_OUT_VOLT, Register.BATT_OUT_CUR]]},
4: {'reg': Register.BATT_PWR_SUPL_STATE, 'func': RegisterFunc.cmp_values, # noqa: E501
'params': {'reg': Register.BATT_OUT_PWR, 'cmp_val': 0, 'res': [0, 0, 1]}}, # noqa: E501
5: {'reg': Register.BATT_STATUS, 'func': RegisterFunc.cmp_values, # noqa: E501
'params': {'reg': Register.BATT_CUR, 'cmp_val': 0.0, 'res': [0, 1, 2]}} # noqa: E501
} }
} }

View File

@@ -126,8 +126,8 @@ class Register(Enum):
BATT_PV2_VOLT = 1002 BATT_PV2_VOLT = 1002
BATT_PV2_CUR = 1003 BATT_PV2_CUR = 1003
BATT_TOTAL_CHARG = 1005 BATT_TOTAL_CHARG = 1005
BATT_STATUS_1 = 1006 BATT_PV1_STATUS = 1006
BATT_STATUS_2 = 1007 BATT_PV2_STATUS = 1007
BATT_VOLT = 1010 BATT_VOLT = 1010
BATT_CUR = 1011 BATT_CUR = 1011
BATT_SOC = 1012 BATT_SOC = 1012
@@ -154,12 +154,14 @@ class Register(Enum):
BATT_OUT_CUR = 1033 BATT_OUT_CUR = 1033
BATT_OUT_STATUS = 1034 BATT_OUT_STATUS = 1034
BATT_TEMP_4 = 1035 BATT_TEMP_4 = 1035
BATT_74 = 1036 BATT_ALARM = 1036
BATT_HW_VERS = 1037 BATT_HW_VERS = 1037
BATT_SW_VERS = 1038 BATT_SW_VERS = 1038
BATT_PV_PWR = 1040 BATT_PV_PWR = 1040
BATT_PWR = 1041 BATT_PWR = 1041
BATT_OUT_PWR = 1042 BATT_OUT_PWR = 1042
BATT_PWR_SUPL_STATE = 1043
BATT_STATUS = 1044
TEST_VAL_0 = 2000 TEST_VAL_0 = 2000
TEST_VAL_1 = 2001 TEST_VAL_1 = 2001
@@ -324,6 +326,7 @@ class Infos:
SOLAR_POWER_VAR = 'mdi:solar-power-variant' SOLAR_POWER_VAR = 'mdi:solar-power-variant'
SOLAR_POWER = 'mdi:solar-power' SOLAR_POWER = 'mdi:solar-power'
WIFI = 'mdi:wifi' WIFI = 'mdi:wifi'
ALARM_LIGHT = 'mdi:alarm-light'
UPDATE = 'mdi:update' UPDATE = 'mdi:update'
DAILY_GEN = 'Daily Generation' DAILY_GEN = 'Daily Generation'
TOTAL_GEN = 'Total Generation' TOTAL_GEN = 'Total Generation'
@@ -377,7 +380,9 @@ class Infos:
__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 __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 = ['Standby', 'On', 'Off'] %}{{mppt_status[value_json['pv1']['MPPT-Status']|int(0)]|default(value_json['pv1']['MPPT-Status'])}}" # noqa: E501 __mppt1_status_type_val_tpl = "{%set mppt_status = ['Standby', 'On', 'Off'] %}{{mppt_status[value_json['pv1']['MPPT-Status']|int(0)]|default(value_json['pv1']['MPPT-Status'])}}" # noqa: E501
__mppt2_status_type_val_tpl = "{%set mppt_status = ['Standby', 'On', 'Off'] %}{{mppt_status[value_json['pv2']['MPPT-Status']|int(0)]|default(value_json['pv2']['MPPT-Status'])}}" # noqa: E501 __mppt2_status_type_val_tpl = "{%set mppt_status = ['Standby', 'On', 'Off'] %}{{mppt_status[value_json['pv2']['MPPT-Status']|int(0)]|default(value_json['pv2']['MPPT-Status'])}}" # noqa: E501
__out_status_type_val_tpl = "{%set out_status = ['Standby', 'On'] %}{{out_status[value_json['out']['Out_Status']|int(0)]|default(value_json['out']['Out_Status'])}}" # noqa: E501 __supply_status_type_val_tpl = "{%set supply_status = ['Idle', 'Power-Supply'] %}{{supply_status[value_json['out']['Suppl_State']|int(0)]|default(value_json['out']['Suppl_State'])}}" # noqa: E501
__batt_status_type_val_tpl = "{%set batt_status = ['Discharging', 'Static', 'Loading'] %}{{batt_status[value_json['batt']['Batt_State']|int(0)]|default(value_json['batt']['Batt_State'])}}" # noqa: E501
__out_status_type_val_tpl = "{%set out_status = ['Standby', 'On', 'Off'] %}{{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 __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 = ''' __designed_power_val_tpl = '''
{% if 'Max_Designed_Power' in value_json and {% if 'Max_Designed_Power' in value_json and
@@ -399,52 +404,52 @@ class Infos:
{% set result = 'noAlarm'%} {% set result = 'noAlarm'%}
{%else%} {%else%}
{% set result = '' %} {% set result = '' %}
{% if val_int | bitwise_and(1)%} {% if val_int | bitwise_and(0x0001)%}
{% set result = result + 'HBridgeFault, '%} {% set result = result + 'HBridgeFault, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(2)%} {% if val_int | bitwise_and(0x0002)%}
{% set result = result + 'DriVoltageFault, '%} {% set result = result + 'DriVoltageFault, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(3)%} {% if val_int | bitwise_and(0x0004)%}
{% set result = result + 'GFDI-Fault, '%} {% set result = result + 'GFDI-Fault, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(4)%} {% if val_int | bitwise_and(0x0008)%}
{% set result = result + 'OverTemp, '%} {% set result = result + 'OverTemp, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(5)%} {% if val_int | bitwise_and(0x0010)%}
{% set result = result + 'CommLose, '%} {% set result = result + 'CommLose, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(6)%} {% if val_int | bitwise_and(0x0020)%}
{% set result = result + 'Bit6, '%} {% set result = result + 'Bit6, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(7)%} {% if val_int | bitwise_and(0x0040)%}
{% set result = result + 'Bit7, '%} {% set result = result + 'Bit7, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(8)%} {% if val_int | bitwise_and(0x0080)%}
{% set result = result + 'EEPROM-Fault, '%} {% set result = result + 'EEPROM-Fault, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(9)%} {% if val_int | bitwise_and(0x0100)%}
{% set result = result + 'NoUtility, '%} {% set result = result + 'NoUtility, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(10)%} {% if val_int | bitwise_and(0x0200)%}
{% set result = result + 'VG_Offset, '%} {% set result = result + 'VG_Offset, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(11)%} {% if val_int | bitwise_and(0x0400)%}
{% set result = result + 'Relais_Open, '%} {% set result = result + 'Relais_Open, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(12)%} {% if val_int | bitwise_and(0x0800)%}
{% set result = result + 'Relais_Short, '%} {% set result = result + 'Relais_Short, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(13)%} {% if val_int | bitwise_and(0x1000)%}
{% set result = result + 'GridVoltOverRating, '%} {% set result = result + 'GridVoltOverRating, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(14)%} {% if val_int | bitwise_and(0x2000)%}
{% set result = result + 'GridVoltUnderRating, '%} {% set result = result + 'GridVoltUnderRating, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(15)%} {% if val_int | bitwise_and(0x4000)%}
{% set result = result + 'GridFreqOverRating, '%} {% set result = result + 'GridFreqOverRating, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(16)%} {% if val_int | bitwise_and(0x8000)%}
{% set result = result + 'GridFreqUnderRating, '%} {% set result = result + 'GridFreqUnderRating, '%}
{% endif %} {% endif %}
{% endif %} {% endif %}
@@ -461,42 +466,104 @@ class Infos:
{% set result = 'noFault'%} {% set result = 'noFault'%}
{%else%} {%else%}
{% set result = '' %} {% set result = '' %}
{% if val_int | bitwise_and(1)%} {% if val_int | bitwise_and(0x0001)%}
{% set result = result + 'PVOV-Fault (PV OverVolt), '%} {% set result = result + 'PVOV-Fault (PV OverVolt), '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(2)%} {% if val_int | bitwise_and(0x0002)%}
{% set result = result + 'PVLV-Fault (PV LowVolt), '%} {% set result = result + 'PVLV-Fault (PV LowVolt), '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(3)%} {% if val_int | bitwise_and(0x0004)%}
{% set result = result + 'PV OI-Fault (PV OverCurrent), '%} {% set result = result + 'PV OI-Fault (PV OverCurrent), '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(4)%} {% if val_int | bitwise_and(0x0008)%}
{% set result = result + 'PV OFV-Fault, '%} {% set result = result + 'PV OFV-Fault, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(5)%} {% if val_int | bitwise_and(0x0010)%}
{% set result = result + 'DC ShortCircuitFault, '%} {% set result = result + 'DC ShortCircuitFault, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(6)%}{% set result = result + 'Bit6, '%} {% if val_int | bitwise_and(0x0020)%}{% set result = result + 'Bit6, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(7)%}{% set result = result + 'Bit7, '%} {% if val_int | bitwise_and(0x0040)%}{% set result = result + 'Bit7, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(8)%}{% set result = result + 'Bit8, '%} {% if val_int | bitwise_and(0x0080)%}{% set result = result + 'Bit8, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(9)%}{% set result = result + 'Bit9, '%} {% if val_int | bitwise_and(0x0100)%}{% set result = result + 'Bit9, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(10)%}{% set result = result + 'Bit10, '%} {% if val_int | bitwise_and(0x0200)%}{% set result = result + 'Bit10, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(11)%}{% set result = result + 'Bit11, '%} {% if val_int | bitwise_and(0x0400)%}{% set result = result + 'Bit11, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(12)%}{% set result = result + 'Bit12, '%} {% if val_int | bitwise_and(0x0800)%}{% set result = result + 'Bit12, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(13)%}{% set result = result + 'Bit13, '%} {% if val_int | bitwise_and(0x1000)%}{% set result = result + 'Bit13, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(14)%}{% set result = result + 'Bit14, '%} {% if val_int | bitwise_and(0x2000)%}{% set result = result + 'Bit14, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(15)%}{% set result = result + 'Bit15, '%} {% if val_int | bitwise_and(0x4000)%}{% set result = result + 'Bit15, '%}
{% endif %} {% endif %}
{% if val_int | bitwise_and(16)%}{% set result = result + 'Bit16, '%} {% if val_int | bitwise_and(0x8000)%}{% set result = result + 'Bit16, '%}
{% endif %}
{% endif %}
{{ result }}
{% else %}
{{ this.state }}
{% endif %}
'''
__batt_alarm_val_tpl = '''
{% if 'Batterie_Alarm' in value_json and
value_json['Batterie_Alarm'] != None %}
{% set val_int = value_json['Batterie_Alarm'] | int %}
{% if val_int == 0 %}
{% set result = 'noAlarm'%}
{%else%}
{% set result = '' %}
{% if val_int | bitwise_and(0x0001)%}
{% set result = result + 'PV1-OverVoltage, '%}
{% endif %}
{% if val_int | bitwise_and(0x0002)%}
{% set result = result + 'PV2-OverVoltage, '%}
{% endif %}
{% if val_int | bitwise_and(0x0004)%}
{% set result = result + 'EquipmentOverheating, '%}
{% endif %}
{% if val_int | bitwise_and(0x0008)%}
{% set result = result + 'EquipmentLowTemp, '%}
{% endif %}
{% if val_int | bitwise_and(0x0010)%}
{% set result = result + 'BMS-CommFailed, '%}
{% endif %}
{% if val_int | bitwise_and(0x0020)%}
{% set result = result + 'UnderVoltageProt, '%}
{% endif %}
{% if val_int | bitwise_and(0x0040)%}
{% set result = result + 'ChargingHighTemp, '%}
{% endif %}
{% if val_int | bitwise_and(0x0080)%}
{% set result = result + 'ChargingLowTemp, '%}
{% endif %}
{% if val_int | bitwise_and(0x0100)%}
{% set result = result + 'DischargeHighTemp, '%}
{% endif %}
{% if val_int | bitwise_and(0x0200)%}
{% set result = result + 'DischargeLowTemp, '%}
{% endif %}
{% if val_int | bitwise_and(0x0400)%}
{% set result = result + 'BatterieOverVoltage, '%}
{% endif %}
{% if val_int | bitwise_and(0x0800)%}
{% set result = result + 'SingleCorePressureDifferenceIsTooLarge, '%}
{% endif %}
{% if val_int | bitwise_and(0x1000)%}
{% set result = result + 'Bit-12, '%}
{% endif %}
{% if val_int | bitwise_and(0x2000)%}
{% set result = result + 'Bit-13, '%}
{% endif %}
{% if val_int | bitwise_and(0x4000)%}
{% set result = result + 'Bit-14, '%}
{% endif %}
{% if val_int | bitwise_and(0x8000)%}
{% set result = result + 'Bit-15, '%}
{% endif %} {% endif %}
{% endif %} {% endif %}
{{ result }} {{ result }}
@@ -562,8 +629,8 @@ class Infos:
# 0xffffff03: {'name':['proxy', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'proxy', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'proxy_volt_', 'fmt':FMT_FLOAT,'name': 'Grid Voltage'}}, # noqa: E501 # 0xffffff03: {'name':['proxy', 'Voltage'], 'level': logging.DEBUG, 'unit': 'V', 'ha':{'dev':'proxy', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id':'proxy_volt_', 'fmt':FMT_FLOAT,'name': 'Grid Voltage'}}, # noqa: E501
# events # events
Register.EVENT_ALARM: {'name': ['events', 'Inverter_Alarm'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_alarm_', 'name': 'Inverter Alarm', 'val_tpl': __inv_alarm_val_tpl, 'icon': 'mdi:alarm-light'}}, # noqa: E501 Register.EVENT_ALARM: {'name': ['events', 'Inverter_Alarm'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_alarm_', 'name': 'Inverter Alarm', 'val_tpl': __inv_alarm_val_tpl, 'icon': ALARM_LIGHT}}, # noqa: E501
Register.EVENT_FAULT: {'name': ['events', 'Inverter_Fault'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_fault_', 'name': 'Inverter Fault', 'val_tpl': __inv_fault_val_tpl, 'icon': 'mdi:alarm-light'}}, # noqa: E501 Register.EVENT_FAULT: {'name': ['events', 'Inverter_Fault'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'inverter', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_fault_', 'name': 'Inverter Fault', 'val_tpl': __inv_fault_val_tpl, 'icon': ALARM_LIGHT}}, # noqa: E501
Register.EVENT_BF1: {'name': ['events', 'Inverter_Bitfield_1'], 'level': logging.INFO, 'unit': ''}, # noqa: E501 Register.EVENT_BF1: {'name': ['events', 'Inverter_Bitfield_1'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
Register.EVENT_BF2: {'name': ['events', 'Inverter_bitfield_2'], 'level': logging.INFO, 'unit': ''}, # noqa: E501 Register.EVENT_BF2: {'name': ['events', 'Inverter_bitfield_2'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
# Register.EVENT_409: {'name': ['events', '409_No_Utility'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 # Register.EVENT_409: {'name': ['events', '409_No_Utility'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
@@ -637,10 +704,10 @@ class Infos:
Register.INV_UNKNOWN_1: {'name': ['inv_unknown', 'Unknown_1'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 Register.INV_UNKNOWN_1: {'name': ['inv_unknown', 'Unknown_1'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
# Batterie DC-1000: Electricity Genration # Batterie DC-1000: Electricity Genration
Register.BATT_STATUS_1: {'name': ['batterie', 'pv1', 'MPPT-Status'], '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_PV1_STATUS: {'name': ['batterie', 'pv1', 'MPPT-Status'], '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_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_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_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_STATUS_2: {'name': ['batterie', 'pv2', 'MPPT-Status'], '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_PV2_STATUS: {'name': ['batterie', 'pv2', 'MPPT-Status'], '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_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_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_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_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_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
@@ -648,6 +715,7 @@ class Infos:
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_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_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_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': 'Supply 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': 'Supply Power'}}, # noqa: E501
Register.BATT_PWR_SUPL_STATE: {'name': ['batterie', 'out', 'Suppl_State'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'status_supply_', 'name': 'Supply State', 'val_tpl': __supply_status_type_val_tpl, 'icon': POWER}}, # noqa: E501
# Batterie DC-1000: Cell Package # Batterie DC-1000: Cell Package
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_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
@@ -673,12 +741,13 @@ class Infos:
Register.BATT_PWR: {'name': ['batterie', 'batt', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_', 'val_tpl': "{{ (value_json['batt']['Power'] | int)}}", 'name': 'Batterie Power'}}, # noqa: E501 Register.BATT_PWR: {'name': ['batterie', 'batt', 'Power'], 'level': logging.INFO, 'unit': 'W', 'ha': {'dev': 'batterie', 'dev_cla': 'power', 'stat_cla': 'measurement', 'id': 'power_', 'val_tpl': "{{ (value_json['batt']['Power'] | int)}}", 'name': 'Batterie Power'}}, # noqa: E501
Register.BATT_SOC: {'name': ['batterie', 'batt', 'SOC'], 'level': logging.INFO, 'unit': '%', 'ha': {'dev': 'batterie', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'soc_', 'val_tpl': "{{ (value_json['batt']['SOC'] | float)}}", 'name': 'State of Charge (SOC)', 'icon': 'mdi:battery-90'}}, # noqa: E501 Register.BATT_SOC: {'name': ['batterie', 'batt', 'SOC'], 'level': logging.INFO, 'unit': '%', 'ha': {'dev': 'batterie', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'soc_', 'val_tpl': "{{ (value_json['batt']['SOC'] | float)}}", 'name': 'State of Charge (SOC)', 'icon': 'mdi:battery-90'}}, # noqa: E501
Register.BATT_TOTAL_CHARG: {'name': ['batterie', 'batt', 'Total_Charging'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'batterie', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_charg_', 'val_tpl': "{{ (value_json['batt']['Total_Charging'] | float)}}", 'name': TOTAL_CHARG, 'icon': 'mdi:battery-charging', 'must_incr': True}}, # noqa: E501 Register.BATT_TOTAL_CHARG: {'name': ['batterie', 'batt', 'Total_Charging'], 'level': logging.INFO, 'unit': 'kWh', 'ha': {'dev': 'batterie', 'dev_cla': 'energy', 'stat_cla': 'total', 'id': 'total_charg_', 'val_tpl': "{{ (value_json['batt']['Total_Charging'] | float)}}", 'name': TOTAL_CHARG, 'icon': 'mdi:battery-charging', 'must_incr': True}}, # noqa: E501
Register.BATT_STATUS: {'name': ['batterie', 'batt', 'Batt_State'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'status_batt_', 'name': 'Batterie State', 'val_tpl': __batt_status_type_val_tpl, 'icon': POWER}}, # noqa: E501
Register.BATT_TEMP_1: {'name': ['batterie', 'cell', 'Temp_1'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_1_', 'val_tpl': "{{ (value_json['cell']['Temp_1'] | int)}}", 'name': 'Cell Temp-1', 'ent_cat': 'diagnostic'}}, # noqa: E501 Register.BATT_TEMP_1: {'name': ['batterie', 'cell', 'Temp_1'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_1_', 'val_tpl': "{{ (value_json['cell']['Temp_1'] | int)}}", 'name': 'Cell Temp-1', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.BATT_TEMP_2: {'name': ['batterie', 'cell', 'Temp_2'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_2_', 'val_tpl': "{{ (value_json['cell']['Temp_2'] | int)}}", 'name': 'Cell Temp-2', 'ent_cat': 'diagnostic'}}, # noqa: E501 Register.BATT_TEMP_2: {'name': ['batterie', 'cell', 'Temp_2'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_2_', 'val_tpl': "{{ (value_json['cell']['Temp_2'] | int)}}", 'name': 'Cell Temp-2', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.BATT_TEMP_3: {'name': ['batterie', 'cell', 'Temp_3'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_3_', 'val_tpl': "{{ (value_json['cell']['Temp_3'] | int)}}", 'name': 'Cell Temp-3', 'ent_cat': 'diagnostic'}}, # noqa: E501 Register.BATT_TEMP_3: {'name': ['batterie', 'cell', 'Temp_3'], 'level': logging.INFO, 'unit': '°C', 'ha': {'dev': 'batterie', 'dev_cla': 'temperature', 'stat_cla': 'measurement', 'id': 'temp_3_', 'val_tpl': "{{ (value_json['cell']['Temp_3'] | int)}}", 'name': 'Cell Temp-3', 'ent_cat': 'diagnostic'}}, # 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': 'Temperature'}}, # 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': '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_ALARM: {'name': ['batterie', 'Batterie_Alarm'], 'level': logging.INFO, 'unit': '', 'ha': {'dev': 'batterie', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'batt_alarm_', 'name': 'Batterie Alarm', 'val_tpl': __batt_alarm_val_tpl, 'icon': ALARM_LIGHT}}, # noqa: E501
Register.BATT_HW_VERS: {'name': ['batterie', 'Hardware_Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501 Register.BATT_HW_VERS: {'name': ['batterie', 'Hardware_Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501
Register.BATT_SW_VERS: {'name': ['batterie', 'Software_Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501 Register.BATT_SW_VERS: {'name': ['batterie', 'Software_Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501

View File

@@ -43,8 +43,8 @@ class Modbus():
0x000a: {'reg': Register.BATT_PV2_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV2 voltage 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 0x000b: {'reg': Register.BATT_PV2_CUR, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, PV2 current
0x000c: {'reg': Register.BATT_TOTAL_CHARG, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 0x000c: {'reg': Register.BATT_TOTAL_CHARG, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501
0x000e: {'reg': Register.BATT_STATUS_1, 'fmt': '!H'}, # noqa: E501 0x000e: {'reg': Register.BATT_PV1_STATUS, 'fmt': '!H'}, # noqa: E501
0x000f: {'reg': Register.BATT_STATUS_2, 'fmt': '!H'}, # noqa: E501 0x000f: {'reg': Register.BATT_PV2_STATUS, 'fmt': '!H'}, # noqa: E501
0x0010: {'reg': Register.BATT_VOLT, 'fmt': '!h', 'ratio': 0.01}, # 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 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 0x0012: {'reg': Register.BATT_SOC, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501, state of charge (SOC) in percent
@@ -71,7 +71,7 @@ class Modbus():
0x0027: {'reg': Register.BATT_OUT_CUR, '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 0x0028: {'reg': Register.BATT_OUT_STATUS, 'fmt': '!H'}, # noqa: E501
0x0029: {'reg': Register.BATT_TEMP_4, 'fmt': '!h'}, # noqa: E501 0x0029: {'reg': Register.BATT_TEMP_4, 'fmt': '!h'}, # noqa: E501
0x002a: {'reg': Register.BATT_74, 'fmt': '!h'}, # noqa: E501 0x002a: {'reg': Register.BATT_ALARM, 'fmt': '!h'}, # noqa: E501
0x002b: {'reg': Register.BATT_HW_VERS, 'fmt': '!h'}, # noqa: E501 0x002b: {'reg': Register.BATT_HW_VERS, 'fmt': '!h'}, # noqa: E501
0x002c: {'reg': Register.BATT_SW_VERS, 'fmt': '!h'}, # noqa: E501 0x002c: {'reg': Register.BATT_SW_VERS, 'fmt': '!h'}, # noqa: E501

View File

@@ -96,8 +96,8 @@ class Proxy():
Infos.new_stat_data[key] = False Infos.new_stat_data[key] = False
@classmethod @classmethod
def class_close(cls, loop) -> None: # pragma: no cover async def class_close(cls, loop) -> None: # pragma: no cover
logging.debug('Proxy.class_close') logging.debug('Proxy.class_close')
logging.info('Close MQTT Task') logging.info('Close MQTT Task')
loop.run_until_complete(cls.mqtt.close()) await cls.mqtt.close()
cls.mqtt = None cls.mqtt = None

View File

@@ -1,11 +1,10 @@
import logging import logging
import asyncio import asyncio
import logging.handlers import logging.handlers
import signal
import os import os
import argparse import argparse
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter
from aiohttp import web from quart import Quart, Response
from logging import config # noqa F401 from logging import config # noqa F401
from proxy import Proxy from proxy import Proxy
from inverter_ifc import InverterIfc from inverter_ifc import InverterIfc
@@ -16,63 +15,51 @@ from cnf.config import Config
from cnf.config_read_env import ConfigReadEnv from cnf.config_read_env import ConfigReadEnv
from cnf.config_read_toml import ConfigReadToml from cnf.config_read_toml import ConfigReadToml
from cnf.config_read_json import ConfigReadJson from cnf.config_read_json import ConfigReadJson
from web.routes import web_routes
from modbus_tcp import ModbusTcp from modbus_tcp import ModbusTcp
routes = web.RouteTableDef()
proxy_is_up = False class ProxyState:
_is_up = False
@staticmethod
def is_up() -> bool:
return ProxyState._is_up
@staticmethod
def set_up(value: bool):
ProxyState._is_up = value
@routes.get('/') app = Quart(__name__)
async def hello(request): app.register_blueprint(web_routes)
return web.Response(text="Hello, world")
@routes.get('/-/ready') @app.route('/-/ready')
async def ready(request): async def ready():
if proxy_is_up: if ProxyState.is_up():
status = 200 status = 200
text = 'Is ready' text = 'Is ready'
else: else:
status = 503 status = 503
text = 'Not ready' text = 'Not ready'
return web.Response(status=status, text=text) return Response(status=status, response=text)
@routes.get('/-/healthy') @app.route('/-/healthy')
async def healthy(request): async def healthy():
if proxy_is_up: if ProxyState.is_up():
# logging.info('web reqeust healthy()') # logging.info('web reqeust healthy()')
for inverter in InverterIfc: for inverter in InverterIfc:
try: try:
res = inverter.healthy() res = inverter.healthy()
if not res: if not res:
return web.Response(status=503, text="I have a problem") return Response(status=503, response="I have a problem")
except Exception as err: except Exception as err:
logging.info(f'Exception:{err}') logging.info(f'Exception:{err}')
return web.Response(status=200, text="I'm fine") return Response(status=200, response="I'm fine")
async def webserver(addr, port):
'''coro running our webserver'''
app = web.Application()
app.add_routes(routes)
runner = web.AppRunner(app)
await runner.setup()
site = web.TCPSite(runner, addr, port)
await site.start()
logging.info(f'HTTP server listen on port: {port}')
try:
# Normal interaction with aiohttp
while True:
await asyncio.sleep(3600) # sleep forever
except asyncio.CancelledError:
logging.info('HTTP server cancelled')
await runner.cleanup()
logging.debug('HTTP cleanup done')
async def handle_client(reader: StreamReader, writer: StreamWriter, inv_class): async def handle_client(reader: StreamReader, writer: StreamWriter, inv_class):
@@ -82,12 +69,13 @@ async def handle_client(reader: StreamReader, writer: StreamWriter, inv_class):
await inv.local.ifc.server_loop() await inv.local.ifc.server_loop()
async def handle_shutdown(loop, web_task): @app.after_serving
async def handle_shutdown(): # pragma: no cover
'''Close all TCP connections and stop the event loop''' '''Close all TCP connections and stop the event loop'''
logging.info('Shutdown due to SIGTERM') logging.info('Shutdown due to SIGTERM')
global proxy_is_up loop = asyncio.get_event_loop()
proxy_is_up = False ProxyState.set_up(False)
# #
# first, disc all open TCP connections gracefully # first, disc all open TCP connections gracefully
@@ -97,24 +85,16 @@ async def handle_shutdown(loop, web_task):
logging.info('Proxy disconnecting done') logging.info('Proxy disconnecting done')
#
# second, cancel the web server
#
web_task.cancel()
await web_task
# #
# now cancel all remaining (pending) tasks # now cancel all remaining (pending) tasks
# #
pending = asyncio.all_tasks() for task in asyncio.all_tasks():
for task in pending: if task == asyncio.current_task():
continue
task.cancel() task.cancel()
logging.info('Proxy cancelling done')
# await Proxy.class_close(loop)
# at last, start a coro for stopping the loop
#
logging.debug("Stop event loop")
loop.stop()
def get_log_level() -> int | None: def get_log_level() -> int | None:
@@ -214,27 +194,20 @@ def main(): # pragma: no cover
loop.create_task(asyncio.start_server(lambda r, w, i=inv_class: loop.create_task(asyncio.start_server(lambda r, w, i=inv_class:
handle_client(r, w, i), handle_client(r, w, i),
'0.0.0.0', port)) '0.0.0.0', port))
web_task = loop.create_task(webserver('0.0.0.0', 8127))
#
# Register some UNIX Signal handler for a gracefully server shutdown
# on Docker restart and stop
#
for signame in ('SIGINT', 'SIGTERM'):
loop.add_signal_handler(getattr(signal, signame),
lambda loop=loop: asyncio.create_task(
handle_shutdown(loop, web_task)))
loop.set_debug(log_level == logging.DEBUG) loop.set_debug(log_level == logging.DEBUG)
try: try:
global proxy_is_up ProxyState.set_up(True)
proxy_is_up = True logging.info("Start Quart")
loop.run_forever() app.run(host='0.0.0.0', port=8127, use_reloader=False, loop=loop)
logging.info("Quart stopped")
except KeyboardInterrupt: except KeyboardInterrupt:
pass pass
except asyncio.exceptions.CancelledError:
logging.info("Quart cancelled")
finally: finally:
logging.info("Event loop is stopped")
Proxy.class_close(loop)
logging.debug('Close event loop') logging.debug('Close event loop')
loop.close() loop.close()
logging.info(f'Finally, exit Server "{serv_name}"') logging.info(f'Finally, exit Server "{serv_name}"')

9
app/src/web/routes.py Normal file
View File

@@ -0,0 +1,9 @@
from quart import Blueprint
from quart import Response
web_routes = Blueprint('web_routes', __name__)
@web_routes.route('/')
async def hello():
return Response(response="Hello, world")

View File

@@ -221,7 +221,7 @@ def inv_data_seq2(): # Data indication from the controller
return msg return msg
@pytest.fixture @pytest.fixture
def inv_data_seq3(): # Inverter indication from MS-2000 def inv_data_seq3(): # Inverter indication from MS-3000
msg = b'\x00\x00\x01\x2c\x00\x00\x00\x64\x53\x00\x00' # | ..^.....,...dS.. 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'\x00\x00\x00\xc8\x53\x44\x00\x00\x00\x01\x2c\x53\x00\x00\x00\x00' # | ....SD....,S....

View File

@@ -82,13 +82,25 @@ def batterie_data(): # 0x4210 ftype: 0x01
msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01' msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01'
return msg return msg
@pytest.fixture
def batterie_data1(): # 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'\x01\x00\x00\x00\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\x0c\x0e\x01\x00'
msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01'
return msg
@pytest.fixture @pytest.fixture
def batterie_data2(): # 0x4210 ftype: 0x01 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'\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'\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'\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'\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'\x14\x0e\x02\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\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' msg += b'\x0c\x89\x0c\x89\x0c\x88\x00\x0f\x00\x0f\x00\x0f\x00\x0e'
return msg return msg
@@ -157,10 +169,29 @@ def test_parse_4210_3026(batterie_data: bytes):
"inverter": {"Serial_Number": "4101240701490314"}, "inverter": {"Serial_Number": "4101240701490314"},
"batterie": {"pv1": {"Voltage": 33.86, "Current": 1.12, "MPPT-Status": 0}, "batterie": {"pv1": {"Voltage": 33.86, "Current": 1.12, "MPPT-Status": 0},
"pv2": {"Voltage": 33.72, "Current": 0.0, "MPPT-Status": 0}, "pv2": {"Voltage": 33.72, "Current": 0.0, "MPPT-Status": 0},
"batt": {"Total_Charging": 20.8, "Voltage": 51.34, "Current": -0.02, "SOC": 10.0, "Power": -1.0268000000000002}, "batt": {"Total_Charging": 20.8, "Voltage": 51.34, "Current": -0.02, "SOC": 10.0, "Power": -1.0268000000000002, 'Batt_State': 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}, "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}, "out": {"Voltage": 0.14, "Current": 0.0, "Out_Status": 0, "Power": 0.0, "Suppl_State": 0},
"Controller_Temp": 15, "Reg_74": 0, "Hardware_Version": 517, "Software_Version": 513, "Controller_Temp": 15, "Batterie_Alarm": 0, "Hardware_Version": 517, "Software_Version": 513,
"PV_Power": 37.9232},
})
def test_parse_4210_3026_prod(batterie_data1: bytes):
i = InfosG3P(client_mode=False)
i.db.clear()
for key, update in i.parse (batterie_data1, 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, "MPPT-Status": 0},
"pv2": {"Voltage": 33.72, "Current": 0.0, "MPPT-Status": 0},
"batt": {"Total_Charging": 20.8, "Voltage": 2.56, "Current": 0.0, "SOC": 10.0, "Power": 0.0, 'Batt_State': 1},
"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": 30.86, "Current": 2.56, "Out_Status": 0, "Power": 79.0016, "Suppl_State": 1},
"Controller_Temp": 15, "Batterie_Alarm": 0, "Hardware_Version": 517, "Software_Version": 513,
"PV_Power": 37.9232}, "PV_Power": 37.9232},
}) })
@@ -176,10 +207,10 @@ def test_parse_4210_3026_incomplete(batterie_data2: bytes):
"inverter": {"Serial_Number": "4101240701490314"}, "inverter": {"Serial_Number": "4101240701490314"},
"batterie": {"pv1": {"Voltage": 33.86, "Current": 1.12, "MPPT-Status": 0}, "batterie": {"pv1": {"Voltage": 33.86, "Current": 1.12, "MPPT-Status": 0},
"pv2": {"Voltage": 33.72, "Current": 0.0, "MPPT-Status": 0}, "pv2": {"Voltage": 33.72, "Current": 0.0, "MPPT-Status": 0},
"batt": {"Total_Charging": 20.8, "Voltage": 51.34, "Current": -0.02, "SOC": 10.0, "Power": -1.0268000000000002}, "batt": {"Total_Charging": 20.8, "Voltage": 51.34, "Current": 7.66, "SOC": 10.0, "Power": 393.2644, 'Batt_State': 2},
"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}, "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}, "out": {"Voltage": 0.14, "Current": None, "Out_Status": None, "Power": None, "Suppl_State": None},
"Controller_Temp": None, "Reg_74": None, "Hardware_Version": None, "Software_Version": None, "Controller_Temp": None, "Batterie_Alarm": None, "Hardware_Version": None, "Software_Version": None,
"PV_Power": 37.9232}, "PV_Power": 37.9232},
}) })

View File

@@ -3,7 +3,9 @@ import pytest
import logging import logging
import os import os
from mock import patch from mock import patch
from server import get_log_level from server import get_log_level, app, ProxyState
pytest_plugins = ('pytest_asyncio',)
def test_get_log_level(): def test_get_log_level():
@@ -30,3 +32,37 @@ def test_get_log_level():
with patch.dict(os.environ, {'LOG_LVL': 'UNKNOWN'}): with patch.dict(os.environ, {'LOG_LVL': 'UNKNOWN'}):
log_lvl = get_log_level() log_lvl = get_log_level()
assert log_lvl == None assert log_lvl == None
@pytest.mark.asyncio
async def test_ready():
"""Test the ready route."""
ProxyState.set_up(False)
client = app.test_client()
response = await client.get('/-/ready')
assert response.status_code == 503
result = await response.get_data()
assert result == b"Not ready"
ProxyState.set_up(True)
response = await client.get('/-/ready')
assert response.status_code == 200
result = await response.get_data()
assert result == b"Is ready"
@pytest.mark.asyncio
async def test_healthy():
"""Test the healthy route."""
ProxyState.set_up(False)
client = app.test_client()
response = await client.get('/-/healthy')
assert response.status_code == 200
result = await response.get_data()
assert result == b"I'm fine"
ProxyState.set_up(True)
response = await client.get('/-/healthy')
assert response.status_code == 200
result = await response.get_data()
assert result == b"I'm fine"

View File

@@ -0,0 +1,15 @@
# test_with_pytest.py
import pytest
from server import app
pytest_plugins = ('pytest_asyncio',)
@pytest.mark.asyncio
async def test_home():
"""Test the home route."""
client = app.test_client()
response = await client.get('/')
assert response.status_code == 200
result = await response.get_data()
assert result == b"Hello, world"

View File

@@ -32,7 +32,7 @@ rel : STAGE=rel
export BUILD_DATE := ${shell date -Iminutes} export BUILD_DATE := ${shell date -Iminutes}
BUILD_ID := ${shell date +'%y%m%d%H%M'} debug dev : BUILD_ID := ${shell date +'%y%m%d%H%M'}
VERSION := $(shell cat $(SRC)/.version) VERSION := $(shell cat $(SRC)/.version)
export MAJOR := $(shell echo $(VERSION) | cut -f1 -d.) export MAJOR := $(shell echo $(VERSION) | cut -f1 -d.)
@@ -47,7 +47,16 @@ dev debug: local_add_on
export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \ export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \
docker buildx bake -f docker-bake.hcl $@ docker buildx bake -f docker-bake.hcl $@
rc rel: local_add_on rc: local_add_on
@[ "${RC}" ] || ( echo ">> RC is not set"; exit 1 )
@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)-$@$(RC) && \
export IMAGE=$(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) && \
docker buildx bake -f docker-bake.hcl $@
rel: local_add_on
@echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PUBLIC_CONTAINER_REGISTRY)$(IMAGE) @echo version: $(VERSION) build-date: $(BUILD_DATE) image: $(PUBLIC_CONTAINER_REGISTRY)$(IMAGE)
@echo login at $(PUBLIC_URL) as $(PUBLIC_USER) @echo login at $(PUBLIC_URL) as $(PUBLIC_USER)
@DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)" @DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)"
@@ -74,7 +83,8 @@ SRC_FILES := $(wildcard $(SRC_PROXY)/*.py)\
$(wildcard $(SRC_PROXY)/cnf/*.py)\ $(wildcard $(SRC_PROXY)/cnf/*.py)\
$(wildcard $(SRC_PROXY)/cnf/*.toml)\ $(wildcard $(SRC_PROXY)/cnf/*.toml)\
$(wildcard $(SRC_PROXY)/gen3/*.py)\ $(wildcard $(SRC_PROXY)/gen3/*.py)\
$(wildcard $(SRC_PROXY)/gen3plus/*.py) $(wildcard $(SRC_PROXY)/gen3plus/*.py)\
$(wildcard $(SRC_PROXY)/web/*.py)
CNF_FILES := $(wildcard $(CNF_PROXY)/*.toml) CNF_FILES := $(wildcard $(CNF_PROXY)/*.toml)
# determine destination files # determine destination files

View File

@@ -90,6 +90,7 @@ target "preview" {
target "rc" { target "rc" {
inherits = ["_common", "_prod"] inherits = ["_common", "_prod"]
tags = ["${IMAGE}:rc", "${IMAGE}:${VERSION}"] tags = ["${IMAGE}:rc", "${IMAGE}:${VERSION}"]
no-cache = true
} }
target "rel" { target "rel" {

View File

@@ -50,7 +50,7 @@ Example add-on configuration after installation:
```yaml ```yaml
inverters: inverters:
- serial: R17E760702080400 - serial: R17E000000000000
node_id: PV-Garage node_id: PV-Garage
suggested_area: Garage suggested_area: Garage
modbus_polling: false modbus_polling: false
@@ -89,7 +89,7 @@ Example add-on configuration for GEN3PLUS energie storages:
```yaml ```yaml
batteries: batteries:
- serial: 4100000000000000 - serial: 4100000000000000
monitor_sn: 2300000000 monitor_sn: 3000000000
node_id: bat_1 node_id: bat_1
suggested_area: Garage suggested_area: Garage
modbus_polling: false modbus_polling: false
@@ -174,4 +174,4 @@ SOFTWARE.
[AdGuard]: https://github.com/hassio-addons/addon-adguard-home [AdGuard]: https://github.com/hassio-addons/addon-adguard-home
[repository-badge]: https://img.shields.io/badge/Add%20repository%20to%20my-Home%20Assistant-41BDF5?logo=home-assistant&style=for-the-badge [repository-badge]: https://img.shields.io/badge/Add%20repository%20to%20my-Home%20Assistant-41BDF5?logo=home-assistant&style=for-the-badge
[repository-url]: https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fs-allius%2Fha-addons [repository-url]: https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fs-allius%2Fha-addons
[configdetails]: https://github.com/s-allius/tsun-gen3-proxy/wiki/Configuration-toml [configdetails]: https://github.com/s-allius/tsun-gen3-proxy/wiki/Configuration-addon

View File

@@ -13,12 +13,12 @@
# 1 Build Base Image # # 1 Build Base Image #
###################### ######################
ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.3" ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.4"
# hadolint ignore=DL3006 # hadolint ignore=DL3006
FROM $BUILD_FROM AS base FROM $BUILD_FROM AS base
# Installiere Python, pip und virtuelle Umgebungstools # Installiere Python, pip und virtuelle Umgebungstools
RUN apk add --no-cache python3=3.12.9-r0 py3-pip=24.3.1-r0 && \ RUN apk add --no-cache python3=3.12.10-r0 py3-pip=24.3.1-r0 && \
python -m venv /opt/venv && \ python -m venv /opt/venv && \
. /opt/venv/bin/activate . /opt/venv/bin/activate

View File

@@ -10,8 +10,8 @@ configuration:
Weitere wechselrichterspezifische Parameter (z.B. Polling Mode) können im Weitere wechselrichterspezifische Parameter (z.B. Polling Mode) können im
Konfigurationsblock gesetzt werden. Konfigurationsblock gesetzt werden.
Die Seriennummer der GEN3 Wechselrichter beginnen mit `R17` oder `R47` und die der GEN3PLUS Die Seriennummer der GEN3 Wechselrichter beginnen mit 'R17' oder 'R47' und die der GEN3PLUS
Wechselrichter mit `Y17`oder `Y47`! Wechselrichter mit 'Y00', 'Y17' oder 'Y47'!
Siehe Beispielkonfiguration im Dokumentations-Tab Siehe Beispielkonfiguration im Dokumentations-Tab
batteries: batteries:
@@ -106,3 +106,4 @@ configuration:
network: network:
5005/tcp: listening Port für TSUN GEN3 Wechselrichter 5005/tcp: listening Port für TSUN GEN3 Wechselrichter
10000/tcp: listening Port für TSUN GEN3PLUS Wechselrichter 10000/tcp: listening Port für TSUN GEN3PLUS Wechselrichter
8127/tcp: Port für das TSUN-Proxy Dashboard

View File

@@ -10,7 +10,7 @@ configuration:
in the configuration block. in the configuration block.
The serial numbers of all GEN3 inverters start with `R17` or `R47` and that of the GEN3PLUS The serial numbers of all GEN3 inverters start with `R17` or `R47` and that of the GEN3PLUS
inverters with Y17 or Y47! inverters with 'Y00', Y17 or Y47!
For reference see example configuration in Documentation Tab For reference see example configuration in Documentation Tab
@@ -107,3 +107,4 @@ configuration:
network: network:
5005/tcp: listening Port for TSUN GEN3 Devices 5005/tcp: listening Port for TSUN GEN3 Devices
10000/tcp: listening Port for TSUN GEN3PLUS Devices 10000/tcp: listening Port for TSUN GEN3PLUS Devices
8127/tcp: Port for the TSUN-Proxy Dashboard

View File

@@ -23,8 +23,11 @@ services:
ports: ports:
5005/tcp: 5005 5005/tcp: 5005
10000/tcp: 10000 10000/tcp: 10000
8127/tcp: 8127
webui: "http://[HOST]:[PORT:8127]/"
watchdog: "http://[HOST]:[PORT:8127]/-/healthy" watchdog: "http://[HOST]:[PORT:8127]/-/healthy"
ingress: true
ingress_port: 8127
# Definition of parameters in the configuration tab of the addon # Definition of parameters in the configuration tab of the addon
# parameters are available within the container as /data/options.json # parameters are available within the container as /data/options.json
@@ -32,7 +35,7 @@ watchdog: "http://[HOST]:[PORT:8127]/-/healthy"
schema: schema:
inverters: inverters:
- serial: match(^(R17|R47|Y17|Y47).{13}$) - serial: match(^(R17|R47|Y00|Y17|Y47).{13}$)
monitor_sn: int? monitor_sn: int?
node_id: str node_id: str
suggested_area: str suggested_area: str

View File

@@ -6,6 +6,6 @@
"slug": "tsun-proxy", "slug": "tsun-proxy",
"advanced": false, "advanced": false,
"stage": "stable", "stage": "stable",
"readme_dsecr": "Integrates TSUN inverters (e.g. TSOL MS800, MS2000, MS3000) and batteries (TSOL DC1000) into Home Assistant.\n\nIt is based on the [TSUN Proxy][tsunproxy] and enables a reliable connection between TSUN devices and an MQTT broker.\n\nWith the Add-on, you can easily retrieve real-time values such as power, current and daily energy and integrate the inverter into Home Assistant.\nThis works even without an internet connection.\n\nThe optional connection to the TSUN Cloud can be disabled!", "readme_descr": "Integrates TSUN inverters (e.g. TSOL MS800, MS2000, MS3000) and batteries (TSOL DC1000) into Home Assistant.\n\nIt is based on the [TSUN Proxy][tsunproxy] and enables a reliable connection between TSUN devices and an MQTT broker.\n\nWith the Add-on, you can easily retrieve real-time values such as power, current and daily energy and integrate the inverter into Home Assistant.\nThis works even without an internet connection.\n\nThe optional connection to the TSUN Cloud can be disabled!",
"readme_links": "\n[tsunproxy]: https://github.com/s-allius/tsun-gen3-proxy\n" "readme_links": "\n[tsunproxy]: https://github.com/s-allius/tsun-gen3-proxy\n"
} }

View File

@@ -153,9 +153,9 @@ def dcu_data_ind_msg(): # 0x4210
msg += b'\x2d\x32\x28\x00\x00\x00\x84\x17\x79\x35\x01\x00\x4c\x12\x00\x00' msg += b'\x2d\x32\x28\x00\x00\x00\x84\x17\x79\x35\x01\x00\x4c\x12\x00\x00'
msg += get_dcu_no() msg += get_dcu_no()
msg += b'\x0d\x3a\x00\x0a\x0d\x2c\x00\x00\x00\x00\x08\x20\x00\x00\x00\x00' 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'\x14\x0e\x05\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\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'\x0c\x89\x0c\x89\x0c\x88\x00\x0f\x00\x0f\x00\x0f\x00\x0e\x02\x00'
msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01' msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01'
msg += correct_checksum(msg) msg += correct_checksum(msg)
msg += b'\x15' msg += b'\x15'