Skip to content

Commit

Permalink
limit bat power (#1861)
Browse files Browse the repository at this point in the history
  • Loading branch information
LKuemmel authored Oct 10, 2024
1 parent 4fa0648 commit 021867b
Show file tree
Hide file tree
Showing 60 changed files with 358 additions and 121 deletions.
5 changes: 3 additions & 2 deletions data/config/mosquitto/openwb_local.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# openwb-version:15
# openwb-version:16
listener 1886 localhost
allow_anonymous true

Expand Down Expand Up @@ -33,10 +33,11 @@ topic openWB/pv/get/# out 2
topic openWB/pv/+/config/# out 2
topic openWB/pv/+/get/# out 2

topic openWB/bat/config/configured out 2
topic openWB/bat/config/# out 2
topic openWB/bat/get/# out 2
topic openWB/bat/+/config/# out 2
topic openWB/bat/+/get/# out 2
topic openWB/bat/+/set/# out 2

topic openWB/general/# out 2

Expand Down
2 changes: 2 additions & 0 deletions docs/Neues Modul programmieren.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ Wenn von der Komponente die Zählerstände für Import und Export gelesen werden

Bei Hybrid-Systemen erfolgt die Verrechnung von Speicher-und PV-Leistung automatisiert, wenn Speicher und Wechselrichter in der Hierarchie wie [hier](https://github.com/openWB/core/wiki/Hybrid-System-aus-Wechselrichter-und-Speicher) beschrieben angeordnet sind. Wenn noch weitere spezifische Berechnungen erforderlich sind, müsst Ihr die Komponenten wie unter sample_request_per_device abfragen. Die update-Methode der Komponenten wird dann in eine get- und set-Methode aufgeteilt. Die get-Methode liefert den Component-State zurück, dieser wird in der update_components-Methode des Geräts verrechnet und dann die set-Methode der Komponente aufgerufen, die die store-Methode der Komponente aufruft.

Bei Speichern, die eine aktive Steuerung unterstützen, kann mit der Methode `set_power_limit` die Speicherleistung gesetzt werden. Die Speicher erben von der Klasse `AbstractBat`, die die abstrakte Methode `set_power_limit` beinhaltet. Bei der Implementierung des Speichers kannst Du diese Methode überschreiben. Die Regelung prüft am Ende, ob die Methode für den jeweiligen Speicher implementiert ist und ruft diese auf. Als Variable wird die Speicherleistung in Watt oder `None` übergeben, dann wird der Speicher nicht mehr aktiv von der openWB gesteuert und soll selbst anhand des EVU-Punktes regeln.

### Neues Fahrzeug programmieren

Beim Aufruf der _updater_-Funktion wird die Variable _vehicle_update_data_ übergeben. Darin sind aktuelle Daten aus der Regelung, wie zB Stecker-Status oder die geladene Energie seit Anstecken, enthalten, um Besonderheiten wie zB das Aufwecken des Fahrzeugs oder eine manuelle Berechnung während des Ladevorgangs umsetzen zu können.
Expand Down
6 changes: 4 additions & 2 deletions packages/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from control import data
from control.bat import Bat, BatData
from control.bat import Get as BatGet
from control.bat import Set as BatSet
from control.chargepoint.chargepoint import Chargepoint, ChargepointData
from control.chargepoint.chargepoint_data import Config, Get, Set
from control.counter import Counter, CounterData
Expand Down Expand Up @@ -130,9 +131,10 @@ def data_() -> None:
daily_imported=10000, daily_exported=0, imported=62000,
fault_state=0),
set=Mock(spec=Set, loadmanagement_available=True)))}
data.data.bat_data.update({"bat2": Mock(spec=Bat, data=Mock(spec=BatData, get=Mock(
data.data.bat_data.update({"bat2": Mock(spec=Bat, num=2, data=Mock(spec=BatData, get=Mock(
spec=BatGet, power=-5000, daily_imported=7000, daily_exported=3000, imported=12000, exported=10000,
currents=None, fault_state=0)))})
currents=None, fault_state=0),
set=Mock(spec=BatSet, power_limit=None)))})
data.data.pv_data.update({"pv1": Mock(spec=Pv, data=Mock(
spec=PvData, get=Mock(spec=PvGet, power=-10000, daily_exported=6000, exported=27000, currents=None,
fault_state=0)))})
Expand Down
6 changes: 3 additions & 3 deletions packages/control/algorithm/additional_current.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

from control.algorithm import common
from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT
from control.loadmanagement import LimitingValue, Loadmanagement
from control.counter import Counter
from control.chargepoint.chargepoint import Chargepoint
Expand All @@ -12,14 +13,13 @@


class AdditionalCurrent:
CONSIDERED_CHARGE_MODES = common.CHARGEMODES[0:8]

def __init__(self) -> None:
pass

def set_additional_current(self) -> None:
common.reset_current_by_chargemode(self.CONSIDERED_CHARGE_MODES)
for mode_tuple, counter in common.mode_and_counter_generator(self.CONSIDERED_CHARGE_MODES):
common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT)
for mode_tuple, counter in common.mode_and_counter_generator(CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT):
preferenced_chargepoints, preferenced_cps_without_set_current = get_preferenced_chargepoint_charging(
get_chargepoints_by_mode_and_counter(mode_tuple, f"counter{counter.num}"))
if preferenced_chargepoints:
Expand Down
26 changes: 26 additions & 0 deletions packages/control/algorithm/chargemodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from control.chargemode import Chargemode

# Lademodi in absteigender Priorität
# Tupel-Inhalt:(eingestellter Modus, tatsächlich genutzter Modus, Priorität)
CHARGEMODES = ((Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, True),
(Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, False),
(None, Chargemode.TIME_CHARGING, True),
(None, Chargemode.TIME_CHARGING, False),
(Chargemode.INSTANT_CHARGING, Chargemode.INSTANT_CHARGING, True),
(Chargemode.INSTANT_CHARGING, Chargemode.INSTANT_CHARGING, False),
(Chargemode.PV_CHARGING, Chargemode.INSTANT_CHARGING, True),
(Chargemode.PV_CHARGING, Chargemode.INSTANT_CHARGING, False),
(Chargemode.SCHEDULED_CHARGING, Chargemode.PV_CHARGING, True),
(Chargemode.SCHEDULED_CHARGING, Chargemode.PV_CHARGING, False),
(Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, True),
(Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, False),
(None, Chargemode.STANDBY, True),
(None, Chargemode.STANDBY, False),
(None, Chargemode.STOP, True),
(None, Chargemode.STOP, False))

CONSIDERED_CHARGE_MODES_SURPLUS = CHARGEMODES[0:2] + CHARGEMODES[6:12]
CONSIDERED_CHARGE_MODES_PV_ONLY = CHARGEMODES[8:12]
CONSIDERED_CHARGE_MODES_ADDITIONAL_CURRENT = CHARGEMODES[0:8]
CONSIDERED_CHARGE_MODES_MIN_CURRENT = CHARGEMODES[0:-1]
CONSIDERED_CHARGE_MODES_NO_CURRENT = CHARGEMODES[12:16]
19 changes: 0 additions & 19 deletions packages/control/algorithm/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,13 @@

from control import data
from control.algorithm.filter_chargepoints import get_chargepoints_by_mode
from control.chargemode import Chargemode
from control.chargepoint.chargepoint import Chargepoint
from control.counter import Counter
from helpermodules.timecheck import check_timestamp
from modules.common.component_type import ComponentType

log = logging.getLogger(__name__)

# Lademodi in absteigender Priorität
# Tupel-Inhalt:(eingestellter Modus, tatsächlich genutzter Modus, Priorität)
CHARGEMODES = ((Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, True),
(Chargemode.SCHEDULED_CHARGING, Chargemode.INSTANT_CHARGING, False),
(None, Chargemode.TIME_CHARGING, True),
(None, Chargemode.TIME_CHARGING, False),
(Chargemode.INSTANT_CHARGING, Chargemode.INSTANT_CHARGING, True),
(Chargemode.INSTANT_CHARGING, Chargemode.INSTANT_CHARGING, False),
(Chargemode.PV_CHARGING, Chargemode.INSTANT_CHARGING, True),
(Chargemode.PV_CHARGING, Chargemode.INSTANT_CHARGING, False),
(Chargemode.SCHEDULED_CHARGING, Chargemode.PV_CHARGING, True),
(Chargemode.SCHEDULED_CHARGING, Chargemode.PV_CHARGING, False),
(Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, True),
(Chargemode.PV_CHARGING, Chargemode.PV_CHARGING, False),
(None, Chargemode.STANDBY, True),
(None, Chargemode.STANDBY, False),
(None, Chargemode.STOP, True),
(None, Chargemode.STOP, False))

LESS_CHARGING_TIMEOUT = 60

Expand Down
7 changes: 7 additions & 0 deletions packages/control/algorithm/filter_chargepoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,10 @@ def _get_preferenced_chargepoint(valid_chargepoints: List[Chargepoint]) -> List:
except Exception:
log.exception("Fehler im Algorithmus-Modul")
return preferenced_chargepoints


def get_chargepoints_by_chargemodes(modes) -> List[Chargepoint]:
chargepoints: List[Chargepoint] = []
for mode in modes:
chargepoints.extend(get_chargepoints_by_mode(mode))
return chargepoints
4 changes: 2 additions & 2 deletions packages/control/algorithm/min_current.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging

from control.algorithm import common
from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_MIN_CURRENT
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
Expand All @@ -9,13 +10,12 @@


class MinCurrent:
CONSIDERED_CHARGE_MODES = common.CHARGEMODES[0:-1]

def __init__(self) -> None:
pass

def set_min_current(self) -> None:
for mode_tuple, counter in common.mode_and_counter_generator(self.CONSIDERED_CHARGE_MODES):
for mode_tuple, counter in common.mode_and_counter_generator(CONSIDERED_CHARGE_MODES_MIN_CURRENT):
preferenced_chargepoints = get_chargepoints_by_mode_and_counter(mode_tuple, f"counter{counter.num}")
if preferenced_chargepoints:
log.info(f"Mode-Tuple {mode_tuple[0]} - {mode_tuple[1]} - {mode_tuple[2]}, Zähler {counter.num}")
Expand Down
10 changes: 3 additions & 7 deletions packages/control/algorithm/no_current.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import logging
from typing import List
from control import data

from control.algorithm.common import CHARGEMODES
from control.chargepoint.chargepoint import Chargepoint
from control.algorithm.filter_chargepoints import get_chargepoints_by_mode
from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_NO_CURRENT
from control.algorithm.filter_chargepoints import get_chargepoints_by_chargemodes

log = logging.getLogger(__name__)

Expand All @@ -14,9 +12,7 @@ def __init__(self) -> None:
pass

def set_no_current(self) -> None:
chargepoints: List[Chargepoint] = []
for mode in CHARGEMODES[12:16]:
chargepoints.extend(get_chargepoints_by_mode(mode))
chargepoints = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_NO_CURRENT)
for cp in chargepoints:
cp.data.set.current = 0

Expand Down
38 changes: 12 additions & 26 deletions packages/control/algorithm/surplus_controlled.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@

from control import data
from control.algorithm import common
from control.algorithm.chargemodes import CONSIDERED_CHARGE_MODES_PV_ONLY, CONSIDERED_CHARGE_MODES_SURPLUS
from control.algorithm.filter_chargepoints import (get_chargepoints_by_chargemodes,
get_chargepoints_by_mode_and_counter,
get_preferenced_chargepoint_charging)
from control.chargepoint.charging_type import ChargingType
from control.loadmanagement import LimitingValue, Loadmanagement
from control.counter import ControlRangeState, Counter
from control.chargepoint.chargepoint import Chargepoint
from control.algorithm.filter_chargepoints import (get_chargepoints_by_mode, get_chargepoints_by_mode_and_counter,
get_preferenced_chargepoint_charging)
from control.chargepoint.chargepoint_state import ChargepointState, CHARGING_STATES
from modules.common.utils.component_parser import get_component_name_by_id
from control.counter import ControlRangeState, Counter
from control.loadmanagement import LimitingValue, Loadmanagement

log = logging.getLogger(__name__)

CONSIDERED_CHARGE_MODES = common.CHARGEMODES[0:2] + common.CHARGEMODES[6:12]
CONSIDERED_CHARGE_MODES_PV = common.CHARGEMODES[8:12]
log = logging.getLogger(__name__)


class SurplusControlled:
Expand All @@ -24,8 +24,8 @@ def __init__(self) -> None:
pass

def set_surplus_current(self) -> None:
common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES)
for mode_tuple, counter in common.mode_and_counter_generator(CONSIDERED_CHARGE_MODES):
common.reset_current_by_chargemode(CONSIDERED_CHARGE_MODES_SURPLUS)
for mode_tuple, counter in common.mode_and_counter_generator(CONSIDERED_CHARGE_MODES_SURPLUS):
preferenced_chargepoints, preferenced_cps_without_set_current = get_preferenced_chargepoint_charging(
get_chargepoints_by_mode_and_counter(mode_tuple, f"counter{counter.num}"))
cp_with_feed_in, cp_without_feed_in = self.filter_by_feed_in_limit(preferenced_chargepoints)
Expand Down Expand Up @@ -148,7 +148,7 @@ def _fix_deviating_evse_current(self, limited_current, chargepoint: Chargepoint)
def check_submode_pv_charging(self) -> None:
evu_counter = data.data.counter_all_data.get_evu_counter()

for cp in get_chargepoints_pv_charging():
for cp in get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_PV_ONLY):
def phase_switch_necessary() -> bool:
return cp.cp_ev_chargemode_support_phase_switch() and cp.data.get.phases_in_use != 1
control_parameter = cp.data.control_parameter
Expand Down Expand Up @@ -176,13 +176,13 @@ def phase_switch_necessary() -> bool:
control_parameter.required_currents = [0]*3

def check_switch_on(self) -> None:
for cp in get_chargepoints_pv_charging():
for cp in get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_SURPLUS):
if (cp.data.control_parameter.state == ChargepointState.NO_CHARGING_ALLOWED or
cp.data.control_parameter.state == ChargepointState.SWITCH_ON_DELAY):
data.data.counter_all_data.get_evu_counter().switch_on_threshold_reached(cp)

def set_required_current_to_max(self) -> None:
for cp in get_chargepoints_surplus_controlled():
for cp in get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_PV_ONLY):
charging_ev_data = cp.data.set.charging_ev_data
required_currents = cp.data.control_parameter.required_currents
control_parameter = cp.data.control_parameter
Expand All @@ -202,17 +202,3 @@ def set_required_current_to_max(self) -> None:

control_parameter.required_currents = [max_current if required_currents[i] != 0 else 0 for i in range(3)]
control_parameter.required_current = max_current


def get_chargepoints_pv_charging() -> List[Chargepoint]:
chargepoints: List[Chargepoint] = []
for mode in CONSIDERED_CHARGE_MODES_PV:
chargepoints.extend(get_chargepoints_by_mode(mode))
return chargepoints


def get_chargepoints_surplus_controlled() -> List[Chargepoint]:
chargepoints: List[Chargepoint] = []
for mode in CONSIDERED_CHARGE_MODES:
chargepoints.extend(get_chargepoints_by_mode(mode))
return chargepoints
7 changes: 4 additions & 3 deletions packages/control/algorithm/surplus_controlled_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

from control import data
from control.algorithm import surplus_controlled
from control.algorithm.surplus_controlled import SurplusControlled, get_chargepoints_pv_charging
from control.algorithm.filter_chargepoints import get_chargepoints_by_chargemodes
from control.algorithm.surplus_controlled import CONSIDERED_CHARGE_MODES_PV_ONLY, SurplusControlled
from control.chargemode import Chargemode
from control.chargepoint.chargepoint import Chargepoint, ChargepointData
from control.chargepoint.chargepoint_data import Get, Set
Expand Down Expand Up @@ -89,7 +90,7 @@ def test_set_required_current_to_max(phases: int,
required_currents=required_currents))
mock_cp1.template = CpTemplate()
mock_get_chargepoints_surplus_controlled = Mock(return_value=[mock_cp1])
monkeypatch.setattr(surplus_controlled, "get_chargepoints_surplus_controlled",
monkeypatch.setattr(surplus_controlled, "get_chargepoints_by_chargemodes",
mock_get_chargepoints_surplus_controlled)

# execution
Expand Down Expand Up @@ -149,7 +150,7 @@ def setup_cp(cp: Chargepoint, submode: str) -> Chargepoint:
"cp2": setup_cp(mock_cp2, submode_2)}

# evaluation
chargepoints = get_chargepoints_pv_charging()
chargepoints = get_chargepoints_by_chargemodes(CONSIDERED_CHARGE_MODES_PV_ONLY)

# assertion
assert chargepoints == expected_chargepoints
11 changes: 11 additions & 0 deletions packages/control/bat.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,26 @@ class Get:
fault_state: int = field(default=0, metadata={"topic": "get/fault_state"})
fault_str: str = field(default=NO_ERROR, metadata={"topic": "get/fault_str"})
power: float = field(default=0, metadata={"topic": "get/power"})
power_limit_controlable: bool = field(default=False, metadata={"topic": "get/power_limit_controlable"})


def get_factory() -> Get:
return Get()


@dataclass
class Set:
power_limit: float = field(default=0, metadata={"topic": "set/power_limit"})


def set_factory() -> Set:
return Set()


@dataclass
class BatData:
get: Get = field(default_factory=get_factory)
set: Set = field(default_factory=set_factory)


class Bat:
Expand Down
Loading

0 comments on commit 021867b

Please sign in to comment.