diff --git a/.coveragerc b/.coveragerc index 2ea0f11a6bd..f0953f49cd5 100644 --- a/.coveragerc +++ b/.coveragerc @@ -24,6 +24,9 @@ omit = homeassistant/components/device_tracker/tomato.py homeassistant/components/device_tracker/netgear.py homeassistant/components/device_tracker/nmap_tracker.py + homeassistant/components/light/vera.py + homeassistant/components/sensor/vera.py + homeassistant/components/switch/vera.py [report] diff --git a/.gitmodules b/.gitmodules index 6e49e76698a..2e43a7d4dd0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "homeassistant/components/frontend/www_static/polymer/home-assistant-js"] path = homeassistant/components/frontend/www_static/polymer/home-assistant-js url = https://github.com/balloob/home-assistant-js.git +[submodule "homeassistant/external/vera"] + path = homeassistant/external/vera + url = https://github.com/jamespcole/home-assistant-vera-api.git diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 94e1bcc805f..8a78f20d485 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -5,8 +5,6 @@ homeassistant.components.automation.event Offers event listening automation rules. """ import logging -import json -from homeassistant.util import convert CONF_EVENT_TYPE = "event_type" CONF_EVENT_DATA = "event_data" @@ -22,7 +20,7 @@ def register(hass, config, action): _LOGGER.error("Missing configuration key %s", CONF_EVENT_TYPE) return False - event_data = convert(config.get(CONF_EVENT_DATA), json.loads, {}) + event_data = config.get(CONF_EVENT_DATA, {}) def handle_event(event): """ Listens for events and calls the action when data matches. """ diff --git a/homeassistant/components/conversation.py b/homeassistant/components/conversation.py new file mode 100644 index 00000000000..bf78e13a094 --- /dev/null +++ b/homeassistant/components/conversation.py @@ -0,0 +1,72 @@ +""" +homeassistant.components.conversation +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Provides functionality to have conversations with Home Assistant. +This is more a proof of concept. +""" +import logging +import re + +import homeassistant +from homeassistant.const import ( + ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF) + +DOMAIN = "conversation" +DEPENDENCIES = [] + +SERVICE_PROCESS = "process" + +ATTR_TEXT = "text" + +REGEX_TURN_COMMAND = re.compile(r'turn (?P(?: |\w)+) (?P\w+)') + + +def setup(hass, config): + """ Registers the process service. """ + logger = logging.getLogger(__name__) + + def process(service): + """ Parses text into commands for Home Assistant. """ + if ATTR_TEXT not in service.data: + logger.error("Received process service call without a text") + return + + text = service.data[ATTR_TEXT].lower() + + match = REGEX_TURN_COMMAND.match(text) + + if not match: + logger.error("Unable to process: %s", text) + return + + name, command = match.groups() + + entity_ids = [ + state.entity_id for state in hass.states.all() + if state.attributes.get(ATTR_FRIENDLY_NAME, "").lower() == name] + + if not entity_ids: + logger.error( + "Could not find entity id %s from text %s", name, text) + return + + if command == 'on': + hass.services.call( + homeassistant.DOMAIN, SERVICE_TURN_ON, { + ATTR_ENTITY_ID: entity_ids, + }, blocking=True) + + elif command == 'off': + hass.services.call( + homeassistant.DOMAIN, SERVICE_TURN_OFF, { + ATTR_ENTITY_ID: entity_ids, + }, blocking=True) + + else: + logger.error( + 'Got unsupported command %s from text %s', command, text) + + hass.services.register(DOMAIN, SERVICE_PROCESS, process) + + return True diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index 8d52210dd23..9c3e699b113 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -10,7 +10,7 @@ import homeassistant as ha import homeassistant.bootstrap as bootstrap import homeassistant.loader as loader from homeassistant.const import ( - CONF_PLATFORM, ATTR_ENTITY_PICTURE, STATE_ON, + CONF_PLATFORM, ATTR_ENTITY_PICTURE, CONF_LATITUDE, CONF_LONGITUDE) DOMAIN = "demo" @@ -52,9 +52,6 @@ def setup(hass, config): group.setup_group(hass, 'living room', [lights[0], lights[1], switches[0]]) group.setup_group(hass, 'bedroom', [lights[2], switches[1]]) - # Setup process - hass.states.set("process.XBMC", STATE_ON) - # Setup device tracker hass.states.set("device_tracker.Paulus", "home", {ATTR_ENTITY_PICTURE: diff --git a/homeassistant/components/device_tracker/nmap_tracker.py b/homeassistant/components/device_tracker/nmap_tracker.py index a4fd5f6cfff..860ba3b45fb 100644 --- a/homeassistant/components/device_tracker/nmap_tracker.py +++ b/homeassistant/components/device_tracker/nmap_tracker.py @@ -1,6 +1,6 @@ """ Supports scanning using nmap. """ import logging -from datetime import timedelta +from datetime import timedelta, datetime import threading from collections import namedtuple import subprocess @@ -11,7 +11,7 @@ from libnmap.parser import NmapParser, NmapParserException from homeassistant.const import CONF_HOSTS from homeassistant.helpers import validate_config -from homeassistant.util import Throttle +from homeassistant.util import Throttle, convert from homeassistant.components.device_tracker import DOMAIN # Return cached results if last scan was less then this time ago @@ -19,6 +19,9 @@ MIN_TIME_BETWEEN_SCANS = timedelta(seconds=5) _LOGGER = logging.getLogger(__name__) +# interval in minutes to exclude devices from a scan while they are home +CONF_HOME_INTERVAL = "home_interval" + def get_scanner(hass, config): """ Validates config and returns a Nmap scanner. """ @@ -30,7 +33,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -Device = namedtuple("Device", ["mac", "name"]) +Device = namedtuple("Device", ["mac", "name", "ip", "last_update"]) def _arp(ip_address): @@ -53,6 +56,8 @@ class NmapDeviceScanner(object): self.lock = threading.Lock() self.hosts = config[CONF_HOSTS] + minutes = convert(config.get(CONF_HOME_INTERVAL), int, 0) + self.home_interval = timedelta(minutes=minutes) self.success_init = True self._update_info() @@ -77,6 +82,33 @@ class NmapDeviceScanner(object): else: return None + def _parse_results(self, stdout): + """ Parses results from an nmap scan. + Returns True if successful, False otherwise. """ + try: + results = NmapParser.parse(stdout) + now = datetime.now() + self.last_results = [] + for host in results.hosts: + if host.is_up(): + if host.hostnames: + name = host.hostnames[0] + else: + name = host.ipv4 + if host.mac: + mac = host.mac + else: + mac = _arp(host.ipv4) + if mac: + device = Device(mac, name, host.ipv4, now) + self.last_results.append(device) + _LOGGER.info("nmap scan successful") + return True + except NmapParserException as parse_exc: + _LOGGER.error("failed to parse nmap results: %s", parse_exc.msg) + self.last_results = [] + return False + @Throttle(MIN_TIME_BETWEEN_SCANS) def _update_info(self): """ Scans the network for devices. @@ -87,35 +119,24 @@ class NmapDeviceScanner(object): with self.lock: _LOGGER.info("Scanning") - nmap = NmapProcess(targets=self.hosts, options="-F") + options = "-F" + exclude_targets = set() + if self.home_interval: + now = datetime.now() + for host in self.last_results: + if host.last_update + self.home_interval > now: + exclude_targets.add(host) + if len(exclude_targets) > 0: + target_list = [t.ip for t in exclude_targets] + options += " --exclude {}".format(",".join(target_list)) + + nmap = NmapProcess(targets=self.hosts, options=options) nmap.run() if nmap.rc == 0: - try: - results = NmapParser.parse(nmap.stdout) - self.last_results = [] - for host in results.hosts: - if host.is_up(): - if host.hostnames: - name = host.hostnames[0] - else: - name = host.ipv4 - if host.mac: - mac = host.mac - else: - mac = _arp(host.ipv4) - if mac: - device = Device(mac, name) - self.last_results.append(device) - _LOGGER.info("nmap scan successful") - return True - except NmapParserException as parse_exc: - _LOGGER.error("failed to parse nmap results: %s", - parse_exc.msg) - self.last_results = [] - return False - + if self._parse_results(nmap.stdout): + self.last_results.extend(exclude_targets) else: self.last_results = [] _LOGGER.error(nmap.stderr) diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 3886b2c2581..f809049ea45 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 = "1c265f0f07e6038c2cbb9b277e58b994" +VERSION = "08fb2ffccc72d7bfa0ad3478f2e8cfe7" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index d9a004de623..b63964b7690 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -122,9 +122,11 @@ b.events&&Object.keys(a).length>0&&console.log("[%s] addHostListeners:",this.loc background-color: #039be5; } - + - +
Log Out -
Streaming updates
Developer Tools
+
Streaming updates
Developer Tools
- + diff --git a/homeassistant/components/frontend/www_static/polymer/components/domain-icon.html b/homeassistant/components/frontend/www_static/polymer/components/domain-icon.html index 42be1b32963..ba2074ab7ca 100644 --- a/homeassistant/components/frontend/www_static/polymer/components/domain-icon.html +++ b/homeassistant/components/frontend/www_static/polymer/components/domain-icon.html @@ -1,72 +1,24 @@ - - - - - diff --git a/homeassistant/components/frontend/www_static/polymer/components/entity-list.html b/homeassistant/components/frontend/www_static/polymer/components/entity-list.html index 524507aaa60..e54e03f409a 100644 --- a/homeassistant/components/frontend/www_static/polymer/components/entity-list.html +++ b/homeassistant/components/frontend/www_static/polymer/components/entity-list.html @@ -22,9 +22,9 @@
- diff --git a/homeassistant/components/frontend/www_static/polymer/components/services-list.html b/homeassistant/components/frontend/www_static/polymer/components/services-list.html index 5ffc1192649..cb0c22a80c9 100644 --- a/homeassistant/components/frontend/www_static/polymer/components/services-list.html +++ b/homeassistant/components/frontend/www_static/polymer/components/services-list.html @@ -34,10 +34,12 @@
-