Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """Integrate with NO-IP Dynamic DNS service."""
2 
3 import asyncio
4 import base64
5 from datetime import datetime, timedelta
6 import logging
7 
8 import aiohttp
9 from aiohttp.hdrs import AUTHORIZATION, USER_AGENT
10 import voluptuous as vol
11 
12 from homeassistant.const import CONF_DOMAIN, CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME
13 from homeassistant.core import HomeAssistant
15  SERVER_SOFTWARE,
16  async_get_clientsession,
17 )
19 from homeassistant.helpers.event import async_track_time_interval
20 from homeassistant.helpers.typing import ConfigType
21 
22 _LOGGER = logging.getLogger(__name__)
23 
24 DOMAIN = "no_ip"
25 
26 # We should set a dedicated address for the user agent.
27 EMAIL = "hello@home-assistant.io"
28 
29 INTERVAL = timedelta(minutes=5)
30 
31 DEFAULT_TIMEOUT = 10
32 
33 NO_IP_ERRORS = {
34  "nohost": "Hostname supplied does not exist under specified account",
35  "badauth": "Invalid username password combination",
36  "badagent": "Client disabled",
37  "!donator": "An update request was sent with a feature that is not available",
38  "abuse": "Username is blocked due to abuse",
39  "911": "A fatal error on NO-IP's side such as a database outage",
40 }
41 
42 UPDATE_URL = "https://dynupdate.no-ip.com/nic/update"
43 HA_USER_AGENT = f"{SERVER_SOFTWARE} {EMAIL}"
44 
45 CONFIG_SCHEMA = vol.Schema(
46  {
47  DOMAIN: vol.Schema(
48  {
49  vol.Required(CONF_DOMAIN): cv.string,
50  vol.Required(CONF_USERNAME): cv.string,
51  vol.Required(CONF_PASSWORD): cv.string,
52  vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
53  }
54  )
55  },
56  extra=vol.ALLOW_EXTRA,
57 )
58 
59 
60 async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
61  """Initialize the NO-IP component."""
62  domain = config[DOMAIN].get(CONF_DOMAIN)
63  user = config[DOMAIN].get(CONF_USERNAME)
64  password = config[DOMAIN].get(CONF_PASSWORD)
65  timeout = config[DOMAIN].get(CONF_TIMEOUT)
66 
67  auth_str = base64.b64encode(f"{user}:{password}".encode())
68 
69  session = async_get_clientsession(hass)
70 
71  result = await _update_no_ip(hass, session, domain, auth_str, timeout)
72 
73  if not result:
74  return False
75 
76  async def update_domain_interval(now: datetime) -> None:
77  """Update the NO-IP entry."""
78  await _update_no_ip(hass, session, domain, auth_str, timeout)
79 
80  async_track_time_interval(hass, update_domain_interval, INTERVAL)
81 
82  return True
83 
84 
85 async def _update_no_ip(
86  hass: HomeAssistant,
87  session: aiohttp.ClientSession,
88  domain: str,
89  auth_str: bytes,
90  timeout: int,
91 ) -> bool:
92  """Update NO-IP."""
93  url = UPDATE_URL
94 
95  params = {"hostname": domain}
96 
97  headers: dict[str, str] = {
98  AUTHORIZATION: f"Basic {auth_str.decode('utf-8')}",
99  USER_AGENT: HA_USER_AGENT,
100  }
101 
102  try:
103  async with asyncio.timeout(timeout):
104  resp = await session.get(url, params=params, headers=headers)
105  body = await resp.text()
106 
107  if body.startswith(("good", "nochg")):
108  _LOGGER.debug("Updating NO-IP success: %s", domain)
109  return True
110 
111  _LOGGER.warning(
112  "Updating NO-IP failed: %s => %s", domain, NO_IP_ERRORS[body.strip()]
113  )
114 
115  except aiohttp.ClientError:
116  _LOGGER.warning("Can't connect to NO-IP API")
117 
118  except TimeoutError:
119  _LOGGER.warning("Timeout from NO-IP API for domain: %s", domain)
120 
121  return False
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88
bool async_setup(HomeAssistant hass, ConfigType config)
Definition: __init__.py:60
bool _update_no_ip(HomeAssistant hass, aiohttp.ClientSession session, str domain, bytes auth_str, int timeout)
Definition: __init__.py:91
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_track_time_interval(HomeAssistant hass, Callable[[datetime], Coroutine[Any, Any, None]|None] action, timedelta interval, *str|None name=None, bool|None cancel_on_shutdown=None)
Definition: event.py:1679