Home Assistant Unofficial Reference 2024.12.1
onewirehub.py
Go to the documentation of this file.
1 """Hub for communication with 1-Wire server or mount_dir."""
2 
3 from __future__ import annotations
4 
5 import logging
6 import os
7 from typing import TYPE_CHECKING
8 
9 from pyownet import protocol
10 
11 from homeassistant.config_entries import ConfigEntry
12 from homeassistant.const import (
13  ATTR_IDENTIFIERS,
14  ATTR_MANUFACTURER,
15  ATTR_MODEL,
16  ATTR_NAME,
17  ATTR_VIA_DEVICE,
18  CONF_HOST,
19  CONF_PORT,
20 )
21 from homeassistant.core import HomeAssistant
22 from homeassistant.exceptions import HomeAssistantError
23 from homeassistant.helpers import device_registry as dr
24 from homeassistant.helpers.device_registry import DeviceInfo
25 
26 from .const import (
27  DEVICE_SUPPORT,
28  DOMAIN,
29  MANUFACTURER_EDS,
30  MANUFACTURER_HOBBYBOARDS,
31  MANUFACTURER_MAXIM,
32 )
33 from .model import OWDeviceDescription
34 
35 DEVICE_COUPLERS = {
36  # Family : [branches]
37  "1F": ["aux", "main"]
38 }
39 
40 DEVICE_MANUFACTURER = {
41  "7E": MANUFACTURER_EDS,
42  "EF": MANUFACTURER_HOBBYBOARDS,
43 }
44 
45 _LOGGER = logging.getLogger(__name__)
46 
47 
48 def _is_known_device(device_family: str, device_type: str | None) -> bool:
49  """Check if device family/type is known to the library."""
50  if device_family in ("7E", "EF"): # EDS or HobbyBoard
51  return device_type in DEVICE_SUPPORT[device_family]
52  return device_family in DEVICE_SUPPORT
53 
54 
55 class OneWireHub:
56  """Hub to communicate with server."""
57 
58  def __init__(self, hass: HomeAssistant) -> None:
59  """Initialize."""
60  self.hasshass = hass
61  self.owproxyowproxy: protocol._Proxy | None = None
62  self.devicesdevices: list[OWDeviceDescription] | None = None
63 
64  async def connect(self, host: str, port: int) -> None:
65  """Connect to the server."""
66  try:
67  self.owproxyowproxy = await self.hasshass.async_add_executor_job(
68  protocol.proxy, host, port
69  )
70  except protocol.ConnError as exc:
71  raise CannotConnect from exc
72 
73  async def initialize(self, config_entry: ConfigEntry) -> None:
74  """Initialize a config entry."""
75  host = config_entry.data[CONF_HOST]
76  port = config_entry.data[CONF_PORT]
77  _LOGGER.debug("Initializing connection to %s:%s", host, port)
78  await self.connectconnect(host, port)
79  await self.discover_devicesdiscover_devices()
80  if TYPE_CHECKING:
81  assert self.devicesdevices
82  # Register discovered devices on Hub
83  device_registry = dr.async_get(self.hasshass)
84  for device in self.devicesdevices:
85  device_info: DeviceInfo = device.device_info
86  device_registry.async_get_or_create(
87  config_entry_id=config_entry.entry_id,
88  identifiers=device_info[ATTR_IDENTIFIERS],
89  manufacturer=device_info[ATTR_MANUFACTURER],
90  model=device_info[ATTR_MODEL],
91  name=device_info[ATTR_NAME],
92  via_device=device_info.get(ATTR_VIA_DEVICE),
93  )
94 
95  async def discover_devices(self) -> None:
96  """Discover all devices."""
97  if self.devicesdevices is None:
98  self.devicesdevices = await self.hasshass.async_add_executor_job(
99  self._discover_devices_discover_devices
100  )
101 
103  self, path: str = "/", parent_id: str | None = None
104  ) -> list[OWDeviceDescription]:
105  """Discover all server devices."""
106  devices: list[OWDeviceDescription] = []
107  assert self.owproxyowproxy
108  for device_path in self.owproxyowproxy.dir(path):
109  device_id = os.path.split(os.path.split(device_path)[0])[1]
110  device_family = self.owproxyowproxy.read(f"{device_path}family").decode()
111  _LOGGER.debug("read `%sfamily`: %s", device_path, device_family)
112  device_type = self._get_device_type_get_device_type(device_path)
113  if not _is_known_device(device_family, device_type):
114  _LOGGER.warning(
115  "Ignoring unknown device family/type (%s/%s) found for device %s",
116  device_family,
117  device_type,
118  device_id,
119  )
120  continue
121  device_info: DeviceInfo = {
122  ATTR_IDENTIFIERS: {(DOMAIN, device_id)},
123  ATTR_MANUFACTURER: DEVICE_MANUFACTURER.get(
124  device_family, MANUFACTURER_MAXIM
125  ),
126  ATTR_MODEL: device_type,
127  ATTR_NAME: device_id,
128  }
129  if parent_id:
130  device_info[ATTR_VIA_DEVICE] = (DOMAIN, parent_id)
131  device = OWDeviceDescription(
132  device_info=device_info,
133  id=device_id,
134  family=device_family,
135  path=device_path,
136  type=device_type,
137  )
138  devices.append(device)
139  if device_branches := DEVICE_COUPLERS.get(device_family):
140  for branch in device_branches:
141  devices += self._discover_devices_discover_devices(
142  f"{device_path}{branch}", device_id
143  )
144 
145  return devices
146 
147  def _get_device_type(self, device_path: str) -> str | None:
148  """Get device model."""
149  if TYPE_CHECKING:
150  assert self.owproxyowproxy
151  try:
152  device_type = self.owproxyowproxy.read(f"{device_path}type").decode()
153  except protocol.ProtocolError as exc:
154  _LOGGER.debug("Unable to read `%stype`: %s", device_path, exc)
155  return None
156  _LOGGER.debug("read `%stype`: %s", device_path, device_type)
157  if device_type == "EDS":
158  device_type = self.owproxyowproxy.read(f"{device_path}device_type").decode()
159  _LOGGER.debug("read `%sdevice_type`: %s", device_path, device_type)
160  if TYPE_CHECKING:
161  assert isinstance(device_type, str)
162  return device_type
163 
164 
166  """Error to indicate we cannot connect."""
167 
168 
169 class InvalidPath(HomeAssistantError):
170  """Error to indicate the path is invalid."""
str|None _get_device_type(self, str device_path)
Definition: onewirehub.py:147
None initialize(self, ConfigEntry config_entry)
Definition: onewirehub.py:73
list[OWDeviceDescription] _discover_devices(self, str path="/", str|None parent_id=None)
Definition: onewirehub.py:104
bool _is_known_device(str device_family, str|None device_type)
Definition: onewirehub.py:48