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,596 @@
1
+ """Runtime properties mixin for BaseInverter.
2
+
3
+ This mixin provides properly-scaled property accessors for all runtime
4
+ sensor data from the inverter. All properties return typed, scaled values
5
+ with graceful None handling.
6
+
7
+ Properties are organized by category:
8
+ - PV (Solar Panel) Properties
9
+ - AC Grid Properties
10
+ - EPS (Emergency Power Supply) Properties
11
+ - Power Flow Properties
12
+ - Battery Properties
13
+ - Temperature Properties
14
+ - Bus Voltage Properties
15
+ - AC Couple & Generator Properties
16
+ - Consumption Properties
17
+ - Status & Info Properties
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from typing import TYPE_CHECKING
23
+
24
+ from pylxpweb.constants import scale_runtime_value
25
+
26
+ if TYPE_CHECKING:
27
+ from pylxpweb.models import InverterRuntime
28
+
29
+
30
+ class InverterRuntimePropertiesMixin:
31
+ """Mixin providing runtime property accessors for inverters."""
32
+
33
+ _runtime: InverterRuntime | None
34
+
35
+ # ===========================================
36
+ # PV (Solar Panel) Properties
37
+ # ===========================================
38
+
39
+ @property
40
+ def pv1_voltage(self) -> float:
41
+ """Get PV string 1 voltage in volts.
42
+
43
+ Returns:
44
+ PV1 voltage (÷10), or 0.0 if no data.
45
+ """
46
+ if self._runtime is None:
47
+ return 0.0
48
+ return scale_runtime_value("vpv1", self._runtime.vpv1)
49
+
50
+ @property
51
+ def pv2_voltage(self) -> float:
52
+ """Get PV string 2 voltage in volts.
53
+
54
+ Returns:
55
+ PV2 voltage (÷10), or 0.0 if no data.
56
+ """
57
+ if self._runtime is None:
58
+ return 0.0
59
+
60
+ return scale_runtime_value("vpv2", self._runtime.vpv2)
61
+
62
+ @property
63
+ def pv3_voltage(self) -> float:
64
+ """Get PV string 3 voltage in volts (if available).
65
+
66
+ Returns:
67
+ PV3 voltage (÷10), or 0.0 if no data or not supported.
68
+ """
69
+ if self._runtime is None or self._runtime.vpv3 is None:
70
+ return 0.0
71
+
72
+ return scale_runtime_value("vpv3", self._runtime.vpv3)
73
+
74
+ @property
75
+ def pv1_power(self) -> int:
76
+ """Get PV string 1 power in watts.
77
+
78
+ Returns:
79
+ PV1 power in watts, or 0 if no data.
80
+ """
81
+ if self._runtime is None:
82
+ return 0
83
+ return self._runtime.ppv1
84
+
85
+ @property
86
+ def pv2_power(self) -> int:
87
+ """Get PV string 2 power in watts.
88
+
89
+ Returns:
90
+ PV2 power in watts, or 0 if no data.
91
+ """
92
+ if self._runtime is None:
93
+ return 0
94
+ return self._runtime.ppv2
95
+
96
+ @property
97
+ def pv3_power(self) -> int:
98
+ """Get PV string 3 power in watts (if available).
99
+
100
+ Returns:
101
+ PV3 power in watts, or 0 if no data or not supported.
102
+ """
103
+ if self._runtime is None or self._runtime.ppv3 is None:
104
+ return 0
105
+ return self._runtime.ppv3
106
+
107
+ @property
108
+ def pv_total_power(self) -> int:
109
+ """Get total PV power from all strings in watts.
110
+
111
+ Returns:
112
+ Total PV power in watts, or 0 if no data.
113
+ """
114
+ if self._runtime is None:
115
+ return 0
116
+ return self._runtime.ppv
117
+
118
+ # ===========================================
119
+ # AC Grid Properties
120
+ # ===========================================
121
+
122
+ @property
123
+ def grid_voltage_r(self) -> float:
124
+ """Get grid AC voltage phase R in volts.
125
+
126
+ Returns:
127
+ AC grid voltage R phase (÷10), or 0.0 if no data.
128
+ """
129
+ if self._runtime is None:
130
+ return 0.0
131
+
132
+ return scale_runtime_value("vacr", self._runtime.vacr)
133
+
134
+ @property
135
+ def grid_voltage_s(self) -> float:
136
+ """Get grid AC voltage phase S in volts.
137
+
138
+ Returns:
139
+ AC grid voltage S phase (÷10), or 0.0 if no data.
140
+ """
141
+ if self._runtime is None:
142
+ return 0.0
143
+
144
+ return scale_runtime_value("vacs", self._runtime.vacs)
145
+
146
+ @property
147
+ def grid_voltage_t(self) -> float:
148
+ """Get grid AC voltage phase T in volts.
149
+
150
+ Returns:
151
+ AC grid voltage T phase (÷10), or 0.0 if no data.
152
+ """
153
+ if self._runtime is None:
154
+ return 0.0
155
+
156
+ return scale_runtime_value("vact", self._runtime.vact)
157
+
158
+ @property
159
+ def grid_frequency(self) -> float:
160
+ """Get grid AC frequency in Hz.
161
+
162
+ Returns:
163
+ Grid frequency (÷100), or 0.0 if no data.
164
+ """
165
+ if self._runtime is None:
166
+ return 0.0
167
+
168
+ return scale_runtime_value("fac", self._runtime.fac)
169
+
170
+ @property
171
+ def power_factor(self) -> str:
172
+ """Get power factor.
173
+
174
+ Returns:
175
+ Power factor as string, or empty string if no data.
176
+ """
177
+ if self._runtime is None:
178
+ return ""
179
+ return self._runtime.pf
180
+
181
+ # ===========================================
182
+ # EPS (Emergency Power Supply) Properties
183
+ # ===========================================
184
+
185
+ @property
186
+ def eps_voltage_r(self) -> float:
187
+ """Get EPS voltage phase R in volts.
188
+
189
+ Returns:
190
+ EPS voltage R phase (÷10), or 0.0 if no data.
191
+ """
192
+ if self._runtime is None:
193
+ return 0.0
194
+
195
+ return scale_runtime_value("vepsr", self._runtime.vepsr)
196
+
197
+ @property
198
+ def eps_voltage_s(self) -> float:
199
+ """Get EPS voltage phase S in volts.
200
+
201
+ Returns:
202
+ EPS voltage S phase (÷10), or 0.0 if no data.
203
+ """
204
+ if self._runtime is None:
205
+ return 0.0
206
+
207
+ return scale_runtime_value("vepss", self._runtime.vepss)
208
+
209
+ @property
210
+ def eps_voltage_t(self) -> float:
211
+ """Get EPS voltage phase T in volts.
212
+
213
+ Returns:
214
+ EPS voltage T phase (÷10), or 0.0 if no data.
215
+ """
216
+ if self._runtime is None:
217
+ return 0.0
218
+
219
+ return scale_runtime_value("vepst", self._runtime.vepst)
220
+
221
+ @property
222
+ def eps_frequency(self) -> float:
223
+ """Get EPS frequency in Hz.
224
+
225
+ Returns:
226
+ EPS frequency (÷100), or 0.0 if no data.
227
+ """
228
+ if self._runtime is None:
229
+ return 0.0
230
+
231
+ return scale_runtime_value("feps", self._runtime.feps)
232
+
233
+ @property
234
+ def eps_power(self) -> int:
235
+ """Get EPS power in watts.
236
+
237
+ Returns:
238
+ EPS power in watts, or 0 if no data.
239
+ """
240
+ if self._runtime is None:
241
+ return 0
242
+ return self._runtime.peps
243
+
244
+ @property
245
+ def eps_power_l1(self) -> int:
246
+ """Get EPS L1 power in watts.
247
+
248
+ Returns:
249
+ EPS L1 power in watts, or 0 if no data.
250
+ """
251
+ if self._runtime is None:
252
+ return 0
253
+ return self._runtime.pEpsL1N
254
+
255
+ @property
256
+ def eps_power_l2(self) -> int:
257
+ """Get EPS L2 power in watts.
258
+
259
+ Returns:
260
+ EPS L2 power in watts, or 0 if no data.
261
+ """
262
+ if self._runtime is None:
263
+ return 0
264
+ return self._runtime.pEpsL2N
265
+
266
+ # ===========================================
267
+ # Power Flow Properties
268
+ # ===========================================
269
+
270
+ @property
271
+ def power_to_grid(self) -> int:
272
+ """Get power flowing to grid in watts.
273
+
274
+ Returns:
275
+ Power to grid in watts, or 0 if no data.
276
+ """
277
+ if self._runtime is None:
278
+ return 0
279
+ return self._runtime.pToGrid
280
+
281
+ @property
282
+ def power_to_user(self) -> int:
283
+ """Get power flowing to user loads in watts.
284
+
285
+ Returns:
286
+ Power to user in watts, or 0 if no data.
287
+ """
288
+ if self._runtime is None:
289
+ return 0
290
+ return self._runtime.pToUser
291
+
292
+ @property
293
+ def inverter_power(self) -> int:
294
+ """Get inverter power in watts.
295
+
296
+ Returns:
297
+ Inverter power in watts, or 0 if no data.
298
+ """
299
+ if self._runtime is None:
300
+ return 0
301
+ return self._runtime.pinv
302
+
303
+ @property
304
+ def rectifier_power(self) -> int:
305
+ """Get rectifier power in watts.
306
+
307
+ Returns:
308
+ Rectifier power in watts, or 0 if no data.
309
+ """
310
+ if self._runtime is None:
311
+ return 0
312
+ return self._runtime.prec
313
+
314
+ # ===========================================
315
+ # Battery Properties
316
+ # ===========================================
317
+
318
+ @property
319
+ def battery_voltage(self) -> float:
320
+ """Get battery voltage in volts.
321
+
322
+ Returns:
323
+ Battery voltage (÷10), or 0.0 if no data.
324
+ """
325
+ if self._runtime is None:
326
+ return 0.0
327
+
328
+ return scale_runtime_value("vBat", self._runtime.vBat)
329
+
330
+ @property
331
+ def battery_charge_power(self) -> int:
332
+ """Get battery charging power in watts.
333
+
334
+ Returns:
335
+ Battery charge power in watts, or 0 if no data.
336
+ """
337
+ if self._runtime is None:
338
+ return 0
339
+ return self._runtime.pCharge
340
+
341
+ @property
342
+ def battery_discharge_power(self) -> int:
343
+ """Get battery discharging power in watts.
344
+
345
+ Returns:
346
+ Battery discharge power in watts, or 0 if no data.
347
+ """
348
+ if self._runtime is None:
349
+ return 0
350
+ return self._runtime.pDisCharge
351
+
352
+ @property
353
+ def battery_power(self) -> int:
354
+ """Get net battery power in watts (positive = charging, negative = discharging).
355
+
356
+ Returns:
357
+ Battery power in watts, or 0 if no data.
358
+ """
359
+ if self._runtime is None:
360
+ return 0
361
+ return self._runtime.batPower
362
+
363
+ @property
364
+ def battery_temperature(self) -> int:
365
+ """Get battery temperature in Celsius.
366
+
367
+ Returns:
368
+ Battery temperature in °C, or 0 if no data.
369
+ """
370
+ if self._runtime is None:
371
+ return 0
372
+ return self._runtime.tBat
373
+
374
+ @property
375
+ def max_charge_current(self) -> float:
376
+ """Get maximum charge current in amps.
377
+
378
+ Returns:
379
+ Max charge current (÷100), or 0.0 if no data.
380
+ """
381
+ if self._runtime is None:
382
+ return 0.0
383
+
384
+ return scale_runtime_value("maxChgCurr", self._runtime.maxChgCurr)
385
+
386
+ @property
387
+ def max_discharge_current(self) -> float:
388
+ """Get maximum discharge current in amps.
389
+
390
+ Returns:
391
+ Max discharge current (÷100), or 0.0 if no data.
392
+ """
393
+ if self._runtime is None:
394
+ return 0.0
395
+
396
+ return scale_runtime_value("maxDischgCurr", self._runtime.maxDischgCurr)
397
+
398
+ # ===========================================
399
+ # Temperature Properties
400
+ # ===========================================
401
+
402
+ @property
403
+ def inverter_temperature(self) -> int:
404
+ """Get inverter internal temperature in Celsius.
405
+
406
+ Returns:
407
+ Internal temperature in °C, or 0 if no data.
408
+ """
409
+ if self._runtime is None:
410
+ return 0
411
+ return self._runtime.tinner
412
+
413
+ @property
414
+ def radiator1_temperature(self) -> int:
415
+ """Get radiator 1 temperature in Celsius.
416
+
417
+ Returns:
418
+ Radiator 1 temperature in °C, or 0 if no data.
419
+ """
420
+ if self._runtime is None:
421
+ return 0
422
+ return self._runtime.tradiator1
423
+
424
+ @property
425
+ def radiator2_temperature(self) -> int:
426
+ """Get radiator 2 temperature in Celsius.
427
+
428
+ Returns:
429
+ Radiator 2 temperature in °C, or 0 if no data.
430
+ """
431
+ if self._runtime is None:
432
+ return 0
433
+ return self._runtime.tradiator2
434
+
435
+ # ===========================================
436
+ # Bus Voltage Properties
437
+ # ===========================================
438
+
439
+ @property
440
+ def bus1_voltage(self) -> float:
441
+ """Get bus 1 voltage in volts.
442
+
443
+ Returns:
444
+ Bus 1 voltage (÷100), or 0.0 if no data.
445
+ """
446
+ if self._runtime is None:
447
+ return 0.0
448
+
449
+ return scale_runtime_value("vBus1", self._runtime.vBus1)
450
+
451
+ @property
452
+ def bus2_voltage(self) -> float:
453
+ """Get bus 2 voltage in volts.
454
+
455
+ Returns:
456
+ Bus 2 voltage (÷100), or 0.0 if no data.
457
+ """
458
+ if self._runtime is None:
459
+ return 0.0
460
+
461
+ return scale_runtime_value("vBus2", self._runtime.vBus2)
462
+
463
+ # ===========================================
464
+ # AC Couple & Generator Properties
465
+ # ===========================================
466
+
467
+ @property
468
+ def ac_couple_power(self) -> int:
469
+ """Get AC coupled power in watts.
470
+
471
+ Returns:
472
+ AC couple power in watts, or 0 if no data.
473
+ """
474
+ if self._runtime is None:
475
+ return 0
476
+ return self._runtime.acCouplePower
477
+
478
+ @property
479
+ def generator_voltage(self) -> float:
480
+ """Get generator voltage in volts.
481
+
482
+ Returns:
483
+ Generator voltage (÷10), or 0.0 if no data.
484
+ """
485
+ if self._runtime is None:
486
+ return 0.0
487
+
488
+ return scale_runtime_value("genVolt", self._runtime.genVolt)
489
+
490
+ @property
491
+ def generator_frequency(self) -> float:
492
+ """Get generator frequency in Hz.
493
+
494
+ Returns:
495
+ Generator frequency (÷100), or 0.0 if no data.
496
+ """
497
+ if self._runtime is None:
498
+ return 0.0
499
+
500
+ return scale_runtime_value("genFreq", self._runtime.genFreq)
501
+
502
+ @property
503
+ def generator_power(self) -> int:
504
+ """Get generator power in watts.
505
+
506
+ Returns:
507
+ Generator power in watts, or 0 if no data.
508
+ """
509
+ if self._runtime is None:
510
+ return 0
511
+ return self._runtime.genPower
512
+
513
+ @property
514
+ def is_using_generator(self) -> bool:
515
+ """Check if generator is currently in use.
516
+
517
+ Returns:
518
+ True if using generator, False otherwise.
519
+ """
520
+ if self._runtime is None:
521
+ return False
522
+ return self._runtime._12KUsingGenerator
523
+
524
+ # ===========================================
525
+ # Consumption Properties
526
+ # ===========================================
527
+
528
+ @property
529
+ def consumption_power(self) -> int:
530
+ """Get consumption power in watts.
531
+
532
+ Returns:
533
+ Consumption power in watts, or 0 if no data.
534
+ """
535
+ if self._runtime is None:
536
+ return 0
537
+ return self._runtime.consumptionPower
538
+
539
+ # ===========================================
540
+ # Status & Info Properties
541
+ # ===========================================
542
+
543
+ @property
544
+ def firmware_version(self) -> str:
545
+ """Get firmware version.
546
+
547
+ Returns:
548
+ Firmware version string, or empty string if no data.
549
+ """
550
+ if self._runtime is None:
551
+ return ""
552
+ return self._runtime.fwCode
553
+
554
+ @property
555
+ def status(self) -> int:
556
+ """Get inverter status code.
557
+
558
+ Returns:
559
+ Status code, or 0 if no data.
560
+ """
561
+ if self._runtime is None:
562
+ return 0
563
+ return self._runtime.status
564
+
565
+ @property
566
+ def status_text(self) -> str:
567
+ """Get inverter status as text.
568
+
569
+ Returns:
570
+ Status text, or empty string if no data.
571
+ """
572
+ if self._runtime is None:
573
+ return ""
574
+ return self._runtime.statusText
575
+
576
+ @property
577
+ def is_lost(self) -> bool:
578
+ """Check if inverter connection is lost.
579
+
580
+ Returns:
581
+ True if connection lost, False otherwise.
582
+ """
583
+ if self._runtime is None:
584
+ return True # No data means lost
585
+ return self._runtime.lost
586
+
587
+ @property
588
+ def power_rating(self) -> str:
589
+ """Get power rating text (e.g., "16kW").
590
+
591
+ Returns:
592
+ Power rating string, or empty string if no data.
593
+ """
594
+ if self._runtime is None:
595
+ return ""
596
+ return self._runtime.powerRatingText