Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 13 additions & 16 deletions homeassistant/components/homekit_controller/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,26 +286,23 @@ def __init__(
self._entity_key = (self._aid, self._iid, char.iid)

@callback
def _async_remove_entity_if_characteristics_disappeared(self) -> bool:
"""Handle characteristic disappearance."""
if (
not self._accessory.entity_map.aid(self._aid)
@override
def _async_config_changed(self) -> None:
"""Handle accessory discovery changes."""
if self._async_remove_entity_if_accessory_or_service_disappeared():
return

char = (
self._accessory.entity_map.aid(self._aid)
.services.iid(self._iid)
.get_char_by_iid(self._char.iid)
):
)
if char is None:
self._async_handle_entity_removed()
return True
return False
return

@callback
@override
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()
self._char = char
super()._async_reconfigure()


class CharacteristicEntity(BaseCharacteristicEntity):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from unittest import mock

from aiohomekit import AccessoryNotFoundError
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.services import ServicesTypes
from aiohomekit.testing import FakePairing

from homeassistant.components.climate import ClimateEntityFeature
Expand Down Expand Up @@ -140,6 +142,37 @@ async def test_ecobee3_setup(hass: HomeAssistant) -> None:
)


async def test_ecobee3_current_temperature_after_config_change(
hass: HomeAssistant,
) -> None:
"""Test current temperature updates after the accessory map is replaced."""
accessories = await setup_accessories_from_file(hass, "ecobee3.json")
await setup_test_accessories(hass, accessories)

state = hass.states.get("sensor.homew_current_temperature")
assert state
assert state.state == "21.8"

accessories = await setup_accessories_from_file(hass, "ecobee3.json")
thermostat = next(
accessory.services.first(service_type=ServicesTypes.THERMOSTAT)
for accessory in accessories
if accessory.aid == 1
)
assert thermostat
current_temperature = thermostat.characteristics.first(
char_types=[CharacteristicsTypes.TEMPERATURE_CURRENT]
)
assert current_temperature
current_temperature.value = 22.4

await device_config_changed(hass, accessories)

state = hass.states.get("sensor.homew_current_temperature")
assert state
assert state.state == "22.4"


async def test_ecobee3_setup_from_cache(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
Expand Down
60 changes: 59 additions & 1 deletion tests/components/homekit_controller/test_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@

from collections.abc import Callable

from aiohomekit.model import Accessory
from aiohomekit.model import Accessories, AccessoriesState, Accessory
from aiohomekit.model.characteristics import CharacteristicsTypes
from aiohomekit.model.characteristics.const import ThreadNodeCapabilities, ThreadStatus
from aiohomekit.model.services import Service, ServicesTypes
from aiohomekit.protocol.statuscodes import HapStatusCode
import pytest

from homeassistant.components.homekit_controller.sensor import (
SimpleSensor,
thread_node_capability_to_str,
thread_status_to_str,
)
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.entity_component import DATA_INSTANCES

from .common import TEST_DEVICE_SERVICE_INFO, Helper, setup_test_component

Expand Down Expand Up @@ -83,6 +85,20 @@ def create_humidifier_with_water_level_sensor(accessory: Accessory) -> Service:
return service


def create_accessory_without_humidifier(aid: int) -> Accessory:
"""Create an accessory without the humidifier service."""
return Accessory.create_with_info(
aid, "TestDevice", "example.com", "Test", "0001", "0.1"
)


def create_humidifier_without_water_level(aid: int) -> Accessory:
"""Create a humidifier accessory without the water level characteristic."""
accessory = create_accessory_without_humidifier(aid)
accessory.add_service(ServicesTypes.HUMIDIFIER_DEHUMIDIFIER)
return accessory


async def test_temperature_sensor_read_state(
hass: HomeAssistant, get_next_aid: Callable[[], int]
) -> None:
Expand Down Expand Up @@ -328,6 +344,48 @@ async def test_water_level_sensor_read_state(
assert state.attributes["state_class"] == SensorStateClass.MEASUREMENT


@pytest.mark.parametrize(
"replacement_accessory_factory",
[
pytest.param(
create_accessory_without_humidifier,
id="service-removed",
),
pytest.param(
create_humidifier_without_water_level,
id="characteristic-removed",
),
],
)
async def test_characteristic_sensor_removed_after_config_change(
hass: HomeAssistant,
get_next_aid: Callable[[], int],
replacement_accessory_factory: Callable[[int], Accessory],
) -> None:
"""Test a characteristic sensor is removed when its source disappears."""
aid = get_next_aid()
helper = await setup_test_component(
hass, aid, create_humidifier_with_water_level_sensor
)

assert hass.states.get("sensor.testdevice_water_level") is not None
entity = hass.data[DATA_INSTANCES]["sensor"].get_entity(
"sensor.testdevice_water_level"
)
assert isinstance(entity, SimpleSensor)

accessory = replacement_accessory_factory(aid)
accessories = Accessories()
accessories.add_accessory(accessory)
helper.pairing._accessories_state = AccessoriesState(
accessories, helper.pairing.config_num + 1
)
entity._async_config_changed()
await hass.async_block_till_done()

assert hass.states.get("sensor.testdevice_water_level") is None


def create_switch_with_sensor(accessory: Accessory) -> Service:
"""Define battery level characteristics."""
service = accessory.add_service(ServicesTypes.OUTLET)
Expand Down
Loading