mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
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
@ -190,3 +190,8 @@ HK_DOOR_CLOSED = 1
|
|||||||
HK_DOOR_OPENING = 2
|
HK_DOOR_OPENING = 2
|
||||||
HK_DOOR_CLOSING = 3
|
HK_DOOR_CLOSING = 3
|
||||||
HK_DOOR_STOPPED = 4
|
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_CLOSING,
|
||||||
HK_DOOR_OPEN,
|
HK_DOOR_OPEN,
|
||||||
HK_DOOR_OPENING,
|
HK_DOOR_OPENING,
|
||||||
|
HK_POSITION_GOING_TO_MAX,
|
||||||
|
HK_POSITION_GOING_TO_MIN,
|
||||||
|
HK_POSITION_STOPPED,
|
||||||
SERV_GARAGE_DOOR_OPENER,
|
SERV_GARAGE_DOOR_OPENER,
|
||||||
SERV_WINDOW_COVERING,
|
SERV_WINDOW_COVERING,
|
||||||
)
|
)
|
||||||
@ -134,10 +137,9 @@ class WindowCoveringBase(HomeAccessory):
|
|||||||
def __init__(self, *args, category):
|
def __init__(self, *args, category):
|
||||||
"""Initialize a WindowCoveringBase accessory object."""
|
"""Initialize a WindowCoveringBase accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_WINDOW_COVERING)
|
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(
|
self.features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||||
ATTR_SUPPORTED_FEATURES, 0
|
|
||||||
)
|
|
||||||
self._supports_stop = self.features & SUPPORT_STOP
|
self._supports_stop = self.features & SUPPORT_STOP
|
||||||
self._homekit_target_tilt = None
|
self._homekit_target_tilt = None
|
||||||
self.chars = []
|
self.chars = []
|
||||||
@ -192,7 +194,8 @@ class WindowCoveringBase(HomeAccessory):
|
|||||||
# We'll have to normalize to [0,100]
|
# We'll have to normalize to [0,100]
|
||||||
current_tilt = (current_tilt / 100.0 * 180.0) - 90.0
|
current_tilt = (current_tilt / 100.0 * 180.0) - 90.0
|
||||||
current_tilt = int(current_tilt)
|
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.
|
# 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
|
# 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(
|
if self._homekit_target_tilt is None or abs(
|
||||||
current_tilt - self._homekit_target_tilt < DEVICE_PRECISION_LEEWAY
|
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
|
self._homekit_target_tilt = None
|
||||||
|
|
||||||
|
|
||||||
@ -215,7 +219,7 @@ class WindowCovering(WindowCoveringBase, HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a WindowCovering accessory object."""
|
"""Initialize a WindowCovering accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_WINDOW_COVERING)
|
super().__init__(*args, category=CATEGORY_WINDOW_COVERING)
|
||||||
|
state = self.hass.states.get(self.entity_id)
|
||||||
self._homekit_target = None
|
self._homekit_target = None
|
||||||
|
|
||||||
self.char_current_position = self.serv_cover.configure_char(
|
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
|
CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover
|
||||||
)
|
)
|
||||||
self.char_position_state = self.serv_cover.configure_char(
|
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
|
@debounce
|
||||||
def move_cover(self, value):
|
def move_cover(self, value):
|
||||||
@ -242,7 +247,8 @@ class WindowCovering(WindowCoveringBase, HomeAccessory):
|
|||||||
current_position = new_state.attributes.get(ATTR_CURRENT_POSITION)
|
current_position = new_state.attributes.get(ATTR_CURRENT_POSITION)
|
||||||
if isinstance(current_position, (float, int)):
|
if isinstance(current_position, (float, int)):
|
||||||
current_position = int(current_position)
|
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.
|
# 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
|
# 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)
|
or abs(current_position - self._homekit_target)
|
||||||
< DEVICE_PRECISION_LEEWAY
|
< 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
|
self._homekit_target = None
|
||||||
if new_state.state == STATE_OPENING:
|
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:
|
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:
|
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)
|
super().update_state(new_state)
|
||||||
|
|
||||||
@ -276,7 +286,7 @@ class WindowCoveringBasic(WindowCoveringBase, HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a WindowCovering accessory object."""
|
"""Initialize a WindowCovering accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_WINDOW_COVERING)
|
super().__init__(*args, category=CATEGORY_WINDOW_COVERING)
|
||||||
|
state = self.hass.states.get(self.entity_id)
|
||||||
self.char_current_position = self.serv_cover.configure_char(
|
self.char_current_position = self.serv_cover.configure_char(
|
||||||
CHAR_CURRENT_POSITION, value=0
|
CHAR_CURRENT_POSITION, value=0
|
||||||
)
|
)
|
||||||
@ -284,8 +294,9 @@ class WindowCoveringBasic(WindowCoveringBase, HomeAccessory):
|
|||||||
CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover
|
CHAR_TARGET_POSITION, value=0, setter_callback=self.move_cover
|
||||||
)
|
)
|
||||||
self.char_position_state = self.serv_cover.configure_char(
|
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
|
@debounce
|
||||||
def move_cover(self, value):
|
def move_cover(self, value):
|
||||||
@ -317,13 +328,18 @@ class WindowCoveringBasic(WindowCoveringBase, HomeAccessory):
|
|||||||
position_mapping = {STATE_OPEN: 100, STATE_CLOSED: 0}
|
position_mapping = {STATE_OPEN: 100, STATE_CLOSED: 0}
|
||||||
hk_position = position_mapping.get(new_state.state)
|
hk_position = position_mapping.get(new_state.state)
|
||||||
if hk_position is not None:
|
if hk_position is not None:
|
||||||
self.char_current_position.set_value(hk_position)
|
if self.char_current_position.value != hk_position:
|
||||||
self.char_target_position.set_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:
|
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:
|
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:
|
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)
|
super().update_state(new_state)
|
||||||
|
@ -53,8 +53,8 @@ class SecuritySystem(HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a SecuritySystem accessory object."""
|
"""Initialize a SecuritySystem accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_ALARM_SYSTEM)
|
super().__init__(*args, category=CATEGORY_ALARM_SYSTEM)
|
||||||
|
state = self.hass.states.get(self.entity_id)
|
||||||
self._alarm_code = self.config.get(ATTR_CODE)
|
self._alarm_code = self.config.get(ATTR_CODE)
|
||||||
self._flag_state = False
|
|
||||||
|
|
||||||
serv_alarm = self.add_preload_service(SERV_SECURITY_SYSTEM)
|
serv_alarm = self.add_preload_service(SERV_SECURITY_SYSTEM)
|
||||||
self.char_current_state = serv_alarm.configure_char(
|
self.char_current_state = serv_alarm.configure_char(
|
||||||
@ -63,11 +63,13 @@ class SecuritySystem(HomeAccessory):
|
|||||||
self.char_target_state = serv_alarm.configure_char(
|
self.char_target_state = serv_alarm.configure_char(
|
||||||
CHAR_TARGET_SECURITY_STATE, value=3, setter_callback=self.set_security_state
|
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):
|
def set_security_state(self, value):
|
||||||
"""Move security state to value if call came from HomeKit."""
|
"""Move security state to value if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set security state to %d", self.entity_id, value)
|
_LOGGER.debug("%s: Set security state to %d", self.entity_id, value)
|
||||||
self._flag_state = True
|
|
||||||
hass_value = HOMEKIT_TO_HASS[value]
|
hass_value = HOMEKIT_TO_HASS[value]
|
||||||
service = STATE_TO_SERVICE[hass_value]
|
service = STATE_TO_SERVICE[hass_value]
|
||||||
|
|
||||||
@ -81,15 +83,18 @@ class SecuritySystem(HomeAccessory):
|
|||||||
hass_state = new_state.state
|
hass_state = new_state.state
|
||||||
if hass_state in HASS_TO_HOMEKIT:
|
if hass_state in HASS_TO_HOMEKIT:
|
||||||
current_security_state = HASS_TO_HOMEKIT[hass_state]
|
current_security_state = HASS_TO_HOMEKIT[hass_state]
|
||||||
self.char_current_state.set_value(current_security_state)
|
if self.char_current_state.value != current_security_state:
|
||||||
_LOGGER.debug(
|
self.char_current_state.set_value(current_security_state)
|
||||||
"%s: Updated current state to %s (%d)",
|
_LOGGER.debug(
|
||||||
self.entity_id,
|
"%s: Updated current state to %s (%d)",
|
||||||
hass_state,
|
self.entity_id,
|
||||||
current_security_state,
|
hass_state,
|
||||||
)
|
current_security_state,
|
||||||
|
)
|
||||||
|
|
||||||
# SecuritySystemTargetState does not support triggered
|
# 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.char_target_state.set_value(current_security_state)
|
||||||
self._flag_state = False
|
|
||||||
|
@ -83,10 +83,14 @@ class TemperatureSensor(HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a TemperatureSensor accessory object."""
|
"""Initialize a TemperatureSensor accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SENSOR)
|
super().__init__(*args, category=CATEGORY_SENSOR)
|
||||||
|
state = self.hass.states.get(self.entity_id)
|
||||||
serv_temp = self.add_preload_service(SERV_TEMPERATURE_SENSOR)
|
serv_temp = self.add_preload_service(SERV_TEMPERATURE_SENSOR)
|
||||||
self.char_temp = serv_temp.configure_char(
|
self.char_temp = serv_temp.configure_char(
|
||||||
CHAR_CURRENT_TEMPERATURE, value=0, properties=PROP_CELSIUS
|
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):
|
def update_state(self, new_state):
|
||||||
"""Update temperature after state changed."""
|
"""Update temperature after state changed."""
|
||||||
@ -94,10 +98,11 @@ class TemperatureSensor(HomeAccessory):
|
|||||||
temperature = convert_to_float(new_state.state)
|
temperature = convert_to_float(new_state.state)
|
||||||
if temperature:
|
if temperature:
|
||||||
temperature = temperature_to_homekit(temperature, unit)
|
temperature = temperature_to_homekit(temperature, unit)
|
||||||
self.char_temp.set_value(temperature)
|
if self.char_temp.value != temperature:
|
||||||
_LOGGER.debug(
|
self.char_temp.set_value(temperature)
|
||||||
"%s: Current temperature set to %.1f°C", self.entity_id, temperature
|
_LOGGER.debug(
|
||||||
)
|
"%s: Current temperature set to %.1f°C", self.entity_id, temperature
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@TYPES.register("HumiditySensor")
|
@TYPES.register("HumiditySensor")
|
||||||
@ -107,15 +112,19 @@ class HumiditySensor(HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a HumiditySensor accessory object."""
|
"""Initialize a HumiditySensor accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SENSOR)
|
super().__init__(*args, category=CATEGORY_SENSOR)
|
||||||
|
state = self.hass.states.get(self.entity_id)
|
||||||
serv_humidity = self.add_preload_service(SERV_HUMIDITY_SENSOR)
|
serv_humidity = self.add_preload_service(SERV_HUMIDITY_SENSOR)
|
||||||
self.char_humidity = serv_humidity.configure_char(
|
self.char_humidity = serv_humidity.configure_char(
|
||||||
CHAR_CURRENT_HUMIDITY, value=0
|
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):
|
def update_state(self, new_state):
|
||||||
"""Update accessory after state change."""
|
"""Update accessory after state change."""
|
||||||
humidity = convert_to_float(new_state.state)
|
humidity = convert_to_float(new_state.state)
|
||||||
if humidity:
|
if humidity and self.char_humidity.value != humidity:
|
||||||
self.char_humidity.set_value(humidity)
|
self.char_humidity.set_value(humidity)
|
||||||
_LOGGER.debug("%s: Percent set to %d%%", self.entity_id, humidity)
|
_LOGGER.debug("%s: Percent set to %d%%", self.entity_id, humidity)
|
||||||
|
|
||||||
@ -127,7 +136,7 @@ class AirQualitySensor(HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a AirQualitySensor accessory object."""
|
"""Initialize a AirQualitySensor accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SENSOR)
|
super().__init__(*args, category=CATEGORY_SENSOR)
|
||||||
|
state = self.hass.states.get(self.entity_id)
|
||||||
serv_air_quality = self.add_preload_service(
|
serv_air_quality = self.add_preload_service(
|
||||||
SERV_AIR_QUALITY_SENSOR, [CHAR_AIR_PARTICULATE_DENSITY]
|
SERV_AIR_QUALITY_SENSOR, [CHAR_AIR_PARTICULATE_DENSITY]
|
||||||
)
|
)
|
||||||
@ -135,14 +144,21 @@ class AirQualitySensor(HomeAccessory):
|
|||||||
self.char_density = serv_air_quality.configure_char(
|
self.char_density = serv_air_quality.configure_char(
|
||||||
CHAR_AIR_PARTICULATE_DENSITY, value=0
|
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):
|
def update_state(self, new_state):
|
||||||
"""Update accessory after state change."""
|
"""Update accessory after state change."""
|
||||||
density = convert_to_float(new_state.state)
|
density = convert_to_float(new_state.state)
|
||||||
if density:
|
if density:
|
||||||
self.char_density.set_value(density)
|
if self.char_density.value != density:
|
||||||
self.char_quality.set_value(density_to_air_quality(density))
|
self.char_density.set_value(density)
|
||||||
_LOGGER.debug("%s: Set to %d", self.entity_id, 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")
|
@TYPES.register("CarbonMonoxideSensor")
|
||||||
@ -152,7 +168,7 @@ class CarbonMonoxideSensor(HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a CarbonMonoxideSensor accessory object."""
|
"""Initialize a CarbonMonoxideSensor accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SENSOR)
|
super().__init__(*args, category=CATEGORY_SENSOR)
|
||||||
|
state = self.hass.states.get(self.entity_id)
|
||||||
serv_co = self.add_preload_service(
|
serv_co = self.add_preload_service(
|
||||||
SERV_CARBON_MONOXIDE_SENSOR,
|
SERV_CARBON_MONOXIDE_SENSOR,
|
||||||
[CHAR_CARBON_MONOXIDE_LEVEL, CHAR_CARBON_MONOXIDE_PEAK_LEVEL],
|
[CHAR_CARBON_MONOXIDE_LEVEL, CHAR_CARBON_MONOXIDE_PEAK_LEVEL],
|
||||||
@ -164,16 +180,22 @@ class CarbonMonoxideSensor(HomeAccessory):
|
|||||||
self.char_detected = serv_co.configure_char(
|
self.char_detected = serv_co.configure_char(
|
||||||
CHAR_CARBON_MONOXIDE_DETECTED, value=0
|
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):
|
def update_state(self, new_state):
|
||||||
"""Update accessory after state change."""
|
"""Update accessory after state change."""
|
||||||
value = convert_to_float(new_state.state)
|
value = convert_to_float(new_state.state)
|
||||||
if value:
|
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:
|
if value > self.char_peak.value:
|
||||||
self.char_peak.set_value(value)
|
self.char_peak.set_value(value)
|
||||||
self.char_detected.set_value(value > THRESHOLD_CO)
|
co_detected = value > THRESHOLD_CO
|
||||||
_LOGGER.debug("%s: Set to %d", self.entity_id, value)
|
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")
|
@TYPES.register("CarbonDioxideSensor")
|
||||||
@ -183,7 +205,7 @@ class CarbonDioxideSensor(HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a CarbonDioxideSensor accessory object."""
|
"""Initialize a CarbonDioxideSensor accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SENSOR)
|
super().__init__(*args, category=CATEGORY_SENSOR)
|
||||||
|
state = self.hass.states.get(self.entity_id)
|
||||||
serv_co2 = self.add_preload_service(
|
serv_co2 = self.add_preload_service(
|
||||||
SERV_CARBON_DIOXIDE_SENSOR,
|
SERV_CARBON_DIOXIDE_SENSOR,
|
||||||
[CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL],
|
[CHAR_CARBON_DIOXIDE_LEVEL, CHAR_CARBON_DIOXIDE_PEAK_LEVEL],
|
||||||
@ -195,16 +217,22 @@ class CarbonDioxideSensor(HomeAccessory):
|
|||||||
self.char_detected = serv_co2.configure_char(
|
self.char_detected = serv_co2.configure_char(
|
||||||
CHAR_CARBON_DIOXIDE_DETECTED, value=0
|
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):
|
def update_state(self, new_state):
|
||||||
"""Update accessory after state change."""
|
"""Update accessory after state change."""
|
||||||
value = convert_to_float(new_state.state)
|
value = convert_to_float(new_state.state)
|
||||||
if value:
|
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:
|
if value > self.char_peak.value:
|
||||||
self.char_peak.set_value(value)
|
self.char_peak.set_value(value)
|
||||||
self.char_detected.set_value(value > THRESHOLD_CO2)
|
co2_detected = value > THRESHOLD_CO2
|
||||||
_LOGGER.debug("%s: Set to %d", self.entity_id, value)
|
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")
|
@TYPES.register("LightSensor")
|
||||||
@ -214,16 +242,19 @@ class LightSensor(HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a LightSensor accessory object."""
|
"""Initialize a LightSensor accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SENSOR)
|
super().__init__(*args, category=CATEGORY_SENSOR)
|
||||||
|
state = self.hass.states.get(self.entity_id)
|
||||||
serv_light = self.add_preload_service(SERV_LIGHT_SENSOR)
|
serv_light = self.add_preload_service(SERV_LIGHT_SENSOR)
|
||||||
self.char_light = serv_light.configure_char(
|
self.char_light = serv_light.configure_char(
|
||||||
CHAR_CURRENT_AMBIENT_LIGHT_LEVEL, value=0
|
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):
|
def update_state(self, new_state):
|
||||||
"""Update accessory after state change."""
|
"""Update accessory after state change."""
|
||||||
luminance = convert_to_float(new_state.state)
|
luminance = convert_to_float(new_state.state)
|
||||||
if luminance:
|
if luminance and self.char_light.value != luminance:
|
||||||
self.char_light.set_value(luminance)
|
self.char_light.set_value(luminance)
|
||||||
_LOGGER.debug("%s: Set to %d", self.entity_id, luminance)
|
_LOGGER.debug("%s: Set to %d", self.entity_id, luminance)
|
||||||
|
|
||||||
@ -235,9 +266,8 @@ class BinarySensor(HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a BinarySensor accessory object."""
|
"""Initialize a BinarySensor accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SENSOR)
|
super().__init__(*args, category=CATEGORY_SENSOR)
|
||||||
device_class = self.hass.states.get(self.entity_id).attributes.get(
|
state = self.hass.states.get(self.entity_id)
|
||||||
ATTR_DEVICE_CLASS
|
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
|
||||||
)
|
|
||||||
service_char = (
|
service_char = (
|
||||||
BINARY_SENSOR_SERVICE_MAP[device_class]
|
BINARY_SENSOR_SERVICE_MAP[device_class]
|
||||||
if device_class in BINARY_SENSOR_SERVICE_MAP
|
if device_class in BINARY_SENSOR_SERVICE_MAP
|
||||||
@ -246,10 +276,14 @@ class BinarySensor(HomeAccessory):
|
|||||||
|
|
||||||
service = self.add_preload_service(service_char[0])
|
service = self.add_preload_service(service_char[0])
|
||||||
self.char_detected = service.configure_char(service_char[1], value=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):
|
def update_state(self, new_state):
|
||||||
"""Update accessory after state change."""
|
"""Update accessory after state change."""
|
||||||
state = new_state.state
|
state = new_state.state
|
||||||
detected = state in (STATE_ON, STATE_HOME)
|
detected = state in (STATE_ON, STATE_HOME)
|
||||||
self.char_detected.set_value(detected)
|
if self.char_detected.value != detected:
|
||||||
_LOGGER.debug("%s: Set to %d", self.entity_id, 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):
|
def __init__(self, *args):
|
||||||
"""Initialize an Outlet accessory object."""
|
"""Initialize an Outlet accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_OUTLET)
|
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)
|
serv_outlet = self.add_preload_service(SERV_OUTLET)
|
||||||
self.char_on = serv_outlet.configure_char(
|
self.char_on = serv_outlet.configure_char(
|
||||||
@ -64,11 +64,13 @@ class Outlet(HomeAccessory):
|
|||||||
self.char_outlet_in_use = serv_outlet.configure_char(
|
self.char_outlet_in_use = serv_outlet.configure_char(
|
||||||
CHAR_OUTLET_IN_USE, value=True
|
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):
|
def set_state(self, value):
|
||||||
"""Move switch state to value if call came from HomeKit."""
|
"""Move switch state to value if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
||||||
self._flag_state = True
|
|
||||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||||
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
||||||
self.call_service(DOMAIN, service, params)
|
self.call_service(DOMAIN, service, params)
|
||||||
@ -76,10 +78,9 @@ class Outlet(HomeAccessory):
|
|||||||
def update_state(self, new_state):
|
def update_state(self, new_state):
|
||||||
"""Update switch state after state changed."""
|
"""Update switch state after state changed."""
|
||||||
current_state = new_state.state == STATE_ON
|
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)
|
_LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state)
|
||||||
self.char_on.set_value(current_state)
|
self.char_on.set_value(current_state)
|
||||||
self._flag_state = False
|
|
||||||
|
|
||||||
|
|
||||||
@TYPES.register("Switch")
|
@TYPES.register("Switch")
|
||||||
@ -90,7 +91,7 @@ class Switch(HomeAccessory):
|
|||||||
"""Initialize a Switch accessory object."""
|
"""Initialize a Switch accessory object."""
|
||||||
super().__init__(*args, category=CATEGORY_SWITCH)
|
super().__init__(*args, category=CATEGORY_SWITCH)
|
||||||
self._domain = split_entity_id(self.entity_id)[0]
|
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))
|
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(
|
self.char_on = serv_switch.configure_char(
|
||||||
CHAR_ON, value=False, setter_callback=self.set_state
|
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):
|
def is_activate(self, state):
|
||||||
"""Check if entity is activate only."""
|
"""Check if entity is activate only."""
|
||||||
@ -111,15 +115,15 @@ class Switch(HomeAccessory):
|
|||||||
def reset_switch(self, *args):
|
def reset_switch(self, *args):
|
||||||
"""Reset switch to emulate activate click."""
|
"""Reset switch to emulate activate click."""
|
||||||
_LOGGER.debug("%s: Reset switch to off", self.entity_id)
|
_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):
|
def set_state(self, value):
|
||||||
"""Move switch state to value if call came from HomeKit."""
|
"""Move switch state to value if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
_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)
|
_LOGGER.debug("%s: Ignoring turn_off call", self.entity_id)
|
||||||
return
|
return
|
||||||
self._flag_state = True
|
|
||||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||||
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
||||||
self.call_service(self._domain, service, params)
|
self.call_service(self._domain, service, params)
|
||||||
@ -137,10 +141,9 @@ class Switch(HomeAccessory):
|
|||||||
return
|
return
|
||||||
|
|
||||||
current_state = new_state.state == STATE_ON
|
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)
|
_LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state)
|
||||||
self.char_on.set_value(current_state)
|
self.char_on.set_value(current_state)
|
||||||
self._flag_state = False
|
|
||||||
|
|
||||||
|
|
||||||
@TYPES.register("Valve")
|
@TYPES.register("Valve")
|
||||||
@ -150,7 +153,7 @@ class Valve(HomeAccessory):
|
|||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
"""Initialize a Valve accessory object."""
|
"""Initialize a Valve accessory object."""
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
self._flag_state = False
|
state = self.hass.states.get(self.entity_id)
|
||||||
valve_type = self.config[CONF_TYPE]
|
valve_type = self.config[CONF_TYPE]
|
||||||
self.category = VALVE_TYPE[valve_type][0]
|
self.category = VALVE_TYPE[valve_type][0]
|
||||||
|
|
||||||
@ -162,11 +165,13 @@ class Valve(HomeAccessory):
|
|||||||
self.char_valve_type = serv_valve.configure_char(
|
self.char_valve_type = serv_valve.configure_char(
|
||||||
CHAR_VALVE_TYPE, value=VALVE_TYPE[valve_type][1]
|
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):
|
def set_state(self, value):
|
||||||
"""Move value state to value if call came from HomeKit."""
|
"""Move value state to value if call came from HomeKit."""
|
||||||
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
_LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
|
||||||
self._flag_state = True
|
|
||||||
self.char_in_use.set_value(value)
|
self.char_in_use.set_value(value)
|
||||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||||
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
||||||
@ -174,9 +179,10 @@ class Valve(HomeAccessory):
|
|||||||
|
|
||||||
def update_state(self, new_state):
|
def update_state(self, new_state):
|
||||||
"""Update switch state after state changed."""
|
"""Update switch state after state changed."""
|
||||||
current_state = new_state.state == STATE_ON
|
current_state = 1 if new_state.state == STATE_ON else 0
|
||||||
if not self._flag_state:
|
if self.char_active.value != current_state:
|
||||||
_LOGGER.debug("%s: Set current state to %s", self.entity_id, current_state)
|
_LOGGER.debug("%s: Set active state to %s", self.entity_id, current_state)
|
||||||
self.char_active.set_value(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.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.aid == 2
|
||||||
assert acc.category == 29 # Faucet
|
assert acc.category == 29 # Faucet
|
||||||
|
|
||||||
assert acc.char_active.value is False
|
assert acc.char_active.value == 0
|
||||||
assert acc.char_in_use.value is False
|
assert acc.char_in_use.value == 0
|
||||||
assert acc.char_valve_type.value == 0 # Generic Valve
|
assert acc.char_valve_type.value == 0 # Generic Valve
|
||||||
|
|
||||||
hass.states.async_set(entity_id, STATE_ON)
|
hass.states.async_set(entity_id, STATE_ON)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_active.value is True
|
assert acc.char_active.value == 1
|
||||||
assert acc.char_in_use.value is True
|
assert acc.char_in_use.value == 1
|
||||||
|
|
||||||
hass.states.async_set(entity_id, STATE_OFF)
|
hass.states.async_set(entity_id, STATE_OFF)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert acc.char_active.value is False
|
assert acc.char_active.value == 0
|
||||||
assert acc.char_in_use.value is False
|
assert acc.char_in_use.value == 0
|
||||||
|
|
||||||
# Set from HomeKit
|
# Set from HomeKit
|
||||||
call_turn_on = async_mock_service(hass, "switch", "turn_on")
|
call_turn_on = async_mock_service(hass, "switch", "turn_on")
|
||||||
call_turn_off = async_mock_service(hass, "switch", "turn_off")
|
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()
|
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
|
||||||
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||||
assert len(events) == 1
|
assert len(events) == 1
|
||||||
assert events[-1].data[ATTR_VALUE] is None
|
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()
|
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
|
||||||
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
||||||
assert len(events) == 2
|
assert len(events) == 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user