Revert "Bugfix camera streams (#5306)"

This reverts commit 4b43537801.

Revert "Version bump for kodi dependency (#5307)"

This reverts commit 6abad6b76e.

Revert "Add HMWIOSwitch to sensor, binary (#5304)"

This reverts commit 2c3f55acc4.

Revert "Remove GTFS default name & string change"

This reverts commit 6000c59bb5.

Revert "Update pyhomematic 1.19 & small cleanups (#5299)"

This reverts commit a30711f1a0.

Revert "[sensor] Add Dublin bus RTPI sensor (#5257)"

This reverts commit 1219ca3c3b.

Revert "Bugfix group reload (#5292)"

This reverts commit baa8e53e66.

Revert "Support for TrackR device trackers (#5010)"

This reverts commit f7a1d63d52.

Revert "Bump pywemo version."

This reverts commit dc937cc8cf.

Revert "Upgrade to voluptuous to 0.9.3 (#5288)"

This reverts commit d12decc471.

Revert "Upgrade distro to 1.0.2 (#5291)"

This reverts commit 64800fd48c.

Revert "Don't build Adafruit_BBIO - doesn't work on all platforms. (#5281)"

This reverts commit 9a3c0c8cd3.

Revert "Convert flic to synchronous platform. (#5276)"

This reverts commit eb9b95c292.

Revert "Upgrade to aiohttp 1.2 (#4964)"

This reverts commit e68e29e03e.

Revert "Fix TCP sensor to correctly use value_template (#5211)"

This reverts commit 1cf9ae5a01.

Revert "Cleanup language support on TTS (#5255)"

This reverts commit 3f3a3bcc8a.

Revert "Add last triggered to script (#5261)"

This reverts commit 467cb18625.

Revert "Bump flux_led version and make use of PyPi package (#5267)"

This reverts commit 34a9fb01ac.

Revert "Add support for NAD receivers (#5191)"

This reverts commit 3b59e169f1.

Revert "Bugfix async device_tracker see callback (#5259)"

This reverts commit 71fddd26eb.

Revert "Use SHA hash to make token harder to guess (#5258)"

This reverts commit 922308bc1f.
This commit is contained in:
nordlead2005
2017-01-13 22:48:45 -05:00
parent a33f049e1f
commit 2ee8c44021
36 changed files with 536 additions and 1111 deletions

View File

@@ -169,7 +169,6 @@ omit =
homeassistant/components/device_tracker/thomson.py
homeassistant/components/device_tracker/tomato.py
homeassistant/components/device_tracker/tplink.py
homeassistant/components/device_tracker/trackr.py
homeassistant/components/device_tracker/ubus.py
homeassistant/components/device_tracker/volvooncall.py
homeassistant/components/discovery.py
@@ -211,7 +210,6 @@ omit =
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/mpchc.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/nad.py
homeassistant/components/media_player/onkyo.py
homeassistant/components/media_player/panasonic_viera.py
homeassistant/components/media_player/pandora.py
@@ -266,7 +264,6 @@ omit =
homeassistant/components/sensor/bitcoin.py
homeassistant/components/sensor/bom.py
homeassistant/components/sensor/broadlink.py
homeassistant/components/sensor/dublin_bus_transport.py
homeassistant/components/sensor/coinmarketcap.py
homeassistant/components/sensor/cpuspeed.py
homeassistant/components/sensor/cups.py

View File

@@ -19,7 +19,6 @@ DOMAIN = 'bbb_gpio'
# pylint: disable=no-member
def setup(hass, config):
"""Setup the Beaglebone black GPIO component."""
# pylint: disable=import-error
import Adafruit_BBIO.GPIO as GPIO
def cleanup_gpio(event):
@@ -34,41 +33,33 @@ def setup(hass, config):
return True
# noqa: F821
def setup_output(pin):
"""Setup a GPIO as output."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
GPIO.setup(pin, GPIO.OUT)
def setup_input(pin, pull_mode):
"""Setup a GPIO as input."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
GPIO.setup(pin, GPIO.IN, # noqa: F821
GPIO.PUD_DOWN if pull_mode == 'DOWN' # noqa: F821
else GPIO.PUD_UP) # noqa: F821
GPIO.setup(pin, GPIO.IN,
GPIO.PUD_DOWN if pull_mode == 'DOWN' else GPIO.PUD_UP)
def write_output(pin, value):
"""Write a value to a GPIO."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
GPIO.output(pin, value)
def read_input(pin):
"""Read a value from a GPIO."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
return GPIO.input(pin)
def edge_detect(pin, event_callback, bounce):
"""Add detection for RISING and FALLING events."""
# pylint: disable=import-error,undefined-variable
import Adafruit_BBIO.GPIO as GPIO
GPIO.add_event_detect(
pin,

View File

@@ -1,6 +1,6 @@
"""Contains functionality to use flic buttons as a binary sensor."""
import asyncio
import logging
import threading
import voluptuous as vol
@@ -10,6 +10,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.util.async import run_callback_threadsafe
REQUIREMENTS = ['https://github.com/soldag/pyflic/archive/0.4.zip#pyflic==0.4']
@@ -42,7 +43,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def setup_platform(hass, config, add_entities, discovery_info=None):
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Setup the flic platform."""
import pyflic
@@ -60,29 +63,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
def new_button_callback(address):
"""Setup newly verified button as device in home assistant."""
setup_button(hass, config, add_entities, client, address)
hass.add_job(async_setup_button(hass, config, async_add_entities,
client, address))
client.on_new_verified_button = new_button_callback
if discovery:
start_scanning(config, add_entities, client)
start_scanning(hass, config, async_add_entities, client)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: client.close())
# Start the pyflic event handling thread
threading.Thread(target=client.handle_events).start()
def get_info_callback(items):
"""Add entities for already verified buttons."""
addresses = items["bd_addr_of_verified_buttons"] or []
for address in addresses:
setup_button(hass, config, add_entities, client, address)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: client.close())
hass.loop.run_in_executor(None, client.handle_events)
# Get addresses of already verified buttons
client.get_info(get_info_callback)
addresses = yield from async_get_verified_addresses(client)
if addresses:
for address in addresses:
yield from async_setup_button(hass, config, async_add_entities,
client, address)
def start_scanning(config, add_entities, client):
def start_scanning(hass, config, async_add_entities, client):
"""Start a new flic client for scanning & connceting to new buttons."""
import pyflic
@@ -97,20 +97,36 @@ def start_scanning(config, add_entities, client):
address, result)
# Restart scan wizard
start_scanning(config, add_entities, client)
start_scanning(hass, config, async_add_entities, client)
scan_wizard.on_completed = scan_completed_callback
client.add_scan_wizard(scan_wizard)
def setup_button(hass, config, add_entities, client, address):
@asyncio.coroutine
def async_setup_button(hass, config, async_add_entities, client, address):
"""Setup single button device."""
timeout = config.get(CONF_TIMEOUT)
ignored_click_types = config.get(CONF_IGNORED_CLICK_TYPES)
button = FlicButton(hass, client, address, timeout, ignored_click_types)
_LOGGER.info("Connected to button (%s)", address)
add_entities([button])
yield from async_add_entities([button])
@asyncio.coroutine
def async_get_verified_addresses(client):
"""Retrieve addresses of verified buttons."""
future = asyncio.Future()
loop = asyncio.get_event_loop()
def get_info_callback(items):
"""Set the addressed of connected buttons as result of the future."""
addresses = items["bd_addr_of_verified_buttons"]
run_callback_threadsafe(loop, future.set_result, addresses)
client.get_info(get_info_callback)
return future
class FlicButton(BinarySensorDevice):

View File

@@ -8,7 +8,6 @@ https://home-assistant.io/components/camera/
import asyncio
from datetime import timedelta
import logging
import hashlib
from aiohttp import web
@@ -48,13 +47,11 @@ class Camera(Entity):
def __init__(self):
"""Initialize a camera."""
self.is_streaming = False
self._access_token = hashlib.sha256(
str.encode(str(id(self)))).hexdigest()
@property
def access_token(self):
"""Access token for this camera."""
return self._access_token
return str(id(self))
@property
def should_poll(self):

View File

@@ -84,15 +84,9 @@ class FFmpegCamera(Camera):
if not data:
break
response.write(data)
except asyncio.CancelledError:
_LOGGER.debug("Close stream by browser.")
response = None
finally:
yield from stream.close()
if response is not None:
yield from response.write_eof()
self.hass.async_add_job(stream.close())
yield from response.write_eof()
@property
def name(self):

View File

@@ -124,13 +124,9 @@ class MjpegCamera(Camera):
except asyncio.TimeoutError:
raise HTTPGatewayTimeout()
except asyncio.CancelledError:
_LOGGER.debug("Close stream by browser.")
response = None
finally:
if stream is not None:
stream.close()
yield from stream.close()
if response is not None:
yield from response.write_eof()

View File

@@ -276,13 +276,9 @@ class SynologyCamera(Camera):
_LOGGER.exception("Error on %s", streaming_url)
raise HTTPGatewayTimeout()
except asyncio.CancelledError:
_LOGGER.debug("Close stream by browser.")
response = None
finally:
if stream is not None:
stream.close()
self.hass.async_add_job(stream.release())
if response is not None:
yield from response.write_eof()

View File

@@ -158,7 +158,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
None, platform.get_scanner, hass, {DOMAIN: p_config})
elif hasattr(platform, 'async_setup_scanner'):
setup = yield from platform.async_setup_scanner(
hass, p_config, tracker.async_see)
hass, p_config, tracker.see)
elif hasattr(platform, 'setup_scanner'):
setup = yield from hass.loop.run_in_executor(
None, platform.setup_scanner, hass, p_config, tracker.see)

View File

@@ -1,79 +0,0 @@
"""
Support for the TrackR platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.trackr/
"""
import logging
import voluptuous as vol
from homeassistant.components.device_tracker import PLATFORM_SCHEMA
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_utc_time_change
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['pytrackr==0.0.5']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
def setup_scanner(hass, config: dict, see):
"""Validate the configuration and return a TrackR scanner."""
TrackRDeviceScanner(hass, config, see)
return True
class TrackRDeviceScanner(object):
"""A class representing a TrackR device."""
def __init__(self, hass, config: dict, see) -> None:
"""Initialize the TrackR device scanner."""
from pytrackr.api import trackrApiInterface
self.hass = hass
self.api = trackrApiInterface(config.get(CONF_USERNAME),
config.get(CONF_PASSWORD))
self.see = see
self.devices = self.api.get_trackrs()
self._update_info()
track_utc_time_change(self.hass, self._update_info,
second=range(0, 60, 30))
def _update_info(self, now=None) -> None:
"""Update the device info."""
_LOGGER.debug('Updating devices %s', now)
# Update self.devices to collect new devices added
# to the users account.
self.devices = self.api.get_trackrs()
for trackr in self.devices:
trackr.update_state()
trackr_id = trackr.tracker_id()
trackr_device_id = trackr.id()
lost = trackr.lost()
dev_id = trackr.name().replace(" ", "_")
if dev_id is None:
dev_id = trackr_id
location = trackr.last_known_location()
lat = location['latitude']
lon = location['longitude']
attrs = {
'last_updated': trackr.last_updated(),
'last_seen': trackr.last_time_seen(),
'trackr_id': trackr_id,
'id': trackr_device_id,
'lost': lost,
'battery_level': trackr.battery_level()
}
self.see(
dev_id=dev_id, gps=(lat, lon), attributes=attrs
)

View File

@@ -91,7 +91,7 @@ class HueOneLightStateView(HomeAssistantView):
self.config = config
@core.callback
def get(self, request, username, entity_id):
def get(self, request, username, entity_id=None):
"""Process a request to get the state of an individual light."""
hass = request.app['hass']
entity_id = self.config.number_to_entity_id(entity_id)

View File

@@ -359,16 +359,14 @@ class Group(Entity):
"""Start tracking members."""
run_callback_threadsafe(self.hass.loop, self.async_start).result()
@callback
def async_start(self):
"""Start tracking members.
This method must be run in the event loop.
"""
if self._async_unsub_state_changed is None:
self._async_unsub_state_changed = async_track_state_change(
self.hass, self.tracking, self._async_state_changed_listener
)
self._async_unsub_state_changed = async_track_state_change(
self.hass, self.tracking, self._state_changed_listener
)
def stop(self):
"""Unregister the group from Home Assistant."""
@@ -394,24 +392,20 @@ class Group(Entity):
This method must be run in the event loop.
"""
yield from super().async_remove()
if self._async_unsub_state_changed:
self._async_unsub_state_changed()
self._async_unsub_state_changed = None
yield from super().async_remove()
@asyncio.coroutine
def _async_state_changed_listener(self, entity_id, old_state, new_state):
@callback
def _state_changed_listener(self, entity_id, old_state, new_state):
"""Respond to a member state changing.
This method must be run in the event loop.
"""
# removed
if self._async_unsub_state_changed is None:
return
self._async_update_group_state(new_state)
yield from self.async_update_ha_state()
self.hass.async_add_job(self.async_update_ha_state())
@property
def _tracking_states(self):

View File

@@ -23,10 +23,10 @@ from homeassistant.config import load_yaml_config_file
from homeassistant.util import Throttle
DOMAIN = 'homematic'
REQUIREMENTS = ["pyhomematic==0.1.19"]
REQUIREMENTS = ["pyhomematic==0.1.18"]
MIN_TIME_BETWEEN_UPDATE_HUB = timedelta(seconds=300)
SCAN_INTERVAL = timedelta(seconds=30)
MIN_TIME_BETWEEN_UPDATE_VAR = timedelta(seconds=30)
DISCOVER_SWITCHES = 'homematic.switch'
DISCOVER_LIGHTS = 'homematic.light'
@@ -54,21 +54,19 @@ SERVICE_SET_DEV_VALUE = 'set_dev_value'
HM_DEVICE_TYPES = {
DISCOVER_SWITCHES: [
'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch',
'IPSwitchPowermeter', 'KeyMatic', 'HMWIOSwitch'],
'IPSwitchPowermeter', 'KeyMatic'],
DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer'],
DISCOVER_SENSORS: [
'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion', 'MotionIP',
'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion',
'ThermostatWall', 'AreaThermostat', 'RotaryHandleSensor',
'WaterSensor', 'PowermeterGas', 'LuxSensor', 'WeatherSensor',
'WeatherStation', 'ThermostatWall2', 'TemperatureDiffSensor',
'TemperatureSensor', 'CO2Sensor', 'IPSwitchPowermeter', 'HMWIOSwitch'],
'TemperatureSensor', 'CO2Sensor', 'IPSwitchPowermeter'],
DISCOVER_CLIMATE: [
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2',
'MAXWallThermostat'],
'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2'],
DISCOVER_BINARY_SENSORS: [
'ShutterContact', 'Smoke', 'SmokeV2', 'Motion', 'MotionV2',
'RemoteMotion', 'WeatherSensor', 'TiltSensor', 'IPShutterContact',
'HMWIOSwitch'],
'RemoteMotion', 'WeatherSensor', 'TiltSensor', 'IPShutterContact'],
DISCOVER_COVER: ['Blind', 'KeyBlind']
}
@@ -236,7 +234,7 @@ def setup(hass, config):
"""Setup the Homematic component."""
from pyhomematic import HMConnection
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
component = EntityComponent(_LOGGER, DOMAIN, hass)
hass.data[DATA_DELAY] = config[DOMAIN].get(CONF_DELAY)
hass.data[DATA_DEVINIT] = {}
@@ -463,7 +461,9 @@ def _get_devices(hass, device_type, keys, proxy):
_LOGGER.debug("Handling %s: %s", param, channels)
for channel in channels:
name = _create_ha_name(
name=device.NAME, channel=channel, param=param,
name=device.NAME,
channel=channel,
param=param,
count=len(channels)
)
device_dict = {
@@ -623,6 +623,7 @@ class HMHub(Entity):
state = self._homematic.getServiceMessages(self._name)
self._state = STATE_UNKNOWN if state is None else len(state)
@Throttle(MIN_TIME_BETWEEN_UPDATE_VAR)
def _update_variables_state(self):
"""Retrive all variable data and update hmvariable states."""
if not self._use_variables:
@@ -854,11 +855,11 @@ class HMDevice(Entity):
# Set callbacks
for channel in channels_to_sub:
_LOGGER.debug(
"Subscribe channel %s from %s", str(channel), self._name)
self._hmdevice.setEventCallback(
callback=self._hm_event_callback, bequeath=False,
channel=channel)
_LOGGER.debug("Subscribe channel %s from %s",
str(channel), self._name)
self._hmdevice.setEventCallback(callback=self._hm_event_callback,
bequeath=False,
channel=channel)
def _load_data_from_hm(self):
"""Load first value from pyhomematic."""

View File

@@ -32,7 +32,7 @@ from .const import (
KEY_USE_X_FORWARDED_FOR, KEY_TRUSTED_NETWORKS,
KEY_BANS_ENABLED, KEY_LOGIN_THRESHOLD,
KEY_DEVELOPMENT, KEY_AUTHENTICATED)
from .static import FILE_SENDER, CACHING_FILE_SENDER, staticresource_middleware
from .static import FILE_SENDER, GZIP_FILE_SENDER, staticresource_middleware
from .util import get_real_ip
DOMAIN = 'http'
@@ -272,7 +272,7 @@ class HomeAssistantWSGI(object):
@asyncio.coroutine
def serve_file(request):
"""Serve file from disk."""
res = yield from CACHING_FILE_SENDER.send(request, filepath)
res = yield from GZIP_FILE_SENDER.send(request, filepath)
return res
# aiohttp supports regex matching for variables. Using that as temp

View File

@@ -1,40 +1,69 @@
"""Static file handling for HTTP component."""
import asyncio
import mimetypes
import re
from aiohttp import hdrs
from aiohttp.file_sender import FileSender
from aiohttp.web_urldispatcher import StaticResource
from aiohttp.web_exceptions import HTTPNotModified
from .const import KEY_DEVELOPMENT
_FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
class CachingFileSender(FileSender):
"""FileSender class that caches output if not in dev mode."""
class GzipFileSender(FileSender):
"""FileSender class capable of sending gzip version if available."""
def __init__(self, *args, **kwargs):
"""Initialize the hass file sender."""
super().__init__(*args, **kwargs)
# pylint: disable=invalid-name
orig_sendfile = self._sendfile
@asyncio.coroutine
def send(self, request, filepath):
"""Send filepath to client using request."""
gzip = False
if 'gzip' in request.headers[hdrs.ACCEPT_ENCODING]:
gzip_path = filepath.with_name(filepath.name + '.gz')
@asyncio.coroutine
def sendfile(request, resp, fobj, count):
"""Sendfile that includes a cache header."""
if not request.app[KEY_DEVELOPMENT]:
cache_time = 31 * 86400 # = 1 month
resp.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format(
cache_time)
if gzip_path.is_file():
filepath = gzip_path
gzip = True
yield from orig_sendfile(request, resp, fobj, count)
st = filepath.stat()
# Overwriting like this because __init__ can change implementation.
self._sendfile = sendfile
modsince = request.if_modified_since
if modsince is not None and st.st_mtime <= modsince.timestamp():
raise HTTPNotModified()
ct, encoding = mimetypes.guess_type(str(filepath))
if not ct:
ct = 'application/octet-stream'
resp = self._response_factory()
resp.content_type = ct
if encoding:
resp.headers[hdrs.CONTENT_ENCODING] = encoding
if gzip:
resp.headers[hdrs.VARY] = hdrs.ACCEPT_ENCODING
resp.last_modified = st.st_mtime
# CACHE HACK
if not request.app[KEY_DEVELOPMENT]:
cache_time = 31 * 86400 # = 1 month
resp.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format(
cache_time)
file_size = st.st_size
resp.content_length = file_size
with filepath.open('rb') as f:
yield from self._sendfile(request, resp, f, file_size)
return resp
GZIP_FILE_SENDER = GzipFileSender()
FILE_SENDER = FileSender()
CACHING_FILE_SENDER = CachingFileSender()
@asyncio.coroutine
@@ -48,7 +77,7 @@ def staticresource_middleware(app, handler):
return handler
# pylint: disable=protected-access
inst._file_sender = CACHING_FILE_SENDER
inst._file_sender = GZIP_FILE_SENDER
@asyncio.coroutine
def static_middleware_handler(request):

View File

@@ -17,7 +17,8 @@ from homeassistant.components.light import (
PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['flux_led==0.12']
REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.11.zip'
'#flux_led==0.11']
_LOGGER = logging.getLogger(__name__)

View File

@@ -21,7 +21,7 @@ from homeassistant.const import (
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['jsonrpc-async==0.2']
REQUIREMENTS = ['jsonrpc-async==0.1']
_LOGGER = logging.getLogger(__name__)

View File

@@ -1,182 +0,0 @@
"""
Support for interfacing with NAD receivers through RS-232.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.nad/
"""
import logging
import voluptuous as vol
from homeassistant.components.media_player import (
SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_MUTE, SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
SUPPORT_VOLUME_STEP, SUPPORT_SELECT_SOURCE, MediaPlayerDevice,
PLATFORM_SCHEMA)
from homeassistant.const import (
CONF_NAME, STATE_OFF, STATE_ON)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['https://github.com/joopert/nad_receiver/archive/'
'0.0.2.zip#nad_receiver==0.0.2']
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'NAD Receiver'
DEFAULT_MIN_VOLUME = -92
DEFAULT_MAX_VOLUME = -20
SUPPORT_NAD = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \
SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_VOLUME_STEP | \
SUPPORT_SELECT_SOURCE
CONF_SERIAL_PORT = 'serial_port'
CONF_MIN_VOLUME = 'min_volume'
CONF_MAX_VOLUME = 'max_volume'
CONF_SOURCE_DICT = 'sources'
SOURCE_DICT_SCHEMA = vol.Schema({
vol.Range(min=1, max=10): cv.string
})
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SERIAL_PORT): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_MIN_VOLUME, default=DEFAULT_MIN_VOLUME): int,
vol.Optional(CONF_MAX_VOLUME, default=DEFAULT_MAX_VOLUME): int,
vol.Optional(CONF_SOURCE_DICT, default={}): SOURCE_DICT_SCHEMA,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the NAD platform."""
from nad_receiver import NADReceiver
add_devices([NAD(
config.get(CONF_NAME),
NADReceiver(config.get(CONF_SERIAL_PORT)),
config.get(CONF_MIN_VOLUME),
config.get(CONF_MAX_VOLUME),
config.get(CONF_SOURCE_DICT)
)])
class NAD(MediaPlayerDevice):
"""Representation of a NAD Receiver."""
def __init__(self, name, nad_receiver, min_volume, max_volume,
source_dict):
"""Initialize the NAD Receiver device."""
self._name = name
self._nad_receiver = nad_receiver
self._min_volume = min_volume
self._max_volume = max_volume
self._source_dict = source_dict
self._reverse_mapping = {value: key for key, value in
self._source_dict.items()}
self._volume = None
self._state = None
self._mute = None
self._source = None
self.update()
def calc_volume(self, decibel):
"""
Calculate the volume given the decibel.
Return the volume (0..1).
"""
return abs(self._min_volume - decibel) / abs(
self._min_volume - self._max_volume)
def calc_db(self, volume):
"""
Calculate the decibel given the volume.
Return the dB.
"""
return self._min_volume + round(
abs(self._min_volume - self._max_volume) * volume)
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
def update(self):
"""Retrieve latest state."""
if self._nad_receiver.main_power('?') == 'Off':
self._state = STATE_OFF
else:
self._state = STATE_ON
if self._nad_receiver.main_mute('?') == 'Off':
self._mute = False
else:
self._mute = True
self._volume = self.calc_volume(self._nad_receiver.main_volume('?'))
self._source = self._source_dict.get(
self._nad_receiver.main_source('?'))
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
return self._volume
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self._mute
@property
def supported_media_commands(self):
"""Flag of media commands that are supported."""
return SUPPORT_NAD
def turn_off(self):
"""Turn the media player off."""
self._nad_receiver.main_power('=', 'Off')
def turn_on(self):
"""Turn the media player on."""
self._nad_receiver.main_power('=', 'On')
def volume_up(self):
"""Volume up the media player."""
self._nad_receiver.main_volume('+')
def volume_down(self):
"""Volume down the media player."""
self._nad_receiver.main_volume('-')
def set_volume_level(self, volume):
"""Set volume level, range 0..1."""
self._nad_receiver.main_volume('=', self.calc_db(volume))
def select_source(self, source):
"""Select input source."""
self._nad_receiver.main_source('=', self._reverse_mapping.get(source))
@property
def source(self):
"""Name of the current input source."""
return self._source
@property
def source_list(self):
"""List of available input sources."""
return sorted(list(self._reverse_mapping.keys()))
def mute_volume(self, mute):
"""Mute (true) or unmute (false) media player."""
if mute:
self._nad_receiver.main_mute('=', 'On')
else:
self._nad_receiver.main_mute('=', 'Off')

View File

@@ -31,7 +31,6 @@ CONF_SEQUENCE = "sequence"
ATTR_VARIABLES = 'variables'
ATTR_LAST_ACTION = 'last_action'
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_CAN_CANCEL = 'can_cancel'
_LOGGER = logging.getLogger(__name__)
@@ -156,7 +155,6 @@ class ScriptEntity(ToggleEntity):
def state_attributes(self):
"""Return the state attributes."""
attrs = {}
attrs[ATTR_LAST_TRIGGERED] = self.script.last_triggered
if self.script.can_cancel:
attrs[ATTR_CAN_CANCEL] = self.script.can_cancel
if self.script.last_action:

View File

@@ -1,184 +0,0 @@
"""Support for Dublin RTPI information from data.dublinked.ie.
For more info on the API see :
https://data.gov.ie/dataset/real-time-passenger-information-rtpi-for-dublin-bus-bus-eireann-luas-and-irish-rail/resource/4b9f2c4f-6bf5-4958-a43a-f12dab04cf61
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.dublin_public_transport/
"""
import logging
from datetime import timedelta, datetime
import requests
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
_RESOURCE = 'https://data.dublinked.ie/cgi-bin/rtpi/realtimebusinformation'
ATTR_STOP_ID = "Stop ID"
ATTR_ROUTE = "Route"
ATTR_DUE_IN = "Due in"
ATTR_DUE_AT = "Due at"
ATTR_NEXT_UP = "Later Bus"
CONF_ATTRIBUTION = "Data provided by data.dublinked.ie"
CONF_STOP_ID = 'stopid'
CONF_ROUTE = 'route'
DEFAULT_NAME = 'Next Bus'
ICON = 'mdi:bus'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60)
TIME_STR_FORMAT = "%H:%M"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_STOP_ID): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_ROUTE, default=""): cv.string,
})
def due_in_minutes(timestamp):
"""Get the time in minutes from a timestamp.
The timestamp should be in the format day/month/year hour/minute/second
"""
diff = datetime.strptime(
timestamp, "%d/%m/%Y %H:%M:%S") - dt_util.now().replace(tzinfo=None)
return str(int(diff.total_seconds() / 60))
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Get the Dublin public transport sensor."""
name = config.get(CONF_NAME)
stop = config.get(CONF_STOP_ID)
route = config.get(CONF_ROUTE)
data = PublicTransportData(stop, route)
add_devices([DublinPublicTransportSensor(data, stop, route, name)])
class DublinPublicTransportSensor(Entity):
"""Implementation of an Dublin public transport sensor."""
def __init__(self, data, stop, route, name):
"""Initialize the sensor."""
self.data = data
self._name = name
self._stop = stop
self._route = route
self.update()
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self._times is not None:
next_up = "None"
if len(self._times) > 1:
next_up = self._times[1][ATTR_ROUTE] + " in "
next_up += self._times[1][ATTR_DUE_IN]
return {
ATTR_DUE_IN: self._times[0][ATTR_DUE_IN],
ATTR_DUE_AT: self._times[0][ATTR_DUE_AT],
ATTR_STOP_ID: self._stop,
ATTR_ROUTE: self._times[0][ATTR_ROUTE],
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
ATTR_NEXT_UP: next_up
}
@property
def unit_of_measurement(self):
"""Return the unit this state is expressed in."""
return "min"
@property
def icon(self):
"""Icon to use in the frontend, if any."""
return ICON
def update(self):
"""Get the latest data from opendata.ch and update the states."""
self.data.update()
self._times = self.data.info
try:
self._state = self._times[0][ATTR_DUE_IN]
except TypeError:
pass
class PublicTransportData(object):
"""The Class for handling the data retrieval."""
def __init__(self, stop, route):
"""Initialize the data object."""
self.stop = stop
self.route = route
self.info = [{ATTR_DUE_AT: 'n/a',
ATTR_ROUTE: self.route,
ATTR_DUE_IN: 'n/a'}]
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Get the latest data from opendata.ch."""
params = {}
params['stopid'] = self.stop
if len(self.route) > 0:
params['routeid'] = self.route
params['maxresults'] = 2
params['format'] = 'json'
response = requests.get(
_RESOURCE,
params,
timeout=10)
if response.status_code != 200:
self.info = [{ATTR_DUE_AT: 'n/a',
ATTR_ROUTE: self.route,
ATTR_DUE_IN: 'n/a'}]
return
result = response.json()
if str(result['errorcode']) != '0':
self.info = [{ATTR_DUE_AT: 'n/a',
ATTR_ROUTE: self.route,
ATTR_DUE_IN: 'n/a'}]
return
self.info = []
for item in result['results']:
due_at = item.get('departuredatetime')
route = item.get('route')
if due_at is not None and route is not None:
bus_data = {ATTR_DUE_AT: due_at,
ATTR_ROUTE: route,
ATTR_DUE_IN:
due_in_minutes(due_at)}
self.info.append(bus_data)
if len(self.info) == 0:
self.info = [{ATTR_DUE_AT: 'n/a',
ATTR_ROUTE: self.route,
ATTR_DUE_IN: 'n/a'}]

View File

@@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_ORIGIN): cv.string,
vol.Required(CONF_DESTINATION): cv.string,
vol.Required(CONF_DATA): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@@ -226,9 +226,9 @@ class GTFSDepartureSensor(Entity):
self.destination)
if not self._departure:
self._state = 0
self._attributes = {'Info': 'No more departures today'}
self._attributes = {'Info': 'No more bus today'}
if self._name == '':
self._name = (self._custom_name or DEFAULT_NAME)
self._name = (self._custom_name or "GTFS Sensor")
return
self._state = self._departure['minutes_until_departure']

View File

@@ -16,6 +16,7 @@ from homeassistant.const import (
CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.template import Template
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -56,7 +57,7 @@ class TcpSensor(Entity):
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
value_template = Template(value_template, hass)
self._hass = hass
self._config = {

View File

@@ -5,6 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/tts/
"""
import asyncio
import functools
import hashlib
import logging
import mimetypes
@@ -246,6 +247,8 @@ class SpeechManager(object):
def async_register_engine(self, engine, provider, config):
"""Register a TTS provider."""
provider.hass = self.hass
if CONF_LANG in config:
provider.language = config.get(CONF_LANG)
self.providers[engine] = provider
@asyncio.coroutine
@@ -254,16 +257,9 @@ class SpeechManager(object):
This method is a coroutine.
"""
provider = self.providers[engine]
language = language or provider.default_language
if language is None or \
language not in provider.supported_languages:
raise HomeAssistantError("Not supported language {0}".format(
language))
msg_hash = hashlib.sha1(bytes(message, 'utf-8')).hexdigest()
key = KEY_PATTERN.format(msg_hash, language, engine).lower()
language_key = language or self.providers[engine].language
key = KEY_PATTERN.format(msg_hash, language_key, engine).lower()
use_cache = cache if cache is not None else self.use_cache
# is speech allready in memory
@@ -391,22 +387,13 @@ class Provider(object):
"""Represent a single provider."""
hass = None
language = None
@property
def default_language(self):
"""Default language."""
return None
@property
def supported_languages(self):
"""List of supported languages."""
return None
def get_tts_audio(self, message, language):
def get_tts_audio(self, message, language=None):
"""Load tts audio file from provider."""
raise NotImplementedError()
def async_get_tts_audio(self, message, language):
def async_get_tts_audio(self, message, language=None):
"""Load tts audio file from provider.
Return a tuple of file extension and data as bytes.
@@ -414,7 +401,8 @@ class Provider(object):
This method must be run in the event loop and returns a coroutine.
"""
return self.hass.loop.run_in_executor(
None, self.get_tts_audio, message, language)
None,
functools.partial(self.get_tts_audio, message, language=language))
class TextToSpeechView(HomeAssistantView):

View File

@@ -6,50 +6,28 @@ https://home-assistant.io/components/demo/
"""
import os
import voluptuous as vol
from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG
SUPPORT_LANGUAGES = [
'en', 'de'
]
DEFAULT_LANG = 'en'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES),
})
from homeassistant.components.tts import Provider
def get_engine(hass, config):
"""Setup Demo speech component."""
return DemoProvider(config[CONF_LANG])
return DemoProvider()
class DemoProvider(Provider):
"""Demo speech api provider."""
def __init__(self, lang):
"""Initialize demo provider."""
self._lang = lang
def __init__(self):
"""Initialize demo provider for TTS."""
self.language = 'en'
@property
def default_language(self):
"""Default language."""
return self._lang
@property
def supported_languages(self):
"""List of supported languages."""
return SUPPORT_LANGUAGES
def get_tts_audio(self, message, language):
def get_tts_audio(self, message, language=None):
"""Load TTS from demo."""
filename = os.path.join(os.path.dirname(__file__), "demo.mp3")
try:
with open(filename, 'rb') as voice:
data = voice.read()
except OSError:
return (None, None)
return
return ("mp3", data)

View File

@@ -42,16 +42,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_get_engine(hass, config):
"""Setup Google speech component."""
return GoogleProvider(hass, config[CONF_LANG])
return GoogleProvider(hass)
class GoogleProvider(Provider):
"""Google speech api provider."""
def __init__(self, hass, lang):
def __init__(self, hass):
"""Init Google TTS service."""
self.hass = hass
self._lang = lang
self.headers = {
'Referer': "http://translate.google.com/",
'User-Agent': ("Mozilla/5.0 (Windows NT 10.0; WOW64) "
@@ -59,18 +58,8 @@ class GoogleProvider(Provider):
"Chrome/47.0.2526.106 Safari/537.36")
}
@property
def default_language(self):
"""Default language."""
return self._lang
@property
def supported_languages(self):
"""List of supported languages."""
return SUPPORT_LANGUAGES
@asyncio.coroutine
def async_get_tts_audio(self, message, language):
def async_get_tts_audio(self, message, language=None):
"""Load TTS from google."""
from gtts_token import gtts_token
@@ -78,6 +67,11 @@ class GoogleProvider(Provider):
websession = async_get_clientsession(self.hass)
message_parts = self._split_message_to_parts(message)
# If language is not specified or is not supported - use the language
# from the config.
if language not in SUPPORT_LANGUAGES:
language = self.language
data = b''
for idx, part in enumerate(message_parts):
part_token = yield from self.hass.loop.run_in_executor(

View File

@@ -29,31 +29,18 @@ def get_engine(hass, config):
if shutil.which("pico2wave") is None:
_LOGGER.error("'pico2wave' was not found")
return False
return PicoProvider(config[CONF_LANG])
return PicoProvider()
class PicoProvider(Provider):
"""pico speech api provider."""
def __init__(self, lang):
"""Initialize pico provider."""
self._lang = lang
@property
def default_language(self):
"""Default language."""
return self._lang
@property
def supported_languages(self):
"""List of supported languages."""
return SUPPORT_LANGUAGES
def get_tts_audio(self, message, language):
def get_tts_audio(self, message, language=None):
"""Load TTS using pico2wave."""
if language not in SUPPORT_LANGUAGES:
language = self.language
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmpf:
fname = tmpf.name
cmd = ['pico2wave', '--wave', fname, '-l', language, message]
subprocess.call(cmd)
data = None
@@ -65,7 +52,6 @@ class PicoProvider(Provider):
return (None, None)
finally:
os.remove(fname)
if data:
return ("wav", data)
return (None, None)

View File

@@ -93,34 +93,27 @@ class VoiceRSSProvider(Provider):
def __init__(self, hass, conf):
"""Init VoiceRSS TTS service."""
self.hass = hass
self._extension = conf[CONF_CODEC]
self._lang = conf[CONF_LANG]
self.extension = conf.get(CONF_CODEC)
self._form_data = {
'key': conf[CONF_API_KEY],
'hl': conf[CONF_LANG],
'c': (conf[CONF_CODEC]).upper(),
'f': conf[CONF_FORMAT],
self.form_data = {
'key': conf.get(CONF_API_KEY),
'hl': conf.get(CONF_LANG),
'c': (conf.get(CONF_CODEC)).upper(),
'f': conf.get(CONF_FORMAT),
}
@property
def default_language(self):
"""Default language."""
return self._lang
@property
def supported_languages(self):
"""List of supported languages."""
return SUPPORT_LANGUAGES
@asyncio.coroutine
def async_get_tts_audio(self, message, language):
def async_get_tts_audio(self, message, language=None):
"""Load TTS from voicerss."""
websession = async_get_clientsession(self.hass)
form_data = self._form_data.copy()
form_data = self.form_data.copy()
form_data['src'] = message
form_data['hl'] = language
# If language is specified and supported - use it instead of the
# language in the config.
if language in SUPPORT_LANGUAGES:
form_data['hl'] = language
request = None
try:
@@ -148,4 +141,4 @@ class VoiceRSSProvider(Provider):
if request is not None:
yield from request.release()
return (self._extension, data)
return (self.extension, data)

View File

@@ -22,7 +22,7 @@ from homeassistant.const import __version__ as CURRENT_VERSION
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.helpers import event
REQUIREMENTS = ['distro==1.0.2']
REQUIREMENTS = ['distro==1.0.1']
_LOGGER = logging.getLogger(__name__)

View File

@@ -14,7 +14,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
REQUIREMENTS = ['pywemo==0.4.9']
REQUIREMENTS = ['pywemo==0.4.7']
DOMAIN = 'wemo'

View File

@@ -46,7 +46,6 @@ class Script():
self._change_listener = change_listener
self._cur = -1
self.last_action = None
self.last_triggered = None
self.can_cancel = any(CONF_DELAY in action for action
in self.sequence)
self._async_unsub_delay_listener = None
@@ -69,7 +68,6 @@ class Script():
This method is a coroutine.
"""
self.last_triggered = date_util.utcnow()
if self._cur == -1:
self._log('Running script')
self._cur = 0

26
requirements_all.txt Executable file → Normal file
View File

@@ -4,16 +4,16 @@ pyyaml>=3.11,<4
pytz>=2016.10
pip>=7.0.0
jinja2>=2.8
voluptuous==0.9.3
voluptuous==0.9.2
typing>=3,<4
aiohttp==1.2
aiohttp==1.1.6
async_timeout==1.1.0
# homeassistant.components.nuimo_controller
--only-binary=all http://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0
# homeassistant.components.bbb_gpio
# Adafruit_BBIO==1.0.0
Adafruit_BBIO==1.0.0
# homeassistant.components.isy994
PyISY==1.0.7
@@ -90,7 +90,7 @@ denonavr==0.3.0
directpy==0.1
# homeassistant.components.updater
distro==1.0.2
distro==1.0.1
# homeassistant.components.switch.digitalloggers
dlipower==0.7.165
@@ -132,9 +132,6 @@ fitbit==0.2.3
# homeassistant.components.sensor.fixer
fixerio==0.1.1
# homeassistant.components.light.flux_led
flux_led==0.12
# homeassistant.components.notify.free_mobile
freesms==0.1.1
@@ -183,6 +180,9 @@ hikvision==0.4
# homeassistant.components.nest
http://github.com/technicalpickles/python-nest/archive/e6c9d56a8df455d4d7746389811f2c1387e8cb33.zip#python-nest==3.0.3
# homeassistant.components.light.flux_led
https://github.com/Danielhiversen/flux_led/archive/0.11.zip#flux_led==0.11
# homeassistant.components.switch.tplink
https://github.com/GadgetReactor/pyHS100/archive/45fc3548882628bcde3e3d365db341849457bef2.zip#pyHS100==0.2.2
@@ -224,9 +224,6 @@ https://github.com/jabesq/pybotvac/archive/v0.0.1.zip#pybotvac==0.0.1
# homeassistant.components.sensor.sabnzbd
https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1
# homeassistant.components.media_player.nad
https://github.com/joopert/nad_receiver/archive/0.0.2.zip#nad_receiver==0.0.2
# homeassistant.components.media_player.russound_rnet
https://github.com/laf/russound/archive/0.1.6.zip#russound==0.1.6
@@ -278,7 +275,7 @@ insteon_hub==0.4.5
insteonlocal==0.39
# homeassistant.components.media_player.kodi
jsonrpc-async==0.2
jsonrpc-async==0.1
# homeassistant.components.notify.kodi
jsonrpc-requests==0.3
@@ -425,7 +422,7 @@ pyharmony==1.0.12
pyhik==0.0.7
# homeassistant.components.homematic
pyhomematic==0.1.19
pyhomematic==0.1.18
# homeassistant.components.device_tracker.icloud
pyicloud==0.9.1
@@ -514,9 +511,6 @@ python-vlc==1.1.2
# homeassistant.components.wink
python-wink==0.11.0
# homeassistant.components.device_tracker.trackr
pytrackr==0.0.5
# homeassistant.components.device_tracker.unifi
pyunifi==1.3
@@ -530,7 +524,7 @@ pyvera==0.2.21
pywebpush==0.6.1
# homeassistant.components.wemo
pywemo==0.4.9
pywemo==0.4.7
# homeassistant.components.light.yeelight
pyyeelight==1.0-beta

View File

@@ -10,7 +10,6 @@ COMMENT_REQUIREMENTS = (
'RPi.GPIO',
'rpi-rf',
'Adafruit_Python_DHT',
'Adafruit_BBIO',
'fritzconnection',
'pybluez',
'bluepy',

View File

@@ -20,9 +20,9 @@ REQUIRES = [
'pytz>=2016.10',
'pip>=7.0.0',
'jinja2>=2.8',
'voluptuous==0.9.3',
'voluptuous==0.9.2',
'typing>=3,<4',
'aiohttp==1.2',
'aiohttp==1.1.6',
'async_timeout==1.1.0',
]

View File

@@ -1,9 +1,9 @@
"""The tests for the emulated Hue component."""
import asyncio
import json
import unittest
from unittest.mock import patch
import pytest
import requests
from homeassistant import bootstrap, const, core
import homeassistant.components as core_components
@@ -12,12 +12,10 @@ from homeassistant.components import (
)
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.components.emulated_hue.hue_api import (
HUE_API_STATE_ON, HUE_API_STATE_BRI, HueUsernameView,
HueAllLightsStateView, HueOneLightStateView, HueOneLightChangeView)
from homeassistant.components.emulated_hue import Config
HUE_API_STATE_ON, HUE_API_STATE_BRI)
from homeassistant.util.async import run_coroutine_threadsafe
from tests.common import (
get_test_instance_port, mock_http_component_app)
from tests.common import get_test_instance_port, get_test_home_assistant
HTTP_SERVER_PORT = get_test_instance_port()
BRIDGE_SERVER_PORT = get_test_instance_port()
@@ -26,38 +24,41 @@ BRIDGE_URL_BASE = 'http://127.0.0.1:{}'.format(BRIDGE_SERVER_PORT) + '{}'
JSON_HEADERS = {const.HTTP_HEADER_CONTENT_TYPE: const.CONTENT_TYPE_JSON}
@pytest.fixture
def hass_hue(loop, hass):
"""Setup a hass instance for these tests."""
# We need to do this to get access to homeassistant/turn_(on,off)
loop.run_until_complete(
core_components.async_setup(hass, {core.DOMAIN: {}}))
class TestEmulatedHueExposedByDefault(unittest.TestCase):
"""Test class for emulated hue component."""
loop.run_until_complete(bootstrap.async_setup_component(
hass, http.DOMAIN,
{http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}}))
@classmethod
def setUpClass(cls):
"""Setup the class."""
cls.hass = hass = get_test_home_assistant()
with patch('homeassistant.components'
'.emulated_hue.UPNPResponderThread'):
loop.run_until_complete(
bootstrap.async_setup_component(hass, emulated_hue.DOMAIN, {
# We need to do this to get access to homeassistant/turn_(on,off)
run_coroutine_threadsafe(
core_components.async_setup(hass, {core.DOMAIN: {}}), hass.loop
).result()
bootstrap.setup_component(
hass, http.DOMAIN,
{http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}})
with patch('homeassistant.components'
'.emulated_hue.UPNPResponderThread'):
bootstrap.setup_component(hass, emulated_hue.DOMAIN, {
emulated_hue.DOMAIN: {
emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT,
emulated_hue.CONF_EXPOSE_BY_DEFAULT: True
}
}))
})
loop.run_until_complete(
bootstrap.async_setup_component(hass, light.DOMAIN, {
bootstrap.setup_component(cls.hass, light.DOMAIN, {
'light': [
{
'platform': 'demo',
}
]
}))
})
loop.run_until_complete(
bootstrap.async_setup_component(hass, script.DOMAIN, {
bootstrap.setup_component(cls.hass, script.DOMAIN, {
'script': {
'set_kitchen_light': {
'sequence': [
@@ -72,354 +73,338 @@ def hass_hue(loop, hass):
]
}
}
}))
})
loop.run_until_complete(
bootstrap.async_setup_component(hass, media_player.DOMAIN, {
bootstrap.setup_component(cls.hass, media_player.DOMAIN, {
'media_player': [
{
'platform': 'demo',
}
]
}))
# Kitchen light is explicitly excluded from being exposed
kitchen_light_entity = hass.states.get('light.kitchen_lights')
attrs = dict(kitchen_light_entity.attributes)
attrs[emulated_hue.ATTR_EMULATED_HUE] = False
hass.states.async_set(
kitchen_light_entity.entity_id, kitchen_light_entity.state,
attributes=attrs)
# Expose the script
script_entity = hass.states.get('script.set_kitchen_light')
attrs = dict(script_entity.attributes)
attrs[emulated_hue.ATTR_EMULATED_HUE] = True
hass.states.async_set(
script_entity.entity_id, script_entity.state, attributes=attrs
)
return hass
@pytest.fixture
def hue_client(loop, hass_hue, test_client):
"""Create web client for emulated hue api."""
web_app = mock_http_component_app(hass_hue)
config = Config({'type': 'alexa'})
HueUsernameView().register(web_app.router)
HueAllLightsStateView(config).register(web_app.router)
HueOneLightStateView(config).register(web_app.router)
HueOneLightChangeView(config).register(web_app.router)
return loop.run_until_complete(test_client(web_app))
@asyncio.coroutine
def test_discover_lights(hue_client):
"""Test the discovery of lights."""
result = yield from hue_client.get('/api/username/lights')
assert result.status == 200
assert 'application/json' in result.headers['content-type']
result_json = yield from result.json()
devices = set(val['uniqueid'] for val in result_json.values())
# Make sure the lights we added to the config are there
assert 'light.ceiling_lights' in devices
assert 'light.bed_light' in devices
assert 'script.set_kitchen_light' in devices
assert 'light.kitchen_lights' not in devices
assert 'media_player.living_room' in devices
assert 'media_player.bedroom' in devices
assert 'media_player.walkman' in devices
assert 'media_player.lounge_room' in devices
@asyncio.coroutine
def test_get_light_state(hass_hue, hue_client):
"""Test the getting of light state."""
# Turn office light on and set to 127 brightness
yield from hass_hue.services.async_call(
light.DOMAIN, const.SERVICE_TURN_ON,
{
const.ATTR_ENTITY_ID: 'light.ceiling_lights',
light.ATTR_BRIGHTNESS: 127
},
blocking=True)
office_json = yield from perform_get_light_state(
hue_client, 'light.ceiling_lights', 200)
assert office_json['state'][HUE_API_STATE_ON] is True
assert office_json['state'][HUE_API_STATE_BRI] == 127
# Check all lights view
result = yield from hue_client.get('/api/username/lights')
assert result.status == 200
assert 'application/json' in result.headers['content-type']
result_json = yield from result.json()
assert 'light.ceiling_lights' in result_json
assert result_json['light.ceiling_lights']['state'][HUE_API_STATE_BRI] == \
127
# Turn bedroom light off
yield from hass_hue.services.async_call(
light.DOMAIN, const.SERVICE_TURN_OFF,
{
const.ATTR_ENTITY_ID: 'light.bed_light'
},
blocking=True)
bedroom_json = yield from perform_get_light_state(
hue_client, 'light.bed_light', 200)
assert bedroom_json['state'][HUE_API_STATE_ON] is False
assert bedroom_json['state'][HUE_API_STATE_BRI] == 0
# Make sure kitchen light isn't accessible
yield from perform_get_light_state(
hue_client, 'light.kitchen_lights', 404)
@asyncio.coroutine
def test_put_light_state(hass_hue, hue_client):
"""Test the seeting of light states."""
yield from perform_put_test_on_ceiling_lights(hass_hue, hue_client)
# Turn the bedroom light on first
yield from hass_hue.services.async_call(
light.DOMAIN, const.SERVICE_TURN_ON,
{const.ATTR_ENTITY_ID: 'light.bed_light',
light.ATTR_BRIGHTNESS: 153},
blocking=True)
bed_light = hass_hue.states.get('light.bed_light')
assert bed_light.state == STATE_ON
assert bed_light.attributes[light.ATTR_BRIGHTNESS] == 153
# Go through the API to turn it off
bedroom_result = yield from perform_put_light_state(
hass_hue, hue_client,
'light.bed_light', False)
bedroom_result_json = yield from bedroom_result.json()
assert bedroom_result.status == 200
assert 'application/json' in bedroom_result.headers['content-type']
assert len(bedroom_result_json) == 1
# Check to make sure the state changed
bed_light = hass_hue.states.get('light.bed_light')
assert bed_light.state == STATE_OFF
# Make sure we can't change the kitchen light state
kitchen_result = yield from perform_put_light_state(
hass_hue, hue_client,
'light.kitchen_light', True)
assert kitchen_result.status == 404
@asyncio.coroutine
def test_put_light_state_script(hass_hue, hue_client):
"""Test the setting of script variables."""
# Turn the kitchen light off first
yield from hass_hue.services.async_call(
light.DOMAIN, const.SERVICE_TURN_OFF,
{const.ATTR_ENTITY_ID: 'light.kitchen_lights'},
blocking=True)
# Emulated hue converts 0-100% to 0-255.
level = 23
brightness = round(level * 255 / 100)
script_result = yield from perform_put_light_state(
hass_hue, hue_client,
'script.set_kitchen_light', True, brightness)
script_result_json = yield from script_result.json()
assert script_result.status == 200
assert len(script_result_json) == 2
kitchen_light = hass_hue.states.get('light.kitchen_lights')
assert kitchen_light.state == 'on'
assert kitchen_light.attributes[light.ATTR_BRIGHTNESS] == level
@asyncio.coroutine
def test_put_light_state_media_player(hass_hue, hue_client):
"""Test turning on media player and setting volume."""
# Turn the music player off first
yield from hass_hue.services.async_call(
media_player.DOMAIN, const.SERVICE_TURN_OFF,
{const.ATTR_ENTITY_ID: 'media_player.walkman'},
blocking=True)
# Emulated hue converts 0.0-1.0 to 0-255.
level = 0.25
brightness = round(level * 255)
mp_result = yield from perform_put_light_state(
hass_hue, hue_client,
'media_player.walkman', True, brightness)
mp_result_json = yield from mp_result.json()
assert mp_result.status == 200
assert len(mp_result_json) == 2
walkman = hass_hue.states.get('media_player.walkman')
assert walkman.state == 'playing'
assert walkman.attributes[media_player.ATTR_MEDIA_VOLUME_LEVEL] == level
# pylint: disable=invalid-name
@asyncio.coroutine
def test_put_with_form_urlencoded_content_type(hass_hue, hue_client):
"""Test the form with urlencoded content."""
# Needed for Alexa
yield from perform_put_test_on_ceiling_lights(
hass_hue, hue_client, 'application/x-www-form-urlencoded')
# Make sure we fail gracefully when we can't parse the data
data = {'key1': 'value1', 'key2': 'value2'}
result = yield from hue_client.put(
'/api/username/lights/light.ceiling_lights/state',
headers={
'content-type': 'application/x-www-form-urlencoded'
},
data=data,
)
assert result.status == 400
@asyncio.coroutine
def test_entity_not_found(hue_client):
"""Test for entity which are not found."""
result = yield from hue_client.get(
'/api/username/lights/not.existant_entity')
assert result.status == 404
result = yield from hue_client.put(
'/api/username/lights/not.existant_entity/state')
assert result.status == 404
@asyncio.coroutine
def test_allowed_methods(hue_client):
"""Test the allowed methods."""
result = yield from hue_client.get(
'/api/username/lights/light.ceiling_lights/state')
assert result.status == 405
result = yield from hue_client.put(
'/api/username/lights/light.ceiling_lights')
assert result.status == 405
result = yield from hue_client.put(
'/api/username/lights')
assert result.status == 405
@asyncio.coroutine
def test_proper_put_state_request(hue_client):
"""Test the request to set the state."""
# Test proper on value parsing
result = yield from hue_client.put(
'/api/username/lights/{}/state'.format(
'light.ceiling_lights'),
})
cls.hass.start()
# Kitchen light is explicitly excluded from being exposed
kitchen_light_entity = cls.hass.states.get('light.kitchen_lights')
attrs = dict(kitchen_light_entity.attributes)
attrs[emulated_hue.ATTR_EMULATED_HUE] = False
cls.hass.states.set(
kitchen_light_entity.entity_id, kitchen_light_entity.state,
attributes=attrs)
# Expose the script
script_entity = cls.hass.states.get('script.set_kitchen_light')
attrs = dict(script_entity.attributes)
attrs[emulated_hue.ATTR_EMULATED_HUE] = True
cls.hass.states.set(
script_entity.entity_id, script_entity.state, attributes=attrs
)
@classmethod
def tearDownClass(cls):
"""Stop the class."""
cls.hass.stop()
def test_discover_lights(self):
"""Test the discovery of lights."""
result = requests.get(
BRIDGE_URL_BASE.format('/api/username/lights'), timeout=5)
self.assertEqual(result.status_code, 200)
self.assertTrue('application/json' in result.headers['content-type'])
result_json = result.json()
# Make sure the lights we added to the config are there
self.assertTrue('light.ceiling_lights' in result_json)
self.assertTrue('light.bed_light' in result_json)
self.assertTrue('script.set_kitchen_light' in result_json)
self.assertTrue('light.kitchen_lights' not in result_json)
self.assertTrue('media_player.living_room' in result_json)
self.assertTrue('media_player.bedroom' in result_json)
self.assertTrue('media_player.walkman' in result_json)
self.assertTrue('media_player.lounge_room' in result_json)
def test_get_light_state(self):
"""Test the getting of light state."""
# Turn office light on and set to 127 brightness
self.hass.services.call(
light.DOMAIN, const.SERVICE_TURN_ON,
{
const.ATTR_ENTITY_ID: 'light.ceiling_lights',
light.ATTR_BRIGHTNESS: 127
},
blocking=True)
office_json = self.perform_get_light_state('light.ceiling_lights', 200)
self.assertEqual(office_json['state'][HUE_API_STATE_ON], True)
self.assertEqual(office_json['state'][HUE_API_STATE_BRI], 127)
# Check all lights view
result = requests.get(
BRIDGE_URL_BASE.format('/api/username/lights'), timeout=5)
self.assertEqual(result.status_code, 200)
self.assertTrue('application/json' in result.headers['content-type'])
result_json = result.json()
self.assertTrue('light.ceiling_lights' in result_json)
self.assertEqual(
result_json['light.ceiling_lights']['state'][HUE_API_STATE_BRI],
127,
)
# Turn bedroom light off
self.hass.services.call(
light.DOMAIN, const.SERVICE_TURN_OFF,
{
const.ATTR_ENTITY_ID: 'light.bed_light'
},
blocking=True)
bedroom_json = self.perform_get_light_state('light.bed_light', 200)
self.assertEqual(bedroom_json['state'][HUE_API_STATE_ON], False)
self.assertEqual(bedroom_json['state'][HUE_API_STATE_BRI], 0)
# Make sure kitchen light isn't accessible
kitchen_url = '/api/username/lights/{}'.format('light.kitchen_lights')
kitchen_result = requests.get(
BRIDGE_URL_BASE.format(kitchen_url), timeout=5)
self.assertEqual(kitchen_result.status_code, 404)
def test_put_light_state(self):
"""Test the seeting of light states."""
self.perform_put_test_on_ceiling_lights()
# Turn the bedroom light on first
self.hass.services.call(
light.DOMAIN, const.SERVICE_TURN_ON,
{const.ATTR_ENTITY_ID: 'light.bed_light',
light.ATTR_BRIGHTNESS: 153},
blocking=True)
bed_light = self.hass.states.get('light.bed_light')
self.assertEqual(bed_light.state, STATE_ON)
self.assertEqual(bed_light.attributes[light.ATTR_BRIGHTNESS], 153)
# Go through the API to turn it off
bedroom_result = self.perform_put_light_state(
'light.bed_light', False)
bedroom_result_json = bedroom_result.json()
self.assertEqual(bedroom_result.status_code, 200)
self.assertTrue(
'application/json' in bedroom_result.headers['content-type'])
self.assertEqual(len(bedroom_result_json), 1)
# Check to make sure the state changed
bed_light = self.hass.states.get('light.bed_light')
self.assertEqual(bed_light.state, STATE_OFF)
# Make sure we can't change the kitchen light state
kitchen_result = self.perform_put_light_state(
'light.kitchen_light', True)
self.assertEqual(kitchen_result.status_code, 404)
def test_put_light_state_script(self):
"""Test the setting of script variables."""
# Turn the kitchen light off first
self.hass.services.call(
light.DOMAIN, const.SERVICE_TURN_OFF,
{const.ATTR_ENTITY_ID: 'light.kitchen_lights'},
blocking=True)
# Emulated hue converts 0-100% to 0-255.
level = 23
brightness = round(level * 255 / 100)
script_result = self.perform_put_light_state(
'script.set_kitchen_light', True, brightness)
script_result_json = script_result.json()
self.assertEqual(script_result.status_code, 200)
self.assertEqual(len(script_result_json), 2)
kitchen_light = self.hass.states.get('light.kitchen_lights')
self.assertEqual(kitchen_light.state, 'on')
self.assertEqual(
kitchen_light.attributes[light.ATTR_BRIGHTNESS],
level)
def test_put_light_state_media_player(self):
"""Test turning on media player and setting volume."""
# Turn the music player off first
self.hass.services.call(
media_player.DOMAIN, const.SERVICE_TURN_OFF,
{const.ATTR_ENTITY_ID: 'media_player.walkman'},
blocking=True)
# Emulated hue converts 0.0-1.0 to 0-255.
level = 0.25
brightness = round(level * 255)
mp_result = self.perform_put_light_state(
'media_player.walkman', True, brightness)
mp_result_json = mp_result.json()
self.assertEqual(mp_result.status_code, 200)
self.assertEqual(len(mp_result_json), 2)
walkman = self.hass.states.get('media_player.walkman')
self.assertEqual(walkman.state, 'playing')
self.assertEqual(
walkman.attributes[media_player.ATTR_MEDIA_VOLUME_LEVEL],
level)
# pylint: disable=invalid-name
def test_put_with_form_urlencoded_content_type(self):
"""Test the form with urlencoded content."""
# Needed for Alexa
self.perform_put_test_on_ceiling_lights(
'application/x-www-form-urlencoded')
# Make sure we fail gracefully when we can't parse the data
data = {'key1': 'value1', 'key2': 'value2'}
result = requests.put(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}/state'.format(
'light.ceiling_lights')), data=data)
self.assertEqual(result.status_code, 400)
def test_entity_not_found(self):
"""Test for entity which are not found."""
result = requests.get(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}'.format("not.existant_entity")),
timeout=5)
self.assertEqual(result.status_code, 404)
result = requests.put(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}/state'.format("non.existant_entity")),
timeout=5)
self.assertEqual(result.status_code, 404)
def test_allowed_methods(self):
"""Test the allowed methods."""
result = requests.get(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}/state'.format(
"light.ceiling_lights")))
self.assertEqual(result.status_code, 405)
result = requests.put(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}'.format("light.ceiling_lights")),
data={'key1': 'value1'})
self.assertEqual(result.status_code, 405)
result = requests.put(
BRIDGE_URL_BASE.format('/api/username/lights'),
data={'key1': 'value1'})
self.assertEqual(result.status_code, 405)
def test_proper_put_state_request(self):
"""Test the request to set the state."""
# Test proper on value parsing
result = requests.put(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}/state'.format(
'light.ceiling_lights')),
data=json.dumps({HUE_API_STATE_ON: 1234}))
assert result.status == 400
self.assertEqual(result.status_code, 400)
# Test proper brightness value parsing
result = yield from hue_client.put(
'/api/username/lights/{}/state'.format(
'light.ceiling_lights'),
data=json.dumps({
HUE_API_STATE_ON: True,
HUE_API_STATE_BRI: 'Hello world!'
}))
# Test proper brightness value parsing
result = requests.put(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}/state'.format(
'light.ceiling_lights')), data=json.dumps({
HUE_API_STATE_ON: True,
HUE_API_STATE_BRI: 'Hello world!'
}))
assert result.status == 400
self.assertEqual(result.status_code, 400)
# pylint: disable=invalid-name
def perform_put_test_on_ceiling_lights(self,
content_type='application/json'):
"""Test the setting of a light."""
# Turn the office light off first
self.hass.services.call(
light.DOMAIN, const.SERVICE_TURN_OFF,
{const.ATTR_ENTITY_ID: 'light.ceiling_lights'},
blocking=True)
# pylint: disable=invalid-name
def perform_put_test_on_ceiling_lights(hass_hue, hue_client,
content_type='application/json'):
"""Test the setting of a light."""
# Turn the office light off first
yield from hass_hue.services.async_call(
light.DOMAIN, const.SERVICE_TURN_OFF,
{const.ATTR_ENTITY_ID: 'light.ceiling_lights'},
blocking=True)
ceiling_lights = self.hass.states.get('light.ceiling_lights')
self.assertEqual(ceiling_lights.state, STATE_OFF)
ceiling_lights = hass_hue.states.get('light.ceiling_lights')
assert ceiling_lights.state == STATE_OFF
# Go through the API to turn it on
office_result = self.perform_put_light_state(
'light.ceiling_lights', True, 56, content_type)
# Go through the API to turn it on
office_result = yield from perform_put_light_state(
hass_hue, hue_client,
'light.ceiling_lights', True, 56, content_type)
office_result_json = office_result.json()
assert office_result.status == 200
assert 'application/json' in office_result.headers['content-type']
self.assertEqual(office_result.status_code, 200)
self.assertTrue(
'application/json' in office_result.headers['content-type'])
office_result_json = yield from office_result.json()
self.assertEqual(len(office_result_json), 2)
assert len(office_result_json) == 2
# Check to make sure the state changed
ceiling_lights = self.hass.states.get('light.ceiling_lights')
self.assertEqual(ceiling_lights.state, STATE_ON)
self.assertEqual(ceiling_lights.attributes[light.ATTR_BRIGHTNESS], 56)
# Check to make sure the state changed
ceiling_lights = hass_hue.states.get('light.ceiling_lights')
assert ceiling_lights.state == STATE_ON
assert ceiling_lights.attributes[light.ATTR_BRIGHTNESS] == 56
def perform_get_light_state(self, entity_id, expected_status):
"""Test the gettting of a light state."""
result = requests.get(
BRIDGE_URL_BASE.format(
'/api/username/lights/{}'.format(entity_id)), timeout=5)
self.assertEqual(result.status_code, expected_status)
@asyncio.coroutine
def perform_get_light_state(client, entity_id, expected_status):
"""Test the gettting of a light state."""
result = yield from client.get('/api/username/lights/{}'.format(entity_id))
if expected_status == 200:
self.assertTrue(
'application/json' in result.headers['content-type'])
assert result.status == expected_status
return result.json()
if expected_status == 200:
assert 'application/json' in result.headers['content-type']
return None
return (yield from result.json())
# pylint: disable=no-self-use
def perform_put_light_state(self, entity_id, is_on, brightness=None,
content_type='application/json'):
"""Test the setting of a light state."""
url = BRIDGE_URL_BASE.format(
'/api/username/lights/{}/state'.format(entity_id))
return None
req_headers = {'Content-Type': content_type}
data = {HUE_API_STATE_ON: is_on}
@asyncio.coroutine
def perform_put_light_state(hass_hue, client, entity_id, is_on,
brightness=None, content_type='application/json'):
"""Test the setting of a light state."""
req_headers = {'Content-Type': content_type}
if brightness is not None:
data[HUE_API_STATE_BRI] = brightness
data = {HUE_API_STATE_ON: is_on}
result = requests.put(
url, data=json.dumps(data), timeout=5, headers=req_headers)
if brightness is not None:
data[HUE_API_STATE_BRI] = brightness
# Wait until state change is complete before continuing
self.hass.block_till_done()
result = yield from client.put(
'/api/username/lights/{}/state'.format(entity_id), headers=req_headers,
data=json.dumps(data).encode())
# Wait until state change is complete before continuing
yield from hass_hue.async_block_till_done()
return result
return result

View File

@@ -9,7 +9,6 @@ from tests.common import (get_test_home_assistant, assert_setup_component)
from homeassistant.bootstrap import setup_component
from homeassistant.components.sensor import tcp
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.template import Template
TEST_CONFIG = {
'sensor': {
@@ -20,7 +19,7 @@ TEST_CONFIG = {
tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1,
tcp.CONF_PAYLOAD: 'test_payload',
tcp.CONF_UNIT_OF_MEASUREMENT: 'test_unit',
tcp.CONF_VALUE_TEMPLATE: Template('test_template'),
tcp.CONF_VALUE_TEMPLATE: 'test_template',
tcp.CONF_VALUE_ON: 'test_on',
tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1
},
@@ -253,7 +252,7 @@ class TestTCPSensor(unittest.TestCase):
mock_socket = mock_socket().__enter__()
mock_socket.recv.return_value = test_value.encode()
config = copy(TEST_CONFIG['sensor'])
config[tcp.CONF_VALUE_TEMPLATE] = Template('{{ value }} {{ 1+1 }}')
config[tcp.CONF_VALUE_TEMPLATE] = '{{ value }} {{ 1+1 }}'
sensor = tcp.TcpSensor(self.hass, config)
assert sensor._state == '%s 2' % test_value
@@ -266,6 +265,6 @@ class TestTCPSensor(unittest.TestCase):
mock_socket = mock_socket().__enter__()
mock_socket.recv.return_value = test_value.encode()
config = copy(TEST_CONFIG['sensor'])
config[tcp.CONF_VALUE_TEMPLATE] = Template("{{ this won't work")
config[tcp.CONF_VALUE_TEMPLATE] = "{{ this won't work"
sensor = tcp.TcpSensor(self.hass, config)
assert sensor.update() is None

View File

@@ -22,7 +22,7 @@ class TestTTS(object):
def setup_method(self):
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.demo_provider = DemoProvider('en')
self.demo_provider = DemoProvider()
self.default_tts_cache = self.hass.config.path(tts.DEFAULT_CACHE_DIR)
def teardown_method(self):
@@ -95,7 +95,7 @@ class TestTTS(object):
config = {
tts.DOMAIN: {
'platform': 'demo',
'language': 'de'
'language': 'lang'
}
}
@@ -111,23 +111,11 @@ class TestTTS(object):
assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
assert calls[0].data[ATTR_MEDIA_CONTENT_ID].find(
"/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd"
"_de_demo.mp3") \
"_lang_demo.mp3") \
!= -1
assert os.path.isfile(os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_demo.mp3"))
def test_setup_component_and_test_service_with_wrong_conf_language(self):
"""Setup the demo platform and call service with wrong config."""
config = {
tts.DOMAIN: {
'platform': 'demo',
'language': 'ru'
}
}
with assert_setup_component(0, tts.DOMAIN):
setup_component(self.hass, tts.DOMAIN, config)
"265944c108cbb00b2a621be5930513e03a0bb2cd_lang_demo.mp3"))
def test_setup_component_and_test_service_with_service_language(self):
"""Setup the demo platform and call service."""
@@ -139,35 +127,6 @@ class TestTTS(object):
}
}
with assert_setup_component(1, tts.DOMAIN):
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(tts.DOMAIN, 'demo_say', {
tts.ATTR_MESSAGE: "I person is on front of your door.",
tts.ATTR_LANGUAGE: "de",
})
self.hass.block_till_done()
assert len(calls) == 1
assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
assert calls[0].data[ATTR_MEDIA_CONTENT_ID].find(
"/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd"
"_de_demo.mp3") \
!= -1
assert os.path.isfile(os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_demo.mp3"))
def test_setup_component_test_service_with_wrong_service_language(self):
"""Setup the demo platform and call service."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
config = {
tts.DOMAIN: {
'platform': 'demo',
}
}
with assert_setup_component(1, tts.DOMAIN):
setup_component(self.hass, tts.DOMAIN, config)
@@ -177,8 +136,13 @@ class TestTTS(object):
})
self.hass.block_till_done()
assert len(calls) == 0
assert not os.path.isfile(os.path.join(
assert len(calls) == 1
assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
assert calls[0].data[ATTR_MEDIA_CONTENT_ID].find(
"/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd"
"_lang_demo.mp3") \
!= -1
assert os.path.isfile(os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_lang_demo.mp3"))
@@ -234,7 +198,7 @@ class TestTTS(object):
assert len(calls) == 1
req = requests.get(calls[0].data[ATTR_MEDIA_CONTENT_ID])
_, demo_data = self.demo_provider.get_tts_audio("bla", 'en')
_, demo_data = self.demo_provider.get_tts_audio("bla")
assert req.status_code == 200
assert req.content == demo_data
@@ -355,7 +319,7 @@ class TestTTS(object):
"""Setup demo platform with cache and call service without cache."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
_, demo_data = self.demo_provider.get_tts_audio("bla", 'en')
_, demo_data = self.demo_provider.get_tts_audio("bla")
cache_file = os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_demo.mp3")
@@ -375,7 +339,7 @@ class TestTTS(object):
setup_component(self.hass, tts.DOMAIN, config)
with patch('homeassistant.components.tts.demo.DemoProvider.'
'get_tts_audio', return_value=(None, None)):
'get_tts_audio', return_value=None):
self.hass.services.call(tts.DOMAIN, 'demo_say', {
tts.ATTR_MESSAGE: "I person is on front of your door.",
})
@@ -388,7 +352,7 @@ class TestTTS(object):
!= -1
@patch('homeassistant.components.tts.demo.DemoProvider.get_tts_audio',
return_value=(None, None))
return_value=None)
def test_setup_component_test_with_error_on_get_tts(self, tts_mock):
"""Setup demo platform with wrong get_tts_audio."""
calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA)
@@ -411,7 +375,7 @@ class TestTTS(object):
def test_setup_component_load_cache_retrieve_without_mem_cache(self):
"""Setup component and load cache and get without mem cache."""
_, demo_data = self.demo_provider.get_tts_audio("bla", 'en')
_, demo_data = self.demo_provider.get_tts_audio("bla")
cache_file = os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_demo.mp3")

View File

@@ -353,22 +353,3 @@ class TestScriptHelper(unittest.TestCase):
script_obj.run()
self.hass.block_till_done()
assert len(script_obj._config_cache) == 2
def test_last_triggered(self):
"""Test the last_triggered."""
event = 'test_event'
script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([
{'event': event},
{'delay': {'seconds': 5}},
{'event': event}]))
assert script_obj.last_triggered is None
time = dt_util.utcnow()
with mock.patch('homeassistant.helpers.script.date_util.utcnow',
return_value=time):
script_obj.run()
self.hass.block_till_done()
assert script_obj.last_triggered == time