Skip to content

Commit 49f5872

Browse files
committed
Dynamische Netzentgelte
1 parent fd8e066 commit 49f5872

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+588
-297
lines changed

packages/control/ev/charge_template.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,8 +292,8 @@ def eco_charging(self,
292292
current = 0
293293
sub_mode = "stop"
294294
message = self.AMOUNT_REACHED
295-
elif data.data.optional_data.et_provider_available():
296-
if data.data.optional_data.et_is_charging_allowed_price_threshold(eco_charging.max_price):
295+
elif data.data.optional_data.data.electricity_pricing.configured:
296+
if data.data.optional_data.ep_is_charging_allowed_price_threshold(eco_charging.max_price):
297297
sub_mode = "instant_charging"
298298
message = self.CHARGING_PRICE_LOW
299299
phases = max_phases_hw
@@ -608,7 +608,7 @@ def end_of_today_timestamp() -> int:
608608
hour=23, minute=59, second=59, microsecond=999000).timestamp()
609609

610610
def is_loading_hour(hour: int) -> bool:
611-
return data.data.optional_data.et_is_charging_allowed_hours_list(hour)
611+
return data.data.optional_data.ep_is_charging_allowed_hours_list(hour)
612612

613613
def convert_loading_hours_to_string(hour_list: List[int]) -> str:
614614
if 1 < len(hour_list):
@@ -639,11 +639,11 @@ def convert_loading_hours_to_string(hour_list: List[int]) -> str:
639639
else '')
640640
return loading_message + '.'
641641

642-
hour_list = data.data.optional_data.et_get_loading_hours(
642+
hour_list = data.data.optional_data.ep_get_loading_hours(
643643
selected_plan.duration, selected_plan.duration + selected_plan.remaining_time)
644644

645645
log.debug(f"Günstige Ladezeiten: {hour_list}")
646-
if data.data.optional_data.et_is_charging_allowed_hours_list(hour_list):
646+
if data.data.optional_data.ep_is_charging_allowed_hours_list(hour_list):
647647
message = self.SCHEDULED_CHARGING_CHEAP_HOUR.format(get_hours_message())
648648
current = plan_current
649649
submode = "instant_charging"

packages/control/ev/charge_template_test.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ def test_time_charging(plans: Dict[int, TimeChargingPlan], soc: float, used_amou
8888
def test_instant_charging(selected: str, current_soc: float, used_amount: float,
8989
expected: Tuple[int, str, Optional[str]]):
9090
# setup
91-
data.data.optional_data.data.et.active = False
9291
ct = ChargeTemplate()
9392
ct.data.chargemode.instant_charging.limit.selected = selected
9493
ct.data.chargemode.instant_charging.limit.amount = 1000
@@ -377,10 +376,10 @@ def test_scheduled_charging_calc_current_electricity_tariff(
377376
plan.limit.selected = "soc"
378377
ct.data.chargemode.scheduled_charging.plans = [plan]
379378
# für Github-Test keinen Zeitstempel verwenden
380-
mock_et_get_loading_hours = Mock(return_value=loading_hours)
381-
monkeypatch.setattr(data.data.optional_data, "et_get_loading_hours", mock_et_get_loading_hours)
379+
mock_ep_get_loading_hours = Mock(return_value=loading_hours)
380+
monkeypatch.setattr(data.data.optional_data, "ep_get_loading_hours", mock_ep_get_loading_hours)
382381
mock_is_list_valid = Mock(return_value=is_loading_hour)
383-
monkeypatch.setattr(data.data.optional_data, "et_is_charging_allowed_hours_list", mock_is_list_valid)
382+
monkeypatch.setattr(data.data.optional_data, "ep_is_charging_allowed_hours_list", mock_is_list_valid)
384383

385384
# execution
386385
ret = ct.scheduled_charging_calc_current(

packages/control/optional.py

Lines changed: 98 additions & 85 deletions
Large diffs are not rendered by default.

packages/control/optional_data.py

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,54 @@
77

88

99
@dataclass
10-
class EtGet:
10+
class PricingGet:
1111
fault_state: int = 0
1212
fault_str: str = NO_ERROR
13+
prices: Dict = field(default_factory=empty_dict_factory)
14+
15+
16+
def get_factory() -> PricingGet:
17+
return PricingGet()
18+
19+
20+
@dataclass
21+
class FlexibleTariff:
22+
get: PricingGet = field(default_factory=get_factory)
23+
24+
25+
def get_flexible_tariff_factory() -> FlexibleTariff:
26+
return FlexibleTariff()
27+
28+
29+
@dataclass
30+
class GridFee:
31+
get: PricingGet = field(default_factory=get_factory)
32+
33+
34+
def get_grid_fee_factory() -> GridFee:
35+
return GridFee()
36+
37+
38+
@dataclass
39+
class ElectricityPricingGet:
1340
next_query_time: Optional[float] = None
1441
prices: Dict = field(default_factory=empty_dict_factory)
1542

1643

17-
def get_factory() -> EtGet:
18-
return EtGet()
44+
def electricity_pricing_get_factory() -> ElectricityPricingGet:
45+
return ElectricityPricingGet()
1946

2047

2148
@dataclass
22-
class Et:
23-
get: EtGet = field(default_factory=get_factory)
49+
class ElectricityPricing:
50+
configured: bool = False
51+
flexible_tariff: FlexibleTariff = field(default_factory=get_flexible_tariff_factory)
52+
grid_fee: GridFee = field(default_factory=get_grid_fee_factory)
53+
get: ElectricityPricingGet = field(default_factory=electricity_pricing_get_factory)
2454

2555

26-
def et_factory() -> Et:
27-
return Et()
56+
def ep_factory() -> ElectricityPricing:
57+
return ElectricityPricing()
2858

2959

3060
@dataclass
@@ -84,7 +114,7 @@ def ocpp_factory() -> Ocpp:
84114

85115
@dataclass
86116
class OptionalData:
87-
et: Et = field(default_factory=et_factory)
117+
electricity_pricing: ElectricityPricing = field(default_factory=ep_factory)
88118
int_display: InternalDisplay = field(default_factory=int_display_factory)
89119
led: Led = field(default_factory=led_factory)
90120
rfid: Rfid = field(default_factory=rfid_factory)

packages/control/optional_test.py

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from unittest.mock import Mock
2-
from control.optional import Optional
3-
from helpermodules import timecheck
42
import pytest
3+
from helpermodules import timecheck
4+
from control.optional import Optional
5+
56

67
ONE_HOUR_SECONDS = 3600
78
IGNORED = 0.0001
@@ -220,7 +221,7 @@
220221
),
221222
],
222223
)
223-
def test_et_get_loading_hours(granularity,
224+
def test_ep_get_loading_hours(granularity,
224225
now_ts,
225226
duration,
226227
remaining_time,
@@ -229,17 +230,16 @@ def test_et_get_loading_hours(granularity,
229230
monkeypatch):
230231
# setup
231232
opt = Optional()
232-
opt.data.et.get.prices = price_list
233-
mock_et_provider_available = Mock(return_value=True)
234-
monkeypatch.setattr(opt, "et_provider_available", mock_et_provider_available)
233+
opt.data.electricity_pricing.get.prices = price_list
234+
opt.data.electricity_pricing.configured = True
235235
monkeypatch.setattr(
236236
timecheck,
237237
"create_timestamp",
238238
Mock(return_value=now_ts)
239239
)
240240

241241
# execution
242-
loading_hours = opt.et_get_loading_hours(duration=duration, remaining_time=remaining_time)
242+
loading_hours = opt.ep_get_loading_hours(duration=duration, remaining_time=remaining_time)
243243

244244
# evaluation
245245
assert loading_hours == expected_loading_hours
@@ -256,18 +256,18 @@ def test_et_get_loading_hours(granularity,
256256
)
257257
def test_et_charging_allowed(monkeypatch, provider_available, current_price, max_price, expected):
258258
opt = Optional()
259-
monkeypatch.setattr(opt, "et_provider_available", Mock(return_value=provider_available))
259+
opt.data.electricity_pricing.configured = provider_available
260260
if provider_available:
261-
monkeypatch.setattr(opt, "et_get_current_price", Mock(return_value=current_price))
262-
result = opt.et_is_charging_allowed_price_threshold(max_price)
261+
monkeypatch.setattr(opt, "ep_get_current_price", Mock(return_value=current_price))
262+
result = opt.ep_is_charging_allowed_price_threshold(max_price)
263263
assert result == expected
264264

265265

266266
def test_et_charging_allowed_exception(monkeypatch):
267267
opt = Optional()
268-
monkeypatch.setattr(opt, "et_provider_available", Mock(return_value=True))
269-
monkeypatch.setattr(opt, "et_get_current_price", Mock(side_effect=Exception))
270-
result = opt.et_is_charging_allowed_price_threshold(0.15)
268+
opt.data.electricity_pricing.configured = True
269+
monkeypatch.setattr(opt, "ep_get_current_price", Mock(side_effect=Exception))
270+
result = opt.ep_is_charging_allowed_price_threshold(0.15)
271271
assert result is False
272272

273273

@@ -425,17 +425,18 @@ def test_et_charging_available(now_ts, provider_available, price_list, selected_
425425
Mock(return_value=now_ts)
426426
)
427427
opt = Optional()
428-
opt.data.et.get.prices = price_list
429-
monkeypatch.setattr(opt, "et_provider_available", Mock(return_value=provider_available))
430-
result = opt.et_is_charging_allowed_hours_list(selected_hours)
428+
opt.data.electricity_pricing.get.prices = price_list
429+
opt.data.electricity_pricing.configured = provider_available
430+
result = opt.ep_is_charging_allowed_hours_list(selected_hours)
431431
assert result == expected
432432

433433

434434
def test_et_charging_available_exception(monkeypatch):
435435
opt = Optional()
436-
monkeypatch.setattr(opt, "et_provider_available", Mock(return_value=True))
437-
opt.data.et.get.prices = {} # empty prices list raises exception
438-
result = opt.et_is_charging_allowed_hours_list([])
436+
opt.data.electricity_pricing.configured = True
437+
438+
opt.data.electricity_pricing.get.prices = {} # empty prices list raises exception
439+
result = opt.ep_is_charging_allowed_hours_list([])
439440
assert result is False
440441

441442

@@ -446,10 +447,6 @@ def test_et_charging_available_exception(monkeypatch):
446447
{}, None, 1698224400, True,
447448
id="update_required_when_no_prices"
448449
),
449-
pytest.param(
450-
{"1698224400": 0.1, "1698228000": 0.2}, None, 1698224400, False,
451-
id="no_update_required_when_prices_available_and_recent"
452-
),
453450
pytest.param(
454451
{"1698224400": 0.1, "1698228000": 0.2}, 1698310800, 1698224400, False,
455452
id="no_update_required_when_next_query_time_not_reached"
@@ -467,10 +464,11 @@ def test_et_charging_available_exception(monkeypatch):
467464
def test_et_price_update_required(monkeypatch, prices, next_query_time, current_timestamp, expected):
468465
# setup
469466
opt = Optional()
470-
opt.data.et.get.prices = prices
471-
opt.data.et.get.next_query_time = next_query_time
467+
opt.data.electricity_pricing.get.prices = prices
468+
opt.data.electricity_pricing.get.next_query_time = next_query_time
472469

473470
monkeypatch.setattr(timecheck, "create_timestamp", Mock(return_value=current_timestamp))
471+
opt.data.electricity_pricing.configured = True
474472

475473
# execution
476474
result = opt.et_price_update_required()

packages/helpermodules/create_debug.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ def __on_connect_broker_essentials(self, client, userdata, flags, rc):
504504
client.subscribe("openWB/counter/#", 2)
505505
client.subscribe("openWB/pv/#", 2)
506506
client.subscribe("openWB/bat/#", 2)
507-
client.subscribe("openWB/optional/et/provider", 2)
507+
client.subscribe("openWB/optional/ep/flexible_tariff/provider", 2)
508508

509509
def __on_connect_bridges(self, client, userdata, flags, rc):
510510
client.subscribe("openWB/system/mqtt/#", 2)

packages/helpermodules/measurement_logging/write_log.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,9 @@ def create_entry(log_type: LogType, sh_log_data: LegacySmartHomeLogData, previou
199199
try:
200200
prices = data.data.general_data.data.prices
201201
try:
202-
grid_price = data.data.optional_data.et_get_current_price()
202+
grid_price = data.data.optional_data.ep_get_current_price()
203203
except Exception:
204+
log.exception("Fehler im Werte-Logging-Modul für aktuellen Netzpreis, nutze hinterlegten Netzpreis")
204205
grid_price = prices.grid
205206
prices_dict = {"grid": grid_price,
206207
"pv": prices.pv,

packages/helpermodules/setdata.py

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -847,16 +847,27 @@ def process_optional_topic(self, msg: mqtt.MQTTMessage):
847847
enthält Topic und Payload
848848
"""
849849
try:
850-
if "openWB/set/optional/et/get/prices" in msg.topic:
850+
pricing_regex = "openWB/set/optional/ep/(flexible_tariff|grid_fee)/"
851+
if re.search(pricing_regex, msg.topic) is not None:
852+
if re.search(f"{pricing_regex}provider$", msg.topic) is not None:
853+
self._validate_value(msg, "json")
854+
elif re.search(f"{pricing_regex}get/prices$", msg.topic) is not None:
855+
self._validate_value(msg, "json")
856+
elif re.search(f"{pricing_regex}get/price$", msg.topic) is not None:
857+
self._validate_value(msg, float)
858+
elif re.search(f"{pricing_regex}get/fault_state$", msg.topic) is not None:
859+
self._validate_value(msg, int, [(0, 2)])
860+
elif re.search(f"{pricing_regex}get/fault_str$", msg.topic) is not None:
861+
self._validate_value(msg, str)
862+
elif "openWB/set/optional/ep/get/prices" in msg.topic:
851863
self._validate_value(msg, "json")
852-
elif "openWB/set/optional/et/get/next_query_time" in msg.topic:
864+
elif "openWB/set/optional/ep/get/next_query_time" in msg.topic:
853865
self._validate_value(msg, float)
854-
elif "openWB/set/optional/et/get/fault_state" in msg.topic:
855-
self._validate_value(msg, int, [(0, 2)])
856-
elif "openWB/set/optional/et/get/fault_str" in msg.topic:
857-
self._validate_value(msg, str)
858-
elif ("openWB/set/optional/et/provider" in msg.topic or
859-
"openWB/set/optional/ocpp/config" in msg.topic):
866+
elif "openWB/set/optional/ep/configured" in msg.topic:
867+
self._validate_value(msg, bool)
868+
elif "module_update_completed" in msg.topic:
869+
self._validate_value(msg, bool)
870+
elif "openWB/set/optional/ocpp/config" in msg.topic:
860871
self._validate_value(msg, "json")
861872
elif "openWB/set/optional/monitoring" in msg.topic:
862873
self._validate_value(msg, "json")

packages/helpermodules/subdata.py

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from dataclass_utils import dataclass_from_dict
3131
from modules.common.abstract_vehicle import CalculatedSocState, GeneralVehicleConfig
3232
from modules.common.configurable_backup_cloud import ConfigurableBackupCloud
33-
from modules.common.configurable_tariff import ConfigurableElectricityTariff
33+
from modules.common.configurable_tariff import ConfigurableFlexibleTariff, ConfigurableGridFee
3434
from modules.common.simcount.simcounter_state import SimCounterState
3535
from modules.internal_chargepoint_handler.internal_chargepoint_handler_config import (
3636
GlobalHandlerData, InternalChargepoint, RfidData)
@@ -712,23 +712,42 @@ def process_optional_topic(self, var: optional.Optional, msg: mqtt.MQTTMessage):
712712
run_command([
713713
str(Path(__file__).resolve().parents[2] / "runs" / "update_local_display.sh")
714714
], process_exception=True)
715-
elif re.search("/optional/et/", msg.topic) is not None:
716-
if re.search("/optional/et/get/prices", msg.topic) is not None:
717-
var.data.et.get.prices = decode_payload(msg.payload)
718-
elif re.search("/optional/et/get/", msg.topic) is not None:
719-
self.set_json_payload_class(var.data.et.get, msg)
720-
elif re.search("/optional/et/provider$", msg.topic) is not None:
715+
elif re.search("/optional/ep/(flexible_tariff|grid_fee)/", msg.topic) is not None:
716+
if re.search("/optional/ep/flexible_tariff/provider$", msg.topic) is not None:
721717
config_dict = decode_payload(msg.payload)
722718
if config_dict["type"] is None:
723-
var.et_module = None
719+
var.flexible_tariff_module = None
724720
else:
725721
mod = importlib.import_module(
726-
f".electricity_tariffs.{config_dict['type']}.tariff", "modules")
722+
f".electricity_pricing.flexible_tariffs.{config_dict['type']}.tariff", "modules")
727723
config = dataclass_from_dict(mod.device_descriptor.configuration_factory, config_dict)
728-
var.et_module = ConfigurableElectricityTariff(config, mod.create_electricity_tariff)
729-
var.et_get_prices()
730-
else:
731-
self.set_json_payload_class(var.data.et, msg)
724+
var.flexible_tariff_module = ConfigurableFlexibleTariff(
725+
config, mod.create_electricity_tariff)
726+
elif re.search("/optional/ep/flexible_tariff/get/prices", msg.topic) is not None:
727+
var.data.electricity_pricing.flexible_tariff.get.prices = decode_payload(msg.payload)
728+
elif re.search("/optional/ep/flexible_tariff/get/", msg.topic) is not None:
729+
self.set_json_payload_class(var.data.electricity_pricing.flexible_tariff.get, msg)
730+
elif re.search("/optional/ep/grid_fee/provider$", msg.topic) is not None:
731+
config_dict = decode_payload(msg.payload)
732+
if config_dict["type"] is None:
733+
var.grid_fee_module = None
734+
else:
735+
mod = importlib.import_module(
736+
f".electricity_pricing.grid_fees.{config_dict['type']}.tariff", "modules")
737+
config = dataclass_from_dict(mod.device_descriptor.configuration_factory, config_dict)
738+
var.grid_fee_module = ConfigurableGridFee(config, mod.create_electricity_tariff)
739+
elif re.search("/optional/ep/grid_fee/get/prices", msg.topic) is not None:
740+
var.data.electricity_pricing.grid_fee.get.prices = decode_payload(msg.payload)
741+
elif re.search("/optional/ep/grid_fee/get/", msg.topic) is not None:
742+
self.set_json_payload_class(var.data.electricity_pricing.grid_fee.get, msg)
743+
elif re.search("/optional/ep/get/prices", msg.topic) is not None:
744+
var.data.electricity_pricing.get.prices = decode_payload(msg.payload)
745+
elif re.search("/optional/ep/get/", msg.topic) is not None:
746+
self.set_json_payload_class(var.data.electricity_pricing.get, msg)
747+
elif re.search("/optional/ep/", msg.topic) is not None:
748+
self.set_json_payload_class(var.data.electricity_pricing, msg)
749+
elif "module_update_completed" in msg.topic:
750+
self.event_module_update_completed.set()
732751
elif re.search("/optional/ocpp/", msg.topic) is not None:
733752
config_dict = decode_payload(msg.payload)
734753
var.data.ocpp = dataclass_from_dict(Ocpp, config_dict)

0 commit comments

Comments
 (0)