pylxpweb 0.1.0__py3-none-any.whl → 0.5.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 +47 -2
- pylxpweb/api_namespace.py +241 -0
- pylxpweb/cli/__init__.py +3 -0
- pylxpweb/cli/collect_device_data.py +874 -0
- pylxpweb/client.py +387 -26
- pylxpweb/constants/__init__.py +481 -0
- pylxpweb/constants/api.py +48 -0
- pylxpweb/constants/devices.py +98 -0
- pylxpweb/constants/locations.py +227 -0
- pylxpweb/{constants.py → constants/registers.py} +72 -238
- pylxpweb/constants/scaling.py +479 -0
- pylxpweb/devices/__init__.py +32 -0
- pylxpweb/devices/_firmware_update_mixin.py +504 -0
- pylxpweb/devices/_mid_runtime_properties.py +545 -0
- pylxpweb/devices/base.py +122 -0
- pylxpweb/devices/battery.py +589 -0
- pylxpweb/devices/battery_bank.py +331 -0
- pylxpweb/devices/inverters/__init__.py +32 -0
- pylxpweb/devices/inverters/_features.py +378 -0
- pylxpweb/devices/inverters/_runtime_properties.py +596 -0
- pylxpweb/devices/inverters/base.py +2124 -0
- pylxpweb/devices/inverters/generic.py +192 -0
- pylxpweb/devices/inverters/hybrid.py +274 -0
- pylxpweb/devices/mid_device.py +183 -0
- pylxpweb/devices/models.py +126 -0
- pylxpweb/devices/parallel_group.py +351 -0
- pylxpweb/devices/station.py +908 -0
- pylxpweb/endpoints/control.py +980 -2
- pylxpweb/endpoints/devices.py +249 -16
- pylxpweb/endpoints/firmware.py +43 -10
- pylxpweb/endpoints/plants.py +15 -19
- pylxpweb/exceptions.py +4 -0
- pylxpweb/models.py +629 -40
- pylxpweb/transports/__init__.py +78 -0
- pylxpweb/transports/capabilities.py +101 -0
- pylxpweb/transports/data.py +495 -0
- pylxpweb/transports/exceptions.py +59 -0
- pylxpweb/transports/factory.py +119 -0
- pylxpweb/transports/http.py +329 -0
- pylxpweb/transports/modbus.py +557 -0
- pylxpweb/transports/protocol.py +217 -0
- {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.0.dist-info}/METADATA +130 -85
- pylxpweb-0.5.0.dist-info/RECORD +52 -0
- {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.0.dist-info}/WHEEL +1 -1
- pylxpweb-0.5.0.dist-info/entry_points.txt +3 -0
- pylxpweb-0.1.0.dist-info/RECORD +0 -19
pylxpweb/__init__.py
CHANGED
|
@@ -1,4 +1,41 @@
|
|
|
1
|
-
"""Python client library for Luxpower/EG4 inverter web monitoring API.
|
|
1
|
+
"""Python client library for Luxpower/EG4 inverter web monitoring API.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
Basic client usage:
|
|
5
|
+
from pylxpweb import LuxpowerClient
|
|
6
|
+
|
|
7
|
+
async with LuxpowerClient(username, password) as client:
|
|
8
|
+
# Use low-level API endpoints
|
|
9
|
+
plants = await client.api.plants.get_plants()
|
|
10
|
+
|
|
11
|
+
High-level device hierarchy:
|
|
12
|
+
from pylxpweb import LuxpowerClient
|
|
13
|
+
from pylxpweb.devices import Station
|
|
14
|
+
|
|
15
|
+
async with LuxpowerClient(username, password) as client:
|
|
16
|
+
# Load stations with auto-discovery
|
|
17
|
+
stations = await Station.load_all(client)
|
|
18
|
+
for station in stations:
|
|
19
|
+
for inverter in station.all_inverters:
|
|
20
|
+
await inverter.refresh()
|
|
21
|
+
|
|
22
|
+
DST auto-correction (optional):
|
|
23
|
+
from pylxpweb import LuxpowerClient
|
|
24
|
+
from pylxpweb.devices import Station
|
|
25
|
+
|
|
26
|
+
# Configure with IANA timezone for DST detection
|
|
27
|
+
async with LuxpowerClient(
|
|
28
|
+
username,
|
|
29
|
+
password,
|
|
30
|
+
iana_timezone="America/Los_Angeles"
|
|
31
|
+
) as client:
|
|
32
|
+
station = await Station.load(client, plant_id)
|
|
33
|
+
|
|
34
|
+
# Optionally sync DST setting (convenience method)
|
|
35
|
+
# This does NOT run automatically - you must call it explicitly
|
|
36
|
+
if user_wants_dst_sync:
|
|
37
|
+
await station.sync_dst_setting()
|
|
38
|
+
"""
|
|
2
39
|
|
|
3
40
|
from __future__ import annotations
|
|
4
41
|
|
|
@@ -17,10 +54,12 @@ from .exceptions import (
|
|
|
17
54
|
LuxpowerAuthError,
|
|
18
55
|
LuxpowerConnectionError,
|
|
19
56
|
LuxpowerDeviceError,
|
|
57
|
+
LuxpowerDeviceOfflineError,
|
|
20
58
|
LuxpowerError,
|
|
21
59
|
)
|
|
60
|
+
from .models import DongleStatus, FirmwareUpdateInfo, OperatingMode
|
|
22
61
|
|
|
23
|
-
__version__ = "0.
|
|
62
|
+
__version__ = "0.5.0"
|
|
24
63
|
__all__ = [
|
|
25
64
|
"LuxpowerClient",
|
|
26
65
|
"LuxpowerError",
|
|
@@ -28,6 +67,7 @@ __all__ = [
|
|
|
28
67
|
"LuxpowerAuthError",
|
|
29
68
|
"LuxpowerConnectionError",
|
|
30
69
|
"LuxpowerDeviceError",
|
|
70
|
+
"LuxpowerDeviceOfflineError",
|
|
31
71
|
# Endpoint modules
|
|
32
72
|
"PlantEndpoints",
|
|
33
73
|
"DeviceEndpoints",
|
|
@@ -36,4 +76,9 @@ __all__ = [
|
|
|
36
76
|
"ForecastingEndpoints",
|
|
37
77
|
"ExportEndpoints",
|
|
38
78
|
"FirmwareEndpoints",
|
|
79
|
+
# Models
|
|
80
|
+
"DongleStatus",
|
|
81
|
+
"FirmwareUpdateInfo",
|
|
82
|
+
# Enums
|
|
83
|
+
"OperatingMode",
|
|
39
84
|
]
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""API Namespace for Luxpower/EG4 Client.
|
|
2
|
+
|
|
3
|
+
This module provides the APINamespace class that organizes all API endpoint
|
|
4
|
+
access under the `client.api.*` namespace for cleaner API design.
|
|
5
|
+
|
|
6
|
+
Design Rationale:
|
|
7
|
+
- Separates direct API calls (client.api.*) from high-level objects (client.get_station())
|
|
8
|
+
- Makes it clear when you're making raw API calls vs using object interface
|
|
9
|
+
- Prevents confusion between endpoint methods and object methods
|
|
10
|
+
- Follows best practices from similar libraries (requests.api, etc.)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from .client import LuxpowerClient
|
|
19
|
+
from .endpoints import (
|
|
20
|
+
AnalyticsEndpoints,
|
|
21
|
+
ControlEndpoints,
|
|
22
|
+
DeviceEndpoints,
|
|
23
|
+
ExportEndpoints,
|
|
24
|
+
FirmwareEndpoints,
|
|
25
|
+
ForecastingEndpoints,
|
|
26
|
+
PlantEndpoints,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class APINamespace:
|
|
31
|
+
"""Namespace for all API endpoint access.
|
|
32
|
+
|
|
33
|
+
This class provides access to all API endpoint groups through the
|
|
34
|
+
`client.api.*` interface, creating a clear separation between:
|
|
35
|
+
- Low-level API calls: `client.api.plants.get_plants()`
|
|
36
|
+
- High-level object interface: `client.get_station(plant_id)`
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
```python
|
|
40
|
+
async with LuxpowerClient(username, password) as client:
|
|
41
|
+
# Low-level API access
|
|
42
|
+
plants = await client.api.plants.get_plants()
|
|
43
|
+
runtime = await client.api.devices.get_inverter_runtime(serial)
|
|
44
|
+
|
|
45
|
+
# High-level object interface
|
|
46
|
+
station = await client.get_station(plant_id)
|
|
47
|
+
await station.refresh_all_data()
|
|
48
|
+
```
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def __init__(self, client: LuxpowerClient) -> None:
|
|
52
|
+
"""Initialize the API namespace.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
client: The LuxpowerClient instance that owns this namespace.
|
|
56
|
+
"""
|
|
57
|
+
self._client = client
|
|
58
|
+
|
|
59
|
+
# Endpoint modules (lazy-loaded via properties)
|
|
60
|
+
self._plants: PlantEndpoints | None = None
|
|
61
|
+
self._devices: DeviceEndpoints | None = None
|
|
62
|
+
self._control: ControlEndpoints | None = None
|
|
63
|
+
self._analytics: AnalyticsEndpoints | None = None
|
|
64
|
+
self._forecasting: ForecastingEndpoints | None = None
|
|
65
|
+
self._export: ExportEndpoints | None = None
|
|
66
|
+
self._firmware: FirmwareEndpoints | None = None
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def plants(self) -> PlantEndpoints:
|
|
70
|
+
"""Access plant/station management endpoints.
|
|
71
|
+
|
|
72
|
+
Provides methods for:
|
|
73
|
+
- Listing stations/plants
|
|
74
|
+
- Getting plant details
|
|
75
|
+
- Managing plant configuration
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
PlantEndpoints: The plant endpoints instance.
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
```python
|
|
82
|
+
plants = await client.api.plants.get_plants()
|
|
83
|
+
details = await client.api.plants.get_plant_details(plant_id)
|
|
84
|
+
```
|
|
85
|
+
"""
|
|
86
|
+
if self._plants is None:
|
|
87
|
+
from .endpoints import PlantEndpoints
|
|
88
|
+
|
|
89
|
+
self._plants = PlantEndpoints(self._client)
|
|
90
|
+
return self._plants
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def devices(self) -> DeviceEndpoints:
|
|
94
|
+
"""Access device discovery and runtime data endpoints.
|
|
95
|
+
|
|
96
|
+
Provides methods for:
|
|
97
|
+
- Device enumeration
|
|
98
|
+
- Inverter runtime data
|
|
99
|
+
- Battery information
|
|
100
|
+
- MID/GridBOSS data
|
|
101
|
+
- Parallel group details
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
DeviceEndpoints: The device endpoints instance.
|
|
105
|
+
|
|
106
|
+
Example:
|
|
107
|
+
```python
|
|
108
|
+
devices = await client.api.devices.get_devices(plant_id)
|
|
109
|
+
runtime = await client.api.devices.get_inverter_runtime(serial)
|
|
110
|
+
battery = await client.api.devices.get_battery_info(serial)
|
|
111
|
+
```
|
|
112
|
+
"""
|
|
113
|
+
if self._devices is None:
|
|
114
|
+
from .endpoints import DeviceEndpoints
|
|
115
|
+
|
|
116
|
+
self._devices = DeviceEndpoints(self._client)
|
|
117
|
+
return self._devices
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def control(self) -> ControlEndpoints:
|
|
121
|
+
"""Access parameter control and device function endpoints.
|
|
122
|
+
|
|
123
|
+
Provides methods for:
|
|
124
|
+
- Parameter read/write
|
|
125
|
+
- Quick charge control
|
|
126
|
+
- EPS mode control
|
|
127
|
+
- Operating mode changes
|
|
128
|
+
- SOC limits
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
ControlEndpoints: The control endpoints instance.
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
```python
|
|
135
|
+
await client.api.control.start_quick_charge(serial)
|
|
136
|
+
await client.api.control.write_parameter(serial, param_id, value)
|
|
137
|
+
params = await client.api.control.read_parameters(serial, [param_ids])
|
|
138
|
+
```
|
|
139
|
+
"""
|
|
140
|
+
if self._control is None:
|
|
141
|
+
from .endpoints import ControlEndpoints
|
|
142
|
+
|
|
143
|
+
self._control = ControlEndpoints(self._client)
|
|
144
|
+
return self._control
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def analytics(self) -> AnalyticsEndpoints:
|
|
148
|
+
"""Access analytics, charts, and event log endpoints.
|
|
149
|
+
|
|
150
|
+
Provides methods for:
|
|
151
|
+
- Energy charts
|
|
152
|
+
- Production statistics
|
|
153
|
+
- Event logs
|
|
154
|
+
- Historical data
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
AnalyticsEndpoints: The analytics endpoints instance.
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
```python
|
|
161
|
+
chart = await client.api.analytics.get_energy_chart(plant_id, date_range)
|
|
162
|
+
events = await client.api.analytics.get_event_logs(plant_id)
|
|
163
|
+
```
|
|
164
|
+
"""
|
|
165
|
+
if self._analytics is None:
|
|
166
|
+
from .endpoints import AnalyticsEndpoints
|
|
167
|
+
|
|
168
|
+
self._analytics = AnalyticsEndpoints(self._client)
|
|
169
|
+
return self._analytics
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def forecasting(self) -> ForecastingEndpoints:
|
|
173
|
+
"""Access weather and solar forecasting endpoints.
|
|
174
|
+
|
|
175
|
+
Provides methods for:
|
|
176
|
+
- Weather forecasts
|
|
177
|
+
- Solar production forecasts
|
|
178
|
+
- Irradiance data
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
ForecastingEndpoints: The forecasting endpoints instance.
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
```python
|
|
185
|
+
weather = await client.api.forecasting.get_weather(plant_id)
|
|
186
|
+
forecast = await client.api.forecasting.get_solar_forecast(plant_id)
|
|
187
|
+
```
|
|
188
|
+
"""
|
|
189
|
+
if self._forecasting is None:
|
|
190
|
+
from .endpoints import ForecastingEndpoints
|
|
191
|
+
|
|
192
|
+
self._forecasting = ForecastingEndpoints(self._client)
|
|
193
|
+
return self._forecasting
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def export(self) -> ExportEndpoints:
|
|
197
|
+
"""Access data export endpoints.
|
|
198
|
+
|
|
199
|
+
Provides methods for:
|
|
200
|
+
- Excel export
|
|
201
|
+
- CSV export
|
|
202
|
+
- Report generation
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
ExportEndpoints: The export endpoints instance.
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
```python
|
|
209
|
+
excel_data = await client.api.export.export_to_excel(plant_id, date_range)
|
|
210
|
+
csv_data = await client.api.export.export_to_csv(plant_id, date_range)
|
|
211
|
+
```
|
|
212
|
+
"""
|
|
213
|
+
if self._export is None:
|
|
214
|
+
from .endpoints import ExportEndpoints
|
|
215
|
+
|
|
216
|
+
self._export = ExportEndpoints(self._client)
|
|
217
|
+
return self._export
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def firmware(self) -> FirmwareEndpoints:
|
|
221
|
+
"""Access firmware management endpoints.
|
|
222
|
+
|
|
223
|
+
Provides methods for:
|
|
224
|
+
- Firmware version checking
|
|
225
|
+
- Firmware updates
|
|
226
|
+
- Update status
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
FirmwareEndpoints: The firmware endpoints instance.
|
|
230
|
+
|
|
231
|
+
Example:
|
|
232
|
+
```python
|
|
233
|
+
version = await client.api.firmware.get_version(serial)
|
|
234
|
+
await client.api.firmware.start_update(serial, version)
|
|
235
|
+
```
|
|
236
|
+
"""
|
|
237
|
+
if self._firmware is None:
|
|
238
|
+
from .endpoints import FirmwareEndpoints
|
|
239
|
+
|
|
240
|
+
self._firmware = FirmwareEndpoints(self._client)
|
|
241
|
+
return self._firmware
|
pylxpweb/cli/__init__.py
ADDED