|
1 | | -"""Legacy SolarEdge Monitoring Portal API client: authentication, site layout (inverters/strings/optimizers), live optimizer data with locale-aware measurement keys, and lifetime energy.""" |
| 1 | +""" |
| 2 | +SolarEdge Optimizers Integration - Legacy Monitoring API Client (solaredgeoptimizers.py) |
| 3 | +
|
| 4 | +This module implements the client for the legacy SolarEdge Monitoring Portal API. It serves |
| 5 | +as a fallback when the SolarEdge One API is unavailable or returns no valid data. |
| 6 | +
|
| 7 | +Authentication: |
| 8 | +- Uses HTTP Basic Authentication with site credentials |
| 9 | +- Session-based with CSRF token handling for subsequent requests |
| 10 | +- Thread-local sessions for safe concurrent access in ThreadPoolExecutor |
| 11 | +
|
| 12 | +API Endpoints (monitoring.solaredge.com/solaredge-apigw/api/...): |
| 13 | +
|
| 14 | +1. Login Check: |
| 15 | + GET .../sites/{siteId}/layout/logical |
| 16 | + Validates credentials and returns HTTP status code |
| 17 | +
|
| 18 | +2. Site Structure (Logical Layout): |
| 19 | + GET .../sites/{siteId}/layout/logical |
| 20 | + Returns JSON with inverters, strings, and optimizers hierarchy |
| 21 | +
|
| 22 | +3. Optimizer Live Data (System Data): |
| 23 | + GET .../solaredge-web/p/systemData?reporterId={optimizerId}&... |
| 24 | + Returns power, voltage, current, optimizer voltage for one optimizer |
| 25 | + Locale-aware measurement keys (supports multiple languages) |
| 26 | +
|
| 27 | +4. Lifetime Energy: |
| 28 | + POST .../sites/{siteId}/layout/energy?timeUnit=ALL |
| 29 | + Returns lifetime energy data keyed by optimizer/string ID |
| 30 | +
|
| 31 | +5. Historical Data: |
| 32 | + GET .../solaredge-web/p/chartData?reporterId={id}&... |
| 33 | + Returns time-series data for power, current, voltage, energy |
| 34 | +
|
| 35 | +Data Classes Defined: |
| 36 | +- SolarEdgeSite: Root container with site ID and list of inverters |
| 37 | +- SolarEdgeInverter: Inverter with serial, name, status, and list of strings |
| 38 | +- SolarEdgeString: String with ID, name, status, and list of optimizers |
| 39 | +- SolarlEdgeOptimizer: Optimizer with ID, serial, name, display name, status |
| 40 | +- SolarEdgeOptimizerData: Live measurement data (power, voltage, current, energy, etc.) |
| 41 | +- SolarEdgeAggregatedData: Aggregated data for string/inverter/site levels |
| 42 | +
|
| 43 | +Key Features: |
| 44 | +- Locale-aware measurement key parsing (supports EN, DE, FR, ES, IT, NL, etc.) |
| 45 | +- Thread-local session reuse for efficient parallel requests |
| 46 | +- Caching for panels (1 hour) and lifetime energy (1 hour) |
| 47 | +- Unicode normalization for measurement keys (handles various dash/space variants) |
| 48 | +- Timezone-aware date parsing for lastMeasurementDate |
| 49 | +""" |
2 | 50 | import time |
3 | 51 | import threading |
4 | 52 | import re |
|
14 | 62 | from datetime import datetime, timedelta |
15 | 63 | from jsonfinder import jsonfinder |
16 | 64 |
|
| 65 | +from .const import API_TIMEOUT_SHORT, API_TIMEOUT_LONG, MAX_PARALLEL_WORKERS |
| 66 | + |
17 | 67 | # Added logger setup to replace print statements with proper logging |
18 | 68 | _LOGGER = logging.getLogger(__name__) |
19 | 69 |
|
@@ -353,7 +403,7 @@ def check_login(self): |
353 | 403 | kwargs["headers"] = {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36", |
354 | 404 | } |
355 | 405 | # Add timeout to prevent hanging and log request attempt |
356 | | - kwargs["timeout"] = 30 # 30 second timeout |
| 406 | + kwargs["timeout"] = API_TIMEOUT_SHORT |
357 | 407 | if _LOGGER.isEnabledFor(logging.DEBUG): |
358 | 408 | _LOGGER.debug("SolarEdge Optimizers: Making login check request with 30s timeout") |
359 | 409 |
|
@@ -416,7 +466,7 @@ def requestLogicalLayout(self): |
416 | 466 | _LOGGER.debug("SolarEdge Optimizers: Making logical layout request with 60s timeout") |
417 | 467 | kwargs = { |
418 | 468 | "auth": requests.auth.HTTPBasicAuth(self.username, self.password), |
419 | | - "timeout": 60, |
| 469 | + "timeout": API_TIMEOUT_LONG, |
420 | 470 | } |
421 | 471 | try: |
422 | 472 | return self._fetch_logical_layout(url, kwargs) |
@@ -483,7 +533,7 @@ def requestSystemData(self, itemId): |
483 | 533 |
|
484 | 534 | kwargs = { |
485 | 535 | "auth": requests.auth.HTTPBasicAuth(self.username, self.password), |
486 | | - "timeout": 30, # Prevent hung requests in ThreadPoolExecutor |
| 536 | + "timeout": API_TIMEOUT_SHORT, |
487 | 537 | } |
488 | 538 | with requests.get(url, **kwargs) as r: |
489 | 539 | if _LOGGER.isEnabledFor(logging.DEBUG): |
@@ -547,7 +597,7 @@ def requestAllData(self): |
547 | 597 | for optimizer in string.optimizers |
548 | 598 | ] |
549 | 599 |
|
550 | | - max_workers = min(os.cpu_count() or 4, len(optimizer_ids), 10) |
| 600 | + max_workers = min(os.cpu_count() or 4, len(optimizer_ids), MAX_PARALLEL_WORKERS) |
551 | 601 | if _LOGGER.isEnabledFor(logging.DEBUG): |
552 | 602 | _LOGGER.debug( |
553 | 603 | "SolarEdge Optimizers (legacy): requestAllData fetching %d optimizers with max_workers=%d", |
|
0 commit comments