plugwise 0.37.5__py3-none-any.whl → 0.37.7__py3-none-any.whl

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.
plugwise/__init__.py CHANGED
@@ -329,9 +329,13 @@ class Smile(SmileComm):
329
329
  key: str,
330
330
  temperature: float,
331
331
  ) -> None:
332
- """Set the max. Boiler or DHW setpoint on the Central Heating boiler."""
332
+ """Set the maximum boiler- or DHW-setpoint on the Central Heating boiler or the temperature-offset on a Thermostat."""
333
333
  await self._smile_api.set_number(dev_id, key, temperature)
334
334
 
335
+ async def set_temperature_offset(self, dev_id: str, offset: float) -> None:
336
+ """Set the Temperature offset for thermostats that support this feature."""
337
+ await self._smile_api.set_offset(dev_id, offset) # pragma: no cover
338
+
335
339
  async def set_switch_state(
336
340
  self, appl_id: str, members: list[str] | None, model: str, state: str
337
341
  ) -> None:
plugwise/constants.py CHANGED
@@ -207,13 +207,13 @@ SMILES: Final[dict[str, SMILE]] = {
207
207
  "stretch_v2": SMILE(STRETCH, "Stretch"),
208
208
  "stretch_v3": SMILE(STRETCH, "Stretch"),
209
209
  }
210
- REQUIRE_APPLIANCES: Final[list[str]] = [
210
+ REQUIRE_APPLIANCES: Final[tuple[str, ...]] = (
211
211
  "smile_thermo_v1",
212
212
  "smile_thermo_v3",
213
213
  "smile_thermo_v4",
214
214
  "stretch_v2",
215
215
  "stretch_v3",
216
- ]
216
+ )
217
217
 
218
218
  # Class, Literal and related tuple-definitions
219
219
 
plugwise/data.py CHANGED
@@ -197,14 +197,15 @@ class SmileData(SmileHelper):
197
197
 
198
198
  # Schedule
199
199
  avail_schedules, sel_schedule = self._schedules(loc_id)
200
- data["available_schedules"] = avail_schedules
201
- data["select_schedule"] = sel_schedule
202
- self._count += 2
200
+ if avail_schedules != [NONE]:
201
+ data["available_schedules"] = avail_schedules
202
+ data["select_schedule"] = sel_schedule
203
+ self._count += 2
203
204
 
204
205
  # Operation modes: auto, heat, heat_cool, cool and off
205
206
  data["mode"] = "auto"
206
207
  self._count += 1
207
- if sel_schedule == NONE:
208
+ if sel_schedule in (NONE, OFF):
208
209
  data["mode"] = "heat"
209
210
  if self._cooling_present:
210
211
  data["mode"] = "cool" if self.check_reg_mode("cooling") else "heat_cool"
plugwise/helper.py CHANGED
@@ -15,8 +15,6 @@ from plugwise.constants import (
15
15
  ADAM,
16
16
  ANNA,
17
17
  ATTR_NAME,
18
- ATTR_UNIT_OF_MEASUREMENT,
19
- BINARY_SENSORS,
20
18
  DATA,
21
19
  DEVICE_MEASUREMENTS,
22
20
  DHW_SETPOINT,
@@ -29,9 +27,6 @@ from plugwise.constants import (
29
27
  NONE,
30
28
  OFF,
31
29
  P1_MEASUREMENTS,
32
- SENSORS,
33
- SPECIALS,
34
- SWITCHES,
35
30
  TEMP_CELSIUS,
36
31
  THERMOSTAT_CLASSES,
37
32
  TOGGLES,
@@ -39,12 +34,9 @@ from plugwise.constants import (
39
34
  ActuatorData,
40
35
  ActuatorDataType,
41
36
  ActuatorType,
42
- BinarySensorType,
43
37
  DeviceData,
44
38
  GatewayData,
45
39
  SensorType,
46
- SpecialType,
47
- SwitchType,
48
40
  ThermoLoc,
49
41
  ToggleNameType,
50
42
  )
@@ -56,6 +48,7 @@ from plugwise.exceptions import (
56
48
  )
57
49
  from plugwise.util import (
58
50
  check_model,
51
+ common_match_cases,
59
52
  escape_illegal_xml_characters,
60
53
  format_measure,
61
54
  skip_obsolete_measurements,
@@ -214,7 +207,7 @@ class SmileHelper(SmileCommon):
214
207
  self._thermo_locs: dict[str, ThermoLoc] = {}
215
208
  ###################################################################
216
209
  # '_cooling_enabled' can refer to the state of the Elga heatpump
217
- # connected to an Anna. For Elga, 'elga_status_code' in [8, 9]
210
+ # connected to an Anna. For Elga, 'elga_status_code' in (8, 9)
218
211
  # means cooling mode is available, next to heating mode.
219
212
  # 'elga_status_code' = 8 means cooling is active, 9 means idle.
220
213
  #
@@ -523,7 +516,7 @@ class SmileHelper(SmileCommon):
523
516
  # Techneco Elga has cooling-capability
524
517
  self._cooling_present = True
525
518
  data["model"] = "Generic heater/cooler"
526
- self._cooling_enabled = data["elga_status_code"] in [8, 9]
519
+ self._cooling_enabled = data["elga_status_code"] in (8, 9)
527
520
  data["binary_sensors"]["cooling_state"] = self._cooling_active = (
528
521
  data["elga_status_code"] == 8
529
522
  )
@@ -585,29 +578,12 @@ class SmileHelper(SmileCommon):
585
578
  measurement = new_name
586
579
 
587
580
  match measurement:
588
- # measurements with states "on" or "off" that need to be passed directly
589
- case "select_dhw_mode":
590
- data["select_dhw_mode"] = appl_p_loc.text
591
- case _ as measurement if measurement in BINARY_SENSORS:
592
- bs_key = cast(BinarySensorType, measurement)
593
- bs_value = appl_p_loc.text in ["on", "true"]
594
- data["binary_sensors"][bs_key] = bs_value
595
- case _ as measurement if measurement in SENSORS:
596
- s_key = cast(SensorType, measurement)
597
- s_value = format_measure(
598
- appl_p_loc.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT)
599
- )
600
- data["sensors"][s_key] = s_value
601
- case _ as measurement if measurement in SWITCHES:
602
- sw_key = cast(SwitchType, measurement)
603
- sw_value = appl_p_loc.text in ["on", "true"]
604
- data["switches"][sw_key] = sw_value
605
- case _ as measurement if measurement in SPECIALS:
606
- sp_key = cast(SpecialType, measurement)
607
- sp_value = appl_p_loc.text in ["on", "true"]
608
- data[sp_key] = sp_value
609
581
  case "elga_status_code":
610
582
  data["elga_status_code"] = int(appl_p_loc.text)
583
+ case "select_dhw_mode":
584
+ data["select_dhw_mode"] = appl_p_loc.text
585
+
586
+ common_match_cases(measurement, attrs, appl_p_loc, data)
611
587
 
612
588
  i_locator = f'.//logs/interval_log[type="{measurement}"]/period/measurement'
613
589
  if (appl_i_loc := appliance.find(i_locator)) is not None:
@@ -1014,6 +990,8 @@ class SmileHelper(SmileCommon):
1014
990
  if schedules:
1015
991
  available.remove(NONE)
1016
992
  available.append(OFF)
993
+ if selected == NONE:
994
+ selected = OFF
1017
995
  if self._last_active.get(location) is None:
1018
996
  self._last_active[location] = self._last_used_schedule(schedules)
1019
997
 
plugwise/legacy/data.py CHANGED
@@ -6,7 +6,7 @@ from __future__ import annotations
6
6
 
7
7
  # Dict as class
8
8
  # Version detection
9
- from plugwise.constants import NONE, DeviceData
9
+ from plugwise.constants import NONE, OFF, DeviceData
10
10
  from plugwise.legacy.helper import SmileLegacyHelper
11
11
  from plugwise.util import remove_empty_platform_dicts
12
12
 
@@ -81,12 +81,13 @@ class SmileLegacyData(SmileLegacyHelper):
81
81
 
82
82
  # Schedule
83
83
  avail_schedules, sel_schedule = self._schedules()
84
- data["available_schedules"] = avail_schedules
85
- data["select_schedule"] = sel_schedule
86
- self._count += 2
84
+ if avail_schedules != [NONE]:
85
+ data["available_schedules"] = avail_schedules
86
+ data["select_schedule"] = sel_schedule
87
+ self._count += 2
87
88
 
88
89
  # Operation modes: auto, heat
89
90
  data["mode"] = "auto"
90
91
  self._count += 1
91
- if sel_schedule == NONE:
92
+ if sel_schedule in (NONE, OFF):
92
93
  data["mode"] = "heat"
plugwise/legacy/helper.py CHANGED
@@ -12,8 +12,6 @@ from plugwise.constants import (
12
12
  ACTUATOR_CLASSES,
13
13
  APPLIANCES,
14
14
  ATTR_NAME,
15
- ATTR_UNIT_OF_MEASUREMENT,
16
- BINARY_SENSORS,
17
15
  DATA,
18
16
  DEVICE_MEASUREMENTS,
19
17
  ENERGY_WATT_HOUR,
@@ -22,10 +20,8 @@ from plugwise.constants import (
22
20
  HEATER_CENTRAL_MEASUREMENTS,
23
21
  LIMITS,
24
22
  NONE,
23
+ OFF,
25
24
  P1_LEGACY_MEASUREMENTS,
26
- SENSORS,
27
- SPECIALS,
28
- SWITCHES,
29
25
  TEMP_CELSIUS,
30
26
  THERMOSTAT_CLASSES,
31
27
  UOM,
@@ -33,15 +29,17 @@ from plugwise.constants import (
33
29
  ActuatorDataType,
34
30
  ActuatorType,
35
31
  ApplianceType,
36
- BinarySensorType,
37
32
  DeviceData,
38
33
  GatewayData,
39
34
  SensorType,
40
- SpecialType,
41
- SwitchType,
42
35
  ThermoLoc,
43
36
  )
44
- from plugwise.util import format_measure, skip_obsolete_measurements, version_to_model
37
+ from plugwise.util import (
38
+ common_match_cases,
39
+ format_measure,
40
+ skip_obsolete_measurements,
41
+ version_to_model,
42
+ )
45
43
 
46
44
  # This way of importing aiohttp is because of patch/mocking in testing (aiohttp timeouts)
47
45
  from defusedxml import ElementTree as etree
@@ -340,25 +338,7 @@ class SmileLegacyHelper(SmileCommon):
340
338
  if new_name := getattr(attrs, ATTR_NAME, None):
341
339
  measurement = new_name
342
340
 
343
- match measurement:
344
- case _ as measurement if measurement in BINARY_SENSORS:
345
- bs_key = cast(BinarySensorType, measurement)
346
- bs_value = appl_p_loc.text in ["on", "true"]
347
- data["binary_sensors"][bs_key] = bs_value
348
- case _ as measurement if measurement in SENSORS:
349
- s_key = cast(SensorType, measurement)
350
- s_value = format_measure(
351
- appl_p_loc.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT)
352
- )
353
- data["sensors"][s_key] = s_value
354
- case _ as measurement if measurement in SWITCHES:
355
- sw_key = cast(SwitchType, measurement)
356
- sw_value = appl_p_loc.text in ["on", "true"]
357
- data["switches"][sw_key] = sw_value
358
- case _ as measurement if measurement in SPECIALS:
359
- sp_key = cast(SpecialType, measurement)
360
- sp_value = appl_p_loc.text in ["on", "true"]
361
- data[sp_key] = sp_value
341
+ common_match_cases(measurement, attrs, appl_p_loc, data)
362
342
 
363
343
  i_locator = f'.//logs/interval_log[type="{measurement}"]/period/measurement'
364
344
  if (appl_i_loc := appliance.find(i_locator)) is not None:
@@ -450,16 +430,15 @@ class SmileLegacyHelper(SmileCommon):
450
430
  return presets
451
431
 
452
432
  def _schedules(self) -> tuple[list[str], str]:
453
- """Collect available schedules/schedules for the legacy thermostat."""
433
+ """Collect the schedule for the legacy thermostat."""
454
434
  available: list[str] = [NONE]
455
- selected = NONE
435
+ rule_id = selected = NONE
456
436
  name: str | None = None
457
437
 
458
438
  search = self._domain_objects
459
- for schedule in search.findall("./rule"):
460
- if rule_name := schedule.find("name").text:
461
- if "preset" not in rule_name:
462
- name = rule_name
439
+ if (result := search.find("./rule[name='Thermostat schedule']")) is not None:
440
+ name = "Thermostat schedule"
441
+ rule_id = result.attrib["id"]
463
442
 
464
443
  log_type = "schedule_state"
465
444
  locator = f"./appliance[type='thermostat']/logs/point_log[type='{log_type}']/period/measurement"
@@ -467,10 +446,11 @@ class SmileLegacyHelper(SmileCommon):
467
446
  if (result := search.find(locator)) is not None:
468
447
  active = result.text == "on"
469
448
 
470
- if name is not None:
471
- available = [name]
472
- if active:
473
- selected = name
449
+ # Show an empty schedule as no schedule found
450
+ directives = search.find(f'./rule[@id="{rule_id}"]/directives/when/then') is not None
451
+ if directives and name is not None:
452
+ available = [name, OFF]
453
+ selected = name if active else OFF
474
454
 
475
455
  return available, selected
476
456
 
@@ -478,5 +458,4 @@ class SmileLegacyHelper(SmileCommon):
478
458
  """Determine the location-set_temperature uri - from APPLIANCES."""
479
459
  locator = "./appliance[type='thermostat']"
480
460
  appliance_id = self._appliances.find(locator).attrib["id"]
481
-
482
461
  return f"{APPLIANCES};id={appliance_id}/thermostat"
plugwise/legacy/smile.py CHANGED
@@ -15,6 +15,7 @@ from plugwise.constants import (
15
15
  LOCATIONS,
16
16
  LOGGER,
17
17
  MODULES,
18
+ OFF,
18
19
  REQUIRE_APPLIANCES,
19
20
  RULES,
20
21
  DeviceData,
@@ -162,6 +163,9 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
162
163
  ) -> None:
163
164
  """Set-function placeholder for legacy devices."""
164
165
 
166
+ async def set_offset(self, dev_id: str, offset: float) -> None:
167
+ """Set-function placeholder for legacy devices."""
168
+
165
169
  async def set_preset(self, _: str, preset: str) -> None:
166
170
  """Set the given Preset on the relevant Thermostat - from DOMAIN_OBJECTS."""
167
171
  if (presets := self._presets()) is None:
@@ -178,16 +182,19 @@ class SmileLegacyAPI(SmileComm, SmileLegacyData):
178
182
  async def set_regulation_mode(self, mode: str) -> None:
179
183
  """Set-function placeholder for legacy devices."""
180
184
 
181
- async def set_schedule_state(self, _: str, state: str, __: str | None) -> None:
185
+ async def set_schedule_state(self, _: str, state: str, name: str | None) -> None:
182
186
  """Activate/deactivate the Schedule.
183
187
 
184
188
  Determined from - DOMAIN_OBJECTS.
185
189
  Used in HA Core to set the hvac_mode: in practice switch between schedule on - off.
186
190
  """
187
- if state not in ["on", "off"]:
191
+ if state not in ("on", "off"):
188
192
  raise PlugwiseError("Plugwise: invalid schedule state.")
189
193
 
190
- name = "Thermostat schedule"
194
+ # Handle no schedule-name / Off-schedule provided
195
+ if name is None or name == OFF:
196
+ name = "Thermostat schedule"
197
+
191
198
  schedule_rule_id: str | None = None
192
199
  for rule in self._domain_objects.findall("rule"):
193
200
  if rule.find("name").text == name:
plugwise/smile.py CHANGED
@@ -266,7 +266,7 @@ class SmileAPI(SmileComm, SmileData):
266
266
  Used in HA Core to set the hvac_mode: in practice switch between schedule on - off.
267
267
  """
268
268
  # Input checking
269
- if new_state not in ["on", "off"]:
269
+ if new_state not in ("on", "off"):
270
270
  raise PlugwiseError("Plugwise: invalid schedule state.")
271
271
 
272
272
  # Translate selection of Off-schedule-option to disabling the active schedule
plugwise/util.py CHANGED
@@ -3,19 +3,30 @@ from __future__ import annotations
3
3
 
4
4
  import datetime as dt
5
5
  import re
6
+ from typing import cast
6
7
 
7
8
  from plugwise.constants import (
8
9
  ATTR_UNIT_OF_MEASUREMENT,
10
+ BINARY_SENSORS,
11
+ DATA,
9
12
  ELECTRIC_POTENTIAL_VOLT,
10
13
  ENERGY_KILO_WATT_HOUR,
11
14
  HW_MODELS,
12
15
  OBSOLETE_MEASUREMENTS,
13
16
  PERCENTAGE,
14
17
  POWER_WATT,
18
+ SENSORS,
15
19
  SPECIAL_FORMAT,
20
+ SPECIALS,
21
+ SWITCHES,
16
22
  TEMP_CELSIUS,
23
+ UOM,
24
+ BinarySensorType,
17
25
  DeviceData,
18
26
  ModelData,
27
+ SensorType,
28
+ SpecialType,
29
+ SwitchType,
19
30
  )
20
31
 
21
32
  from defusedxml import ElementTree as etree
@@ -23,7 +34,7 @@ from munch import Munch
23
34
 
24
35
 
25
36
  def check_alternative_location(loc: Munch, legacy: bool) -> Munch:
26
- """Try."""
37
+ """Helper-function for _power_data_peak_value()."""
27
38
  if in_alternative_location(loc, legacy):
28
39
  # Avoid double processing by skipping one peak-list option
29
40
  if loc.peak_select == "nl_offpeak":
@@ -102,6 +113,32 @@ def check_model(name: str | None, vendor_name: str | None) -> str | None:
102
113
  return name
103
114
 
104
115
 
116
+ def common_match_cases(
117
+ measurement: str,
118
+ attrs: DATA | UOM,
119
+ location: etree,
120
+ data: DeviceData,
121
+ ) -> None:
122
+ """Helper-function for common match-case execution."""
123
+ value = location.text in ("on", "true")
124
+ match measurement:
125
+ case _ as measurement if measurement in BINARY_SENSORS:
126
+ bs_key = cast(BinarySensorType, measurement)
127
+ data["binary_sensors"][bs_key] = value
128
+ case _ as measurement if measurement in SENSORS:
129
+ s_key = cast(SensorType, measurement)
130
+ s_value = format_measure(
131
+ location.text, getattr(attrs, ATTR_UNIT_OF_MEASUREMENT)
132
+ )
133
+ data["sensors"][s_key] = s_value
134
+ case _ as measurement if measurement in SWITCHES:
135
+ sw_key = cast(SwitchType, measurement)
136
+ data["switches"][sw_key] = value
137
+ case _ as measurement if measurement in SPECIALS:
138
+ sp_key = cast(SpecialType, measurement)
139
+ data[sp_key] = value
140
+
141
+
105
142
  def escape_illegal_xml_characters(xmldata: str) -> str:
106
143
  """Replace illegal &-characters."""
107
144
  return re.sub(r"&([^a-zA-Z#])", r"&\1", xmldata)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: plugwise
3
- Version: 0.37.5
3
+ Version: 0.37.7
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
@@ -0,0 +1,17 @@
1
+ plugwise/__init__.py,sha256=KMLU90T2k_z1MuZGEPnLPtHoNbw32kboeGK8rCqjibE,14084
2
+ plugwise/common.py,sha256=P4sUYzgVcFsIR2DmQxeVeOiZvFZWpZXgwHA3XRc1Sx0,12538
3
+ plugwise/constants.py,sha256=dvafW3u7LBbWUN05D1Tss7s4jxEDJiZDqnh4-q9isUM,16580
4
+ plugwise/data.py,sha256=9sH2FkwGh5Uvbf42LmZPWutY9Qa-j9qmFZBobwJkLQo,9022
5
+ plugwise/exceptions.py,sha256=rymGtWnXosdFkzSehc_41HVl2jXJEg_9Hq9APDEUwrk,1223
6
+ plugwise/helper.py,sha256=nF1xJEw-frvuGMAydFAJrz_tN24Q3PcBnYNIwPeueZg,42817
7
+ plugwise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ plugwise/smile.py,sha256=9eJR8iLyYOMrWq8MMw3-uQrN8fS5sZCHdJpp5Kspszo,17177
9
+ plugwise/util.py,sha256=9ld46KJHWFze2eUrVSgUYn0g3zNerlpboM0iUa0H3ak,7830
10
+ plugwise/legacy/data.py,sha256=DsHR9xgiFDg_Vh_6ZpOskw8ZhNQ3CmwjstI3yiH6MEk,3048
11
+ plugwise/legacy/helper.py,sha256=6-tYQMEXepE5rec-hn6lt2EeknADI3J8UFuBSLgu8dk,17878
12
+ plugwise/legacy/smile.py,sha256=Jo0ynhhxfOC6NGemgVNN-LBwUWmA736lHTO5WXQ65r4,10492
13
+ plugwise-0.37.7.dist-info/LICENSE,sha256=mL22BjmXtg_wnoDnnaqps5_Bg_VGj_yHueX5lsKwbCc,1144
14
+ plugwise-0.37.7.dist-info/METADATA,sha256=dEKKzLXlGY7SRjbD2PXNGMPG17dN-7g4faHxtM_lmwI,8939
15
+ plugwise-0.37.7.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
16
+ plugwise-0.37.7.dist-info/top_level.txt,sha256=MYOmktMFf8ZmX6_OE1y9MoCZFfY-L8DA0F2tA2IvE4s,9
17
+ plugwise-0.37.7.dist-info/RECORD,,
@@ -1,17 +0,0 @@
1
- plugwise/__init__.py,sha256=6htaD3AMGbtiuI30EJX9D6gy-gYjA9Lfdl_GZ7Syzmw,13796
2
- plugwise/common.py,sha256=P4sUYzgVcFsIR2DmQxeVeOiZvFZWpZXgwHA3XRc1Sx0,12538
3
- plugwise/constants.py,sha256=adi2UFHJUcPRWNz1bn155NC9DLzhYbeNriMKTpSyWAg,16574
4
- plugwise/data.py,sha256=OTkPJ80b1L1h_TeddfvmUPX1-jBacTjbHKD0XgUIfeg,8965
5
- plugwise/exceptions.py,sha256=rymGtWnXosdFkzSehc_41HVl2jXJEg_9Hq9APDEUwrk,1223
6
- plugwise/helper.py,sha256=bNT_Tdc0LAxmVZEikMgKm461jeGP-WY-cI1r3JGgIcI,44037
7
- plugwise/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- plugwise/smile.py,sha256=iGaA_1pWHeFUSK4k_jAS1qV7Bm3hDJsIDAj2GSixyms,17177
9
- plugwise/util.py,sha256=w24CiW-xSV3ARZAeNYZ9T5C4kJpYGCcBoIlX2BhNOYg,6618
10
- plugwise/legacy/data.py,sha256=y7gBG9Sg40lltB3B5CpWPtAXggMbcx2IMay-mwvOB1M,2986
11
- plugwise/legacy/helper.py,sha256=tSrqA2UeMO7HSYudQHAxBThBTevAOKIxHqN6NCN-r5k,18947
12
- plugwise/legacy/smile.py,sha256=ltg9XQBPSCCZV4QnqSZ3e9Ta1Zkdqs8MwQJgyyavg3A,10250
13
- plugwise-0.37.5.dist-info/LICENSE,sha256=mL22BjmXtg_wnoDnnaqps5_Bg_VGj_yHueX5lsKwbCc,1144
14
- plugwise-0.37.5.dist-info/METADATA,sha256=ho502aS821_t2yuQA8C2fvAyqPSaFfRJaEq5NHyQEWQ,8939
15
- plugwise-0.37.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
16
- plugwise-0.37.5.dist-info/top_level.txt,sha256=MYOmktMFf8ZmX6_OE1y9MoCZFfY-L8DA0F2tA2IvE4s,9
17
- plugwise-0.37.5.dist-info/RECORD,,