Skip to content

Commit 1ede539

Browse files
authored
Merge pull request #159 from plugwise/check_for_adam
Anna: add check for connected to Adam
2 parents 79b64b0 + 31bdcac commit 1ede539

File tree

11 files changed

+2107
-57
lines changed

11 files changed

+2107
-57
lines changed

CHANGELOG.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,33 @@
11
# Changelog
22

3-
# v0.xy.z - Smile: more changes and improvements
3+
# v0.16.7 - Smile: Bugfixes, more changes and improvements
4+
- Fix for #158: error setting up for systems with an Anna and and Elga (heatpump).
5+
- Block connecting to the Anna when an Adam is present (fixes pw-beta #231).
46
- Combine helper-functions, possible after removing code related to the device_state sensor.
57
- Remove single_master_thermostat() function and the related self's, no longer needed.
68
- Use .get() where possible.
79
- Implement walrus constructs ( := ) where possible.
810
- Improve and simplify.
911

1012
# v0.16.6 - Smile: various changes/improvements
11-
- Provide cooling_state and heating_state as `binary_sensors`, show cooling_state only when cooling is present
12-
- Clean up gw_data, e.g. remove `single_master_thermostat` key
13+
- Provide cooling_state and heating_state as `binary_sensors`, show cooling_state only when cooling is present.
14+
- Clean up gw_data, e.g. remove `single_master_thermostat` key.
1315

1416
# v0.16.5 - Smile: small improvements
15-
- Move schedule debug-message to the correct position
16-
- Code quality fixes
17+
- Move schedule debug-message to the correct position.
18+
- Code quality fixes.
1719

1820
# v0.16.4 - Adding measurements
19-
- Expose mac-addresses for network and zigbee devices
20-
- Expose min/max thermostat (and heater) values and resolution (step in HA)
21-
- Changed mac-addresses in userdata/fixtures to be obfuscated but unique
21+
- Expose mac-addresses for network and zigbee devices.
22+
- Expose min/max thermostat (and heater) values and resolution (step in HA).
23+
- Changed mac-addresses in userdata/fixtures to be obfuscated but unique.
2224

2325
# v0.16.3 - Typing
24-
- Code quality improvements
26+
- Code quality improvements.
2527

2628
# v0.16.2 - Generic and Stretch
27-
- As per Core deprecation of python 3.8, removed CI/CD testing and bumped pypi to 3.9 and production
28-
- Add support for Stretch with fw 2.7.18
29+
- As per Core deprecation of python 3.8, removed CI/CD testing and bumped pypi to 3.9 and production.
30+
- Add support for Stretch with fw 2.7.18.
2931

3032
# v0.16.1 - Smile - various updates:
3133
- BREAKING: Change active device detection, detect both OpenTherm (replace Auxiliary) and OnOff (new) heating and cooling devices.

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.16.7a6"
3+
__version__ = "0.16.7"
44

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

plugwise/constants.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,12 @@
203203
"120006": "Sense Legrand",
204204
"070051": "Switch",
205205
"080029": "Switch",
206-
"168-01": "Jip",
207-
"160-01": "Plug",
206+
"143.1": "Anna",
207+
"159.2": "Adam",
208208
"106-03": "Tom/Floor",
209209
"158-01": "Lisa",
210-
"143.1": "Anna",
210+
"160-01": "Plug",
211+
"168-01": "Jip",
211212
}
212213

213214
# Defaults for SED's (Sleeping End Devices)

plugwise/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ class InvalidAuthentication(PlugwiseException):
5555
"""Raised when unable to authenticate."""
5656

5757

58+
class InvalidSetupError(PlugwiseException):
59+
"""Raised when adding an Anna while an Adam exists."""
60+
61+
5862
class UnsupportedDeviceError(PlugwiseException):
5963
"""Raised when device is not supported."""
6064

plugwise/helper.py

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ def _get_module_data(
418418
if (appl_search := appliance.find(locator)) is not None:
419419
link_id = appl_search.attrib["id"]
420420
locator = f".//{mod_type}[@id='{link_id}']...."
421+
# Not possible to walrus...
421422
module = self._modules.find(locator)
422423
if module is not None:
423424
model_data["contents"] = True
@@ -507,20 +508,19 @@ def _appliance_info_finder(self, appliance: etree, appl: Munch) -> Munch:
507508
return appl
508509

509510
if appl.pwclass == "heater_central":
510-
# Provide info for On-Off device
511+
# Remove heater_central when no active device present
512+
if not self._opentherm_device and not self._on_off_device:
513+
return None
514+
515+
self._heater_id = appliance.attrib["id"]
516+
# info for On-Off device
511517
if self._on_off_device:
512-
self._heater_id = appliance.attrib["id"]
513518
appl.name = "OnOff"
514519
appl.v_name = None
515520
appl.model = "Unknown"
516-
517521
return appl
518522

519-
# Remove heater_central when no active device present
520-
if not self._opentherm_device and not self._on_off_device:
521-
return None
522-
523-
self._heater_id = appliance.attrib["id"]
523+
# Obtain info for OpenTherm device
524524
appl.name = "OpenTherm"
525525
locator1 = "./logs/point_log[type='flame_state']/boiler_state"
526526
locator2 = "./services/boiler_state"
@@ -873,19 +873,6 @@ def _get_appliance_data(self, d_id: str) -> dict[str, Any]:
873873
if "temperature" in data:
874874
data.pop("heating_state", None)
875875

876-
# Anna: indicate possible active heating/cooling operation-mode
877-
# Actual ongoing heating/cooling is shown via heating_state/cooling_state
878-
if "cooling_activation_outdoor_temperature" in data:
879-
self._cooling_present = True
880-
if not self.cooling_active and self._outdoor_temp > data.get(
881-
"cooling_activation_outdoor_temperature"
882-
):
883-
self.cooling_active = True
884-
if self.cooling_active and self._outdoor_temp < data.get(
885-
"cooling_deactivation_threshold"
886-
):
887-
self.cooling_active = False
888-
889876
return data
890877

891878
def _rank_thermostat(

plugwise/smile.py

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131
SYSTEM,
3232
THERMOSTAT_CLASSES,
3333
)
34-
from .exceptions import ConnectionFailedError, InvalidXMLError, UnsupportedDeviceError
34+
from .exceptions import (
35+
ConnectionFailedError,
36+
InvalidSetupError,
37+
InvalidXMLError,
38+
UnsupportedDeviceError,
39+
)
3540
from .helper import SmileComm, SmileHelper, update_helper
3641

3742

@@ -108,9 +113,7 @@ def _device_data_switching_group(
108113
if member_data.get("relay"):
109114
counter += 1
110115

111-
device_data["relay"] = True
112-
if counter == 0:
113-
device_data["relay"] = False
116+
device_data["relay"] = counter != 0
114117

115118
return device_data
116119

@@ -124,9 +127,7 @@ def _device_data_adam(
124127
# Indicate heating_state based on valves being open in case of city-provided heating
125128
if details.get("class") == "heater_central":
126129
if self._on_off_device and self._heating_valves() is not None:
127-
device_data["heating_state"] = True
128-
if self._heating_valves() == 0:
129-
device_data["heating_state"] = False
130+
device_data["heating_state"] = self._heating_valves() != 0
130131

131132
return device_data
132133

@@ -156,21 +157,30 @@ def _device_data_climate(
156157
device_data["last_used"] = last_active
157158
device_data["schedule_temperature"] = sched_setpoint
158159

159-
# Operation mode: auto, heat, cool
160-
device_data["mode"] = "auto"
161-
schedule_status = False
162-
if sel_schema != "None":
163-
schedule_status = True
164-
if not schedule_status:
165-
device_data["mode"] = "heat"
166-
if self._heater_id is not None:
167-
if self.cooling_active:
168-
device_data["mode"] = "cool"
169-
170160
# Control_state, only for Adam master thermostats
171161
if ctrl_state := self._control_state(loc_id):
172162
device_data["control_state"] = ctrl_state
173163

164+
# Anna: indicate possible active heating/cooling operation-mode
165+
# Actual ongoing heating/cooling is shown via heating_state/cooling_state
166+
if "cooling_activation_outdoor_temperature" in device_data:
167+
self._cooling_present = True
168+
if not self.cooling_active and self._outdoor_temp > device_data.get(
169+
"cooling_activation_outdoor_temperature"
170+
):
171+
self.cooling_active = True
172+
if self.cooling_active and self._outdoor_temp < device_data.get(
173+
"cooling_deactivation_threshold"
174+
):
175+
self.cooling_active = False
176+
177+
# Operation mode: auto, heat, cool
178+
device_data["mode"] = "auto"
179+
if sel_schema == "None":
180+
device_data["mode"] = "heat"
181+
if self._heater_id is not None and self.cooling_active:
182+
device_data["mode"] = "cool"
183+
174184
return device_data
175185

176186
def _get_device_data(self, dev_id: str) -> dict[str, Any]:
@@ -242,14 +252,18 @@ async def connect(self) -> bool:
242252
"""Connect to Plugwise device and determine its name, type and version."""
243253
result = await self._request(DOMAIN_OBJECTS)
244254
vendor_names: list[etree] = result.findall("./module/vendor_name")
255+
vendor_models: list[etree] = result.findall("./module/vendor_model")
256+
# Work-around for Stretch fv 2.7.18
245257
if not vendor_names:
246-
# Work-around for Stretch fv 2.7.18
247258
result = await self._request(MODULES)
248259
vendor_names = result.findall("./module/vendor_name")
249260

250261
names: list[str] = []
262+
models: list[str] = []
251263
for name in vendor_names:
252264
names.append(name.text)
265+
for model in vendor_models:
266+
models.append(model.text)
253267

254268
dsmrmain = result.find("./module/protocols/dsmrmain")
255269
if "Plugwise" not in names:
@@ -262,6 +276,14 @@ async def connect(self) -> bool:
262276
)
263277
raise ConnectionFailedError
264278

279+
# Check if Anna is connected to an Adam
280+
if "159.2" in models:
281+
LOGGER.error(
282+
"Your Anna is connected to an Adam, make \
283+
sure to only add the Adam as integration.",
284+
)
285+
raise InvalidSetupError
286+
265287
# Determine smile specifics
266288
await self._smile_detect(result, dsmrmain)
267289

@@ -372,8 +394,7 @@ async def _smile_detect(self, result: etree, dsmrmain: etree) -> None:
372394
self._stretch_v2 = self.smile_version[1].major == 2
373395
self._stretch_v3 = self.smile_version[1].major == 3
374396

375-
if self.smile_type == "thermostat":
376-
self._is_thermostat = True
397+
self._is_thermostat = self.smile_type == "thermostat"
377398

378399
async def _full_update_device(self) -> None:
379400
"""Perform a first fetch of all XML data, needed for initialization."""

tests/test_smile.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,6 +2773,16 @@ async def test_fail_legacy_system(self):
27732773
except pw_exceptions.ConnectionFailedError:
27742774
assert True
27752775

2776+
@pytest.mark.asyncio
2777+
async def test_fail_anna_connected_to_adam(self):
2778+
"""Test erroneous legacy stretch system."""
2779+
self.smile_setup = "anna_connected_to_adam"
2780+
try:
2781+
_server, _smile, _client = await self.connect_wrapper()
2782+
assert False # pragma: no cover
2783+
except pw_exceptions.InvalidSetupError:
2784+
assert True
2785+
27762786
@pytest.mark.asyncio
27772787
async def test_invalid_credentials(self):
27782788
"""Test P1 with invalid credentials setup."""

0 commit comments

Comments
 (0)