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.
- 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 +1427 -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 +364 -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 +708 -41
- pylxpweb/transports/__init__.py +78 -0
- pylxpweb/transports/capabilities.py +101 -0
- pylxpweb/transports/data.py +501 -0
- pylxpweb/transports/exceptions.py +59 -0
- pylxpweb/transports/factory.py +119 -0
- pylxpweb/transports/http.py +329 -0
- pylxpweb/transports/modbus.py +617 -0
- pylxpweb/transports/protocol.py +217 -0
- {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.2.dist-info}/METADATA +130 -85
- pylxpweb-0.5.2.dist-info/RECORD +52 -0
- {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.2.dist-info}/WHEEL +1 -1
- pylxpweb-0.5.2.dist-info/entry_points.txt +3 -0
- pylxpweb-0.1.0.dist-info/RECORD +0 -19
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
"""Data scaling constants and functions for API values.
|
|
2
|
+
|
|
3
|
+
This module contains all scaling factors and helper functions for converting
|
|
4
|
+
raw API values to properly scaled units (volts, amps, watts, etc.).
|
|
5
|
+
|
|
6
|
+
Source: Analysis of EG4 Web Monitor and actual API responses.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Literal
|
|
13
|
+
|
|
14
|
+
# ============================================================================
|
|
15
|
+
# DATA SCALING CONSTANTS
|
|
16
|
+
# ============================================================================
|
|
17
|
+
# Centralized scaling configuration for all API data types.
|
|
18
|
+
# Source: Analysis of EG4 Web Monitor and actual API responses.
|
|
19
|
+
# Reference: docs/claude/PARAMETER_MAPPING_ANALYSIS.md
|
|
20
|
+
#
|
|
21
|
+
# **Design Rationale**:
|
|
22
|
+
# - Use dictionaries for O(1) lookup performance
|
|
23
|
+
# - Group by data source (runtime, energy, battery, etc.)
|
|
24
|
+
# - Include documentation for maintainability
|
|
25
|
+
# - Support both field-based and frozenset-based lookups
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ScaleFactor(int, Enum):
|
|
29
|
+
"""Enumeration of scaling factors used in API data.
|
|
30
|
+
|
|
31
|
+
Values represent the divisor to apply to raw API values.
|
|
32
|
+
Example: SCALE_10 means divide by 10 (e.g., 5300 → 530.0)
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
SCALE_10 = 10 # Divide by 10
|
|
36
|
+
SCALE_100 = 100 # Divide by 100
|
|
37
|
+
SCALE_1000 = 1000 # Divide by 1000
|
|
38
|
+
SCALE_NONE = 1 # No scaling (direct value)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ============================================================================
|
|
42
|
+
# INVERTER RUNTIME DATA SCALING
|
|
43
|
+
# ============================================================================
|
|
44
|
+
# Source: InverterRuntime model from getInverterRuntime endpoint
|
|
45
|
+
# Verified against: research/.../runtime_4512670118.json
|
|
46
|
+
|
|
47
|
+
INVERTER_RUNTIME_SCALING: dict[str, ScaleFactor] = {
|
|
48
|
+
# PV Input Voltages (÷10: 5100 → 510.0V)
|
|
49
|
+
"vpv1": ScaleFactor.SCALE_10,
|
|
50
|
+
"vpv2": ScaleFactor.SCALE_10,
|
|
51
|
+
"vpv3": ScaleFactor.SCALE_10,
|
|
52
|
+
# AC Voltages (÷10: 2411 → 241.1V)
|
|
53
|
+
"vacr": ScaleFactor.SCALE_10,
|
|
54
|
+
"vacs": ScaleFactor.SCALE_10,
|
|
55
|
+
"vact": ScaleFactor.SCALE_10,
|
|
56
|
+
# EPS Voltages (÷10)
|
|
57
|
+
"vepsr": ScaleFactor.SCALE_10,
|
|
58
|
+
"vepss": ScaleFactor.SCALE_10,
|
|
59
|
+
"vepst": ScaleFactor.SCALE_10,
|
|
60
|
+
# Battery Voltage in Runtime (÷10: 530 → 53.0V)
|
|
61
|
+
"vBat": ScaleFactor.SCALE_10,
|
|
62
|
+
# Bus Voltages (÷100: 3703 → 37.03V)
|
|
63
|
+
"vBus1": ScaleFactor.SCALE_100,
|
|
64
|
+
"vBus2": ScaleFactor.SCALE_100,
|
|
65
|
+
# AC Frequency (÷100: 5998 → 59.98Hz)
|
|
66
|
+
"fac": ScaleFactor.SCALE_100,
|
|
67
|
+
"feps": ScaleFactor.SCALE_100,
|
|
68
|
+
# Generator Frequency (÷100)
|
|
69
|
+
"genFreq": ScaleFactor.SCALE_100,
|
|
70
|
+
# Generator Voltage (÷10)
|
|
71
|
+
"genVolt": ScaleFactor.SCALE_10,
|
|
72
|
+
# Currents (÷100: 1500 → 15.00A)
|
|
73
|
+
"maxChgCurr": ScaleFactor.SCALE_100,
|
|
74
|
+
"maxDischgCurr": ScaleFactor.SCALE_100,
|
|
75
|
+
"maxChgCurrValue": ScaleFactor.SCALE_100,
|
|
76
|
+
"maxDischgCurrValue": ScaleFactor.SCALE_100,
|
|
77
|
+
# Power values - NO SCALING (direct Watts)
|
|
78
|
+
"ppv1": ScaleFactor.SCALE_NONE,
|
|
79
|
+
"ppv2": ScaleFactor.SCALE_NONE,
|
|
80
|
+
"ppv3": ScaleFactor.SCALE_NONE,
|
|
81
|
+
"ppv": ScaleFactor.SCALE_NONE,
|
|
82
|
+
"pCharge": ScaleFactor.SCALE_NONE,
|
|
83
|
+
"pDisCharge": ScaleFactor.SCALE_NONE,
|
|
84
|
+
"batPower": ScaleFactor.SCALE_NONE,
|
|
85
|
+
"pToGrid": ScaleFactor.SCALE_NONE,
|
|
86
|
+
"pToUser": ScaleFactor.SCALE_NONE,
|
|
87
|
+
"pinv": ScaleFactor.SCALE_NONE,
|
|
88
|
+
"prec": ScaleFactor.SCALE_NONE,
|
|
89
|
+
"peps": ScaleFactor.SCALE_NONE,
|
|
90
|
+
"acCouplePower": ScaleFactor.SCALE_NONE,
|
|
91
|
+
"genPower": ScaleFactor.SCALE_NONE,
|
|
92
|
+
"consumptionPower114": ScaleFactor.SCALE_NONE,
|
|
93
|
+
"consumptionPower": ScaleFactor.SCALE_NONE,
|
|
94
|
+
"pEpsL1N": ScaleFactor.SCALE_NONE,
|
|
95
|
+
"pEpsL2N": ScaleFactor.SCALE_NONE,
|
|
96
|
+
# Temperature - NO SCALING (direct Celsius)
|
|
97
|
+
"tinner": ScaleFactor.SCALE_NONE,
|
|
98
|
+
"tradiator1": ScaleFactor.SCALE_NONE,
|
|
99
|
+
"tradiator2": ScaleFactor.SCALE_NONE,
|
|
100
|
+
"tBat": ScaleFactor.SCALE_NONE,
|
|
101
|
+
# Percentages - NO SCALING
|
|
102
|
+
"soc": ScaleFactor.SCALE_NONE,
|
|
103
|
+
"seps": ScaleFactor.SCALE_NONE,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ============================================================================
|
|
108
|
+
# ENERGY DATA SCALING
|
|
109
|
+
# ============================================================================
|
|
110
|
+
# Source: EnergyInfo model from getInverterEnergyInfo endpoint
|
|
111
|
+
# All energy values from API are in 0.1 kWh units, need ÷10 to get kWh directly
|
|
112
|
+
# Example: API returns 184 → 184 ÷ 10 = 18.4 kWh
|
|
113
|
+
|
|
114
|
+
ENERGY_INFO_SCALING: dict[str, ScaleFactor] = {
|
|
115
|
+
# Daily Energy (÷10 to get kWh: 184 → 18.4 kWh)
|
|
116
|
+
"todayYielding": ScaleFactor.SCALE_10,
|
|
117
|
+
"todayCharging": ScaleFactor.SCALE_10,
|
|
118
|
+
"todayDischarging": ScaleFactor.SCALE_10,
|
|
119
|
+
"todayGridImport": ScaleFactor.SCALE_10,
|
|
120
|
+
"todayImport": ScaleFactor.SCALE_10, # Alternative field name for todayGridImport
|
|
121
|
+
"todayUsage": ScaleFactor.SCALE_10,
|
|
122
|
+
"todayExport": ScaleFactor.SCALE_10,
|
|
123
|
+
# Monthly Energy (÷10 to get kWh)
|
|
124
|
+
"monthYielding": ScaleFactor.SCALE_10,
|
|
125
|
+
"monthCharging": ScaleFactor.SCALE_10,
|
|
126
|
+
"monthDischarging": ScaleFactor.SCALE_10,
|
|
127
|
+
"monthGridImport": ScaleFactor.SCALE_10,
|
|
128
|
+
"monthImport": ScaleFactor.SCALE_10, # Alternative field name for monthGridImport
|
|
129
|
+
"monthUsage": ScaleFactor.SCALE_10,
|
|
130
|
+
"monthExport": ScaleFactor.SCALE_10,
|
|
131
|
+
# Yearly Energy (÷10 to get kWh)
|
|
132
|
+
"yearYielding": ScaleFactor.SCALE_10,
|
|
133
|
+
"yearCharging": ScaleFactor.SCALE_10,
|
|
134
|
+
"yearDischarging": ScaleFactor.SCALE_10,
|
|
135
|
+
"yearGridImport": ScaleFactor.SCALE_10,
|
|
136
|
+
"yearImport": ScaleFactor.SCALE_10, # Alternative field name for yearGridImport
|
|
137
|
+
"yearUsage": ScaleFactor.SCALE_10,
|
|
138
|
+
"yearExport": ScaleFactor.SCALE_10,
|
|
139
|
+
# Lifetime Total Energy (÷10 to get kWh)
|
|
140
|
+
"totalYielding": ScaleFactor.SCALE_10,
|
|
141
|
+
"totalCharging": ScaleFactor.SCALE_10,
|
|
142
|
+
"totalDischarging": ScaleFactor.SCALE_10,
|
|
143
|
+
"totalGridImport": ScaleFactor.SCALE_10,
|
|
144
|
+
"totalImport": ScaleFactor.SCALE_10, # Alternative field name for totalGridImport
|
|
145
|
+
"totalUsage": ScaleFactor.SCALE_10,
|
|
146
|
+
"totalExport": ScaleFactor.SCALE_10,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ============================================================================
|
|
151
|
+
# BATTERY DATA SCALING
|
|
152
|
+
# ============================================================================
|
|
153
|
+
|
|
154
|
+
# Battery Bank Aggregate (from BatteryInfo header)
|
|
155
|
+
BATTERY_BANK_SCALING: dict[str, ScaleFactor] = {
|
|
156
|
+
# Aggregate voltage (÷10: 530 → 53.0V)
|
|
157
|
+
"vBat": ScaleFactor.SCALE_10,
|
|
158
|
+
# Power - NO SCALING (direct Watts)
|
|
159
|
+
"pCharge": ScaleFactor.SCALE_NONE,
|
|
160
|
+
"pDisCharge": ScaleFactor.SCALE_NONE,
|
|
161
|
+
"batPower": ScaleFactor.SCALE_NONE,
|
|
162
|
+
# Capacity (direct Ah)
|
|
163
|
+
"maxBatteryCharge": ScaleFactor.SCALE_NONE,
|
|
164
|
+
"currentBatteryCharge": ScaleFactor.SCALE_NONE,
|
|
165
|
+
"remainCapacity": ScaleFactor.SCALE_NONE,
|
|
166
|
+
"fullCapacity": ScaleFactor.SCALE_NONE,
|
|
167
|
+
# Percentage - NO SCALING
|
|
168
|
+
"soc": ScaleFactor.SCALE_NONE,
|
|
169
|
+
"capacityPercent": ScaleFactor.SCALE_NONE,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Individual Battery Module (from batteryArray)
|
|
173
|
+
BATTERY_MODULE_SCALING: dict[str, ScaleFactor] = {
|
|
174
|
+
# Total voltage (÷100: 5305 → 53.05V)
|
|
175
|
+
"totalVoltage": ScaleFactor.SCALE_100,
|
|
176
|
+
# Current (÷10: 60 → 6.0A) **CRITICAL: Not ÷100**
|
|
177
|
+
"current": ScaleFactor.SCALE_10,
|
|
178
|
+
# Cell Voltages (÷1000: 3317 → 3.317V - millivolts)
|
|
179
|
+
"batMaxCellVoltage": ScaleFactor.SCALE_1000,
|
|
180
|
+
"batMinCellVoltage": ScaleFactor.SCALE_1000,
|
|
181
|
+
# Cell Temperatures (÷10: 240 → 24.0°C)
|
|
182
|
+
"batMaxCellTemp": ScaleFactor.SCALE_10,
|
|
183
|
+
"batMinCellTemp": ScaleFactor.SCALE_10,
|
|
184
|
+
"ambientTemp": ScaleFactor.SCALE_10,
|
|
185
|
+
"mosTemp": ScaleFactor.SCALE_10,
|
|
186
|
+
# Charge/Discharge Reference Values (÷10, consistent with battery current scaling)
|
|
187
|
+
"batChargeMaxCur": ScaleFactor.SCALE_10, # 2000 → 200.0A
|
|
188
|
+
"batChargeVoltRef": ScaleFactor.SCALE_10, # 560 → 56.0V
|
|
189
|
+
# Percentages - NO SCALING
|
|
190
|
+
"soc": ScaleFactor.SCALE_NONE,
|
|
191
|
+
"soh": ScaleFactor.SCALE_NONE,
|
|
192
|
+
"currentCapacityPercent": ScaleFactor.SCALE_NONE,
|
|
193
|
+
# Capacity (direct Ah)
|
|
194
|
+
"currentRemainCapacity": ScaleFactor.SCALE_NONE,
|
|
195
|
+
"currentFullCapacity": ScaleFactor.SCALE_NONE,
|
|
196
|
+
"maxBatteryCharge": ScaleFactor.SCALE_NONE,
|
|
197
|
+
# Cycle Count - NO SCALING
|
|
198
|
+
"cycleCnt": ScaleFactor.SCALE_NONE,
|
|
199
|
+
# Cell Numbers - NO SCALING (integer indices)
|
|
200
|
+
"batMaxCellNumTemp": ScaleFactor.SCALE_NONE,
|
|
201
|
+
"batMinCellNumTemp": ScaleFactor.SCALE_NONE,
|
|
202
|
+
"batMaxCellNumVolt": ScaleFactor.SCALE_NONE,
|
|
203
|
+
"batMinCellNumVolt": ScaleFactor.SCALE_NONE,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ============================================================================
|
|
208
|
+
# GRIDBOSS (MIDBOX) RUNTIME DATA SCALING
|
|
209
|
+
# ============================================================================
|
|
210
|
+
# Source: MIDBoxRuntime model from getMidboxRuntime endpoint
|
|
211
|
+
# NOTE: GridBOSS has different scaling than standard inverters
|
|
212
|
+
|
|
213
|
+
GRIDBOSS_RUNTIME_SCALING: dict[str, ScaleFactor] = {
|
|
214
|
+
# Voltages (÷10)
|
|
215
|
+
"gridVoltageR": ScaleFactor.SCALE_10,
|
|
216
|
+
"gridVoltageS": ScaleFactor.SCALE_10,
|
|
217
|
+
"gridVoltageT": ScaleFactor.SCALE_10,
|
|
218
|
+
"loadVoltageR": ScaleFactor.SCALE_10,
|
|
219
|
+
"loadVoltageS": ScaleFactor.SCALE_10,
|
|
220
|
+
"loadVoltageT": ScaleFactor.SCALE_10,
|
|
221
|
+
"genVoltageR": ScaleFactor.SCALE_10,
|
|
222
|
+
"genVoltageS": ScaleFactor.SCALE_10,
|
|
223
|
+
"genVoltageT": ScaleFactor.SCALE_10,
|
|
224
|
+
# Currents (÷10: Different from standard inverter!)
|
|
225
|
+
"gridCurrentR": ScaleFactor.SCALE_10,
|
|
226
|
+
"gridCurrentS": ScaleFactor.SCALE_10,
|
|
227
|
+
"gridCurrentT": ScaleFactor.SCALE_10,
|
|
228
|
+
"loadCurrentR": ScaleFactor.SCALE_10,
|
|
229
|
+
"loadCurrentS": ScaleFactor.SCALE_10,
|
|
230
|
+
"loadCurrentT": ScaleFactor.SCALE_10,
|
|
231
|
+
# Frequency (÷100)
|
|
232
|
+
"gridFrequency": ScaleFactor.SCALE_100,
|
|
233
|
+
"loadFrequency": ScaleFactor.SCALE_100,
|
|
234
|
+
"genFrequency": ScaleFactor.SCALE_100,
|
|
235
|
+
# Power - NO SCALING (direct Watts)
|
|
236
|
+
"gridPower": ScaleFactor.SCALE_NONE,
|
|
237
|
+
"loadPower": ScaleFactor.SCALE_NONE,
|
|
238
|
+
"smartLoadPower": ScaleFactor.SCALE_NONE,
|
|
239
|
+
"generatorPower": ScaleFactor.SCALE_NONE,
|
|
240
|
+
# Energy (÷10 for Wh)
|
|
241
|
+
"todayGridEnergy": ScaleFactor.SCALE_10,
|
|
242
|
+
"todayLoadEnergy": ScaleFactor.SCALE_10,
|
|
243
|
+
"totalGridEnergy": ScaleFactor.SCALE_10,
|
|
244
|
+
"totalLoadEnergy": ScaleFactor.SCALE_10,
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# ============================================================================
|
|
249
|
+
# INVERTER OVERVIEW DATA SCALING
|
|
250
|
+
# ============================================================================
|
|
251
|
+
# Source: InverterOverviewItem from inverterOverview/list endpoint
|
|
252
|
+
|
|
253
|
+
INVERTER_OVERVIEW_SCALING: dict[str, ScaleFactor] = {
|
|
254
|
+
# Battery voltage (÷10: 530 → 53.0V)
|
|
255
|
+
"vBat": ScaleFactor.SCALE_10,
|
|
256
|
+
# Power - NO SCALING (direct Watts)
|
|
257
|
+
"ppv": ScaleFactor.SCALE_NONE,
|
|
258
|
+
"pCharge": ScaleFactor.SCALE_NONE,
|
|
259
|
+
"pDisCharge": ScaleFactor.SCALE_NONE,
|
|
260
|
+
"pConsumption": ScaleFactor.SCALE_NONE,
|
|
261
|
+
# Energy totals (÷10 for Wh)
|
|
262
|
+
"totalYielding": ScaleFactor.SCALE_10,
|
|
263
|
+
"totalDischarging": ScaleFactor.SCALE_10,
|
|
264
|
+
"totalExport": ScaleFactor.SCALE_10,
|
|
265
|
+
"totalUsage": ScaleFactor.SCALE_10,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ============================================================================
|
|
270
|
+
# PARAMETER DATA SCALING (Hold Registers)
|
|
271
|
+
# ============================================================================
|
|
272
|
+
# Scaling for parameter values read via remoteRead endpoint
|
|
273
|
+
|
|
274
|
+
PARAMETER_SCALING: dict[str, ScaleFactor] = {
|
|
275
|
+
# Voltage Parameters (÷100)
|
|
276
|
+
"HOLD_BAT_VOLT_MAX_CHG": ScaleFactor.SCALE_100,
|
|
277
|
+
"HOLD_BAT_VOLT_MIN_CHG": ScaleFactor.SCALE_100,
|
|
278
|
+
"HOLD_BAT_VOLT_MAX_DISCHG": ScaleFactor.SCALE_100,
|
|
279
|
+
"HOLD_BAT_VOLT_MIN_DISCHG": ScaleFactor.SCALE_100,
|
|
280
|
+
"HOLD_GRID_VOLT_HIGH_1": ScaleFactor.SCALE_10,
|
|
281
|
+
"HOLD_GRID_VOLT_LOW_1": ScaleFactor.SCALE_10,
|
|
282
|
+
"HOLD_LEAD_ACID_CHARGE_VOLT_REF": ScaleFactor.SCALE_100,
|
|
283
|
+
"HOLD_LEAD_ACID_DISCHARGE_CUT_OFF_VOLT": ScaleFactor.SCALE_100,
|
|
284
|
+
"HOLD_EQUALIZATION_VOLTAGE": ScaleFactor.SCALE_100,
|
|
285
|
+
"HOLD_FLOATING_VOLTAGE": ScaleFactor.SCALE_100,
|
|
286
|
+
"HOLD_EPS_VOLT_SET": ScaleFactor.SCALE_10,
|
|
287
|
+
# Current Parameters (÷10)
|
|
288
|
+
"HOLD_MAX_CHG_CURR": ScaleFactor.SCALE_10,
|
|
289
|
+
"HOLD_MAX_DISCHG_CURR": ScaleFactor.SCALE_10,
|
|
290
|
+
"HOLD_AC_CHARGE_BATTERY_CURRENT": ScaleFactor.SCALE_10,
|
|
291
|
+
"OFF_GRID_HOLD_MAX_GEN_CHG_BAT_CURR": ScaleFactor.SCALE_10,
|
|
292
|
+
# Frequency Parameters (÷100)
|
|
293
|
+
"HOLD_GRID_FREQ_HIGH_1": ScaleFactor.SCALE_100,
|
|
294
|
+
"HOLD_GRID_FREQ_LOW_1": ScaleFactor.SCALE_100,
|
|
295
|
+
"HOLD_EPS_FREQ_SET": ScaleFactor.SCALE_100,
|
|
296
|
+
# Power Parameters (direct Watts or percentage)
|
|
297
|
+
"HOLD_AC_CHARGE_POWER_CMD": ScaleFactor.SCALE_NONE, # Watts
|
|
298
|
+
"HOLD_DISCHG_POWER_CMD": ScaleFactor.SCALE_NONE, # Percentage (0-100)
|
|
299
|
+
"HOLD_FEED_IN_GRID_POWER_PERCENT": ScaleFactor.SCALE_NONE, # Percentage
|
|
300
|
+
# SOC Parameters (percentage, no scaling)
|
|
301
|
+
"HOLD_AC_CHARGE_SOC_LIMIT": ScaleFactor.SCALE_NONE,
|
|
302
|
+
"HOLD_DISCHG_CUT_OFF_SOC_EOD": ScaleFactor.SCALE_NONE,
|
|
303
|
+
"HOLD_SOC_LOW_LIMIT_EPS_DISCHG": ScaleFactor.SCALE_NONE,
|
|
304
|
+
"HOLD_AC_CHARGE_START_BATTERY_SOC": ScaleFactor.SCALE_NONE,
|
|
305
|
+
"HOLD_AC_CHARGE_END_BATTERY_SOC": ScaleFactor.SCALE_NONE,
|
|
306
|
+
# Time Parameters (no scaling - hours/minutes)
|
|
307
|
+
"HOLD_AC_CHARGE_START_HOUR_1": ScaleFactor.SCALE_NONE,
|
|
308
|
+
"HOLD_AC_CHARGE_START_MIN_1": ScaleFactor.SCALE_NONE,
|
|
309
|
+
"HOLD_AC_CHARGE_END_HOUR_1": ScaleFactor.SCALE_NONE,
|
|
310
|
+
"HOLD_AC_CHARGE_END_MIN_1": ScaleFactor.SCALE_NONE,
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ============================================================================
|
|
315
|
+
# SCALING HELPER FUNCTIONS
|
|
316
|
+
# ============================================================================
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def apply_scale(value: int | float, scale_factor: ScaleFactor) -> float:
|
|
320
|
+
"""Apply scaling factor to a value.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
value: Raw value from API
|
|
324
|
+
scale_factor: ScaleFactor enum indicating how to scale
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
Scaled floating-point value
|
|
328
|
+
|
|
329
|
+
Example:
|
|
330
|
+
>>> apply_scale(5300, ScaleFactor.SCALE_10)
|
|
331
|
+
530.0
|
|
332
|
+
>>> apply_scale(3317, ScaleFactor.SCALE_1000)
|
|
333
|
+
3.317
|
|
334
|
+
"""
|
|
335
|
+
if scale_factor == ScaleFactor.SCALE_NONE:
|
|
336
|
+
return float(value)
|
|
337
|
+
return float(value) / float(scale_factor.value)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def get_precision(scale_factor: ScaleFactor) -> int:
|
|
341
|
+
"""Get decimal precision from a scale factor.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
scale_factor: ScaleFactor enum
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Number of decimal places (0 for SCALE_NONE, 1 for SCALE_10, etc.)
|
|
348
|
+
|
|
349
|
+
Example:
|
|
350
|
+
>>> get_precision(ScaleFactor.SCALE_10)
|
|
351
|
+
1
|
|
352
|
+
>>> get_precision(ScaleFactor.SCALE_1000)
|
|
353
|
+
3
|
|
354
|
+
"""
|
|
355
|
+
if scale_factor == ScaleFactor.SCALE_NONE:
|
|
356
|
+
return 0
|
|
357
|
+
# log10 of scale factor gives decimal places
|
|
358
|
+
import math
|
|
359
|
+
|
|
360
|
+
return int(math.log10(scale_factor.value))
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def get_battery_field_precision(field_name: str) -> int:
|
|
364
|
+
"""Get decimal precision for a battery module field.
|
|
365
|
+
|
|
366
|
+
Args:
|
|
367
|
+
field_name: Field name from BatteryModule model
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
Number of decimal places for that field
|
|
371
|
+
|
|
372
|
+
Example:
|
|
373
|
+
>>> get_battery_field_precision("batMaxCellVoltage")
|
|
374
|
+
3
|
|
375
|
+
>>> get_battery_field_precision("current")
|
|
376
|
+
1
|
|
377
|
+
"""
|
|
378
|
+
if field_name not in BATTERY_MODULE_SCALING:
|
|
379
|
+
return 0
|
|
380
|
+
return get_precision(BATTERY_MODULE_SCALING[field_name])
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _get_scaling_for_field(
|
|
384
|
+
field_name: str,
|
|
385
|
+
data_type: Literal[
|
|
386
|
+
"runtime", "energy", "battery_bank", "battery_module", "gridboss", "overview", "parameter"
|
|
387
|
+
],
|
|
388
|
+
) -> ScaleFactor:
|
|
389
|
+
"""Get the appropriate scaling factor for a field.
|
|
390
|
+
|
|
391
|
+
This is an internal function. External users should use the data type-specific
|
|
392
|
+
convenience functions instead (e.g., scale_runtime_value, scale_battery_value).
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
field_name: Name of the field (e.g., "vpv1", "totalVoltage")
|
|
396
|
+
data_type: Type of data source
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
ScaleFactor enum indicating how to scale the value
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
KeyError: If field_name not found in the specified data type
|
|
403
|
+
|
|
404
|
+
Example:
|
|
405
|
+
>>> scale = _get_scaling_for_field("vpv1", "runtime")
|
|
406
|
+
>>> apply_scale(5100, scale)
|
|
407
|
+
510.0
|
|
408
|
+
"""
|
|
409
|
+
scaling_map = {
|
|
410
|
+
"runtime": INVERTER_RUNTIME_SCALING,
|
|
411
|
+
"energy": ENERGY_INFO_SCALING,
|
|
412
|
+
"battery_bank": BATTERY_BANK_SCALING,
|
|
413
|
+
"battery_module": BATTERY_MODULE_SCALING,
|
|
414
|
+
"gridboss": GRIDBOSS_RUNTIME_SCALING,
|
|
415
|
+
"overview": INVERTER_OVERVIEW_SCALING,
|
|
416
|
+
"parameter": PARAMETER_SCALING,
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return scaling_map[data_type][field_name]
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def scale_runtime_value(field_name: str, value: int | float) -> float:
|
|
423
|
+
"""Convenience function to scale inverter runtime values.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
field_name: Field name from InverterRuntime model
|
|
427
|
+
value: Raw API value
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
Scaled value
|
|
431
|
+
"""
|
|
432
|
+
if field_name not in INVERTER_RUNTIME_SCALING:
|
|
433
|
+
# Field doesn't need scaling (or unknown field)
|
|
434
|
+
return float(value)
|
|
435
|
+
return apply_scale(value, INVERTER_RUNTIME_SCALING[field_name])
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def scale_battery_value(field_name: str, value: int | float) -> float:
|
|
439
|
+
"""Convenience function to scale battery module values.
|
|
440
|
+
|
|
441
|
+
Args:
|
|
442
|
+
field_name: Field name from BatteryModule model
|
|
443
|
+
value: Raw API value
|
|
444
|
+
|
|
445
|
+
Returns:
|
|
446
|
+
Scaled value
|
|
447
|
+
"""
|
|
448
|
+
if field_name not in BATTERY_MODULE_SCALING:
|
|
449
|
+
return float(value)
|
|
450
|
+
return apply_scale(value, BATTERY_MODULE_SCALING[field_name])
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def scale_energy_value(field_name: str, value: int | float, to_kwh: bool = True) -> float:
|
|
454
|
+
"""Convenience function to scale energy values.
|
|
455
|
+
|
|
456
|
+
Args:
|
|
457
|
+
field_name: Field name from EnergyInfo model
|
|
458
|
+
value: Raw API value (in 0.1 kWh units)
|
|
459
|
+
to_kwh: If True, return kWh; if False, return Wh
|
|
460
|
+
|
|
461
|
+
Returns:
|
|
462
|
+
Scaled value in kWh (if to_kwh=True) or Wh
|
|
463
|
+
|
|
464
|
+
Example:
|
|
465
|
+
>>> scale_energy_value("todayYielding", 184, to_kwh=True)
|
|
466
|
+
18.4 # kWh
|
|
467
|
+
>>> scale_energy_value("todayYielding", 184, to_kwh=False)
|
|
468
|
+
18400.0 # Wh
|
|
469
|
+
"""
|
|
470
|
+
if field_name not in ENERGY_INFO_SCALING:
|
|
471
|
+
return float(value)
|
|
472
|
+
|
|
473
|
+
# Apply API scaling (÷10 to get kWh directly - API uses 0.1 kWh units)
|
|
474
|
+
kwh_value = apply_scale(value, ENERGY_INFO_SCALING[field_name])
|
|
475
|
+
|
|
476
|
+
# Convert to Wh if requested
|
|
477
|
+
if not to_kwh:
|
|
478
|
+
return kwh_value * 1000.0
|
|
479
|
+
return kwh_value
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Device hierarchy for pylxpweb.
|
|
2
|
+
|
|
3
|
+
This module provides object-oriented access to stations, inverters,
|
|
4
|
+
batteries, and MID devices.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .base import BaseDevice
|
|
8
|
+
from .battery import Battery
|
|
9
|
+
from .battery_bank import BatteryBank
|
|
10
|
+
from .inverters import BaseInverter, GenericInverter, HybridInverter
|
|
11
|
+
from .mid_device import MIDDevice
|
|
12
|
+
from .models import DeviceClass, DeviceInfo, Entity, EntityCategory, StateClass
|
|
13
|
+
from .parallel_group import ParallelGroup
|
|
14
|
+
from .station import Location, Station
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"BaseDevice",
|
|
18
|
+
"Battery",
|
|
19
|
+
"BatteryBank",
|
|
20
|
+
"BaseInverter",
|
|
21
|
+
"GenericInverter",
|
|
22
|
+
"HybridInverter",
|
|
23
|
+
"MIDDevice",
|
|
24
|
+
"DeviceInfo",
|
|
25
|
+
"Entity",
|
|
26
|
+
"DeviceClass",
|
|
27
|
+
"StateClass",
|
|
28
|
+
"EntityCategory",
|
|
29
|
+
"Location",
|
|
30
|
+
"Station",
|
|
31
|
+
"ParallelGroup",
|
|
32
|
+
]
|