plugwise 1.5.0__tar.gz → 1.5.1a3__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.
Files changed (32) hide show
  1. {plugwise-1.5.0 → plugwise-1.5.1a3}/PKG-INFO +1 -1
  2. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/constants.py +13 -13
  3. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/data.py +6 -6
  4. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/helper.py +51 -57
  5. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/legacy/data.py +3 -3
  6. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/util.py +1 -1
  7. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise.egg-info/PKG-INFO +1 -1
  8. {plugwise-1.5.0 → plugwise-1.5.1a3}/pyproject.toml +2 -2
  9. {plugwise-1.5.0 → plugwise-1.5.1a3}/LICENSE +0 -0
  10. {plugwise-1.5.0 → plugwise-1.5.1a3}/README.md +0 -0
  11. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/__init__.py +0 -0
  12. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/common.py +0 -0
  13. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/exceptions.py +0 -0
  14. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/legacy/helper.py +0 -0
  15. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/legacy/smile.py +0 -0
  16. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/py.typed +0 -0
  17. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise/smile.py +0 -0
  18. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise.egg-info/SOURCES.txt +0 -0
  19. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise.egg-info/dependency_links.txt +0 -0
  20. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise.egg-info/requires.txt +0 -0
  21. {plugwise-1.5.0 → plugwise-1.5.1a3}/plugwise.egg-info/top_level.txt +0 -0
  22. {plugwise-1.5.0 → plugwise-1.5.1a3}/setup.cfg +0 -0
  23. {plugwise-1.5.0 → plugwise-1.5.1a3}/setup.py +0 -0
  24. {plugwise-1.5.0 → plugwise-1.5.1a3}/tests/test_adam.py +0 -0
  25. {plugwise-1.5.0 → plugwise-1.5.1a3}/tests/test_anna.py +0 -0
  26. {plugwise-1.5.0 → plugwise-1.5.1a3}/tests/test_generic.py +0 -0
  27. {plugwise-1.5.0 → plugwise-1.5.1a3}/tests/test_init.py +0 -0
  28. {plugwise-1.5.0 → plugwise-1.5.1a3}/tests/test_legacy_anna.py +0 -0
  29. {plugwise-1.5.0 → plugwise-1.5.1a3}/tests/test_legacy_generic.py +0 -0
  30. {plugwise-1.5.0 → plugwise-1.5.1a3}/tests/test_legacy_p1.py +0 -0
  31. {plugwise-1.5.0 → plugwise-1.5.1a3}/tests/test_legacy_stretch.py +0 -0
  32. {plugwise-1.5.0 → plugwise-1.5.1a3}/tests/test_p1.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 1.5.0
3
+ Version: 1.5.1a3
4
4
  Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
5
5
  Home-page: https://github.com/plugwise/python-plugwise
6
6
  Author: Plugwise device owners
@@ -431,30 +431,30 @@ class SmileSensors(TypedDict, total=False):
431
431
  dhw_temperature: float
432
432
  domestic_hot_water_setpoint: float
433
433
  temperature: float
434
- electricity_consumed: float
435
- electricity_consumed_interval: float
434
+ electricity_consumed: int
435
+ electricity_consumed_interval:int
436
436
  electricity_consumed_off_peak_cumulative: float
437
437
  electricity_consumed_off_peak_interval: int
438
438
  electricity_consumed_off_peak_point: int
439
439
  electricity_consumed_peak_cumulative: float
440
440
  electricity_consumed_peak_interval: int
441
441
  electricity_consumed_peak_point: int
442
- electricity_consumed_point: float
443
- electricity_phase_one_consumed: float
444
- electricity_phase_two_consumed: float
445
- electricity_phase_three_consumed: float
446
- electricity_phase_one_produced: float
447
- electricity_phase_two_produced: float
448
- electricity_phase_three_produced: float
449
- electricity_produced: float
450
- electricity_produced_interval: float
442
+ electricity_consumed_point: int
443
+ electricity_phase_one_consumed: int
444
+ electricity_phase_two_consumed: int
445
+ electricity_phase_three_consumed: int
446
+ electricity_phase_one_produced: int
447
+ electricity_phase_two_produced: int
448
+ electricity_phase_three_produced: int
449
+ electricity_produced: int
450
+ electricity_produced_interval: int
451
451
  electricity_produced_off_peak_cumulative: float
452
452
  electricity_produced_off_peak_interval: int
453
453
  electricity_produced_off_peak_point: int
454
454
  electricity_produced_peak_cumulative: float
455
455
  electricity_produced_peak_interval: int
456
456
  electricity_produced_peak_point: int
457
- electricity_produced_point: float
457
+ electricity_produced_point: int
458
458
  gas_consumed_cumulative: float
459
459
  gas_consumed_interval: float
460
460
  humidity: float
@@ -552,7 +552,7 @@ class DeviceData(TypedDict, total=False):
552
552
  last_used: str | None
553
553
  select_schedule: str
554
554
 
555
- mode: str
555
+ climate_mode: str
556
556
  # Extra for Adam Master Thermostats
557
557
  control_state: str | bool
558
558
 
@@ -240,16 +240,16 @@ class SmileData(SmileHelper):
240
240
  data["select_schedule"] = sel_schedule
241
241
  self._count += 2
242
242
 
243
- # Operation modes: auto, heat, heat_cool, cool and off
244
- data["mode"] = "auto"
243
+ # Set HA climate HVACMode: auto, heat, heat_cool, cool and off
244
+ data["climate_mode"] = "auto"
245
245
  self._count += 1
246
246
  if sel_schedule in (NONE, OFF):
247
- data["mode"] = "heat"
247
+ data["climate_mode"] = "heat"
248
248
  if self._cooling_present:
249
- data["mode"] = "cool" if self.check_reg_mode("cooling") else "heat_cool"
249
+ data["climate_mode"] = "cool" if self.check_reg_mode("cooling") else "heat_cool"
250
250
 
251
251
  if self.check_reg_mode("off"):
252
- data["mode"] = "off"
252
+ data["climate_mode"] = "off"
253
253
 
254
254
  if NONE not in avail_schedules:
255
255
  self._get_schedule_states_with_off(
@@ -273,7 +273,7 @@ class SmileData(SmileHelper):
273
273
  loc_schedule_states: dict[str, str] = {}
274
274
  for schedule in schedules:
275
275
  loc_schedule_states[schedule] = "off"
276
- if schedule == selected and data["mode"] == "auto":
276
+ if schedule == selected and data["climate_mode"] == "auto":
277
277
  loc_schedule_states[schedule] = "on"
278
278
  self._schedule_old_states[location] = loc_schedule_states
279
279
 
@@ -269,6 +269,10 @@ class SmileHelper(SmileCommon):
269
269
  for appliance in self._domain_objects.findall("./appliance"):
270
270
  appl = Munch()
271
271
  appl.pwclass = appliance.find("type").text
272
+ # Don't collect data for the OpenThermGateway appliance
273
+ if appl.pwclass == "open_therm_gateway":
274
+ continue
275
+
272
276
  # Extend device_class name of Plugs (Plugwise and Aqara) - Pw-Beta Issue #739
273
277
  description = appliance.find("description").text
274
278
  if description is not None and (
@@ -291,6 +295,10 @@ class SmileHelper(SmileCommon):
291
295
  elif appl.pwclass not in THERMOSTAT_CLASSES:
292
296
  appl.location = self._home_location
293
297
 
298
+ # Don't show orphaned thermostat-types
299
+ if appl.pwclass in THERMOSTAT_CLASSES and appl.location is None:
300
+ continue
301
+
294
302
  appl.dev_id = appliance.attrib["id"]
295
303
  appl.name = appliance.find("name").text
296
304
  appl.model = None
@@ -301,24 +309,15 @@ class SmileHelper(SmileCommon):
301
309
  appl.zigbee_mac = None
302
310
  appl.vendor_name = None
303
311
 
304
- # Determine class for this appliance
305
- # Skip on heater_central when no active device present
312
+ # Collect appliance info, skip orphaned/removed devices
306
313
  if not (appl := self._appliance_info_finder(appl, appliance)):
307
314
  continue
308
315
 
309
- # Skip orphaned heater_central (Core Issue #104433)
310
- if appl.pwclass == "heater_central" and appl.dev_id != self._heater_id:
311
- continue
312
-
313
316
  # P1: for gateway and smartmeter switch device_id - part 1
314
317
  # This is done to avoid breakage in HA Core
315
318
  if appl.pwclass == "gateway" and self.smile_type == "power":
316
319
  appl.dev_id = appl.location
317
320
 
318
- # Don't show orphaned thermostat-types or the OpenTherm Gateway.
319
- if appl.pwclass in THERMOSTAT_CLASSES and appl.location is None:
320
- continue
321
-
322
321
  self._create_gw_devices(appl)
323
322
 
324
323
  # For P1 collect the connected SmartMeter info
@@ -354,23 +353,32 @@ class SmileHelper(SmileCommon):
354
353
  self.loc_data[loc.loc_id] = {"name": loc.name}
355
354
 
356
355
  def _p1_smartmeter_info_finder(self, appl: Munch) -> None:
357
- """Collect P1 DSMR Smartmeter info."""
356
+ """Collect P1 DSMR SmartMeter info."""
358
357
  loc_id = next(iter(self.loc_data.keys()))
358
+ location = self._domain_objects.find(f'./location[@id="{loc_id}"]')
359
+ locator = "./logs/point_log/electricity_point_meter"
360
+ mod_type = "electricity_point_meter"
361
+ module_data = self._get_module_data(location, locator, mod_type)
362
+ if not module_data["contents"]:
363
+ LOGGER.error("No module data found for SmartMeter") # pragma: no cover
364
+ return None # pragma: no cover
365
+
359
366
  appl.dev_id = self.gateway_id
367
+ appl.firmware = module_data["firmware_version"]
368
+ appl.hardware = module_data["hardware_version"]
360
369
  appl.location = loc_id
361
370
  appl.mac = None
362
- appl.model = None
363
- appl.model_id = None
371
+ appl.model = module_data["vendor_model"]
372
+ appl.model_id = None # don't use model_id for SmartMeter
364
373
  appl.name = "P1"
365
374
  appl.pwclass = "smartmeter"
375
+ appl.vendor_name = module_data["vendor_name"]
366
376
  appl.zigbee_mac = None
367
- location = self._domain_objects.find(f'./location[@id="{loc_id}"]')
368
- appl = self._energy_device_info_finder(appl, location)
369
377
 
370
378
  self._create_gw_devices(appl)
371
379
 
372
380
  def _appliance_info_finder(self, appl: Munch, appliance: etree) -> Munch:
373
- """Collect device info (Smile/Stretch, Thermostats, OpenTherm/On-Off): firmware, model and vendor name."""
381
+ """Collect info for all appliances found."""
374
382
  match appl.pwclass:
375
383
  case "gateway":
376
384
  # Collect gateway device info
@@ -382,45 +390,28 @@ class SmileHelper(SmileCommon):
382
390
  # Collect heater_central device info
383
391
  self._appl_heater_central_info(appl, appliance, False) # False means non-legacy device
384
392
  self._appl_dhw_mode_info(appl, appliance)
393
+ # Skip orphaned heater_central (Core Issue #104433)
394
+ if appl.dev_id != self._heater_id:
395
+ return Munch()
396
+ return appl
397
+ case _ as s if s.endswith("_plug"):
398
+ # Collect info from plug-types (Plug, Aqara Smart Plug)
399
+ locator = "./logs/interval_log/electricity_interval_meter"
400
+ mod_type = "electricity_interval_meter"
401
+ module_data = self._get_module_data(appliance, locator, mod_type)
402
+ # A plug without module-data is orphaned/ no present
403
+ if not module_data["contents"]:
404
+ return Munch()
405
+
406
+ appl.firmware = module_data["firmware_version"]
407
+ appl.hardware = module_data["hardware_version"]
408
+ appl.model_id = module_data["vendor_model"]
409
+ appl.vendor_name = module_data["vendor_name"]
410
+ appl.model = check_model(appl.model_id, appl.vendor_name)
411
+ appl.zigbee_mac = module_data["zigbee_mac_address"]
412
+ return appl
413
+ case _: # pragma: no cover
385
414
  return appl
386
- case _:
387
- # Collect info from power-related devices (Plug, Aqara Smart Plug)
388
- return self._energy_device_info_finder(appl, appliance)
389
-
390
- def _energy_device_info_finder(self, appl: Munch, appliance: etree) -> Munch:
391
- """Helper-function for _appliance_info_finder().
392
-
393
- Collect energy device info (Smartmeter): firmware, model and vendor name.
394
- """
395
- if self.smile_type == "power":
396
- locator = "./logs/point_log/electricity_point_meter"
397
- mod_type = "electricity_point_meter"
398
- module_data = self._get_module_data(appliance, locator, mod_type)
399
- appl.hardware = module_data["hardware_version"]
400
- appl.model = module_data["vendor_model"] # don't use model_id for Smartmeter
401
- appl.vendor_name = module_data["vendor_name"]
402
- appl.firmware = module_data["firmware_version"]
403
-
404
- return appl
405
-
406
- if self.smile(ADAM):
407
- locator = "./logs/interval_log/electricity_interval_meter"
408
- mod_type = "electricity_interval_meter"
409
- module_data = self._get_module_data(appliance, locator, mod_type)
410
- # Filter appliance without zigbee_mac, it's an orphaned device
411
- appl.zigbee_mac = module_data["zigbee_mac_address"]
412
- if appl.zigbee_mac is None:
413
- return None
414
-
415
- appl.vendor_name = module_data["vendor_name"]
416
- appl.model_id = module_data["vendor_model"]
417
- appl.model = check_model(appl.model_id, appl.vendor_name)
418
- appl.hardware = module_data["hardware_version"]
419
- appl.firmware = module_data["firmware_version"]
420
-
421
- return appl
422
-
423
- return appl # pragma: no cover
424
415
 
425
416
  def _appl_gateway_info(self, appl: Munch, appliance: etree) -> Munch:
426
417
  """Helper-function for _appliance_info_finder()."""
@@ -725,15 +716,18 @@ class SmileHelper(SmileCommon):
725
716
  Collect the availability-status for wireless connected devices.
726
717
  """
727
718
  if self.smile(ADAM):
728
- # Collect for Plugs
719
+ # Try collecting for a Plug
729
720
  locator = "./logs/interval_log/electricity_interval_meter"
730
721
  mod_type = "electricity_interval_meter"
731
722
  module_data = self._get_module_data(appliance, locator, mod_type)
732
- if module_data["reachable"] is None:
733
- # Collect for wireless thermostats
723
+ if not module_data["contents"]:
724
+ # Try collecting for a wireless thermostat
734
725
  locator = "./logs/point_log[type='thermostat']/thermostat"
735
726
  mod_type = "thermostat"
736
727
  module_data = self._get_module_data(appliance, locator, mod_type)
728
+ if not module_data["contents"]:
729
+ LOGGER.error("No module data found for Plug or wireless thermostat") # pragma: no cover
730
+ return None # pragma: no cover
737
731
 
738
732
  if module_data["reachable"] is not None:
739
733
  data["available"] = module_data["reachable"]
@@ -86,8 +86,8 @@ class SmileLegacyData(SmileLegacyHelper):
86
86
  data["select_schedule"] = sel_schedule
87
87
  self._count += 2
88
88
 
89
- # Operation modes: auto, heat
90
- data["mode"] = "auto"
89
+ # Set HA climate HVACMode: auto, heat
90
+ data["climate_mode"] = "auto"
91
91
  self._count += 1
92
92
  if sel_schedule in (NONE, OFF):
93
- data["mode"] = "heat"
93
+ data["climate_mode"] = "heat"
@@ -115,7 +115,7 @@ def check_model(name: str | None, vendor_name: str | None) -> str | None:
115
115
  if name is not None and "lumi.plug" in name:
116
116
  return "Aqara Smart Plug"
117
117
 
118
- return name
118
+ return name # pragma: no cover
119
119
 
120
120
 
121
121
  def common_match_cases(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 1.5.0
3
+ Version: 1.5.1a3
4
4
  Summary: Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3.
5
5
  Home-page: https://github.com/plugwise/python-plugwise
6
6
  Author: Plugwise device owners
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["setuptools~=75.1", "wheel~=0.44.0"]
2
+ requires = ["setuptools~=75.1", "wheel~=0.45.0"]
3
3
  build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plugwise"
7
- version = "1.5.0"
7
+ version = "1.5.1a3"
8
8
  license = {file = "LICENSE"}
9
9
  description = "Plugwise Smile (Adam/Anna/P1) and Stretch module for Python 3."
10
10
  readme = "README.md"
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
File without changes
File without changes