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.
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 +545 -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 +351 -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 +629 -40
  34. pylxpweb/transports/__init__.py +78 -0
  35. pylxpweb/transports/capabilities.py +101 -0
  36. pylxpweb/transports/data.py +495 -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 +557 -0
  41. pylxpweb/transports/protocol.py +217 -0
  42. {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.0.dist-info}/METADATA +130 -85
  43. pylxpweb-0.5.0.dist-info/RECORD +52 -0
  44. {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.0.dist-info}/WHEEL +1 -1
  45. pylxpweb-0.5.0.dist-info/entry_points.txt +3 -0
  46. pylxpweb-0.1.0.dist-info/RECORD +0 -19
@@ -0,0 +1,217 @@
1
+ """Transport protocol definition.
2
+
3
+ This module defines the InverterTransport protocol that all transport
4
+ implementations must follow. Using Protocol allows for structural subtyping
5
+ (duck typing) while still providing type safety.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING, Protocol, Self, runtime_checkable
11
+
12
+ if TYPE_CHECKING:
13
+ from .capabilities import TransportCapabilities
14
+ from .data import BatteryBankData, InverterEnergyData, InverterRuntimeData
15
+
16
+
17
+ @runtime_checkable
18
+ class InverterTransport(Protocol):
19
+ """Protocol defining the interface for inverter communication.
20
+
21
+ All transport implementations (HTTP, Modbus) must implement this interface.
22
+ This enables the same device code to work with any transport type.
23
+
24
+ The protocol is runtime-checkable, allowing isinstance() checks:
25
+ if isinstance(transport, InverterTransport):
26
+ await transport.read_runtime()
27
+ """
28
+
29
+ @property
30
+ def serial(self) -> str:
31
+ """Get the inverter serial number.
32
+
33
+ Returns:
34
+ 10-digit serial number string
35
+ """
36
+ ...
37
+
38
+ @property
39
+ def is_connected(self) -> bool:
40
+ """Check if transport is currently connected.
41
+
42
+ Returns:
43
+ True if connected and ready for operations
44
+ """
45
+ ...
46
+
47
+ @property
48
+ def capabilities(self) -> TransportCapabilities:
49
+ """Get transport capabilities.
50
+
51
+ Returns:
52
+ Capabilities indicating what operations are supported
53
+ """
54
+ ...
55
+
56
+ async def connect(self) -> None:
57
+ """Establish connection to the device.
58
+
59
+ For HTTP: Validates credentials and establishes session
60
+ For Modbus: Opens TCP connection to the adapter
61
+
62
+ Raises:
63
+ TransportConnectionError: If connection fails
64
+ """
65
+ ...
66
+
67
+ async def disconnect(self) -> None:
68
+ """Close the connection.
69
+
70
+ Should be called when done with the transport.
71
+ Safe to call multiple times.
72
+ """
73
+ ...
74
+
75
+ async def read_runtime(self) -> InverterRuntimeData:
76
+ """Read real-time operating data from inverter.
77
+
78
+ Returns:
79
+ Runtime data with all values properly scaled
80
+
81
+ Raises:
82
+ TransportReadError: If read operation fails
83
+ TransportConnectionError: If not connected
84
+ """
85
+ ...
86
+
87
+ async def read_energy(self) -> InverterEnergyData:
88
+ """Read energy statistics from inverter.
89
+
90
+ Returns:
91
+ Energy data with all values in kWh
92
+
93
+ Raises:
94
+ TransportReadError: If read operation fails
95
+ TransportConnectionError: If not connected
96
+ """
97
+ ...
98
+
99
+ async def read_battery(self) -> BatteryBankData | None:
100
+ """Read battery bank information.
101
+
102
+ Returns:
103
+ Battery bank data if batteries present, None otherwise
104
+
105
+ Raises:
106
+ TransportReadError: If read operation fails
107
+ TransportConnectionError: If not connected
108
+ """
109
+ ...
110
+
111
+ async def read_parameters(
112
+ self,
113
+ start_address: int,
114
+ count: int,
115
+ ) -> dict[int, int]:
116
+ """Read configuration parameters (hold registers).
117
+
118
+ Args:
119
+ start_address: Starting register address
120
+ count: Number of registers to read (max 127 for HTTP, 40 for Modbus)
121
+
122
+ Returns:
123
+ Dict mapping register address to raw integer value
124
+
125
+ Raises:
126
+ TransportReadError: If read operation fails
127
+ TransportConnectionError: If not connected
128
+ """
129
+ ...
130
+
131
+ async def write_parameters(
132
+ self,
133
+ parameters: dict[int, int],
134
+ ) -> bool:
135
+ """Write configuration parameters (hold registers).
136
+
137
+ Args:
138
+ parameters: Dict mapping register address to value to write
139
+
140
+ Returns:
141
+ True if write succeeded
142
+
143
+ Raises:
144
+ TransportWriteError: If write operation fails
145
+ TransportConnectionError: If not connected
146
+ """
147
+ ...
148
+
149
+
150
+ class BaseTransport:
151
+ """Base class providing common transport functionality.
152
+
153
+ Transport implementations can inherit from this class to get
154
+ common utilities while implementing the InverterTransport protocol.
155
+
156
+ Supports async context manager for automatic connection management:
157
+ async with transport:
158
+ data = await transport.read_runtime()
159
+ """
160
+
161
+ def __init__(self, serial: str) -> None:
162
+ """Initialize base transport.
163
+
164
+ Args:
165
+ serial: Inverter serial number
166
+ """
167
+ self._serial = serial
168
+ self._connected = False
169
+
170
+ @property
171
+ def serial(self) -> str:
172
+ """Get the inverter serial number."""
173
+ return self._serial
174
+
175
+ @property
176
+ def is_connected(self) -> bool:
177
+ """Check if transport is connected."""
178
+ return self._connected
179
+
180
+ async def __aenter__(self) -> Self:
181
+ """Enter async context manager, connecting the transport.
182
+
183
+ Returns:
184
+ Self after connecting
185
+ """
186
+ await self.connect()
187
+ return self
188
+
189
+ async def __aexit__(
190
+ self,
191
+ exc_type: type[BaseException] | None,
192
+ exc_val: BaseException | None,
193
+ exc_tb: object,
194
+ ) -> None:
195
+ """Exit async context manager, disconnecting the transport."""
196
+ await self.disconnect()
197
+
198
+ async def connect(self) -> None:
199
+ """Establish connection. Must be implemented by subclasses."""
200
+ raise NotImplementedError
201
+
202
+ async def disconnect(self) -> None:
203
+ """Close connection. Must be implemented by subclasses."""
204
+ raise NotImplementedError
205
+
206
+ def _ensure_connected(self) -> None:
207
+ """Ensure transport is connected.
208
+
209
+ Raises:
210
+ TransportConnectionError: If not connected
211
+ """
212
+ if not self._connected:
213
+ from .exceptions import TransportConnectionError
214
+
215
+ raise TransportConnectionError(
216
+ f"Transport not connected for inverter {self._serial}. Call connect() first."
217
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pylxpweb
3
- Version: 0.1.0
3
+ Version: 0.5.0
4
4
  Summary: Python client library for Luxpower/EG4 inverter web monitoring API
5
5
  Keywords: luxpower,eg4,inverter,solar,api,client
6
6
  Author: Bryan Li
@@ -15,19 +15,29 @@ Classifier: Programming Language :: Python :: 3.13
15
15
  Classifier: Topic :: Home Automation
16
16
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
17
  Requires-Dist: aiohttp>=3.13.2
18
- Requires-Dist: pydantic>=2.12.4
18
+ Requires-Dist: pydantic>=2.12.0
19
+ Requires-Dist: pymodbus>=3.11.4 ; extra == 'all'
19
20
  Requires-Dist: pytest>=9.0.1 ; extra == 'dev'
20
21
  Requires-Dist: pytest-asyncio>=1.3.0 ; extra == 'dev'
21
22
  Requires-Dist: pytest-cov>=7.0.0 ; extra == 'dev'
22
23
  Requires-Dist: aioresponses>=0.7.8 ; extra == 'dev'
23
24
  Requires-Dist: mypy>=1.18.2 ; extra == 'dev'
24
25
  Requires-Dist: ruff>=0.14.5 ; extra == 'dev'
26
+ Requires-Dist: pymodbus>=3.11.4 ; extra == 'modbus'
25
27
  Requires-Python: >=3.12
28
+ Provides-Extra: all
26
29
  Provides-Extra: dev
30
+ Provides-Extra: modbus
27
31
  Description-Content-Type: text/markdown
28
32
 
29
33
  # pylxpweb
30
34
 
35
+ [![CI](https://github.com/joyfulhouse/pylxpweb/actions/workflows/ci.yml/badge.svg)](https://github.com/joyfulhouse/pylxpweb/actions/workflows/ci.yml)
36
+ [![codecov](https://codecov.io/gh/joyfulhouse/pylxpweb/branch/main/graph/badge.svg)](https://codecov.io/gh/joyfulhouse/pylxpweb)
37
+ [![PyPI version](https://badge.fury.io/py/pylxpweb.svg)](https://badge.fury.io/py/pylxpweb)
38
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
39
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
40
+
31
41
  A Python client library for Luxpower/EG4 solar inverters and energy storage systems, providing programmatic access to the Luxpower/EG4 web monitoring API.
32
42
 
33
43
  ## Supported API Endpoints
@@ -69,9 +79,12 @@ uv sync --all-extras --dev
69
79
 
70
80
  ## Quick Start
71
81
 
82
+ ### Basic Usage with Device Objects
83
+
72
84
  ```python
73
85
  import asyncio
74
86
  from pylxpweb import LuxpowerClient
87
+ from pylxpweb.devices.station import Station
75
88
 
76
89
  async def main():
77
90
  # Create client with credentials
@@ -81,43 +94,93 @@ async def main():
81
94
  password="your_password",
82
95
  base_url="https://monitor.eg4electronics.com" # or us.luxpowertek.com, eu.luxpowertek.com
83
96
  ) as client:
84
- # Get all stations/plants
85
- plants = await client.get_plants()
86
- print(f"Found {len(plants)} stations")
87
-
88
- # Select first station
89
- plant = plants[0]
90
- plant_id = plant["plantId"]
91
-
92
- # Get devices for this station
93
- devices = await client.get_devices(plant_id)
94
-
95
- # Get runtime data for each inverter
96
- for device in devices:
97
- if device["type"] == "inverter":
98
- serial = device["serialNum"]
99
-
100
- # Get real-time data
101
- runtime = await client.get_inverter_runtime(serial)
102
- energy = await client.get_inverter_energy(serial)
103
-
104
- print(f"\nInverter {serial}:")
105
- print(f" AC Power: {runtime['pac']}W")
106
- print(f" Battery SOC: {runtime['soc']}%")
107
- print(f" Daily Energy: {energy['eToday']}kWh")
108
- print(f" Grid Power: {runtime['pToGrid']}W")
109
-
110
- # Get battery information
111
- batteries = await client.get_battery_info(serial)
112
- for battery in batteries.get("batteryArray", []):
113
- key = battery["batteryKey"]
114
- soc = battery["soc"]
115
- voltage = battery["voltage"] / 100 # Scale voltage
116
- print(f" Battery {key}: {soc}% @ {voltage}V")
97
+ # Load all stations with device hierarchy
98
+ stations = await Station.load_all(client)
99
+ print(f"Found {len(stations)} stations")
100
+
101
+ # Work with first station
102
+ station = stations[0]
103
+ print(f"\nStation: {station.name}")
104
+
105
+ # Access inverters - all have properly-scaled properties
106
+ for inverter in station.all_inverters:
107
+ await inverter.refresh() # Fetch latest data
108
+
109
+ print(f"\n{inverter.model} {inverter.serial_number}:")
110
+
111
+ # All properties return properly-scaled values
112
+ print(f" PV Power: {inverter.pv_total_power}W")
113
+ print(f" Battery: {inverter.battery_soc}% @ {inverter.battery_voltage}V")
114
+ print(f" Grid: {inverter.grid_voltage_r}V @ {inverter.grid_frequency}Hz")
115
+ print(f" Inverter Power: {inverter.inverter_power}W")
116
+ print(f" To Grid: {inverter.power_to_grid}W")
117
+ print(f" To User: {inverter.power_to_user}W")
118
+ print(f" Temperature: {inverter.inverter_temperature}°C")
119
+ print(f" Today: {inverter.total_energy_today}kWh")
120
+ print(f" Lifetime: {inverter.total_energy_lifetime}kWh")
121
+
122
+ # Access battery bank if available
123
+ if inverter.battery_bank:
124
+ bank = inverter.battery_bank
125
+ print(f"\n Battery Bank:")
126
+ print(f" Voltage: {bank.voltage}V")
127
+ print(f" SOC: {bank.soc}%")
128
+ print(f" Charge Power: {bank.charge_power}W")
129
+ print(f" Discharge Power: {bank.discharge_power}W")
130
+ print(f" Capacity: {bank.current_capacity}/{bank.max_capacity} Ah")
131
+
132
+ # Individual battery modules
133
+ for battery in bank.batteries:
134
+ print(f" Battery {battery.battery_index + 1}:")
135
+ print(f" Voltage: {battery.voltage}V")
136
+ print(f" Current: {battery.current}A")
137
+ print(f" SOC: {battery.soc}%")
138
+ print(f" Temp: {battery.max_cell_temp}°C")
139
+
140
+ # Access GridBOSS (MID) devices if present
141
+ for group in station.parallel_groups:
142
+ if group.mid_device:
143
+ mid = group.mid_device
144
+ await mid.refresh()
145
+
146
+ print(f"\nGridBOSS {mid.serial_number}:")
147
+ print(f" Grid: {mid.grid_voltage}V @ {mid.grid_frequency}Hz")
148
+ print(f" Grid Power: {mid.grid_power}W")
149
+ print(f" UPS Power: {mid.ups_power}W")
150
+ print(f" Load L1: {mid.load_l1_power}W @ {mid.load_l1_current}A")
151
+ print(f" Load L2: {mid.load_l2_power}W @ {mid.load_l2_current}A")
117
152
 
118
153
  asyncio.run(main())
119
154
  ```
120
155
 
156
+ ### Low-Level API Access
157
+
158
+ For direct API access without device objects:
159
+
160
+ ```python
161
+ async with LuxpowerClient(username, password) as client:
162
+ # Get stations
163
+ plants = await client.api.plants.get_plants()
164
+ plant_id = plants.rows[0].plantId
165
+
166
+ # Get devices
167
+ devices = await client.api.devices.get_devices(str(plant_id))
168
+
169
+ # Get runtime data for first inverter
170
+ inverter = devices.rows[0]
171
+ serial = inverter.serialNum
172
+
173
+ # Fetch data (returns Pydantic models)
174
+ runtime = await client.api.devices.get_inverter_runtime(serial)
175
+ energy = await client.api.devices.get_inverter_energy(serial)
176
+
177
+ # NOTE: Raw API returns scaled integers - you must scale manually
178
+ print(f"AC Power: {runtime.pac}W") # No scaling needed for power
179
+ print(f"Grid Voltage: {runtime.vacr / 10}V") # Must divide by 10
180
+ print(f"Grid Frequency: {runtime.fac / 100}Hz") # Must divide by 100
181
+ print(f"Battery Voltage: {runtime.vBat / 10}V") # Must divide by 10
182
+ ```
183
+
121
184
  ## Advanced Usage
122
185
 
123
186
  ### Regional Endpoints and Custom Session
@@ -284,18 +347,40 @@ pylxpweb/
284
347
 
285
348
  ## Data Scaling
286
349
 
287
- The API returns scaled integer values that must be converted:
350
+ ### Automatic Scaling with Device Properties (Recommended)
288
351
 
289
- | Data Type | Scaling | Example |
290
- |-----------|---------|---------|
291
- | Voltage | ÷ 100 | 5100 → 51.00V |
292
- | Current | ÷ 100 | 1500 → 15.00A |
293
- | Frequency | ÷ 100 | 5998 → 59.98Hz |
294
- | Cell Voltage | ÷ 1000 | 3350 → 3.350V |
295
- | Power | none | 1030 → 1030W |
296
- | Temperature | none | 39 → 39°C |
352
+ **Device objects automatically handle all scaling** - just use the properties:
297
353
 
298
- See [API Reference](docs/api/LUXPOWER_API.md#data-scaling-reference) for complete details.
354
+ ```python
355
+ # ✅ RECOMMENDED: Use device properties (automatically scaled)
356
+ await inverter.refresh()
357
+ voltage = inverter.grid_voltage_r # Returns 241.8 (already scaled)
358
+ frequency = inverter.grid_frequency # Returns 59.98 (already scaled)
359
+ power = inverter.pv_total_power # Returns 1500 (already scaled)
360
+ ```
361
+
362
+ All device classes (`BaseInverter`, `MIDDevice`, `Battery`, `BatteryBank`, `ParallelGroup`) provide properly-scaled properties. **You never need to manually scale values when using device objects.**
363
+
364
+ ### Manual Scaling for Raw API Data
365
+
366
+ If you use the low-level API directly (not recommended for most users), you must scale values manually:
367
+
368
+ | Data Type | Scaling | Raw API | Scaled | Property Name |
369
+ |-----------|---------|---------|--------|---------------|
370
+ | Inverter Voltage | ÷10 | 2410 | 241.0V | `grid_voltage_r` |
371
+ | Battery Voltage (Bank) | ÷10 | 539 | 53.9V | `battery_voltage` |
372
+ | Battery Voltage (Module) | ÷100 | 5394 | 53.94V | `voltage` |
373
+ | Cell Voltage | ÷1000 | 3364 | 3.364V | `max_cell_voltage` |
374
+ | Current | ÷100 | 1500 | 15.00A | `grid_l1_current` |
375
+ | Frequency | ÷100 | 5998 | 59.98Hz | `grid_frequency` |
376
+ | Bus Voltage | ÷100 | 3703 | 37.03V | `bus1_voltage` |
377
+ | Power | Direct | 1030 | 1030W | `inverter_power` |
378
+ | Temperature | Direct | 39 | 39°C | `inverter_temperature` |
379
+ | Energy | ÷10 | 184 | 18.4 kWh | `today_yielding` |
380
+
381
+ **Note**: Different voltage types use different scaling factors. Use device properties to avoid confusion.
382
+
383
+ See [Scaling Guide](docs/SCALING_GUIDE.md) and [API Reference](docs/api/LUXPOWER_API.md#data-scaling-reference) for complete details.
299
384
 
300
385
  ## API Endpoints
301
386
 
@@ -388,46 +473,6 @@ This library communicates with the official EG4/Luxpower API using the same endp
388
473
  - **Issues**: [GitHub Issues](https://github.com/joyfulhouse/pylxpweb/issues)
389
474
  - **API Reference**: [docs/api/LUXPOWER_API.md](docs/api/LUXPOWER_API.md)
390
475
 
391
- ## Status
392
-
393
- **Current Phase**: Core Implementation Complete
394
-
395
- - ✅ Research and API documentation complete
396
- - ✅ CLAUDE.md development guidelines
397
- - ✅ Comprehensive API reference documentation
398
- - ✅ Core library implementation (async client with full API coverage)
399
- - ✅ Test suite development (95% code coverage with 44 unit tests)
400
- - ✅ Package configuration (uv + pyproject.toml)
401
- - ✅ Type safety (mypy --strict passing)
402
- - ✅ Code quality (ruff linting passing)
403
- - ⏳ PyPI publication
404
-
405
- ## Roadmap
406
-
407
- 1. **Phase 1**: Core library implementation
408
- - Client class with authentication
409
- - Device discovery and management
410
- - Runtime data retrieval
411
- - Data models and scaling
412
-
413
- 2. **Phase 2**: Advanced features
414
- - Control operations
415
- - Caching with configurable TTL
416
- - Retry logic and error handling
417
- - Session injection support
418
-
419
- 3. **Phase 3**: Testing and polish
420
- - Comprehensive test suite (>90% coverage)
421
- - Integration tests
422
- - Documentation examples
423
- - Type checking with mypy strict mode
424
-
425
- 4. **Phase 4**: Distribution
426
- - Package configuration (pyproject.toml)
427
- - PyPI publication
428
- - CI/CD pipeline
429
- - Release automation
430
-
431
476
  ---
432
477
 
433
478
  **Happy monitoring!** ☀️⚡🔋
@@ -0,0 +1,52 @@
1
+ pylxpweb/__init__.py,sha256=splapsTwdAEK7YTB-QeizTO0k6_D2GHXMNYTXTAzrIo,2370
2
+ pylxpweb/api_namespace.py,sha256=wvQa1UQKR_yG-kzs7NydWQ1pLW8T5MhsJq5CBlsgrRk,7552
3
+ pylxpweb/cli/__init__.py,sha256=VXKyWZs50UeY2xofuTiwbE87U4ts8wSacgX00ThDurM,85
4
+ pylxpweb/cli/collect_device_data.py,sha256=17y8YQLceuzv90-8slB_FfGGEecsPtJB1C84tHG2pic,28919
5
+ pylxpweb/client.py,sha256=oRKnQQ_5N7fM2wRb7amHCHy3UeQQmAjbol4nXH-DW2w,31268
6
+ pylxpweb/constants/__init__.py,sha256=JUwt0mxACX93WjwF6tdBXhJqxvGKJGBm443VEfQRxxc,12682
7
+ pylxpweb/constants/api.py,sha256=7GGyvOgHNc3BPgPrgCBnrAtjwiI8fUVYXTceAuN3LGo,1943
8
+ pylxpweb/constants/devices.py,sha256=XF5y51LodBG6N4mOZddr6DJVd3tMvSXgjSkQ5r1JKWI,3257
9
+ pylxpweb/constants/locations.py,sha256=1V6MHfwFf5KVX1AQeiuc50ZQsca9Aei2RsTHaWpc3Ck,7385
10
+ pylxpweb/constants/registers.py,sha256=V5B3Bkvgbp-8mJ_tVpN8z5GM0OnWrLE2LOR4DfYEHbM,38193
11
+ pylxpweb/constants/scaling.py,sha256=l1QHf4Fa1VQKqcVK9snTHy9BOB0mNzb3d5bGynZSmJ8,17819
12
+ pylxpweb/devices/__init__.py,sha256=gWJ-lHEwfIm7bFwMx4TRDG-eaKg7jObCzSGyflvEfls,788
13
+ pylxpweb/devices/_firmware_update_mixin.py,sha256=LkwS36dSvoE6iTlCk3osRqcOySgEG2MD09pPnbnaIEU,21045
14
+ pylxpweb/devices/_mid_runtime_properties.py,sha256=7UHm-27HetxKyqwb2raIhbYIO7Ve8H0ulpA5tKm-QlA,15100
15
+ pylxpweb/devices/base.py,sha256=Hdr7O-ct-Ouju8O8XqmzfGMAHKa5JuchElhSye2SYDk,3627
16
+ pylxpweb/devices/battery.py,sha256=6YQ3vG70kPu18Av6RA3Rd0u8qktSgJbVRa-Jkyveq-4,18032
17
+ pylxpweb/devices/battery_bank.py,sha256=lQurUHx8THXgMQMB3ZVudcIVEGF9Lo0w1mCn-NSfZrc,9620
18
+ pylxpweb/devices/inverters/__init__.py,sha256=UmH9C_QxKGydPNm6KpzpUjUGjgAxyw5OijG_mvqtgxg,744
19
+ pylxpweb/devices/inverters/_features.py,sha256=1Y6Bx3Hb7nSr9_i4bIjvAq4uqk0_thXsbaiS35iQRKg,13104
20
+ pylxpweb/devices/inverters/_runtime_properties.py,sha256=C8c0qj8BSes0pHfRBZMqzY1eALsaB2cIi4lipF0zG4M,15634
21
+ pylxpweb/devices/inverters/base.py,sha256=DqwABIrR917nPWqIDLf1Zj599loSqaxQrg2Rg_C4VSw,73756
22
+ pylxpweb/devices/inverters/generic.py,sha256=dfydpVSa38fHCptfaCPDhE1Q3tpP8OPizgOTapiotLE,7148
23
+ pylxpweb/devices/inverters/hybrid.py,sha256=rRncXS0SfVxWLKE7PHTqbQMTDHkiAZvFlfojzKwUpo0,9203
24
+ pylxpweb/devices/mid_device.py,sha256=xXCr5XcL3eRA6TqoN_pxxg99mVkWdX4GXfOQQXumEeU,6167
25
+ pylxpweb/devices/models.py,sha256=OAMGZ9FCkgE3jY9xIky5Qf71dLzgi5bSd_dng-YMwak,3931
26
+ pylxpweb/devices/parallel_group.py,sha256=Fbwad4604PQnI0B6paHrwZmmidrv5ceWjLhIN66Dito,11525
27
+ pylxpweb/devices/station.py,sha256=r7616lb8DUx_BSI20AWBB4bzqn2l6zYw2P4PCs9Kdzw,34067
28
+ pylxpweb/endpoints/__init__.py,sha256=lzHDUiFCoyqRgncBYs5_EHy4I8I5CJAReTx8o4TdbTk,934
29
+ pylxpweb/endpoints/analytics.py,sha256=Zyi9lVDXGr-4cvpv-POdLaF1OE3qgOcYwyzMNvL1xzI,14482
30
+ pylxpweb/endpoints/base.py,sha256=QFFH4l83Bui1oK_ghrROYXAXAnwbLgTTd2Z6KFP6APw,1265
31
+ pylxpweb/endpoints/control.py,sha256=ELGbDfgEsnuhL7UzSH0-hk7VYkkYd0AmHFEKouaf0bU,44476
32
+ pylxpweb/endpoints/devices.py,sha256=PPTMj9g-GOSNUGhRX8CZPkkAWHphy1g3UddmVCOWQqw,17640
33
+ pylxpweb/endpoints/export.py,sha256=UPedThsYCmkeo1Yxu_CMIuG8h6Cu1Z9l4veYwKDVN8I,2508
34
+ pylxpweb/endpoints/firmware.py,sha256=vwuPRzc0TcDNpY2ygtLslUDn8LyxVh11uHum4mzofoQ,10286
35
+ pylxpweb/endpoints/forecasting.py,sha256=0Zs4qGdqIFrNsKn94qSpDixQJrWJIadfPs8XULbEn4w,3836
36
+ pylxpweb/endpoints/plants.py,sha256=zUl1vdQXpQSdG2YhzZfAIRyeSfjbxmTZIqZW4DBcD2g,16278
37
+ pylxpweb/exceptions.py,sha256=-D-dbUFPMGupzhiV52X9Kz3vx7UOvx67lsCx6hR0KWI,671
38
+ pylxpweb/models.py,sha256=I5FT9tdX1D9L3Jf5Lpz4fxdjTltDsXADQFLB2EpOC4U,38258
39
+ pylxpweb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ pylxpweb/registers.py,sha256=Chk1uIoc5DjapSquEHYtBYgiPKuPhTjVn7PMl5JuPF0,12946
41
+ pylxpweb/transports/__init__.py,sha256=xvLjLWS0VaiyQJqgiuxwq3_3uHETUIMmmZZOnNDKSdU,2119
42
+ pylxpweb/transports/capabilities.py,sha256=IOJ_eWaLFInKKXObXjdTHXYz1UZNgLyP6QvUhAfWK7k,3460
43
+ pylxpweb/transports/data.py,sha256=o02IzK5eQFHYmPOLCLOEKmteh31llEnpYD9mnJY_Crs,18193
44
+ pylxpweb/transports/exceptions.py,sha256=re2wCq2h25TIhXKrz7BjZjA1G1sZtTjZzVooTNHsaWU,1437
45
+ pylxpweb/transports/factory.py,sha256=hcorD4SHOouoBG5qAP-0TqJbKMPah2kTl-z58UQ-t8E,3380
46
+ pylxpweb/transports/http.py,sha256=O93lxfU052YLKesx-opwAq_gHnAN9n_PR3tb1wbfM6I,13358
47
+ pylxpweb/transports/modbus.py,sha256=xUSmUvVYGU_tdxoFbWEkDh-T2XqEQgrBQDj2JmrUQO4,19412
48
+ pylxpweb/transports/protocol.py,sha256=yIsZJIJ1x0zO1YC0ymEY2Y7T4Ld3zPGojubr_rPsrl0,6069
49
+ pylxpweb-0.5.0.dist-info/WHEEL,sha256=KSLUh82mDPEPk0Bx0ScXlWL64bc8KmzIPNcpQZFV-6E,79
50
+ pylxpweb-0.5.0.dist-info/entry_points.txt,sha256=4BNqpz9UhA301Z2CKJy1914L4AlHVHHrweIem2qk64g,76
51
+ pylxpweb-0.5.0.dist-info/METADATA,sha256=hKCq_wGt_j_mgXb131apjOlpTP4HmUKxM2cZuN6B_yY,17575
52
+ pylxpweb-0.5.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: uv 0.9.10
2
+ Generator: uv 0.9.22
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ pylxpweb-collect = pylxpweb.cli.collect_device_data:main
3
+
@@ -1,19 +0,0 @@
1
- pylxpweb/__init__.py,sha256=z2eTcHo04RfZcyeiPE5Kn4Tt-Audtv-XZTRETOJ8Pvk,857
2
- pylxpweb/client.py,sha256=5lQoa2i2fmnL6eGuE1m8RWSZcYpYmYusTxZQ0GyRy5c,15507
3
- pylxpweb/constants.py,sha256=4Bx7CEnl9T8-mTlCOYd5jG80FdSTIH-msOXnIuo9utY,43198
4
- pylxpweb/endpoints/__init__.py,sha256=lzHDUiFCoyqRgncBYs5_EHy4I8I5CJAReTx8o4TdbTk,934
5
- pylxpweb/endpoints/analytics.py,sha256=Zyi9lVDXGr-4cvpv-POdLaF1OE3qgOcYwyzMNvL1xzI,14482
6
- pylxpweb/endpoints/base.py,sha256=QFFH4l83Bui1oK_ghrROYXAXAnwbLgTTd2Z6KFP6APw,1265
7
- pylxpweb/endpoints/control.py,sha256=_p1wIc7tKeV8KhDrPdBgrmWBUR302_s9KTbL_TYWp9g,10360
8
- pylxpweb/endpoints/devices.py,sha256=0fBWQ5S14HGnEk57zUHUjX4xju4FY5Uh8vS8_3FwISM,8244
9
- pylxpweb/endpoints/export.py,sha256=UPedThsYCmkeo1Yxu_CMIuG8h6Cu1Z9l4veYwKDVN8I,2508
10
- pylxpweb/endpoints/firmware.py,sha256=b1_cjgPVZmn6oZCnHdyX24tB1zDjE1lWgyV5sZ3OcNs,8773
11
- pylxpweb/endpoints/forecasting.py,sha256=0Zs4qGdqIFrNsKn94qSpDixQJrWJIadfPs8XULbEn4w,3836
12
- pylxpweb/endpoints/plants.py,sha256=Yt7BE8N-CvsUiRWFR30yb0DRw5UZNC3sX0jI8UsIfzE,16407
13
- pylxpweb/exceptions.py,sha256=5otooo7NCLMoj4i7tgHPAb85AwgsqqemHTrkqX8R12g,556
14
- pylxpweb/models.py,sha256=zkw_LU4K3IIF1CwlXkkTYakXU9fiN-PgzwSscC2sULI,18598
15
- pylxpweb/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- pylxpweb/registers.py,sha256=Chk1uIoc5DjapSquEHYtBYgiPKuPhTjVn7PMl5JuPF0,12946
17
- pylxpweb-0.1.0.dist-info/WHEEL,sha256=ZHijuPszqKbNczrBXkSuoxdxocbxgFghqnequ9ZQlVk,79
18
- pylxpweb-0.1.0.dist-info/METADATA,sha256=N1Hao1B93D0JZnSa-0AbgVlTBPvukrxVBeAevkQp8oA,14268
19
- pylxpweb-0.1.0.dist-info/RECORD,,