11from typing import TypeVar , Generic , Callable
2- from helpermodules . timecheck import (
3- create_unix_timestamp_current_quarter_hour ,
4- create_unix_timestamp_current_full_hour
5- )
2+ from datetime import datetime , timedelta
3+ from helpermodules import timecheck
4+ import random
5+ import logging
66from modules .common import store
77from modules .common .component_context import SingleComponentUpdateContext
88from modules .common .component_state import TariffState
1111
1212
1313T_TARIFF_CONFIG = TypeVar ("T_TARIFF_CONFIG" )
14+ ONE_HOUR_SECONDS : int = 3600
15+ log = logging .getLogger (__name__ )
1416
1517
1618class ConfigurableElectricityTariff (Generic [T_TARIFF_CONFIG ]):
1719 def __init__ (self ,
1820 config : T_TARIFF_CONFIG ,
1921 component_initializer : Callable [[], float ]) -> None :
22+ self .__next_query_time = datetime .fromtimestamp (1 )
23+ self .__tariff_state : TariffState = None
2024 self .config = config
2125 self .store = store .get_electricity_tariff_value_store ()
2226 self .fault_state = FaultState (ComponentInfo (None , self .config .name , ComponentType .ELECTRICITY_TARIFF .value ))
@@ -26,33 +30,49 @@ def __init__(self,
2630 with SingleComponentUpdateContext (self .fault_state ):
2731 self ._component_updater = component_initializer (config )
2832
33+ def __calulate_next_query_time (self ) -> None :
34+ self .__next_query_time = datetime .now ().replace (
35+ hour = 14 , minute = 0 , second = 0
36+ ) + timedelta (
37+ # aktually ET providers issue next day prices up to half an hour earlier then 14:00
38+ # reduce serverload on their site by randomizing query time
39+ minutes = random .randint (- 7 , 7 ),
40+ seconds = random .randint (0 , 59 )
41+ )
42+ if datetime .now () > self .__next_query_time :
43+ self .__next_query_time += timedelta (days = 1 )
44+
2945 def update (self ):
3046 if hasattr (self , "_component_updater" ):
31- # Wenn beim Initialisieren etwas schief gelaufen ist, ursprüngliche Fehlermeldung beibehalten
32- with SingleComponentUpdateContext (self .fault_state ):
33- tariff_state = self ._remove_outdated_prices (self ._component_updater ())
34- self .store .set (tariff_state )
35- self .store .update ()
36- expected_time_slots = 24 * tariff_state .prices_per_hour
37- if len (tariff_state .prices ) < expected_time_slots :
38- self .fault_state .no_error (
39- f'Die Preisliste hat nicht { expected_time_slots } , '
40- f'sondern { len (tariff_state .prices )} Einträge. '
41- 'Die Strompreise werden vom Anbieter erst um 14:00 für den Folgetag aktualisiert.' )
42-
43- def _remove_outdated_prices (self , tariff_state : TariffState , ONE_HOUR_SECONDS : int = 3600 ) -> TariffState :
44- first_timestamps = list (tariff_state .prices .keys ())[:2 ]
45- timeslot_length_seconds = int (first_timestamps [1 ]) - int (first_timestamps [0 ])
46- is_hourely_prices = ONE_HOUR_SECONDS == timeslot_length_seconds
47- current_hour = (
48- create_unix_timestamp_current_full_hour ()
49- if is_hourely_prices
50- else create_unix_timestamp_current_quarter_hour ()
51- )
47+ if datetime .now () > self .__next_query_time :
48+ # Wenn beim Initialisieren etwas schief gelaufen ist, ursprüngliche Fehlermeldung beibehalten
49+ with SingleComponentUpdateContext (self .fault_state ):
50+ self .__tariff_state = self ._component_updater ()
51+ self .__calulate_next_query_time ()
52+ log .debug (f'nächster Abruf der Strompreise nach { self .__next_query_time .strftime ("%Y%m%d-%H:%M" )} ' )
53+ timeslot_length_seconds = self .__calculate_price_timeslot_length ()
54+ self .__tariff_state = self ._remove_outdated_prices (self .__tariff_state , timeslot_length_seconds )
55+ self .store .set (self .__tariff_state )
56+ self .store .update ()
57+ expected_time_slots = int (24 * ONE_HOUR_SECONDS / timeslot_length_seconds )
58+ if len (self .__tariff_state .prices ) < expected_time_slots :
59+ self .fault_state .no_error (
60+ f'Die Preisliste hat nicht { expected_time_slots } , '
61+ f'sondern { len (self .__tariff_state .prices )} Einträge. '
62+ f'nächster Abruf der Strompreise nach { self .__next_query_time .strftime ("%Y%m%d-%H:%M" )} ' )
63+
64+ def __calculate_price_timeslot_length (self ) -> int :
65+ first_timestamps = list (self .__tariff_state .prices .keys ())[:2 ]
66+ return int (first_timestamps [1 ]) - int (first_timestamps [0 ])
67+
68+ def _remove_outdated_prices (self , tariff_state : TariffState , timeslot_length_seconds : int ) -> TariffState :
69+ now = timecheck .create_timestamp ()
5270 for timestamp in list (tariff_state .prices .keys ()):
53- if int (timestamp ) < int ( current_hour ):
71+ if int (timestamp ) < now - ( timeslot_length_seconds - 1 ): # keep current time slot
5472 self .fault_state .warning (
5573 'Die Preisliste startet nicht mit der aktuellen Stunde. '
5674 'Abgelaufene Einträge wurden entfernt.' )
5775 tariff_state .prices .pop (timestamp )
76+ self .fault_state .no_error (
77+ f'Die Preisliste hat { len (tariff_state .prices )} Einträge. ' )
5878 return tariff_state
0 commit comments