Homevolt 0.2.3__tar.gz → 0.2.4__tar.gz
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.
- {homevolt-0.2.3 → homevolt-0.2.4}/Homevolt.egg-info/PKG-INFO +1 -1
- {homevolt-0.2.3 → homevolt-0.2.4}/PKG-INFO +1 -1
- {homevolt-0.2.3 → homevolt-0.2.4}/homevolt/device.py +53 -9
- {homevolt-0.2.3 → homevolt-0.2.4}/homevolt/homevolt.py +6 -6
- {homevolt-0.2.3 → homevolt-0.2.4}/homevolt/models.py +3 -0
- {homevolt-0.2.3 → homevolt-0.2.4}/pyproject.toml +1 -1
- {homevolt-0.2.3 → homevolt-0.2.4}/Homevolt.egg-info/SOURCES.txt +0 -0
- {homevolt-0.2.3 → homevolt-0.2.4}/Homevolt.egg-info/dependency_links.txt +0 -0
- {homevolt-0.2.3 → homevolt-0.2.4}/Homevolt.egg-info/requires.txt +0 -0
- {homevolt-0.2.3 → homevolt-0.2.4}/Homevolt.egg-info/top_level.txt +0 -0
- {homevolt-0.2.3 → homevolt-0.2.4}/README.md +0 -0
- {homevolt-0.2.3 → homevolt-0.2.4}/homevolt/__init__.py +0 -0
- {homevolt-0.2.3 → homevolt-0.2.4}/homevolt/const.py +0 -0
- {homevolt-0.2.3 → homevolt-0.2.4}/homevolt/exceptions.py +0 -0
- {homevolt-0.2.3 → homevolt-0.2.4}/setup.cfg +0 -0
|
@@ -30,18 +30,18 @@ class Device:
|
|
|
30
30
|
|
|
31
31
|
def __init__(
|
|
32
32
|
self,
|
|
33
|
-
|
|
33
|
+
base_url: str,
|
|
34
34
|
password: str | None,
|
|
35
35
|
websession: aiohttp.ClientSession,
|
|
36
36
|
) -> None:
|
|
37
37
|
"""Initialize the device.
|
|
38
38
|
|
|
39
39
|
Args:
|
|
40
|
-
|
|
40
|
+
base_url: Base URL of the Homevolt device (e.g., http://192.168.1.100)
|
|
41
41
|
password: Optional password for authentication
|
|
42
42
|
websession: aiohttp ClientSession for making requests
|
|
43
43
|
"""
|
|
44
|
-
self.
|
|
44
|
+
self.base_url = base_url
|
|
45
45
|
self._password = password
|
|
46
46
|
self._websession = websession
|
|
47
47
|
self._auth = aiohttp.BasicAuth("admin", password) if password else None
|
|
@@ -59,7 +59,7 @@ class Device:
|
|
|
59
59
|
async def fetch_ems_data(self) -> None:
|
|
60
60
|
"""Fetch EMS data from the device."""
|
|
61
61
|
try:
|
|
62
|
-
url = f"{self.
|
|
62
|
+
url = f"{self.base_url}{ENDPOINT_EMS}"
|
|
63
63
|
async with self._websession.get(url, auth=self._auth) as response:
|
|
64
64
|
if response.status == 401:
|
|
65
65
|
raise HomevoltAuthenticationError("Authentication failed")
|
|
@@ -75,7 +75,7 @@ class Device:
|
|
|
75
75
|
async def fetch_schedule_data(self) -> None:
|
|
76
76
|
"""Fetch schedule data from the device."""
|
|
77
77
|
try:
|
|
78
|
-
url = f"{self.
|
|
78
|
+
url = f"{self.base_url}{ENDPOINT_SCHEDULE}"
|
|
79
79
|
async with self._websession.get(url, auth=self._auth) as response:
|
|
80
80
|
if response.status == 401:
|
|
81
81
|
raise HomevoltAuthenticationError("Authentication failed")
|
|
@@ -116,96 +116,115 @@ class Device:
|
|
|
116
116
|
value=ems["ems_voltage"]["l1"] / 10,
|
|
117
117
|
type=SensorType.VOLTAGE,
|
|
118
118
|
device_identifier=ems_device_id,
|
|
119
|
+
slug="l1_voltage",
|
|
119
120
|
),
|
|
120
121
|
"L2 Voltage": Sensor(
|
|
121
122
|
value=ems["ems_voltage"]["l2"] / 10,
|
|
122
123
|
type=SensorType.VOLTAGE,
|
|
123
124
|
device_identifier=ems_device_id,
|
|
125
|
+
slug="l2_voltage",
|
|
124
126
|
),
|
|
125
127
|
"L3 Voltage": Sensor(
|
|
126
128
|
value=ems["ems_voltage"]["l3"] / 10,
|
|
127
129
|
type=SensorType.VOLTAGE,
|
|
128
130
|
device_identifier=ems_device_id,
|
|
131
|
+
slug="l3_voltage",
|
|
129
132
|
),
|
|
130
133
|
"L1_L2 Voltage": Sensor(
|
|
131
134
|
value=ems["ems_voltage"]["l1_l2"] / 10,
|
|
132
135
|
type=SensorType.VOLTAGE,
|
|
133
136
|
device_identifier=ems_device_id,
|
|
137
|
+
slug="l1_l2_voltage",
|
|
134
138
|
),
|
|
135
139
|
"L2_L3 Voltage": Sensor(
|
|
136
140
|
value=ems["ems_voltage"]["l2_l3"] / 10,
|
|
137
141
|
type=SensorType.VOLTAGE,
|
|
138
142
|
device_identifier=ems_device_id,
|
|
143
|
+
slug="l2_l3_voltage",
|
|
139
144
|
),
|
|
140
145
|
"L3_L1 Voltage": Sensor(
|
|
141
146
|
value=ems["ems_voltage"]["l3_l1"] / 10,
|
|
142
147
|
type=SensorType.VOLTAGE,
|
|
143
148
|
device_identifier=ems_device_id,
|
|
149
|
+
slug="l3_l1_voltage",
|
|
144
150
|
),
|
|
145
151
|
"L1 Current": Sensor(
|
|
146
152
|
value=ems["ems_current"]["l1"],
|
|
147
153
|
type=SensorType.CURRENT,
|
|
148
154
|
device_identifier=ems_device_id,
|
|
155
|
+
slug="l1_current",
|
|
149
156
|
),
|
|
150
157
|
"L2 Current": Sensor(
|
|
151
158
|
value=ems["ems_current"]["l2"],
|
|
152
159
|
type=SensorType.CURRENT,
|
|
153
160
|
device_identifier=ems_device_id,
|
|
161
|
+
slug="l2_current",
|
|
154
162
|
),
|
|
155
163
|
"L3 Current": Sensor(
|
|
156
164
|
value=ems["ems_current"]["l3"],
|
|
157
165
|
type=SensorType.CURRENT,
|
|
158
166
|
device_identifier=ems_device_id,
|
|
167
|
+
slug="l3_current",
|
|
159
168
|
),
|
|
160
169
|
"System Temperature": Sensor(
|
|
161
170
|
value=ems["ems_data"]["sys_temp"] / 10.0,
|
|
162
171
|
type=SensorType.TEMPERATURE,
|
|
163
172
|
device_identifier=ems_device_id,
|
|
173
|
+
slug="system_temperature",
|
|
164
174
|
),
|
|
165
175
|
"Imported Energy": Sensor(
|
|
166
176
|
value=ems["ems_aggregate"]["imported_kwh"],
|
|
167
177
|
type=SensorType.ENERGY_INCREASING,
|
|
168
178
|
device_identifier=ems_device_id,
|
|
179
|
+
slug="imported_energy",
|
|
169
180
|
),
|
|
170
181
|
"Exported Energy": Sensor(
|
|
171
182
|
value=ems["ems_aggregate"]["exported_kwh"],
|
|
172
183
|
type=SensorType.ENERGY_INCREASING,
|
|
173
184
|
device_identifier=ems_device_id,
|
|
185
|
+
slug="exported_energy",
|
|
174
186
|
),
|
|
175
187
|
"Available Charging Power": Sensor(
|
|
176
188
|
value=ems["ems_prediction"]["avail_ch_pwr"],
|
|
177
189
|
type=SensorType.POWER,
|
|
178
190
|
device_identifier=ems_device_id,
|
|
191
|
+
slug="available_charging_power",
|
|
179
192
|
),
|
|
180
193
|
"Available Discharge Power": Sensor(
|
|
181
194
|
value=ems["ems_prediction"]["avail_di_pwr"],
|
|
182
195
|
type=SensorType.POWER,
|
|
183
196
|
device_identifier=ems_device_id,
|
|
197
|
+
slug="available_discharge_power",
|
|
184
198
|
),
|
|
185
199
|
"Available Charging Energy": Sensor(
|
|
186
200
|
value=ems["ems_prediction"]["avail_ch_energy"],
|
|
187
201
|
type=SensorType.ENERGY_TOTAL,
|
|
188
202
|
device_identifier=ems_device_id,
|
|
203
|
+
slug="available_charging_energy",
|
|
189
204
|
),
|
|
190
205
|
"Available Discharge Energy": Sensor(
|
|
191
206
|
value=ems["ems_prediction"]["avail_di_energy"],
|
|
192
207
|
type=SensorType.ENERGY_TOTAL,
|
|
193
208
|
device_identifier=ems_device_id,
|
|
209
|
+
slug="available_discharge_energy",
|
|
194
210
|
),
|
|
195
211
|
"Power": Sensor(
|
|
196
212
|
value=ems["ems_data"]["power"],
|
|
197
213
|
type=SensorType.POWER,
|
|
198
214
|
device_identifier=ems_device_id,
|
|
215
|
+
slug="power",
|
|
199
216
|
),
|
|
200
217
|
"Frequency": Sensor(
|
|
201
218
|
value=ems["ems_data"]["frequency"],
|
|
202
219
|
type=SensorType.FREQUENCY,
|
|
203
220
|
device_identifier=ems_device_id,
|
|
221
|
+
slug="frequency",
|
|
204
222
|
),
|
|
205
223
|
"Battery State of Charge": Sensor(
|
|
206
224
|
value=ems["ems_data"]["soc_avg"] / 100,
|
|
207
225
|
type=SensorType.PERCENTAGE,
|
|
208
226
|
device_identifier=ems_device_id,
|
|
227
|
+
slug="battery_state_of_charge",
|
|
209
228
|
),
|
|
210
229
|
}
|
|
211
230
|
)
|
|
@@ -222,48 +241,56 @@ class Device:
|
|
|
222
241
|
value=battery["soc"] / 100,
|
|
223
242
|
type=SensorType.PERCENTAGE,
|
|
224
243
|
device_identifier=battery_device_id,
|
|
244
|
+
slug="battery_state_of_charge",
|
|
225
245
|
)
|
|
226
246
|
if "tmin" in battery:
|
|
227
247
|
self.sensors[f"Homevolt battery {bat_id} tmin"] = Sensor(
|
|
228
248
|
value=battery["tmin"] / 10,
|
|
229
249
|
type=SensorType.TEMPERATURE,
|
|
230
250
|
device_identifier=battery_device_id,
|
|
251
|
+
slug="tmin",
|
|
231
252
|
)
|
|
232
253
|
if "tmax" in battery:
|
|
233
254
|
self.sensors[f"Homevolt battery {bat_id} tmax"] = Sensor(
|
|
234
255
|
value=battery["tmax"] / 10,
|
|
235
256
|
type=SensorType.TEMPERATURE,
|
|
236
257
|
device_identifier=battery_device_id,
|
|
258
|
+
slug="tmax",
|
|
237
259
|
)
|
|
238
260
|
if "cycle_count" in battery:
|
|
239
261
|
self.sensors[f"Homevolt battery {bat_id} charge cycles"] = Sensor(
|
|
240
262
|
value=battery["cycle_count"],
|
|
241
263
|
type=SensorType.COUNT,
|
|
242
264
|
device_identifier=battery_device_id,
|
|
265
|
+
slug="charge_cycles",
|
|
243
266
|
)
|
|
244
267
|
if "voltage" in battery:
|
|
245
268
|
self.sensors[f"Homevolt battery {bat_id} voltage"] = Sensor(
|
|
246
269
|
value=battery["voltage"] / 100,
|
|
247
270
|
type=SensorType.VOLTAGE,
|
|
248
271
|
device_identifier=battery_device_id,
|
|
272
|
+
slug="voltage",
|
|
249
273
|
)
|
|
250
274
|
if "current" in battery:
|
|
251
275
|
self.sensors[f"Homevolt battery {bat_id} current"] = Sensor(
|
|
252
276
|
value=battery["current"],
|
|
253
277
|
type=SensorType.CURRENT,
|
|
254
278
|
device_identifier=battery_device_id,
|
|
279
|
+
slug="current",
|
|
255
280
|
)
|
|
256
281
|
if "power" in battery:
|
|
257
282
|
self.sensors[f"Homevolt battery {bat_id} power"] = Sensor(
|
|
258
283
|
value=battery["power"],
|
|
259
284
|
type=SensorType.POWER,
|
|
260
285
|
device_identifier=battery_device_id,
|
|
286
|
+
slug="power",
|
|
261
287
|
)
|
|
262
288
|
if "soh" in battery:
|
|
263
289
|
self.sensors[f"Homevolt battery {bat_id} soh"] = Sensor(
|
|
264
290
|
value=battery["soh"] / 100,
|
|
265
291
|
type=SensorType.PERCENTAGE,
|
|
266
292
|
device_identifier=battery_device_id,
|
|
293
|
+
slug="soh",
|
|
267
294
|
)
|
|
268
295
|
|
|
269
296
|
# External sensors (grid, solar, load)
|
|
@@ -277,6 +304,9 @@ class Device:
|
|
|
277
304
|
if not sensor_device_id:
|
|
278
305
|
continue
|
|
279
306
|
|
|
307
|
+
# Suffix for translation keys (e.g., "_grid", "_load")
|
|
308
|
+
suffix = f"_{sensor_type}"
|
|
309
|
+
|
|
280
310
|
# Calculate total power from all phases
|
|
281
311
|
total_power = sum(phase["power"] for phase in sensor.get("phase", []))
|
|
282
312
|
|
|
@@ -284,44 +314,53 @@ class Device:
|
|
|
284
314
|
value=total_power,
|
|
285
315
|
type=SensorType.POWER,
|
|
286
316
|
device_identifier=sensor_device_id,
|
|
317
|
+
slug=f"power{suffix}",
|
|
287
318
|
)
|
|
288
319
|
self.sensors[f"Energy imported {sensor_type}"] = Sensor(
|
|
289
320
|
value=sensor.get("energy_imported", 0),
|
|
290
321
|
type=SensorType.ENERGY_INCREASING,
|
|
291
322
|
device_identifier=sensor_device_id,
|
|
323
|
+
slug=f"energy_imported{suffix}",
|
|
292
324
|
)
|
|
293
325
|
self.sensors[f"Energy exported {sensor_type}"] = Sensor(
|
|
294
326
|
value=sensor.get("energy_exported", 0),
|
|
295
327
|
type=SensorType.ENERGY_INCREASING,
|
|
296
328
|
device_identifier=sensor_device_id,
|
|
329
|
+
slug=f"energy_exported{suffix}",
|
|
297
330
|
)
|
|
298
331
|
self.sensors[f"RSSI {sensor_type}"] = Sensor(
|
|
299
332
|
value=sensor.get("rssi"),
|
|
300
333
|
type=SensorType.SIGNAL_STRENGTH,
|
|
301
334
|
device_identifier=sensor_device_id,
|
|
335
|
+
slug=f"rssi{suffix}",
|
|
302
336
|
)
|
|
303
337
|
self.sensors[f"Average RSSI {sensor_type}"] = Sensor(
|
|
304
338
|
value=sensor.get("average_rssi"),
|
|
305
339
|
type=SensorType.SIGNAL_STRENGTH,
|
|
306
340
|
device_identifier=sensor_device_id,
|
|
341
|
+
slug=f"average_rssi{suffix}",
|
|
307
342
|
)
|
|
308
343
|
|
|
309
344
|
# Phase-specific sensors
|
|
310
|
-
for phase_name, phase in zip(["L1", "L2", "L3"], sensor.get("phase", [])
|
|
345
|
+
for phase_name, phase in zip(["L1", "L2", "L3"], sensor.get("phase", [])):
|
|
346
|
+
phase_lower = phase_name.lower()
|
|
311
347
|
self.sensors[f"{phase_name} Voltage {sensor_type}"] = Sensor(
|
|
312
348
|
value=phase.get("voltage"),
|
|
313
349
|
type=SensorType.VOLTAGE,
|
|
314
350
|
device_identifier=sensor_device_id,
|
|
351
|
+
slug=f"{phase_lower}_voltage{suffix}",
|
|
315
352
|
)
|
|
316
353
|
self.sensors[f"{phase_name} Current {sensor_type}"] = Sensor(
|
|
317
354
|
value=phase.get("amp"),
|
|
318
355
|
type=SensorType.CURRENT,
|
|
319
356
|
device_identifier=sensor_device_id,
|
|
357
|
+
slug=f"{phase_lower}_current{suffix}",
|
|
320
358
|
)
|
|
321
359
|
self.sensors[f"{phase_name} Power {sensor_type}"] = Sensor(
|
|
322
360
|
value=phase.get("power"),
|
|
323
361
|
type=SensorType.POWER,
|
|
324
362
|
device_identifier=sensor_device_id,
|
|
363
|
+
slug=f"{phase_lower}_power{suffix}",
|
|
325
364
|
)
|
|
326
365
|
|
|
327
366
|
def _parse_schedule_data(self, schedule_data: dict[str, Any]) -> None:
|
|
@@ -337,6 +376,7 @@ class Device:
|
|
|
337
376
|
value=schedule_data.get("schedule_id"),
|
|
338
377
|
type=SensorType.TEXT,
|
|
339
378
|
device_identifier=ems_device_id,
|
|
379
|
+
slug="schedule_id",
|
|
340
380
|
)
|
|
341
381
|
|
|
342
382
|
schedule = (
|
|
@@ -349,21 +389,25 @@ class Device:
|
|
|
349
389
|
value=SCHEDULE_TYPE.get(schedule.get("type", -1)),
|
|
350
390
|
type=SensorType.SCHEDULE_TYPE,
|
|
351
391
|
device_identifier=ems_device_id,
|
|
392
|
+
slug="schedule_type",
|
|
352
393
|
)
|
|
353
394
|
self.sensors["Schedule Power Setpoint"] = Sensor(
|
|
354
395
|
value=schedule.get("params", {}).get("setpoint"),
|
|
355
396
|
type=SensorType.POWER,
|
|
356
397
|
device_identifier=ems_device_id,
|
|
398
|
+
slug="schedule_power_setpoint",
|
|
357
399
|
)
|
|
358
400
|
self.sensors["Schedule Max Power"] = Sensor(
|
|
359
401
|
value=schedule.get("max_charge"),
|
|
360
402
|
type=SensorType.POWER,
|
|
361
403
|
device_identifier=ems_device_id,
|
|
404
|
+
slug="schedule_max_power",
|
|
362
405
|
)
|
|
363
406
|
self.sensors["Schedule Max Discharge"] = Sensor(
|
|
364
407
|
value=schedule.get("max_discharge"),
|
|
365
408
|
type=SensorType.POWER,
|
|
366
409
|
device_identifier=ems_device_id,
|
|
410
|
+
slug="schedule_max_discharge",
|
|
367
411
|
)
|
|
368
412
|
|
|
369
413
|
async def _execute_console_command(self, command: str) -> dict[str, Any]:
|
|
@@ -381,7 +425,7 @@ class Device:
|
|
|
381
425
|
HomevoltDataError: If response parsing fails
|
|
382
426
|
"""
|
|
383
427
|
try:
|
|
384
|
-
url = f"{self.
|
|
428
|
+
url = f"{self.base_url}{ENDPOINT_CONSOLE}"
|
|
385
429
|
async with self._websession.post(
|
|
386
430
|
url,
|
|
387
431
|
auth=self._auth,
|
|
@@ -589,7 +633,7 @@ class Device:
|
|
|
589
633
|
HomevoltDataError: If parameter setting fails
|
|
590
634
|
"""
|
|
591
635
|
try:
|
|
592
|
-
url = f"{self.
|
|
636
|
+
url = f"{self.base_url}{ENDPOINT_PARAMS}"
|
|
593
637
|
async with self._websession.post(
|
|
594
638
|
url,
|
|
595
639
|
auth=self._auth,
|
|
@@ -619,7 +663,7 @@ class Device:
|
|
|
619
663
|
HomevoltDataError: If parameter retrieval fails
|
|
620
664
|
"""
|
|
621
665
|
try:
|
|
622
|
-
url = f"{self.
|
|
666
|
+
url = f"{self.base_url}{ENDPOINT_PARAMS}"
|
|
623
667
|
async with self._websession.get(url, auth=self._auth) as response:
|
|
624
668
|
if response.status == 401:
|
|
625
669
|
raise HomevoltAuthenticationError("Authentication failed")
|
|
@@ -16,20 +16,20 @@ class Homevolt:
|
|
|
16
16
|
|
|
17
17
|
def __init__(
|
|
18
18
|
self,
|
|
19
|
-
|
|
19
|
+
host: str,
|
|
20
20
|
password: str | None = None,
|
|
21
21
|
websession: aiohttp.ClientSession | None = None,
|
|
22
22
|
) -> None:
|
|
23
23
|
"""Initialize the Homevolt connection.
|
|
24
24
|
|
|
25
25
|
Args:
|
|
26
|
-
|
|
26
|
+
host: Hostname or IP address of the Homevolt device
|
|
27
27
|
password: Optional password for authentication
|
|
28
28
|
websession: Optional aiohttp ClientSession. If not provided, one will be created.
|
|
29
29
|
"""
|
|
30
|
-
if not
|
|
31
|
-
|
|
32
|
-
self.
|
|
30
|
+
if not host.startswith("http"):
|
|
31
|
+
host = f"http://{host}"
|
|
32
|
+
self.base_url = host
|
|
33
33
|
self._password = password
|
|
34
34
|
self._websession = websession
|
|
35
35
|
self._own_session = websession is None
|
|
@@ -42,7 +42,7 @@ class Homevolt:
|
|
|
42
42
|
await self._ensure_session()
|
|
43
43
|
assert self._websession is not None
|
|
44
44
|
self._device = Device(
|
|
45
|
-
|
|
45
|
+
base_url=self.base_url,
|
|
46
46
|
password=self._password,
|
|
47
47
|
websession=self._websession,
|
|
48
48
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|