From 9e159999cc95bead0872f92c74cfba674dd3a173 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 28 Jul 2026 16:26:44 +0200 Subject: [PATCH 1/8] Add IoBrokerSocConfiguration and IoBrokerSocSetup classes --- packages/modules/vehicles/iobroker/config.py | 34 ++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 packages/modules/vehicles/iobroker/config.py diff --git a/packages/modules/vehicles/iobroker/config.py b/packages/modules/vehicles/iobroker/config.py new file mode 100644 index 0000000000..89776d41de --- /dev/null +++ b/packages/modules/vehicles/iobroker/config.py @@ -0,0 +1,34 @@ +from helpermodules.auto_str import auto_str +from typing import Optional + + +@auto_str +class IoBrokerSocConfiguration: + def __init__( + self, + calculate_soc: bool = False, + url: Optional[str] = "http://10.1.1.13:8093", + state_soc: Optional[str] = None, + state_range: Optional[str] = None, + state_odometer: Optional[str] = None, + timeout: int = 5 + ): + self.calculate_soc = calculate_soc + self.url = url + self.state_soc = state_soc + self.state_range = state_range + self.state_odometer = state_odometer + self.timeout = timeout + + +@auto_str +class IoBrokerSocSetup(): + def __init__(self, + name: str = "ioBroker", + type: str = "iobroker", + official: bool = False, + configuration: IoBrokerSocConfiguration = None) -> None: + self.name = name + self.type = type + self.official = official + self.configuration = configuration or IoBrokerSocConfiguration() From 012ea6162f397cc3aa751f24f7125fa05096d98a Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 28 Jul 2026 16:26:54 +0200 Subject: [PATCH 2/8] Add files via upload --- packages/modules/vehicles/iobroker/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/modules/vehicles/iobroker/__init__.py diff --git a/packages/modules/vehicles/iobroker/__init__.py b/packages/modules/vehicles/iobroker/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 9c94e9d681efa4325572d937a71c520b82ae6559 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 28 Jul 2026 16:27:48 +0200 Subject: [PATCH 3/8] Add IoBroker state of charge functionality --- packages/modules/vehicles/iobroker/soc.py | 70 +++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 packages/modules/vehicles/iobroker/soc.py diff --git a/packages/modules/vehicles/iobroker/soc.py b/packages/modules/vehicles/iobroker/soc.py new file mode 100644 index 0000000000..0dc5b43a66 --- /dev/null +++ b/packages/modules/vehicles/iobroker/soc.py @@ -0,0 +1,70 @@ +import logging + +from typing import List, Optional + +from helpermodules.cli import run_using_positional_cli_args +from modules.common import req +from modules.common import store +from modules.common.abstract_device import DeviceDescriptor +from modules.common.abstract_vehicle import VehicleUpdateData +from modules.common.component_state import CarState +from modules.common.configurable_vehicle import ConfigurableVehicle +from modules.vehicles.iobroker.config import IoBrokerSocSetup, IoBrokerSocConfiguration + +log = logging.getLogger(__name__) + + +def _get_state(base_url: str, state_id: str, timeout: int) -> dict: + url = f"{base_url}/get/{state_id}" + response = req.get_http_session().get(url, timeout=timeout) + return response.json() + + +def fetch_soc(config: IoBrokerSocSetup) -> CarState: + cfg = config.configuration + if cfg.url is None or cfg.url == "": + raise ValueError("Keine ioBroker-URL definiert. Bitte Konfiguration anpassen.") + if cfg.state_soc is None or cfg.state_soc == "": + raise ValueError("Keine State-ID für SoC definiert. Bitte Konfiguration anpassen.") + + soc_state = _get_state(cfg.url, cfg.state_soc, cfg.timeout) + soc = float(soc_state['val']) + soc_timestamp = int(soc_state['ts'] / 1000) if soc_state.get('ts') else None + + if cfg.state_range is None or cfg.state_range == "": + range = None + else: + range = float(_get_state(cfg.url, cfg.state_range, cfg.timeout)['val']) + + if cfg.state_odometer is None or cfg.state_odometer == "": + odometer = None + else: + odometer = float(_get_state(cfg.url, cfg.state_odometer, cfg.timeout)['val']) + + return CarState(soc=soc, range=range, odometer=odometer, soc_timestamp=soc_timestamp) + + +def create_vehicle(vehicle_config: IoBrokerSocSetup, vehicle: int): + def updater(vehicle_update_data: VehicleUpdateData) -> CarState: + return fetch_soc(vehicle_config) + return ConfigurableVehicle(vehicle_config=vehicle_config, + component_updater=updater, + vehicle=vehicle, + calc_while_charging=vehicle_config.configuration.calculate_soc) + + +def json_update(charge_point: int, url: str, state_soc: str, state_range: str, state_odometer: str): + log.debug(f'iobroker-soc: charge_point={charge_point} url="{url}" state_soc="{state_soc}" ' + f'state_range="{state_range}" state_odometer="{state_odometer}"') + store.get_car_value_store(charge_point).store.set( + fetch_soc(IoBrokerSocSetup(configuration=IoBrokerSocConfiguration(url=url, + state_soc=state_soc, + state_range=state_range, + state_odometer=state_odometer)))) + + +def main(argv: List[str]): + run_using_positional_cli_args(json_update, argv) + + +device_descriptor = DeviceDescriptor(configuration_factory=IoBrokerSocSetup) From a49d6c8ec3ac3b4b15e621f37c60591caca31e47 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 28 Jul 2026 16:43:02 +0200 Subject: [PATCH 4/8] Refactor IoBrokerSocConfiguration to use optional parameters --- packages/modules/vehicles/iobroker/config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/modules/vehicles/iobroker/config.py b/packages/modules/vehicles/iobroker/config.py index 89776d41de..4bc87c6240 100644 --- a/packages/modules/vehicles/iobroker/config.py +++ b/packages/modules/vehicles/iobroker/config.py @@ -7,7 +7,9 @@ class IoBrokerSocConfiguration: def __init__( self, calculate_soc: bool = False, - url: Optional[str] = "http://10.1.1.13:8093", + url: Optional[str] = None, + user: Optional[str] = None, + password: Optional[str] = None, state_soc: Optional[str] = None, state_range: Optional[str] = None, state_odometer: Optional[str] = None, @@ -15,6 +17,8 @@ def __init__( ): self.calculate_soc = calculate_soc self.url = url + self.user = user + self.password = password self.state_soc = state_soc self.state_range = state_range self.state_odometer = state_odometer From bde0152bf5188bdaffbbb80e3938e33dcd326260 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 28 Jul 2026 16:43:28 +0200 Subject: [PATCH 5/8] Refactor _get_state to use IoBrokerSocConfiguration --- packages/modules/vehicles/iobroker/soc.py | 24 ++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/modules/vehicles/iobroker/soc.py b/packages/modules/vehicles/iobroker/soc.py index 0dc5b43a66..dd0b222192 100644 --- a/packages/modules/vehicles/iobroker/soc.py +++ b/packages/modules/vehicles/iobroker/soc.py @@ -1,6 +1,6 @@ import logging -from typing import List, Optional +from typing import List from helpermodules.cli import run_using_positional_cli_args from modules.common import req @@ -14,9 +14,12 @@ log = logging.getLogger(__name__) -def _get_state(base_url: str, state_id: str, timeout: int) -> dict: - url = f"{base_url}/get/{state_id}" - response = req.get_http_session().get(url, timeout=timeout) +def _get_state(cfg: IoBrokerSocConfiguration, state_id: str) -> dict: + url = f"{cfg.url}/v1/state/{state_id}" + params = {} + if cfg.user: + params = {"user": cfg.user, "pass": cfg.password} + response = req.get_http_session().get(url, params=params, timeout=cfg.timeout) return response.json() @@ -27,19 +30,26 @@ def fetch_soc(config: IoBrokerSocSetup) -> CarState: if cfg.state_soc is None or cfg.state_soc == "": raise ValueError("Keine State-ID für SoC definiert. Bitte Konfiguration anpassen.") - soc_state = _get_state(cfg.url, cfg.state_soc, cfg.timeout) + soc_state = _get_state(cfg, cfg.state_soc) soc = float(soc_state['val']) + if not (0 <= soc <= 100): + raise ValueError(f"Ungültiger SoC-Wert {soc}% von State '{cfg.state_soc}' erhalten " + "(erwartet: 0-100). Bitte State-ID und Skalierung in ioBroker prüfen.") soc_timestamp = int(soc_state['ts'] / 1000) if soc_state.get('ts') else None if cfg.state_range is None or cfg.state_range == "": range = None else: - range = float(_get_state(cfg.url, cfg.state_range, cfg.timeout)['val']) + range = float(_get_state(cfg, cfg.state_range)['val']) + if range < 0: + raise ValueError(f"Ungültiger Reichweiten-Wert {range} von State '{cfg.state_range}' erhalten.") if cfg.state_odometer is None or cfg.state_odometer == "": odometer = None else: - odometer = float(_get_state(cfg.url, cfg.state_odometer, cfg.timeout)['val']) + odometer = float(_get_state(cfg, cfg.state_odometer)['val']) + if odometer < 0: + raise ValueError(f"Ungültiger Kilometerstand {odometer} von State '{cfg.state_odometer}' erhalten.") return CarState(soc=soc, range=range, odometer=odometer, soc_timestamp=soc_timestamp) From daca08dfa7e69ec2aeccc0b95118ed95c09b985e Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 28 Jul 2026 20:52:07 +0200 Subject: [PATCH 6/8] Refactor store method call in json_update function --- packages/modules/vehicles/iobroker/soc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modules/vehicles/iobroker/soc.py b/packages/modules/vehicles/iobroker/soc.py index dd0b222192..a4991439b9 100644 --- a/packages/modules/vehicles/iobroker/soc.py +++ b/packages/modules/vehicles/iobroker/soc.py @@ -66,7 +66,7 @@ def updater(vehicle_update_data: VehicleUpdateData) -> CarState: def json_update(charge_point: int, url: str, state_soc: str, state_range: str, state_odometer: str): log.debug(f'iobroker-soc: charge_point={charge_point} url="{url}" state_soc="{state_soc}" ' f'state_range="{state_range}" state_odometer="{state_odometer}"') - store.get_car_value_store(charge_point).store.set( + store.get_car_value_store(charge_point).set( fetch_soc(IoBrokerSocSetup(configuration=IoBrokerSocConfiguration(url=url, state_soc=state_soc, state_range=state_range, From 7d7cd3feba03505fb22a1c2c6f27607b90254ec5 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 28 Jul 2026 21:26:32 +0200 Subject: [PATCH 7/8] flake8 --- packages/modules/vehicles/iobroker/soc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/modules/vehicles/iobroker/soc.py b/packages/modules/vehicles/iobroker/soc.py index a4991439b9..0e8fbe3827 100644 --- a/packages/modules/vehicles/iobroker/soc.py +++ b/packages/modules/vehicles/iobroker/soc.py @@ -33,8 +33,9 @@ def fetch_soc(config: IoBrokerSocSetup) -> CarState: soc_state = _get_state(cfg, cfg.state_soc) soc = float(soc_state['val']) if not (0 <= soc <= 100): - raise ValueError(f"Ungültiger SoC-Wert {soc}% von State '{cfg.state_soc}' erhalten " - "(erwartet: 0-100). Bitte State-ID und Skalierung in ioBroker prüfen.") + raise ValueError( + f"Ungültiger SoC-Wert {soc}% von State '{cfg.state_soc}' erhalten " + "(erwartet: 0-100). Bitte State-ID und Skalierung in ioBroker prüfen.") soc_timestamp = int(soc_state['ts'] / 1000) if soc_state.get('ts') else None if cfg.state_range is None or cfg.state_range == "": From b73be3cb48eb1edbc950286984ab646437114f35 Mon Sep 17 00:00:00 2001 From: SeaSpotter Date: Tue, 28 Jul 2026 21:44:06 +0200 Subject: [PATCH 8/8] Refactor soc.py by removing unused code Removed unused imports and the json_update function. Updated main function to remove CLI argument handling. --- packages/modules/vehicles/iobroker/soc.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/modules/vehicles/iobroker/soc.py b/packages/modules/vehicles/iobroker/soc.py index 0e8fbe3827..f6ddffc9d7 100644 --- a/packages/modules/vehicles/iobroker/soc.py +++ b/packages/modules/vehicles/iobroker/soc.py @@ -1,10 +1,6 @@ import logging -from typing import List - -from helpermodules.cli import run_using_positional_cli_args from modules.common import req -from modules.common import store from modules.common.abstract_device import DeviceDescriptor from modules.common.abstract_vehicle import VehicleUpdateData from modules.common.component_state import CarState @@ -64,18 +60,4 @@ def updater(vehicle_update_data: VehicleUpdateData) -> CarState: calc_while_charging=vehicle_config.configuration.calculate_soc) -def json_update(charge_point: int, url: str, state_soc: str, state_range: str, state_odometer: str): - log.debug(f'iobroker-soc: charge_point={charge_point} url="{url}" state_soc="{state_soc}" ' - f'state_range="{state_range}" state_odometer="{state_odometer}"') - store.get_car_value_store(charge_point).set( - fetch_soc(IoBrokerSocSetup(configuration=IoBrokerSocConfiguration(url=url, - state_soc=state_soc, - state_range=state_range, - state_odometer=state_odometer)))) - - -def main(argv: List[str]): - run_using_positional_cli_args(json_update, argv) - - device_descriptor = DeviceDescriptor(configuration_factory=IoBrokerSocSetup)