Skip to content

Commit 307c5a7

Browse files
authored
Merge pull request #212 from plugwise/P1_2_devices
Change to 2 devices for P1
2 parents 5c45830 + 8f64111 commit 307c5a7

File tree

6 files changed

+298
-164
lines changed

6 files changed

+298
-164
lines changed

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
# Changelog
22

3+
# v0.22.0: Smile P1 - create an additional P1 device, representing the smartmeter
4+
- Change all gateway model names to Gateway
5+
- Change Anna Smile name to Smile Anna, Anna model name to ThermoTouch
6+
- Change P1 Smile name to Smile P1
7+
- Remove raise error-message, change priority of logger messages to less critical
8+
- Fix for issue #213
9+
310
# v0.21.3: Revert all hvac_mode HEAT_COOL related
411
- The Anna-Elga usecase, providing a heating and a cooling setpoint, was reverted back to providing a single setpoint.
512

613
# v0.21.2: Code improvements, cleanup
714

815
# v0.21.1: Smile: various updates % fixes
9-
- Change Anna-gateway name to Smile - related to https://developers.home-assistant.io/blog/2022/07/10/entity_naming/ and changes in the Core Plugwise(-beta) code.
16+
- Change Anna-gateway model to Smile - related to https://developers.home-assistant.io/blog/2022/07/10/entity_naming/ and changes in the Core Plugwise(-beta) code.
1017
- Output elga_cooling_enabled, lortherm_cooling_enabled or adam_cooling_enabled when applicable. To be used in Core Plugwise(-beta) instead of calling api-variables.
1118
- Protect the self-variables that will no longer be used in Core Plugwise(-beta).
1219
- pyproject.toml updates.

plugwise/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Plugwise module."""
22

3-
__version__ = "0.21.3"
3+
__version__ = "0.22.0a"
44

55
from plugwise.smile import Smile
66
from plugwise.stick import Stick

plugwise/constants.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@
211211
"120006": "Sense Legrand",
212212
"070051": "Switch",
213213
"080029": "Switch",
214-
"143.1": "Anna",
214+
"143.1": "ThermoTouch",
215215
"159.2": "Adam",
216216
"106-03": "Tom/Floor",
217217
"158-01": "Lisa",
@@ -389,6 +389,7 @@
389389
DEFAULT_USERNAME: Final = "smile"
390390
DEFAULT_PORT: Final = 80
391391
NONE: Final = "None"
392+
FAKE_APPL: Final = "aaaa0000aaaa0000aaaa0000aaaa00aa"
392393
FAKE_LOC: Final = "0000aaaa0000aaaa0000aaaa0000aa00"
393394
LIMITS: Final[tuple[str, ...]] = (
394395
"setpoint",
@@ -499,14 +500,14 @@
499500
# Known types of Smiles and Stretches
500501
SMILE = namedtuple("SMILE", "smile_type smile_name")
501502
SMILES: Final[dict[str, SMILE]] = {
502-
"smile_v2": SMILE("power", "P1"),
503-
"smile_v3": SMILE("power", "P1"),
504-
"smile_v4": SMILE("power", "P1"),
503+
"smile_v2": SMILE("power", "Smile P1"),
504+
"smile_v3": SMILE("power", "Smile P1"),
505+
"smile_v4": SMILE("power", "Smile P1"),
505506
"smile_open_therm_v2": SMILE("thermostat", "Adam"),
506507
"smile_open_therm_v3": SMILE("thermostat", "Adam"),
507-
"smile_thermo_v1": SMILE("thermostat", "Smile"),
508-
"smile_thermo_v3": SMILE("thermostat", "Smile"),
509-
"smile_thermo_v4": SMILE("thermostat", "Smile"),
508+
"smile_thermo_v1": SMILE("thermostat", "Smile Anna"),
509+
"smile_thermo_v3": SMILE("thermostat", "Smile Anna"),
510+
"smile_thermo_v4": SMILE("thermostat", "Smile Anna"),
510511
"stretch_v2": SMILE("stretch", "Stretch"),
511512
"stretch_v3": SMILE("stretch", "Stretch"),
512513
}
@@ -590,7 +591,7 @@ class GatewayData(TypedDict, total=False):
590591
"""The Gateway Data class."""
591592

592593
smile_name: str
593-
gateway_id: str
594+
gateway_id: str | None
594595
heater_id: str | None
595596
cooling_present: bool
596597
notifications: dict[str, str]

plugwise/helper.py

Lines changed: 92 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
DEVICE_MEASUREMENTS,
2929
ENERGY_KILO_WATT_HOUR,
3030
ENERGY_WATT_HOUR,
31+
FAKE_APPL,
3132
FAKE_LOC,
3233
HEATER_CENTRAL_MEASUREMENTS,
3334
LIMITS,
@@ -90,9 +91,7 @@ def update_helper(
9091

9192
def check_model(name: str | None, vendor_name: str | None) -> str | None:
9293
"""Model checking before using version_to_model."""
93-
if vendor_name in ["Plugwise", "Plugwise B.V."]:
94-
if name == "ThermoTouch":
95-
return "Anna"
94+
if vendor_name == "Plugwise":
9695
if (model := version_to_model(name)) != "Unknown":
9796
return model
9897
return name
@@ -353,12 +352,13 @@ def __init__(self) -> None:
353352
self._lortherm_cooling_active = False
354353
self._lortherm_cooling_enabled = False
355354

356-
self.gateway_id: str
355+
self.gateway_id: str | None = None
357356
self.gw_data: GatewayData = {}
358357
self.gw_devices: dict[str, DeviceData] = {}
359358
self.smile_fw_version: str | None = None
360359
self.smile_hw_version: str | None = None
361360
self.smile_mac_address: str | None = None
361+
self.smile_model: str
362362
self.smile_name: str
363363
self.smile_type: str
364364
self.smile_version: tuple[str, VersionInfo]
@@ -445,6 +445,8 @@ def _get_module_data(
445445
if module is not None: # pylint: disable=consider-using-assignment-expr
446446
model_data["contents"] = True
447447
model_data["vendor_name"] = module.find("vendor_name").text
448+
if model_data["vendor_name"] == "Plugwise B.V.":
449+
model_data["vendor_name"] = "Plugwise"
448450
model_data["vendor_model"] = module.find("vendor_model").text
449451
model_data["hardware_version"] = module.find("hardware_version").text
450452
model_data["firmware_version"] = module.find("firmware_version").text
@@ -464,18 +466,21 @@ def _energy_device_info_finder(self, appliance: etree, appl: Munch) -> Munch | N
464466
"""Helper-function for _appliance_info_finder().
465467
Collect energy device info (Circle, Plug, Stealth): firmware, model and vendor name.
466468
"""
467-
if self.smile_type == "stretch":
469+
if self.smile_type in ("power", "stretch"):
468470
locator = "./services/electricity_point_meter"
471+
if not self._smile_legacy:
472+
locator = "./logs/point_log/electricity_point_meter"
469473
mod_type = "electricity_point_meter"
470474

471475
module_data = self._get_module_data(appliance, locator, mod_type)
472476
# Filter appliance without zigbee_mac, it's an orphaned device
473477
appl.zigbee_mac = module_data["zigbee_mac_address"]
474-
if appl.zigbee_mac is None:
478+
if appl.zigbee_mac is None and self.smile_type != "power":
475479
return None
476480

477-
appl.vendor_name = module_data["vendor_name"]
478481
appl.hardware = module_data["hardware_version"]
482+
appl.model = module_data["vendor_model"]
483+
appl.vendor_name = module_data["vendor_name"]
479484
if appl.hardware is not None:
480485
hw_version = appl.hardware.replace("-", "")
481486
appl.model = version_to_model(hw_version)
@@ -506,10 +511,12 @@ def _appliance_info_finder(self, appliance: etree, appl: Munch) -> Munch:
506511
# Collect gateway device info
507512
if appl.pwclass == "gateway":
508513
self.gateway_id = appliance.attrib["id"]
509-
appl.fw = self.smile_fw_version
514+
appl.firmware = self.smile_fw_version
515+
appl.hardware = self.smile_hw_version
510516
appl.mac = self.smile_mac_address
511-
appl.model = appl.name = self.smile_name
512-
appl.vendor_name = "Plugwise B.V."
517+
appl.model = self.smile_model
518+
appl.name = self.smile_name
519+
appl.vendor_name = "Plugwise"
513520

514521
# Adam: look for the ZigBee MAC address of the Smile
515522
if self.smile_name == "Adam" and (
@@ -580,54 +587,74 @@ def _appliance_info_finder(self, appliance: etree, appl: Munch) -> Munch:
580587

581588
return appl
582589

583-
def _all_appliances(self) -> None:
584-
"""Collect all appliances with relevant info."""
585-
self._all_locations()
590+
def _p1_smartmeter_info_finder(self, appl: Munch) -> Munch:
591+
"""Collect P1 DSMR Smartmeter info."""
592+
loc_id = next(iter(self._loc_data.keys()))
593+
appl.dev_id = self.gateway_id
594+
appl.location = loc_id
595+
if self._smile_legacy:
596+
appl.dev_id = loc_id
597+
appl.mac = None
598+
appl.model = self.smile_model
599+
appl.name = "P1"
600+
appl.pwclass = "smartmeter"
601+
appl.zigbee_mac = None
602+
location = self._locations.find(f'./location[@id="{loc_id}"]')
603+
appl = self._energy_device_info_finder(location, appl)
604+
605+
self._appl_data[appl.dev_id] = {"dev_class": appl.pwclass}
606+
607+
for key, value in {
608+
"firmware": appl.firmware,
609+
"hardware": appl.hardware,
610+
"location": appl.location,
611+
"mac_address": appl.mac,
612+
"model": appl.model,
613+
"name": appl.name,
614+
"zigbee_mac_address": appl.zigbee_mac,
615+
"vendor": appl.vendor_name,
616+
}.items():
617+
if value is not None or key == "location":
618+
self._appl_data[appl.dev_id].update({key: value}) # type: ignore[misc]
586619

587-
# Create a gateway for legacy Anna, P1 and Stretches
588-
# and inject a home_location as device id for legacy so
589-
# appl_data can use the location id as device id, where needed.
620+
return appl
621+
622+
def _create_legacy_gateway(self) -> None:
623+
"""Create the (missing) gateway devices for legacy Anna, P1 and Stretch.
624+
625+
Use the home_location or FAKE_APPL as device id.
626+
"""
590627
if self._smile_legacy:
591628
self.gateway_id = self._home_location
592-
self._appl_data[self._home_location] = {
593-
"dev_class": "gateway",
629+
if self.smile_type == "power":
630+
self.gateway_id = FAKE_APPL
631+
632+
self._appl_data[self.gateway_id] = {"dev_class": "gateway"}
633+
for key, value in {
594634
"firmware": self.smile_fw_version,
595635
"location": self._home_location,
596-
}
597-
if self.smile_mac_address is not None:
598-
self._appl_data[self._home_location].update(
599-
{"mac_address": self.smile_mac_address}
600-
)
636+
"mac_address": self.smile_mac_address,
637+
"model": self.smile_model,
638+
"name": self.smile_name,
639+
"zigbee_mac_address": self.smile_zigbee_mac_address,
640+
"vendor": "Plugwise",
641+
}.items():
642+
if value is not None:
643+
self._appl_data[self.gateway_id].update({key: value}) # type: ignore[misc]
601644

602645
if self.smile_type == "power":
603-
self._appl_data[self._home_location].update(
604-
{
605-
"model": "P1",
606-
"name": "P1",
607-
"vendor": "Plugwise B.V.",
608-
}
609-
)
610-
# legacy p1 has no more devices
611-
return
646+
# For legacy P1 collect the connected SmartMeter info
647+
appl = Munch()
648+
appl = self._p1_smartmeter_info_finder(appl)
612649

613-
if self.smile_type == "thermostat":
614-
self._appl_data[self._home_location].update(
615-
{
616-
"model": "Smile",
617-
"name": "Smile",
618-
"vendor": "Plugwise B.V.",
619-
}
620-
)
650+
def _all_appliances(self) -> None:
651+
"""Collect all appliances with relevant info."""
652+
self._all_locations()
621653

622-
if self.smile_type == "stretch":
623-
self._appl_data[self._home_location].update(
624-
{
625-
"model": "Stretch",
626-
"name": "Stretch",
627-
"vendor": "Plugwise B.V.",
628-
"zigbee_mac_address": self.smile_zigbee_mac_address,
629-
}
630-
)
654+
self._create_legacy_gateway()
655+
# Legacy P1 has no more devices
656+
if self._smile_legacy and self.smile_type == "power":
657+
return
631658

632659
for appliance in self._appliances.findall("./appliance"):
633660
appl = Munch()
@@ -661,9 +688,10 @@ def _all_appliances(self) -> None:
661688
if (appl := self._appliance_info_finder(appliance, appl)) is None:
662689
continue
663690

664-
if appl.pwclass == "gateway":
665-
appl.firmware = self.smile_fw_version
666-
appl.hardware = self.smile_hw_version
691+
# P1: for gateway and smartmeter switch device_id - part 1
692+
# This is done to avoid breakage in HA Core
693+
if appl.pwclass == "gateway" and self.smile_type == "power":
694+
appl.dev_id = appl.location
667695

668696
# Don't show orphaned non-legacy thermostat-types.
669697
if (
@@ -674,7 +702,6 @@ def _all_appliances(self) -> None:
674702
continue
675703

676704
self._appl_data[appl.dev_id] = {"dev_class": appl.pwclass}
677-
678705
for key, value in {
679706
"firmware": appl.firmware,
680707
"hardware": appl.hardware,
@@ -688,6 +715,16 @@ def _all_appliances(self) -> None:
688715
if value is not None or key == "location":
689716
self._appl_data[appl.dev_id].update({key: value}) # type: ignore[misc]
690717

718+
# For non-legacy P1 collect the connected SmartMeter info
719+
if self.smile_type == "power":
720+
appl = self._p1_smartmeter_info_finder(appl)
721+
# P1: for gateway and smartmeter switch device_id - part 2
722+
for item in self._appl_data:
723+
if item != self.gateway_id:
724+
self.gateway_id = item
725+
# Leave for-loop to avoid a 2nd switch
726+
break
727+
691728
def _match_locations(self) -> dict[str, ThermoLoc]:
692729
"""Helper-function for _scan_thermostats().
693730
Match appliances with locations.
@@ -887,7 +924,7 @@ def _get_appliance_data(self, d_id: str) -> DeviceData:
887924
if d_id == self._heater_id:
888925
if self._adam_cooling_enabled:
889926
data["adam_cooling_enabled"] = self._adam_cooling_enabled
890-
if self.smile_name == "Smile":
927+
if self.smile_name == "Smile Anna":
891928
# Use elga_status_code or cooling_state to set the relevant *_cooling_enabled to True
892929
if not self._anna_cooling_present:
893930
pass
@@ -990,7 +1027,7 @@ def _group_switches(self) -> dict[str, ApplianceData]:
9901027
"""
9911028
switch_groups: dict[str, ApplianceData] = {}
9921029
# P1 and Anna don't have switchgroups
993-
if self.smile_type == "power" or self.smile_name == "Smile":
1030+
if self.smile_type == "power" or self.smile_name == "Smile Anna":
9941031
return switch_groups
9951032

9961033
for group in self._domain_objects.findall("./group"):
@@ -1076,9 +1113,6 @@ def _power_data_from_location(self, loc_id: str) -> DeviceData:
10761113
direct_data: DeviceData = {}
10771114
loc = Munch()
10781115

1079-
if self.smile_type != "power":
1080-
return {}
1081-
10821116
search = self._locations
10831117
log_list: list[str] = ["point_log", "cumulative_log", "interval_log"]
10841118
peak_list: list[str] = ["nl_peak", "nl_offpeak"]

plugwise/smile.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def update_for_cooling(self, devices: dict[str, DeviceData]) -> None:
5151
"""Helper-function for adding/updating various cooling-related values."""
5252
for _, device in devices.items():
5353
# For Anna + cooling, modify cooling_state based on provided info by Plugwise
54-
if self.smile_name == "Smile":
54+
if self.smile_name == "Smile Anna":
5555
if device["dev_class"] == "heater_central" and self._cooling_present:
5656
device["binary_sensors"]["cooling_state"] = False
5757
if self._elga_cooling_active or self._lortherm_cooling_active:
@@ -119,7 +119,7 @@ def get_all_devices(self) -> None:
119119
search = self._domain_objects
120120
self._anna_cooling_present = adam_cooling_present = False
121121
if search.find(locator_1) is not None:
122-
if self.smile_name == "Smile":
122+
if self.smile_name == "Smile Anna":
123123
self._anna_cooling_present = True
124124
else:
125125
adam_cooling_present = True
@@ -247,6 +247,7 @@ def _get_device_data(self, dev_id: str) -> DeviceData:
247247
# Show the allowed regulation modes
248248
device_data["regulation_modes"] = self._allowed_modes
249249

250+
if details["dev_class"] == "smartmeter":
250251
# Get P1 data from LOCATIONS
251252
if (
252253
power_data := self._power_data_from_location(details["location"])
@@ -419,11 +420,12 @@ async def _smile_detect(self, result: etree, dsmrmain: etree) -> None:
419420
)
420421
raise UnsupportedDeviceError
421422

423+
self.smile_model = "Gateway"
422424
self.smile_name = SMILES[target_smile].smile_name
423425
self.smile_type = SMILES[target_smile].smile_type
424426
self.smile_version = (self.smile_fw_version, ver)
425427

426-
if target_smile in ["smile_thermo_v1", "smile_v2", "stretch_v3", "stretch_v2"]:
428+
if target_smile in ("smile_thermo_v1", "smile_v2", "stretch_v3", "stretch_v2"):
427429
self._smile_legacy = True
428430

429431
if self.smile_type == "stretch":

0 commit comments

Comments
 (0)