diff --git a/.coveragerc b/.coveragerc index 00e10da8110..2050ac8d606 100644 --- a/.coveragerc +++ b/.coveragerc @@ -41,6 +41,7 @@ omit = homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/sabnzbd.py + homeassistant/components/sensor/swiss_public_transport.py homeassistant/components/sensor/systemmonitor.py homeassistant/components/sensor/time_date.py homeassistant/components/sensor/transmission.py diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48523c1a4fe..22ea990aca2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Adding support for a new device -For help on building your component, please see the See the [developer documentation on home-assistant.io](https://home-assistant.io/developers/). +For help on building your component, please see the [developer documentation](https://home-assistant.io/developers/) on [home-assistant.io](https://home-assistant.io/). After you finish adding support for your device: diff --git a/README.md b/README.md index 5eb5ad4937a..4aadfd32335 100644 --- a/README.md +++ b/README.md @@ -6,18 +6,20 @@ Home Assistant is a home automation platform running on Python 3. The goal of Ho It offers the following functionality through built-in components: - * Track if devices are home by monitoring connected devices to a wireless router (supporting [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), [DD-WRT](http://www.dd-wrt.com/site/index)) - * Track and control [Philips Hue](http://meethue.com) lights - * Track and control [WeMo switches](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) - * Track and control [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast) - * Track running services by monitoring `ps` output - * Track and control [Tellstick devices and sensors](http://www.telldus.se/products/tellstick) + * Track if devices are home by monitoring connected devices to a wireless router (supporting [OpenWrt](https://openwrt.org/), [Tomato](http://www.polarcloud.com/tomato), [Netgear](http://netgear.com), and [DD-WRT](http://www.dd-wrt.com/site/index)) + * Track and control [Philips Hue](http://meethue.com) lights, [WeMo](http://www.belkin.com/us/Products/home-automation/c/wemo-home-automation/) switches, and [Tellstick](http://www.telldus.se/products/tellstick) devices and sensors + * Track and control [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast) and [Music Player Daemon](http://www.musicpd.org/) + * Support for [ISY994](https://www.universal-devices.com/residential/isy994i-series/) (Insteon and X10 devices), [Z-Wave](http://www.z-wave.com/), [Nest Thermostats](https://nest.com/), and [Modbus](http://www.modbus.org/) + * Track running system services and monitoring your system stats (Memory, disk usage, and more) + * Control low-cost 433 MHz remote control wall-socket devices (https://github.com/r10r/rcswitch-pi) and other switches that can be turned on/off with shell commands * Turn on the lights when people get home after sun set * Turn on lights slowly during sun set to compensate for light loss * Turn off all lights and devices when everybody leaves the house * Offers web interface to monitor and control Home Assistant * Offers a [REST API](https://home-assistant.io/developers/api.html) for easy integration with other projects * [Ability to have multiple instances of Home Assistant work together](https://home-assistant.io/developers/architecture.html) + * Allow sending notifications using [Instapush](https://instapush.im), [Notify My Android (NMA)](http://www.notifymyandroid.com/), [PushBullet](https://www.pushbullet.com/), [PushOver](https://pushover.net/), and [Jabber (XMPP)](http://xmpp.org) + * Allow to display details about a running [Transmission](http://www.transmissionbt.com/) client, the [Bitcoin](https://bitcoin.org) network, local meteorological data from [OpenWeatherMap](http://openweathermap.org/), the time, the date, and the downloads from [SABnzbd](http://sabnzbd.org) Home Assistant also includes functionality for controlling HTPCs: diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 787e0f80562..ee2ee54df79 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -235,8 +235,13 @@ def process_ha_core_config(hass, config): set_time_zone(config.get(CONF_TIME_ZONE)) - for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items(): - Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values()) + customize = config.get(CONF_CUSTOMIZE) + + if isinstance(customize, dict): + for entity_id, attrs in config.get(CONF_CUSTOMIZE, {}).items(): + if not isinstance(attrs, dict): + continue + Entity.overwrite_attribute(entity_id, attrs.keys(), attrs.values()) if CONF_TEMPERATURE_UNIT in config: unit = config[CONF_TEMPERATURE_UNIT] diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 5cefed5c5c8..611136aac5b 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -17,7 +17,7 @@ import homeassistant.util.dt as dt_util from homeassistant.const import ( STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_PICTURE, ATTR_FRIENDLY_NAME, - CONF_PLATFORM) + CONF_PLATFORM, DEVICE_DEFAULT_NAME) from homeassistant.components import group DOMAIN = "device_tracker" @@ -169,66 +169,28 @@ class DeviceTracker(object): if not self.lock.acquire(False): return - found_devices = set(dev.upper() for dev in - self.device_scanner.scan_devices()) + try: + found_devices = set(dev.upper() for dev in + self.device_scanner.scan_devices()) - for device in self.tracked: - is_home = device in found_devices + for device in self.tracked: + is_home = device in found_devices - self._update_state(now, device, is_home) + self._update_state(now, device, is_home) - if is_home: - found_devices.remove(device) + if is_home: + found_devices.remove(device) - # Did we find any devices that we didn't know about yet? - new_devices = found_devices - self.untracked_devices + # Did we find any devices that we didn't know about yet? + new_devices = found_devices - self.untracked_devices - if new_devices: - if not self.track_new_devices: - self.untracked_devices.update(new_devices) + if new_devices: + if not self.track_new_devices: + self.untracked_devices.update(new_devices) - # Write new devices to known devices file - if not self.invalid_known_devices_file: - - known_dev_path = self.hass.config.path(KNOWN_DEVICES_FILE) - - try: - # If file does not exist we will write the header too - is_new_file = not os.path.isfile(known_dev_path) - - with open(known_dev_path, 'a') as outp: - _LOGGER.info( - "Found %d new devices, updating %s", - len(new_devices), known_dev_path) - - writer = csv.writer(outp) - - if is_new_file: - writer.writerow(( - "device", "name", "track", "picture")) - - for device in new_devices: - # See if the device scanner knows the name - # else defaults to unknown device - dname = self.device_scanner.get_device_name(device) - name = dname or "unknown device" - - track = 0 - if self.track_new_devices: - self._track_device(device, name) - track = 1 - - writer.writerow((device, name, track, "")) - - if self.track_new_devices: - self._generate_entity_ids(new_devices) - - except IOError: - _LOGGER.exception( - "Error updating %s with %d new devices", - known_dev_path, len(new_devices)) - - self.lock.release() + self._update_known_devices_file(new_devices) + finally: + self.lock.release() # pylint: disable=too-many-branches def _read_known_devices_file(self): @@ -309,6 +271,44 @@ class DeviceTracker(object): finally: self.lock.release() + def _update_known_devices_file(self, new_devices): + """ Add new devices to known devices file. """ + if not self.invalid_known_devices_file: + known_dev_path = self.hass.config.path(KNOWN_DEVICES_FILE) + + try: + # If file does not exist we will write the header too + is_new_file = not os.path.isfile(known_dev_path) + + with open(known_dev_path, 'a') as outp: + _LOGGER.info("Found %d new devices, updating %s", + len(new_devices), known_dev_path) + + writer = csv.writer(outp) + + if is_new_file: + writer.writerow(("device", "name", "track", "picture")) + + for device in new_devices: + # See if the device scanner knows the name + # else defaults to unknown device + name = self.device_scanner.get_device_name(device) or \ + DEVICE_DEFAULT_NAME + + track = 0 + if self.track_new_devices: + self._track_device(device, name) + track = 1 + + writer.writerow((device, name, track, "")) + + if self.track_new_devices: + self._generate_entity_ids(new_devices) + + except IOError: + _LOGGER.exception("Error updating %s with %d new devices", + known_dev_path, len(new_devices)) + def _track_device(self, device, name): """ Add a device to the list of tracked devices. diff --git a/homeassistant/components/frontend/index.html.template b/homeassistant/components/frontend/index.html.template index b9126ed25e2..f84c8653b31 100644 --- a/homeassistant/components/frontend/index.html.template +++ b/homeassistant/components/frontend/index.html.template @@ -15,8 +15,8 @@ - + diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index c419cca2942..4f30d388227 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 = "2d15135e9bfd0ee5b023d9abb79be62d" +VERSION = "ed339673ca129a1a51dcc3975d0a492d" diff --git a/homeassistant/components/frontend/www_static/favicon-apple-180x180.png b/homeassistant/components/frontend/www_static/favicon-apple-180x180.png new file mode 100644 index 00000000000..20117d00f22 Binary files /dev/null and b/homeassistant/components/frontend/www_static/favicon-apple-180x180.png differ diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 7ec670dacf0..14bf41970f0 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -12326,9 +12326,9 @@ window.hass.uiUtil.formatDate = function(dateObj) { --paper-deep-orange-a400: #ff3d00; --paper-deep-orange-a700: #dd2c00; - --paper-brown-50: #795548; - --paper-brown-100: #efebe9; - --paper-brown-200: #d7ccc8; + --paper-brown-50: #efebe9; + --paper-brown-100: #d7ccc8; + --paper-brown-200: #bcaaa4; --paper-brown-300: #a1887f; --paper-brown-400: #8d6e63; --paper-brown-500: #795548; @@ -12569,13 +12569,18 @@ is separate from validation, and `allowed-pattern` does not affect how the input _previousValidInput: { type: String, value: '' + }, + + _patternAlreadyChecked: { + type: Boolean, + value: false } }, listeners: { 'input': '_onInput', - 'keydown': '_onKeydown' + 'keypress': '_onKeypress' }, get _patternRegExp() { @@ -12600,33 +12605,54 @@ is separate from validation, and `allowed-pattern` does not affect how the input _bindValueChanged: function() { if (this.value !== this.bindValue) { - this.value = this.bindValue; + this.value = !this.bindValue ? '' : this.bindValue; } // manually notify because we don't want to notify until after setting value this.fire('bind-value-changed', {value: this.bindValue}); }, _onInput: function() { + // Need to validate each of the characters pasted if they haven't + // been validated inside `_onKeypress` already. + if (this.preventInvalidInput && !this._patternAlreadyChecked) { + var valid = this._checkPatternValidity(); + if (!valid) { + this.value = this._previousValidInput; + } + } + this.bindValue = this.value; + this._previousValidInput = this.value; + this._patternAlreadyChecked = false; }, - _isPrintable: function(keyCode) { - var printable = - (keyCode > 47 && keyCode < 58) || // number keys - keyCode == 32 || keyCode == 13 || // spacebar & return key - (keyCode > 64 && keyCode < 91) || // letter keys - (keyCode > 95 && keyCode < 112) || // numpad keys - (keyCode > 185 && keyCode < 193) || // ;=,-./` (in order) - (keyCode > 218 && keyCode < 223); // [\]' (in order) - return printable; + _isPrintable: function(event) { + // What a control/printable character is varies wildly based on the browser. + // - most control characters (arrows, backspace) do not send a `keypress` event + // in Chrome, but the *do* on Firefox + // - in Firefox, when they do send a `keypress` event, control chars have + // a charCode = 0, keyCode = xx (for ex. 40 for down arrow) + // - printable characters always send a keypress event. + // - in Firefox, printable chars always have a keyCode = 0. In Chrome, the keyCode + // always matches the charCode. + // None of this makes any sense. + + var nonPrintable = + (event.keyCode == 8) || // backspace + (event.keyCode == 19) || // pause + (event.keyCode == 20) || // caps lock + (event.keyCode == 27) || // escape + (event.keyCode == 45) || // insert + (event.keyCode == 46) || // delete + (event.keyCode == 144) || // num lock + (event.keyCode == 145) || // scroll lock + (event.keyCode > 32 && event.keyCode < 41) || // page up/down, end, home, arrows + (event.keyCode > 111 && event.keyCode < 124); // fn keys + + return !(event.charCode == 0 && nonPrintable); }, - // convert printable numpad keys to number keys - _correctNumpadKeys: function(keyCode) { - return (keyCode > 95 && keyCode < 112) ? keyCode - 48 : keyCode; - }, - - _onKeydown: function(event) { + _onKeypress: function(event) { if (!this.preventInvalidInput && this.type !== 'number') { return; } @@ -12634,12 +12660,33 @@ is separate from validation, and `allowed-pattern` does not affect how the input if (!regexp) { return; } - var thisChar = String.fromCharCode(this._correctNumpadKeys(event.keyCode)); - if (this._isPrintable(event.keyCode) && !regexp.test(thisChar)) { + + // Handle special keys and backspace + if (event.metaKey || event.ctrlKey || event.altKey) + return; + + // Check the pattern either here or in `_onInput`, but not in both. + this._patternAlreadyChecked = true; + + var thisChar = String.fromCharCode(event.charCode); + if (this._isPrintable(event) && !regexp.test(thisChar)) { event.preventDefault(); } }, + _checkPatternValidity: function() { + var regexp = this._patternRegExp; + if (!regexp) { + return true; + } + for (var i = 0; i < this.value.length; i++) { + if (!regexp.test(this.value[i])) { + return false; + } + } + return true; + }, + /** * Returns true if `value` is valid. The validator provided in `validator` will be used first, * then any constraints. @@ -12649,7 +12696,7 @@ is separate from validation, and `allowed-pattern` does not affect how the input // Empty, non-required input is valid. if (!this.required && this.value == '') return true; - + var valid; if (this.hasValidator()) { valid = Polymer.IronValidatableBehavior.validate.call(this, this.value); @@ -13660,6 +13707,47 @@ is separate from validation, and `allowed-pattern` does not affect how the input } + - - - - - - -