From baea9f5e1b65980a0cdcd206066bc6bd9312120e Mon Sep 17 00:00:00 2001 From: David Rapan Date: Wed, 7 Aug 2024 17:38:46 +0200 Subject: [PATCH] feat: Experimental testing of ethernet stick --- custom_components/solarman/__init__.py | 5 +++- custom_components/solarman/api.py | 23 +++++++++++++++++-- custom_components/solarman/config_flow.py | 7 +++--- custom_components/solarman/const.py | 2 ++ .../solarman/translations/cs.json | 2 ++ .../solarman/translations/de.json | 2 ++ .../solarman/translations/en.json | 2 ++ .../solarman/translations/it.json | 2 ++ .../solarman/translations/pl.json | 2 ++ .../solarman/translations/pt-BR.json | 2 ++ 10 files changed, 43 insertions(+), 6 deletions(-) diff --git a/custom_components/solarman/__init__.py b/custom_components/solarman/__init__.py index da68ec6..a438738 100644 --- a/custom_components/solarman/__init__.py +++ b/custom_components/solarman/__init__.py @@ -32,6 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: inverter_serial = options.get(CONF_INVERTER_SERIAL) inverter_port = options.get(CONF_INVERTER_PORT) inverter_mb_slave_id = options.get(CONF_INVERTER_MB_SLAVE_ID) + inverter_passthrough = options.get(CONF_PASSTHROUGH) inverter_mac = None lookup_path = hass.config.path(LOOKUP_DIRECTORY_PATH) @@ -61,10 +62,12 @@ async def async_setup_entry(hass: HomeAssistant, config: ConfigEntry) -> bool: raise vol.Invalid("Configuration parameter [inverter_port] does not have a value") if not inverter_mb_slave_id: inverter_mb_slave_id = DEFAULT_INVERTER_MB_SLAVE_ID + if not inverter_passthrough: + inverter_passthrough = False if lookup_file is None: raise vol.Invalid("Configuration parameter [lookup_file] does not have a value") - inverter = Inverter(inverter_host, inverter_serial, inverter_port, inverter_mb_slave_id) + inverter = Inverter(inverter_host, inverter_serial, inverter_port, inverter_mb_slave_id, inverter_passthrough) await inverter.load(name, inverter_mac, lookup_path, lookup_file) diff --git a/custom_components/solarman/api.py b/custom_components/solarman/api.py index 1ef5075..689e62a 100644 --- a/custom_components/solarman/api.py +++ b/custom_components/solarman/api.py @@ -19,9 +19,28 @@ _LOGGER = logging.getLogger(__name__) -class Inverter(PySolarmanV5Async): - def __init__(self, address, serial, port, mb_slave_id): +class PySolarmanV5AsyncWrapper(PySolarmanV5Async): + def __init__(self, address, serial, port, mb_slave_id, passthrough): super().__init__(address, serial, port = port, mb_slave_id = mb_slave_id, logger = _LOGGER, auto_reconnect = AUTO_RECONNECT, socket_timeout = TIMINGS_SOCKET_TIMEOUT) + self._passthrough = passthrough + + def _received_frame_is_valid(self, frame): + return super()._received_frame_is_valid(frame) if not self._passthrough else True + + def _v5_frame_decoder(self, v5_frame): + if not self._passthrough: + return super()._v5_frame_decoder(v5_frame) + + modbus_frame = v5_frame[10:] + + if len(modbus_frame) < 5: + raise V5FrameError("V5 frame does not contain a valid Modbus RTU frame") + + return modbus_frame + +class Inverter(PySolarmanV5AsyncWrapper): + def __init__(self, address, serial, port, mb_slave_id, passthrough): + super().__init__(address, serial, port, mb_slave_id, passthrough) self._is_reading = 0 self.status_updated = datetime.now() self.status_interval = 0 diff --git a/custom_components/solarman/config_flow.py b/custom_components/solarman/config_flow.py index 9e5b895..8218a63 100644 --- a/custom_components/solarman/config_flow.py +++ b/custom_components/solarman/config_flow.py @@ -30,13 +30,13 @@ async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None def step_user_data_prefill(): _LOGGER.debug(f"step_user_data_process") - return { CONF_NAME: DEFAULT_NAME, CONF_DISCOVERY: DEFAULT_DISCOVERY, CONF_INVERTER_HOST: "", CONF_INVERTER_SERIAL: 0, CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVE_ID: DEFAULT_INVERTER_MB_SLAVE_ID, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE, CONF_BATTERY_NOMINAL_VOLTAGE: DEFAULT_BATTERY_NOMINAL_VOLTAGE, CONF_BATTERY_LIFE_CYCLE_RATING: DEFAULT_BATTERY_LIFE_CYCLE_RATING } + return { CONF_NAME: DEFAULT_NAME, CONF_DISCOVERY: DEFAULT_DISCOVERY, CONF_INVERTER_HOST: "", CONF_INVERTER_SERIAL: 0, CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVE_ID: DEFAULT_INVERTER_MB_SLAVE_ID, CONF_PASSTHROUGH: DEFAULT_PASSTHROUGH, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE, CONF_BATTERY_NOMINAL_VOLTAGE: DEFAULT_BATTERY_NOMINAL_VOLTAGE, CONF_BATTERY_LIFE_CYCLE_RATING: DEFAULT_BATTERY_LIFE_CYCLE_RATING } async def step_user_data_process(discovery): _LOGGER.debug(f"step_user_data_process: discovery: {discovery}") - return { CONF_NAME: DEFAULT_NAME, CONF_DISCOVERY: DEFAULT_DISCOVERY, CONF_INVERTER_HOST: await discovery.discover_ip(), CONF_INVERTER_SERIAL: await discovery.discover_serial(), CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVE_ID: DEFAULT_INVERTER_MB_SLAVE_ID, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE, CONF_BATTERY_NOMINAL_VOLTAGE: DEFAULT_BATTERY_NOMINAL_VOLTAGE, CONF_BATTERY_LIFE_CYCLE_RATING: DEFAULT_BATTERY_LIFE_CYCLE_RATING } + return { CONF_NAME: DEFAULT_NAME, CONF_DISCOVERY: DEFAULT_DISCOVERY, CONF_INVERTER_HOST: await discovery.discover_ip(), CONF_INVERTER_SERIAL: await discovery.discover_serial(), CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVE_ID: DEFAULT_INVERTER_MB_SLAVE_ID, CONF_PASSTHROUGH: DEFAULT_PASSTHROUGH, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE, CONF_BATTERY_NOMINAL_VOLTAGE: DEFAULT_BATTERY_NOMINAL_VOLTAGE, CONF_BATTERY_LIFE_CYCLE_RATING: DEFAULT_BATTERY_LIFE_CYCLE_RATING } -async def step_user_data_schema(hass: HomeAssistant, data: dict[str, Any] = { CONF_NAME: DEFAULT_NAME, CONF_DISCOVERY: DEFAULT_DISCOVERY, CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVE_ID: DEFAULT_INVERTER_MB_SLAVE_ID, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE, CONF_BATTERY_NOMINAL_VOLTAGE: DEFAULT_BATTERY_NOMINAL_VOLTAGE, CONF_BATTERY_LIFE_CYCLE_RATING: DEFAULT_BATTERY_LIFE_CYCLE_RATING }, wname: bool = True) -> vol.Schema: +async def step_user_data_schema(hass: HomeAssistant, data: dict[str, Any] = { CONF_NAME: DEFAULT_NAME, CONF_DISCOVERY: DEFAULT_DISCOVERY, CONF_INVERTER_PORT: DEFAULT_PORT_INVERTER, CONF_INVERTER_MB_SLAVE_ID: DEFAULT_INVERTER_MB_SLAVE_ID, CONF_PASSTHROUGH: DEFAULT_PASSTHROUGH, CONF_LOOKUP_FILE: DEFAULT_LOOKUP_FILE, CONF_BATTERY_NOMINAL_VOLTAGE: DEFAULT_BATTERY_NOMINAL_VOLTAGE, CONF_BATTERY_LIFE_CYCLE_RATING: DEFAULT_BATTERY_LIFE_CYCLE_RATING }, wname: bool = True) -> vol.Schema: lookup_files = await async_listdir(hass.config.path(LOOKUP_DIRECTORY_PATH)) + await async_listdir(hass.config.path(LOOKUP_CUSTOM_DIRECTORY_PATH), "custom/") _LOGGER.debug(f"step_user_data_schema: data: {data}, {LOOKUP_DIRECTORY_PATH}: {lookup_files}") #STEP_USER_DATA_SCHEMA = vol.Schema({ vol.Required(CONF_NAME, default = data.get(CONF_NAME)): str }, extra = vol.PREVENT_EXTRA) if wname else vol.Schema({}, extra = vol.PREVENT_EXTRA) @@ -49,6 +49,7 @@ async def step_user_data_schema(hass: HomeAssistant, data: dict[str, Any] = { CO vol.Required(CONF_INVERTER_SERIAL, default = data.get(CONF_INVERTER_SERIAL)): int, vol.Optional(CONF_INVERTER_PORT, default = data.get(CONF_INVERTER_PORT)): int, vol.Optional(CONF_INVERTER_MB_SLAVE_ID, default = data.get(CONF_INVERTER_MB_SLAVE_ID)): int, + vol.Optional(CONF_PASSTHROUGH, default = data.get(CONF_PASSTHROUGH)): bool, vol.Optional(CONF_LOOKUP_FILE, default = data.get(CONF_LOOKUP_FILE)): vol.In(lookup_files), vol.Optional(CONF_BATTERY_NOMINAL_VOLTAGE, default = data.get(CONF_BATTERY_NOMINAL_VOLTAGE)): int, vol.Optional(CONF_BATTERY_LIFE_CYCLE_RATING, default = data.get(CONF_BATTERY_LIFE_CYCLE_RATING)): int, diff --git a/custom_components/solarman/const.py b/custom_components/solarman/const.py index 128fdb6..fdd4d28 100644 --- a/custom_components/solarman/const.py +++ b/custom_components/solarman/const.py @@ -24,6 +24,7 @@ CONF_INVERTER_SERIAL = "inverter_serial" CONF_INVERTER_PORT = "inverter_port" CONF_INVERTER_MB_SLAVE_ID = "inverter_mb_slave_id" +CONF_PASSTHROUGH = "inverter_passthrough" CONF_LOOKUP_FILE = "lookup_file" CONF_BATTERY_NOMINAL_VOLTAGE = "battery_nominal_voltage" CONF_BATTERY_LIFE_CYCLE_RATING = "battery_life_cycle_rating" @@ -32,6 +33,7 @@ DEFAULT_DISCOVERY = True DEFAULT_PORT_INVERTER = 8899 DEFAULT_INVERTER_MB_SLAVE_ID = 1 +DEFAULT_PASSTHROUGH = False DEFAULT_LOOKUP_FILE = "deye_hybrid.yaml" DEFAULT_BATTERY_NOMINAL_VOLTAGE = 48 DEFAULT_BATTERY_LIFE_CYCLE_RATING = 6000 diff --git a/custom_components/solarman/translations/cs.json b/custom_components/solarman/translations/cs.json index 290dce5..7cebb66 100644 --- a/custom_components/solarman/translations/cs.json +++ b/custom_components/solarman/translations/cs.json @@ -18,6 +18,7 @@ "inverter_serial": "Sériové číslo zařízení (z webového rozhraní)", "inverter_port": "Port", "inverter_mb_slave_id": "Modbus Slave ID (obvykle 1)", + "inverter_passthrough": "Solarman protokol passthrough mód", "lookup_file": "Soubor yaml obsahující definice parametrů měniče", "battery_nominal_voltage": "Jmenovité napětí lithium-iontové baterie", "battery_life_cycle_rating": "Předpokládaná životnost lithium-iontové baterie" @@ -46,6 +47,7 @@ "inverter_serial": "Sériové číslo zařízení (z webového rozhraní)", "inverter_port": "Port", "inverter_mb_slave_id": "Modbus Slave ID (obvykle 1)", + "inverter_passthrough": "Solarman protokol passthrough mód", "lookup_file": "Soubor yaml obsahující definice parametrů měniče", "battery_nominal_voltage": "Jmenovité napětí lithium-iontové baterie", "battery_life_cycle_rating": "Předpokládaná životnost lithium-iontové baterie" diff --git a/custom_components/solarman/translations/de.json b/custom_components/solarman/translations/de.json index 00a048b..2a8b996 100644 --- a/custom_components/solarman/translations/de.json +++ b/custom_components/solarman/translations/de.json @@ -18,6 +18,7 @@ "inverter_serial": "Seriennummer des Geräts (über die Weboberfläche abrufbar)", "inverter_port": "Port (normalerweise 8899)", "inverter_mb_slave_id": "Modbus-Slave-ID (normalerweise 1)", + "inverter_passthrough": "Solarman Protokoll-Passthrough Modus", "lookup_file": "YAML-Datei mit der Parameter-Definition", "battery_nominal_voltage": "Nennspannung des Lithium-Ionen-Akkus", "battery_life_cycle_rating": "Erwartete Lebensdauer der Lithium-Ionen-Batterie" @@ -46,6 +47,7 @@ "inverter_serial": "Seriennummer des Geräts (über die Weboberfläche abrufbar)", "inverter_port": "Port (normalerweise 8899)", "inverter_mb_slave_id": "Modbus-Slave-ID (normalerweise 1)", + "inverter_passthrough": "Solarman Protokoll-Passthrough Modus", "lookup_file": "YAML-Datei mit der Parameter-Definition", "battery_nominal_voltage": "Nennspannung des Lithium-Ionen-Akkus", "battery_life_cycle_rating": "Erwartete Lebensdauer der Lithium-Ionen-Batterie" diff --git a/custom_components/solarman/translations/en.json b/custom_components/solarman/translations/en.json index 538805e..28c3370 100644 --- a/custom_components/solarman/translations/en.json +++ b/custom_components/solarman/translations/en.json @@ -18,6 +18,7 @@ "inverter_serial": "Device Serial Number (retrieve from the web interface)", "inverter_port": "Port", "inverter_mb_slave_id": "Modbus Slave ID (usually 1)", + "inverter_passthrough": "Solarman protocol passthrough mode", "lookup_file": "The yaml file containing inverter parameter definitions", "battery_nominal_voltage": "Lithium-ion battery nominal voltage", "battery_life_cycle_rating": "Lithium-ion battery expected life cycle rating" @@ -46,6 +47,7 @@ "inverter_serial": "Device Serial Number (retrieve from the web interface)", "inverter_port": "Port", "inverter_mb_slave_id": "Modbus Slave ID (usually 1)", + "inverter_passthrough": "Solarman protocol passthrough mode", "lookup_file": "The yaml file containing inverter parameter definitions", "battery_nominal_voltage": "Lithium-ion battery nominal voltage", "battery_life_cycle_rating": "Lithium-ion battery expected life cycle rating" diff --git a/custom_components/solarman/translations/it.json b/custom_components/solarman/translations/it.json index ed1477c..d089a1c 100644 --- a/custom_components/solarman/translations/it.json +++ b/custom_components/solarman/translations/it.json @@ -18,6 +18,7 @@ "inverter_serial": "Numero seriale del logger (da interfaccia web)", "inverter_port": "Porta (solitamente 8899)", "inverter_mb_slave_id": "Slave ID di Modbus (solitamente 1)", + "inverter_passthrough": "Modalità passthrough del protocollo Solarman", "lookup_file": "File YAML contenente la definizione Inverter", "battery_nominal_voltage": "Voltaggio nominale della batteria agli ioni di litio", "battery_life_cycle_rating": "Ciclo di vita previsto della batteria agli ioni di litio" @@ -46,6 +47,7 @@ "inverter_serial": "Numero seriale del logger (da interfaccia web)", "inverter_port": "Porta (solitamente 8899)", "inverter_mb_slave_id": "Slave ID di Modbus (solitamente 1)", + "inverter_passthrough": "Modalità passthrough del protocollo Solarman", "lookup_file": "File YAML contenente la definizione Inverter", "battery_nominal_voltage": "Voltaggio nominale della batteria agli ioni di litio", "battery_life_cycle_rating": "Ciclo di vita previsto della batteria agli ioni di litio" diff --git a/custom_components/solarman/translations/pl.json b/custom_components/solarman/translations/pl.json index 292fa2a..a0ce9d0 100644 --- a/custom_components/solarman/translations/pl.json +++ b/custom_components/solarman/translations/pl.json @@ -18,6 +18,7 @@ "inverter_serial": "Numer seryjny urz\u0105dzenia (mo\u017cna go sprawdzi\u0107 z interfejsu sieciowego)", "inverter_port": "Port (zwykle 8899)", "inverter_mb_slave_id": "Modbus Slave ID (zwykle 1)", + "inverter_passthrough": "Tryb przekazywania protokołu Solarman", "lookup_file": "Plik yaml u\u017cywany do definiowania parametr\u00f3w", "battery_nominal_voltage": "Napi\u0119cie znamionowe akumulatora litowo-jonowego", "battery_life_cycle_rating": "Oczekiwany wska\u017anik cyklu \u017cycia akumulatora litowo-jonowego" @@ -46,6 +47,7 @@ "inverter_serial": "Numer seryjny urz\u0105dzenia (mo\u017cna go sprawdzi\u0107 z interfejsu sieciowego)", "inverter_port": "Port (zwykle 8899)", "inverter_mb_slave_id": "Modbus Slave ID (zwykle 1)", + "inverter_passthrough": "Tryb przekazywania protokołu Solarman", "lookup_file": "Plik yaml u\u017cywany do definiowania parametr\u00f3w", "battery_nominal_voltage": "Napi\u0119cie znamionowe akumulatora litowo-jonowego", "battery_life_cycle_rating": "Oczekiwany wska\u017anik cyklu \u017cycia akumulatora litowo-jonowego" diff --git a/custom_components/solarman/translations/pt-BR.json b/custom_components/solarman/translations/pt-BR.json index 2ac4851..f1feceb 100644 --- a/custom_components/solarman/translations/pt-BR.json +++ b/custom_components/solarman/translations/pt-BR.json @@ -18,6 +18,7 @@ "inverter_serial": "Número de série do dispositivo (recuperar da interface da web)", "inverter_port": "Porta (geralmente 8899)", "inverter_mb_slave_id": "Modbus Slave ID (geralmente 1)", + "inverter_passthrough": "Modo de passagem do protocolo Solarman", "lookup_file": "O arquivo yaml a ser usado para definição de parâmetro", "battery_nominal_voltage": "Tensão nominal da bateria de íons de lítio", "battery_life_cycle_rating": "Classificação do ciclo de vida esperado da bateria de íons de lítio" @@ -46,6 +47,7 @@ "inverter_serial": "Número de série do dispositivo (recuperar da interface da web)", "inverter_port": "Porta (geralmente 8899)", "inverter_mb_slave_id": "Modbus Slave ID (geralmente 1)", + "inverter_passthrough": "Modo de passagem do protocolo Solarman", "lookup_file": "O arquivo yaml a ser usado para definição de parâmetro", "battery_nominal_voltage": "Tensão nominal da bateria de íons de lítio", "battery_life_cycle_rating": "Classificação do ciclo de vida esperado da bateria de íons de lítio"