Skip to content

Commit 85da896

Browse files
authored
Merge pull request #69 from plugwise/add_lock_state
Add support for getting and setting the lock-state of Plugs, Circles, etc.
2 parents 01b3872 + 3cc214c commit 85da896

File tree

8 files changed

+86
-23
lines changed

8 files changed

+86
-23
lines changed

.github/workflows/verify.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
name: Latest commit
55

66
env:
7-
CACHE_VERSION: 3
7+
CACHE_VERSION: 2
88
DEFAULT_PYTHON: 3.9
99
PRE_COMMIT_HOME: ~/.cache/pre-commit
1010

CHANGELOG.md

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

3+
## 0.9.3 - Add lock-state switches
4+
5+
- Support for getting and setting the lock-state of Plugs-, Circles-, Stealth-switches. A set lock-state prevents a switch from being turned off.
6+
- There is no lock_state available for the following special Plugwise classes: `central heating pump` and `value actuator`
7+
38
## 0.9.2 - Smile Optimize
49

510
- Functions not called by the plugwise(-beta) code have been moved to helper.py in which they are part of the subclass SmileHelper

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.9.2"
3+
__version__ = "0.9.3"
44

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

plugwise/helper.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,6 @@ def appliance_data(self, dev_id):
518518
"""
519519
data = {}
520520
search = self._appliances
521-
522521
if self._smile_legacy and self.smile_type != "stretch":
523522
search = self._domain_objects
524523

@@ -531,6 +530,7 @@ def appliance_data(self, dev_id):
531530
**DEVICE_MEASUREMENTS,
532531
**HEATER_CENTRAL_MEASUREMENTS,
533532
}.items()
533+
534534
for measurement, attrs in measurements:
535535

536536
p_locator = (
@@ -565,9 +565,10 @@ def appliance_data(self, dev_id):
565565
if appliance.find(i_locator) is not None:
566566
name = f"{measurement}_interval"
567567
measure = appliance.find(i_locator).text
568-
569568
data[name] = format_measure(measure, ENERGY_WATT_HOUR)
570569

570+
data.update(self.get_lock_state(appliance))
571+
571572
# Fix for Adam + Anna: heating_state also present under Anna, remove
572573
if "temperature" in data:
573574
data.pop("heating_state", None)
@@ -914,3 +915,20 @@ def object_value(self, obj_type, obj_id, measurement):
914915
return val
915916

916917
return None
918+
919+
def get_lock_state(self, xml):
920+
"""Adam & Stretches: find relay switch lock state."""
921+
data = {}
922+
actuator = "actuator_functionalities"
923+
func_type = "relay_functionality"
924+
if self.smile_type == "stretch" and self.smile_version[1].major == 2:
925+
actuator = "actuators"
926+
func_type = "relay"
927+
appl_class = xml.find("type").text
928+
if appl_class not in ["central_heating_pump", "valve_actuator"]:
929+
locator = f".//{actuator}/{func_type}/lock"
930+
if xml.find(locator) is not None:
931+
measure = xml.find(locator).text
932+
data["lock"] = format_measure(measure, None)
933+
934+
return data

plugwise/smile.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -371,11 +371,17 @@ async def set_temperature(self, loc_id, temperature):
371371
async def set_switch_state(self, appl_id, members, model, state):
372372
"""Switch the Switch off/on."""
373373
actuator = "actuator_functionalities"
374-
func_type = "relay_functionality"
375374
device = "relay"
375+
func_type = "relay_functionality"
376+
func = "state"
376377
if model == "dhw_cm_switch":
377-
func_type = "toggle_functionality"
378378
device = "toggle"
379+
func_type = "toggle_functionality"
380+
381+
if model == "lock":
382+
func = "lock"
383+
state = "false" if state == "off" else "true"
384+
379385
stretch_v2 = self.smile_type == "stretch" and self.smile_version[1].major == 2
380386
if stretch_v2:
381387
actuator = "actuators"
@@ -399,8 +405,17 @@ async def set_switch_state(self, appl_id, members, model, state):
399405
uri = f"{APPLIANCES};id={appl_id}/{device};id={switch_id}"
400406
if stretch_v2:
401407
uri = f"{APPLIANCES};id={appl_id}/{device}"
402-
state = str(state)
403-
data = f"<{func_type}><state>{state}</state></{func_type}>"
408+
data = f"<{func_type}><{func}>{state}</{func}></{func_type}>"
409+
410+
if model == "relay":
411+
locator = f'appliance[@id="{appl_id}"]/{actuator}/{func_type}/lock'
412+
lock_state = self._appliances.find(locator).text
413+
print("Lock state: ", lock_state)
414+
# Don't bother switching a relay when the corresponding lock-state is true
415+
if lock_state == "true":
416+
return False
417+
await self.request(uri, method="put", data=data)
418+
return True
404419

405420
await self.request(uri, method="put", data=data)
406421
return True

plugwise/util.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,9 @@ def format_measure(measure, unit):
114114
elif abs(float(measure)) >= 100:
115115
measure = int(round(float(measure)))
116116
except ValueError:
117-
if measure == "on":
117+
if measure in ["on", "true"]:
118118
measure = True
119-
elif measure == "off":
119+
if measure in ["off", "false"]:
120120
measure = False
121121
return measure
122122

tests/test_smile.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -408,19 +408,18 @@ async def device_test(self, smile=pw_smile.Smile, testdata=None):
408408

409409
@pytest.mark.asyncio
410410
async def tinker_switch(
411-
self, smile, dev_id=None, members=None, model=None, unhappy=False
411+
self, smile, dev_id=None, members=None, model="relay", unhappy=False
412412
):
413413
"""Turn a Switch on and off to test functionality."""
414414
_LOGGER.info("Asserting modifying settings for switch devices:")
415415
_LOGGER.info("- Devices (%s):", dev_id)
416+
switch_change = False
416417
for new_state in [False, True, False]:
417418
_LOGGER.info("- Switching %s", new_state)
418419
try:
419420
switch_change = await smile.set_switch_state(
420421
dev_id, members, model, new_state
421422
)
422-
assert switch_change
423-
_LOGGER.info(" + worked as intended")
424423
except (
425424
pw_exceptions.ErrorSendingCommandError,
426425
pw_exceptions.ResponseError,
@@ -430,6 +429,7 @@ async def tinker_switch(
430429
else: # pragma: no cover
431430
_LOGGER.info(" - failed unexpectedly")
432431
raise self.UnexpectedError
432+
return switch_change
433433

434434
@pytest.mark.asyncio
435435
async def tinker_thermostat(self, smile, loc_id, good_schemas=None, unhappy=False):
@@ -941,6 +941,7 @@ async def test_connect_adam_plus_anna(self):
941941
# Plug MediaCenter
942942
"aa6b0002df0a46e1b1eb94beb61eddfe": {
943943
"electricity_consumed": 10.3,
944+
"lock": False,
944945
"relay": True,
945946
},
946947
}
@@ -967,7 +968,10 @@ async def test_connect_adam_plus_anna(self):
967968
await self.tinker_thermostat(
968969
smile, "009490cc2f674ce6b576863fbb64f867", good_schemas=["Weekschema"]
969970
)
970-
await self.tinker_switch(smile, "aa6b0002df0a46e1b1eb94beb61eddfe")
971+
switch_change = await self.tinker_switch(
972+
smile, "aa6b0002df0a46e1b1eb94beb61eddfe"
973+
)
974+
assert switch_change
971975
await smile.close_connection()
972976
await self.disconnect(server, client)
973977

@@ -978,9 +982,10 @@ async def test_connect_adam_plus_anna(self):
978982
good_schemas=["Weekschema"],
979983
unhappy=True,
980984
)
981-
await self.tinker_switch(
985+
switch_change = await self.tinker_switch(
982986
smile, "aa6b0002df0a46e1b1eb94beb61eddfe", unhappy=True
983987
)
988+
assert not switch_change
984989
await smile.close_connection()
985990
await self.disconnect(server, client)
986991

@@ -1009,15 +1014,24 @@ async def test_connect_adam_plus_anna_new(self):
10091014
await self.device_test(smile, testdata)
10101015
assert smile.active_device_present
10111016

1012-
await self.tinker_switch(
1017+
switch_change = await self.tinker_switch(
10131018
smile,
10141019
"b83f9f9758064c0fab4af6578cba4c6d",
10151020
["aa6b0002df0a46e1b1eb94beb61eddfe", "f2be121e4a9345ac83c6e99ed89a98be"],
10161021
)
1017-
await self.tinker_switch(
1022+
assert switch_change
1023+
switch_change = await self.tinker_switch(
10181024
smile, "2743216f626f43948deec1f7ab3b3d70", model="dhw_cm_switch"
10191025
)
1020-
1026+
assert switch_change
1027+
switch_change = await self.tinker_switch(
1028+
smile, "40ec6ebe67844b21914c4a5382a3f09f", model="lock"
1029+
)
1030+
assert switch_change
1031+
switch_change = await self.tinker_switch(
1032+
smile, "f2be121e4a9345ac83c6e99ed89a98be"
1033+
)
1034+
assert not switch_change
10211035
await smile.close_connection()
10221036
await self.disconnect(server, client)
10231037

@@ -1087,7 +1101,10 @@ async def test_connect_adam_zone_per_device(self):
10871101
await self.tinker_thermostat(
10881102
smile, "82fa13f017d240daa0d0ea1775420f24", good_schemas=["CV Jessie"]
10891103
)
1090-
await self.tinker_switch(smile, "675416a629f343c495449970e2ca37b5")
1104+
switch_change = await self.tinker_switch(
1105+
smile, "675416a629f343c495449970e2ca37b5"
1106+
)
1107+
assert not switch_change
10911108
await smile.close_connection()
10921109
await self.disconnect(server, client)
10931110

@@ -1181,7 +1198,10 @@ async def test_connect_adam_multiple_devices_per_zone(self):
11811198
await self.tinker_thermostat(
11821199
smile, "82fa13f017d240daa0d0ea1775420f24", good_schemas=["CV Jessie"]
11831200
)
1184-
await self.tinker_switch(smile, "675416a629f343c495449970e2ca37b5")
1201+
switch_change = await self.tinker_switch(
1202+
smile, "675416a629f343c495449970e2ca37b5"
1203+
)
1204+
assert not switch_change
11851205
await smile.close_connection()
11861206
await self.disconnect(server, client)
11871207

@@ -1459,6 +1479,7 @@ async def test_connect_stretch_v23(self):
14591479
# Tv hoek 25F6790
14601480
"c71f1cb2100b42ca942f056dcb7eb01f": {
14611481
"electricity_consumed": 33.3,
1482+
"lock": False,
14621483
"relay": True,
14631484
},
14641485
# Wasdroger 043AECA
@@ -1481,12 +1502,16 @@ async def test_connect_stretch_v23(self):
14811502
_LOGGER.info(" # Assert no master thermostat")
14821503
assert smile.single_master_thermostat() is None # it's not a thermostat :)
14831504

1484-
await self.tinker_switch(smile, "2587a7fcdd7e482dab03fda256076b4b")
1485-
await self.tinker_switch(
1505+
switch_change = await self.tinker_switch(
1506+
smile, "2587a7fcdd7e482dab03fda256076b4b"
1507+
)
1508+
assert switch_change
1509+
switch_change = await self.tinker_switch(
14861510
smile,
14871511
"f7b145c8492f4dd7a4de760456fdef3e",
14881512
["407aa1c1099d463c9137a3a9eda787fd"],
14891513
)
1514+
assert switch_change
14901515

14911516
smile.get_all_devices()
14921517
await self.device_test(smile, testdata)

userdata/adam_plus_anna_new/core.appliances.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
<actuator_functionalities>
7272
<relay_functionality id='dc6998c6abcb4fec94db2043f78a29c1'>
7373
<updated_date>2020-11-10T15:49:54.788+01:00</updated_date>
74-
<lock>false</lock>
74+
<lock>true</lock>
7575
<state>on</state>
7676
<relay id='9f35a3b3d6094816952e801f66964849'/>
7777
</relay_functionality>

0 commit comments

Comments
 (0)