Merge branch 'dev-0.12' of https://github.com/s-allius/tsun-gen3-proxy into dev-0.12

This commit is contained in:
Stefan Allius
2024-12-22 20:33:46 +01:00
15 changed files with 274 additions and 104 deletions

View File

@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased] ## [unreleased]
- detect usage of a local DNS resolver [#37](https://github.com/s-allius/tsun-gen3-proxy/issues/37)
- path for logs is now configurable by cli args - path for logs is now configurable by cli args
- configure the number of keeped logfiles by cli args - configure the number of keeped logfiles by cli args
- add DOCS.md for add-ons - add DOCS.md for add-ons

View File

@@ -301,7 +301,7 @@ modbus_polling = true # Enable optional MODBUS polling
# if your inverter supports SSL connections you must use the client_mode. Pls, uncomment # if your inverter supports SSL connections you must use the client_mode. Pls, uncomment
# the next line and configure the fixed IP of your inverter # the next line and configure the fixed IP of your inverter
#client_mode = {host = '192.168.0.1', port = 8899} #client_mode = {host = '192.168.0.1', port = 8899, forward = true}
pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr

View File

@@ -149,7 +149,7 @@ modbus_polling = true # Enable optional MODBUS polling
# if your inverter supports SSL connections you must use the client_mode. Pls, uncomment # if your inverter supports SSL connections you must use the client_mode. Pls, uncomment
# the next line and configure the fixed IP of your inverter # the next line and configure the fixed IP of your inverter
#client_mode = {host = '192.168.0.1', port = 8899} #client_mode = {host = '192.168.0.1', port = 8899, forward = true}
pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr pv1 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr
pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr pv2 = {type = 'RSM40-8-410M', manufacturer = 'Risen'} # Optional, PV module descr

View File

@@ -189,6 +189,7 @@ here. The default config reader is handled in the Config.init method'''
cls.err = f'error: {error}' cls.err = f'error: {error}'
logging.error( logging.error(
f"Can't read from {reader.descr()} => error\n {error}") f"Can't read from {reader.descr()} => error\n {error}")
return cls.err
logging.info(f'Read from {reader.descr()} => {res}') logging.info(f'Read from {reader.descr()} => {res}')
return cls.err return cls.err

View File

@@ -22,4 +22,4 @@ class ConfigReadEnv(ConfigIfc):
return conf return conf
def descr(self): def descr(self):
return "Read environment" return "environment"

View File

@@ -6,6 +6,7 @@ import json
import gc import gc
from aiomqtt import MqttCodeError from aiomqtt import MqttCodeError
from asyncio import StreamReader, StreamWriter from asyncio import StreamReader, StreamWriter
from ipaddress import ip_address
from inverter_ifc import InverterIfc from inverter_ifc import InverterIfc
from proxy import Proxy from proxy import Proxy
@@ -101,6 +102,20 @@ class InverterBase(InverterIfc, Proxy):
logging.info(f'[{stream.node_id}] Connect to {addr}') logging.info(f'[{stream.node_id}] Connect to {addr}')
connect = asyncio.open_connection(host, port) connect = asyncio.open_connection(host, port)
reader, writer = await connect reader, writer = await connect
r_addr = writer.get_extra_info('peername')
if r_addr is not None:
(ip, _) = r_addr
if ip_address(ip).is_private:
logging.error(
f"""resolve {host} to {ip}, which is a private IP!
\u001B[31m Check your DNS settings and use a public DNS resolver!
To prevent a possible loop, forwarding to local IP addresses is
not supported and is deactivated for subsequent connections
\u001B[0m
""")
Config.act_config[self.config_id]['enabled'] = False
ifc = AsyncStreamClient( ifc = AsyncStreamClient(
reader, writer, self.local, self.__del_remote) reader, writer, self.local, self.__del_remote)

View File

@@ -49,7 +49,7 @@ class ModbusTcp():
and 'monitor_sn' in inv and 'monitor_sn' in inv
and 'client_mode' in inv): and 'client_mode' in inv):
client = inv['client_mode'] client = inv['client_mode']
# logging.info(f"SerialNo:{inv['monitor_sn']} host:{client['host']} port:{client['port']}") # noqa: E501 logger.info(f"'client_mode' for snr: {inv['monitor_sn']} host: {client['host']}:{client['port']}, forward: {client['forward']}") # noqa: E501
loop.create_task(self.modbus_loop(client['host'], loop.create_task(self.modbus_loop(client['host'],
client['port'], client['port'],
inv['monitor_sn'], inv['monitor_sn'],

View File

@@ -142,7 +142,7 @@ def main(): # pragma: no cover
parser.add_argument('-t', '--toml_config', type=str, parser.add_argument('-t', '--toml_config', type=str,
help='read user config from toml-file') help='read user config from toml-file')
parser.add_argument('-l', '--log_path', type=str, parser.add_argument('-l', '--log_path', type=str,
default='log/', default='./log/',
help='set path for the logging files') help='set path for the logging files')
parser.add_argument('-b', '--log_backups', type=int, parser.add_argument('-b', '--log_backups', type=int,
default=0, default=0,
@@ -159,11 +159,15 @@ def main(): # pragma: no cover
logging.config.fileConfig('logging.ini') logging.config.fileConfig('logging.ini')
logging.info(f'Server "{serv_name} - {version}" will be started') logging.info(f'Server "{serv_name} - {version}" will be started')
logging.info(f'current dir: {os.getcwd()}')
logging.info(f"config_path: {args.config_path}") logging.info(f"config_path: {args.config_path}")
logging.info(f"json_config: {args.json_config}") logging.info(f"json_config: {args.json_config}")
logging.info(f"toml_config: {args.toml_config}") logging.info(f"toml_config: {args.toml_config}")
logging.info(f"log_path: {args.log_path}") logging.info(f"log_path: {args.log_path}")
logging.info(f"log_backups: {args.log_backups} days") if args.log_backups == 0:
logging.info("log_backups: unlimited")
else:
logging.info(f"log_backups: {args.log_backups} days")
log_level = get_log_level() log_level = get_log_level()
logging.info('******') logging.info('******')
@@ -204,6 +208,7 @@ def main(): # pragma: no cover
# and we can't receive and handle the UNIX signals! # and we can't receive and handle the UNIX signals!
# #
for inv_class, port in [(InverterG3, 5005), (InverterG3P, 10000)]: for inv_class, port in [(InverterG3, 5005), (InverterG3P, 10000)]:
logging.info(f'listen on port: {port} for inverters')
loop.create_task(asyncio.start_server(lambda r, w, i=inv_class: loop.create_task(asyncio.start_server(lambda r, w, i=inv_class:
handle_client(r, w, i), handle_client(r, w, i),
'0.0.0.0', port)) '0.0.0.0', port))

View File

@@ -195,10 +195,10 @@ def test_cnv4():
"node_id": "PV-Garage/", "node_id": "PV-Garage/",
"suggested_area": "Garage", "suggested_area": "Garage",
"modbus_polling": False, "modbus_polling": False,
"pv1_manufacturer": "man1", "pv1.manufacturer": "man1",
"pv1_type": "type1", "pv1.type": "type1",
"pv2_manufacturer": "man2", "pv2.manufacturer": "man2",
"pv2_type": "type2", "pv2.type": "type2",
"sensor_list": 688 "sensor_list": 688
}, },
{ {
@@ -207,16 +207,17 @@ def test_cnv4():
"node_id": "PV-Garage2/", "node_id": "PV-Garage2/",
"suggested_area": "Garage2", "suggested_area": "Garage2",
"modbus_polling": True, "modbus_polling": True,
"client_mode_host": "InverterIP", "client_mode.host": "InverterIP",
"client_mode_port": 1234, "client_mode.port": 1234,
"pv1_manufacturer": "man1", "client_mode.forward": True,
"pv1_type": "type1", "pv1.manufacturer": "man1",
"pv2_manufacturer": "man2", "pv1.type": "type1",
"pv2_type": "type2", "pv2.manufacturer": "man2",
"pv3_manufacturer": "man3", "pv2.type": "type2",
"pv3_type": "type3", "pv3.manufacturer": "man3",
"pv4_manufacturer": "man4", "pv3.type": "type3",
"pv4_type": "type4", "pv4.manufacturer": "man4",
"pv4.type": "type4",
"sensor_list": 688 "sensor_list": 688
} }
], ],
@@ -247,25 +248,33 @@ def test_cnv4():
'block': ['AT+SUPDATE']}}}, 'block': ['AT+SUPDATE']}}},
'inverters': {'R170000000000001': {'modbus_polling': False, 'inverters': {'R170000000000001': {'modbus_polling': False,
'node_id': 'PV-Garage/', 'node_id': 'PV-Garage/',
'pv1_manufacturer': 'man1', 'pv1': {
'pv1_type': 'type1', 'manufacturer': 'man1',
'pv2_manufacturer': 'man2', 'type': 'type1'},
'pv2_type': 'type2', 'pv2': {
'manufacturer': 'man2',
'type': 'type2'},
'sensor_list': 688, 'sensor_list': 688,
'suggested_area': 'Garage'}, 'suggested_area': 'Garage'},
'Y170000000000001': {'client_mode_host': 'InverterIP', 'Y170000000000001': {'client_mode': {
'client_mode_port': 1234, 'host': 'InverterIP',
'port': 1234,
'forward': True},
'modbus_polling': True, 'modbus_polling': True,
'monitor_sn': 2000000000, 'monitor_sn': 2000000000,
'node_id': 'PV-Garage2/', 'node_id': 'PV-Garage2/',
'pv1_manufacturer': 'man1', 'pv1': {
'pv1_type': 'type1', 'manufacturer': 'man1',
'pv2_manufacturer': 'man2', 'type': 'type1'},
'pv2_type': 'type2', 'pv2': {
'pv3_manufacturer': 'man3', 'manufacturer': 'man2',
'pv3_type': 'type3', 'type': 'type2'},
'pv4_manufacturer': 'man4', 'pv3': {
'pv4_type': 'type4', 'manufacturer': 'man3',
'type': 'type3'},
'pv4': {
'manufacturer': 'man4',
'type': 'type4'},
'sensor_list': 688, 'sensor_list': 688,
'suggested_area': 'Garage2'}, 'suggested_area': 'Garage2'},
'allow_all': False}, 'allow_all': False},
@@ -362,8 +371,6 @@ def test_full_config(ConfigComplete):
"node_id": "PV-Garage2/", "node_id": "PV-Garage2/",
"suggested_area": "Garage2", "suggested_area": "Garage2",
"modbus_polling": true, "modbus_polling": true,
"client_mode_host": "InverterIP",
"client_mode_port": 1234,
"pv1.manufacturer": "man1", "pv1.manufacturer": "man1",
"pv1.type": "type1", "pv1.type": "type1",
"pv2.manufacturer": "man2", "pv2.manufacturer": "man2",

View File

@@ -54,11 +54,12 @@ class FakeReader():
class FakeWriter(): class FakeWriter():
peer = ('47.1.2.3', 10000)
def write(self, buf: bytes): def write(self, buf: bytes):
return return
def get_extra_info(self, sel: str): def get_extra_info(self, sel: str):
if sel == 'peername': if sel == 'peername':
return ('47.1.2.3', 10000) return self.peer
elif sel == 'sockname': elif sel == 'sockname':
return 'sock:1234' return 'sock:1234'
assert False assert False
@@ -241,6 +242,118 @@ async def test_remote_conn(config_conn, patch_open_connection):
cnt += 1 cnt += 1
assert cnt == 0 assert cnt == 0
@pytest.mark.asyncio
async def test_remote_conn_to_private(config_conn, patch_open_connection):
'''check DNS resolving of the TSUN FQDN to a local address'''
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
InverterBase._registry.clear()
reader = FakeReader()
writer = FakeWriter()
FakeWriter.peer = ("192.168.0.1", 10000)
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
await inverter.create_remote()
await asyncio.sleep(0)
assert not Config.act_config['tsun']['enabled']
assert inverter.remote.stream
assert inverter.remote.ifc
assert inverter.local.ifc.healthy()
# outside context manager the unhealth AsyncStream is released
FakeWriter.peer = ("47.1.2.3", 10000)
cnt = 0
for inv in InverterBase:
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
cnt += 1
del inv
assert cnt == 1
del inverter
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_remote_conn_to_loopback(config_conn, patch_open_connection):
'''check DNS resolving of the TSUN FQDN to the loopback address'''
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
InverterBase._registry.clear()
reader = FakeReader()
writer = FakeWriter()
FakeWriter.peer = ("127.0.0.1", 10000)
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
await inverter.create_remote()
await asyncio.sleep(0)
assert not Config.act_config['tsun']['enabled']
assert inverter.remote.stream
assert inverter.remote.ifc
assert inverter.local.ifc.healthy()
# outside context manager the unhealth AsyncStream is released
FakeWriter.peer = ("47.1.2.3", 10000)
cnt = 0
for inv in InverterBase:
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
cnt += 1
del inv
assert cnt == 1
del inverter
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio
async def test_remote_conn_to_None(config_conn, patch_open_connection):
'''check if get_extra_info() return None in case of an error'''
_ = config_conn
_ = patch_open_connection
assert asyncio.get_running_loop()
InverterBase._registry.clear()
reader = FakeReader()
writer = FakeWriter()
FakeWriter.peer = None
with InverterBase(reader, writer, 'tsun', Talent) as inverter:
assert inverter.local.stream
assert inverter.local.ifc
await inverter.create_remote()
await asyncio.sleep(0)
assert Config.act_config['tsun']['enabled']
assert inverter.remote.stream
assert inverter.remote.ifc
assert inverter.local.ifc.healthy()
# outside context manager the unhealth AsyncStream is released
FakeWriter.peer = ("47.1.2.3", 10000)
cnt = 0
for inv in InverterBase:
assert inv.healthy() # inverter is healthy again (without the unhealty AsyncStream)
cnt += 1
del inv
assert cnt == 1
del inverter
cnt = 0
for inv in InverterBase:
print(f'InverterBase refs:{gc.get_referrers(inv)}')
cnt += 1
assert cnt == 0
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_unhealthy_remote(config_conn, patch_open_connection, patch_unhealthy_remote): async def test_unhealthy_remote(config_conn, patch_open_connection, patch_unhealthy_remote):
_ = config_conn _ = config_conn

View File

@@ -105,15 +105,17 @@ FORCE : ;
INST=$(INST_BASE)/ha_addon_dev INST=$(INST_BASE)/ha_addon_dev
repro_files = DOCS.md icon.png logo.png translations/de.yaml translations/en.yaml repro_files = DOCS.md icon.png logo.png translations/de.yaml translations/en.yaml
repro_root = CHANGELOG.md
repro_templates = config.yaml repro_templates = config.yaml
repro_subdirs = translations repro_subdirs = translations
repro_vers = debug dev rel repro_vers = debug dev rel
repro_all_files := $(foreach dir,$(repro_vers), $(foreach file,$(repro_files),$(INST_BASE)/ha_addon_$(dir)/$(file))) repro_all_files := $(foreach dir,$(repro_vers), $(foreach file,$(repro_files),$(INST_BASE)/ha_addon_$(dir)/$(file)))
repro_root_files := $(foreach dir,$(repro_vers), $(foreach file,$(repro_root),$(INST_BASE)/ha_addon_$(dir)/$(file)))
repro_all_templates := $(foreach dir,$(repro_vers), $(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_$(dir)/$(file))) repro_all_templates := $(foreach dir,$(repro_vers), $(foreach file,$(repro_templates),$(INST_BASE)/ha_addon_$(dir)/$(file)))
repro_all_subdirs := $(foreach dir,$(repro_vers), $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_$(dir)/$(file))) repro_all_subdirs := $(foreach dir,$(repro_vers), $(foreach file,$(repro_subdirs),$(INST_BASE)/ha_addon_$(dir)/$(file)))
repro: $(repro_all_subdirs) $(repro_all_templates) $(repro_all_files) repro: $(repro_all_subdirs) $(repro_all_templates) $(repro_all_files) $(repro_root_files)
$(repro_all_subdirs) : $(repro_all_subdirs) :
mkdir -p $@ mkdir -p $@
@@ -121,6 +123,8 @@ $(repro_all_subdirs) :
$(repro_all_templates) : $(INST_BASE)/ha_addon_%/config.yaml: $(TEMPL)/config.jinja $(TEMPL)/%_data.json $(SRC)/.version $(repro_all_templates) : $(INST_BASE)/ha_addon_%/config.yaml: $(TEMPL)/config.jinja $(TEMPL)/%_data.json $(SRC)/.version
$(JINJA) --strict -D AppVersion=$(VERSION) $< $(filter %.json,$^) -o $@ $(JINJA) --strict -D AppVersion=$(VERSION) $< $(filter %.json,$^) -o $@
$(repro_root_files) : %/CHANGELOG.md : ../CHANGELOG.md
cp $< $@
$(filter $(INST_BASE)/ha_addon_debug/%,$(repro_all_files)) : $(INST_BASE)/ha_addon_debug/% : ha_addon/% $(filter $(INST_BASE)/ha_addon_debug/%,$(repro_all_files)) : $(INST_BASE)/ha_addon_debug/% : ha_addon/%
cp $< $@ cp $< $@

View File

@@ -71,6 +71,9 @@ inverters:
node_id: PV-Garage node_id: PV-Garage
suggested_area: Garage suggested_area: Garage
modbus_polling: true modbus_polling: true
client_mode.host: 192.168.x.x
client_mode.port: 8899
client_mode.forward: true
pv1.manufacturer: Shinefar pv1.manufacturer: Shinefar
pv1.type: SF-M18/144550 pv1.type: SF-M18/144550
pv2.manufacturer: Shinefar pv2.manufacturer: Shinefar
@@ -83,6 +86,8 @@ inverters:
**Note**: _This is just an example, you need to replace the values with your own!_ **Note**: _This is just an example, you need to replace the values with your own!_
more information about the configuration can be found in the [configuration details page][configdetails].
## MQTT settings ## MQTT settings
By default, this add-on requires no `mqtt` config from the user. **This is not an error!** By default, this add-on requires no `mqtt` config from the user. **This is not an error!**
@@ -154,3 +159,4 @@ SOFTWARE.
[AdGuard]: https://github.com/hassio-addons/addon-adguard-home [AdGuard]: https://github.com/hassio-addons/addon-adguard-home
[repository-badge]: https://img.shields.io/badge/Add%20repository%20to%20my-Home%20Assistant-41BDF5?logo=home-assistant&style=for-the-badge [repository-badge]: https://img.shields.io/badge/Add%20repository%20to%20my-Home%20Assistant-41BDF5?logo=home-assistant&style=for-the-badge
[repository-url]: https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fs-allius%2Fha-addons [repository-url]: https://my.home-assistant.io/redirect/supervisor_add_addon_repository/?repository_url=https%3A%2F%2Fgithub.com%2Fs-allius%2Fha-addons
[configdetails]: https://github.com/s-allius/tsun-gen3-proxy/wiki/Configuration-details

View File

@@ -5,83 +5,91 @@ configuration:
description: >+ description: >+
Für jeden Wechselrichter muss die Seriennummer des Wechselrichters einer MQTT Für jeden Wechselrichter muss die Seriennummer des Wechselrichters einer MQTT
Definition zugeordnet werden. Dazu wird der entsprechende Konfigurationsblock mit der Definition zugeordnet werden. Dazu wird der entsprechende Konfigurationsblock mit der
<16-stellige Seriennummer> gestartet, so dass alle nachfolgenden Parameter diesem 16-stellige Seriennummer gestartet, so dass alle nachfolgenden Parameter diesem
Wechselricher zugeordnet sind. Wechselrichter zugeordnet sind.
Weitere wechselrichterspezifische Parameter (z.B. Polling Mode) können im Weitere wechselrichterspezifische Parameter (z.B. Polling Mode) können im
Konfigurationsblock gesetzt werden. Konfigurationsblock gesetzt werden.
Die Seriennummer der GEN3 Wechselrichter beginnne mit `R17` und die der GEN3PLUS Die Seriennummer der GEN3 Wechselrichter beginnen mit `R17` und die der GEN3PLUS
Wechselrichter mir `Y17`oder `47`! Wechselrichter mir `Y17`oder `47`!
monitor_sn # Die GEN3PLUS "Monitoring SN:" Siehe Beispielkonfiguration im Dokumentations-Tab
node_id # MQTT-Ersatz für die Seriennummer des Wechselrichters
suggested_area # Vorgeschlagener Installationsbereich für Home-Assistant
modbus_polling # Optionale MODBUS-Polling deaktivieren
pv1 # Optional, PV Module Beschreibung
pv2 # Optional, PV Module Beschreibung
tsun.enabled: tsun.enabled:
name: Verbindung zur TSUN Cloud - nur für GEN3-Wechselrichter name: Verbindung zur TSUN Cloud - nur für GEN3-Wechselrichter
description: >- description: >+
switch on/off connection to the TSUN cloud Schaltet die Verbindung zur TSUN Cloud ein/aus.
This connection is only required if you want send data to the TSUN cloud Diese Verbindung ist erforderlich, wenn Sie Daten an die TSUN Cloud senden möchten,
eg. to use the TSUN APPs or receive firmware updates. z.B. um die TSUN-Apps zu nutzen oder Firmware-Updates zu erhalten.
on - normal proxy operation ein => normaler Proxy-Betrieb.
off - The Inverter become isolated from Internet aus => Der Wechselrichter wird vom Internet isoliert.
solarman.enabled: solarman.enabled:
name: Connection to Solarman Cloud - for GEN3PLUS inverter only name: Verbindung zur Solarman Cloud - nur für GEN3PLUS Wechselrichter
description: >- description: >+
switch on/off connection to the Solarman cloud Schaltet die Verbindung zur Solarman Cloud ein/aus.
This connection is only required if you want send data to the Solarman cloud Diese Verbindung ist erforderlich, wenn Sie Daten an die Solarman Cloud senden möchten,
eg. to use the Solarman APPs or receive firmware updates. z.B. um die Solarman Apps zu nutzen oder Firmware-Updates zu erhalten.
on - normal proxy operation ein => normaler Proxy-Betrieb.
off - The Inverter become isolated from Internet aus => Der Wechselrichter wird vom Internet isoliert.
inverters.allow_all: inverters.allow_all:
name: Allow all connections from all inverters name: Erlaube Verbindungen von sämtlichen Wechselrichtern
description: >- description: >-
The proxy only usually accepts connections from known inverters. Der Proxy akzeptiert normalerweise nur Verbindungen von konfigurierten Wechselrichtern.
Switch on for test purposes and unknown serial numbers. Schalten Sie dies für Testzwecke und unbekannte Seriennummern ein.
mqtt.host: mqtt.host:
name: MQTT Broker Host name: MQTT Broker Host
description: >- description: >-
Hostname or IP address of the MQTT broker. if not set, the addon will try to connect to the Home Assistant MQTT broker Hostname oder IP-Adresse des MQTT-Brokers. Wenn nicht gesetzt, versucht das Addon, eine Verbindung zum Home Assistant MQTT-Broker herzustellen.
mqtt.port: mqtt.port:
name: MQTT Broker Port name: MQTT Broker Port
description: >- description: >-
Port of the MQTT broker. if not set, the addon will try to connect to the Home Assistant MQTT broker Port des MQTT-Brokers. Wenn nicht gesetzt, versucht das Addon, eine Verbindung zum Home Assistant MQTT-Broker herzustellen.
mqtt.user: mqtt.user:
name: MQTT Broker User name: MQTT Broker Benutzer
description: >- description: >-
User for the MQTT broker. if not set, the addon will try to connect to the Home Assistant MQTT broker Benutzer für den MQTT-Broker. Wenn nicht gesetzt, versucht das Addon, eine Verbindung zum Home Assistant MQTT-Broker herzustellen.
mqtt.passwd: mqtt.passwd:
name: MQTT Broker Password name: MQTT Broker Passwort
description: >- description: >-
Password for the MQTT broker. if not set, the addon will try to connect to the Home Assistant MQTT broker Passwort für den MQTT-Broker. Wenn nicht gesetzt, versucht das Addon, eine Verbindung zum Home Assistant MQTT-Broker herzustellen.
ha.auto_conf_prefix: ha.auto_conf_prefix:
name: MQTT prefix for subscribing for homeassistant status updates name: MQTT-Präfix für das Abonnieren von Home Assistant-Statusaktualisierungen
ha.discovery_prefix: ha.discovery_prefix:
name: MQTT prefix for discovery topic name: MQTT-Präfix für das discovery topic
ha.entity_prefix: ha.entity_prefix:
name: MQTT topic prefix for publishing inverter values name: MQTT-Themenpräfix für die Veröffentlichung von Wechselrichterwerten
ha.proxy_node_id: ha.proxy_node_id:
name: MQTT node id, for the proxy_node_id name: MQTT-Knoten-ID für die proxy_node_id
ha.proxy_unique_id: ha.proxy_unique_id:
name: MQTT unique id, to identify a proxy instance name: MQTT-eindeutige ID zur Identifizierung einer Proxy-Instanz
tsun.host: tsun.host:
name: TSUN Cloud Host name: TSUN Cloud Host
description: >- description: >-
Hostname or IP address of the TSUN cloud. if not set, the addon will try to connect to the cloud default Hostname oder IP-Adresse der TSUN-Cloud. Wenn nicht gesetzt, versucht das Addon, eine Verbindung zur Cloud logger.talent-monitoring.com herzustellen.
on logger.talent-monitoring.com
solarman.host: solarman.host:
name: Solarman Cloud Host name: Solarman Cloud Host
description: >- description: >-
Hostname or IP address of the Solarman cloud. if not set, the addon will try to connect to the cloud default Hostname oder IP-Adresse der Solarman-Cloud. Wenn nicht gesetzt, versucht das Addon, eine Verbindung zur Cloud iot.talent-monitoring.com herzustellen.
on iot.talent-monitoring.com gen3plus.at_acl.tsun.allow:
name: TSUN GEN3PLUS ACL allow
description: >-
Liste erlaubter AT-Befehle für TSUN GEN3PLUS
gen3plus.at_acl.tsun.block:
name: TSUN GEN3 ACL block
description: >-
Liste blockierter AT-Befehle für TSUN GEN3PLUS
gen3plus.at_acl.mqtt.allow:
name: MQTT GEN3PLUS ACL allow
description: >-
Liste erlaubter MQTT-Befehle für GEN3PLUS
gen3plus.at_acl.mqtt.block:
name: MQTT GEN3PLUS ACL block
description: >-
Liste blockierter MQTT-Befehle für GEN3PLUS
network: network:
8127/tcp: x... 5005/tcp: listening Port für TSUN GEN3 Wechselrichter
5005/tcp: listening Port for TSUN GEN3 Devices 10000/tcp: listening Port für TSUN GEN3PLUS Wechselrichter
10000/tcp: listening Port for TSUN GEN3PLUS Devices

View File

@@ -5,42 +5,37 @@ configuration:
description: >+ description: >+
For each GEN3 inverter, the serial number of the inverter must be mapped to an MQTT For each GEN3 inverter, the serial number of the inverter must be mapped to an MQTT
definition. To do this, the corresponding configuration block is started with definition. To do this, the corresponding configuration block is started with
<16-digit serial number> so that all subsequent parameters are assigned 16-digit serial number so that all subsequent parameters are assigned
to this inverter. Further inverter-specific parameters (e.g. polling mode) can be set to this inverter. Further inverter-specific parameters (e.g. polling mode) can be set
in the configuration block in the configuration block
The serial numbers of all GEN3 inverters start with `R17` and that of the GEN3PLUS The serial numbers of all GEN3 inverters start with `R17` and that of the GEN3PLUS
inverters with Y17 or 47! inverters with Y17 or 47!
monitor_sn # The GEN3PLUS "Monitoring SN:" For reference see example configuration in Documentation Tab
node_id # MQTT replacement for inverters serial number
suggested_area # suggested installation area for home-assistant
modbus_polling # Disable optional MODBUS polling
pv1 # Optional, PV module descr
pv2 # Optional, PV module descr
tsun.enabled: tsun.enabled:
name: Connection to TSUN Cloud - for GEN3 inverter only name: Connection to TSUN Cloud - for GEN3 inverter only
description: >- description: >+
switch on/off connection to the TSUN cloud switch on/off connection to the TSUN cloud.
This connection is only required if you want send data to the TSUN cloud This connection is only required if you want send data to the TSUN cloud
eg. to use the TSUN APPs or receive firmware updates. eg. to use the TSUN APPs or receive firmware updates.
on - normal proxy operation on => normal proxy operation.
off - The Inverter become isolated from Internet off => The Inverter become isolated from Internet.
solarman.enabled: solarman.enabled:
name: Connection to Solarman Cloud - for GEN3PLUS inverter only name: Connection to Solarman Cloud - for GEN3PLUS inverter only
description: >- description: >+
switch on/off connection to the Solarman cloud switch on/off connection to the Solarman cloud.
This connection is only required if you want send data to the Solarman cloud This connection is only required if you want send data to the Solarman cloud
eg. to use the Solarman APPs or receive firmware updates. eg. to use the Solarman APPs or receive firmware updates.
on - normal proxy operation on => normal proxy operation.
off - The Inverter become isolated from Internet off => The Inverter become isolated from Internet
inverters.allow_all: inverters.allow_all:
name: Allow all connections from all inverters name: Allow all connections from all inverters
description: >- description: >-
The proxy only usually accepts connections from known inverters. The proxy only usually accepts connections from configured inverters.
Switch on for test purposes and unknown serial numbers. Switch on for test purposes and unknown serial numbers.
mqtt.host: mqtt.host:
name: MQTT Broker Host name: MQTT Broker Host
@@ -71,16 +66,30 @@ configuration:
tsun.host: tsun.host:
name: TSUN Cloud Host name: TSUN Cloud Host
description: >- description: >-
Hostname or IP address of the TSUN cloud. if not set, the addon will try to connect to the cloud default Hostname or IP address of the TSUN cloud. if not set, the addon will try to connect to the cloud
on logger.talent-monitoring.com on logger.talent-monitoring.com
solarman.host: solarman.host:
name: Solarman Cloud Host name: Solarman Cloud Host
description: >- description: >-
Hostname or IP address of the Solarman cloud. if not set, the addon will try to connect to the cloud default Hostname or IP address of the Solarman cloud. if not set, the addon will try to connect to the cloud
on iot.talent-monitoring.com on iot.talent-monitoring.com
gen3plus.at_acl.tsun.allow:
name: TSUN GEN3PLUS ACL allow
description: >-
List of allowed TSUN GEN3PLUS AT commands
gen3plus.at_acl.tsun.block:
name: TSUN GEN3 ACL block
description: >-
List of blocked TSUN GEN3PLUS AT commands
gen3plus.at_acl.mqtt.allow:
name: MQTT GEN3PLUS ACL allow
description: >-
List of allowed MQTT GEN3PLUS commands
gen3plus.at_acl.mqtt.block:
name: MQTT GEN3PLUS ACL block
description: >-
List of blocked MQTT GEN3PLUS commands
network: network:
8127/tcp: x...
5005/tcp: listening Port for TSUN GEN3 Devices 5005/tcp: listening Port for TSUN GEN3 Devices
10000/tcp: listening Port for TSUN GEN3PLUS Devices 10000/tcp: listening Port for TSUN GEN3PLUS Devices

View File

@@ -39,8 +39,9 @@ schema:
node_id: str node_id: str
suggested_area: str suggested_area: str
modbus_polling: bool modbus_polling: bool
client_mode_host: str? client_mode.host: str?
client_mode_port: int? client_mode.port: int?
client_mode.forward: bool?
#strings: # leider funktioniert es nicht die folgenden 3 parameter im schema aufzulisten. möglicherweise wird die verschachtelung nicht unterstützt. #strings: # leider funktioniert es nicht die folgenden 3 parameter im schema aufzulisten. möglicherweise wird die verschachtelung nicht unterstützt.
# - string: str # - string: str
# type: str # type: str