Compare commits

...

30 Commits

Author SHA1 Message Date
Paulus Schoutsen
f26aff4885 Version bump to 0.66.0b3 2018-03-29 17:21:48 -07:00
Johann Kellerman
32b0712089 Don't add Falsy items to list #13412 (#13536) 2018-03-29 17:21:28 -07:00
Sebastian Muszynski
867010240a Construct version pinned (#13528)
* Construct added to the requirements

* requirements_all.txt updated
2018-03-29 17:21:28 -07:00
cdce8p
0428559f69 HomeKit: Fix setting light brightness (#13518)
* Added test
2018-03-29 17:21:28 -07:00
Tom Harris
dfd15900c7 Fix Insteon Leak Sensor (#13515)
* update leak sensor

* Fix error when insteon state type is unknown

* Bump insteon version to 0.8.3

* Update requirements all and test

* Fix requirements conflicts due to lack of commit sync

* Requirements sync

* Rerun script/gen_requirements_all.py

* Try requirements update again

* Update requirements
2018-03-29 17:21:27 -07:00
Martin Hjelmare
e993d095cb Fix mysensors light supported features (#13512)
* Different types of light should have different supported features.
2018-03-29 17:21:27 -07:00
Sebastian Muszynski
26fb3d7faa python-miio version bumped (Closes: 13449) (#13511) 2018-03-29 17:21:26 -07:00
cdce8p
b0073b437f Homekit: Fix security systems (#13499)
* Fix alarm_code=None
* Added test
2018-03-29 17:21:26 -07:00
cdce8p
e04b01daad Validate basic customize entries (#13478)
* Added schema to validate customize dictionary

* Added test
2018-03-29 17:21:25 -07:00
cdce8p
020669fc60 Homekit: Bugfix Thermostat Fahrenheit support (#13477)
* Bugfix thermostat temperature conversion
* util -> temperature_to_homekit
* util -> temperature_to_states
* util -> convert_to_float
* Added tests, deleted log msg
2018-03-29 17:21:25 -07:00
Philip Rosenberg-Watt
d897a07d0b Fix Google Calendar caching when offline (#13375)
* Fix Google Calendar caching when offline

Events from Google Calendar were not firing under the following
circumstances:

1. Start ha as normal with Google Calendar configured as per
   instructions.
2. ha loses network connectivity to Google
3. ha attempts update of Google Calendar
4. calendar/google component throws uncaught Exception causing update
   method to not return
5. (cached) Google Calendar event does not fire, remains "Off"

Catching the Exception and returning False from the update() method
causes the correct behavior (i.e., the calendar component firing the
event as scheduled using cached data).

* Add requirements

* Revert code cleanup

* Remove explicit return value from update()

* Revert "Remove explicit return value from update()"

This reverts commit 7cd77708af658ccea855de47a32ce4ac5262ac30.

* Use MockDependency decorator

No need to whitelist google-python-api-client for a single unit test at
this point.
2018-03-29 17:21:24 -07:00
Paulus Schoutsen
27865f58f1 Bump frontend to 20180330.0 2018-03-29 17:00:16 -07:00
Paulus Schoutsen
3639a4470c Use twine for release 2018-03-26 16:16:42 -07:00
Paulus Schoutsen
94d9aa0c5f Bump version to 0.66.0.b2 2018-03-26 16:10:03 -07:00
Paulus Schoutsen
a06eea444a version should contain just 'b' not 'beta' (#13476) 2018-03-26 16:09:18 -07:00
Paulus Schoutsen
ce3a5972c7 Upgrade aiohue and fix race condition (#13475)
* Bump aiohue to 1.3

* Store bridge in hass.data before setting up platform

* Fix tests
2018-03-26 16:09:18 -07:00
Fabian Affolter
f48ce3d437 Fix ID (fixes #13444) (#13471) 2018-03-26 16:09:17 -07:00
Paulus Schoutsen
dfe3219f3f Hue: Convert XY to HS color if HS not present (#13465)
* Hue: Convert XY to HS color if HS not present

* Revert change to test

* Address comments

* Lint
2018-03-26 16:09:17 -07:00
Paulus Schoutsen
a507ed0af8 Version bump to 0.66.0.beta1 2018-03-25 18:24:16 -07:00
Beat
068b037944 Fix encoding errors in mikrotik device tracker (#13464) 2018-03-25 18:23:32 -07:00
Cedric Van Goethem
38d2702e3c Add extra check for ESSID field in case there's a wired connection (#13459) 2018-03-25 18:23:32 -07:00
Paulus Schoutsen
93b9ec0b0f Add version bump script (#13447)
* Add version bump script

* Lint
2018-03-25 18:23:31 -07:00
Anders Melchiorsen
22cefc7e64 Improve detection of entity names in templates (#13432)
* Improve detection of entity names in templates

* Only test variables
2018-03-25 18:23:31 -07:00
cdce8p
60f6109cbf HomeKit: Bugfix & improved logging (#13431)
* Bugfix & improved logging

* Removed logging statements

* Removed logging test
2018-03-25 18:23:30 -07:00
a-andre
24d299e266 Hyperion: fix typo (#13429) 2018-03-25 18:23:30 -07:00
Anders Melchiorsen
444805df10 LimitlessLED hs_color fixes (#13425) 2018-03-25 18:23:30 -07:00
Anders Melchiorsen
a08293cff7 Log invalid templates in script delays (#13423)
* Log invalid templates in script delays

* Abort on error

* Remove unused import
2018-03-25 18:23:29 -07:00
Patrick Hofmann
0d48a8eec6 Security fix & lock for HomeMatic (#11980)
* HomeMatic KeyMatic device become a real lock component

* Adds supported features to lock component.

Locks may are capable to open the door latch.
If component is support it, the SUPPORT_OPENING bitmask can be supplied in the supported_features property.

* hound improvements.

* Travis improvements.

* Improvements from review process

* Simplifies is_locked method

* Adds an openable lock in the lock demo component

* removes blank line

* Adds test for openable demo lock and lint and reviewer improvements.

* adds new line...

* Comment end with a period.

* Additional blank line.

* Mock service based testing, lint fixes

* Update description
2018-03-25 18:23:29 -07:00
Paulus Schoutsen
8a204fd15b Bump frontend to 20180326.0 2018-03-25 18:10:59 -07:00
Paulus Schoutsen
8e14e803cb Fix release script 2018-03-23 14:27:21 -07:00
54 changed files with 744 additions and 201 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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__)

View File

@@ -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:

View File

@@ -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

View File

@@ -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'

View File

@@ -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']

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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):

View File

@@ -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)

View File

@@ -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})

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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'),

View File

@@ -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):

View File

@@ -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]

View File

@@ -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)))

View File

@@ -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()

View File

@@ -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

View File

@@ -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."""

View File

@@ -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

View 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

View File

@@ -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)

View File

@@ -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__)

View File

@@ -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__)

View File

@@ -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'

View File

@@ -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'

View File

@@ -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__)

View File

@@ -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

View File

@@ -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)

View File

@@ -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,

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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
View 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()

View File

@@ -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)

View File

@@ -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):

View File

@@ -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)})

View File

@@ -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)

View File

@@ -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."""

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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'

View File

@@ -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):

View File

@@ -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 = {