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

This commit is contained in:
carlosmgr 2016-02-02 20:07:31 +00:00
commit cd00ff8b56
26 changed files with 435 additions and 276 deletions

View file

@ -1,7 +1,10 @@
""" Starts home assistant. """
from __future__ import print_function
from multiprocessing import Process
import signal
import sys
import threading
import os
import argparse
@ -204,6 +207,64 @@ def uninstall_osx():
print("Home Assistant has been uninstalled.")
def setup_and_run_hass(config_dir, args):
""" Setup HASS and run. Block until stopped. """
if args.demo_mode:
config = {
'frontend': {},
'demo': {}
}
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, daemon=args.daemon,
verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)
else:
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
if args.open_ui:
def open_browser(event):
""" Open the webinterface in a browser. """
if hass.config.api is not None:
import webbrowser
webbrowser.open(hass.config.api.base_url)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)
hass.start()
sys.exit(int(hass.block_till_stopped()))
def run_hass_process(hass_proc):
""" Runs a child hass process. Returns True if it should be restarted. """
requested_stop = threading.Event()
hass_proc.daemon = True
def request_stop():
""" request hass stop """
requested_stop.set()
hass_proc.terminate()
try:
signal.signal(signal.SIGTERM, request_stop)
except ValueError:
print('Could not bind to SIGTERM. Are you running in a thread?')
hass_proc.start()
try:
hass_proc.join()
except KeyboardInterrupt:
request_stop()
try:
hass_proc.join()
except KeyboardInterrupt:
return False
return not requested_stop.isSet() and hass_proc.exitcode == 100
def main():
""" Starts Home Assistant. """
validate_python()
@ -233,33 +294,12 @@ def main():
if args.pid_file:
write_pid(args.pid_file)
if args.demo_mode:
config = {
'frontend': {},
'demo': {}
}
hass = bootstrap.from_config_dict(
config, config_dir=config_dir, daemon=args.daemon,
verbose=args.verbose, skip_pip=args.skip_pip,
log_rotate_days=args.log_rotate_days)
else:
config_file = ensure_config_file(config_dir)
print('Config directory:', config_dir)
hass = bootstrap.from_config_file(
config_file, daemon=args.daemon, verbose=args.verbose,
skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days)
# Run hass as child process. Restart if necessary.
keep_running = True
while keep_running:
hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args))
keep_running = run_hass_process(hass_proc)
if args.open_ui:
def open_browser(event):
""" Open the webinterface in a browser. """
if hass.config.api is not None:
import webbrowser
webbrowser.open(hass.config.api.base_url)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, open_browser)
hass.start()
hass.block_till_stopped()
if __name__ == "__main__":
main()

View file

@ -16,7 +16,7 @@ from homeassistant.components.light import \
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['liffylights==0.9.3']
REQUIREMENTS = ['liffylights==0.9.4']
DEPENDENCIES = []
CONF_SERVER = "server" # server address configuration item
@ -64,6 +64,12 @@ class LIFX():
power, hue, sat, bri, kel)
self._devices.append(bulb)
self._add_devices_callback([bulb])
else:
_LOGGER.debug("update bulb %s %s %d %d %d %d %d",
ipaddr, name, power, hue, sat, bri, kel)
bulb.set_power(power)
bulb.set_color(hue, sat, bri, kel)
bulb.update_ha_state()
# pylint: disable=too-many-arguments
def on_color(self, ipaddr, hue, sat, bri, kel):
@ -97,7 +103,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
lifx_library = LIFX(add_devices_callback, server_addr, broadcast_addr)
# register our poll service
track_time_change(hass, lifx_library.poll, second=10)
track_time_change(hass, lifx_library.poll, second=[10, 40])
lifx_library.probe()

View file

@ -32,7 +32,6 @@ DISCOVERY_PLATFORMS = {
discovery.SERVICE_PLEX: 'plex',
}
SERVICE_YOUTUBE_VIDEO = 'play_youtube_video'
SERVICE_PLAY_MEDIA = 'play_media'
ATTR_MEDIA_VOLUME_LEVEL = 'volume_level'
@ -68,14 +67,12 @@ SUPPORT_VOLUME_SET = 4
SUPPORT_VOLUME_MUTE = 8
SUPPORT_PREVIOUS_TRACK = 16
SUPPORT_NEXT_TRACK = 32
SUPPORT_YOUTUBE = 64
SUPPORT_TURN_ON = 128
SUPPORT_TURN_OFF = 256
SUPPORT_PLAY_MEDIA = 512
SUPPORT_VOLUME_STEP = 1024
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
SERVICE_TO_METHOD = {
SERVICE_TURN_ON: 'turn_on',
SERVICE_TURN_OFF: 'turn_off',
@ -200,6 +197,13 @@ def media_previous_track(hass, entity_id=None):
hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
def media_seek(hass, position, entity_id=None):
""" Send the media player the command to seek in current playing media. """
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
data[ATTR_MEDIA_SEEK_POSITION] = position
hass.services.call(DOMAIN, SERVICE_MEDIA_SEEK, data)
def play_media(hass, media_type, media_id, entity_id=None):
""" Send the media player the command for playing media. """
data = {"media_type": media_type, "media_id": media_id}
@ -283,7 +287,7 @@ def setup(hass, config):
position = service.data[ATTR_MEDIA_SEEK_POSITION]
for player in target_players:
player.seek(position)
player.media_seek(position)
if player.should_poll:
player.update_ha_state(True)
@ -291,20 +295,6 @@ def setup(hass, config):
hass.services.register(DOMAIN, SERVICE_MEDIA_SEEK, media_seek_service,
descriptions.get(SERVICE_MEDIA_SEEK))
def play_youtube_video_service(service, media_id=None):
""" Plays specified media_id on the media player. """
if media_id is None:
service.data.get('video')
if media_id is None:
return
for player in component.extract_from_service(service):
player.play_youtube(media_id)
if player.should_poll:
player.update_ha_state(True)
def play_media_service(service):
""" Plays specified media_id on the media player. """
media_type = service.data.get('media_type')
@ -322,20 +312,6 @@ def setup(hass, config):
if player.should_poll:
player.update_ha_state(True)
hass.services.register(
DOMAIN, "start_fireplace",
lambda service: play_youtube_video_service(service, "eyU3bRy2x44"),
descriptions.get('start_fireplace'))
hass.services.register(
DOMAIN, "start_epic_sax",
lambda service: play_youtube_video_service(service, "kxopViU98Xo"),
descriptions.get('start_epic_sax'))
hass.services.register(
DOMAIN, SERVICE_YOUTUBE_VIDEO, play_youtube_video_service,
descriptions.get(SERVICE_YOUTUBE_VIDEO))
hass.services.register(
DOMAIN, SERVICE_PLAY_MEDIA, play_media_service,
descriptions.get(SERVICE_PLAY_MEDIA))
@ -490,10 +466,6 @@ class MediaPlayerDevice(Entity):
""" Send seek command. """
raise NotImplementedError()
def play_youtube(self, media_id):
""" Plays a YouTube media. """
raise NotImplementedError()
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
raise NotImplementedError()
@ -529,11 +501,6 @@ class MediaPlayerDevice(Entity):
""" Boolean if next track command supported. """
return bool(self.supported_media_commands & SUPPORT_NEXT_TRACK)
@property
def support_youtube(self):
""" Boolean if YouTube is supported. """
return bool(self.supported_media_commands & SUPPORT_YOUTUBE)
@property
def support_play_media(self):
""" Boolean if play media command supported. """

View file

@ -16,7 +16,7 @@ from homeassistant.const import (
from homeassistant.components.media_player import (
MediaPlayerDevice,
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_YOUTUBE, SUPPORT_PLAY_MEDIA,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PLAY_MEDIA,
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
@ -25,51 +25,48 @@ CONF_IGNORE_CEC = 'ignore_cec'
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PREVIOUS_TRACK | \
SUPPORT_NEXT_TRACK | SUPPORT_YOUTUBE | SUPPORT_PLAY_MEDIA
SUPPORT_NEXT_TRACK | SUPPORT_PLAY_MEDIA
KNOWN_HOSTS = []
# pylint: disable=invalid-name
cast = None
DEFAULT_PORT = 8009
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the cast platform. """
global cast
import pychromecast
cast = pychromecast
logger = logging.getLogger(__name__)
# import CEC IGNORE attributes
ignore_cec = config.get(CONF_IGNORE_CEC, [])
if isinstance(ignore_cec, list):
cast.IGNORE_CEC += ignore_cec
pychromecast.IGNORE_CEC += ignore_cec
else:
logger.error('Chromecast conig, %s must be a list.', CONF_IGNORE_CEC)
logger.error('CEC config "%s" must be a list.', CONF_IGNORE_CEC)
hosts = []
if discovery_info and discovery_info[0] not in KNOWN_HOSTS:
hosts = [discovery_info[0]]
if discovery_info and discovery_info in KNOWN_HOSTS:
return
elif discovery_info:
hosts = [discovery_info]
elif CONF_HOST in config:
hosts = [config[CONF_HOST]]
hosts = [(config[CONF_HOST], DEFAULT_PORT)]
else:
hosts = (host_port[0] for host_port
in cast.discover_chromecasts()
if host_port[0] not in KNOWN_HOSTS)
hosts = [host for host in pychromecast.discover_chromecasts()
if host not in KNOWN_HOSTS]
casts = []
for host in hosts:
try:
casts.append(CastDevice(host))
except cast.ChromecastConnectionError:
pass
else:
casts.append(CastDevice(*host))
KNOWN_HOSTS.append(host)
except pychromecast.ChromecastConnectionError:
pass
add_devices(casts)
@ -80,11 +77,9 @@ class CastDevice(MediaPlayerDevice):
# pylint: disable=abstract-method
# pylint: disable=too-many-public-methods
def __init__(self, host):
import pychromecast.controllers.youtube as youtube
self.cast = cast.Chromecast(host)
self.youtube = youtube.YouTubeController()
self.cast.register_handler(self.youtube)
def __init__(self, host, port):
import pychromecast
self.cast = pychromecast.Chromecast(host, port)
self.cast.socket_client.receiver_controller.register_status_listener(
self)
@ -224,11 +219,13 @@ class CastDevice(MediaPlayerDevice):
""" Turns on the ChromeCast. """
# The only way we can turn the Chromecast is on is by launching an app
if not self.cast.status or not self.cast.status.is_active_input:
import pychromecast
if self.cast.app_id:
self.cast.quit_app()
self.cast.play_media(
CAST_SPLASH, cast.STREAM_TYPE_BUFFERED)
CAST_SPLASH, pychromecast.STREAM_TYPE_BUFFERED)
def turn_off(self):
""" Turns Chromecast off. """
@ -266,10 +263,6 @@ class CastDevice(MediaPlayerDevice):
""" Plays media from a URL """
self.cast.media_controller.play_media(media_id, media_type)
def play_youtube(self, media_id):
""" Plays a YouTube media. """
self.youtube.play_video(media_id)
# implementation of chromecast status_listener methods
def new_cast_status(self, status):

View file

@ -7,11 +7,11 @@ from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF)
from homeassistant.components.media_player import (
MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT,
MediaPlayerDevice,
MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW,
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_YOUTUBE,
SUPPORT_PAUSE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK)
SUPPORT_NEXT_TRACK, SUPPORT_PLAY_MEDIA)
# pylint: disable=unused-argument
@ -26,9 +26,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
])
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'
YOUTUBE_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_YOUTUBE | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA
MUSIC_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
@ -150,10 +152,9 @@ class DemoYoutubePlayer(AbstractDemoPlayer):
""" Flags of media commands that are supported. """
return YOUTUBE_PLAYER_SUPPORT
def play_youtube(self, media_id):
""" Plays a YouTube media. """
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
self.youtube_id = media_id
self._media_title = 'some YouTube video'
self.update_ha_state()
@ -234,7 +235,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
""" Flags of media commands that are supported. """
support = MUSIC_PLAYER_SUPPORT
if self._cur_track > 1:
if self._cur_track > 0:
support |= SUPPORT_PREVIOUS_TRACK
if self._cur_track < len(self.tracks)-1:

View file

@ -24,7 +24,6 @@ SUPPORT_DENON = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Denon platform. """
if not config.get(CONF_HOST):
@ -48,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DenonDevice(MediaPlayerDevice):
""" Represents a Denon device. """
# pylint: disable=too-many-public-methods
# pylint: disable=too-many-public-methods, abstract-method
def __init__(self, name, host):
self._name = name
@ -145,10 +144,6 @@ class DenonDevice(MediaPlayerDevice):
""" mute (true) or unmute (false) media player. """
self.telnet_command("MU" + ("ON" if mute else "OFF"))
def media_play_pause(self):
""" media_play_pause media player. """
raise NotImplementedError()
def media_play(self):
""" media_play media player. """
self.telnet_command("NS9A")
@ -164,9 +159,6 @@ class DenonDevice(MediaPlayerDevice):
def media_previous_track(self):
self.telnet_command("NS9E")
def media_seek(self, position):
raise NotImplementedError()
def turn_on(self):
""" turn the media player on. """
self.telnet_command("PWON")

View file

@ -105,6 +105,8 @@ class FireTV(object):
class FireTVDevice(MediaPlayerDevice):
""" Represents an Amazon Fire TV device on the network. """
# pylint: disable=abstract-method
def __init__(self, host, device, name):
self._firetv = FireTV(host, device)
self._name = name
@ -176,15 +178,3 @@ class FireTVDevice(MediaPlayerDevice):
def media_next_track(self):
""" Send next track command (results in fast-forward). """
self._firetv.action('media_next')
def media_seek(self, position):
raise NotImplementedError()
def mute_volume(self, mute):
raise NotImplementedError()
def play_youtube(self, media_id):
raise NotImplementedError()
def set_volume_level(self, volume):
raise NotImplementedError()

View file

@ -22,7 +22,6 @@ SUPPORT_KODI = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the kodi platform. """
@ -47,7 +46,7 @@ def _get_image_url(kodi_url):
class KodiDevice(MediaPlayerDevice):
""" Represents a XBMC/Kodi device. """
# pylint: disable=too-many-public-methods
# pylint: disable=too-many-public-methods, abstract-method
def __init__(self, name, url, auth=None):
import jsonrpc_requests
@ -263,11 +262,3 @@ class KodiDevice(MediaPlayerDevice):
self._server.Player.Seek(players[0]['playerid'], time)
self.update_ha_state()
def turn_on(self):
""" turn the media player on. """
raise NotImplementedError()
def play_youtube(self, media_id):
""" Plays a YouTube media. """
raise NotImplementedError()

View file

@ -59,7 +59,7 @@ def config_from_file(filename, config=None):
return {}
# pylint: disable=abstract-method, unused-argument
# pylint: disable=abstract-method
def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Sets up the plex platform. """

View file

@ -27,7 +27,6 @@ SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the squeezebox platform. """
if not config.get(CONF_HOST):
@ -138,7 +137,7 @@ class LogitechMediaServer(object):
class SqueezeBoxDevice(MediaPlayerDevice):
""" Represents a SqueezeBox device. """
# pylint: disable=too-many-arguments
# pylint: disable=too-many-arguments, abstract-method
def __init__(self, lms, player_id):
super(SqueezeBoxDevice, self).__init__()
self._lms = lms
@ -292,7 +291,3 @@ class SqueezeBoxDevice(MediaPlayerDevice):
""" turn the media player on. """
self._lms.query(self._id, 'power', '1')
self.update_ha_state()
def play_youtube(self, media_id):
""" Plays a YouTube media. """
raise NotImplementedError()

View file

@ -27,7 +27,7 @@ from homeassistant.components.media_player import (
MediaPlayerDevice, DOMAIN,
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
SERVICE_PLAY_MEDIA, SERVICE_YOUTUBE_VIDEO,
SERVICE_PLAY_MEDIA,
ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_MEDIA_VOLUME_MUTED,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION,
ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, ATTR_MEDIA_ALBUM_NAME,
@ -397,11 +397,6 @@ class UniversalMediaPlayer(MediaPlayerDevice):
data = {ATTR_MEDIA_SEEK_POSITION: position}
self._call_service(SERVICE_MEDIA_SEEK, data)
def play_youtube(self, media_id):
""" Plays a YouTube media. """
data = {'media_id': media_id}
self._call_service(SERVICE_YOUTUBE_VIDEO, data)
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
data = {'media_type': media_type, 'media_id': media_id}

View file

@ -96,5 +96,7 @@ class OneWire(Entity):
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp = float(temp_string) / 1000.0
temp = round(float(temp_string) / 1000.0, 1)
if temp < -55 or temp > 125:
return
self._state = temp

View file

@ -11,7 +11,9 @@ import logging
from datetime import datetime
from homeassistant.const import TEMP_CELCIUS, ATTR_BATTERY_LEVEL
from homeassistant.const import (TEMP_CELCIUS,
ATTR_BATTERY_LEVEL,
DEVICE_DEFAULT_NAME)
from homeassistant.helpers.entity import Entity
from homeassistant.components import tellduslive
@ -64,7 +66,8 @@ class TelldusLiveSensor(Entity):
self._sensor_id = sensor_id
self._sensor_type = sensor_type
self._state = None
self._name = sensor_name + ' ' + SENSOR_TYPES[sensor_type][0]
self._name = "{} {}".format(sensor_name or DEVICE_DEFAULT_NAME,
SENSOR_TYPES[sensor_type][0])
self._last_update = None
self._battery_level = None
self.update()

View file

@ -9,16 +9,20 @@ https://home-assistant.io/components/sensor.template/
"""
import logging
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity import Entity, generate_entity_id
from homeassistant.core import EVENT_STATE_CHANGED
from homeassistant.const import (
ATTR_FRIENDLY_NAME,
CONF_VALUE_TEMPLATE,
ATTR_UNIT_OF_MEASUREMENT)
from homeassistant.util import template
from homeassistant.util import template, slugify
from homeassistant.exceptions import TemplateError
from homeassistant.components.sensor import DOMAIN
ENTITY_ID_FORMAT = DOMAIN + '.{}'
_LOGGER = logging.getLogger(__name__)
CONF_SENSORS = 'sensors'
STATE_ERROR = 'error'
@ -34,9 +38,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return False
for device, device_config in config[CONF_SENSORS].items():
if device != slugify(device):
_LOGGER.error("Found invalid key for sensor.template: %s. "
"Use %s instead", device, slugify(device))
continue
if not isinstance(device_config, dict):
_LOGGER.error("Missing configuration data for sensor %s", device)
continue
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
unit_of_measurement = device_config.get(ATTR_UNIT_OF_MEASUREMENT)
state_template = device_config.get(CONF_VALUE_TEMPLATE)
@ -44,14 +55,16 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error(
"Missing %s for sensor %s", CONF_VALUE_TEMPLATE, device)
continue
sensors.append(
SensorTemplate(
hass,
device,
friendly_name,
unit_of_measurement,
state_template)
)
if sensors is None:
if not sensors:
_LOGGER.error("No sensors added")
return False
add_devices(sensors)
@ -64,10 +77,15 @@ class SensorTemplate(Entity):
# pylint: disable=too-many-arguments
def __init__(self,
hass,
device_id,
friendly_name,
unit_of_measurement,
state_template):
self.entity_id = generate_entity_id(
ENTITY_ID_FORMAT, device_id,
hass=hass)
self.hass = hass
self._name = friendly_name
self._unit_of_measurement = unit_of_measurement

View file

@ -28,7 +28,7 @@ SENSOR_TYPES = {
'temperature': ['Temperature', '°C'],
'windSpeed': ['Wind speed', 'm/s'],
'windGust': ['Wind gust', 'm/s'],
'pressure': ['Pressure', 'mbar'],
'pressure': ['Pressure', 'hPa'],
'windDirection': ['Wind direction', '°'],
'humidity': ['Humidity', '%'],
'fog': ['Fog', '%'],

View file

@ -126,6 +126,7 @@ ATTR_GPS_ACCURACY = 'gps_accuracy'
# #### SERVICES ####
SERVICE_HOMEASSISTANT_STOP = "stop"
SERVICE_HOMEASSISTANT_RESTART = "restart"
SERVICE_TURN_ON = 'turn_on'
SERVICE_TURN_OFF = 'turn_off'

View file

@ -16,7 +16,8 @@ from collections import namedtuple
from homeassistant.const import (
__version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
SERVICE_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
EVENT_TIME_CHANGED, EVENT_STATE_CHANGED,
EVENT_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME, ATTR_SERVICE_DATA)
@ -47,6 +48,9 @@ _LOGGER = logging.getLogger(__name__)
# Temporary to support deprecated methods
_MockHA = namedtuple("MockHomeAssistant", ['bus'])
# The exit code to send to request a restart
RESTART_EXIT_CODE = 100
class HomeAssistant(object):
"""Root object of the Home Assistant home automation."""
@ -70,28 +74,33 @@ class HomeAssistant(object):
def block_till_stopped(self):
"""Register service homeassistant/stop and will block until called."""
request_shutdown = threading.Event()
request_restart = threading.Event()
def stop_homeassistant(*args):
"""Stop Home Assistant."""
request_shutdown.set()
def restart_homeassistant(*args):
"""Reset Home Assistant."""
request_restart.set()
request_shutdown.set()
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_RESTART, restart_homeassistant)
if os.name != "nt":
try:
signal.signal(signal.SIGTERM, stop_homeassistant)
except ValueError:
_LOGGER.warning(
'Could not bind to SIGQUIT. Are you running in a thread?')
try:
signal.signal(signal.SIGTERM, stop_homeassistant)
except ValueError:
_LOGGER.warning(
'Could not bind to SIGTERM. Are you running in a thread?')
while not request_shutdown.isSet():
try:
time.sleep(1)
except KeyboardInterrupt:
break
time.sleep(1)
self.stop()
return RESTART_EXIT_CODE if request_restart.isSet() else 0
def stop(self):
"""Stop Home Assistant and shuts down all threads."""

View file

@ -19,7 +19,7 @@ def load_yaml(fname):
with open(fname, encoding='utf-8') as conf_file:
# If configuration file is empty YAML returns None
# We convert that to an empty dict
return yaml.load(conf_file) or {}
return yaml.safe_load(conf_file) or {}
except yaml.YAMLError:
error = 'Error reading YAML configuration file {}'.format(fname)
_LOGGER.exception(error)
@ -45,6 +45,6 @@ def _ordered_dict(loader, node):
return OrderedDict(loader.construct_pairs(node))
yaml.add_constructor('!include', _include_yaml)
yaml.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
_ordered_dict)
yaml.SafeLoader.add_constructor('!include', _include_yaml)
yaml.SafeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
_ordered_dict)

View file

@ -100,7 +100,7 @@ insteon_hub==0.4.5
jsonrpc-requests==0.1
# homeassistant.components.light.lifx
liffylights==0.9.3
liffylights==0.9.4
# homeassistant.components.light.limitlessled
limitlessled==1.0.0

View file

@ -0,0 +1,30 @@
"""
tests.component.media_player.test_cast
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests cast media_player component.
"""
# pylint: disable=too-many-public-methods,protected-access
import unittest
from unittest.mock import patch
from homeassistant.components.media_player import cast
class TestCastMediaPlayer(unittest.TestCase):
""" Test the media_player module. """
@patch('homeassistant.components.media_player.cast.CastDevice')
def test_filter_duplicates(self, mock_device):
cast.setup_platform(None, {
'host': 'some_host'
}, lambda _: _)
assert mock_device.called
mock_device.reset_mock()
assert not mock_device.called
cast.setup_platform(None, {}, lambda _: _, ('some_host',
cast.DEFAULT_PORT))
assert not mock_device.called

View file

@ -0,0 +1,141 @@
"""
tests.component.media_player.test_demo
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests demo media_player component.
"""
import unittest
from unittest.mock import patch
from pprint import pprint
import homeassistant.core as ha
from homeassistant.const import (
STATE_OFF, STATE_ON, STATE_UNKNOWN, STATE_PLAYING, STATE_PAUSED)
import homeassistant.components.media_player as mp
entity_id = 'media_player.walkman'
class TestDemoMediaPlayer(unittest.TestCase):
""" Test the media_player module. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_volume_services(self):
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
state = self.hass.states.get(entity_id)
assert 1.0 == state.attributes.get('volume_level')
mp.set_volume_level(self.hass, 0.5, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert 0.5 == state.attributes.get('volume_level')
mp.volume_down(self.hass, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert 0.4 == state.attributes.get('volume_level')
mp.volume_up(self.hass, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert 0.5 == state.attributes.get('volume_level')
assert False is state.attributes.get('is_volume_muted')
mp.mute_volume(self.hass, True, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert True is state.attributes.get('is_volume_muted')
def test_turning_off_and_on(self):
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
assert self.hass.states.is_state(entity_id, 'playing')
mp.turn_off(self.hass, entity_id)
self.hass.pool.block_till_done()
assert self.hass.states.is_state(entity_id, 'off')
assert not mp.is_on(self.hass, entity_id)
mp.turn_on(self.hass, entity_id)
self.hass.pool.block_till_done()
assert self.hass.states.is_state(entity_id, 'playing')
mp.toggle(self.hass, entity_id)
self.hass.pool.block_till_done()
assert self.hass.states.is_state(entity_id, 'off')
assert not mp.is_on(self.hass, entity_id)
def test_playing_pausing(self):
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
assert self.hass.states.is_state(entity_id, 'playing')
mp.media_pause(self.hass, entity_id)
self.hass.pool.block_till_done()
assert self.hass.states.is_state(entity_id, 'paused')
mp.media_play_pause(self.hass, entity_id)
self.hass.pool.block_till_done()
assert self.hass.states.is_state(entity_id, 'playing')
mp.media_play_pause(self.hass, entity_id)
self.hass.pool.block_till_done()
assert self.hass.states.is_state(entity_id, 'paused')
mp.media_play(self.hass, entity_id)
self.hass.pool.block_till_done()
assert self.hass.states.is_state(entity_id, 'playing')
def test_prev_next_track(self):
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
state = self.hass.states.get(entity_id)
assert 1 == state.attributes.get('media_track')
assert 0 == (mp.SUPPORT_PREVIOUS_TRACK &
state.attributes.get('supported_media_commands'))
mp.media_next_track(self.hass, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert 2 == state.attributes.get('media_track')
assert 0 < (mp.SUPPORT_PREVIOUS_TRACK &
state.attributes.get('supported_media_commands'))
mp.media_next_track(self.hass, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert 3 == state.attributes.get('media_track')
assert 0 < (mp.SUPPORT_PREVIOUS_TRACK &
state.attributes.get('supported_media_commands'))
mp.media_previous_track(self.hass, entity_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(entity_id)
assert 2 == state.attributes.get('media_track')
assert 0 < (mp.SUPPORT_PREVIOUS_TRACK &
state.attributes.get('supported_media_commands'))
@patch('homeassistant.components.media_player.demo.DemoYoutubePlayer.media_seek')
def test_play_media(self, mock_seek):
assert mp.setup(self.hass, {'media_player': {'platform': 'demo'}})
ent_id = 'media_player.living_room'
state = self.hass.states.get(ent_id)
assert 0 < (mp.SUPPORT_PLAY_MEDIA &
state.attributes.get('supported_media_commands'))
assert state.attributes.get('media_content_id') is not None
mp.play_media(self.hass, 'youtube', 'some_id', ent_id)
self.hass.pool.block_till_done()
state = self.hass.states.get(ent_id)
assert 0 < (mp.SUPPORT_PLAY_MEDIA &
state.attributes.get('supported_media_commands'))
assert 'some_id' == state.attributes.get('media_content_id')
assert not mock_seek.called
mp.media_seek(self.hass, 100, ent_id)
self.hass.pool.block_till_done()
assert mock_seek.called

View file

@ -1,78 +0,0 @@
"""
tests.test_component_media_player
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests media_player component.
"""
# pylint: disable=too-many-public-methods,protected-access
import unittest
import homeassistant.core as ha
from homeassistant.const import (
STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_TOGGLE,
ATTR_ENTITY_ID)
import homeassistant.components.media_player as media_player
from tests.common import mock_service
class TestMediaPlayer(unittest.TestCase):
""" Test the media_player module. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
self.test_entity = media_player.ENTITY_ID_FORMAT.format('living_room')
self.hass.states.set(self.test_entity, STATE_OFF)
self.test_entity2 = media_player.ENTITY_ID_FORMAT.format('bedroom')
self.hass.states.set(self.test_entity2, "YouTube")
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_is_on(self):
""" Test is_on method. """
self.assertFalse(media_player.is_on(self.hass, self.test_entity))
self.assertTrue(media_player.is_on(self.hass, self.test_entity2))
def test_services(self):
"""
Test if the call service methods convert to correct service calls.
"""
services = {
SERVICE_TURN_ON: media_player.turn_on,
SERVICE_TURN_OFF: media_player.turn_off,
SERVICE_TOGGLE: media_player.toggle,
SERVICE_VOLUME_UP: media_player.volume_up,
SERVICE_VOLUME_DOWN: media_player.volume_down,
SERVICE_MEDIA_PLAY_PAUSE: media_player.media_play_pause,
SERVICE_MEDIA_PLAY: media_player.media_play,
SERVICE_MEDIA_PAUSE: media_player.media_pause,
SERVICE_MEDIA_NEXT_TRACK: media_player.media_next_track,
SERVICE_MEDIA_PREVIOUS_TRACK: media_player.media_previous_track
}
for service_name, service_method in services.items():
calls = mock_service(self.hass, media_player.DOMAIN, service_name)
service_method(self.hass)
self.hass.pool.block_till_done()
self.assertEqual(1, len(calls))
call = calls[-1]
self.assertEqual(media_player.DOMAIN, call.domain)
self.assertEqual(service_name, call.service)
service_method(self.hass, self.test_entity)
self.hass.pool.block_till_done()
self.assertEqual(2, len(calls))
call = calls[-1]
self.assertEqual(media_player.DOMAIN, call.domain)
self.assertEqual(service_name, call.service)
self.assertEqual(self.test_entity,
call.data.get(ATTR_ENTITY_ID))

View file

@ -4,9 +4,6 @@ tests.components.sensor.template
Tests template sensor.
"""
from unittest.mock import patch
import pytest
import homeassistant.core as ha
import homeassistant.components.sensor as sensor
@ -56,8 +53,54 @@ class TestTemplateSensor:
}
})
self.hass.states.set('sensor.test_state', 'Works')
self.hass.pool.block_till_done()
state = self.hass.states.get('sensor.test_template_sensor')
assert state.state == 'error'
def test_invalid_name_does_not_create(self):
assert sensor.setup(self.hass, {
'sensor': {
'platform': 'template',
'sensors': {
'test INVALID sensor': {
'value_template':
"{{ states.sensor.test_state.state }}"
}
}
}
})
assert self.hass.states.all() == []
def test_invalid_sensor_does_not_create(self):
assert sensor.setup(self.hass, {
'sensor': {
'platform': 'template',
'sensors': {
'test_template_sensor': 'invalid'
}
}
})
assert self.hass.states.all() == []
def test_no_sensors_does_not_create(self):
assert sensor.setup(self.hass, {
'sensor': {
'platform': 'template'
}
})
assert self.hass.states.all() == []
def test_missing_template_does_not_create(self):
assert sensor.setup(self.hass, {
'sensor': {
'platform': 'template',
'sensors': {
'test_template_sensor': {
'not_value_template':
"{{ states.sensor.test_state.state }}"
}
}
}
})
assert self.hass.states.all() == []

View file

@ -43,37 +43,47 @@ class TestSensorYr:
state = self.hass.states.get('sensor.yr_symbol')
assert '46' == state.state
assert state.state.isnumeric()
assert state.attributes.get('unit_of_measurement') is None
def test_custom_setup(self, betamax_session):
now = datetime(2016, 1, 5, 1, tzinfo=dt_util.UTC)
with patch('homeassistant.components.sensor.yr.requests.Session',
return_value=betamax_session):
assert sensor.setup(self.hass, {
'sensor': {
'platform': 'yr',
'elevation': 0,
'monitored_conditions': {
'pressure',
'windDirection',
'humidity',
'fog',
'windSpeed'
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
return_value=now):
assert sensor.setup(self.hass, {
'sensor': {
'platform': 'yr',
'elevation': 0,
'monitored_conditions': {
'pressure',
'windDirection',
'humidity',
'fog',
'windSpeed'
}
}
}
})
})
state = self.hass.states.get('sensor.yr_pressure')
assert 'hPa', state.attributes.get('unit_of_measurement')
assert 'hPa' == state.attributes.get('unit_of_measurement')
assert '1025.1' == state.state
state = self.hass.states.get('sensor.yr_wind_direction')
assert '°', state.attributes.get('unit_of_measurement')
assert '°'== state.attributes.get('unit_of_measurement')
assert '81.8' == state.state
state = self.hass.states.get('sensor.yr_humidity')
assert '%', state.attributes.get('unit_of_measurement')
assert '%' == state.attributes.get('unit_of_measurement')
assert '79.6' == state.state
state = self.hass.states.get('sensor.yr_fog')
assert '%', state.attributes.get('unit_of_measurement')
assert '%' == state.attributes.get('unit_of_measurement')
assert '0.0' == state.state
state = self.hass.states.get('sensor.yr_wind_speed')
assert 'm/s', state.attributes.get('unit_of_measurement')
assert '4.3' == state.state

View file

@ -94,6 +94,15 @@ class TestConfig(unittest.TestCase):
with self.assertRaises(HomeAssistantError):
config_util.load_yaml_config_file(YAML_PATH)
def test_load_yaml_config_raises_error_if_unsafe_yaml(self):
""" Test error raised if unsafe YAML. """
with open(YAML_PATH, 'w') as f:
f.write('hello: !!python/object/apply:os.system')
with self.assertRaises(HomeAssistantError):
config_util.load_yaml_config_file(YAML_PATH)
def test_load_yaml_config_preserves_key_order(self):
with open(YAML_PATH, 'w') as f:
f.write('hello: 0\n')

View file

@ -7,6 +7,7 @@ Provides tests to verify that Home Assistant core works.
# pylint: disable=protected-access,too-many-public-methods
# pylint: disable=too-few-public-methods
import os
import signal
import unittest
from unittest.mock import patch
import time
@ -79,15 +80,15 @@ class TestHomeAssistant(unittest.TestCase):
self.assertFalse(blocking_thread.is_alive())
def test_stopping_with_keyboardinterrupt(self):
def test_stopping_with_sigterm(self):
calls = []
self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: calls.append(1))
def raise_keyboardinterrupt(length):
raise KeyboardInterrupt
def send_sigterm(length):
os.kill(os.getpid(), signal.SIGTERM)
with patch('homeassistant.core.time.sleep', raise_keyboardinterrupt):
with patch('homeassistant.core.time.sleep', send_sigterm):
self.hass.block_till_stopped()
self.assertEqual(1, len(calls))