From 27865f58f11774a74f5a014e8c54feedb180ef30 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 29 Mar 2018 17:00:16 -0700 Subject: [PATCH 01/12] Bump frontend to 20180330.0 --- homeassistant/components/frontend/__init__.py | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index dad07c87cb6..b2f50148bd3 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -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==20180326.0'] +REQUIREMENTS = ['home-assistant-frontend==20180330.0'] DOMAIN = 'frontend' DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log'] diff --git a/requirements_all.txt b/requirements_all.txt index e30b70eb976..df81dae9f00 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -356,7 +356,7 @@ hipnotify==1.0.8 holidays==0.9.4 # homeassistant.components.frontend -home-assistant-frontend==20180326.0 +home-assistant-frontend==20180330.0 # homeassistant.components.homematicip_cloud homematicip==0.8 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 27d3bd21ad7..33527a913a5 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -81,7 +81,7 @@ hbmqtt==0.9.1 holidays==0.9.4 # homeassistant.components.frontend -home-assistant-frontend==20180326.0 +home-assistant-frontend==20180330.0 # homeassistant.components.influxdb # homeassistant.components.sensor.influxdb From d897a07d0be7c1fd62a838344fef4f1745b72a23 Mon Sep 17 00:00:00 2001 From: Philip Rosenberg-Watt Date: Mon, 26 Mar 2018 19:10:22 -0600 Subject: [PATCH 02/12] 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. --- homeassistant/components/calendar/google.py | 9 ++++++++- tests/components/calendar/test_google.py | 17 +++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/calendar/google.py b/homeassistant/components/calendar/google.py index 098c7c70834..a8763e8ca9e 100644 --- a/homeassistant/components/calendar/google.py +++ b/homeassistant/components/calendar/google.py @@ -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 diff --git a/tests/components/calendar/test_google.py b/tests/components/calendar/test_google.py index 62c8ea8854f..9f94ea9f44c 100644 --- a/tests/components/calendar/test_google.py +++ b/tests/components/calendar/test_google.py @@ -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) From 020669fc6026a4083062debef33ccb4d400ae71c Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Tue, 27 Mar 2018 11:31:18 +0200 Subject: [PATCH 03/12] 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 --- homeassistant/components/homekit/__init__.py | 3 -- .../components/homekit/type_sensors.py | 35 ++++------------ .../components/homekit/type_thermostats.py | 33 ++++++++++----- homeassistant/components/homekit/util.py | 21 +++++++++- .../homekit/test_get_accessories.py | 14 +++++++ tests/components/homekit/test_type_sensors.py | 21 +--------- .../homekit/test_type_thermostats.py | 41 ++++++++++++++++++- tests/components/homekit/test_util.py | 23 ++++++++++- 8 files changed, 126 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index 4854a828e41..8ef8445aa70 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -186,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: diff --git a/homeassistant/components/homekit/type_sensors.py b/homeassistant/components/homekit/type_sensors.py index 7575acb5c35..e980ce4a316 100644 --- a/homeassistant/components/homekit/type_sensors.py +++ b/homeassistant/components/homekit/type_sensors.py @@ -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) diff --git a/homeassistant/components/homekit/type_thermostats.py b/homeassistant/components/homekit/type_thermostats.py index 3f545e90eb3..d49c1ca626b 100644 --- a/homeassistant/components/homekit/type_thermostats.py +++ b/homeassistant/components/homekit/type_thermostats.py @@ -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 @@ -107,33 +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) @@ -142,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) @@ -158,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) @@ -167,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) diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py index f18eb2273db..2fa2ebd396a 100644 --- a/homeassistant/components/homekit/util.py +++ b/homeassistant/components/homekit/util.py @@ -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) diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py index ee7baae2755..e29ed85b5fc 100644 --- a/tests/components/homekit/test_get_accessories.py +++ b/tests/components/homekit/test_get_accessories.py @@ -16,6 +16,20 @@ _LOGGER = logging.getLogger(__name__) CONFIG = {} +def test_get_accessory_invalid_aid(caplog): + """Test with unsupported component.""" + 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 + + +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 + + class TestGetAccessories(unittest.TestCase): """Methods to test the get_accessory method.""" diff --git a/tests/components/homekit/test_type_sensors.py b/tests/components/homekit/test_type_sensors.py index 551dfc6780d..c04c250613d 100644 --- a/tests/components/homekit/test_type_sensors.py +++ b/tests/components/homekit/test_type_sensors.py @@ -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.""" diff --git a/tests/components/homekit/test_type_thermostats.py b/tests/components/homekit/test_type_thermostats.py index 6505bf72efb..011fe73377d 100644 --- a/tests/components/homekit/test_type_thermostats.py +++ b/tests/components/homekit/test_type_thermostats.py @@ -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) diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py index f95db9a4a13..d6ef5856f85 100644 --- a/tests/components/homekit/test_util.py +++ b/tests/components/homekit/test_util.py @@ -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) From e04b01daad3b25335fc9479e1552e44915c9fc7f Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Tue, 27 Mar 2018 04:50:29 +0200 Subject: [PATCH 04/12] Validate basic customize entries (#13478) * Added schema to validate customize dictionary * Added test --- homeassistant/config.py | 13 ++++++++++--- tests/test_config.py | 24 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/homeassistant/config.py b/homeassistant/config.py index 58cfe845e8f..53e611ac725 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -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({ diff --git a/tests/test_config.py b/tests/test_config.py index aaa793f91a9..22fcebc6ea4 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -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( From b0073b437f04510a305286d54e1eeb989bee8518 Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Tue, 27 Mar 2018 23:39:25 +0200 Subject: [PATCH 05/12] Homekit: Fix security systems (#13499) * Fix alarm_code=None * Added test --- .../components/homekit/type_security_systems.py | 4 +++- .../homekit/test_type_security_systems.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index 146fca95b53..b23522f0ea2 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -58,7 +58,9 @@ class SecuritySystem(HomeAccessory): 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): diff --git a/tests/components/homekit/test_type_security_systems.py b/tests/components/homekit/test_type_security_systems.py index 4d61fc4a44c..c689a73bac2 100644 --- a/tests/components/homekit/test_type_security_systems.py +++ b/tests/components/homekit/test_type_security_systems.py @@ -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) From 26fb3d7faa2c4eb485b5450fb5838c41128d66dd Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Thu, 29 Mar 2018 00:55:05 +0200 Subject: [PATCH 06/12] python-miio version bumped (Closes: 13449) (#13511) --- homeassistant/components/fan/xiaomi_miio.py | 2 +- homeassistant/components/light/xiaomi_miio.py | 2 +- homeassistant/components/remote/xiaomi_miio.py | 2 +- homeassistant/components/sensor/xiaomi_miio.py | 2 +- homeassistant/components/switch/xiaomi_miio.py | 2 +- homeassistant/components/vacuum/xiaomi_miio.py | 2 +- requirements_all.txt | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index 09df55200a2..3e6aee6ba3a 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -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'] ATTR_TEMPERATURE = 'temperature' ATTR_HUMIDITY = 'humidity' diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py index a21c86f49c0..999d0f7f0e6 100644 --- a/homeassistant/components/light/xiaomi_miio.py +++ b/homeassistant/components/light/xiaomi_miio.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 'philips.light.candle2']), }) -REQUIREMENTS = ['python-miio==0.3.8'] +REQUIREMENTS = ['python-miio==0.3.9'] # The light does not accept cct values < 1 CCT_MIN = 1 diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py index 91f753391fc..13ec9c873b1 100644 --- a/homeassistant/components/remote/xiaomi_miio.py +++ b/homeassistant/components/remote/xiaomi_miio.py @@ -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'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/xiaomi_miio.py b/homeassistant/components/sensor/xiaomi_miio.py index cb172735ac4..33ba5793fe0 100644 --- a/homeassistant/components/sensor/xiaomi_miio.py +++ b/homeassistant/components/sensor/xiaomi_miio.py @@ -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'] ATTR_POWER = 'power' ATTR_CHARGING = 'charging' diff --git a/homeassistant/components/switch/xiaomi_miio.py b/homeassistant/components/switch/xiaomi_miio.py index 9f0f163df69..27c3c4c72f1 100644 --- a/homeassistant/components/switch/xiaomi_miio.py +++ b/homeassistant/components/switch/xiaomi_miio.py @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 'chuangmi.plug.v2']), }) -REQUIREMENTS = ['python-miio==0.3.8'] +REQUIREMENTS = ['python-miio==0.3.9'] ATTR_POWER = 'power' ATTR_TEMPERATURE = 'temperature' diff --git a/homeassistant/components/vacuum/xiaomi_miio.py b/homeassistant/components/vacuum/xiaomi_miio.py index f42a895f94f..887a50fdcce 100644 --- a/homeassistant/components/vacuum/xiaomi_miio.py +++ b/homeassistant/components/vacuum/xiaomi_miio.py @@ -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'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index df81dae9f00..b900328cab5 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -948,7 +948,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 From e993d095cbff9976f45141944054d39b44ceaf1c Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Fri, 30 Mar 2018 02:10:56 +0200 Subject: [PATCH 07/12] Fix mysensors light supported features (#13512) * Different types of light should have different supported features. --- homeassistant/components/light/mysensors.py | 29 ++++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/light/mysensors.py b/homeassistant/components/light/mysensors.py index 14a770b7632..7aa1e754c43 100644 --- a/homeassistant/components/light/mysensors.py +++ b/homeassistant/components/light/mysensors.py @@ -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() From dfd15900c76de6c286a6a7555cd19ddc5f6f32eb Mon Sep 17 00:00:00 2001 From: Tom Harris Date: Thu, 29 Mar 2018 20:10:27 -0400 Subject: [PATCH 08/12] 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 --- .../components/binary_sensor/insteon_plm.py | 16 +++++----- homeassistant/components/insteon_plm.py | 31 ++++++++++--------- requirements_all.txt | 2 +- 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/homeassistant/components/binary_sensor/insteon_plm.py b/homeassistant/components/binary_sensor/insteon_plm.py index 09c4b5c8ea7..06079d6aa3b 100644 --- a/homeassistant/components/binary_sensor/insteon_plm.py +++ b/homeassistant/components/binary_sensor/insteon_plm.py @@ -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) diff --git a/homeassistant/components/insteon_plm.py b/homeassistant/components/insteon_plm.py index 2381e3db69e..6f5c5223ea0 100644 --- a/homeassistant/components/insteon_plm.py +++ b/homeassistant/components/insteon_plm.py @@ -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'), diff --git a/requirements_all.txt b/requirements_all.txt index b900328cab5..a72d33e35ec 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -417,7 +417,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 From 0428559f694e4ec67061ccc087d13e2bc688c786 Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Thu, 29 Mar 2018 18:35:57 +0200 Subject: [PATCH 09/12] HomeKit: Fix setting light brightness (#13518) * Added test --- .../components/homekit/type_lights.py | 21 +++++--- tests/components/homekit/test_type_lights.py | 50 ++++++++++++------- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index 2415bb1a4df..d88e7100131 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -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,7 +67,7 @@ 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) @@ -83,8 +84,11 @@ class Light(HomeAccessory): _LOGGER.debug('%s: Set brightness to %d', self._entity_id, value) self._flag[CHAR_BRIGHTNESS] = True self.char_brightness.set_value(value, should_callback=False) - self._hass.components.light.turn_on( - self._entity_id, brightness_pct=value) + 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.""" @@ -121,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: diff --git a/tests/components/homekit/test_type_lights.py b/tests/components/homekit/test_type_lights.py index b4d4d5a5945..ee1900fd7c5 100644 --- a/tests/components/homekit/test_type_lights.py +++ b/tests/components/homekit/test_type_lights.py @@ -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)}) From 867010240accca53ad706f9878484af17328f138 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski Date: Fri, 30 Mar 2018 02:12:11 +0200 Subject: [PATCH 10/12] Construct version pinned (#13528) * Construct added to the requirements * requirements_all.txt updated --- homeassistant/components/climate/eq3btsmart.py | 2 +- homeassistant/components/fan/xiaomi_miio.py | 2 +- homeassistant/components/light/xiaomi_miio.py | 2 +- homeassistant/components/remote/xiaomi_miio.py | 2 +- .../components/sensor/eddystone_temperature.py | 2 +- homeassistant/components/sensor/xiaomi_miio.py | 2 +- homeassistant/components/switch/xiaomi_miio.py | 2 +- homeassistant/components/vacuum/xiaomi_miio.py | 2 +- requirements_all.txt | 10 ++++++++++ 9 files changed, 18 insertions(+), 8 deletions(-) diff --git a/homeassistant/components/climate/eq3btsmart.py b/homeassistant/components/climate/eq3btsmart.py index 5c0a3530006..820e715b00d 100644 --- a/homeassistant/components/climate/eq3btsmart.py +++ b/homeassistant/components/climate/eq3btsmart.py @@ -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__) diff --git a/homeassistant/components/fan/xiaomi_miio.py b/homeassistant/components/fan/xiaomi_miio.py index 3e6aee6ba3a..4df85711cfd 100644 --- a/homeassistant/components/fan/xiaomi_miio.py +++ b/homeassistant/components/fan/xiaomi_miio.py @@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -REQUIREMENTS = ['python-miio==0.3.9'] +REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41'] ATTR_TEMPERATURE = 'temperature' ATTR_HUMIDITY = 'humidity' diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py index 999d0f7f0e6..21a27c33203 100644 --- a/homeassistant/components/light/xiaomi_miio.py +++ b/homeassistant/components/light/xiaomi_miio.py @@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 'philips.light.candle2']), }) -REQUIREMENTS = ['python-miio==0.3.9'] +REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41'] # The light does not accept cct values < 1 CCT_MIN = 1 diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py index 13ec9c873b1..b71eb2cb447 100644 --- a/homeassistant/components/remote/xiaomi_miio.py +++ b/homeassistant/components/remote/xiaomi_miio.py @@ -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.9'] +REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/sensor/eddystone_temperature.py b/homeassistant/components/sensor/eddystone_temperature.py index fb5fa2c1fba..06accb26eb6 100644 --- a/homeassistant/components/sensor/eddystone_temperature.py +++ b/homeassistant/components/sensor/eddystone_temperature.py @@ -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__) diff --git a/homeassistant/components/sensor/xiaomi_miio.py b/homeassistant/components/sensor/xiaomi_miio.py index 33ba5793fe0..066dc384007 100644 --- a/homeassistant/components/sensor/xiaomi_miio.py +++ b/homeassistant/components/sensor/xiaomi_miio.py @@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) -REQUIREMENTS = ['python-miio==0.3.9'] +REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41'] ATTR_POWER = 'power' ATTR_CHARGING = 'charging' diff --git a/homeassistant/components/switch/xiaomi_miio.py b/homeassistant/components/switch/xiaomi_miio.py index 27c3c4c72f1..6110b6dc469 100644 --- a/homeassistant/components/switch/xiaomi_miio.py +++ b/homeassistant/components/switch/xiaomi_miio.py @@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ 'chuangmi.plug.v2']), }) -REQUIREMENTS = ['python-miio==0.3.9'] +REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41'] ATTR_POWER = 'power' ATTR_TEMPERATURE = 'temperature' diff --git a/homeassistant/components/vacuum/xiaomi_miio.py b/homeassistant/components/vacuum/xiaomi_miio.py index 887a50fdcce..b2451ed495c 100644 --- a/homeassistant/components/vacuum/xiaomi_miio.py +++ b/homeassistant/components/vacuum/xiaomi_miio.py @@ -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.9'] +REQUIREMENTS = ['python-miio==0.3.9', 'construct==2.9.41'] _LOGGER = logging.getLogger(__name__) diff --git a/requirements_all.txt b/requirements_all.txt index a72d33e35ec..7ac9bd5fd7e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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 From 32b0712089e9c52a6b840dd1d177f96627d39d2b Mon Sep 17 00:00:00 2001 From: Johann Kellerman Date: Fri, 30 Mar 2018 02:13:08 +0200 Subject: [PATCH 11/12] Don't add Falsy items to list #13412 (#13536) --- homeassistant/config.py | 4 ++++ tests/test_config.py | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/homeassistant/config.py b/homeassistant/config.py index 53e611ac725..28936ae12e9 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -554,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 @@ -562,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 diff --git a/tests/test_config.py b/tests/test_config.py index 22fcebc6ea4..652b931366a 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -592,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 = { From f26aff48859ed11a04acd22dfeaf34741d6f5aa2 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Thu, 29 Mar 2018 17:21:48 -0700 Subject: [PATCH 12/12] Version bump to 0.66.0b3 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 58c49289989..ccb75634601 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 66 -PATCH_VERSION = '0.b2' +PATCH_VERSION = '0b3' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3)