pylxpweb 0.1.0__py3-none-any.whl → 0.5.2__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.
Files changed (46) hide show
  1. pylxpweb/__init__.py +47 -2
  2. pylxpweb/api_namespace.py +241 -0
  3. pylxpweb/cli/__init__.py +3 -0
  4. pylxpweb/cli/collect_device_data.py +874 -0
  5. pylxpweb/client.py +387 -26
  6. pylxpweb/constants/__init__.py +481 -0
  7. pylxpweb/constants/api.py +48 -0
  8. pylxpweb/constants/devices.py +98 -0
  9. pylxpweb/constants/locations.py +227 -0
  10. pylxpweb/{constants.py → constants/registers.py} +72 -238
  11. pylxpweb/constants/scaling.py +479 -0
  12. pylxpweb/devices/__init__.py +32 -0
  13. pylxpweb/devices/_firmware_update_mixin.py +504 -0
  14. pylxpweb/devices/_mid_runtime_properties.py +1427 -0
  15. pylxpweb/devices/base.py +122 -0
  16. pylxpweb/devices/battery.py +589 -0
  17. pylxpweb/devices/battery_bank.py +331 -0
  18. pylxpweb/devices/inverters/__init__.py +32 -0
  19. pylxpweb/devices/inverters/_features.py +378 -0
  20. pylxpweb/devices/inverters/_runtime_properties.py +596 -0
  21. pylxpweb/devices/inverters/base.py +2124 -0
  22. pylxpweb/devices/inverters/generic.py +192 -0
  23. pylxpweb/devices/inverters/hybrid.py +274 -0
  24. pylxpweb/devices/mid_device.py +183 -0
  25. pylxpweb/devices/models.py +126 -0
  26. pylxpweb/devices/parallel_group.py +364 -0
  27. pylxpweb/devices/station.py +908 -0
  28. pylxpweb/endpoints/control.py +980 -2
  29. pylxpweb/endpoints/devices.py +249 -16
  30. pylxpweb/endpoints/firmware.py +43 -10
  31. pylxpweb/endpoints/plants.py +15 -19
  32. pylxpweb/exceptions.py +4 -0
  33. pylxpweb/models.py +708 -41
  34. pylxpweb/transports/__init__.py +78 -0
  35. pylxpweb/transports/capabilities.py +101 -0
  36. pylxpweb/transports/data.py +501 -0
  37. pylxpweb/transports/exceptions.py +59 -0
  38. pylxpweb/transports/factory.py +119 -0
  39. pylxpweb/transports/http.py +329 -0
  40. pylxpweb/transports/modbus.py +617 -0
  41. pylxpweb/transports/protocol.py +217 -0
  42. {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.2.dist-info}/METADATA +130 -85
  43. pylxpweb-0.5.2.dist-info/RECORD +52 -0
  44. {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.2.dist-info}/WHEEL +1 -1
  45. pylxpweb-0.5.2.dist-info/entry_points.txt +3 -0
  46. 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.1.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
@@ -0,0 +1,3 @@
1
+ """Command-line interface tools for pylxpweb."""
2
+
3
+ from __future__ import annotations