mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 04:37:06 +00:00
2023.1.6 (#86251)
This commit is contained in:
commit
9a4329aa1d
@ -47,6 +47,7 @@ async def verify_redirect_uri(
|
|||||||
if client_id == "https://home-assistant.io/android" and redirect_uri in (
|
if client_id == "https://home-assistant.io/android" and redirect_uri in (
|
||||||
"homeassistant://auth-callback",
|
"homeassistant://auth-callback",
|
||||||
"https://wear.googleapis.com/3p_auth/io.homeassistant.companion.android",
|
"https://wear.googleapis.com/3p_auth/io.homeassistant.companion.android",
|
||||||
|
"https://wear.googleapis-cn.com/3p_auth/io.homeassistant.companion.android",
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@
|
|||||||
"connectable": false
|
"connectable": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"requirements": ["govee-ble==0.21.0"],
|
"requirements": ["govee-ble==0.21.1"],
|
||||||
"dependencies": ["bluetooth"],
|
"dependencies": ["bluetooth"],
|
||||||
"codeowners": ["@bdraco", "@PierreAronnax"],
|
"codeowners": ["@bdraco", "@PierreAronnax"],
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push"
|
||||||
|
@ -83,6 +83,7 @@ async def _async_send_historical_events(
|
|||||||
formatter: Callable[[int, Any], dict[str, Any]],
|
formatter: Callable[[int, Any], dict[str, Any]],
|
||||||
event_processor: EventProcessor,
|
event_processor: EventProcessor,
|
||||||
partial: bool,
|
partial: bool,
|
||||||
|
force_send: bool = False,
|
||||||
) -> dt | None:
|
) -> dt | None:
|
||||||
"""Select historical data from the database and deliver it to the websocket.
|
"""Select historical data from the database and deliver it to the websocket.
|
||||||
|
|
||||||
@ -116,7 +117,7 @@ async def _async_send_historical_events(
|
|||||||
# if its the last one (not partial) so
|
# if its the last one (not partial) so
|
||||||
# consumers of the api know their request was
|
# consumers of the api know their request was
|
||||||
# answered but there were no results
|
# answered but there were no results
|
||||||
if last_event_time or not partial:
|
if last_event_time or not partial or force_send:
|
||||||
connection.send_message(message)
|
connection.send_message(message)
|
||||||
return last_event_time
|
return last_event_time
|
||||||
|
|
||||||
@ -150,7 +151,7 @@ async def _async_send_historical_events(
|
|||||||
# if its the last one (not partial) so
|
# if its the last one (not partial) so
|
||||||
# consumers of the api know their request was
|
# consumers of the api know their request was
|
||||||
# answered but there were no results
|
# answered but there were no results
|
||||||
if older_query_last_event_time or not partial:
|
if older_query_last_event_time or not partial or force_send:
|
||||||
connection.send_message(older_message)
|
connection.send_message(older_message)
|
||||||
|
|
||||||
# Returns the time of the newest event
|
# Returns the time of the newest event
|
||||||
@ -384,6 +385,11 @@ async def ws_event_stream(
|
|||||||
messages.event_message,
|
messages.event_message,
|
||||||
event_processor,
|
event_processor,
|
||||||
partial=True,
|
partial=True,
|
||||||
|
# Force a send since the wait for the sync task
|
||||||
|
# can take a a while if the recorder is busy and
|
||||||
|
# we want to make sure the client is not still spinning
|
||||||
|
# because it is waiting for the first message
|
||||||
|
force_send=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
live_stream.task = asyncio.create_task(
|
live_stream.task = asyncio.create_task(
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
"""Matter to Home Assistant adapter."""
|
"""Matter to Home Assistant adapter."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, cast
|
||||||
|
|
||||||
from chip.clusters import Objects as all_clusters
|
from chip.clusters import Objects as all_clusters
|
||||||
from matter_server.common.models.events import EventType
|
from matter_server.common.models.events import EventType
|
||||||
from matter_server.common.models.node_device import AbstractMatterNodeDevice
|
from matter_server.common.models.node_device import (
|
||||||
|
AbstractMatterNodeDevice,
|
||||||
|
MatterBridgedNodeDevice,
|
||||||
|
)
|
||||||
|
from matter_server.common.models.server_information import ServerInfo
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
@ -13,8 +17,9 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
from .const import DOMAIN, LOGGER
|
from .const import DOMAIN, ID_TYPE_DEVICE_ID, ID_TYPE_SERIAL, LOGGER
|
||||||
from .device_platform import DEVICE_PLATFORM
|
from .device_platform import DEVICE_PLATFORM
|
||||||
|
from .helpers import get_device_id
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from matter_server.client import MatterClient
|
from matter_server.client import MatterClient
|
||||||
@ -66,31 +71,56 @@ class MatterAdapter:
|
|||||||
bridge_unique_id: str | None = None
|
bridge_unique_id: str | None = None
|
||||||
|
|
||||||
if node.aggregator_device_type_instance is not None and (
|
if node.aggregator_device_type_instance is not None and (
|
||||||
node_info := node.root_device_type_instance.get_cluster(all_clusters.Basic)
|
node.root_device_type_instance.get_cluster(all_clusters.Basic)
|
||||||
):
|
):
|
||||||
self._create_device_registry(
|
# create virtual (parent) device for bridge node device
|
||||||
node_info, node_info.nodeLabel or "Hub device", None
|
bridge_device = MatterBridgedNodeDevice(
|
||||||
|
node.aggregator_device_type_instance
|
||||||
)
|
)
|
||||||
bridge_unique_id = node_info.uniqueID
|
self._create_device_registry(bridge_device)
|
||||||
|
server_info = cast(ServerInfo, self.matter_client.server_info)
|
||||||
|
bridge_unique_id = get_device_id(server_info, bridge_device)
|
||||||
|
|
||||||
for node_device in node.node_devices:
|
for node_device in node.node_devices:
|
||||||
self._setup_node_device(node_device, bridge_unique_id)
|
self._setup_node_device(node_device, bridge_unique_id)
|
||||||
|
|
||||||
def _create_device_registry(
|
def _create_device_registry(
|
||||||
self,
|
self,
|
||||||
info: all_clusters.Basic | all_clusters.BridgedDeviceBasic,
|
node_device: AbstractMatterNodeDevice,
|
||||||
name: str,
|
bridge_unique_id: str | None = None,
|
||||||
bridge_unique_id: str | None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create a device registry entry."""
|
"""Create a device registry entry."""
|
||||||
|
server_info = cast(ServerInfo, self.matter_client.server_info)
|
||||||
|
|
||||||
|
basic_info = node_device.device_info()
|
||||||
|
device_type_instances = node_device.device_type_instances()
|
||||||
|
|
||||||
|
name = basic_info.nodeLabel
|
||||||
|
if not name and isinstance(node_device, MatterBridgedNodeDevice):
|
||||||
|
# fallback name for Bridge
|
||||||
|
name = "Hub device"
|
||||||
|
elif not name and device_type_instances:
|
||||||
|
# use the productName if no node label is present
|
||||||
|
name = basic_info.productName
|
||||||
|
|
||||||
|
node_device_id = get_device_id(
|
||||||
|
server_info,
|
||||||
|
node_device,
|
||||||
|
)
|
||||||
|
identifiers = {(DOMAIN, f"{ID_TYPE_DEVICE_ID}_{node_device_id}")}
|
||||||
|
# if available, we also add the serialnumber as identifier
|
||||||
|
if basic_info.serialNumber and "test" not in basic_info.serialNumber.lower():
|
||||||
|
# prefix identifier with 'serial_' to be able to filter it
|
||||||
|
identifiers.add((DOMAIN, f"{ID_TYPE_SERIAL}_{basic_info.serialNumber}"))
|
||||||
|
|
||||||
dr.async_get(self.hass).async_get_or_create(
|
dr.async_get(self.hass).async_get_or_create(
|
||||||
name=name,
|
name=name,
|
||||||
config_entry_id=self.config_entry.entry_id,
|
config_entry_id=self.config_entry.entry_id,
|
||||||
identifiers={(DOMAIN, info.uniqueID)},
|
identifiers=identifiers,
|
||||||
hw_version=info.hardwareVersionString,
|
hw_version=basic_info.hardwareVersionString,
|
||||||
sw_version=info.softwareVersionString,
|
sw_version=basic_info.softwareVersionString,
|
||||||
manufacturer=info.vendorName,
|
manufacturer=basic_info.vendorName,
|
||||||
model=info.productName,
|
model=basic_info.productName,
|
||||||
via_device=(DOMAIN, bridge_unique_id) if bridge_unique_id else None,
|
via_device=(DOMAIN, bridge_unique_id) if bridge_unique_id else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -98,17 +128,9 @@ class MatterAdapter:
|
|||||||
self, node_device: AbstractMatterNodeDevice, bridge_unique_id: str | None
|
self, node_device: AbstractMatterNodeDevice, bridge_unique_id: str | None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up a node device."""
|
"""Set up a node device."""
|
||||||
node = node_device.node()
|
self._create_device_registry(node_device, bridge_unique_id)
|
||||||
basic_info = node_device.device_info()
|
# run platform discovery from device type instances
|
||||||
device_type_instances = node_device.device_type_instances()
|
for instance in node_device.device_type_instances():
|
||||||
|
|
||||||
name = basic_info.nodeLabel
|
|
||||||
if not name and device_type_instances:
|
|
||||||
name = f"{device_type_instances[0].device_type.__doc__[:-1]} {node.node_id}"
|
|
||||||
|
|
||||||
self._create_device_registry(basic_info, name, bridge_unique_id)
|
|
||||||
|
|
||||||
for instance in device_type_instances:
|
|
||||||
created = False
|
created = False
|
||||||
|
|
||||||
for platform, devices in DEVICE_PLATFORM.items():
|
for platform, devices in DEVICE_PLATFORM.items():
|
||||||
|
@ -8,3 +8,7 @@ CONF_USE_ADDON = "use_addon"
|
|||||||
|
|
||||||
DOMAIN = "matter"
|
DOMAIN = "matter"
|
||||||
LOGGER = logging.getLogger(__package__)
|
LOGGER = logging.getLogger(__package__)
|
||||||
|
|
||||||
|
# prefixes to identify device identifier id types
|
||||||
|
ID_TYPE_DEVICE_ID = "deviceid"
|
||||||
|
ID_TYPE_SERIAL = "serial"
|
||||||
|
@ -5,16 +5,18 @@ from abc import abstractmethod
|
|||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
from matter_server.common.models.device_type_instance import MatterDeviceTypeInstance
|
from matter_server.common.models.device_type_instance import MatterDeviceTypeInstance
|
||||||
from matter_server.common.models.events import EventType
|
from matter_server.common.models.events import EventType
|
||||||
from matter_server.common.models.node_device import AbstractMatterNodeDevice
|
from matter_server.common.models.node_device import AbstractMatterNodeDevice
|
||||||
|
from matter_server.common.models.server_information import ServerInfo
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback
|
||||||
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN, ID_TYPE_DEVICE_ID
|
||||||
|
from .helpers import get_device_id, get_operational_instance_id
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from matter_server.client import MatterClient
|
from matter_server.client import MatterClient
|
||||||
@ -55,24 +57,21 @@ class MatterEntity(Entity):
|
|||||||
self._node_device = node_device
|
self._node_device = node_device
|
||||||
self._device_type_instance = device_type_instance
|
self._device_type_instance = device_type_instance
|
||||||
self.entity_description = entity_description
|
self.entity_description = entity_description
|
||||||
node = device_type_instance.node
|
|
||||||
self._unsubscribes: list[Callable] = []
|
self._unsubscribes: list[Callable] = []
|
||||||
# for fast lookups we create a mapping to the attribute paths
|
# for fast lookups we create a mapping to the attribute paths
|
||||||
self._attributes_map: dict[type, str] = {}
|
|
||||||
server_info = matter_client.server_info
|
|
||||||
# The server info is set when the client connects to the server.
|
# The server info is set when the client connects to the server.
|
||||||
assert server_info is not None
|
self._attributes_map: dict[type, str] = {}
|
||||||
|
server_info = cast(ServerInfo, self.matter_client.server_info)
|
||||||
|
# create unique_id based on "Operational Instance Name" and endpoint/device type
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{server_info.compressed_fabric_id}-"
|
f"{get_operational_instance_id(server_info, self._node_device.node())}-"
|
||||||
f"{node.unique_id}-"
|
|
||||||
f"{device_type_instance.endpoint}-"
|
f"{device_type_instance.endpoint}-"
|
||||||
f"{device_type_instance.device_type.device_type}"
|
f"{device_type_instance.device_type.device_type}"
|
||||||
)
|
)
|
||||||
|
node_device_id = get_device_id(server_info, node_device)
|
||||||
@property
|
self._attr_device_info = DeviceInfo(
|
||||||
def device_info(self) -> DeviceInfo | None:
|
identifiers={(DOMAIN, f"{ID_TYPE_DEVICE_ID}_{node_device_id}")}
|
||||||
"""Return device info for device registry."""
|
)
|
||||||
return {"identifiers": {(DOMAIN, self._node_device.device_info().uniqueID)}}
|
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Handle being added to Home Assistant."""
|
"""Handle being added to Home Assistant."""
|
||||||
@ -115,7 +114,7 @@ class MatterEntity(Entity):
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def get_matter_attribute(self, attribute: type) -> MatterAttribute | None:
|
def get_matter_attribute(self, attribute: type) -> MatterAttribute | None:
|
||||||
"""Lookup MatterAttribute instance on device instance by providing the attribute class."""
|
"""Lookup MatterAttribute on device by providing the attribute class."""
|
||||||
return next(
|
return next(
|
||||||
(
|
(
|
||||||
x
|
x
|
||||||
|
@ -10,6 +10,10 @@ from homeassistant.core import HomeAssistant, callback
|
|||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from matter_server.common.models.node import MatterNode
|
||||||
|
from matter_server.common.models.node_device import AbstractMatterNodeDevice
|
||||||
|
from matter_server.common.models.server_information import ServerInfo
|
||||||
|
|
||||||
from .adapter import MatterAdapter
|
from .adapter import MatterAdapter
|
||||||
|
|
||||||
|
|
||||||
@ -25,7 +29,32 @@ class MatterEntryData:
|
|||||||
def get_matter(hass: HomeAssistant) -> MatterAdapter:
|
def get_matter(hass: HomeAssistant) -> MatterAdapter:
|
||||||
"""Return MatterAdapter instance."""
|
"""Return MatterAdapter instance."""
|
||||||
# NOTE: This assumes only one Matter connection/fabric can exist.
|
# NOTE: This assumes only one Matter connection/fabric can exist.
|
||||||
# Shall we support connecting to multiple servers in the client or by config entries?
|
# Shall we support connecting to multiple servers in the client or by
|
||||||
# In case of the config entry we need to fix this.
|
# config entries? In case of the config entry we need to fix this.
|
||||||
matter_entry_data: MatterEntryData = next(iter(hass.data[DOMAIN].values()))
|
matter_entry_data: MatterEntryData = next(iter(hass.data[DOMAIN].values()))
|
||||||
return matter_entry_data.adapter
|
return matter_entry_data.adapter
|
||||||
|
|
||||||
|
|
||||||
|
def get_operational_instance_id(
|
||||||
|
server_info: ServerInfo,
|
||||||
|
node: MatterNode,
|
||||||
|
) -> str:
|
||||||
|
"""Return `Operational Instance Name` for given MatterNode."""
|
||||||
|
fabric_id_hex = f"{server_info.compressed_fabric_id:016X}"
|
||||||
|
node_id_hex = f"{node.node_id:016X}"
|
||||||
|
# Operational instance id matches the mDNS advertisement for the node
|
||||||
|
# this is the recommended ID to recognize a unique matter node (within a fabric).
|
||||||
|
return f"{fabric_id_hex}-{node_id_hex}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_device_id(
|
||||||
|
server_info: ServerInfo,
|
||||||
|
node_device: AbstractMatterNodeDevice,
|
||||||
|
) -> str:
|
||||||
|
"""Return HA device_id for the given MatterNodeDevice."""
|
||||||
|
operational_instance_id = get_operational_instance_id(
|
||||||
|
server_info, node_device.node()
|
||||||
|
)
|
||||||
|
# Append nodedevice(type) to differentiate between a root node
|
||||||
|
# and bridge within Home Assistant devices.
|
||||||
|
return f"{operational_instance_id}-{node_device.__class__.__name__}"
|
||||||
|
@ -475,6 +475,11 @@ class ShellyRpcCoordinator(DataUpdateCoordinator[None]):
|
|||||||
|
|
||||||
async def _async_disconnected(self) -> None:
|
async def _async_disconnected(self) -> None:
|
||||||
"""Handle device disconnected."""
|
"""Handle device disconnected."""
|
||||||
|
# Sleeping devices send data and disconnects
|
||||||
|
# There are no disconnect events for sleeping devices
|
||||||
|
if self.entry.data.get(CONF_SLEEP_PERIOD):
|
||||||
|
return
|
||||||
|
|
||||||
async with self._connection_lock:
|
async with self._connection_lock:
|
||||||
if not self.connected: # Already disconnected
|
if not self.connected: # Already disconnected
|
||||||
return
|
return
|
||||||
|
@ -8,7 +8,7 @@ from .backports.enum import StrEnum
|
|||||||
APPLICATION_NAME: Final = "HomeAssistant"
|
APPLICATION_NAME: Final = "HomeAssistant"
|
||||||
MAJOR_VERSION: Final = 2023
|
MAJOR_VERSION: Final = 2023
|
||||||
MINOR_VERSION: Final = 1
|
MINOR_VERSION: Final = 1
|
||||||
PATCH_VERSION: Final = "5"
|
PATCH_VERSION: Final = "6"
|
||||||
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
__short_version__: Final = f"{MAJOR_VERSION}.{MINOR_VERSION}"
|
||||||
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
__version__: Final = f"{__short_version__}.{PATCH_VERSION}"
|
||||||
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
REQUIRED_PYTHON_VER: Final[tuple[int, int, int]] = (3, 9, 0)
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "homeassistant"
|
name = "homeassistant"
|
||||||
version = "2023.1.5"
|
version = "2023.1.6"
|
||||||
license = {text = "Apache-2.0"}
|
license = {text = "Apache-2.0"}
|
||||||
description = "Open-source home automation platform running on Python 3."
|
description = "Open-source home automation platform running on Python 3."
|
||||||
readme = "README.rst"
|
readme = "README.rst"
|
||||||
|
@ -803,7 +803,7 @@ googlemaps==2.5.1
|
|||||||
goslide-api==0.5.1
|
goslide-api==0.5.1
|
||||||
|
|
||||||
# homeassistant.components.govee_ble
|
# homeassistant.components.govee_ble
|
||||||
govee-ble==0.21.0
|
govee-ble==0.21.1
|
||||||
|
|
||||||
# homeassistant.components.remote_rpi_gpio
|
# homeassistant.components.remote_rpi_gpio
|
||||||
gpiozero==1.6.2
|
gpiozero==1.6.2
|
||||||
|
@ -607,7 +607,7 @@ google-nest-sdm==2.2.2
|
|||||||
googlemaps==2.5.1
|
googlemaps==2.5.1
|
||||||
|
|
||||||
# homeassistant.components.govee_ble
|
# homeassistant.components.govee_ble
|
||||||
govee-ble==0.21.0
|
govee-ble==0.21.1
|
||||||
|
|
||||||
# homeassistant.components.gree
|
# homeassistant.components.gree
|
||||||
greeclimate==1.3.0
|
greeclimate==1.3.0
|
||||||
|
@ -190,9 +190,19 @@ async def test_verify_redirect_uri_android_ios(client_id):
|
|||||||
client_id,
|
client_id,
|
||||||
"https://wear.googleapis.com/3p_auth/io.homeassistant.companion.android",
|
"https://wear.googleapis.com/3p_auth/io.homeassistant.companion.android",
|
||||||
)
|
)
|
||||||
|
assert await indieauth.verify_redirect_uri(
|
||||||
|
None,
|
||||||
|
client_id,
|
||||||
|
"https://wear.googleapis-cn.com/3p_auth/io.homeassistant.companion.android",
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
assert not await indieauth.verify_redirect_uri(
|
assert not await indieauth.verify_redirect_uri(
|
||||||
None,
|
None,
|
||||||
client_id,
|
client_id,
|
||||||
"https://wear.googleapis.com/3p_auth/io.homeassistant.companion.android",
|
"https://wear.googleapis.com/3p_auth/io.homeassistant.companion.android",
|
||||||
)
|
)
|
||||||
|
assert not await indieauth.verify_redirect_uri(
|
||||||
|
None,
|
||||||
|
client_id,
|
||||||
|
"https://wear.googleapis-cn.com/3p_auth/io.homeassistant.companion.android",
|
||||||
|
)
|
||||||
|
@ -1817,6 +1817,7 @@ async def test_subscribe_unsubscribe_logbook_stream_device(
|
|||||||
assert msg["id"] == 7
|
assert msg["id"] == 7
|
||||||
assert msg["type"] == TYPE_RESULT
|
assert msg["type"] == TYPE_RESULT
|
||||||
assert msg["success"]
|
assert msg["success"]
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
# There are no answers to our initial query
|
# There are no answers to our initial query
|
||||||
# so we get an empty reply. This is to ensure
|
# so we get an empty reply. This is to ensure
|
||||||
@ -1828,6 +1829,15 @@ async def test_subscribe_unsubscribe_logbook_stream_device(
|
|||||||
assert msg["id"] == 7
|
assert msg["id"] == 7
|
||||||
assert msg["type"] == "event"
|
assert msg["type"] == "event"
|
||||||
assert msg["event"]["events"] == []
|
assert msg["event"]["events"] == []
|
||||||
|
assert "partial" in msg["event"]
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
|
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
|
||||||
|
assert msg["id"] == 7
|
||||||
|
assert msg["type"] == "event"
|
||||||
|
assert msg["event"]["events"] == []
|
||||||
|
assert "partial" not in msg["event"]
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
hass.states.async_set("binary_sensor.should_not_appear", STATE_ON)
|
hass.states.async_set("binary_sensor.should_not_appear", STATE_ON)
|
||||||
hass.states.async_set("binary_sensor.should_not_appear", STATE_OFF)
|
hass.states.async_set("binary_sensor.should_not_appear", STATE_OFF)
|
||||||
@ -1942,6 +1952,14 @@ async def test_logbook_stream_match_multiple_entities(
|
|||||||
assert msg["id"] == 7
|
assert msg["id"] == 7
|
||||||
assert msg["type"] == "event"
|
assert msg["type"] == "event"
|
||||||
assert msg["event"]["events"] == []
|
assert msg["event"]["events"] == []
|
||||||
|
assert "partial" in msg["event"]
|
||||||
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
|
msg = await asyncio.wait_for(websocket_client.receive_json(), 2)
|
||||||
|
assert msg["id"] == 7
|
||||||
|
assert msg["type"] == "event"
|
||||||
|
assert msg["event"]["events"] == []
|
||||||
|
assert "partial" not in msg["event"]
|
||||||
await async_wait_recording_done(hass)
|
await async_wait_recording_done(hass)
|
||||||
|
|
||||||
hass.states.async_set("binary_sensor.should_not_appear", STATE_ON)
|
hass.states.async_set("binary_sensor.should_not_appear", STATE_ON)
|
||||||
|
@ -175,7 +175,7 @@
|
|||||||
"attribute_id": 5,
|
"attribute_id": 5,
|
||||||
"attribute_type": "chip.clusters.Objects.Basic.Attributes.NodeLabel",
|
"attribute_type": "chip.clusters.Objects.Basic.Attributes.NodeLabel",
|
||||||
"attribute_name": "NodeLabel",
|
"attribute_name": "NodeLabel",
|
||||||
"value": "Mock OnOff Plugin Unit"
|
"value": ""
|
||||||
},
|
},
|
||||||
"0/40/6": {
|
"0/40/6": {
|
||||||
"node_id": 1,
|
"node_id": 1,
|
||||||
|
@ -469,7 +469,7 @@
|
|||||||
"attribute_id": 15,
|
"attribute_id": 15,
|
||||||
"attribute_type": "chip.clusters.Objects.Basic.Attributes.SerialNumber",
|
"attribute_type": "chip.clusters.Objects.Basic.Attributes.SerialNumber",
|
||||||
"attribute_name": "SerialNumber",
|
"attribute_name": "SerialNumber",
|
||||||
"value": "TEST_SN"
|
"value": "12345678"
|
||||||
},
|
},
|
||||||
"0/40/16": {
|
"0/40/16": {
|
||||||
"node_id": 1,
|
"node_id": 1,
|
||||||
|
@ -27,10 +27,14 @@ async def test_device_registry_single_node_device(
|
|||||||
)
|
)
|
||||||
|
|
||||||
dev_reg = dr.async_get(hass)
|
dev_reg = dr.async_get(hass)
|
||||||
|
entry = dev_reg.async_get_device(
|
||||||
entry = dev_reg.async_get_device({(DOMAIN, "mock-onoff-light")})
|
{(DOMAIN, "deviceid_00000000000004D2-0000000000000001-MatterNodeDevice")}
|
||||||
|
)
|
||||||
assert entry is not None
|
assert entry is not None
|
||||||
|
|
||||||
|
# test serial id present as additional identifier
|
||||||
|
assert (DOMAIN, "serial_12345678") in entry.identifiers
|
||||||
|
|
||||||
assert entry.name == "Mock OnOff Light"
|
assert entry.name == "Mock OnOff Light"
|
||||||
assert entry.manufacturer == "Nabu Casa"
|
assert entry.manufacturer == "Nabu Casa"
|
||||||
assert entry.model == "Mock Light"
|
assert entry.model == "Mock Light"
|
||||||
@ -38,6 +42,30 @@ async def test_device_registry_single_node_device(
|
|||||||
assert entry.sw_version == "v1.0"
|
assert entry.sw_version == "v1.0"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_device_registry_single_node_device_alt(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
matter_client: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test additional device with different attribute values."""
|
||||||
|
await setup_integration_with_node_fixture(
|
||||||
|
hass,
|
||||||
|
"on-off-plugin-unit",
|
||||||
|
matter_client,
|
||||||
|
)
|
||||||
|
|
||||||
|
dev_reg = dr.async_get(hass)
|
||||||
|
entry = dev_reg.async_get_device(
|
||||||
|
{(DOMAIN, "deviceid_00000000000004D2-0000000000000001-MatterNodeDevice")}
|
||||||
|
)
|
||||||
|
assert entry is not None
|
||||||
|
|
||||||
|
# test name is derived from productName (because nodeLabel is absent)
|
||||||
|
assert entry.name == "Mock OnOffPluginUnit (powerplug/switch)"
|
||||||
|
|
||||||
|
# test serial id NOT present as additional identifier
|
||||||
|
assert (DOMAIN, "serial_TEST_SN") not in entry.identifiers
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip("Waiting for a new test fixture")
|
@pytest.mark.skip("Waiting for a new test fixture")
|
||||||
async def test_device_registry_bridge(
|
async def test_device_registry_bridge(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
|
@ -30,7 +30,7 @@ async def test_turn_on(
|
|||||||
switch_node: MatterNode,
|
switch_node: MatterNode,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test turning on a switch."""
|
"""Test turning on a switch."""
|
||||||
state = hass.states.get("switch.mock_onoff_plugin_unit")
|
state = hass.states.get("switch.mock_onoffpluginunit_powerplug_switch")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == "off"
|
assert state.state == "off"
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ async def test_turn_on(
|
|||||||
"switch",
|
"switch",
|
||||||
"turn_on",
|
"turn_on",
|
||||||
{
|
{
|
||||||
"entity_id": "switch.mock_onoff_plugin_unit",
|
"entity_id": "switch.mock_onoffpluginunit_powerplug_switch",
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
@ -53,7 +53,7 @@ async def test_turn_on(
|
|||||||
set_node_attribute(switch_node, 1, 6, 0, True)
|
set_node_attribute(switch_node, 1, 6, 0, True)
|
||||||
await trigger_subscription_callback(hass, matter_client)
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
state = hass.states.get("switch.mock_onoff_plugin_unit")
|
state = hass.states.get("switch.mock_onoffpluginunit_powerplug_switch")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == "on"
|
assert state.state == "on"
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ async def test_turn_off(
|
|||||||
switch_node: MatterNode,
|
switch_node: MatterNode,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test turning off a switch."""
|
"""Test turning off a switch."""
|
||||||
state = hass.states.get("switch.mock_onoff_plugin_unit")
|
state = hass.states.get("switch.mock_onoffpluginunit_powerplug_switch")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == "off"
|
assert state.state == "off"
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ async def test_turn_off(
|
|||||||
"switch",
|
"switch",
|
||||||
"turn_off",
|
"turn_off",
|
||||||
{
|
{
|
||||||
"entity_id": "switch.mock_onoff_plugin_unit",
|
"entity_id": "switch.mock_onoffpluginunit_powerplug_switch",
|
||||||
},
|
},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user