plugwise 1.5.0__tar.gz → 1.5.1__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.1}/PKG-INFO +1 -1
  2. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/constants.py +14 -14
  3. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/data.py +6 -6
  4. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/helper.py +51 -57
  5. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/legacy/data.py +3 -3
  6. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/util.py +2 -4
  7. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise.egg-info/PKG-INFO +1 -1
  8. {plugwise-1.5.0 → plugwise-1.5.1}/pyproject.toml +2 -2
  9. {plugwise-1.5.0 → plugwise-1.5.1}/LICENSE +0 -0
  10. {plugwise-1.5.0 → plugwise-1.5.1}/README.md +0 -0
  11. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/__init__.py +0 -0
  12. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/common.py +0 -0
  13. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/exceptions.py +0 -0
  14. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/legacy/helper.py +0 -0
  15. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/legacy/smile.py +0 -0
  16. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/py.typed +0 -0
  17. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise/smile.py +0 -0
  18. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise.egg-info/SOURCES.txt +0 -0
  19. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise.egg-info/dependency_links.txt +0 -0
  20. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise.egg-info/requires.txt +0 -0
  21. {plugwise-1.5.0 → plugwise-1.5.1}/plugwise.egg-info/top_level.txt +0 -0
  22. {plugwise-1.5.0 → plugwise-1.5.1}/setup.cfg +0 -0
  23. {plugwise-1.5.0 → plugwise-1.5.1}/setup.py +0 -0
  24. {plugwise-1.5.0 → plugwise-1.5.1}/tests/test_adam.py +0 -0
  25. {plugwise-1.5.0 → plugwise-1.5.1}/tests/test_anna.py +0 -0
  26. {plugwise-1.5.0 → plugwise-1.5.1}/tests/test_generic.py +0 -0
  27. {plugwise-1.5.0 → plugwise-1.5.1}/tests/test_init.py +0 -0
  28. {plugwise-1.5.0 → plugwise-1.5.1}/tests/test_legacy_anna.py +0 -0
  29. {plugwise-1.5.0 → plugwise-1.5.1}/tests/test_legacy_generic.py +0 -0
  30. {plugwise-1.5.0 → plugwise-1.5.1}/tests/test_legacy_p1.py +0 -0
  31. {plugwise-1.5.0 → plugwise-1.5.1}/tests/test_legacy_stretch.py +0 -0
  32. {plugwise-1.5.0 → plugwise-1.5.1}/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.1
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
@@ -123,7 +123,7 @@ P1_LEGACY_MEASUREMENTS: Final[dict[str, UOM]] = {
123
123
  # radiator_valve: 'uncorrected_temperature', 'temperature_offset'
124
124
 
125
125
  DEVICE_MEASUREMENTS: Final[dict[str, DATA | UOM]] = {
126
- "humidity": UOM(PERCENTAGE), # Specific for a Jip
126
+ "humidity": UOM(NONE), # Specific for a Jip
127
127
  "illuminance": UOM(UNIT_LUMEN), # Specific for an Anna
128
128
  "temperature": UOM(TEMP_CELSIUS), # HA Core thermostat current_temperature
129
129
  "thermostat": DATA("setpoint", TEMP_CELSIUS), # HA Core thermostat setpoint
@@ -425,7 +425,7 @@ class SmileBinarySensors(TypedDict, total=False):
425
425
  class SmileSensors(TypedDict, total=False):
426
426
  """Smile Sensors class."""
427
427
 
428
- battery: float
428
+ battery: int
429
429
  cooling_activation_outdoor_temperature: float
430
430
  cooling_deactivation_threshold: float
431
431
  dhw_temperature: float
@@ -434,11 +434,11 @@ class SmileSensors(TypedDict, total=False):
434
434
  electricity_consumed: float
435
435
  electricity_consumed_interval: float
436
436
  electricity_consumed_off_peak_cumulative: float
437
- electricity_consumed_off_peak_interval: int
438
- electricity_consumed_off_peak_point: int
437
+ electricity_consumed_off_peak_interval: float
438
+ electricity_consumed_off_peak_point: float
439
439
  electricity_consumed_peak_cumulative: float
440
- electricity_consumed_peak_interval: int
441
- electricity_consumed_peak_point: int
440
+ electricity_consumed_peak_interval: float
441
+ electricity_consumed_peak_point: float
442
442
  electricity_consumed_point: float
443
443
  electricity_phase_one_consumed: float
444
444
  electricity_phase_two_consumed: float
@@ -449,20 +449,20 @@ class SmileSensors(TypedDict, total=False):
449
449
  electricity_produced: float
450
450
  electricity_produced_interval: float
451
451
  electricity_produced_off_peak_cumulative: float
452
- electricity_produced_off_peak_interval: int
453
- electricity_produced_off_peak_point: int
452
+ electricity_produced_off_peak_interval: float
453
+ electricity_produced_off_peak_point: float
454
454
  electricity_produced_peak_cumulative: float
455
- electricity_produced_peak_interval: int
456
- electricity_produced_peak_point: int
455
+ electricity_produced_peak_interval: float
456
+ electricity_produced_peak_point: float
457
457
  electricity_produced_point: float
458
458
  gas_consumed_cumulative: float
459
459
  gas_consumed_interval: float
460
460
  humidity: float
461
461
  illuminance: float
462
462
  intended_boiler_temperature: float
463
- modulation_level: float
463
+ modulation_level: int
464
464
  net_electricity_cumulative: float
465
- net_electricity_point: int
465
+ net_electricity_point: float
466
466
  outdoor_air_temperature: float
467
467
  outdoor_temperature: float
468
468
  return_temperature: float
@@ -470,7 +470,7 @@ class SmileSensors(TypedDict, total=False):
470
470
  setpoint_high: float
471
471
  setpoint_low: float
472
472
  temperature_difference: float
473
- valve_position: float
473
+ valve_position: int
474
474
  voltage_phase_one: float
475
475
  voltage_phase_two: float
476
476
  voltage_phase_three: 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(
@@ -173,10 +173,8 @@ def format_measure(measure: str, unit: str) -> float | int:
173
173
  result = float(f"{round(float_measure, 1):.1f}")
174
174
  elif abs(float_measure) < 10:
175
175
  result = float(f"{round(float_measure, 2):.2f}")
176
- elif abs(float_measure) >= 10 and abs(float_measure) < 100:
176
+ elif abs(float_measure) >= 10:
177
177
  result = float(f"{round(float_measure, 1):.1f}")
178
- elif abs(float_measure) >= 100:
179
- result = int(round(float_measure))
180
178
 
181
179
  return result
182
180
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 1.5.0
3
+ Version: 1.5.1
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.1"
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
File without changes
File without changes