From 1ec97a3e9cd43d9215d61b077d24a1d45427c80c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Apr 2025 14:51:13 +0200 Subject: [PATCH 01/10] Update ghcr.io/hassio-addons/base Docker tag to v17.2.3 (#342) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Stefan Allius --- ha_addons/ha_addon/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ha_addons/ha_addon/Dockerfile b/ha_addons/ha_addon/Dockerfile index 6f35ceb..9d3ecdb 100755 --- a/ha_addons/ha_addon/Dockerfile +++ b/ha_addons/ha_addon/Dockerfile @@ -13,7 +13,7 @@ # 1 Build Base Image # ###################### -ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.2" +ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.3" # hadolint ignore=DL3006 FROM $BUILD_FROM AS base From 970b611d470caba5f7417d05c720143ed3d37cc6 Mon Sep 17 00:00:00 2001 From: Stefan Allius <122395479+s-allius@users.noreply.github.com> Date: Fri, 4 Apr 2025 18:38:17 +0200 Subject: [PATCH 02/10] fix systemtest (#344) --- .env_example | 7 ++- system_tests/test_tcp_socket_v2.py | 77 +++++++++++++++--------------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/.env_example b/.env_example index 4d28078..cd6a572 100644 --- a/.env_example +++ b/.env_example @@ -6,4 +6,9 @@ PRIVAT_CONTAINER_REGISTRY=docker.io// # registry for official container (preview, rc, rel) PUBLIC_CONTAINER_REGISTRY=ghcr.io// -PUBLIC_CR_KEY= \ No newline at end of file +PUBLIC_CR_KEY= + +# define serial number of GEN3PLUS devices for systemtests +# the serialnumber are coded as 4-byte hex-strings +SOLARMAN_INV_SNR='00000000' +SOLARMAN_DCU_SNR='00000000' \ No newline at end of file diff --git a/system_tests/test_tcp_socket_v2.py b/system_tests/test_tcp_socket_v2.py index 94816fd..3b5b0b3 100644 --- a/system_tests/test_tcp_socket_v2.py +++ b/system_tests/test_tcp_socket_v2.py @@ -5,13 +5,14 @@ from dotenv import load_dotenv load_dotenv() -SOLARMAN_SNR = os.getenv('SOLARMAN_SNR', '00000080') +SOLARMAN_INV_SNR = os.getenv('SOLARMAN_INV_SNR', '00000080') +SOLARMAN_DCU_SNR = os.getenv('SOLARMAN_INV_SNR', '00000080') def get_sn() -> bytes: - return bytes.fromhex(SOLARMAN_SNR) + return bytes.fromhex(SOLARMAN_INV_SNR) def get_dcu_sn() -> bytes: - return b'\x20\x43\x65\x7b' + return bytes.fromhex(SOLARMAN_DCU_SNR) def get_dcu_no() -> bytes: return b'4100000000000001' @@ -27,7 +28,7 @@ def correct_checksum(buf): return checksum.to_bytes(length=1) @pytest.fixture -def MsgContactInfo(): # Contact Info message +def msg_contact_info(): # Contact Info message msg = b'\xa5\xd4\x00\x10\x41\x00\x01' +get_sn() +b'\x02\xba\xd2\x00\x00' msg += b'\x19\x00\x00\x00\x00\x00\x00\x00\x05\x3c\x78\x01\x64\x01\x4c\x53' msg += b'\x57\x35\x42\x4c\x45\x5f\x31\x37\x5f\x30\x32\x42\x30\x5f\x31\x2e' @@ -46,13 +47,13 @@ def MsgContactInfo(): # Contact Info message return msg @pytest.fixture -def MsgContactResp(): # Contact Response message +def msg_contact_resp(): # Contact Response message msg = b'\xa5\x0a\x00\x10\x11\x01\x01' +get_sn() +b'\x02\x01\x6a\xfd\x8f' msg += b'\x65\x3c\x00\x00\x00\x75\x15' return msg @pytest.fixture -def MsgDataInd(): +def msg_data_ind(): msg = b'\xa5\x99\x01\x10\x42\x59\x84' +get_sn() +b'\x01\xb0\x02\x2c\x87' msg += b'\x22\x32\xb7\x29\x00\x00\xd6\xcf\xe1\x33\x01\x00\x0c\x05\x00\x00' msg += b'\x59\x31\x37\x45\x37\x41\x30\x46\x30\x31\x30\x42\x30\x31\x33\x45' @@ -86,14 +87,14 @@ def MsgDataInd(): return msg @pytest.fixture -def MsgDataResp(): # Contact Response message +def msg_data_rsp(): # Contact Response message msg = b'\xa5\x0a\x00\x10\x12\x80\x84' +get_sn() +b'\x01\x01\xd1\x96\x04' msg += b'\x66\x3c\x00\x00\x00\xed\x15' return msg @pytest.fixture -def MsgInvalidInfo(): # Contact Info message wrong start byte +def msg_invalid_info(): # Contact Info message wrong start byte msg = b'\x47\xd4\x00\x10\x41\x00\x01' +get_sn() +b'\x02\xba\xd2\x00\x00' msg += b'\x19\x00\x00\x00\x00\x00\x00\x00\x05\x3c\x78\x01\x64\x01\x4c\x53' msg += b'\x57\x35\x42\x4c\x45\x5f\x31\x37\x5f\x30\x32\x42\x30\x5f\x31\x2e' @@ -169,7 +170,7 @@ def dcu_data_rsp_msg(): # 0x1210 return msg @pytest.fixture(scope="session") -def ClientConnection(): +def client_connection(): host = 'logger.talent-monitoring.com' port = 10000 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: @@ -178,15 +179,15 @@ def ClientConnection(): yield s s.close() -def checkResponse(data, Msg): +def check_response(data, msg): check = bytearray(data) - check[5]= Msg[5] # ignore seq - check[13:18]= Msg[13:18] # ignore timestamp + first byte of repeat time - check[21]= Msg[21] # ignore crc - assert check == Msg + check[5]= msg[5] # ignore seq + check[13:18]= msg[13:18] # ignore timestamp + first byte of repeat time + check[21]= msg[21] # ignore crc + assert check == msg -def tempClientConnection(): +def tempclient_connection(): host = 'logger.talent-monitoring.com' port = 10000 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: @@ -198,53 +199,53 @@ def tempClientConnection(): def test_open_close(): try: - for _ in tempClientConnection(): - pass # test generator tempClientConnection() - except: + for _ in tempclient_connection(): + pass # test generator tempclient_connection() + except TimeoutError: assert False -def test_conn_msg(ClientConnection,MsgContactInfo, MsgContactResp): - s = ClientConnection +def test_conn_msg(client_connection,msg_contact_info, msg_contact_resp): + s = client_connection try: - s.sendall(MsgContactInfo) - # time.sleep(2.5) + s.sendall(msg_contact_info) + time.sleep(2.5) data = s.recv(1024) except TimeoutError: pass # time.sleep(2.5) - checkResponse(data, MsgContactResp) + check_response(data, msg_contact_resp) -def test_data_ind(ClientConnection,MsgDataInd, MsgDataResp): - s = ClientConnection +def test_data_ind(client_connection,msg_data_ind, msg_data_rsp): + s = client_connection try: - s.sendall(MsgDataInd) + s.sendall(msg_data_ind) # time.sleep(2.5) data = s.recv(1024) except TimeoutError: pass # time.sleep(2.5) - checkResponse(data, MsgDataResp) + check_response(data, msg_data_rsp) -def test_inavlid_msg(ClientConnection,MsgInvalidInfo,MsgContactInfo, MsgContactResp): - s = ClientConnection +def test_inavlid_msg(client_connection,msg_invalid_info,msg_contact_info, msg_contact_resp): + s = client_connection try: - s.sendall(MsgInvalidInfo) + s.sendall(msg_invalid_info) # time.sleep(2.5) data = s.recv(1024) except TimeoutError: pass # time.sleep(2.5) try: - s.sendall(MsgContactInfo) + s.sendall(msg_contact_info) # time.sleep(2.5) data = s.recv(1024) except TimeoutError: pass # time.sleep(2.5) - checkResponse(data, MsgContactResp) + check_response(data, msg_contact_resp) -def test_dcu_dev(ClientConnection,dcu_dev_ind_msg, dcu_dev_rsp_msg): - s = ClientConnection +def test_dcu_dev(client_connection,dcu_dev_ind_msg, dcu_dev_rsp_msg): + s = client_connection try: s.sendall(dcu_dev_ind_msg) # time.sleep(2.5) @@ -252,10 +253,10 @@ def test_dcu_dev(ClientConnection,dcu_dev_ind_msg, dcu_dev_rsp_msg): except TimeoutError: pass # time.sleep(2.5) - checkResponse(data, dcu_dev_rsp_msg) + check_response(data, dcu_dev_rsp_msg) -def test_dcu_ind(ClientConnection,dcu_data_ind_msg, dcu_data_rsp_msg): - s = ClientConnection +def test_dcu_ind(client_connection,dcu_data_ind_msg, dcu_data_rsp_msg): + s = client_connection try: s.sendall(dcu_data_ind_msg) # time.sleep(2.5) @@ -263,4 +264,4 @@ def test_dcu_ind(ClientConnection,dcu_data_ind_msg, dcu_data_rsp_msg): except TimeoutError: pass # time.sleep(2.5) - checkResponse(data, dcu_data_rsp_msg) + check_response(data, dcu_data_rsp_msg) From 4988a29a34334575c13fd078d184117031c3c7b6 Mon Sep 17 00:00:00 2001 From: Stefan Allius <122395479+s-allius@users.noreply.github.com> Date: Fri, 4 Apr 2025 20:11:43 +0200 Subject: [PATCH 03/10] S allius/issue340 (#345) * build the README.md files for the HA Add-ons --- ha_addons/.gitignore | 3 ++- ha_addons/Makefile | 14 +++++++++++++- ha_addons/templates/README.jinja | 21 +++++++++++++++++++++ ha_addons/templates/debug_data.json | 4 +++- ha_addons/templates/dev_data.json | 4 +++- ha_addons/templates/rc_data.json | 5 ++++- ha_addons/templates/rel_data.json | 4 +++- 7 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 ha_addons/templates/README.jinja diff --git a/ha_addons/.gitignore b/ha_addons/.gitignore index 8a014ce..1e60ea6 100644 --- a/ha_addons/.gitignore +++ b/ha_addons/.gitignore @@ -1,3 +1,4 @@ .data.json config.yaml -apparmor.txt \ No newline at end of file +apparmor.txt +README.md \ No newline at end of file diff --git a/ha_addons/Makefile b/ha_addons/Makefile index 15b8794..ea9bc55 100644 --- a/ha_addons/Makefile +++ b/ha_addons/Makefile @@ -66,7 +66,7 @@ clean: # Build the local add-on with a rootfs and config.yaml # The rootfs is needed to build the add-on Docker container # -local_add_on: rootfs $(ADDON_PATH)/config.yaml $(ADDON_PATH)/apparmor.txt +local_add_on: rootfs $(ADDON_PATH)/config.yaml $(ADDON_PATH)/apparmor.txt $(ADDON_PATH)/README.md # collect source files SRC_FILES := $(wildcard $(SRC_PROXY)/*.py)\ @@ -103,6 +103,9 @@ $(ADDON_PATH)/%.yaml: $(TEMPL)/%.jinja $(TEMPL)/.data.json $(ADDON_PATH)/%.txt: $(TEMPL)/%.jinja $(TEMPL)/.data.json $(JINJA) --strict --format=json $^ -o $@ +$(ADDON_PATH)/%.md: $(TEMPL)/%.jinja $(TEMPL)/.data.json + $(JINJA) --strict --format=json $^ -o $@ + # build a common data.json file from STAGE depending source files # don't touch the destination if the checksum of src and dst is equal $(TEMPL)/.data.json: FORCE @@ -119,6 +122,7 @@ repro_files = DOCS.md icon.png logo.png translations/de.yaml translations/en.yam repro_root = CHANGELOG.md LICENSE.md repro_templates = config.yaml repro_apparmor = apparmor.txt +repro_readme = README.md repro_subdirs = translations rootfs repro_vers = debug dev rc rel @@ -126,29 +130,34 @@ repro_all_files := $(foreach dir,$(repro_vers), $(foreach file,$(repro_files),$( repro_root_files := $(foreach dir,$(repro_vers), $(foreach file,$(repro_root),$(INST_BASE)/ha_addon_$(dir)/$(file))) repro_all_templates := $(foreach dir,$(repro_vers), $(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_$(dir)/$(file))) repro_all_apparmor := $(foreach dir,$(repro_vers), $(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_$(dir)/$(file))) +repro_all_readme := $(foreach dir,$(repro_vers), $(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_$(dir)/$(file))) repro_all_subdirs := $(foreach dir,$(repro_vers), $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_$(dir)/$(file))) debug: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_debug/$(file)) \ $(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_debug/$(file)) \ $(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_debug/$(file)) \ + $(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_debug/$(file)) \ $(foreach file,$(repro_files),$(INST_BASE)/ha_addon_debug/$(file)) \ $(foreach file,$(repro_root),$(INST_BASE)/ha_addon_debug/$(file)) dev: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_dev/$(file)) \ $(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_dev/$(file)) \ $(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_dev/$(file)) \ + $(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_dev/$(file)) \ $(foreach file,$(repro_files),$(INST_BASE)/ha_addon_dev/$(file)) \ $(foreach file,$(repro_root),$(INST_BASE)/ha_addon_dev/$(file)) rc: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_rc/$(file)) \ $(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_rc/$(file)) \ $(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_rc/$(file)) \ + $(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_rc/$(file)) \ $(foreach file,$(repro_files),$(INST_BASE)/ha_addon_rc/$(file)) \ $(foreach file,$(repro_root),$(INST_BASE)/ha_addon_rc/$(file)) rel: $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_rel/$(file)) \ $(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_rel/$(file)) \ $(foreach file,$(repro_apparmor),$(INST_BASE)/ha_addon_rel/$(file)) \ + $(foreach file,$(repro_readme),$(INST_BASE)/ha_addon_rel/$(file)) \ $(foreach file,$(repro_files),$(INST_BASE)/ha_addon_rel/$(file)) \ $(foreach file,$(repro_root),$(INST_BASE)/ha_addon_rel/$(file)) @@ -161,6 +170,9 @@ $(repro_all_templates) : $(INST_BASE)/ha_addon_%/config.yaml: $(TEMPL)/config.ji $(repro_all_apparmor) : $(INST_BASE)/ha_addon_%/apparmor.txt: $(TEMPL)/apparmor.jinja $(TEMPL)/%_data.json $(JINJA) --strict $< $(filter %.json,$^) -o $@ +$(repro_all_readme) : $(INST_BASE)/ha_addon_%/README.md: $(TEMPL)/README.jinja $(TEMPL)/%_data.json + $(JINJA) --strict $< $(filter %.json,$^) -o $@ + $(filter $(INST_BASE)/ha_addon_debug/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_debug/% : ../% cp $< $@ $(filter $(INST_BASE)/ha_addon_dev/%,$(repro_root_files)) : $(INST_BASE)/ha_addon_dev/% : ../% diff --git a/ha_addons/templates/README.jinja b/ha_addons/templates/README.jinja new file mode 100644 index 0000000..421ddad --- /dev/null +++ b/ha_addons/templates/README.jinja @@ -0,0 +1,21 @@ +# Home Assistant Add-on: {{name}} + +{{readme_descr}} + +## Features + +- Supports TSUN GEN3 PLUS inverters: TSOL-MS2000, MS1800 and MS1600 +- Supports TSUN GEN3 PLUS batteries: TSOL-DC1000 (from version 0.13) +- Supports TSUN GEN3 inverters: TSOL-MS3000, MS800, MS700, MS600, MS400, MS350 and MS300 +- `Home-Assistant` auto-discovery support +- `MODBUS` support via MQTT topics +- `AT-Command` support via MQTT topics (GEN3PLUS only) +- Faster DataUp interval sends measurement data to the MQTT broker every minute +- Self-sufficient island operation without internet +- Security-Features: + - control access via `AT-commands` + +## About + +This Add-on and the TSUN Proxy is not related to the company TSUN. It is a private initiative that aims to connect TSUN inverters and storage systems with an MQTT broker. There is no support and no warranty from TSUN. +{{readme_links}} \ No newline at end of file diff --git a/ha_addons/templates/debug_data.json b/ha_addons/templates/debug_data.json index fcfdfe7..47fa2d2 100644 --- a/ha_addons/templates/debug_data.json +++ b/ha_addons/templates/debug_data.json @@ -5,5 +5,7 @@ "image": "docker.io/sallius/tsun-gen3-addon", "slug": "tsun-proxy-debug", "advanced": true, - "stage": "experimental" + "stage": "experimental", + "readme_descr": "This is a bleeding-edge version of the `TSUN Proxy` Add-On with debuging enabled by default.\n\nThe versions may be based on different feature branches and therefore the range of functions may change.\n\nIt is intended to be used to simulate special situations/problems and should only be used in consultation with the maintainer.\n\nFor production please use the stable version `TSUN Proxy`. If you are interested in a bleeding edge version, we offer the `TSUN Proxy (dev)` version.", + "readme_links": "" } \ No newline at end of file diff --git a/ha_addons/templates/dev_data.json b/ha_addons/templates/dev_data.json index a7fbb82..68e933c 100644 --- a/ha_addons/templates/dev_data.json +++ b/ha_addons/templates/dev_data.json @@ -5,5 +5,7 @@ "image": "docker.io/sallius/tsun-gen3-addon", "slug": "tsun-proxy-dev", "advanced": false, - "stage": "experimental" + "stage": "experimental", + "readme_descr": "This is a bleeding-edge version of the `TSUN Proxy` Add-On.\n\nThe versions may be based on different feature branches and therefore the range of functions may change.\n\nIt is intended for testing new functions or testing new devices that are to be supported with the next release.\nFor production, please use the stable version 'TSUN Proxy'.", + "readme_links": "" } \ No newline at end of file diff --git a/ha_addons/templates/rc_data.json b/ha_addons/templates/rc_data.json index 6f21cc4..05d33ce 100644 --- a/ha_addons/templates/rc_data.json +++ b/ha_addons/templates/rc_data.json @@ -6,5 +6,8 @@ "image": "ghcr.io/s-allius/tsun-gen3-addon", "slug": "tsun-proxy-rc", "advanced": true, - "stage": "experimental" + "stage": "experimental", + "readme_descr": "This is a release candidate of the `TSUN Proxy` Add-On.\n\nIt is intended for testing the next release.\nFor production, please use the stable version 'TSUN Proxy'.", + "readme_links": "" + } \ No newline at end of file diff --git a/ha_addons/templates/rel_data.json b/ha_addons/templates/rel_data.json index d6046f5..edf673c 100644 --- a/ha_addons/templates/rel_data.json +++ b/ha_addons/templates/rel_data.json @@ -5,5 +5,7 @@ "image": "ghcr.io/s-allius/tsun-gen3-addon", "slug": "tsun-proxy", "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_links": "\n[tsunproxy]: https://github.com/s-allius/tsun-gen3-proxy\n" } \ No newline at end of file From 6974672ba057fa5100cb71a07ac06e709e084a93 Mon Sep 17 00:00:00 2001 From: Stefan Allius <122395479+s-allius@users.noreply.github.com> Date: Sat, 5 Apr 2025 14:37:52 +0200 Subject: [PATCH 04/10] S allius/issue334 (#335) * move forward_at_cmd_resp into InfosG3P class - the variable is shared between the two connections of an inverter. One is for the TSUN cloud and the other for the device. * use inverter class to share values between the two protocol instances of a proxy - move forward_at_cmd_resp into class InverterG3P - store inverter ptr in Solarman_V5 instances - add inverter ptr to all constructurs of protocols - adapt doku and unit tests- - add integration tests for AT+ commands which check the forwarding from and to the TSUN cloud * adapt and improve the unit tests - fix node_id declaration, which always has a / at the end. See config grammar for this rule - set global var test to default after test run --- app/docu/proxy_2.svg | 706 ++++++++++++++++--------------- app/docu/proxy_2.yuml | 8 +- app/docu/proxy_3.svg | 701 +++++++++++++++--------------- app/docu/proxy_3.yuml | 9 +- app/src/gen3/talent.py | 3 +- app/src/gen3plus/inverter_g3p.py | 9 + app/src/gen3plus/solarman_emu.py | 3 +- app/src/gen3plus/solarman_v5.py | 16 +- app/src/inverter_base.py | 6 +- app/tests/test_inverter_g3.py | 1 + app/tests/test_inverter_g3p.py | 3 + app/tests/test_modbus_tcp.py | 4 +- app/tests/test_mqtt.py | 6 +- app/tests/test_solarman.py | 162 ++++++- app/tests/test_solarman_emu.py | 6 +- app/tests/test_talent.py | 8 +- 16 files changed, 912 insertions(+), 739 deletions(-) diff --git a/app/docu/proxy_2.svg b/app/docu/proxy_2.svg index 232983f..0298327 100644 --- a/app/docu/proxy_2.svg +++ b/app/docu/proxy_2.svg @@ -4,368 +4,380 @@ - + G - + A0 - - - -Example of -instantiation for a -GEN3 inverter! + + + +Example of +instantiation for a +GEN3 inverter! A1 - -<<AbstractIterMeta>> - - -__iter__() - - - -A14 - -<<ProtocolIfc>> - -_registry - -close() - - - -A1->A14 - - - - - -A2 - -InverterG3 - -addr -remote:StreamPtr -local:StreamPtr - -create_remote() -close() - - - -A3 - -local:StreamPtr - - - -A2->A3 - - - - - - -A4 - -remote:StreamPtr - - - -A2->A4 - - - - - - -A8 - -AsyncStreamServer - -create_remote - -<async>server_loop() -<async>_async_forward() -<async>publish_outstanding_mqtt() -close() - - - -A3->A8 - - - - - - -A9 - -AsyncStreamClient - - -<async>client_loop() -<async>_async_forward()) - - - -A4->A9 - - -0..1 - - - -A5 - -<<AsyncIfc>> - - -set_node_id() -get_conn_no() -tx_add() -tx_flush() -tx_get() -tx_peek() -tx_log() -tx_clear() -tx_len() -fwd_add() -fwd_log() -rx_get() -rx_peek() -rx_log() -rx_clear() -rx_len() -rx_set_cb() -prot_set_timeout_cb() - - - -A6 - -AsyncIfcImpl - -fwd_fifo:ByteFifo -tx_fifo:ByteFifo -rx_fifo:ByteFifo -conn_no:Count -node_id -timeout_cb - - - -A5->A6 - - - - - -A7 - -AsyncStream - -reader -writer -addr -r_addr -l_addr - -<async>loop -disc() -close() -healthy() -__async_read() -__async_write() -__async_forward() - - - -A6->A7 - - - - - -A7->A8 - - - - - -A7->A9 - - - - - -A10 - -Talent - -conn_no -addr -await_conn_resp_cnt -id_str -contact_name -contact_mail -db:InfosG3 -mb:Modbus -switch - -msg_contact_info() -msg_ota_update() -msg_get_time() -msg_collector_data() -msg_inverter_data() -msg_unknown() -healthy() -close() - - - -A10->A3 - - - - - - -A10->A4 - - -0..1 - - - -A12 - -InfosG3 - - -ha_confs() -parse() - - - -A10->A12 - - - - - -A11 - -Infos - -stat -new_stat_data -info_dev - -static_init() -dev_value() -inc_counter() -dec_counter() -ha_proxy_conf -ha_conf -ha_remove -update_db -set_db_def_value -get_db_value -ignore_this_device - - - -A11->A12 - - - - - -A13 - -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() - - - -A13->A5 - - -use - - - -A13->A10 - - - - - -A14->A13 - - + +<<AbstractIterMeta>> + + +__iter__() A15 - -Modbus - -que -snd_handler -rsp_handler -timeout -max_retires -last_xxx -err -retry_cnt -req_pend -tim - -build_msg() -recv_req() -recv_resp() -close() + +<<ProtocolIfc>> + +_registry + +close() - + + +A1->A15 + + + + + +A2 + +InverterBase + +addr +remote:StreamPtr +local:StreamPtr + +create_remote() +close() + + + +A3 + +InverterG3 + + + +A2->A3 + + + + + +A4 + +local:StreamPtr + + + +A2->A4 + + + + + + +A5 + +remote:StreamPtr + + + +A2->A5 + + + + + + +A9 + +AsyncStreamServer + +create_remote + +<async>server_loop() +<async>_async_forward() +<async>publish_outstanding_mqtt() +close() + + + +A4->A9 + + + + + + +A10 + +AsyncStreamClient + + +<async>client_loop() +<async>_async_forward()) + + + +A5->A10 + + +0..1 + + + +A6 + +<<AsyncIfc>> + + +set_node_id() +get_conn_no() +tx_add() +tx_flush() +tx_get() +tx_peek() +tx_log() +tx_clear() +tx_len() +fwd_add() +fwd_log() +rx_get() +rx_peek() +rx_log() +rx_clear() +rx_len() +rx_set_cb() +prot_set_timeout_cb() + + + +A7 + +AsyncIfcImpl + +fwd_fifo:ByteFifo +tx_fifo:ByteFifo +rx_fifo:ByteFifo +conn_no:Count +node_id +timeout_cb + + + +A6->A7 + + + + + +A8 + +AsyncStream + +reader +writer +addr +r_addr +l_addr + +<async>loop +disc() +close() +healthy() +__async_read() +__async_write() +__async_forward() + + + +A7->A8 + + + + + +A8->A9 + + + + + +A8->A10 + + + + + +A11 + +Talent + +conn_no +addr +await_conn_resp_cnt +id_str +contact_name +contact_mail +db:InfosG3 +mb:Modbus +switch + +msg_contact_info() +msg_ota_update() +msg_get_time() +msg_collector_data() +msg_inverter_data() +msg_unknown() +healthy() +close() + + + +A11->A4 + + + + + + +A11->A5 + + +0..1 + + + +A13 + +InfosG3 + + +ha_confs() +parse() + + + +A11->A13 + + + + + +A12 + +Infos + +stat +new_stat_data +info_dev + +static_init() +dev_value() +inc_counter() +dec_counter() +ha_proxy_conf +ha_conf +ha_remove +update_db +set_db_def_value +get_db_value +ignore_this_device + + + +A12->A13 + + + + + +A14 + +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() + + + +A14->A6 + + +use + + -A15->A13 - - -has -0..1 +A14->A11 + + + + + +A15->A14 + + + + + +A16 + +Modbus + +que +snd_handler +rsp_handler +timeout +max_retires +last_xxx +err +retry_cnt +req_pend +tim + +build_msg() +recv_req() +recv_resp() +close() + + + +A16->A14 + + +has +0..1 diff --git a/app/docu/proxy_2.yuml b/app/docu/proxy_2.yuml index 5138428..6c50022 100644 --- a/app/docu/proxy_2.yuml +++ b/app/docu/proxy_2.yuml @@ -5,10 +5,10 @@ [note: Example of instantiation for a GEN3 inverter!{bg:cornsilk}] [<>||__iter__()] -[InverterG3|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()] -[InverterG3]++->[local:StreamPtr] -[InverterG3]++->[remote:StreamPtr] - +[InverterBase|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()] +[InverterBase]^[InverterG3] +[InverterBase]++->[local:StreamPtr] +[InverterBase]++->[remote:StreamPtr] [<>||set_node_id();get_conn_no();;tx_add();tx_flush();tx_get();tx_peek();tx_log();tx_clear();tx_len();;fwd_add();fwd_log();rx_get();rx_peek();rx_log();rx_clear();rx_len();rx_set_cb();;prot_set_timeout_cb()] [AsyncIfcImpl|fwd_fifo:ByteFifo;tx_fifo:ByteFifo;rx_fifo:ByteFifo;conn_no:Count;node_id;timeout_cb] [AsyncStream|reader;writer;addr;r_addr;l_addr|;loop;disc();close();healthy();;__async_read();__async_write();__async_forward()] diff --git a/app/docu/proxy_3.svg b/app/docu/proxy_3.svg index 8be4969..d429bed 100644 --- a/app/docu/proxy_3.svg +++ b/app/docu/proxy_3.svg @@ -4,364 +4,379 @@ - + G - + A0 - - - -Example of -instantiation for a -GEN3PLUS inverter! + + + +Example of +instantiation for a +GEN3PLUS inverter! A1 - -<<AbstractIterMeta>> - - -__iter__() - - - -A14 - -<<ProtocolIfc>> - -_registry - -close() - - - -A1->A14 - - - - - -A2 - -InverterG3P - -addr -remote:StreamPtr -local:StreamPtr - -create_remote() -close() - - - -A3 - -local:StreamPtr - - - -A2->A3 - - - - - - -A4 - -remote:StreamPtr - - - -A2->A4 - - - - - - -A8 - -AsyncStreamServer - -create_remote - -<async>server_loop() -<async>_async_forward() -<async>publish_outstanding_mqtt() -close() - - - -A3->A8 - - - - - - -A9 - -AsyncStreamClient - - -<async>client_loop() -<async>_async_forward()) - - - -A4->A9 - - -0..1 - - - -A5 - -<<AsyncIfc>> - - -set_node_id() -get_conn_no() -tx_add() -tx_flush() -tx_get() -tx_peek() -tx_log() -tx_clear() -tx_len() -fwd_add() -fwd_log() -rx_get() -rx_peek() -rx_log() -rx_clear() -rx_len() -rx_set_cb() -prot_set_timeout_cb() - - - -A6 - -AsyncIfcImpl - -fwd_fifo:ByteFifo -tx_fifo:ByteFifo -rx_fifo:ByteFifo -conn_no:Count -node_id -timeout_cb - - - -A5->A6 - - - - - -A7 - -AsyncStream - -reader -writer -addr -r_addr -l_addr - -<async>loop -disc() -close() -healthy() -__async_read() -__async_write() -__async_forward() - - - -A6->A7 - - - - - -A7->A8 - - - - - -A7->A9 - - - - - -A10 - -SolarmanV5 - -conn_no -addr -control -serial -snr -db:InfosG3P -switch - -msg_unknown() -healthy() -close() - - - -A10->A3 - - - - - - -A10->A4 - - -0..1 - - - -A12 - -InfosG3P - -client_mode:bool - -ha_confs() -parse() -calc() -build() - - - -A10->A12 - - - - - -A11 - -Infos - -stat -new_stat_data -info_dev - -static_init() -dev_value() -inc_counter() -dec_counter() -ha_proxy_conf -ha_conf -ha_remove -update_db -set_db_def_value -get_db_value -ignore_this_device - - - -A11->A12 - - - - - -A13 - -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() - - - -A13->A5 - - -use - - - -A13->A10 - - - - - -A14->A13 - - + +<<AbstractIterMeta>> + + +__iter__() A15 - -Modbus - -que -snd_handler -rsp_handler -timeout -max_retires -last_xxx -err -retry_cnt -req_pend -tim - -build_msg() -recv_req() -recv_resp() -close() + +<<ProtocolIfc>> + +_registry + +close() - + + +A1->A15 + + + + + +A2 + +InverterBase + +addr +remote:StreamPtr +local:StreamPtr + +create_remote() +close() + + + +A3 + +InverterG3P + +forward_at_cmd_resp + + + +A2->A3 + + + + + +A4 + +local:StreamPtr + + + +A2->A4 + + + + + + +A5 + +remote:StreamPtr + + + +A2->A5 + + + + + + +A9 + +AsyncStreamServer + +create_remote + +<async>server_loop() +<async>_async_forward() +<async>publish_outstanding_mqtt() +close() + + + +A4->A9 + + + + + + +A10 + +AsyncStreamClient + + +<async>client_loop() +<async>_async_forward()) + + + +A5->A10 + + +0..1 + + + +A6 + +<<AsyncIfc>> + + +set_node_id() +get_conn_no() +tx_add() +tx_flush() +tx_get() +tx_peek() +tx_log() +tx_clear() +tx_len() +fwd_add() +fwd_log() +rx_get() +rx_peek() +rx_log() +rx_clear() +rx_len() +rx_set_cb() +prot_set_timeout_cb() + + + +A7 + +AsyncIfcImpl + +fwd_fifo:ByteFifo +tx_fifo:ByteFifo +rx_fifo:ByteFifo +conn_no:Count +node_id +timeout_cb + + + +A6->A7 + + + + + +A8 + +AsyncStream + +reader +writer +addr +r_addr +l_addr + +<async>loop +disc() +close() +healthy() +__async_read() +__async_write() +__async_forward() + + + +A7->A8 + + + + + +A8->A9 + + + + + +A8->A10 + + + + + +A11 + +SolarmanV5 + +conn_no +addr +inverter:InverterG3P +control +serial +snr +db:InfosG3P +switch + +msg_unknown() +healthy() +close() + + + +A11->A4 + + + + + + +A11->A5 + + +0..1 + + + +A13 + +InfosG3P + +client_mode:bool + +ha_confs() +parse() +calc() +build() + + + +A11->A13 + + + + + +A12 + +Infos + +stat +new_stat_data +info_dev + +static_init() +dev_value() +inc_counter() +dec_counter() +ha_proxy_conf +ha_conf +ha_remove +update_db +set_db_def_value +get_db_value +ignore_this_device + + + +A12->A13 + + + + + +A14 + +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() + + + +A14->A6 + + +use + + -A15->A13 - - -has -0..1 +A14->A11 + + + + + +A15->A14 + + + + + +A16 + +Modbus + +que +snd_handler +rsp_handler +timeout +max_retires +last_xxx +err +retry_cnt +req_pend +tim + +build_msg() +recv_req() +recv_resp() +close() + + + +A16->A14 + + +has +0..1 diff --git a/app/docu/proxy_3.yuml b/app/docu/proxy_3.yuml index 3703658..18bcec1 100644 --- a/app/docu/proxy_3.yuml +++ b/app/docu/proxy_3.yuml @@ -5,9 +5,10 @@ [note: Example of instantiation for a GEN3PLUS inverter!{bg:cornsilk}] [<>||__iter__()] -[InverterG3P|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()] -[InverterG3P]++->[local:StreamPtr] -[InverterG3P]++->[remote:StreamPtr] +[InverterBase|addr;remote:StreamPtr;local:StreamPtr|create_remote();;close()] +[InverterBase]^[InverterG3P|forward_at_cmd_resp;] +[InverterBase]++->[local:StreamPtr] +[InverterBase]++->[remote:StreamPtr] [<>||set_node_id();get_conn_no();;tx_add();tx_flush();tx_get();tx_peek();tx_log();tx_clear();tx_len();;fwd_add();fwd_log();rx_get();rx_peek();rx_log();rx_clear();rx_len();rx_set_cb();;prot_set_timeout_cb()] [AsyncIfcImpl|fwd_fifo:ByteFifo;tx_fifo:ByteFifo;rx_fifo:ByteFifo;conn_no:Count;node_id;timeout_cb] @@ -19,7 +20,7 @@ [AsyncStream]^[AsyncStreamServer] [AsyncStream]^[AsyncStreamClient] -[SolarmanV5|conn_no;addr;;control;serial;snr;db:InfosG3P;switch|msg_unknown();;healthy();close()] +[SolarmanV5|conn_no;addr;inverter:InverterG3P;control;serial;snr;db:InfosG3P;switch|msg_unknown();;healthy();close()] [SolarmanV5]<-++[local:StreamPtr] [local:StreamPtr]++->[AsyncStreamServer] [SolarmanV5]<-0..1[remote:StreamPtr] diff --git a/app/src/gen3/talent.py b/app/src/gen3/talent.py index e799a36..292dfdf 100644 --- a/app/src/gen3/talent.py +++ b/app/src/gen3/talent.py @@ -34,10 +34,11 @@ class Control: class Talent(Message): TXT_UNKNOWN_CTRL = 'Unknown Ctrl' - def __init__(self, addr, ifc: "AsyncIfc", server_side: bool, + def __init__(self, inverter, addr, ifc: "AsyncIfc", server_side: bool, client_mode: bool = False, id_str=b''): super().__init__('G3', ifc, server_side, self.send_modbus_cb, mb_timeout=15) + _ = inverter ifc.rx_set_cb(self.read) ifc.prot_set_timeout_cb(self._timeout) ifc.prot_set_init_new_client_conn_cb(self._init_new_client_conn) diff --git a/app/src/gen3plus/inverter_g3p.py b/app/src/gen3plus/inverter_g3p.py index f3680c9..6a3f55d 100644 --- a/app/src/gen3plus/inverter_g3p.py +++ b/app/src/gen3plus/inverter_g3p.py @@ -8,6 +8,15 @@ from gen3plus.solarman_emu import SolarmanEmu class InverterG3P(InverterBase): def __init__(self, reader: StreamReader, writer: StreamWriter, client_mode: bool = False): + # shared value between both inverter connections + self.forward_at_cmd_resp = False + '''Flag if response for the last at command must be send to the cloud. + + False: send result only to the MQTT broker, cause the AT+ command + came from there + True: send response packet to the cloud, cause the AT+ command + came from the cloud''' + remote_prot = None if client_mode: remote_prot = SolarmanEmu diff --git a/app/src/gen3plus/solarman_emu.py b/app/src/gen3plus/solarman_emu.py index 7462388..93bb874 100644 --- a/app/src/gen3plus/solarman_emu.py +++ b/app/src/gen3plus/solarman_emu.py @@ -10,11 +10,12 @@ logger = logging.getLogger('msg') class SolarmanEmu(SolarmanBase): - def __init__(self, addr, ifc: "AsyncIfc", + def __init__(self, inverter, addr, ifc: "AsyncIfc", server_side: bool, client_mode: bool): super().__init__(addr, ifc, server_side=False, _send_modbus_cb=None, mb_timeout=8) + _ = inverter logging.debug('SolarmanEmu.init()') self.db = ifc.remote.stream.db self.snr = ifc.remote.stream.snr diff --git a/app/src/gen3plus/solarman_v5.py b/app/src/gen3plus/solarman_v5.py index 9b4bee8..7c8b997 100644 --- a/app/src/gen3plus/solarman_v5.py +++ b/app/src/gen3plus/solarman_v5.py @@ -253,13 +253,13 @@ class SolarmanV5(SolarmanBase): HDR_FMT = ' we get a memory leak + self.inverter = None self.switch.clear() self.log_lvl.clear() super().close() @@ -519,7 +520,7 @@ class SolarmanV5(SolarmanBase): await Proxy.mqtt.publish(f'{Proxy.entity_prfx}{node_id}{key}', data_json) # noqa: E501 return - self.forward_at_cmd_resp = False + self.inverter.forward_at_cmd_resp = False self._build_header(0x4510) self.ifc.tx_add(struct.pack(f' int: ftype = self.ifc.rx_peek()[self.header_len] - if ftype == self.AT_CMD: - if self.forward_at_cmd_resp: + if ftype == self.AT_CMD or \ + ftype == self.AT_CMD_RSP: + if self.inverter.forward_at_cmd_resp: return logging.INFO return logging.DEBUG elif ftype == self.MB_RTU_CMD \ @@ -680,7 +682,7 @@ class SolarmanV5(SolarmanBase): ftype = data[0] if ftype == self.AT_CMD or \ ftype == self.AT_CMD_RSP: - if not self.forward_at_cmd_resp: + if not self.inverter.forward_at_cmd_resp: data_json = data[14:].decode("utf-8") node_id = self.node_id key = 'at_resp' diff --git a/app/src/inverter_base.py b/app/src/inverter_base.py index a4036c9..18c8902 100644 --- a/app/src/inverter_base.py +++ b/app/src/inverter_base.py @@ -41,7 +41,7 @@ class InverterBase(InverterIfc, Proxy): self.remote) self.local = StreamPtr( - prot_class(self.addr, ifc, True, client_mode), ifc + prot_class(self, self.addr, ifc, True, client_mode), ifc ) def __enter__(self): @@ -122,11 +122,11 @@ class InverterBase(InverterIfc, Proxy): self.remote.ifc = ifc if hasattr(stream, 'id_str'): self.remote.stream = self.prot_class( - addr, ifc, server_side=False, + self, addr, ifc, server_side=False, client_mode=False, id_str=stream.id_str) else: self.remote.stream = self.prot_class( - addr, ifc, server_side=False, + self, addr, ifc, server_side=False, client_mode=False) logging.info(f'[{self.remote.stream.node_id}:' diff --git a/app/tests/test_inverter_g3.py b/app/tests/test_inverter_g3.py index e591d04..626ba7d 100644 --- a/app/tests/test_inverter_g3.py +++ b/app/tests/test_inverter_g3.py @@ -155,6 +155,7 @@ async def test_remote_except(config_conn, patch_open_connection): await asyncio.sleep(0) assert inverter.remote.stream==None del inverter + test = MockType.RD_TEST_0_BYTES cnt = 0 for inv in InverterBase: diff --git a/app/tests/test_inverter_g3p.py b/app/tests/test_inverter_g3p.py index 5b19121..f16a2d8 100644 --- a/app/tests/test_inverter_g3p.py +++ b/app/tests/test_inverter_g3p.py @@ -133,6 +133,9 @@ async def test_remote_except(config_conn, patch_open_connection): await asyncio.sleep(0) assert inverter.remote.stream==None + test = MockType.RD_TEST_0_BYTES + + @pytest.mark.asyncio async def test_mqtt_publish(config_conn, patch_open_connection): _ = config_conn diff --git a/app/tests/test_modbus_tcp.py b/app/tests/test_modbus_tcp.py index 1c69b60..dcb7e3e 100644 --- a/app/tests/test_modbus_tcp.py +++ b/app/tests/test_modbus_tcp.py @@ -190,7 +190,8 @@ def patch_mqtt_except(): yield conn @pytest.mark.asyncio -async def test_modbus_conn(patch_open): +async def test_modbus_conn(config_conn, patch_open): + _ = config_conn _ = patch_open assert Infos.stat['proxy']['Inverter_Cnt'] == 0 @@ -210,6 +211,7 @@ async def test_modbus_conn(patch_open): @pytest.mark.asyncio async def test_modbus_no_cnf(): + _ = config_conn assert Infos.stat['proxy']['Inverter_Cnt'] == 0 loop = asyncio.get_event_loop() ModbusTcp(loop) diff --git a/app/tests/test_mqtt.py b/app/tests/test_mqtt.py index 85f0ab2..421fadb 100644 --- a/app/tests/test_mqtt.py +++ b/app/tests/test_mqtt.py @@ -47,7 +47,7 @@ def config_no_conn(test_port): @pytest.fixture def spy_at_cmd(): - conn = SolarmanV5(('test.local', 1234), server_side=True, client_mode= False, ifc=AsyncIfcImpl()) + conn = SolarmanV5(None, ('test.local', 1234), server_side=True, client_mode= False, ifc=AsyncIfcImpl()) conn.node_id = 'inv_2/' with patch.object(conn, 'send_at_cmd', wraps=conn.send_at_cmd) as wrapped_conn: yield wrapped_conn @@ -55,7 +55,7 @@ def spy_at_cmd(): @pytest.fixture def spy_modbus_cmd(): - conn = SolarmanV5(('test.local', 1234), server_side=True, client_mode= False, ifc=AsyncIfcImpl()) + conn = SolarmanV5(None, ('test.local', 1234), server_side=True, client_mode= False, ifc=AsyncIfcImpl()) conn.node_id = 'inv_1/' with patch.object(conn, 'send_modbus_cmd', wraps=conn.send_modbus_cmd) as wrapped_conn: yield wrapped_conn @@ -63,7 +63,7 @@ def spy_modbus_cmd(): @pytest.fixture def spy_modbus_cmd_client(): - conn = SolarmanV5(('test.local', 1234), server_side=False, client_mode= False, ifc=AsyncIfcImpl()) + conn = SolarmanV5(None, ('test.local', 1234), server_side=False, client_mode= False, ifc=AsyncIfcImpl()) conn.node_id = 'inv_1/' with patch.object(conn, 'send_modbus_cmd', wraps=conn.send_modbus_cmd) as wrapped_conn: yield wrapped_conn diff --git a/app/tests/test_solarman.py b/app/tests/test_solarman.py index e9a28b1..0c3c86c 100644 --- a/app/tests/test_solarman.py +++ b/app/tests/test_solarman.py @@ -4,7 +4,9 @@ import time import asyncio import logging import random +from asyncio import StreamReader, StreamWriter from math import isclose + from async_stream import AsyncIfcImpl, StreamPtr from gen3plus.solarman_v5 import SolarmanV5, SolarmanBase from cnf.config import Config @@ -12,6 +14,11 @@ from infos import Infos, Register from modbus import Modbus from messages import State, Message from proxy import Proxy +from test_inverter_g3p import FakeReader, FakeWriter, patch_open_connection +from inverter_base import InverterBase +from test_modbus_tcp import test_port, test_hostname + + pytest_plugins = ('pytest_asyncio',) @@ -43,10 +50,14 @@ class FakeIfc(AsyncIfcImpl): async def create_remote(self): await asyncio.sleep(0) +class FakeInverter(): + def __init__(self): + self.forward_at_cmd_resp = False + class MemoryStream(SolarmanV5): - def __init__(self, msg, chunks = (0,), server_side: bool = True): + def __init__(self, msg, chunks = (0,), server_side: bool = True, inverter=FakeInverter()): _ifc = FakeIfc() - super().__init__(('test.local', 1234), _ifc, server_side, client_mode=False) + super().__init__(inverter, ('test.local', 1234), _ifc, server_side, client_mode=False) if server_side: self.mb.timeout = 0.4 # overwrite for faster testing self.mb_first_timeout = 0.5 @@ -816,7 +827,7 @@ def config_tsun_allow_all(): @pytest.fixture def config_no_tsun_inv1(): - Config.act_config = {'solarman':{'enabled': False},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 688}}} + Config.act_config = {'solarman':{'enabled': False},'inverters':{'Y170000000000001':{'monitor_sn': 2070233889, 'node_id':'inv1/', 'modbus_polling': True, 'suggested_area':'roof', 'sensor_list': 688}}} @pytest.fixture def config_tsun_inv1(): @@ -828,21 +839,21 @@ def config_tsun_inv1(): '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}}} + 'solarman':{'enabled': True, 'host': 'test_cloud.local', 'port': 1234},'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}}} + 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}}} + 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}}} + 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}} @@ -1432,9 +1443,9 @@ def test_build_logger_modell(config_tsun_allow_all, device_ind_msg): def test_msg_iterator(): Message._registry.clear() - m1 = SolarmanV5(('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) - m2 = SolarmanV5(('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) - m3 = SolarmanV5(('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) + m1 = SolarmanV5(None, ('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) + m2 = SolarmanV5(None, ('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) + m3 = SolarmanV5(None, ('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) m3.close() del m3 test1 = 0 @@ -1452,7 +1463,7 @@ def test_msg_iterator(): assert test2 == 1 def test_proxy_counter(): - m = SolarmanV5(('test.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) + m = SolarmanV5(None, ('test.local', 1234), ifc=AsyncIfcImpl(), server_side=True, client_mode=False) assert m.new_data == {} m.db.stat['proxy']['Unknown_Msg'] = 0 Infos.new_stat_data['proxy'] = False @@ -1559,7 +1570,7 @@ 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) == '03:04' - assert m.forward_at_cmd_resp == False + assert m.inverter.forward_at_cmd_resp == False assert Proxy.mqtt.key == '' assert Proxy.mqtt.data == "" m.close() @@ -1594,12 +1605,12 @@ 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) == '02:02' - assert m.forward_at_cmd_resp == False + assert m.inverter.forward_at_cmd_resp == False 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): +def test_at_cmd_ind(config_tsun_inv1, at_command_ind_msg, at_command_rsp_msg): _ = config_tsun_inv1 m = MemoryStream(at_command_ind_msg, (0,), False) m.db.stat['proxy']['Unknown_Ctrl'] = 0 @@ -1621,6 +1632,17 @@ def test_at_cmd_ind(config_tsun_inv1, at_command_ind_msg): assert m.db.stat['proxy']['AT_Command'] == 1 assert m.db.stat['proxy']['AT_Command_Blocked'] == 0 assert m.db.stat['proxy']['Modbus_Command'] == 0 + + m.append_msg(at_command_rsp_msg) + m.read() # read at resp + assert m.control == 0x1510 + assert str(m.seq) == '03:03' + assert m.ifc.rx_get()==b'' + assert m.ifc.tx_fifo.get()==b'' + assert m.ifc.fwd_fifo.get()==at_command_rsp_msg + assert Proxy.mqtt.key == '' + assert Proxy.mqtt.data == "" + m.close() def test_at_cmd_ind_block(config_tsun_inv1, at_command_ind_msg_block): @@ -1630,6 +1652,7 @@ def test_at_cmd_ind_block(config_tsun_inv1, at_command_ind_msg_block): m.db.stat['proxy']['AT_Command'] = 0 m.db.stat['proxy']['AT_Command_Blocked'] = 0 m.db.stat['proxy']['Modbus_Command'] = 0 + m.inverter.forward_at_cmd_resp = False 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 @@ -1645,6 +1668,9 @@ def test_at_cmd_ind_block(config_tsun_inv1, at_command_ind_msg_block): assert m.db.stat['proxy']['AT_Command'] == 0 assert m.db.stat['proxy']['AT_Command_Blocked'] == 1 assert m.db.stat['proxy']['Modbus_Command'] == 0 + assert m.inverter.forward_at_cmd_resp == False + assert Proxy.mqtt.key == '' + assert Proxy.mqtt.data == "" m.close() def test_msg_at_command_rsp1(config_tsun_inv1, at_command_rsp_msg): @@ -1652,7 +1678,7 @@ def test_msg_at_command_rsp1(config_tsun_inv1, at_command_rsp_msg): m = MemoryStream(at_command_rsp_msg) m.db.stat['proxy']['Unknown_Ctrl'] = 0 m.db.stat['proxy']['Modbus_Command'] = 0 - m.forward_at_cmd_resp = True + m.inverter.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 @@ -1671,7 +1697,7 @@ def test_msg_at_command_rsp2(config_tsun_inv1, at_command_rsp_msg): m = MemoryStream(at_command_rsp_msg) m.db.stat['proxy']['Unknown_Ctrl'] = 0 m.db.stat['proxy']['Modbus_Command'] = 0 - m.forward_at_cmd_resp = False + m.inverter.forward_at_cmd_resp = False 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 @@ -1683,6 +1709,8 @@ def test_msg_at_command_rsp2(config_tsun_inv1, at_command_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 Proxy.mqtt.key == 'tsun/inv1/at_resp' + assert Proxy.mqtt.data == "+ok" m.close() def test_msg_at_command_rsp3(config_tsun_inv1, at_command_interim_rsp_msg): @@ -1692,7 +1720,7 @@ def test_msg_at_command_rsp3(config_tsun_inv1, at_command_interim_rsp_msg): 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.inverter.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 @@ -1706,6 +1734,8 @@ def test_msg_at_command_rsp3(config_tsun_inv1, at_command_interim_rsp_msg): 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 + assert Proxy.mqtt.key == '' + assert Proxy.mqtt.data == "" m.close() def test_msg_modbus_req(config_tsun_inv1, msg_modbus_cmd, msg_modbus_cmd_fwd): @@ -2218,4 +2248,100 @@ def test_timestamp(): m = MemoryStream(b'') ts = m._timestamp() ts_emu = m._emu_timestamp() - assert ts == ts_emu + 24*60*60 \ No newline at end of file + assert ts == ts_emu + 24*60*60 + +class MemoryStream2(MemoryStream): + def __init__(self, inverter, addr, ifc, + server_side: bool, client_mode: bool): + super().__init__(b'', inverter=inverter) + + +class InverterTest(InverterBase): + def __init__(self, reader: StreamReader, writer: StreamWriter, + client_mode: bool = False): + remote_prot = None + super().__init__(reader, writer, 'solarman', + MemoryStream2, client_mode, remote_prot) + + def forward(self, src, dst) -> None: + """forward handler transmits data over the remote connection""" + # dst.ifc.update_header_cb(src.fwd_fifo.peek()) + + dst.ifc.tx_add(src.ifc.fwd_fifo.get()) + + +@pytest.mark.asyncio +async def test_proxy_at_cmd(config_tsun_inv1, patch_open_connection, at_command_ind_msg, at_command_rsp_msg): + _ = config_tsun_inv1 + _ = patch_open_connection + assert asyncio.get_running_loop() + + with InverterTest(FakeReader(), FakeWriter(), client_mode=False) as inverter: + await inverter.create_remote() + await asyncio.sleep(0) + r = inverter.remote.stream + l = inverter.local.stream + + l.db.stat['proxy']['AT_Command'] = 0 + l.db.stat['proxy']['Unknown_Ctrl'] = 0 + l.db.stat['proxy']['AT_Command_Blocked'] = 0 + l.db.stat['proxy']['Modbus_Command'] = 0 + inverter.forward_at_cmd_resp = False + r.append_msg(at_command_ind_msg) + r.read() # read complete msg, and dispatch msg + assert inverter.forward_at_cmd_resp + inverter.forward(r,l) + + assert l.ifc.tx_fifo.get()==at_command_ind_msg + + assert l.db.stat['proxy']['Invalid_Msg_Format'] == 0 + assert l.db.stat['proxy']['AT_Command'] == 1 + assert l.db.stat['proxy']['AT_Command_Blocked'] == 0 + assert l.db.stat['proxy']['Modbus_Command'] == 0 + + l.append_msg(at_command_rsp_msg) + l.read() # read at resp + assert l.ifc.fwd_fifo.peek()==at_command_rsp_msg + inverter.forward(l,r) + assert r.ifc.tx_fifo.get()==at_command_rsp_msg + + assert Proxy.mqtt.key == '' + assert Proxy.mqtt.data == "" + +@pytest.mark.asyncio +async def test_proxy_at_blocked(config_tsun_inv1, patch_open_connection, at_command_ind_msg_block, at_command_rsp_msg): + _ = config_tsun_inv1 + _ = patch_open_connection + assert asyncio.get_running_loop() + + with InverterTest(FakeReader(), FakeWriter(), client_mode=False) as inverter: + await inverter.create_remote() + await asyncio.sleep(0) + r = inverter.remote.stream + l = inverter.local.stream + + l.db.stat['proxy']['AT_Command'] = 0 + l.db.stat['proxy']['Unknown_Ctrl'] = 0 + l.db.stat['proxy']['AT_Command_Blocked'] = 0 + l.db.stat['proxy']['Modbus_Command'] = 0 + inverter.forward_at_cmd_resp = False + r.append_msg(at_command_ind_msg_block) + r.read() # read complete msg, and dispatch msg + assert not inverter.forward_at_cmd_resp + inverter.forward(r,l) + + assert l.ifc.tx_fifo.get()==b'' + + assert l.db.stat['proxy']['Invalid_Msg_Format'] == 0 + assert l.db.stat['proxy']['AT_Command'] == 0 + assert l.db.stat['proxy']['AT_Command_Blocked'] == 1 + assert l.db.stat['proxy']['Modbus_Command'] == 0 + + l.append_msg(at_command_rsp_msg) + l.read() # read at resp + assert l.ifc.fwd_fifo.peek()==b'' + inverter.forward(l,r) + assert r.ifc.tx_fifo.get()==b'' + + assert Proxy.mqtt.key == 'tsun/inv1/at_resp' + assert Proxy.mqtt.data == "+ok" diff --git a/app/tests/test_solarman_emu.py b/app/tests/test_solarman_emu.py index 41e0e48..a62fbdc 100644 --- a/app/tests/test_solarman_emu.py +++ b/app/tests/test_solarman_emu.py @@ -6,7 +6,7 @@ from gen3plus.solarman_v5 import SolarmanV5, SolarmanBase from gen3plus.solarman_emu import SolarmanEmu from infos import Infos, Register -from test_solarman import FakeIfc, MemoryStream, get_sn_int, get_sn, correct_checksum, config_tsun_inv1, msg_modbus_rsp +from test_solarman import FakeIfc, FakeInverter, MemoryStream, get_sn_int, get_sn, correct_checksum, config_tsun_inv1, msg_modbus_rsp from test_infos_g3p import str_test_ip, bytes_test_ip timestamp = 0x3224c8bc @@ -19,10 +19,10 @@ class InvStream(MemoryStream): return timestamp class CldStream(SolarmanEmu): - def __init__(self, inv: InvStream): + def __init__(self, inv: InvStream, inverter=FakeInverter()): _ifc = FakeIfc() _ifc.remote.stream = inv - super().__init__(('test.local', 1234), _ifc, server_side=False, client_mode=False) + super().__init__(inverter, ('test.local', 1234), _ifc, server_side=False, client_mode=False) self.__msg = b'' self.__msg_len = 0 self.__offs = 0 diff --git a/app/tests/test_talent.py b/app/tests/test_talent.py index 2e7a4f0..225c38e 100644 --- a/app/tests/test_talent.py +++ b/app/tests/test_talent.py @@ -25,7 +25,7 @@ class FakeIfc(AsyncIfcImpl): class MemoryStream(Talent): def __init__(self, msg, chunks = (0,), server_side: bool = True): self.ifc = FakeIfc() - super().__init__(('test.local', 1234), self.ifc, server_side) + super().__init__(None, ('test.local', 1234), self.ifc, server_side) if server_side: self.mb.timeout = 0.4 # overwrite for faster testing self.mb_first_timeout = 0.5 @@ -2026,9 +2026,9 @@ def test_ctrl_byte(): def test_msg_iterator(): - m1 = Talent(('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True) - m2 = Talent(('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True) - m3 = Talent(('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True) + m1 = Talent(None, ('test1.local', 1234), ifc=AsyncIfcImpl(), server_side=True) + m2 = Talent(None, ('test2.local', 1234), ifc=AsyncIfcImpl(), server_side=True) + m3 = Talent(None, ('test3.local', 1234), ifc=AsyncIfcImpl(), server_side=True) m3.close() del m3 test1 = 0 From 3d073acc58dacc3c6ece2e02a2927a0fc4902dce Mon Sep 17 00:00:00 2001 From: Stefan Allius <122395479+s-allius@users.noreply.github.com> Date: Sat, 5 Apr 2025 22:30:57 +0200 Subject: [PATCH 05/10] 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 --- app/src/gen3plus/infos_g3p.py | 74 +++++++++++++------------ app/src/infos.py | 87 ++++++++++++++++-------------- app/src/modbus.py | 49 +++++++++-------- app/tests/test_infos_g3p.py | 32 +++++------ system_tests/test_tcp_socket_v2.py | 2 +- 5 files changed, 121 insertions(+), 123 deletions(-) diff --git a/app/src/gen3plus/infos_g3p.py b/app/src/gen3plus/infos_g3p.py index 4cbe08c..2eb1dfa 100644 --- a/app/src/gen3plus/infos_g3p.py +++ b/app/src/gen3plus/infos_g3p.py @@ -131,51 +131,49 @@ class RegisterMap: 0x4201000c: {'reg': Register.SENSOR_LIST, 'fmt': ' 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 - 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 + 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: E501H + 0x42010066: {'reg': Register.BATT_TEMP_1, 'fmt': '!h'}, # noqa: E501 Cell Temperture 1 + 0x42010068: {'reg': Register.BATT_TEMP_2, 'fmt': '!h'}, # noqa: E501 Cell Temperture 2 + 0x4201006a: {'reg': Register.BATT_TEMP_3, 'fmt': '!h'}, # noqa: E501 Cell Temperture 3 + 0x4201006c: {'reg': Register.BATT_OUT_VOLT, 'fmt': '!H', 'ratio': 0.01}, # noqa: E501 Output Voltage + 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) + 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 + 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 'calc': { - 1: {'reg': Register.BATT_PV_PWR, 'func': RegisterFunc.prod_sum, # noqa: E501 + 1: {'reg': Register.BATT_PV_PWR, 'func': RegisterFunc.prod_sum, # noqa: E501 Generated Power '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 + 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]]}, } } diff --git a/app/src/infos.py b/app/src/infos.py index 3bc6a4c..c69fb23 100644 --- a/app/src/infos.py +++ b/app/src/infos.py @@ -125,8 +125,7 @@ class Register(Enum): BATT_PV1_CUR = 1001 BATT_PV2_VOLT = 1002 BATT_PV2_CUR = 1003 - BATT_38 = 1004 - BATT_TOTAL_GEN = 1005 + BATT_TOTAL_CHARG = 1005 BATT_STATUS_1 = 1006 BATT_STATUS_2 = 1007 BATT_VOLT = 1010 @@ -156,8 +155,8 @@ class Register(Enum): BATT_OUT_STATUS = 1034 BATT_TEMP_4 = 1035 BATT_74 = 1036 - BATT_76 = 1037 - BATT_78 = 1038 + BATT_HW_VERS = 1037 + BATT_SW_VERS = 1038 BATT_PV_PWR = 1040 BATT_PWR = 1041 BATT_OUT_PWR = 1042 @@ -328,6 +327,7 @@ class Infos: UPDATE = 'mdi:update' DAILY_GEN = 'Daily Generation' TOTAL_GEN = 'Total Generation' + TOTAL_CHARG = 'Total Charging Energy' FMT_INT = '| int' FMT_FLOAT = '| float' FMT_STRING_SEC = '| string + " s"' @@ -367,7 +367,7 @@ class Infos: '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 + 'batterie': {'via': 'controller', 'name': 'Batterie', 'mdl': Register.EQUIPMENT_MODEL, 'mf': Register.MANUFACTURER, 'hw': Register.BATT_HW_VERS, 'sw': Register.BATT_SW_VERS, '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 } @@ -375,9 +375,9 @@ class Infos: __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 + __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 + __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 __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 @@ -636,46 +636,51 @@ 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 + # 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_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_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_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_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_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_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_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 + + # 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_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 + + # Batterie DC-1000: Batterie Pack + Register.BATT_VOLT: {'name': ['batterie', 'batt', 'Voltage'], 'level': logging.INFO, 'unit': 'V', 'ha': {'dev': 'batterie', 'dev_cla': 'voltage', 'stat_cla': 'measurement', 'id': 'volt_bat_', 'val_tpl': "{{ (value_json['batt']['Voltage'] | float)}}", 'name': 'Batterie Voltage', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # noqa: E501 + Register.BATT_CUR: {'name': ['batterie', 'batt', 'Current'], 'level': logging.INFO, 'unit': 'A', 'ha': {'dev': 'batterie', 'dev_cla': 'current', 'stat_cla': 'measurement', 'id': 'cur_bat_', 'val_tpl': "{{ (value_json['batt']['Current'] | float)}}", 'name': 'Batterie Current', 'icon': GAUGE, 'ent_cat': 'diagnostic'}}, # 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_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_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_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_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.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.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 diff --git a/app/src/modbus.py b/app/src/modbus.py index c63b545..c09f3fc 100644 --- a/app/src/modbus.py +++ b/app/src/modbus.py @@ -42,39 +42,38 @@ class Modbus(): 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 + 0x000c: {'reg': Register.BATT_TOTAL_CHARG, 'fmt': '!L', '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 + 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 + 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 + 0x002b: {'reg': Register.BATT_HW_VERS, 'fmt': '!h'}, # noqa: E501 + 0x002c: {'reg': Register.BATT_SW_VERS, 'fmt': '!h'}, # noqa: E501 0x2000: {'reg': Register.BOOT_STATUS, 'fmt': '!H'}, # noqa: E501 0x2001: {'reg': Register.DSP_STATUS, 'fmt': '!H'}, # noqa: E501 diff --git a/app/tests/test_infos_g3p.py b/app/tests/test_infos_g3p.py index 87271d8..1dbd203 100644 --- a/app/tests/test_infos_g3p.py +++ b/app/tests/test_infos_g3p.py @@ -155,15 +155,13 @@ def test_parse_4210_3026(batterie_data: bytes): 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, + "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": 51.34, "Current": -0.02, "SOC": 10.0, "Power": -1.0268000000000002}, + "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}, + "Controller_Temp": 15, "Reg_74": 0, "Hardware_Version": 517, "Software_Version": 513, + "PV_Power": 37.9232}, }) def test_parse_4210_3026_incomplete(batterie_data2: bytes): @@ -176,16 +174,14 @@ def test_parse_4210_3026_incomplete(batterie_data2: bytes): 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, + "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": 51.34, "Current": -0.02, "SOC": 10.0, "Power": -1.0268000000000002}, + "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}, - }) + "Controller_Temp": None, "Reg_74": None, "Hardware_Version": None, "Software_Version": None, + "PV_Power": 37.9232}, + }) def test_build_4210(inverter_data: bytes): i = InfosG3P(client_mode=False) @@ -359,7 +355,7 @@ def test_build_ha_conf5(): 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"}}) + assert d_json == json.dumps({"name": "Supply 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 diff --git a/system_tests/test_tcp_socket_v2.py b/system_tests/test_tcp_socket_v2.py index 3b5b0b3..f8903f8 100644 --- a/system_tests/test_tcp_socket_v2.py +++ b/system_tests/test_tcp_socket_v2.py @@ -6,7 +6,7 @@ from dotenv import load_dotenv load_dotenv() SOLARMAN_INV_SNR = os.getenv('SOLARMAN_INV_SNR', '00000080') -SOLARMAN_DCU_SNR = os.getenv('SOLARMAN_INV_SNR', '00000080') +SOLARMAN_DCU_SNR = os.getenv('SOLARMAN_DCU_SNR', '00000080') def get_sn() -> bytes: return bytes.fromhex(SOLARMAN_INV_SNR) From 7782a3cb5752b1ec83ab216dbc1d451666e5b240 Mon Sep 17 00:00:00 2001 From: Stefan Allius <122395479+s-allius@users.noreply.github.com> Date: Sun, 6 Apr 2025 01:21:41 +0200 Subject: [PATCH 06/10] 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 --- app/src/gen3plus/infos_g3p.py | 23 ++++++++++++++--- app/src/infos.py | 14 +++++++--- app/src/modbus.py | 4 +-- app/tests/test_infos_g3p.py | 41 ++++++++++++++++++++++++++---- system_tests/test_tcp_socket_v2.py | 4 +-- 5 files changed, 70 insertions(+), 16 deletions(-) diff --git a/app/src/gen3plus/infos_g3p.py b/app/src/gen3plus/infos_g3p.py index 2eb1dfa..987378c 100644 --- a/app/src/gen3plus/infos_g3p.py +++ b/app/src/gen3plus/infos_g3p.py @@ -19,6 +19,19 @@ class RegisterFunc: result += prod 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: # 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 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 - 0x4201003c: {'reg': Register.BATT_STATUS_1, '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) + 0x4201003c: {'reg': Register.BATT_PV1_STATUS, 'fmt': '!H'}, # noqa: E501 MPTT-1 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 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 @@ -173,8 +186,12 @@ class RegisterMap: [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 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]]}, + 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 } } diff --git a/app/src/infos.py b/app/src/infos.py index c69fb23..e7477a3 100644 --- a/app/src/infos.py +++ b/app/src/infos.py @@ -126,8 +126,8 @@ class Register(Enum): BATT_PV2_VOLT = 1002 BATT_PV2_CUR = 1003 BATT_TOTAL_CHARG = 1005 - BATT_STATUS_1 = 1006 - BATT_STATUS_2 = 1007 + BATT_PV1_STATUS = 1006 + BATT_PV2_STATUS = 1007 BATT_VOLT = 1010 BATT_CUR = 1011 BATT_SOC = 1012 @@ -160,6 +160,8 @@ class Register(Enum): BATT_PV_PWR = 1040 BATT_PWR = 1041 BATT_OUT_PWR = 1042 + BATT_PWR_SUPL_STATE = 1043 + BATT_STATUS = 1044 TEST_VAL_0 = 2000 TEST_VAL_1 = 2001 @@ -377,6 +379,8 @@ 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 __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 + __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'] %}{{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 = ''' @@ -637,10 +641,10 @@ class Infos: Register.INV_UNKNOWN_1: {'name': ['inv_unknown', 'Unknown_1'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 # 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_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_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 @@ -648,6 +652,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_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_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 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,6 +678,7 @@ 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_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_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_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 diff --git a/app/src/modbus.py b/app/src/modbus.py index c09f3fc..8991621 100644 --- a/app/src/modbus.py +++ b/app/src/modbus.py @@ -43,8 +43,8 @@ class Modbus(): 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_TOTAL_CHARG, 'fmt': '!L', '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 + 0x000e: {'reg': Register.BATT_PV1_STATUS, '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 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 diff --git a/app/tests/test_infos_g3p.py b/app/tests/test_infos_g3p.py index 1dbd203..a45d623 100644 --- a/app/tests/test_infos_g3p.py +++ b/app/tests/test_infos_g3p.py @@ -82,13 +82,25 @@ def batterie_data(): # 0x4210 ftype: 0x01 msg += b'\x00\x00\x00\x0f\x00\x00\x02\x05\x02\x01' 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 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'\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\x89\x0c\x88\x00\x0f\x00\x0f\x00\x0f\x00\x0e' return msg @@ -157,9 +169,28 @@ def test_parse_4210_3026(batterie_data: bytes): "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": 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}, - "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, + "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, "Reg_74": 0, "Hardware_Version": 517, "Software_Version": 513, "PV_Power": 37.9232}, }) @@ -176,9 +207,9 @@ def test_parse_4210_3026_incomplete(batterie_data2: bytes): "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": 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}, - "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, "PV_Power": 37.9232}, }) diff --git a/system_tests/test_tcp_socket_v2.py b/system_tests/test_tcp_socket_v2.py index f8903f8..faa0e63 100644 --- a/system_tests/test_tcp_socket_v2.py +++ b/system_tests/test_tcp_socket_v2.py @@ -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 += 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'\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\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 += correct_checksum(msg) msg += b'\x15' From 015b6b8db08b9561955af335ef12351e4b348564 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 6 Apr 2025 01:28:27 +0200 Subject: [PATCH 07/10] Update dependency pytest-cov to v6.1.1 (#346) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- app/requirements-test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/requirements-test.txt b/app/requirements-test.txt index dd4dfbd..36de7d0 100644 --- a/app/requirements-test.txt +++ b/app/requirements-test.txt @@ -1,7 +1,7 @@ flake8==7.2.0 pytest==8.3.5 pytest-asyncio==0.26.0 - pytest-cov==6.1.0 + pytest-cov==6.1.1 python-dotenv==1.1.0 mock==5.2.0 coverage==7.8.0 From af5604d0296db6b9d91bbf6ced33545df4ccd9c7 Mon Sep 17 00:00:00 2001 From: Stefan Allius <122395479+s-allius@users.noreply.github.com> Date: Sun, 6 Apr 2025 20:07:17 +0200 Subject: [PATCH 08/10] add alarm bitfields (#352) - fix bitfield of the inverter alarms - add batterie alarms --- app/src/gen3plus/infos_g3p.py | 6 +- app/src/infos.py | 135 +++++++++++++++++++++++++--------- app/src/modbus.py | 6 +- app/tests/test_infos_g3.py | 2 +- app/tests/test_infos_g3p.py | 6 +- 5 files changed, 109 insertions(+), 46 deletions(-) diff --git a/app/src/gen3plus/infos_g3p.py b/app/src/gen3plus/infos_g3p.py index 987378c..470c695 100644 --- a/app/src/gen3plus/infos_g3p.py +++ b/app/src/gen3plus/infos_g3p.py @@ -149,8 +149,8 @@ class RegisterMap: 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 0x42010038: {'reg': Register.BATT_TOTAL_CHARG, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 - 0x4201003c: {'reg': Register.BATT_PV1_STATUS, 'fmt': '!H'}, # noqa: E501 MPTT-1 Operating Status: 0(Standby), 1(Work) - 0x4201003e: {'reg': Register.BATT_PV2_STATUS, 'fmt': '!H'}, # noqa: E501 MPTT-2 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_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 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 @@ -177,7 +177,7 @@ class RegisterMap: 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) 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 0x42010078: {'reg': Register.BATT_SW_VERS, 'fmt': '!h'}, # noqa: E501 software main version 'calc': { diff --git a/app/src/infos.py b/app/src/infos.py index e7477a3..e0d4226 100644 --- a/app/src/infos.py +++ b/app/src/infos.py @@ -154,7 +154,7 @@ class Register(Enum): BATT_OUT_CUR = 1033 BATT_OUT_STATUS = 1034 BATT_TEMP_4 = 1035 - BATT_74 = 1036 + BATT_ALARM = 1036 BATT_HW_VERS = 1037 BATT_SW_VERS = 1038 BATT_PV_PWR = 1040 @@ -326,6 +326,7 @@ class Infos: SOLAR_POWER_VAR = 'mdi:solar-power-variant' SOLAR_POWER = 'mdi:solar-power' WIFI = 'mdi:wifi' + ALARM_LIGHT = 'mdi:alarm-light' UPDATE = 'mdi:update' DAILY_GEN = 'Daily Generation' TOTAL_GEN = 'Total Generation' @@ -403,52 +404,52 @@ class Infos: {% set result = 'noAlarm'%} {%else%} {% set result = '' %} - {% if val_int | bitwise_and(1)%} + {% if val_int | bitwise_and(0x0001)%} {% set result = result + 'HBridgeFault, '%} {% endif %} - {% if val_int | bitwise_and(2)%} + {% if val_int | bitwise_and(0x0002)%} {% set result = result + 'DriVoltageFault, '%} {% endif %} - {% if val_int | bitwise_and(3)%} + {% if val_int | bitwise_and(0x0004)%} {% set result = result + 'GFDI-Fault, '%} {% endif %} - {% if val_int | bitwise_and(4)%} + {% if val_int | bitwise_and(0x0008)%} {% set result = result + 'OverTemp, '%} {% endif %} - {% if val_int | bitwise_and(5)%} + {% if val_int | bitwise_and(0x0010)%} {% set result = result + 'CommLose, '%} {% endif %} - {% if val_int | bitwise_and(6)%} + {% if val_int | bitwise_and(0x0020)%} {% set result = result + 'Bit6, '%} {% endif %} - {% if val_int | bitwise_and(7)%} + {% if val_int | bitwise_and(0x0040)%} {% set result = result + 'Bit7, '%} {% endif %} - {% if val_int | bitwise_and(8)%} + {% if val_int | bitwise_and(0x0080)%} {% set result = result + 'EEPROM-Fault, '%} {% endif %} - {% if val_int | bitwise_and(9)%} + {% if val_int | bitwise_and(0x0100)%} {% set result = result + 'NoUtility, '%} {% endif %} - {% if val_int | bitwise_and(10)%} + {% if val_int | bitwise_and(0x0200)%} {% set result = result + 'VG_Offset, '%} {% endif %} - {% if val_int | bitwise_and(11)%} + {% if val_int | bitwise_and(0x0400)%} {% set result = result + 'Relais_Open, '%} {% endif %} - {% if val_int | bitwise_and(12)%} + {% if val_int | bitwise_and(0x0800)%} {% set result = result + 'Relais_Short, '%} {% endif %} - {% if val_int | bitwise_and(13)%} + {% if val_int | bitwise_and(0x1000)%} {% set result = result + 'GridVoltOverRating, '%} {% endif %} - {% if val_int | bitwise_and(14)%} + {% if val_int | bitwise_and(0x2000)%} {% set result = result + 'GridVoltUnderRating, '%} {% endif %} - {% if val_int | bitwise_and(15)%} + {% if val_int | bitwise_and(0x4000)%} {% set result = result + 'GridFreqOverRating, '%} {% endif %} - {% if val_int | bitwise_and(16)%} + {% if val_int | bitwise_and(0x8000)%} {% set result = result + 'GridFreqUnderRating, '%} {% endif %} {% endif %} @@ -465,42 +466,104 @@ class Infos: {% set result = 'noFault'%} {%else%} {% set result = '' %} - {% if val_int | bitwise_and(1)%} + {% if val_int | bitwise_and(0x0001)%} {% set result = result + 'PVOV-Fault (PV OverVolt), '%} {% endif %} - {% if val_int | bitwise_and(2)%} + {% if val_int | bitwise_and(0x0002)%} {% set result = result + 'PVLV-Fault (PV LowVolt), '%} {% endif %} - {% if val_int | bitwise_and(3)%} + {% if val_int | bitwise_and(0x0004)%} {% set result = result + 'PV OI-Fault (PV OverCurrent), '%} {% endif %} - {% if val_int | bitwise_and(4)%} + {% if val_int | bitwise_and(0x0008)%} {% set result = result + 'PV OFV-Fault, '%} {% endif %} - {% if val_int | bitwise_and(5)%} + {% if val_int | bitwise_and(0x0010)%} {% set result = result + 'DC ShortCircuitFault, '%} {% endif %} - {% if val_int | bitwise_and(6)%}{% set result = result + 'Bit6, '%} + {% if val_int | bitwise_and(0x0020)%}{% set result = result + 'Bit6, '%} {% endif %} - {% if val_int | bitwise_and(7)%}{% set result = result + 'Bit7, '%} + {% if val_int | bitwise_and(0x0040)%}{% set result = result + 'Bit7, '%} {% endif %} - {% if val_int | bitwise_and(8)%}{% set result = result + 'Bit8, '%} + {% if val_int | bitwise_and(0x0080)%}{% set result = result + 'Bit8, '%} {% endif %} - {% if val_int | bitwise_and(9)%}{% set result = result + 'Bit9, '%} + {% if val_int | bitwise_and(0x0100)%}{% set result = result + 'Bit9, '%} {% endif %} - {% if val_int | bitwise_and(10)%}{% set result = result + 'Bit10, '%} + {% if val_int | bitwise_and(0x0200)%}{% set result = result + 'Bit10, '%} {% endif %} - {% if val_int | bitwise_and(11)%}{% set result = result + 'Bit11, '%} + {% if val_int | bitwise_and(0x0400)%}{% set result = result + 'Bit11, '%} {% endif %} - {% if val_int | bitwise_and(12)%}{% set result = result + 'Bit12, '%} + {% if val_int | bitwise_and(0x0800)%}{% set result = result + 'Bit12, '%} {% endif %} - {% if val_int | bitwise_and(13)%}{% set result = result + 'Bit13, '%} + {% if val_int | bitwise_and(0x1000)%}{% set result = result + 'Bit13, '%} {% endif %} - {% if val_int | bitwise_and(14)%}{% set result = result + 'Bit14, '%} + {% if val_int | bitwise_and(0x2000)%}{% set result = result + 'Bit14, '%} {% endif %} - {% if val_int | bitwise_and(15)%}{% set result = result + 'Bit15, '%} + {% if val_int | bitwise_and(0x4000)%}{% set result = result + 'Bit15, '%} {% 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 %} {{ result }} @@ -566,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 # 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_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_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': ALARM_LIGHT}}, # 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_409: {'name': ['events', '409_No_Utility'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501 @@ -684,7 +747,7 @@ class Infos: 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_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_SW_VERS: {'name': ['batterie', 'Software_Version'], 'level': logging.INFO, 'unit': ''}, # noqa: E501 diff --git a/app/src/modbus.py b/app/src/modbus.py index 8991621..23ae92d 100644 --- a/app/src/modbus.py +++ b/app/src/modbus.py @@ -43,8 +43,8 @@ class Modbus(): 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_TOTAL_CHARG, 'fmt': '!L', 'ratio': 0.01}, # noqa: E501 - 0x000e: {'reg': Register.BATT_PV1_STATUS, 'fmt': '!H'}, # noqa: E501 - 0x000f: {'reg': Register.BATT_PV2_STATUS, 'fmt': '!H'}, # noqa: E501 + 0x000e: {'reg': Register.BATT_PV1_STATUS, '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 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 @@ -71,7 +71,7 @@ class Modbus(): 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 + 0x002a: {'reg': Register.BATT_ALARM, 'fmt': '!h'}, # noqa: E501 0x002b: {'reg': Register.BATT_HW_VERS, 'fmt': '!h'}, # noqa: E501 0x002c: {'reg': Register.BATT_SW_VERS, 'fmt': '!h'}, # noqa: E501 diff --git a/app/tests/test_infos_g3.py b/app/tests/test_infos_g3.py index d0425e8..f786cc9 100644 --- a/app/tests/test_infos_g3.py +++ b/app/tests/test_infos_g3.py @@ -221,7 +221,7 @@ def inv_data_seq2(): # Data indication from the controller return msg @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\x00\xc8\x53\x44\x00\x00\x00\x01\x2c\x53\x00\x00\x00\x00' # | ....SD....,S.... diff --git a/app/tests/test_infos_g3p.py b/app/tests/test_infos_g3p.py index a45d623..0596044 100644 --- a/app/tests/test_infos_g3p.py +++ b/app/tests/test_infos_g3p.py @@ -172,7 +172,7 @@ def test_parse_4210_3026(batterie_data: bytes): "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}, "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}, }) @@ -191,7 +191,7 @@ def test_parse_4210_3026_prod(batterie_data1: bytes): "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, "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}, }) @@ -210,7 +210,7 @@ def test_parse_4210_3026_incomplete(batterie_data2: bytes): "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}, "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}, }) From ea749dcce67bfcafe7f92879062e0be4c825c03b Mon Sep 17 00:00:00 2001 From: Stefan Allius <122395479+s-allius@users.noreply.github.com> Date: Sun, 6 Apr 2025 22:28:32 +0200 Subject: [PATCH 09/10] enforce numbered release candidates (#353) --- app/Makefile | 11 ++++++++++- ha_addons/Makefile | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/app/Makefile b/app/Makefile index fb96df2..dff4631 100644 --- a/app/Makefile +++ b/app/Makefile @@ -22,7 +22,16 @@ dev debug: export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \ 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 login at $(PUBLIC_URL) as $(PUBLIC_USER) @DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)" diff --git a/ha_addons/Makefile b/ha_addons/Makefile index ea9bc55..ab162c4 100644 --- a/ha_addons/Makefile +++ b/ha_addons/Makefile @@ -47,7 +47,16 @@ dev debug: local_add_on export IMAGE=$(PRIVAT_CONTAINER_REGISTRY)$(IMAGE) && \ 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 login at $(PUBLIC_URL) as $(PUBLIC_USER) @DO_LOGIN="$(shell echo $(PUBLIC_CR_KEY) | docker login $(PUBLIC_URL) -u $(PUBLIC_USER) --password-stdin)" From e0ba14ef75621f462f0b8d861f4468776622268f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Apr 2025 18:57:44 +0000 Subject: [PATCH 10/10] Update ghcr.io/hassio-addons/base Docker tag to v17.2.4 --- ha_addons/ha_addon/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ha_addons/ha_addon/Dockerfile b/ha_addons/ha_addon/Dockerfile index 9d3ecdb..1d9df4d 100755 --- a/ha_addons/ha_addon/Dockerfile +++ b/ha_addons/ha_addon/Dockerfile @@ -13,7 +13,7 @@ # 1 Build Base Image # ###################### -ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.3" +ARG BUILD_FROM="ghcr.io/hassio-addons/base:17.2.4" # hadolint ignore=DL3006 FROM $BUILD_FROM AS base