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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Homevolt
3
- Version: 0.2.3
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.3
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,18 +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
- self.hostname = hostname
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.hostname}{ENDPOINT_EMS}"
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.hostname}{ENDPOINT_SCHEDULE}"
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", []), strict=False):
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.hostname}{ENDPOINT_CONSOLE}"
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.hostname}{ENDPOINT_PARAMS}"
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.hostname}{ENDPOINT_PARAMS}"
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
- 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
- if not hostname.startswith("http"):
31
- hostname = f"http://{hostname}"
32
- self.hostname = hostname
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
- hostname=self.hostname,
45
+ base_url=self.base_url,
46
46
  password=self._password,
47
47
  websession=self._websession,
48
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.3"
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