1 """The Matter integration."""
3 from __future__
import annotations
6 from functools
import cache
8 from matter_server.client
import MatterClient
9 from matter_server.client.exceptions
import (
16 from matter_server.common.errors
import MatterError, NodeNotExists
31 from .adapter
import MatterAdapter
32 from .addon
import get_addon_manager
33 from .api
import async_register_api
34 from .const
import CONF_INTEGRATION_CREATED_ADDON, CONF_USE_ADDON, DOMAIN, LOGGER
35 from .discovery
import SUPPORTED_PLATFORMS
36 from .helpers
import (
39 get_node_from_device_entry,
40 node_from_ha_device_id,
42 from .models
import MatterDeviceInfo
45 LISTEN_READY_TIMEOUT = 30
51 hass: HomeAssistant, device_id: str
52 ) -> MatterDeviceInfo |
None:
53 """Return Matter device info or None if device does not exist."""
55 if not hass.data.get(DOMAIN,
False)
or not (
61 unique_id=node.device_info.uniqueID,
62 vendor_id=hex(node.device_info.vendorID),
63 product_id=hex(node.device_info.productID),
68 """Set up Matter from a config entry."""
69 if use_addon := entry.data.get(CONF_USE_ADDON):
74 async
with asyncio.timeout(CONNECT_TIMEOUT):
75 await matter_client.connect()
76 except (CannotConnect, TimeoutError)
as err:
78 except InvalidServerVersion
as err:
79 if isinstance(err, ServerVersionTooOld):
82 addon_manager.async_schedule_update_addon(catch_error=
True)
87 "server_version_version_too_old",
89 severity=IssueSeverity.ERROR,
90 translation_key=
"server_version_version_too_old",
92 elif isinstance(err, ServerVersionTooNew):
96 "server_version_version_too_new",
98 severity=IssueSeverity.ERROR,
99 translation_key=
"server_version_version_too_new",
103 except Exception
as err:
104 LOGGER.exception(
"Failed to connect to matter server")
106 "Unknown error connecting to the Matter server"
112 async
def on_hass_stop(event: Event) ->
None:
113 """Handle incoming stop event from Home Assistant."""
114 await matter_client.disconnect()
116 entry.async_on_unload(
117 hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
124 init_ready = asyncio.Event()
125 listen_task = asyncio.create_task(
130 async
with asyncio.timeout(LISTEN_READY_TIMEOUT):
131 await init_ready.wait()
132 except TimeoutError
as err:
138 await matter_client.set_default_fabric_label(
139 hass.config.location_name
or "Home"
141 except (NotConnected, MatterError)
as err:
145 if DOMAIN
not in hass.data:
146 hass.data[DOMAIN] = {}
151 hass.data[DOMAIN][entry.entry_id] =
MatterEntryData(matter, listen_task)
153 await hass.config_entries.async_forward_entry_setups(entry, SUPPORTED_PLATFORMS)
154 await matter.setup_nodes()
157 if listen_task.done()
and (listen_error := listen_task.exception())
is not None:
158 await hass.config_entries.async_unload_platforms(entry, SUPPORTED_PLATFORMS)
159 hass.data[DOMAIN].pop(entry.entry_id)
161 await matter_client.disconnect()
171 matter_client: MatterClient,
172 init_ready: asyncio.Event,
174 """Listen with the client."""
176 await matter_client.start_listening(init_ready)
177 except MatterError
as err:
178 if entry.state != ConfigEntryState.LOADED:
180 LOGGER.error(
"Failed to listen: %s", err)
181 except Exception
as err:
183 LOGGER.exception(
"Unexpected exception: %s", err)
184 if entry.state != ConfigEntryState.LOADED:
187 if not hass.is_stopping:
188 LOGGER.debug(
"Disconnected from server. Reloading integration")
189 hass.async_create_task(hass.config_entries.async_reload(entry.entry_id))
193 """Unload a config entry."""
194 unload_ok = await hass.config_entries.async_unload_platforms(
195 entry, SUPPORTED_PLATFORMS
199 matter_entry_data: MatterEntryData = hass.data[DOMAIN].pop(entry.entry_id)
200 matter_entry_data.listen_task.cancel()
201 await matter_entry_data.adapter.matter_client.disconnect()
203 if entry.data.get(CONF_USE_ADDON)
and entry.disabled_by:
205 LOGGER.debug(
"Stopping Matter Server add-on")
207 await addon_manager.async_stop_addon()
208 except AddonError
as err:
209 LOGGER.error(
"Failed to stop the Matter Server add-on: %s", err)
216 """Config entry is being removed."""
218 if not entry.data.get(CONF_INTEGRATION_CREATED_ADDON):
223 await addon_manager.async_stop_addon()
224 except AddonError
as err:
228 await addon_manager.async_create_backup()
229 except AddonError
as err:
233 await addon_manager.async_uninstall_addon()
234 except AddonError
as err:
239 hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
241 """Remove all via devices associated with a device."""
242 device_registry = dr.async_get(hass)
243 devices = dr.async_entries_for_config_entry(device_registry, config_entry.entry_id)
244 for device
in devices:
245 if device.via_device_id == device_entry.id:
246 device_registry.async_update_device(
247 device.id, remove_config_entry_id=config_entry.entry_id
252 hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
254 """Remove a config entry from a device."""
263 if device_entry.via_device_id:
269 await matter.matter_client.remove_node(node.node_id)
270 except NodeNotExists:
272 LOGGER.debug(
"Node %s didn't exist on the Matter server", node.node_id)
275 if node.is_bridge_device:
282 """Ensure that Matter Server add-on is installed and running."""
285 addon_info = await addon_manager.async_get_addon_info()
286 except AddonError
as err:
289 addon_state = addon_info.state
291 if addon_state == AddonState.NOT_INSTALLED:
292 addon_manager.async_schedule_install_setup_addon(
296 raise ConfigEntryNotReady
298 if addon_state == AddonState.NOT_RUNNING:
299 addon_manager.async_schedule_start_addon(catch_error=
True)
300 raise ConfigEntryNotReady
305 """Ensure that Matter Server add-on is updated and running.
307 May only be used as part of async_setup_entry above.
310 if addon_manager.task_in_progress():
311 raise ConfigEntryNotReady
AddonManager get_addon_manager(HomeAssistant hass)
None async_register_api(HomeAssistant hass)
MatterAdapter get_matter(HomeAssistant hass)
MatterNode|None node_from_ha_device_id(HomeAssistant hass, str ha_device_id)
MatterNode|None get_node_from_device_entry(HomeAssistant hass, dr.DeviceEntry device)
None _client_listen(HomeAssistant hass, ConfigEntry entry, MatterClient matter_client, asyncio.Event init_ready)
None _async_ensure_addon_running(HomeAssistant hass, ConfigEntry entry)
bool async_remove_config_entry_device(HomeAssistant hass, ConfigEntry config_entry, dr.DeviceEntry device_entry)
AddonManager _get_addon_manager(HomeAssistant hass)
MatterDeviceInfo|None get_matter_device_info(HomeAssistant hass, str device_id)
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
None async_remove_entry(HomeAssistant hass, ConfigEntry entry)
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
None _remove_via_devices(HomeAssistant hass, ConfigEntry config_entry, dr.DeviceEntry device_entry)
None async_create_issue(HomeAssistant hass, str entry_id)
None async_delete_issue(HomeAssistant hass, str entry_id)
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)