From f717f28bc27bcc43ac3042526ac1546376c91d6d Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:53:54 +0100 Subject: [PATCH 01/15] Merge master into Release (#2050) * Merge master into Beta (#2033) * build UI (#2009) * Update for Polestar auth change:client id and optional acceptance of terms and conditions * add terser * Added const for client_id * Added blank line for Flake check * fix heartbeat internal chargepoint (#2013) * Build Display Theme: Cards * Wiki (#2014) * Wiki * typos Wiki * clear browser console at midnight * Build Display Theme: Cards * fix SolarEdge synergy units (#2026) * reset boot_done before shutdown (#2027) * fix solaredge synergy units (#2030) * build * Satellit: Fix telnet (#2032) * Update version 2.1.6-RC.2 --------- Co-authored-by: PK Co-authored-by: Lutz Bender Co-authored-by: benderl * fix disable after unplugging (#2043) * fix disable after unplugging * Wiki * fix * Update version 2.1.6-Patch.1 --------- Co-authored-by: benderl Co-authored-by: Lutz Bender Co-authored-by: PK --- docs/Identifikation.md | 2 +- packages/control/chargepoint/chargepoint.py | 20 +++++++++++++------- packages/helpermodules/setdata.py | 8 +++----- web/version | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/docs/Identifikation.md b/docs/Identifikation.md index 48054d538d..2fcb688871 100644 --- a/docs/Identifikation.md +++ b/docs/Identifikation.md @@ -1,4 +1,4 @@ -Die openWB bietet die Möglichkeit, den Ladepunkt vor dem Laden zu entsperren und/oder das Fahrzeug zuzuordnen, welches geladen wird. Es gibt zwei grundlegende Konzepte, die für sich oder in Kombination genutzt werden können: Das Entsperren eines Ladepunkts und das Zuordnen eines Fahrzeugs. +Mit den verschiedenen Identifikations-Möglichkeiten kannst du die openWB grundsätzlich vor unbefugtem Laden schützen oder fahrzeugbasierte Funktionen nutzen. Es gibt zwei grundlegende Konzepte: Das Entsperren eines Ladepunkts und das Zuordnen eines Fahrzeugs. Die Identifikation erfolgt über diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index 15ad1bf812..5c1c1f955e 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -164,16 +164,21 @@ def _is_autolock_inactive(self) -> Tuple[bool, Optional[str]]: return state, message def _is_manual_lock_inactive(self) -> Tuple[bool, Optional[str]]: - if (self.data.set.manual_lock is False or - (self.data.get.rfid or - self.data.get.vehicle_id or - self.data.set.rfid) in self.template.data.valid_tags): + # Die Pro schickt je nach Timing auch nach Abstecken noch ein paar Zyklen den Tag. Dann darf der Ladepunkt + # nicht wieder entsperrt werden. + if (self.data.get.rfid or + self.data.get.vehicle_id or + self.data.set.rfid) in self.template.data.valid_tags: Pub().pub(f"openWB/set/chargepoint/{self.num}/set/manual_lock", False) - charging_possible = True - message = None - else: + elif self.template.data.disable_after_unplug and self.data.get.plug_state is False: + Pub().pub(f"openWB/set/chargepoint/{self.num}/set/manual_lock", True) + + if self.data.set.manual_lock: charging_possible = False message = "Keine Ladung, da der Ladepunkt gesperrt ist." + else: + charging_possible = True + message = None return charging_possible, message def _is_ev_plugged(self) -> Tuple[bool, Optional[str]]: @@ -231,6 +236,7 @@ def _process_charge_stop(self) -> None: if self.template.data.disable_after_unplug: self.data.set.manual_lock = True Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/manual_lock", True) + log.debug("/set/manual_lock True") # Ev wurde noch nicht aktualisiert. chargelog.save_and_reset_data(self, data.data.ev_data["ev"+str(self.data.set.charging_ev_prev)]) self.data.set.charging_ev_prev = -1 diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index ee9831b709..8dc6292998 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -623,6 +623,9 @@ def process_chargepoint_get_topics(self, msg): self._validate_value(msg, int, [(0, 2)]) elif "/get/evse_current" in msg.topic: self._validate_value(msg, float, [(0, 0), (6, 32), (600, 3200)]) + elif ("/get/error_timestamp" in msg.topic or + "/get/rfid_timestamp" in msg.topic): + self._validate_value(msg, float) elif ("/get/fault_str" in msg.topic or "/get/state_str" in msg.topic or "/get/heartbeat" in msg.topic or @@ -630,13 +633,8 @@ def process_chargepoint_get_topics(self, msg): "/get/vehicle_id" in msg.topic or "/get/serial_number" in msg.topic): self._validate_value(msg, str) - elif ("/get/error_timestamp" in msg.topic or - "/get/rfid_timestamp" in msg.topic): - self._validate_value(msg, float) elif ("/get/soc" in msg.topic): self._validate_value(msg, float, [(0, 100)]) - elif "/get/rfid_timestamp" in msg.topic: - self._validate_value(msg, float) elif "/get/simulation" in msg.topic: self._validate_value(msg, "json") else: diff --git a/web/version b/web/version index 399088bf46..052a820e50 100644 --- a/web/version +++ b/web/version @@ -1 +1 @@ -2.1.6 +2.1.6-Patch.1 From d1ccee657a530dac6b424ddb86b670379a1251d4 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Thu, 5 Dec 2024 09:52:34 +0100 Subject: [PATCH 02/15] Revert "Merge master into Release (#2050)" This reverts commit f717f28bc27bcc43ac3042526ac1546376c91d6d. --- docs/Identifikation.md | 2 +- packages/control/chargepoint/chargepoint.py | 20 +++++++------------- packages/helpermodules/setdata.py | 8 +++++--- web/version | 2 +- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/docs/Identifikation.md b/docs/Identifikation.md index 2fcb688871..48054d538d 100644 --- a/docs/Identifikation.md +++ b/docs/Identifikation.md @@ -1,4 +1,4 @@ -Mit den verschiedenen Identifikations-Möglichkeiten kannst du die openWB grundsätzlich vor unbefugtem Laden schützen oder fahrzeugbasierte Funktionen nutzen. Es gibt zwei grundlegende Konzepte: Das Entsperren eines Ladepunkts und das Zuordnen eines Fahrzeugs. +Die openWB bietet die Möglichkeit, den Ladepunkt vor dem Laden zu entsperren und/oder das Fahrzeug zuzuordnen, welches geladen wird. Es gibt zwei grundlegende Konzepte, die für sich oder in Kombination genutzt werden können: Das Entsperren eines Ladepunkts und das Zuordnen eines Fahrzeugs. Die Identifikation erfolgt über diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index 5c1c1f955e..15ad1bf812 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -164,21 +164,16 @@ def _is_autolock_inactive(self) -> Tuple[bool, Optional[str]]: return state, message def _is_manual_lock_inactive(self) -> Tuple[bool, Optional[str]]: - # Die Pro schickt je nach Timing auch nach Abstecken noch ein paar Zyklen den Tag. Dann darf der Ladepunkt - # nicht wieder entsperrt werden. - if (self.data.get.rfid or - self.data.get.vehicle_id or - self.data.set.rfid) in self.template.data.valid_tags: + if (self.data.set.manual_lock is False or + (self.data.get.rfid or + self.data.get.vehicle_id or + self.data.set.rfid) in self.template.data.valid_tags): Pub().pub(f"openWB/set/chargepoint/{self.num}/set/manual_lock", False) - elif self.template.data.disable_after_unplug and self.data.get.plug_state is False: - Pub().pub(f"openWB/set/chargepoint/{self.num}/set/manual_lock", True) - - if self.data.set.manual_lock: - charging_possible = False - message = "Keine Ladung, da der Ladepunkt gesperrt ist." - else: charging_possible = True message = None + else: + charging_possible = False + message = "Keine Ladung, da der Ladepunkt gesperrt ist." return charging_possible, message def _is_ev_plugged(self) -> Tuple[bool, Optional[str]]: @@ -236,7 +231,6 @@ def _process_charge_stop(self) -> None: if self.template.data.disable_after_unplug: self.data.set.manual_lock = True Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/manual_lock", True) - log.debug("/set/manual_lock True") # Ev wurde noch nicht aktualisiert. chargelog.save_and_reset_data(self, data.data.ev_data["ev"+str(self.data.set.charging_ev_prev)]) self.data.set.charging_ev_prev = -1 diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index 8dc6292998..ee9831b709 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -623,9 +623,6 @@ def process_chargepoint_get_topics(self, msg): self._validate_value(msg, int, [(0, 2)]) elif "/get/evse_current" in msg.topic: self._validate_value(msg, float, [(0, 0), (6, 32), (600, 3200)]) - elif ("/get/error_timestamp" in msg.topic or - "/get/rfid_timestamp" in msg.topic): - self._validate_value(msg, float) elif ("/get/fault_str" in msg.topic or "/get/state_str" in msg.topic or "/get/heartbeat" in msg.topic or @@ -633,8 +630,13 @@ def process_chargepoint_get_topics(self, msg): "/get/vehicle_id" in msg.topic or "/get/serial_number" in msg.topic): self._validate_value(msg, str) + elif ("/get/error_timestamp" in msg.topic or + "/get/rfid_timestamp" in msg.topic): + self._validate_value(msg, float) elif ("/get/soc" in msg.topic): self._validate_value(msg, float, [(0, 100)]) + elif "/get/rfid_timestamp" in msg.topic: + self._validate_value(msg, float) elif "/get/simulation" in msg.topic: self._validate_value(msg, "json") else: diff --git a/web/version b/web/version index 052a820e50..399088bf46 100644 --- a/web/version +++ b/web/version @@ -1 +1 @@ -2.1.6-Patch.1 +2.1.6 From 8a8cefdb1235b974fc80b42b4737b6d77b0de3b0 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Fri, 16 May 2025 13:25:43 +0200 Subject: [PATCH 03/15] pro+: fix network setup (#2405) --- runs/setup_network.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runs/setup_network.sh b/runs/setup_network.sh index f3da7a03b3..cc45f0d98d 100755 --- a/runs/setup_network.sh +++ b/runs/setup_network.sh @@ -100,6 +100,8 @@ function setup_dhcpcd_proplus() { echo "done" echo "restarting dhcpcd" sudo systemctl restart dhcpcd + sleep 5 + sudo dhclient -1 eth0 fi } @@ -113,6 +115,8 @@ function disable_dhcpcd_proplus() { sudo sed -i "/$pattern_begin/,/$pattern_end/d" "$dhcpcd_config_target" echo "restarting dhcpcd" sudo systemctl restart dhcpcd + sleep 5 + sudo dhclient -1 eth0 else echo "no changes required" fi From cf7d39d449517384520a36db61367ca8b38f95b7 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:11:37 +0200 Subject: [PATCH 04/15] update ubuntu version to latest in github action (#2337) --- .github/workflows/github-actions-python.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-actions-python.yml b/.github/workflows/github-actions-python.yml index cff37b6e33..e942a8e893 100644 --- a/.github/workflows/github-actions-python.yml +++ b/.github/workflows/github-actions-python.yml @@ -4,13 +4,13 @@ on: pull_request jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Python 3.9 uses: actions/setup-python@v5 with: - python-version: "3.9.2" + python-version: "3.9.12" - name: Install dependencies run: | pip3 install -r "/home/runner/work/core/core/requirements.txt" From 503b4754ff5cc5199f4a6319476274b9fdad2e83 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Fri, 16 May 2025 13:33:39 +0200 Subject: [PATCH 05/15] fix keep cloud config on startup (#2406) --- packages/helpermodules/subdata.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/helpermodules/subdata.py b/packages/helpermodules/subdata.py index f834771322..9cd2dc13fa 100644 --- a/packages/helpermodules/subdata.py +++ b/packages/helpermodules/subdata.py @@ -844,9 +844,11 @@ def process_system_topic(self, client: mqtt.Client, var: dict, msg: mqtt.MQTTMes self.set_json_payload(var["system"].data["backup_cloud"], msg) elif ("openWB/system/dataprotection_acknowledged" == msg.topic and decode_payload(msg.payload) is False): - Pub().pub("openWB/set/command/removeCloudBridge/todo", { - "command": "removeCloudBridge" - }) + if self.event_subdata_initialized.is_set(): + Pub().pub("openWB/set/command/removeCloudBridge/todo", + {"command": "removeCloudBridge"}) + else: + log.debug("skipping data protection message on startup") else: if "module_update_completed" in msg.topic: self.event_module_update_completed.set() From 87108ed9cb257ef5b4b4b657a9716947d6441a65 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Fri, 16 May 2025 13:34:14 +0200 Subject: [PATCH 06/15] Update version 2.1.7-Patch.2 --- web/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/version b/web/version index 7b149f70d5..b5c0842f5a 100644 --- a/web/version +++ b/web/version @@ -1 +1 @@ -2.1.7-Patch.1 +2.1.7-Patch.2 From 87ffd48f173d6f1c21c01956b45298e9d1c91ae2 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Wed, 28 May 2025 12:00:01 +0200 Subject: [PATCH 07/15] Pro+: fix soc and mac (#2424) --- packages/modules/common/store/_chargepoint_internal.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/modules/common/store/_chargepoint_internal.py b/packages/modules/common/store/_chargepoint_internal.py index e1ab78736d..aa9a9f2c65 100644 --- a/packages/modules/common/store/_chargepoint_internal.py +++ b/packages/modules/common/store/_chargepoint_internal.py @@ -33,6 +33,13 @@ def update(self): "/get/evse_current", self.state.evse_current, 2) pub_to_broker("openWB/set/internal_chargepoint/" + str(self.num) + "/get/max_evse_current", self.state.max_evse_current, 2) + if self.state.soc is not None: + pub_to_broker("openwb/set/internal_chargepoint/" + str(self.num) + "/get/soc", self.state.soc) + if self.state.soc_timestamp is not None: + pub_to_broker("openwb/set/internal_chargepoint/" + str(self.num) + + "/soc_timestamp", self.state.soc_timestamp) + if self.state.rfid_timestamp is not None: + pub_to_broker("openwb/set/internal_chargepoint/" + str(self.num) + "/vehicle_id", self.state.vehicle_id) def get_internal_chargepoint_value_store(id: int) -> ValueStore[ChargepointState]: From 8fd4fe2efe961d30858353833878e56ffb440040 Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Wed, 28 May 2025 12:05:56 +0200 Subject: [PATCH 08/15] Update version 2.1.7-Patch.3 --- web/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/version b/web/version index b5c0842f5a..5555ba4582 100644 --- a/web/version +++ b/web/version @@ -1 +1 @@ -2.1.7-Patch.2 +2.1.7-Patch.3 From f4e20ce2f5b3e07842175d809b16d1857fda4647 Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Wed, 11 Jun 2025 19:40:08 +0200 Subject: [PATCH 09/15] testpatch for Victron 3P75CT --- packages/modules/common/modbus.py | 17 +++++++++++++++-- .../modules/devices/victron/victron/counter.py | 18 +++++++++--------- .../modules/devices/victron/victron/device.py | 4 ++-- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/packages/modules/common/modbus.py b/packages/modules/common/modbus.py index f81f28b41e..9efead7f43 100644 --- a/packages/modules/common/modbus.py +++ b/packages/modules/common/modbus.py @@ -11,7 +11,7 @@ from typing import Any, Callable, Iterable, Optional, Union, overload, List import pymodbus -from pymodbus.client.sync import ModbusTcpClient, ModbusSerialClient +from pymodbus.client.sync import ModbusTcpClient, ModbusUdpClient, ModbusSerialClient from pymodbus.constants import Endian from pymodbus.payload import BinaryPayloadDecoder from urllib3.util import parse_url @@ -48,7 +48,7 @@ def __init__(self, bits: int, decoding_method: str): class ModbusClient: def __init__(self, - delegate: Union[ModbusSerialClient, ModbusTcpClient], + delegate: Union[ModbusSerialClient, ModbusTcpClient, ModbusUdpClient], address: str, port: int = 502, sleep_after_connect: Optional[int] = 0): self._delegate = delegate @@ -202,6 +202,19 @@ def __init__(self, super().__init__(ModbusTcpClient(host, port, **kwargs), address, port, sleep_after_connect) +class ModbusUdpClient_(ModbusClient): + def __init__(self, + address: str, + port: int = 502, + sleep_after_connect: Optional[int] = 0, + **kwargs): + parsed_url = parse_url(address) + host = parsed_url.host + if parsed_url.port is not None: + port = parsed_url.port + super().__init__(ModbusUdpClient(host, port, **kwargs), address, port, sleep_after_connect) + + class ModbusSerialClient_(ModbusClient): def __init__(self, port: int, diff --git a/packages/modules/devices/victron/victron/counter.py b/packages/modules/devices/victron/victron/counter.py index 00a992cfd7..2781792368 100644 --- a/packages/modules/devices/victron/victron/counter.py +++ b/packages/modules/devices/victron/victron/counter.py @@ -17,10 +17,10 @@ class VictronCounter(AbstractCounter): def __init__(self, device_id: int, component_config: Union[Dict, VictronCounterSetup], - tcp_client: modbus.ModbusTcpClient_) -> None: + udp_client: modbus.ModbusUdpClient_) -> None: self.__device_id = device_id self.component_config = dataclass_from_dict(VictronCounterSetup, component_config) - self.__tcp_client = tcp_client + self.__udp_client = udp_client self.sim_counter = SimCounter(self.__device_id, self.component_config.id, prefix="bezug") self.store = get_counter_value_store(self.component_config.id) self.fault_state = FaultState(ComponentInfo.from_component_config(self.component_config)) @@ -28,18 +28,18 @@ def __init__(self, def update(self): unit = self.component_config.configuration.modbus_id energy_meter = self.component_config.configuration.energy_meter - with self.__tcp_client: + with self.__udp_client: if energy_meter: - powers = self.__tcp_client.read_holding_registers(2600, [ModbusDataType.INT_16]*3, unit=unit) + powers = self.__udp_client.read_holding_registers(0x3082, [ModbusDataType.INT_32]*3, unit=unit) currents = [ - self.__tcp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 10 - for reg in [2617, 2619, 2621]] + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 + for reg in [0x3041, 0x3049, 0x3051]] voltages = [ - self.__tcp_client.read_holding_registers(reg, ModbusDataType.UINT_16, unit=unit) / 10 - for reg in [2616, 2618, 2620]] + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 + for reg in [0x3040, 0x3048, 0x3050]] power = sum(powers) else: - powers = self.__tcp_client.read_holding_registers(820, [ModbusDataType.INT_16]*3, unit=unit) + powers = self.__udp_client.read_holding_registers(820, [ModbusDataType.INT_16]*3, unit=unit) power = sum(powers) imported, exported = self.sim_counter.sim_count(power) diff --git a/packages/modules/devices/victron/victron/device.py b/packages/modules/devices/victron/victron/device.py index c74fdda0f8..87f01151ce 100644 --- a/packages/modules/devices/victron/victron/device.py +++ b/packages/modules/devices/victron/victron/device.py @@ -6,7 +6,7 @@ from modules.common.component_context import SingleComponentUpdateContext from modules.common.configurable_device import ComponentFactoryByType, ConfigurableDevice, MultiComponentUpdater -from modules.common.modbus import ModbusTcpClient_ +from modules.common.modbus import ModbusUdpClient_ from modules.devices.victron.victron.bat import VictronBat from modules.devices.victron.victron.config import Victron, VictronBatSetup, VictronCounterSetup, VictronInverterSetup from modules.devices.victron.victron.counter import VictronCounter @@ -32,7 +32,7 @@ def update_components(components: Iterable[Union[VictronBat, VictronCounter, Vic component.update() try: - client = ModbusTcpClient_(device_config.configuration.ip_address, device_config.configuration.port) + client = ModbusUdpClient_(device_config.configuration.ip_address, device_config.configuration.port) except Exception: log.exception("Fehler in create_device") return ConfigurableDevice( From 349d15089258965538cc08333382b9c8093649e4 Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Wed, 11 Jun 2025 19:56:22 +0200 Subject: [PATCH 10/15] fix for unrealistic phase values --- packages/modules/devices/victron/victron/counter.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/modules/devices/victron/victron/counter.py b/packages/modules/devices/victron/victron/counter.py index 2781792368..84a3b52e0f 100644 --- a/packages/modules/devices/victron/victron/counter.py +++ b/packages/modules/devices/victron/victron/counter.py @@ -30,7 +30,9 @@ def update(self): energy_meter = self.component_config.configuration.energy_meter with self.__udp_client: if energy_meter: - powers = self.__udp_client.read_holding_registers(0x3082, [ModbusDataType.INT_32]*3, unit=unit) + powers = [ + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit) / 1 + for reg in [0x3082, 0x3086, 0x308A]] currents = [ self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 for reg in [0x3041, 0x3049, 0x3051]] From f0f2049e69118ab2eb1e556240e5b0399cb15b8f Mon Sep 17 00:00:00 2001 From: AlexHuebi Date: Wed, 11 Jun 2025 20:07:13 +0200 Subject: [PATCH 11/15] fix power/current directions --- packages/modules/devices/victron/victron/counter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/modules/devices/victron/victron/counter.py b/packages/modules/devices/victron/victron/counter.py index 84a3b52e0f..7da2834cd9 100644 --- a/packages/modules/devices/victron/victron/counter.py +++ b/packages/modules/devices/victron/victron/counter.py @@ -31,10 +31,10 @@ def update(self): with self.__udp_client: if energy_meter: powers = [ - self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit) / 1 + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_32, unit=unit) / -1 for reg in [0x3082, 0x3086, 0x308A]] currents = [ - self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 + self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / -100 for reg in [0x3041, 0x3049, 0x3051]] voltages = [ self.__udp_client.read_holding_registers(reg, ModbusDataType.INT_16, unit=unit) / 100 From d7924dc71d00b5882c1b6a50d3ed304076b76a5d Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Mon, 19 May 2025 12:16:50 +0200 Subject: [PATCH 12/15] Pro+:RFID-Read plugged to Pi or Pro (#2408) --- packages/modules/internal_chargepoint_handler/pro_plus.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/modules/internal_chargepoint_handler/pro_plus.py b/packages/modules/internal_chargepoint_handler/pro_plus.py index 2798be3a93..19a6922563 100644 --- a/packages/modules/internal_chargepoint_handler/pro_plus.py +++ b/packages/modules/internal_chargepoint_handler/pro_plus.py @@ -27,7 +27,8 @@ def store_state(chargepoint_state: ChargepointState) -> None: try: chargepoint_state = super().request_values() - chargepoint_state.rfid = last_tag + if last_tag is not None and last_tag != "": + chargepoint_state.rfid = last_tag except (requests.exceptions.ConnectTimeout, requests.exceptions.ConnectionError): raise Exception("Interner Ladepunkt ist nicht erreichbar.") From 563e25db4042b0f3d21912c695c0094946b73582 Mon Sep 17 00:00:00 2001 From: LKuemmel Date: Tue, 15 Jul 2025 11:42:24 +0200 Subject: [PATCH 13/15] update version 2.1.7-Patch.4 --- web/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/version b/web/version index 5555ba4582..f4014350a4 100644 --- a/web/version +++ b/web/version @@ -1 +1 @@ -2.1.7-Patch.3 +2.1.7-Patch.4 From f346706e8b4408d9b7cdbef6e6249fc618e1c743 Mon Sep 17 00:00:00 2001 From: benderl Date: Tue, 29 Jul 2025 11:16:19 +0200 Subject: [PATCH 14/15] Merge pull request #2605 from benderl/remote-support rewrite Remote-Support process handling --- runs/remoteSupport/remoteSupport.py | 196 +++++++++++++++------------- 1 file changed, 106 insertions(+), 90 deletions(-) diff --git a/runs/remoteSupport/remoteSupport.py b/runs/remoteSupport/remoteSupport.py index 15913eae09..331b29f92d 100755 --- a/runs/remoteSupport/remoteSupport.py +++ b/runs/remoteSupport/remoteSupport.py @@ -5,12 +5,15 @@ from datetime import datetime from subprocess import Popen from pathlib import Path +import sys +from signal import signal, Signals, SIGTERM, SIGINT from time import sleep from typing import Optional import paho.mqtt.client as mqtt import platform +VERSION = "1.0.0" API_VERSION = "1" BASE_PATH = Path(__file__).resolve().parents[2] RAMDISK_PATH = BASE_PATH / "ramdisk" @@ -26,17 +29,23 @@ mqtt_broker_host = "localhost" mqtt_broker_port = 1886 -support_tunnel: Popen = None -partner_tunnel: Popen = None -cloud_tunnel: Popen = None +support_tunnel: Optional[Popen] = None +partner_tunnel: Optional[Popen] = None +cloud_tunnel: Optional[Popen] = None valid_partner_ids: list[str] = [] logging.basicConfig( filename=str(RAMDISK_PATH / "remote_support.log"), - level=logging.DEBUG, format='%(asctime)s: %(message)s' + level=logging.DEBUG, format='%(asctime)s - {%(name)s:%(lineno)s} - {%(levelname)s:%(threadName)s}: %(message)s' ) log = logging.getLogger("RemoteSupport") +def handle_terminate(signal_number: int, frame: Optional[object]): + signal_name = Signals(signal_number).name + log.info(f"{signal_name} received, shutting down gracefully...") + sys.exit(0) + + def get_serial(): """Extract serial from cpuinfo file""" with open('/proc/cpuinfo', 'r') as f: @@ -72,6 +81,26 @@ def get_lt_executable() -> Optional[Path]: return lt_path +def stop_tunnel(tunnel: Optional[Popen], tunnel_name: str) -> None: + log.debug(f"Stopping tunnel: {tunnel_name}") + if tunnel is not None: + if tunnel.poll() is None: + log.info(f"terminating {tunnel_name} ...") + tunnel.terminate() + try: + tunnel.wait(timeout=3) + except Exception as e: + log.error(f"Error terminating {tunnel_name}: {e}") + else: + # Tunnel process is already terminated, but may not have been collected yet + try: + tunnel.wait(timeout=1) + except Exception: + pass + else: + log.error(f"tunnel {tunnel_name} is not running.") + + def on_connect(client: mqtt.Client, userdata, flags: dict, rc: int): """connect to broker and subscribe to set topics""" log.info("Connected") @@ -87,19 +116,6 @@ def on_connect(client: mqtt.Client, userdata, flags: dict, rc: int): def on_message(client: mqtt.Client, userdata, msg: mqtt.MQTTMessage): """handle incoming messages""" - def is_tunnel_closed(tunnel: Popen) -> bool: - log.debug(str(tunnel)) - is_closed = False - if tunnel is not None: - if tunnel.poll() is None: - log.error("received start tunnel message but tunnel is already running") - else: - is_closed = True - log.info("tunnel was closed by server") - else: - is_closed = True - return is_closed - global support_tunnel global partner_tunnel global cloud_tunnel @@ -110,24 +126,21 @@ def is_tunnel_closed(tunnel: Popen) -> bool: log.debug("Topic: %s, Message: %s", msg.topic, payload) if msg.topic == REMOTE_SUPPORT_TOPIC: if payload == 'stop': - if support_tunnel is None: - log.error("received stop tunnel message but tunnel is not running") - else: - log.info("stop remote support") - support_tunnel.terminate() - support_tunnel.wait(timeout=3) - support_tunnel = None + stop_tunnel(support_tunnel, "support_tunnel") + support_tunnel = None elif re.match(r'^([^;]+)(?:;([1-9][0-9]+)(?:;([a-zA-Z0-9]+))?)?$', payload): - if is_tunnel_closed(support_tunnel): - splitted = payload.split(";") - token = splitted[0] - port = splitted[1] if len(splitted) > 1 else "2223" - user = splitted[2] if len(splitted) > 2 else "getsupport" - log.info("start remote support") - support_tunnel = Popen(["sshpass", "-p", token, "ssh", "-N", "-tt", "-o", - "StrictHostKeyChecking=no", "-o", "ServerAliveInterval 60", "-R", - f"{port}:localhost:22", f"{user}@remotesupport.openwb.de"]) - log.info(f"tunnel running with pid {support_tunnel.pid}") + # Always stop existing tunnel before starting a new one + stop_tunnel(support_tunnel, "support_tunnel") + support_tunnel = None + splitted = payload.split(";") + token = splitted[0] + port = splitted[1] if len(splitted) > 1 else "2223" + user = splitted[2] if len(splitted) > 2 else "getsupport" + log.info("start remote support") + support_tunnel = Popen(["sshpass", "-p", token, "ssh", "-N", "-tt", "-o", + "StrictHostKeyChecking=no", "-o", "ServerAliveInterval 60", "-R", + f"{port}:localhost:22", f"{user}@remotesupport.openwb.de"]) + log.info(f"tunnel running with pid {support_tunnel.pid}") else: log.info("unknown message: " + payload) clear_topic = True @@ -135,72 +148,66 @@ def is_tunnel_closed(tunnel: Popen) -> bool: valid_partner_ids = json.loads(payload) elif msg.topic == REMOTE_PARTNER_TOPIC: if payload == 'stop': - if partner_tunnel is None: - log.error("received stop tunnel message but tunnel is not running") - else: - log.info("stop partner support") - partner_tunnel.terminate() - partner_tunnel.wait(timeout=3) - partner_tunnel = None + stop_tunnel(partner_tunnel, "partner_tunnel") + partner_tunnel = None elif re.match(r'^([^;]+)(?:;((?:cnode)?[0-9]+)(?:;([\wäöüÄÖÜ-]+))?)?$', payload): - if is_tunnel_closed(partner_tunnel): - splitted = payload.split(";") - if len(splitted) != 3: - log.error("invalid number of settings received!") + # Always stop existing tunnel before starting a new one + stop_tunnel(partner_tunnel, "partner_tunnel") + partner_tunnel = None + splitted = payload.split(";") + if len(splitted) != 3: + log.error("invalid number of settings received!") + else: + token = splitted[0] + port_or_node = splitted[1] + user = splitted[2] # not used in v0, partner-id in v1 + if port_or_node.isdecimal(): + # v0 + log.info("start partner support") + partner_tunnel = Popen(["sshpass", "-p", token, "ssh", "-N", "-tt", "-o", + "StrictHostKeyChecking=no", "-o", "ServerAliveInterval 60", "-R", + f"{port_or_node}:localhost:80", f"{user}@partner.openwb.de"]) + log.info(f"tunnel running with pid {partner_tunnel.pid}") else: - token = splitted[0] - port_or_node = splitted[1] - user = splitted[2] # not used in v0, partner-id in v1 - if port_or_node.isdecimal(): - # v0 - log.info("start partner support") - partner_tunnel = Popen(["sshpass", "-p", token, "ssh", "-N", "-tt", "-o", - "StrictHostKeyChecking=no", "-o", "ServerAliveInterval 60", "-R", - f"{port_or_node}:localhost:80", f"{user}@partner.openwb.de"]) - log.info(f"tunnel running with pid {partner_tunnel.pid}") + # v1 + if lt_executable is None: + log.error("start partner tunnel requested but lt executable not found!") else: - # v1 - if lt_executable is None: - log.error("start partner tunnel requested but lt executable not found!") + if user in valid_partner_ids: + log.info("start partner support v1") + if lt_executable is not None: + partner_tunnel = Popen([f"{lt_executable}", "-h", + "https://" + port_or_node + ".openwb.de/", + "-p", "80", "-s", token]) + log.info(f"tunnel running with pid {partner_tunnel.pid}") else: - if user in valid_partner_ids: - log.info("start partner support v1") - if lt_executable is not None: - partner_tunnel = Popen([f"{lt_executable}", "-h", - "https://" + port_or_node + ".openwb.de/", - "-p", "80", "-s", token]) - log.info(f"tunnel running with pid {partner_tunnel.pid}") - else: - log.error(f"invalid partner-id: {user}") + log.error(f"invalid partner-id: {user}") else: log.info("unknown message: " + payload) clear_topic = True elif msg.topic == CLOUD_TOPIC: if payload == 'stop': - if cloud_tunnel is None: - log.error("received stop cloud message but tunnel is not running") - else: - log.info("stop cloud tunnel") - cloud_tunnel.terminate() - cloud_tunnel.wait(timeout=3) - cloud_tunnel = None + stop_tunnel(cloud_tunnel, "cloud_tunnel") + cloud_tunnel = None elif re.match(r'^([^;]+)(?:;([a-zA-Z0-9]+)(?:;([a-zA-Z0-9]+))?)?$', payload): - if is_tunnel_closed(cloud_tunnel): - splitted = payload.split(";") - if len(splitted) != 3: - log.error("invalid number of settings received!") - else: - token = splitted[0] - cloud_node = splitted[1] - user = splitted[2] + # Always stop existing tunnel before starting a new one + stop_tunnel(cloud_tunnel, "cloud_tunnel") + cloud_tunnel = None + splitted = payload.split(";") + if len(splitted) != 3: + log.error("invalid number of settings received!") + else: + token = splitted[0] + cloud_node = splitted[1] + user = splitted[2] - if lt_executable is None: - log.error("start cloud tunnel requested but lt executable not found!") - else: - log.info(f"start cloud tunnel '{token[:4]}...{token[-4:]}' on '{cloud_node}'") - cloud_tunnel = Popen([f"{lt_executable}", "-h", - "https://" + cloud_node + ".openwb.de/", "-p", "80", "-s", token]) - log.info(f"cloud tunnel running with pid {cloud_tunnel.pid}") + if lt_executable is None: + log.error("start cloud tunnel requested but lt executable not found!") + else: + log.info(f"start cloud tunnel '{token[:4]}...{token[-4:]}' on '{cloud_node}'") + cloud_tunnel = Popen([f"{lt_executable}", "-h", + "https://" + cloud_node + ".openwb.de/", "-p", "80", "-s", token]) + log.info(f"cloud tunnel running with pid {cloud_tunnel.pid}") else: log.info("unknown message: " + payload) clear_topic = True @@ -209,6 +216,11 @@ def is_tunnel_closed(tunnel: Popen) -> bool: client.publish(msg.topic, "", qos=2, retain=True) +log.info("Starting remote support client") +log.debug(f"openWB remote support client v{VERSION} (API v{API_VERSION})") +log.debug("registering signal handlers") +signal(SIGTERM, handle_terminate) # Handle SIGTERM from systemctl for graceful shutdown +signal(SIGINT, handle_terminate) # Handle SIGINT from keyboard (Strg+C) for graceful shutdown lt_executable = get_lt_executable() client = mqtt.Client(f"openWB-remote-{get_serial()}-{datetime.today().timestamp()}") client.on_connect = on_connect @@ -222,7 +234,7 @@ def is_tunnel_closed(tunnel: Popen) -> bool: try: while True: sleep(1) -except (Exception, KeyboardInterrupt) as e: +except Exception as e: log.debug(e) log.debug("terminated") finally: @@ -233,4 +245,8 @@ def is_tunnel_closed(tunnel: Popen) -> bool: client.loop_stop() client.disconnect() log.debug("disconnected") + # terminate tunnels + stop_tunnel(support_tunnel, "support_tunnel") + stop_tunnel(partner_tunnel, "partner_tunnel") + stop_tunnel(cloud_tunnel, "cloud_tunnel") log.debug("exit") From 2d946b8d7e08ee1cc00ee7cb771050fa3f2404fc Mon Sep 17 00:00:00 2001 From: LKuemmel <76958050+LKuemmel@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:01:31 +0200 Subject: [PATCH 15/15] Update version 2.1.7-Patch.5 --- web/version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/version b/web/version index f4014350a4..223ca90984 100644 --- a/web/version +++ b/web/version @@ -1 +1 @@ -2.1.7-Patch.4 +2.1.7-Patch.5