/
SolarEdgeAPI.py
500 lines (437 loc) · 22.8 KB
/
SolarEdgeAPI.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
import os
import shelve
import logging
import tempfile
import functools
from solaredge_interface import __title__ as NAME
from solaredge_interface import __solaredge_api_baseurl__ as BASEURL
from solaredge_interface.exceptions.SolarEdgeInterfaceException import SolarEdgeInterfaceException
from solaredge_interface.utils.url_join import url_join, url_join_site_ids
from solaredge_interface.utils.http_request import http_request
from solaredge_interface.utils.json import json_decode
logger = logging.getLogger(__name__)
class SolarEdgeAPI:
"""
This class implements Python3 interfaces to the documented SolarEdge API end-points. Refer to
[se_monitoring_api.pdf](https://www.solaredge.com/sites/default/files/se_monitoring_api.pdf) for more details
on the SolarEdge API.
"""
api_key = None
datetime_response = None
pandas_response = None
def __init__(self, api_key, datetime_response=False, pandas_response=False):
"""
To call the SolarEdge API you need a valid `api_key` which can be obtained from your SolarEdge account.
_parameters_
* _api_key_ (str) required - a valid api_key from https://monitoring.solaredge.com
* _datetime_response_ (bool) default: False - if True then parse all fields with a date or datetime string and
convert them into timezone aware Python datetime objects.
* _pandas_response_ (bool) default: False - if True then parse response data and flatten into Pandas DataFrame
and make available in the `.pandas` response attribute
"""
if not api_key:
raise SolarEdgeInterfaceException('Must provide a SolarEdge api_key value.')
self.api_key = api_key
self.datetime_response = datetime_response
self.pandas_response = pandas_response
@functools.lru_cache()
def get_accounts(self, size=100, start_index=0, search_text="", sort_property="", sort_order="ASC"):
"""
Returns a list of sub-accounts (if available) that are accessible by the `api_key` with an ability to
search and filter the results.
NB: `api_key` that do not have access to sub-accounts will return a "not authorized" response.
_parameters_
* _size_ (int) default: `100` - The maximum number of accounts returned by this call. The maximum number of
accounts that can be returned by this call is 100. If you have more than 100 accounts, just request another
100 accounts with startIndex=100 which will fetch accounts 100-199.
* _start_index_ (int) default: `0` - The first account index to be returned in the results.
* _search_text_ (str) default: - The search text for accounts. Searchable accounts properties: Name, Notes,
Email, Country, State, City, Zip, Full address
* _sort_property_ (str) default: - A sorting option for this account list, based on one of its properties.
Available sort properties: Name, country, city, Address, zip, fax, phone, notes
* _sort_order_ (str) default: `ASC` - Sort order for the sort property. Allowed values are ASC (ascending) and
DESC (descending)
Uses Least-Recently-Used caching strategy to reduce calls to API backend and speed re-occurring function calls.
"""
url = url_join(BASEURL, "accounts", "list")
params = {
'api_key': self.api_key,
'size': size,
'startIndex': start_index,
'sortOrder': sort_order,
}
if search_text:
params['searchText'] = search_text
if sort_property:
params['sortProperty'] = sort_property
return self.__response_wrapper(http_request(url, params))
@functools.lru_cache()
def get_sites(self, size=100, start_index=0, search_text="", sort_property="", sort_order="ASC", status="Active,Pending"):
"""
Returns the sites accessible by the `api_key` with an ability to search and filter.
_parameters_
* _size_ (int) default: `100` - The maximum number of sites returned by this call. The maximum number of sites
that can be returned by this call is 100. If you have more than 100 sites, just request another 100 sites with
startIndex=100 which will fetch sites 100-199.
* _start_index_ (int) default: `0` - The first site index to be returned in the results.
* _search_text_ (str) default: - The search text for sites. Searchable site properties: Name, Notes,
Address, City, Zip code, Full address, Country
* _sort_property_ (str) default: - A sorting option for this site list, based on one of its properties.
Available sort properties: Name, Country, State, City, Address, Zip, Status, PeakPower, InstallationDate,
Amount, MaxSeverity, CreationTime
* _sort_order_ (str) default: `ASC` - Sort order for the sort property. Allowed values are ASC (ascending) and
DESC (descending)
* _status_ (str) default: `Active,Pending` - Select the sites to be included in the list by their status:
Active, Pending, Disabled, All.
Uses Least-Recently-Used caching strategy to reduce calls to API backend and speed re-occurring function calls.
"""
url = url_join(BASEURL, "sites", "list")
params = {
'api_key': self.api_key,
'size': size,
'startIndex': start_index,
'sortOrder': sort_order,
'status': status
}
if search_text:
params['searchText'] = search_text
if sort_property:
params['sortProperty'] = sort_property
return self.__response_wrapper(http_request(url, params))
@functools.lru_cache()
def get_site_details(self, site_id):
"""
Returns site details for `site_id` such as name, location, status, etc.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
Uses Least-Recently-Used caching strategy to reduce calls to API backend and speed re-occurring function calls.
"""
url = url_join(BASEURL, "site", site_id, "details")
params = {
'api_key': self.api_key
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
@functools.lru_cache()
def get_site_timezone(self, site_id, tempfile_cache_use=True):
"""
Returns site timezone for `site_id` - returns from local tempfile cache to prevent repeated requests. This
function is provided as a convenience.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
Uses Least-Recently-Used caching strategy to reduce calls to API backend and speed re-occurring function calls.
"""
if ',' in str(site_id):
return None
if tempfile_cache_use:
temp_filename = os.path.join(tempfile.gettempdir(), '{}.cache'.format(NAME))
with shelve.open(temp_filename) as cached:
key = '{}.timezone'.format(str(site_id).strip())
logger.debug('get_site_timezone; cache key={}'.format(key))
if key not in cached:
logger.debug('get_site_timezone; value not cached, adding to cache file {}'.format(temp_filename))
response = http_request(url_join(BASEURL, "site", site_id, "details"), {'api_key': self.api_key})
response.data = json_decode(response.text)
cached[key] = response.data['details']['location']['timeZone']
else:
logger.debug('get_site_timezone; value from cache file {}'.format(temp_filename))
tz = cached[key]
else:
logger.debug('get_site_timezone; no cache use')
response = http_request(url_join(BASEURL, "site", site_id, "details"), {'api_key': self.api_key})
response.data = json_decode(response.text)
tz = response.data['details']['location']['timeZone']
return tz
@functools.lru_cache()
def get_site_data_period(self, site_id):
"""
Returns the start-date and end-date of energy production at the site(s).
_parameters_
* _site_id_ (int or list) required - The site identifier(s) to retrieve data for, may be provided as a single
int value or a list of int values to retrieve data in "bulk-mode"
Uses Least-Recently-Used caching strategy to reduce calls to API backend and speed re-occurring function calls.
"""
url = url_join(BASEURL, url_join_site_ids(site_id), 'dataPeriod')
params = {
'api_key': self.api_key
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_energy(self, site_id, start_date, end_date, time_unit="DAY"):
"""
Returns the site(s) energy measurements.
NB: the time input parameters required are date values in the format YYYY-MM-DD not full timestamp values.
_parameters_
* _site_id_ (int or list) required - The site identifier(s) to retrieve data for, may be provided as a single
int value or a list of int values to retrieve data in "bulk-mode"
* _start_date_ (str) required - must be in format YYYY-MM-DD
* _end_date_ (str) required - must be in format YYYY-MM-DD
* _time_unit_ (str) default: `DAY` - Permitted values are: QUARTER_OF_AN_HOUR, HOUR, DAY, WEEK, MONTH, YEAR
"""
url = url_join(BASEURL, url_join_site_ids(site_id), 'energy')
params = {
'api_key': self.api_key,
'startDate': start_date,
'endDate': end_date,
'timeUnit': time_unit
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_time_frame_energy(self, site_id, start_date, end_date):
"""
Return the site(s) total energy produced for a given date period.
NB: the time input parameters required are date values in the format YYYY-MM-DD not full timestamp values.
_parameters_
* _site_id_ (int or list) required - The site identifier(s) to retrieve data for, may be provided as a single
int value or a list of int values to retrieve data in "bulk-mode"
* _start_date_ (str) required - must be in format YYYY-MM-DD
* _end_date_ (str) required - must be in format YYYY-MM-DD
"""
url = url_join(BASEURL, url_join_site_ids(site_id), 'timeFrameEnergy')
params = {
'api_key': self.api_key,
'startDate': start_date,
'endDate': end_date
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_overview(self, site_id):
"""
Return the site(s) overview data.
_parameters_
* _site_id_ (int or list) required - The site identifier(s) to retrieve data for, may be provided as a single
int value or a list of int values to retrieve data in "bulk-mode"
"""
url = url_join(BASEURL, url_join_site_ids(site_id), 'overview')
params = {
'api_key': self.api_key
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_power(self, site_id, start_time, end_time):
"""
Return the site(s) power measurements in 15 minute resolution.
_parameters_
* _site_id_ (int or list) required - The site identifier(s) to retrieve data for, may be provided as a single
int value or a list of int values to retrieve data in "bulk-mode"
* _start_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _end_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
"""
url = url_join(BASEURL, url_join_site_ids(site_id), 'power')
params = {
'api_key': self.api_key,
'startTime': start_time,
'endTime': end_time
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_power_details(self, site_id, start_time, end_time, meters=None):
"""
Detailed site power measurements from meters such as consumption, export (feed-in), import (purchase), etc.
Calculated meter readings (also referred to as "virtual meters"), such as self-consumption, are calculated
using the data measured by the meter and the inverters.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
* _start_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _end_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _meters_ (str) default: - If this value is omitted all meter readings are returned. The following values are
permitted separated by comma: Production, Consumption, SelfConsumption, FeedIn, Purchased
"""
url = url_join(BASEURL, "site", site_id, "powerDetails")
params = {
'api_key': self.api_key,
'startTime': start_time,
'endTime': end_time
}
if meters:
params['meters'] = meters
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_energy_details(self, site_id, start_time, end_time, meters=None, time_unit="DAY"):
"""
Detailed site energy measurements from meters such as consumption, export (feed-in), import (purchase), etc.
Calculated meter readings (also referred to as "virtual meters"), such as self-consumption, are calculated
using the data measured by the meter and the inverters.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
* _start_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _end_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _meters_ (str) default: None - If this value is omitted all meter readings are returned. The following values
are permitted separated by comma: Production, Consumption, SelfConsumption, FeedIn, Purchased
* _time_unit_ (str) default: `DAY` - Permitted values are: QUARTER_OF_AN_HOUR, HOUR, DAY, WEEK, MONTH, YEAR
"""
url = url_join(BASEURL, "site", site_id, "energyDetails")
params = {
'api_key': self.api_key,
'startTime': start_time,
'endTime': end_time,
'timeUnit': time_unit
}
if meters:
params['meters'] = meters
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_current_power_flow(self, site_id):
"""
Provides the current power flow between all elements of the site including PV array, storage (battery), loads
(consumption) and grid.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
"""
url = url_join(BASEURL, "site", site_id, "currentPowerFlow")
params = {
'api_key': self.api_key
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_storage_data(self, site_id, start_time, end_time, serials=None):
"""
Get detailed storage information from batteries:the state of energy, power and lifetime energy.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
* _start_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _end_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _serials_ (list) default: None - Return data only for specific battery serial numbers; If omitted, the
response includes all the batteries at the site.
"""
url = url_join(BASEURL, "site", site_id, "storageData")
params = {
'api_key': self.api_key,
'startTime': start_time,
'endTime': end_time
}
if serials:
params['serials'] = serials
return self.__response_wrapper(http_request(url, params), site_id=site_id)
# def get_site_image(self, site_id, name=None, max_width=None, max_height=None, hash=None):
# pass
# def get_site_installer_logo(self, site_id, name=None):
# pass
def get_site_environmental_benefits(self, site_id, system_units=None):
"""
Get all environmental benefits based on site energy production:CO2 emissions saved, equivalent trees planted,
and light bulbs powered for a day.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
* _system_units_ (str) default: None - The system units used when returning gas emission savings. Valid
values: `Metrics`, `Imperial` note these values are case sensitive. If system_units is not specified, the user
system units are used.
"""
url = url_join(BASEURL, "site", site_id, "envBenefits")
params = {
'api_key': self.api_key,
}
if system_units:
params['systemUnits'] = system_units
return self.__response_wrapper(http_request(url, params), site_id=site_id)
# def get_site_equipment_list(self, site_id):
# pass
@functools.lru_cache()
def get_site_inventory(self, site_id):
"""
Get the inventory of SolarEdge equipment at the site, including inverters/SMIs, batteries, meters, gateways
and sensors.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
Uses Least-Recently-Used caching strategy to reduce calls to API backend and speed re-occurring function calls.
"""
url = url_join(BASEURL, "site", site_id, "inventory")
params = {
'api_key': self.api_key
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_equipment_data(self, site_id, start_time, end_time, serial_number):
"""
Get specific inverter data for a given timeframe.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
* _start_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _end_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _serial_number_ (str) required - The inverter short serial number, eg 12345678-90
"""
url = url_join(BASEURL, "equipment", site_id, serial_number, "data")
params = {
'api_key': self.api_key,
'startTime': start_time,
'endTime': end_time
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_equipment_change_log(self, site_id, serial_number):
"""
Returns a list of equipment component replacements ordered by date. This method is applicable to inverters,
optimizers, batteries and gateways.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
* _serial_number_ (str) required - Inverter, battery, optimizer or gateway short serial number.
"""
url = url_join(BASEURL, "equipment", site_id, serial_number, "changeLog")
params = {
'api_key': self.api_key,
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_meters(self, site_id, start_time, end_time, meters=None):
"""
Returns for each meter on site its lifetime energy reading, metadata and the device to which it is connected.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
* _start_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _end_time_ (str) required - must be in format YYYY-MM-DD hh:mm:ss
* _meters_ (str ot list) default: None - Select specific meters only. If this value is omitted, all meter
readings are returned. Valid values: Production, Consumption,
FeedIn, Purchased.
"""
url = url_join(BASEURL, "site", site_id, "meters")
params = {
'api_key': self.api_key,
'startTime': start_time,
'endTime': end_time
}
if meters:
params['meters'] = meters
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_site_equipment_sensors(self, site_id):
"""
Returns a list of all the sensors in the site, and the device to which they are connected.
_parameters_
* _site_id_ (int) required - The site identifier to retrieve data for.
"""
url = url_join(BASEURL, "equipment", site_id, "sensors")
params = {
'api_key': self.api_key,
}
return self.__response_wrapper(http_request(url, params), site_id=site_id)
def get_version_current(self):
"""
Return the most updated version number in <major.minor.revision> format.
"""
url = url_join(BASEURL, "version", "current")
params = {
'api_key': self.api_key,
}
return self.__response_wrapper(http_request(url, params))
def get_version_supported(self):
"""
Return a list of supported version numbers in <major.minor.revision> format
"""
url = url_join(BASEURL, "version", "supported")
params = {
'api_key': self.api_key,
}
return self.__response_wrapper(http_request(url, params))
def __response_wrapper(self, response, site_id=None, parse_response=True, pandas_column_trim=None):
if parse_response:
response.data = json_decode(response.text)
if response.data:
if self.datetime_response:
try:
data_to_datetime
except NameError:
logger.debug('from solaredge_interface.utils.timedates import data_to_datetime')
from solaredge_interface.utils.timedates import data_to_datetime
response.data = data_to_datetime(data=response.data)
if site_id:
try:
set_datetime_tzinfo
except NameError:
logger.debug('from solaredge_interface.utils.timedates import set_datetime_tzinfo')
from solaredge_interface.utils.timedates import set_datetime_tzinfo
response.data = set_datetime_tzinfo(data=response.data, tz=self.get_site_timezone(site_id))
if self.pandas_response:
try:
data_to_pandas
except NameError:
logger.debug('solaredge_interface.utils.pandas import data_to_pandas')
from solaredge_interface.utils.pandas import data_to_pandas
response.pandas = data_to_pandas(data=response.data, prefix_to_remove=pandas_column_trim)
return response