Only compute homekit_controller accessory_info when entity is added or config changes (#102145)
parent
e6895b5738
commit
d8e541a284
|
@ -4,6 +4,7 @@ from __future__ import annotations
|
|||
import asyncio
|
||||
from collections.abc import Callable, Iterable
|
||||
from datetime import datetime, timedelta
|
||||
from functools import partial
|
||||
import logging
|
||||
from operator import attrgetter
|
||||
from types import MappingProxyType
|
||||
|
@ -144,6 +145,7 @@ class HKDevice:
|
|||
)
|
||||
|
||||
self._availability_callbacks: set[CALLBACK_TYPE] = set()
|
||||
self._config_changed_callbacks: set[CALLBACK_TYPE] = set()
|
||||
self._subscriptions: dict[tuple[int, int], set[CALLBACK_TYPE]] = {}
|
||||
|
||||
@property
|
||||
|
@ -605,6 +607,8 @@ class HKDevice:
|
|||
await self.async_process_entity_map()
|
||||
if self.watchable_characteristics:
|
||||
await self.pairing.subscribe(self.watchable_characteristics)
|
||||
for callback_ in self._config_changed_callbacks:
|
||||
callback_()
|
||||
await self.async_update()
|
||||
await self.async_add_new_entities()
|
||||
|
||||
|
@ -805,6 +809,16 @@ class HKDevice:
|
|||
for callback_ in to_callback:
|
||||
callback_()
|
||||
|
||||
@callback
|
||||
def _remove_characteristics_callback(
|
||||
self, characteristics: Iterable[tuple[int, int]], callback_: CALLBACK_TYPE
|
||||
) -> None:
|
||||
"""Remove a characteristics callback."""
|
||||
for aid_iid in characteristics:
|
||||
self._subscriptions[aid_iid].remove(callback_)
|
||||
if not self._subscriptions[aid_iid]:
|
||||
del self._subscriptions[aid_iid]
|
||||
|
||||
@callback
|
||||
def async_subscribe(
|
||||
self, characteristics: Iterable[tuple[int, int]], callback_: CALLBACK_TYPE
|
||||
|
@ -812,24 +826,31 @@ class HKDevice:
|
|||
"""Add characteristics to the watch list."""
|
||||
for aid_iid in characteristics:
|
||||
self._subscriptions.setdefault(aid_iid, set()).add(callback_)
|
||||
return partial(
|
||||
self._remove_characteristics_callback, characteristics, callback_
|
||||
)
|
||||
|
||||
def _unsub():
|
||||
for aid_iid in characteristics:
|
||||
self._subscriptions[aid_iid].remove(callback_)
|
||||
if not self._subscriptions[aid_iid]:
|
||||
del self._subscriptions[aid_iid]
|
||||
|
||||
return _unsub
|
||||
@callback
|
||||
def _remove_availability_callback(self, callback_: CALLBACK_TYPE) -> None:
|
||||
"""Remove an availability callback."""
|
||||
self._availability_callbacks.remove(callback_)
|
||||
|
||||
@callback
|
||||
def async_subscribe_availability(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE:
|
||||
"""Add characteristics to the watch list."""
|
||||
self._availability_callbacks.add(callback_)
|
||||
return partial(self._remove_availability_callback, callback_)
|
||||
|
||||
def _unsub():
|
||||
self._availability_callbacks.remove(callback_)
|
||||
@callback
|
||||
def _remove_config_changed_callback(self, callback_: CALLBACK_TYPE) -> None:
|
||||
"""Remove an availability callback."""
|
||||
self._config_changed_callbacks.remove(callback_)
|
||||
|
||||
return _unsub
|
||||
@callback
|
||||
def async_subscribe_config_changed(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE:
|
||||
"""Subscribe to config of the accessory being changed aka c# changes."""
|
||||
self._config_changed_callbacks.add(callback_)
|
||||
return partial(self._remove_config_changed_callback, callback_)
|
||||
|
||||
async def get_characteristics(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
|
||||
"""Read latest state from homekit accessory."""
|
||||
|
|
|
@ -3,15 +3,15 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
from aiohomekit.model import Accessory
|
||||
from aiohomekit.model.characteristics import (
|
||||
EVENT_CHARACTERISTICS,
|
||||
Characteristic,
|
||||
CharacteristicPermissions,
|
||||
CharacteristicsTypes,
|
||||
)
|
||||
from aiohomekit.model.services import Service, ServicesTypes
|
||||
from aiohomekit.model.services import ServicesTypes
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
@ -32,41 +32,43 @@ class HomeKitEntity(Entity):
|
|||
self._iid = devinfo["iid"]
|
||||
self._char_name: str | None = None
|
||||
self.all_characteristics: set[tuple[int, int]] = set()
|
||||
self._async_set_accessory_and_service()
|
||||
self.setup()
|
||||
|
||||
super().__init__()
|
||||
|
||||
@property
|
||||
def accessory(self) -> Accessory:
|
||||
"""Return an Accessory model that this entity is attached to."""
|
||||
return self._accessory.entity_map.aid(self._aid)
|
||||
|
||||
@property
|
||||
def accessory_info(self) -> Service:
|
||||
"""Information about the make and model of an accessory."""
|
||||
return self.accessory.services.first(
|
||||
@callback
|
||||
def _async_set_accessory_and_service(self) -> None:
|
||||
"""Set the accessory and service for this entity."""
|
||||
accessory = self._accessory
|
||||
self.accessory = accessory.entity_map.aid(self._aid)
|
||||
self.service = self.accessory.services.iid(self._iid)
|
||||
self.accessory_info = self.accessory.services.first(
|
||||
service_type=ServicesTypes.ACCESSORY_INFORMATION
|
||||
)
|
||||
|
||||
@property
|
||||
def service(self) -> Service:
|
||||
"""Return a Service model that this entity is attached to."""
|
||||
return self.accessory.services.iid(self._iid)
|
||||
@callback
|
||||
def _async_config_changed(self) -> None:
|
||||
"""Handle accessory discovery changes."""
|
||||
self._async_set_accessory_and_service()
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Entity added to hass."""
|
||||
accessory = self._accessory
|
||||
self.async_on_remove(
|
||||
self._accessory.async_subscribe(
|
||||
accessory.async_subscribe(
|
||||
self.all_characteristics, self._async_write_ha_state
|
||||
)
|
||||
)
|
||||
self.async_on_remove(
|
||||
self._accessory.async_subscribe_availability(self._async_write_ha_state)
|
||||
accessory.async_subscribe_availability(self._async_write_ha_state)
|
||||
)
|
||||
self._accessory.add_pollable_characteristics(self.pollable_characteristics)
|
||||
await self._accessory.add_watchable_characteristics(
|
||||
self.watchable_characteristics
|
||||
self.async_on_remove(
|
||||
accessory.async_subscribe_config_changed(self._async_config_changed)
|
||||
)
|
||||
accessory.add_pollable_characteristics(self.pollable_characteristics)
|
||||
await accessory.add_watchable_characteristics(self.watchable_characteristics)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Prepare to be removed from hass."""
|
||||
|
|
|
@ -8,6 +8,7 @@ import os
|
|||
from typing import Any, Final
|
||||
from unittest import mock
|
||||
|
||||
from aiohomekit.controller.abstract import AbstractPairing
|
||||
from aiohomekit.hkjson import loads as hkloads
|
||||
from aiohomekit.model import (
|
||||
Accessories,
|
||||
|
@ -180,7 +181,7 @@ async def time_changed(hass, seconds):
|
|||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def setup_accessories_from_file(hass, path):
|
||||
async def setup_accessories_from_file(hass: HomeAssistant, path: str) -> Accessories:
|
||||
"""Load an collection of accessory defs from JSON data."""
|
||||
accessories_fixture = await hass.async_add_executor_job(
|
||||
load_fixture, os.path.join("homekit_controller", path)
|
||||
|
@ -242,11 +243,11 @@ async def setup_test_accessories_with_controller(
|
|||
return config_entry, pairing
|
||||
|
||||
|
||||
async def device_config_changed(hass, accessories):
|
||||
async def device_config_changed(hass: HomeAssistant, accessories: Accessories):
|
||||
"""Discover new devices added to Home Assistant at runtime."""
|
||||
# Update the accessories our FakePairing knows about
|
||||
controller = hass.data[CONTROLLER]
|
||||
pairing = controller.pairings["00:00:00:00:00:00"]
|
||||
pairing: AbstractPairing = controller.pairings["00:00:00:00:00:00"]
|
||||
|
||||
accessories_obj = Accessories()
|
||||
for accessory in accessories:
|
||||
|
|
|
@ -0,0 +1,244 @@
|
|||
[
|
||||
{
|
||||
"aid": 1,
|
||||
"services": [
|
||||
{
|
||||
"characteristics": [
|
||||
{
|
||||
"description": "Identify",
|
||||
"format": "bool",
|
||||
"iid": 2,
|
||||
"perms": ["pw"],
|
||||
"type": "00000014-0000-1000-8000-0026BB765291"
|
||||
},
|
||||
{
|
||||
"description": "Manufacturer",
|
||||
"format": "string",
|
||||
"iid": 3,
|
||||
"perms": ["pr"],
|
||||
"type": "00000020-0000-1000-8000-0026BB765291",
|
||||
"value": "Home Assistant"
|
||||
},
|
||||
{
|
||||
"description": "Model",
|
||||
"format": "string",
|
||||
"iid": 4,
|
||||
"perms": ["pr"],
|
||||
"type": "00000021-0000-1000-8000-0026BB765291",
|
||||
"value": "Bridge"
|
||||
},
|
||||
{
|
||||
"description": "Name",
|
||||
"format": "string",
|
||||
"iid": 5,
|
||||
"perms": ["pr"],
|
||||
"type": "00000023-0000-1000-8000-0026BB765291",
|
||||
"value": "Home Assistant Bridge"
|
||||
},
|
||||
{
|
||||
"description": "SerialNumber",
|
||||
"format": "string",
|
||||
"iid": 6,
|
||||
"perms": ["pr"],
|
||||
"type": "00000030-0000-1000-8000-0026BB765291",
|
||||
"value": "homekit.bridge"
|
||||
},
|
||||
{
|
||||
"description": "FirmwareRevision",
|
||||
"format": "string",
|
||||
"iid": 7,
|
||||
"perms": ["pr"],
|
||||
"type": "00000052-0000-1000-8000-0026BB765291",
|
||||
"value": "0.104.0.dev0"
|
||||
}
|
||||
],
|
||||
"iid": 1,
|
||||
"stype": "accessory-information",
|
||||
"type": "0000003E-0000-1000-8000-0026BB765291"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aid": 1256851357,
|
||||
"services": [
|
||||
{
|
||||
"characteristics": [
|
||||
{
|
||||
"description": "Identify",
|
||||
"format": "bool",
|
||||
"iid": 2,
|
||||
"perms": ["pw"],
|
||||
"type": "00000014-0000-1000-8000-0026BB765291"
|
||||
},
|
||||
{
|
||||
"description": "Manufacturer",
|
||||
"format": "string",
|
||||
"iid": 3,
|
||||
"perms": ["pr"],
|
||||
"type": "00000020-0000-1000-8000-0026BB765291",
|
||||
"value": "Home Assistant"
|
||||
},
|
||||
{
|
||||
"description": "Model",
|
||||
"format": "string",
|
||||
"iid": 4,
|
||||
"perms": ["pr"],
|
||||
"type": "00000021-0000-1000-8000-0026BB765291",
|
||||
"value": "Fan"
|
||||
},
|
||||
{
|
||||
"description": "Name",
|
||||
"format": "string",
|
||||
"iid": 5,
|
||||
"perms": ["pr"],
|
||||
"type": "00000023-0000-1000-8000-0026BB765291",
|
||||
"value": "Living Room Fan"
|
||||
},
|
||||
{
|
||||
"description": "SerialNumber",
|
||||
"format": "string",
|
||||
"iid": 6,
|
||||
"perms": ["pr"],
|
||||
"type": "00000030-0000-1000-8000-0026BB765291",
|
||||
"value": "fan.living_room_fan"
|
||||
},
|
||||
{
|
||||
"description": "FirmwareRevision",
|
||||
"format": "string",
|
||||
"iid": 7,
|
||||
"perms": ["pr"],
|
||||
"type": "00000052-0000-1000-8000-0026BB765291",
|
||||
"value": "0.104.0.dev0"
|
||||
}
|
||||
],
|
||||
"iid": 1,
|
||||
"stype": "accessory-information",
|
||||
"type": "0000003E-0000-1000-8000-0026BB765291"
|
||||
},
|
||||
{
|
||||
"characteristics": [
|
||||
{
|
||||
"description": "Active",
|
||||
"format": "uint8",
|
||||
"iid": 9,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"type": "000000B0-0000-1000-8000-0026BB765291",
|
||||
"valid-values": [0, 1],
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"description": "RotationDirection",
|
||||
"format": "int",
|
||||
"iid": 10,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"type": "00000028-0000-1000-8000-0026BB765291",
|
||||
"valid-values": [0, 1],
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"description": "RotationSpeed",
|
||||
"format": "float",
|
||||
"iid": 12,
|
||||
"maxValue": 100,
|
||||
"minStep": 1,
|
||||
"minValue": 0,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"type": "00000029-0000-1000-8000-0026BB765291",
|
||||
"unit": "percentage",
|
||||
"value": 100
|
||||
}
|
||||
],
|
||||
"iid": 8,
|
||||
"stype": "fanv2",
|
||||
"type": "000000B7-0000-1000-8000-0026BB765291"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aid": 766313939,
|
||||
"services": [
|
||||
{
|
||||
"characteristics": [
|
||||
{
|
||||
"description": "Identify",
|
||||
"format": "bool",
|
||||
"iid": 2,
|
||||
"perms": ["pw"],
|
||||
"type": "00000014-0000-1000-8000-0026BB765291"
|
||||
},
|
||||
{
|
||||
"description": "Manufacturer",
|
||||
"format": "string",
|
||||
"iid": 3,
|
||||
"perms": ["pr"],
|
||||
"type": "00000020-0000-1000-8000-0026BB765291",
|
||||
"value": "Home Assistant"
|
||||
},
|
||||
{
|
||||
"description": "Model",
|
||||
"format": "string",
|
||||
"iid": 4,
|
||||
"perms": ["pr"],
|
||||
"type": "00000021-0000-1000-8000-0026BB765291",
|
||||
"value": "Fan"
|
||||
},
|
||||
{
|
||||
"description": "Name",
|
||||
"format": "string",
|
||||
"iid": 5,
|
||||
"perms": ["pr"],
|
||||
"type": "00000023-0000-1000-8000-0026BB765291",
|
||||
"value": "Ceiling Fan"
|
||||
},
|
||||
{
|
||||
"description": "SerialNumber",
|
||||
"format": "string",
|
||||
"iid": 6,
|
||||
"perms": ["pr"],
|
||||
"type": "00000030-0000-1000-8000-0026BB765291",
|
||||
"value": "fan.ceiling_fan"
|
||||
},
|
||||
{
|
||||
"description": "FirmwareRevision",
|
||||
"format": "string",
|
||||
"iid": 7,
|
||||
"perms": ["pr"],
|
||||
"type": "00000052-0000-1000-8000-0026BB765291",
|
||||
"value": "0.104.0.dev0"
|
||||
}
|
||||
],
|
||||
"iid": 1,
|
||||
"stype": "accessory-information",
|
||||
"type": "0000003E-0000-1000-8000-0026BB765291"
|
||||
},
|
||||
{
|
||||
"characteristics": [
|
||||
{
|
||||
"description": "Active",
|
||||
"format": "uint8",
|
||||
"iid": 9,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"type": "000000B0-0000-1000-8000-0026BB765291",
|
||||
"valid-values": [0, 1],
|
||||
"value": 0
|
||||
},
|
||||
{
|
||||
"description": "RotationSpeed",
|
||||
"format": "float",
|
||||
"iid": 10,
|
||||
"maxValue": 100,
|
||||
"minStep": 1,
|
||||
"minValue": 0,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"type": "00000029-0000-1000-8000-0026BB765291",
|
||||
"unit": "percentage",
|
||||
"value": 100
|
||||
}
|
||||
],
|
||||
"iid": 8,
|
||||
"stype": "fanv2",
|
||||
"type": "000000B7-0000-1000-8000-0026BB765291"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -5544,6 +5544,292 @@
|
|||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_snapshots[home_assistant_bridge_basic_fan]
|
||||
list([
|
||||
dict({
|
||||
'device': dict({
|
||||
'area_id': None,
|
||||
'config_entries': list([
|
||||
'TestData',
|
||||
]),
|
||||
'configuration_url': None,
|
||||
'connections': list([
|
||||
]),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': '',
|
||||
'identifiers': list([
|
||||
list([
|
||||
'homekit_controller:accessory-id',
|
||||
'00:00:00:00:00:00:aid:766313939',
|
||||
]),
|
||||
]),
|
||||
'is_new': False,
|
||||
'manufacturer': 'Home Assistant',
|
||||
'model': 'Fan',
|
||||
'name': 'Ceiling Fan',
|
||||
'name_by_user': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '0.104.0.dev0',
|
||||
}),
|
||||
'entities': list([
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.ceiling_fan_identify',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Ceiling Fan Identify',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_766313939_1_2',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'Ceiling Fan Identify',
|
||||
}),
|
||||
'entity_id': 'button.ceiling_fan_identify',
|
||||
'state': 'unknown',
|
||||
}),
|
||||
}),
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'preset_modes': None,
|
||||
}),
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'fan',
|
||||
'entity_category': None,
|
||||
'entity_id': 'fan.ceiling_fan',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Ceiling Fan',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_766313939_8',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'Ceiling Fan',
|
||||
'percentage': 0,
|
||||
'percentage_step': 1.0,
|
||||
'preset_mode': None,
|
||||
'preset_modes': None,
|
||||
'supported_features': <FanEntityFeature: 1>,
|
||||
}),
|
||||
'entity_id': 'fan.ceiling_fan',
|
||||
'state': 'off',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
dict({
|
||||
'device': dict({
|
||||
'area_id': None,
|
||||
'config_entries': list([
|
||||
'TestData',
|
||||
]),
|
||||
'configuration_url': None,
|
||||
'connections': list([
|
||||
]),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': '',
|
||||
'identifiers': list([
|
||||
list([
|
||||
'homekit_controller:accessory-id',
|
||||
'00:00:00:00:00:00:aid:1',
|
||||
]),
|
||||
]),
|
||||
'is_new': False,
|
||||
'manufacturer': 'Home Assistant',
|
||||
'model': 'Bridge',
|
||||
'name': 'Home Assistant Bridge',
|
||||
'name_by_user': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '0.104.0.dev0',
|
||||
}),
|
||||
'entities': list([
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.home_assistant_bridge_identify',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Home Assistant Bridge Identify',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_1_1_2',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'Home Assistant Bridge Identify',
|
||||
}),
|
||||
'entity_id': 'button.home_assistant_bridge_identify',
|
||||
'state': 'unknown',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
dict({
|
||||
'device': dict({
|
||||
'area_id': None,
|
||||
'config_entries': list([
|
||||
'TestData',
|
||||
]),
|
||||
'configuration_url': None,
|
||||
'connections': list([
|
||||
]),
|
||||
'disabled_by': None,
|
||||
'entry_type': None,
|
||||
'hw_version': '',
|
||||
'identifiers': list([
|
||||
list([
|
||||
'homekit_controller:accessory-id',
|
||||
'00:00:00:00:00:00:aid:1256851357',
|
||||
]),
|
||||
]),
|
||||
'is_new': False,
|
||||
'manufacturer': 'Home Assistant',
|
||||
'model': 'Fan',
|
||||
'name': 'Living Room Fan',
|
||||
'name_by_user': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '0.104.0.dev0',
|
||||
}),
|
||||
'entities': list([
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.living_room_fan_identify',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Living Room Fan Identify',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_1256851357_1_2',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'Living Room Fan Identify',
|
||||
}),
|
||||
'entity_id': 'button.living_room_fan_identify',
|
||||
'state': 'unknown',
|
||||
}),
|
||||
}),
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'preset_modes': None,
|
||||
}),
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'fan',
|
||||
'entity_category': None,
|
||||
'entity_id': 'fan.living_room_fan',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Living Room Fan',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <FanEntityFeature: 5>,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_1256851357_8',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'direction': 'forward',
|
||||
'friendly_name': 'Living Room Fan',
|
||||
'percentage': 0,
|
||||
'percentage_step': 1.0,
|
||||
'preset_mode': None,
|
||||
'preset_modes': None,
|
||||
'supported_features': <FanEntityFeature: 5>,
|
||||
}),
|
||||
'entity_id': 'fan.living_room_fan',
|
||||
'state': 'off',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_snapshots[home_assistant_bridge_fan]
|
||||
list([
|
||||
dict({
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
"""Test for a Home Assistant bridge that changes fan features at runtime."""
|
||||
|
||||
|
||||
from homeassistant.components.fan import FanEntityFeature
|
||||
from homeassistant.const import ATTR_SUPPORTED_FEATURES
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from ..common import (
|
||||
device_config_changed,
|
||||
setup_accessories_from_file,
|
||||
setup_test_accessories,
|
||||
)
|
||||
|
||||
|
||||
async def test_fan_add_feature_at_runtime(hass: HomeAssistant) -> None:
|
||||
"""Test that new features can be added at runtime."""
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
# Set up a basic fan that does not support oscillation
|
||||
accessories = await setup_accessories_from_file(
|
||||
hass, "home_assistant_bridge_basic_fan.json"
|
||||
)
|
||||
await setup_test_accessories(hass, accessories)
|
||||
|
||||
fan = entity_registry.async_get("fan.living_room_fan")
|
||||
assert fan.unique_id == "00:00:00:00:00:00_1256851357_8"
|
||||
|
||||
fan_state = hass.states.get("fan.living_room_fan")
|
||||
assert (
|
||||
fan_state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
is FanEntityFeature.SET_SPEED | FanEntityFeature.DIRECTION
|
||||
)
|
||||
|
||||
fan = entity_registry.async_get("fan.ceiling_fan")
|
||||
assert fan.unique_id == "00:00:00:00:00:00_766313939_8"
|
||||
|
||||
fan_state = hass.states.get("fan.ceiling_fan")
|
||||
assert fan_state.attributes[ATTR_SUPPORTED_FEATURES] is FanEntityFeature.SET_SPEED
|
||||
|
||||
# Now change the config to add oscillation
|
||||
accessories = await setup_accessories_from_file(
|
||||
hass, "home_assistant_bridge_fan.json"
|
||||
)
|
||||
await device_config_changed(hass, accessories)
|
||||
|
||||
fan_state = hass.states.get("fan.living_room_fan")
|
||||
assert (
|
||||
fan_state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
is FanEntityFeature.SET_SPEED
|
||||
| FanEntityFeature.DIRECTION
|
||||
| FanEntityFeature.OSCILLATE
|
||||
)
|
||||
fan_state = hass.states.get("fan.ceiling_fan")
|
||||
assert fan_state.attributes[ATTR_SUPPORTED_FEATURES] is FanEntityFeature.SET_SPEED
|
Loading…
Reference in New Issue