Skip to content

Commit 3517e30

Browse files
authored
Merge pull request #809 from plugwise/anna_p1
Add support for Anna P1
2 parents e11a809 + 222e6cd commit 3517e30

File tree

13 files changed

+1855
-60
lines changed

13 files changed

+1855
-60
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
## v1.9.0
4+
5+
- Add support for Anna P1 via PR [#809](https://github.com/plugwise/python-plugwise/pull/809)
6+
37
## v1.8.3
48

59
- Remove storing the last active schedule(s) via PR [#806](https://github.com/plugwise/python-plugwise/pull/806), to be handled by the HA Integration

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,15 @@ Module providing interfacing with the Plugwise devices:
6262
### Smile
6363

6464
- [x] Adam
65-
- [x] Emma (only tested as ZigBee device connected to Adam)
65+
- [x] Emma
6666
- [x] Jip
6767
- [x] Lisa
6868
- [x] Tom/Floor
6969
- [x] Koen (a Koen always comes with a Plug, the Plug is the active part)
7070
- [x] Plug
7171
- [x] Aqara Plug
7272
- [x] Anna (v1.8 and later firmware versions)
73-
- [ ] Anna P1
73+
- [x] Anna P1
7474
- [x] Smile P1 (v2.0 and later firmware versions)
7575
- [x] Stretch (only with Circles, please help out with other devices)
7676

fixtures/anna_p1/data.json

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
{
2+
"1e5e55b958ac445583602f767cb45942": {
3+
"active_preset": "home",
4+
"available_schedules": ["Thermostat schedule", "off"],
5+
"climate_mode": "heat",
6+
"control_state": "idle",
7+
"dev_class": "thermostat",
8+
"firmware": "2018-02-08T11:15:53+01:00",
9+
"hardware": "6539-1301-500",
10+
"location": "5b13651d79c4454684fd268850b1bff8",
11+
"model": "ThermoTouch",
12+
"name": "Anna",
13+
"preset_modes": ["home", "asleep", "away", "vacation", "no_frost"],
14+
"select_schedule": "off",
15+
"sensors": {
16+
"illuminance": 2.0,
17+
"setpoint": 19.0,
18+
"temperature": 19.4
19+
},
20+
"temperature_offset": {
21+
"lower_bound": -2.0,
22+
"resolution": 0.1,
23+
"setpoint": 0.0,
24+
"upper_bound": 2.0
25+
},
26+
"thermostat": {
27+
"lower_bound": 4.0,
28+
"resolution": 0.1,
29+
"setpoint": 19.0,
30+
"upper_bound": 30.0
31+
},
32+
"vendor": "Plugwise"
33+
},
34+
"36b937e44ad145bab165fa0fe99d742d": {
35+
"available": true,
36+
"binary_sensors": {
37+
"dhw_state": false,
38+
"flame_state": false,
39+
"heating_state": false
40+
},
41+
"dev_class": "heater_central",
42+
"location": "da7be222ab3b420c927f3e49fade0304",
43+
"model": "Generic heater",
44+
"model_id": "HR24",
45+
"name": "OpenTherm",
46+
"sensors": {
47+
"intended_boiler_temperature": 0.0,
48+
"modulation_level": 0.0,
49+
"water_pressure": 6.0,
50+
"water_temperature": 35.0
51+
},
52+
"switches": {
53+
"dhw_cm_switch": true
54+
},
55+
"vendor": "Intergas"
56+
},
57+
"53130847be2f436cb946b78dedb9053a": {
58+
"binary_sensors": {
59+
"plugwise_notification": false
60+
},
61+
"dev_class": "gateway",
62+
"firmware": "4.4.4",
63+
"hardware": "AME Smile 2.0 board",
64+
"location": "da7be222ab3b420c927f3e49fade0304",
65+
"mac_address": "C493000ABCD",
66+
"model": "Gateway",
67+
"model_id": "smile_thermo",
68+
"name": "Smile Anna P1",
69+
"notifications": {},
70+
"sensors": {
71+
"outdoor_temperature": 11.8
72+
},
73+
"vendor": "Plugwise"
74+
},
75+
"da7be222ab3b420c927f3e49fade0304": {
76+
"available": true,
77+
"dev_class": "smartmeter",
78+
"location": "da7be222ab3b420c927f3e49fade0304",
79+
"model": "2MS212 SMR5.5",
80+
"name": "P1",
81+
"sensors": {
82+
"electricity_consumed_off_peak_cumulative": 618.001,
83+
"electricity_consumed_off_peak_interval": 7,
84+
"electricity_consumed_off_peak_point": 393,
85+
"electricity_consumed_peak_cumulative": 576.014,
86+
"electricity_consumed_peak_interval": 0,
87+
"electricity_consumed_peak_point": 0,
88+
"electricity_phase_one_consumed": 393,
89+
"electricity_phase_one_produced": 0,
90+
"electricity_produced_off_peak_cumulative": 246.504,
91+
"electricity_produced_off_peak_interval": 0,
92+
"electricity_produced_off_peak_point": 0,
93+
"electricity_produced_peak_cumulative": 709.442,
94+
"electricity_produced_peak_interval": 0,
95+
"electricity_produced_peak_point": 0,
96+
"gas_consumed_cumulative": 25.37,
97+
"gas_consumed_interval": 0.01,
98+
"net_electricity_cumulative": 238.069,
99+
"net_electricity_point": 393,
100+
"voltage_phase_one": 234.6
101+
},
102+
"vendor": "SAGEM"
103+
}
104+
}

plugwise/__init__.py

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def __init__(
7575
self._stretch_v2 = False
7676
self._target_smile: str = NONE
7777
self.smile: Munch = Munch()
78+
self.smile.anna_p1 = False
7879
self.smile.hostname = NONE
7980
self.smile.hw_version = None
8081
self.smile.legacy = False
@@ -189,13 +190,23 @@ async def _smile_detect(
189190
"""
190191
model: str = "Unknown"
191192
if (gateway := result.find("./gateway")) is not None:
192-
if (v_model := gateway.find("vendor_model")) is not None:
193-
model = v_model.text
194193
self.smile.version = parse(gateway.find("firmware_version").text)
195194
self.smile.hw_version = gateway.find("hardware_version").text
196195
self.smile.hostname = gateway.find("hostname").text
197196
self.smile.mac_address = gateway.find("mac_address").text
198-
self.smile.model_id = gateway.find("vendor_model").text
197+
if (vendor_model := gateway.find("vendor_model")) is None:
198+
return # pragma: no cover
199+
200+
model = vendor_model.text
201+
elec_measurement = gateway.find(
202+
"gateway_environment/electricity_consumption_tariff_structure"
203+
)
204+
if (
205+
elec_measurement is not None
206+
and elec_measurement.text
207+
and model == "smile_thermo"
208+
):
209+
self.smile.anna_p1 = True
199210
else:
200211
model = await self._smile_detect_legacy(result, dsmrmain, model)
201212

@@ -231,29 +242,38 @@ async def _smile_detect(
231242
raise UnsupportedDeviceError # pragma: no cover
232243

233244
self.smile.model = "Gateway"
245+
self.smile.model_id = model
234246
self.smile.name = SMILES[self._target_smile].smile_name
235247
self.smile.type = SMILES[self._target_smile].smile_type
248+
if self.smile.name == "Smile Anna" and self.smile.anna_p1:
249+
self.smile.name = "Smile Anna P1"
236250

237251
if self.smile.type == "stretch":
238252
self._stretch_v2 = int(version_major) == 2
239253

240-
if self.smile.type == "thermostat":
241-
self._is_thermostat = True
242-
# For Adam, Anna, determine the system capabilities:
243-
# Find the connected heating/cooling device (heater_central),
244-
# e.g. heat-pump or gas-fired heater
245-
onoff_boiler = result.find("./module/protocols/onoff_boiler")
246-
open_therm_boiler = result.find("./module/protocols/open_therm_boiler")
247-
self._on_off_device = onoff_boiler is not None
248-
self._opentherm_device = open_therm_boiler is not None
249-
250-
# Determine the presence of special features
251-
locator_1 = "./gateway/features/cooling"
252-
locator_2 = "./gateway/features/elga_support"
253-
if result.find(locator_1) is not None:
254-
self._cooling_present = True
255-
if result.find(locator_2) is not None:
256-
self._elga = True
254+
self._process_for_thermostat(result)
255+
256+
def _process_for_thermostat(self, result: etree.Element) -> None:
257+
"""Extra processing for thermostats."""
258+
if self.smile.type != "thermostat":
259+
return
260+
261+
self._is_thermostat = True
262+
# For Adam, Anna, determine the system capabilities:
263+
# Find the connected heating/cooling device (heater_central),
264+
# e.g. heat-pump or gas-fired heater
265+
onoff_boiler = result.find("./module/protocols/onoff_boiler")
266+
open_therm_boiler = result.find("./module/protocols/open_therm_boiler")
267+
self._on_off_device = onoff_boiler is not None
268+
self._opentherm_device = open_therm_boiler is not None
269+
270+
# Determine the presence of special features
271+
locator_1 = "./gateway/features/cooling"
272+
locator_2 = "./gateway/features/elga_support"
273+
if result.find(locator_1) is not None:
274+
self._cooling_present = True
275+
if result.find(locator_2) is not None:
276+
self._elga = True
257277

258278
async def _smile_detect_legacy(
259279
self, result: etree.Element, dsmrmain: etree.Element, model: str

plugwise/common.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,11 @@ def heater_id(self) -> str:
6464
return self._heater_id
6565

6666
def check_name(self, name: str) -> bool:
67-
"""Helper-function checking the smile-name."""
68-
return bool(self.smile.name == name)
67+
"""Helper-function checking the smile-name.
68+
69+
20251101: modified for finding name = `Smile Anna` in `Smile Anna P1`.
70+
"""
71+
return bool(name in self.smile.name)
6972

7073
def _appl_heater_central_info(
7174
self,
@@ -98,9 +101,9 @@ def _appl_heater_central_info(
98101
# xml_1: appliance
99102
# xml_3: self._modules for legacy, self._domain_objects for actual
100103
xml_3 = return_valid(xml_3, self._domain_objects)
101-
module_data = self._get_module_data(xml_1, locator_1, xml_3)
104+
module_data = self._get_module_data(xml_1, locator_1, xml_2=xml_3)
102105
if not module_data["contents"]:
103-
module_data = self._get_module_data(xml_1, locator_2, xml_3)
106+
module_data = self._get_module_data(xml_1, locator_2, xml_2=xml_3)
104107
if not module_data["contents"]:
105108
self._heater_id = NONE
106109
return (
@@ -121,7 +124,7 @@ def _appl_thermostat_info(
121124
"""Helper-function for _appliance_info_finder()."""
122125
locator = "./logs/point_log[type='thermostat']/thermostat"
123126
xml_2 = return_valid(xml_2, self._domain_objects)
124-
module_data = self._get_module_data(xml_1, locator, xml_2)
127+
module_data = self._get_module_data(xml_1, locator, xml_2=xml_2)
125128
if not module_data["contents"]:
126129
return Munch() # no module-data present means the device has been removed
127130

@@ -239,7 +242,8 @@ def _get_module_data(
239242
self,
240243
xml_1: etree.Element,
241244
locator: str,
242-
xml_2: etree.Element = None,
245+
key: str | None = None,
246+
xml_2: etree.Element | None = None,
243247
legacy: bool = False,
244248
) -> ModuleData:
245249
"""Helper-function for _energy_device_info_finder() and _appliance_info_finder().
@@ -256,8 +260,11 @@ def _get_module_data(
256260
"zigbee_mac_address": None,
257261
}
258262

259-
if (appl_search := xml_1.find(locator)) is not None:
263+
for appl_search in xml_1.findall(locator):
260264
link_tag = appl_search.tag
265+
if key is not None and key not in link_tag:
266+
continue
267+
261268
link_id = appl_search.attrib["id"]
262269
loc = f".//services/{link_tag}[@id='{link_id}']...."
263270
# Not possible to walrus for some reason...
@@ -272,4 +279,6 @@ def _get_module_data(
272279
module_data["firmware_version"] = module.find("firmware_version").text
273280
get_zigbee_data(module, module_data, legacy)
274281

282+
break
283+
275284
return module_data

plugwise/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
ADAM: Final = "Adam"
3636
ANNA: Final = "Smile Anna"
37+
ANNA_P1: Final = "Smile Anna P1"
3738
DEFAULT_TIMEOUT: Final = 10
3839
DEFAULT_LEGACY_TIMEOUT: Final = 30
3940
DEFAULT_USERNAME: Final = "smile"

0 commit comments

Comments
 (0)