Handle removal of accessories/services/chars in homekit_controller (#102179)
This commit is contained in:
parent
e2e9c84c88
commit
c3d1db5db6
11 changed files with 1907 additions and 65 deletions
|
@ -29,7 +29,7 @@ from homeassistant.exceptions import HomeAssistantError
|
|||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.debounce import Debouncer
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers.event import async_call_later, async_track_time_interval
|
||||
|
||||
from .config_flow import normalize_hkid
|
||||
from .const import (
|
||||
|
@ -43,6 +43,7 @@ from .const import (
|
|||
IDENTIFIER_LEGACY_SERIAL_NUMBER,
|
||||
IDENTIFIER_SERIAL_NUMBER,
|
||||
STARTUP_EXCEPTIONS,
|
||||
SUBSCRIBE_COOLDOWN,
|
||||
)
|
||||
from .device_trigger import async_fire_triggers, async_setup_triggers_for_entry
|
||||
|
||||
|
@ -116,7 +117,7 @@ class HKDevice:
|
|||
|
||||
# This just tracks aid/iid pairs so we know if a HK service has been
|
||||
# mapped to a HA entity.
|
||||
self.entities: list[tuple[int, int | None, int | None]] = []
|
||||
self.entities: set[tuple[int, int | None, int | None]] = set()
|
||||
|
||||
# A map of aid -> device_id
|
||||
# Useful when routing events to triggers
|
||||
|
@ -124,7 +125,7 @@ class HKDevice:
|
|||
|
||||
self.available = False
|
||||
|
||||
self.pollable_characteristics: list[tuple[int, int]] = []
|
||||
self.pollable_characteristics: set[tuple[int, int]] = set()
|
||||
|
||||
# Never allow concurrent polling of the same accessory or bridge
|
||||
self._polling_lock = asyncio.Lock()
|
||||
|
@ -134,7 +135,7 @@ class HKDevice:
|
|||
# This is set to True if we can't rely on serial numbers to be unique
|
||||
self.unreliable_serial_numbers = False
|
||||
|
||||
self.watchable_characteristics: list[tuple[int, int]] = []
|
||||
self.watchable_characteristics: set[tuple[int, int]] = set()
|
||||
|
||||
self._debounced_update = Debouncer(
|
||||
hass,
|
||||
|
@ -147,6 +148,8 @@ 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]] = {}
|
||||
self._pending_subscribes: set[tuple[int, int]] = set()
|
||||
self._subscribe_timer: CALLBACK_TYPE | None = None
|
||||
|
||||
@property
|
||||
def entity_map(self) -> Accessories:
|
||||
|
@ -162,26 +165,51 @@ class HKDevice:
|
|||
self, characteristics: list[tuple[int, int]]
|
||||
) -> None:
|
||||
"""Add (aid, iid) pairs that we need to poll."""
|
||||
self.pollable_characteristics.extend(characteristics)
|
||||
self.pollable_characteristics.update(characteristics)
|
||||
|
||||
def remove_pollable_characteristics(self, accessory_id: int) -> None:
|
||||
def remove_pollable_characteristics(
|
||||
self, characteristics: list[tuple[int, int]]
|
||||
) -> None:
|
||||
"""Remove all pollable characteristics by accessory id."""
|
||||
self.pollable_characteristics = [
|
||||
char for char in self.pollable_characteristics if char[0] != accessory_id
|
||||
]
|
||||
for aid_iid in characteristics:
|
||||
self.pollable_characteristics.discard(aid_iid)
|
||||
|
||||
async def add_watchable_characteristics(
|
||||
def add_watchable_characteristics(
|
||||
self, characteristics: list[tuple[int, int]]
|
||||
) -> None:
|
||||
"""Add (aid, iid) pairs that we need to poll."""
|
||||
self.watchable_characteristics.extend(characteristics)
|
||||
await self.pairing.subscribe(characteristics)
|
||||
self.watchable_characteristics.update(characteristics)
|
||||
self._pending_subscribes.update(characteristics)
|
||||
# Try to subscribe to the characteristics all at once
|
||||
if not self._subscribe_timer:
|
||||
self._subscribe_timer = async_call_later(
|
||||
self.hass,
|
||||
SUBSCRIBE_COOLDOWN,
|
||||
self._async_subscribe,
|
||||
)
|
||||
|
||||
def remove_watchable_characteristics(self, accessory_id: int) -> None:
|
||||
@callback
|
||||
def _async_cancel_subscription_timer(self) -> None:
|
||||
"""Cancel the subscribe timer."""
|
||||
if self._subscribe_timer:
|
||||
self._subscribe_timer()
|
||||
self._subscribe_timer = None
|
||||
|
||||
async def _async_subscribe(self, _now: datetime) -> None:
|
||||
"""Subscribe to characteristics."""
|
||||
self._subscribe_timer = None
|
||||
if self._pending_subscribes:
|
||||
subscribes = self._pending_subscribes.copy()
|
||||
self._pending_subscribes.clear()
|
||||
await self.pairing.subscribe(subscribes)
|
||||
|
||||
def remove_watchable_characteristics(
|
||||
self, characteristics: list[tuple[int, int]]
|
||||
) -> None:
|
||||
"""Remove all pollable characteristics by accessory id."""
|
||||
self.watchable_characteristics = [
|
||||
char for char in self.watchable_characteristics if char[0] != accessory_id
|
||||
]
|
||||
for aid_iid in characteristics:
|
||||
self.watchable_characteristics.discard(aid_iid)
|
||||
self._pending_subscribes.discard(aid_iid)
|
||||
|
||||
@callback
|
||||
def async_set_available_state(self, available: bool) -> None:
|
||||
|
@ -264,6 +292,7 @@ class HKDevice:
|
|||
entry.async_on_unload(
|
||||
pairing.dispatcher_availability_changed(self.async_set_available_state)
|
||||
)
|
||||
entry.async_on_unload(self._async_cancel_subscription_timer)
|
||||
|
||||
await self.async_process_entity_map()
|
||||
|
||||
|
@ -605,8 +634,6 @@ class HKDevice:
|
|||
async def async_update_new_accessories_state(self) -> None:
|
||||
"""Process a change in the pairings accessories state."""
|
||||
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()
|
||||
|
@ -623,7 +650,7 @@ class HKDevice:
|
|||
if (accessory.aid, None, None) in self.entities:
|
||||
continue
|
||||
if handler(accessory):
|
||||
self.entities.append((accessory.aid, None, None))
|
||||
self.entities.add((accessory.aid, None, None))
|
||||
break
|
||||
|
||||
def add_char_factory(self, add_entities_cb: AddCharacteristicCb) -> None:
|
||||
|
@ -639,7 +666,7 @@ class HKDevice:
|
|||
if (accessory.aid, service.iid, char.iid) in self.entities:
|
||||
continue
|
||||
if handler(char):
|
||||
self.entities.append((accessory.aid, service.iid, char.iid))
|
||||
self.entities.add((accessory.aid, service.iid, char.iid))
|
||||
break
|
||||
|
||||
def add_listener(self, add_entities_cb: AddServiceCb) -> None:
|
||||
|
@ -687,7 +714,7 @@ class HKDevice:
|
|||
|
||||
for listener in callbacks:
|
||||
if listener(service):
|
||||
self.entities.append((aid, None, iid))
|
||||
self.entities.add((aid, None, iid))
|
||||
break
|
||||
|
||||
async def async_load_platform(self, platform: str) -> None:
|
||||
|
@ -811,7 +838,7 @@ class HKDevice:
|
|||
|
||||
@callback
|
||||
def _remove_characteristics_callback(
|
||||
self, characteristics: Iterable[tuple[int, int]], callback_: CALLBACK_TYPE
|
||||
self, characteristics: set[tuple[int, int]], callback_: CALLBACK_TYPE
|
||||
) -> None:
|
||||
"""Remove a characteristics callback."""
|
||||
for aid_iid in characteristics:
|
||||
|
@ -821,7 +848,7 @@ class HKDevice:
|
|||
|
||||
@callback
|
||||
def async_subscribe(
|
||||
self, characteristics: Iterable[tuple[int, int]], callback_: CALLBACK_TYPE
|
||||
self, characteristics: set[tuple[int, int]], callback_: CALLBACK_TYPE
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Add characteristics to the watch list."""
|
||||
for aid_iid in characteristics:
|
||||
|
|
|
@ -120,3 +120,5 @@ STARTUP_EXCEPTIONS = (
|
|||
# also happens to be the same value used by
|
||||
# the update coordinator.
|
||||
DEBOUNCE_COOLDOWN = 10 # seconds
|
||||
|
||||
SUBSCRIBE_COOLDOWN = 0.25 # seconds
|
||||
|
|
|
@ -64,7 +64,8 @@ class TriggerSource:
|
|||
self._callbacks: dict[tuple[str, str], list[Callable[[Any], None]]] = {}
|
||||
self._iid_trigger_keys: dict[int, set[tuple[str, str]]] = {}
|
||||
|
||||
async def async_setup(
|
||||
@callback
|
||||
def async_setup(
|
||||
self, connection: HKDevice, aid: int, triggers: list[dict[str, Any]]
|
||||
) -> None:
|
||||
"""Set up a set of triggers for a device.
|
||||
|
@ -78,7 +79,7 @@ class TriggerSource:
|
|||
self._triggers[trigger_key] = trigger_data
|
||||
iid = trigger_data["characteristic"]
|
||||
self._iid_trigger_keys.setdefault(iid, set()).add(trigger_key)
|
||||
await connection.add_watchable_characteristics([(aid, iid)])
|
||||
connection.add_watchable_characteristics([(aid, iid)])
|
||||
|
||||
def fire(self, iid: int, ev: dict[str, Any]) -> None:
|
||||
"""Process events that have been received from a HomeKit accessory."""
|
||||
|
@ -237,7 +238,7 @@ async def async_setup_triggers_for_entry(
|
|||
return False
|
||||
|
||||
trigger = async_get_or_create_trigger_source(conn.hass, device_id)
|
||||
hass.async_create_task(trigger.async_setup(conn, aid, triggers))
|
||||
trigger.async_setup(conn, aid, triggers)
|
||||
|
||||
return True
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import Any
|
||||
|
||||
from aiohomekit.model import Service, Services
|
||||
from aiohomekit.model.characteristics import (
|
||||
EVENT_CHARACTERISTICS,
|
||||
Characteristic,
|
||||
|
@ -11,7 +12,7 @@ from aiohomekit.model.characteristics import (
|
|||
)
|
||||
from aiohomekit.model.services import ServicesTypes
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.core import CALLBACK_TYPE, callback
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
@ -20,10 +21,21 @@ from .connection import HKDevice, valid_serial_number
|
|||
from .utils import folded_name
|
||||
|
||||
|
||||
def _get_service_by_iid_or_none(services: Services, iid: int) -> Service | None:
|
||||
"""Return a service by iid or None."""
|
||||
try:
|
||||
return services.iid(iid)
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
class HomeKitEntity(Entity):
|
||||
"""Representation of a Home Assistant HomeKit device."""
|
||||
|
||||
_attr_should_poll = False
|
||||
pollable_characteristics: list[tuple[int, int]]
|
||||
watchable_characteristics: list[tuple[int, int]]
|
||||
all_characteristics: set[tuple[int, int]]
|
||||
|
||||
def __init__(self, accessory: HKDevice, devinfo: ConfigType) -> None:
|
||||
"""Initialise a generic HomeKit device."""
|
||||
|
@ -31,49 +43,77 @@ class HomeKitEntity(Entity):
|
|||
self._aid = devinfo["aid"]
|
||||
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()
|
||||
|
||||
self._char_subscription: CALLBACK_TYPE | None = None
|
||||
self.async_setup()
|
||||
super().__init__()
|
||||
|
||||
@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
|
||||
)
|
||||
def _async_handle_entity_removed(self) -> None:
|
||||
"""Handle entity removal."""
|
||||
# We call _async_unsubscribe_chars as soon as we
|
||||
# know the entity is about to be removed so we do not try to
|
||||
# update characteristics that no longer exist. It will get
|
||||
# called in async_will_remove_from_hass as well, but that is
|
||||
# too late.
|
||||
self._async_unsubscribe_chars()
|
||||
self.hass.async_create_task(self.async_remove(force_remove=True))
|
||||
|
||||
@callback
|
||||
def _async_remove_entity_if_accessory_or_service_disappeared(self) -> bool:
|
||||
"""Handle accessory or service disappearance."""
|
||||
entity_map = self._accessory.entity_map
|
||||
if not entity_map.has_aid(self._aid) or not _get_service_by_iid_or_none(
|
||||
entity_map.aid(self._aid).services, self._iid
|
||||
):
|
||||
self._async_handle_entity_removed()
|
||||
return True
|
||||
return False
|
||||
|
||||
@callback
|
||||
def _async_config_changed(self) -> None:
|
||||
"""Handle accessory discovery changes."""
|
||||
self._async_set_accessory_and_service()
|
||||
if not self._async_remove_entity_if_accessory_or_service_disappeared():
|
||||
self._async_reconfigure()
|
||||
|
||||
@callback
|
||||
def _async_reconfigure(self) -> None:
|
||||
"""Reconfigure the entity."""
|
||||
self._async_unsubscribe_chars()
|
||||
self.async_setup()
|
||||
self._async_subscribe_chars()
|
||||
self.async_write_ha_state()
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Entity added to hass."""
|
||||
accessory = self._accessory
|
||||
self._async_subscribe_chars()
|
||||
self.async_on_remove(
|
||||
accessory.async_subscribe(
|
||||
self.all_characteristics, self._async_write_ha_state
|
||||
)
|
||||
self._accessory.async_subscribe_config_changed(self._async_config_changed)
|
||||
)
|
||||
self.async_on_remove(
|
||||
accessory.async_subscribe_availability(self._async_write_ha_state)
|
||||
self._accessory.async_subscribe_availability(self._async_write_ha_state)
|
||||
)
|
||||
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."""
|
||||
self._accessory.remove_pollable_characteristics(self._aid)
|
||||
self._accessory.remove_watchable_characteristics(self._aid)
|
||||
self._async_unsubscribe_chars()
|
||||
|
||||
@callback
|
||||
def _async_unsubscribe_chars(self):
|
||||
"""Handle unsubscribing from characteristics."""
|
||||
if self._char_subscription:
|
||||
self._char_subscription()
|
||||
self._char_subscription = None
|
||||
self._accessory.remove_pollable_characteristics(self.pollable_characteristics)
|
||||
self._accessory.remove_watchable_characteristics(self.watchable_characteristics)
|
||||
|
||||
@callback
|
||||
def _async_subscribe_chars(self):
|
||||
"""Handle registering characteristics to watch and subscribe."""
|
||||
self._accessory.add_pollable_characteristics(self.pollable_characteristics)
|
||||
self._accessory.add_watchable_characteristics(self.watchable_characteristics)
|
||||
self._char_subscription = self._accessory.async_subscribe(
|
||||
self.all_characteristics, self._async_write_ha_state
|
||||
)
|
||||
|
||||
async def async_put_characteristics(self, characteristics: dict[str, Any]) -> None:
|
||||
"""Write characteristics to the device.
|
||||
|
@ -92,10 +132,22 @@ class HomeKitEntity(Entity):
|
|||
payload = self.service.build_update(characteristics)
|
||||
return await self._accessory.put_characteristics(payload)
|
||||
|
||||
def setup(self) -> None:
|
||||
@callback
|
||||
def async_setup(self) -> None:
|
||||
"""Configure an entity based on its HomeKit characteristics metadata."""
|
||||
self.pollable_characteristics: list[tuple[int, int]] = []
|
||||
self.watchable_characteristics: list[tuple[int, int]] = []
|
||||
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
|
||||
)
|
||||
# If we re-setup, we need to make sure we make new
|
||||
# lists since we passed them to the connection before
|
||||
# and we do not want to inadvertently modify the old
|
||||
# ones.
|
||||
self.pollable_characteristics = []
|
||||
self.watchable_characteristics = []
|
||||
self.all_characteristics = set()
|
||||
|
||||
char_types = self.get_characteristic_types()
|
||||
|
||||
|
@ -203,7 +255,7 @@ class AccessoryEntity(HomeKitEntity):
|
|||
return f"{self._accessory.unique_id}_{self._aid}"
|
||||
|
||||
|
||||
class CharacteristicEntity(HomeKitEntity):
|
||||
class BaseCharacteristicEntity(HomeKitEntity):
|
||||
"""A HomeKit entity that is related to an single characteristic rather than a whole service.
|
||||
|
||||
This is typically used to expose additional sensor, binary_sensor or number entities that don't belong with
|
||||
|
@ -217,6 +269,35 @@ class CharacteristicEntity(HomeKitEntity):
|
|||
self._char = char
|
||||
super().__init__(accessory, devinfo)
|
||||
|
||||
@callback
|
||||
def _async_remove_entity_if_characteristics_disappeared(self) -> bool:
|
||||
"""Handle characteristic disappearance."""
|
||||
if (
|
||||
not self._accessory.entity_map.aid(self._aid)
|
||||
.services.iid(self._iid)
|
||||
.get_char_by_iid(self._char.iid)
|
||||
):
|
||||
self._async_handle_entity_removed()
|
||||
return True
|
||||
return False
|
||||
|
||||
@callback
|
||||
def _async_config_changed(self) -> None:
|
||||
"""Handle accessory discovery changes."""
|
||||
if (
|
||||
not self._async_remove_entity_if_accessory_or_service_disappeared()
|
||||
and not self._async_remove_entity_if_characteristics_disappeared()
|
||||
):
|
||||
super()._async_reconfigure()
|
||||
|
||||
|
||||
class CharacteristicEntity(BaseCharacteristicEntity):
|
||||
"""A HomeKit entity that is related to an single characteristic rather than a whole service.
|
||||
|
||||
This is typically used to expose additional sensor, binary_sensor or number entities that don't belong with
|
||||
the service entity.
|
||||
"""
|
||||
|
||||
@property
|
||||
def old_unique_id(self) -> str:
|
||||
"""Return the old ID of this device."""
|
||||
|
|
|
@ -17,7 +17,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
|
||||
from . import KNOWN_DEVICES
|
||||
from .connection import HKDevice
|
||||
from .entity import HomeKitEntity
|
||||
from .entity import BaseCharacteristicEntity
|
||||
|
||||
INPUT_EVENT_VALUES = {
|
||||
InputEventValues.SINGLE_PRESS: "single_press",
|
||||
|
@ -26,7 +26,7 @@ INPUT_EVENT_VALUES = {
|
|||
}
|
||||
|
||||
|
||||
class HomeKitEventEntity(HomeKitEntity, EventEntity):
|
||||
class HomeKitEventEntity(BaseCharacteristicEntity, EventEntity):
|
||||
"""Representation of a Homekit event entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
|
@ -44,10 +44,8 @@ class HomeKitEventEntity(HomeKitEntity, EventEntity):
|
|||
"aid": service.accessory.aid,
|
||||
"iid": service.iid,
|
||||
},
|
||||
service.characteristics_by_type[CharacteristicsTypes.INPUT_EVENT],
|
||||
)
|
||||
self._characteristic = service.characteristics_by_type[
|
||||
CharacteristicsTypes.INPUT_EVENT
|
||||
]
|
||||
|
||||
self.entity_description = entity_description
|
||||
|
||||
|
@ -55,7 +53,7 @@ class HomeKitEventEntity(HomeKitEntity, EventEntity):
|
|||
# clamp InputEventValues for this exact device
|
||||
self._attr_event_types = [
|
||||
INPUT_EVENT_VALUES[v]
|
||||
for v in clamp_enum_to_char(InputEventValues, self._characteristic)
|
||||
for v in clamp_enum_to_char(InputEventValues, self._char)
|
||||
]
|
||||
|
||||
def get_characteristic_types(self) -> list[str]:
|
||||
|
@ -68,19 +66,19 @@ class HomeKitEventEntity(HomeKitEntity, EventEntity):
|
|||
|
||||
self.async_on_remove(
|
||||
self._accessory.async_subscribe(
|
||||
[(self._aid, self._characteristic.iid)],
|
||||
{(self._aid, self._char.iid)},
|
||||
self._handle_event,
|
||||
)
|
||||
)
|
||||
|
||||
@callback
|
||||
def _handle_event(self):
|
||||
if self._characteristic.value is None:
|
||||
if self._char.value is None:
|
||||
# For IP backed devices the characteristic is marked as
|
||||
# pollable, but always returns None when polled
|
||||
# Make sure we don't explode if we see that edge case.
|
||||
return
|
||||
self._trigger_event(INPUT_EVENT_VALUES[self._characteristic.value])
|
||||
self._trigger_event(INPUT_EVENT_VALUES[self._char.value])
|
||||
self.async_write_ha_state()
|
||||
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ from homeassistant.components.homekit_controller.const import (
|
|||
DOMAIN,
|
||||
HOMEKIT_ACCESSORY_DISPATCH,
|
||||
IDENTIFIER_ACCESSORY_ID,
|
||||
SUBSCRIBE_COOLDOWN,
|
||||
)
|
||||
from homeassistant.components.homekit_controller.utils import async_get_controller
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
|
@ -238,6 +239,7 @@ async def setup_test_accessories_with_controller(
|
|||
config_entry.add_to_hass(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await time_changed(hass, SUBSCRIBE_COOLDOWN)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return config_entry, pairing
|
||||
|
|
|
@ -0,0 +1,561 @@
|
|||
[
|
||||
{
|
||||
"aid": 1,
|
||||
"services": [
|
||||
{
|
||||
"type": "3E",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": "HomeW",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 2
|
||||
},
|
||||
{
|
||||
"value": "ecobee Inc.",
|
||||
"perms": ["pr"],
|
||||
"type": "20",
|
||||
"format": "string",
|
||||
"iid": 3
|
||||
},
|
||||
{
|
||||
"value": "123456789012",
|
||||
"perms": ["pr"],
|
||||
"type": "30",
|
||||
"format": "string",
|
||||
"iid": 4
|
||||
},
|
||||
{
|
||||
"value": "ecobee3",
|
||||
"perms": ["pr"],
|
||||
"type": "21",
|
||||
"format": "string",
|
||||
"iid": 5
|
||||
},
|
||||
{
|
||||
"perms": ["pw"],
|
||||
"type": "14",
|
||||
"format": "bool",
|
||||
"iid": 6
|
||||
},
|
||||
{
|
||||
"value": "4.2.394",
|
||||
"perms": ["pr"],
|
||||
"type": "52",
|
||||
"format": "string",
|
||||
"iid": 8
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "A6",
|
||||
"format": "uint32",
|
||||
"iid": 9
|
||||
}
|
||||
],
|
||||
"iid": 1
|
||||
},
|
||||
{
|
||||
"type": "A2",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": "1.1.0",
|
||||
"perms": ["pr"],
|
||||
"maxLen": 64,
|
||||
"type": "37",
|
||||
"format": "string",
|
||||
"iid": 31
|
||||
}
|
||||
],
|
||||
"iid": 30
|
||||
},
|
||||
{
|
||||
"primary": true,
|
||||
"type": "4A",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": 1,
|
||||
"maxValue": 2,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "F",
|
||||
"minValue": 0,
|
||||
"format": "uint8",
|
||||
"iid": 17
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"maxValue": 3,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"type": "33",
|
||||
"minValue": 0,
|
||||
"format": "uint8",
|
||||
"iid": 18
|
||||
},
|
||||
{
|
||||
"value": 21.8,
|
||||
"maxValue": 100,
|
||||
"minStep": 0.1,
|
||||
"perms": ["pr", "ev"],
|
||||
"unit": "celsius",
|
||||
"type": "11",
|
||||
"minValue": 0,
|
||||
"format": "float",
|
||||
"iid": 19
|
||||
},
|
||||
{
|
||||
"value": 22.2,
|
||||
"maxValue": 33.3,
|
||||
"minStep": 0.1,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"unit": "celsius",
|
||||
"type": "35",
|
||||
"minValue": 7.2,
|
||||
"format": "float",
|
||||
"iid": 20
|
||||
},
|
||||
{
|
||||
"value": 1,
|
||||
"maxValue": 1,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"type": "36",
|
||||
"minValue": 0,
|
||||
"format": "uint8",
|
||||
"iid": 21
|
||||
},
|
||||
{
|
||||
"value": 24.4,
|
||||
"maxValue": 33.3,
|
||||
"minStep": 0.1,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"unit": "celsius",
|
||||
"type": "D",
|
||||
"minValue": 18.3,
|
||||
"format": "float",
|
||||
"iid": 22
|
||||
},
|
||||
{
|
||||
"value": 22.2,
|
||||
"maxValue": 26.1,
|
||||
"minStep": 0.1,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"unit": "celsius",
|
||||
"type": "12",
|
||||
"minValue": 7.2,
|
||||
"format": "float",
|
||||
"iid": 23
|
||||
},
|
||||
{
|
||||
"value": 34,
|
||||
"maxValue": 100,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "ev"],
|
||||
"unit": "percentage",
|
||||
"type": "10",
|
||||
"minValue": 0,
|
||||
"format": "float",
|
||||
"iid": 24
|
||||
},
|
||||
{
|
||||
"value": 36,
|
||||
"maxValue": 50,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"unit": "percentage",
|
||||
"type": "34",
|
||||
"minValue": 20,
|
||||
"format": "float",
|
||||
"iid": 25
|
||||
},
|
||||
{
|
||||
"value": "HomeW",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 27
|
||||
}
|
||||
],
|
||||
"iid": 16
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aid": 2,
|
||||
"services": [
|
||||
{
|
||||
"type": "3E",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": "Kitchen",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 2049
|
||||
},
|
||||
{
|
||||
"value": "ecobee Inc.",
|
||||
"perms": ["pr"],
|
||||
"type": "20",
|
||||
"format": "string",
|
||||
"iid": 2050
|
||||
},
|
||||
{
|
||||
"value": "AB1C",
|
||||
"perms": ["pr"],
|
||||
"type": "30",
|
||||
"format": "string",
|
||||
"iid": 2051
|
||||
},
|
||||
{
|
||||
"value": "REMOTE SENSOR",
|
||||
"perms": ["pr"],
|
||||
"type": "21",
|
||||
"format": "string",
|
||||
"iid": 2052
|
||||
},
|
||||
{
|
||||
"value": "1.0.0",
|
||||
"perms": ["pr"],
|
||||
"type": "52",
|
||||
"format": "string",
|
||||
"iid": 8
|
||||
},
|
||||
{
|
||||
"perms": ["pw"],
|
||||
"type": "14",
|
||||
"format": "bool",
|
||||
"iid": 2053
|
||||
}
|
||||
],
|
||||
"iid": 1
|
||||
},
|
||||
{
|
||||
"type": "8A",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": 21.5,
|
||||
"maxValue": 100,
|
||||
"minStep": 0.1,
|
||||
"perms": ["pr", "ev"],
|
||||
"unit": "celsius",
|
||||
"type": "11",
|
||||
"minValue": 0,
|
||||
"format": "float",
|
||||
"iid": 2064
|
||||
},
|
||||
{
|
||||
"value": "Kitchen",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 2067
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "75",
|
||||
"format": "bool",
|
||||
"iid": 2066
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"maxValue": 1,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "79",
|
||||
"minValue": 0,
|
||||
"format": "uint8",
|
||||
"iid": 2065
|
||||
}
|
||||
],
|
||||
"iid": 55
|
||||
},
|
||||
{
|
||||
"type": "85",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": false,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "22",
|
||||
"format": "bool",
|
||||
"iid": 2060
|
||||
},
|
||||
{
|
||||
"value": "Kitchen",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 2063
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "75",
|
||||
"format": "bool",
|
||||
"iid": 2062
|
||||
},
|
||||
{
|
||||
"minValue": 0,
|
||||
"maxValue": 1,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "79",
|
||||
"value": 0,
|
||||
"format": "uint8",
|
||||
"iid": 2061
|
||||
},
|
||||
{
|
||||
"minValue": -1,
|
||||
"maxValue": 86400,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "BFE61C70-4A40-11E6-BDF4-0800200C9A66",
|
||||
"value": 3620,
|
||||
"format": "int",
|
||||
"iid": 2059
|
||||
}
|
||||
],
|
||||
"iid": 56
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aid": 3,
|
||||
"services": [
|
||||
{
|
||||
"type": "3E",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": "Porch",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 3073
|
||||
},
|
||||
{
|
||||
"value": "ecobee Inc.",
|
||||
"perms": ["pr"],
|
||||
"type": "20",
|
||||
"format": "string",
|
||||
"iid": 3074
|
||||
},
|
||||
{
|
||||
"value": "AB2C",
|
||||
"perms": ["pr"],
|
||||
"type": "30",
|
||||
"format": "string",
|
||||
"iid": 3075
|
||||
},
|
||||
{
|
||||
"value": "REMOTE SENSOR",
|
||||
"perms": ["pr"],
|
||||
"type": "21",
|
||||
"format": "string",
|
||||
"iid": 3076
|
||||
},
|
||||
{
|
||||
"value": "1.0.0",
|
||||
"perms": ["pr"],
|
||||
"type": "52",
|
||||
"format": "string",
|
||||
"iid": 8
|
||||
},
|
||||
{
|
||||
"perms": ["pw"],
|
||||
"type": "14",
|
||||
"format": "bool",
|
||||
"iid": 3077
|
||||
}
|
||||
],
|
||||
"iid": 1
|
||||
},
|
||||
{
|
||||
"type": "8A",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": 21,
|
||||
"maxValue": 100,
|
||||
"minStep": 0.1,
|
||||
"perms": ["pr", "ev"],
|
||||
"unit": "celsius",
|
||||
"type": "11",
|
||||
"minValue": 0,
|
||||
"format": "float",
|
||||
"iid": 3088
|
||||
},
|
||||
{
|
||||
"value": "Porch",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 3091
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "75",
|
||||
"format": "bool",
|
||||
"iid": 3090
|
||||
},
|
||||
{
|
||||
"value": 0,
|
||||
"maxValue": 1,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "79",
|
||||
"minValue": 0,
|
||||
"format": "uint8",
|
||||
"iid": 3089
|
||||
}
|
||||
],
|
||||
"iid": 55
|
||||
},
|
||||
{
|
||||
"type": "85",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": false,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "22",
|
||||
"format": "bool",
|
||||
"iid": 3084
|
||||
},
|
||||
{
|
||||
"value": "Porch",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 3087
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "75",
|
||||
"format": "bool",
|
||||
"iid": 3086
|
||||
},
|
||||
{
|
||||
"minValue": 0,
|
||||
"maxValue": 1,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "79",
|
||||
"value": 0,
|
||||
"format": "uint8",
|
||||
"iid": 3085
|
||||
},
|
||||
{
|
||||
"minValue": -1,
|
||||
"maxValue": 86400,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "BFE61C70-4A40-11E6-BDF4-0800200C9A66",
|
||||
"value": 5766,
|
||||
"format": "int",
|
||||
"iid": 3083
|
||||
}
|
||||
],
|
||||
"iid": 56
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"aid": 4,
|
||||
"services": [
|
||||
{
|
||||
"type": "3E",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": "Basement",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 4097
|
||||
},
|
||||
{
|
||||
"value": "ecobee Inc.",
|
||||
"perms": ["pr"],
|
||||
"type": "20",
|
||||
"format": "string",
|
||||
"iid": 4098
|
||||
},
|
||||
{
|
||||
"value": "AB3C",
|
||||
"perms": ["pr"],
|
||||
"type": "30",
|
||||
"format": "string",
|
||||
"iid": 4099
|
||||
},
|
||||
{
|
||||
"value": "REMOTE SENSOR",
|
||||
"perms": ["pr"],
|
||||
"type": "21",
|
||||
"format": "string",
|
||||
"iid": 4100
|
||||
},
|
||||
{
|
||||
"value": "1.0.0",
|
||||
"perms": ["pr"],
|
||||
"type": "52",
|
||||
"format": "string",
|
||||
"iid": 8
|
||||
},
|
||||
{
|
||||
"perms": ["pw"],
|
||||
"type": "14",
|
||||
"format": "bool",
|
||||
"iid": 4101
|
||||
}
|
||||
],
|
||||
"iid": 1
|
||||
},
|
||||
{
|
||||
"type": "85",
|
||||
"characteristics": [
|
||||
{
|
||||
"value": false,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "22",
|
||||
"format": "bool",
|
||||
"iid": 4108
|
||||
},
|
||||
{
|
||||
"value": "Basement",
|
||||
"perms": ["pr"],
|
||||
"type": "23",
|
||||
"format": "string",
|
||||
"iid": 4111
|
||||
},
|
||||
{
|
||||
"value": true,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "75",
|
||||
"format": "bool",
|
||||
"iid": 4110
|
||||
},
|
||||
{
|
||||
"minValue": 0,
|
||||
"maxValue": 1,
|
||||
"minStep": 1,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "79",
|
||||
"value": 0,
|
||||
"format": "uint8",
|
||||
"iid": 4109
|
||||
},
|
||||
{
|
||||
"minValue": -1,
|
||||
"maxValue": 86400,
|
||||
"perms": ["pr", "ev"],
|
||||
"type": "BFE61C70-4A40-11E6-BDF4-0800200C9A66",
|
||||
"value": 5472,
|
||||
"format": "int",
|
||||
"iid": 4107
|
||||
}
|
||||
],
|
||||
"iid": 56
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -0,0 +1,166 @@
|
|||
[
|
||||
{
|
||||
"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": "SwingMode",
|
||||
"format": "uint8",
|
||||
"iid": 11,
|
||||
"perms": ["pr", "pw", "ev"],
|
||||
"type": "000000B6-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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -3935,6 +3935,656 @@
|
|||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_snapshots[ecobee3_service_removed]
|
||||
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:4',
|
||||
]),
|
||||
]),
|
||||
'is_new': False,
|
||||
'manufacturer': 'ecobee Inc.',
|
||||
'model': 'REMOTE SENSOR',
|
||||
'name': 'Basement',
|
||||
'name_by_user': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '1.0.0',
|
||||
}),
|
||||
'entities': list([
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'binary_sensor.basement',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.MOTION: 'motion'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Basement',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_4_56',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'device_class': 'motion',
|
||||
'friendly_name': 'Basement',
|
||||
}),
|
||||
'entity_id': 'binary_sensor.basement',
|
||||
'state': 'off',
|
||||
}),
|
||||
}),
|
||||
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.basement_identify',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Basement Identify',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_4_1_4101',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'Basement Identify',
|
||||
}),
|
||||
'entity_id': 'button.basement_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:1',
|
||||
]),
|
||||
]),
|
||||
'is_new': False,
|
||||
'manufacturer': 'ecobee Inc.',
|
||||
'model': 'ecobee3',
|
||||
'name': 'HomeW',
|
||||
'name_by_user': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '4.2.394',
|
||||
}),
|
||||
'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.homew_identify',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'HomeW Identify',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_1_1_6',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'HomeW Identify',
|
||||
}),
|
||||
'entity_id': 'button.homew_identify',
|
||||
'state': 'unknown',
|
||||
}),
|
||||
}),
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_humidity': 50,
|
||||
'max_temp': 33.3,
|
||||
'min_humidity': 20,
|
||||
'min_temp': 7.2,
|
||||
}),
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'climate',
|
||||
'entity_category': None,
|
||||
'entity_id': 'climate.homew',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'HomeW',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 7>,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_1_16',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'current_humidity': 34,
|
||||
'current_temperature': 21.8,
|
||||
'friendly_name': 'HomeW',
|
||||
'humidity': 36,
|
||||
'hvac_action': <HVACAction.HEATING: 'heating'>,
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_humidity': 50,
|
||||
'max_temp': 33.3,
|
||||
'min_humidity': 20,
|
||||
'min_temp': 7.2,
|
||||
'supported_features': <ClimateEntityFeature: 7>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'temperature': 22.2,
|
||||
}),
|
||||
'entity_id': 'climate.homew',
|
||||
'state': 'heat',
|
||||
}),
|
||||
}),
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'celsius',
|
||||
'fahrenheit',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.homew_temperature_display_units',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': 'mdi:thermometer',
|
||||
'original_name': 'HomeW Temperature Display Units',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'temperature_display_units',
|
||||
'unique_id': '00:00:00:00:00:00_1_16_21',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'HomeW Temperature Display Units',
|
||||
'icon': 'mdi:thermometer',
|
||||
'options': list([
|
||||
'celsius',
|
||||
'fahrenheit',
|
||||
]),
|
||||
}),
|
||||
'entity_id': 'select.homew_temperature_display_units',
|
||||
'state': 'fahrenheit',
|
||||
}),
|
||||
}),
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.homew_current_humidity',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'HomeW Current Humidity',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_1_16_24',
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'device_class': 'humidity',
|
||||
'friendly_name': 'HomeW Current Humidity',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'entity_id': 'sensor.homew_current_humidity',
|
||||
'state': '34',
|
||||
}),
|
||||
}),
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.homew_current_temperature',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'HomeW Current Temperature',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_1_16_19',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'HomeW Current Temperature',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'entity_id': 'sensor.homew_current_temperature',
|
||||
'state': '21.8',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
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:2',
|
||||
]),
|
||||
]),
|
||||
'is_new': False,
|
||||
'manufacturer': 'ecobee Inc.',
|
||||
'model': 'REMOTE SENSOR',
|
||||
'name': 'Kitchen',
|
||||
'name_by_user': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '1.0.0',
|
||||
}),
|
||||
'entities': list([
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'binary_sensor.kitchen',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.MOTION: 'motion'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Kitchen',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_2_56',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'device_class': 'motion',
|
||||
'friendly_name': 'Kitchen',
|
||||
}),
|
||||
'entity_id': 'binary_sensor.kitchen',
|
||||
'state': 'off',
|
||||
}),
|
||||
}),
|
||||
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.kitchen_identify',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Kitchen Identify',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_2_1_2053',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'Kitchen Identify',
|
||||
}),
|
||||
'entity_id': 'button.kitchen_identify',
|
||||
'state': 'unknown',
|
||||
}),
|
||||
}),
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.kitchen_temperature',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Kitchen Temperature',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_2_55',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'Kitchen Temperature',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'entity_id': 'sensor.kitchen_temperature',
|
||||
'state': '21.5',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
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:3',
|
||||
]),
|
||||
]),
|
||||
'is_new': False,
|
||||
'manufacturer': 'ecobee Inc.',
|
||||
'model': 'REMOTE SENSOR',
|
||||
'name': 'Porch',
|
||||
'name_by_user': None,
|
||||
'suggested_area': None,
|
||||
'sw_version': '1.0.0',
|
||||
}),
|
||||
'entities': list([
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'binary_sensor.porch',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.MOTION: 'motion'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Porch',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_3_56',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'device_class': 'motion',
|
||||
'friendly_name': 'Porch',
|
||||
}),
|
||||
'entity_id': 'binary_sensor.porch',
|
||||
'state': 'off',
|
||||
}),
|
||||
}),
|
||||
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.porch_identify',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Porch Identify',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_3_1_3077',
|
||||
'unit_of_measurement': None,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'friendly_name': 'Porch Identify',
|
||||
}),
|
||||
'entity_id': 'button.porch_identify',
|
||||
'state': 'unknown',
|
||||
}),
|
||||
}),
|
||||
dict({
|
||||
'entry': dict({
|
||||
'aliases': list([
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': 'TestData',
|
||||
'device_class': None,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.porch_temperature',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Porch Temperature',
|
||||
'platform': 'homekit_controller',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00:00:00:00:00:00_3_55',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'state': dict({
|
||||
'attributes': dict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'Porch Temperature',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'entity_id': 'sensor.porch_temperature',
|
||||
'state': '21',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_snapshots[ecobee_501]
|
||||
list([
|
||||
dict({
|
||||
|
@ -6117,6 +6767,185 @@
|
|||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_snapshots[home_assistant_bridge_fan_one_removed]
|
||||
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: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: 7>,
|
||||
'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',
|
||||
'oscillating': False,
|
||||
'percentage': 0,
|
||||
'percentage_step': 1.0,
|
||||
'preset_mode': None,
|
||||
'preset_modes': None,
|
||||
'supported_features': <FanEntityFeature: 7>,
|
||||
}),
|
||||
'entity_id': 'fan.living_room_fan',
|
||||
'state': 'off',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
# ---
|
||||
# name: test_snapshots[homespan_daikin_bridge]
|
||||
list([
|
||||
dict({
|
||||
|
|
|
@ -252,3 +252,91 @@ async def test_ecobee3_add_sensors_at_runtime(hass: HomeAssistant) -> None:
|
|||
|
||||
occ3 = entity_registry.async_get("binary_sensor.basement")
|
||||
assert occ3.unique_id == "00:00:00:00:00:00_4_56"
|
||||
|
||||
|
||||
async def test_ecobee3_remove_sensors_at_runtime(hass: HomeAssistant) -> None:
|
||||
"""Test that sensors are automatically removed."""
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
# Set up a base Ecobee 3 with additional sensors.
|
||||
accessories = await setup_accessories_from_file(hass, "ecobee3.json")
|
||||
await setup_test_accessories(hass, accessories)
|
||||
|
||||
climate = entity_registry.async_get("climate.homew")
|
||||
assert climate.unique_id == "00:00:00:00:00:00_1_16"
|
||||
|
||||
occ1 = entity_registry.async_get("binary_sensor.kitchen")
|
||||
assert occ1.unique_id == "00:00:00:00:00:00_2_56"
|
||||
|
||||
occ2 = entity_registry.async_get("binary_sensor.porch")
|
||||
assert occ2.unique_id == "00:00:00:00:00:00_3_56"
|
||||
|
||||
occ3 = entity_registry.async_get("binary_sensor.basement")
|
||||
assert occ3.unique_id == "00:00:00:00:00:00_4_56"
|
||||
|
||||
assert hass.states.get("binary_sensor.kitchen") is not None
|
||||
assert hass.states.get("binary_sensor.porch") is not None
|
||||
assert hass.states.get("binary_sensor.basement") is not None
|
||||
|
||||
# Now remove 3 new sensors at runtime - sensors should disappear and climate
|
||||
# shouldn't be duplicated.
|
||||
accessories = await setup_accessories_from_file(hass, "ecobee3_no_sensors.json")
|
||||
await device_config_changed(hass, accessories)
|
||||
|
||||
assert hass.states.get("binary_sensor.kitchen") is None
|
||||
assert hass.states.get("binary_sensor.porch") is None
|
||||
assert hass.states.get("binary_sensor.basement") is None
|
||||
|
||||
# Now add the sensors back
|
||||
accessories = await setup_accessories_from_file(hass, "ecobee3.json")
|
||||
await device_config_changed(hass, accessories)
|
||||
|
||||
occ1 = entity_registry.async_get("binary_sensor.kitchen")
|
||||
assert occ1.unique_id == "00:00:00:00:00:00_2_56"
|
||||
|
||||
occ2 = entity_registry.async_get("binary_sensor.porch")
|
||||
assert occ2.unique_id == "00:00:00:00:00:00_3_56"
|
||||
|
||||
occ3 = entity_registry.async_get("binary_sensor.basement")
|
||||
assert occ3.unique_id == "00:00:00:00:00:00_4_56"
|
||||
|
||||
# Currently it is not possible to add the entities back once
|
||||
# they are removed because _add_new_entities has a guard to prevent
|
||||
# the same entity from being added twice.
|
||||
|
||||
|
||||
async def test_ecobee3_services_and_chars_removed(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test handling removal of some services and chars."""
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
# Set up a base Ecobee 3 with additional sensors.
|
||||
accessories = await setup_accessories_from_file(hass, "ecobee3.json")
|
||||
await setup_test_accessories(hass, accessories)
|
||||
|
||||
climate = entity_registry.async_get("climate.homew")
|
||||
assert climate.unique_id == "00:00:00:00:00:00_1_16"
|
||||
|
||||
assert hass.states.get("sensor.basement_temperature") is not None
|
||||
assert hass.states.get("sensor.kitchen_temperature") is not None
|
||||
assert hass.states.get("sensor.porch_temperature") is not None
|
||||
|
||||
assert hass.states.get("select.homew_current_mode") is not None
|
||||
assert hass.states.get("button.homew_clear_hold") is not None
|
||||
|
||||
# Reconfigure with some of the chars removed and the basement temperature sensor
|
||||
accessories = await setup_accessories_from_file(
|
||||
hass, "ecobee3_service_removed.json"
|
||||
)
|
||||
await device_config_changed(hass, accessories)
|
||||
|
||||
# Make sure the climate entity is still there
|
||||
assert hass.states.get("climate.homew") is not None
|
||||
|
||||
# Make sure the basement temperature sensor is gone
|
||||
assert hass.states.get("sensor.basement_temperature") is None
|
||||
|
||||
# Make sure the current mode select and clear hold button are gone
|
||||
assert hass.states.get("select.homew_current_mode") is None
|
||||
assert hass.states.get("button.homew_clear_hold") is None
|
||||
|
|
|
@ -53,3 +53,90 @@ async def test_fan_add_feature_at_runtime(hass: HomeAssistant) -> None:
|
|||
)
|
||||
fan_state = hass.states.get("fan.ceiling_fan")
|
||||
assert fan_state.attributes[ATTR_SUPPORTED_FEATURES] is FanEntityFeature.SET_SPEED
|
||||
|
||||
|
||||
async def test_fan_remove_feature_at_runtime(hass: HomeAssistant) -> None:
|
||||
"""Test that features can be removed 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_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
|
||||
| FanEntityFeature.OSCILLATE
|
||||
)
|
||||
|
||||
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_basic_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
|
||||
)
|
||||
fan_state = hass.states.get("fan.ceiling_fan")
|
||||
assert fan_state.attributes[ATTR_SUPPORTED_FEATURES] is FanEntityFeature.SET_SPEED
|
||||
|
||||
|
||||
async def test_bridge_with_two_fans_one_removed(hass: HomeAssistant) -> None:
|
||||
"""Test a bridge with two fans and one gets removed."""
|
||||
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_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
|
||||
| FanEntityFeature.OSCILLATE
|
||||
)
|
||||
|
||||
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 remove one of the fans
|
||||
accessories = await setup_accessories_from_file(
|
||||
hass, "home_assistant_bridge_fan_one_removed.json"
|
||||
)
|
||||
await device_config_changed(hass, accessories)
|
||||
|
||||
# Verify the first fan is still there
|
||||
fan_state = hass.states.get("fan.living_room_fan")
|
||||
assert (
|
||||
fan_state.attributes[ATTR_SUPPORTED_FEATURES]
|
||||
is FanEntityFeature.SET_SPEED
|
||||
| FanEntityFeature.DIRECTION
|
||||
| FanEntityFeature.OSCILLATE
|
||||
)
|
||||
# The second fan should have been removed
|
||||
assert not hass.states.get("fan.ceiling_fan")
|
||||
|
|
Loading…
Add table
Reference in a new issue