Merge remote-tracking branch 'balloob/dev' into dev

This commit is contained in:
Ryan Kraus 2015-11-15 17:37:57 -05:00
commit 01daac066a
20 changed files with 391 additions and 71 deletions

View File

@ -84,6 +84,7 @@ omit =
homeassistant/components/sensor/glances.py homeassistant/components/sensor/glances.py
homeassistant/components/sensor/mysensors.py homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/openweathermap.py homeassistant/components/sensor/openweathermap.py
homeassistant/components/switch/orvibo.py
homeassistant/components/sensor/rest.py homeassistant/components/sensor/rest.py
homeassistant/components/sensor/rpi_gpio.py homeassistant/components/sensor/rpi_gpio.py
homeassistant/components/sensor/sabnzbd.py homeassistant/components/sensor/sabnzbd.py

View File

@ -9,11 +9,12 @@ After bootstrapping you can add your own components or
start by calling homeassistant.start_home_assistant(bus) start by calling homeassistant.start_home_assistant(bus)
""" """
import os from collections import defaultdict
import sys
import logging import logging
import logging.handlers import logging.handlers
from collections import defaultdict import os
import shutil
import sys
import homeassistant.core as core import homeassistant.core as core
import homeassistant.util.dt as date_util import homeassistant.util.dt as date_util
@ -25,7 +26,7 @@ import homeassistant.components as core_components
import homeassistant.components.group as group import homeassistant.components.group as group
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.const import ( from homeassistant.const import (
EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE, __version__, EVENT_COMPONENT_LOADED, CONF_LATITUDE, CONF_LONGITUDE,
CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, CONF_CUSTOMIZE, CONF_TEMPERATURE_UNIT, CONF_NAME, CONF_TIME_ZONE, CONF_CUSTOMIZE,
TEMP_CELCIUS, TEMP_FAHRENHEIT) TEMP_CELCIUS, TEMP_FAHRENHEIT)
@ -168,6 +169,7 @@ def from_config_dict(config, hass=None, config_dir=None, enable_log=True,
hass.config.config_dir = config_dir hass.config.config_dir = config_dir
mount_local_lib_path(config_dir) mount_local_lib_path(config_dir)
process_ha_config_upgrade(hass)
process_ha_core_config(hass, config.get(core.DOMAIN, {})) process_ha_core_config(hass, config.get(core.DOMAIN, {}))
if enable_log: if enable_log:
@ -281,6 +283,31 @@ def enable_logging(hass, verbose=False, daemon=False, log_rotate_days=None):
'Unable to setup error log %s (access denied)', err_log_path) 'Unable to setup error log %s (access denied)', err_log_path)
def process_ha_config_upgrade(hass):
""" Upgrade config if necessary. """
version_path = hass.config.path('.HA_VERSION')
try:
with open(version_path, 'rt') as inp:
conf_version = inp.readline().strip()
except FileNotFoundError:
# Last version to not have this file
conf_version = '0.7.7'
if conf_version == __version__:
return
_LOGGER.info('Upgrading config directory from %s to %s', conf_version,
__version__)
lib_path = hass.config.path('lib')
if os.path.isdir(lib_path):
shutil.rmtree(lib_path)
with open(version_path, 'wt') as outp:
outp.write(__version__)
def process_ha_core_config(hass, config): def process_ha_core_config(hass, config):
""" Processes the [homeassistant] section from the config. """ """ Processes the [homeassistant] section from the config. """
hac = hass.config hac = hass.config

View File

@ -87,7 +87,10 @@ def setup(hass, config):
if camera: if camera:
response = camera.camera_image() response = camera.camera_image()
handler.wfile.write(response) if response is not None:
handler.wfile.write(response)
else:
handler.send_response(HTTP_NOT_FOUND)
else: else:
handler.send_response(HTTP_NOT_FOUND) handler.send_response(HTTP_NOT_FOUND)
@ -129,7 +132,8 @@ def setup(hass, config):
while True: while True:
img_bytes = camera.camera_image() img_bytes = camera.camera_image()
if img_bytes is None:
continue
headers_str = '\r\n'.join(( headers_str = '\r\n'.join((
'Content-length: {}'.format(len(img_bytes)), 'Content-length: {}'.format(len(img_bytes)),
'Content-type: image/jpeg', 'Content-type: image/jpeg',

View File

@ -42,11 +42,19 @@ class GenericCamera(Camera):
def camera_image(self): def camera_image(self):
""" Return a still image reponse from the camera. """ """ Return a still image reponse from the camera. """
if self._username and self._password: if self._username and self._password:
response = requests.get( try:
self._still_image_url, response = requests.get(
auth=HTTPBasicAuth(self._username, self._password)) self._still_image_url,
auth=HTTPBasicAuth(self._username, self._password))
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
else: else:
response = requests.get(self._still_image_url) try:
response = requests.get(self._still_image_url)
except requests.exceptions.RequestException as error:
_LOGGER.error('Error getting camera image: %s', error)
return None
return response.content return response.content

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """ """ DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "75532015507fd544f46081ec0eeb5004" VERSION = "b75e3c9ebd3de2dae0912a89499127a9"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 8e33f925e171fd279adf0338f5440c1d7160311d Subproject commit 99af263595dbbf057d26bb266101fa1e386442c6

File diff suppressed because one or more lines are too long

View File

@ -10,8 +10,8 @@ from functools import partial
import logging import logging
import os import os
import homeassistant.bootstrap as bootstrap
from homeassistant.config import load_yaml_config_file from homeassistant.config import load_yaml_config_file
from homeassistant.loader import get_component
from homeassistant.helpers import config_per_platform from homeassistant.helpers import config_per_platform
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
@ -45,8 +45,8 @@ def setup(hass, config):
for platform, p_config in config_per_platform(config, DOMAIN, _LOGGER): for platform, p_config in config_per_platform(config, DOMAIN, _LOGGER):
# get platform # get platform
notify_implementation = get_component( notify_implementation = bootstrap.prepare_setup_platform(
'notify.{}'.format(platform)) hass, config, DOMAIN, platform)
if notify_implementation is None: if notify_implementation is None:
_LOGGER.error("Unknown notification service specified.") _LOGGER.error("Unknown notification service specified.")

View File

@ -37,6 +37,7 @@ CONF_EVENT_DATA = "event_data"
CONF_DELAY = "delay" CONF_DELAY = "delay"
ATTR_LAST_ACTION = 'last_action' ATTR_LAST_ACTION = 'last_action'
ATTR_CAN_CANCEL = 'can_cancel'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -113,6 +114,8 @@ class Script(ToggleEntity):
self._cur = -1 self._cur = -1
self._last_action = None self._last_action = None
self._listener = None self._listener = None
self._can_cancel = not any(CONF_DELAY in action for action
in self.sequence)
@property @property
def should_poll(self): def should_poll(self):
@ -126,7 +129,9 @@ class Script(ToggleEntity):
@property @property
def state_attributes(self): def state_attributes(self):
""" Returns the state attributes. """ """ Returns the state attributes. """
attrs = {} attrs = {
ATTR_CAN_CANCEL: self._can_cancel
}
if self._last_action: if self._last_action:
attrs[ATTR_LAST_ACTION] = self._last_action attrs[ATTR_LAST_ACTION] = self._last_action

View File

@ -70,6 +70,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL: elif value.command_class == zwave.COMMAND_CLASS_SENSOR_MULTILEVEL:
add_devices([ZWaveMultilevelSensor(value)]) add_devices([ZWaveMultilevelSensor(value)])
elif (value.command_class == zwave.COMMAND_CLASS_METER and
value.type == zwave.TYPE_DECIMAL):
add_devices([ZWaveMultilevelSensor(value)])
class ZWaveSensor(Entity): class ZWaveSensor(Entity):
""" Represents a Z-Wave sensor. """ """ Represents a Z-Wave sensor. """

View File

@ -16,7 +16,8 @@ from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import ( from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID) STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.components import group, discovery, wink, isy994, verisure from homeassistant.components import (
group, discovery, wink, isy994, verisure, zwave)
DOMAIN = 'switch' DOMAIN = 'switch'
DEPENDENCIES = [] DEPENDENCIES = []
@ -38,7 +39,8 @@ DISCOVERY_PLATFORMS = {
discovery.SERVICE_WEMO: 'wemo', discovery.SERVICE_WEMO: 'wemo',
wink.DISCOVER_SWITCHES: 'wink', wink.DISCOVER_SWITCHES: 'wink',
isy994.DISCOVER_SWITCHES: 'isy994', isy994.DISCOVER_SWITCHES: 'isy994',
verisure.DISCOVER_SWITCHES: 'verisure' verisure.DISCOVER_SWITCHES: 'verisure',
zwave.DISCOVER_SWITCHES: 'zwave',
} }
PROP_TO_ATTR = { PROP_TO_ATTR = {

View File

@ -0,0 +1,75 @@
"""
homeassistant.components.switch.orvibo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for Orvibo S20 Wifi Smart Switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.orvibo/
"""
import logging
from homeassistant.components.switch import SwitchDevice
from orvibo.s20 import S20, S20Exception
DEFAULT_NAME = "Orvibo S20 Switch"
REQUIREMENTS = ['orvibo==1.0.0']
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Find and return S20 switches. """
if config.get('host') is None:
_LOGGER.error("Missing required variable: host")
return
try:
s20 = S20(config.get('host'))
add_devices_callback([S20Switch(config.get('name', DEFAULT_NAME),
s20)])
except S20Exception:
_LOGGER.exception("S20 couldn't be initialized")
class S20Switch(SwitchDevice):
""" Represents an S20 switch. """
def __init__(self, name, s20):
self._name = name
self._s20 = s20
self._state = False
@property
def should_poll(self):
""" Poll. """
return True
@property
def name(self):
""" The name of the switch. """
return self._name
@property
def is_on(self):
""" True if device is on. """
return self._state
def update(self):
""" Update device state. """
try:
self._state = self._s20.on
except S20Exception:
_LOGGER.exception("Error while fetching S20 state")
def turn_on(self, **kwargs):
""" Turn the device on. """
try:
self._s20.on = True
except S20Exception:
_LOGGER.exception("Error while turning on S20")
def turn_off(self, **kwargs):
""" Turn the device off. """
try:
self._s20.on = False
except S20Exception:
_LOGGER.exception("Error while turning off S20")

View File

@ -0,0 +1,76 @@
"""
homeassistant.components.switch.zwave
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Zwave platform that handles simple binary switches.
"""
# pylint: disable=import-error
from openzwave.network import ZWaveNetwork
from pydispatch import dispatcher
import homeassistant.components.zwave as zwave
from homeassistant.components.switch import SwitchDevice
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Find and return demo switches. """
if discovery_info is None:
return
node = zwave.NETWORK.nodes[discovery_info[zwave.ATTR_NODE_ID]]
value = node.values[discovery_info[zwave.ATTR_VALUE_ID]]
if value.command_class != zwave.COMMAND_CLASS_SWITCH_BINARY:
return
if value.type != zwave.TYPE_BOOL:
return
if value.genre != zwave.GENRE_USER:
return
value.set_change_verified(False)
add_devices([ZwaveSwitch(value)])
class ZwaveSwitch(SwitchDevice):
""" Provides a zwave switch. """
def __init__(self, value):
self._value = value
self._node = value.node
self._state = value.data
dispatcher.connect(
self._value_changed, ZWaveNetwork.SIGNAL_VALUE_CHANGED)
def _value_changed(self, value):
""" Called when a value has changed on the network. """
if self._value.value_id == value.value_id:
self._state = value.data
self.update_ha_state()
@property
def should_poll(self):
""" No polling needed for a demo switch. """
return False
@property
def name(self):
""" Returns the name of the device if any. """
name = self._node.name or "{}".format(self._node.product_name)
return "{}".format(name or self._value.label)
@property
def is_on(self):
""" True if device is on. """
return self._state
def turn_on(self, **kwargs):
""" Turn the device on. """
self._node.set_switch(self._value.value_id, True)
def turn_off(self, **kwargs):
""" Turn the device off. """
self._node.set_switch(self._value.value_id, False)

View File

@ -0,0 +1,57 @@
"""
homeassistant.components.sensor.updater
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Sensor that checks for available updates.
For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/sensor.updater/
"""
import logging
import requests
from homeassistant.const import __version__ as CURRENT_VERSION
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.helpers import event
_LOGGER = logging.getLogger(__name__)
PYPI_URL = 'https://pypi.python.org/pypi/homeassistant/json'
DEPENDENCIES = []
DOMAIN = 'updater'
ENTITY_ID = 'updater.updater'
def setup(hass, config):
''' setup the updater component '''
def check_newest_version(_=None):
''' check if a new version is available and report if one is '''
newest = get_newest_version()
if newest != CURRENT_VERSION and newest is not None:
hass.states.set(
ENTITY_ID, newest, {ATTR_FRIENDLY_NAME: 'Update Available'})
event.track_time_change(hass, check_newest_version,
hour=[0, 12], minute=0, second=0)
check_newest_version()
return True
def get_newest_version():
''' Get the newest HA version form PyPI '''
try:
req = requests.get(PYPI_URL)
return req.json()['info']['version']
except requests.RequestException:
_LOGGER.exception('Could not contact PyPI to check for updates')
return
except ValueError:
_LOGGER.exception('Received invalid response from PyPI')
return
except KeyError:
_LOGGER.exception('Response from PyPI did not include version')
return

View File

@ -20,13 +20,18 @@ REQUIREMENTS = ['pydispatcher==2.0.5']
CONF_USB_STICK_PATH = "usb_path" CONF_USB_STICK_PATH = "usb_path"
DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick" DEFAULT_CONF_USB_STICK_PATH = "/zwaveusbstick"
CONF_DEBUG = "debug" CONF_DEBUG = "debug"
CONF_POLLING_INTERVAL = "polling_interval"
DISCOVER_SENSORS = "zwave.sensors" DISCOVER_SENSORS = "zwave.sensors"
DISCOVER_SWITCHES = "zwave.switch"
DISCOVER_LIGHTS = "zwave.light" DISCOVER_LIGHTS = "zwave.light"
COMMAND_CLASS_SWITCH_MULTILEVEL = 38 COMMAND_CLASS_SWITCH_MULTILEVEL = 38
COMMAND_CLASS_SWITCH_BINARY = 37
COMMAND_CLASS_SENSOR_BINARY = 48 COMMAND_CLASS_SENSOR_BINARY = 48
COMMAND_CLASS_SENSOR_MULTILEVEL = 49 COMMAND_CLASS_SENSOR_MULTILEVEL = 49
COMMAND_CLASS_METER = 50
COMMAND_CLASS_BATTERY = 128 COMMAND_CLASS_BATTERY = 128
GENRE_WHATEVER = None GENRE_WHATEVER = None
@ -35,20 +40,28 @@ GENRE_USER = "User"
TYPE_WHATEVER = None TYPE_WHATEVER = None
TYPE_BYTE = "Byte" TYPE_BYTE = "Byte"
TYPE_BOOL = "Bool" TYPE_BOOL = "Bool"
TYPE_DECIMAL = "Decimal"
# list of tuple (DOMAIN, discovered service, supported command # list of tuple (DOMAIN, discovered service, supported command
# classes, value type) # classes, value type)
DISCOVERY_COMPONENTS = [ DISCOVERY_COMPONENTS = [
('sensor', ('sensor',
DISCOVER_SENSORS, DISCOVER_SENSORS,
[COMMAND_CLASS_SENSOR_BINARY, COMMAND_CLASS_SENSOR_MULTILEVEL], [COMMAND_CLASS_SENSOR_BINARY,
COMMAND_CLASS_SENSOR_MULTILEVEL,
COMMAND_CLASS_METER],
TYPE_WHATEVER, TYPE_WHATEVER,
GENRE_WHATEVER), GENRE_USER),
('light', ('light',
DISCOVER_LIGHTS, DISCOVER_LIGHTS,
[COMMAND_CLASS_SWITCH_MULTILEVEL], [COMMAND_CLASS_SWITCH_MULTILEVEL],
TYPE_BYTE, TYPE_BYTE,
GENRE_USER), GENRE_USER),
('switch',
DISCOVER_SWITCHES,
[COMMAND_CLASS_SWITCH_BINARY],
TYPE_BOOL,
GENRE_USER),
] ]
ATTR_NODE_ID = "node_id" ATTR_NODE_ID = "node_id"
@ -165,6 +178,10 @@ def setup(hass, config):
""" Called when Home Assistant starts up. """ """ Called when Home Assistant starts up. """
NETWORK.start() NETWORK.start()
polling_interval = config[DOMAIN].get(CONF_POLLING_INTERVAL, None)
if polling_interval is not None:
NETWORK.setPollInterval(polling_interval)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zwave) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zwave)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave) hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_zwave)

View File

@ -29,6 +29,7 @@ def install_package(package, upgrade=True, target=None):
try: try:
return 0 == subprocess.call(args) return 0 == subprocess.call(args)
except subprocess.SubprocessError: except subprocess.SubprocessError:
_LOGGER.exception('Unable to install pacakge %s', package)
return False return False

View File

@ -156,3 +156,6 @@ evohomeclient==0.2.3
# Pushetta (notify.pushetta) # Pushetta (notify.pushetta)
pushetta==1.0.15 pushetta==1.0.15
# Orvibo S10
orvibo==1.0.0

View File

@ -88,6 +88,8 @@ class TestScript(unittest.TestCase):
self.assertEqual(1, len(calls)) self.assertEqual(1, len(calls))
self.assertEqual('world', calls[0].data.get('hello')) self.assertEqual('world', calls[0].data.get('hello'))
self.assertEqual(
True, self.hass.states.get(ENTITY_ID).attributes.get('can_cancel'))
def test_calling_service_old(self): def test_calling_service_old(self):
calls = [] calls = []
@ -172,6 +174,9 @@ class TestScript(unittest.TestCase):
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertTrue(script.is_on(self.hass, ENTITY_ID)) self.assertTrue(script.is_on(self.hass, ENTITY_ID))
self.assertEqual(
False,
self.hass.states.get(ENTITY_ID).attributes.get('can_cancel'))
self.assertEqual( self.assertEqual(
event, event,

View File

@ -5,11 +5,13 @@ tests.test_bootstrap
Tests bootstrap. Tests bootstrap.
""" """
# pylint: disable=too-many-public-methods,protected-access # pylint: disable=too-many-public-methods,protected-access
import os
import tempfile import tempfile
import unittest import unittest
from unittest import mock from unittest import mock
from homeassistant import bootstrap from homeassistant import core, bootstrap
from homeassistant.const import __version__
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
from tests.common import mock_detect_location_info from tests.common import mock_detect_location_info
@ -39,3 +41,45 @@ class TestBootstrap(unittest.TestCase):
self.assertEqual(sorted(components), self.assertEqual(sorted(components),
sorted(hass.config.components)) sorted(hass.config.components))
def test_remove_lib_on_upgrade(self):
with tempfile.TemporaryDirectory() as config_dir:
version_path = os.path.join(config_dir, '.HA_VERSION')
lib_dir = os.path.join(config_dir, 'lib')
check_file = os.path.join(lib_dir, 'check')
with open(version_path, 'wt') as outp:
outp.write('0.7.0')
os.mkdir(lib_dir)
with open(check_file, 'w'):
pass
hass = core.HomeAssistant()
hass.config.config_dir = config_dir
self.assertTrue(os.path.isfile(check_file))
bootstrap.process_ha_config_upgrade(hass)
self.assertFalse(os.path.isfile(check_file))
def test_not_remove_lib_if_not_upgrade(self):
with tempfile.TemporaryDirectory() as config_dir:
version_path = os.path.join(config_dir, '.HA_VERSION')
lib_dir = os.path.join(config_dir, 'lib')
check_file = os.path.join(lib_dir, 'check')
with open(version_path, 'wt') as outp:
outp.write(__version__)
os.mkdir(lib_dir)
with open(check_file, 'w'):
pass
hass = core.HomeAssistant()
hass.config.config_dir = config_dir
bootstrap.process_ha_config_upgrade(hass)
self.assertTrue(os.path.isfile(check_file))