mirror of
https://github.com/home-assistant/core.git
synced 2025-08-04 05:05:09 +02:00
Revert "Bugfix camera streams (#5306)"
This reverts commit4b43537801
. Revert "Version bump for kodi dependency (#5307)" This reverts commit6abad6b76e
. Revert "Add HMWIOSwitch to sensor, binary (#5304)" This reverts commit2c3f55acc4
. Revert "Remove GTFS default name & string change" This reverts commit6000c59bb5
. Revert "Update pyhomematic 1.19 & small cleanups (#5299)" This reverts commita30711f1a0
. Revert "[sensor] Add Dublin bus RTPI sensor (#5257)" This reverts commit1219ca3c3b
. Revert "Bugfix group reload (#5292)" This reverts commitbaa8e53e66
. Revert "Support for TrackR device trackers (#5010)" This reverts commitf7a1d63d52
. Revert "Bump pywemo version." This reverts commitdc937cc8cf
. Revert "Upgrade to voluptuous to 0.9.3 (#5288)" This reverts commitd12decc471
. Revert "Upgrade distro to 1.0.2 (#5291)" This reverts commit64800fd48c
. Revert "Don't build Adafruit_BBIO - doesn't work on all platforms. (#5281)" This reverts commit9a3c0c8cd3
. Revert "Convert flic to synchronous platform. (#5276)" This reverts commiteb9b95c292
. Revert "Upgrade to aiohttp 1.2 (#4964)" This reverts commite68e29e03e
. Revert "Fix TCP sensor to correctly use value_template (#5211)" This reverts commit1cf9ae5a01
. Revert "Cleanup language support on TTS (#5255)" This reverts commit3f3a3bcc8a
. Revert "Add last triggered to script (#5261)" This reverts commit467cb18625
. Revert "Bump flux_led version and make use of PyPi package (#5267)" This reverts commit34a9fb01ac
. Revert "Add support for NAD receivers (#5191)" This reverts commit3b59e169f1
. Revert "Bugfix async device_tracker see callback (#5259)" This reverts commit71fddd26eb
. Revert "Use SHA hash to make token harder to guess (#5258)" This reverts commit922308bc1f
.
This commit is contained in:
@@ -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
|
||||||
|
@@ -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,
|
||||||
|
@@ -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):
|
||||||
|
@@ -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):
|
||||||
|
@@ -84,14 +84,8 @@ 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
|
||||||
|
@@ -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()
|
||||||
|
|
||||||
|
@@ -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()
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
|
||||||
)
|
|
@@ -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)
|
||||||
|
@@ -359,15 +359,13 @@ 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._async_state_changed_listener
|
self.hass, self.tracking, self._state_changed_listener
|
||||||
)
|
)
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
@@ -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):
|
||||||
|
@@ -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,10 +855,10 @@ 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):
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
@asyncio.coroutine
|
||||||
def sendfile(request, resp, fobj, count):
|
def send(self, request, filepath):
|
||||||
"""Sendfile that includes a cache header."""
|
"""Send filepath to client using request."""
|
||||||
|
gzip = False
|
||||||
|
if 'gzip' in request.headers[hdrs.ACCEPT_ENCODING]:
|
||||||
|
gzip_path = filepath.with_name(filepath.name + '.gz')
|
||||||
|
|
||||||
|
if gzip_path.is_file():
|
||||||
|
filepath = gzip_path
|
||||||
|
gzip = True
|
||||||
|
|
||||||
|
st = filepath.stat()
|
||||||
|
|
||||||
|
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]:
|
if not request.app[KEY_DEVELOPMENT]:
|
||||||
cache_time = 31 * 86400 # = 1 month
|
cache_time = 31 * 86400 # = 1 month
|
||||||
resp.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format(
|
resp.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format(
|
||||||
cache_time)
|
cache_time)
|
||||||
|
|
||||||
yield from orig_sendfile(request, resp, fobj, count)
|
file_size = st.st_size
|
||||||
|
|
||||||
# Overwriting like this because __init__ can change implementation.
|
resp.content_length = file_size
|
||||||
self._sendfile = sendfile
|
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):
|
||||||
|
@@ -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__)
|
||||||
|
|
||||||
|
@@ -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__)
|
||||||
|
|
||||||
|
@@ -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')
|
|
@@ -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:
|
||||||
|
@@ -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'}]
|
|
@@ -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']
|
||||||
|
@@ -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 = {
|
||||||
|
@@ -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):
|
||||||
|
@@ -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)
|
||||||
|
@@ -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(
|
||||||
|
@@ -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)
|
||||||
|
@@ -93,33 +93,26 @@ 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
|
||||||
|
|
||||||
|
# If language is specified and supported - use it instead of the
|
||||||
|
# language in the config.
|
||||||
|
if language in SUPPORT_LANGUAGES:
|
||||||
form_data['hl'] = language
|
form_data['hl'] = language
|
||||||
|
|
||||||
request = None
|
request = None
|
||||||
@@ -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)
|
||||||
|
@@ -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__)
|
||||||
|
|
||||||
|
@@ -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'
|
||||||
|
|
||||||
|
@@ -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
26
requirements_all.txt
Executable file → Normal 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
|
||||||
|
@@ -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',
|
||||||
|
4
setup.py
4
setup.py
@@ -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',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -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
|
||||||
|
def setUpClass(cls):
|
||||||
|
"""Setup the class."""
|
||||||
|
cls.hass = hass = get_test_home_assistant()
|
||||||
|
|
||||||
|
# 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,
|
hass, http.DOMAIN,
|
||||||
{http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}}))
|
{http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}})
|
||||||
|
|
||||||
with patch('homeassistant.components'
|
with patch('homeassistant.components'
|
||||||
'.emulated_hue.UPNPResponderThread'):
|
'.emulated_hue.UPNPResponderThread'):
|
||||||
loop.run_until_complete(
|
bootstrap.setup_component(hass, emulated_hue.DOMAIN, {
|
||||||
bootstrap.async_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,78 +73,63 @@ 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',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}))
|
})
|
||||||
|
|
||||||
|
cls.hass.start()
|
||||||
|
|
||||||
# Kitchen light is explicitly excluded from being exposed
|
# Kitchen light is explicitly excluded from being exposed
|
||||||
kitchen_light_entity = hass.states.get('light.kitchen_lights')
|
kitchen_light_entity = cls.hass.states.get('light.kitchen_lights')
|
||||||
attrs = dict(kitchen_light_entity.attributes)
|
attrs = dict(kitchen_light_entity.attributes)
|
||||||
attrs[emulated_hue.ATTR_EMULATED_HUE] = False
|
attrs[emulated_hue.ATTR_EMULATED_HUE] = False
|
||||||
hass.states.async_set(
|
cls.hass.states.set(
|
||||||
kitchen_light_entity.entity_id, kitchen_light_entity.state,
|
kitchen_light_entity.entity_id, kitchen_light_entity.state,
|
||||||
attributes=attrs)
|
attributes=attrs)
|
||||||
|
|
||||||
# Expose the script
|
# Expose the script
|
||||||
script_entity = hass.states.get('script.set_kitchen_light')
|
script_entity = cls.hass.states.get('script.set_kitchen_light')
|
||||||
attrs = dict(script_entity.attributes)
|
attrs = dict(script_entity.attributes)
|
||||||
attrs[emulated_hue.ATTR_EMULATED_HUE] = True
|
attrs[emulated_hue.ATTR_EMULATED_HUE] = True
|
||||||
hass.states.async_set(
|
cls.hass.states.set(
|
||||||
script_entity.entity_id, script_entity.state, attributes=attrs
|
script_entity.entity_id, script_entity.state, attributes=attrs
|
||||||
)
|
)
|
||||||
|
|
||||||
return hass
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
"""Stop the class."""
|
||||||
|
cls.hass.stop()
|
||||||
|
|
||||||
|
def test_discover_lights(self):
|
||||||
@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."""
|
"""Test the discovery of lights."""
|
||||||
result = yield from hue_client.get('/api/username/lights')
|
result = requests.get(
|
||||||
|
BRIDGE_URL_BASE.format('/api/username/lights'), timeout=5)
|
||||||
|
|
||||||
assert result.status == 200
|
self.assertEqual(result.status_code, 200)
|
||||||
assert 'application/json' in result.headers['content-type']
|
self.assertTrue('application/json' in result.headers['content-type'])
|
||||||
|
|
||||||
result_json = yield from result.json()
|
result_json = result.json()
|
||||||
|
|
||||||
devices = set(val['uniqueid'] for val in result_json.values())
|
|
||||||
|
|
||||||
# Make sure the lights we added to the config are there
|
# Make sure the lights we added to the config are there
|
||||||
assert 'light.ceiling_lights' in devices
|
self.assertTrue('light.ceiling_lights' in result_json)
|
||||||
assert 'light.bed_light' in devices
|
self.assertTrue('light.bed_light' in result_json)
|
||||||
assert 'script.set_kitchen_light' in devices
|
self.assertTrue('script.set_kitchen_light' in result_json)
|
||||||
assert 'light.kitchen_lights' not in devices
|
self.assertTrue('light.kitchen_lights' not in result_json)
|
||||||
assert 'media_player.living_room' in devices
|
self.assertTrue('media_player.living_room' in result_json)
|
||||||
assert 'media_player.bedroom' in devices
|
self.assertTrue('media_player.bedroom' in result_json)
|
||||||
assert 'media_player.walkman' in devices
|
self.assertTrue('media_player.walkman' in result_json)
|
||||||
assert 'media_player.lounge_room' in devices
|
self.assertTrue('media_player.lounge_room' in result_json)
|
||||||
|
|
||||||
|
def test_get_light_state(self):
|
||||||
@asyncio.coroutine
|
|
||||||
def test_get_light_state(hass_hue, hue_client):
|
|
||||||
"""Test the getting of light state."""
|
"""Test the getting of light state."""
|
||||||
# Turn office light on and set to 127 brightness
|
# Turn office light on and set to 127 brightness
|
||||||
yield from hass_hue.services.async_call(
|
self.hass.services.call(
|
||||||
light.DOMAIN, const.SERVICE_TURN_ON,
|
light.DOMAIN, const.SERVICE_TURN_ON,
|
||||||
{
|
{
|
||||||
const.ATTR_ENTITY_ID: 'light.ceiling_lights',
|
const.ATTR_ENTITY_ID: 'light.ceiling_lights',
|
||||||
@@ -151,87 +137,86 @@ def test_get_light_state(hass_hue, hue_client):
|
|||||||
},
|
},
|
||||||
blocking=True)
|
blocking=True)
|
||||||
|
|
||||||
office_json = yield from perform_get_light_state(
|
office_json = self.perform_get_light_state('light.ceiling_lights', 200)
|
||||||
hue_client, 'light.ceiling_lights', 200)
|
|
||||||
|
|
||||||
assert office_json['state'][HUE_API_STATE_ON] is True
|
self.assertEqual(office_json['state'][HUE_API_STATE_ON], True)
|
||||||
assert office_json['state'][HUE_API_STATE_BRI] == 127
|
self.assertEqual(office_json['state'][HUE_API_STATE_BRI], 127)
|
||||||
|
|
||||||
# Check all lights view
|
# Check all lights view
|
||||||
result = yield from hue_client.get('/api/username/lights')
|
result = requests.get(
|
||||||
|
BRIDGE_URL_BASE.format('/api/username/lights'), timeout=5)
|
||||||
|
|
||||||
assert result.status == 200
|
self.assertEqual(result.status_code, 200)
|
||||||
assert 'application/json' in result.headers['content-type']
|
self.assertTrue('application/json' in result.headers['content-type'])
|
||||||
|
|
||||||
result_json = yield from result.json()
|
result_json = result.json()
|
||||||
|
|
||||||
assert 'light.ceiling_lights' in result_json
|
self.assertTrue('light.ceiling_lights' in result_json)
|
||||||
assert result_json['light.ceiling_lights']['state'][HUE_API_STATE_BRI] == \
|
self.assertEqual(
|
||||||
127
|
result_json['light.ceiling_lights']['state'][HUE_API_STATE_BRI],
|
||||||
|
127,
|
||||||
|
)
|
||||||
|
|
||||||
# Turn bedroom light off
|
# Turn bedroom light off
|
||||||
yield from hass_hue.services.async_call(
|
self.hass.services.call(
|
||||||
light.DOMAIN, const.SERVICE_TURN_OFF,
|
light.DOMAIN, const.SERVICE_TURN_OFF,
|
||||||
{
|
{
|
||||||
const.ATTR_ENTITY_ID: 'light.bed_light'
|
const.ATTR_ENTITY_ID: 'light.bed_light'
|
||||||
},
|
},
|
||||||
blocking=True)
|
blocking=True)
|
||||||
|
|
||||||
bedroom_json = yield from perform_get_light_state(
|
bedroom_json = self.perform_get_light_state('light.bed_light', 200)
|
||||||
hue_client, 'light.bed_light', 200)
|
|
||||||
|
|
||||||
assert bedroom_json['state'][HUE_API_STATE_ON] is False
|
self.assertEqual(bedroom_json['state'][HUE_API_STATE_ON], False)
|
||||||
assert bedroom_json['state'][HUE_API_STATE_BRI] == 0
|
self.assertEqual(bedroom_json['state'][HUE_API_STATE_BRI], 0)
|
||||||
|
|
||||||
# Make sure kitchen light isn't accessible
|
# Make sure kitchen light isn't accessible
|
||||||
yield from perform_get_light_state(
|
kitchen_url = '/api/username/lights/{}'.format('light.kitchen_lights')
|
||||||
hue_client, 'light.kitchen_lights', 404)
|
kitchen_result = requests.get(
|
||||||
|
BRIDGE_URL_BASE.format(kitchen_url), timeout=5)
|
||||||
|
|
||||||
|
self.assertEqual(kitchen_result.status_code, 404)
|
||||||
|
|
||||||
@asyncio.coroutine
|
def test_put_light_state(self):
|
||||||
def test_put_light_state(hass_hue, hue_client):
|
|
||||||
"""Test the seeting of light states."""
|
"""Test the seeting of light states."""
|
||||||
yield from perform_put_test_on_ceiling_lights(hass_hue, hue_client)
|
self.perform_put_test_on_ceiling_lights()
|
||||||
|
|
||||||
# Turn the bedroom light on first
|
# Turn the bedroom light on first
|
||||||
yield from hass_hue.services.async_call(
|
self.hass.services.call(
|
||||||
light.DOMAIN, const.SERVICE_TURN_ON,
|
light.DOMAIN, const.SERVICE_TURN_ON,
|
||||||
{const.ATTR_ENTITY_ID: 'light.bed_light',
|
{const.ATTR_ENTITY_ID: 'light.bed_light',
|
||||||
light.ATTR_BRIGHTNESS: 153},
|
light.ATTR_BRIGHTNESS: 153},
|
||||||
blocking=True)
|
blocking=True)
|
||||||
|
|
||||||
bed_light = hass_hue.states.get('light.bed_light')
|
bed_light = self.hass.states.get('light.bed_light')
|
||||||
assert bed_light.state == STATE_ON
|
self.assertEqual(bed_light.state, STATE_ON)
|
||||||
assert bed_light.attributes[light.ATTR_BRIGHTNESS] == 153
|
self.assertEqual(bed_light.attributes[light.ATTR_BRIGHTNESS], 153)
|
||||||
|
|
||||||
# Go through the API to turn it off
|
# Go through the API to turn it off
|
||||||
bedroom_result = yield from perform_put_light_state(
|
bedroom_result = self.perform_put_light_state(
|
||||||
hass_hue, hue_client,
|
|
||||||
'light.bed_light', False)
|
'light.bed_light', False)
|
||||||
|
|
||||||
bedroom_result_json = yield from bedroom_result.json()
|
bedroom_result_json = bedroom_result.json()
|
||||||
|
|
||||||
assert bedroom_result.status == 200
|
self.assertEqual(bedroom_result.status_code, 200)
|
||||||
assert 'application/json' in bedroom_result.headers['content-type']
|
self.assertTrue(
|
||||||
|
'application/json' in bedroom_result.headers['content-type'])
|
||||||
|
|
||||||
assert len(bedroom_result_json) == 1
|
self.assertEqual(len(bedroom_result_json), 1)
|
||||||
|
|
||||||
# Check to make sure the state changed
|
# Check to make sure the state changed
|
||||||
bed_light = hass_hue.states.get('light.bed_light')
|
bed_light = self.hass.states.get('light.bed_light')
|
||||||
assert bed_light.state == STATE_OFF
|
self.assertEqual(bed_light.state, STATE_OFF)
|
||||||
|
|
||||||
# Make sure we can't change the kitchen light state
|
# Make sure we can't change the kitchen light state
|
||||||
kitchen_result = yield from perform_put_light_state(
|
kitchen_result = self.perform_put_light_state(
|
||||||
hass_hue, hue_client,
|
|
||||||
'light.kitchen_light', True)
|
'light.kitchen_light', True)
|
||||||
assert kitchen_result.status == 404
|
self.assertEqual(kitchen_result.status_code, 404)
|
||||||
|
|
||||||
|
def test_put_light_state_script(self):
|
||||||
@asyncio.coroutine
|
|
||||||
def test_put_light_state_script(hass_hue, hue_client):
|
|
||||||
"""Test the setting of script variables."""
|
"""Test the setting of script variables."""
|
||||||
# Turn the kitchen light off first
|
# Turn the kitchen light off first
|
||||||
yield from hass_hue.services.async_call(
|
self.hass.services.call(
|
||||||
light.DOMAIN, const.SERVICE_TURN_OFF,
|
light.DOMAIN, const.SERVICE_TURN_OFF,
|
||||||
{const.ATTR_ENTITY_ID: 'light.kitchen_lights'},
|
{const.ATTR_ENTITY_ID: 'light.kitchen_lights'},
|
||||||
blocking=True)
|
blocking=True)
|
||||||
@@ -240,25 +225,24 @@ def test_put_light_state_script(hass_hue, hue_client):
|
|||||||
level = 23
|
level = 23
|
||||||
brightness = round(level * 255 / 100)
|
brightness = round(level * 255 / 100)
|
||||||
|
|
||||||
script_result = yield from perform_put_light_state(
|
script_result = self.perform_put_light_state(
|
||||||
hass_hue, hue_client,
|
|
||||||
'script.set_kitchen_light', True, brightness)
|
'script.set_kitchen_light', True, brightness)
|
||||||
|
|
||||||
script_result_json = yield from script_result.json()
|
script_result_json = script_result.json()
|
||||||
|
|
||||||
assert script_result.status == 200
|
self.assertEqual(script_result.status_code, 200)
|
||||||
assert len(script_result_json) == 2
|
self.assertEqual(len(script_result_json), 2)
|
||||||
|
|
||||||
kitchen_light = hass_hue.states.get('light.kitchen_lights')
|
kitchen_light = self.hass.states.get('light.kitchen_lights')
|
||||||
assert kitchen_light.state == 'on'
|
self.assertEqual(kitchen_light.state, 'on')
|
||||||
assert kitchen_light.attributes[light.ATTR_BRIGHTNESS] == level
|
self.assertEqual(
|
||||||
|
kitchen_light.attributes[light.ATTR_BRIGHTNESS],
|
||||||
|
level)
|
||||||
|
|
||||||
|
def test_put_light_state_media_player(self):
|
||||||
@asyncio.coroutine
|
|
||||||
def test_put_light_state_media_player(hass_hue, hue_client):
|
|
||||||
"""Test turning on media player and setting volume."""
|
"""Test turning on media player and setting volume."""
|
||||||
# Turn the music player off first
|
# Turn the music player off first
|
||||||
yield from hass_hue.services.async_call(
|
self.hass.services.call(
|
||||||
media_player.DOMAIN, const.SERVICE_TURN_OFF,
|
media_player.DOMAIN, const.SERVICE_TURN_OFF,
|
||||||
{const.ATTR_ENTITY_ID: 'media_player.walkman'},
|
{const.ATTR_ENTITY_ID: 'media_player.walkman'},
|
||||||
blocking=True)
|
blocking=True)
|
||||||
@@ -267,147 +251,149 @@ def test_put_light_state_media_player(hass_hue, hue_client):
|
|||||||
level = 0.25
|
level = 0.25
|
||||||
brightness = round(level * 255)
|
brightness = round(level * 255)
|
||||||
|
|
||||||
mp_result = yield from perform_put_light_state(
|
mp_result = self.perform_put_light_state(
|
||||||
hass_hue, hue_client,
|
|
||||||
'media_player.walkman', True, brightness)
|
'media_player.walkman', True, brightness)
|
||||||
|
|
||||||
mp_result_json = yield from mp_result.json()
|
mp_result_json = mp_result.json()
|
||||||
|
|
||||||
assert mp_result.status == 200
|
self.assertEqual(mp_result.status_code, 200)
|
||||||
assert len(mp_result_json) == 2
|
self.assertEqual(len(mp_result_json), 2)
|
||||||
|
|
||||||
walkman = hass_hue.states.get('media_player.walkman')
|
walkman = self.hass.states.get('media_player.walkman')
|
||||||
assert walkman.state == 'playing'
|
self.assertEqual(walkman.state, 'playing')
|
||||||
assert walkman.attributes[media_player.ATTR_MEDIA_VOLUME_LEVEL] == level
|
self.assertEqual(
|
||||||
|
walkman.attributes[media_player.ATTR_MEDIA_VOLUME_LEVEL],
|
||||||
|
level)
|
||||||
|
|
||||||
|
# pylint: disable=invalid-name
|
||||||
# pylint: disable=invalid-name
|
def test_put_with_form_urlencoded_content_type(self):
|
||||||
@asyncio.coroutine
|
|
||||||
def test_put_with_form_urlencoded_content_type(hass_hue, hue_client):
|
|
||||||
"""Test the form with urlencoded content."""
|
"""Test the form with urlencoded content."""
|
||||||
# Needed for Alexa
|
# Needed for Alexa
|
||||||
yield from perform_put_test_on_ceiling_lights(
|
self.perform_put_test_on_ceiling_lights(
|
||||||
hass_hue, hue_client, 'application/x-www-form-urlencoded')
|
'application/x-www-form-urlencoded')
|
||||||
|
|
||||||
# Make sure we fail gracefully when we can't parse the data
|
# Make sure we fail gracefully when we can't parse the data
|
||||||
data = {'key1': 'value1', 'key2': 'value2'}
|
data = {'key1': 'value1', 'key2': 'value2'}
|
||||||
result = yield from hue_client.put(
|
result = requests.put(
|
||||||
'/api/username/lights/light.ceiling_lights/state',
|
BRIDGE_URL_BASE.format(
|
||||||
headers={
|
'/api/username/lights/{}/state'.format(
|
||||||
'content-type': 'application/x-www-form-urlencoded'
|
'light.ceiling_lights')), data=data)
|
||||||
},
|
|
||||||
data=data,
|
|
||||||
)
|
|
||||||
|
|
||||||
assert result.status == 400
|
self.assertEqual(result.status_code, 400)
|
||||||
|
|
||||||
|
def test_entity_not_found(self):
|
||||||
@asyncio.coroutine
|
|
||||||
def test_entity_not_found(hue_client):
|
|
||||||
"""Test for entity which are not found."""
|
"""Test for entity which are not found."""
|
||||||
result = yield from hue_client.get(
|
result = requests.get(
|
||||||
'/api/username/lights/not.existant_entity')
|
BRIDGE_URL_BASE.format(
|
||||||
|
'/api/username/lights/{}'.format("not.existant_entity")),
|
||||||
|
timeout=5)
|
||||||
|
|
||||||
assert result.status == 404
|
self.assertEqual(result.status_code, 404)
|
||||||
|
|
||||||
result = yield from hue_client.put(
|
result = requests.put(
|
||||||
'/api/username/lights/not.existant_entity/state')
|
BRIDGE_URL_BASE.format(
|
||||||
|
'/api/username/lights/{}/state'.format("non.existant_entity")),
|
||||||
|
timeout=5)
|
||||||
|
|
||||||
assert result.status == 404
|
self.assertEqual(result.status_code, 404)
|
||||||
|
|
||||||
|
def test_allowed_methods(self):
|
||||||
@asyncio.coroutine
|
|
||||||
def test_allowed_methods(hue_client):
|
|
||||||
"""Test the allowed methods."""
|
"""Test the allowed methods."""
|
||||||
result = yield from hue_client.get(
|
result = requests.get(
|
||||||
'/api/username/lights/light.ceiling_lights/state')
|
BRIDGE_URL_BASE.format(
|
||||||
|
'/api/username/lights/{}/state'.format(
|
||||||
|
"light.ceiling_lights")))
|
||||||
|
|
||||||
assert result.status == 405
|
self.assertEqual(result.status_code, 405)
|
||||||
|
|
||||||
result = yield from hue_client.put(
|
result = requests.put(
|
||||||
'/api/username/lights/light.ceiling_lights')
|
BRIDGE_URL_BASE.format(
|
||||||
|
'/api/username/lights/{}'.format("light.ceiling_lights")),
|
||||||
|
data={'key1': 'value1'})
|
||||||
|
|
||||||
assert result.status == 405
|
self.assertEqual(result.status_code, 405)
|
||||||
|
|
||||||
result = yield from hue_client.put(
|
result = requests.put(
|
||||||
'/api/username/lights')
|
BRIDGE_URL_BASE.format('/api/username/lights'),
|
||||||
|
data={'key1': 'value1'})
|
||||||
|
|
||||||
assert result.status == 405
|
self.assertEqual(result.status_code, 405)
|
||||||
|
|
||||||
|
def test_proper_put_state_request(self):
|
||||||
@asyncio.coroutine
|
|
||||||
def test_proper_put_state_request(hue_client):
|
|
||||||
"""Test the request to set the state."""
|
"""Test the request to set the state."""
|
||||||
# Test proper on value parsing
|
# Test proper on value parsing
|
||||||
result = yield from hue_client.put(
|
result = requests.put(
|
||||||
|
BRIDGE_URL_BASE.format(
|
||||||
'/api/username/lights/{}/state'.format(
|
'/api/username/lights/{}/state'.format(
|
||||||
'light.ceiling_lights'),
|
'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(
|
||||||
|
BRIDGE_URL_BASE.format(
|
||||||
'/api/username/lights/{}/state'.format(
|
'/api/username/lights/{}/state'.format(
|
||||||
'light.ceiling_lights'),
|
'light.ceiling_lights')), data=json.dumps({
|
||||||
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
|
||||||
# pylint: disable=invalid-name
|
def perform_put_test_on_ceiling_lights(self,
|
||||||
def perform_put_test_on_ceiling_lights(hass_hue, hue_client,
|
|
||||||
content_type='application/json'):
|
content_type='application/json'):
|
||||||
"""Test the setting of a light."""
|
"""Test the setting of a light."""
|
||||||
# Turn the office light off first
|
# Turn the office light off first
|
||||||
yield from hass_hue.services.async_call(
|
self.hass.services.call(
|
||||||
light.DOMAIN, const.SERVICE_TURN_OFF,
|
light.DOMAIN, const.SERVICE_TURN_OFF,
|
||||||
{const.ATTR_ENTITY_ID: 'light.ceiling_lights'},
|
{const.ATTR_ENTITY_ID: 'light.ceiling_lights'},
|
||||||
blocking=True)
|
blocking=True)
|
||||||
|
|
||||||
ceiling_lights = hass_hue.states.get('light.ceiling_lights')
|
ceiling_lights = self.hass.states.get('light.ceiling_lights')
|
||||||
assert ceiling_lights.state == STATE_OFF
|
self.assertEqual(ceiling_lights.state, STATE_OFF)
|
||||||
|
|
||||||
# Go through the API to turn it on
|
# Go through the API to turn it on
|
||||||
office_result = yield from perform_put_light_state(
|
office_result = self.perform_put_light_state(
|
||||||
hass_hue, hue_client,
|
|
||||||
'light.ceiling_lights', True, 56, content_type)
|
'light.ceiling_lights', True, 56, content_type)
|
||||||
|
|
||||||
assert office_result.status == 200
|
office_result_json = office_result.json()
|
||||||
assert 'application/json' in office_result.headers['content-type']
|
|
||||||
|
|
||||||
office_result_json = yield from office_result.json()
|
self.assertEqual(office_result.status_code, 200)
|
||||||
|
self.assertTrue(
|
||||||
|
'application/json' in office_result.headers['content-type'])
|
||||||
|
|
||||||
assert len(office_result_json) == 2
|
self.assertEqual(len(office_result_json), 2)
|
||||||
|
|
||||||
# Check to make sure the state changed
|
# Check to make sure the state changed
|
||||||
ceiling_lights = hass_hue.states.get('light.ceiling_lights')
|
ceiling_lights = self.hass.states.get('light.ceiling_lights')
|
||||||
assert ceiling_lights.state == STATE_ON
|
self.assertEqual(ceiling_lights.state, STATE_ON)
|
||||||
assert ceiling_lights.attributes[light.ATTR_BRIGHTNESS] == 56
|
self.assertEqual(ceiling_lights.attributes[light.ATTR_BRIGHTNESS], 56)
|
||||||
|
|
||||||
|
def perform_get_light_state(self, entity_id, expected_status):
|
||||||
@asyncio.coroutine
|
|
||||||
def perform_get_light_state(client, entity_id, expected_status):
|
|
||||||
"""Test the gettting of a light state."""
|
"""Test the gettting of a light state."""
|
||||||
result = yield from client.get('/api/username/lights/{}'.format(entity_id))
|
result = requests.get(
|
||||||
|
BRIDGE_URL_BASE.format(
|
||||||
|
'/api/username/lights/{}'.format(entity_id)), timeout=5)
|
||||||
|
|
||||||
assert result.status == expected_status
|
self.assertEqual(result.status_code, expected_status)
|
||||||
|
|
||||||
if expected_status == 200:
|
if expected_status == 200:
|
||||||
assert 'application/json' in result.headers['content-type']
|
self.assertTrue(
|
||||||
|
'application/json' in result.headers['content-type'])
|
||||||
|
|
||||||
return (yield from result.json())
|
return result.json()
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# pylint: disable=no-self-use
|
||||||
@asyncio.coroutine
|
def perform_put_light_state(self, entity_id, is_on, brightness=None,
|
||||||
def perform_put_light_state(hass_hue, client, entity_id, is_on,
|
content_type='application/json'):
|
||||||
brightness=None, content_type='application/json'):
|
|
||||||
"""Test the setting of a light state."""
|
"""Test the setting of a light state."""
|
||||||
|
url = BRIDGE_URL_BASE.format(
|
||||||
|
'/api/username/lights/{}/state'.format(entity_id))
|
||||||
|
|
||||||
req_headers = {'Content-Type': content_type}
|
req_headers = {'Content-Type': content_type}
|
||||||
|
|
||||||
data = {HUE_API_STATE_ON: is_on}
|
data = {HUE_API_STATE_ON: is_on}
|
||||||
@@ -415,11 +401,10 @@ def perform_put_light_state(hass_hue, client, entity_id, is_on,
|
|||||||
if brightness is not None:
|
if brightness is not None:
|
||||||
data[HUE_API_STATE_BRI] = brightness
|
data[HUE_API_STATE_BRI] = brightness
|
||||||
|
|
||||||
result = yield from client.put(
|
result = requests.put(
|
||||||
'/api/username/lights/{}/state'.format(entity_id), headers=req_headers,
|
url, data=json.dumps(data), timeout=5, headers=req_headers)
|
||||||
data=json.dumps(data).encode())
|
|
||||||
|
|
||||||
# Wait until state change is complete before continuing
|
# Wait until state change is complete before continuing
|
||||||
yield from hass_hue.async_block_till_done()
|
self.hass.block_till_done()
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@@ -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
|
||||||
|
@@ -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")
|
||||||
|
@@ -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
|
|
||||||
|
Reference in New Issue
Block a user