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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Homevolt
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python library for Homevolt EMS devices
5
5
  Author-email: Your Name <your.email@example.com>
6
6
  License: GPL-3.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Homevolt
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python library for Homevolt EMS devices
5
5
  Author-email: Your Name <your.email@example.com>
6
6
  License: GPL-3.0
@@ -30,20 +30,18 @@ class Device:
30
30
 
31
31
  def __init__(
32
32
  self,
33
- hostname: str,
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
- hostname: Hostname of the Homevolt device
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
- if not hostname.startswith("http"):
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.hostname}{ENDPOINT_EMS}"
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.hostname}{ENDPOINT_SCHEDULE}"
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", []), strict=False):
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.hostname}{ENDPOINT_CONSOLE}"
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.hostname}{ENDPOINT_PARAMS}"
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.hostname}{ENDPOINT_PARAMS}"
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
- hostname: str,
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
- hostname: Hostname of the Homevolt device
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
- self.hostname = hostname
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
- hostname=self.hostname,
45
+ base_url=self.base_url,
44
46
  password=self._password,
45
47
  websession=self._websession,
46
48
  )
@@ -40,3 +40,6 @@ class Sensor:
40
40
  value: float | str | None
41
41
  type: SensorType
42
42
  device_identifier: str = "main" # Device identifier for grouping sensors into devices
43
+ slug: str | None = (
44
+ None # Normalized identifier for the sensor (e.g., for entity names, translations)
45
+ )
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "Homevolt"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Python library for Homevolt EMS devices"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
File without changes
File without changes
File without changes
File without changes