-
-
Notifications
You must be signed in to change notification settings - Fork 33.8k
Add doorsensor + coordinator to nuki #40933
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
Changes from all commits
fdc4fbf
291da03
cd1ba00
b6eafbf
0c89da3
6038e8a
43f4a93
85a5be2
6711daa
9b9415c
c01e9c9
8565c19
42245ca
70ad077
818b181
5270609
0ea295c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
"""Doorsensor Support for the Nuki Lock.""" | ||
|
||
import logging | ||
|
||
from pynuki import STATE_DOORSENSOR_OPENED | ||
|
||
from homeassistant.components.binary_sensor import DEVICE_CLASS_DOOR, BinarySensorEntity | ||
|
||
from . import NukiEntity | ||
from .const import ATTR_NUKI_ID, DATA_COORDINATOR, DATA_LOCKS, DOMAIN as NUKI_DOMAIN | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
async def async_setup_entry(hass, entry, async_add_entities): | ||
"""Set up the Nuki lock binary sensor.""" | ||
data = hass.data[NUKI_DOMAIN][entry.entry_id] | ||
coordinator = data[DATA_COORDINATOR] | ||
|
||
entities = [] | ||
|
||
for lock in data[DATA_LOCKS]: | ||
MartinHjelmare marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if lock.is_door_sensor_activated: | ||
entities.extend([NukiDoorsensorEntity(coordinator, lock)]) | ||
|
||
async_add_entities(entities) | ||
|
||
|
||
class NukiDoorsensorEntity(NukiEntity, BinarySensorEntity): | ||
"""Representation of a Nuki Lock Doorsensor.""" | ||
|
||
@property | ||
def name(self): | ||
"""Return the name of the lock.""" | ||
return self._nuki_device.name | ||
|
||
@property | ||
def unique_id(self) -> str: | ||
"""Return a unique ID.""" | ||
return f"{self._nuki_device.nuki_id}_doorsensor" | ||
|
||
@property | ||
def extra_state_attributes(self): | ||
"""Return the device specific state attributes.""" | ||
data = { | ||
ATTR_NUKI_ID: self._nuki_device.nuki_id, | ||
} | ||
return data | ||
|
||
@property | ||
def available(self): | ||
"""Return true if door sensor is present and activated.""" | ||
return super().available and self._nuki_device.is_door_sensor_activated | ||
|
||
@property | ||
def door_sensor_state(self): | ||
"""Return the state of the door sensor.""" | ||
return self._nuki_device.door_sensor_state | ||
|
||
@property | ||
def door_sensor_state_name(self): | ||
"""Return the state name of the door sensor.""" | ||
return self._nuki_device.door_sensor_state_name | ||
|
||
@property | ||
def is_on(self): | ||
"""Return true if the door is open.""" | ||
return self.door_sensor_state == STATE_DOORSENSOR_OPENED | ||
|
||
@property | ||
def device_class(self): | ||
"""Return the class of this device, from component DEVICE_CLASSES.""" | ||
return DEVICE_CLASS_DOOR |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,6 +26,8 @@ | |
} | ||
) | ||
|
||
REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_TOKEN): str}) | ||
|
||
|
||
async def validate_input(hass, data): | ||
"""Validate the user input allows us to connect. | ||
|
@@ -58,6 +60,7 @@ class NukiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): | |
def __init__(self): | ||
"""Initialize the Nuki config flow.""" | ||
self.discovery_schema = {} | ||
self._data = {} | ||
|
||
async def async_step_import(self, user_input=None): | ||
"""Handle a flow initiated by import.""" | ||
|
@@ -83,6 +86,50 @@ async def async_step_dhcp(self, discovery_info: dict): | |
|
||
return await self.async_step_validate() | ||
|
||
async def async_step_reauth(self, data): | ||
"""Perform reauth upon an API authentication error.""" | ||
self._data = data | ||
|
||
return await self.async_step_reauth_confirm() | ||
|
||
async def async_step_reauth_confirm(self, user_input=None): | ||
"""Dialog that inform the user that reauth is required.""" | ||
errors = {} | ||
if user_input is None: | ||
return self.async_show_form( | ||
step_id="reauth_confirm", data_schema=REAUTH_SCHEMA | ||
) | ||
|
||
conf = { | ||
CONF_HOST: self._data[CONF_HOST], | ||
CONF_PORT: self._data[CONF_PORT], | ||
CONF_TOKEN: user_input[CONF_TOKEN], | ||
} | ||
|
||
try: | ||
info = await validate_input(self.hass, conf) | ||
except CannotConnect: | ||
errors["base"] = "cannot_connect" | ||
except InvalidAuth: | ||
errors["base"] = "invalid_auth" | ||
except Exception: # pylint: disable=broad-except | ||
_LOGGER.exception("Unexpected exception") | ||
errors["base"] = "unknown" | ||
|
||
if not errors: | ||
existing_entry = await self.async_set_unique_id(info["ids"]["hardwareId"]) | ||
if existing_entry: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we handle the other case and show an error that no matching entry was found? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the docs it's not handling the other case, if I should implement this, should it abort the flow or just print an error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The example in the docs creates a new entry if there's no existing entry. Here we just show the form again without any error message. How will the user know what to do? I suggest showing an error message that the reauthentication failed with the user provided details. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah, I've misread that in the docs. I've added the "unknown" error since that is the only one from the common strings that would match here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can add a custom message if we have better info than unknown. |
||
self.hass.config_entries.async_update_entry(existing_entry, data=conf) | ||
self.hass.async_create_task( | ||
self.hass.config_entries.async_reload(existing_entry.entry_id) | ||
) | ||
return self.async_abort(reason="reauth_successful") | ||
errors["base"] = "unknown" | ||
|
||
return self.async_show_form( | ||
step_id="reauth_confirm", data_schema=REAUTH_SCHEMA, errors=errors | ||
) | ||
|
||
async def async_step_validate(self, user_input=None): | ||
"""Handle init step of a flow.""" | ||
|
||
|
@@ -106,7 +153,6 @@ async def async_step_validate(self, user_input=None): | |
) | ||
|
||
data_schema = self.discovery_schema or USER_SCHEMA | ||
|
||
return self.async_show_form( | ||
step_id="user", data_schema=data_schema, errors=errors | ||
) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,19 @@ | ||
"""Constants for Nuki.""" | ||
DOMAIN = "nuki" | ||
|
||
# Attributes | ||
ATTR_BATTERY_CRITICAL = "battery_critical" | ||
ATTR_NUKI_ID = "nuki_id" | ||
ATTR_UNLATCH = "unlatch" | ||
|
||
# Data | ||
DATA_BRIDGE = "nuki_bridge_data" | ||
DATA_LOCKS = "nuki_locks_data" | ||
DATA_OPENERS = "nuki_openers_data" | ||
DATA_COORDINATOR = "nuki_coordinator" | ||
|
||
# Defaults | ||
DEFAULT_PORT = 8080 | ||
DEFAULT_TIMEOUT = 20 | ||
|
||
ERROR_STATES = (0, 254, 255) |
Uh oh!
There was an error while loading. Please reload this page.