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
@@ -0,0 +1,331 @@
1
+ """Battery bank module for aggregate battery monitoring.
2
+
3
+ This module provides the BatteryBank class that represents the aggregate
4
+ battery system data (total capacity, charge/discharge power, overall status)
5
+ for all batteries connected to an inverter.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ from pylxpweb.constants import ScaleFactor, apply_scale
13
+
14
+ from .base import BaseDevice
15
+ from .models import DeviceInfo, Entity
16
+
17
+ if TYPE_CHECKING:
18
+ from pylxpweb import LuxpowerClient
19
+ from pylxpweb.models import BatteryInfo
20
+
21
+ from .battery import Battery
22
+
23
+
24
+ class BatteryBank(BaseDevice):
25
+ """Represents the aggregate battery bank for an inverter.
26
+
27
+ This class provides aggregate information for all batteries connected
28
+ to an inverter, including total capacity, charge/discharge power, and
29
+ overall status.
30
+
31
+ Example:
32
+ ```python
33
+ # BatteryBank is typically created from BatteryInfo API response
34
+ battery_info = await client.api.devices.get_battery_info(serial_num)
35
+
36
+ battery_bank = BatteryBank(
37
+ client=client,
38
+ inverter_serial=serial_num,
39
+ battery_info=battery_info
40
+ )
41
+
42
+ print(f"Battery Bank Status: {battery_bank.status}")
43
+ print(f"Total Capacity: {battery_bank.max_capacity} Ah")
44
+ print(f"Current Capacity: {battery_bank.current_capacity} Ah")
45
+ print(f"SOC: {battery_bank.soc}%")
46
+ print(f"Charge Power: {battery_bank.charge_power}W")
47
+ ```
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ client: LuxpowerClient,
53
+ inverter_serial: str,
54
+ battery_info: BatteryInfo,
55
+ ) -> None:
56
+ """Initialize battery bank.
57
+
58
+ Args:
59
+ client: LuxpowerClient instance for API access
60
+ inverter_serial: Serial number of parent inverter
61
+ battery_info: BatteryInfo data from API
62
+ """
63
+ # Use inverter serial + "_battery_bank" as unique ID
64
+ super().__init__(client, f"{inverter_serial}_battery_bank", "Battery Bank")
65
+
66
+ self.inverter_serial = inverter_serial
67
+ self.data = battery_info
68
+
69
+ # Individual battery modules in this bank
70
+ self.batteries: list[Battery] = [] # Will be Battery objects
71
+
72
+ # ========== Status Properties ==========
73
+
74
+ @property
75
+ def status(self) -> str:
76
+ """Get battery bank charging status.
77
+
78
+ Returns:
79
+ Status string (e.g., "Charging", "Discharging", "Idle").
80
+ """
81
+ return self.data.batStatus
82
+
83
+ @property
84
+ def status_text(self) -> str | None:
85
+ """Get detailed status text.
86
+
87
+ Returns:
88
+ Detailed status text, or None if not available.
89
+ """
90
+ return self.data.statusText
91
+
92
+ @property
93
+ def is_lost(self) -> bool:
94
+ """Check if battery communication is lost.
95
+
96
+ Returns:
97
+ True if battery is not communicating, False otherwise.
98
+ """
99
+ return self.data.lost if self.data.lost is not None else False
100
+
101
+ @property
102
+ def has_runtime_data(self) -> bool:
103
+ """Check if runtime data is available.
104
+
105
+ Returns:
106
+ True if runtime data is available, False otherwise.
107
+ """
108
+ return self.data.hasRuntimeData if self.data.hasRuntimeData is not None else False
109
+
110
+ # ========== State of Charge ==========
111
+
112
+ @property
113
+ def soc(self) -> int:
114
+ """Get aggregate state of charge for battery bank.
115
+
116
+ Returns:
117
+ State of charge percentage (0-100).
118
+ """
119
+ return self.data.soc
120
+
121
+ # ========== Voltage Properties ==========
122
+
123
+ @property
124
+ def voltage(self) -> float:
125
+ """Get battery bank voltage in volts.
126
+
127
+ Returns:
128
+ Battery voltage (scaled from vBat ÷10).
129
+ """
130
+ return apply_scale(self.data.vBat, ScaleFactor.SCALE_10)
131
+
132
+ @property
133
+ def voltage_text(self) -> str | None:
134
+ """Get formatted voltage text.
135
+
136
+ Returns:
137
+ Voltage text (e.g., "53.8V"), or None if not available.
138
+ """
139
+ return self.data.totalVoltageText
140
+
141
+ # ========== Power Properties ==========
142
+
143
+ @property
144
+ def charge_power(self) -> int:
145
+ """Get total charging power in watts.
146
+
147
+ Returns:
148
+ Charging power in watts.
149
+ """
150
+ return self.data.pCharge
151
+
152
+ @property
153
+ def discharge_power(self) -> int:
154
+ """Get total discharging power in watts.
155
+
156
+ Returns:
157
+ Discharging power in watts.
158
+ """
159
+ return self.data.pDisCharge
160
+
161
+ @property
162
+ def battery_power(self) -> int | None:
163
+ """Get net battery power in watts (positive = charging, negative = discharging).
164
+
165
+ Returns:
166
+ Net battery power in watts, or None if not available.
167
+ """
168
+ return self.data.batPower
169
+
170
+ @property
171
+ def pv_power(self) -> int | None:
172
+ """Get PV solar power in watts.
173
+
174
+ Returns:
175
+ PV power in watts, or None if not available.
176
+ """
177
+ return self.data.ppv
178
+
179
+ @property
180
+ def inverter_power(self) -> int | None:
181
+ """Get inverter power in watts.
182
+
183
+ Returns:
184
+ Inverter power in watts, or None if not available.
185
+ """
186
+ return self.data.pinv
187
+
188
+ @property
189
+ def grid_power(self) -> int | None:
190
+ """Get grid power in watts.
191
+
192
+ Returns:
193
+ Grid power in watts, or None if not available.
194
+ """
195
+ return self.data.prec
196
+
197
+ @property
198
+ def eps_power(self) -> int | None:
199
+ """Get EPS/backup power in watts.
200
+
201
+ Returns:
202
+ EPS power in watts, or None if not available.
203
+ """
204
+ return self.data.peps
205
+
206
+ # ========== Capacity Properties ==========
207
+
208
+ @property
209
+ def max_capacity(self) -> int:
210
+ """Get maximum battery bank capacity in amp-hours.
211
+
212
+ Returns:
213
+ Maximum capacity in Ah.
214
+ """
215
+ return self.data.maxBatteryCharge
216
+
217
+ @property
218
+ def current_capacity(self) -> float:
219
+ """Get current battery bank capacity in amp-hours.
220
+
221
+ Returns:
222
+ Current capacity in Ah, rounded to 1 decimal place.
223
+ """
224
+ return round(self.data.currentBatteryCharge, 1)
225
+
226
+ @property
227
+ def remain_capacity(self) -> int | None:
228
+ """Get remaining capacity in amp-hours.
229
+
230
+ Returns:
231
+ Remaining capacity in Ah, or None if not available.
232
+ """
233
+ return self.data.remainCapacity
234
+
235
+ @property
236
+ def full_capacity(self) -> int | None:
237
+ """Get full capacity in amp-hours.
238
+
239
+ Returns:
240
+ Full capacity in Ah, or None if not available.
241
+ """
242
+ return self.data.fullCapacity
243
+
244
+ @property
245
+ def capacity_percent(self) -> int | None:
246
+ """Get capacity percentage.
247
+
248
+ Returns:
249
+ Capacity percentage (0-100), or None if not available.
250
+ """
251
+ return self.data.capacityPercent
252
+
253
+ # ========== Current Properties ==========
254
+
255
+ @property
256
+ def current_text(self) -> str | None:
257
+ """Get formatted current text.
258
+
259
+ Returns:
260
+ Current text (e.g., "49.8A"), or None if not available.
261
+ """
262
+ return self.data.currentText
263
+
264
+ @property
265
+ def current_type(self) -> str | None:
266
+ """Get current flow direction.
267
+
268
+ Returns:
269
+ "charge" or "discharge", or None if not available.
270
+ """
271
+ return self.data.currentType
272
+
273
+ # ========== Battery Count ==========
274
+
275
+ @property
276
+ def battery_count(self) -> int:
277
+ """Get number of batteries in the bank.
278
+
279
+ Returns:
280
+ Number of battery modules.
281
+ """
282
+ if self.data.totalNumber is not None:
283
+ return self.data.totalNumber
284
+ return len(self.data.batteryArray)
285
+
286
+ async def refresh(self) -> None:
287
+ """Refresh battery bank data.
288
+
289
+ Note: Battery bank data is refreshed through the parent inverter.
290
+ This method is a no-op for battery banks.
291
+ """
292
+ # Battery bank data comes from inverter's getBatteryInfo call
293
+ # Individual battery banks don't have their own refresh endpoint
294
+ pass
295
+
296
+ def to_device_info(self) -> DeviceInfo:
297
+ """Convert to device info model.
298
+
299
+ Note: BatteryBank entities are not currently exposed to Home Assistant.
300
+ Aggregate battery data is available through inverter sensors.
301
+ This method is preserved for potential future use.
302
+
303
+ Returns:
304
+ DeviceInfo with battery bank metadata.
305
+ """
306
+ return DeviceInfo(
307
+ identifiers={("pylxpweb", f"battery_bank_{self.inverter_serial}")},
308
+ name=f"Battery Bank ({self.inverter_serial})",
309
+ manufacturer="EG4/Luxpower",
310
+ model=f"Battery Bank ({self.battery_count} modules)",
311
+ via_device=("pylxpweb", f"inverter_{self.inverter_serial}"),
312
+ )
313
+
314
+ def to_entities(self) -> list[Entity]:
315
+ """Generate entities for this battery bank.
316
+
317
+ Note: BatteryBank entities are not currently generated for Home Assistant
318
+ to avoid excessive entity proliferation. Aggregate battery data is available
319
+ through inverter sensors, and individual battery data is available through
320
+ Battery entities.
321
+
322
+ This method is preserved for potential future use if aggregate battery
323
+ entities are needed.
324
+
325
+ Returns:
326
+ Empty list (entities not currently generated).
327
+ """
328
+ # Return empty list - BatteryBank entities not needed for HA integration
329
+ # Aggregate data is accessible via inverter sensors
330
+ # Individual battery data is accessible via Battery entities
331
+ return []
@@ -0,0 +1,32 @@
1
+ """Inverter implementations for different EG4/Luxpower models."""
2
+
3
+ from ._features import (
4
+ DEVICE_TYPE_CODE_TO_FAMILY,
5
+ FAMILY_DEFAULT_FEATURES,
6
+ GridType,
7
+ InverterFamily,
8
+ InverterFeatures,
9
+ InverterModelInfo,
10
+ get_family_features,
11
+ get_inverter_family,
12
+ )
13
+ from .base import BaseInverter
14
+ from .generic import GenericInverter
15
+ from .hybrid import HybridInverter
16
+
17
+ __all__ = [
18
+ # Inverter classes
19
+ "BaseInverter",
20
+ "GenericInverter",
21
+ "HybridInverter",
22
+ # Feature detection
23
+ "InverterFamily",
24
+ "InverterFeatures",
25
+ "InverterModelInfo",
26
+ "GridType",
27
+ # Feature utilities
28
+ "get_inverter_family",
29
+ "get_family_features",
30
+ "DEVICE_TYPE_CODE_TO_FAMILY",
31
+ "FAMILY_DEFAULT_FEATURES",
32
+ ]