diff --git a/Dockerfile b/Dockerfile index 1c13ac9542e..a78322bbd80 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,4 +3,10 @@ MAINTAINER Paulus Schoutsen VOLUME /config +RUN apt-get update && \ + apt-get install -y cython3 libudev-dev python-sphinx python3-setuptools mercurial && \ + apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \ + pip3 install cython && \ + scripts/build_python_openzwave + CMD [ "python", "-m", "homeassistant", "--config", "/config" ] diff --git a/config/configuration.yaml.example b/config/configuration.yaml.example new file mode 100644 index 00000000000..355a24e94d4 --- /dev/null +++ b/config/configuration.yaml.example @@ -0,0 +1,104 @@ +homeassistant: + # Location required to calculate the time the sun rises and sets + latitude: 32.87336 + longitude: 117.22743 + +http: + api_password: mypass + # Set to 1 to enable development mode + # development: 1 + +light: +# platform: hue + +wink: + # Get your token at https://winkbearertoken.appspot.com + access_token: 'YOUR_TOKEN' + +device_tracker: + # The following types are available: netgear, tomato, luci, nmap_tracker + platform: netgear + host: 192.168.1.1 + username: admin + password: PASSWORD + # http_id is needed for Tomato routers only + # http_id: ABCDEFGHH + # For nmap_tracker, only the IP addresses to scan are needed: + # hosts: 192.168.1.1/24 # netmask prefix notation or + # hosts: 192.168.1.1-255 # address range + +chromecast: + +switch: + platform: wemo + +thermostat: + platform: nest + # Required: username and password that are used to login to the Nest thermostat. + username: myemail@mydomain.com + password: mypassword + +downloader: + download_dir: downloads + +notify: + platform: pushbullet + api_key: ABCDEFGHJKLMNOPQRSTUVXYZ + +device_sun_light_trigger: + # Optional: specify a specific light/group of lights that has to be turned on + light_group: group.living_room + # Optional: specify which light profile to use when turning lights on + light_profile: relax + # Optional: disable lights being turned off when everybody leaves the house + # disable_turn_off: 1 + +# A comma seperated list of states that have to be tracked as a single group +# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME) +group: + living_room: + - light.Bowl + - light.Ceiling + - light.TV_back_light + children: + - device_tracker.child_1 + - device_tracker.child_2 + +process: + # items are which processes to look for: : + xbmc: XBMC.App + +example: + +simple_alarm: + # Which light/light group has to flash when a known device comes home + known_light: light.Bowl + # Which light/light group has to flash red when light turns on while no one home + unknown_light: group.living_room + +browser: + +keyboard: + +automation: + platform: state + alias: Sun starts shining + + state_entity_id: sun.sun + # Next two are optional, omit to match all + state_from: below_horizon + state_to: above_horizon + + execute_service: light.turn_off + service_entity_id: group.living_room + +automation 2: + platform: time + alias: Beer o Clock + + time_hours: 16 + time_minutes: 0 + time_seconds: 0 + + execute_service: notify.notify + service_data: {"message":"It's 4, time for beer!"} diff --git a/config/home-assistant.conf.example b/config/home-assistant.conf.example deleted file mode 100644 index d71c3c0cc14..00000000000 --- a/config/home-assistant.conf.example +++ /dev/null @@ -1,99 +0,0 @@ -[homeassistant] -# Location required to calculate the time the sun rises and sets -latitude=32.87336 -longitude=-117.22743 - -[http] -api_password=mypass -# Set to 1 to load each Polymer component separately -# development=1 - -[light] -platform=hue - -[wink] -# Get your token at https://winkbearertoken.appspot.com -access_token=YOUR_TOKEN - -[device_tracker] -# The following types are available: netgear, tomato, luci, nmap_tracker -platform=netgear -host=192.168.1.1 -username=admin -password=PASSWORD -# http_id is needed for Tomato routers only -# http_id=ABCDEFGHH -# For nmap_tracker, only the IP addresses to scan are needed: -# hosts=192.168.1.1/24 # netmask prefix notation or -# hosts=192.168.1.1-255 # address range - -[chromecast] - -[switch] -platform=wemo - -[thermostat] -platform=nest -# Required: username and password that are used to login to the Nest thermostat. -username=myemail@mydomain.com -password=mypassword - -[downloader] -download_dir=downloads - -[notify] -platform=pushbullet -api_key=ABCDEFGHJKLMNOPQRSTUVXYZ - -[device_sun_light_trigger] -# Optional: specify a specific light/group of lights that has to be turned on -light_group=group.living_room -# Optional: specify which light profile to use when turning lights on -light_profile=relax -# Optional: disable lights being turned off when everybody leaves the house -# disable_turn_off=1 - -# A comma seperated list of states that have to be tracked as a single group -# Grouped states should share the same type of states (ON/OFF or HOME/NOT_HOME) -[group] -living_room=light.Bowl,light.Ceiling,light.TV_back_light -children=device_tracker.child_1,device_tracker.child_2 - -[process] -# items are which processes to look for: = -xbmc=XBMC.App - -[example] - -[simple_alarm] -# Which light/light group has to flash when a known device comes home -known_light=light.Bowl -# Which light/light group has to flash red when light turns on while no one home -unknown_light=group.living_room - -[browser] - -[keyboard] - -[automation] -platform=state -alias=Sun starts shining - -state_entity_id=sun.sun -# Next two are optional, omit to match all -state_from=below_horizon -state_to=above_horizon - -execute_service=light.turn_off -service_entity_id=group.living_room - -[automation 2] -platform=time -alias=Beer o Clock - -time_hours=16 -time_minutes=0 -time_seconds=0 - -execute_service=notify.notify -service_data={"message":"It's 4, time for beer!"} diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 05de14dbefc..13703dee416 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -72,15 +72,19 @@ def ensure_config_path(config_dir): 'directory {} ').format(config_dir)) sys.exit() - config_path = os.path.join(config_dir, 'home-assistant.conf') + # Try to use yaml configuration first + config_path = os.path.join(config_dir, 'configuration.yaml') + if not os.path.isfile(config_path): + config_path = os.path.join(config_dir, 'home-assistant.conf') # Ensure a config file exists to make first time usage easier if not os.path.isfile(config_path): + config_path = os.path.join(config_dir, 'configuration.yaml') try: with open(config_path, 'w') as conf: - conf.write("[frontend]\n\n") - conf.write("[discovery]\n\n") - conf.write("[history]\n\n") + conf.write("frontend:\n\n") + conf.write("discovery:\n\n") + conf.write("history:\n\n") except IOError: print(('Fatal Error: No configuration file found and unable ' 'to write a default one to {}').format(config_path)) diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 99278a6a426..56d77fdcb26 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -11,6 +11,8 @@ start by calling homeassistant.start_home_assistant(bus) import os import configparser +import yaml +import io import logging from collections import defaultdict @@ -77,7 +79,9 @@ def from_config_dict(config, hass=None): # Make a copy because we are mutating it. # Convert it to defaultdict so components can always have config dict - config = defaultdict(dict, config) + # Convert values to dictionaries if they are None + config = defaultdict( + dict, {key: value or {} for key, value in config.items()}) # Filter out the repeating and common config section [homeassistant] components = (key for key in config.keys() @@ -110,17 +114,21 @@ def from_config_file(config_path, hass=None): # Set config dir to directory holding config file hass.config_dir = os.path.abspath(os.path.dirname(config_path)) - # Read config - config = configparser.ConfigParser() - config.read(config_path) - config_dict = {} + # check config file type + if os.path.splitext(config_path)[1] == '.yaml': + # Read yaml + config_dict = yaml.load(io.open(config_path, 'r')) + else: + # Read config + config = configparser.ConfigParser() + config.read(config_path) - for section in config.sections(): - config_dict[section] = {} + for section in config.sections(): + config_dict[section] = {} - for key, val in config.items(section): - config_dict[section][key] = val + for key, val in config.items(section): + config_dict[section][key] = val return from_config_dict(config_dict, hass) diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index d5846f450e2..db1dec94351 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -4,27 +4,21 @@ homeassistant.components.demo Sets up a demo environment that mimics interaction with devices """ -import random import time import homeassistant as ha +import homeassistant.bootstrap as bootstrap import homeassistant.loader as loader -from homeassistant.helpers import extract_entity_ids from homeassistant.const import ( - SERVICE_TURN_ON, SERVICE_TURN_OFF, - STATE_ON, STATE_OFF, TEMP_CELCIUS, - ATTR_ENTITY_PICTURE, ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, + CONF_PLATFORM, ATTR_ENTITY_PICTURE, STATE_ON, CONF_LATITUDE, CONF_LONGITUDE) -from homeassistant.components.light import ( - ATTR_XY_COLOR, ATTR_RGB_COLOR, ATTR_BRIGHTNESS, GROUP_NAME_ALL_LIGHTS) -from homeassistant.components.thermostat import ( - ATTR_CURRENT_TEMPERATURE, ATTR_AWAY_MODE) -from homeassistant.util import split_entity_id, color_RGB_to_xy DOMAIN = "demo" DEPENDENCIES = [] +COMPONENTS_WITH_DEMO_PLATFORM = ['switch', 'light', 'thermostat', 'sensor'] + def setup(hass, config): """ Setup a demo environment. """ @@ -37,57 +31,6 @@ def setup(hass, config): if config[DOMAIN].get('hide_demo_state') != '1': hass.states.set('a.Demo_Mode', 'Enabled') - light_colors = [ - [0.861, 0.3259], - [0.6389, 0.3028], - [0.1684, 0.0416] - ] - - def mock_turn_on(service): - """ Will fake the component has been turned on. """ - if service.data and ATTR_ENTITY_ID in service.data: - entity_ids = extract_entity_ids(hass, service) - else: - entity_ids = hass.states.entity_ids(service.domain) - - for entity_id in entity_ids: - domain, _ = split_entity_id(entity_id) - - if domain == "light": - rgb_color = service.data.get(ATTR_RGB_COLOR) - - if rgb_color: - color = color_RGB_to_xy( - rgb_color[0], rgb_color[1], rgb_color[2]) - - else: - cur_state = hass.states.get(entity_id) - - # Use current color if available - if cur_state and cur_state.attributes.get(ATTR_XY_COLOR): - color = cur_state.attributes.get(ATTR_XY_COLOR) - else: - color = random.choice(light_colors) - - data = { - ATTR_BRIGHTNESS: service.data.get(ATTR_BRIGHTNESS, 200), - ATTR_XY_COLOR: color - } - else: - data = None - - hass.states.set(entity_id, STATE_ON, data) - - def mock_turn_off(service): - """ Will fake the component has been turned off. """ - if service.data and ATTR_ENTITY_ID in service.data: - entity_ids = extract_entity_ids(hass, service) - else: - entity_ids = hass.states.entity_ids(service.domain) - - for entity_id in entity_ids: - hass.states.set(entity_id, STATE_OFF) - # Setup sun if CONF_LATITUDE not in config[ha.DOMAIN]: config[ha.DOMAIN][CONF_LATITUDE] = '32.87336' @@ -97,34 +40,16 @@ def setup(hass, config): loader.get_component('sun').setup(hass, config) - # Setup fake lights - lights = ['light.Bowl', 'light.Ceiling', 'light.TV_Back_light', - 'light.Bed_light'] - - hass.services.register('light', SERVICE_TURN_ON, mock_turn_on) - hass.services.register('light', SERVICE_TURN_OFF, mock_turn_off) - - mock_turn_on(ha.ServiceCall('light', SERVICE_TURN_ON, - {'entity_id': lights[0:2]})) - mock_turn_off(ha.ServiceCall('light', SERVICE_TURN_OFF, - {'entity_id': lights[2:]})) - - group.setup_group(hass, GROUP_NAME_ALL_LIGHTS, lights, False) - - # Setup switch - switches = ['switch.AC', 'switch.Christmas_Lights'] - - hass.services.register('switch', SERVICE_TURN_ON, mock_turn_on) - hass.services.register('switch', SERVICE_TURN_OFF, mock_turn_off) - - mock_turn_on(ha.ServiceCall('switch', SERVICE_TURN_ON, - {'entity_id': switches[0:1]})) - mock_turn_off(ha.ServiceCall('switch', SERVICE_TURN_OFF, - {'entity_id': switches[1:]})) + # Setup demo platforms + for component in COMPONENTS_WITH_DEMO_PLATFORM: + bootstrap.setup_component( + hass, component, {component: {CONF_PLATFORM: 'demo'}}) # Setup room groups - group.setup_group(hass, 'living room', lights[0:3] + switches[0:1]) - group.setup_group(hass, 'bedroom', [lights[3]] + switches[1:]) + lights = hass.states.entity_ids('light') + switches = hass.states.entity_ids('switch') + 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) @@ -152,26 +77,7 @@ def setup(hass, config): ATTR_ENTITY_PICTURE: 'http://graph.facebook.com/KillBillMovie/picture'}) - # Setup tellstick sensors - hass.states.set("tellstick_sensor.Outside_temperature", "15.6", - { - 'friendly_name': 'Outside temperature', - 'unit_of_measurement': '°C' - }) - hass.states.set("tellstick_sensor.Outside_humidity", "54", - { - 'friendly_name': 'Outside humidity', - 'unit_of_measurement': '%' - }) - - # Nest demo - hass.states.set("thermostat.Nest", "23", - { - ATTR_UNIT_OF_MEASUREMENT: TEMP_CELCIUS, - ATTR_CURRENT_TEMPERATURE: '18', - ATTR_AWAY_MODE: STATE_OFF - }) - + # Setup configurator configurator_ids = [] def hue_configuration_callback(data): diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py index 195203e8134..9125140a63e 100644 --- a/homeassistant/components/discovery.py +++ b/homeassistant/components/discovery.py @@ -11,7 +11,6 @@ import logging import threading # pylint: disable=no-name-in-module, import-error -from homeassistant.external.netdisco.netdisco import DiscoveryService import homeassistant.external.netdisco.netdisco.const as services from homeassistant import bootstrap @@ -52,12 +51,20 @@ def listen(hass, service, callback): def setup(hass, config): """ Starts a discovery service. """ + logger = logging.getLogger(__name__) + + try: + from homeassistant.external.netdisco.netdisco.service import \ + DiscoveryService + except ImportError: + logger.exception( + "Unable to import netdisco. " + "Did you install all the zeroconf dependency?") + return False # Disable zeroconf logging, it spams logging.getLogger('zeroconf').setLevel(logging.CRITICAL) - logger = logging.getLogger(__name__) - lock = threading.Lock() def new_service_listener(service, info): diff --git a/homeassistant/components/frontend/version.py b/homeassistant/components/frontend/version.py index 064d09fdf58..a9b8d548069 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 = "10b554c3f2db1c5441d5e74c0f6d8469" +VERSION = "30729f23c19a66d9364d78b2379867cc" diff --git a/homeassistant/components/frontend/www_static/frontend.html b/homeassistant/components/frontend/www_static/frontend.html index cdfa9eb8ae5..9a708fe13cd 100644 --- a/homeassistant/components/frontend/www_static/frontend.html +++ b/homeassistant/components/frontend/www_static/frontend.html @@ -122,9 +122,9 @@ b.events&&Object.keys(a).length>0&&console.log("[%s] addHostListeners:",this.loc background-color: #039be5; } - + - +