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
|
import asyncio
|
||||||
from collections.abc import Callable, Iterable
|
from collections.abc import Callable, Iterable
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from types import MappingProxyType
|
from types import MappingProxyType
|
||||||
|
@ -144,6 +145,7 @@ class HKDevice:
|
||||||
)
|
)
|
||||||
|
|
||||||
self._availability_callbacks: set[CALLBACK_TYPE] = set()
|
self._availability_callbacks: set[CALLBACK_TYPE] = set()
|
||||||
|
self._config_changed_callbacks: set[CALLBACK_TYPE] = set()
|
||||||
self._subscriptions: dict[tuple[int, int], set[CALLBACK_TYPE]] = {}
|
self._subscriptions: dict[tuple[int, int], set[CALLBACK_TYPE]] = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -605,6 +607,8 @@ class HKDevice:
|
||||||
await self.async_process_entity_map()
|
await self.async_process_entity_map()
|
||||||
if self.watchable_characteristics:
|
if self.watchable_characteristics:
|
||||||
await self.pairing.subscribe(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_update()
|
||||||
await self.async_add_new_entities()
|
await self.async_add_new_entities()
|
||||||
|
|
||||||
|
@ -805,6 +809,16 @@ class HKDevice:
|
||||||
for callback_ in to_callback:
|
for callback_ in to_callback:
|
||||||
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
|
@callback
|
||||||
def async_subscribe(
|
def async_subscribe(
|
||||||
self, characteristics: Iterable[tuple[int, int]], callback_: CALLBACK_TYPE
|
self, characteristics: Iterable[tuple[int, int]], callback_: CALLBACK_TYPE
|
||||||
|
@ -812,24 +826,31 @@ class HKDevice:
|
||||||
"""Add characteristics to the watch list."""
|
"""Add characteristics to the watch list."""
|
||||||
for aid_iid in characteristics:
|
for aid_iid in characteristics:
|
||||||
self._subscriptions.setdefault(aid_iid, set()).add(callback_)
|
self._subscriptions.setdefault(aid_iid, set()).add(callback_)
|
||||||
|
return partial(
|
||||||
|
self._remove_characteristics_callback, characteristics, callback_
|
||||||
|
)
|
||||||
|
|
||||||
def _unsub():
|
@callback
|
||||||
for aid_iid in characteristics:
|
def _remove_availability_callback(self, callback_: CALLBACK_TYPE) -> None:
|
||||||
self._subscriptions[aid_iid].remove(callback_)
|
"""Remove an availability callback."""
|
||||||
if not self._subscriptions[aid_iid]:
|
self._availability_callbacks.remove(callback_)
|
||||||
del self._subscriptions[aid_iid]
|
|
||||||
|
|
||||||
return _unsub
|
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_subscribe_availability(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE:
|
def async_subscribe_availability(self, callback_: CALLBACK_TYPE) -> CALLBACK_TYPE:
|
||||||
"""Add characteristics to the watch list."""
|
"""Add characteristics to the watch list."""
|
||||||
self._availability_callbacks.add(callback_)
|
self._availability_callbacks.add(callback_)
|
||||||
|
return partial(self._remove_availability_callback, callback_)
|
||||||
|
|
||||||
def _unsub():
|
@callback
|
||||||
self._availability_callbacks.remove(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]:
|
async def get_characteristics(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
|
||||||
"""Read latest state from homekit accessory."""
|
"""Read latest state from homekit accessory."""
|
||||||
|
|
|
@ -3,15 +3,15 @@ from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from aiohomekit.model import Accessory
|
|
||||||
from aiohomekit.model.characteristics import (
|
from aiohomekit.model.characteristics import (
|
||||||
EVENT_CHARACTERISTICS,
|
EVENT_CHARACTERISTICS,
|
||||||
Characteristic,
|
Characteristic,
|
||||||
CharacteristicPermissions,
|
CharacteristicPermissions,
|
||||||
CharacteristicsTypes,
|
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.device_registry import DeviceInfo
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
@ -32,41 +32,43 @@ class HomeKitEntity(Entity):
|
||||||
self._iid = devinfo["iid"]
|
self._iid = devinfo["iid"]
|
||||||
self._char_name: str | None = None
|
self._char_name: str | None = None
|
||||||
self.all_characteristics: set[tuple[int, int]] = set()
|
self.all_characteristics: set[tuple[int, int]] = set()
|
||||||
|
self._async_set_accessory_and_service()
|
||||||
self.setup()
|
self.setup()
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@property
|
@callback
|
||||||
def accessory(self) -> Accessory:
|
def _async_set_accessory_and_service(self) -> None:
|
||||||
"""Return an Accessory model that this entity is attached to."""
|
"""Set the accessory and service for this entity."""
|
||||||
return self._accessory.entity_map.aid(self._aid)
|
accessory = self._accessory
|
||||||
|
self.accessory = accessory.entity_map.aid(self._aid)
|
||||||
@property
|
self.service = self.accessory.services.iid(self._iid)
|
||||||
def accessory_info(self) -> Service:
|
self.accessory_info = self.accessory.services.first(
|
||||||
"""Information about the make and model of an accessory."""
|
|
||||||
return self.accessory.services.first(
|
|
||||||
service_type=ServicesTypes.ACCESSORY_INFORMATION
|
service_type=ServicesTypes.ACCESSORY_INFORMATION
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@callback
|
||||||
def service(self) -> Service:
|
def _async_config_changed(self) -> None:
|
||||||
"""Return a Service model that this entity is attached to."""
|
"""Handle accessory discovery changes."""
|
||||||
return self.accessory.services.iid(self._iid)
|
self._async_set_accessory_and_service()
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
async def async_added_to_hass(self) -> None:
|
async def async_added_to_hass(self) -> None:
|
||||||
"""Entity added to hass."""
|
"""Entity added to hass."""
|
||||||
|
accessory = self._accessory
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
self._accessory.async_subscribe(
|
accessory.async_subscribe(
|
||||||
self.all_characteristics, self._async_write_ha_state
|
self.all_characteristics, self._async_write_ha_state
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.async_on_remove(
|
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)
|
self.async_on_remove(
|
||||||
await self._accessory.add_watchable_characteristics(
|
accessory.async_subscribe_config_changed(self._async_config_changed)
|
||||||
self.watchable_characteristics
|
|
||||||
)
|
)
|
||||||
|
accessory.add_pollable_characteristics(self.pollable_characteristics)
|
||||||
|
await accessory.add_watchable_characteristics(self.watchable_characteristics)
|
||||||
|
|
||||||
async def async_will_remove_from_hass(self) -> None:
|
async def async_will_remove_from_hass(self) -> None:
|
||||||
"""Prepare to be removed from hass."""
|
"""Prepare to be removed from hass."""
|
||||||
|
|
|
@ -8,6 +8,7 @@ import os
|
||||||
from typing import Any, Final
|
from typing import Any, Final
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
|
from aiohomekit.controller.abstract import AbstractPairing
|
||||||
from aiohomekit.hkjson import loads as hkloads
|
from aiohomekit.hkjson import loads as hkloads
|
||||||
from aiohomekit.model import (
|
from aiohomekit.model import (
|
||||||
Accessories,
|
Accessories,
|
||||||
|
@ -180,7 +181,7 @@ async def time_changed(hass, seconds):
|
||||||
await hass.async_block_till_done()
|
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."""
|
"""Load an collection of accessory defs from JSON data."""
|
||||||
accessories_fixture = await hass.async_add_executor_job(
|
accessories_fixture = await hass.async_add_executor_job(
|
||||||
load_fixture, os.path.join("homekit_controller", path)
|
load_fixture, os.path.join("homekit_controller", path)
|
||||||
|
@ -242,11 +243,11 @@ async def setup_test_accessories_with_controller(
|
||||||
return config_entry, pairing
|
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."""
|
"""Discover new devices added to Home Assistant at runtime."""
|
||||||
# Update the accessories our FakePairing knows about
|
# Update the accessories our FakePairing knows about
|
||||||
controller = hass.data[CONTROLLER]
|
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()
|
accessories_obj = Accessories()
|
||||||
for accessory in 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]
|
# name: test_snapshots[home_assistant_bridge_fan]
|
||||||
list([
|
list([
|
||||||
dict({
|
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