diff --git a/packages/control/chargelog/chargelog.py b/packages/control/chargelog/chargelog.py index 6bbb946b8a..f6924b1fa1 100644 --- a/packages/control/chargelog/chargelog.py +++ b/packages/control/chargelog/chargelog.py @@ -2,9 +2,8 @@ from enum import Enum import json import logging -import math import pathlib -from typing import Dict, List, Union +from typing import Dict, List from control import data from dataclass_utils import asdict @@ -51,6 +50,9 @@ def collect_data(chargepoint): log_data.chargemode_log_entry = "time_charging" else: log_data.chargemode_log_entry = chargepoint.data.control_parameter.chargemode.value + log_data.ev = chargepoint.data.set.charging_ev_data.num + log_data.prio = chargepoint.data.control_parameter.prio + log_data.rfid = chargepoint.data.set.rfid log_data.imported_since_mode_switch = chargepoint.data.get.imported - log_data.imported_at_mode_switch log.debug(f"imported_since_mode_switch {log_data.imported_since_mode_switch} " f"counter {chargepoint.data.get.imported}") @@ -62,9 +64,27 @@ def collect_data(chargepoint): log.exception("Fehler im Ladelog-Modul") -def save_data(chargepoint, charging_ev, immediately: bool = True, reset: bool = False): - """ json-Objekt für den Log-Eintrag erstellen, an die Datei anhängen und die Daten, die sich auf den Ladevorgang - beziehen, löschen. +def save_interim_data(chargepoint, charging_ev, immediately: bool = True): + try: + log_data = chargepoint.data.set.log + # Es wurde noch nie ein Auto zugeordnet + if charging_ev == -1: + return + if log_data.timestamp_start_charging is None: + # Die Daten wurden schon erfasst. + return + if not immediately: + if chargepoint.data.get.power != 0: + # Das Fahrzeug hat die Ladung noch nicht beendet. Der Logeintrag wird später erstellt. + return + save_data(chargepoint, charging_ev, immediately) + chargepoint.reset_log_data_chargemode_switch() + except Exception: + log.exception("Fehler im Ladelog-Modul") + + +def save_and_reset_data(chargepoint, charging_ev, immediately: bool = True): + """nach dem Abstecken Log-Eintrag erstellen und alle Log-Daten zurücksetzen. Parameter --------- @@ -73,84 +93,106 @@ def save_data(chargepoint, charging_ev, immediately: bool = True, reset: bool = charging_ev: class EV, das an diesem Ladepunkt lädt. (Wird extra übergeben, da es u.U. noch nicht zugewiesen ist und nur die Nummer aus dem Broker in der LP-Klasse hinterlegt ist.) - reset: bool - Wenn die Daten komplett zurückgesetzt werden, wird nicht der Zwischenzählerstand für - imported_at_mode_switch notiert. Sonst schon, damit zwischen save_data und dem nächsten collect_data keine - Daten verloren gehen. + immediately: bool + Soll sofort ein Eintrag erstellt werden oder gewartet werden, bis die Ladung beendet ist. """ try: - log_data = chargepoint.data.set.log - # Es wurde noch nie ein Auto zugeordnet if charging_ev == -1: - return - if log_data.timestamp_start_charging is None: - # Die Daten wurden schon erfasst. + # Es wurde noch nie ein Auto zugeordnet. return if not immediately: if chargepoint.data.get.power != 0: + # Das Fahrzeug hat die Ladung noch nicht beendet. Der Logeintrag wird später erstellt. return - # Daten vor dem Speichern nochmal aktualisieren, auch wenn nicht mehr geladen wird. - log_data.imported_since_plugged = chargepoint.data.get.imported - log_data.imported_at_plugtime - log_data.imported_since_mode_switch = chargepoint.data.get.imported - log_data.imported_at_mode_switch - log_data.range_charged = log_data.imported_since_mode_switch / charging_ev.ev_template.data.average_consump*100 - log_data.time_charged, duration = timecheck.get_difference_to_now(log_data.timestamp_start_charging) - power = 0 - if duration > 0: - power = log_data.imported_since_mode_switch / duration - calculate_charge_cost(chargepoint, True) - costs = log_data.costs - new_entry = { - "chargepoint": - { - "id": chargepoint.num, - "name": chargepoint.data.config.name, - }, - "vehicle": - { - "id": charging_ev.num, - "name": charging_ev.data.name, - "chargemode": log_data.chargemode_log_entry, - "prio": chargepoint.data.control_parameter.prio, - "rfid": chargepoint.data.set.rfid - }, - "time": - { - "begin": log_data.timestamp_start_charging, - "end": timecheck.create_timestamp(), - "time_charged": log_data.time_charged - }, - "data": - { - "range_charged": truncate(log_data.range_charged, 2), - "imported_since_mode_switch": truncate(log_data.imported_since_mode_switch, 2), - "imported_since_plugged": truncate(log_data.imported_since_plugged, 2), - "power": truncate(power, 2), - "costs": truncate(costs, 2) - } + if chargepoint.data.set.log.timestamp_start_charging: + # Die Daten wurden noch nicht erfasst. + save_data(chargepoint, charging_ev, immediately) + chargepoint.reset_log_data() + except Exception: + log.exception("Fehler im Ladelog-Modul") + + +def save_data(chargepoint, charging_ev, immediately: bool = True): + """ json-Objekt für den Log-Eintrag erstellen, an die Datei anhängen und die Daten, die sich auf den Ladevorgang + beziehen, löschen. + + Parameter + --------- + chargepoint: class + Ladepunkt + charging_ev: class + EV, das an diesem Ladepunkt lädt. (Wird extra übergeben, da es u.U. noch nicht zugewiesen ist und nur die + Nummer aus dem Broker in der LP-Klasse hinterlegt ist.) + reset: bool + Wenn die Daten komplett zurückgesetzt werden, wird nicht der Zwischenzählerstand für + imported_at_mode_switch notiert. Sonst schon, damit zwischen save_data und dem nächsten collect_data keine + Daten verloren gehen. + """ + log_data = chargepoint.data.set.log + # Daten vor dem Speichern nochmal aktualisieren, auch wenn nicht mehr geladen wird. + log_data.imported_since_plugged = chargepoint.data.get.imported - log_data.imported_at_plugtime + log_data.imported_since_mode_switch = chargepoint.data.get.imported - log_data.imported_at_mode_switch + log_data.range_charged = log_data.imported_since_mode_switch / charging_ev.ev_template.data.average_consump*100 + log_data.time_charged, duration = timecheck.get_difference_to_now(log_data.timestamp_start_charging) + power = 0 + if duration > 0: + power = log_data.imported_since_mode_switch / duration + calculate_charge_cost(chargepoint, True) + costs = log_data.costs + new_entry = { + "chargepoint": + { + "id": chargepoint.num, + "name": chargepoint.data.config.name, + }, + "vehicle": + { + "id": log_data.ev, + "name": _get_ev_name(log_data.ev), + "chargemode": log_data.chargemode_log_entry, + "prio": log_data.prio, + "rfid": log_data.rfid + }, + "time": + { + "begin": log_data.timestamp_start_charging, + "end": timecheck.create_timestamp(), + "time_charged": log_data.time_charged + }, + "data": + { + "range_charged": round(log_data.range_charged, 2), + "imported_since_mode_switch": round(log_data.imported_since_mode_switch, 2), + "imported_since_plugged": round(log_data.imported_since_plugged, 2), + "power": round(power, 2), + "costs": round(costs, 2) } + } - # json-Objekt in Datei einfügen - (_get_parent_file() / "data"/"charge_log").mkdir(mode=0o755, parents=True, exist_ok=True) - 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) - 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) - with open(filepath, "w", encoding="utf-8") as json_file: - json.dump(content, json_file) - log.debug(f"Neuer Ladelog-Eintrag: {new_entry}") - - chargepoint.reset_log_data_regarding_chargemode(reset) - Pub().pub(f"openWB/set/chargepoint/{chargepoint.num}/set/log", asdict(chargepoint.data.set.log)) + # json-Objekt in Datei einfügen + (_get_parent_file() / "data"/"charge_log").mkdir(mode=0o755, parents=True, exist_ok=True) + 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) + 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) + with open(filepath, "w", encoding="utf-8") as json_file: + json.dump(content, json_file) + log.debug(f"Neuer Ladelog-Eintrag: {new_entry}") + + +def _get_ev_name(ev: int) -> str: + try: + return data.data.ev_data[f"ev{ev}"].data.name except Exception: - log.exception("Fehler im Ladelog-Modul") + return "" def get_log_data(request: Dict): @@ -255,53 +297,10 @@ def get_log_data(request: Dict): return log_data -def reset_data(chargepoint, charging_ev, immediately: bool = True): - """nach dem Abstecken Log-Eintrag erstellen und alle Log-Daten zurücksetzen. - - Parameter - --------- - chargepoint: class - Ladepunkt - charging_ev: class - EV, das an diesem Ladepunkt lädt. (Wird extra übergeben, da es u.U. noch nicht zugewiesen ist und nur die - Nummer aus dem Broker in der LP-Klasse hinterlegt ist.) - immediately: bool - Soll sofort ein Eintrag erstellt werden oder gewartet werden, bis die Ladung beendet ist. - """ - try: - if charging_ev == -1: - # Es wurde noch nie ein Auto zugeordnet. - return - if not immediately: - if chargepoint.data.get.power != 0: - return - save_data(chargepoint, charging_ev, immediately, reset=True) - except Exception: - log.exception("Fehler im Ladelog-Modul") - - -def truncate(number: Union[int, float], decimals: int = 0): - """ - Returns a value truncated to a specific number of decimal places. - """ - try: - if not isinstance(decimals, int): - raise TypeError("decimal places must be an integer.") - elif decimals < 0: - raise ValueError("decimal places has to be 0 or more.") - elif decimals == 0: - return math.trunc(number) - - factor = 10.0 ** decimals - return math.trunc(number * factor) / factor - except Exception: - log.exception("Fehler im Ladelog-Modul") - - def calculate_charge_cost(cp, create_log_entry: bool = False): content = get_todays_daily_log() try: - if cp.data.set.log.imported_since_plugged != 0: + if cp.data.set.log.imported_since_plugged != 0 and cp.data.set.log.imported_since_mode_switch != 0: reference = _get_reference_position(cp, create_log_entry) reference_time = get_reference_time(cp, reference) reference_entry = _get_reference_entry(content["entries"], reference_time) @@ -328,6 +327,7 @@ def calculate_charge_cost(cp, create_log_entry: bool = False): cp.data.set.log.costs += _calc(power_source_entry["power_source"], charged_energy, (data.data.optional_data.et_module is not None)) + Pub().pub(f"openWB/set/chargepoint/{cp.num}/set/log", asdict(cp.data.set.log)) except Exception: log.exception(f"Fehler beim Berechnen der Ladekosten für Ladepunkt {cp.num}") diff --git a/packages/control/chargelog/chargelog_test.py b/packages/control/chargelog/chargelog_test.py index 404227cffa..b441f32f0e 100644 --- a/packages/control/chargelog/chargelog_test.py +++ b/packages/control/chargelog/chargelog_test.py @@ -145,6 +145,7 @@ def test_calculate_charge_cost(monkeypatch): # setup data.data.cp_data["cp3"].data.set.log.timestamp_start_charging = "11/01/2023, 08:12:40" data.data.cp_data["cp3"].data.set.log.imported_since_plugged = 1000 + data.data.cp_data["cp3"].data.set.log.imported_since_mode_switch = 1000 # Mock today() to values in log-file datetime_mock = MagicMock(wraps=datetime.datetime) # Thu Nov 02 2023 07:00:51 diff --git a/packages/control/chargepoint/chargepoint.py b/packages/control/chargepoint/chargepoint.py index 84e19f02f5..52c54a3096 100644 --- a/packages/control/chargepoint/chargepoint.py +++ b/packages/control/chargepoint/chargepoint.py @@ -122,6 +122,9 @@ class Log: range_charged: float = 0 time_charged: str = "00:00" timestamp_start_charging: Optional[str] = None + ev: int = -1 + prio: bool = False + rfid: Optional[str] = None def connected_vehicle_factory() -> ConnectedVehicle: @@ -420,7 +423,7 @@ def _process_charge_stop(self) -> None: self.data.set.manual_lock = True Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/manual_lock", True) # Ev wurde noch nicht aktualisiert. - chargelog.reset_data(self, data.data.ev_data["ev"+str(self.data.set.charging_ev_prev)]) + 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 Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/charging_ev_prev", self.data.set.charging_ev_prev) @@ -484,14 +487,18 @@ def remember_previous_values(self): self.data.set.plug_state_prev = self.data.get.plug_state Pub().pub("openWB/set/chargepoint/"+str(self.num)+"/set/plug_state_prev", self.data.set.plug_state_prev) - def reset_log_data_regarding_chargemode(self, reset: bool = False) -> None: + def reset_log_data_chargemode_switch(self) -> None: reset_log = Log() - if reset is False: - # Wenn ein Zwischeneintrag, zB bei Wechsel des Lademodus, erstellt wird, Zählerstände nicht verwerfen. - reset_log.imported_at_mode_switch = self.data.get.imported - reset_log.imported_at_plugtime = self.data.set.log.imported_at_plugtime - reset_log.imported_since_plugged = self.data.set.log.imported_since_plugged + # Wenn ein Zwischeneintrag, zB bei Wechsel des Lademodus, erstellt wird, Zählerstände nicht verwerfen. + reset_log.imported_at_mode_switch = self.data.get.imported + reset_log.imported_at_plugtime = self.data.set.log.imported_at_plugtime + reset_log.imported_since_plugged = self.data.set.log.imported_since_plugged self.data.set.log = reset_log + Pub().pub(f"openWB/set/chargepoint/{self.num}/set/log", asdict(self.data.set.log)) + + def reset_log_data(self) -> None: + self.data.set.log = Log() + Pub().pub(f"openWB/set/chargepoint/{self.num}/set/log", asdict(self.data.set.log)) def prepare_cp(self) -> Tuple[int, Optional[str]]: try: @@ -902,7 +909,7 @@ def update(self, ev_list: Dict[str, Ev]) -> None: # Ein Eintrag muss nur erstellt werden, wenn vorher schon geladen wurde und auch danach noch # geladen werden soll. if charging_ev.chargemode_changed and self.data.get.charge_state and state: - chargelog.save_data(self, charging_ev) + chargelog.save_interim_data(self, charging_ev) # Wenn die Nachrichten gesendet wurden, EV wieder löschen, wenn das EV im Algorithmus nicht # berücksichtigt werden soll. diff --git a/packages/control/process.py b/packages/control/process.py index 92ff1f2478..bf34f8579f 100644 --- a/packages/control/process.py +++ b/packages/control/process.py @@ -37,7 +37,7 @@ def process_algorithm_results(self) -> None: else: # LP, an denen nicht geladen werden darf if cp.data.set.charging_ev_prev != -1: - chargelog.save_data( + chargelog.save_interim_data( cp, data.data.ev_data ["ev" + str(cp.data.set.charging_ev_prev)], immediately=False) diff --git a/packages/main.py b/packages/main.py index c7a052311f..6f550f1811 100755 --- a/packages/main.py +++ b/packages/main.py @@ -166,7 +166,9 @@ def schedule_jobs(): [schedule.every().minute.at(f":{i:02d}").do(smarthome_handler).tag("algorithm") for i in range(0, 60, 5)] [schedule.every().hour.at(f":{i:02d}").do(handler.handler5Min) for i in range(0, 60, 5)] [schedule.every().hour.at(f":{i:02d}").do(handler.handler5MinAlgorithm).tag("algorithm") for i in range(0, 60, 5)] - schedule.every().hour.do(handler.handler_hour).tag("algorithm") + [schedule.every().day.at(f"{i:02d}:00").do(handler.handler_hour).tag("algorithm") for i in range(0, 24, 1)] + # every().hour ruft nicht jede Stunde den Handler auf. + # schedule.every().hour.do(handler.handler_hour).tag("algorithm") schedule.every().day.at("00:00:00").do(handler.handler_midnight).tag("algorithm") schedule.every().day.at(f"0{randrange(0, 5)}:{randrange(0, 59):02d}:{randrange(0, 59):02d}").do( handler.handler_random_nightly)