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.
- pylxpweb/__init__.py +39 -0
- pylxpweb/client.py +417 -0
- pylxpweb/constants.py +1183 -0
- pylxpweb/endpoints/__init__.py +27 -0
- pylxpweb/endpoints/analytics.py +446 -0
- pylxpweb/endpoints/base.py +43 -0
- pylxpweb/endpoints/control.py +306 -0
- pylxpweb/endpoints/devices.py +250 -0
- pylxpweb/endpoints/export.py +86 -0
- pylxpweb/endpoints/firmware.py +235 -0
- pylxpweb/endpoints/forecasting.py +109 -0
- pylxpweb/endpoints/plants.py +470 -0
- pylxpweb/exceptions.py +23 -0
- pylxpweb/models.py +765 -0
- pylxpweb/py.typed +0 -0
- pylxpweb/registers.py +511 -0
- pylxpweb-0.1.0.dist-info/METADATA +433 -0
- pylxpweb-0.1.0.dist-info/RECORD +19 -0
- pylxpweb-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""Firmware update endpoints for the Luxpower API.
|
|
2
|
+
|
|
3
|
+
This module provides firmware update functionality including:
|
|
4
|
+
- Checking for available updates
|
|
5
|
+
- Monitoring update status
|
|
6
|
+
- Checking update eligibility
|
|
7
|
+
- Starting firmware updates
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from pylxpweb.endpoints.base import BaseEndpoint
|
|
15
|
+
from pylxpweb.models import (
|
|
16
|
+
FirmwareUpdateCheck,
|
|
17
|
+
FirmwareUpdateStatus,
|
|
18
|
+
UpdateEligibilityStatus,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from pylxpweb.client import LuxpowerClient
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FirmwareEndpoints(BaseEndpoint):
|
|
26
|
+
"""Firmware update endpoints for checking and managing device firmware."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, client: LuxpowerClient) -> None:
|
|
29
|
+
"""Initialize firmware endpoints.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
client: The parent LuxpowerClient instance
|
|
33
|
+
"""
|
|
34
|
+
super().__init__(client)
|
|
35
|
+
|
|
36
|
+
async def check_firmware_updates(self, serial_num: str) -> FirmwareUpdateCheck:
|
|
37
|
+
"""Check for available firmware updates for a device.
|
|
38
|
+
|
|
39
|
+
This is a READ-ONLY operation that checks if firmware updates are available
|
|
40
|
+
and returns information about the current and available firmware versions.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
serial_num: Device serial number (10-digit string)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
FirmwareUpdateCheck object containing:
|
|
47
|
+
- success: Boolean indicating success
|
|
48
|
+
- details: Detailed firmware information including:
|
|
49
|
+
- Current firmware versions (v1, v2, v3)
|
|
50
|
+
- Latest available versions (lastV1, lastV2)
|
|
51
|
+
- Update compatibility flags
|
|
52
|
+
- Device type information
|
|
53
|
+
- infoForwardUrl: URL to firmware changelog/release notes (optional)
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
LuxpowerAuthError: If authentication fails
|
|
57
|
+
LuxpowerAPIError: If API returns an error
|
|
58
|
+
LuxpowerConnectionError: If connection fails
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
update_info = await client.firmware.check_firmware_updates("1234567890")
|
|
62
|
+
if update_info.details.has_update():
|
|
63
|
+
print(f"Update available: {update_info.details.lastV1FileName}")
|
|
64
|
+
print(f"Changelog: {update_info.infoForwardUrl}")
|
|
65
|
+
"""
|
|
66
|
+
await self.client._ensure_authenticated()
|
|
67
|
+
|
|
68
|
+
data = {"serialNum": serial_num}
|
|
69
|
+
|
|
70
|
+
response = await self.client._request(
|
|
71
|
+
"POST",
|
|
72
|
+
"/WManage/web/maintain/standardUpdate/checkUpdates",
|
|
73
|
+
data=data,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
return FirmwareUpdateCheck.model_validate(response)
|
|
77
|
+
|
|
78
|
+
async def get_firmware_update_status(self) -> FirmwareUpdateStatus:
|
|
79
|
+
"""Get firmware update status for all devices in user's account.
|
|
80
|
+
|
|
81
|
+
This is a READ-ONLY operation that monitors active firmware updates.
|
|
82
|
+
Use this to track update progress for devices that are currently updating.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
FirmwareUpdateStatus object containing:
|
|
86
|
+
- receiving: Whether system is receiving firmware file
|
|
87
|
+
- progressing: Whether any update is in progress
|
|
88
|
+
- fileReady: Whether firmware file is ready
|
|
89
|
+
- deviceInfos: List of devices with active or recent updates
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
LuxpowerAuthError: If authentication fails
|
|
93
|
+
LuxpowerAPIError: If API returns an error
|
|
94
|
+
LuxpowerConnectionError: If connection fails
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
status = await client.firmware.get_firmware_update_status()
|
|
98
|
+
if status.has_active_updates():
|
|
99
|
+
for device in status.deviceInfos:
|
|
100
|
+
if device.is_in_progress():
|
|
101
|
+
print(f"{device.inverterSn}: {device.updateRate}")
|
|
102
|
+
"""
|
|
103
|
+
await self.client._ensure_authenticated()
|
|
104
|
+
|
|
105
|
+
from pylxpweb.exceptions import LuxpowerAuthError
|
|
106
|
+
|
|
107
|
+
if not hasattr(self.client, "_user_id") or self.client._user_id is None:
|
|
108
|
+
msg = "User ID not available. Please login first."
|
|
109
|
+
raise LuxpowerAuthError(msg)
|
|
110
|
+
|
|
111
|
+
data = {"userId": self.client._user_id}
|
|
112
|
+
|
|
113
|
+
response = await self.client._request(
|
|
114
|
+
"POST",
|
|
115
|
+
"/WManage/web/maintain/remoteUpdate/info",
|
|
116
|
+
data=data,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return FirmwareUpdateStatus.model_validate(response)
|
|
120
|
+
|
|
121
|
+
async def check_update_eligibility(self, serial_num: str) -> UpdateEligibilityStatus:
|
|
122
|
+
"""Check if device is eligible for firmware update.
|
|
123
|
+
|
|
124
|
+
This is a READ-ONLY operation that verifies if a device can be updated.
|
|
125
|
+
Important: Despite the endpoint name, this works for ALL devices, not just
|
|
126
|
+
12K parallel configurations.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
serial_num: Device serial number (10-digit string)
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
UpdateEligibilityStatus object containing:
|
|
133
|
+
- success: Boolean indicating success
|
|
134
|
+
- msg: Eligibility message ("allowToUpdate", "deviceUpdating", etc.)
|
|
135
|
+
|
|
136
|
+
Raises:
|
|
137
|
+
LuxpowerAuthError: If authentication fails
|
|
138
|
+
LuxpowerAPIError: If API returns an error
|
|
139
|
+
LuxpowerConnectionError: If connection fails
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
eligibility = await client.firmware.check_update_eligibility("1234567890")
|
|
143
|
+
if eligibility.is_allowed():
|
|
144
|
+
await client.firmware.start_firmware_update("1234567890")
|
|
145
|
+
else:
|
|
146
|
+
print(f"Cannot update: {eligibility.msg}")
|
|
147
|
+
"""
|
|
148
|
+
await self.client._ensure_authenticated()
|
|
149
|
+
|
|
150
|
+
from pylxpweb.exceptions import LuxpowerAuthError
|
|
151
|
+
|
|
152
|
+
if not hasattr(self.client, "_user_id") or self.client._user_id is None:
|
|
153
|
+
msg = "User ID not available. Please login first."
|
|
154
|
+
raise LuxpowerAuthError(msg)
|
|
155
|
+
|
|
156
|
+
data = {"userId": self.client._user_id, "serialNum": serial_num}
|
|
157
|
+
|
|
158
|
+
response = await self.client._request(
|
|
159
|
+
"POST",
|
|
160
|
+
"/WManage/web/maintain/standardUpdate/check12KParallelStatus",
|
|
161
|
+
data=data,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
return UpdateEligibilityStatus.model_validate(response)
|
|
165
|
+
|
|
166
|
+
async def start_firmware_update(self, serial_num: str, *, try_fast_mode: bool = False) -> bool:
|
|
167
|
+
"""Start firmware update for a device.
|
|
168
|
+
|
|
169
|
+
⚠️ CRITICAL WARNING - WRITE OPERATION
|
|
170
|
+
This initiates an actual firmware update that:
|
|
171
|
+
- Takes 20-40 minutes to complete
|
|
172
|
+
- Makes device unavailable during update
|
|
173
|
+
- Requires uninterrupted power and network
|
|
174
|
+
- May brick device if interrupted
|
|
175
|
+
|
|
176
|
+
Recommended workflow:
|
|
177
|
+
1. Call check_firmware_updates() to verify update is available
|
|
178
|
+
2. Call check_update_eligibility() to verify device is ready
|
|
179
|
+
3. Get explicit user confirmation
|
|
180
|
+
4. Call this method to start update
|
|
181
|
+
5. Monitor progress with get_firmware_update_status()
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
serial_num: Device serial number (10-digit string)
|
|
185
|
+
try_fast_mode: Attempt fast update mode (may reduce time by 20-30%)
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Boolean indicating if update was initiated successfully
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
LuxpowerAuthError: If authentication fails
|
|
192
|
+
LuxpowerAPIError: If update cannot be started (already updating,
|
|
193
|
+
no update available, parallel group updating)
|
|
194
|
+
LuxpowerConnectionError: If connection fails
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
# Check for updates first
|
|
198
|
+
update_info = await client.firmware.check_firmware_updates("1234567890")
|
|
199
|
+
if not update_info.details.has_update():
|
|
200
|
+
print("No update available")
|
|
201
|
+
return
|
|
202
|
+
|
|
203
|
+
# Check eligibility
|
|
204
|
+
eligibility = await client.firmware.check_update_eligibility("1234567890")
|
|
205
|
+
if not eligibility.is_allowed():
|
|
206
|
+
print(f"Cannot update: {eligibility.msg}")
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
# Get user confirmation
|
|
210
|
+
if confirm_with_user():
|
|
211
|
+
success = await client.firmware.start_firmware_update("1234567890")
|
|
212
|
+
if success:
|
|
213
|
+
print("Update started. Monitor with get_firmware_update_status()")
|
|
214
|
+
"""
|
|
215
|
+
await self.client._ensure_authenticated()
|
|
216
|
+
|
|
217
|
+
from pylxpweb.exceptions import LuxpowerAuthError
|
|
218
|
+
|
|
219
|
+
if not hasattr(self.client, "_user_id") or self.client._user_id is None:
|
|
220
|
+
msg = "User ID not available. Please login first."
|
|
221
|
+
raise LuxpowerAuthError(msg)
|
|
222
|
+
|
|
223
|
+
data = {
|
|
224
|
+
"userId": self.client._user_id,
|
|
225
|
+
"serialNum": serial_num,
|
|
226
|
+
"tryFastMode": str(try_fast_mode).lower(),
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
response = await self.client._request(
|
|
230
|
+
"POST",
|
|
231
|
+
"/WManage/web/maintain/standardUpdate/run",
|
|
232
|
+
data=data,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return bool(response.get("success", False))
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Forecasting endpoints for the Luxpower API.
|
|
2
|
+
|
|
3
|
+
This module provides forecasting functionality including:
|
|
4
|
+
- Solar production forecasting
|
|
5
|
+
- Weather forecasts for plant locations
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import TYPE_CHECKING, Any
|
|
11
|
+
|
|
12
|
+
from pylxpweb.endpoints.base import BaseEndpoint
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from pylxpweb.client import LuxpowerClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ForecastingEndpoints(BaseEndpoint):
|
|
19
|
+
"""Forecasting endpoints for solar and weather predictions."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, client: LuxpowerClient) -> None:
|
|
22
|
+
"""Initialize forecasting endpoints.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
client: The parent LuxpowerClient instance
|
|
26
|
+
"""
|
|
27
|
+
super().__init__(client)
|
|
28
|
+
|
|
29
|
+
async def get_solar_forecast(self, serial_num: str) -> dict[str, Any]:
|
|
30
|
+
"""Get solar production forecast for parallel group.
|
|
31
|
+
|
|
32
|
+
Retrieves predicted solar production for today/tomorrow based on
|
|
33
|
+
weather data and historical patterns. Useful for optimizing battery
|
|
34
|
+
charging schedules and energy management.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
serial_num: Device serial number (any inverter in parallel group)
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Dict containing:
|
|
41
|
+
- success: Boolean
|
|
42
|
+
- serialNum: Device serial number
|
|
43
|
+
- forecastDate: Date of forecast
|
|
44
|
+
- predictions: List of prediction objects with:
|
|
45
|
+
- time: Time period (hour or date)
|
|
46
|
+
- predictedPower: Predicted PV power (W)
|
|
47
|
+
- confidence: Prediction confidence (0-100%)
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
forecast = await client.forecasting.get_solar_forecast("1234567890")
|
|
51
|
+
for pred in forecast["predictions"]:
|
|
52
|
+
print(f"{pred['time']}: {pred['predictedPower']}W "
|
|
53
|
+
f"(confidence: {pred['confidence']}%)")
|
|
54
|
+
"""
|
|
55
|
+
await self.client._ensure_authenticated()
|
|
56
|
+
|
|
57
|
+
data = {"serialNum": serial_num}
|
|
58
|
+
|
|
59
|
+
response = await self.client._request(
|
|
60
|
+
"POST",
|
|
61
|
+
"/WManage/api/predict/solar/dayPredictColumnParallel",
|
|
62
|
+
data=data,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return dict(response)
|
|
66
|
+
|
|
67
|
+
async def get_weather_forecast(self, serial_num: str) -> dict[str, Any]:
|
|
68
|
+
"""Get weather forecast for plant location.
|
|
69
|
+
|
|
70
|
+
Retrieves weather forecast data for the plant's geographic location.
|
|
71
|
+
Useful for understanding conditions affecting solar production.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
serial_num: Device serial number
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dict containing:
|
|
78
|
+
- success: Boolean
|
|
79
|
+
- location: Location info (latitude, longitude, city)
|
|
80
|
+
- current: Current weather conditions
|
|
81
|
+
- temperature: Current temp (Celsius)
|
|
82
|
+
- conditions: Weather description
|
|
83
|
+
- cloudCover: Cloud cover percentage
|
|
84
|
+
- forecast: Multi-day forecast array
|
|
85
|
+
- date: Forecast date
|
|
86
|
+
- tempHigh: High temperature
|
|
87
|
+
- tempLow: Low temperature
|
|
88
|
+
- conditions: Weather description
|
|
89
|
+
- cloudCover: Cloud cover percentage
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
weather = await client.forecasting.get_weather_forecast("1234567890")
|
|
93
|
+
print(f"Current: {weather['current']['temperature']}°C, "
|
|
94
|
+
f"{weather['current']['conditions']}")
|
|
95
|
+
for day in weather["forecast"]:
|
|
96
|
+
print(f"{day['date']}: {day['tempHigh']}°C/{day['tempLow']}°C, "
|
|
97
|
+
f"{day['conditions']}")
|
|
98
|
+
"""
|
|
99
|
+
await self.client._ensure_authenticated()
|
|
100
|
+
|
|
101
|
+
data = {"serialNum": serial_num}
|
|
102
|
+
|
|
103
|
+
response = await self.client._request(
|
|
104
|
+
"POST",
|
|
105
|
+
"/WManage/api/weather/forecast",
|
|
106
|
+
data=data,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
return dict(response)
|