Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/control/chargelog/chargelog.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from control.chargemode import Chargemode
from modules.common.component_type import ComponentType
from helpermodules.utils.json_file_handler import write_and_check
from helpermodules import timecheck
import copy
Expand Down Expand Up @@ -32,7 +33,7 @@
# "id": 1,
# "name": "Auto",
# "chargemode": instant_charging,
# "prio": False,
# "prio": 2,
# "rfid": "123"
# "soc_at_start": 50,
# "soc_at_end": 60,
Expand Down Expand Up @@ -121,7 +122,8 @@ def collect_data(chargepoint):
log_data.soc_at_start = get_value_or_default(lambda: charging_ev.data.get.soc)

log_data.ev = get_value_or_default(lambda: chargepoint.data.set.charging_ev_data.num, 0)
log_data.prio = get_value_or_default(lambda: chargepoint.data.control_parameter.prio, False)
log_data.prio = get_value_or_default(
lambda: data.data.counter_all_data.get_loadmanagement_priority(ComponentType.VEHICLE, log_data.ev))
log_data.rfid = get_value_or_default(lambda: chargepoint.data.set.rfid)
log_data.odometer = get_value_or_default(lambda: charging_ev.data.get.odometer)
log_data.imported_since_mode_switch = get_value_or_default(
Expand Down
36 changes: 36 additions & 0 deletions packages/control/chargelog/priority_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from unittest.mock import Mock

import pytest

from control import data
from control.chargelog import chargelog
from control.chargepoint.chargepoint import Chargepoint
from control.ev.ev import Ev


@pytest.mark.parametrize("entries, expected", [
([{"type": "vehicle", "id": 9}], 1),
([{"type": "consumer", "id": 9}, {"type": "vehicle", "id": 9}], 2),
([{"type": "consumer", "id": 0}, {"type": "group", "children": [
{"type": "vehicle", "id": 1}, {"type": "vehicle", "id": 9}]}], 2),
([{"type": "vehicle", "id": 1}], None),
])
def test_collect_data_logs_shared_priority_rank(entries, expected, monkeypatch):
data.data_init(Mock())
data.data.counter_all_data.data.get.loadmanagement_prios = entries
cp = Chargepoint(4, None)
vehicle = Ev(9)
cp.data.set.charging_ev_data = vehicle
cp.data.get.plug_state = True
cp.data.get.charge_state = True
cp.data.get.imported = 1000
cp.data.get.exported = 0

chargelog.collect_data(cp)

assert cp.data.set.log.ev == 9
assert cp.data.set.log.prio == expected
assert cp.data.set.log.prio is None or type(cp.data.set.log.prio) is int
monkeypatch.setattr(chargelog, "calc_energy_costs", Mock())
entry = chargelog._create_entry(cp, vehicle)
assert entry["vehicle"]["prio"] == expected
10 changes: 0 additions & 10 deletions packages/control/chargelog/process_chargelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,6 @@ def get_log_data(request: Dict):
(str(entry["vehicle"]["chargemode"]), str(request["filter"]["vehicle"]["chargemode"]))
)
continue
if (
"prio" in request["filter"]["vehicle"] and
request["filter"]["vehicle"]["prio"] is not entry["vehicle"]["prio"]
):
log.debug(
"Verwerfe Eintrag wegen Priorität: %s != %s" %
(str(entry["vehicle"]["prio"]), str(request["filter"]["vehicle"]["prio"]))
)
continue

# wenn wir hier ankommen, passt der Eintrag zum Filter
log_data["entries"].append(entry)
log_data["totals"] = get_totals_of_filtered_log_data(log_data)
Expand Down
2 changes: 1 addition & 1 deletion packages/control/chargepoint/chargepoint_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class Log:
timestamp_start_charging: Optional[float] = None
timestamp_mode_switch: Optional[float] = None
ev: int = -1
prio: bool = False
prio: Optional[int] = None
rfid: Optional[str] = None
serial_number: Optional[str] = None
soc_at_start: Optional[int] = None
Expand Down
2 changes: 2 additions & 0 deletions packages/control/counter_all/counter_all_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ def update_linked_counter_hierarchy(self,


class LoadmanagementPrioProtocol(Protocol):
def get_loadmanagement_priority(self, type: ComponentType, id: int) -> Optional[int]: ...

@property
def data(self) -> CounterAllData: ...
def add_loadmanagement_prio_item(self, type: ComponentType, id: int) -> None: ...
Expand Down
9 changes: 8 additions & 1 deletion packages/control/counter_all/loadmanagement_prio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Dict, Generator, List, Tuple
from typing import Dict, Generator, List, Optional, Tuple

from control.chargepoint.chargepoint import Chargepoint
from control.consumer.usage import ConsumerUsage, NOT_CONTROLLED
Expand All @@ -11,6 +11,13 @@


class LoadmanagementPrioMixin:
def get_loadmanagement_priority(self: LoadmanagementPrioProtocol,
type: ComponentType, id: int) -> Optional[int]:
for rank, entry in enumerate(self.data.get.loadmanagement_prios, start=1):
if self._has_loadmanagement_prio_item(type, id, [entry]):
return rank
return None

def add_loadmanagement_prio_item(self: LoadmanagementPrioProtocol, type: ComponentType, id: int) -> None:
self.data.get.loadmanagement_prios.append({"type": type.value, "id": id})

Expand Down
104 changes: 104 additions & 0 deletions packages/helpermodules/chargelog_priority_migration_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import json
from unittest.mock import Mock

import pytest

from helpermodules import update_config
from helpermodules.update_config import UpdateConfig


def make_update_config(tmp_path):
config = UpdateConfig()
config.base_path = tmp_path
config.all_received_topics = {
"openWB/system/file_operation_version": [0],
}
return config


def test_charge_log_priority_migration_preserves_numbers_and_is_idempotent(tmp_path, monkeypatch):
config = make_update_config(tmp_path)
directory = tmp_path / "data" / "charge_log"
directory.mkdir(parents=True)
entries = [
{"vehicle": {"prio": value}, "data": {"costs": 1.23}}
for value in (True, False, 1, 2, 7, None, "1")
] + [{"vehicle": {}}, {}, {"vehicle": None}]
path = directory / "202610.json"
path.write_text(json.dumps(entries))
untouched = directory / "202609.json"
untouched.write_text('[{"vehicle": {"prio": 3}}]')
original_untouched = untouched.read_bytes()

config.upgrade_file_operation_1()

migrated = json.loads(path.read_text())
assert [entry["vehicle"]["prio"] for entry in migrated[:7]] == [1, 2, 1, 2, 7, None, "1"]
assert all(type(entry["vehicle"]["prio"]) is int for entry in migrated[:5])
assert all(entry["data"] == {"costs": 1.23} for entry in migrated[:7])
assert migrated[7:] == entries[7:]
assert untouched.read_bytes() == original_untouched
assert config.all_received_topics["openWB/system/file_operation_version"] == [0, 1]

writer = Mock(side_effect=AssertionError("Bereits migrierte Dateien dürfen nicht erneut geschrieben werden."))
monkeypatch.setattr(update_config, "write_and_check", writer)
config.upgrade_file_operation_1()
writer.assert_not_called()


def test_charge_log_priority_migration_without_files(tmp_path):
config = make_update_config(tmp_path)
config.upgrade_file_operation_1()
assert config.all_received_topics["openWB/system/file_operation_version"] == [0, 1]


def test_charge_log_priority_migration_runs_once_via_regular_file_upgrade(tmp_path, monkeypatch):
config = make_update_config(tmp_path)
config.all_received_topics["openWB/system/log_data_ready"] = True
directory = tmp_path / "data" / "charge_log"
directory.mkdir(parents=True)
path = directory / "202610.json"
path.write_text('[{"vehicle": {"prio": true}}]')
migration = Mock(wraps=config.upgrade_file_operation_1)
monkeypatch.setattr(config, "upgrade_file_operation_1", migration)

config._UpdateConfig__solve_breaking_changes_filesystem()
config._UpdateConfig__solve_breaking_changes_filesystem()

migration.assert_called_once_with()
assert type(json.loads(path.read_text())[0]["vehicle"]["prio"]) is int
assert config.all_received_topics["openWB/system/file_operation_version"] == [0, 1]


@pytest.mark.parametrize("content", ["{broken", '{"entries": []}'])
def test_charge_log_priority_migration_retries_failed_files(tmp_path, content):
config = make_update_config(tmp_path)
directory = tmp_path / "data" / "charge_log"
directory.mkdir(parents=True)
path = directory / "202610.json"
path.write_text(content)

with pytest.raises(RuntimeError):
config.upgrade_file_operation_1()

assert path.read_text() == content
assert config.all_received_topics["openWB/system/file_operation_version"] == [0]
path.write_text('[{"vehicle": {"prio": false}}]')
config.upgrade_file_operation_1()
assert json.loads(path.read_text())[0]["vehicle"]["prio"] == 2
assert config.all_received_topics["openWB/system/file_operation_version"] == [0, 1]


def test_charge_log_priority_migration_detects_failed_write_of_true_to_one(tmp_path, monkeypatch):
config = make_update_config(tmp_path)
directory = tmp_path / "data" / "charge_log"
directory.mkdir(parents=True)
path = directory / "202610.json"
path.write_text('[{"vehicle": {"prio": true}}]')
monkeypatch.setattr(update_config, "write_and_check", Mock())

with pytest.raises(RuntimeError):
config.upgrade_file_operation_1()

assert json.loads(path.read_text())[0]["vehicle"]["prio"] is True
assert config.all_received_topics["openWB/system/file_operation_version"] == [0]
2 changes: 1 addition & 1 deletion packages/helpermodules/data_migration/data_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def conv_1_9_datetimes(datetime_str):
"id": vehicle_id,
"name": vehicle_name,
"chargemode": chargemode,
"prio": False,
"prio": 2,
"rfid": rfid
},
"time":
Expand Down
31 changes: 30 additions & 1 deletion packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class UpdateConfig:

DATASTORE_VERSION = 154

FILE_OPERATION_VERSION = 0
FILE_OPERATION_VERSION = 1

valid_topic = [
"^openWB/bat/config/bat_control_activated$",
Expand Down Expand Up @@ -996,6 +996,35 @@ def upgrade_file_operation_0(self) -> None:
_generate_totals_subprocess = None
log.exception("Fehler beim Starten des generate_totals_subprocess.")

def upgrade_file_operation_1(self) -> None:
"""Boolesche Fahrzeug-Priorität in historischen Ladeprotokollen in Rang 1/2 umwandeln."""
failed_files = []
for file_path in sorted((self.base_path / "data" / "charge_log").glob("*.json")):
try:
with file_path.open(encoding="utf-8") as file:
entries = json.load(file)
if not isinstance(entries, list):
raise ValueError("Das Ladeprotokoll enthält keine Liste.")
changed = False
for entry in entries:
vehicle = entry.get("vehicle") if isinstance(entry, dict) else None
if isinstance(vehicle, dict) and isinstance(vehicle.get("prio"), bool):
vehicle["prio"] = 1 if vehicle["prio"] else 2
changed = True
if changed:
write_and_check(str(file_path), entries)
with file_path.open(encoding="utf-8") as file:
written_entries = json.load(file)
# bool und int müssen unterschieden werden (True == 1 in Python).
if json.dumps(written_entries, sort_keys=True) != json.dumps(entries, sort_keys=True):
raise ValueError("Die Prioritäts-Migration wurde nicht vollständig gespeichert.")
except (OSError, ValueError):
log.exception("Fehler bei der Prioritäts-Migration des Ladeprotokolls %s", file_path)
failed_files.append(str(file_path))
if failed_files:
raise RuntimeError(f"Ladeprotokoll-Prioritäten konnten nicht migriert werden: {failed_files}")
self._append_file_operation_version(1)

def _loop_all_received_topics(self, callback) -> None:
modified_topics = {}
for topic, payload in self.all_received_topics.items():
Expand Down
Loading