1
- from datetime import datetime , time
1
+ from datetime import date , datetime , time , timedelta
2
2
3
3
import requests
4
+ from database_handler import DatabaseHandler , InfluxDBField
4
5
from energy_amount import EnergyAmount , Power
5
6
from environment_variable_getter import EnvironmentVariableGetter
6
7
from logger import LoggerMixin
@@ -16,6 +17,8 @@ def __init__(self):
16
17
self .timestamp = None
17
18
self .user_id = None
18
19
20
+ self .database_handler = DatabaseHandler ("power" )
21
+
19
22
def login (self ) -> None :
20
23
"""
21
24
Authenticates a user by sending a POST request to the SEMS Portal API and retrieves
@@ -63,15 +66,51 @@ def get_average_energy_consumption_per_day(self) -> EnergyAmount:
63
66
"""
64
67
self .log .debug ("Determining average energy consumption per day" )
65
68
66
- self .login ()
67
-
68
69
api_response = self ._retrieve_energy_consumption_data ()
69
70
consumption_data = self ._extract_energy_usage_data_of_response (api_response )
70
71
average_consumption_per_day = sum ([consumption .watt_hours for consumption in consumption_data ]) / len (
71
72
consumption_data
72
73
)
73
74
return EnergyAmount (watt_hours = average_consumption_per_day )
74
75
76
+ def _retrieve_power_data (self , date_to_crawl : date ) -> dict :
77
+ """
78
+ Retrieves the power data from the SEMSPORTAL API. This includes:
79
+ - solar generation
80
+ - battery charge/discharge
81
+ - grid consumption/feed
82
+ - power usage
83
+ - state of charge
84
+
85
+ This method sends a POST request to the SEMSPORTAL API to fetch the energy consumption data of a specified plant station.
86
+ It constructs the necessary headers and payload required by the API and handles the response appropriately.
87
+
88
+ Returns:
89
+ dict: A dictionary containing the power data retrieved from the SEMSPORTAL API.
90
+ """
91
+ self .login ()
92
+
93
+ self .log .debug ("Crawling the SEMSPORTAL API for power data..." )
94
+
95
+ url = "https://eu.semsportal.com/api/v2/Charts/GetPlantPowerChart"
96
+ headers = {
97
+ "Content-Type" : "application/json" ,
98
+ "Token" : f'{{"version":"v2.1.0","client":"ios","language":"en", "timestamp": "{ self .timestamp } ", "uid": "{ self .user_id } ", "token": "{ self .token } "}}' ,
99
+ }
100
+ payload = {
101
+ "id" : EnvironmentVariableGetter .get ("SEMSPORTAL_POWERSTATION_ID" ),
102
+ "date" : date_to_crawl .strftime ("%Y-%m-%d" ),
103
+ "full_script" : False ,
104
+ }
105
+
106
+ response = requests .post (url , headers = headers , json = payload , timeout = 20 )
107
+ response .raise_for_status ()
108
+ response = response .json ()
109
+
110
+ self .log .trace (f"Retrieved data: { response } " )
111
+
112
+ return response
113
+
75
114
def _retrieve_energy_consumption_data (self ) -> dict :
76
115
"""
77
116
Retrieves energy consumption data from the SEMSPORTAL API.
@@ -82,6 +121,8 @@ def _retrieve_energy_consumption_data(self) -> dict:
82
121
Returns:
83
122
dict: A dictionary containing the energy consumption data retrieved from the SEMSPORTAL API.
84
123
"""
124
+ self .login ()
125
+
85
126
self .log .debug ("Crawling the SEMSPORTAL API for energy consumption data..." )
86
127
87
128
url = "https://eu.semsportal.com/api/v2/Charts/GetChartByPlant"
@@ -218,7 +259,64 @@ def estimate_energy_usage_in_timeframe(self, timestamp_start: datetime, timestam
218
259
219
260
return energy_usage_during_the_day + energy_usage_during_the_night
220
261
262
+ def write_values_to_database (self ) -> None :
263
+ """
264
+ Writes energy-related metrics to the database for the last three days.
265
+
266
+ This method retrieves and processes energy data for the past three days, including solar generation, battery
267
+ discharge, grid feed, power usage and state of charge. It writes the resulting records to the database.
268
+ """
269
+ self .log .debug ("Writing values to database..." )
270
+ today = date .today ()
271
+ for days_in_past in range (3 ):
272
+ date_to_crawl = today - timedelta (days = days_in_past )
273
+ data = self ._retrieve_power_data (date_to_crawl )
274
+ self .log .debug (f"Retrieved power data: { data } " )
275
+ lines = data ["data" ]["lines" ]
276
+
277
+ time_keys = [line ["x" ] for line in lines [0 ]["xy" ]]
278
+ for time_key in time_keys :
279
+ timestamp = datetime .combine (date_to_crawl , datetime .strptime (time_key , "%H:%M" ).time ())
280
+ timestamp = timestamp .replace (tzinfo = TimeHandler .get_timezone ())
281
+ self .database_handler .write_to_database (
282
+ [
283
+ InfluxDBField (
284
+ "solar_generation" , self ._get_value_of_line_by_line_index_and_time_key (lines , 0 , time_key )
285
+ ),
286
+ InfluxDBField (
287
+ "battery_discharge" , self ._get_value_of_line_by_line_index_and_time_key (lines , 1 , time_key )
288
+ ),
289
+ InfluxDBField (
290
+ "grid_feed" , self ._get_value_of_line_by_line_index_and_time_key (lines , 2 , time_key )
291
+ ),
292
+ InfluxDBField (
293
+ "power_usage" , self ._get_value_of_line_by_line_index_and_time_key (lines , 3 , time_key )
294
+ ),
295
+ InfluxDBField (
296
+ "state_of_charge" , self ._get_value_of_line_by_line_index_and_time_key (lines , 4 , time_key )
297
+ ),
298
+ ],
299
+ timestamp ,
300
+ )
301
+
302
+ @staticmethod
303
+ def _get_value_of_line_by_line_index_and_time_key (lines : dict , line_index : int , time_key : str ) -> int :
304
+ """
305
+ Retrieves the value associated with a specific time key from a line in a nested dictionary structure.
306
+
307
+ This method searches for a specific time key ('x') within the 'xy' list of dictionaries
308
+ contained in a given line at a specified index. It extracts the associated value ('y')
309
+ for the matching time key and converts it to an integer.
310
+
311
+ Args:
312
+ lines (dict): A dictionary where each key corresponds to a line index. Each line index
313
+ maps to a dictionary containing a key 'xy', which is a list of dictionaries.
314
+ Each dictionary within 'xy' contains keys 'x' and 'y'.
315
+ line_index (int): The index of the line to search within the 'lines' dictionary.
316
+ time_key (str): The time key to search for, used to identify the specific dictionary in
317
+ the 'xy' list where the 'x' value matches.
221
318
222
- if __name__ == "__main__" :
223
- s = SemsPortalApiHandler ()
224
- print (s .get_energy_buy (1 ))
319
+ Returns:
320
+ int: The integer representation of the value ('y') corresponding to the provided time key.
321
+ """
322
+ return int ([line for line in lines [line_index ]["xy" ] if line ["x" ] == time_key ][0 ]["y" ])
0 commit comments