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

View File

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

View File

@@ -1,6 +1,6 @@
"""Contains functionality to use flic buttons as a binary sensor.""" """Contains functionality to use flic buttons as a binary sensor."""
import asyncio
import logging import logging
import threading
import voluptuous as vol import voluptuous as vol
@@ -10,6 +10,7 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_STOP)
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA) BinarySensorDevice, PLATFORM_SCHEMA)
from homeassistant.util.async import run_callback_threadsafe
REQUIREMENTS = ['https://github.com/soldag/pyflic/archive/0.4.zip#pyflic==0.4'] 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.""" """Setup the flic platform."""
import pyflic import pyflic
@@ -60,29 +63,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
def new_button_callback(address): def new_button_callback(address):
"""Setup newly verified button as device in home assistant.""" """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 client.on_new_verified_button = new_button_callback
if discovery: if discovery:
start_scanning(config, add_entities, client) start_scanning(hass, config, async_add_entities, client)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: client.close()) lambda event: client.close())
hass.loop.run_in_executor(None, client.handle_events)
# 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)
# Get addresses of already verified buttons # 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.""" """Start a new flic client for scanning & connceting to new buttons."""
import pyflic import pyflic
@@ -97,20 +97,36 @@ def start_scanning(config, add_entities, client):
address, result) address, result)
# Restart scan wizard # Restart scan wizard
start_scanning(config, add_entities, client) start_scanning(hass, config, async_add_entities, client)
scan_wizard.on_completed = scan_completed_callback scan_wizard.on_completed = scan_completed_callback
client.add_scan_wizard(scan_wizard) 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.""" """Setup single button device."""
timeout = config.get(CONF_TIMEOUT) timeout = config.get(CONF_TIMEOUT)
ignored_click_types = config.get(CONF_IGNORED_CLICK_TYPES) ignored_click_types = config.get(CONF_IGNORED_CLICK_TYPES)
button = FlicButton(hass, client, address, timeout, ignored_click_types) button = FlicButton(hass, client, address, timeout, ignored_click_types)
_LOGGER.info("Connected to button (%s)", address) _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): class FlicButton(BinarySensorDevice):

View File

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

View File

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

View File

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

View File

@@ -276,13 +276,9 @@ class SynologyCamera(Camera):
_LOGGER.exception("Error on %s", streaming_url) _LOGGER.exception("Error on %s", streaming_url)
raise HTTPGatewayTimeout() raise HTTPGatewayTimeout()
except asyncio.CancelledError:
_LOGGER.debug("Close stream by browser.")
response = None
finally: finally:
if stream is not None: if stream is not None:
stream.close() self.hass.async_add_job(stream.release())
if response is not None: if response is not None:
yield from response.write_eof() 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}) None, platform.get_scanner, hass, {DOMAIN: p_config})
elif hasattr(platform, 'async_setup_scanner'): elif hasattr(platform, 'async_setup_scanner'):
setup = yield from 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'): elif hasattr(platform, 'setup_scanner'):
setup = yield from hass.loop.run_in_executor( setup = yield from hass.loop.run_in_executor(
None, platform.setup_scanner, hass, p_config, tracker.see) 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 self.config = config
@core.callback @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.""" """Process a request to get the state of an individual light."""
hass = request.app['hass'] hass = request.app['hass']
entity_id = self.config.number_to_entity_id(entity_id) entity_id = self.config.number_to_entity_id(entity_id)

View File

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

View File

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

View File

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

View File

@@ -1,40 +1,69 @@
"""Static file handling for HTTP component.""" """Static file handling for HTTP component."""
import asyncio import asyncio
import mimetypes
import re import re
from aiohttp import hdrs from aiohttp import hdrs
from aiohttp.file_sender import FileSender from aiohttp.file_sender import FileSender
from aiohttp.web_urldispatcher import StaticResource from aiohttp.web_urldispatcher import StaticResource
from aiohttp.web_exceptions import HTTPNotModified
from .const import KEY_DEVELOPMENT from .const import KEY_DEVELOPMENT
_FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE) _FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE)
class CachingFileSender(FileSender): class GzipFileSender(FileSender):
"""FileSender class that caches output if not in dev mode.""" """FileSender class capable of sending gzip version if available."""
def __init__(self, *args, **kwargs): # pylint: disable=invalid-name
"""Initialize the hass file sender."""
super().__init__(*args, **kwargs)
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 if gzip_path.is_file():
def sendfile(request, resp, fobj, count): filepath = gzip_path
"""Sendfile that includes a cache header.""" gzip = True
if not request.app[KEY_DEVELOPMENT]:
cache_time = 31 * 86400 # = 1 month
resp.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format(
cache_time)
yield from orig_sendfile(request, resp, fobj, count) st = filepath.stat()
# Overwriting like this because __init__ can change implementation. modsince = request.if_modified_since
self._sendfile = sendfile 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() FILE_SENDER = FileSender()
CACHING_FILE_SENDER = CachingFileSender()
@asyncio.coroutine @asyncio.coroutine
@@ -48,7 +77,7 @@ def staticresource_middleware(app, handler):
return handler return handler
# pylint: disable=protected-access # pylint: disable=protected-access
inst._file_sender = CACHING_FILE_SENDER inst._file_sender = GZIP_FILE_SENDER
@asyncio.coroutine @asyncio.coroutine
def static_middleware_handler(request): def static_middleware_handler(request):

View File

@@ -17,7 +17,8 @@ from homeassistant.components.light import (
PLATFORM_SCHEMA) PLATFORM_SCHEMA)
import homeassistant.helpers.config_validation as cv 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__) _LOGGER = logging.getLogger(__name__)

View File

@@ -21,7 +21,7 @@ from homeassistant.const import (
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['jsonrpc-async==0.2'] REQUIREMENTS = ['jsonrpc-async==0.1']
_LOGGER = logging.getLogger(__name__) _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_VARIABLES = 'variables'
ATTR_LAST_ACTION = 'last_action' ATTR_LAST_ACTION = 'last_action'
ATTR_LAST_TRIGGERED = 'last_triggered'
ATTR_CAN_CANCEL = 'can_cancel' ATTR_CAN_CANCEL = 'can_cancel'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -156,7 +155,6 @@ class ScriptEntity(ToggleEntity):
def state_attributes(self): def state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""
attrs = {} attrs = {}
attrs[ATTR_LAST_TRIGGERED] = self.script.last_triggered
if self.script.can_cancel: if self.script.can_cancel:
attrs[ATTR_CAN_CANCEL] = self.script.can_cancel attrs[ATTR_CAN_CANCEL] = self.script.can_cancel
if self.script.last_action: 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_ORIGIN): cv.string,
vol.Required(CONF_DESTINATION): cv.string, vol.Required(CONF_DESTINATION): cv.string,
vol.Required(CONF_DATA): 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) self.destination)
if not self._departure: if not self._departure:
self._state = 0 self._state = 0
self._attributes = {'Info': 'No more departures today'} self._attributes = {'Info': 'No more bus today'}
if self._name == '': if self._name == '':
self._name = (self._custom_name or DEFAULT_NAME) self._name = (self._custom_name or "GTFS Sensor")
return return
self._state = self._departure['minutes_until_departure'] self._state = self._departure['minutes_until_departure']

View File

@@ -16,6 +16,7 @@ from homeassistant.const import (
CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE) CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE)
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.template import Template
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -56,7 +57,7 @@ class TcpSensor(Entity):
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None: if value_template is not None:
value_template.hass = hass value_template = Template(value_template, hass)
self._hass = hass self._hass = hass
self._config = { 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/ https://home-assistant.io/components/tts/
""" """
import asyncio import asyncio
import functools
import hashlib import hashlib
import logging import logging
import mimetypes import mimetypes
@@ -246,6 +247,8 @@ class SpeechManager(object):
def async_register_engine(self, engine, provider, config): def async_register_engine(self, engine, provider, config):
"""Register a TTS provider.""" """Register a TTS provider."""
provider.hass = self.hass provider.hass = self.hass
if CONF_LANG in config:
provider.language = config.get(CONF_LANG)
self.providers[engine] = provider self.providers[engine] = provider
@asyncio.coroutine @asyncio.coroutine
@@ -254,16 +257,9 @@ class SpeechManager(object):
This method is a coroutine. 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() 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 use_cache = cache if cache is not None else self.use_cache
# is speech allready in memory # is speech allready in memory
@@ -391,22 +387,13 @@ class Provider(object):
"""Represent a single provider.""" """Represent a single provider."""
hass = None hass = None
language = None
@property def get_tts_audio(self, message, language=None):
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):
"""Load tts audio file from provider.""" """Load tts audio file from provider."""
raise NotImplementedError() 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. """Load tts audio file from provider.
Return a tuple of file extension and data as bytes. 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. This method must be run in the event loop and returns a coroutine.
""" """
return self.hass.loop.run_in_executor( 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): class TextToSpeechView(HomeAssistantView):

View File

@@ -6,50 +6,28 @@ https://home-assistant.io/components/demo/
""" """
import os import os
import voluptuous as vol from homeassistant.components.tts import Provider
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),
})
def get_engine(hass, config): def get_engine(hass, config):
"""Setup Demo speech component.""" """Setup Demo speech component."""
return DemoProvider(config[CONF_LANG]) return DemoProvider()
class DemoProvider(Provider): class DemoProvider(Provider):
"""Demo speech api provider.""" """Demo speech api provider."""
def __init__(self, lang): def __init__(self):
"""Initialize demo provider.""" """Initialize demo provider for TTS."""
self._lang = lang self.language = 'en'
@property def get_tts_audio(self, message, language=None):
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):
"""Load TTS from demo.""" """Load TTS from demo."""
filename = os.path.join(os.path.dirname(__file__), "demo.mp3") filename = os.path.join(os.path.dirname(__file__), "demo.mp3")
try: try:
with open(filename, 'rb') as voice: with open(filename, 'rb') as voice:
data = voice.read() data = voice.read()
except OSError: except OSError:
return (None, None) return
return ("mp3", data) return ("mp3", data)

View File

@@ -42,16 +42,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine @asyncio.coroutine
def async_get_engine(hass, config): def async_get_engine(hass, config):
"""Setup Google speech component.""" """Setup Google speech component."""
return GoogleProvider(hass, config[CONF_LANG]) return GoogleProvider(hass)
class GoogleProvider(Provider): class GoogleProvider(Provider):
"""Google speech api provider.""" """Google speech api provider."""
def __init__(self, hass, lang): def __init__(self, hass):
"""Init Google TTS service.""" """Init Google TTS service."""
self.hass = hass self.hass = hass
self._lang = lang
self.headers = { self.headers = {
'Referer': "http://translate.google.com/", 'Referer': "http://translate.google.com/",
'User-Agent': ("Mozilla/5.0 (Windows NT 10.0; WOW64) " '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") "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 @asyncio.coroutine
def async_get_tts_audio(self, message, language): def async_get_tts_audio(self, message, language=None):
"""Load TTS from google.""" """Load TTS from google."""
from gtts_token import gtts_token from gtts_token import gtts_token
@@ -78,6 +67,11 @@ class GoogleProvider(Provider):
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
message_parts = self._split_message_to_parts(message) 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'' data = b''
for idx, part in enumerate(message_parts): for idx, part in enumerate(message_parts):
part_token = yield from self.hass.loop.run_in_executor( 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: if shutil.which("pico2wave") is None:
_LOGGER.error("'pico2wave' was not found") _LOGGER.error("'pico2wave' was not found")
return False return False
return PicoProvider(config[CONF_LANG]) return PicoProvider()
class PicoProvider(Provider): class PicoProvider(Provider):
"""pico speech api provider.""" """pico speech api provider."""
def __init__(self, lang): def get_tts_audio(self, message, language=None):
"""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):
"""Load TTS using pico2wave.""" """Load TTS using pico2wave."""
if language not in SUPPORT_LANGUAGES:
language = self.language
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmpf: with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmpf:
fname = tmpf.name fname = tmpf.name
cmd = ['pico2wave', '--wave', fname, '-l', language, message] cmd = ['pico2wave', '--wave', fname, '-l', language, message]
subprocess.call(cmd) subprocess.call(cmd)
data = None data = None
@@ -65,7 +52,6 @@ class PicoProvider(Provider):
return (None, None) return (None, None)
finally: finally:
os.remove(fname) os.remove(fname)
if data: if data:
return ("wav", data) return ("wav", data)
return (None, None) return (None, None)

View File

@@ -93,34 +93,27 @@ class VoiceRSSProvider(Provider):
def __init__(self, hass, conf): def __init__(self, hass, conf):
"""Init VoiceRSS TTS service.""" """Init VoiceRSS TTS service."""
self.hass = hass self.hass = hass
self._extension = conf[CONF_CODEC] self.extension = conf.get(CONF_CODEC)
self._lang = conf[CONF_LANG]
self._form_data = { self.form_data = {
'key': conf[CONF_API_KEY], 'key': conf.get(CONF_API_KEY),
'hl': conf[CONF_LANG], 'hl': conf.get(CONF_LANG),
'c': (conf[CONF_CODEC]).upper(), 'c': (conf.get(CONF_CODEC)).upper(),
'f': conf[CONF_FORMAT], '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 @asyncio.coroutine
def async_get_tts_audio(self, message, language): def async_get_tts_audio(self, message, language=None):
"""Load TTS from voicerss.""" """Load TTS from voicerss."""
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
form_data = self._form_data.copy() form_data = self.form_data.copy()
form_data['src'] = message 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 request = None
try: try:
@@ -148,4 +141,4 @@ class VoiceRSSProvider(Provider):
if request is not None: if request is not None:
yield from request.release() 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.const import ATTR_FRIENDLY_NAME
from homeassistant.helpers import event from homeassistant.helpers import event
REQUIREMENTS = ['distro==1.0.2'] REQUIREMENTS = ['distro==1.0.1']
_LOGGER = logging.getLogger(__name__) _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 from homeassistant.const import EVENT_HOMEASSISTANT_STOP
REQUIREMENTS = ['pywemo==0.4.9'] REQUIREMENTS = ['pywemo==0.4.7']
DOMAIN = 'wemo' DOMAIN = 'wemo'

View File

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

26
requirements_all.txt Executable file → Normal file
View File

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

View File

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

View File

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

View File

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

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.bootstrap import setup_component
from homeassistant.components.sensor import tcp from homeassistant.components.sensor import tcp
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.helpers.template import Template
TEST_CONFIG = { TEST_CONFIG = {
'sensor': { 'sensor': {
@@ -20,7 +19,7 @@ TEST_CONFIG = {
tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1, tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1,
tcp.CONF_PAYLOAD: 'test_payload', tcp.CONF_PAYLOAD: 'test_payload',
tcp.CONF_UNIT_OF_MEASUREMENT: 'test_unit', 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_VALUE_ON: 'test_on',
tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1 tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1
}, },
@@ -253,7 +252,7 @@ class TestTCPSensor(unittest.TestCase):
mock_socket = mock_socket().__enter__() mock_socket = mock_socket().__enter__()
mock_socket.recv.return_value = test_value.encode() mock_socket.recv.return_value = test_value.encode()
config = copy(TEST_CONFIG['sensor']) 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) sensor = tcp.TcpSensor(self.hass, config)
assert sensor._state == '%s 2' % test_value assert sensor._state == '%s 2' % test_value
@@ -266,6 +265,6 @@ class TestTCPSensor(unittest.TestCase):
mock_socket = mock_socket().__enter__() mock_socket = mock_socket().__enter__()
mock_socket.recv.return_value = test_value.encode() mock_socket.recv.return_value = test_value.encode()
config = copy(TEST_CONFIG['sensor']) 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) sensor = tcp.TcpSensor(self.hass, config)
assert sensor.update() is None assert sensor.update() is None

View File

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

View File

@@ -353,22 +353,3 @@ class TestScriptHelper(unittest.TestCase):
script_obj.run() script_obj.run()
self.hass.block_till_done() self.hass.block_till_done()
assert len(script_obj._config_cache) == 2 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