Skip to content
Merged
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
30 changes: 23 additions & 7 deletions packages/control/chargelog/chargelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from helpermodules.measurement_logging.process_log_calculation import (
FILE_ERRORS, CalculationType, _analyse_energy_source, _process_entries, get_totals)

from helpermodules.measurement_logging.process_log_entry_builder import create_entry

# alte Daten: Startzeitpunkt der Ladung, Endzeitpunkt, Geladene Reichweite, Energie, Leistung, Ladedauer, LP-Nummer,
# Lademodus, ID-Tag
# json-Objekt: new_entry = {
Expand Down Expand Up @@ -328,7 +330,7 @@ def write_new_entry(new_entry):
def calc_energy_costs(cp, create_log_entry: bool = False):
try:
if cp.data.set.log.imported_since_plugged != 0 and cp.data.set.log.imported_since_mode_switch != 0:
processed_entries, reference_entries = _get_reference_entries(cp)
processed_entries, reference_entries = _get_reference_entries(cp, create_log_entry)
charged_energy_by_source = calculate_charged_energy_by_source(
cp, processed_entries, reference_entries, create_log_entry)
_add_charged_energy_by_source(cp, charged_energy_by_source)
Expand Down Expand Up @@ -361,16 +363,18 @@ def calculate_charged_energy_by_source(cp, processed_entries, reference_entries,
reference_entries[0]["cp"][f"cp{cp.num}"]["imported"])
elif reference == ReferenceTime.END:
if sum(cp.data.set.log.charged_energy_by_source.values()) == 0:
# Kurze Ladung, für die noch kein 5min-Intervall vergangen ist
charged_energy = cp.data.set.log.imported_since_mode_switch
else:
log.debug(f"cp.data.get.imported {cp.data.get.imported}")
charged_energy = cp.data.get.imported - \
reference_entries[-1]["cp"][f"cp{cp.num}"]["imported"]
# Nur das noch nicht verbuchte Intervall
charged_energy = (reference_entries[-1]["cp"][f"cp{cp.num}"]["imported"] -
reference_entries[0]["cp"][f"cp{cp.num}"]["imported"])
else:
raise TypeError(f"Unbekannter Referenz-Zeitpunkt {reference}")
log.debug(f'power source {relative_energy_source}')
log.debug(f"charged_energy {charged_energy}")
if charged_energy < 100:
# Wenn keine Energie geladen wurde, keine Anteile berechnen
if charged_energy <= 0:
# wenn nur entladen wurde, keine Anteile berechnen
return {source: 0 for source in ENERGY_SOURCES}
return _get_charged_energy_by_source(
Expand Down Expand Up @@ -402,14 +406,26 @@ def _get_reference_position(cp, create_log_entry: bool) -> ReferenceTime:
return ReferenceTime.MIDDLE


def _get_reference_entries(cp) -> Tuple[List[Dict], List]:
def _get_reference_entries(cp, create_log_entry: bool = False) -> Tuple[List[Dict], List]:
processed_entries = {}
reference_entries = []
try:
log_data = get_todays_daily_log()
names = log_data["names"]
entries = log_data["entries"]
if len(entries) >= 2:
if create_log_entry:
# beim beenden einer Ladung den aktuellen Messwert benutzen
if entries:
previous_entry = entries[-1]
else:
# Bei Tageswechsel
date_day_before = (datetime.datetime.now() + datetime.timedelta(days=-1)).strftime("%Y%m%d")
entries_day_before = get_daily_log(date_day_before)["entries"]
previous_entry = entries_day_before[-1]

current_entry = create_entry(None, previous_entry)
reference_entries = [previous_entry, current_entry]
elif len(entries) >= 2:
reference_entries = [entries[-2], entries[-1]]
else:
date_day_before = (datetime.datetime.now() + datetime.timedelta(days=-1)).strftime("%Y%m%d")
Expand Down
137 changes: 136 additions & 1 deletion packages/control/chargelog/chargelog_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from control.chargelog.chargelog import calc_energy_costs
from control.chargepoint.chargepoint import Chargepoint
from control.general import Prices
from helpermodules.measurement_logging import process_log_entry_builder


@pytest.fixture()
Expand All @@ -17,6 +18,19 @@ def mock_data() -> None:
data.data.optional_data.et_module = None


@pytest.mark.parametrize("sh_values", [{}, {"sh1": {"imported": 1200, "exported": 0, "temp0": 21}}])
def test_create_entry_optional_smarthome(mock_data, sh_values):
sh_log_data = Mock(sh_dict=sh_values) if sh_values else None

with patch.object(process_log_entry_builder, "BrokerClient") as broker_client:
entry = process_log_entry_builder.create_entry(sh_log_data, None)

assert entry["sh"] == sh_values
assert "cp" in entry
assert "prices" in entry
broker_client.assert_not_called()


def mock_daily_log(monkeypatch):
daily_log = {"entries": [{'bat': {'all': {'exported': 2000, 'imported': 2000, 'soc': 100},
'bat2': {'exported': 2000, 'imported': 2000, 'soc': 100}},
Expand Down Expand Up @@ -92,21 +106,112 @@ def test_calc_charge_cost_reference_end(mock_data, monkeypatch):
cp.data.get.imported = 4100
cp.data.set.log.charged_energy_by_source = {'grid': 1243, 'pv': 386, 'bat': 671, 'cp': 0.0}
daily_log = mock_daily_log(monkeypatch)
current_entry = json.loads(json.dumps(daily_log["entries"][-1]))
current_entry["bat"]["all"]["exported"] = 4000
current_entry["bat"]["bat2"]["exported"] = 4000
current_entry["counter"]["counter0"]["imported"] = 4500
current_entry["cp"]["cp4"]["imported"] = 4100
current_entry["pv"]["all"]["exported"] = 3000
current_entry["pv"]["pv1"]["exported"] = 3000
create_entry_mock = Mock(return_value=current_entry)
monkeypatch.setattr(chargelog, "create_entry", create_entry_mock)

with patch("builtins.open", mock_open(read_data=json.dumps(daily_log))):
with patch("builtins.open", mock_open(read_data=json.dumps(daily_log))), \
patch.object(process_log_entry_builder, "BrokerClient") as broker_client:
calc_energy_costs(cp, True)

create_entry_mock.assert_called_once_with(None, daily_log["entries"][-1])
broker_client.assert_not_called()
assert cp.data.set.log.charged_energy_by_source == {'bat': 699.57, 'cp': 0.0, 'grid': 1300.14, 'pv': 400.29}
assert round(cp.data.set.log.costs, 5) == 0.025


def test_calc_charge_cost_short_charge_uses_current_entry(mock_data, monkeypatch):
cp = Chargepoint(4, None)

# Kurzer Ladevorgang: insgesamt nur 80 Wh geladen.
# Es wurde vorher noch kein Energieanteil verbucht.
cp.data.set.log.imported_since_plugged = 80
cp.data.set.log.imported_since_mode_switch = 80
cp.data.set.log.timestamp_mode_switch = 1652683260 # 8:41
cp.data.get.imported = 4080
cp.data.set.log.charged_energy_by_source = {
'bat': 0,
'cp': 0,
'grid': 0,
'pv': 0
}

daily_log = mock_daily_log(monkeypatch)

# Letzter gespeicherter Daily-Log ist 8:40:
#
# Grid imported = 2500 Wh
# PV exported = 2500 Wh
# CP imported = 4000 Wh
#
# Jetzt erzeugen wir den Live-Snapshot beim Ladeende um 8:42.
current_entry = json.loads(json.dumps(daily_log["entries"][-1]))

current_entry["date"] = "8:42"
current_entry["timestamp"] = 1652683320

# Zwischen 8:40 und 8:42:
#
# +20 Wh Netz
# +60 Wh PV
# =80 Wh Verbrauch/Ladung
#
# Erwarteter Strommix:
# Netz = 20 / 80 = 25 %
# PV = 60 / 80 = 75 %

current_entry["counter"]["counter0"]["imported"] = 2520

current_entry["pv"]["all"]["exported"] = 2560
current_entry["pv"]["pv1"]["exported"] = 2560

current_entry["cp"]["all"]["imported"] = 4080
current_entry["cp"]["cp4"]["imported"] = 4080

# _get_reference_entries() soll diesen aktuellen Snapshot verwenden.
monkeypatch.setattr(
chargelog,
"create_entry",
Mock(return_value=current_entry)
)

calc_energy_costs(cp, True)

assert cp.data.set.log.charged_energy_by_source == pytest.approx({
'bat': 0,
'cp': 0,
'grid': 20,
'pv': 60
})

# Kosten:
# Netz: 20 Wh * 0.0003 €/Wh = 0.006 €
# PV: 60 Wh * 0.00015 €/Wh = 0.009 €
# Gesamt 0.015 €
assert cp.data.set.log.costs == pytest.approx(0.015)


def test_calc_charge_cost_reference_end_unique_price(mock_data, monkeypatch):
cp = Chargepoint(4, None)
cp.data.set.log.imported_since_plugged = cp.data.set.log.imported_since_mode_switch = 3950
cp.data.set.log.timestamp_mode_switch = 1652682600 # 8:30
cp.data.get.imported = 4100
cp.data.set.log.charged_energy_by_source = {'grid': 1243, 'pv': 386, 'bat': 671, 'cp': 0.0}
daily_log = mock_daily_log(monkeypatch)
current_entry = json.loads(json.dumps(daily_log["entries"][-1]))
current_entry["bat"]["all"]["exported"] = 4000
current_entry["bat"]["bat2"]["exported"] = 4000
current_entry["counter"]["counter0"]["imported"] = 4500
current_entry["cp"]["cp4"]["imported"] = 4100
current_entry["pv"]["all"]["exported"] = 3000
current_entry["pv"]["pv1"]["exported"] = 3000
monkeypatch.setattr(chargelog, "create_entry", Mock(return_value=current_entry))
data.data.general_data.data.prices = Prices(bat=0.0002, cp=0, grid=0.0002, pv=0.0002)

with patch("builtins.open", mock_open(read_data=json.dumps(daily_log))):
Expand Down Expand Up @@ -170,3 +275,33 @@ def test_calc_charge_cost_reference_middle_day_change(mock_data, monkeypatch):
assert cp.data.set.log.charged_energy_by_source == {
'grid': 1242.8, 'pv': 385.8, 'bat': 671.4, 'cp': 0.0}
assert round(cp.data.set.log.costs, 5) == 0.5


def test_small_positive_charge_is_assigned_to_energy_sources(mock_data, monkeypatch):
cp = Chargepoint(4, None)
charged_energy = 80
cp.data.set.log.imported_since_plugged = cp.data.set.log.imported_since_mode_switch = charged_energy

# Beispielhafte Verteilung
processed_entries = {
"totals": {
"cp": {
"cp4": {
"energy_imported": 1000,
"bat": 250,
"cp": 0,
"grid": 500,
"pv": 250,
}
}
}
}
monkeypatch.setattr(chargelog, "_get_reference_position", Mock(return_value=chargelog.ReferenceTime.START))

charged_energy_by_source = chargelog.calculate_charged_energy_by_source(
cp, processed_entries, []
)

assert charged_energy_by_source == {'bat': 20, 'cp': 0, 'grid': 40, 'pv': 20}

assert sum(charged_energy_by_source.values()) == cp.data.set.log.imported_since_mode_switch
3 changes: 2 additions & 1 deletion packages/helpermodules/data_migration/data_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
from helpermodules.data_migration.id_mapping import MapId
from helpermodules.hardware_configuration import update_hardware_configuration
from helpermodules.measurement_logging.process_log import get_totals
from helpermodules.measurement_logging.write_log import LegacySmartHomeLogData, get_names
from helpermodules.measurement_logging.process_log_entry_builder import LegacySmartHomeLogData
from helpermodules.measurement_logging.write_log import get_names
from helpermodules.timecheck import convert_timedelta_to_time_string, get_difference
from helpermodules.utils import joined_thread_handler
from helpermodules.utils.precision_math import string_to_float, string_to_int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,11 @@ def get_grid_counter(entry) -> Dict:
return counter
else:
raise KeyError(f"Kein Zähler für das Netz gefunden in Eintrag '{entry['timestamp']}'.")

try:
message = ""
grid_counter = get_grid_counter(entry)
# Wenn neben dem "all" Eintrag kein weiterer Eintrag existiert, dann gibt es keine Komponenten.
grid_counter = get_grid_counter(entry)
if ((safe_get_nested(entry, "bat", "all", "fault_state") == 2 and len(entry.get("bat", {})) > 1) or
(safe_get_nested(entry, "cp", "all", "fault_state") == 2 and len(entry.get("cp", {})) > 1) or
(safe_get_nested(entry, "pv", "all", "fault_state") == 2 and len(entry.get("pv", {})) > 1) or
Expand Down
Loading
Loading