diff --git a/packages/control/bat_all.py b/packages/control/bat_all.py index ea4020dddd..d1085b0d2b 100644 --- a/packages/control/bat_all.py +++ b/packages/control/bat_all.py @@ -291,40 +291,42 @@ def set_power_limit_controllable(self): def get_power_limit(self): if self.data.config.bat_control_permitted is False: - return - chargepoint_by_chargemodes = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_CHARGING) - # Falls aktive Steuerung an und Fahrzeuge laden und kein Überschuss im System ist, - # dann Speicherleistung begrenzen. - if (self.data.config.power_limit_mode != BatPowerLimitMode.NO_LIMIT.value and - len(chargepoint_by_chargemodes) > 0 and - data.data.cp_all_data.data.get.power > 100 and - self.data.get.power_limit_controllable and - self.data.get.power <= 0 and - data.data.counter_all_data.get_evu_counter().data.get.power >= -100): - if self.data.config.power_limit_mode == BatPowerLimitMode.LIMIT_STOP.value: - self.data.set.power_limit = 0 - elif self.data.config.power_limit_mode == BatPowerLimitMode.LIMIT_TO_HOME_CONSUMPTION.value: - self.data.set.power_limit = data.data.counter_all_data.data.set.home_consumption * -1 - log.debug(f"Speicher-Leistung begrenzen auf {self.data.set.power_limit/1000}kW") - else: self.data.set.power_limit = None - control_range_low = data.data.general_data.data.chargemode_config.pv_charging.control_range[0] - control_range_high = data.data.general_data.data.chargemode_config.pv_charging.control_range[1] - control_range_center = control_range_high - (control_range_high - control_range_low) / 2 - if len(chargepoint_by_chargemodes) == 0: - log.debug("Speicher-Leistung nicht begrenzen, " - "da keine Ladepunkte in einem Lademodus mit Netzbezug sind.") - elif data.data.cp_all_data.data.get.power <= 100: - log.debug("Speicher-Leistung nicht begrenzen, da kein Ladepunkt mit Netzbezug lädt.") - elif self.data.get.power_limit_controllable is False: - log.debug("Speicher-Leistung nicht begrenzen, da keine regelbaren Speicher vorhanden sind.") - elif self.data.get.power > 0: - log.debug("Speicher-Leistung nicht begrenzen, da kein Speicher entladen wird.") - elif data.data.counter_all_data.get_evu_counter().data.get.power < control_range_center + 80: - # Wenn der Regelbereich zB auf Bezug steht, darf auch die Leistung des Regelbereichs entladen werden. - log.debug("Speicher-Leistung nicht begrenzen, da EVU-Überschuss vorhanden ist.") + else: + chargepoint_by_chargemodes = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_CHARGING) + # Falls aktive Steuerung an und Fahrzeuge laden und kein Überschuss im System ist, + # dann Speicherleistung begrenzen. + if (self.data.config.power_limit_mode != BatPowerLimitMode.NO_LIMIT.value and + len(chargepoint_by_chargemodes) > 0 and + data.data.cp_all_data.data.get.power > 100 and + self.data.get.power_limit_controllable and + self.data.get.power <= 0 and + data.data.counter_all_data.get_evu_counter().data.get.power >= -100): + if self.data.config.power_limit_mode == BatPowerLimitMode.LIMIT_STOP.value: + self.data.set.power_limit = 0 + elif self.data.config.power_limit_mode == BatPowerLimitMode.LIMIT_TO_HOME_CONSUMPTION.value: + self.data.set.power_limit = data.data.counter_all_data.data.set.home_consumption * -1 + log.debug(f"Speicher-Leistung begrenzen auf {self.data.set.power_limit/1000}kW") else: - log.debug("Speicher-Leistung nicht begrenzen.") + self.data.set.power_limit = None + control_range_low = data.data.general_data.data.chargemode_config.pv_charging.control_range[0] + control_range_high = data.data.general_data.data.chargemode_config.pv_charging.control_range[1] + control_range_center = control_range_high - (control_range_high - control_range_low) / 2 + if len(chargepoint_by_chargemodes) == 0: + log.debug("Speicher-Leistung nicht begrenzen, " + "da keine Ladepunkte in einem Lademodus mit Netzbezug sind.") + elif data.data.cp_all_data.data.get.power <= 100: + log.debug("Speicher-Leistung nicht begrenzen, da kein Ladepunkt mit Netzbezug lädt.") + elif self.data.get.power_limit_controllable is False: + log.debug("Speicher-Leistung nicht begrenzen, da keine regelbaren Speicher vorhanden sind.") + elif self.data.get.power > 0: + log.debug("Speicher-Leistung nicht begrenzen, da kein Speicher entladen wird.") + elif data.data.counter_all_data.get_evu_counter().data.get.power < control_range_center + 80: + # Wenn der Regelbereich zB auf Bezug steht, darf auch die Leistung des Regelbereichs entladen + # werden. + log.debug("Speicher-Leistung nicht begrenzen, da EVU-Überschuss vorhanden ist.") + else: + log.debug("Speicher-Leistung nicht begrenzen.") remaining_power_limit = self.data.set.power_limit for bat_component in get_controllable_bat_components(): if self.data.set.power_limit is None: diff --git a/packages/control/chargelog/chargelog.py b/packages/control/chargelog/chargelog.py index 4dcad2e8b2..b8605e058d 100644 --- a/packages/control/chargelog/chargelog.py +++ b/packages/control/chargelog/chargelog.py @@ -2,6 +2,7 @@ from enum import Enum import json import logging +import os import pathlib from typing import Any, Dict, List, Optional @@ -250,13 +251,18 @@ def write_new_entry(new_entry): filepath = str(_get_parent_file() / "data" / "charge_log" / (timecheck.create_timestamp_YYYYMM() + ".json")) try: - with open(filepath, "r", encoding="utf-8") as json_file: - content = json.load(json_file) + if os.path.exists(filepath) and os.path.getsize(filepath) == 0: + content = [] + else: + with open(filepath, "r", encoding="utf-8") as json_file: + try: + content = json.load(json_file) + except json.decoder.JSONDecodeError: + corrupt_path = f"{filepath}.unparsable_{timecheck.create_timestamp()}" + os.rename(filepath, corrupt_path) + log.error(f"ChargeLog: Korrupte Datei umbenannt nach {corrupt_path}") + content = [] except FileNotFoundError: - # with open(filepath, "w", encoding="utf-8") as jsonFile: - # json.dump([], jsonFile) - # with open(filepath, "r", encoding="utf-8") as jsonFile: - # content = json.load(jsonFile) content = [] content.append(new_entry) write_and_check(filepath, content) diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index 8dedab09c4..0d403dea61 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -160,13 +160,15 @@ def _is_manual_lock_inactive(self) -> Tuple[bool, Optional[str]]: # Vergleiche werden case-insensitive durchgeführt # das vereinfacht die Eingabe, kann aber auch für falsche Treffer sorgen. # 'fnmatch()' ist case-insensitive + match = False for tag_id in self.template.data.valid_tags: if ((self.data.get.rfid is not None and fnmatch(self.data.get.rfid, tag_id)) or (self.data.get.vehicle_id is not None and fnmatch(self.data.get.vehicle_id, tag_id)) or (self.data.set.rfid is not None and fnmatch(self.data.set.rfid, tag_id))): - Pub().pub(f"openWB/set/chargepoint/{self.num}/set/manual_lock", False) - # Wenn der Ladepunkt nach dem Abstecken gesperrt werden soll, und kein Fahrzeug angeschlossen ist wird gesperrt - if self.template.data.disable_after_unplug and self.data.get.plug_state is False: + match = True + if match: + 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: @@ -374,7 +376,7 @@ def _is_phase_switch_required(self) -> bool: self.check_deviating_contactor_states(self.data.set.phases_to_use, self.data.control_parameter.phases)) and # Wenn der Ladevorgang gestartet wird, muss vor dem ersten Laden umgeschaltet werden. - self.data.set.current != 0): + self.data.set.current != 0 and self.data.get.charge_state is False): phase_switch_required = True if phase_switch_required: # Umschaltung fehlgeschlagen @@ -383,18 +385,18 @@ def _is_phase_switch_required(self) -> bool: if self.data.control_parameter.failed_phase_switches > self.MAX_FAILED_PHASE_SWITCHES: phase_switch_required = False self.set_state_and_log( - "Keine Phasenumschaltung, da die maximale Anzahl an Fehlversuchen erreicht wurde. Die " - "aktuelle Phasenzahl wird bis zum Abstecken beibehalten.") + "Keine Phasenumschaltung, da die maximale Anzahl an Fehlversuchen erreicht wurde.") self.data.control_parameter.failed_phase_switches += 1 else: # Umschaltung vor Ladestart zulassen - if self.data.set.log.imported_since_plugged != 0: + if (self.data.set.log.imported_since_plugged != 0 and + self.data.control_parameter.failed_phase_switches > 0): phase_switch_required = False self.set_state_and_log( "Keine Phasenumschaltung, da wiederholtes Anstoßen der Umschaltung in den übergreifenden " "Ladeeinstellungen deaktiviert wurde. Die aktuelle " "Phasenzahl wird bis zum Abstecken beibehalten.") - self.data.control_parameter.failed_phase_switches += 1 + self.data.control_parameter.failed_phase_switches += 1 return phase_switch_required STOP_CHARGING = ", dafür wird die Ladung unterbrochen." @@ -723,6 +725,8 @@ def update(self, ev_list: Dict[str, Ev]) -> None: data.data.counter_all_data.get_evu_counter().reset_switch_on_off( self, charging_ev) charging_ev.reset_phase_switch(self.data.control_parameter) + if self.chargemode_changed: + self.data.control_parameter.failed_phase_switches = 0 message = message_ev if message_ev else message # Ein Eintrag muss nur erstellt werden, wenn vorher schon geladen wurde und auch danach noch # geladen werden soll. @@ -823,8 +827,9 @@ def _get_charging_ev(self, vehicle: int, ev_list: Dict[str, Ev]) -> Ev: if self.data.set.charging_ev_prev != vehicle: Pub().pub(f"openWB/set/vehicle/{charging_ev.num}/get/force_soc_update", True) log.debug("SoC nach EV-Wechsel") - self.update_charge_template(charging_ev.charge_template) - if self.data.set.charge_template.data.id != charging_ev.charge_template.data.id: + # wenn vorher kein anderes Fahrzeug zugeordnet war, Ladeprofil nicht zurücksetzen + if ((self.data.set.charging_ev_prev != vehicle and self.data.set.charging_ev_prev != -1) or + (self.data.set.charge_template.data.id != charging_ev.charge_template.data.id)): self.update_charge_template(charging_ev.charge_template) self.data.set.charging_ev_data = charging_ev self.data.set.charging_ev = vehicle @@ -908,7 +913,8 @@ def cp_ev_chargemode_support_phase_switch(self) -> bool: def cp_ev_support_phase_switch(self) -> bool: return (self.data.config.auto_phase_switch_hw and self.data.get.evse_signaling != EvseSignaling.HLC and - self.data.set.charging_ev_data.ev_template.data.prevent_phase_switch is False) + (self.data.set.charging_ev_data.ev_template.data.prevent_phase_switch is False or + self.data.set.log.imported_since_plugged == 0)) def chargemode_support_phase_switch(self) -> bool: control_parameter = self.data.control_parameter @@ -934,7 +940,7 @@ def failed_phase_switches_reached(self) -> bool: (data.data.general_data.data.chargemode_config.retry_failed_phase_switches is False and self.data.control_parameter.failed_phase_switches == 1)): self.set_state_and_log( - "Keine automatische Umschaltung, da die maximale Anzahl an Fehlversuchen erreicht wurde. ") + "Keine Phasenumschaltung, da die maximale Anzahl an Fehlversuchen erreicht wurde. ") return False else: return True diff --git a/packages/control/ev/ev.py b/packages/control/ev/ev.py index ab74e2d326..6736002064 100644 --- a/packages/control/ev/ev.py +++ b/packages/control/ev/ev.py @@ -353,11 +353,13 @@ def auto_phase_switch(self, waiting_time, delay)[1]) control_parameter.state = ChargepointState.PHASE_SWITCH_DELAY - elif condition_msg: - if condition_msg == self.CURRENT_OUT_OF_NOMINAL_DIFFERENCE: - message = f"Keine Phasenumschaltung{condition_msg}" - else: - log.debug(f"Keine Phasenumschaltung{condition_msg}") + else: + if condition_msg: + if condition_msg == self.CURRENT_OUT_OF_NOMINAL_DIFFERENCE: + message = f"Keine Phasenumschaltung{condition_msg}" + else: + log.debug(f"Keine Phasenumschaltung{condition_msg}") + control_parameter.timestamp_phase_switch_buffer_start = None else: if condition: # Timer laufen lassen @@ -379,6 +381,7 @@ def auto_phase_switch(self, ).data.set.reserved_surplus -= max(0, required_reserved_power) message = f"Verzögerung für die {direction_str} Phasen abgebrochen{condition_msg}" control_parameter.state = ChargepointState.CHARGING_ALLOWED + control_parameter.timestamp_phase_switch_buffer_start = None if message: log.info(f"LP {cp_num}: {message}") diff --git a/packages/control/phase_switch.py b/packages/control/phase_switch.py index 89d0edd407..ca4b3bc84b 100644 --- a/packages/control/phase_switch.py +++ b/packages/control/phase_switch.py @@ -38,8 +38,6 @@ def _perform_phase_switch(chargepoint_module: AbstractChargepoint, phases: int, time.sleep(5) # Phasenumschaltung entsprechend Modul chargepoint_module.switch_phases(phases, ev.ev_template.data.phase_switch_pause) - # Die Ladung wird in start_charging wieder gestartet, wenn phase_switch_timestamp wieder auf None gesetzt wird. - time.sleep(ev.ev_template.data.keep_charge_active_duration) except Exception: log.exception("Fehler im Phasenumschaltungs-Modul") diff --git a/packages/helpermodules/command.py b/packages/helpermodules/command.py index 2c19972c8d..41067b8c1f 100644 --- a/packages/helpermodules/command.py +++ b/packages/helpermodules/command.py @@ -461,7 +461,7 @@ def addChargeTemplate(self, connection_id: str, payload: dict) -> None: new_charge_template = asdict(new_charge_template) else: new_charge_template = get_new_charge_template() - new_charge_template["id"] = new_id + new_charge_template["id"] = new_id Pub().pub("openWB/set/command/max_id/charge_template", new_id) Pub().pub(f"openWB/set/vehicle/template/charge_template/{new_id}", new_charge_template) @@ -648,8 +648,9 @@ def addEvTemplate(self, connection_id: str, payload: dict) -> None: else: new_ev_template = dataclass_utils.asdict(EvTemplateData()) new_id = self.max_id_ev_template + 1 - Pub().pub(f'openWB/set/vehicle/template/ev_template/{new_id}', new_ev_template) + new_ev_template["id"] = new_id self.max_id_ev_template = new_id + Pub().pub(f'openWB/set/vehicle/template/ev_template/{new_id}', new_ev_template) Pub().pub("openWB/set/command/max_id/ev_template", new_id) pub_user_message( payload, connection_id, diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index f5ce2c377b..094067adcd 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -57,7 +57,7 @@ class UpdateConfig: - DATASTORE_VERSION = 94 + DATASTORE_VERSION = 96 valid_topic = [ "^openWB/bat/config/bat_control_permitted$", @@ -647,7 +647,7 @@ def __update_topic(self, topic: str, payload): if payload == "": del self.all_received_topics[topic] else: - self.all_received_topics[topic] = payload + self.all_received_topics[topic] = copy.deepcopy(payload) def __remove_outdated_topics(self): """ remove outdated topics from all_received_topics and broker @@ -1967,7 +1967,7 @@ def upgrade(topic: str, payload) -> None: # replace smarteq soc module by no_module if payload.get("type") == "smarteq": payload = NO_MODULE - Pub().pub(topic, payload) + return {topic: payload} self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 71) @@ -1983,7 +1983,7 @@ def upgrade(topic: str, payload) -> None: payload["configuration"]["firmware"] = "v1" elif payload["configuration"].get("firmware") == "v112": payload["configuration"]["firmware"] = "v2" - Pub().pub(topic, payload) + return {topic: payload} self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 72) @@ -1994,7 +1994,7 @@ def upgrade(topic: str, payload) -> None: # replace bmw soc module by no_module if payload.get("type") == "bmw": payload = NO_MODULE - Pub().pub(topic, payload) + return {topic: payload} self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 73) @@ -2017,7 +2017,7 @@ def upgrade(topic: str, payload) -> None: if payload.get("type") == "solax": if "version" not in payload["configuration"]: payload["configuration"].update({"version": "g3"}) - Pub().pub(topic, payload) + return {topic: payload} self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 75) @@ -2129,8 +2129,8 @@ def upgrade(topic: str, payload) -> Optional[dict]: component_topic): component_config = decode_payload(component_payload) if "counter" == component_config["type"]: - Pub().pub((f"openWB/system/device/{device_config['id']}/component/" - f"{component_config['id']}/simulation"), "") + return {(f"openWB/system/device/{device_config['id']}/component/" + f"{component_config['id']}/simulation"): ""} self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 80) @@ -2333,12 +2333,12 @@ def upgrade_datastore_86(self) -> None: def upgrade_datastore_87(self) -> None: def upgrade(topic: str, payload) -> None: - if (re.search("openWB/vehicle/template/charge_template/[0-9]+", topic) is not None or - re.search("openWB/vehicle/template/ev_template/[0-9]+", topic) is not None): + if (re.search("openWB/vehicle/template/charge_template/[0-9]+$", topic) is not None or + re.search("openWB/vehicle/template/ev_template/[0-9]+$", topic) is not None): payload = decode_payload(payload) index = int(get_index(topic)) payload.update({"id": index}) - Pub().pub(topic, payload) + return {topic: payload} self._loop_all_received_topics(upgrade) self.__update_topic("openWB/system/datastore_version", 88) @@ -2430,6 +2430,7 @@ def upgrade_datastore_93(self) -> None: # Pläne die keinen plans Key haben, id=None max_id = -1 none_id = False + modified_topics = {} for topic, payload in self.all_received_topics.items(): if re.search("openWB/vehicle/template/charge_template/[0-9]+$", topic) is not None: payload = decode_payload(payload) @@ -2444,8 +2445,7 @@ def upgrade_datastore_93(self) -> None: raise TypeError(f"Plan {plan} hat keinen Key 'id' und ist kein NoneType.") except KeyError: payload["chargemode"]["scheduled_charging"].update({"plans": []}) - self.all_received_topics[topic] = json.dumps(payload, ensure_ascii=False).encode("utf-8") - Pub().pub(f"openWB/set/vehicle/template/charge_template/{get_index(topic)}", payload) + modified_topics[f"openWB/set/vehicle/template/charge_template/{get_index(topic)}"] = payload if none_id: for topic, payload in self.all_received_topics.items(): if re.search("openWB/vehicle/template/charge_template/[0-9]+$", topic) is not None: @@ -2454,8 +2454,7 @@ def upgrade_datastore_93(self) -> None: if plan["id"] is None: plan["id"] = max_id + 1 max_id += 1 - self.all_received_topics[topic] = json.dumps(payload, ensure_ascii=False).encode("utf-8") - Pub().pub(f"openWB/set/vehicle/template/charge_template/{get_index(topic)}", payload) + modified_topics[f"openWB/vehicle/template/charge_template/{get_index(topic)}"] = payload max_id = -1 none_id = False @@ -2473,8 +2472,7 @@ def upgrade_datastore_93(self) -> None: raise TypeError(f"Plan {plan} hat keinen Key 'id' und ist kein NoneType.") except KeyError: payload["time_charging"].update({"plans": []}) - self.all_received_topics[topic] = json.dumps(payload, ensure_ascii=False).encode("utf-8") - Pub().pub(f"openWB/set/vehicle/template/charge_template/{get_index(topic)}", payload) + modified_topics[f"openWB/vehicle/template/charge_template/{get_index(topic)}"] = payload if none_id: for topic, payload in self.all_received_topics.items(): if re.search("openWB/vehicle/template/charge_template/[0-9]+$", topic) is not None: @@ -2483,8 +2481,7 @@ def upgrade_datastore_93(self) -> None: if plan["id"] is None: plan["id"] = max_id + 1 max_id += 1 - self.all_received_topics[topic] = json.dumps(payload, ensure_ascii=False).encode("utf-8") - Pub().pub(f"openWB/set/vehicle/template/charge_template/{get_index(topic)}", payload) + modified_topics[f"openWB/vehicle/template/charge_template/{get_index(topic)}"] = payload max_id = -1 none_id = False @@ -2502,8 +2499,7 @@ def upgrade_datastore_93(self) -> None: raise TypeError(f"Plan {plan} hat keinen Key 'id' und ist kein NoneType.") except KeyError: payload["autolock"].update({"plans": []}) - self.all_received_topics[topic] = json.dumps(payload, ensure_ascii=False).encode("utf-8") - Pub().pub(f"openWB/set/chargepoint/template/{get_index(topic)}", payload) + modified_topics[f"openWB/chargepoint/template/{get_index(topic)}"] = payload if none_id: for topic, payload in self.all_received_topics.items(): if re.search("openWB/chargepoint/template/[0-9]+$", topic) is not None: @@ -2512,6 +2508,48 @@ def upgrade_datastore_93(self) -> None: if plan["id"] is None: plan["id"] = max_id + 1 max_id += 1 - self.all_received_topics[topic] = json.dumps(payload, ensure_ascii=False).encode("utf-8") - Pub().pub(f"openWB/set/chargepoint/template/{get_index(topic)}", payload) + modified_topics[f"openWB/chargepoint/template/{get_index(topic)}"] = payload + for topic, payload in modified_topics.items(): + self.__update_topic(topic, payload) self.__update_topic("openWB/system/datastore_version", 94) + + def upgrade_datastore_94(self): + def upgrade(topic, payload): + ids = [] + if re.search("openWB/vehicle/template/charge_template/[0-9]+$", topic) is not None: + payload = decode_payload(payload) + for plan in payload["chargemode"]["scheduled_charging"]["plans"]: + if plan["id"] is not None: + ids.append(plan["id"]) + ids.sort() + unique_ids = set(ids) + if len(ids) != len(unique_ids): + max_id = decode_payload( + self.all_received_topics["openWB/command/max_id/charge_template_scheduled_plan"]) + for plan in payload["chargemode"]["scheduled_charging"]["plans"]: + try: + unique_ids.remove(plan["id"]) + except KeyError: + max_id += 1 + plan["id"] = max_id + return {topic: payload, "openWB/command/max_id/charge_template_scheduled_plan": max_id} + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 95) + + def upgrade_datastore_95(self) -> None: + def upgrade(topic: str, payload) -> Optional[dict]: + # Fix id in charge and ev templates + if ( + re.search("openWB/vehicle/template/charge_template/[0-9]+$", topic) is not None + or re.search("openWB/vehicle/template/ev_template/[0-9]+$", topic) is not None + ): + payload = decode_payload(payload) + topic_index = int(get_index(topic)) + if "id" not in payload or payload["id"] != topic_index: + log.error( + f"Fixing id in template {topic} from {payload.get('id')} to {topic_index}" + ) + payload["id"] = topic_index + return {topic: payload} + self._loop_all_received_topics(upgrade) + self.__update_topic("openWB/system/datastore_version", 96) diff --git a/packages/helpermodules/update_config_test.py b/packages/helpermodules/update_config_test.py index 79405ebd4f..3b2addd873 100644 --- a/packages/helpermodules/update_config_test.py +++ b/packages/helpermodules/update_config_test.py @@ -1,3 +1,7 @@ +import json +from pathlib import Path + +import pytest from helpermodules.update_config import UpdateConfig @@ -28,3 +32,24 @@ def test_remove_invalid_topics(mock_pub): assert len(mock_pub.method_calls) == 2 assert mock_pub.method_calls[0][1][0] == 'openWB/chargepoint/5/get/voltages' assert mock_pub.method_calls[1][1][0] == 'openWB/optional/int_display/theme' + + +@pytest.mark.parametrize("index_test_template, expected_index", [ + pytest.param(0, [2, 1], id="IDs korrekt"), + pytest.param(1, [0, 3], id="IDs gleich"), + pytest.param(2, [], id="keine Pläne"), +]) +def test_upgrade_datastore_94(index_test_template, expected_index): + update_con = UpdateConfig() + update_con.all_received_topics = {"openWB/command/max_id/charge_template_scheduled_plan": 2} + with open(Path(__file__).resolve().parents[0]/"upgrade_datastore_94.json", "r") as f: + test_data = f.read() + update_con.all_received_topics.update(json.loads(test_data)[index_test_template]) + + update_con.upgrade_datastore_94() + + plan_ids = [] + for plan in update_con.all_received_topics["openWB/vehicle/template/charge_template/0"]["chargemode"][ + "scheduled_charging"]["plans"]: + plan_ids.append(plan["id"]) + assert plan_ids == expected_index diff --git a/packages/helpermodules/upgrade_datastore_94.json b/packages/helpermodules/upgrade_datastore_94.json new file mode 100644 index 0000000000..11a3cd1c85 --- /dev/null +++ b/packages/helpermodules/upgrade_datastore_94.json @@ -0,0 +1,3 @@ +[{"openWB/vehicle/template/charge_template/0": {"name": "Standard-Lade-Profil", "prio": false, "load_default": false, "time_charging": {"active": false, "plans": []}, "chargemode": {"selected": "stop", "pv_charging": {"dc_min_current": 145, "dc_min_soc_current": 145, "min_soc_current": 10, "min_current": 0, "feed_in_limit": false, "min_soc": 0, "phases_to_use": 0, "phases_to_use_min_soc": 3, "limit": {"selected": "soc", "amount": 1000, "soc": 100}}, "scheduled_charging": {"plans": [{"active": true, "frequency": {"selected": "daily", "once": "2021-11-01", "weekly": [false, false, false, false, false, false, false]}, "current": 14, "dc_current": 145, "id": 2, "name": "neuer Zielladen-Plan", "limit": {"selected": "amount", "amount": 1000, "soc_limit": 90, "soc_scheduled": 80}, "time": "07: 00", "phases_to_use": 0, "phases_to_use_pv": 0, "et_active": false, "bidi_power": 10000, "bidi_charging_enabled": false}, {"active": true, "frequency": {"selected": "daily", "once": "2021-11-01", "weekly": [false, false, false, false, false, false, false]}, "current": 14, "dc_current": 145, "id": 1, "name": "neuer Zielladen-Plan", "limit": {"selected": "amount", "amount": 1000, "soc_limit": 90, "soc_scheduled": 80}, "time": "07:00", "phases_to_use": 0, "phases_to_use_pv": 0, "et_active": false, "bidi_power": 10000, "bidi_charging_enabled": false}]}, "instant_charging": {"current": 10, "dc_current": 145, "limit": {"selected": "none", "amount": 1000, "soc": 50}, "phases_to_use": 3}, "eco_charging": {"current": 6, "dc_current": 145, "limit": {"selected": "none", "amount": 1000, "soc": 50}, "max_price": 0.0002, "phases_to_use": 3}}, "id": 0}}, +{"openWB/vehicle/template/charge_template/0": {"name": "Standard-Lade-Profil", "prio": false, "load_default": false, "time_charging": {"active": false, "plans": []}, "chargemode": {"selected": "stop", "pv_charging": {"dc_min_current": 145, "dc_min_soc_current": 145, "min_soc_current": 10, "min_current": 0, "feed_in_limit": false, "min_soc": 0, "phases_to_use": 0, "phases_to_use_min_soc": 3, "limit": {"selected": "soc", "amount": 1000, "soc": 100}}, "scheduled_charging": {"plans": [{"active": true, "frequency": {"selected": "daily", "once": "2021-11-01", "weekly": [false, false, false, false, false, false, false]}, "current": 14, "dc_current": 145, "id": 0, "name": "neuer Zielladen-Plan", "limit": {"selected": "amount", "amount": 1000, "soc_limit": 90, "soc_scheduled": 80}, "time": "07: 00", "phases_to_use": 0, "phases_to_use_pv": 0, "et_active": false, "bidi_power": 10000, "bidi_charging_enabled": false}, {"active": true, "frequency": {"selected": "daily", "once": "2021-11-01", "weekly": [false, false, false, false, false, false, false]}, "current": 14, "dc_current": 145, "id": 0, "name": "neuer Zielladen-Plan", "limit": {"selected": "amount", "amount": 1000, "soc_limit": 90, "soc_scheduled": 80}, "time": "07:00", "phases_to_use": 0, "phases_to_use_pv": 0, "et_active": false, "bidi_power": 10000, "bidi_charging_enabled": false}]}, "instant_charging": {"current": 10, "dc_current": 145, "limit": {"selected": "none", "amount": 1000, "soc": 50}, "phases_to_use": 3}, "eco_charging": {"current": 6, "dc_current": 145, "limit": {"selected": "none", "amount": 1000, "soc": 50}, "max_price": 0.0002, "phases_to_use": 3}}, "id": 0}}, +{"openWB/vehicle/template/charge_template/0": {"name": "Standard-Lade-Profil", "prio": false, "load_default": false, "time_charging": {"active": false, "plans": []}, "chargemode": {"selected": "stop", "pv_charging": {"dc_min_current": 145, "dc_min_soc_current": 145, "min_soc_current": 10, "min_current": 0, "feed_in_limit": false, "min_soc": 0, "phases_to_use": 0, "phases_to_use_min_soc": 3, "limit": {"selected": "soc", "amount": 1000, "soc": 100}}, "scheduled_charging": {"plans": []}, "instant_charging": {"current": 10, "dc_current": 145, "limit": {"selected": "none", "amount": 1000, "soc": 50}, "phases_to_use": 3}, "eco_charging": {"current": 6, "dc_current": 145, "limit": {"selected": "none", "amount": 1000, "soc": 50}, "max_price": 0.0002, "phases_to_use": 3}}, "id": 0}}] \ No newline at end of file diff --git a/packages/helpermodules/utils/error_handling.py b/packages/helpermodules/utils/error_handling.py index 56e2a29bad..ee2e2810f8 100644 --- a/packages/helpermodules/utils/error_handling.py +++ b/packages/helpermodules/utils/error_handling.py @@ -11,6 +11,14 @@ CP_ERROR = ("Anhaltender Fehler beim Auslesen des Ladepunkts. Soll-Stromstärke, Lade- und Stecker-Status wird " "zurückgesetzt.") +INTERNAL_ERROR_HINT = ("Liebe Kunden, das Log ist zur Auswertung durch Support-Mitarbeiter der openWB GmbH gedacht. " + "Meldungen, die hier erscheinen können wie Fehlermeldungen aussehen, sind aber oft ganz normal. " + "Beispielsweise führen wir Abfragen der internen Hardware mehrere tausend mal pro Stunde aus " + "(wir gehen bis ans Limit der seriellen Kommunikation um eine möglichst feine Auflösung zu " + "erreichen), eine Abfrage-Fehlerquote von 1-2% ist dabei normal. Wirklich relevante " + "Fehlermeldungen erscheinen in der grafischen Nutzeroberfläche an prominenter Stelle. Bitte " + "belastet unseren Support nicht mit Fragen nach euch unbekannten Log-Meldungen.") + class ErrorTimerContext: def __init__(self, topic: str, exceeded_msg: str, timeout: int = 60, hide_exception: bool = False): @@ -28,12 +36,14 @@ def __exit__(self, exception_type, exception, exception_traceback) -> bool: if self.error_timestamp is None: self.error_timestamp = timecheck.create_timestamp() Pub().pub(self.topic, self.error_timestamp) - log.error(exception) if (self.hide_exception is False or timecheck.check_timestamp(self.error_timestamp, self.timeout + 10) is False): # Fehlermeldung als abgelaufen markieren, bevor die Exception gesetzt wird, mit der Exception werden # keine Werte mehr gepublished. return False + else: + log.error(f"{exception}\n{INTERNAL_ERROR_HINT}") + return True return True def error_counter_exceeded(self) -> bool: diff --git a/packages/main.py b/packages/main.py index 25fd3ca184..2fbd15b490 100755 --- a/packages/main.py +++ b/packages/main.py @@ -233,6 +233,11 @@ def handler_midnight(self): @__with_handler_lock(error_threshold=60) def handler_random_nightly(self): + log.warning("Display wird neu geladen.") # nur zur Info im Log + # chromium neu starten, um größere Auswirkungen eines Speicherlecks zu vermeiden + run_command.run_command([ + str(Path(__file__).resolve().parents[1] / "runs" / "update_local_display.sh"), "1" + ], process_exception=True) try: data.data.system_data["system"].thread_backup_and_send_to_cloud() except Exception: diff --git a/packages/modules/common/hardware_check.py b/packages/modules/common/hardware_check.py index b43d9200e6..01abc892da 100644 --- a/packages/modules/common/hardware_check.py +++ b/packages/modules/common/hardware_check.py @@ -20,8 +20,7 @@ METER_NO_SERIAL_NUMBER = ("Die Seriennummer des Zählers für das Ladelog kann nicht ausgelesen werden. Wenn Sie die " "Seriennummer für Abrechnungszwecke benötigen, wenden Sie sich bitte an unseren Support. Die " "Funktionalität wird dadurch nicht beeinträchtigt!") -EVSE_BROKEN = ("Auslesen der EVSE nicht möglich. Vermutlich ist die EVSE defekt oder hat eine unbekannte Modbus-ID. " - "(Fehlermeldung nur relevant, wenn diese auf der Startseite oder im Status angezeigt wird.)") +EVSE_BROKEN = "Auslesen der EVSE nicht möglich. Vermutlich ist die EVSE defekt oder hat eine unbekannte Modbus-ID. " def check_meter_values(counter_state: CounterState, fault_state: Optional[FaultState] = None) -> None: diff --git a/packages/modules/common/simcount/_simcounter.py b/packages/modules/common/simcount/_simcounter.py index d4dbd83069..956a131123 100644 --- a/packages/modules/common/simcount/_simcounter.py +++ b/packages/modules/common/simcount/_simcounter.py @@ -10,7 +10,9 @@ def __init__(self, device_id: int, component_id: int, prefix: str): self.prefix = "pv2" if prefix == "pv" and component_id != 1 else prefix self.data: Optional[SimCounterState] = None - def sim_count(self, power: float) -> Tuple[float, float]: + def sim_count(self, power: float, dc_power: Optional[float] = None) -> Tuple[float, float]: + if (self.prefix == "pv" or self.prefix == "pv2") and dc_power is not None and dc_power == 0: + power = 0 self.data = sim_count(power, self.topic, self.data, self.prefix) return self.data.imported, self.data.exported diff --git a/packages/modules/common/store/_chargepoint_internal.py b/packages/modules/common/store/_chargepoint_internal.py index d16e821742..b4f3cc5560 100644 --- a/packages/modules/common/store/_chargepoint_internal.py +++ b/packages/modules/common/store/_chargepoint_internal.py @@ -24,7 +24,7 @@ def update(self): pub_to_broker(f"{topic_prefix}/phases_in_use", self.state.phases_in_use, 2) pub_to_broker(f"{topic_prefix}/charge_state", self.state.charge_state, 2) pub_to_broker(f"{topic_prefix}/plug_state", self.state.plug_state, 2) - pub_to_broker(f"{topic_prefix}/rfid", self.state.rfid) + pub_to_broker(f"{topic_prefix}/vehicle_id", self.state.vehicle_id) pub_to_broker(f"{topic_prefix}/serial_number", self.state.serial_number) pub_to_broker(f"{topic_prefix}/evse_current", self.state.evse_current, 2) pub_to_broker(f"{topic_prefix}/max_evse_current", self.state.max_evse_current, 2) @@ -32,11 +32,11 @@ def update(self): pub_to_broker(f"{topic_prefix}/current_branch", self.state.current_branch) pub_to_broker(f"{topic_prefix}/current_commit", self.state.current_commit) if self.state.soc is not None: - pub_to_broker(f"{topic_prefix}/get/soc", self.state.soc) + pub_to_broker(f"{topic_prefix}/soc", self.state.soc) if self.state.soc_timestamp is not None: pub_to_broker(f"{topic_prefix}/soc_timestamp", self.state.soc_timestamp) if self.state.rfid_timestamp is not None: - pub_to_broker(f"{topic_prefix}/vehicle_id", self.state.vehicle_id) + pub_to_broker(f"{topic_prefix}/rfid", self.state.rfid) pub_to_broker(f"{topic_prefix}/rfid_timestamp", self.state.rfid_timestamp) diff --git a/packages/modules/devices/discovergy/discovergy/api.py b/packages/modules/devices/discovergy/discovergy/api.py index 978f3565bb..1dd6dcc5b0 100644 --- a/packages/modules/devices/discovergy/discovergy/api.py +++ b/packages/modules/devices/discovergy/discovergy/api.py @@ -5,7 +5,7 @@ def get_last_reading(session: Session, meter_id: str): values = session.get( - "https://api.discovergy.com/public/v1/last_reading", + "https://api.inexogy.com/public/v1/last_reading", params={"meterId": meter_id}, timeout=3 ).json()["values"] diff --git a/packages/modules/devices/discovergy/discovergy/api_test.py b/packages/modules/devices/discovergy/discovergy/api_test.py index 881f0ffd94..8fd2452d2b 100644 --- a/packages/modules/devices/discovergy/discovergy/api_test.py +++ b/packages/modules/devices/discovergy/discovergy/api_test.py @@ -71,7 +71,7 @@ @pytest.fixture def mock_discovery_response(requests_mock): def do_mock(json_str: str): - requests_mock.get("https://api.discovergy.com/public/v1/last_reading?meterId=someMeterId", text=json_str) + requests_mock.get("https://api.inexogy.com/public/v1/last_reading?meterId=someMeterId", text=json_str) return do_mock diff --git a/packages/modules/devices/nibe/nibe/counter.py b/packages/modules/devices/nibe/nibe/counter.py index 1c16e00b51..8fc69323a9 100644 --- a/packages/modules/devices/nibe/nibe/counter.py +++ b/packages/modules/devices/nibe/nibe/counter.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 from typing import TypedDict, Any +from pymodbus.constants import Endian + from modules.common.abstract_device import AbstractCounter from modules.common.component_state import CounterState from modules.common.component_type import ComponentDescriptor @@ -29,7 +31,7 @@ def initialize(self) -> None: def update(self): unit = self.component_config.configuration.modbus_id - power = self.client.read_input_registers(2166, ModbusDataType.UINT_32, unit=unit) / 10 + power = self.client.read_input_registers(2166, ModbusDataType.UINT_32, wordorder=Endian.Little, unit=unit) imported, exported = self.sim_counter.sim_count(power) counter_state = CounterState( diff --git a/packages/modules/devices/sungrow/sungrow/device.py b/packages/modules/devices/sungrow/sungrow/device.py index b9becf32a5..69e5ed1e3a 100644 --- a/packages/modules/devices/sungrow/sungrow/device.py +++ b/packages/modules/devices/sungrow/sungrow/device.py @@ -29,6 +29,7 @@ def create_inverter_component(component_config: SungrowInverterSetup): return SungrowInverter(component_config, device_config=device_config, client=client) def update_components(components: Iterable[Union[SungrowBat, SungrowCounter, SungrowInverter]]): + pv_power = 0 nonlocal client with client: for component in components: diff --git a/packages/modules/devices/sungrow/sungrow/inverter.py b/packages/modules/devices/sungrow/sungrow/inverter.py index a934e04315..db2d5a3807 100644 --- a/packages/modules/devices/sungrow/sungrow/inverter.py +++ b/packages/modules/devices/sungrow/sungrow/inverter.py @@ -50,7 +50,7 @@ def update(self) -> float: currents = self.__tcp_client.read_input_registers(5021, [ModbusDataType.INT_16]*3, unit=unit) currents = [value * -0.1 for value in currents] - imported, exported = self.sim_counter.sim_count(power) + imported, exported = self.sim_counter.sim_count(power, dc_power) inverter_state = InverterState( power=power, diff --git a/packages/modules/internal_chargepoint_handler/chargepoint_module.py b/packages/modules/internal_chargepoint_handler/chargepoint_module.py index 74eaf00d46..8b34b021d0 100644 --- a/packages/modules/internal_chargepoint_handler/chargepoint_module.py +++ b/packages/modules/internal_chargepoint_handler/chargepoint_module.py @@ -1,9 +1,12 @@ import logging import time +from helpermodules.broker import BrokerClient from helpermodules.logger import ModifyLoglevelContext +from helpermodules.utils import run_command from helpermodules.utils.error_handling import CP_ERROR, ErrorTimerContext +from helpermodules.utils.topic_parser import decode_payload from modules.common.abstract_chargepoint import AbstractChargepoint from modules.common.component_context import SingleComponentUpdateContext from modules.common.component_state import ChargepointState @@ -42,7 +45,6 @@ def __init__(self, local_charge_point_num: int, hide_exception=True) self.client_error_context.error_timestamp = internal_cp.get.error_timestamp self.old_plug_state = False - self.old_phases_in_use = 0 self.old_chargepoint_state = ChargepointState(plug_state=False, charge_state=False, imported=None, @@ -65,6 +67,20 @@ def __init__(self, local_charge_point_num: int, self.current_branch = SubData.system_data["system"].data["current_branch"] self.current_commit = SubData.system_data["system"].data["current_commit"] + if float(run_command.run_command(["cat", "/proc/uptime"]).split(" ")[0]) < 180: + self.perform_phase_switch(1, 4) + self.old_phases_in_use = 1 + else: + def on_connect(client, userdata, flags, rc): + client.subscribe(f"openWB/internal_chargepoint/{self.local_charge_point_num}/get/phases_in_use") + + def on_message(client, userdata, message): + self.old_phases_in_use = decode_payload(message.payload) + + self.old_phases_in_use = None + BrokerClient(f"subscribeInternalCp{self.local_charge_point_num}", + on_connect, on_message).start_finite_loop() + def set_current(self, current: float) -> None: with SingleComponentUpdateContext(self.fault_state, update_always=False): formatted_current = round(current*100) if self._precise_current else round(current) diff --git a/packages/modules/web_themes/colors/source/src/components/mqttViewer/mqttClient.ts b/packages/modules/web_themes/colors/source/src/components/mqttViewer/mqttClient.ts index 6113a764f4..16f5819884 100755 --- a/packages/modules/web_themes/colors/source/src/components/mqttViewer/mqttClient.ts +++ b/packages/modules/web_themes/colors/source/src/components/mqttViewer/mqttClient.ts @@ -10,7 +10,7 @@ import { type QoS } from 'mqtt-packet' const defaultQoS: QoS = 0 const mqttConnection = { host: location.hostname, - port: location.protocol == 'https:' ? 443 : 80, + port: parseInt(location.port) || (location.protocol == 'https:' ? 443 : 80), endpoint: '/ws', protocol: (location.protocol == 'https:' ? 'wss' : 'ws') as MqttProtocol, clean: true, @@ -20,8 +20,6 @@ const mqttConnection = { .toString(36) .replace(/[^a-z]+/g, '') .substring(0, 6), - username: 'openWB', - password: 'openWB', } const subscription = { topic: '', diff --git a/packages/modules/web_themes/koala/source/src/components/BaseCarousel.vue b/packages/modules/web_themes/koala/source/src/components/BaseCarousel.vue index e8dce0cbe8..15659bf6e7 100644 --- a/packages/modules/web_themes/koala/source/src/components/BaseCarousel.vue +++ b/packages/modules/web_themes/koala/source/src/components/BaseCarousel.vue @@ -34,7 +34,14 @@ -