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