2018-03-15 01:48:21 +00:00
|
|
|
"""Collection of useful functions for the HomeKit component."""
|
|
|
|
import logging
|
|
|
|
|
|
|
|
import voluptuous as vol
|
|
|
|
|
2018-05-28 14:26:33 +00:00
|
|
|
import homeassistant.components.media_player as media_player
|
2018-03-15 01:48:21 +00:00
|
|
|
from homeassistant.core import split_entity_id
|
|
|
|
from homeassistant.const import (
|
2018-05-28 14:26:33 +00:00
|
|
|
ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, TEMP_CELSIUS)
|
2018-03-15 01:48:21 +00:00
|
|
|
import homeassistant.helpers.config_validation as cv
|
2018-03-27 09:31:18 +00:00
|
|
|
import homeassistant.util.temperature as temp_util
|
2018-05-25 09:37:20 +00:00
|
|
|
from .const import (
|
2018-05-28 14:26:33 +00:00
|
|
|
CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF,
|
|
|
|
FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE)
|
2018-03-15 01:48:21 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
2018-05-28 14:26:33 +00:00
|
|
|
|
|
|
|
BASIC_INFO_SCHEMA = vol.Schema({
|
|
|
|
vol.Optional(CONF_NAME): cv.string,
|
|
|
|
})
|
|
|
|
|
|
|
|
FEATURE_SCHEMA = BASIC_INFO_SCHEMA.extend({
|
|
|
|
vol.Optional(CONF_FEATURE_LIST, default=None): cv.ensure_list,
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
CODE_SCHEMA = BASIC_INFO_SCHEMA.extend({
|
|
|
|
vol.Optional(ATTR_CODE, default=None): vol.Any(None, cv.string),
|
|
|
|
})
|
|
|
|
|
|
|
|
MEDIA_PLAYER_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(CONF_FEATURE): vol.All(
|
|
|
|
cv.string, vol.In((FEATURE_ON_OFF, FEATURE_PLAY_PAUSE,
|
|
|
|
FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE))),
|
|
|
|
})
|
2018-05-25 09:37:20 +00:00
|
|
|
|
2018-03-15 01:48:21 +00:00
|
|
|
|
|
|
|
def validate_entity_config(values):
|
|
|
|
"""Validate config entry for CONF_ENTITY."""
|
|
|
|
entities = {}
|
2018-05-11 12:22:45 +00:00
|
|
|
for entity_id, config in values.items():
|
|
|
|
entity = cv.entity_id(entity_id)
|
2018-05-28 14:26:33 +00:00
|
|
|
domain, _ = split_entity_id(entity)
|
|
|
|
|
2018-03-15 01:48:21 +00:00
|
|
|
if not isinstance(config, dict):
|
2018-05-28 14:26:33 +00:00
|
|
|
raise vol.Invalid('The configuration for {} must be '
|
2018-05-13 10:09:28 +00:00
|
|
|
' a dictionary.'.format(entity))
|
2018-03-15 01:48:21 +00:00
|
|
|
|
2018-05-18 11:52:52 +00:00
|
|
|
if domain in ('alarm_control_panel', 'lock'):
|
2018-05-28 14:26:33 +00:00
|
|
|
config = CODE_SCHEMA(config)
|
|
|
|
|
|
|
|
elif domain == media_player.DOMAIN:
|
|
|
|
config = FEATURE_SCHEMA(config)
|
|
|
|
feature_list = {}
|
|
|
|
for feature in config[CONF_FEATURE_LIST]:
|
|
|
|
params = MEDIA_PLAYER_SCHEMA(feature)
|
|
|
|
key = params.pop(CONF_FEATURE)
|
|
|
|
if key in feature_list:
|
|
|
|
raise vol.Invalid('A feature can be added only once for {}'
|
|
|
|
.format(entity))
|
|
|
|
feature_list[key] = params
|
|
|
|
config[CONF_FEATURE_LIST] = feature_list
|
|
|
|
|
|
|
|
else:
|
|
|
|
config = BASIC_INFO_SCHEMA(config)
|
|
|
|
|
|
|
|
entities[entity] = config
|
2018-03-15 01:48:21 +00:00
|
|
|
return entities
|
|
|
|
|
|
|
|
|
2018-05-28 14:26:33 +00:00
|
|
|
def validate_media_player_features(state, feature_list):
|
|
|
|
"""Validate features for media players."""
|
2018-05-25 09:37:20 +00:00
|
|
|
features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
|
|
|
|
|
|
|
supported_modes = []
|
2018-05-28 14:26:33 +00:00
|
|
|
if features & (media_player.SUPPORT_TURN_ON |
|
|
|
|
media_player.SUPPORT_TURN_OFF):
|
|
|
|
supported_modes.append(FEATURE_ON_OFF)
|
|
|
|
if features & (media_player.SUPPORT_PLAY | media_player.SUPPORT_PAUSE):
|
|
|
|
supported_modes.append(FEATURE_PLAY_PAUSE)
|
|
|
|
if features & (media_player.SUPPORT_PLAY | media_player.SUPPORT_STOP):
|
|
|
|
supported_modes.append(FEATURE_PLAY_STOP)
|
|
|
|
if features & media_player.SUPPORT_VOLUME_MUTE:
|
|
|
|
supported_modes.append(FEATURE_TOGGLE_MUTE)
|
|
|
|
|
|
|
|
error_list = []
|
|
|
|
for feature in feature_list:
|
|
|
|
if feature not in supported_modes:
|
|
|
|
error_list.append(feature)
|
|
|
|
|
|
|
|
if error_list:
|
|
|
|
_LOGGER.error("%s does not support features: %s",
|
|
|
|
state.entity_id, error_list)
|
|
|
|
return False
|
|
|
|
return True
|
2018-05-25 09:37:20 +00:00
|
|
|
|
|
|
|
|
2018-05-18 14:32:57 +00:00
|
|
|
def show_setup_message(hass, pincode):
|
2018-03-15 01:48:21 +00:00
|
|
|
"""Display persistent notification with setup information."""
|
2018-05-18 14:32:57 +00:00
|
|
|
pin = pincode.decode()
|
2018-04-01 15:38:29 +00:00
|
|
|
_LOGGER.info('Pincode: %s', pin)
|
2018-03-15 01:48:21 +00:00
|
|
|
message = 'To setup Home Assistant in the Home App, enter the ' \
|
|
|
|
'following code:\n### {}'.format(pin)
|
|
|
|
hass.components.persistent_notification.create(
|
|
|
|
message, 'HomeKit Setup', HOMEKIT_NOTIFY_ID)
|
|
|
|
|
|
|
|
|
|
|
|
def dismiss_setup_message(hass):
|
|
|
|
"""Dismiss persistent notification and remove QR code."""
|
|
|
|
hass.components.persistent_notification.dismiss(HOMEKIT_NOTIFY_ID)
|
2018-03-27 09:31:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
def convert_to_float(state):
|
|
|
|
"""Return float of state, catch errors."""
|
|
|
|
try:
|
|
|
|
return float(state)
|
|
|
|
except (ValueError, TypeError):
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def temperature_to_homekit(temperature, unit):
|
|
|
|
"""Convert temperature to Celsius for HomeKit."""
|
|
|
|
return round(temp_util.convert(temperature, unit, TEMP_CELSIUS), 1)
|
|
|
|
|
|
|
|
|
|
|
|
def temperature_to_states(temperature, unit):
|
|
|
|
"""Convert temperature back from Celsius to Home Assistant unit."""
|
|
|
|
return round(temp_util.convert(temperature, TEMP_CELSIUS, unit), 1)
|
2018-04-12 13:01:41 +00:00
|
|
|
|
|
|
|
|
|
|
|
def density_to_air_quality(density):
|
|
|
|
"""Map PM2.5 density to HomeKit AirQuality level."""
|
|
|
|
if density <= 35:
|
|
|
|
return 1
|
|
|
|
elif density <= 75:
|
|
|
|
return 2
|
|
|
|
elif density <= 115:
|
|
|
|
return 3
|
|
|
|
elif density <= 150:
|
|
|
|
return 4
|
|
|
|
return 5
|