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.
- 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 +545 -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 +351 -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 +629 -40
- pylxpweb/transports/__init__.py +78 -0
- pylxpweb/transports/capabilities.py +101 -0
- pylxpweb/transports/data.py +495 -0
- pylxpweb/transports/exceptions.py +59 -0
- pylxpweb/transports/factory.py +119 -0
- pylxpweb/transports/http.py +329 -0
- pylxpweb/transports/modbus.py +557 -0
- pylxpweb/transports/protocol.py +217 -0
- {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.0.dist-info}/METADATA +130 -85
- pylxpweb-0.5.0.dist-info/RECORD +52 -0
- {pylxpweb-0.1.0.dist-info → pylxpweb-0.5.0.dist-info}/WHEEL +1 -1
- pylxpweb-0.5.0.dist-info/entry_points.txt +3 -0
- pylxpweb-0.1.0.dist-info/RECORD +0 -19
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Constants and mappings for Luxpower/EG4 API.
|
|
2
|
+
|
|
3
|
+
This module contains mapping tables extracted from the EG4 web interface
|
|
4
|
+
to convert between human-readable API values and the enum values required
|
|
5
|
+
for configuration updates.
|
|
6
|
+
|
|
7
|
+
These mappings were discovered by analyzing the HTML form at:
|
|
8
|
+
/WManage/web/config/plant/edit/{plant_id}
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
# Timezone mappings: Human-readable (from API) → Form enum (for POST)
|
|
14
|
+
# Source: Analyzed all 28 timezone options from the HTML form
|
|
15
|
+
TIMEZONE_MAP: dict[str, str] = {
|
|
16
|
+
"GMT -12": "WEST12",
|
|
17
|
+
"GMT -11": "WEST11",
|
|
18
|
+
"GMT -10": "WEST10",
|
|
19
|
+
"GMT -9": "WEST9",
|
|
20
|
+
"GMT -8": "WEST8",
|
|
21
|
+
"GMT -7": "WEST7",
|
|
22
|
+
"GMT -6": "WEST6",
|
|
23
|
+
"GMT -5": "WEST5",
|
|
24
|
+
"GMT -4": "WEST4",
|
|
25
|
+
"GMT -3": "WEST3",
|
|
26
|
+
"GMT -2": "WEST2",
|
|
27
|
+
"GMT -1": "WEST1",
|
|
28
|
+
"GMT 0": "ZERO",
|
|
29
|
+
"GMT +1": "EAST1",
|
|
30
|
+
"GMT +2": "EAST2",
|
|
31
|
+
"GMT +3": "EAST3",
|
|
32
|
+
"GMT +3:30": "EAST3_30",
|
|
33
|
+
"GMT +4": "EAST4",
|
|
34
|
+
"GMT +5": "EAST5",
|
|
35
|
+
"GMT +5:30": "EAST5_30",
|
|
36
|
+
"GMT +6": "EAST6",
|
|
37
|
+
"GMT +6:30": "EAST6_30",
|
|
38
|
+
"GMT +7": "EAST7",
|
|
39
|
+
"GMT +8": "EAST8",
|
|
40
|
+
"GMT +9": "EAST9",
|
|
41
|
+
"GMT +10": "EAST10",
|
|
42
|
+
"GMT +11": "EAST11",
|
|
43
|
+
"GMT +12": "EAST12",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Reverse mapping: Form enum → Human-readable
|
|
47
|
+
TIMEZONE_REVERSE_MAP: dict[str, str] = {v: k for k, v in TIMEZONE_MAP.items()}
|
|
48
|
+
|
|
49
|
+
# Country mappings: Human-readable (from API) → Form enum (for POST)
|
|
50
|
+
# Source: Analyzed country options from HTML form (North America region shown)
|
|
51
|
+
# NOTE: This list is incomplete - only shows North American countries
|
|
52
|
+
# Additional countries would appear based on selected continent/region
|
|
53
|
+
COUNTRY_MAP: dict[str, str] = {
|
|
54
|
+
"Canada": "CANADA",
|
|
55
|
+
"United States of America": "UNITED_STATES_OF_AMERICA",
|
|
56
|
+
"Mexico": "MEXICO",
|
|
57
|
+
"Greenland": "GREENLAND",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Reverse mapping: Form enum → Human-readable
|
|
61
|
+
COUNTRY_REVERSE_MAP: dict[str, str] = {v: k for k, v in COUNTRY_MAP.items()}
|
|
62
|
+
|
|
63
|
+
# Continent mappings: Human-readable → Form enum
|
|
64
|
+
# Source: All 6 continent options from HTML form
|
|
65
|
+
CONTINENT_MAP: dict[str, str] = {
|
|
66
|
+
"Africa": "AFRICA",
|
|
67
|
+
"Asia": "ASIA",
|
|
68
|
+
"Europe": "EUROPE",
|
|
69
|
+
"North America": "NORTH_AMERICA",
|
|
70
|
+
"Oceania": "OCEANIA",
|
|
71
|
+
"South America": "SOUTH_AMERICA",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
CONTINENT_REVERSE_MAP: dict[str, str] = {v: k for k, v in CONTINENT_MAP.items()}
|
|
75
|
+
|
|
76
|
+
# Region mappings: Human-readable → Form enum
|
|
77
|
+
# Source: Region options from HTML form (context: North America continent)
|
|
78
|
+
# NOTE: Region options are hierarchical and depend on selected continent
|
|
79
|
+
REGION_MAP: dict[str, str] = {
|
|
80
|
+
# North America regions (when continent = NORTH_AMERICA)
|
|
81
|
+
"Caribbean": "CARIBBEAN",
|
|
82
|
+
"Central America": "CENTRAL_AMERICA",
|
|
83
|
+
"North America": "NORTH_AMERICA",
|
|
84
|
+
# Additional regions would be discovered when exploring other continents
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
REGION_REVERSE_MAP: dict[str, str] = {v: k for k, v in REGION_MAP.items()}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_timezone_enum(human_readable: str) -> str:
|
|
91
|
+
"""Convert human-readable timezone to API enum.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
human_readable: Timezone string like "GMT -8"
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
API enum like "WEST8"
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: If timezone is not recognized
|
|
101
|
+
"""
|
|
102
|
+
if human_readable in TIMEZONE_MAP:
|
|
103
|
+
return TIMEZONE_MAP[human_readable]
|
|
104
|
+
raise ValueError(f"Unknown timezone: {human_readable}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_country_enum(human_readable: str) -> str:
|
|
108
|
+
"""Convert human-readable country to API enum.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
human_readable: Country string like "United States of America"
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
API enum like "UNITED_STATES_OF_AMERICA"
|
|
115
|
+
|
|
116
|
+
Raises:
|
|
117
|
+
ValueError: If country is not recognized
|
|
118
|
+
"""
|
|
119
|
+
if human_readable in COUNTRY_MAP:
|
|
120
|
+
return COUNTRY_MAP[human_readable]
|
|
121
|
+
raise ValueError(f"Unknown country: {human_readable}")
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_region_enum(human_readable: str) -> str:
|
|
125
|
+
"""Convert human-readable region to API enum.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
human_readable: Region string like "North America"
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
API enum like "NORTH_AMERICA"
|
|
132
|
+
|
|
133
|
+
Raises:
|
|
134
|
+
ValueError: If region is not recognized
|
|
135
|
+
"""
|
|
136
|
+
if human_readable in REGION_MAP:
|
|
137
|
+
return REGION_MAP[human_readable]
|
|
138
|
+
raise ValueError(f"Unknown region: {human_readable}")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def get_continent_enum(human_readable: str) -> str:
|
|
142
|
+
"""Convert human-readable continent to API enum.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
human_readable: Continent string like "North America"
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
API enum like "NORTH_AMERICA"
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
ValueError: If continent is not recognized
|
|
152
|
+
"""
|
|
153
|
+
if human_readable in CONTINENT_MAP:
|
|
154
|
+
return CONTINENT_MAP[human_readable]
|
|
155
|
+
raise ValueError(f"Unknown continent: {human_readable}")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# Static mapping for common countries (fast path)
|
|
159
|
+
# This covers the most frequently used countries to avoid API calls
|
|
160
|
+
COUNTRY_TO_LOCATION_STATIC: dict[str, tuple[str, str]] = {
|
|
161
|
+
# North America
|
|
162
|
+
"United States of America": ("NORTH_AMERICA", "NORTH_AMERICA"),
|
|
163
|
+
"Canada": ("NORTH_AMERICA", "NORTH_AMERICA"),
|
|
164
|
+
"Mexico": ("NORTH_AMERICA", "CENTRAL_AMERICA"),
|
|
165
|
+
"Greenland": ("NORTH_AMERICA", "NORTH_AMERICA"),
|
|
166
|
+
# Europe (common)
|
|
167
|
+
"United Kingdom": ("EUROPE", "WESTERN_EUROPE"),
|
|
168
|
+
"Germany": ("EUROPE", "CENTRAL_EUROPE"),
|
|
169
|
+
"France": ("EUROPE", "WESTERN_EUROPE"),
|
|
170
|
+
"Spain": ("EUROPE", "SOUTHERN_EUROPE"),
|
|
171
|
+
"Italy": ("EUROPE", "SOUTHERN_EUROPE"),
|
|
172
|
+
"The Netherlands": ("EUROPE", "WESTERN_EUROPE"),
|
|
173
|
+
"Belgium": ("EUROPE", "WESTERN_EUROPE"),
|
|
174
|
+
"Switzerland": ("EUROPE", "CENTRAL_EUROPE"),
|
|
175
|
+
"Austria": ("EUROPE", "CENTRAL_EUROPE"),
|
|
176
|
+
"Poland": ("EUROPE", "CENTRAL_EUROPE"),
|
|
177
|
+
"Sweden": ("EUROPE", "NORDIC_EUROPE"),
|
|
178
|
+
"Norway": ("EUROPE", "NORDIC_EUROPE"),
|
|
179
|
+
"Denmark": ("EUROPE", "NORDIC_EUROPE"),
|
|
180
|
+
# Asia (common)
|
|
181
|
+
"China": ("ASIA", "EAST_ASIA"),
|
|
182
|
+
"Japan": ("ASIA", "EAST_ASIA"),
|
|
183
|
+
"South korea": ("ASIA", "EAST_ASIA"),
|
|
184
|
+
"India": ("ASIA", "SOUTH_ASIA"),
|
|
185
|
+
"Singapore": ("ASIA", "SOUTHEAST_ASIA"),
|
|
186
|
+
"Thailand": ("ASIA", "SOUTHEAST_ASIA"),
|
|
187
|
+
"Malaysia": ("ASIA", "SOUTHEAST_ASIA"),
|
|
188
|
+
"Indonesia": ("ASIA", "SOUTHEAST_ASIA"),
|
|
189
|
+
"Philippines": ("ASIA", "SOUTHEAST_ASIA"),
|
|
190
|
+
"Vietnam": ("ASIA", "SOUTHEAST_ASIA"),
|
|
191
|
+
# Oceania
|
|
192
|
+
"Australia": ("OCEANIA", "OCEANIA"),
|
|
193
|
+
"New Zealand": ("OCEANIA", "OCEANIA"),
|
|
194
|
+
# South America
|
|
195
|
+
"Brazil": ("SOUTH_AMERICA", "SA_EAST"),
|
|
196
|
+
"Argentina": ("SOUTH_AMERICA", "SA_SOUTHERN_PART"), # Note: API has "Aregntine" typo
|
|
197
|
+
"Chile": ("SOUTH_AMERICA", "SA_SOUTHERN_PART"),
|
|
198
|
+
# Africa (common)
|
|
199
|
+
"South Africa": ("AFRICA", "SOUTH_AFRICA"),
|
|
200
|
+
"Egypt": ("AFRICA", "NORTH_AFRICA"),
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def get_continent_region_from_country(country_human: str) -> tuple[str, str]:
|
|
205
|
+
"""Derive continent and region enums from country name.
|
|
206
|
+
|
|
207
|
+
Uses static mapping for common countries (fast path).
|
|
208
|
+
For unknown countries, requires dynamic fetching from locale API.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
country_human: Human-readable country name from API
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Tuple of (continent_enum, region_enum)
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
ValueError: If country is not in static mapping (requires dynamic fetch)
|
|
218
|
+
"""
|
|
219
|
+
# Fast path: check static mapping
|
|
220
|
+
if country_human in COUNTRY_TO_LOCATION_STATIC:
|
|
221
|
+
return COUNTRY_TO_LOCATION_STATIC[country_human]
|
|
222
|
+
|
|
223
|
+
# Country not in static mapping - requires dynamic fetch
|
|
224
|
+
raise ValueError(
|
|
225
|
+
f"Country '{country_human}' not in static mapping. "
|
|
226
|
+
"Dynamic fetching from locale API required."
|
|
227
|
+
)
|
|
@@ -1,232 +1,14 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Inverter parameter register definitions and mappings.
|
|
2
2
|
|
|
3
|
-
This module contains
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
This module contains all hold register (configuration) and input register
|
|
4
|
+
(runtime data) definitions for Luxpower/EG4 inverters, plus GridBOSS/MID
|
|
5
|
+
device parameters.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
/WManage/web/config/plant/edit/{plant_id}
|
|
7
|
+
Source: EG4-18KPV-12LV Modbus Protocol specification
|
|
9
8
|
"""
|
|
10
9
|
|
|
11
10
|
from __future__ import annotations
|
|
12
11
|
|
|
13
|
-
# Timezone mappings: Human-readable (from API) → Form enum (for POST)
|
|
14
|
-
# Source: Analyzed all 28 timezone options from the HTML form
|
|
15
|
-
TIMEZONE_MAP: dict[str, str] = {
|
|
16
|
-
"GMT -12": "WEST12",
|
|
17
|
-
"GMT -11": "WEST11",
|
|
18
|
-
"GMT -10": "WEST10",
|
|
19
|
-
"GMT -9": "WEST9",
|
|
20
|
-
"GMT -8": "WEST8",
|
|
21
|
-
"GMT -7": "WEST7",
|
|
22
|
-
"GMT -6": "WEST6",
|
|
23
|
-
"GMT -5": "WEST5",
|
|
24
|
-
"GMT -4": "WEST4",
|
|
25
|
-
"GMT -3": "WEST3",
|
|
26
|
-
"GMT -2": "WEST2",
|
|
27
|
-
"GMT -1": "WEST1",
|
|
28
|
-
"GMT 0": "ZERO",
|
|
29
|
-
"GMT +1": "EAST1",
|
|
30
|
-
"GMT +2": "EAST2",
|
|
31
|
-
"GMT +3": "EAST3",
|
|
32
|
-
"GMT +3:30": "EAST3_30",
|
|
33
|
-
"GMT +4": "EAST4",
|
|
34
|
-
"GMT +5": "EAST5",
|
|
35
|
-
"GMT +5:30": "EAST5_30",
|
|
36
|
-
"GMT +6": "EAST6",
|
|
37
|
-
"GMT +6:30": "EAST6_30",
|
|
38
|
-
"GMT +7": "EAST7",
|
|
39
|
-
"GMT +8": "EAST8",
|
|
40
|
-
"GMT +9": "EAST9",
|
|
41
|
-
"GMT +10": "EAST10",
|
|
42
|
-
"GMT +11": "EAST11",
|
|
43
|
-
"GMT +12": "EAST12",
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
# Reverse mapping: Form enum → Human-readable
|
|
47
|
-
TIMEZONE_REVERSE_MAP: dict[str, str] = {v: k for k, v in TIMEZONE_MAP.items()}
|
|
48
|
-
|
|
49
|
-
# Country mappings: Human-readable (from API) → Form enum (for POST)
|
|
50
|
-
# Source: Analyzed country options from HTML form (North America region shown)
|
|
51
|
-
# NOTE: This list is incomplete - only shows North American countries
|
|
52
|
-
# Additional countries would appear based on selected continent/region
|
|
53
|
-
COUNTRY_MAP: dict[str, str] = {
|
|
54
|
-
"Canada": "CANADA",
|
|
55
|
-
"United States of America": "UNITED_STATES_OF_AMERICA",
|
|
56
|
-
"Mexico": "MEXICO",
|
|
57
|
-
"Greenland": "GREENLAND",
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
# Reverse mapping: Form enum → Human-readable
|
|
61
|
-
COUNTRY_REVERSE_MAP: dict[str, str] = {v: k for k, v in COUNTRY_MAP.items()}
|
|
62
|
-
|
|
63
|
-
# Continent mappings: Human-readable → Form enum
|
|
64
|
-
# Source: All 6 continent options from HTML form
|
|
65
|
-
CONTINENT_MAP: dict[str, str] = {
|
|
66
|
-
"Africa": "AFRICA",
|
|
67
|
-
"Asia": "ASIA",
|
|
68
|
-
"Europe": "EUROPE",
|
|
69
|
-
"North America": "NORTH_AMERICA",
|
|
70
|
-
"Oceania": "OCEANIA",
|
|
71
|
-
"South America": "SOUTH_AMERICA",
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
CONTINENT_REVERSE_MAP: dict[str, str] = {v: k for k, v in CONTINENT_MAP.items()}
|
|
75
|
-
|
|
76
|
-
# Region mappings: Human-readable → Form enum
|
|
77
|
-
# Source: Region options from HTML form (context: North America continent)
|
|
78
|
-
# NOTE: Region options are hierarchical and depend on selected continent
|
|
79
|
-
REGION_MAP: dict[str, str] = {
|
|
80
|
-
# North America regions (when continent = NORTH_AMERICA)
|
|
81
|
-
"Caribbean": "CARIBBEAN",
|
|
82
|
-
"Central America": "CENTRAL_AMERICA",
|
|
83
|
-
"North America": "NORTH_AMERICA",
|
|
84
|
-
# Additional regions would be discovered when exploring other continents
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
REGION_REVERSE_MAP: dict[str, str] = {v: k for k, v in REGION_MAP.items()}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def get_timezone_enum(human_readable: str) -> str:
|
|
91
|
-
"""Convert human-readable timezone to API enum.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
human_readable: Timezone string like "GMT -8"
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
API enum like "WEST8"
|
|
98
|
-
|
|
99
|
-
Raises:
|
|
100
|
-
ValueError: If timezone is not recognized
|
|
101
|
-
"""
|
|
102
|
-
if human_readable in TIMEZONE_MAP:
|
|
103
|
-
return TIMEZONE_MAP[human_readable]
|
|
104
|
-
raise ValueError(f"Unknown timezone: {human_readable}")
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def get_country_enum(human_readable: str) -> str:
|
|
108
|
-
"""Convert human-readable country to API enum.
|
|
109
|
-
|
|
110
|
-
Args:
|
|
111
|
-
human_readable: Country string like "United States of America"
|
|
112
|
-
|
|
113
|
-
Returns:
|
|
114
|
-
API enum like "UNITED_STATES_OF_AMERICA"
|
|
115
|
-
|
|
116
|
-
Raises:
|
|
117
|
-
ValueError: If country is not recognized
|
|
118
|
-
"""
|
|
119
|
-
if human_readable in COUNTRY_MAP:
|
|
120
|
-
return COUNTRY_MAP[human_readable]
|
|
121
|
-
raise ValueError(f"Unknown country: {human_readable}")
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
def get_region_enum(human_readable: str) -> str:
|
|
125
|
-
"""Convert human-readable region to API enum.
|
|
126
|
-
|
|
127
|
-
Args:
|
|
128
|
-
human_readable: Region string like "North America"
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
API enum like "NORTH_AMERICA"
|
|
132
|
-
|
|
133
|
-
Raises:
|
|
134
|
-
ValueError: If region is not recognized
|
|
135
|
-
"""
|
|
136
|
-
if human_readable in REGION_MAP:
|
|
137
|
-
return REGION_MAP[human_readable]
|
|
138
|
-
raise ValueError(f"Unknown region: {human_readable}")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def get_continent_enum(human_readable: str) -> str:
|
|
142
|
-
"""Convert human-readable continent to API enum.
|
|
143
|
-
|
|
144
|
-
Args:
|
|
145
|
-
human_readable: Continent string like "North America"
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
API enum like "NORTH_AMERICA"
|
|
149
|
-
|
|
150
|
-
Raises:
|
|
151
|
-
ValueError: If continent is not recognized
|
|
152
|
-
"""
|
|
153
|
-
if human_readable in CONTINENT_MAP:
|
|
154
|
-
return CONTINENT_MAP[human_readable]
|
|
155
|
-
raise ValueError(f"Unknown continent: {human_readable}")
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
# Static mapping for common countries (fast path)
|
|
159
|
-
# This covers the most frequently used countries to avoid API calls
|
|
160
|
-
COUNTRY_TO_LOCATION_STATIC: dict[str, tuple[str, str]] = {
|
|
161
|
-
# North America
|
|
162
|
-
"United States of America": ("NORTH_AMERICA", "NORTH_AMERICA"),
|
|
163
|
-
"Canada": ("NORTH_AMERICA", "NORTH_AMERICA"),
|
|
164
|
-
"Mexico": ("NORTH_AMERICA", "CENTRAL_AMERICA"),
|
|
165
|
-
"Greenland": ("NORTH_AMERICA", "NORTH_AMERICA"),
|
|
166
|
-
# Europe (common)
|
|
167
|
-
"United Kingdom": ("EUROPE", "WESTERN_EUROPE"),
|
|
168
|
-
"Germany": ("EUROPE", "CENTRAL_EUROPE"),
|
|
169
|
-
"France": ("EUROPE", "WESTERN_EUROPE"),
|
|
170
|
-
"Spain": ("EUROPE", "SOUTHERN_EUROPE"),
|
|
171
|
-
"Italy": ("EUROPE", "SOUTHERN_EUROPE"),
|
|
172
|
-
"The Netherlands": ("EUROPE", "WESTERN_EUROPE"),
|
|
173
|
-
"Belgium": ("EUROPE", "WESTERN_EUROPE"),
|
|
174
|
-
"Switzerland": ("EUROPE", "CENTRAL_EUROPE"),
|
|
175
|
-
"Austria": ("EUROPE", "CENTRAL_EUROPE"),
|
|
176
|
-
"Poland": ("EUROPE", "CENTRAL_EUROPE"),
|
|
177
|
-
"Sweden": ("EUROPE", "NORDIC_EUROPE"),
|
|
178
|
-
"Norway": ("EUROPE", "NORDIC_EUROPE"),
|
|
179
|
-
"Denmark": ("EUROPE", "NORDIC_EUROPE"),
|
|
180
|
-
# Asia (common)
|
|
181
|
-
"China": ("ASIA", "EAST_ASIA"),
|
|
182
|
-
"Japan": ("ASIA", "EAST_ASIA"),
|
|
183
|
-
"South korea": ("ASIA", "EAST_ASIA"),
|
|
184
|
-
"India": ("ASIA", "SOUTH_ASIA"),
|
|
185
|
-
"Singapore": ("ASIA", "SOUTHEAST_ASIA"),
|
|
186
|
-
"Thailand": ("ASIA", "SOUTHEAST_ASIA"),
|
|
187
|
-
"Malaysia": ("ASIA", "SOUTHEAST_ASIA"),
|
|
188
|
-
"Indonesia": ("ASIA", "SOUTHEAST_ASIA"),
|
|
189
|
-
"Philippines": ("ASIA", "SOUTHEAST_ASIA"),
|
|
190
|
-
"Vietnam": ("ASIA", "SOUTHEAST_ASIA"),
|
|
191
|
-
# Oceania
|
|
192
|
-
"Australia": ("OCEANIA", "OCEANIA"),
|
|
193
|
-
"New Zealand": ("OCEANIA", "OCEANIA"),
|
|
194
|
-
# South America
|
|
195
|
-
"Brazil": ("SOUTH_AMERICA", "SA_EAST"),
|
|
196
|
-
"Argentina": ("SOUTH_AMERICA", "SA_SOUTHERN_PART"), # Note: API has "Aregntine" typo
|
|
197
|
-
"Chile": ("SOUTH_AMERICA", "SA_SOUTHERN_PART"),
|
|
198
|
-
# Africa (common)
|
|
199
|
-
"South Africa": ("AFRICA", "SOUTH_AFRICA"),
|
|
200
|
-
"Egypt": ("AFRICA", "NORTH_AFRICA"),
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def get_continent_region_from_country(country_human: str) -> tuple[str, str]:
|
|
205
|
-
"""Derive continent and region enums from country name.
|
|
206
|
-
|
|
207
|
-
Uses static mapping for common countries (fast path).
|
|
208
|
-
For unknown countries, requires dynamic fetching from locale API.
|
|
209
|
-
|
|
210
|
-
Args:
|
|
211
|
-
country_human: Human-readable country name from API
|
|
212
|
-
|
|
213
|
-
Returns:
|
|
214
|
-
Tuple of (continent_enum, region_enum)
|
|
215
|
-
|
|
216
|
-
Raises:
|
|
217
|
-
ValueError: If country is not in static mapping (requires dynamic fetch)
|
|
218
|
-
"""
|
|
219
|
-
# Fast path: check static mapping
|
|
220
|
-
if country_human in COUNTRY_TO_LOCATION_STATIC:
|
|
221
|
-
return COUNTRY_TO_LOCATION_STATIC[country_human]
|
|
222
|
-
|
|
223
|
-
# Country not in static mapping - requires dynamic fetch
|
|
224
|
-
raise ValueError(
|
|
225
|
-
f"Country '{country_human}' not in static mapping. "
|
|
226
|
-
"Dynamic fetching from locale API required."
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
|
|
230
12
|
# ============================================================================
|
|
231
13
|
# INVERTER PARAMETER MAPPINGS (Hold Registers)
|
|
232
14
|
# ============================================================================
|
|
@@ -242,7 +24,7 @@ FUNC_EN_BIT_FORCED_DISCHG_EN = 10 # Forced discharge enable
|
|
|
242
24
|
FUNC_EN_BIT_FORCED_CHG_EN = 11 # Force charge enable
|
|
243
25
|
|
|
244
26
|
# AC Charge Parameters
|
|
245
|
-
HOLD_AC_CHARGE_POWER_CMD = 66 # AC charge power (0-
|
|
27
|
+
HOLD_AC_CHARGE_POWER_CMD = 66 # AC charge power (0.0-15.0 kW)
|
|
246
28
|
HOLD_AC_CHARGE_SOC_LIMIT = 67 # AC charge SOC limit (0-100%)
|
|
247
29
|
HOLD_AC_CHARGE_START_HOUR_1 = 68 # Time period 1 start hour (0-23)
|
|
248
30
|
HOLD_AC_CHARGE_START_MIN_1 = 69 # Time period 1 start minute (0-59)
|
|
@@ -327,22 +109,22 @@ INPUT_P_EPS = (30, 31) # EPS output power (W, 2 registers)
|
|
|
327
109
|
INPUT_S_EPS = 32 # EPS status
|
|
328
110
|
INPUT_P_TO_GRID = 33 # Export to grid power (W)
|
|
329
111
|
INPUT_P_TO_USER = (34, 35) # Load consumption power (W, 2 registers)
|
|
330
|
-
INPUT_E_INV_ALL = 36 # Total inverter energy (
|
|
331
|
-
INPUT_E_REC_ALL = 37 # Total grid import energy (
|
|
332
|
-
INPUT_E_CHG_ALL = 38 # Total charge energy (
|
|
333
|
-
INPUT_E_DISCHG_ALL = 39 # Total discharge energy (
|
|
334
|
-
INPUT_E_EPS_ALL = 40 # Total EPS energy (
|
|
335
|
-
INPUT_E_TO_GRID_ALL = 41 # Total export energy (
|
|
336
|
-
INPUT_E_TO_USER_ALL = 42 # Total load energy (
|
|
112
|
+
INPUT_E_INV_ALL = 36 # Total inverter energy (Wh after /10, divide by 10 for Wh)
|
|
113
|
+
INPUT_E_REC_ALL = 37 # Total grid import energy (Wh after /10, divide by 10 for Wh)
|
|
114
|
+
INPUT_E_CHG_ALL = 38 # Total charge energy (Wh after /10, divide by 10 for Wh)
|
|
115
|
+
INPUT_E_DISCHG_ALL = 39 # Total discharge energy (Wh after /10, divide by 10 for Wh)
|
|
116
|
+
INPUT_E_EPS_ALL = 40 # Total EPS energy (Wh after /10, divide by 10 for Wh)
|
|
117
|
+
INPUT_E_TO_GRID_ALL = 41 # Total export energy (Wh after /10, divide by 10 for Wh)
|
|
118
|
+
INPUT_E_TO_USER_ALL = 42 # Total load energy (Wh after /10, divide by 10 for Wh)
|
|
337
119
|
INPUT_V_BUS1 = 43 # Bus 1 voltage (V, /10)
|
|
338
120
|
INPUT_V_BUS2 = 44 # Bus 2 voltage (V, /10)
|
|
339
|
-
INPUT_E_INV_DAY = (45, 46) # Daily inverter energy (
|
|
340
|
-
INPUT_E_REC_DAY = (47, 48) # Daily grid import (
|
|
341
|
-
INPUT_E_CHG_DAY = (49, 50) # Daily charge energy (
|
|
342
|
-
INPUT_E_DISCHG_DAY = (51, 52) # Daily discharge energy (
|
|
343
|
-
INPUT_E_EPS_DAY = (53, 54) # Daily EPS energy (
|
|
344
|
-
INPUT_E_TO_GRID_DAY = (55, 56) # Daily export energy (
|
|
345
|
-
INPUT_E_TO_USER_DAY = (57, 58) # Daily load energy (
|
|
121
|
+
INPUT_E_INV_DAY = (45, 46) # Daily inverter energy (Wh after /10, 2 registers)
|
|
122
|
+
INPUT_E_REC_DAY = (47, 48) # Daily grid import (Wh after /10, 2 registers)
|
|
123
|
+
INPUT_E_CHG_DAY = (49, 50) # Daily charge energy (Wh after /10, 2 registers)
|
|
124
|
+
INPUT_E_DISCHG_DAY = (51, 52) # Daily discharge energy (Wh after /10, 2 registers)
|
|
125
|
+
INPUT_E_EPS_DAY = (53, 54) # Daily EPS energy (Wh after /10, 2 registers)
|
|
126
|
+
INPUT_E_TO_GRID_DAY = (55, 56) # Daily export energy (Wh after /10, 2 registers)
|
|
127
|
+
INPUT_E_TO_USER_DAY = (57, 58) # Daily load energy (Wh after /10, 2 registers)
|
|
346
128
|
INPUT_V_BAT_LIMIT = 59 # Max charge voltage (V, /100)
|
|
347
129
|
INPUT_I_BAT_LIMIT = 60 # Max charge current (A, /10)
|
|
348
130
|
|
|
@@ -1135,6 +917,58 @@ GRIDBOSS_STATS = {
|
|
|
1135
917
|
"midbox_specific_parameters": 159, # Smart Load, AC Coupling, Generator control
|
|
1136
918
|
}
|
|
1137
919
|
|
|
920
|
+
# ============================================================================
|
|
921
|
+
# MODEL-SPECIFIC PARAMETER KEYS
|
|
922
|
+
# ============================================================================
|
|
923
|
+
# These parameters are only available on specific inverter model families.
|
|
924
|
+
# The feature detection system uses these to identify model capabilities.
|
|
925
|
+
|
|
926
|
+
# SNA Series (Split-phase, North America) - Device Type Code: 54
|
|
927
|
+
# These parameters are unique to SNA models like SNA12K-US
|
|
928
|
+
SNA_PARAMETERS = [
|
|
929
|
+
# Discharge recovery hysteresis (prevents oscillation at SOC cutoff)
|
|
930
|
+
"HOLD_DISCHG_RECOVERY_LAG_SOC", # SOC hysteresis percentage
|
|
931
|
+
"HOLD_DISCHG_RECOVERY_LAG_VOLT", # Voltage hysteresis (÷10)
|
|
932
|
+
# Quick charge configuration
|
|
933
|
+
"SNA_HOLD_QUICK_CHARGE_MINUTE", # Quick charge duration in minutes
|
|
934
|
+
# Off-grid specific
|
|
935
|
+
"OFF_GRID_HOLD_EPS_VOLT_SET",
|
|
936
|
+
"OFF_GRID_HOLD_EPS_FREQ_SET",
|
|
937
|
+
]
|
|
938
|
+
|
|
939
|
+
# PV Series (High-voltage DC, US) - Device Type Code: 2092
|
|
940
|
+
# These parameters are available on 18KPV and similar models
|
|
941
|
+
PV_SERIES_PARAMETERS = [
|
|
942
|
+
# Volt-Watt curve parameters
|
|
943
|
+
"HOLD_VW_V1",
|
|
944
|
+
"HOLD_VW_V2",
|
|
945
|
+
"HOLD_VW_V3",
|
|
946
|
+
"HOLD_VW_V4",
|
|
947
|
+
"HOLD_VW_P1",
|
|
948
|
+
"HOLD_VW_P2",
|
|
949
|
+
"HOLD_VW_P3",
|
|
950
|
+
"HOLD_VW_P4",
|
|
951
|
+
# Grid peak shaving
|
|
952
|
+
"_12K_HOLD_GRID_PEAK_SHAVING_POWER",
|
|
953
|
+
# Parallel operation
|
|
954
|
+
"HOLD_PARALLEL_REGISTER",
|
|
955
|
+
]
|
|
956
|
+
|
|
957
|
+
# LXP-EU Series (European) - Device Type Code: 12
|
|
958
|
+
# These parameters are available on LXP-EU 12K and similar models
|
|
959
|
+
LXP_EU_PARAMETERS = [
|
|
960
|
+
# EU grid compliance
|
|
961
|
+
"HOLD_EU_GRID_CODE",
|
|
962
|
+
"HOLD_EU_COUNTRY_CODE",
|
|
963
|
+
]
|
|
964
|
+
|
|
965
|
+
# Device Type Code Constants (HOLD_DEVICE_TYPE_CODE register 19)
|
|
966
|
+
# These identify the specific inverter model/variant
|
|
967
|
+
DEVICE_TYPE_CODE_SNA = 54 # SNA Series (e.g., SNA12K-US)
|
|
968
|
+
DEVICE_TYPE_CODE_PV_SERIES = 2092 # PV Series (e.g., 18KPV)
|
|
969
|
+
DEVICE_TYPE_CODE_LXP_EU = 12 # LXP-EU Series (e.g., LXP-EU 12K)
|
|
970
|
+
|
|
971
|
+
|
|
1138
972
|
# ============================================================================
|
|
1139
973
|
# HELPER FUNCTIONS FOR PARAMETER OPERATIONS
|
|
1140
974
|
# ============================================================================
|