From d6a47cb3e058ca3c10eecc048347ef4e51ae4ebe Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 15 Apr 2020 21:38:31 -0500 Subject: [PATCH] Set homekit alarm/sensor/switch/cover state as soon as possible (#34245) * Set homekit alarm/sensor/switch state as soon as possible This change is part of a multi-part effort to fix the HomeKit event storms on startup. Previously we would set the states after HomeKit had started up which meant that when the controller client connected it would request the states and get a list of default states so all the initial states would always be wrong. The defaults states generally went unnoticed because we set the state of each HomeKit device soon after which would result in an event storm in the log that looked like the following for every client and every device: Sending event to client: ('192.168.x.x', 58410) Sending event to client: ('192.168.x.x', 53399) Sending event to client: ('192.168.x.x', 53399) To solve this, we now set the state right away when we create the entity in HomeKit, so it is correct on initial sync, which avoids the event storm. Additionally, we now check all states values before sending an update to HomeKit to ensure we do not send events when nothing has changed. * pylint * Fix event storm in covers as well * fix refactoring error in security system * cover positions, now with constants --- homeassistant/components/homekit/const.py | 5 ++ .../components/homekit/type_covers.py | 54 +++++++----- .../homekit/type_security_systems.py | 27 +++--- .../components/homekit/type_sensors.py | 82 +++++++++++++------ .../components/homekit/type_switches.py | 38 +++++---- .../components/homekit/test_type_switches.py | 20 ++--- 6 files changed, 146 insertions(+), 80 deletions(-) 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