pylxpweb 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ """Endpoint-specific modules for the Luxpower API client.
2
+
3
+ This package organizes API endpoints into logical modules following the strategy pattern.
4
+ Each module handles a specific category of endpoints (analytics, plants, devices, etc.).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from pylxpweb.endpoints.analytics import AnalyticsEndpoints
10
+ from pylxpweb.endpoints.base import BaseEndpoint
11
+ from pylxpweb.endpoints.control import ControlEndpoints
12
+ from pylxpweb.endpoints.devices import DeviceEndpoints
13
+ from pylxpweb.endpoints.export import ExportEndpoints
14
+ from pylxpweb.endpoints.firmware import FirmwareEndpoints
15
+ from pylxpweb.endpoints.forecasting import ForecastingEndpoints
16
+ from pylxpweb.endpoints.plants import PlantEndpoints
17
+
18
+ __all__ = [
19
+ "BaseEndpoint",
20
+ "PlantEndpoints",
21
+ "DeviceEndpoints",
22
+ "ControlEndpoints",
23
+ "AnalyticsEndpoints",
24
+ "ExportEndpoints",
25
+ "ForecastingEndpoints",
26
+ "FirmwareEndpoints",
27
+ ]
@@ -0,0 +1,446 @@
1
+ """Analytics endpoints for the Luxpower API.
2
+
3
+ This module provides analytics functionality including:
4
+ - Time-series chart data for sensors
5
+ - Energy breakdowns (hourly, daily, monthly, yearly, lifetime)
6
+ - Event/fault/warning logs
7
+ - Battery and inverter information
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from datetime import datetime
13
+ from typing import TYPE_CHECKING, Any
14
+
15
+ from pylxpweb.endpoints.base import BaseEndpoint
16
+
17
+ if TYPE_CHECKING:
18
+ from pylxpweb.client import LuxpowerClient
19
+
20
+
21
+ class AnalyticsEndpoints(BaseEndpoint):
22
+ """Analytics endpoints for charts, energy breakdowns, and event logs."""
23
+
24
+ def __init__(self, client: LuxpowerClient) -> None:
25
+ """Initialize analytics endpoints.
26
+
27
+ Args:
28
+ client: The parent LuxpowerClient instance
29
+ """
30
+ super().__init__(client)
31
+
32
+ async def get_chart_data(
33
+ self,
34
+ serial_num: str,
35
+ attribute: str,
36
+ date: str,
37
+ ) -> dict[str, Any]:
38
+ """Get time-series data for specific sensor attribute.
39
+
40
+ This endpoint retrieves hourly time-series data for any sensor attribute
41
+ over a specific date. Very flexible - supports all InverterRuntime fields.
42
+
43
+ Available attributes: vpv1, vpv2, ppv, ppv1, ppv2, vBat, soc, pCharge,
44
+ pDisCharge, pToGrid, pToUser, pInv, pRec, pEps, vacr, vacs, vact, fac,
45
+ tinner, tradiator1, tradiator2, tBat, and more.
46
+
47
+ Args:
48
+ serial_num: Device serial number
49
+ attribute: Sensor attribute name (from InverterRuntime fields)
50
+ date: Date in YYYY-MM-DD format
51
+
52
+ Returns:
53
+ Dict containing:
54
+ - success: Boolean
55
+ - dataPoints: List of {time: str, value: number} objects
56
+
57
+ Example:
58
+ # Get PV voltage over time
59
+ data = await client.analytics.get_chart_data("1234567890", "vpv1", "2025-11-19")
60
+ for point in data["dataPoints"]:
61
+ print(f"{point['time']}: {point['value']/100}V") # Scale voltage
62
+
63
+ Note:
64
+ Data points are typically hourly (24 entries for full day).
65
+ Remember to apply scaling factors (voltage ÷100, etc.)
66
+ """
67
+ await self.client._ensure_authenticated()
68
+
69
+ data = {
70
+ "serialNum": serial_num,
71
+ "attr": attribute,
72
+ "dateText": date,
73
+ }
74
+
75
+ response = await self.client._request(
76
+ "POST",
77
+ "/WManage/api/analyze/chart/dayLine",
78
+ data=data,
79
+ )
80
+
81
+ return dict(response)
82
+
83
+ async def get_energy_day_breakdown(
84
+ self,
85
+ serial_num: str,
86
+ date: str,
87
+ energy_type: str = "eInvDay",
88
+ *,
89
+ parallel: bool = False,
90
+ ) -> dict[str, Any]:
91
+ """Get hourly energy breakdown for specific day.
92
+
93
+ Returns 24 hourly energy values for the specified date and energy type.
94
+
95
+ Energy Types:
96
+ - eInvDay: Inverter daily energy production
97
+ - eToUserDay: Load consumption
98
+ - eToGridDay: Grid export
99
+ - eAcChargeDay: AC charging
100
+ - eBatChargeDay: Battery charging
101
+ - eBatDischargeDay: Battery discharging
102
+
103
+ Args:
104
+ serial_num: Device serial number
105
+ date: Date in YYYY-MM-DD format
106
+ energy_type: Energy type to query (default: eInvDay)
107
+ parallel: Query parallel group data (default: False)
108
+
109
+ Returns:
110
+ Dict containing:
111
+ - success: Boolean
112
+ - dataPoints: List of {period: str, value: number} hourly energy values
113
+
114
+ Example:
115
+ # Get hourly solar production
116
+ breakdown = await client.analytics.get_energy_day_breakdown(
117
+ "1234567890",
118
+ "2025-11-19",
119
+ "eInvDay"
120
+ )
121
+ total = sum(p["value"] for p in breakdown["dataPoints"])
122
+ print(f"Total production: {total/1000}kWh")
123
+ """
124
+ await self.client._ensure_authenticated()
125
+
126
+ # Parse date to extract year, month, day
127
+ date_obj = datetime.strptime(date, "%Y-%m-%d")
128
+
129
+ data = {
130
+ "serialNum": serial_num,
131
+ "parallel": str(parallel).lower(),
132
+ "year": date_obj.year,
133
+ "month": date_obj.month,
134
+ "day": date_obj.day,
135
+ "energyType": energy_type,
136
+ }
137
+
138
+ response = await self.client._request(
139
+ "POST",
140
+ "/WManage/api/analyze/energy/dayColumn",
141
+ data=data,
142
+ )
143
+
144
+ return dict(response)
145
+
146
+ async def get_energy_month_breakdown(
147
+ self,
148
+ serial_num: str,
149
+ year: int,
150
+ month: int,
151
+ energy_type: str = "eInvDay",
152
+ *,
153
+ parallel: bool = False,
154
+ ) -> dict[str, Any]:
155
+ """Get daily energy breakdown for specific month.
156
+
157
+ Returns daily energy values for each day in the specified month.
158
+
159
+ Args:
160
+ serial_num: Device serial number
161
+ year: Year (e.g., 2025)
162
+ month: Month (1-12)
163
+ energy_type: Energy type to query (default: eInvDay)
164
+ parallel: Query parallel group data (default: False)
165
+
166
+ Returns:
167
+ Dict containing:
168
+ - success: Boolean
169
+ - dataPoints: List of {period: str, value: number} daily energy values
170
+
171
+ Example:
172
+ # Get November 2025 daily production
173
+ breakdown = await client.analytics.get_energy_month_breakdown(
174
+ "1234567890",
175
+ 2025,
176
+ 11,
177
+ "eInvDay"
178
+ )
179
+ """
180
+ await self.client._ensure_authenticated()
181
+
182
+ data = {
183
+ "serialNum": serial_num,
184
+ "parallel": str(parallel).lower(),
185
+ "year": year,
186
+ "month": month,
187
+ "energyType": energy_type,
188
+ }
189
+
190
+ response = await self.client._request(
191
+ "POST",
192
+ "/WManage/api/analyze/energy/monthColumn",
193
+ data=data,
194
+ )
195
+
196
+ return dict(response)
197
+
198
+ async def get_energy_year_breakdown(
199
+ self,
200
+ serial_num: str,
201
+ year: int,
202
+ energy_type: str = "eInvDay",
203
+ *,
204
+ parallel: bool = False,
205
+ ) -> dict[str, Any]:
206
+ """Get monthly energy breakdown for specific year.
207
+
208
+ Returns monthly energy values for the specified year (12 months).
209
+
210
+ Args:
211
+ serial_num: Device serial number
212
+ year: Year (e.g., 2025)
213
+ energy_type: Energy type to query (default: eInvDay)
214
+ parallel: Query parallel group data (default: False)
215
+
216
+ Returns:
217
+ Dict containing:
218
+ - success: Boolean
219
+ - dataPoints: List of {period: str, value: number} monthly energy values
220
+
221
+ Example:
222
+ # Get 2025 monthly production
223
+ breakdown = await client.analytics.get_energy_year_breakdown(
224
+ "1234567890",
225
+ 2025,
226
+ "eInvDay"
227
+ )
228
+ """
229
+ await self.client._ensure_authenticated()
230
+
231
+ data = {
232
+ "serialNum": serial_num,
233
+ "parallel": str(parallel).lower(),
234
+ "year": year,
235
+ "energyType": energy_type,
236
+ }
237
+
238
+ response = await self.client._request(
239
+ "POST",
240
+ "/WManage/api/analyze/energy/yearColumn",
241
+ data=data,
242
+ )
243
+
244
+ return dict(response)
245
+
246
+ async def get_energy_total_breakdown(
247
+ self,
248
+ serial_num: str,
249
+ energy_type: str = "eInvDay",
250
+ *,
251
+ parallel: bool = False,
252
+ ) -> dict[str, Any]:
253
+ """Get yearly energy breakdown for device lifetime.
254
+
255
+ Returns energy data broken down by year for the device's entire lifetime.
256
+
257
+ Args:
258
+ serial_num: Device serial number
259
+ energy_type: Energy type to query (default: eInvDay)
260
+ parallel: Query parallel group data (default: False)
261
+
262
+ Returns:
263
+ Dict containing:
264
+ - success: Boolean
265
+ - dataPoints: List of {period: str, value: number} yearly energy values
266
+
267
+ Example:
268
+ # Get lifetime yearly production
269
+ breakdown = await client.analytics.get_energy_total_breakdown(
270
+ "1234567890",
271
+ "eInvDay"
272
+ )
273
+ """
274
+ await self.client._ensure_authenticated()
275
+
276
+ data = {
277
+ "serialNum": serial_num,
278
+ "parallel": str(parallel).lower(),
279
+ "energyType": energy_type,
280
+ }
281
+
282
+ response = await self.client._request(
283
+ "POST",
284
+ "/WManage/api/analyze/energy/totalColumn",
285
+ data=data,
286
+ )
287
+
288
+ return dict(response)
289
+
290
+ async def get_event_list(
291
+ self,
292
+ serial_num: str,
293
+ *,
294
+ page: int = 1,
295
+ rows: int = 30,
296
+ plant_id: int = -1,
297
+ event_filter: str = "_all",
298
+ ) -> dict[str, Any]:
299
+ """Get fault/warning/event log with pagination.
300
+
301
+ Retrieves system events including faults, warnings, and informational messages.
302
+ Critical for monitoring system health and tracking issues over time.
303
+
304
+ Args:
305
+ serial_num: Device serial number
306
+ page: Page number (default: 1)
307
+ rows: Rows per page (default: 30)
308
+ plant_id: Plant ID filter (-1 for all plants)
309
+ event_filter: Event type filter ("_all", or specific fault/warning code)
310
+
311
+ Returns:
312
+ Dict containing:
313
+ - success: Boolean
314
+ - total: Total number of events
315
+ - rows: List of event objects with:
316
+ - eventId: Event identifier
317
+ - eventCode: Fault/warning code
318
+ - eventType: FAULT/WARNING/INFO
319
+ - eventText: Human-readable description
320
+ - startTime: Event start timestamp
321
+ - endTime: Event end timestamp (empty if ongoing)
322
+ - statusText: ACTIVE/RESOLVED
323
+
324
+ Example:
325
+ # Get all events
326
+ events = await client.analytics.get_event_list("1234567890")
327
+ for event in events["rows"]:
328
+ if event["statusText"] == "ACTIVE":
329
+ print(f"Active {event['eventType']}: {event['eventText']}")
330
+
331
+ # Get only faults
332
+ faults = await client.analytics.get_event_list(
333
+ "1234567890",
334
+ event_filter="FAULT"
335
+ )
336
+ """
337
+ await self.client._ensure_authenticated()
338
+
339
+ data = {
340
+ "page": page,
341
+ "rows": rows,
342
+ "plantId": plant_id,
343
+ "serialNum": serial_num,
344
+ "eventText": event_filter,
345
+ }
346
+
347
+ response = await self.client._request(
348
+ "POST",
349
+ "/WManage/api/analyze/event/list",
350
+ data=data,
351
+ )
352
+
353
+ return dict(response)
354
+
355
+ async def get_battery_list(self, serial_num: str) -> dict[str, Any]:
356
+ """Get simplified battery list for UI selection.
357
+
358
+ Retrieves lightweight battery enumeration without full metrics.
359
+ Useful for populating dropdown menus or battery selection UI.
360
+
361
+ Difference from devices.get_battery_info():
362
+ - This endpoint: Only batteryKey, SN, index, online status
363
+ - Full endpoint: Complete metrics (voltage, current, SOC, SoH, temps, etc.)
364
+
365
+ Args:
366
+ serial_num: Inverter serial number
367
+
368
+ Returns:
369
+ Dict containing:
370
+ - success: Boolean
371
+ - serialNum: Inverter serial number
372
+ - totalNumber: Total battery count
373
+ - batteryArray: List of battery objects with:
374
+ - batteryKey: Unique identifier
375
+ - batterySn: Battery serial number
376
+ - batIndex: Position in array (0-indexed)
377
+ - lost: Whether battery is offline
378
+
379
+ Example:
380
+ batteries = await client.analytics.get_battery_list("1234567890")
381
+ print(f"Found {batteries['totalNumber']} batteries")
382
+ for bat in batteries["batteryArray"]:
383
+ status = "Offline" if bat["lost"] else "Online"
384
+ print(f" {bat['batterySn']}: {status}")
385
+ """
386
+ await self.client._ensure_authenticated()
387
+
388
+ data = {"serialNum": serial_num}
389
+
390
+ cache_key = self._get_cache_key("battery_list", serialNum=serial_num)
391
+ response = await self.client._request(
392
+ "POST",
393
+ "/WManage/api/battery/getBatteryInfoForSet",
394
+ data=data,
395
+ cache_key=cache_key,
396
+ cache_endpoint="battery_info",
397
+ )
398
+
399
+ return dict(response)
400
+
401
+ async def get_inverter_info(self, serial_num: str) -> dict[str, Any]:
402
+ """Get inverter static information.
403
+
404
+ Retrieves inverter configuration and static details (model, firmware, capacity).
405
+
406
+ Complements devices.get_inverter_runtime():
407
+ - This endpoint: Static configuration data
408
+ - Runtime endpoint: Dynamic operational metrics
409
+
410
+ Args:
411
+ serial_num: Inverter serial number
412
+
413
+ Returns:
414
+ Dict containing:
415
+ - success: Boolean
416
+ - serialNum: Inverter serial number
417
+ - deviceType: Device type code
418
+ - deviceTypeText: Model name (e.g., "18KPV")
419
+ - powerRating: Power rating code
420
+ - powerRatingText: Power rating (e.g., "12kW")
421
+ - fwCode: Firmware version
422
+ - batteryType: LITHIUM/LEAD_ACID
423
+ - nominalCapacity: Battery capacity (Ah)
424
+ - installedCapacity: PV capacity (W)
425
+ - phase: 1=single phase, 3=three phase
426
+
427
+ Example:
428
+ info = await client.analytics.get_inverter_info("1234567890")
429
+ print(f"Model: {info['deviceTypeText']}")
430
+ print(f"Firmware: {info['fwCode']}")
431
+ print(f"PV Capacity: {info['installedCapacity']}W")
432
+ """
433
+ await self.client._ensure_authenticated()
434
+
435
+ data = {"serialNum": serial_num}
436
+
437
+ cache_key = self._get_cache_key("inverter_info", serialNum=serial_num)
438
+ response = await self.client._request(
439
+ "POST",
440
+ "/WManage/api/inverter/getInverterInfo",
441
+ data=data,
442
+ cache_key=cache_key,
443
+ cache_endpoint="device_discovery",
444
+ )
445
+
446
+ return dict(response)
@@ -0,0 +1,43 @@
1
+ """Base endpoint class for all endpoint-specific modules.
2
+
3
+ This module provides the BaseEndpoint class that all endpoint modules inherit from.
4
+ It provides access to the parent client's session and request method.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ if TYPE_CHECKING:
12
+ from pylxpweb.client import LuxpowerClient
13
+
14
+
15
+ class BaseEndpoint:
16
+ """Base class for endpoint-specific functionality.
17
+
18
+ All endpoint modules (analytics, plants, devices, etc.) inherit from this class
19
+ to gain access to the parent client's session and request method.
20
+
21
+ Attributes:
22
+ client: Reference to the parent LuxpowerClient instance
23
+ """
24
+
25
+ def __init__(self, client: LuxpowerClient) -> None:
26
+ """Initialize the endpoint with a reference to the parent client.
27
+
28
+ Args:
29
+ client: The parent LuxpowerClient instance
30
+ """
31
+ self.client = client
32
+
33
+ def _get_cache_key(self, endpoint: str, **kwargs: Any) -> str:
34
+ """Generate cache key for request.
35
+
36
+ Args:
37
+ endpoint: The endpoint name
38
+ **kwargs: Parameters to include in cache key
39
+
40
+ Returns:
41
+ Cache key string
42
+ """
43
+ return self.client._get_cache_key(endpoint, **kwargs)