diff --git a/packages/modules/vehicles/iobroker/__init__.py b/packages/modules/vehicles/iobroker/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/modules/vehicles/iobroker/config.py b/packages/modules/vehicles/iobroker/config.py new file mode 100644 index 0000000000..4bc87c6240 --- /dev/null +++ b/packages/modules/vehicles/iobroker/config.py @@ -0,0 +1,38 @@ +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] = None, + user: Optional[str] = None, + password: Optional[str] = None, + 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.user = user + self.password = password + 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() diff --git a/packages/modules/vehicles/iobroker/soc.py b/packages/modules/vehicles/iobroker/soc.py new file mode 100644 index 0000000000..f6ddffc9d7 --- /dev/null +++ b/packages/modules/vehicles/iobroker/soc.py @@ -0,0 +1,63 @@ +import logging + +from modules.common import req +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(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() + + +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, 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, 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, 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) + + +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) + + +device_descriptor = DeviceDescriptor(configuration_factory=IoBrokerSocSetup)