Home Assistant Unofficial Reference 2024.12.1
discovery.py
Go to the documentation of this file.
1 """The Steamist integration discovery."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 import logging
7 from typing import Any
8 
9 from discovery30303 import AIODiscovery30303, Device30303
10 
11 from homeassistant import config_entries
12 from homeassistant.components import network
13 from homeassistant.const import CONF_MODEL, CONF_NAME
14 from homeassistant.core import HomeAssistant, callback
15 from homeassistant.helpers import device_registry as dr, discovery_flow
16 from homeassistant.util.network import is_ip_address
17 
18 from .const import DISCOVER_SCAN_TIMEOUT, DISCOVERY, DOMAIN
19 
20 _LOGGER = logging.getLogger(__name__)
21 
22 
23 MODEL_450_HOSTNAME_PREFIX = "MY450-"
24 MODEL_550_HOSTNAME_PREFIX = "MY550-"
25 
26 
27 @callback
28 def async_is_steamist_device(device: Device30303) -> bool:
29  """Check if a 30303 discovery is a steamist device."""
30  return device.hostname.startswith(
31  MODEL_450_HOSTNAME_PREFIX
32  ) or device.hostname.startswith(MODEL_550_HOSTNAME_PREFIX)
33 
34 
35 @callback
37  hass: HomeAssistant,
39  device: Device30303,
40 ) -> bool:
41  """Update a config entry from a discovery."""
42  data_updates: dict[str, Any] = {}
43  updates: dict[str, Any] = {}
44  if not entry.unique_id:
45  updates["unique_id"] = dr.format_mac(device.mac)
46  if not entry.data.get(CONF_NAME) or is_ip_address(entry.data[CONF_NAME]):
47  updates["title"] = data_updates[CONF_NAME] = device.name
48  if not entry.data.get(CONF_MODEL) and "-" in device.hostname:
49  data_updates[CONF_MODEL] = device.hostname.split("-", maxsplit=1)[0]
50  if data_updates:
51  updates["data"] = {**entry.data, **data_updates}
52  if updates:
53  return hass.config_entries.async_update_entry(entry, **updates)
54  return False
55 
56 
58  hass: HomeAssistant, timeout: int, address: str | None = None
59 ) -> list[Device30303]:
60  """Discover devices."""
61  if address:
62  targets = [address]
63  else:
64  targets = [
65  str(broadcast_address)
66  for broadcast_address in await network.async_get_ipv4_broadcast_addresses(
67  hass
68  )
69  ]
70 
71  scanner = AIODiscovery30303()
72  for idx, discovered in enumerate(
73  await asyncio.gather(
74  *[
75  scanner.async_scan(timeout=timeout, address=target_address)
76  for target_address in targets
77  ],
78  return_exceptions=True,
79  )
80  ):
81  if isinstance(discovered, Exception):
82  _LOGGER.debug("Scanning %s failed with error: %s", targets[idx], discovered)
83  continue
84 
85  _LOGGER.debug("Found devices: %s", scanner.found_devices)
86  if not address:
87  return [
88  device
89  for device in scanner.found_devices
90  if async_is_steamist_device(device)
91  ]
92 
93  return [device for device in scanner.found_devices if device.ipaddress == address]
94 
95 
96 @callback
98  discoveries: list[Device30303], host: str
99 ) -> Device30303 | None:
100  """Search a list of discoveries for one with a matching ip."""
101  for discovery in discoveries:
102  if discovery.ipaddress == host:
103  return discovery
104  return None
105 
106 
107 async def async_discover_device(hass: HomeAssistant, host: str) -> Device30303 | None:
108  """Direct discovery to a single ip instead of broadcast."""
110  await async_discover_devices(hass, DISCOVER_SCAN_TIMEOUT, host), host
111  )
112 
113 
114 @callback
115 def async_get_discovery(hass: HomeAssistant, host: str) -> Device30303 | None:
116  """Check if a device was already discovered via a broadcast discovery."""
117  discoveries: list[Device30303] = hass.data[DOMAIN][DISCOVERY]
118  return async_find_discovery_by_ip(discoveries, host)
119 
120 
121 @callback
123  hass: HomeAssistant,
124  discovered_devices: list[Device30303],
125 ) -> None:
126  """Trigger config flows for discovered devices."""
127  for device in discovered_devices:
128  discovery_flow.async_create_flow(
129  hass,
130  DOMAIN,
131  context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY},
132  data={
133  "ipaddress": device.ipaddress,
134  "name": device.name,
135  "mac": device.mac,
136  "hostname": device.hostname,
137  },
138  )
Device30303|None async_find_discovery_by_ip(list[Device30303] discoveries, str host)
Definition: discovery.py:99
Device30303|None async_get_discovery(HomeAssistant hass, str host)
Definition: discovery.py:115
list[Device30303] async_discover_devices(HomeAssistant hass, int timeout, str|None address=None)
Definition: discovery.py:59
None async_trigger_discovery(HomeAssistant hass, list[Device30303] discovered_devices)
Definition: discovery.py:125
bool async_update_entry_from_discovery(HomeAssistant hass, config_entries.ConfigEntry entry, Device30303 device)
Definition: discovery.py:40
Device30303|None async_discover_device(HomeAssistant hass, str host)
Definition: discovery.py:107
bool async_is_steamist_device(Device30303 device)
Definition: discovery.py:28
bool is_ip_address(str address)
Definition: network.py:63