diff --git a/.coveragerc b/.coveragerc index 0147c9cc5b6..756c82f755e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -7,6 +7,9 @@ omit = homeassistant/external/* # omit pieces of code that rely on external devices being present + homeassistant/components/arduino.py + homeassistant/components/*/arduino.py + homeassistant/components/wink.py homeassistant/components/*/wink.py @@ -32,13 +35,16 @@ omit = homeassistant/components/light/hue.py homeassistant/components/media_player/cast.py homeassistant/components/media_player/mpd.py + homeassistant/components/notify/file.py homeassistant/components/notify/instapush.py homeassistant/components/notify/nma.py homeassistant/components/notify/pushbullet.py homeassistant/components/notify/pushover.py homeassistant/components/notify/smtp.py + homeassistant/components/notify/syslog.py homeassistant/components/notify/xmpp.py homeassistant/components/sensor/bitcoin.py + homeassistant/components/sensor/forecast.py homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/sabnzbd.py diff --git a/README.md b/README.md index 05fe01340bc..7c6997e4750 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Home Assistant [![Build Status](https://travis-ci.org/balloob/home-assistant.svg?branch=master)](https://travis-ci.org/balloob/home-assistant) [![Coverage Status](https://img.shields.io/coveralls/balloob/home-assistant.svg)](https://coveralls.io/r/balloob/home-assistant?branch=master) +# Home Assistant [![Build Status](https://travis-ci.org/balloob/home-assistant.svg?branch=master)](https://travis-ci.org/balloob/home-assistant) [![Coverage Status](https://img.shields.io/coveralls/balloob/home-assistant.svg)](https://coveralls.io/r/balloob/home-assistant?branch=master) [![Join the chat at https://gitter.im/balloob/home-assistant](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/balloob/home-assistant?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) This is the source code for Home Assistant. For installation instructions, tutorials and the docs, please see [the website](https://home-assistant.io). For a functioning demo frontend of Home Assistant, [click here](https://home-assistant.io/demo/). @@ -8,7 +8,7 @@ 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), 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/) + * Track and control [Google Chromecasts](http://www.google.com/intl/en/chrome/devices/chromecast), [Music Player Daemon](http://www.musicpd.org/) and XBMC/Kodi (http://kodi.tv/) * 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 diff --git a/config/configuration.yaml.example b/config/configuration.yaml.example index c99f760f21f..5acca361a30 100644 --- a/config/configuration.yaml.example +++ b/config/configuration.yaml.example @@ -159,5 +159,5 @@ scene: light.tv_back_light: on light.ceiling: state: on - color: [0.33, 0.66] + xy_color: [0.33, 0.66] brightness: 200 diff --git a/config/custom_components/example.py b/config/custom_components/example.py index dc18aae4b98..5bfb03353e0 100644 --- a/config/custom_components/example.py +++ b/config/custom_components/example.py @@ -8,6 +8,22 @@ Example component to target an entity_id to: - turn it off if all lights are turned off - turn it off if all people leave the house - offer a service to turn it on for 10 seconds + +Configuration: + +To use the Example custom component you will need to add the following to +your config/configuration.yaml + +example: + target: TARGET_ENTITY + +Variable: + +target +*Required +TARGET_ENTITY should be one of your devices that can be turned on and off, +ie a light or a switch. Example value could be light.Ceiling or switch.AC +(if you have these devices with those names). """ import time import logging @@ -31,6 +47,7 @@ CONF_TARGET = 'target' # Name of the service that we expose SERVICE_FLASH = 'flash' +# Shortcut for the logger _LOGGER = logging.getLogger(__name__) diff --git a/config/custom_components/hello_world.py b/config/custom_components/hello_world.py index be1b935c8ad..96d9a788b6b 100644 --- a/config/custom_components/hello_world.py +++ b/config/custom_components/hello_world.py @@ -3,6 +3,14 @@ custom_components.hello_world ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implements the bare minimum that a component should implement. + +Configuration: + +To use the hello_word component you will need to add the following to your +config/configuration.yaml + +hello_world: + """ # The domain of your component. Should be equal to the name of your component diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index ee2ee54df79..f8c595255d9 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -186,6 +186,24 @@ def from_config_file(config_path, hass=None): def enable_logging(hass): """ Setup the logging for home assistant. """ logging.basicConfig(level=logging.INFO) + fmt = ("%(log_color)s%(asctime)s %(levelname)s (%(threadName)s) " + "[%(name)s] %(message)s%(reset)s") + try: + from colorlog import ColoredFormatter + logging.getLogger().handlers[0].setFormatter(ColoredFormatter( + fmt, + datefmt='%y-%m-%d %H:%M:%S', + reset=True, + log_colors={ + 'DEBUG': 'cyan', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'red', + } + )) + except ImportError: + _LOGGER.warn("Colorlog package not found, console coloring disabled") # Log errors to a file if we have write access to file or config dir err_log_path = hass.config.path('home-assistant.log') @@ -202,7 +220,7 @@ def enable_logging(hass): err_handler.setLevel(logging.WARNING) err_handler.setFormatter( logging.Formatter('%(asctime)s %(name)s: %(message)s', - datefmt='%H:%M %d-%m-%y')) + datefmt='%y-%m-%d %H:%M:%S')) logging.getLogger('').addHandler(err_handler) else: diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py new file mode 100644 index 00000000000..e7131f9c9e0 --- /dev/null +++ b/homeassistant/components/arduino.py @@ -0,0 +1,134 @@ +""" +components.arduino +~~~~~~~~~~~~~~~~~~ +Arduino component that connects to a directly attached Arduino board which +runs with the Firmata firmware. + +Configuration: + +To use the Arduino board you will need to add something like the following +to your config/configuration.yaml + +arduino: + port: /dev/ttyACM0 + +Variables: + +port +*Required +The port where is your board connected to your Home Assistant system. +If you are using an original Arduino the port will be named ttyACM*. The exact +number can be determined with 'ls /dev/ttyACM*' or check your 'dmesg'/ +'journalctl -f' output. Keep in mind that Arduino clones are often using a +different name for the port (e.g. '/dev/ttyUSB*'). + +A word of caution: The Arduino is not storing states. This means that with +every initialization the pins are set to off/low. +""" +import logging + +from PyMata.pymata import PyMata +import serial + +from homeassistant.helpers import validate_config +from homeassistant.const import (EVENT_HOMEASSISTANT_START, + EVENT_HOMEASSISTANT_STOP) + +DOMAIN = "arduino" +DEPENDENCIES = [] +BOARD = None +_LOGGER = logging.getLogger(__name__) + + +def setup(hass, config): + """ Setup the Arduino component. """ + + if not validate_config(config, + {DOMAIN: ['port']}, + _LOGGER): + return False + + # pylint: disable=global-statement + global BOARD + try: + BOARD = ArduinoBoard(config[DOMAIN]['port']) + except (serial.serialutil.SerialException, FileNotFoundError): + _LOGGER.exception("Your port is not accessible.") + return False + + if BOARD.get_firmata()[1] <= 2: + _LOGGER.error("The StandardFirmata sketch should be 2.2 or newer.") + return False + + def stop_arduino(event): + """ Stop the Arduino service. """ + BOARD.disconnect() + + def start_arduino(event): + """ Start the Arduino service. """ + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_arduino) + + hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_arduino) + + return True + + +class ArduinoBoard(object): + """ Represents an Arduino board. """ + + def __init__(self, port): + self._port = port + self._board = PyMata(self._port, verbose=False) + + def set_mode(self, pin, direction, mode): + """ Sets the mode and the direction of a given pin. """ + if mode == 'analog' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.INPUT, + self._board.ANALOG) + elif mode == 'analog' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.ANALOG) + elif mode == 'digital' and direction == 'in': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'digital' and direction == 'out': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.DIGITAL) + elif mode == 'pwm': + self._board.set_pin_mode(pin, + self._board.OUTPUT, + self._board.PWM) + + def get_analog_inputs(self): + """ Get the values from the pins. """ + self._board.capability_query() + return self._board.get_analog_response_table() + + def set_digital_out_high(self, pin): + """ Sets a given digital pin to high. """ + self._board.digital_write(pin, 1) + + def set_digital_out_low(self, pin): + """ Sets a given digital pin to low. """ + self._board.digital_write(pin, 0) + + def get_digital_in(self, pin): + """ Gets the value from a given digital pin. """ + self._board.digital_read(pin) + + def get_analog_in(self, pin): + """ Gets the value from a given analog pin. """ + self._board.analog_read(pin) + + def get_firmata(self): + """ Return the version of the Firmata firmware. """ + return self._board.get_firmata_version() + + def disconnect(self): + """ Disconnects the board and closes the serial connection. """ + self._board.reset() + self._board.close() diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 2abae8095d6..07f04dc7aeb 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -93,17 +93,17 @@ def setup(hass, config): # Setup fake device tracker hass.states.set("device_tracker.paulus", "home", {ATTR_ENTITY_PICTURE: - "http://graph.facebook.com/schoutsen/picture"}) + "http://graph.facebook.com/297400035/picture"}) hass.states.set("device_tracker.anne_therese", "not_home", {ATTR_ENTITY_PICTURE: - "http://graph.facebook.com/anne.t.frederiksen/picture"}) + "http://graph.facebook.com/621994601/picture"}) hass.states.set("group.all_devices", "home", { "auto": True, ATTR_ENTITY_ID: [ - "device_tracker.Paulus", - "device_tracker.Anne_Therese" + "device_tracker.paulus", + "device_tracker.anne_therese" ] }) diff --git a/homeassistant/components/device_tracker/tplink.py b/homeassistant/components/device_tracker/tplink.py new file mode 100755 index 00000000000..24d170a5de7 --- /dev/null +++ b/homeassistant/components/device_tracker/tplink.py @@ -0,0 +1,117 @@ +""" +homeassistant.components.device_tracker.tplink +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Device tracker platform that supports scanning a TP-Link router for device +presence. + +Configuration: + +To use the TP-Link tracker you will need to add something like the following +to your config/configuration.yaml + +device_tracker: + platform: tplink + host: YOUR_ROUTER_IP + username: YOUR_ADMIN_USERNAME + password: YOUR_ADMIN_PASSWORD + +Variables: + +host +*Required +The IP address of your router, e.g. 192.168.1.1. + +username +*Required +The username of an user with administrative privileges, usually 'admin'. + +password +*Required +The password for your given admin account. + +""" +import logging +from datetime import timedelta +import re +import threading +import requests + +from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD +from homeassistant.helpers import validate_config +from homeassistant.util import Throttle +from homeassistant.components.device_tracker import DOMAIN + +# Return cached results if last scan was less then this time ago +MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) + +_LOGGER = logging.getLogger(__name__) + + +def get_scanner(hass, config): + """ Validates config and returns a TP-Link scanner. """ + if not validate_config(config, + {DOMAIN: [CONF_HOST, CONF_USERNAME, CONF_PASSWORD]}, + _LOGGER): + return None + + scanner = TplinkDeviceScanner(config[DOMAIN]) + + return scanner if scanner.success_init else None + + +class TplinkDeviceScanner(object): + """ This class queries a wireless router running TP-Link firmware + for connected devices. + """ + + def __init__(self, config): + host = config[CONF_HOST] + username, password = config[CONF_USERNAME], config[CONF_PASSWORD] + + self.parse_macs = re.compile('[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}-' + + '[0-9A-F]{2}-[0-9A-F]{2}-[0-9A-F]{2}') + + self.host = host + self.username = username + self.password = password + + self.last_results = {} + self.lock = threading.Lock() + self.success_init = self._update_info() + + def scan_devices(self): + """ Scans for new devices and return a + list containing found device ids. """ + + self._update_info() + + return self.last_results + + # pylint: disable=no-self-use + def get_device_name(self, device): + """ The TP-Link firmware doesn't save the name of the wireless + device. """ + + return None + + @Throttle(MIN_TIME_BETWEEN_SCANS) + def _update_info(self): + """ Ensures the information from the TP-Link router is up to date. + Returns boolean if scanning successful. """ + + with self.lock: + _LOGGER.info("Loading wireless clients...") + + url = 'http://{}/userRpm/WlanStationRpm.htm'.format(self.host) + referer = 'http://{}'.format(self.host) + page = requests.get(url, auth=(self.username, self.password), + headers={'referer': referer}) + + result = self.parse_macs.findall(page.text) + + if result: + self.last_results = [mac.replace("-", ":") for mac in result] + return True + + return False diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 8ff722e41b2..2892e278c5c 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -20,13 +20,21 @@ INDEX_PATH = os.path.join(os.path.dirname(__file__), 'index.html.template') _LOGGER = logging.getLogger(__name__) +FRONTEND_URLS = [ + URL_ROOT, '/logbook', '/history', '/devService', '/devState', '/devEvent'] +STATES_URL = re.compile(r'/states(/([a-zA-Z\._\-0-9/]+)|)') + + def setup(hass, config): """ Setup serving the frontend. """ if 'http' not in hass.config.components: _LOGGER.error('Dependency http is not loaded') return False - hass.http.register_path('GET', URL_ROOT, _handle_get_root, False) + for url in FRONTEND_URLS: + hass.http.register_path('GET', url, _handle_get_root, False) + + hass.http.register_path('GET', STATES_URL, _handle_get_root, False) # Static files hass.http.register_path( diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index d47eb125209..c388a23c50c 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 = "24f15feebc48785ce908064dccbdb204" +VERSION = "edce0feb9f77dd8b0bbe3c9b1e749fe0" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index 446985ade55..72ed56794b7 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -5646,7 +5646,6 @@ this._removeChildren(); } }); - + + + + + + + + + + + + @@ -16059,185 +14226,13 @@ The `aria-labelledby` attribute will be set to the header element, if one exists - + + + + - - - - @@ -20529,659 +18657,19 @@ iron-selector:not(.narrow-layout) #main ::content [paper-drawer-toggle] { }()); - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -22314,6 +19802,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -25736,9 +23739,9 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN - - - + + + @@ -25768,7 +23771,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN this.brightnessSliderValue = newVal.attributes.brightness; } - this.debounce('more-info-light-animation-finish', function() { + this.async(function() { this.fire('iron-resize'); }.bind(this), 500); }, @@ -25810,8 +23813,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN text-transform: capitalize; } - /* Accent the power button because the user should use that first */ - paper-icon-button[focus] { + paper-icon-button[highlight] { color: var(--accent-color); } @@ -25823,7 +23825,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN transition: max-height .5s ease-in; } - .has-media_volume .volume { + .has-volume_level .volume { max-height: 40px; } @@ -25831,19 +23833,19 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
- +
-