forked from home-assistant/core
Compare commits
30 Commits
0.66.0.bet
...
0.66.0b3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f26aff4885 | ||
|
|
32b0712089 | ||
|
|
867010240a | ||
|
|
0428559f69 | ||
|
|
dfd15900c7 | ||
|
|
e993d095cb | ||
|
|
26fb3d7faa | ||
|
|
b0073b437f | ||
|
|
e04b01daad | ||
|
|
020669fc60 | ||
|
|
d897a07d0b | ||
|
|
27865f58f1 | ||
|
|
3639a4470c | ||
|
|
94d9aa0c5f | ||
|
|
a06eea444a | ||
|
|
ce3a5972c7 | ||
|
|
f48ce3d437 | ||
|
|
dfe3219f3f | ||
|
|
a507ed0af8 | ||
|
|
068b037944 | ||
|
|
38d2702e3c | ||
|
|
93b9ec0b0f | ||
|
|
22cefc7e64 | ||
|
|
60f6109cbf | ||
|
|
24d299e266 | ||
|
|
444805df10 | ||
|
|
a08293cff7 | ||
|
|
0d48a8eec6 | ||
|
|
8a204fd15b | ||
|
|
8e14e803cb |
@@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
SENSOR_TYPES = {'openClosedSensor': 'opening',
|
||||
'motionSensor': 'motion',
|
||||
'doorSensor': 'door',
|
||||
'leakSensor': 'moisture'}
|
||||
'wetLeakSensor': 'moisture'}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
@@ -28,13 +28,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||
address = discovery_info['address']
|
||||
device = plm.devices[address]
|
||||
state_key = discovery_info['state_key']
|
||||
name = device.states[state_key].name
|
||||
if name != 'dryLeakSensor':
|
||||
_LOGGER.debug('Adding device %s entity %s to Binary Sensor platform',
|
||||
device.address.hex, device.states[state_key].name)
|
||||
|
||||
_LOGGER.debug('Adding device %s entity %s to Binary Sensor platform',
|
||||
device.address.hex, device.states[state_key].name)
|
||||
new_entity = InsteonPLMBinarySensor(device, state_key)
|
||||
|
||||
new_entity = InsteonPLMBinarySensor(device, state_key)
|
||||
|
||||
async_add_devices([new_entity])
|
||||
async_add_devices([new_entity])
|
||||
|
||||
|
||||
class InsteonPLMBinarySensor(InsteonPLMEntity, BinarySensorDevice):
|
||||
@@ -53,5 +54,4 @@ class InsteonPLMBinarySensor(InsteonPLMEntity, BinarySensorDevice):
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the boolean response if the node is on."""
|
||||
sensorstate = self._insteon_device_state.value
|
||||
return bool(sensorstate)
|
||||
return bool(self._insteon_device_state.value)
|
||||
|
||||
@@ -62,7 +62,14 @@ class GoogleCalendarData(object):
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def update(self):
|
||||
"""Get the latest data."""
|
||||
service = self.calendar_service.get()
|
||||
from httplib2 import ServerNotFoundError
|
||||
|
||||
try:
|
||||
service = self.calendar_service.get()
|
||||
except ServerNotFoundError:
|
||||
_LOGGER.warning("Unable to connect to Google, using cached data")
|
||||
return False
|
||||
|
||||
params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS)
|
||||
params['timeMin'] = dt.now().isoformat('T')
|
||||
params['calendarId'] = self.calendar_id
|
||||
|
||||
@@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['python-eq3bt==0.1.9']
|
||||
REQUIREMENTS = ['python-eq3bt==0.1.9', 'construct==2.9.41']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ class MikrotikScanner(DeviceScanner):
|
||||
self.host,
|
||||
self.username,
|
||||
self.password,
|
||||
port=int(self.port)
|
||||
port=int(self.port),
|
||||
encoding='utf-8'
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -98,7 +98,8 @@ class UnifiScanner(DeviceScanner):
|
||||
# Filter clients to provided SSID list
|
||||
if self._ssid_filter:
|
||||
clients = [client for client in clients
|
||||
if client['essid'] in self._ssid_filter]
|
||||
if 'essid' in client and
|
||||
client['essid'] in self._ssid_filter]
|
||||
|
||||
self._clients = {
|
||||
client['mac']: client
|
||||
|
||||
@@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
})
|
||||
|
||||
REQUIREMENTS = ['python-miio==0.3.8']
|
||||
REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41']
|
||||
|
||||
ATTR_TEMPERATURE = 'temperature'
|
||||
ATTR_HUMIDITY = 'humidity'
|
||||
|
||||
@@ -24,7 +24,7 @@ from homeassistant.core import callback
|
||||
from homeassistant.helpers.translation import async_get_translations
|
||||
from homeassistant.loader import bind_hass
|
||||
|
||||
REQUIREMENTS = ['home-assistant-frontend==20180322.0']
|
||||
REQUIREMENTS = ['home-assistant-frontend==20180330.0']
|
||||
|
||||
DOMAIN = 'frontend'
|
||||
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
|
||||
|
||||
@@ -73,8 +73,6 @@ async def async_setup(hass, config):
|
||||
|
||||
def get_accessory(hass, state, aid, config):
|
||||
"""Take state and return an accessory object if supported."""
|
||||
_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.',
|
||||
@@ -129,8 +127,6 @@ def get_accessory(hass, state, aid, config):
|
||||
_LOGGER.debug('Add "%s" as "%s"', state.entity_id, 'Switch')
|
||||
return TYPES['Switch'](hass, state.entity_id, state.name, aid=aid)
|
||||
|
||||
_LOGGER.warning('The entity "%s" is not supported yet',
|
||||
state.entity_id)
|
||||
return None
|
||||
|
||||
|
||||
@@ -190,9 +186,6 @@ class HomeKit():
|
||||
|
||||
for state in self._hass.states.all():
|
||||
self.add_bridge_accessory(state)
|
||||
for entity_id in self._config:
|
||||
_LOGGER.warning('The entity "%s" was not setup when HomeKit '
|
||||
'was started', entity_id)
|
||||
self.bridge.set_broker(self.driver)
|
||||
|
||||
if not self.bridge.paired:
|
||||
|
||||
@@ -46,6 +46,7 @@ class WindowCovering(HomeAccessory):
|
||||
|
||||
def move_cover(self, value):
|
||||
"""Move cover to value if call came from HomeKit."""
|
||||
self.char_target_position.set_value(value, should_callback=False)
|
||||
if value != self.current_position:
|
||||
_LOGGER.debug('%s: Set position to %d', self._entity_id, value)
|
||||
self.homekit_target = value
|
||||
|
||||
@@ -32,6 +32,7 @@ class Light(HomeAccessory):
|
||||
self._flag = {CHAR_ON: False, CHAR_BRIGHTNESS: False,
|
||||
CHAR_HUE: False, CHAR_SATURATION: False,
|
||||
RGB_COLOR: False}
|
||||
self._state = 0
|
||||
|
||||
self.chars = []
|
||||
self._features = self._hass.states.get(self._entity_id) \
|
||||
@@ -47,7 +48,7 @@ class Light(HomeAccessory):
|
||||
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
|
||||
self.char_on.value = self._state
|
||||
|
||||
if CHAR_BRIGHTNESS in self.chars:
|
||||
self.char_brightness = serv_light \
|
||||
@@ -66,11 +67,12 @@ class Light(HomeAccessory):
|
||||
|
||||
def set_state(self, value):
|
||||
"""Set state if call came from HomeKit."""
|
||||
if self._flag[CHAR_BRIGHTNESS]:
|
||||
if self._state == value:
|
||||
return
|
||||
|
||||
_LOGGER.debug('%s: Set state to %d', self._entity_id, value)
|
||||
self._flag[CHAR_ON] = True
|
||||
self.char_on.set_value(value, should_callback=False)
|
||||
|
||||
if value == 1:
|
||||
self._hass.components.light.turn_on(self._entity_id)
|
||||
@@ -81,13 +83,18 @@ class Light(HomeAccessory):
|
||||
"""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)
|
||||
self.char_brightness.set_value(value, should_callback=False)
|
||||
if value != 0:
|
||||
self._hass.components.light.turn_on(
|
||||
self._entity_id, brightness_pct=value)
|
||||
else:
|
||||
self._hass.components.light.turn_off(self._entity_id)
|
||||
|
||||
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.char_saturation.set_value(value, should_callback=False)
|
||||
self._saturation = value
|
||||
self.set_color()
|
||||
|
||||
@@ -95,6 +102,7 @@ class Light(HomeAccessory):
|
||||
"""Set hue if call came from HomeKit."""
|
||||
_LOGGER.debug('%s: Set hue to %d', self._entity_id, value)
|
||||
self._flag[CHAR_HUE] = True
|
||||
self.char_hue.set_value(value, should_callback=False)
|
||||
self._hue = value
|
||||
self.set_color()
|
||||
|
||||
@@ -117,10 +125,11 @@ class Light(HomeAccessory):
|
||||
|
||||
# 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
|
||||
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, should_callback=False)
|
||||
self._flag[CHAR_ON] = False
|
||||
|
||||
# Handle Brightness
|
||||
if CHAR_BRIGHTNESS in self.chars:
|
||||
|
||||
@@ -54,10 +54,13 @@ class SecuritySystem(HomeAccessory):
|
||||
_LOGGER.debug('%s: Set security state to %d',
|
||||
self._entity_id, value)
|
||||
self.flag_target_state = True
|
||||
self.char_target_state.set_value(value, should_callback=False)
|
||||
hass_value = HOMEKIT_TO_HASS[value]
|
||||
service = STATE_TO_SERVICE[hass_value]
|
||||
|
||||
params = {ATTR_ENTITY_ID: self._entity_id, ATTR_CODE: self._alarm_code}
|
||||
params = {ATTR_ENTITY_ID: self._entity_id}
|
||||
if self._alarm_code:
|
||||
params[ATTR_CODE] = self._alarm_code
|
||||
self._hass.services.call('alarm_control_panel', service, params)
|
||||
|
||||
def update_state(self, entity_id=None, old_state=None, new_state=None):
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
import logging
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_FAHRENHEIT, TEMP_CELSIUS)
|
||||
from homeassistant.util.temperature import fahrenheit_to_celsius
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS)
|
||||
|
||||
from . import TYPES
|
||||
from .accessories import (
|
||||
@@ -11,33 +10,12 @@ from .accessories import (
|
||||
from .const import (
|
||||
CATEGORY_SENSOR, SERV_HUMIDITY_SENSOR, SERV_TEMPERATURE_SENSOR,
|
||||
CHAR_CURRENT_HUMIDITY, CHAR_CURRENT_TEMPERATURE, PROP_CELSIUS)
|
||||
from .util import convert_to_float, temperature_to_homekit
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def calc_temperature(state, unit=TEMP_CELSIUS):
|
||||
"""Calculate temperature from state and unit.
|
||||
|
||||
Always return temperature as Celsius value.
|
||||
Conversion is handled on the device.
|
||||
"""
|
||||
try:
|
||||
value = float(state)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
return fahrenheit_to_celsius(value) 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.
|
||||
@@ -63,9 +41,10 @@ class TemperatureSensor(HomeAccessory):
|
||||
if new_state is None:
|
||||
return
|
||||
|
||||
unit = new_state.attributes[ATTR_UNIT_OF_MEASUREMENT]
|
||||
temperature = calc_temperature(new_state.state, unit)
|
||||
unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS)
|
||||
temperature = convert_to_float(new_state.state)
|
||||
if temperature:
|
||||
temperature = temperature_to_homekit(temperature, unit)
|
||||
self.char_temp.set_value(temperature, should_callback=False)
|
||||
_LOGGER.debug('%s: Current temperature set to %d°C',
|
||||
self._entity_id, temperature)
|
||||
@@ -92,8 +71,8 @@ class HumiditySensor(HomeAccessory):
|
||||
if new_state is None:
|
||||
return
|
||||
|
||||
humidity = calc_humidity(new_state.state)
|
||||
humidity = convert_to_float(new_state.state)
|
||||
if humidity:
|
||||
self.char_humidity.set_value(humidity, should_callback=False)
|
||||
_LOGGER.debug('%s: Current humidity set to %d%%',
|
||||
_LOGGER.debug('%s: Percent set to %d%%',
|
||||
self._entity_id, humidity)
|
||||
|
||||
@@ -36,6 +36,7 @@ class Switch(HomeAccessory):
|
||||
_LOGGER.debug('%s: Set switch state to %s',
|
||||
self._entity_id, value)
|
||||
self.flag_target_state = True
|
||||
self.char_on.set_value(value, should_callback=False)
|
||||
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
||||
self._hass.services.call(self._domain, service,
|
||||
{ATTR_ENTITY_ID: self._entity_id})
|
||||
|
||||
@@ -16,6 +16,7 @@ from .const import (
|
||||
CHAR_TARGET_HEATING_COOLING, CHAR_CURRENT_TEMPERATURE,
|
||||
CHAR_TARGET_TEMPERATURE, CHAR_TEMP_DISPLAY_UNITS,
|
||||
CHAR_COOLING_THRESHOLD_TEMPERATURE, CHAR_HEATING_THRESHOLD_TEMPERATURE)
|
||||
from .util import temperature_to_homekit, temperature_to_states
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -40,6 +41,7 @@ class Thermostat(HomeAccessory):
|
||||
self._hass = hass
|
||||
self._entity_id = entity_id
|
||||
self._call_timer = None
|
||||
self._unit = TEMP_CELSIUS
|
||||
|
||||
self.heat_cool_flag_target_state = False
|
||||
self.temperature_flag_target_state = False
|
||||
@@ -97,6 +99,7 @@ class Thermostat(HomeAccessory):
|
||||
|
||||
def set_heat_cool(self, value):
|
||||
"""Move operation mode to value if call came from HomeKit."""
|
||||
self.char_target_heat_cool.set_value(value, should_callback=False)
|
||||
if value in HC_HOMEKIT_TO_HASS:
|
||||
_LOGGER.debug('%s: Set heat-cool to %d', self._entity_id, value)
|
||||
self.heat_cool_flag_target_state = True
|
||||
@@ -106,30 +109,38 @@ class Thermostat(HomeAccessory):
|
||||
|
||||
def set_cooling_threshold(self, value):
|
||||
"""Set cooling threshold temp to value if call came from HomeKit."""
|
||||
_LOGGER.debug('%s: Set cooling threshold temperature to %.2f',
|
||||
_LOGGER.debug('%s: Set cooling threshold temperature to %.2f°C',
|
||||
self._entity_id, value)
|
||||
self.coolingthresh_flag_target_state = True
|
||||
self.char_cooling_thresh_temp.set_value(value, should_callback=False)
|
||||
low = self.char_heating_thresh_temp.value
|
||||
low = temperature_to_states(low, self._unit)
|
||||
value = temperature_to_states(value, self._unit)
|
||||
self._hass.components.climate.set_temperature(
|
||||
entity_id=self._entity_id, target_temp_high=value,
|
||||
target_temp_low=low)
|
||||
|
||||
def set_heating_threshold(self, value):
|
||||
"""Set heating threshold temp to value if call came from HomeKit."""
|
||||
_LOGGER.debug('%s: Set heating threshold temperature to %.2f',
|
||||
_LOGGER.debug('%s: Set heating threshold temperature to %.2f°C',
|
||||
self._entity_id, value)
|
||||
self.heatingthresh_flag_target_state = True
|
||||
self.char_heating_thresh_temp.set_value(value, should_callback=False)
|
||||
# Home assistant always wants to set low and high at the same time
|
||||
high = self.char_cooling_thresh_temp.value
|
||||
high = temperature_to_states(high, self._unit)
|
||||
value = temperature_to_states(value, self._unit)
|
||||
self._hass.components.climate.set_temperature(
|
||||
entity_id=self._entity_id, target_temp_high=high,
|
||||
target_temp_low=value)
|
||||
|
||||
def set_target_temperature(self, value):
|
||||
"""Set target temperature to value if call came from HomeKit."""
|
||||
_LOGGER.debug('%s: Set target temperature to %.2f',
|
||||
_LOGGER.debug('%s: Set target temperature to %.2f°C',
|
||||
self._entity_id, value)
|
||||
self.temperature_flag_target_state = True
|
||||
self.char_target_temp.set_value(value, should_callback=False)
|
||||
value = temperature_to_states(value, self._unit)
|
||||
self._hass.components.climate.set_temperature(
|
||||
temperature=value, entity_id=self._entity_id)
|
||||
|
||||
@@ -138,14 +149,19 @@ class Thermostat(HomeAccessory):
|
||||
if new_state is None:
|
||||
return
|
||||
|
||||
self._unit = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT,
|
||||
TEMP_CELSIUS)
|
||||
|
||||
# Update current temperature
|
||||
current_temp = new_state.attributes.get(ATTR_CURRENT_TEMPERATURE)
|
||||
if isinstance(current_temp, (int, float)):
|
||||
current_temp = temperature_to_homekit(current_temp, self._unit)
|
||||
self.char_current_temp.set_value(current_temp)
|
||||
|
||||
# Update target temperature
|
||||
target_temp = new_state.attributes.get(ATTR_TEMPERATURE)
|
||||
if isinstance(target_temp, (int, float)):
|
||||
target_temp = temperature_to_homekit(target_temp, self._unit)
|
||||
if not self.temperature_flag_target_state:
|
||||
self.char_target_temp.set_value(target_temp,
|
||||
should_callback=False)
|
||||
@@ -154,7 +170,9 @@ class Thermostat(HomeAccessory):
|
||||
# Update cooling threshold temperature if characteristic exists
|
||||
if self.char_cooling_thresh_temp:
|
||||
cooling_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_HIGH)
|
||||
if cooling_thresh:
|
||||
if isinstance(cooling_thresh, (int, float)):
|
||||
cooling_thresh = temperature_to_homekit(cooling_thresh,
|
||||
self._unit)
|
||||
if not self.coolingthresh_flag_target_state:
|
||||
self.char_cooling_thresh_temp.set_value(
|
||||
cooling_thresh, should_callback=False)
|
||||
@@ -163,18 +181,17 @@ class Thermostat(HomeAccessory):
|
||||
# Update heating threshold temperature if characteristic exists
|
||||
if self.char_heating_thresh_temp:
|
||||
heating_thresh = new_state.attributes.get(ATTR_TARGET_TEMP_LOW)
|
||||
if heating_thresh:
|
||||
if isinstance(heating_thresh, (int, float)):
|
||||
heating_thresh = temperature_to_homekit(heating_thresh,
|
||||
self._unit)
|
||||
if not self.heatingthresh_flag_target_state:
|
||||
self.char_heating_thresh_temp.set_value(
|
||||
heating_thresh, should_callback=False)
|
||||
self.heatingthresh_flag_target_state = False
|
||||
|
||||
# Update display units
|
||||
display_units = new_state.attributes.get(ATTR_UNIT_OF_MEASUREMENT)
|
||||
if display_units \
|
||||
and display_units in UNIT_HASS_TO_HOMEKIT:
|
||||
self.char_display_units.set_value(
|
||||
UNIT_HASS_TO_HOMEKIT[display_units])
|
||||
if self._unit and self._unit in UNIT_HASS_TO_HOMEKIT:
|
||||
self.char_display_units.set_value(UNIT_HASS_TO_HOMEKIT[self._unit])
|
||||
|
||||
# Update target operation mode
|
||||
operation_mode = new_state.attributes.get(ATTR_OPERATION_MODE)
|
||||
|
||||
@@ -5,8 +5,9 @@ import voluptuous as vol
|
||||
|
||||
from homeassistant.core import split_entity_id
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE)
|
||||
ATTR_CODE, TEMP_CELSIUS)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import homeassistant.util.temperature as temp_util
|
||||
from .const import HOMEKIT_NOTIFY_ID
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -44,3 +45,21 @@ def show_setup_message(bridge, hass):
|
||||
def dismiss_setup_message(hass):
|
||||
"""Dismiss persistent notification and remove QR code."""
|
||||
hass.components.persistent_notification.dismiss(HOMEKIT_NOTIFY_ID)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
@@ -33,6 +33,7 @@ DISCOVER_SENSORS = 'homematic.sensor'
|
||||
DISCOVER_BINARY_SENSORS = 'homematic.binary_sensor'
|
||||
DISCOVER_COVER = 'homematic.cover'
|
||||
DISCOVER_CLIMATE = 'homematic.climate'
|
||||
DISCOVER_LOCKS = 'homematic.locks'
|
||||
|
||||
ATTR_DISCOVER_DEVICES = 'devices'
|
||||
ATTR_PARAM = 'param'
|
||||
@@ -59,7 +60,7 @@ SERVICE_SET_INSTALL_MODE = 'set_install_mode'
|
||||
HM_DEVICE_TYPES = {
|
||||
DISCOVER_SWITCHES: [
|
||||
'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch', 'RFSiren',
|
||||
'IPSwitchPowermeter', 'KeyMatic', 'HMWIOSwitch', 'Rain', 'EcoLogic'],
|
||||
'IPSwitchPowermeter', 'HMWIOSwitch', 'Rain', 'EcoLogic'],
|
||||
DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer', 'IPKeyDimmer'],
|
||||
DISCOVER_SENSORS: [
|
||||
'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion', 'MotionIP',
|
||||
@@ -78,7 +79,8 @@ HM_DEVICE_TYPES = {
|
||||
'MotionIP', 'RemoteMotion', 'WeatherSensor', 'TiltSensor',
|
||||
'IPShutterContact', 'HMWIOSwitch', 'MaxShutterContact', 'Rain',
|
||||
'WiredSensor', 'PresenceIP'],
|
||||
DISCOVER_COVER: ['Blind', 'KeyBlind', 'IPKeyBlind', 'IPKeyBlindTilt']
|
||||
DISCOVER_COVER: ['Blind', 'KeyBlind', 'IPKeyBlind', 'IPKeyBlindTilt'],
|
||||
DISCOVER_LOCKS: ['KeyMatic']
|
||||
}
|
||||
|
||||
HM_IGNORE_DISCOVERY_NODE = [
|
||||
@@ -464,7 +466,8 @@ def _system_callback_handler(hass, config, src, *args):
|
||||
('cover', DISCOVER_COVER),
|
||||
('binary_sensor', DISCOVER_BINARY_SENSORS),
|
||||
('sensor', DISCOVER_SENSORS),
|
||||
('climate', DISCOVER_CLIMATE)):
|
||||
('climate', DISCOVER_CLIMATE),
|
||||
('lock', DISCOVER_LOCKS)):
|
||||
# Get all devices of a specific type
|
||||
found_devices = _get_devices(
|
||||
hass, discovery_type, addresses, interface)
|
||||
|
||||
@@ -21,7 +21,7 @@ from homeassistant.helpers import discovery, aiohttp_client
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.util.json import save_json
|
||||
|
||||
REQUIREMENTS = ['aiohue==1.2.0']
|
||||
REQUIREMENTS = ['aiohue==1.3.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -145,7 +145,6 @@ async def async_setup_bridge(
|
||||
bridge = HueBridge(host, hass, filename, username, allow_unreachable,
|
||||
allow_hue_groups)
|
||||
await bridge.async_setup()
|
||||
hass.data[DOMAIN][host] = bridge
|
||||
|
||||
|
||||
def _find_username_from_config(hass, filename):
|
||||
@@ -209,6 +208,8 @@ class HueBridge(object):
|
||||
self.host)
|
||||
return
|
||||
|
||||
self.hass.data[DOMAIN][self.host] = self
|
||||
|
||||
# If we came here and configuring this host, mark as done
|
||||
if self.config_request_id:
|
||||
request_id = self.config_request_id
|
||||
|
||||
@@ -16,7 +16,7 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['insteonplm==0.8.2']
|
||||
REQUIREMENTS = ['insteonplm==0.8.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -64,19 +64,20 @@ def async_setup(hass, config):
|
||||
"""Detect device from transport to be delegated to platform."""
|
||||
for state_key in device.states:
|
||||
platform_info = ipdb[device.states[state_key]]
|
||||
platform = platform_info.platform
|
||||
if platform is not None:
|
||||
_LOGGER.info("New INSTEON PLM device: %s (%s) %s",
|
||||
device.address,
|
||||
device.states[state_key].name,
|
||||
platform)
|
||||
if platform_info:
|
||||
platform = platform_info.platform
|
||||
if platform:
|
||||
_LOGGER.info("New INSTEON PLM device: %s (%s) %s",
|
||||
device.address,
|
||||
device.states[state_key].name,
|
||||
platform)
|
||||
|
||||
hass.async_add_job(
|
||||
discovery.async_load_platform(
|
||||
hass, platform, DOMAIN,
|
||||
discovered={'address': device.address.hex,
|
||||
'state_key': state_key},
|
||||
hass_config=config))
|
||||
hass.async_add_job(
|
||||
discovery.async_load_platform(
|
||||
hass, platform, DOMAIN,
|
||||
discovered={'address': device.address.hex,
|
||||
'state_key': state_key},
|
||||
hass_config=config))
|
||||
|
||||
_LOGGER.info("Looking for PLM on %s", port)
|
||||
conn = yield from insteonplm.Connection.create(
|
||||
@@ -127,13 +128,15 @@ class IPDB(object):
|
||||
from insteonplm.states.sensor import (VariableSensor,
|
||||
OnOffSensor,
|
||||
SmokeCO2Sensor,
|
||||
IoLincSensor)
|
||||
IoLincSensor,
|
||||
LeakSensorDryWet)
|
||||
|
||||
self.states = [State(OnOffSwitch_OutletTop, 'switch'),
|
||||
State(OnOffSwitch_OutletBottom, 'switch'),
|
||||
State(OpenClosedRelay, 'switch'),
|
||||
State(OnOffSwitch, 'switch'),
|
||||
|
||||
State(LeakSensorDryWet, 'binary_sensor'),
|
||||
State(IoLincSensor, 'binary_sensor'),
|
||||
State(SmokeCO2Sensor, 'sensor'),
|
||||
State(OnOffSensor, 'binary_sensor'),
|
||||
|
||||
@@ -18,6 +18,7 @@ from homeassistant.components.light import (
|
||||
FLASH_LONG, FLASH_SHORT, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP,
|
||||
SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_COLOR, SUPPORT_TRANSITION,
|
||||
Light)
|
||||
from homeassistant.util import color
|
||||
|
||||
DEPENDENCIES = ['hue']
|
||||
SCAN_INTERVAL = timedelta(seconds=5)
|
||||
@@ -235,19 +236,26 @@ class HueLight(Light):
|
||||
@property
|
||||
def hs_color(self):
|
||||
"""Return the hs color value."""
|
||||
# Don't return hue/sat if in color temperature mode
|
||||
if self._color_mode == "ct":
|
||||
# pylint: disable=redefined-outer-name
|
||||
mode = self._color_mode
|
||||
|
||||
if mode not in ('hs', 'xy'):
|
||||
return
|
||||
|
||||
source = self.light.action if self.is_group else self.light.state
|
||||
|
||||
hue = source.get('hue')
|
||||
sat = source.get('sat')
|
||||
|
||||
# Sometimes the state will not include valid hue/sat values.
|
||||
# Reported as issue 13434
|
||||
if hue is not None and sat is not None:
|
||||
return hue / 65535 * 360, sat / 255 * 100
|
||||
|
||||
if 'xy' not in source:
|
||||
return None
|
||||
|
||||
if self.is_group:
|
||||
return (
|
||||
self.light.action.get('hue') / 65535 * 360,
|
||||
self.light.action.get('sat') / 255 * 100,
|
||||
)
|
||||
return (
|
||||
self.light.state.get('hue') / 65535 * 360,
|
||||
self.light.state.get('sat') / 255 * 100,
|
||||
)
|
||||
return color.color_xy_to_hs(*source['xy'])
|
||||
|
||||
@property
|
||||
def color_temp(self):
|
||||
|
||||
@@ -215,7 +215,7 @@ class Hyperion(Light):
|
||||
pass
|
||||
|
||||
led_color = response['info']['activeLedColor']
|
||||
if not led_color or led_color[0]['RGB value'] == [0, 0, 0]:
|
||||
if not led_color or led_color[0]['RGB Value'] == [0, 0, 0]:
|
||||
# Get the active effect
|
||||
if response['info'].get('activeEffects'):
|
||||
self._rgb_color = [175, 0, 255]
|
||||
@@ -234,8 +234,7 @@ class Hyperion(Light):
|
||||
self._effect = None
|
||||
else:
|
||||
# Get the RGB color
|
||||
self._rgb_color =\
|
||||
response['info']['activeLedColor'][0]['RGB Value']
|
||||
self._rgb_color = led_color[0]['RGB Value']
|
||||
self._brightness = max(self._rgb_color)
|
||||
self._rgb_mem = [int(round(float(x)*255/self._brightness))
|
||||
for x in self._rgb_color]
|
||||
|
||||
@@ -197,7 +197,7 @@ class LimitlessLEDGroup(Light):
|
||||
self._is_on = (last_state.state == STATE_ON)
|
||||
self._brightness = last_state.attributes.get('brightness')
|
||||
self._temperature = last_state.attributes.get('color_temp')
|
||||
self._color = last_state.attributes.get('rgb_color')
|
||||
self._color = last_state.attributes.get('hs_color')
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
@@ -336,4 +336,4 @@ class LimitlessLEDGroup(Light):
|
||||
def limitlessled_color(self):
|
||||
"""Convert Home Assistant HS list to RGB Color tuple."""
|
||||
from limitlessled import Color
|
||||
return Color(color_hs_to_RGB(*tuple(self._color)))
|
||||
return Color(*color_hs_to_RGB(*tuple(self._color)))
|
||||
|
||||
@@ -12,8 +12,7 @@ from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.util.color import rgb_hex_to_rgb_list
|
||||
import homeassistant.util.color as color_util
|
||||
|
||||
SUPPORT_MYSENSORS = (SUPPORT_BRIGHTNESS | SUPPORT_COLOR |
|
||||
SUPPORT_WHITE_VALUE)
|
||||
SUPPORT_MYSENSORS_RGBW = SUPPORT_COLOR | SUPPORT_WHITE_VALUE
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
@@ -64,11 +63,6 @@ class MySensorsLight(mysensors.MySensorsEntity, Light):
|
||||
"""Return true if device is on."""
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_MYSENSORS
|
||||
|
||||
def _turn_on_light(self):
|
||||
"""Turn on light child device."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
@@ -171,6 +165,11 @@ class MySensorsLight(mysensors.MySensorsEntity, Light):
|
||||
class MySensorsLightDimmer(MySensorsLight):
|
||||
"""Dimmer child class to MySensorsLight."""
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_BRIGHTNESS
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
self._turn_on_light()
|
||||
@@ -188,6 +187,14 @@ class MySensorsLightDimmer(MySensorsLight):
|
||||
class MySensorsLightRGB(MySensorsLight):
|
||||
"""RGB child class to MySensorsLight."""
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
if set_req.V_DIMMER in self._values:
|
||||
return SUPPORT_BRIGHTNESS | SUPPORT_COLOR
|
||||
return SUPPORT_COLOR
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
self._turn_on_light()
|
||||
@@ -209,6 +216,14 @@ class MySensorsLightRGBW(MySensorsLightRGB):
|
||||
|
||||
# pylint: disable=too-many-ancestors
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
set_req = self.gateway.const.SetReq
|
||||
if set_req.V_DIMMER in self._values:
|
||||
return SUPPORT_BRIGHTNESS | SUPPORT_MYSENSORS_RGBW
|
||||
return SUPPORT_MYSENSORS_RGBW
|
||||
|
||||
def turn_on(self, **kwargs):
|
||||
"""Turn the device on."""
|
||||
self._turn_on_light()
|
||||
|
||||
@@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
'philips.light.candle2']),
|
||||
})
|
||||
|
||||
REQUIREMENTS = ['python-miio==0.3.8']
|
||||
REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41']
|
||||
|
||||
# The light does not accept cct values < 1
|
||||
CCT_MIN = 1
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED,
|
||||
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK)
|
||||
STATE_UNKNOWN, SERVICE_LOCK, SERVICE_UNLOCK, SERVICE_OPEN)
|
||||
from homeassistant.components import group
|
||||
|
||||
ATTR_CHANGED_BY = 'changed_by'
|
||||
@@ -39,6 +39,9 @@ LOCK_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_CODE): cv.string,
|
||||
})
|
||||
|
||||
# Bitfield of features supported by the lock entity
|
||||
SUPPORT_OPEN = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PROP_TO_ATTR = {
|
||||
@@ -78,6 +81,18 @@ def unlock(hass, entity_id=None, code=None):
|
||||
hass.services.call(DOMAIN, SERVICE_UNLOCK, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def open_lock(hass, entity_id=None, code=None):
|
||||
"""Open all or specified locks."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_OPEN, data)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
"""Track states and offer events for locks."""
|
||||
@@ -97,6 +112,8 @@ def async_setup(hass, config):
|
||||
for entity in target_locks:
|
||||
if service.service == SERVICE_LOCK:
|
||||
yield from entity.async_lock(code=code)
|
||||
elif service.service == SERVICE_OPEN:
|
||||
yield from entity.async_open(code=code)
|
||||
else:
|
||||
yield from entity.async_unlock(code=code)
|
||||
|
||||
@@ -113,6 +130,9 @@ def async_setup(hass, config):
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_LOCK, async_handle_lock_service,
|
||||
schema=LOCK_SERVICE_SCHEMA)
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_OPEN, async_handle_lock_service,
|
||||
schema=LOCK_SERVICE_SCHEMA)
|
||||
|
||||
return True
|
||||
|
||||
@@ -158,6 +178,17 @@ class LockDevice(Entity):
|
||||
"""
|
||||
return self.hass.async_add_job(ft.partial(self.unlock, **kwargs))
|
||||
|
||||
def open(self, **kwargs):
|
||||
"""Open the door latch."""
|
||||
raise NotImplementedError()
|
||||
|
||||
def async_open(self, **kwargs):
|
||||
"""Open the door latch.
|
||||
|
||||
This method must be run in the event loop and returns a coroutine.
|
||||
"""
|
||||
return self.hass.async_add_job(ft.partial(self.open, **kwargs))
|
||||
|
||||
@property
|
||||
def state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
|
||||
@@ -4,7 +4,7 @@ Demo lock platform that has two fake locks.
|
||||
For more details about this platform, please refer to the documentation
|
||||
https://home-assistant.io/components/demo/
|
||||
"""
|
||||
from homeassistant.components.lock import LockDevice
|
||||
from homeassistant.components.lock import LockDevice, SUPPORT_OPEN
|
||||
from homeassistant.const import (STATE_LOCKED, STATE_UNLOCKED)
|
||||
|
||||
|
||||
@@ -13,17 +13,19 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Demo lock platform."""
|
||||
add_devices([
|
||||
DemoLock('Front Door', STATE_LOCKED),
|
||||
DemoLock('Kitchen Door', STATE_UNLOCKED)
|
||||
DemoLock('Kitchen Door', STATE_UNLOCKED),
|
||||
DemoLock('Openable Lock', STATE_LOCKED, True)
|
||||
])
|
||||
|
||||
|
||||
class DemoLock(LockDevice):
|
||||
"""Representation of a Demo lock."""
|
||||
|
||||
def __init__(self, name, state):
|
||||
def __init__(self, name, state, openable=False):
|
||||
"""Initialize the lock."""
|
||||
self._name = name
|
||||
self._state = state
|
||||
self._openable = openable
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
@@ -49,3 +51,14 @@ class DemoLock(LockDevice):
|
||||
"""Unlock the device."""
|
||||
self._state = STATE_UNLOCKED
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
def open(self, **kwargs):
|
||||
"""Open the door latch."""
|
||||
self._state = STATE_UNLOCKED
|
||||
self.schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
if self._openable:
|
||||
return SUPPORT_OPEN
|
||||
|
||||
58
homeassistant/components/lock/homematic.py
Normal file
58
homeassistant/components/lock/homematic.py
Normal file
@@ -0,0 +1,58 @@
|
||||
"""
|
||||
Support for Homematic lock.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/lock.homematic/
|
||||
"""
|
||||
import logging
|
||||
from homeassistant.components.lock import LockDevice, SUPPORT_OPEN
|
||||
from homeassistant.components.homematic import HMDevice, ATTR_DISCOVER_DEVICES
|
||||
from homeassistant.const import STATE_UNKNOWN
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['homematic']
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||
"""Set up the Homematic lock platform."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
devices = []
|
||||
for conf in discovery_info[ATTR_DISCOVER_DEVICES]:
|
||||
devices.append(HMLock(conf))
|
||||
|
||||
add_devices(devices)
|
||||
|
||||
|
||||
class HMLock(HMDevice, LockDevice):
|
||||
"""Representation of a Homematic lock aka KeyMatic."""
|
||||
|
||||
@property
|
||||
def is_locked(self):
|
||||
"""Return true if the lock is locked."""
|
||||
return not bool(self._hm_get_state())
|
||||
|
||||
def lock(self, **kwargs):
|
||||
"""Lock the lock."""
|
||||
self._hmdevice.lock()
|
||||
|
||||
def unlock(self, **kwargs):
|
||||
"""Unlock the lock."""
|
||||
self._hmdevice.unlock()
|
||||
|
||||
def open(self, **kwargs):
|
||||
"""Open the door latch."""
|
||||
self._hmdevice.open()
|
||||
|
||||
def _init_data_struct(self):
|
||||
"""Generate the data dictionary (self._data) from metadata."""
|
||||
self._state = "STATE"
|
||||
self._data.update({self._state: STATE_UNKNOWN})
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_OPEN
|
||||
@@ -155,8 +155,8 @@ class MpcHcDevice(MediaPlayerDevice):
|
||||
|
||||
def media_next_track(self):
|
||||
"""Send next track command."""
|
||||
self._send_command(921)
|
||||
self._send_command(920)
|
||||
|
||||
def media_previous_track(self):
|
||||
"""Send previous track command."""
|
||||
self._send_command(920)
|
||||
self._send_command(919)
|
||||
|
||||
@@ -22,7 +22,7 @@ from homeassistant.exceptions import PlatformNotReady
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util.dt import utcnow
|
||||
|
||||
REQUIREMENTS = ['python-miio==0.3.8']
|
||||
REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ from homeassistant.const import (
|
||||
CONF_NAME, TEMP_CELSIUS, STATE_UNKNOWN, EVENT_HOMEASSISTANT_STOP,
|
||||
EVENT_HOMEASSISTANT_START)
|
||||
|
||||
REQUIREMENTS = ['beacontools[scan]==1.2.1']
|
||||
REQUIREMENTS = ['beacontools[scan]==1.2.1', 'construct==2.9.41']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
})
|
||||
|
||||
REQUIREMENTS = ['python-miio==0.3.8']
|
||||
REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41']
|
||||
|
||||
ATTR_POWER = 'power'
|
||||
ATTR_CHARGING = 'charging'
|
||||
|
||||
@@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
'chuangmi.plug.v2']),
|
||||
})
|
||||
|
||||
REQUIREMENTS = ['python-miio==0.3.8']
|
||||
REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41']
|
||||
|
||||
ATTR_POWER = 'power'
|
||||
ATTR_TEMPERATURE = 'temperature'
|
||||
|
||||
@@ -19,7 +19,7 @@ from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, CONF_HOST, CONF_NAME, CONF_TOKEN, STATE_OFF, STATE_ON)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['python-miio==0.3.8']
|
||||
REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import voluptuous as vol
|
||||
from voluptuous.humanize import humanize_error
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ASSUMED_STATE,
|
||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, CONF_PACKAGES, CONF_UNIT_SYSTEM,
|
||||
CONF_TIME_ZONE, CONF_ELEVATION, CONF_UNIT_SYSTEM_METRIC,
|
||||
CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT, TEMP_CELSIUS,
|
||||
@@ -129,13 +130,19 @@ PACKAGES_CONFIG_SCHEMA = vol.Schema({
|
||||
{cv.slug: vol.Any(dict, list, None)}) # Only slugs for component names
|
||||
})
|
||||
|
||||
CUSTOMIZE_DICT_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
|
||||
vol.Optional(ATTR_HIDDEN): cv.boolean,
|
||||
vol.Optional(ATTR_ASSUMED_STATE): cv.boolean,
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
CUSTOMIZE_CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_CUSTOMIZE, default={}):
|
||||
vol.Schema({cv.entity_id: dict}),
|
||||
vol.Schema({cv.entity_id: CUSTOMIZE_DICT_SCHEMA}),
|
||||
vol.Optional(CONF_CUSTOMIZE_DOMAIN, default={}):
|
||||
vol.Schema({cv.string: dict}),
|
||||
vol.Schema({cv.string: CUSTOMIZE_DICT_SCHEMA}),
|
||||
vol.Optional(CONF_CUSTOMIZE_GLOB, default={}):
|
||||
vol.Schema({cv.string: OrderedDict}),
|
||||
vol.Schema({cv.string: CUSTOMIZE_DICT_SCHEMA}),
|
||||
})
|
||||
|
||||
CORE_CONFIG_SCHEMA = CUSTOMIZE_CONFIG_SCHEMA.extend({
|
||||
@@ -547,6 +554,8 @@ def merge_packages_config(config, packages, _log_pkg_error=_log_pkg_error):
|
||||
continue
|
||||
|
||||
if hasattr(component, 'PLATFORM_SCHEMA'):
|
||||
if not comp_conf:
|
||||
continue # Ensure we dont add Falsy items to list
|
||||
config[comp_name] = cv.ensure_list(config.get(comp_name))
|
||||
config[comp_name].extend(cv.ensure_list(comp_conf))
|
||||
continue
|
||||
@@ -555,6 +564,8 @@ def merge_packages_config(config, packages, _log_pkg_error=_log_pkg_error):
|
||||
merge_type, _ = _identify_config_schema(component)
|
||||
|
||||
if merge_type == 'list':
|
||||
if not comp_conf:
|
||||
continue # Ensure we dont add Falsy items to list
|
||||
config[comp_name] = cv.ensure_list(config.get(comp_name))
|
||||
config[comp_name].extend(cv.ensure_list(comp_conf))
|
||||
continue
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 0
|
||||
MINOR_VERSION = 66
|
||||
PATCH_VERSION = '0.beta0'
|
||||
PATCH_VERSION = '0b3'
|
||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||
REQUIRED_PYTHON_VER = (3, 5, 3)
|
||||
|
||||
@@ -97,11 +97,16 @@ class Script():
|
||||
|
||||
delay = action[CONF_DELAY]
|
||||
|
||||
if isinstance(delay, template.Template):
|
||||
delay = vol.All(
|
||||
cv.time_period,
|
||||
cv.positive_timedelta)(
|
||||
delay.async_render(variables))
|
||||
try:
|
||||
if isinstance(delay, template.Template):
|
||||
delay = vol.All(
|
||||
cv.time_period,
|
||||
cv.positive_timedelta)(
|
||||
delay.async_render(variables))
|
||||
except (TemplateError, vol.Invalid) as ex:
|
||||
_LOGGER.error("Error rendering '%s' delay template: %s",
|
||||
self.name, ex)
|
||||
break
|
||||
|
||||
unsub = async_track_point_in_utc_time(
|
||||
self.hass, async_script_delay,
|
||||
|
||||
@@ -13,7 +13,7 @@ from jinja2.sandbox import ImmutableSandboxedEnvironment
|
||||
from homeassistant.const import (
|
||||
ATTR_LATITUDE, ATTR_LONGITUDE, ATTR_UNIT_OF_MEASUREMENT, MATCH_ALL,
|
||||
STATE_UNKNOWN)
|
||||
from homeassistant.core import State
|
||||
from homeassistant.core import State, valid_entity_id
|
||||
from homeassistant.exceptions import TemplateError
|
||||
from homeassistant.helpers import location as loc_helper
|
||||
from homeassistant.loader import bind_hass, get_component
|
||||
@@ -73,7 +73,8 @@ def extract_entities(template, variables=None):
|
||||
extraction_final.append(result[0])
|
||||
|
||||
if variables and result[1] in variables and \
|
||||
isinstance(variables[result[1]], str):
|
||||
isinstance(variables[result[1]], str) and \
|
||||
valid_entity_id(variables[result[1]]):
|
||||
extraction_final.append(variables[result[1]])
|
||||
|
||||
if extraction_final:
|
||||
|
||||
@@ -74,7 +74,7 @@ aiodns==1.1.1
|
||||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==1.2.0
|
||||
aiohue==1.3.0
|
||||
|
||||
# homeassistant.components.sensor.imap
|
||||
aioimaplib==0.7.13
|
||||
@@ -189,6 +189,16 @@ colorlog==3.1.2
|
||||
# homeassistant.components.binary_sensor.concord232
|
||||
concord232==0.15
|
||||
|
||||
# homeassistant.components.climate.eq3btsmart
|
||||
# homeassistant.components.fan.xiaomi_miio
|
||||
# homeassistant.components.light.xiaomi_miio
|
||||
# homeassistant.components.remote.xiaomi_miio
|
||||
# homeassistant.components.sensor.eddystone_temperature
|
||||
# homeassistant.components.sensor.xiaomi_miio
|
||||
# homeassistant.components.switch.xiaomi_miio
|
||||
# homeassistant.components.vacuum.xiaomi_miio
|
||||
construct==2.9.41
|
||||
|
||||
# homeassistant.scripts.credstash
|
||||
# credstash==1.14.0
|
||||
|
||||
@@ -356,7 +366,7 @@ hipnotify==1.0.8
|
||||
holidays==0.9.4
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20180322.0
|
||||
home-assistant-frontend==20180330.0
|
||||
|
||||
# homeassistant.components.homematicip_cloud
|
||||
homematicip==0.8
|
||||
@@ -417,7 +427,7 @@ influxdb==5.0.0
|
||||
insteonlocal==0.53
|
||||
|
||||
# homeassistant.components.insteon_plm
|
||||
insteonplm==0.8.2
|
||||
insteonplm==0.8.3
|
||||
|
||||
# homeassistant.components.verisure
|
||||
jsonpath==0.75
|
||||
@@ -948,7 +958,7 @@ python-juicenet==0.0.5
|
||||
# homeassistant.components.sensor.xiaomi_miio
|
||||
# homeassistant.components.switch.xiaomi_miio
|
||||
# homeassistant.components.vacuum.xiaomi_miio
|
||||
python-miio==0.3.8
|
||||
python-miio==0.3.9
|
||||
|
||||
# homeassistant.components.media_player.mpd
|
||||
python-mpd2==0.5.5
|
||||
|
||||
@@ -35,7 +35,7 @@ aioautomatic==0.6.5
|
||||
aiohttp_cors==0.7.0
|
||||
|
||||
# homeassistant.components.hue
|
||||
aiohue==1.2.0
|
||||
aiohue==1.3.0
|
||||
|
||||
# homeassistant.components.notify.apns
|
||||
apns2==0.3.0
|
||||
@@ -81,7 +81,7 @@ hbmqtt==0.9.1
|
||||
holidays==0.9.4
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20180322.0
|
||||
home-assistant-frontend==20180330.0
|
||||
|
||||
# homeassistant.components.influxdb
|
||||
# homeassistant.components.sensor.influxdb
|
||||
|
||||
@@ -21,10 +21,11 @@ fi
|
||||
|
||||
CURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD`
|
||||
|
||||
if [ "$CURRENT_BRANCH" != "master" ]
|
||||
if [ "$CURRENT_BRANCH" != "master" ] && [ "$CURRENT_BRANCH" != "rc" ]
|
||||
then
|
||||
echo "You have to be on the master branch to release."
|
||||
echo "You have to be on the master or rc branch to release."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
python3 setup.py sdist bdist_wheel upload
|
||||
python3 setup.py sdist bdist_wheel
|
||||
python3 -m twine upload dist/* --skip-existing
|
||||
|
||||
135
script/version_bump.py
Executable file
135
script/version_bump.py
Executable file
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Helper script to bump the current version."""
|
||||
import argparse
|
||||
import re
|
||||
|
||||
from homeassistant import const
|
||||
|
||||
|
||||
PARSE_PATCH = r'(?P<patch>\d+)(\.(?P<prerel>\D+)(?P<prerelversion>\d+))?'
|
||||
|
||||
|
||||
def format_patch(patch_parts):
|
||||
"""Format the patch parts back into a patch string."""
|
||||
return '{patch}.{prerel}{prerelversion}'.format(**patch_parts)
|
||||
|
||||
|
||||
def bump_version(cur_major, cur_minor, cur_patch, bump_type):
|
||||
"""Return a new version given a current version and action."""
|
||||
patch_parts = re.match(PARSE_PATCH, cur_patch).groupdict()
|
||||
patch_parts['patch'] = int(patch_parts['patch'])
|
||||
if patch_parts['prerelversion'] is not None:
|
||||
patch_parts['prerelversion'] = int(patch_parts['prerelversion'])
|
||||
|
||||
if bump_type == 'release_patch':
|
||||
# Convert 0.67.3 to 0.67.4
|
||||
# Convert 0.67.3.b5 to 0.67.3
|
||||
# Convert 0.67.3.dev0 to 0.67.3
|
||||
new_major = cur_major
|
||||
new_minor = cur_minor
|
||||
|
||||
if patch_parts['prerel'] is None:
|
||||
new_patch = str(patch_parts['patch'] + 1)
|
||||
else:
|
||||
new_patch = str(patch_parts['patch'])
|
||||
|
||||
elif bump_type == 'dev':
|
||||
# Convert 0.67.3 to 0.67.4.dev0
|
||||
# Convert 0.67.3.b5 to 0.67.4.dev0
|
||||
# Convert 0.67.3.dev0 to 0.67.3.dev1
|
||||
new_major = cur_major
|
||||
|
||||
if patch_parts['prerel'] == 'dev':
|
||||
new_minor = cur_minor
|
||||
patch_parts['prerelversion'] += 1
|
||||
new_patch = format_patch(patch_parts)
|
||||
else:
|
||||
new_minor = cur_minor + 1
|
||||
new_patch = '0.dev0'
|
||||
|
||||
elif bump_type == 'beta':
|
||||
# Convert 0.67.5 to 0.67.8.b0
|
||||
# Convert 0.67.0.dev0 to 0.67.0.b0
|
||||
# Convert 0.67.5.b4 to 0.67.5.b5
|
||||
new_major = cur_major
|
||||
new_minor = cur_minor
|
||||
|
||||
if patch_parts['prerel'] is None:
|
||||
patch_parts['patch'] += 1
|
||||
patch_parts['prerel'] = 'b'
|
||||
patch_parts['prerelversion'] = 0
|
||||
|
||||
elif patch_parts['prerel'] == 'b':
|
||||
patch_parts['prerelversion'] += 1
|
||||
|
||||
elif patch_parts['prerel'] == 'dev':
|
||||
patch_parts['prerel'] = 'b'
|
||||
patch_parts['prerelversion'] = 0
|
||||
|
||||
else:
|
||||
raise Exception('Can only bump from beta or no prerel version')
|
||||
|
||||
new_patch = format_patch(patch_parts)
|
||||
|
||||
return new_major, new_minor, new_patch
|
||||
|
||||
|
||||
def write_version(major, minor, patch):
|
||||
"""Update Home Assistant constant file with new version."""
|
||||
with open('homeassistant/const.py') as fil:
|
||||
content = fil.read()
|
||||
|
||||
content = re.sub('MAJOR_VERSION = .*\n',
|
||||
'MAJOR_VERSION = {}\n'.format(major),
|
||||
content)
|
||||
content = re.sub('MINOR_VERSION = .*\n',
|
||||
'MINOR_VERSION = {}\n'.format(minor),
|
||||
content)
|
||||
content = re.sub('PATCH_VERSION = .*\n',
|
||||
"PATCH_VERSION = '{}'\n".format(patch),
|
||||
content)
|
||||
|
||||
with open('homeassistant/const.py', 'wt') as fil:
|
||||
content = fil.write(content)
|
||||
|
||||
|
||||
def main():
|
||||
"""Execute script."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Bump version of Home Assistant")
|
||||
parser.add_argument(
|
||||
'type',
|
||||
help="The type of the bump the version to.",
|
||||
choices=['beta', 'dev', 'release_patch'],
|
||||
)
|
||||
arguments = parser.parse_args()
|
||||
write_version(*bump_version(const.MAJOR_VERSION, const.MINOR_VERSION,
|
||||
const.PATCH_VERSION, arguments.type))
|
||||
|
||||
|
||||
def test_bump_version():
|
||||
"""Make sure it all works."""
|
||||
assert bump_version(0, 56, '0', 'beta') == \
|
||||
(0, 56, '1.b0')
|
||||
assert bump_version(0, 56, '0.b3', 'beta') == \
|
||||
(0, 56, '0.b4')
|
||||
assert bump_version(0, 56, '0.dev0', 'beta') == \
|
||||
(0, 56, '0.b0')
|
||||
|
||||
assert bump_version(0, 56, '3', 'dev') == \
|
||||
(0, 57, '0.dev0')
|
||||
assert bump_version(0, 56, '0.b3', 'dev') == \
|
||||
(0, 57, '0.dev0')
|
||||
assert bump_version(0, 56, '0.dev0', 'dev') == \
|
||||
(0, 56, '0.dev1')
|
||||
|
||||
assert bump_version(0, 56, '3', 'release_patch') == \
|
||||
(0, 56, '4')
|
||||
assert bump_version(0, 56, '3.b3', 'release_patch') == \
|
||||
(0, 56, '3')
|
||||
assert bump_version(0, 56, '0.dev0', 'release_patch') == \
|
||||
(0, 56, '0')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -2,7 +2,7 @@
|
||||
# pylint: disable=protected-access
|
||||
import logging
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -11,7 +11,7 @@ import homeassistant.components.calendar.google as calendar
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.const import CONF_PLATFORM, STATE_OFF, STATE_ON
|
||||
from homeassistant.helpers.template import DATE_STR_FORMAT
|
||||
from tests.common import get_test_home_assistant
|
||||
from tests.common import get_test_home_assistant, MockDependency
|
||||
|
||||
TEST_PLATFORM = {calendar_base.DOMAIN: {CONF_PLATFORM: 'test'}}
|
||||
|
||||
@@ -421,3 +421,16 @@ class TestComponentsGoogleCalendar(unittest.TestCase):
|
||||
'location': event['location'],
|
||||
'description': event['description']
|
||||
})
|
||||
|
||||
@MockDependency("httplib2")
|
||||
def test_update_false(self, mock_httplib2):
|
||||
"""Test that the update returns False upon Error."""
|
||||
mock_service = Mock()
|
||||
mock_service.get = Mock(
|
||||
side_effect=mock_httplib2.ServerNotFoundError("unit test"))
|
||||
|
||||
cal = calendar.GoogleCalendarEventDevice(self.hass, mock_service, None,
|
||||
{'name': "test"})
|
||||
result = cal.data.update()
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
@@ -16,15 +16,18 @@ _LOGGER = logging.getLogger(__name__)
|
||||
CONFIG = {}
|
||||
|
||||
|
||||
def test_get_accessory_invalid(caplog):
|
||||
def test_get_accessory_invalid_aid(caplog):
|
||||
"""Test with unsupported component."""
|
||||
assert get_accessory(None, State('test.unsupported', 'on'), 2, None) \
|
||||
is None
|
||||
assert caplog.records[1].levelname == 'WARNING'
|
||||
assert get_accessory(None, State('light.demo', 'on'),
|
||||
aid=None, config=None) is None
|
||||
assert caplog.records[0].levelname == 'WARNING'
|
||||
assert 'invalid aid' in caplog.records[0].msg
|
||||
|
||||
assert get_accessory(None, State('test.test', 'on'), None, None) \
|
||||
|
||||
def test_not_supported():
|
||||
"""Test if none is returned if entity isn't supported."""
|
||||
assert get_accessory(None, State('demo.demo', 'on'), aid=2, config=None) \
|
||||
is None
|
||||
assert caplog.records[3].levelname == 'WARNING'
|
||||
|
||||
|
||||
class TestGetAccessories(unittest.TestCase):
|
||||
|
||||
@@ -57,19 +57,21 @@ class TestHomekitLights(unittest.TestCase):
|
||||
self.assertEqual(acc.char_on.value, 0)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_on.set_value(True)
|
||||
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)
|
||||
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.states.set(entity_id, STATE_ON)
|
||||
self.hass.block_till_done()
|
||||
|
||||
acc.char_on.set_value(0)
|
||||
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)
|
||||
|
||||
self.hass.states.set(entity_id, STATE_OFF)
|
||||
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)
|
||||
@@ -95,15 +97,27 @@ class TestHomekitLights(unittest.TestCase):
|
||||
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_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_BRIGHTNESS_PCT: 20})
|
||||
|
||||
acc.char_on.set_value(1)
|
||||
acc.char_brightness.set_value(40)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(self.events[1].data[ATTR_DOMAIN], DOMAIN)
|
||||
self.assertEqual(self.events[1].data[ATTR_SERVICE], SERVICE_TURN_ON)
|
||||
self.assertEqual(
|
||||
self.events[1].data[ATTR_SERVICE_DATA], {
|
||||
ATTR_ENTITY_ID: entity_id, ATTR_BRIGHTNESS_PCT: 40})
|
||||
|
||||
acc.char_on.set_value(1)
|
||||
acc.char_brightness.set_value(0)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(self.events[2].data[ATTR_DOMAIN], DOMAIN)
|
||||
self.assertEqual(self.events[2].data[ATTR_SERVICE], SERVICE_TURN_OFF)
|
||||
|
||||
def test_light_rgb_color(self):
|
||||
"""Test light with rgb_color."""
|
||||
entity_id = 'light.demo'
|
||||
@@ -123,10 +137,8 @@ class TestHomekitLights(unittest.TestCase):
|
||||
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_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_HS_COLOR: (145, 75)})
|
||||
|
||||
@@ -102,3 +102,19 @@ class TestHomekitSecuritySystems(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE_DATA][ATTR_CODE], '1234')
|
||||
self.assertEqual(acc.char_target_state.value, 3)
|
||||
|
||||
def test_no_alarm_code(self):
|
||||
"""Test accessory if security_system doesn't require a alarm_code."""
|
||||
acp = 'alarm_control_panel.test'
|
||||
|
||||
acc = SecuritySystem(self.hass, acp, 'SecuritySystem',
|
||||
alarm_code=None, aid=2)
|
||||
acc.run()
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_target_state.set_value(0)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(
|
||||
self.events[0].data[ATTR_SERVICE], 'alarm_arm_home')
|
||||
self.assertNotIn(ATTR_CODE, self.events[0].data[ATTR_SERVICE_DATA])
|
||||
self.assertEqual(acc.char_target_state.value, 0)
|
||||
|
||||
@@ -3,32 +3,13 @@ import unittest
|
||||
|
||||
from homeassistant.components.homekit.const import PROP_CELSIUS
|
||||
from homeassistant.components.homekit.type_sensors import (
|
||||
TemperatureSensor, HumiditySensor, calc_temperature, calc_humidity)
|
||||
TemperatureSensor, HumiditySensor)
|
||||
from homeassistant.const import (
|
||||
ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
def test_calc_temperature():
|
||||
"""Test if temperature in Celsius is calculated correctly."""
|
||||
assert calc_temperature(STATE_UNKNOWN) is None
|
||||
assert calc_temperature('test') is None
|
||||
|
||||
assert calc_temperature('20') == 20
|
||||
assert calc_temperature('20.12', TEMP_CELSIUS) == 20.12
|
||||
assert calc_temperature('75.2', TEMP_FAHRENHEIT) == 24
|
||||
|
||||
|
||||
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."""
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from homeassistant.components.homekit.type_thermostats import (
|
||||
Thermostat, STATE_OFF)
|
||||
from homeassistant.const import (
|
||||
ATTR_SERVICE, EVENT_CALL_SERVICE, ATTR_SERVICE_DATA,
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS)
|
||||
ATTR_UNIT_OF_MEASUREMENT, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
@@ -238,3 +238,42 @@ class TestHomekitThermostats(unittest.TestCase):
|
||||
self.events[1].data[ATTR_SERVICE_DATA][ATTR_TARGET_TEMP_HIGH],
|
||||
25.0)
|
||||
self.assertEqual(acc.char_cooling_thresh_temp.value, 25.0)
|
||||
|
||||
def test_thermostat_fahrenheit(self):
|
||||
"""Test if accessory and HA are updated accordingly."""
|
||||
climate = 'climate.test'
|
||||
|
||||
acc = Thermostat(self.hass, climate, 'Climate', True)
|
||||
acc.run()
|
||||
|
||||
self.hass.states.set(climate, STATE_AUTO,
|
||||
{ATTR_OPERATION_MODE: STATE_AUTO,
|
||||
ATTR_TARGET_TEMP_HIGH: 75.2,
|
||||
ATTR_TARGET_TEMP_LOW: 68,
|
||||
ATTR_TEMPERATURE: 71.6,
|
||||
ATTR_CURRENT_TEMPERATURE: 73.4,
|
||||
ATTR_UNIT_OF_MEASUREMENT: TEMP_FAHRENHEIT})
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(acc.char_heating_thresh_temp.value, 20.0)
|
||||
self.assertEqual(acc.char_cooling_thresh_temp.value, 24.0)
|
||||
self.assertEqual(acc.char_current_temp.value, 23.0)
|
||||
self.assertEqual(acc.char_target_temp.value, 22.0)
|
||||
self.assertEqual(acc.char_display_units.value, 1)
|
||||
|
||||
# Set from HomeKit
|
||||
acc.char_cooling_thresh_temp.set_value(23)
|
||||
self.hass.block_till_done()
|
||||
service_data = self.events[-1].data[ATTR_SERVICE_DATA]
|
||||
self.assertEqual(service_data[ATTR_TARGET_TEMP_HIGH], 73.4)
|
||||
self.assertEqual(service_data[ATTR_TARGET_TEMP_LOW], 68)
|
||||
|
||||
acc.char_heating_thresh_temp.set_value(22)
|
||||
self.hass.block_till_done()
|
||||
service_data = self.events[-1].data[ATTR_SERVICE_DATA]
|
||||
self.assertEqual(service_data[ATTR_TARGET_TEMP_HIGH], 73.4)
|
||||
self.assertEqual(service_data[ATTR_TARGET_TEMP_LOW], 71.6)
|
||||
|
||||
acc.char_target_temp.set_value(24.0)
|
||||
self.hass.block_till_done()
|
||||
service_data = self.events[-1].data[ATTR_SERVICE_DATA]
|
||||
self.assertEqual(service_data[ATTR_TEMPERATURE], 75.2)
|
||||
|
||||
@@ -7,13 +7,15 @@ from homeassistant.core import callback
|
||||
from homeassistant.components.homekit.accessories import HomeBridge
|
||||
from homeassistant.components.homekit.const import HOMEKIT_NOTIFY_ID
|
||||
from homeassistant.components.homekit.util import (
|
||||
show_setup_message, dismiss_setup_message, ATTR_CODE)
|
||||
show_setup_message, dismiss_setup_message, convert_to_float,
|
||||
temperature_to_homekit, temperature_to_states, ATTR_CODE)
|
||||
from homeassistant.components.homekit.util import validate_entity_config \
|
||||
as vec
|
||||
from homeassistant.components.persistent_notification import (
|
||||
SERVICE_CREATE, SERVICE_DISMISS, ATTR_NOTIFICATION_ID)
|
||||
from homeassistant.const import (
|
||||
EVENT_CALL_SERVICE, ATTR_DOMAIN, ATTR_SERVICE, ATTR_SERVICE_DATA)
|
||||
EVENT_CALL_SERVICE, ATTR_DOMAIN, ATTR_SERVICE, ATTR_SERVICE_DATA,
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT, STATE_UNKNOWN)
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
@@ -81,3 +83,20 @@ class TestUtil(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
data[ATTR_SERVICE_DATA].get(ATTR_NOTIFICATION_ID, None),
|
||||
HOMEKIT_NOTIFY_ID)
|
||||
|
||||
def test_convert_to_float(self):
|
||||
"""Test convert_to_float method."""
|
||||
self.assertEqual(convert_to_float(12), 12)
|
||||
self.assertEqual(convert_to_float(12.4), 12.4)
|
||||
self.assertIsNone(convert_to_float(STATE_UNKNOWN))
|
||||
self.assertIsNone(convert_to_float(None))
|
||||
|
||||
def test_temperature_to_homekit(self):
|
||||
"""Test temperature conversion from HA to HomeKit."""
|
||||
self.assertEqual(temperature_to_homekit(20.46, TEMP_CELSIUS), 20.5)
|
||||
self.assertEqual(temperature_to_homekit(92.1, TEMP_FAHRENHEIT), 33.4)
|
||||
|
||||
def test_temperature_to_states(self):
|
||||
"""Test temperature conversion from HomeKit to HA."""
|
||||
self.assertEqual(temperature_to_states(20, TEMP_CELSIUS), 20.0)
|
||||
self.assertEqual(temperature_to_states(20.2, TEMP_FAHRENHEIT), 68.4)
|
||||
|
||||
@@ -66,6 +66,7 @@ async def test_only_create_no_username(hass):
|
||||
|
||||
async def test_configurator_callback(hass, mock_request):
|
||||
"""."""
|
||||
hass.data[hue.DOMAIN] = {}
|
||||
with patch('aiohue.Bridge.create_user',
|
||||
side_effect=aiohue.LinkButtonNotPressed):
|
||||
await MockBridge(hass).async_setup()
|
||||
|
||||
@@ -18,7 +18,6 @@ async def test_setup_with_multiple_hosts(hass, mock_bridge):
|
||||
assert len(mock_bridge.mock_calls) == 2
|
||||
hosts = sorted(mock_call[1][0] for mock_call in mock_bridge.mock_calls)
|
||||
assert hosts == ['127.0.0.1', '192.168.1.10']
|
||||
assert len(hass.data[hue.DOMAIN]) == 2
|
||||
|
||||
|
||||
async def test_bridge_discovered(hass, mock_bridge):
|
||||
@@ -33,7 +32,6 @@ async def test_bridge_discovered(hass, mock_bridge):
|
||||
|
||||
assert len(mock_bridge.mock_calls) == 1
|
||||
assert mock_bridge.mock_calls[0][1][0] == '192.168.1.10'
|
||||
assert len(hass.data[hue.DOMAIN]) == 1
|
||||
|
||||
|
||||
async def test_bridge_configure_and_discovered(hass, mock_bridge):
|
||||
@@ -48,7 +46,7 @@ async def test_bridge_configure_and_discovered(hass, mock_bridge):
|
||||
|
||||
assert len(mock_bridge.mock_calls) == 1
|
||||
assert mock_bridge.mock_calls[0][1][0] == '192.168.1.10'
|
||||
assert len(hass.data[hue.DOMAIN]) == 1
|
||||
hass.data[hue.DOMAIN] = {'192.168.1.10': {}}
|
||||
|
||||
mock_bridge.reset_mock()
|
||||
|
||||
@@ -59,7 +57,6 @@ async def test_bridge_configure_and_discovered(hass, mock_bridge):
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(mock_bridge.mock_calls) == 0
|
||||
assert len(hass.data[hue.DOMAIN]) == 1
|
||||
|
||||
|
||||
async def test_setup_no_host(hass, aioclient_mock):
|
||||
@@ -71,4 +68,3 @@ async def test_setup_no_host(hass, aioclient_mock):
|
||||
assert result
|
||||
|
||||
assert len(aioclient_mock.mock_calls) == 1
|
||||
assert len(hass.data[hue.DOMAIN]) == 0
|
||||
|
||||
@@ -11,6 +11,7 @@ import pytest
|
||||
|
||||
from homeassistant.components import hue
|
||||
import homeassistant.components.light.hue as hue_light
|
||||
from homeassistant.util import color
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -623,3 +624,59 @@ def test_available():
|
||||
)
|
||||
|
||||
assert light.available is True
|
||||
|
||||
|
||||
def test_hs_color():
|
||||
"""Test hs_color property."""
|
||||
light = hue_light.HueLight(
|
||||
light=Mock(state={
|
||||
'colormode': 'ct',
|
||||
'hue': 1234,
|
||||
'sat': 123,
|
||||
}),
|
||||
request_bridge_update=None,
|
||||
bridge=Mock(),
|
||||
is_group=False,
|
||||
)
|
||||
|
||||
assert light.hs_color is None
|
||||
|
||||
light = hue_light.HueLight(
|
||||
light=Mock(state={
|
||||
'colormode': 'hs',
|
||||
'hue': 1234,
|
||||
'sat': 123,
|
||||
}),
|
||||
request_bridge_update=None,
|
||||
bridge=Mock(),
|
||||
is_group=False,
|
||||
)
|
||||
|
||||
assert light.hs_color == (1234 / 65535 * 360, 123 / 255 * 100)
|
||||
|
||||
light = hue_light.HueLight(
|
||||
light=Mock(state={
|
||||
'colormode': 'xy',
|
||||
'hue': 1234,
|
||||
'sat': 123,
|
||||
}),
|
||||
request_bridge_update=None,
|
||||
bridge=Mock(),
|
||||
is_group=False,
|
||||
)
|
||||
|
||||
assert light.hs_color == (1234 / 65535 * 360, 123 / 255 * 100)
|
||||
|
||||
light = hue_light.HueLight(
|
||||
light=Mock(state={
|
||||
'colormode': 'xy',
|
||||
'hue': None,
|
||||
'sat': 123,
|
||||
'xy': [0.4, 0.5]
|
||||
}),
|
||||
request_bridge_update=None,
|
||||
bridge=Mock(),
|
||||
is_group=False,
|
||||
)
|
||||
|
||||
assert light.hs_color == color.color_xy_to_hs(0.4, 0.5)
|
||||
|
||||
@@ -4,11 +4,10 @@ import unittest
|
||||
from homeassistant.setup import setup_component
|
||||
from homeassistant.components import lock
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
from tests.common import get_test_home_assistant, mock_service
|
||||
FRONT = 'lock.front_door'
|
||||
KITCHEN = 'lock.kitchen_door'
|
||||
OPENABLE_LOCK = 'lock.openable_lock'
|
||||
|
||||
|
||||
class TestLockDemo(unittest.TestCase):
|
||||
@@ -48,3 +47,10 @@ class TestLockDemo(unittest.TestCase):
|
||||
self.hass.block_till_done()
|
||||
|
||||
self.assertFalse(lock.is_locked(self.hass, FRONT))
|
||||
|
||||
def test_opening(self):
|
||||
"""Test the opening of a lock."""
|
||||
calls = mock_service(self.hass, lock.DOMAIN, lock.SERVICE_OPEN)
|
||||
lock.open_lock(self.hass, OPENABLE_LOCK)
|
||||
self.hass.block_till_done()
|
||||
self.assertEqual(1, len(calls))
|
||||
|
||||
@@ -218,6 +218,32 @@ class TestScriptHelper(unittest.TestCase):
|
||||
assert not script_obj.is_running
|
||||
assert len(events) == 2
|
||||
|
||||
def test_delay_invalid_template(self):
|
||||
"""Test the delay as a template that fails."""
|
||||
event = 'test_event'
|
||||
events = []
|
||||
|
||||
@callback
|
||||
def record_event(event):
|
||||
"""Add recorded event to set."""
|
||||
events.append(event)
|
||||
|
||||
self.hass.bus.listen(event, record_event)
|
||||
|
||||
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
|
||||
{'event': event},
|
||||
{'delay': '{{ invalid_delay }}'},
|
||||
{'delay': {'seconds': 5}},
|
||||
{'event': event}]))
|
||||
|
||||
with mock.patch.object(script, '_LOGGER') as mock_logger:
|
||||
script_obj.run()
|
||||
self.hass.block_till_done()
|
||||
assert mock_logger.error.called
|
||||
|
||||
assert not script_obj.is_running
|
||||
assert len(events) == 1
|
||||
|
||||
def test_cancel_while_delay(self):
|
||||
"""Test the cancelling while the delay is present."""
|
||||
event = 'test_event'
|
||||
|
||||
@@ -836,6 +836,12 @@ is_state_attr('device_tracker.phone_2', 'battery', 40)
|
||||
"{{ is_state(trigger.entity_id, 'off') }}",
|
||||
{'trigger': {'entity_id': 'input_boolean.switch'}}))
|
||||
|
||||
self.assertEqual(
|
||||
MATCH_ALL,
|
||||
template.extract_entities(
|
||||
"{{ is_state('media_player.' ~ where , 'playing') }}",
|
||||
{'where': 'livingroom'}))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_state_with_unit(hass):
|
||||
|
||||
@@ -12,6 +12,7 @@ from voluptuous import MultipleInvalid
|
||||
from homeassistant.core import DOMAIN, HomeAssistantError, Config
|
||||
import homeassistant.config as config_util
|
||||
from homeassistant.const import (
|
||||
ATTR_FRIENDLY_NAME, ATTR_HIDDEN, ATTR_ASSUMED_STATE,
|
||||
CONF_LATITUDE, CONF_LONGITUDE, CONF_UNIT_SYSTEM, CONF_NAME,
|
||||
CONF_TIME_ZONE, CONF_ELEVATION, CONF_CUSTOMIZE, __version__,
|
||||
CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_IMPERIAL, CONF_TEMPERATURE_UNIT)
|
||||
@@ -235,6 +236,29 @@ class TestConfig(unittest.TestCase):
|
||||
},
|
||||
})
|
||||
|
||||
def test_customize_dict_schema(self):
|
||||
"""Test basic customize config validation."""
|
||||
values = (
|
||||
{ATTR_FRIENDLY_NAME: None},
|
||||
{ATTR_HIDDEN: '2'},
|
||||
{ATTR_ASSUMED_STATE: '2'},
|
||||
)
|
||||
|
||||
for val in values:
|
||||
print(val)
|
||||
with pytest.raises(MultipleInvalid):
|
||||
config_util.CUSTOMIZE_DICT_SCHEMA(val)
|
||||
|
||||
assert config_util.CUSTOMIZE_DICT_SCHEMA({
|
||||
ATTR_FRIENDLY_NAME: 2,
|
||||
ATTR_HIDDEN: '1',
|
||||
ATTR_ASSUMED_STATE: '0',
|
||||
}) == {
|
||||
ATTR_FRIENDLY_NAME: '2',
|
||||
ATTR_HIDDEN: True,
|
||||
ATTR_ASSUMED_STATE: False
|
||||
}
|
||||
|
||||
def test_customize_glob_is_ordered(self):
|
||||
"""Test that customize_glob preserves order."""
|
||||
conf = config_util.CORE_CONFIG_SCHEMA(
|
||||
@@ -568,6 +592,25 @@ def test_merge(merge_log_err):
|
||||
assert config['wake_on_lan'] is None
|
||||
|
||||
|
||||
def test_merge_try_falsy(merge_log_err):
|
||||
"""Ensure we dont add falsy items like empty OrderedDict() to list."""
|
||||
packages = {
|
||||
'pack_falsy_to_lst': {'automation': OrderedDict()},
|
||||
'pack_list2': {'light': OrderedDict()},
|
||||
}
|
||||
config = {
|
||||
config_util.CONF_CORE: {config_util.CONF_PACKAGES: packages},
|
||||
'automation': {'do': 'something'},
|
||||
'light': {'some': 'light'},
|
||||
}
|
||||
config_util.merge_packages_config(config, packages)
|
||||
|
||||
assert merge_log_err.call_count == 0
|
||||
assert len(config) == 3
|
||||
assert len(config['automation']) == 1
|
||||
assert len(config['light']) == 1
|
||||
|
||||
|
||||
def test_merge_new(merge_log_err):
|
||||
"""Test adding new components to outer scope."""
|
||||
packages = {
|
||||
|
||||
Reference in New Issue
Block a user