mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 04:37:06 +00:00
Homekit: New supported devices (#13244)
* Fixed log message * Added support for scripts * Added support for lights * Small refactoring * Added support for humidity sensor * Added tests
This commit is contained in:
parent
de1ff1e952
commit
2350ce96a6
@ -73,7 +73,8 @@ async def async_setup(hass, config):
|
||||
|
||||
def get_accessory(hass, state, aid, config):
|
||||
"""Take state and return an accessory object if supported."""
|
||||
_LOGGER.debug('%s: <aid=%d config=%s>')
|
||||
_LOGGER.debug('<entity_id=%s aid=%d config=%s>',
|
||||
state.entity_id, aid, config)
|
||||
if not aid:
|
||||
_LOGGER.warning('The entitiy "%s" is not supported, since it '
|
||||
'generates an invalid aid, please change it.',
|
||||
@ -87,6 +88,11 @@ def get_accessory(hass, state, aid, config):
|
||||
state.entity_id, 'TemperatureSensor')
|
||||
return TYPES['TemperatureSensor'](hass, state.entity_id,
|
||||
state.name, aid=aid)
|
||||
elif unit == '%':
|
||||
_LOGGER.debug('Add "%s" as %s"',
|
||||
state.entity_id, 'HumiditySensor')
|
||||
return TYPES['HumiditySensor'](hass, state.entity_id, state.name,
|
||||
aid=aid)
|
||||
|
||||
elif state.domain == 'cover':
|
||||
# Only add covers that support set_cover_position
|
||||
@ -114,8 +120,11 @@ def get_accessory(hass, state, aid, config):
|
||||
return TYPES['Thermostat'](hass, state.entity_id,
|
||||
state.name, support_auto, aid=aid)
|
||||
|
||||
elif state.domain == 'light':
|
||||
return TYPES['Light'](hass, state.entity_id, state.name, aid=aid)
|
||||
|
||||
elif state.domain == 'switch' or state.domain == 'remote' \
|
||||
or state.domain == 'input_boolean':
|
||||
or state.domain == 'input_boolean' or state.domain == 'script':
|
||||
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'Switch')
|
||||
return TYPES['Switch'](hass, state.entity_id, state.name, aid=aid)
|
||||
|
||||
@ -175,7 +184,7 @@ class HomeKit():
|
||||
|
||||
# pylint: disable=unused-variable
|
||||
from . import ( # noqa F401
|
||||
type_covers, type_security_systems, type_sensors,
|
||||
type_covers, type_lights, type_security_systems, type_sensors,
|
||||
type_switches, type_thermostats)
|
||||
|
||||
for state in self._hass.states.all():
|
||||
|
@ -4,6 +4,8 @@ import logging
|
||||
from pyhap.accessory import Accessory, Bridge, Category
|
||||
from pyhap.accessory_driver import AccessoryDriver
|
||||
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
from .const import (
|
||||
ACCESSORY_MODEL, ACCESSORY_NAME, BRIDGE_MODEL, BRIDGE_NAME,
|
||||
MANUFACTURER, SERV_ACCESSORY_INFO, SERV_BRIDGING_STATE,
|
||||
@ -49,6 +51,8 @@ def override_properties(char, properties=None, valid_values=None):
|
||||
class HomeAccessory(Accessory):
|
||||
"""Adapter class for Accessory."""
|
||||
|
||||
# pylint: disable=no-member
|
||||
|
||||
def __init__(self, name=ACCESSORY_NAME, model=ACCESSORY_MODEL,
|
||||
category='OTHER', **kwargs):
|
||||
"""Initialize a Accessory object."""
|
||||
@ -59,6 +63,13 @@ class HomeAccessory(Accessory):
|
||||
def _set_services(self):
|
||||
add_preload_service(self, SERV_ACCESSORY_INFO)
|
||||
|
||||
def run(self):
|
||||
"""Method called by accessory after driver is started."""
|
||||
state = self._hass.states.get(self._entity_id)
|
||||
self.update_state(new_state=state)
|
||||
async_track_state_change(
|
||||
self._hass, self._entity_id, self.update_state)
|
||||
|
||||
|
||||
class HomeBridge(Bridge):
|
||||
"""Adapter class for Bridge."""
|
||||
|
@ -23,10 +23,18 @@ BRIDGE_MODEL = 'homekit.bridge'
|
||||
BRIDGE_NAME = 'Home Assistant'
|
||||
MANUFACTURER = 'HomeAssistant'
|
||||
|
||||
# #### Categories ####
|
||||
CATEGORY_LIGHT = 'LIGHTBULB'
|
||||
CATEGORY_SENSOR = 'SENSOR'
|
||||
|
||||
|
||||
# #### Services ####
|
||||
SERV_ACCESSORY_INFO = 'AccessoryInformation'
|
||||
SERV_BRIDGING_STATE = 'BridgingState'
|
||||
SERV_HUMIDITY_SENSOR = 'HumiditySensor'
|
||||
# CurrentRelativeHumidity | StatusActive, StatusFault, StatusTampered,
|
||||
# StatusLowBattery, Name
|
||||
SERV_LIGHTBULB = 'Lightbulb' # On | Brightness, Hue, Saturation, Name
|
||||
SERV_SECURITY_SYSTEM = 'SecuritySystem'
|
||||
SERV_SWITCH = 'Switch'
|
||||
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
|
||||
@ -36,20 +44,24 @@ SERV_WINDOW_COVERING = 'WindowCovering'
|
||||
|
||||
# #### Characteristics ####
|
||||
CHAR_ACC_IDENTIFIER = 'AccessoryIdentifier'
|
||||
CHAR_BRIGHTNESS = 'Brightness' # Int | [0, 100]
|
||||
CHAR_CATEGORY = 'Category'
|
||||
CHAR_COOLING_THRESHOLD_TEMPERATURE = 'CoolingThresholdTemperature'
|
||||
CHAR_CURRENT_HEATING_COOLING = 'CurrentHeatingCoolingState'
|
||||
CHAR_CURRENT_POSITION = 'CurrentPosition'
|
||||
CHAR_CURRENT_HUMIDITY = 'CurrentRelativeHumidity' # percent
|
||||
CHAR_CURRENT_SECURITY_STATE = 'SecuritySystemCurrentState'
|
||||
CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature'
|
||||
CHAR_HEATING_THRESHOLD_TEMPERATURE = 'HeatingThresholdTemperature'
|
||||
CHAR_HUE = 'Hue' # arcdegress | [0, 360]
|
||||
CHAR_LINK_QUALITY = 'LinkQuality'
|
||||
CHAR_MANUFACTURER = 'Manufacturer'
|
||||
CHAR_MODEL = 'Model'
|
||||
CHAR_NAME = 'Name'
|
||||
CHAR_ON = 'On'
|
||||
CHAR_ON = 'On' # boolean
|
||||
CHAR_POSITION_STATE = 'PositionState'
|
||||
CHAR_REACHABLE = 'Reachable'
|
||||
CHAR_SATURATION = 'Saturation' # percent
|
||||
CHAR_SERIAL_NUMBER = 'SerialNumber'
|
||||
CHAR_TARGET_HEATING_COOLING = 'TargetHeatingCoolingState'
|
||||
CHAR_TARGET_POSITION = 'TargetPosition'
|
||||
|
@ -2,7 +2,6 @@
|
||||
import logging
|
||||
|
||||
from homeassistant.components.cover import ATTR_CURRENT_POSITION
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, add_preload_service
|
||||
@ -22,7 +21,7 @@ class WindowCovering(HomeAccessory):
|
||||
"""
|
||||
|
||||
def __init__(self, hass, entity_id, display_name, *args, **kwargs):
|
||||
"""Initialize a Window accessory object."""
|
||||
"""Initialize a WindowCovering accessory object."""
|
||||
super().__init__(display_name, entity_id, 'WINDOW_COVERING',
|
||||
*args, **kwargs)
|
||||
|
||||
@ -45,14 +44,6 @@ class WindowCovering(HomeAccessory):
|
||||
|
||||
self.char_target_position.setter_callback = self.move_cover
|
||||
|
||||
def run(self):
|
||||
"""Method called be object after driver is started."""
|
||||
state = self._hass.states.get(self._entity_id)
|
||||
self.update_cover_position(new_state=state)
|
||||
|
||||
async_track_state_change(
|
||||
self._hass, self._entity_id, self.update_cover_position)
|
||||
|
||||
def move_cover(self, value):
|
||||
"""Move cover to value if call came from HomeKit."""
|
||||
if value != self.current_position:
|
||||
@ -65,8 +56,7 @@ class WindowCovering(HomeAccessory):
|
||||
self._hass.components.cover.set_cover_position(
|
||||
value, self._entity_id)
|
||||
|
||||
def update_cover_position(self, entity_id=None, old_state=None,
|
||||
new_state=None):
|
||||
def update_state(self, entity_id=None, old_state=None, new_state=None):
|
||||
"""Update cover position after state changed."""
|
||||
if new_state is None:
|
||||
return
|
||||
|
209
homeassistant/components/homekit/type_lights.py
Normal file
209
homeassistant/components/homekit/type_lights.py
Normal file
@ -0,0 +1,209 @@
|
||||
"""Class to hold all light accessories."""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_RGB_COLOR, ATTR_BRIGHTNESS,
|
||||
SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR)
|
||||
from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_ON, STATE_OFF
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, add_preload_service
|
||||
from .const import (
|
||||
CATEGORY_LIGHT, SERV_LIGHTBULB,
|
||||
CHAR_BRIGHTNESS, CHAR_HUE, CHAR_ON, CHAR_SATURATION)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
RGB_COLOR = 'rgb_color'
|
||||
|
||||
|
||||
class Color:
|
||||
"""Class to handle color conversions."""
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
def __init__(self, hue=None, saturation=None):
|
||||
"""Initialize a new Color object."""
|
||||
self.hue = hue # [0, 360]
|
||||
self.saturation = saturation # [0, 1]
|
||||
|
||||
def calc_hsv_to_rgb(self):
|
||||
"""Convert hsv_color value to rgb_color."""
|
||||
if not self.hue or not self.saturation:
|
||||
return [None] * 3
|
||||
|
||||
i = int(self.hue / 60)
|
||||
f = self.hue / 60 - i
|
||||
v = 1
|
||||
p = 1 - self.saturation
|
||||
q = 1 - self.saturation * f
|
||||
t = 1 - self.saturation * (1 - f)
|
||||
|
||||
rgb = []
|
||||
if i in [0, 6]:
|
||||
rgb = [v, t, p]
|
||||
elif i == 1:
|
||||
rgb = [q, v, p]
|
||||
elif i == 2:
|
||||
rgb = [p, v, t]
|
||||
elif i == 3:
|
||||
rgb = [p, q, v]
|
||||
elif i == 4:
|
||||
rgb = [t, p, v]
|
||||
elif i == 5:
|
||||
rgb = [v, p, q]
|
||||
|
||||
return [round(c * 255) for c in rgb]
|
||||
|
||||
@classmethod
|
||||
def calc_rgb_to_hsv(cls, rgb_color):
|
||||
"""Convert a give rgb_color back to a hsv_color."""
|
||||
rgb_color = [c / 255 for c in rgb_color]
|
||||
c_max = max(rgb_color)
|
||||
c_min = min(rgb_color)
|
||||
c_diff = c_max - c_min
|
||||
r, g, b = rgb_color
|
||||
|
||||
hue, saturation = 0, 0
|
||||
if c_max == r:
|
||||
hue = 60 * (0 + (g - b) / c_diff)
|
||||
elif c_max == g:
|
||||
hue = 60 * (2 + (b - r) / c_diff)
|
||||
elif c_max == b:
|
||||
hue = 60 * (4 + (r - g) / c_diff)
|
||||
|
||||
hue = round(hue + 360) if hue < 0 else round(hue)
|
||||
|
||||
if c_max != 0:
|
||||
saturation = round((c_max - c_min) / c_max * 100)
|
||||
|
||||
return (hue, saturation)
|
||||
|
||||
|
||||
@TYPES.register('Light')
|
||||
class Light(HomeAccessory):
|
||||
"""Generate a Light accessory for a light entity.
|
||||
|
||||
Currently supports: state, brightness, rgb_color.
|
||||
"""
|
||||
|
||||
def __init__(self, hass, entity_id, name, *args, **kwargs):
|
||||
"""Initialize a new Light accessory object."""
|
||||
super().__init__(name, entity_id, CATEGORY_LIGHT, *args, **kwargs)
|
||||
|
||||
self._hass = hass
|
||||
self._entity_id = entity_id
|
||||
self._flag = {CHAR_ON: False, CHAR_BRIGHTNESS: False,
|
||||
CHAR_HUE: False, CHAR_SATURATION: False,
|
||||
RGB_COLOR: False}
|
||||
|
||||
self.color = Color()
|
||||
|
||||
self.chars = []
|
||||
self._features = self._hass.states.get(self._entity_id) \
|
||||
.attributes.get(ATTR_SUPPORTED_FEATURES)
|
||||
if self._features & SUPPORT_BRIGHTNESS:
|
||||
self.chars.append(CHAR_BRIGHTNESS)
|
||||
if self._features & SUPPORT_RGB_COLOR:
|
||||
self.chars.append(CHAR_HUE)
|
||||
self.chars.append(CHAR_SATURATION)
|
||||
|
||||
serv_light = add_preload_service(self, SERV_LIGHTBULB, self.chars)
|
||||
self.char_on = serv_light.get_characteristic(CHAR_ON)
|
||||
self.char_on.setter_callback = self.set_state
|
||||
self.char_on.value = 0
|
||||
|
||||
if CHAR_BRIGHTNESS in self.chars:
|
||||
self.char_brightness = serv_light \
|
||||
.get_characteristic(CHAR_BRIGHTNESS)
|
||||
self.char_brightness.setter_callback = self.set_brightness
|
||||
self.char_brightness.value = 0
|
||||
if CHAR_HUE in self.chars:
|
||||
self.char_hue = serv_light.get_characteristic(CHAR_HUE)
|
||||
self.char_hue.setter_callback = self.set_hue
|
||||
self.char_hue.value = 0
|
||||
if CHAR_SATURATION in self.chars:
|
||||
self.char_saturation = serv_light \
|
||||
.get_characteristic(CHAR_SATURATION)
|
||||
self.char_saturation.setter_callback = self.set_saturation
|
||||
self.char_saturation.value = 75
|
||||
|
||||
def set_state(self, value):
|
||||
"""Set state if call came from HomeKit."""
|
||||
if self._flag[CHAR_BRIGHTNESS]:
|
||||
return
|
||||
|
||||
_LOGGER.debug('%s: Set state to %d', self._entity_id, value)
|
||||
self._flag[CHAR_ON] = True
|
||||
|
||||
if value == 1:
|
||||
self._hass.components.light.turn_on(self._entity_id)
|
||||
elif value == 0:
|
||||
self._hass.components.light.turn_off(self._entity_id)
|
||||
|
||||
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
|
||||
self._hass.components.light.turn_on(
|
||||
self._entity_id, brightness_pct=value)
|
||||
|
||||
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.color.saturation = value / 100
|
||||
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.color.hue = value
|
||||
self.set_color()
|
||||
|
||||
def set_color(self):
|
||||
"""Set color if call came from HomeKit."""
|
||||
# Handle RGB Color
|
||||
if self._features & SUPPORT_RGB_COLOR and self._flag[CHAR_HUE] and \
|
||||
self._flag[CHAR_SATURATION]:
|
||||
color = self.color.calc_hsv_to_rgb()
|
||||
_LOGGER.debug('%s: Set rgb_color to %s', self._entity_id, color)
|
||||
self._flag.update({
|
||||
CHAR_HUE: False, CHAR_SATURATION: False, RGB_COLOR: True})
|
||||
self._hass.components.light.turn_on(
|
||||
self._entity_id, rgb_color=color)
|
||||
|
||||
def update_state(self, entity_id=None, old_state=None, new_state=None):
|
||||
"""Update light after state change."""
|
||||
if not new_state:
|
||||
return
|
||||
|
||||
# Handle State
|
||||
state = new_state.state
|
||||
if not self._flag[CHAR_ON] and state in [STATE_ON, STATE_OFF] and \
|
||||
self.char_on.value != (state == STATE_ON):
|
||||
self.char_on.set_value(state == STATE_ON, should_callback=False)
|
||||
self._flag[CHAR_ON] = False
|
||||
|
||||
# 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):
|
||||
brightness = round(brightness / 255 * 100, 0)
|
||||
if self.char_brightness.value != brightness:
|
||||
self.char_brightness.set_value(brightness,
|
||||
should_callback=False)
|
||||
self._flag[CHAR_BRIGHTNESS] = False
|
||||
|
||||
# Handle RGB Color
|
||||
if CHAR_SATURATION in self.chars and CHAR_HUE in self.chars:
|
||||
rgb_color = new_state.attributes.get(ATTR_RGB_COLOR)
|
||||
if not self._flag[RGB_COLOR] and \
|
||||
isinstance(rgb_color, (list, tuple)) and \
|
||||
list(rgb_color) != self.color.calc_hsv_to_rgb():
|
||||
hue, saturation = Color.calc_rgb_to_hsv(rgb_color)
|
||||
self.char_hue.set_value(hue, should_callback=False)
|
||||
self.char_saturation.set_value(saturation,
|
||||
should_callback=False)
|
||||
self._flag[RGB_COLOR] = False
|
@ -5,7 +5,6 @@ from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_DISARMED,
|
||||
ATTR_ENTITY_ID, ATTR_CODE)
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, add_preload_service
|
||||
@ -50,14 +49,6 @@ class SecuritySystem(HomeAccessory):
|
||||
|
||||
self.char_target_state.setter_callback = self.set_security_state
|
||||
|
||||
def run(self):
|
||||
"""Method called be object after driver is started."""
|
||||
state = self._hass.states.get(self._entity_id)
|
||||
self.update_security_state(new_state=state)
|
||||
|
||||
async_track_state_change(self._hass, self._entity_id,
|
||||
self.update_security_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',
|
||||
@ -69,8 +60,7 @@ class SecuritySystem(HomeAccessory):
|
||||
params = {ATTR_ENTITY_ID: self._entity_id, ATTR_CODE: self._alarm_code}
|
||||
self._hass.services.call('alarm_control_panel', service, params)
|
||||
|
||||
def update_security_state(self, entity_id=None,
|
||||
old_state=None, new_state=None):
|
||||
def update_state(self, entity_id=None, old_state=None, new_state=None):
|
||||
"""Update security state after state changed."""
|
||||
if new_state is None:
|
||||
return
|
||||
|
@ -3,13 +3,13 @@ import logging
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_FAHRENHEIT, TEMP_CELSIUS)
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import (
|
||||
HomeAccessory, add_preload_service, override_properties)
|
||||
from .const import (
|
||||
SERV_TEMPERATURE_SENSOR, CHAR_CURRENT_TEMPERATURE, PROP_CELSIUS)
|
||||
CATEGORY_SENSOR, SERV_HUMIDITY_SENSOR, SERV_TEMPERATURE_SENSOR,
|
||||
CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, PROP_CELSIUS)
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -29,6 +29,14 @@ def calc_temperature(state, unit=TEMP_CELSIUS):
|
||||
return round((value - 32) / 1.8, 2) if unit == TEMP_FAHRENHEIT else value
|
||||
|
||||
|
||||
def calc_humidity(state):
|
||||
"""Calculate humidity from state."""
|
||||
try:
|
||||
return float(state)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
@TYPES.register('TemperatureSensor')
|
||||
class TemperatureSensor(HomeAccessory):
|
||||
"""Generate a TemperatureSensor accessory for a temperature sensor.
|
||||
@ -36,9 +44,9 @@ class TemperatureSensor(HomeAccessory):
|
||||
Sensor entity must return temperature in °C, °F.
|
||||
"""
|
||||
|
||||
def __init__(self, hass, entity_id, display_name, *args, **kwargs):
|
||||
def __init__(self, hass, entity_id, name, *args, **kwargs):
|
||||
"""Initialize a TemperatureSensor accessory object."""
|
||||
super().__init__(display_name, entity_id, 'SENSOR', *args, **kwargs)
|
||||
super().__init__(name, entity_id, CATEGORY_SENSOR, *args, **kwargs)
|
||||
|
||||
self._hass = hass
|
||||
self._entity_id = entity_id
|
||||
@ -49,23 +57,42 @@ class TemperatureSensor(HomeAccessory):
|
||||
self.char_temp.value = 0
|
||||
self.unit = None
|
||||
|
||||
def run(self):
|
||||
"""Method called be object after driver is started."""
|
||||
state = self._hass.states.get(self._entity_id)
|
||||
self.update_temperature(new_state=state)
|
||||
|
||||
async_track_state_change(
|
||||
self._hass, self._entity_id, self.update_temperature)
|
||||
|
||||
def update_temperature(self, entity_id=None, old_state=None,
|
||||
new_state=None):
|
||||
def update_state(self, entity_id=None, old_state=None, new_state=None):
|
||||
"""Update temperature after state changed."""
|
||||
if new_state is None:
|
||||
return
|
||||
|
||||
unit = new_state.attributes[ATTR_UNIT_OF_MEASUREMENT]
|
||||
temperature = calc_temperature(new_state.state, unit)
|
||||
if temperature is not None:
|
||||
self.char_temp.set_value(temperature)
|
||||
if temperature:
|
||||
self.char_temp.set_value(temperature, should_callback=False)
|
||||
_LOGGER.debug('%s: Current temperature set to %d°C',
|
||||
self._entity_id, temperature)
|
||||
|
||||
|
||||
@TYPES.register('HumiditySensor')
|
||||
class HumiditySensor(HomeAccessory):
|
||||
"""Generate a HumiditySensor accessory as humidity sensor."""
|
||||
|
||||
def __init__(self, hass, entity_id, name, *args, **kwargs):
|
||||
"""Initialize a HumiditySensor accessory object."""
|
||||
super().__init__(name, entity_id, CATEGORY_SENSOR, *args, **kwargs)
|
||||
|
||||
self._hass = hass
|
||||
self._entity_id = entity_id
|
||||
|
||||
serv_humidity = add_preload_service(self, SERV_HUMIDITY_SENSOR)
|
||||
self.char_humidity = serv_humidity \
|
||||
.get_characteristic(CHAR_CURRENT_HUMIDITY)
|
||||
self.char_humidity.value = 0
|
||||
|
||||
def update_state(self, entity_id=None, old_state=None, new_state=None):
|
||||
"""Update accessory after state change."""
|
||||
if new_state is None:
|
||||
return
|
||||
|
||||
humidity = calc_humidity(new_state.state)
|
||||
if humidity:
|
||||
self.char_humidity.set_value(humidity, should_callback=False)
|
||||
_LOGGER.debug('%s: Current humidity set to %d%%',
|
||||
self._entity_id, humidity)
|
||||
|
@ -4,7 +4,6 @@ import logging
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
|
||||
from homeassistant.core import split_entity_id
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, add_preload_service
|
||||
@ -32,14 +31,6 @@ class Switch(HomeAccessory):
|
||||
self.char_on.value = False
|
||||
self.char_on.setter_callback = self.set_state
|
||||
|
||||
def run(self):
|
||||
"""Method called be object after driver is started."""
|
||||
state = self._hass.states.get(self._entity_id)
|
||||
self.update_state(new_state=state)
|
||||
|
||||
async_track_state_change(self._hass, self._entity_id,
|
||||
self.update_state)
|
||||
|
||||
def set_state(self, value):
|
||||
"""Move switch state to value if call came from HomeKit."""
|
||||
_LOGGER.debug('%s: Set switch state to %s',
|
||||
|
@ -8,7 +8,6 @@ from homeassistant.components.climate import (
|
||||
STATE_HEAT, STATE_COOL, STATE_AUTO)
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
from homeassistant.helpers.event import async_track_state_change
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import HomeAccessory, add_preload_service
|
||||
@ -96,14 +95,6 @@ class Thermostat(HomeAccessory):
|
||||
self.char_cooling_thresh_temp = None
|
||||
self.char_heating_thresh_temp = None
|
||||
|
||||
def run(self):
|
||||
"""Method called be object after driver is started."""
|
||||
state = self._hass.states.get(self._entity_id)
|
||||
self.update_thermostat(new_state=state)
|
||||
|
||||
async_track_state_change(self._hass, self._entity_id,
|
||||
self.update_thermostat)
|
||||
|
||||
def set_heat_cool(self, value):
|
||||
"""Move operation mode to value if call came from HomeKit."""
|
||||
if value in HC_HOMEKIT_TO_HASS:
|
||||
@ -142,8 +133,7 @@ class Thermostat(HomeAccessory):
|
||||
self._hass.components.climate.set_temperature(
|
||||
temperature=value, entity_id=self._entity_id)
|
||||
|
||||
def update_thermostat(self, entity_id=None,
|
||||
old_state=None, new_state=None):
|
||||
def update_state(self, entity_id=None, old_state=None, new_state=None):
|
||||
"""Update security state after state changed."""
|
||||
if new_state is None:
|
||||
return
|
||||
|
@ -53,6 +53,13 @@ class TestGetAccessories(unittest.TestCase):
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT})
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_sensor_humidity(self):
|
||||
"""Test humidity sensor with % as unit."""
|
||||
with patch.dict(TYPES, {'HumiditySensor': self.mock_type}):
|
||||
state = State('sensor.humidity', '20',
|
||||
{ATTR_UNIT_OF_MEASUREMENT: '%'})
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_cover_set_position(self):
|
||||
"""Test cover with support for set_cover_position."""
|
||||
with patch.dict(TYPES, {'WindowCovering': self.mock_type}):
|
||||
@ -81,6 +88,12 @@ class TestGetAccessories(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
self.mock_type.call_args[0][-1], False) # support_auto
|
||||
|
||||
def test_light(self):
|
||||
"""Test light devices."""
|
||||
with patch.dict(TYPES, {'Light': self.mock_type}):
|
||||
state = State('light.test', 'on')
|
||||
get_accessory(None, state, 2, {})
|
||||
|
||||
def test_climate_support_auto(self):
|
||||
"""Test climate devices with support for auto mode."""
|
||||
with patch.dict(TYPES, {'Thermostat': self.mock_type}):
|
||||
|
160
tests/components/homekit/test_type_lights.py
Normal file
160
tests/components/homekit/test_type_lights.py
Normal file
@ -0,0 +1,160 @@
|
||||
"""Test different accessory types: Lights."""
|
||||
import unittest
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.homekit.type_lights import Light, Color
|
||||
from homeassistant.components.light import (
|
||||
DOMAIN, ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_RGB_COLOR,
|
||||
SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR)
|
||||
from homeassistant.const import (
|
||||
ATTR_DOMAIN, ATTR_ENTITY_ID, ATTR_SERVICE, ATTR_SERVICE_DATA,
|
||||
ATTR_SUPPORTED_FEATURES, EVENT_CALL_SERVICE, SERVICE_TURN_ON,
|
||||
SERVICE_TURN_OFF, STATE_ON, STATE_OFF, STATE_UNKNOWN)
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
def test_calc_hsv_to_rgb():
|
||||
"""Test conversion hsv to rgb."""
|
||||
color = Color(43, 23 / 100)
|
||||
assert color.calc_hsv_to_rgb() == [255, 238, 196]
|
||||
|
||||
color.hue, color.saturation = (79, 12 / 100)
|
||||
assert color.calc_hsv_to_rgb() == [245, 255, 224]
|
||||
|
||||
color.hue, color.saturation = (177, 2 / 100)
|
||||
assert color.calc_hsv_to_rgb() == [250, 255, 255]
|
||||
|
||||
color.hue, color.saturation = (212, 26 / 100)
|
||||
assert color.calc_hsv_to_rgb() == [189, 220, 255]
|
||||
|
||||
color.hue, color.saturation = (271, 93 / 100)
|
||||
assert color.calc_hsv_to_rgb() == [140, 18, 255]
|
||||
|
||||
color.hue, color.saturation = (355, 100 / 100)
|
||||
assert color.calc_hsv_to_rgb() == [255, 0, 21]
|
||||
|
||||
|
||||
def test_calc_rgb_to_hsv():
|
||||
"""Test conversion rgb to hsv."""
|
||||
assert Color.calc_rgb_to_hsv([255, 0, 21]) == (355, 100)
|
||||
assert Color.calc_rgb_to_hsv([245, 255, 224]) == (79, 12)
|
||||
assert Color.calc_rgb_to_hsv([189, 220, 255]) == (212, 26)
|
||||
|
||||
|
||||
class TestHomekitLights(unittest.TestCase):
|
||||
"""Test class for all accessory types regarding lights."""
|
||||
|
||||
def setUp(self):
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.events = []
|
||||
|
||||
@callback
|
||||
def record_event(event):
|
||||
"""Track called event."""
|
||||
self.events.append(event)
|
||||
|
||||
self.hass.bus.listen(EVENT_CALL_SERVICE, record_event)
|
||||
|
||||
def tearDown(self):
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_light_basic(self):
|
||||
"""Test light with char state."""
|
||||
entity_id = 'light.demo'
|
||||
self.hass.states.set(entity_id, STATE_ON,
|
||||
{ATTR_SUPPORTED_FEATURES: 0})
|
||||
acc = Light(self.hass, entity_id, 'Light', aid=2)
|
||||
self.assertEqual(acc.aid, 2)
|
||||
self.assertEqual(acc.category, 5) # Lightbulb
|
||||
self.assertEqual(acc.char_on.value, 0)
|
||||
|
||||
acc.run()
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_on.value, 1)
|
||||
|
||||
self.hass.states.set(entity_id, STATE_OFF,
|
||||
{ATTR_SUPPORTED_FEATURES: 0})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_on.value, 0)
|
||||
|
||||
self.hass.states.set(entity_id, STATE_UNKNOWN)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_on.value, 0)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_on.set_value(True)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_DOMAIN], DOMAIN)
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE], SERVICE_TURN_ON)
|
||||
|
||||
acc.char_on.set_value(False)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[1].data[ATTR_DOMAIN], DOMAIN)
|
||||
self.assertEqual(
|
||||
self.events[1].data[ATTR_SERVICE], SERVICE_TURN_OFF)
|
||||
|
||||
# Remove entity
|
||||
self.hass.states.remove(entity_id)
|
||||
self.hass.block_till_done()
|
||||
|
||||
def test_light_brightness(self):
|
||||
"""Test light with brightness."""
|
||||
entity_id = 'light.demo'
|
||||
self.hass.states.set(entity_id, STATE_ON, {
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_BRIGHTNESS, ATTR_BRIGHTNESS: 255})
|
||||
acc = Light(self.hass, entity_id, 'Light', aid=2)
|
||||
self.assertEqual(acc.char_brightness.value, 0)
|
||||
|
||||
acc.run()
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_brightness.value, 100)
|
||||
|
||||
self.hass.states.set(entity_id, STATE_ON, {ATTR_BRIGHTNESS: 102})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_brightness.value, 40)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_brightness.set_value(20)
|
||||
acc.char_on.set_value(1)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_DOMAIN], DOMAIN)
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE], SERVICE_TURN_ON)
|
||||
print(self.events[0].data)
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE_DATA], {
|
||||
ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS_PCT: 20})
|
||||
|
||||
def test_light_rgb_color(self):
|
||||
"""Test light with rgb_color."""
|
||||
entity_id = 'light.demo'
|
||||
self.hass.states.set(entity_id, STATE_ON, {
|
||||
ATTR_SUPPORTED_FEATURES: SUPPORT_RGB_COLOR,
|
||||
ATTR_RGB_COLOR: (120, 20, 300)})
|
||||
acc = Light(self.hass, entity_id, 'Light', aid=2)
|
||||
self.assertEqual(acc.char_hue.value, 0)
|
||||
self.assertEqual(acc.char_saturation.value, 75)
|
||||
|
||||
acc.run()
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_hue.value, 261)
|
||||
self.assertEqual(acc.char_saturation.value, 93)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_hue.set_value(145)
|
||||
acc.char_saturation.set_value(75)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_DOMAIN], DOMAIN)
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE], SERVICE_TURN_ON)
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE_DATA], {
|
||||
ATTR_ENTITY_ID: entity_id, ATTR_RGB_COLOR: [64, 255, 143]})
|
@ -3,7 +3,7 @@ import unittest
|
||||
|
||||
from homeassistant.components.homekit.const import PROP_CELSIUS
|
||||
from homeassistant.components.homekit.type_sensors import (
|
||||
TemperatureSensor, calc_temperature)
|
||||
TemperatureSensor, HumiditySensor, calc_temperature, calc_humidity)
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
@ -22,6 +22,15 @@ def test_calc_temperature():
|
||||
assert calc_temperature('-20.6', TEMP_FAHRENHEIT) == -29.22
|
||||
|
||||
|
||||
def test_calc_humidity():
|
||||
"""Test if humidity is a integer."""
|
||||
assert calc_humidity(STATE_UNKNOWN) is None
|
||||
assert calc_humidity('test') is None
|
||||
|
||||
assert calc_humidity('20') == 20
|
||||
assert calc_humidity('75.2') == 75.2
|
||||
|
||||
|
||||
class TestHomekitSensors(unittest.TestCase):
|
||||
"""Test class for all accessory types regarding sensors."""
|
||||
|
||||
@ -60,3 +69,23 @@ class TestHomekitSensors(unittest.TestCase):
|
||||
{ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_temp.value, 24)
|
||||
|
||||
def test_humidity(self):
|
||||
"""Test if accessory is updated after state change."""
|
||||
entity_id = 'sensor.humidity'
|
||||
|
||||
acc = HumiditySensor(self.hass, entity_id, 'Humidity', aid=2)
|
||||
acc.run()
|
||||
|
||||
self.assertEqual(acc.aid, 2)
|
||||
self.assertEqual(acc.category, 10) # Sensor
|
||||
|
||||
self.assertEqual(acc.char_humidity.value, 0)
|
||||
|
||||
self.hass.states.set(entity_id, STATE_UNKNOWN,
|
||||
{ATTR_UNIT_OF_MEASUREMENT: "%"})
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.hass.states.set(entity_id, '20', {ATTR_UNIT_OF_MEASUREMENT: "%"})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_humidity.value, 20)
|
||||
|
Loading…
x
Reference in New Issue
Block a user