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
This commit is contained in:
parent
188f3e35fd
commit
d6a47cb3e0
6 changed files with 146 additions and 80 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue