Skip to content

Commit 0493f0b

Browse files
committed
separate update times for flexible tariff and grid fee
1 parent af1030b commit 0493f0b

12 files changed

Lines changed: 256 additions & 168 deletions

File tree

packages/control/optional.py

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def flexible_tariff_module(self, value: TypingOptional[ConfigurableFlexibleTarif
4444
(self._flexible_tariff_module and value and
4545
self._flexible_tariff_module.config.name != value.config.name)):
4646
self.data.electricity_pricing.flexible_tariff.get = PricingGet()
47-
self._reset_state(self.data.electricity_pricing.flexible_tariff, "flexible_tariff")
47+
self._reset_state(self.data.electricity_pricing.flexible_tariff)
4848
self._flexible_tariff_module = value
4949
self._set_ep_configured()
5050

@@ -55,9 +55,10 @@ def grid_fee_module(self) -> TypingOptional[ConfigurableGridFee]:
5555
@grid_fee_module.setter
5656
def grid_fee_module(self, value: TypingOptional[ConfigurableGridFee]):
5757
if (value is None or
58-
(self._grid_fee_module and value and self._grid_fee_module.config.name != value.config.name)):
58+
(self._grid_fee_module and value and
59+
self._grid_fee_module.config.name != value.config.name)):
5960
self.data.electricity_pricing.grid_fee.get = PricingGet()
60-
self._reset_state(self.data.electricity_pricing.grid_fee, "grid_fee")
61+
self._reset_state(self.data.electricity_pricing.grid_fee)
6162
self._grid_fee_module = value
6263
self._set_ep_configured()
6364

@@ -69,15 +70,15 @@ def _set_ep_configured(self):
6970
self.data.electricity_pricing.configured = False
7071
Pub().pub("openWB/set/optional/ep/configured", False)
7172

72-
def _reset_state(self, module: Union[FlexibleTariff, GridFee], module_name: str):
73+
def _reset_state(self, module: Union[FlexibleTariff, GridFee]):
7374
if (module.get.fault_state != 0 or module.get.fault_str != NO_ERROR):
7475
module.get.fault_state = 0
7576
module.get.fault_str = NO_ERROR
76-
Pub().pub(f"openWB/set/optional/ep/{module_name}/get/fault_state", 0)
77-
Pub().pub(f"openWB/set/optional/ep/{module_name}/get/fault_str", NO_ERROR)
78-
Pub().pub(f"openWB/set/optional/ep/{module_name}/get/prices", {})
77+
Pub().pub(f"openWB/set/optional/ep/{module.name}/get/fault_state", 0)
78+
Pub().pub(f"openWB/set/optional/ep/{module.name}/get/fault_str", NO_ERROR)
79+
Pub().pub(f"openWB/set/optional/ep/{module.name}/get/prices", {})
80+
Pub().pub(f"openWB/set/optional/ep/{module.name}/get/next_query_time", None)
7981
Pub().pub("openWB/set/optional/ep/get/prices", {})
80-
Pub().pub("openWB/set/optional/ep/get/next_query_time", None)
8182

8283
def monitoring_start(self):
8384
if self.monitoring_module is not None:
@@ -104,7 +105,7 @@ def ep_is_charging_allowed_hours_list(self, selected_hours: list[int]) -> bool:
104105
if self.data.electricity_pricing.configured:
105106
return self.__get_current_timeslot_start() in selected_hours
106107
else:
107-
log.info("Prüfe strompreisbasiertes Laden: Nicht konfiguriert")
108+
log.debug("Prüfe strompreisbasiertes Laden: Nicht konfiguriert")
108109
return False
109110
except Exception as e:
110111
log.exception(f"Fehler im Optional-Modul: {e}")
@@ -144,14 +145,13 @@ def __get_first_entry(self) -> tuple[str, float]:
144145
return timestamp, first
145146

146147
def remove_outdated_prices(self):
147-
def remove(price_data: Dict) -> Dict:
148-
price_timeslot_seconds = self.__calculate_price_timeslot_length(price_data)
148+
def remove(price_data: Dict, last_active_timestamp: int = 0) -> Dict:
149149
now = timecheck.create_timestamp()
150-
return {
151-
price[0]: price[1]
152-
for price in price_data.items()
153-
if float(price[0]) > now - (price_timeslot_seconds - 1)
154-
}
150+
first_timestamp = (max([timestamp for timestamp in price_data.keys()
151+
if float(timestamp) <= now], default=0))
152+
return {int(timestamp): price for timestamp, price in price_data.items()
153+
if (timestamp) >= first_timestamp and
154+
(int(timestamp) <= last_active_timestamp if last_active_timestamp > 0 else True)}
155155

156156
try:
157157
if self.data.electricity_pricing.configured:
@@ -162,7 +162,10 @@ def remove(price_data: Dict) -> Dict:
162162
if self._flexible_tariff_module:
163163
Pub().pub(f"{MQTT_PREFIX}/flexible_tariff/get/prices", remove(ep.flexible_tariff.get.prices))
164164
if self._grid_fee_module:
165-
Pub().pub(f"{MQTT_PREFIX}/grid_fee/get/prices", remove(ep.grid_fee.get.prices))
165+
Pub().pub(f"{MQTT_PREFIX}/grid_fee/get/prices",
166+
remove(ep.grid_fee.get.prices,
167+
last_active_timestamp=int(max(ep.get.prices.keys()))
168+
if ep.get.prices else 0))
166169
except Exception as e:
167170
log.exception("Fehler beim Entfernen veralteter Preise: %s", e)
168171

@@ -178,7 +181,7 @@ def ep_get_current_price(self) -> float:
178181
raise Exception("Kein Anbieter für strompreisbasiertes Laden konfiguriert.")
179182

180183
def __calculate_price_timeslot_length(self, prices: dict) -> int:
181-
first_timestamps = list(prices.keys())[:2]
184+
first_timestamps = sorted(list(prices.keys()))[:2]
182185
return float(first_timestamps[1]) - float(first_timestamps[0])
183186

184187
def ep_get_loading_hours(self, duration: float, remaining_time: float) -> List[int]:
@@ -197,7 +200,12 @@ def ep_get_loading_hours(self, duration: float, remaining_time: float) -> List[i
197200
raise Exception("Kein Anbieter für strompreisbasiertes Laden konfiguriert.")
198201
try:
199202
prices = self.data.electricity_pricing.get.prices
203+
log.debug("Berechne günstige Zeit-Slots für strompreisbasiertes Laden, "
204+
"benötigte Ladezeit: %.2f Sekunden, Restzeit bis Termin: %.2f Sekunden",
205+
duration, remaining_time)
206+
log.debug("Verfügbare Preise: %s", prices)
200207
price_timeslot_seconds = self.__calculate_price_timeslot_length(prices)
208+
log.debug("Preis-Zeitslot-Länge: %.2f Sekunden", price_timeslot_seconds)
201209
now = timecheck.create_timestamp()
202210
price_candidates = {
203211
timestamp: price
@@ -214,43 +222,47 @@ def ep_get_loading_hours(self, duration: float, remaining_time: float) -> List[i
214222
duration,
215223
datetime.fromtimestamp(now),
216224
datetime.fromtimestamp(now + remaining_time),
217-
datetime.fromtimestamp(float(min(price_candidates))),
218-
datetime.fromtimestamp(float(max(price_candidates))+price_timeslot_seconds))
225+
datetime.fromtimestamp(float(min(price_candidates, default=0))),
226+
datetime.fromtimestamp(float(max(price_candidates, default=0))+price_timeslot_seconds))
219227
ordered_by_date_reverse = reversed(sorted(price_candidates.items(), key=lambda x: x[0]))
220228
ordered_by_price = sorted(ordered_by_date_reverse, key=lambda x: x[1])
221229
selected_time_slots = {float(i[0]): float(i[1])
222230
for i in ordered_by_price[:1 + ceil(duration/price_timeslot_seconds)]}
223231
selected_lenght = (
224232
price_timeslot_seconds * (len(selected_time_slots)-1) -
225-
(float(now) - min(selected_time_slots))
233+
(float(now) - min(selected_time_slots, default=now))
226234
)
227235
return sorted(selected_time_slots.keys()
228-
if not (min(selected_time_slots) > now or duration <= selected_lenght)
236+
if not (min(selected_time_slots, default=0) > now or duration <= selected_lenght)
229237
else [timestamp[0] for timestamp in iter(selected_time_slots.items())][:-1]
230238
)
231239
# if sum() sorted([int(i[0]) for i in ordered_by_price][:ceil(duration/price_timeslot_seconds)])
232240
except Exception as e:
233241
log.exception("Fehler im Optional-Modul: %s", e)
234242
return []
235243

244+
def _is_et_price_update_required_for_module(self, module: Union[FlexibleTariff, GridFee]) -> bool:
245+
if module is None:
246+
return False
247+
if module.get.next_query_time is None:
248+
return True
249+
now = timecheck.create_timestamp()
250+
next_query_formatted = datetime.fromtimestamp(module.get.next_query_time).strftime("%Y%m%d-%H:%M:%S")
251+
if now > module.get.next_query_time:
252+
log.debug(f'Wartezeit {next_query_formatted} für {module.name} abgelaufen, Strompreise werden abgefragt')
253+
return True
254+
else:
255+
log.debug(f'Nächster Abruf der {module.name} Strompreise {next_query_formatted}.')
256+
return False
257+
236258
def et_price_update_required(self) -> bool:
237259
self._set_ep_configured()
238260
if self.data.electricity_pricing.configured is False:
239261
return False
240262
if len(self.data.electricity_pricing.get.prices) == 0:
241263
return True
242-
if self.data.electricity_pricing.get.next_query_time is None:
243-
return True
244-
if timecheck.create_timestamp() > self.data.electricity_pricing.get.next_query_time:
245-
next_query_formatted = datetime.fromtimestamp(
246-
self.data.electricity_pricing.get.next_query_time).strftime("%Y%m%d-%H:%M:%S")
247-
log.info(f'Wartezeit {next_query_formatted} abgelaufen, Strompreise werden abgefragt')
248-
return True
249-
else:
250-
next_query_formatted = datetime.fromtimestamp(
251-
self.data.electricity_pricing.get.next_query_time).strftime("%Y%m%d-%H:%M:%S")
252-
log.info(f'Nächster Abruf der Strompreise {next_query_formatted}.')
253-
return False
264+
return (self._is_et_price_update_required_for_module(self.data.electricity_pricing.flexible_tariff) or
265+
self._is_et_price_update_required_for_module(self.data.electricity_pricing.grid_fee))
254266

255267
def ocpp_transfer_meter_values(self):
256268
try:

packages/control/optional_data.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
@dataclass
1010
class PricingGet:
1111
fault_state: int = field(default=0)
12+
next_query_time: int = field(default=0)
1213
fault_str: str = field(default=NO_ERROR)
1314
prices: Dict = field(default_factory=empty_dict_factory)
1415

@@ -19,6 +20,7 @@ def create_pricing_get_with_topics(topic_prefix: str) -> PricingGet:
1920
pricing_get.__dataclass_fields__['fault_state'].metadata = {"topic": f"{topic_prefix}/get/fault_state"}
2021
pricing_get.__dataclass_fields__['fault_str'].metadata = {"topic": f"{topic_prefix}/get/fault_str"}
2122
pricing_get.__dataclass_fields__['prices'].metadata = {"topic": f"{topic_prefix}/get/prices"}
23+
pricing_get.__dataclass_fields__['next_query_time'].metadata = {"topic": f"{topic_prefix}/get/next_query_time"}
2224
return pricing_get
2325

2426

@@ -33,6 +35,7 @@ def grid_fee_get_factory() -> PricingGet:
3335
@dataclass
3436
class FlexibleTariff:
3537
get: PricingGet = field(default_factory=flexible_tariff_get_factory)
38+
name: str = field(default="flexible_tariff")
3639

3740

3841
def get_flexible_tariff_factory() -> FlexibleTariff:
@@ -42,6 +45,7 @@ def get_flexible_tariff_factory() -> FlexibleTariff:
4245
@dataclass
4346
class GridFee:
4447
get: PricingGet = field(default_factory=grid_fee_get_factory)
48+
name: str = field(default="grid_fee")
4549

4650

4751
def get_grid_fee_factory() -> GridFee:
@@ -50,7 +54,6 @@ def get_grid_fee_factory() -> GridFee:
5054

5155
@dataclass
5256
class ElectricityPricingGet:
53-
next_query_time: Optional[int] = field(default=None, metadata={"topic": "ep/next_query_time"})
5457
_prices: Dict = field(default_factory=empty_dict_factory, metadata={"topic": "ep/prices"})
5558

5659
@property

packages/control/optional_test.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -441,33 +441,43 @@ def test_et_charging_available_exception(monkeypatch):
441441

442442

443443
@pytest.mark.parametrize(
444-
"prices, next_query_time, current_timestamp, expected",
444+
"flexible_tariff, grid_fee, current_timestamp, expected",
445445
[
446446
pytest.param(
447-
{}, None, 1698224400, True,
447+
{"prices": {}, "next_query_time": None},
448+
{"prices": {}, "next_query_time": None},
449+
1698224400, True,
448450
id="update_required_when_no_prices"
449451
),
450452
pytest.param(
451-
{"1698224400": 0.1, "1698228000": 0.2}, 1698310800, 1698224400, False,
453+
{"prices": {"1698224400": 0.1, "1698228000": 0.2}, "next_query_time": 1698310800},
454+
{"prices": {}, "next_query_time": None},
455+
1698224400, False,
452456
id="no_update_required_when_next_query_time_not_reached"
453457
),
454458
pytest.param(
455-
{"1698224400": 0.1, "1698228000": 0.2}, 1698224000, 1698310800, True,
459+
{"prices": {"1698224400": 0.1, "1698228000": 0.2}, "next_query_time": 1698224000},
460+
{"prices": {}, "next_query_time": None},
461+
1698310800, True,
456462
id="update_required_when_next_query_time_passed"
457463
),
458464
pytest.param(
459-
{"1609459200": 0.1, "1609462800": 0.2}, None, 1698224400, True,
465+
{"prices": {"1609459200": 0.1, "1609462800": 0.2}, "next_query_time": None},
466+
{"prices": {}, "next_query_time": None},
467+
1698224400, True,
460468
id="update_required_when_prices_from_yesterday"
461469
),
462470
]
463471
)
464-
def test_et_price_update_required(monkeypatch, prices, next_query_time, current_timestamp, expected):
472+
def test_et_price_update_required(monkeypatch, flexible_tariff, grid_fee, current_timestamp, expected):
465473
# setup
466474
opt = Optional()
467475
opt._flexible_tariff_module = Mock()
468476
opt._grid_fee_module = Mock()
469-
opt.data.electricity_pricing.get.prices = prices
470-
opt.data.electricity_pricing.get.next_query_time = next_query_time
477+
opt.data.electricity_pricing.get.prices = flexible_tariff["prices"]
478+
opt.data.electricity_pricing.flexible_tariff.get.prices = flexible_tariff["prices"]
479+
opt.data.electricity_pricing.flexible_tariff.get.next_query_time = flexible_tariff["next_query_time"]
480+
opt.data.electricity_pricing.grid_fee = None
471481

472482
monkeypatch.setattr(timecheck, "create_timestamp", Mock(return_value=current_timestamp))
473483
opt.data.electricity_pricing.configured = True

packages/helpermodules/setdata.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -856,12 +856,12 @@ def process_optional_topic(self, msg: mqtt.MQTTMessage):
856856
self._validate_value(msg, float)
857857
elif re.search(f"{pricing_regex}get/fault_state$", msg.topic) is not None:
858858
self._validate_value(msg, int, [(0, 2)])
859+
elif re.search(f"{pricing_regex}get/next_query_time$", msg.topic) is not None:
860+
self._validate_value(msg, int)
859861
elif re.search(f"{pricing_regex}get/fault_str$", msg.topic) is not None:
860862
self._validate_value(msg, str)
861863
elif "openWB/set/optional/ep/get/prices" in msg.topic:
862864
self._validate_value(msg, "json")
863-
elif "openWB/set/optional/ep/get/next_query_time" in msg.topic:
864-
self._validate_value(msg, int)
865865
elif "openWB/set/optional/ep/configured" in msg.topic:
866866
self._validate_value(msg, bool)
867867
elif "module_update_completed" in msg.topic:

0 commit comments

Comments
 (0)