-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Homekit locks support #13625
Add Homekit locks support #13625
Conversation
@@ -121,6 +121,9 @@ def get_accessory(hass, state, aid, config): | |||
|
|||
elif state.domain == 'light': | |||
return TYPES['Light'](hass, state.entity_id, state.name, aid=aid) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
blank line contains whitespace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm looking forward to adding this to Home Assistant. A small home-assistant.github
PR would be great. Left a few comments.
@@ -182,7 +185,7 @@ def start(self, *args): | |||
# pylint: disable=unused-variable | |||
from . import ( # noqa F401 | |||
type_covers, type_lights, type_security_systems, type_sensors, | |||
type_switches, type_thermostats) | |||
type_switches, type_thermostats, type_locks) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just order it alphabetically, like the rest is already.
|
||
def __init__(self, hass, entity_id, name, *args, **kwargs): | ||
"""Initialize a Lock accessory object.""" | ||
super().__init__(name, entity_id, CATEGORY_LOCK, *args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't pass *args
. I changed that in #13534 as well. For init
as well. It should be enough if additional parameter are passed via **kwargs
. That mainly applies to aid
.
CATEGORY_LOCK, SERV_LOCK, | ||
CHAR_LOCK_CURRENT_STATE, CHAR_LOCK_TARGET_STATE) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One empty line would be enough here, for consistency.
|
||
self.current_state = None | ||
|
||
self.serv_lock_mechanism = add_preload_service(self, SERV_LOCK) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The service doesn't need to be an instance attribute, since it is only used in __init__
.
get_characteristic(CHAR_LOCK_TARGET_STATE) | ||
|
||
self.char_current_state.value = 3 # unknown | ||
self.char_target_state.value = 1 # unsecured |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unsecured
should be 0
.
def set_state(self, value): | ||
"""Set lock state to value if call came from HomeKit.""" | ||
self.char_target_state.set_value(value, should_callback=False) | ||
if value != self.current_state: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This if
statement isn't necessary, since a call from HomeKit
implies that it is different. type_cover
might not be the best example, since it tries to set a continuous value (and I didn't had the chance to optimize it yet). A good example is type_lights
.
self.char_target_state.set_value(value, should_callback=False) | ||
if value != self.current_state: | ||
_LOGGER.debug("%s: Set state to %d", self._entity_id, value) | ||
print(self._hass.components) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Print statement
self._hass.components.lock.unlock(self._entity_id) | ||
else: | ||
_LOGGER.warning( | ||
"unknown lock value %s for %s", value, self._entity_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Target State
can only be set to 0
or 1
from HomeKit. The else clause will never be called.
self.char_target_state.set_value(0, should_callback=False) | ||
elif new_state.state == STATE_UNKNOWN: | ||
self.char_current_state.set_value(3) | ||
# self.char_target_state.set_value(0, should_callback=False) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block needs improvement. HomeKit
uses transition states, like opening
and closing
for covers and I would guess locking
and unlocking
for locks. To use them, I think the the best way is to use a flag variable (e.g. self.flag_target_state
). Initialized in init to false
, set to true
in the set_state
and reset in update_state
. You might want to take a look at type_security_systems
.
If you have questions about that, I'm happy to help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm looking in the wrong place LockCurrentState doesn't have transitional states. The only one I see with opening/closing is CurrentDoorState
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It isn't really a state. The Home app does its own interpretation when current_state != target_state
, at least for a short moment.
if new_state is None: | ||
return | ||
|
||
# TODO: handle various lock states beyond just lock unlock |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove that.
Sorry for the kind of sloppy initial PR, forget to push all my rebased changes originally. Thanks for the advice on I'll do the _hass, _entity_id, and args* fixes then squash rebase once #13534 is merged. Doc PR is done as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just some minor suggestions. Otherwise looking good
|
||
from homeassistant.components.lock import ( | ||
ATTR_ENTITY_ID, | ||
STATE_LOCKED, STATE_UNLOCKED, STATE_UNKNOWN) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need an extra line here
from .accessories import HomeAccessory, add_preload_service | ||
from .const import ( | ||
CATEGORY_LOCK, SERV_LOCK, | ||
CHAR_LOCK_CURRENT_STATE, CHAR_LOCK_TARGET_STATE) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
self.char_target_state.set_value(current_lock_state, | ||
should_callback=False) | ||
if self.char_target_state.value == self.char_current_state.value: | ||
self.flag_target_state = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
L71-73: Nobody will see the debug statement.
L85: Shouldn't be necessary.
Maybe that should work:
hass_state = new_state.state
if HASS_TO_HOMEKIT.get(hass_state) is not None:
current_lock_state = HASS_TO_HOMEKIT[hass_state]
self.char_current_state.set_value(current_lock_state,
should_callback=False)
_LOGGER.debug('%s: Updated current state to %s (%d)',
self._entity_id, hass_state, current_lock_state)
if not self.flag_target_state:
self.char_target_state.set_value(current_lock_state,
should_callback=False)
self.flag_target_state = False
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have that check in case additional STATE_*
are added to the lock component in the future, that seems like a reasonable possibility to me? (STATE_JAMMED
for instance).
Right on L85, curious why security_system
does it now that I think about it. Taking a look at that (and turning up logs in test) did lead me to noticing an exception (that for some reason doesn't cause tests to fail even though they cause it?):
ERROR:homeassistant.core:Error doing job: Future exception was never retrieved
Traceback (most recent call last):
File "/Users/pkates/.pyenv/versions/3.6.4/lib/python3.6/concurrent/futures/thread.py", line 56, in run
result = self.fn(*self.args, **self.kwargs)
File "/Users/pkates/pksrc/home-assistant/homeassistant/components/homekit/type_locks.py", line 84, in update_state
should_callback=False)
File "/Users/pkates/.pyenv/versions/3.6.4/envs/hass/lib/python3.6/site-packages/pyhap/characteristic.py", line 150, in set_value
raise ValueError
ValueError
This happens when Hass sets STATE_UNKNOWN which target_state doesn't support as a valid value.
My first thought was just if state == STATE_UNLOCKED
but that feels lazy and brittle, I went with a try/except but I definitely willing to hear alternate opinions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe try: if hass_state in (STATE_UNLOCKED, STATE_LOCKED)
. I wouldn't agree that it's lazy, just practical and easier to read.
You don't need any debug statements for state not supported. That would get out of hand otherwise.
self.assertEqual(acc.char_current_state.value, 3) | ||
self.assertEqual(acc.char_target_state.value, 0) | ||
|
||
# # Set from HomeKit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Set from HomeKit
Personally I'm not that big a fan of using |
Rebased this on #13654 if you notice the commits look slightly weird. |
Hi, sorry that I haven't replied. I somehow missed that you had already updated the PR.
Only waiting for |
* homekit: Add locks support * Improved upgradeability
Description:
Adding lock support for Homekit. It's largely a copy and modify from the covers device, but feel free to point out if I missed something.
Future work might include integrating LockLastKnownAction but that would require upstream lock components to report their status in a standard way (feels way out of scope for this).
cc: @cdce8p
I'll can do the minor PR for docs (add to list of supported components) if you're good with the way this looks.
Will depend on #13534 for the naming changes to
self._hass
andself._entity_id
.Pull request in home-assistant.github.io with documentation (if applicable): home-assistant/home-assistant.io#5093
Checklist:
tox
. Your PR cannot be merged unless tests passIf user exposed functionality or configuration variables are added/changed:
If the code does not interact with devices: