Use homekit service callbacks for lights to resolve out of sync states (#32348)
* Switch homekit lights to use service callbacks Service callbacks allow us to get the on/off, brightness, etc all in one call so we remove all the complexity that was previously needed to handle the out of sync states We now get the on event and brightness event at the same time which allows us to prevent lights from flashing up to 100% before the requested brightness. * Fix STATE_OFF -> STATE_ON,brightness:0pull/31889/head^2
parent
2b0bdd580c
commit
f25321e010
|
@ -25,7 +25,7 @@ from homeassistant.const import (
|
|||
)
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, debounce
|
||||
from .accessories import HomeAccessory
|
||||
from .const import (
|
||||
CHAR_BRIGHTNESS,
|
||||
CHAR_COLOR_TEMPERATURE,
|
||||
|
@ -52,15 +52,6 @@ class Light(HomeAccessory):
|
|||
def __init__(self, *args):
|
||||
"""Initialize a new Light accessory object."""
|
||||
super().__init__(*args, category=CATEGORY_LIGHTBULB)
|
||||
self._flag = {
|
||||
CHAR_ON: False,
|
||||
CHAR_BRIGHTNESS: False,
|
||||
CHAR_HUE: False,
|
||||
CHAR_SATURATION: False,
|
||||
CHAR_COLOR_TEMPERATURE: False,
|
||||
RGB_COLOR: False,
|
||||
}
|
||||
self._state = 0
|
||||
|
||||
self.chars = []
|
||||
self._features = self.hass.states.get(self.entity_id).attributes.get(
|
||||
|
@ -82,17 +73,14 @@ class Light(HomeAccessory):
|
|||
self.chars.append(CHAR_COLOR_TEMPERATURE)
|
||||
|
||||
serv_light = self.add_preload_service(SERV_LIGHTBULB, self.chars)
|
||||
self.char_on = serv_light.configure_char(
|
||||
CHAR_ON, value=self._state, setter_callback=self.set_state
|
||||
)
|
||||
|
||||
self.char_on = serv_light.configure_char(CHAR_ON, value=0)
|
||||
|
||||
if CHAR_BRIGHTNESS in self.chars:
|
||||
# Initial value is set to 100 because 0 is a special value (off). 100 is
|
||||
# an arbitrary non-zero value. It is updated immediately by update_state
|
||||
# to set to the correct initial value.
|
||||
self.char_brightness = serv_light.configure_char(
|
||||
CHAR_BRIGHTNESS, value=100, setter_callback=self.set_brightness
|
||||
)
|
||||
self.char_brightness = serv_light.configure_char(CHAR_BRIGHTNESS, value=100)
|
||||
|
||||
if CHAR_COLOR_TEMPERATURE in self.chars:
|
||||
min_mireds = self.hass.states.get(self.entity_id).attributes.get(
|
||||
|
@ -105,133 +93,98 @@ class Light(HomeAccessory):
|
|||
CHAR_COLOR_TEMPERATURE,
|
||||
value=min_mireds,
|
||||
properties={PROP_MIN_VALUE: min_mireds, PROP_MAX_VALUE: max_mireds},
|
||||
setter_callback=self.set_color_temperature,
|
||||
)
|
||||
|
||||
if CHAR_HUE in self.chars:
|
||||
self.char_hue = serv_light.configure_char(
|
||||
CHAR_HUE, value=0, setter_callback=self.set_hue
|
||||
)
|
||||
self.char_hue = serv_light.configure_char(CHAR_HUE, value=0)
|
||||
|
||||
if CHAR_SATURATION in self.chars:
|
||||
self.char_saturation = serv_light.configure_char(
|
||||
CHAR_SATURATION, value=75, setter_callback=self.set_saturation
|
||||
)
|
||||
self.char_saturation = serv_light.configure_char(CHAR_SATURATION, value=75)
|
||||
|
||||
def set_state(self, value):
|
||||
"""Set state if call came from HomeKit."""
|
||||
if self._state == value:
|
||||
return
|
||||
serv_light.setter_callback = self._set_chars
|
||||
|
||||
_LOGGER.debug("%s: Set state to %d", self.entity_id, value)
|
||||
self._flag[CHAR_ON] = True
|
||||
def _set_chars(self, char_values):
|
||||
_LOGGER.debug("_set_chars: %s", char_values)
|
||||
events = []
|
||||
service = SERVICE_TURN_ON
|
||||
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||
service = SERVICE_TURN_ON if value == 1 else SERVICE_TURN_OFF
|
||||
self.call_service(DOMAIN, service, params)
|
||||
if CHAR_ON in char_values:
|
||||
if not char_values[CHAR_ON]:
|
||||
service = SERVICE_TURN_OFF
|
||||
events.append(f"Set state to {char_values[CHAR_ON]}")
|
||||
|
||||
@debounce
|
||||
def set_brightness(self, value):
|
||||
"""Set brightness if call came from HomeKit."""
|
||||
_LOGGER.debug("%s: Set brightness to %d", self.entity_id, value)
|
||||
self._flag[CHAR_BRIGHTNESS] = True
|
||||
if value == 0:
|
||||
self.set_state(0) # Turn off light
|
||||
return
|
||||
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_BRIGHTNESS_PCT: value}
|
||||
self.call_service(DOMAIN, SERVICE_TURN_ON, params, f"brightness at {value}%")
|
||||
if CHAR_BRIGHTNESS in char_values:
|
||||
if char_values[CHAR_BRIGHTNESS] == 0:
|
||||
events[-1] = f"Set state to 0"
|
||||
service = SERVICE_TURN_OFF
|
||||
else:
|
||||
params[ATTR_BRIGHTNESS_PCT] = char_values[CHAR_BRIGHTNESS]
|
||||
events.append(f"brightness at {char_values[CHAR_BRIGHTNESS]}%")
|
||||
|
||||
def set_color_temperature(self, value):
|
||||
"""Set color temperature if call came from HomeKit."""
|
||||
_LOGGER.debug("%s: Set color temp to %s", self.entity_id, value)
|
||||
self._flag[CHAR_COLOR_TEMPERATURE] = True
|
||||
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_COLOR_TEMP: value}
|
||||
self.call_service(
|
||||
DOMAIN, SERVICE_TURN_ON, params, f"color temperature at {value}"
|
||||
)
|
||||
if CHAR_COLOR_TEMPERATURE in char_values:
|
||||
params[ATTR_COLOR_TEMP] = char_values[CHAR_COLOR_TEMPERATURE]
|
||||
events.append(f"color temperature at {char_values[CHAR_COLOR_TEMPERATURE]}")
|
||||
|
||||
def set_saturation(self, value):
|
||||
"""Set saturation if call came from HomeKit."""
|
||||
_LOGGER.debug("%s: Set saturation to %d", self.entity_id, value)
|
||||
self._flag[CHAR_SATURATION] = True
|
||||
self._saturation = value
|
||||
self.set_color()
|
||||
|
||||
def set_hue(self, value):
|
||||
"""Set hue if call came from HomeKit."""
|
||||
_LOGGER.debug("%s: Set hue to %d", self.entity_id, value)
|
||||
self._flag[CHAR_HUE] = True
|
||||
self._hue = value
|
||||
self.set_color()
|
||||
|
||||
def set_color(self):
|
||||
"""Set color if call came from HomeKit."""
|
||||
if (
|
||||
self._features & SUPPORT_COLOR
|
||||
and self._flag[CHAR_HUE]
|
||||
and self._flag[CHAR_SATURATION]
|
||||
and CHAR_HUE in char_values
|
||||
and CHAR_SATURATION in char_values
|
||||
):
|
||||
color = (self._hue, self._saturation)
|
||||
color = (char_values[CHAR_HUE], char_values[CHAR_SATURATION])
|
||||
_LOGGER.debug("%s: Set hs_color to %s", self.entity_id, color)
|
||||
self._flag.update(
|
||||
{CHAR_HUE: False, CHAR_SATURATION: False, RGB_COLOR: True}
|
||||
)
|
||||
params = {ATTR_ENTITY_ID: self.entity_id, ATTR_HS_COLOR: color}
|
||||
self.call_service(DOMAIN, SERVICE_TURN_ON, params, f"set color at {color}")
|
||||
params[ATTR_HS_COLOR] = color
|
||||
events.append(f"set color at {color}")
|
||||
|
||||
self.call_service(DOMAIN, service, params, ", ".join(events))
|
||||
|
||||
def update_state(self, new_state):
|
||||
"""Update light after state change."""
|
||||
# Handle State
|
||||
state = new_state.state
|
||||
if state in (STATE_ON, STATE_OFF):
|
||||
self._state = 1 if state == STATE_ON else 0
|
||||
if not self._flag[CHAR_ON] and self.char_on.value != self._state:
|
||||
self.char_on.set_value(self._state)
|
||||
self._flag[CHAR_ON] = False
|
||||
if state == STATE_ON and self.char_on.value != 1:
|
||||
self.char_on.set_value(1)
|
||||
elif state == STATE_OFF and self.char_on.value != 0:
|
||||
self.char_on.set_value(0)
|
||||
|
||||
# Handle Brightness
|
||||
if CHAR_BRIGHTNESS in self.chars:
|
||||
brightness = new_state.attributes.get(ATTR_BRIGHTNESS)
|
||||
if not self._flag[CHAR_BRIGHTNESS] and isinstance(brightness, int):
|
||||
if isinstance(brightness, int):
|
||||
brightness = round(brightness / 255 * 100, 0)
|
||||
# The homeassistant component might report its brightness as 0 but is
|
||||
# not off. But 0 is a special value in homekit. When you turn on a
|
||||
# homekit accessory it will try to restore the last brightness state
|
||||
# which will be the last value saved by char_brightness.set_value.
|
||||
# But if it is set to 0, HomeKit will update the brightness to 100 as
|
||||
# it thinks 0 is off.
|
||||
#
|
||||
# Therefore, if the the brightness is 0 and the device is still on,
|
||||
# the brightness is mapped to 1 otherwise the update is ignored in
|
||||
# order to avoid this incorrect behavior.
|
||||
if brightness == 0 and state == STATE_ON:
|
||||
brightness = 1
|
||||
if self.char_brightness.value != brightness:
|
||||
# The homeassistant component might report its brightness as 0 but is
|
||||
# not off. But 0 is a special value in homekit. When you turn on a
|
||||
# homekit accessory it will try to restore the last brightness state
|
||||
# which will be the last value saved by char_brightness.set_value.
|
||||
# But if it is set to 0, HomeKit will update the brightness to 100 as
|
||||
# it thinks 0 is off.
|
||||
#
|
||||
# Therefore, if the the brightness is 0 and the device is still on,
|
||||
# the brightness is mapped to 1 otherwise the update is ignored in
|
||||
# order to avoid this incorrect behavior.
|
||||
if brightness == 0:
|
||||
if state == STATE_ON:
|
||||
self.char_brightness.set_value(1)
|
||||
else:
|
||||
self.char_brightness.set_value(brightness)
|
||||
self._flag[CHAR_BRIGHTNESS] = False
|
||||
self.char_brightness.set_value(brightness)
|
||||
|
||||
# Handle color temperature
|
||||
if CHAR_COLOR_TEMPERATURE in self.chars:
|
||||
color_temperature = new_state.attributes.get(ATTR_COLOR_TEMP)
|
||||
if (
|
||||
not self._flag[CHAR_COLOR_TEMPERATURE]
|
||||
and isinstance(color_temperature, int)
|
||||
isinstance(color_temperature, int)
|
||||
and self.char_color_temperature.value != color_temperature
|
||||
):
|
||||
self.char_color_temperature.set_value(color_temperature)
|
||||
self._flag[CHAR_COLOR_TEMPERATURE] = False
|
||||
|
||||
# Handle Color
|
||||
if CHAR_SATURATION in self.chars and CHAR_HUE in self.chars:
|
||||
hue, saturation = new_state.attributes.get(ATTR_HS_COLOR, (None, None))
|
||||
if (
|
||||
not self._flag[RGB_COLOR]
|
||||
and (hue != self._hue or saturation != self._saturation)
|
||||
and isinstance(hue, (int, float))
|
||||
isinstance(hue, (int, float))
|
||||
and isinstance(saturation, (int, float))
|
||||
and (
|
||||
hue != self.char_hue.value
|
||||
or saturation != self.char_saturation.value
|
||||
)
|
||||
):
|
||||
self.char_hue.set_value(hue)
|
||||
self.char_saturation.set_value(saturation)
|
||||
self._hue, self._saturation = (hue, saturation)
|
||||
self._flag[RGB_COLOR] = False
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
"""Test different accessory types: Lights."""
|
||||
from collections import namedtuple
|
||||
|
||||
from asynctest import patch
|
||||
from pyhap.accessory_driver import AccessoryDriver
|
||||
from pyhap.const import HAP_REPR_AID, HAP_REPR_CHARS, HAP_REPR_IID, HAP_REPR_VALUE
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.homekit.const import ATTR_VALUE
|
||||
|
@ -30,6 +33,15 @@ from tests.common import async_mock_service
|
|||
from tests.components.homekit.common import patch_debounce
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def driver():
|
||||
"""Patch AccessoryDriver without zeroconf or HAPServer."""
|
||||
with patch("pyhap.accessory_driver.HAPServer"), patch(
|
||||
"pyhap.accessory_driver.Zeroconf"
|
||||
), patch("pyhap.accessory_driver.AccessoryDriver.persist"):
|
||||
yield AccessoryDriver()
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def cls():
|
||||
"""Patch debounce decorator during import of type_lights."""
|
||||
|
@ -43,15 +55,16 @@ def cls():
|
|||
patcher.stop()
|
||||
|
||||
|
||||
async def test_light_basic(hass, hk_driver, cls, events):
|
||||
async def test_light_basic(hass, hk_driver, cls, events, driver):
|
||||
"""Test light with char state."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
hass.states.async_set(entity_id, STATE_ON, {ATTR_SUPPORTED_FEATURES: 0})
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None)
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
assert acc.aid == 2
|
||||
assert acc.aid == 1
|
||||
assert acc.category == 5 # Lightbulb
|
||||
assert acc.char_on.value == 0
|
||||
|
||||
|
@ -75,25 +88,43 @@ async def test_light_basic(hass, hk_driver, cls, events):
|
|||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
call_turn_off = async_mock_service(hass, DOMAIN, "turn_off")
|
||||
|
||||
char_on_iid = acc.char_on.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1}
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
|
||||
await hass.async_add_job(acc.char_on.client_update_value, 1)
|
||||
await hass.async_block_till_done()
|
||||
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
|
||||
assert events[-1].data[ATTR_VALUE] == "Set state to 1"
|
||||
|
||||
hass.states.async_set(entity_id, STATE_ON)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await hass.async_add_job(acc.char_on.client_update_value, 0)
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 0}
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_off
|
||||
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] is None
|
||||
assert events[-1].data[ATTR_VALUE] == "Set state to 0"
|
||||
|
||||
|
||||
async def test_light_brightness(hass, hk_driver, cls, events):
|
||||
async def test_light_brightness(hass, hk_driver, cls, events, driver):
|
||||
"""Test light with brightness."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
|
@ -103,11 +134,14 @@ async def test_light_brightness(hass, hk_driver, cls, events):
|
|||
{ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS, ATTR_BRIGHTNESS: 255},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None)
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
|
||||
# brightness to 100 when turning on a light on a freshly booted up server.
|
||||
assert acc.char_brightness.value != 0
|
||||
char_on_iid = acc.char_on.to_HAP()[HAP_REPR_IID]
|
||||
char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
await hass.async_add_job(acc.run)
|
||||
await hass.async_block_till_done()
|
||||
|
@ -121,34 +155,88 @@ async def test_light_brightness(hass, hk_driver, cls, events):
|
|||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
call_turn_off = async_mock_service(hass, DOMAIN, "turn_off")
|
||||
|
||||
await hass.async_add_job(acc.char_brightness.client_update_value, 20)
|
||||
await hass.async_add_job(acc.char_on.client_update_value, 1)
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_brightness_iid,
|
||||
HAP_REPR_VALUE: 20,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_on[0]
|
||||
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[0].data[ATTR_BRIGHTNESS_PCT] == 20
|
||||
assert len(events) == 1
|
||||
assert events[-1].data[ATTR_VALUE] == f"brightness at 20{UNIT_PERCENTAGE}"
|
||||
assert (
|
||||
events[-1].data[ATTR_VALUE]
|
||||
== f"Set state to 1, brightness at 20{UNIT_PERCENTAGE}"
|
||||
)
|
||||
|
||||
await hass.async_add_job(acc.char_on.client_update_value, 1)
|
||||
await hass.async_add_job(acc.char_brightness.client_update_value, 40)
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_brightness_iid,
|
||||
HAP_REPR_VALUE: 40,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_on[1]
|
||||
assert call_turn_on[1].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[1].data[ATTR_BRIGHTNESS_PCT] == 40
|
||||
assert len(events) == 2
|
||||
assert events[-1].data[ATTR_VALUE] == f"brightness at 40{UNIT_PERCENTAGE}"
|
||||
assert (
|
||||
events[-1].data[ATTR_VALUE]
|
||||
== f"Set state to 1, brightness at 40{UNIT_PERCENTAGE}"
|
||||
)
|
||||
|
||||
await hass.async_add_job(acc.char_on.client_update_value, 1)
|
||||
await hass.async_add_job(acc.char_brightness.client_update_value, 0)
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_brightness_iid,
|
||||
HAP_REPR_VALUE: 0,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_off
|
||||
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert len(events) == 3
|
||||
assert events[-1].data[ATTR_VALUE] is None
|
||||
assert (
|
||||
events[-1].data[ATTR_VALUE]
|
||||
== f"Set state to 0, brightness at 0{UNIT_PERCENTAGE}"
|
||||
)
|
||||
|
||||
# 0 is a special case for homekit, see "Handle Brightness"
|
||||
# in update_state
|
||||
hass.states.async_set(entity_id, STATE_ON, {ATTR_BRIGHTNESS: 0})
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_brightness.value == 1
|
||||
hass.states.async_set(entity_id, STATE_ON, {ATTR_BRIGHTNESS: 255})
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_brightness.value == 100
|
||||
hass.states.async_set(entity_id, STATE_ON, {ATTR_BRIGHTNESS: 0})
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_brightness.value == 1
|
||||
|
||||
|
||||
async def test_light_color_temperature(hass, hk_driver, cls, events):
|
||||
async def test_light_color_temperature(hass, hk_driver, cls, events, driver):
|
||||
"""Test light with color temperature."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
|
@ -158,7 +246,8 @@ async def test_light_color_temperature(hass, hk_driver, cls, events):
|
|||
{ATTR_SUPPORTED_FEATURES: SUPPORT_COLOR_TEMP, ATTR_COLOR_TEMP: 190},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None)
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
assert acc.char_color_temperature.value == 153
|
||||
|
||||
|
@ -169,6 +258,20 @@ async def test_light_color_temperature(hass, hk_driver, cls, events):
|
|||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
||||
char_color_temperature_iid = acc.char_color_temperature.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_color_temperature_iid,
|
||||
HAP_REPR_VALUE: 250,
|
||||
}
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_add_job(acc.char_color_temperature.client_update_value, 250)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_on
|
||||
|
@ -197,7 +300,7 @@ async def test_light_color_temperature_and_rgb_color(hass, hk_driver, cls, event
|
|||
assert not hasattr(acc, "char_color_temperature")
|
||||
|
||||
|
||||
async def test_light_rgb_color(hass, hk_driver, cls, events):
|
||||
async def test_light_rgb_color(hass, hk_driver, cls, events, driver):
|
||||
"""Test light with rgb_color."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
|
@ -207,7 +310,8 @@ async def test_light_rgb_color(hass, hk_driver, cls, events):
|
|||
{ATTR_SUPPORTED_FEATURES: SUPPORT_COLOR, ATTR_HS_COLOR: (260, 90)},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 2, None)
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
assert acc.char_hue.value == 0
|
||||
assert acc.char_saturation.value == 75
|
||||
|
@ -220,8 +324,26 @@ async def test_light_rgb_color(hass, hk_driver, cls, events):
|
|||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
||||
await hass.async_add_job(acc.char_hue.client_update_value, 145)
|
||||
await hass.async_add_job(acc.char_saturation.client_update_value, 75)
|
||||
char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID]
|
||||
char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_hue_iid,
|
||||
HAP_REPR_VALUE: 145,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_saturation_iid,
|
||||
HAP_REPR_VALUE: 75,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_on
|
||||
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
|
@ -230,7 +352,7 @@ async def test_light_rgb_color(hass, hk_driver, cls, events):
|
|||
assert events[-1].data[ATTR_VALUE] == "set color at (145, 75)"
|
||||
|
||||
|
||||
async def test_light_restore(hass, hk_driver, cls, events):
|
||||
async def test_light_restore(hass, hk_driver, cls, events, driver):
|
||||
"""Test setting up an entity from state in the event registry."""
|
||||
hass.state = CoreState.not_running
|
||||
|
||||
|
@ -250,7 +372,9 @@ async def test_light_restore(hass, hk_driver, cls, events):
|
|||
hass.bus.async_fire(EVENT_HOMEASSISTANT_START, {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
acc = cls.light(hass, hk_driver, "Light", "light.simple", 2, None)
|
||||
acc = cls.light(hass, hk_driver, "Light", "light.simple", 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
assert acc.category == 5 # Lightbulb
|
||||
assert acc.chars == []
|
||||
assert acc.char_on.value == 0
|
||||
|
@ -259,3 +383,141 @@ async def test_light_restore(hass, hk_driver, cls, events):
|
|||
assert acc.category == 5 # Lightbulb
|
||||
assert acc.chars == ["Brightness"]
|
||||
assert acc.char_on.value == 0
|
||||
|
||||
|
||||
async def test_light_set_brightness_and_color(hass, hk_driver, cls, events, driver):
|
||||
"""Test light with all chars in one go."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR,
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
|
||||
# brightness to 100 when turning on a light on a freshly booted up server.
|
||||
assert acc.char_brightness.value != 0
|
||||
char_on_iid = acc.char_on.to_HAP()[HAP_REPR_IID]
|
||||
char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID]
|
||||
char_hue_iid = acc.char_hue.to_HAP()[HAP_REPR_IID]
|
||||
char_saturation_iid = acc.char_saturation.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
await hass.async_add_job(acc.run)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
hass.states.async_set(entity_id, STATE_ON, {ATTR_BRIGHTNESS: 102})
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_brightness.value == 40
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_brightness_iid,
|
||||
HAP_REPR_VALUE: 20,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_hue_iid,
|
||||
HAP_REPR_VALUE: 145,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_saturation_iid,
|
||||
HAP_REPR_VALUE: 75,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_on[0]
|
||||
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[0].data[ATTR_BRIGHTNESS_PCT] == 20
|
||||
assert call_turn_on[0].data[ATTR_HS_COLOR] == (145, 75)
|
||||
|
||||
assert len(events) == 1
|
||||
assert (
|
||||
events[-1].data[ATTR_VALUE]
|
||||
== f"Set state to 1, brightness at 20{UNIT_PERCENTAGE}, set color at (145, 75)"
|
||||
)
|
||||
|
||||
|
||||
async def test_light_set_brightness_and_color_temp(
|
||||
hass, hk_driver, cls, events, driver
|
||||
):
|
||||
"""Test light with all chars in one go."""
|
||||
entity_id = "light.demo"
|
||||
|
||||
hass.states.async_set(
|
||||
entity_id,
|
||||
STATE_ON,
|
||||
{
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP,
|
||||
ATTR_BRIGHTNESS: 255,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
acc = cls.light(hass, hk_driver, "Light", entity_id, 1, None)
|
||||
driver.add_accessory(acc)
|
||||
|
||||
# Initial value can be anything but 0. If it is 0, it might cause HomeKit to set the
|
||||
# brightness to 100 when turning on a light on a freshly booted up server.
|
||||
assert acc.char_brightness.value != 0
|
||||
char_on_iid = acc.char_on.to_HAP()[HAP_REPR_IID]
|
||||
char_brightness_iid = acc.char_brightness.to_HAP()[HAP_REPR_IID]
|
||||
char_color_temperature_iid = acc.char_color_temperature.to_HAP()[HAP_REPR_IID]
|
||||
|
||||
await hass.async_add_job(acc.run)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_brightness.value == 100
|
||||
|
||||
hass.states.async_set(entity_id, STATE_ON, {ATTR_BRIGHTNESS: 102})
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_brightness.value == 40
|
||||
|
||||
# Set from HomeKit
|
||||
call_turn_on = async_mock_service(hass, DOMAIN, "turn_on")
|
||||
|
||||
driver.set_characteristics(
|
||||
{
|
||||
HAP_REPR_CHARS: [
|
||||
{HAP_REPR_AID: acc.aid, HAP_REPR_IID: char_on_iid, HAP_REPR_VALUE: 1},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_brightness_iid,
|
||||
HAP_REPR_VALUE: 20,
|
||||
},
|
||||
{
|
||||
HAP_REPR_AID: acc.aid,
|
||||
HAP_REPR_IID: char_color_temperature_iid,
|
||||
HAP_REPR_VALUE: 250,
|
||||
},
|
||||
]
|
||||
},
|
||||
"mock_addr",
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert call_turn_on[0]
|
||||
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||
assert call_turn_on[0].data[ATTR_BRIGHTNESS_PCT] == 20
|
||||
assert call_turn_on[0].data[ATTR_COLOR_TEMP] == 250
|
||||
|
||||
assert len(events) == 1
|
||||
assert (
|
||||
events[-1].data[ATTR_VALUE]
|
||||
== f"Set state to 1, brightness at 20{UNIT_PERCENTAGE}, color temperature at 250"
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue