1 """Integrate with DuckDNS."""
3 from __future__
import annotations
5 from collections.abc
import Callable, Coroutine, Sequence
6 from datetime
import datetime, timedelta
8 from typing
import Any, cast
10 from aiohttp
import ClientSession
11 import voluptuous
as vol
28 _LOGGER = logging.getLogger(__name__)
36 SERVICE_SET_TXT =
"set_txt"
38 UPDATE_URL =
"https://www.duckdns.org/update"
40 CONFIG_SCHEMA = vol.Schema(
44 vol.Required(CONF_DOMAIN): cv.string,
45 vol.Required(CONF_ACCESS_TOKEN): cv.string,
49 extra=vol.ALLOW_EXTRA,
52 SERVICE_TXT_SCHEMA = vol.Schema({vol.Required(ATTR_TXT): vol.Any(
None, cv.string)})
55 async
def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
56 """Initialize the DuckDNS component."""
57 domain: str = config[DOMAIN][CONF_DOMAIN]
58 token: str = config[DOMAIN][CONF_ACCESS_TOKEN]
61 async
def update_domain_interval(_now: datetime) -> bool:
62 """Update the DuckDNS entry."""
74 async
def update_domain_service(call: ServiceCall) ->
None:
75 """Update the DuckDNS entry."""
78 hass.services.async_register(
79 DOMAIN, SERVICE_SET_TXT, update_domain_service, schema=SERVICE_TXT_SCHEMA
89 session: ClientSession,
93 txt: str |
None | object = _SENTINEL,
97 params = {
"domains": domain,
"token": token}
99 if txt
is not _SENTINEL:
105 params[
"txt"] = cast(str, txt)
108 params[
"clear"] =
"true"
110 resp = await session.get(UPDATE_URL, params=params)
111 body = await resp.text()
114 _LOGGER.warning(
"Updating DuckDNS domain failed: %s", domain)
124 action: Callable[[datetime], Coroutine[Any, Any, bool]],
125 intervals: Sequence[timedelta],
127 """Add a listener that fires repetitively at every timedelta interval."""
128 remove: CALLBACK_TYPE |
None =
None
131 async
def interval_listener(now: datetime) ->
None:
132 """Handle elapsed intervals with backoff."""
133 nonlocal failed, remove
136 if await action(now):
139 delay = intervals[failed]
if failed < len(intervals)
else intervals[-1]
141 hass, delay.total_seconds(), interval_listener_job
144 interval_listener_job =
HassJob(interval_listener, cancel_on_shutdown=
True)
145 hass.async_run_hass_job(interval_listener_job, dt_util.utcnow())
147 def remove_listener() -> None:
148 """Remove interval listener."""
152 return remove_listener
bool remove(self, _T matcher)
bool async_setup(HomeAssistant hass, ConfigType config)
bool _update_duckdns(ClientSession session, str domain, str token, *str|None|object txt=_SENTINEL, bool clear=False)
CALLBACK_TYPE async_track_time_interval_backoff(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, bool]] action, Sequence[timedelta] intervals)
aiohttp.ClientSession async_get_clientsession(HomeAssistant hass, bool verify_ssl=True, socket.AddressFamily family=socket.AF_UNSPEC, ssl_util.SSLCipherList ssl_cipher=ssl_util.SSLCipherList.PYTHON_DEFAULT)
CALLBACK_TYPE async_call_later(HomeAssistant hass, float|timedelta delay, HassJob[[datetime], Coroutine[Any, Any, None]|None]|Callable[[datetime], Coroutine[Any, Any, None]|None] action)