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. """ """ Starts home assistant. """
from __future__ import print_function from __future__ import print_function
from multiprocessing import Process
import signal
import sys import sys
import threading
import os import os
import argparse import argparse
@ -204,6 +207,64 @@ def uninstall_osx():
print("Home Assistant has been uninstalled.") 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(): def main():
""" Starts Home Assistant. """ """ Starts Home Assistant. """
validate_python() validate_python()
@ -233,33 +294,12 @@ def main():
if args.pid_file: if args.pid_file:
write_pid(args.pid_file) write_pid(args.pid_file)
if args.demo_mode: # Run hass as child process. Restart if necessary.
config = { keep_running = True
'frontend': {}, while keep_running:
'demo': {} hass_proc = Process(target=setup_and_run_hass, args=(config_dir, args))
} keep_running = run_hass_process(hass_proc)
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()
hass.block_till_stopped()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -16,7 +16,7 @@ from homeassistant.components.light import \
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['liffylights==0.9.3'] REQUIREMENTS = ['liffylights==0.9.4']
DEPENDENCIES = [] DEPENDENCIES = []
CONF_SERVER = "server" # server address configuration item CONF_SERVER = "server" # server address configuration item
@ -64,6 +64,12 @@ class LIFX():
power, hue, sat, bri, kel) power, hue, sat, bri, kel)
self._devices.append(bulb) self._devices.append(bulb)
self._add_devices_callback([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 # pylint: disable=too-many-arguments
def on_color(self, ipaddr, hue, sat, bri, kel): 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) lifx_library = LIFX(add_devices_callback, server_addr, broadcast_addr)
# register our poll service # 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() lifx_library.probe()

View File

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

View File

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

View File

@ -7,11 +7,11 @@ from homeassistant.const import (
STATE_PLAYING, STATE_PAUSED, STATE_OFF) STATE_PLAYING, STATE_PAUSED, STATE_OFF)
from homeassistant.components.media_player import ( from homeassistant.components.media_player import (
MediaPlayerDevice, YOUTUBE_COVER_URL_FORMAT, MediaPlayerDevice,
MEDIA_TYPE_VIDEO, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, 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_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_PREVIOUS_TRACK,
SUPPORT_NEXT_TRACK) SUPPORT_NEXT_TRACK, SUPPORT_PLAY_MEDIA)
# pylint: disable=unused-argument # 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 = \ YOUTUBE_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ 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 = \ MUSIC_PLAYER_SUPPORT = \
SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \ SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
@ -150,10 +152,9 @@ class DemoYoutubePlayer(AbstractDemoPlayer):
""" Flags of media commands that are supported. """ """ Flags of media commands that are supported. """
return YOUTUBE_PLAYER_SUPPORT return YOUTUBE_PLAYER_SUPPORT
def play_youtube(self, media_id): def play_media(self, media_type, media_id):
""" Plays a YouTube media. """ """ Plays a piece of media. """
self.youtube_id = media_id self.youtube_id = media_id
self._media_title = 'some YouTube video'
self.update_ha_state() self.update_ha_state()
@ -234,7 +235,7 @@ class DemoMusicPlayer(AbstractDemoPlayer):
""" Flags of media commands that are supported. """ """ Flags of media commands that are supported. """
support = MUSIC_PLAYER_SUPPORT support = MUSIC_PLAYER_SUPPORT
if self._cur_track > 1: if self._cur_track > 0:
support |= SUPPORT_PREVIOUS_TRACK support |= SUPPORT_PREVIOUS_TRACK
if self._cur_track < len(self.tracks)-1: 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 SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the Denon platform. """ """ Sets up the Denon platform. """
if not config.get(CONF_HOST): if not config.get(CONF_HOST):
@ -48,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class DenonDevice(MediaPlayerDevice): class DenonDevice(MediaPlayerDevice):
""" Represents a Denon device. """ """ Represents a Denon device. """
# pylint: disable=too-many-public-methods # pylint: disable=too-many-public-methods, abstract-method
def __init__(self, name, host): def __init__(self, name, host):
self._name = name self._name = name
@ -145,10 +144,6 @@ class DenonDevice(MediaPlayerDevice):
""" mute (true) or unmute (false) media player. """ """ mute (true) or unmute (false) media player. """
self.telnet_command("MU" + ("ON" if mute else "OFF")) 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): def media_play(self):
""" media_play media player. """ """ media_play media player. """
self.telnet_command("NS9A") self.telnet_command("NS9A")
@ -164,9 +159,6 @@ class DenonDevice(MediaPlayerDevice):
def media_previous_track(self): def media_previous_track(self):
self.telnet_command("NS9E") self.telnet_command("NS9E")
def media_seek(self, position):
raise NotImplementedError()
def turn_on(self): def turn_on(self):
""" turn the media player on. """ """ turn the media player on. """
self.telnet_command("PWON") self.telnet_command("PWON")

View File

@ -105,6 +105,8 @@ class FireTV(object):
class FireTVDevice(MediaPlayerDevice): class FireTVDevice(MediaPlayerDevice):
""" Represents an Amazon Fire TV device on the network. """ """ Represents an Amazon Fire TV device on the network. """
# pylint: disable=abstract-method
def __init__(self, host, device, name): def __init__(self, host, device, name):
self._firetv = FireTV(host, device) self._firetv = FireTV(host, device)
self._name = name self._name = name
@ -176,15 +178,3 @@ class FireTVDevice(MediaPlayerDevice):
def media_next_track(self): def media_next_track(self):
""" Send next track command (results in fast-forward). """ """ Send next track command (results in fast-forward). """
self._firetv.action('media_next') 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 SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the kodi platform. """ """ Sets up the kodi platform. """
@ -47,7 +46,7 @@ def _get_image_url(kodi_url):
class KodiDevice(MediaPlayerDevice): class KodiDevice(MediaPlayerDevice):
""" Represents a XBMC/Kodi device. """ """ 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): def __init__(self, name, url, auth=None):
import jsonrpc_requests import jsonrpc_requests
@ -263,11 +262,3 @@ class KodiDevice(MediaPlayerDevice):
self._server.Player.Seek(players[0]['playerid'], time) self._server.Player.Seek(players[0]['playerid'], time)
self.update_ha_state() 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 {} return {}
# pylint: disable=abstract-method, unused-argument # pylint: disable=abstract-method
def setup_platform(hass, config, add_devices_callback, discovery_info=None): def setup_platform(hass, config, add_devices_callback, discovery_info=None):
""" Sets up the plex platform. """ """ 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 SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
""" Sets up the squeezebox platform. """ """ Sets up the squeezebox platform. """
if not config.get(CONF_HOST): if not config.get(CONF_HOST):
@ -138,7 +137,7 @@ class LogitechMediaServer(object):
class SqueezeBoxDevice(MediaPlayerDevice): class SqueezeBoxDevice(MediaPlayerDevice):
""" Represents a SqueezeBox device. """ """ Represents a SqueezeBox device. """
# pylint: disable=too-many-arguments # pylint: disable=too-many-arguments, abstract-method
def __init__(self, lms, player_id): def __init__(self, lms, player_id):
super(SqueezeBoxDevice, self).__init__() super(SqueezeBoxDevice, self).__init__()
self._lms = lms self._lms = lms
@ -292,7 +291,3 @@ class SqueezeBoxDevice(MediaPlayerDevice):
""" turn the media player on. """ """ turn the media player on. """
self._lms.query(self._id, 'power', '1') self._lms.query(self._id, 'power', '1')
self.update_ha_state() 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, MediaPlayerDevice, DOMAIN,
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF, SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
SERVICE_PLAY_MEDIA, SERVICE_YOUTUBE_VIDEO, SERVICE_PLAY_MEDIA,
ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_MEDIA_VOLUME_MUTED, ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_MEDIA_VOLUME_MUTED,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION,
ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, ATTR_MEDIA_ALBUM_NAME, ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, ATTR_MEDIA_ALBUM_NAME,
@ -397,11 +397,6 @@ class UniversalMediaPlayer(MediaPlayerDevice):
data = {ATTR_MEDIA_SEEK_POSITION: position} data = {ATTR_MEDIA_SEEK_POSITION: position}
self._call_service(SERVICE_MEDIA_SEEK, data) 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): def play_media(self, media_type, media_id):
""" Plays a piece of media. """ """ Plays a piece of media. """
data = {'media_type': media_type, 'media_id': media_id} data = {'media_type': media_type, 'media_id': media_id}

View File

@ -96,5 +96,7 @@ class OneWire(Entity):
equals_pos = lines[1].find('t=') equals_pos = lines[1].find('t=')
if equals_pos != -1: if equals_pos != -1:
temp_string = lines[1][equals_pos+2:] 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 self._state = temp

View File

@ -11,7 +11,9 @@ import logging
from datetime import datetime 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.helpers.entity import Entity
from homeassistant.components import tellduslive from homeassistant.components import tellduslive
@ -64,7 +66,8 @@ class TelldusLiveSensor(Entity):
self._sensor_id = sensor_id self._sensor_id = sensor_id
self._sensor_type = sensor_type self._sensor_type = sensor_type
self._state = None 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._last_update = None
self._battery_level = None self._battery_level = None
self.update() self.update()

View File

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

View File

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

View File

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

View File

@ -16,7 +16,8 @@ from collections import namedtuple
from homeassistant.const import ( from homeassistant.const import (
__version__, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, __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_CALL_SERVICE, ATTR_NOW, ATTR_DOMAIN, ATTR_SERVICE, MATCH_ALL,
EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED, EVENT_SERVICE_EXECUTED, ATTR_SERVICE_CALL_ID, EVENT_SERVICE_REGISTERED,
TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME, ATTR_SERVICE_DATA) TEMP_CELCIUS, TEMP_FAHRENHEIT, ATTR_FRIENDLY_NAME, ATTR_SERVICE_DATA)
@ -47,6 +48,9 @@ _LOGGER = logging.getLogger(__name__)
# Temporary to support deprecated methods # Temporary to support deprecated methods
_MockHA = namedtuple("MockHomeAssistant", ['bus']) _MockHA = namedtuple("MockHomeAssistant", ['bus'])
# The exit code to send to request a restart
RESTART_EXIT_CODE = 100
class HomeAssistant(object): class HomeAssistant(object):
"""Root object of the Home Assistant home automation.""" """Root object of the Home Assistant home automation."""
@ -70,28 +74,33 @@ class HomeAssistant(object):
def block_till_stopped(self): def block_till_stopped(self):
"""Register service homeassistant/stop and will block until called.""" """Register service homeassistant/stop and will block until called."""
request_shutdown = threading.Event() request_shutdown = threading.Event()
request_restart = threading.Event()
def stop_homeassistant(*args): def stop_homeassistant(*args):
"""Stop Home Assistant.""" """Stop Home Assistant."""
request_shutdown.set() request_shutdown.set()
def restart_homeassistant(*args):
"""Reset Home Assistant."""
request_restart.set()
request_shutdown.set()
self.services.register( self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant) DOMAIN, SERVICE_HOMEASSISTANT_STOP, stop_homeassistant)
self.services.register(
DOMAIN, SERVICE_HOMEASSISTANT_RESTART, restart_homeassistant)
if os.name != "nt": try:
try: signal.signal(signal.SIGTERM, stop_homeassistant)
signal.signal(signal.SIGTERM, stop_homeassistant) except ValueError:
except ValueError: _LOGGER.warning(
_LOGGER.warning( 'Could not bind to SIGTERM. Are you running in a thread?')
'Could not bind to SIGQUIT. Are you running in a thread?')
while not request_shutdown.isSet(): while not request_shutdown.isSet():
try: time.sleep(1)
time.sleep(1)
except KeyboardInterrupt:
break
self.stop() self.stop()
return RESTART_EXIT_CODE if request_restart.isSet() else 0
def stop(self): def stop(self):
"""Stop Home Assistant and shuts down all threads.""" """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: with open(fname, encoding='utf-8') as conf_file:
# If configuration file is empty YAML returns None # If configuration file is empty YAML returns None
# We convert that to an empty dict # We convert that to an empty dict
return yaml.load(conf_file) or {} return yaml.safe_load(conf_file) or {}
except yaml.YAMLError: except yaml.YAMLError:
error = 'Error reading YAML configuration file {}'.format(fname) error = 'Error reading YAML configuration file {}'.format(fname)
_LOGGER.exception(error) _LOGGER.exception(error)
@ -45,6 +45,6 @@ def _ordered_dict(loader, node):
return OrderedDict(loader.construct_pairs(node)) return OrderedDict(loader.construct_pairs(node))
yaml.add_constructor('!include', _include_yaml) yaml.SafeLoader.add_constructor('!include', _include_yaml)
yaml.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, yaml.SafeLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
_ordered_dict) _ordered_dict)

View File

@ -100,7 +100,7 @@ insteon_hub==0.4.5
jsonrpc-requests==0.1 jsonrpc-requests==0.1
# homeassistant.components.light.lifx # homeassistant.components.light.lifx
liffylights==0.9.3 liffylights==0.9.4
# homeassistant.components.light.limitlessled # homeassistant.components.light.limitlessled
limitlessled==1.0.0 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. Tests template sensor.
""" """
from unittest.mock import patch
import pytest
import homeassistant.core as ha import homeassistant.core as ha
import homeassistant.components.sensor as sensor import homeassistant.components.sensor as sensor
@ -56,8 +53,54 @@ class TestTemplateSensor:
} }
}) })
self.hass.states.set('sensor.test_state', 'Works') self.hass.states.set('sensor.test_state', 'Works')
self.hass.pool.block_till_done() self.hass.pool.block_till_done()
state = self.hass.states.get('sensor.test_template_sensor') state = self.hass.states.get('sensor.test_template_sensor')
assert state.state == 'error' 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') state = self.hass.states.get('sensor.yr_symbol')
assert '46' == state.state
assert state.state.isnumeric() assert state.state.isnumeric()
assert state.attributes.get('unit_of_measurement') is None assert state.attributes.get('unit_of_measurement') is None
def test_custom_setup(self, betamax_session): 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', with patch('homeassistant.components.sensor.yr.requests.Session',
return_value=betamax_session): return_value=betamax_session):
assert sensor.setup(self.hass, { with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
'sensor': { return_value=now):
'platform': 'yr', assert sensor.setup(self.hass, {
'elevation': 0, 'sensor': {
'monitored_conditions': { 'platform': 'yr',
'pressure', 'elevation': 0,
'windDirection', 'monitored_conditions': {
'humidity', 'pressure',
'fog', 'windDirection',
'windSpeed' 'humidity',
'fog',
'windSpeed'
}
} }
} })
})
state = self.hass.states.get('sensor.yr_pressure') 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') 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') 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') 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') state = self.hass.states.get('sensor.yr_wind_speed')
assert 'm/s', state.attributes.get('unit_of_measurement') 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): with self.assertRaises(HomeAssistantError):
config_util.load_yaml_config_file(YAML_PATH) 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): def test_load_yaml_config_preserves_key_order(self):
with open(YAML_PATH, 'w') as f: with open(YAML_PATH, 'w') as f:
f.write('hello: 0\n') 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=protected-access,too-many-public-methods
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
import os import os
import signal
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
import time import time
@ -79,15 +80,15 @@ class TestHomeAssistant(unittest.TestCase):
self.assertFalse(blocking_thread.is_alive()) self.assertFalse(blocking_thread.is_alive())
def test_stopping_with_keyboardinterrupt(self): def test_stopping_with_sigterm(self):
calls = [] calls = []
self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: calls.append(1)) lambda event: calls.append(1))
def raise_keyboardinterrupt(length): def send_sigterm(length):
raise KeyboardInterrupt 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.hass.block_till_stopped()
self.assertEqual(1, len(calls)) self.assertEqual(1, len(calls))