Merge branch 'dev' of https://github.com/balloob/home-assistant into automation_confg_list

This commit is contained in:
Stefan Jonasson 2015-09-19 15:27:46 +02:00
commit e1a7b8f988
30 changed files with 198 additions and 136 deletions

View File

@ -3,11 +3,6 @@ language: python
python: python:
- "3.4" - "3.4"
install: install:
- pip install -r requirements_all.txt - script/bootstrap_server
- pip install flake8 pylint coveralls
script: script:
- flake8 homeassistant - script/cibuild
- pylint homeassistant
- coverage run -m unittest discover tests
after_success:
- coveralls

View File

@ -103,6 +103,10 @@ def get_arguments():
'--uninstall-osx', '--uninstall-osx',
action='store_true', action='store_true',
help='Uninstalls from OS X.') help='Uninstalls from OS X.')
parser.add_argument(
'--restart-osx',
action='store_true',
help='Restarts on OS X.')
if os.name != "nt": if os.name != "nt":
parser.add_argument( parser.add_argument(
'--daemon', '--daemon',
@ -216,6 +220,10 @@ def main():
if args.uninstall_osx: if args.uninstall_osx:
uninstall_osx() uninstall_osx()
return return
if args.restart_osx:
uninstall_osx()
install_osx()
return
# daemon functions # daemon functions
if args.pid_file: if args.pid_file:

View File

@ -27,8 +27,7 @@ def trigger(hass, config, action):
if CONF_AFTER in config: if CONF_AFTER in config:
after = dt_util.parse_time_str(config[CONF_AFTER]) after = dt_util.parse_time_str(config[CONF_AFTER])
if after is None: if after is None:
_LOGGER.error( _error_time(config[CONF_AFTER], CONF_AFTER)
'Received invalid after value: %s', config[CONF_AFTER])
return False return False
hours, minutes, seconds = after.hour, after.minute, after.second hours, minutes, seconds = after.hour, after.minute, after.second
elif (CONF_HOURS in config or CONF_MINUTES in config elif (CONF_HOURS in config or CONF_MINUTES in config
@ -63,27 +62,27 @@ def if_action(hass, config):
CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY) CONF_BEFORE, CONF_AFTER, CONF_WEEKDAY)
return None return None
if before is not None:
before = dt_util.parse_time_str(before)
if before is None:
_error_time(before, CONF_BEFORE)
return None
if after is not None:
after = dt_util.parse_time_str(after)
if after is None:
_error_time(after, CONF_AFTER)
return None
def time_if(): def time_if():
""" Validate time based if-condition """ """ Validate time based if-condition """
now = dt_util.now() now = dt_util.now()
if before is not None: if before is not None and now > now.replace(hour=before.hour,
time = dt_util.parse_time_str(before) minute=before.minute):
if time is None:
return False return False
before_point = now.replace(hour=time.hour, minute=time.minute) if after is not None and now < now.replace(hour=after.hour,
minute=after.minute):
if now > before_point:
return False
if after is not None:
time = dt_util.parse_time_str(after)
if time is None:
return False
after_point = now.replace(hour=time.hour, minute=time.minute)
if now < after_point:
return False return False
if weekday is not None: if weekday is not None:
@ -96,3 +95,11 @@ def if_action(hass, config):
return True return True
return time_if return time_if
def _error_time(value, key):
""" Helper method to print error. """
_LOGGER.error(
"Received invalid value for '%s': %s", key, value)
if isinstance(value, int):
_LOGGER.error('Make sure you wrap time values in quotes')

View File

@ -1,41 +1,31 @@
""" """
Support for Foscam IP Cameras. homeassistant.components.camera.foscam
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This component provides basic support for Foscam IP cameras. This component provides basic support for Foscam IP cameras.
As part of the basic support the following features will be provided: As part of the basic support the following features will be provided:
-MJPEG video streaming -MJPEG video streaming
To use this component, add the following to your config/configuration.yaml: To use this component, add the following to your configuration.yaml file.
camera: camera:
platform: foscam platform: foscam
name: Door Camera name: Door Camera
ip: 192.168.0.123 ip: 192.168.0.123
port: 88 port: 88
username: visitor username: YOUR_USERNAME
password: password password: YOUR_PASSWORD
camera 2: Variables:
name: 'Second Camera'
...
camera 3:
name: 'Camera Three'
...
VARIABLES:
These are the variables for the device_data array:
ip ip
*Required *Required
The IP address of your foscam device The IP address of your Foscam device.
username username
*Required *Required
The username of a visitor or operator of your camera. The username of a visitor or operator of your camera. Oddly admin accounts
Oddly admin accounts don't seem to have access to take snapshots. don't seem to have access to take snapshots.
password password
*Required *Required
@ -49,6 +39,8 @@ port
*Optional *Optional
The port that the camera is running on. The default is 88. The port that the camera is running on. The default is 88.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.foscam.html
""" """
import logging import logging
from homeassistant.helpers import validate_config from homeassistant.helpers import validate_config
@ -72,9 +64,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
class FoscamCamera(Camera): class FoscamCamera(Camera):
""" """ An implementation of a Foscam IP camera. """
An implementation of a Foscam IP camera.
"""
def __init__(self, device_info): def __init__(self, device_info):
super(FoscamCamera, self).__init__() super(FoscamCamera, self).__init__()
@ -94,7 +84,7 @@ class FoscamCamera(Camera):
self._name, self._snap_picture_url) self._name, self._snap_picture_url)
def camera_image(self): def camera_image(self):
""" Return a still image reponse from the camera """ """ Return a still image reponse from the camera. """
# send the request to snap a picture # send the request to snap a picture
response = requests.get(self._snap_picture_url) response = requests.get(self._snap_picture_url)
@ -111,5 +101,5 @@ class FoscamCamera(Camera):
@property @property
def name(self): def name(self):
""" Return the name of this device """ """ Return the name of this device. """
return self._name return self._name

View File

@ -28,6 +28,14 @@ REQUIREMENTS = ['SoCo==0.11.1']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# The soco library is excessively chatty when it comes to logging and
# causes a LOT of spam in the logs due to making a http connection to each
# speaker every 10 seconds. Quiet it down a bit to just actual problems.
_SOCO_LOGGER = logging.getLogger('soco')
_SOCO_LOGGER.setLevel(logging.ERROR)
_REQUESTS_LOGGER = logging.getLogger('requests')
_REQUESTS_LOGGER.setLevel(logging.ERROR)
SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\ SUPPORT_SONOS = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK

View File

@ -256,7 +256,7 @@ class Recorder(threading.Thread):
""" Query the database. """ """ Query the database. """
try: try:
with self.conn, self.lock: with self.conn, self.lock:
_LOGGER.info("Running query %s", sql_query) _LOGGER.debug("Running query %s", sql_query)
cur = self.conn.cursor() cur = self.conn.cursor()

View File

@ -70,7 +70,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
data, data,
config.get('name', DEFAULT_NAME), config.get('name', DEFAULT_NAME),
config.get('unit_of_measurement'), config.get('unit_of_measurement'),
config.get('correction_factor', None), config.get('correction_factor', 1.0),
config.get('decimal_places', 0) config.get('decimal_places', 0)
)]) )])
@ -108,12 +108,15 @@ class CommandSensor(Entity):
self.data.update() self.data.update()
value = self.data.value value = self.data.value
try:
if value is not None: if value is not None:
if self._corr_factor is not None: if self._corr_factor is not None:
self._state = round((int(value) * self._corr_factor), self._state = round((float(value) * self._corr_factor),
self._decimal_places) self._decimal_places)
else: else:
self._state = value self._state = value
except ValueError:
self._state = value
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods

View File

@ -3,7 +3,6 @@ homeassistant.components.sensor.glances
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Gathers system information of hosts which running glances. Gathers system information of hosts which running glances.
Configuration: Configuration:
To use the glances sensor you will need to add something like the following To use the glances sensor you will need to add something like the following

View File

@ -91,7 +91,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error( _LOGGER.error(
"Connection error " "Connection error "
"Please check your settings for OpenWeatherMap.") "Please check your settings for OpenWeatherMap.")
return None return False
data = WeatherData(owm, forecast, hass.config.latitude, data = WeatherData(owm, forecast, hass.config.latitude,
hass.config.longitude) hass.config.longitude)

View File

@ -31,7 +31,7 @@ Details for the API : http://transport.opendata.ch
""" """
import logging import logging
from datetime import timedelta from datetime import timedelta
from requests import get import requests
from homeassistant.util import Throttle from homeassistant.util import Throttle
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
try: try:
for location in [config.get('from', None), config.get('to', None)]: for location in [config.get('from', None), config.get('to', None)]:
# transport.opendata.ch doesn't play nice with requests.Session # transport.opendata.ch doesn't play nice with requests.Session
result = get(_RESOURCE + 'locations?query=%s' % location, result = requests.get(_RESOURCE + 'locations?query=%s' % location,
timeout=10) timeout=10)
journey.append(result.json()['stations'][0]['name']) journey.append(result.json()['stations'][0]['name'])
except KeyError: except KeyError:
@ -110,7 +110,7 @@ class PublicTransportData(object):
def update(self): def update(self):
""" Gets the latest data from opendata.ch. """ """ Gets the latest data from opendata.ch. """
response = get( response = requests.get(
_RESOURCE + _RESOURCE +
'connections?' + 'connections?' +
'from=' + self.start + '&' + 'from=' + self.start + '&' +

View File

@ -1,6 +1,6 @@
"""Helpers to install PyPi packages.""" """Helpers to install PyPi packages."""
import os
import logging import logging
import os
import pkg_resources import pkg_resources
import subprocess import subprocess
import sys import sys
@ -15,25 +15,24 @@ def install_package(package, upgrade=True, target=None):
"""Install a package on PyPi. Accepts pip compatible package strings. """Install a package on PyPi. Accepts pip compatible package strings.
Return boolean if install successfull.""" Return boolean if install successfull."""
# Not using 'import pip; pip.main([])' because it breaks the logger # Not using 'import pip; pip.main([])' because it breaks the logger
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
if upgrade:
args.append('--upgrade')
if target:
args += ['--target', os.path.abspath(target)]
with INSTALL_LOCK: with INSTALL_LOCK:
if check_package_exists(package, target): if check_package_exists(package, target):
return True return True
_LOGGER.info('Attempting install of %s', package) _LOGGER.info('Attempting install of %s', package)
args = [sys.executable, '-m', 'pip', 'install', '--quiet', package]
if upgrade:
args.append('--upgrade')
if target:
args += ['--target', os.path.abspath(target)]
try: try:
return 0 == subprocess.call(args) return 0 == subprocess.call(args)
except subprocess.SubprocessError: except subprocess.SubprocessError:
return False return False
def check_package_exists(package, target=None): def check_package_exists(package, target):
"""Check if a package exists. """Check if a package exists.
Returns True when the requirement is met. Returns True when the requirement is met.
Returns False when the package is not installed or doesn't meet req.""" Returns False when the package is not installed or doesn't meet req."""
@ -43,16 +42,5 @@ def check_package_exists(package, target=None):
# This is a zip file # This is a zip file
req = pkg_resources.Requirement.parse(urlparse(package).fragment) req = pkg_resources.Requirement.parse(urlparse(package).fragment)
if target: return any(dist in req for dist in
work_set = pkg_resources.WorkingSet([target]) pkg_resources.find_distributions(target))
search_fun = work_set.find
else:
search_fun = pkg_resources.get_distribution
try:
result = search_fun(req)
except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict):
return False
return bool(result)

9
script/bootstrap Executable file
View File

@ -0,0 +1,9 @@
#!/bin/sh
# script/bootstrap: Resolve all dependencies that the application requires to
# run.
cd "$(dirname "$0")/.."
script/bootstrap_server
script/bootstrap_frontend

5
script/bootstrap_frontend Executable file
View File

@ -0,0 +1,5 @@
echo "Bootstrapping frontend..."
cd homeassistant/components/frontend/www_static/home-assistant-polymer
npm install
npm run setup_js_dev
cd ../../../../..

10
script/bootstrap_server Executable file
View File

@ -0,0 +1,10 @@
cd "$(dirname "$0")/.."
echo "Update the submodule to latest version..."
git submodule update
echo "Installing dependencies..."
python3 -m pip install --upgrade -r requirements_all.txt
echo "Installing development dependencies.."
python3 -m pip install --upgrade flake8 pylint coveralls pytest pytest-cov

View File

@ -1,12 +1,8 @@
# Builds the frontend for production # Builds the frontend for production
# If current pwd is scripts, go 1 up. cd "$(dirname "$0")/.."
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
cd homeassistant/components/frontend/www_static/home-assistant-polymer cd homeassistant/components/frontend/www_static/home-assistant-polymer
npm install
npm run frontend_prod npm run frontend_prod
cp bower_components/webcomponentsjs/webcomponents-lite.min.js .. cp bower_components/webcomponentsjs/webcomponents-lite.min.js ..

View File

@ -3,10 +3,7 @@
# apt-get install cython3 libudev-dev python-sphinx python3-setuptools # apt-get install cython3 libudev-dev python-sphinx python3-setuptools
# pip3 install cython # pip3 install cython
# If current pwd is scripts, go 1 up. cd "$(dirname "$0")/.."
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
if [ ! -d build ]; then if [ ! -d build ]; then
mkdir build mkdir build

7
script/cibuild Executable file
View File

@ -0,0 +1,7 @@
#!/bin/sh
# script/cibuild: Setup environment for CI to run tests. This is primarily
# designed to run on the continuous integration server.
script/test coverage
coveralls

View File

@ -3,10 +3,7 @@
# Optional: pass in a timezone as first argument # Optional: pass in a timezone as first argument
# If not given will attempt to mount /etc/localtime # If not given will attempt to mount /etc/localtime
# If current pwd is scripts, go 1 up. cd "$(dirname "$0")/.."
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
docker build -t home-assistant-dev . docker build -t home-assistant-dev .

View File

@ -1,10 +1,7 @@
# Open a docker that can be used to debug/dev python-openzwave # Open a docker that can be used to debug/dev python-openzwave
# Pass in a command line argument to build # Pass in a command line argument to build
# If current pwd is scripts, go 1 up. cd "$(dirname "$0")/.."
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
if [ $# -gt 0 ] if [ $# -gt 0 ]
then then

3
scripts/hass-daemon → script/hass-daemon Normal file → Executable file
View File

@ -34,6 +34,7 @@ RUN_AS="USER"
PID_FILE="/var/run/hass.pid" PID_FILE="/var/run/hass.pid"
CONFIG_DIR="/var/opt/homeassistant" CONFIG_DIR="/var/opt/homeassistant"
FLAGS="-v --config $CONFIG_DIR --pid-file $PID_FILE --daemon" FLAGS="-v --config $CONFIG_DIR --pid-file $PID_FILE --daemon"
REDIRECT="> $CONFIG_DIR/home-assistant.log 2>&1"
start() { start() {
if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE); then if [ -f $PID_FILE ] && kill -0 $(cat $PID_FILE); then
@ -41,7 +42,7 @@ start() {
return 1 return 1
fi fi
echo 'Starting service…' >&2 echo 'Starting service…' >&2
local CMD="$PRE_EXEC hass $FLAGS;" local CMD="$PRE_EXEC hass $FLAGS $REDIRECT;"
su -c "$CMD" $RUN_AS su -c "$CMD" $RUN_AS
echo 'Service started' >&2 echo 'Service started' >&2
} }

9
script/lint Executable file
View File

@ -0,0 +1,9 @@
# Run style checks
cd "$(dirname "$0")/.."
echo "Checking style with flake8..."
flake8 homeassistant
echo "Checking style with pylint..."
pylint homeassistant

8
script/server Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
# script/server: Launch the application and any extra required processes
# locally.
cd "$(dirname "$0")/.."
python3 -m homeassistant -c config

5
script/setup Executable file
View File

@ -0,0 +1,5 @@
cd "$(dirname "$0")/.."
git submodule init
script/bootstrap
python3 setup.py develop

16
script/test Executable file
View File

@ -0,0 +1,16 @@
#!/bin/sh
# script/test: Run test suite for application. Optionallly pass in a path to an
# individual test file to run a single test.
cd "$(dirname "$0")/.."
script/lint
echo "Running tests..."
if [ "$1" = "coverage" ]; then
py.test --cov homeassistant tests
else
py.test tests
fi

8
script/update Executable file
View File

@ -0,0 +1,8 @@
#!/bin/sh
# script/update: Update application to run for its current checkout.
cd "$(dirname "$0")/.."
git pull
git submodule update

View File

@ -1,9 +0,0 @@
# Run style checks
# If current pwd is scripts, go 1 up.
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
flake8 homeassistant
pylint homeassistant

View File

@ -1,10 +0,0 @@
# If current pwd is scripts, go 1 up.
if [ ${PWD##*/} == "scripts" ]; then
cd ..
fi
if [ "$1" = "coverage" ]; then
coverage run -m unittest discover tests
else
python3 -m unittest discover tests
fi

View File

@ -1,6 +0,0 @@
echo "The update script has been deprecated since Home Assistant v0.7"
echo
echo "Home Assistant is now distributed via PyPi and can be installed and"
echo "upgraded by running: pip3 install --upgrade homeassistant"
echo
echo "If you are developing a new feature for Home Assistant, run: git pull"

View File

@ -285,9 +285,9 @@ class TestAutomationTime(unittest.TestCase):
automation.DOMAIN: { automation.DOMAIN: {
'trigger': { 'trigger': {
'platform': 'time', 'platform': 'time',
'hours': 0, 'hours': 1,
'minutes': 0, 'minutes': 2,
'seconds': 0, 'seconds': 3,
}, },
'action': { 'action': {
'execute_service': 'test.automation' 'execute_service': 'test.automation'
@ -296,7 +296,7 @@ class TestAutomationTime(unittest.TestCase):
})) }))
fire_time_changed(self.hass, dt_util.utcnow().replace( fire_time_changed(self.hass, dt_util.utcnow().replace(
hour=0, minute=0, second=0)) hour=1, minute=2, second=3))
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls)) self.assertEqual(1, len(self.calls))
@ -320,6 +320,30 @@ class TestAutomationTime(unittest.TestCase):
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
self.assertEqual(1, len(self.calls)) self.assertEqual(1, len(self.calls))
@patch('homeassistant.components.automation.time._LOGGER.error')
def test_if_not_fires_using_wrong_after(self, mock_error):
""" YAML translates time values to total seconds. This should break the
before rule. """
self.assertTrue(automation.setup(self.hass, {
automation.DOMAIN: {
'trigger': {
'platform': 'time',
'after': 3605,
# Total seconds. Hour = 3600 second
},
'action': {
'execute_service': 'test.automation'
}
}
}))
fire_time_changed(self.hass, dt_util.utcnow().replace(
hour=1, minute=0, second=5))
self.hass.pool.block_till_done()
self.assertEqual(0, len(self.calls))
self.assertEqual(2, mock_error.call_count)
def test_if_action_before(self): def test_if_action_before(self):
automation.setup(self.hass, { automation.setup(self.hass, {
automation.DOMAIN: { automation.DOMAIN: {