Skip to content

Commit

Permalink
Evu error timer, use default power in case of error instead of charge…
Browse files Browse the repository at this point in the history
… stop (#1855)

* error timer

* text

* test
  • Loading branch information
LKuemmel authored Oct 8, 2024
1 parent 51dd536 commit c9bd14f
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 61 deletions.
4 changes: 3 additions & 1 deletion packages/control/algorithm/min_current.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from control.algorithm import common
from control.loadmanagement import Loadmanagement
from control.algorithm.filter_chargepoints import get_chargepoints_by_mode_and_counter
from modules.common.utils.component_parser import get_component_name_by_id

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -33,7 +34,8 @@ def set_min_current(self) -> None:
common.set_current_counterdiff(-(cp.data.set.current or 0), 0, cp)
if limit:
cp.set_state_and_log(
f"Ladung kann nicht gestartet werden{limit.value.format(counter.num)}")
"Ladung kann nicht gestartet werden"
f"{limit.value.format(get_component_name_by_id(counter.num))}")
else:
common.set_current_counterdiff(
cp.data.set.target_current,
Expand Down
106 changes: 62 additions & 44 deletions packages/control/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from enum import Enum
import logging
import operator
from typing import List, Tuple
from typing import List, Optional, Tuple

from control import data
from control.chargemode import Chargemode
Expand All @@ -16,13 +16,16 @@
from helpermodules.constants import NO_ERROR
from helpermodules.phase_mapping import convert_cp_currents_to_evu_currents
from modules.common.fault_state import FaultStateLevel
from modules.common.utils.component_parser import get_component_name_by_id

log = logging.getLogger(__name__)


def get_counter_default_config():
return {"max_currents": [35]*3,
"max_total_power": 24000}
return {"max_power_errorcase": 7000,
"max_currents": [35]*3,
"max_total_power": 24000,
}


class ControlRangeState(Enum):
Expand All @@ -33,6 +36,7 @@ class ControlRangeState(Enum):

@dataclass
class Config:
max_power_errorcase: float = field(default=7000, metadata={"topic": "get/max_power_errorcase"})
max_currents: List[float] = field(default_factory=currents_list_factory, metadata={
"topic": "get/max_currents"})
max_total_power: float = field(default=0, metadata={"topic": "get/max_total_power"})
Expand Down Expand Up @@ -68,7 +72,7 @@ def get_factory() -> Get:

@dataclass
class Set:
error_counter: int = field(default=0, metadata={"topic": "set/error_counter"})
error_timer: Optional[float] = field(default=None, metadata={"topic": "set/error_timer"})
reserved_surplus: float = field(default=0, metadata={"topic": "set/reserved_surplus"})
released_surplus: float = field(default=0, metadata={"topic": "set/released_surplus"})
raw_power_left: float = 0
Expand All @@ -88,7 +92,7 @@ class CounterData:


class Counter:
MAX_EVU_ERRORS = 2
MAX_EVU_ERROR_DURATION = 60

def __init__(self, index):
try:
Expand All @@ -100,52 +104,62 @@ def __init__(self, index):
def setup_counter(self):
# Zählvariablen vor dem Start der Regelung zurücksetzen
try:
self._set_loadmanagement_state()
self._set_current_left()
self._set_power_left()
if self.data.get.fault_state == FaultStateLevel.ERROR:
loadmanagement_available = self._get_loadmanagement_state()
self._set_current_left(loadmanagement_available)
self._set_power_left(loadmanagement_available)
if loadmanagement_available is False:
self.data.get.power = 0
return
except Exception:
log.exception("Fehler in der Zähler-Klasse von "+str(self.num))

# tested
def _set_loadmanagement_state(self) -> None:
def _get_loadmanagement_state(self) -> None:
# Wenn der Zähler keine Werte liefert, darf nicht geladen werden.
connected_cps = data.data.counter_all_data.get_chargepoints_of_counter(f'counter{self.num}')
loadmanagement_available = True
if self.data.get.fault_state == FaultStateLevel.ERROR:
self.data.set.error_counter += 1
if self.data.set.error_counter >= self.MAX_EVU_ERRORS:
loadmanagement_available = False
if self.data.set.error_timer is None:
self.data.set.error_timer = timecheck.create_timestamp()
if timecheck.check_timestamp(self.data.set.error_timer, self.MAX_EVU_ERROR_DURATION) is False:
for cp in connected_cps:
if self.num == data.data.counter_all_data.get_id_evu_counter():
data.data.cp_data[cp].set_state_and_log(
f"Fehler beim Auslesen des Zählers {get_component_name_by_id(self.num)}. Es wird eine "
f"maximale Leistung von {self.data.config.max_power_errorcase/1000} kW genutzt.")
else:
data.data.cp_data[cp].set_state_and_log(
f"Fehler beim Auslesen des Zählers {get_component_name_by_id(self.num)}. Es werden"
f" maximal Ströme von {[int(round(self.data.config.max_power_errorcase/230/3, 0))]*3} A"
" genutzt.")
return False
else:
self.data.set.error_counter = 0
for cp in connected_cps:
# Wird zu Beginn des Zyklus auf True gesetzt, wenn es einmal auf False gesetzt wird, darf es nicht wieder
# auf True gesetzt werden.
if loadmanagement_available is False:
data.data.cp_data[cp].data.set.loadmanagement_available = loadmanagement_available
self.data.set.error_timer = None
return True

# tested

def _set_current_left(self) -> None:
currents_raw = self.data.get.currents
cp_keys = data.data.counter_all_data.get_chargepoints_of_counter(f"counter{self.num}")
for cp_key in cp_keys:
chargepoint = data.data.cp_data[cp_key]
try:
element_current = convert_cp_currents_to_evu_currents(
chargepoint.data.config.phase_1,
chargepoint.data.get.currents)
except KeyError:
element_current = [max(chargepoint.data.get.currents)]*3
currents_raw = list(map(operator.sub, currents_raw, element_current))
currents_raw = list(map(operator.sub, self.data.config.max_currents, currents_raw))
if min(currents_raw) < 0:
log.debug(f"Verbleibende Ströme: {currents_raw}, Überbelastung wird durch Hausverbrauch verursacht")
currents_raw = [max(currents_raw[i], 0) for i in range(0, 3)]
self.data.set.raw_currents_left = currents_raw
log.info(f'Verbleibende Ströme an Zähler {self.num}: {self.data.set.raw_currents_left}A')
def _set_current_left(self, loadmanagement_available: bool) -> None:
if loadmanagement_available:
currents_raw = self.data.get.currents
cp_keys = data.data.counter_all_data.get_chargepoints_of_counter(f"counter{self.num}")
for cp_key in cp_keys:
chargepoint = data.data.cp_data[cp_key]
try:
element_current = convert_cp_currents_to_evu_currents(
chargepoint.data.config.phase_1,
chargepoint.data.get.currents)
except KeyError:
element_current = [max(chargepoint.data.get.currents)]*3
currents_raw = list(map(operator.sub, currents_raw, element_current))
currents_raw = list(map(operator.sub, self.data.config.max_currents, currents_raw))
if min(currents_raw) < 0:
log.debug(f"Verbleibende Ströme: {currents_raw}, Überbelastung wird durch Hausverbrauch verursacht")
currents_raw = [max(currents_raw[i], 0) for i in range(0, 3)]
self.data.set.raw_currents_left = currents_raw
log.info(f'Verbleibende Ströme an Zähler {self.num}: {self.data.set.raw_currents_left}A')
else:
self.data.set.raw_currents_left = [self.data.config.max_power_errorcase/230/3]*3
log.info(f'Verbleibende Ströme an Zähler {self.num} (Fehlerfall): {self.data.set.raw_currents_left}A')

# tested
def get_unbalanced_load_exceeding(self, raw_currents_left: List[float]) -> List[float]:
Expand All @@ -162,13 +176,17 @@ def get_unbalanced_load_exceeding(self, raw_currents_left: List[float]) -> List[
unbalanced_load - data.data.general_data.data.chargemode_config.unbalanced_load_limit, 0)
return max_exceeding

def _set_power_left(self) -> None:
def _set_power_left(self, loadmanagement_available: bool) -> None:
if f'counter{self.num}' == data.data.counter_all_data.get_evu_counter_str():
power_raw = self.data.get.power
for cp in data.data.cp_data.values():
power_raw -= cp.data.get.power
self.data.set.raw_power_left = self.data.config.max_total_power - power_raw
log.info(f'Verbleibende Leistung an Zähler {self.num}: {self.data.set.raw_power_left}W')
if loadmanagement_available:
power_raw = self.data.get.power
for cp in data.data.cp_data.values():
power_raw -= cp.data.get.power
self.data.set.raw_power_left = self.data.config.max_total_power - power_raw
log.info(f'Verbleibende Leistung an Zähler {self.num}: {self.data.set.raw_power_left}W')
else:
self.data.set.raw_power_left = self.data.config.max_power_errorcase
log.info(f'Verbleibende Leistung an Zähler {self.num} (Fehlerfall): {self.data.set.raw_power_left}W')
else:
self.data.set.raw_power_left = None

Expand Down
29 changes: 18 additions & 11 deletions packages/control/counter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from unittest.mock import Mock
import pytest

from control import counter as counter_module
from control import data
from control.chargepoint.chargepoint import Chargepoint
from control.counter import Counter, CounterData, Get
Expand All @@ -19,25 +20,27 @@ def general_data_fixture() -> None:


@pytest.mark.parametrize("fault_state, expected_loadmanagement_available",
[pytest.param(FaultStateLevel.ERROR, False),
pytest.param(FaultStateLevel.NO_ERROR, True)])
[pytest.param(FaultStateLevel.ERROR, 1652683252),
pytest.param(FaultStateLevel.NO_ERROR, None)])
def test_set_loadmanagement_state(fault_state: FaultStateLevel,
expected_loadmanagement_available: bool,
monkeypatch,
data_):
# setup
connected_cps_mock = Mock(return_value=["cp3", "cp4"])
monkeypatch.setattr(data.data.counter_all_data, "get_chargepoints_of_counter", connected_cps_mock)
id_mock = Mock(return_value=0)
monkeypatch.setattr(data.data.counter_all_data, "get_id_evu_counter", id_mock)
name_mock = Mock(return_value="Test")
monkeypatch.setattr(counter_module, "get_component_name_by_id", name_mock)
counter = Counter(0)
counter.data.get.fault_state = fault_state
counter.data.set.error_counter = 2

# execution
counter._set_loadmanagement_state()
counter._get_loadmanagement_state()

# evaluation
assert data.data.cp_data["cp3"].data.set.loadmanagement_available == expected_loadmanagement_available
assert data.data.cp_data["cp4"].data.set.loadmanagement_available == expected_loadmanagement_available
assert counter.data.set.error_timer == expected_loadmanagement_available


@pytest.mark.parametrize("raw_currents_left, expected_max_exceeding",
Expand Down Expand Up @@ -65,10 +68,12 @@ def test_get_unbalanced_load_exceeding(raw_currents_left: List[float],
assert max_exceeding == expected_max_exceeding


@pytest.mark.parametrize("max_currents, expected_raw_currents_left",
[pytest.param([40]*3, [40, 0, 10], id="Überbelastung"),
([60]*3, [60, 15, 30])])
def test_set_current_left(max_currents: List[float],
@pytest.mark.parametrize("loadmanagement_available, max_currents, expected_raw_currents_left",
[pytest.param(True, [40]*3, [40, 0, 10], id="Überbelastung"),
pytest.param(True, [60]*3, [60, 15, 30]),
pytest.param(False, [40]*3, [10.1449275362318853]*3, id="Kein Lastmanagement")])
def test_set_current_left(loadmanagement_available: bool,
max_currents: List[float],
expected_raw_currents_left: List[float],
monkeypatch,
data_):
Expand All @@ -77,10 +82,12 @@ def test_set_current_left(max_currents: List[float],
monkeypatch.setattr(data.data.counter_all_data, "get_chargepoints_of_counter", get_chargepoints_of_counter_mock)
counter = Counter(0)
counter.data.config.max_currents = max_currents
counter.data.config.max_total_power = sum(max_currents)*230
counter.data.config.max_power_errorcase = 7000
counter.data.get.currents = [55]*3

# execution
counter._set_current_left()
counter._set_current_left(loadmanagement_available)

# evaluation
assert counter.data.set.raw_currents_left == expected_raw_currents_left
Expand Down
7 changes: 4 additions & 3 deletions packages/helpermodules/setdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,8 @@ def process_counter_topic(self, msg: mqtt.MQTTMessage):
self._validate_value(msg, "json")
elif "/config/max_currents" in msg.topic:
self._validate_value(msg, int, [(7, 1500)], collection=list)
elif "/config/max_total_power" in msg.topic:
elif ("/config/max_total_power" in msg.topic or
"/config/max_power_errorcase" in msg.topic):
self._validate_value(msg, int, [(0, float("inf"))])
elif subdata.SubData.counter_data.get(f"counter{get_index(msg.topic)}"):
if ("/get/powers" in msg.topic or
Expand All @@ -933,8 +934,8 @@ def process_counter_topic(self, msg: mqtt.MQTTMessage):
msg, float, [(0, float("inf"))])
elif "/get/fault_state" in msg.topic:
self._validate_value(msg, int, [(0, 2)])
elif "/set/error_counter" in msg.topic:
self._validate_value(msg, int, [(0, float("inf"))])
elif "/set/error_timer" in msg.topic:
self._validate_value(msg, float, [(0, float("inf"))])
elif "/get/fault_str" in msg.topic:
self._validate_value(msg, str)
elif "/get/power" in msg.topic:
Expand Down
16 changes: 14 additions & 2 deletions packages/helpermodules/update_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from paho.mqtt.client import Client as MqttClient, MQTTMessage
from control.bat_all import BatConsiderationMode
from control.chargepoint.charging_type import ChargingType
from control.counter import get_counter_default_config
from control.general import ChargemodeConfig
import dataclass_utils

Expand Down Expand Up @@ -47,7 +48,7 @@


class UpdateConfig:
DATASTORE_VERSION = 61
DATASTORE_VERSION = 62
valid_topic = [
"^openWB/bat/config/configured$",
"^openWB/bat/set/charging_power_left$",
Expand Down Expand Up @@ -164,9 +165,10 @@ class UpdateConfig:
"^openWB/counter/[0-9]+/get/imported$",
"^openWB/counter/[0-9]+/get/exported$",
"^openWB/counter/[0-9]+/set/consumption_left$",
"^openWB/counter/[0-9]+/set/error_counter$",
"^openWB/counter/[0-9]+/set/error_timer$",
"^openWB/counter/[0-9]+/set/released_surplus$",
"^openWB/counter/[0-9]+/set/reserved_surplus$",
"^openWB/counter/[0-9]+/config/max_power_errorcase$",
"^openWB/counter/[0-9]+/config/max_currents$",
"^openWB/counter/[0-9]+/config/max_total_power$",

Expand Down Expand Up @@ -1792,3 +1794,13 @@ def upgrade(topic: str, payload) -> None:
Pub().pub(topic, payload)
self._loop_all_received_topics(upgrade)
self.__update_topic("openWB/system/datastore_version", 61)

def upgrade_datastore_61(self) -> None:
def upgrade(topic: str, payload) -> Optional[dict]:
if re.search("openWB/counter/[0-9]+/config/max_total_power", topic) is not None:
index = get_index(topic)
if f"openWB/counter/{index}/config/max_power_errorcase" not in self.all_received_topics.keys():
max_power_errorcase = get_counter_default_config()["max_power_errorcase"]
return {f"openWB/counter/{index}/config/max_power_errorcase": max_power_errorcase}
self._loop_all_received_topics(upgrade)
self.__update_topic("openWB/system/datastore_version", 62)

0 comments on commit c9bd14f

Please sign in to comment.