From a7815bcf65510f8a51f4a6e18dcbf5d37707849e Mon Sep 17 00:00:00 2001
From: Stefan Allius
Date: Sat, 16 Dec 2023 15:39:13 +0100
Subject: [PATCH 01/15] move Connect_Count into the diagnostic area
---
app/src/infos.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/infos.py b/app/src/infos.py
index 3dd35c4..2e83354 100644
--- a/app/src/infos.py
+++ b/app/src/infos.py
@@ -121,7 +121,7 @@ class Infos:
0x000c3500: {'name': ['controller', 'Signal_Strength'], 'level': logging.DEBUG, 'unit': '%', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'signal_', 'fmt': '| int', 'name': 'Signal Strength', 'icon': 'mdi:wifi'}}, # noqa: E501
0x000c96a8: {'name': ['controller', 'Power_On_Time'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': 'duration', 'stat_cla': 'measurement', 'id': 'power_on_time_', 'name': 'Power on Time', 'val_tpl': "{{ (value_json['Power_On_Time'] | float)}}", 'nat_prc': '3', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x000d0020: {'name': ['controller', 'Collect_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'data_collect_intval_', 'fmt': '| int', 'name': 'Data Collect Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501
- 0x000cfc38: {'name': ['controller', 'Connect_Count'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'connect_count_', 'fmt': '| int', 'name': 'Connect Count', 'icon': 'mdi:counter'}}, # noqa: E501
+ 0x000cfc38: {'name': ['controller', 'Connect_Count'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'connect_count_', 'fmt': '| int', 'name': 'Connect Count', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
0x000c7f38: {'name': ['controller', 'Communication_Type'], 'level': logging.DEBUG, 'unit': '', 'ha': {'dev': 'controller', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'comm_type_', 'name': 'Communication Type', 'val_tpl': __comm_type_val_tpl, 'icon': 'mdi:wifi'}}, # noqa: E501
# 0x000c7f38: {'name': ['controller', 'Communication_Type'], 'level': logging.DEBUG, 'unit': 's', 'new_value': 5}, # noqa: E501
0x000cf850: {'name': ['controller', 'Data_Up_Interval'], 'level': logging.DEBUG, 'unit': 's', 'ha': {'dev': 'controller', 'dev_cla': None, 'stat_cla': 'measurement', 'id': 'data_up_intval_', 'fmt': '| int', 'name': 'Data Up Interval', 'icon': 'mdi:update', 'ent_cat': 'diagnostic'}}, # noqa: E501
From 97079974f16d114c33cf6d75d4a3b413e1f0d4aa Mon Sep 17 00:00:00 2001
From: Stefan Allius
Date: Sun, 31 Dec 2023 16:47:53 +0100
Subject: [PATCH 02/15] add schedular for regular tasks
---
app/requirements.txt | 3 ++-
app/src/scheduler.py | 25 +++++++++++++++++++++++++
app/src/server.py | 4 ++++
3 files changed, 31 insertions(+), 1 deletion(-)
create mode 100644 app/src/scheduler.py
diff --git a/app/requirements.txt b/app/requirements.txt
index 5d62110..fd06d2f 100644
--- a/app/requirements.txt
+++ b/app/requirements.txt
@@ -1,2 +1,3 @@
aiomqtt==1.2.1
- schema==0.7.5
\ No newline at end of file
+ schema==0.7.5
+ aiocron==1.8
\ No newline at end of file
diff --git a/app/src/scheduler.py b/app/src/scheduler.py
new file mode 100644
index 0000000..f7e37be
--- /dev/null
+++ b/app/src/scheduler.py
@@ -0,0 +1,25 @@
+import logging
+from mqtt import Mqtt
+from aiocron import crontab
+
+
+class Schedule:
+ mqtt = None
+
+ @classmethod
+ def start(cls):
+ logging.info("Scheduler init")
+ cls.mqtt = Mqtt(None)
+ # json.dumps(i.db['total']) == json.dumps({'Daily_Generation': 0.0})
+ # json.dumps(i.db['input']) == json.dumps({"pv1": {"Daily_Generation": 0.0}, "pv2": {"Daily_Generation": 0.0}, "pv3": {"Daily_Generation": 0.0}, "pv4": {"Daily_Generation": 0.0}}) # noqa: E501
+
+ crontab('0 0 * * *', func=cls.atmidnight, start=True)
+
+ async def atmidnight():
+ logging.info("Scheduler is working")
+ # db = self.db.db
+ # if key in db and self.new_data[key]:
+ # data_json = json.dumps(db[key])
+ # node_id = self.node_id
+ # logger_mqtt.debug(f'{key}: {data_json}')
+ # await cls.mqtt.publish(f'{self.entity_prfx}{node_id}{key}', data_json) # noqa: E501
diff --git a/app/src/server.py b/app/src/server.py
index 1bbbcd1..6ec0b85 100644
--- a/app/src/server.py
+++ b/app/src/server.py
@@ -6,6 +6,7 @@ import os
from logging import config # noqa F401
from async_stream import AsyncStream
from inverter import Inverter
+from scheduler import Schedule
from config import Config
@@ -64,6 +65,7 @@ if __name__ == "__main__":
logging.getLogger('msg').setLevel(log_level)
logging.getLogger('conn').setLevel(log_level)
logging.getLogger('data').setLevel(log_level)
+ # logging.getLogger('mqtt').setLevel(log_level)
# read config file
Config.read()
@@ -72,6 +74,8 @@ if __name__ == "__main__":
asyncio.set_event_loop(loop)
Inverter.class_init()
+ Schedule.start()
+
#
# Register some UNIX Signal handler for a gracefully server shutdown
# on Docker restart and stop
From 8fc8a29be29964aaf6de40e40e8f023828a977b4 Mon Sep 17 00:00:00 2001
From: Stefan Allius
Date: Sat, 6 Apr 2024 00:04:25 +0200
Subject: [PATCH 03/15] clear daily energy production at midnight
---
app/proxy.svg | 379 ++++++++++++++++++-------------
app/proxy.yuml | 5 +
app/src/gen3/infos_g3.py | 2 +-
app/src/gen3/inverter_g3.py | 2 +
app/src/gen3plus/infos_g3p.py | 2 +-
app/src/gen3plus/inverter_g3p.py | 2 +
app/src/infos.py | 64 +++++-
app/src/scheduler.py | 23 +-
app/tests/test_infos.py | 24 +-
9 files changed, 327 insertions(+), 176 deletions(-)
diff --git a/app/proxy.svg b/app/proxy.svg
index 5d2d1d7..588835e 100644
--- a/app/proxy.svg
+++ b/app/proxy.svg
@@ -4,275 +4,340 @@
-
From 4d6813ae7c8d4ea8a7fcbb8bdab341159503c17c Mon Sep 17 00:00:00 2001
From: Stefan Allius
Date: Sun, 7 Apr 2024 10:57:17 +0200
Subject: [PATCH 10/15] - fix TSUN model names
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index cae371d..1225ecb 100644
--- a/README.md
+++ b/README.md
@@ -40,12 +40,12 @@ If you use a Pi-hole, you can also store the host entry in the Pi-hole.
## Features
-- supports TSUN GEN3 inverters: TSOL MS-300, MS-350, MS-400, MS-600, MS-700 and MS-800
-- support for TSUN GEN3 PLUS inverters since proxy version 0.6 (e.g. MS-2000)
+- supports TSUN GEN3 PLUS inverters: TSOL-MS2000, MS1800 and MS1600
+- supports TSUN GEN3 inverters: TSOL-MS800, MS700, MS600, MS400, MS350 and MS300
- `MQTT` support
- `Home-Assistant` auto-discovery support
- Self-sufficient island operation without internet (for TSUN GEN3 PLUS inverters in preparation)
-- non-root Docker Container
+- runs in a non-root Docker Container
## Home Assistant Screenshots
From 93b89062f5bafbbf5256184a2f72844e0858e45a Mon Sep 17 00:00:00 2001
From: Stefan Allius
Date: Sun, 7 Apr 2024 19:41:05 +0200
Subject: [PATCH 11/15] Read pv module details for HA from config file
---
CHANGELOG.md | 2 ++
app/config/default_config.toml | 10 +++++++++-
app/src/config.py | 26 +++++++++++++++++++++++++-
app/src/gen3/talent.py | 5 +++--
app/src/gen3plus/solarman_v5.py | 9 +++++----
app/src/infos.py | 30 ++++++++++++++++++++++++++++++
6 files changed, 74 insertions(+), 8 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d50288f..86483ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Prepare support of inverters with 6 MTPPs
- Clear `Daily Generation` values at midnigth
+- Read pv module details from config file and use it for the Home Assistant registration
+ see: [#43](https://github.com/s-allius/tsun-gen3-proxy/issues/43)
## [0.6.0] - 2024-04-02
diff --git a/app/config/default_config.toml b/app/config/default_config.toml
index fccc54b..050ca32 100644
--- a/app/config/default_config.toml
+++ b/app/config/default_config.toml
@@ -31,13 +31,21 @@ inverters.allow_all = true # allow inverters, even if we have no inverter mapp
[inverters."R170000000000001"]
#node_id = '' # Optional, MQTT replacement for inverters serial number
#suggested_area = '' # Optional, suggested installation area for home-assistant
+#pv1 = {type = 'RSM40-8-395M', manufacturer = 'Risen'}
+#pv2 = {type = 'RSM40-8-395M', manufacturer = 'Risen'}
#[inverters."R17xxxxxxxxxxxx2"]
#node_id = '' # Optional, MQTT replacement for inverters serial number
#suggested_area = '' # Optional, suggested installation area for home-assistant
+#pv1 = {type = 'RSM40-8-405M', manufacturer = 'Risen'}
+#pv2 = {type = 'RSM40-8-405M', manufacturer = 'Risen'}
[inverters."Y170000000000001"]
-#monitor_sn = 2000000000 # The "Monitoring SN:" can be found on a sticker enclosed with the inverter
+monitor_sn = 2000000000 # The "Monitoring SN:" can be found on a sticker enclosed with the inverter
#node_id = '' # Optional, MQTT replacement for inverters serial number
#suggested_area = '' # Optional, suggested installation place for home-assistant
+#pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'}
+#pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'}
+#pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'}
+#pv4 = {type = 'RSM40-8-410M', manufacturer = 'Risen'}
diff --git a/app/src/config.py b/app/src/config.py
index 3778e09..589bb6c 100644
--- a/app/src/config.py
+++ b/app/src/config.py
@@ -45,7 +45,31 @@ class Config():
if len(s) > 0 and
s[-1] != '/' else s)),
- Optional('suggested_area', default=""): Use(str)
+ Optional('suggested_area', default=""): Use(str),
+ Optional('pv1'): {
+ Optional('type'): Use(str),
+ Optional('manufacturer'): Use(str),
+ },
+ Optional('pv2'): {
+ Optional('type'): Use(str),
+ Optional('manufacturer'): Use(str),
+ },
+ Optional('pv3'): {
+ Optional('type'): Use(str),
+ Optional('manufacturer'): Use(str),
+ },
+ Optional('pv4'): {
+ Optional('type'): Use(str),
+ Optional('manufacturer'): Use(str),
+ },
+ Optional('pv5'): {
+ Optional('type'): Use(str),
+ Optional('manufacturer'): Use(str),
+ },
+ Optional('pv6'): {
+ Optional('type'): Use(str),
+ Optional('manufacturer'): Use(str),
+ }
}}
}, ignore_extra_keys=True
)
diff --git a/app/src/gen3/talent.py b/app/src/gen3/talent.py
index 0bc08a8..46302ac 100644
--- a/app/src/gen3/talent.py
+++ b/app/src/gen3/talent.py
@@ -59,7 +59,7 @@ class Talent(Message):
# deallocated by the garbage collector ==> we get a memory leak
self.switch.clear()
- def set_serial_no(self, serial_no: str):
+ def __set_serial_no(self, serial_no: str):
if self.unique_id == serial_no:
logger.debug(f'SerialNo: {serial_no}')
@@ -72,6 +72,7 @@ class Talent(Message):
self.node_id = inv['node_id']
self.sug_area = inv['suggested_area']
logger.debug(f'SerialNo {serial_no} allowed! area:{self.sug_area}') # noqa: E501
+ self.db.set_pv_module_details(inv)
else:
self.node_id = ''
self.sug_area = ''
@@ -95,7 +96,7 @@ class Talent(Message):
hex_dump_memory(logging.INFO, f'Received from {self.addr}:',
self._recv_buffer, self.header_len+self.data_len)
- self.set_serial_no(self.id_str.decode("utf-8"))
+ self.__set_serial_no(self.id_str.decode("utf-8"))
self.__dispatch_msg()
self.__flush_recv_msg()
return
diff --git a/app/src/gen3plus/solarman_v5.py b/app/src/gen3plus/solarman_v5.py
index c09512b..fc189b3 100644
--- a/app/src/gen3plus/solarman_v5.py
+++ b/app/src/gen3plus/solarman_v5.py
@@ -70,7 +70,7 @@ class SolarmanV5(Message):
# deallocated by the garbage collector ==> we get a memory leak
self.switch.clear()
- def set_serial_no(self, snr: int):
+ def __set_serial_no(self, snr: int):
serial_no = str(snr)
if self.unique_id == serial_no:
logger.debug(f'SerialNo: {serial_no}')
@@ -87,6 +87,7 @@ class SolarmanV5(Message):
self.node_id = inv['node_id']
self.sug_area = inv['suggested_area']
logger.debug(f'SerialNo {serial_no} allowed! area:{self.sug_area}') # noqa: E501
+ self.db.set_pv_module_details(inv)
if not found:
self.node_id = ''
@@ -112,7 +113,7 @@ class SolarmanV5(Message):
self._recv_buffer, self.header_len+self.data_len+2)
if self.__trailer_is_ok(self._recv_buffer, self.header_len
+ self.data_len + 2):
- self.set_serial_no(self.snr)
+ self.__set_serial_no(self.snr)
self.__dispatch_msg()
self.__flush_recv_msg()
return
@@ -352,9 +353,9 @@ class SolarmanV5(Message):
ftype = result[0] # always 2
valid = result[1] == 1 # status
ts = result[2]
- repeat = result[3] # always 60
+ set_hb = result[3] # always 60 or 120
logger.info(f'ftype:{ftype} accepted:{valid}'
- f' ts:{ts:08x} repeat:{repeat}s')
+ f' ts:{ts:08x} nextHeartbeat: {set_hb}s')
dt = datetime.fromtimestamp(ts)
logger.info(f'ts: {dt.strftime("%Y-%m-%d %H:%M:%S")}')
diff --git a/app/src/infos.py b/app/src/infos.py
index 21c121e..88e70c4 100644
--- a/app/src/infos.py
+++ b/app/src/infos.py
@@ -193,6 +193,19 @@ class Infos:
Register.MAX_DESIGNED_POWER: {'name': ['inverter', 'Max_Designed_Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'designed_power_', 'fmt': '| string + " W"', 'name': 'Max Designed Power', 'icon': 'mdi:lightning-bolt', 'ent_cat': 'diagnostic'}}, # noqa: E501
Register.RATED_POWER: {'name': ['inverter', 'Rated_Power'], 'level': logging.DEBUG, 'unit': 'W', 'ha': {'dev': 'inverter', 'dev_cla': None, 'stat_cla': None, 'id': 'rated_power_', 'fmt': '| string + " W"', 'name': 'Rated Power', 'icon': 'mdi:lightning-bolt', 'ent_cat': 'diagnostic'}}, # noqa: E501
+ Register.PV1_MANUFACTURER: {'name': ['inverter', 'PV1_Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV1_MODEL: {'name': ['inverter', 'PV1_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV2_MANUFACTURER: {'name': ['inverter', 'PV2_Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV2_MODEL: {'name': ['inverter', 'PV2_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV3_MANUFACTURER: {'name': ['inverter', 'PV3_Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV3_MODEL: {'name': ['inverter', 'PV3_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV4_MANUFACTURER: {'name': ['inverter', 'PV4_Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV4_MODEL: {'name': ['inverter', 'PV4_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV5_MANUFACTURER: {'name': ['inverter', 'PV5_Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV5_MODEL: {'name': ['inverter', 'PV5_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV6_MANUFACTURER: {'name': ['inverter', 'PV6_Manufacturer'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+ Register.PV6_MODEL: {'name': ['inverter', 'PV6_Model'], 'level': logging.DEBUG, 'unit': ''}, # noqa: E501
+
# proxy:
Register.INVERTER_CNT: {'name': ['proxy', 'Inverter_Cnt'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'inv_count_', 'fmt': '| int', 'name': 'Active Inverter Connections', 'icon': 'mdi:counter'}}, # noqa: E501
Register.UNKNOWN_SNR: {'name': ['proxy', 'Unknown_SNR'], 'singleton': True, 'ha': {'dev': 'proxy', 'comp': 'sensor', 'dev_cla': None, 'stat_cla': None, 'id': 'unknown_snr_', 'fmt': '| int', 'name': 'Unknown Serial No', 'icon': 'mdi:counter', 'ent_cat': 'diagnostic'}}, # noqa: E501
@@ -530,3 +543,20 @@ class Infos:
elif 'less_eq' in dep:
return not value <= dep['less_eq']
return True
+
+ def set_pv_module_details(self, inv: dict) -> None:
+ map = {'pv1': {'manufacturer': Register.PV1_MANUFACTURER, 'model': Register.PV1_MODEL}, # noqa: E501
+ 'pv2': {'manufacturer': Register.PV2_MANUFACTURER, 'model': Register.PV2_MODEL}, # noqa: E501
+ 'pv3': {'manufacturer': Register.PV3_MANUFACTURER, 'model': Register.PV3_MODEL}, # noqa: E501
+ 'pv4': {'manufacturer': Register.PV4_MANUFACTURER, 'model': Register.PV4_MODEL}, # noqa: E501
+ 'pv5': {'manufacturer': Register.PV5_MANUFACTURER, 'model': Register.PV5_MODEL}, # noqa: E501
+ 'pv6': {'manufacturer': Register.PV6_MANUFACTURER, 'model': Register.PV6_MODEL} # noqa: E501
+ }
+
+ for key, reg in map.items():
+ if key in inv:
+ if 'manufacturer' in inv[key]:
+ self.set_db_def_value(reg['manufacturer'],
+ inv[key]['manufacturer'])
+ if 'type' in inv[key]:
+ self.set_db_def_value(reg['model'], inv[key]['type'])
From 35bbfee80ae4ac8a54301b79b819c5b787c09cf3 Mon Sep 17 00:00:00 2001
From: Stefan Allius
Date: Sun, 7 Apr 2024 20:02:39 +0200
Subject: [PATCH 12/15] fix name of aiocron badge
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 1225ecb..f35de3c 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
-
+
From 9d395af9861897aa184dbd55bef34908c5d92db0 Mon Sep 17 00:00:00 2001
From: Stefan Allius
Date: Sun, 7 Apr 2024 20:52:07 +0200
Subject: [PATCH 13/15] add samples for pv module configurations
---
app/config/default_config.toml | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/app/config/default_config.toml b/app/config/default_config.toml
index 050ca32..cd95d75 100644
--- a/app/config/default_config.toml
+++ b/app/config/default_config.toml
@@ -31,21 +31,21 @@ inverters.allow_all = true # allow inverters, even if we have no inverter mapp
[inverters."R170000000000001"]
#node_id = '' # Optional, MQTT replacement for inverters serial number
#suggested_area = '' # Optional, suggested installation area for home-assistant
-#pv1 = {type = 'RSM40-8-395M', manufacturer = 'Risen'}
-#pv2 = {type = 'RSM40-8-395M', manufacturer = 'Risen'}
+#pv1 = {type = 'RSM40-8-395M', manufacturer = 'Risen'} # Optional, PV module descr
+#pv2 = {type = 'RSM40-8-395M', manufacturer = 'Risen'} # Optional, PV module descr
#[inverters."R17xxxxxxxxxxxx2"]
#node_id = '' # Optional, MQTT replacement for inverters serial number
#suggested_area = '' # Optional, suggested installation area for home-assistant
-#pv1 = {type = 'RSM40-8-405M', manufacturer = 'Risen'}
-#pv2 = {type = 'RSM40-8-405M', manufacturer = 'Risen'}
+#pv1 = {type = 'RSM40-8-405M', manufacturer = 'Risen'} # Optional, PV module descr
+#pv2 = {type = 'RSM40-8-405M', manufacturer = 'Risen'} # Optional, PV module descr
[inverters."Y170000000000001"]
monitor_sn = 2000000000 # The "Monitoring SN:" can be found on a sticker enclosed with the inverter
#node_id = '' # Optional, MQTT replacement for inverters serial number
#suggested_area = '' # Optional, suggested installation place for home-assistant
-#pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'}
-#pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'}
-#pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'}
-#pv4 = {type = 'RSM40-8-410M', manufacturer = 'Risen'}
+#pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
+#pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
+#pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
+#pv4 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
From 06b896d6e969af9b12766441574afdec05e7b7fb Mon Sep 17 00:00:00 2001
From: Stefan Allius
Date: Sun, 7 Apr 2024 20:52:48 +0200
Subject: [PATCH 14/15] add samples for pv module configurations
---
README.md | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/README.md b/README.md
index 538f7cf..3487ce8 100644
--- a/README.md
+++ b/README.md
@@ -127,15 +127,23 @@ inverters.allow_all = false # True: allow inverters, even if we have no invert
[inverters."R17xxxxxxxxxxxx1"]
node_id = 'inv1' # Optional, MQTT replacement for inverters serial number
suggested_area = 'roof' # Optional, suggested installation area for home-assistant
+pv1 = {type = 'RSM40-8-395M', manufacturer = 'Risen'} # Optional, PV module descr
+pv2 = {type = 'RSM40-8-395M', manufacturer = 'Risen'} # Optional, PV module descr
[inverters."R17xxxxxxxxxxxx2"]
node_id = 'inv2' # Optional, MQTT replacement for inverters serial number
suggested_area = 'balcony' # Optional, suggested installation area for home-assistant
+pv1 = {type = 'RSM40-8-405M', manufacturer = 'Risen'} # Optional, PV module descr
+pv2 = {type = 'RSM40-8-405M', manufacturer = 'Risen'} # Optional, PV module descr
[inverters."Y17xxxxxxxxxxxx1"]
monitor_sn = 2000000000 # The "Monitoring SN:" can be found on a sticker enclosed with the inverter
node_id = 'inv_3' # MQTT replacement for inverters serial number
suggested_area = 'garage' # suggested installation place for home-assistant
+pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
+pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
+pv3 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
+pv4 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
```
From 97da24c8397e13878e464f2a57763209cd199890 Mon Sep 17 00:00:00 2001
From: Stefan Allius
Date: Sun, 7 Apr 2024 22:44:53 +0200
Subject: [PATCH 15/15] add missing tests
---
app/src/infos.py | 2 +-
app/tests/test_infos.py | 46 ++++++++++++++++++++++++++++++++++++++++-
2 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/app/src/infos.py b/app/src/infos.py
index 88e70c4..5fa0697 100644
--- a/app/src/infos.py
+++ b/app/src/infos.py
@@ -123,7 +123,7 @@ class ClrAtMidnight:
dict = dict[prfx]
for key in keys[1:-1]:
- if key not in dict: # pragma: no cover
+ if key not in dict:
dict[key] = {}
dict = dict[key]
dict[keys[-1]] = 0
diff --git a/app/tests/test_infos.py b/app/tests/test_infos.py
index f799456..8bf419d 100644
--- a/app/tests/test_infos.py
+++ b/app/tests/test_infos.py
@@ -530,12 +530,18 @@ def test_clr_at_midnight():
i.set_db_def_value(Register.NO_INPUTS, 2)
val = i.dev_value(Register.NO_INPUTS) # valid addr but not initiliazed
assert val == 2
-
+ i.info_defs[Register.TEST_REG1] = { # add a entry with incomplete ha definition
+ 'name': ['test', 'grp', 'REG_1'], 'ha': {'dev_cla': None }
+ }
i.reg_clr_at_midnight('tsun/inv_1/')
# tsun/inv_2/input
assert json.dumps(ClrAtMidnight.db['tsun/inv_1/total']) == json.dumps({'Daily_Generation': 0})
assert json.dumps(ClrAtMidnight.db['tsun/inv_1/input']) == json.dumps({"pv1": {"Daily_Generation": 0}, "pv2": {"Daily_Generation": 0}})
+ i.reg_clr_at_midnight('tsun/inv_1/')
+ assert json.dumps(ClrAtMidnight.db['tsun/inv_1/total']) == json.dumps({'Daily_Generation': 0})
+ assert json.dumps(ClrAtMidnight.db['tsun/inv_1/input']) == json.dumps({"pv1": {"Daily_Generation": 0}, "pv2": {"Daily_Generation": 0}})
+
test = 0
for key, data in ClrAtMidnight.elm():
if key == 'tsun/inv_1/total':
@@ -546,3 +552,41 @@ def test_clr_at_midnight():
test += 1
assert test == 2
assert json.dumps(ClrAtMidnight.db) == json.dumps({})
+
+ i.reg_clr_at_midnight('tsun/inv_1/')
+
+
+
+
+def test_pv_module_config():
+ i = InfosG3()
+ # i.set_db_def_value(Register.NO_INPUTS, 2)
+
+ dt = {
+ 'pv1':{'manufacturer':'TSUN1','type': 'Module 100W'},
+ 'pv2':{'manufacturer':'TSUN2'},
+ 'pv3':{'manufacturer':'TSUN3','type': 'Module 300W'},
+ 'pv4':{'type': 'Module 400W'},
+ 'pv5':{},
+ }
+ i.set_pv_module_details(dt)
+ assert 'TSUN1' == i.dev_value(Register.PV1_MANUFACTURER)
+ assert 'TSUN2' == i.dev_value(Register.PV2_MANUFACTURER)
+ assert 'TSUN3' == i.dev_value(Register.PV3_MANUFACTURER)
+ assert None == i.dev_value(Register.PV4_MANUFACTURER)
+ assert None == i.dev_value(Register.PV5_MANUFACTURER)
+ assert 'Module 100W' == i.dev_value(Register.PV1_MODEL)
+ assert None == i.dev_value(Register.PV2_MODEL)
+ assert 'Module 300W' == i.dev_value(Register.PV3_MODEL)
+ assert 'Module 400W' == i.dev_value(Register.PV4_MODEL)
+ assert None == i.dev_value(Register.PV5_MODEL)
+
+def test_broken_info_defs():
+ i = InfosG3()
+ val = i.get_db_value(Register.NO_INPUTS, 666)
+ assert val == 666
+ i.info_defs[Register.TEST_REG1] = 'test' # add a string instead of a dict
+ val = i.get_db_value(Register.TEST_REG1, 666)
+ assert val == 666
+ i.set_db_def_value(Register.TEST_REG1, 2)
+