diff --git a/.coveragerc b/.coveragerc index c37e4f47291..370d229d87d 100644 --- a/.coveragerc +++ b/.coveragerc @@ -26,11 +26,13 @@ omit = homeassistant/components/modbus.py homeassistant/components/*/modbus.py + homeassistant/components/tellstick.py homeassistant/components/*/tellstick.py homeassistant/components/tellduslive.py homeassistant/components/*/tellduslive.py + homeassistant/components/vera.py homeassistant/components/*/vera.py homeassistant/components/ecobee.py @@ -57,9 +59,6 @@ omit = homeassistant/components/nest.py homeassistant/components/*/nest.py - homeassistant/components/rfxtrx.py - homeassistant/components/*/rfxtrx.py - homeassistant/components/rpi_gpio.py homeassistant/components/*/rpi_gpio.py @@ -108,9 +107,12 @@ omit = homeassistant/components/media_player/snapcast.py homeassistant/components/media_player/sonos.py homeassistant/components/media_player/squeezebox.py + homeassistant/components/media_player/yamaha.py homeassistant/components/notify/free_mobile.py homeassistant/components/notify/googlevoice.py + homeassistant/components/notify/gntp.py homeassistant/components/notify/instapush.py + homeassistant/components/notify/message_bird.py homeassistant/components/notify/nma.py homeassistant/components/notify/pushbullet.py homeassistant/components/notify/pushetta.py @@ -149,6 +151,7 @@ omit = homeassistant/components/sensor/torque.py homeassistant/components/sensor/transmission.py homeassistant/components/sensor/twitch.py + homeassistant/components/sensor/uber.py homeassistant/components/sensor/worldclock.py homeassistant/components/switch/arest.py homeassistant/components/switch/dlink.py @@ -156,8 +159,10 @@ omit = homeassistant/components/switch/hikvisioncam.py homeassistant/components/switch/mystrom.py homeassistant/components/switch/orvibo.py + homeassistant/components/switch/pulseaudio_loopback.py homeassistant/components/switch/rest.py homeassistant/components/switch/transmission.py + homeassistant/components/switch/wake_on_lan.py homeassistant/components/thermostat/heatmiser.py homeassistant/components/thermostat/homematic.py homeassistant/components/thermostat/proliphix.py diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index c654a8c75e6..3d87814ac8b 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,16 +10,15 @@ **Checklist:** -- [ ] Local tests with `tox` run successfully. -- [ ] TravisCI does not fail. **Your PR cannot be merged unless CI is green!** -- [ ] [Fork is up to date][fork] and was rebased on the `dev` branch before creating the PR. -- [ ] Commits have been [squashed][squash]. -- If code communicates with devices: +If code communicates with devices: + - [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass** - [ ] New dependencies have been added to the `REQUIREMENTS` variable ([example][ex-requir]). - [ ] New dependencies are only imported inside functions that use them ([example][ex-import]). - [ ] New dependencies have been added to `requirements_all.txt` by running `script/gen_requirements_all.py`. - [ ] New files were added to `.coveragerc`. -- If the code does not interact with devices: + +If the code does not interact with devices: + - [ ] Local tests with `tox` run successfully. **Your PR cannot be merged unless tests pass** - [ ] Tests have been added to verify that the new code works. [fork]: http://stackoverflow.com/a/7244456 diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index 65ef2d84cfd..00426e24894 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -83,11 +83,13 @@ def setup(hass, config): hass.http.register_path( 'GET', URL_API_COMPONENTS, _handle_get_api_components) + # /error_log hass.http.register_path('GET', URL_API_ERROR_LOG, _handle_get_api_error_log) hass.http.register_path('POST', URL_API_LOG_OUT, _handle_post_api_log_out) + # /template hass.http.register_path('POST', URL_API_TEMPLATE, _handle_post_api_template) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 31cac543b6c..7a3f635bc49 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -9,8 +9,9 @@ import logging from homeassistant.bootstrap import prepare_setup_platform from homeassistant.const import CONF_PLATFORM from homeassistant.components import logbook -from homeassistant.helpers.service import call_from_config -from homeassistant.helpers.service import validate_service_call +from homeassistant.helpers import extract_domain_configs +from homeassistant.helpers.service import (call_from_config, + validate_service_call) DOMAIN = 'automation' @@ -35,30 +36,17 @@ _LOGGER = logging.getLogger(__name__) def setup(hass, config): """Setup the automation.""" - config_key = DOMAIN - found = 1 + for config_key in extract_domain_configs(config, DOMAIN): + conf = config[config_key] - while config_key in config: - # Check for one block syntax - if isinstance(config[config_key], dict): - config_block = _migrate_old_config(config[config_key]) - name = config_block.get(CONF_ALIAS, config_key) + if not isinstance(conf, list): + conf = [conf] + + for list_no, config_block in enumerate(conf): + name = config_block.get(CONF_ALIAS, "{}, {}".format(config_key, + list_no)) _setup_automation(hass, config_block, name, config) - # Check for multiple block syntax - elif isinstance(config[config_key], list): - for list_no, config_block in enumerate(config[config_key]): - name = config_block.get(CONF_ALIAS, - "{}, {}".format(config_key, list_no)) - _setup_automation(hass, config_block, name, config) - - # Any scalar value is incorrect - else: - _LOGGER.error('Error in config in section %s.', config_key) - - found += 1 - config_key = "{} {}".format(DOMAIN, found) - return True @@ -97,40 +85,6 @@ def _get_action(hass, config, name): return action -def _migrate_old_config(config): - """Migrate old configuration to new.""" - if CONF_PLATFORM not in config: - return config - - _LOGGER.warning( - 'You are using an old configuration format. Please upgrade: ' - 'https://home-assistant.io/components/automation/') - - new_conf = { - CONF_TRIGGER: dict(config), - CONF_CONDITION: config.get('if', []), - CONF_ACTION: dict(config), - } - - for cat, key, new_key in (('trigger', 'mqtt_topic', 'topic'), - ('trigger', 'mqtt_payload', 'payload'), - ('trigger', 'state_entity_id', 'entity_id'), - ('trigger', 'state_before', 'before'), - ('trigger', 'state_after', 'after'), - ('trigger', 'state_to', 'to'), - ('trigger', 'state_from', 'from'), - ('trigger', 'state_hours', 'hours'), - ('trigger', 'state_minutes', 'minutes'), - ('trigger', 'state_seconds', 'seconds'), - ('action', 'execute_service', 'service'), - ('action', 'service_entity_id', 'entity_id'), - ('action', 'service_data', 'data')): - if key in new_conf[cat]: - new_conf[cat][new_key] = new_conf[cat].pop(key) - - return new_conf - - def _process_if(hass, config, p_config, action): """Process if checks.""" cond_type = p_config.get(CONF_CONDITION_TYPE, diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index aae892ea80d..1d17246c012 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -55,8 +55,13 @@ def _check_template(hass, value_template): """Check if result of template is true.""" try: value = template.render(hass, value_template, {}) - except TemplateError: - _LOGGER.exception('Error parsing template') + except TemplateError as ex: + if ex.args and ex.args[0].startswith( + "UndefinedError: 'None' has no attribute"): + # Common during HA startup - so just a warning + _LOGGER.warning(ex) + else: + _LOGGER.error(ex) return False return value.lower() == 'true' diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index fe47a91abb1..777974905b8 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -9,7 +9,8 @@ import logging from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity from homeassistant.const import (STATE_ON, STATE_OFF) -from homeassistant.components import (bloomsky, mysensors, zwave, wemo, wink) +from homeassistant.components import ( + bloomsky, mysensors, zwave, vera, wemo, wink) DOMAIN = 'binary_sensor' SCAN_INTERVAL = 30 @@ -37,6 +38,7 @@ DISCOVERY_PLATFORMS = { bloomsky.DISCOVER_BINARY_SENSORS: 'bloomsky', mysensors.DISCOVER_BINARY_SENSORS: 'mysensors', zwave.DISCOVER_BINARY_SENSORS: 'zwave', + vera.DISCOVER_BINARY_SENSORS: 'vera', wemo.DISCOVER_BINARY_SENSORS: 'wemo', wink.DISCOVER_BINARY_SENSORS: 'wink' } diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index ece9d706646..d952d04d32b 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -6,7 +6,8 @@ https://home-assistant.io/components/binary_sensor.rest/ """ import logging -from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.binary_sensor import (BinarySensorDevice, + SENSOR_CLASSES) from homeassistant.components.sensor.rest import RestData from homeassistant.const import CONF_VALUE_TEMPLATE from homeassistant.helpers import template @@ -19,12 +20,17 @@ DEFAULT_METHOD = 'GET' # pylint: disable=unused-variable def setup_platform(hass, config, add_devices, discovery_info=None): - """Setup REST binary sensors.""" + """Setup the REST binary sensor.""" resource = config.get('resource', None) method = config.get('method', DEFAULT_METHOD) payload = config.get('payload', None) verify_ssl = config.get('verify_ssl', True) + sensor_class = config.get('sensor_class') + if sensor_class not in SENSOR_CLASSES: + _LOGGER.warning('Unknown sensor class: %s', sensor_class) + sensor_class = None + rest = RestData(method, resource, payload, verify_ssl) rest.update() @@ -33,19 +39,23 @@ def setup_platform(hass, config, add_devices, discovery_info=None): return False add_devices([RestBinarySensor( - hass, rest, config.get('name', DEFAULT_NAME), + hass, + rest, + config.get('name', DEFAULT_NAME), + sensor_class, config.get(CONF_VALUE_TEMPLATE))]) # pylint: disable=too-many-arguments class RestBinarySensor(BinarySensorDevice): - """A REST binary sensor.""" + """Representation of a REST binary sensor.""" - def __init__(self, hass, rest, name, value_template): + def __init__(self, hass, rest, name, sensor_class, value_template): """Initialize a REST binary sensor.""" self._hass = hass self.rest = rest self._name = name + self._sensor_class = sensor_class self._state = False self._value_template = value_template self.update() @@ -55,6 +65,11 @@ class RestBinarySensor(BinarySensorDevice): """Return the name of the binary sensor.""" return self._name + @property + def sensor_class(self): + """Return the class of this sensor.""" + return self._sensor_class + @property def is_on(self): """Return true if the binary sensor is on.""" diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index ef33d128bd7..612177533b7 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.template/ import logging from homeassistant.components.binary_sensor import (BinarySensorDevice, - DOMAIN, + ENTITY_ID_FORMAT, SENSOR_CLASSES) from homeassistant.const import ATTR_FRIENDLY_NAME, CONF_VALUE_TEMPLATE from homeassistant.core import EVENT_STATE_CHANGED @@ -16,7 +16,6 @@ from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers import template from homeassistant.util import slugify -ENTITY_ID_FORMAT = DOMAIN + '.{}' CONF_SENSORS = 'sensors' _LOGGER = logging.getLogger(__name__) @@ -76,34 +75,22 @@ class BinarySensorTemplate(BinarySensorDevice): def __init__(self, hass, device, friendly_name, sensor_class, value_template): """Initialize the Template binary sensor.""" - self._hass = hass - self._device = device + self.hass = hass + self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device, + hass=hass) self._name = friendly_name self._sensor_class = sensor_class self._template = value_template self._state = None - self.entity_id = generate_entity_id( - ENTITY_ID_FORMAT, device, - hass=hass) + self.update() - _LOGGER.info('Started template sensor %s', device) - hass.bus.listen(EVENT_STATE_CHANGED, self._event_listener) + def template_bsensor_event_listener(event): + """Called when the target device changes state.""" + self.update_ha_state(True) - def _event_listener(self, event): - if not hasattr(self, 'hass'): - return - self.update_ha_state(True) - - @property - def should_poll(self): - """No polling needed.""" - return False - - @property - def sensor_class(self): - """Return the sensor class of the sensor.""" - return self._sensor_class + hass.bus.listen(EVENT_STATE_CHANGED, + template_bsensor_event_listener) @property def name(self): @@ -115,10 +102,21 @@ class BinarySensorTemplate(BinarySensorDevice): """Return true if sensor is on.""" return self._state + @property + def sensor_class(self): + """Return the sensor class of the sensor.""" + return self._sensor_class + + @property + def should_poll(self): + """No polling needed.""" + return False + def update(self): """Get the latest data and update the state.""" try: - value = template.render(self._hass, self._template) + self._state = template.render(self.hass, + self._template).lower() == 'true' except TemplateError as ex: if ex.args and ex.args[0].startswith( "UndefinedError: 'None' has no attribute"): @@ -126,5 +124,4 @@ class BinarySensorTemplate(BinarySensorDevice): _LOGGER.warning(ex) return _LOGGER.error(ex) - value = 'false' - self._state = value.lower() == 'true' + self._state = False diff --git a/homeassistant/components/binary_sensor/vera.py b/homeassistant/components/binary_sensor/vera.py new file mode 100644 index 00000000000..3f92503dcbf --- /dev/null +++ b/homeassistant/components/binary_sensor/vera.py @@ -0,0 +1,69 @@ +""" +Support for Vera binary sensors. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.vera/ +""" +import logging + +import homeassistant.util.dt as dt_util +from homeassistant.const import ( + ATTR_ARMED, ATTR_BATTERY_LEVEL, ATTR_LAST_TRIP_TIME, ATTR_TRIPPED) +from homeassistant.components.binary_sensor import ( + BinarySensorDevice) +from homeassistant.components.vera import ( + VeraDevice, VERA_DEVICES, VERA_CONTROLLER) + +DEPENDENCIES = ['vera'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices_callback, discovery_info=None): + """Perform the setup for Vera controller devices.""" + add_devices_callback( + VeraBinarySensor(device, VERA_CONTROLLER) + for device in VERA_DEVICES['binary_sensor']) + + +class VeraBinarySensor(VeraDevice, BinarySensorDevice): + """Representation of a Vera Binary Sensor.""" + + def __init__(self, vera_device, controller): + """Initialize the binary_sensor.""" + self._state = False + VeraDevice.__init__(self, vera_device, controller) + + @property + def device_state_attributes(self): + """Return the state attributes.""" + attr = {} + if self.vera_device.has_battery: + attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%' + + if self.vera_device.is_armable: + armed = self.vera_device.is_armed + attr[ATTR_ARMED] = 'True' if armed else 'False' + + if self.vera_device.is_trippable: + last_tripped = self.vera_device.last_trip + if last_tripped is not None: + utc_time = dt_util.utc_from_timestamp(int(last_tripped)) + attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str( + utc_time) + else: + attr[ATTR_LAST_TRIP_TIME] = None + tripped = self.vera_device.is_tripped + attr[ATTR_TRIPPED] = 'True' if tripped else 'False' + + attr['Vera Device Id'] = self.vera_device.vera_device_id + return attr + + @property + def is_on(self): + """Return true if sensor is on.""" + return self._state + + def update(self): + """Get the latest data and update the state.""" + self._state = self.vera_device.is_tripped diff --git a/homeassistant/components/binary_sensor/wemo.py b/homeassistant/components/binary_sensor/wemo.py index f3cd91baa04..0e3259a3a96 100644 --- a/homeassistant/components/binary_sensor/wemo.py +++ b/homeassistant/components/binary_sensor/wemo.py @@ -45,6 +45,9 @@ class WemoBinarySensor(BinarySensorDevice): _LOGGER.info( 'Subscription update for %s', _device) + if not hasattr(self, 'hass'): + self.update() + return self.update_ha_state(True) @property diff --git a/homeassistant/components/binary_sensor/wink.py b/homeassistant/components/binary_sensor/wink.py index 3fe092f6cc8..c24253ce715 100644 --- a/homeassistant/components/binary_sensor/wink.py +++ b/homeassistant/components/binary_sensor/wink.py @@ -10,7 +10,7 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.const import CONF_ACCESS_TOKEN from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['python-wink==0.6.2'] +REQUIREMENTS = ['python-wink==0.6.4'] # These are the available sensors mapped to binary_sensor class SENSOR_TYPES = { @@ -77,6 +77,11 @@ class WinkBinarySensorDevice(BinarySensorDevice, Entity): """Return the name of the sensor if any.""" return self.wink.name() + @property + def available(self): + """True if connection == True.""" + return self.wink.available + def update(self): """Update state of the sensor.""" self.wink.update_state() diff --git a/homeassistant/components/device_tracker/icloud.py b/homeassistant/components/device_tracker/icloud.py index 051cd962945..bcdc88a6f28 100644 --- a/homeassistant/components/device_tracker/icloud.py +++ b/homeassistant/components/device_tracker/icloud.py @@ -12,7 +12,7 @@ from homeassistant.helpers.event import track_utc_time_change _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pyicloud==0.7.2'] +REQUIREMENTS = ['pyicloud==0.8.1'] CONF_INTERVAL = 'interval' DEFAULT_INTERVAL = 8 diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py index f1225d2fb73..6b0cbc5f465 100644 --- a/homeassistant/components/device_tracker/netgear.py +++ b/homeassistant/components/device_tracker/netgear.py @@ -89,4 +89,9 @@ class NetgearDeviceScanner(object): with self.lock: _LOGGER.info("Scanning") - self.last_results = self._api.get_attached_devices() or [] + results = self._api.get_attached_devices() + + if results is None: + _LOGGER.warning('Error scanning devices') + + self.last_results = results or [] diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index deb761b1714..29e5773c9a6 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -24,7 +24,7 @@ _LOGGER = logging.getLogger(__name__) # interval in minutes to exclude devices from a scan while they are home CONF_HOME_INTERVAL = "home_interval" -REQUIREMENTS = ['python-nmap==0.4.3'] +REQUIREMENTS = ['python-nmap==0.6.0'] def get_scanner(hass, config): diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py index b8d202907b0..709312acc0b 100644 --- a/homeassistant/components/device_tracker/owntracks.py +++ b/homeassistant/components/device_tracker/owntracks.py @@ -99,14 +99,11 @@ def setup_scanner(hass, config, see): _LOGGER.info("Added beacon %s", location) else: # Normal region - if not zone.attributes.get('passive'): - kwargs['location_name'] = location - regions = REGIONS_ENTERED[dev_id] if location not in regions: regions.append(location) _LOGGER.info("Enter region %s", location) - _set_gps_from_zone(kwargs, zone) + _set_gps_from_zone(kwargs, location, zone) see(**kwargs) see_beacons(dev_id, kwargs) @@ -121,9 +118,7 @@ def setup_scanner(hass, config, see): if new_region: # Exit to previous region zone = hass.states.get("zone.{}".format(new_region)) - if not zone.attributes.get('passive'): - kwargs['location_name'] = new_region - _set_gps_from_zone(kwargs, zone) + _set_gps_from_zone(kwargs, new_region, zone) _LOGGER.info("Exit to %s", new_region) see(**kwargs) see_beacons(dev_id, kwargs) @@ -184,11 +179,12 @@ def _parse_see_args(topic, data): return dev_id, kwargs -def _set_gps_from_zone(kwargs, zone): +def _set_gps_from_zone(kwargs, location, zone): """Set the see parameters from the zone parameters.""" if zone is not None: kwargs['gps'] = ( zone.attributes['latitude'], zone.attributes['longitude']) kwargs['gps_accuracy'] = zone.attributes['radius'] + kwargs['location_name'] = location return kwargs diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 79a152e5848..2d58c54ac0d 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -15,7 +15,7 @@ from homeassistant.const import ( EVENT_PLATFORM_DISCOVERED) DOMAIN = "discovery" -REQUIREMENTS = ['netdisco==0.5.4'] +REQUIREMENTS = ['netdisco==0.5.5'] SCAN_INTERVAL = 300 # seconds @@ -25,6 +25,7 @@ SERVICE_CAST = 'google_cast' SERVICE_NETGEAR = 'netgear_router' SERVICE_SONOS = 'sonos' SERVICE_PLEX = 'plex_mediaserver' +SERVICE_SQUEEZEBOX = 'logitech_mediaserver' SERVICE_HANDLERS = { SERVICE_WEMO: "wemo", @@ -33,6 +34,7 @@ SERVICE_HANDLERS = { SERVICE_NETGEAR: 'device_tracker', SERVICE_SONOS: 'media_player', SERVICE_PLEX: 'media_player', + SERVICE_SQUEEZEBOX: 'media_player', } diff --git a/homeassistant/components/frontend/mdi_version.py b/homeassistant/components/frontend/mdi_version.py index 3da4b20133d..87b533d9673 100644 --- a/homeassistant/components/frontend/mdi_version.py +++ b/homeassistant/components/frontend/mdi_version.py @@ -1,2 +1,2 @@ """DO NOT MODIFY. Auto-generated by update_mdi script.""" -VERSION = "e85dc66e1a0730e44f79ed11501cd79a" +VERSION = "df49e6b7c930eb39b42ff1909712e95e" diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index d1c34c7e7ab..3103cf36027 100644 --- a/homeassistant/components/frontend/version.py +++ b/homeassistant/components/frontend/version.py @@ -1,2 +1,2 @@ """DO NOT MODIFY. Auto-generated by build_frontend script.""" -VERSION = "30bcc0eacc13a2317000824741dc9ac0" +VERSION = "49974cb3bb443751f7548e4e3b353304" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index f7e6a8da0bf..76db8ee085d 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -1,7 +1,7 @@ -