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
34 changes: 18 additions & 16 deletions packages/control/auto_phase_switch_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Params:
def __init__(self,
name: str,
max_current_single_phase: int,
timestamp_last_phase_switch: Optional[float],
timestamp_phase_switch_buffer_start: Optional[float],
phases_to_use: int,
required_current: float,
evu_surplus: int,
Expand All @@ -46,7 +46,7 @@ def __init__(self,
expected_message: Optional[str] = None) -> None:
self.name = name
self.max_current_single_phase = max_current_single_phase
self.timestamp_last_phase_switch = timestamp_last_phase_switch
self.timestamp_phase_switch_buffer_start = timestamp_phase_switch_buffer_start
self.phases_to_use = phases_to_use
self.required_current = required_current
self.available_power = evu_surplus
Expand All @@ -60,54 +60,55 @@ def __init__(self,


cases = [
Params("1to3, enough power, start timer", max_current_single_phase=16, timestamp_last_phase_switch=1652683202,
Params("1to3, enough power, start timer", max_current_single_phase=16, timestamp_phase_switch_buffer_start=None,
phases_to_use=1, required_current=6, evu_surplus=800, get_currents=[15.6, 0, 0],
get_power=3450, state=ChargepointState.CHARGING_ALLOWED, expected_phases_to_use=1, expected_current=6,
expected_message=Ev.PHASE_SWITCH_DELAY_TEXT.format("Umschaltung von 1 auf 3", "4 Min. 10 Sek."),
expected_message=Ev.PHASE_SWITCH_DELAY_TEXT.format("Umschaltung von 1 auf 3", "30 Sek."),
expected_state=ChargepointState.PHASE_SWITCH_DELAY),
Params("1to3, not enough power, start timer", max_current_single_phase=16, timestamp_last_phase_switch=1652683202,
Params("1to3, not enough power, start timer", max_current_single_phase=16, timestamp_phase_switch_buffer_start=None,
phases_to_use=1, required_current=6, evu_surplus=300, get_currents=[15.6, 0, 0],
get_power=3450, state=ChargepointState.CHARGING_ALLOWED, expected_phases_to_use=1, expected_current=6,
expected_state=ChargepointState.CHARGING_ALLOWED),
Params("1to3, enough power, timer not expired", max_current_single_phase=16,
timestamp_last_phase_switch=1652683202.0, phases_to_use=1, required_current=6,
timestamp_phase_switch_buffer_start=1652683232.0, phases_to_use=1, required_current=6,
evu_surplus=1460, get_currents=[15.6, 0, 0], get_power=3450,
state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=1, expected_current=6,
expected_message=Ev.PHASE_SWITCH_DELAY_TEXT.format("Umschaltung von 1 auf 3", "4 Min. 10 Sek."),
expected_message=Ev.PHASE_SWITCH_DELAY_TEXT.format("Umschaltung von 1 auf 3", "10 Sek."),
expected_state=ChargepointState.PHASE_SWITCH_DELAY),
Params("1to3, not enough power, timer not expired", max_current_single_phase=16,
timestamp_last_phase_switch=1652683202.0, phases_to_use=1, required_current=6,
timestamp_phase_switch_buffer_start=1652683202.0, phases_to_use=1, required_current=6,
evu_surplus=460, get_currents=[15.6, 0, 0], get_power=3450,
state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=1, expected_current=6,
expected_message=f"Verzögerung für die Umschaltung von 1 auf 3 Phasen abgebrochen{Ev.NOT_ENOUGH_POWER}",
expected_state=ChargepointState.CHARGING_ALLOWED),
Params("1to3, enough power, timer expired", max_current_single_phase=16,
timestamp_last_phase_switch=1652682802.0, phases_to_use=1, required_current=6,
timestamp_phase_switch_buffer_start=1652682802.0, phases_to_use=1, required_current=6,
evu_surplus=1640, get_currents=[15.6, 0, 0], get_power=3450,
state=ChargepointState.PHASE_SWITCH_DELAY,
expected_phases_to_use=3, expected_current=6, expected_state=ChargepointState.PHASE_SWITCH_AWAITED),

Params("3to1, not enough power, start timer", max_current_single_phase=16, timestamp_last_phase_switch=1652683202,
Params("3to1, not enough power, start timer", max_current_single_phase=16,
timestamp_phase_switch_buffer_start=1652683202,
phases_to_use=3, required_current=6, evu_surplus=0,
get_currents=[4.5, 4.4, 5.8], get_power=3381, state=ChargepointState.CHARGING_ALLOWED,
expected_phases_to_use=3, expected_current=6,
expected_message="Umschaltung von 3 auf 1 Phasen in 4 Min. 10 Sek..",
expected_message="Umschaltung von 3 auf 1 Phasen in 10 Sek..",
expected_state=ChargepointState.PHASE_SWITCH_DELAY),
Params("3to1, not enough power, timer not expired", max_current_single_phase=16,
timestamp_last_phase_switch=1652683202.0,
timestamp_phase_switch_buffer_start=1652683202.0,
phases_to_use=3, required_current=6, evu_surplus=-460,
get_currents=[4.5, 4.4, 5.8], get_power=3381, state=ChargepointState.PHASE_SWITCH_DELAY,
expected_phases_to_use=3, expected_current=6,
expected_message="Umschaltung von 3 auf 1 Phasen in 4 Min. 10 Sek..",
expected_message="Umschaltung von 3 auf 1 Phasen in 10 Sek..",
expected_state=ChargepointState.PHASE_SWITCH_DELAY),
Params("3to1, enough power, timer not expired", max_current_single_phase=16,
timestamp_last_phase_switch=1652683202.0, phases_to_use=3, required_current=6,
timestamp_phase_switch_buffer_start=1652683202.0, phases_to_use=3, required_current=6,
evu_surplus=860, get_currents=[4.5, 4.4, 5.8],
get_power=3381, state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=3, expected_current=6,
expected_message=f"Verzögerung für die Umschaltung von 3 auf 1 Phasen abgebrochen{Ev.ENOUGH_POWER}",
expected_state=ChargepointState.CHARGING_ALLOWED),
Params("3to1, not enough power, timer expired", max_current_single_phase=16,
timestamp_last_phase_switch=1652682802.0, phases_to_use=3, required_current=6,
timestamp_phase_switch_buffer_start=1652682802.0, phases_to_use=3, required_current=6,
evu_surplus=-460, get_currents=[4.5, 4.4, 5.8],
get_power=3381, state=ChargepointState.PHASE_SWITCH_DELAY, expected_phases_to_use=1, expected_current=16,
expected_state=ChargepointState.PHASE_SWITCH_AWAITED),
Expand All @@ -127,7 +128,8 @@ def test_auto_phase_switch(monkeypatch, vehicle: Ev, params: Params):

vehicle.ev_template.data.max_current_single_phase = params.max_current_single_phase
control_parameter = ControlParameter()
control_parameter.timestamp_last_phase_switch = params.timestamp_last_phase_switch
control_parameter.timestamp_last_phase_switch = 1652682802
control_parameter.timestamp_phase_switch_buffer_start = params.timestamp_phase_switch_buffer_start
control_parameter.phases = params.phases_to_use
control_parameter.required_current = params.required_current
control_parameter.state = params.state
Expand Down
2 changes: 2 additions & 0 deletions packages/control/chargepoint/control_parameter.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class ControlParameter:
default=None, metadata={"topic": "control_parameter/timestamp_chargemode_changed"})
timestamp_last_phase_switch: float = field(
default=0, metadata={"topic": "control_parameter/timestamp_last_phase_switch"})
timestamp_phase_switch_buffer_start: Optional[float] = field(
default=None, metadata={"topic": "control_parameter/timestamp_phase_switch_buffer_start"})
timestamp_switch_on_off: Optional[float] = field(
default=None, metadata={"topic": "control_parameter/timestamp_switch_on_off"})

Expand Down
10 changes: 5 additions & 5 deletions packages/control/counter.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,11 @@ def get_usable_surplus(self, feed_in_yield: float) -> float:
return (-self.calc_surplus() - self.data.set.released_surplus +
self.data.set.reserved_surplus - feed_in_yield)

SWITCH_ON_FALLEN_BELOW = "Einschaltschwelle während der Einschaltverzögerung unterschritten."
SWITCH_ON_WAITING = "Die Ladung wird gestartet, sobald in {} die Einschaltverzögerung abgelaufen ist."
SWITCH_ON_FALLEN_BELOW = "Einschaltschwelle während der Wartezeit unterschritten."
SWITCH_ON_WAITING = "Die Ladung wird gestartet, sobald in {} die Wartezeit abgelaufen ist."
SWITCH_ON_NOT_EXCEEDED = ("Die Ladung kann nicht gestartet werden, da die Einschaltschwelle nicht erreicht "
"wird.")
SWITCH_ON_EXPIRED = "Einschaltschwelle für die Dauer der Einschaltverzögerung überschritten."
SWITCH_ON_EXPIRED = "Einschaltschwelle für die Dauer der Wartezeit überschritten."
SWITCH_ON_MAX_PHASES = "Der Überschuss ist ausreichend, um direkt mit {} Phasen zu laden."

def calc_switch_on_power(self, chargepoint: Chargepoint) -> Tuple[float, float]:
Expand Down Expand Up @@ -354,8 +354,8 @@ def switch_on_timer_expired(self, chargepoint: Chargepoint) -> None:
except Exception:
log.exception("Fehler im allgemeinen PV-Modul")

SWITCH_OFF_STOP = "Ladevorgang nach Ablauf der Abschaltverzögerung gestoppt."
SWITCH_OFF_WAITING = "Ladevorgang wird nach Ablauf der Abschaltverzögerung in {} gestoppt."
SWITCH_OFF_STOP = "Ladevorgang nach Ablauf der Wartezeit gestoppt."
SWITCH_OFF_WAITING = "Ladevorgang wird nach Ablauf der Wartezeit in {} gestoppt."
SWITCH_OFF_NO_STOP = ("Der Ladevorgang wird trotz fehlenden Überschusses nicht gestoppt, da in dem Fahrzeug-Profil "
"die Einstellung 'Ladung aktiv halten' aktiviert ist.")
SWITCH_OFF_EXCEEDED = "Abschaltschwelle während der Verzögerung überschritten."
Expand Down
47 changes: 41 additions & 6 deletions packages/control/ev/ev.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,6 @@ def auto_phase_switch(self,
max_phases: int,
limit: LoadmanagementLimit) -> Tuple[int, float, Optional[str]]:
message = None
timestamp_last_phase_switch = control_parameter.timestamp_last_phase_switch
current = control_parameter.required_current
phases_to_use = control_parameter.phases
phases_in_use = control_parameter.phases
Expand All @@ -334,12 +333,14 @@ def auto_phase_switch(self,

new_phase = max_phases
new_current = control_parameter.min_current
waiting_time = pv_config.switch_on_delay
else:
direction_str = f"Umschaltung von {max_phases} auf 1"
# Es kann einphasig mit entsprechend niedriger Leistung gestartet werden.
required_reserved_power = 0
new_phase = 1
new_current = self.ev_template.data.max_current_single_phase
waiting_time = pv_config.switch_off_delay

log.debug(
f'Genutzter Strom: {get_medium_charging_current(get_currents)}A, Überschuss: {all_surplus}W, benötigte '
Expand All @@ -360,17 +361,21 @@ def auto_phase_switch(self,
).data.set.reserved_surplus += max(0, required_reserved_power)
message = self.PHASE_SWITCH_DELAY_TEXT.format(
direction_str,
timecheck.convert_timestamp_delta_to_time_string(timestamp_last_phase_switch, delay))
self._remaining_phase_switch_time(control_parameter,
waiting_time,
delay)[1])
control_parameter.state = ChargepointState.PHASE_SWITCH_DELAY
elif condition_msg:
log.debug(f"Keine Phasenumschaltung{condition_msg}")
else:
if condition:
# Timer laufen lassen
if timecheck.check_timestamp(control_parameter.timestamp_last_phase_switch, delay):
message = self.PHASE_SWITCH_DELAY_TEXT.format(
direction_str,
timecheck.convert_timestamp_delta_to_time_string(timestamp_last_phase_switch, delay))
timestamp_passed, remaining_time = self._remaining_phase_switch_time(
control_parameter,
waiting_time,
delay)
if timestamp_passed is False:
message = self.PHASE_SWITCH_DELAY_TEXT.format(direction_str, remaining_time)
else:
data.data.counter_all_data.get_evu_counter(
).data.set.reserved_surplus -= max(0, required_reserved_power)
Expand All @@ -388,6 +393,36 @@ def auto_phase_switch(self,
log.info(f"LP {cp_num}: {message}")
return phases_to_use, current, message

def _remaining_phase_switch_time(self, control_parameter: ControlParameter,
waiting_time: float,
buffer: float) -> float:

if control_parameter.timestamp_phase_switch_buffer_start is None:
control_parameter.timestamp_phase_switch_buffer_start = timecheck.create_timestamp()
# Wenn der Puffer seit der letzen Umschaltung abgelaufen ist, warte noch die Umschaltverzögerung ab. ODER
# Wenn der Puffer noch nicht abgelaufen ist und Wartezeit länger als Pufferzeit, dann warte Wartezeit ab
remaining_buffer = (buffer - (control_parameter.timestamp_phase_switch_buffer_start -
control_parameter.timestamp_last_phase_switch))
if remaining_buffer < 0 or remaining_buffer < waiting_time:
if timecheck.check_timestamp(control_parameter.timestamp_phase_switch_buffer_start, waiting_time):
remaining_time = timecheck.convert_timestamp_delta_to_time_string(
control_parameter.timestamp_phase_switch_buffer_start, waiting_time)
log.debug(f"Warte verbleibende Wartezeit ab: {remaining_time}")
return False, remaining_time
else:
control_parameter.timestamp_phase_switch_buffer_start = None
return True, None
else:
# Puffer noch nicht abgelaufen, verbleibende Pufferzeit ist länger als Wartezeit
if timecheck.check_timestamp(control_parameter.timestamp_last_phase_switch, buffer):
remaining_time = timecheck.convert_timestamp_delta_to_time_string(
control_parameter.timestamp_last_phase_switch, buffer)
log.debug(f"Warte verbleibende Pufferzeit ab: {remaining_time}")
return False, remaining_time
else:
control_parameter.timestamp_phase_switch_buffer_start = None
return True, None

def reset_phase_switch(self, control_parameter: ControlParameter):
""" Zurücksetzen der Zeitstempel und reservierten Leistung.

Expand Down
38 changes: 38 additions & 0 deletions packages/control/ev/ev_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pytest

from control.chargepoint.control_parameter import ControlParameter
from control.ev.ev import Ev
from helpermodules import timecheck
from modules.common.abstract_vehicle import VehicleUpdateData
Expand Down Expand Up @@ -35,3 +36,40 @@ def test_soc_interval_expired(check_timestamp: bool,

# evaluation
assert request_soc == expected_request_soc


@pytest.mark.parametrize(
"timestamp_last_phase_switch, timestamp_phase_switch_buffer_start, expected_result",
[
pytest.param(1652682881, None, (False, "30 Sek."), id="Puffer abgelaufen, Wartezeit noch nicht gestartet"),
pytest.param(1652682881, 1652683232, (False, "10 Sek."), id="Puffer abgelaufen, Wartezeit gestartet"),
pytest.param(1652682881, 1652683212, (True, None), id="Puffer abgelaufen, Wartezeit abgelaufen"),
pytest.param(1652682962, None, (False, "30 Sek."),
id="Puffer noch nicht abgelaufen, Wartezeit länger, Wartezeit noch nicht gestartet"),
pytest.param(1652682962, 1652683237, (False, "15 Sek."),
id="Puffer noch nicht abgelaufen, Wartezeit länger, Wartezeit gestartet"),
pytest.param(1652682932, 1652683220, (True, None),
id="Puffer noch nicht abgelaufen, Wartezeit länger, Wartezeit abgelaufen"),
pytest.param(1652683132, None, (False, "3 Min."), id="Puffer noch nicht abgelaufen, Puffer länger, abwarten"),
pytest.param(1652682950, 1652682972, (True, None),
id="Puffer noch nicht abgelaufen, Puffer länger, abgelaufen"),
],
)
def test_remaining_phase_switch_time(timestamp_last_phase_switch,
timestamp_phase_switch_buffer_start,
expected_result):
# setup
ev = Ev(0)
control_parameter = ControlParameter()
control_parameter.timestamp_last_phase_switch = timestamp_last_phase_switch
control_parameter.timestamp_phase_switch_buffer_start = timestamp_phase_switch_buffer_start

# execution
result = ev._remaining_phase_switch_time(
control_parameter=control_parameter,
waiting_time=30,
buffer=300,
)

# evaluation
assert result == expected_result
3 changes: 2 additions & 1 deletion packages/helpermodules/setdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,8 @@ def process_chargepoint_topic(self, msg: mqtt.MQTTMessage):
"/control_parameter/timestamp_switch_on_off" in msg.topic or
"/control_parameter/timestamp_charge_start" in msg.topic or
"/control_parameter/timestamp_chargemode_changed" in msg.topic or
"/control_parameter/timestamp_last_phase_switch" in msg.topic):
"/control_parameter/timestamp_last_phase_switch" in msg.topic or
"/control_parameter/timestamp_phase_switch_buffer_start" in msg.topic):
self._validate_value(msg, float, [(0, float("inf"))])
elif "/control_parameter/state" in msg.topic:
self._validate_value(msg, int, [(0, 7)])
Expand Down