mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 04:07:08 +00:00
Subscribe to Withings webhooks outside of coordinator (#101759)
* Subscribe to Withings webhooks outside of coordinator * Subscribe to Withings webhooks outside of coordinator * Update homeassistant/components/withings/__init__.py Co-authored-by: J. Nick Koston <nick@koston.org> * Update homeassistant/components/withings/__init__.py Co-authored-by: J. Nick Koston <nick@koston.org> --------- Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
9b785ef766
commit
ffb752c804
@ -4,8 +4,10 @@ For more details about this platform, please refer to the documentation at
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import asyncio
|
||||||
from collections.abc import Awaitable, Callable
|
from collections.abc import Awaitable, Callable
|
||||||
import contextlib
|
import contextlib
|
||||||
|
from datetime import timedelta
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohttp.hdrs import METH_HEAD, METH_POST
|
from aiohttp.hdrs import METH_HEAD, METH_POST
|
||||||
@ -78,6 +80,8 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
},
|
},
|
||||||
extra=vol.ALLOW_EXTRA,
|
extra=vol.ALLOW_EXTRA,
|
||||||
)
|
)
|
||||||
|
SUBSCRIBE_DELAY = timedelta(seconds=5)
|
||||||
|
UNSUBSCRIBE_DELAY = timedelta(seconds=1)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
@ -141,7 +145,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
) -> None:
|
) -> None:
|
||||||
LOGGER.debug("Unregister Withings webhook (%s)", entry.data[CONF_WEBHOOK_ID])
|
LOGGER.debug("Unregister Withings webhook (%s)", entry.data[CONF_WEBHOOK_ID])
|
||||||
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
|
webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID])
|
||||||
await hass.data[DOMAIN][entry.entry_id].async_unsubscribe_webhooks()
|
await async_unsubscribe_webhooks(client)
|
||||||
|
coordinator.webhook_subscription_listener(False)
|
||||||
|
|
||||||
async def register_webhook(
|
async def register_webhook(
|
||||||
_: Any,
|
_: Any,
|
||||||
@ -170,7 +175,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
get_webhook_handler(coordinator),
|
get_webhook_handler(coordinator),
|
||||||
)
|
)
|
||||||
|
|
||||||
await hass.data[DOMAIN][entry.entry_id].async_subscribe_webhooks(webhook_url)
|
await async_subscribe_webhooks(client, webhook_url)
|
||||||
|
coordinator.webhook_subscription_listener(True)
|
||||||
LOGGER.debug("Register Withings webhook: %s", webhook_url)
|
LOGGER.debug("Register Withings webhook: %s", webhook_url)
|
||||||
entry.async_on_unload(
|
entry.async_on_unload(
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook)
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook)
|
||||||
@ -213,6 +219,53 @@ async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|||||||
await hass.config_entries.async_reload(entry.entry_id)
|
await hass.config_entries.async_reload(entry.entry_id)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_subscribe_webhooks(
|
||||||
|
client: ConfigEntryWithingsApi, webhook_url: str
|
||||||
|
) -> None:
|
||||||
|
"""Subscribe to Withings webhooks."""
|
||||||
|
await async_unsubscribe_webhooks(client)
|
||||||
|
|
||||||
|
notification_to_subscribe = {
|
||||||
|
NotifyAppli.WEIGHT,
|
||||||
|
NotifyAppli.CIRCULATORY,
|
||||||
|
NotifyAppli.ACTIVITY,
|
||||||
|
NotifyAppli.SLEEP,
|
||||||
|
NotifyAppli.BED_IN,
|
||||||
|
NotifyAppli.BED_OUT,
|
||||||
|
}
|
||||||
|
|
||||||
|
for notification in notification_to_subscribe:
|
||||||
|
LOGGER.debug(
|
||||||
|
"Subscribing %s for %s in %s seconds",
|
||||||
|
webhook_url,
|
||||||
|
notification,
|
||||||
|
SUBSCRIBE_DELAY.total_seconds(),
|
||||||
|
)
|
||||||
|
# Withings will HTTP HEAD the callback_url and needs some downtime
|
||||||
|
# between each call or there is a higher chance of failure.
|
||||||
|
await asyncio.sleep(SUBSCRIBE_DELAY.total_seconds())
|
||||||
|
await client.async_notify_subscribe(webhook_url, notification)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unsubscribe_webhooks(client: ConfigEntryWithingsApi) -> None:
|
||||||
|
"""Unsubscribe to all Withings webhooks."""
|
||||||
|
current_webhooks = await client.async_notify_list()
|
||||||
|
|
||||||
|
for webhook_configuration in current_webhooks.profiles:
|
||||||
|
LOGGER.debug(
|
||||||
|
"Unsubscribing %s for %s in %s seconds",
|
||||||
|
webhook_configuration.callbackurl,
|
||||||
|
webhook_configuration.appli,
|
||||||
|
UNSUBSCRIBE_DELAY.total_seconds(),
|
||||||
|
)
|
||||||
|
# Quick calls to Withings can result in the service returning errors.
|
||||||
|
# Give them some time to cool down.
|
||||||
|
await asyncio.sleep(UNSUBSCRIBE_DELAY.total_seconds())
|
||||||
|
await client.async_notify_revoke(
|
||||||
|
webhook_configuration.callbackurl, webhook_configuration.appli
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_cloudhook_generate_url(hass: HomeAssistant, entry: ConfigEntry) -> str:
|
async def async_cloudhook_generate_url(hass: HomeAssistant, entry: ConfigEntry) -> str:
|
||||||
"""Generate the full URL for a webhook_id."""
|
"""Generate the full URL for a webhook_id."""
|
||||||
if CONF_CLOUDHOOK_URL not in entry.data:
|
if CONF_CLOUDHOOK_URL not in entry.data:
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
"""Withings coordinator."""
|
"""Withings coordinator."""
|
||||||
import asyncio
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@ -24,9 +23,6 @@ from homeassistant.util import dt as dt_util
|
|||||||
from .api import ConfigEntryWithingsApi
|
from .api import ConfigEntryWithingsApi
|
||||||
from .const import LOGGER, Measurement
|
from .const import LOGGER, Measurement
|
||||||
|
|
||||||
SUBSCRIBE_DELAY = timedelta(seconds=5)
|
|
||||||
UNSUBSCRIBE_DELAY = timedelta(seconds=1)
|
|
||||||
|
|
||||||
WITHINGS_MEASURE_TYPE_MAP: dict[
|
WITHINGS_MEASURE_TYPE_MAP: dict[
|
||||||
NotifyAppli | GetSleepSummaryField | MeasureType, Measurement
|
NotifyAppli | GetSleepSummaryField | MeasureType, Measurement
|
||||||
] = {
|
] = {
|
||||||
@ -84,55 +80,12 @@ class WithingsDataUpdateCoordinator(DataUpdateCoordinator[dict[Measurement, Any]
|
|||||||
super().__init__(hass, LOGGER, name="Withings", update_interval=UPDATE_INTERVAL)
|
super().__init__(hass, LOGGER, name="Withings", update_interval=UPDATE_INTERVAL)
|
||||||
self._client = client
|
self._client = client
|
||||||
|
|
||||||
async def async_subscribe_webhooks(self, webhook_url: str) -> None:
|
def webhook_subscription_listener(self, connected: bool) -> None:
|
||||||
"""Subscribe to webhooks."""
|
"""Call when webhook status changed."""
|
||||||
await self.async_unsubscribe_webhooks()
|
if connected:
|
||||||
|
self.update_interval = None
|
||||||
current_webhooks = await self._client.async_notify_list()
|
else:
|
||||||
|
self.update_interval = UPDATE_INTERVAL
|
||||||
subscribed_notifications = frozenset(
|
|
||||||
profile.appli
|
|
||||||
for profile in current_webhooks.profiles
|
|
||||||
if profile.callbackurl == webhook_url
|
|
||||||
)
|
|
||||||
|
|
||||||
notification_to_subscribe = (
|
|
||||||
set(NotifyAppli)
|
|
||||||
- subscribed_notifications
|
|
||||||
- {NotifyAppli.USER, NotifyAppli.UNKNOWN}
|
|
||||||
)
|
|
||||||
|
|
||||||
for notification in notification_to_subscribe:
|
|
||||||
LOGGER.debug(
|
|
||||||
"Subscribing %s for %s in %s seconds",
|
|
||||||
webhook_url,
|
|
||||||
notification,
|
|
||||||
SUBSCRIBE_DELAY.total_seconds(),
|
|
||||||
)
|
|
||||||
# Withings will HTTP HEAD the callback_url and needs some downtime
|
|
||||||
# between each call or there is a higher chance of failure.
|
|
||||||
await asyncio.sleep(SUBSCRIBE_DELAY.total_seconds())
|
|
||||||
await self._client.async_notify_subscribe(webhook_url, notification)
|
|
||||||
self.update_interval = None
|
|
||||||
|
|
||||||
async def async_unsubscribe_webhooks(self) -> None:
|
|
||||||
"""Unsubscribe to webhooks."""
|
|
||||||
current_webhooks = await self._client.async_notify_list()
|
|
||||||
|
|
||||||
for webhook_configuration in current_webhooks.profiles:
|
|
||||||
LOGGER.debug(
|
|
||||||
"Unsubscribing %s for %s in %s seconds",
|
|
||||||
webhook_configuration.callbackurl,
|
|
||||||
webhook_configuration.appli,
|
|
||||||
UNSUBSCRIBE_DELAY.total_seconds(),
|
|
||||||
)
|
|
||||||
# Quick calls to Withings can result in the service returning errors.
|
|
||||||
# Give them some time to cool down.
|
|
||||||
await asyncio.sleep(UNSUBSCRIBE_DELAY.total_seconds())
|
|
||||||
await self._client.async_notify_revoke(
|
|
||||||
webhook_configuration.callbackurl, webhook_configuration.appli
|
|
||||||
)
|
|
||||||
self.update_interval = UPDATE_INTERVAL
|
|
||||||
|
|
||||||
async def _async_update_data(self) -> dict[Measurement, Any]:
|
async def _async_update_data(self) -> dict[Measurement, Any]:
|
||||||
try:
|
try:
|
||||||
|
@ -160,10 +160,10 @@ def disable_webhook_delay():
|
|||||||
|
|
||||||
mock = AsyncMock()
|
mock = AsyncMock()
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.withings.coordinator.SUBSCRIBE_DELAY",
|
"homeassistant.components.withings.SUBSCRIBE_DELAY",
|
||||||
timedelta(seconds=0),
|
timedelta(seconds=0),
|
||||||
), patch(
|
), patch(
|
||||||
"homeassistant.components.withings.coordinator.UNSUBSCRIBE_DELAY",
|
"homeassistant.components.withings.UNSUBSCRIBE_DELAY",
|
||||||
timedelta(seconds=0),
|
timedelta(seconds=0),
|
||||||
):
|
):
|
||||||
yield mock
|
yield mock
|
||||||
|
@ -126,7 +126,7 @@ async def test_data_manager_webhook_subscription(
|
|||||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=1))
|
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=1))
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert withings.async_notify_subscribe.call_count == 4
|
assert withings.async_notify_subscribe.call_count == 6
|
||||||
|
|
||||||
webhook_url = "https://example.local:8123/api/webhook/55a7335ea8dee830eed4ef8f84cda8f6d80b83af0847dc74032e86120bffed5e"
|
webhook_url = "https://example.local:8123/api/webhook/55a7335ea8dee830eed4ef8f84cda8f6d80b83af0847dc74032e86120bffed5e"
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user