Fall back to polling if webhook cannot be registered on Nuki (#91013)
fix(nuki): throw warning if webhook cannot be created
This commit is contained in:
parent
95109072b5
commit
62bc8df964
2 changed files with 89 additions and 74 deletions
|
@ -25,7 +25,6 @@ from homeassistant.const import (
|
||||||
Platform,
|
Platform,
|
||||||
)
|
)
|
||||||
from homeassistant.core import Event, HomeAssistant
|
from homeassistant.core import Event, HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryNotReady
|
|
||||||
from homeassistant.helpers import (
|
from homeassistant.helpers import (
|
||||||
device_registry as dr,
|
device_registry as dr,
|
||||||
entity_registry as er,
|
entity_registry as er,
|
||||||
|
@ -47,7 +46,7 @@ from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
ERROR_STATES,
|
ERROR_STATES,
|
||||||
)
|
)
|
||||||
from .helpers import parse_id
|
from .helpers import NukiWebhookException, parse_id
|
||||||
|
|
||||||
_NukiDeviceT = TypeVar("_NukiDeviceT", bound=NukiDevice)
|
_NukiDeviceT = TypeVar("_NukiDeviceT", bound=NukiDevice)
|
||||||
|
|
||||||
|
@ -61,6 +60,87 @@ def _get_bridge_devices(bridge: NukiBridge) -> tuple[list[NukiLock], list[NukiOp
|
||||||
return bridge.locks, bridge.openers
|
return bridge.locks, bridge.openers
|
||||||
|
|
||||||
|
|
||||||
|
async def _create_webhook(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, bridge: NukiBridge
|
||||||
|
) -> None:
|
||||||
|
# Create HomeAssistant webhook
|
||||||
|
async def handle_webhook(
|
||||||
|
hass: HomeAssistant, webhook_id: str, request: web.Request
|
||||||
|
) -> web.Response:
|
||||||
|
"""Handle webhook callback."""
|
||||||
|
try:
|
||||||
|
data = await request.json()
|
||||||
|
except ValueError:
|
||||||
|
return web.Response(status=HTTPStatus.BAD_REQUEST)
|
||||||
|
|
||||||
|
locks = hass.data[DOMAIN][entry.entry_id][DATA_LOCKS]
|
||||||
|
openers = hass.data[DOMAIN][entry.entry_id][DATA_OPENERS]
|
||||||
|
|
||||||
|
devices = [x for x in locks + openers if x.nuki_id == data["nukiId"]]
|
||||||
|
if len(devices) == 1:
|
||||||
|
devices[0].update_from_callback(data)
|
||||||
|
|
||||||
|
coordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
|
||||||
|
coordinator.async_set_updated_data(None)
|
||||||
|
|
||||||
|
return web.Response(status=HTTPStatus.OK)
|
||||||
|
|
||||||
|
webhook.async_register(
|
||||||
|
hass, DOMAIN, entry.title, entry.entry_id, handle_webhook, local_only=True
|
||||||
|
)
|
||||||
|
|
||||||
|
webhook_url = webhook.async_generate_path(entry.entry_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
hass_url = get_url(
|
||||||
|
hass,
|
||||||
|
allow_cloud=False,
|
||||||
|
allow_external=False,
|
||||||
|
allow_ip=True,
|
||||||
|
require_ssl=False,
|
||||||
|
)
|
||||||
|
except NoURLAvailableError:
|
||||||
|
webhook.async_unregister(hass, entry.entry_id)
|
||||||
|
raise NukiWebhookException(
|
||||||
|
f"Error registering URL for webhook {entry.entry_id}: "
|
||||||
|
"HomeAssistant URL is not available"
|
||||||
|
) from None
|
||||||
|
|
||||||
|
url = f"{hass_url}{webhook_url}"
|
||||||
|
|
||||||
|
if hass_url.startswith("https"):
|
||||||
|
ir.async_create_issue(
|
||||||
|
hass,
|
||||||
|
DOMAIN,
|
||||||
|
"https_webhook",
|
||||||
|
is_fixable=False,
|
||||||
|
severity=ir.IssueSeverity.WARNING,
|
||||||
|
translation_key="https_webhook",
|
||||||
|
translation_placeholders={
|
||||||
|
"base_url": hass_url,
|
||||||
|
"network_link": "https://my.home-assistant.io/redirect/network/",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ir.async_delete_issue(hass, DOMAIN, "https_webhook")
|
||||||
|
|
||||||
|
try:
|
||||||
|
async with async_timeout.timeout(10):
|
||||||
|
await hass.async_add_executor_job(
|
||||||
|
_register_webhook, bridge, entry.entry_id, url
|
||||||
|
)
|
||||||
|
except InvalidCredentialsException as err:
|
||||||
|
webhook.async_unregister(hass, entry.entry_id)
|
||||||
|
raise NukiWebhookException(
|
||||||
|
f"Invalid credentials for Bridge: {err}"
|
||||||
|
) from err
|
||||||
|
except RequestException as err:
|
||||||
|
webhook.async_unregister(hass, entry.entry_id)
|
||||||
|
raise NukiWebhookException(
|
||||||
|
f"Error communicating with Bridge: {err}"
|
||||||
|
) from err
|
||||||
|
|
||||||
|
|
||||||
def _register_webhook(bridge: NukiBridge, entry_id: str, url: str) -> bool:
|
def _register_webhook(bridge: NukiBridge, entry_id: str, url: str) -> bool:
|
||||||
# Register HA URL as webhook if not already
|
# Register HA URL as webhook if not already
|
||||||
callbacks = bridge.callback_list()
|
callbacks = bridge.callback_list()
|
||||||
|
@ -126,79 +206,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
sw_version=info["versions"]["firmwareVersion"],
|
sw_version=info["versions"]["firmwareVersion"],
|
||||||
)
|
)
|
||||||
|
|
||||||
async def handle_webhook(
|
|
||||||
hass: HomeAssistant, webhook_id: str, request: web.Request
|
|
||||||
) -> web.Response:
|
|
||||||
"""Handle webhook callback."""
|
|
||||||
try:
|
|
||||||
data = await request.json()
|
|
||||||
except ValueError:
|
|
||||||
return web.Response(status=HTTPStatus.BAD_REQUEST)
|
|
||||||
|
|
||||||
locks = hass.data[DOMAIN][entry.entry_id][DATA_LOCKS]
|
|
||||||
openers = hass.data[DOMAIN][entry.entry_id][DATA_OPENERS]
|
|
||||||
|
|
||||||
devices = [x for x in locks + openers if x.nuki_id == data["nukiId"]]
|
|
||||||
if len(devices) == 1:
|
|
||||||
devices[0].update_from_callback(data)
|
|
||||||
|
|
||||||
coordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
|
|
||||||
coordinator.async_set_updated_data(None)
|
|
||||||
|
|
||||||
return web.Response(status=HTTPStatus.OK)
|
|
||||||
|
|
||||||
webhook.async_register(
|
|
||||||
hass, DOMAIN, entry.title, entry.entry_id, handle_webhook, local_only=True
|
|
||||||
)
|
|
||||||
|
|
||||||
webhook_url = webhook.async_generate_path(entry.entry_id)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hass_url = get_url(
|
await _create_webhook(hass, entry, bridge)
|
||||||
hass,
|
except NukiWebhookException as err:
|
||||||
allow_cloud=False,
|
_LOGGER.warning("Error registering HomeAssistant webhook: %s", err)
|
||||||
allow_external=False,
|
|
||||||
allow_ip=True,
|
|
||||||
require_ssl=False,
|
|
||||||
)
|
|
||||||
except NoURLAvailableError:
|
|
||||||
webhook.async_unregister(hass, entry.entry_id)
|
|
||||||
raise ConfigEntryNotReady(
|
|
||||||
f"Error registering URL for webhook {entry.entry_id}: "
|
|
||||||
"HomeAssistant URL is not available"
|
|
||||||
) from None
|
|
||||||
|
|
||||||
url = f"{hass_url}{webhook_url}"
|
|
||||||
|
|
||||||
if hass_url.startswith("https"):
|
|
||||||
ir.async_create_issue(
|
|
||||||
hass,
|
|
||||||
DOMAIN,
|
|
||||||
"https_webhook",
|
|
||||||
is_fixable=False,
|
|
||||||
severity=ir.IssueSeverity.WARNING,
|
|
||||||
translation_key="https_webhook",
|
|
||||||
translation_placeholders={
|
|
||||||
"base_url": hass_url,
|
|
||||||
"network_link": "https://my.home-assistant.io/redirect/network/",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
ir.async_delete_issue(hass, DOMAIN, "https_webhook")
|
|
||||||
|
|
||||||
try:
|
|
||||||
async with async_timeout.timeout(10):
|
|
||||||
await hass.async_add_executor_job(
|
|
||||||
_register_webhook, bridge, entry.entry_id, url
|
|
||||||
)
|
|
||||||
except InvalidCredentialsException as err:
|
|
||||||
webhook.async_unregister(hass, entry.entry_id)
|
|
||||||
raise ConfigEntryNotReady(f"Invalid credentials for Bridge: {err}") from err
|
|
||||||
except RequestException as err:
|
|
||||||
webhook.async_unregister(hass, entry.entry_id)
|
|
||||||
raise ConfigEntryNotReady(
|
|
||||||
f"Error communicating with Bridge: {err}"
|
|
||||||
) from err
|
|
||||||
|
|
||||||
async def _stop_nuki(_: Event):
|
async def _stop_nuki(_: Event):
|
||||||
"""Stop and remove the Nuki webhook."""
|
"""Stop and remove the Nuki webhook."""
|
||||||
|
|
|
@ -13,3 +13,7 @@ class CannotConnect(exceptions.HomeAssistantError):
|
||||||
|
|
||||||
class InvalidAuth(exceptions.HomeAssistantError):
|
class InvalidAuth(exceptions.HomeAssistantError):
|
||||||
"""Error to indicate there is invalid auth."""
|
"""Error to indicate there is invalid auth."""
|
||||||
|
|
||||||
|
|
||||||
|
class NukiWebhookException(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate there was an issue with the webhook."""
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue