diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index 3d64aaf3bea..5f48b122e64 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -190,3 +190,8 @@ HK_DOOR_CLOSED = 1 HK_DOOR_OPENING = 2 HK_DOOR_CLOSING = 3 HK_DOOR_STOPPED = 4 + +# ### Position State #### +HK_POSITION_GOING_TO_MIN = 0 +HK_POSITION_GOING_TO_MAX = 1 +HK_POSITION_STOPPED = 2 diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 8e55bc2a4b9..987ba900bc8 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -42,6 +42,9 @@ from .const import ( HK_DOOR_CLOSING, HK_DOOR_OPEN, HK_DOOR_OPENING, + HK_POSITION_GOING_TO_MAX, + HK_POSITION_GOING_TO_MIN, + HK_POSITION_STOPPED, SERV_GARAGE_DOOR_OPENER, SERV_WINDOW_COVERING, ) @@ -134,10 +137,9 @@ class WindowCoveringBase(HomeAccessory): def __init__(self, *args, category): """Initialize a WindowCoveringBase accessory object.""" super().__init__(*args, category=CATEGORY_WINDOW_COVERING) + state = self.hass.states.get(self.entity_id) - self.features = self.hass.states.get(self.entity_id).attributes.get( - ATTR_SUPPORTED_FEATURES, 0 - ) + self.features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) self._supports_stop = self.features & SUPPORT_STOP self._homekit_target_tilt = None self.chars = [] @@ -192,7 +194,8 @@ class WindowCoveringBase(HomeAccessory): # We'll have to normalize to [0,100] current_tilt = (current_tilt / 100.0 * 180.0) - 90.0 current_tilt = int(current_tilt) - self.char_current_tilt.set_value(current_tilt) + if self.char_current_tilt.value != current_tilt: + self.char_current_tilt.set_value(current_tilt) # We have to assume that the device has worse precision than HomeKit. # If it reports back a state that is only _close_ to HK's requested @@ -201,7 +204,8 @@ class WindowCoveringBase(HomeAccessory): if self._homekit_target_tilt is None or abs( current_tilt - self._homekit_target_tilt < DEVICE_PRECISION_LEEWAY ): - self.char_target_tilt.set_value(current_tilt) + if self.char_target_tilt.value != current_tilt: + self.char_target_tilt.set_value(current_tilt) self._homekit_target_tilt = None @@ -215,7 +219,7 @@ class WindowCovering(WindowCoveringBase, HomeAccessory): def __init__(self, *args): """Initialize a WindowCovering accessory object.""" super().__init__(*args, category=CATEGORY_WINDOW_COVERING) - + state = self.hass.states.get(self.entity_id) self._homekit_target = None self.char_current_position = self.serv_cover.configure_char( @@ -225,8 +229,9 @@ class WindowCovering(WindowCoveringBase, HomeAccessory): CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover ) self.char_position_state = self.serv_cover.configure_char( - CHAR_POSITION_STATE, value=2 + CHAR_POSITION_STATE, value=HK_POSITION_STOPPED ) + self.update_state(state) @debounce def move_cover(self, value): @@ -242,7 +247,8 @@ class WindowCovering(WindowCoveringBase, HomeAccessory): current_position = new_state.attributes.get(ATTR_CURRENT_POSITION) if isinstance(current_position, (float, int)): current_position = int(current_position) - self.char_current_position.set_value(current_position) + if self.char_current_position.value != current_position: + self.char_current_position.set_value(current_position) # We have to assume that the device has worse precision than HomeKit. # If it reports back a state that is only _close_ to HK's requested @@ -253,14 +259,18 @@ class WindowCovering(WindowCoveringBase, HomeAccessory): or abs(current_position - self._homekit_target) < DEVICE_PRECISION_LEEWAY ): - self.char_target_position.set_value(current_position) + if self.char_target_position.value != current_position: + self.char_target_position.set_value(current_position) self._homekit_target = None if new_state.state == STATE_OPENING: - self.char_position_state.set_value(1) + if self.char_position_state.value != HK_POSITION_GOING_TO_MAX: + self.char_position_state.set_value(HK_POSITION_GOING_TO_MAX) elif new_state.state == STATE_CLOSING: - self.char_position_state.set_value(0) + if self.char_position_state.value != HK_POSITION_GOING_TO_MIN: + self.char_position_state.set_value(HK_POSITION_GOING_TO_MIN) else: - self.char_position_state.set_value(2) + if self.char_position_state.value != HK_POSITION_STOPPED: + self.char_position_state.set_value(HK_POSITION_STOPPED) super().update_state(new_state) @@ -276,7 +286,7 @@ class WindowCoveringBasic(WindowCoveringBase, HomeAccessory): def __init__(self, *args): """Initialize a WindowCovering accessory object.""" super().__init__(*args, category=CATEGORY_WINDOW_COVERING) - + state = self.hass.states.get(self.entity_id) self.char_current_position = self.serv_cover.configure_char( CHAR_CURRENT_POSITION, value=0 ) @@ -284,8 +294,9 @@ class WindowCoveringBasic(WindowCoveringBase, HomeAccessory): CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover ) self.char_position_state = self.serv_cover.configure_char( - CHAR_POSITION_STATE, value=2 + CHAR_POSITION_STATE, value=HK_POSITION_STOPPED ) + self.update_state(state) @debounce def move_cover(self, value): @@ -317,13 +328,18 @@ class WindowCoveringBasic(WindowCoveringBase, HomeAccessory): position_mapping = {STATE_OPEN: 100, STATE_CLOSED: 0} hk_position = position_mapping.get(new_state.state) if hk_position is not None: - self.char_current_position.set_value(hk_position) - self.char_target_position.set_value(hk_position) + if self.char_current_position.value != hk_position: + self.char_current_position.set_value(hk_position) + if self.char_target_position.value != hk_position: + self.char_target_position.set_value(hk_position) if new_state.state == STATE_OPENING: - self.char_position_state.set_value(1) + if self.char_position_state.value != HK_POSITION_GOING_TO_MAX: + self.char_position_state.set_value(HK_POSITION_GOING_TO_MAX) elif new_state.state == STATE_CLOSING: - self.char_position_state.set_value(0) + if self.char_position_state.value != HK_POSITION_GOING_TO_MIN: + self.char_position_state.set_value(HK_POSITION_GOING_TO_MIN) else: - self.char_position_state.set_value(2) + if self.char_position_state.value != HK_POSITION_STOPPED: + self.char_position_state.set_value(HK_POSITION_STOPPED) super().update_state(new_state) diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index 345709eb7da..59e10a42c29 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -53,8 +53,8 @@ class SecuritySystem(HomeAccessory): def __init__(self, *args): """Initialize a SecuritySystem accessory object.""" super().__init__(*args, category=CATEGORY_ALARM_SYSTEM) + state = self.hass.states.get(self.entity_id) self._alarm_code = self.config.get(ATTR_CODE) - self._flag_state = False serv_alarm = self.add_preload_service(SERV_SECURITY_SYSTEM) self.char_current_state = serv_alarm.configure_char( @@ -63,11 +63,13 @@ class SecuritySystem(HomeAccessory): self.char_target_state = serv_alarm.configure_char( CHAR_TARGET_SECURITY_STATE, value=3, setter_callback=self.set_security_state ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def set_security_state(self, value): """Move security state to value if call came from HomeKit.""" _LOGGER.debug("%s: Set security state to %d", self.entity_id, value) - self._flag_state = True hass_value = HOMEKIT_TO_HASS[value] service = STATE_TO_SERVICE[hass_value] @@ -81,15 +83,18 @@ class SecuritySystem(HomeAccessory): hass_state = new_state.state if hass_state in HASS_TO_HOMEKIT: current_security_state = HASS_TO_HOMEKIT[hass_state] - self.char_current_state.set_value(current_security_state) - _LOGGER.debug( - "%s: Updated current state to %s (%d)", - self.entity_id, - hass_state, - current_security_state, - ) + if self.char_current_state.value != current_security_state: + self.char_current_state.set_value(current_security_state) + _LOGGER.debug( + "%s: Updated current state to %s (%d)", + self.entity_id, + hass_state, + current_security_state, + ) # SecuritySystemTargetState does not support triggered - if not self._flag_state and hass_state != STATE_ALARM_TRIGGERED: + if ( + hass_state != STATE_ALARM_TRIGGERED + and self.char_target_state.value != current_security_state + ): self.char_target_state.set_value(current_security_state) - self._flag_state = False diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index a1450518e0c..78cb21bc88a 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -83,10 +83,14 @@ class TemperatureSensor(HomeAccessory): def __init__(self, *args): """Initialize a TemperatureSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) + state = self.hass.states.get(self.entity_id) serv_temp = self.add_preload_service(SERV_TEMPERATURE_SENSOR) self.char_temp = serv_temp.configure_char( CHAR_CURRENT_TEMPERATURE, value=0, properties=PROP_CELSIUS ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def update_state(self, new_state): """Update temperature after state changed.""" @@ -94,10 +98,11 @@ class TemperatureSensor(HomeAccessory): temperature = convert_to_float(new_state.state) if temperature: temperature = temperature_to_homekit(temperature, unit) - self.char_temp.set_value(temperature) - _LOGGER.debug( - "%s: Current temperature set to %.1f°C", self.entity_id, temperature - ) + if self.char_temp.value != temperature: + self.char_temp.set_value(temperature) + _LOGGER.debug( + "%s: Current temperature set to %.1f°C", self.entity_id, temperature + ) @TYPES.register("HumiditySensor") @@ -107,15 +112,19 @@ class HumiditySensor(HomeAccessory): def __init__(self, *args): """Initialize a HumiditySensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) + state = self.hass.states.get(self.entity_id) serv_humidity = self.add_preload_service(SERV_HUMIDITY_SENSOR) self.char_humidity = serv_humidity.configure_char( CHAR_CURRENT_HUMIDITY, value=0 ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def update_state(self, new_state): """Update accessory after state change.""" humidity = convert_to_float(new_state.state) - if humidity: + if humidity and self.char_humidity.value != humidity: self.char_humidity.set_value(humidity) _LOGGER.debug("%s: Percent set to %d%%", self.entity_id, humidity) @@ -127,7 +136,7 @@ class AirQualitySensor(HomeAccessory): def __init__(self, *args): """Initialize a AirQualitySensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - + state = self.hass.states.get(self.entity_id) serv_air_quality = self.add_preload_service( SERV_AIR_QUALITY_SENSOR, [CHAR_AIR_PARTICULATE_DENSITY] ) @@ -135,14 +144,21 @@ class AirQualitySensor(HomeAccessory): self.char_density = serv_air_quality.configure_char( CHAR_AIR_PARTICULATE_DENSITY, value=0 ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def update_state(self, new_state): """Update accessory after state change.""" density = convert_to_float(new_state.state) if density: - self.char_density.set_value(density) - self.char_quality.set_value(density_to_air_quality(density)) - _LOGGER.debug("%s: Set to %d", self.entity_id, density) + if self.char_density.value != density: + self.char_density.set_value(density) + _LOGGER.debug("%s: Set density to %d", self.entity_id, density) + air_quality = density_to_air_quality(density) + if self.char_quality.value != air_quality: + self.char_quality.set_value(air_quality) + _LOGGER.debug("%s: Set air_quality to %d", self.entity_id, air_quality) @TYPES.register("CarbonMonoxideSensor") @@ -152,7 +168,7 @@ class CarbonMonoxideSensor(HomeAccessory): def __init__(self, *args): """Initialize a CarbonMonoxideSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - + state = self.hass.states.get(self.entity_id) serv_co = self.add_preload_service( SERV_CARBON_MONOXIDE_SENSOR, [CHAR_CARBON_MONOXIDE_LEVEL, CHAR_CARBON_MONOXIDE_PEAK_LEVEL], @@ -164,16 +180,22 @@ class CarbonMonoxideSensor(HomeAccessory): self.char_detected = serv_co.configure_char( CHAR_CARBON_MONOXIDE_DETECTED, value=0 ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def update_state(self, new_state): """Update accessory after state change.""" value = convert_to_float(new_state.state) if value: - self.char_level.set_value(value) + if self.char_level.value != value: + self.char_level.set_value(value) if value > self.char_peak.value: self.char_peak.set_value(value) - self.char_detected.set_value(value > THRESHOLD_CO) - _LOGGER.debug("%s: Set to %d", self.entity_id, value) + co_detected = value > THRESHOLD_CO + if self.char_detected.value is not co_detected: + self.char_detected.set_value(co_detected) + _LOGGER.debug("%s: Set to %d", self.entity_id, value) @TYPES.register("CarbonDioxideSensor") @@ -183,7 +205,7 @@ class CarbonDioxideSensor(HomeAccessory): def __init__(self, *args): """Initialize a CarbonDioxideSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - + state = self.hass.states.get(self.entity_id) serv_co2 = self.add_preload_service( SERV_CARBON_DIOXIDE_SENSOR, [CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL], @@ -195,16 +217,22 @@ class CarbonDioxideSensor(HomeAccessory): self.char_detected = serv_co2.configure_char( CHAR_CARBON_DIOXIDE_DETECTED, value=0 ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def update_state(self, new_state): """Update accessory after state change.""" value = convert_to_float(new_state.state) if value: - self.char_level.set_value(value) + if self.char_level.value != value: + self.char_level.set_value(value) if value > self.char_peak.value: self.char_peak.set_value(value) - self.char_detected.set_value(value > THRESHOLD_CO2) - _LOGGER.debug("%s: Set to %d", self.entity_id, value) + co2_detected = value > THRESHOLD_CO2 + if self.char_detected.value is not co2_detected: + self.char_detected.set_value(co2_detected) + _LOGGER.debug("%s: Set to %d", self.entity_id, value) @TYPES.register("LightSensor") @@ -214,16 +242,19 @@ class LightSensor(HomeAccessory): def __init__(self, *args): """Initialize a LightSensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - + state = self.hass.states.get(self.entity_id) serv_light = self.add_preload_service(SERV_LIGHT_SENSOR) self.char_light = serv_light.configure_char( CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, value=0 ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def update_state(self, new_state): """Update accessory after state change.""" luminance = convert_to_float(new_state.state) - if luminance: + if luminance and self.char_light.value != luminance: self.char_light.set_value(luminance) _LOGGER.debug("%s: Set to %d", self.entity_id, luminance) @@ -235,9 +266,8 @@ class BinarySensor(HomeAccessory): def __init__(self, *args): """Initialize a BinarySensor accessory object.""" super().__init__(*args, category=CATEGORY_SENSOR) - device_class = self.hass.states.get(self.entity_id).attributes.get( - ATTR_DEVICE_CLASS - ) + state = self.hass.states.get(self.entity_id) + device_class = state.attributes.get(ATTR_DEVICE_CLASS) service_char = ( BINARY_SENSOR_SERVICE_MAP[device_class] if device_class in BINARY_SENSOR_SERVICE_MAP @@ -246,10 +276,14 @@ class BinarySensor(HomeAccessory): service = self.add_preload_service(service_char[0]) self.char_detected = service.configure_char(service_char[1], value=0) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def update_state(self, new_state): """Update accessory after state change.""" state = new_state.state detected = state in (STATE_ON, STATE_HOME) - self.char_detected.set_value(detected) - _LOGGER.debug("%s: Set to %d", self.entity_id, detected) + if self.char_detected.value != detected: + self.char_detected.set_value(detected) + _LOGGER.debug("%s: Set to %d", self.entity_id, detected) diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py index 66d3037b894..5dcac8b7259 100644 --- a/homeassistant/components/homekit/type_switches.py +++ b/homeassistant/components/homekit/type_switches.py @@ -55,7 +55,7 @@ class Outlet(HomeAccessory): def __init__(self, *args): """Initialize an Outlet accessory object.""" super().__init__(*args, category=CATEGORY_OUTLET) - self._flag_state = False + state = self.hass.states.get(self.entity_id) serv_outlet = self.add_preload_service(SERV_OUTLET) self.char_on = serv_outlet.configure_char( @@ -64,11 +64,13 @@ class Outlet(HomeAccessory): self.char_outlet_in_use = serv_outlet.configure_char( CHAR_OUTLET_IN_USE, value=True ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def set_state(self, value): """Move switch state to value if call came from HomeKit.""" _LOGGER.debug("%s: Set switch state to %s", self.entity_id, value) - self._flag_state = True params = {ATTR_ENTITY_ID: self.entity_id} service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF self.call_service(DOMAIN, service, params) @@ -76,10 +78,9 @@ class Outlet(HomeAccessory): def update_state(self, new_state): """Update switch state after state changed.""" current_state = new_state.state == STATE_ON - if not self._flag_state: + if self.char_on.value is not current_state: _LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state) self.char_on.set_value(current_state) - self._flag_state = False @TYPES.register("Switch") @@ -90,7 +91,7 @@ class Switch(HomeAccessory): """Initialize a Switch accessory object.""" super().__init__(*args, category=CATEGORY_SWITCH) self._domain = split_entity_id(self.entity_id)[0] - self._flag_state = False + state = self.hass.states.get(self.entity_id) self.activate_only = self.is_activate(self.hass.states.get(self.entity_id)) @@ -98,6 +99,9 @@ class Switch(HomeAccessory): self.char_on = serv_switch.configure_char( CHAR_ON, value=False, setter_callback=self.set_state ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def is_activate(self, state): """Check if entity is activate only.""" @@ -111,15 +115,15 @@ class Switch(HomeAccessory): def reset_switch(self, *args): """Reset switch to emulate activate click.""" _LOGGER.debug("%s: Reset switch to off", self.entity_id) - self.char_on.set_value(0) + if self.char_on.value is not False: + self.char_on.set_value(False) def set_state(self, value): """Move switch state to value if call came from HomeKit.""" _LOGGER.debug("%s: Set switch state to %s", self.entity_id, value) - if self.activate_only and value == 0: + if self.activate_only and not value: _LOGGER.debug("%s: Ignoring turn_off call", self.entity_id) return - self._flag_state = True params = {ATTR_ENTITY_ID: self.entity_id} service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF self.call_service(self._domain, service, params) @@ -137,10 +141,9 @@ class Switch(HomeAccessory): return current_state = new_state.state == STATE_ON - if not self._flag_state: + if self.char_on.value is not current_state: _LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state) self.char_on.set_value(current_state) - self._flag_state = False @TYPES.register("Valve") @@ -150,7 +153,7 @@ class Valve(HomeAccessory): def __init__(self, *args): """Initialize a Valve accessory object.""" super().__init__(*args) - self._flag_state = False + state = self.hass.states.get(self.entity_id) valve_type = self.config[CONF_TYPE] self.category = VALVE_TYPE[valve_type][0] @@ -162,11 +165,13 @@ class Valve(HomeAccessory): self.char_valve_type = serv_valve.configure_char( CHAR_VALVE_TYPE, value=VALVE_TYPE[valve_type][1] ) + # Set the state so it is in sync on initial + # GET to avoid an event storm after homekit startup + self.update_state(state) def set_state(self, value): """Move value state to value if call came from HomeKit.""" _LOGGER.debug("%s: Set switch state to %s", self.entity_id, value) - self._flag_state = True self.char_in_use.set_value(value) params = {ATTR_ENTITY_ID: self.entity_id} service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF @@ -174,9 +179,10 @@ class Valve(HomeAccessory): def update_state(self, new_state): """Update switch state after state changed.""" - current_state = new_state.state == STATE_ON - if not self._flag_state: - _LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state) + current_state = 1 if new_state.state == STATE_ON else 0 + if self.char_active.value != current_state: + _LOGGER.debug("%s: Set active state to %s", self.entity_id, current_state) self.char_active.set_value(current_state) + if self.char_in_use.value != current_state: + _LOGGER.debug("%s: Set in_use state to %s", self.entity_id, current_state) self.char_in_use.set_value(current_state) - self._flag_state = False diff --git a/tests/components/homekit/test_type_switches.py b/tests/components/homekit/test_type_switches.py index 1a24c883c04..f8120d8ebbc 100644 --- a/tests/components/homekit/test_type_switches.py +++ b/tests/components/homekit/test_type_switches.py @@ -147,35 +147,35 @@ async def test_valve_set_state(hass, hk_driver, events): assert acc.aid == 2 assert acc.category == 29 # Faucet - assert acc.char_active.value is False - assert acc.char_in_use.value is False + assert acc.char_active.value == 0 + assert acc.char_in_use.value == 0 assert acc.char_valve_type.value == 0 # Generic Valve hass.states.async_set(entity_id, STATE_ON) await hass.async_block_till_done() - assert acc.char_active.value is True - assert acc.char_in_use.value is True + assert acc.char_active.value == 1 + assert acc.char_in_use.value == 1 hass.states.async_set(entity_id, STATE_OFF) await hass.async_block_till_done() - assert acc.char_active.value is False - assert acc.char_in_use.value is False + assert acc.char_active.value == 0 + assert acc.char_in_use.value == 0 # Set from HomeKit call_turn_on = async_mock_service(hass, "switch", "turn_on") call_turn_off = async_mock_service(hass, "switch", "turn_off") - await hass.async_add_executor_job(acc.char_active.client_update_value, True) + await hass.async_add_executor_job(acc.char_active.client_update_value, 1) await hass.async_block_till_done() - assert acc.char_in_use.value is True + assert acc.char_in_use.value == 1 assert call_turn_on assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id assert len(events) == 1 assert events[-1].data[ATTR_VALUE] is None - await hass.async_add_executor_job(acc.char_active.client_update_value, False) + await hass.async_add_executor_job(acc.char_active.client_update_value, 0) await hass.async_block_till_done() - assert acc.char_in_use.value is False + assert acc.char_in_use.value == 0 assert call_turn_off assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id assert len(events) == 2