diff --git a/.coveragerc b/.coveragerc index aa5921526de5f7..6a766906e056fc 100644 --- a/.coveragerc +++ b/.coveragerc @@ -169,7 +169,6 @@ omit = homeassistant/components/device_tracker/thomson.py homeassistant/components/device_tracker/tomato.py homeassistant/components/device_tracker/tplink.py - homeassistant/components/device_tracker/trackr.py homeassistant/components/device_tracker/ubus.py homeassistant/components/device_tracker/volvooncall.py homeassistant/components/discovery.py @@ -211,7 +210,6 @@ omit = homeassistant/components/media_player/lg_netcast.py homeassistant/components/media_player/mpchc.py homeassistant/components/media_player/mpd.py - homeassistant/components/media_player/nad.py homeassistant/components/media_player/onkyo.py homeassistant/components/media_player/panasonic_viera.py homeassistant/components/media_player/pandora.py @@ -266,7 +264,6 @@ omit = homeassistant/components/sensor/bitcoin.py homeassistant/components/sensor/bom.py homeassistant/components/sensor/broadlink.py - homeassistant/components/sensor/dublin_bus_transport.py homeassistant/components/sensor/coinmarketcap.py homeassistant/components/sensor/cpuspeed.py homeassistant/components/sensor/cups.py diff --git a/homeassistant/components/bbb_gpio.py b/homeassistant/components/bbb_gpio.py index 52ab14689fd3ec..e85c027882fd04 100644 --- a/homeassistant/components/bbb_gpio.py +++ b/homeassistant/components/bbb_gpio.py @@ -19,7 +19,6 @@ # pylint: disable=no-member def setup(hass, config): """Setup the Beaglebone black GPIO component.""" - # pylint: disable=import-error import Adafruit_BBIO.GPIO as GPIO def cleanup_gpio(event): @@ -34,41 +33,33 @@ def prepare_gpio(event): return True -# noqa: F821 - def setup_output(pin): """Setup a GPIO as output.""" - # pylint: disable=import-error,undefined-variable import Adafruit_BBIO.GPIO as GPIO GPIO.setup(pin, GPIO.OUT) def setup_input(pin, pull_mode): """Setup a GPIO as input.""" - # pylint: disable=import-error,undefined-variable import Adafruit_BBIO.GPIO as GPIO - GPIO.setup(pin, GPIO.IN, # noqa: F821 - GPIO.PUD_DOWN if pull_mode == 'DOWN' # noqa: F821 - else GPIO.PUD_UP) # noqa: F821 + GPIO.setup(pin, GPIO.IN, + GPIO.PUD_DOWN if pull_mode == 'DOWN' else GPIO.PUD_UP) def write_output(pin, value): """Write a value to a GPIO.""" - # pylint: disable=import-error,undefined-variable import Adafruit_BBIO.GPIO as GPIO GPIO.output(pin, value) def read_input(pin): """Read a value from a GPIO.""" - # pylint: disable=import-error,undefined-variable import Adafruit_BBIO.GPIO as GPIO return GPIO.input(pin) def edge_detect(pin, event_callback, bounce): """Add detection for RISING and FALLING events.""" - # pylint: disable=import-error,undefined-variable import Adafruit_BBIO.GPIO as GPIO GPIO.add_event_detect( pin, diff --git a/homeassistant/components/binary_sensor/flic.py b/homeassistant/components/binary_sensor/flic.py index 94a75fcda4bc34..980af069f38dbb 100644 --- a/homeassistant/components/binary_sensor/flic.py +++ b/homeassistant/components/binary_sensor/flic.py @@ -1,6 +1,6 @@ """Contains functionality to use flic buttons as a binary sensor.""" +import asyncio import logging -import threading import voluptuous as vol @@ -10,6 +10,7 @@ EVENT_HOMEASSISTANT_STOP) from homeassistant.components.binary_sensor import ( BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.util.async import run_callback_threadsafe REQUIREMENTS = ['https://github.com/soldag/pyflic/archive/0.4.zip#pyflic==0.4'] @@ -42,7 +43,9 @@ }) -def setup_platform(hass, config, add_entities, discovery_info=None): +@asyncio.coroutine +def async_setup_platform(hass, config, async_add_entities, + discovery_info=None): """Setup the flic platform.""" import pyflic @@ -60,29 +63,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def new_button_callback(address): """Setup newly verified button as device in home assistant.""" - setup_button(hass, config, add_entities, client, address) + hass.add_job(async_setup_button(hass, config, async_add_entities, + client, address)) client.on_new_verified_button = new_button_callback if discovery: - start_scanning(config, add_entities, client) + start_scanning(hass, config, async_add_entities, client) - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - lambda event: client.close()) - - # Start the pyflic event handling thread - threading.Thread(target=client.handle_events).start() - - def get_info_callback(items): - """Add entities for already verified buttons.""" - addresses = items["bd_addr_of_verified_buttons"] or [] - for address in addresses: - setup_button(hass, config, add_entities, client, address) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, + lambda event: client.close()) + hass.loop.run_in_executor(None, client.handle_events) # Get addresses of already verified buttons - client.get_info(get_info_callback) + addresses = yield from async_get_verified_addresses(client) + if addresses: + for address in addresses: + yield from async_setup_button(hass, config, async_add_entities, + client, address) -def start_scanning(config, add_entities, client): +def start_scanning(hass, config, async_add_entities, client): """Start a new flic client for scanning & connceting to new buttons.""" import pyflic @@ -97,20 +97,36 @@ def scan_completed_callback(scan_wizard, result, address, name): address, result) # Restart scan wizard - start_scanning(config, add_entities, client) + start_scanning(hass, config, async_add_entities, client) scan_wizard.on_completed = scan_completed_callback client.add_scan_wizard(scan_wizard) -def setup_button(hass, config, add_entities, client, address): +@asyncio.coroutine +def async_setup_button(hass, config, async_add_entities, client, address): """Setup single button device.""" timeout = config.get(CONF_TIMEOUT) ignored_click_types = config.get(CONF_IGNORED_CLICK_TYPES) button = FlicButton(hass, client, address, timeout, ignored_click_types) _LOGGER.info("Connected to button (%s)", address) - add_entities([button]) + yield from async_add_entities([button]) + + +@asyncio.coroutine +def async_get_verified_addresses(client): + """Retrieve addresses of verified buttons.""" + future = asyncio.Future() + loop = asyncio.get_event_loop() + + def get_info_callback(items): + """Set the addressed of connected buttons as result of the future.""" + addresses = items["bd_addr_of_verified_buttons"] + run_callback_threadsafe(loop, future.set_result, addresses) + client.get_info(get_info_callback) + + return future class FlicButton(BinarySensorDevice): diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index 5ba68dea0589a3..5b2aa463607e03 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -8,7 +8,6 @@ import asyncio from datetime import timedelta import logging -import hashlib from aiohttp import web @@ -48,13 +47,11 @@ class Camera(Entity): def __init__(self): """Initialize a camera.""" self.is_streaming = False - self._access_token = hashlib.sha256( - str.encode(str(id(self)))).hexdigest() @property def access_token(self): """Access token for this camera.""" - return self._access_token + return str(id(self)) @property def should_poll(self): diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index 9c1aaa25f6f7bf..bb7c7ac6cdc0ea 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -84,15 +84,9 @@ def handle_async_mjpeg_stream(self, request): if not data: break response.write(data) - - except asyncio.CancelledError: - _LOGGER.debug("Close stream by browser.") - response = None - finally: - yield from stream.close() - if response is not None: - yield from response.write_eof() + self.hass.async_add_job(stream.close()) + yield from response.write_eof() @property def name(self): diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index 4bc62d661438ee..d3af55a91f1256 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -124,13 +124,9 @@ def handle_async_mjpeg_stream(self, request): except asyncio.TimeoutError: raise HTTPGatewayTimeout() - except asyncio.CancelledError: - _LOGGER.debug("Close stream by browser.") - response = None - finally: if stream is not None: - stream.close() + yield from stream.close() if response is not None: yield from response.write_eof() diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py index d7359c14dedc97..6d5b4546933086 100644 --- a/homeassistant/components/camera/synology.py +++ b/homeassistant/components/camera/synology.py @@ -276,13 +276,9 @@ def handle_async_mjpeg_stream(self, request): _LOGGER.exception("Error on %s", streaming_url) raise HTTPGatewayTimeout() - except asyncio.CancelledError: - _LOGGER.debug("Close stream by browser.") - response = None - finally: if stream is not None: - stream.close() + self.hass.async_add_job(stream.release()) if response is not None: yield from response.write_eof() diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 21e7c7b0da1389..f08a9badb6f577 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -158,7 +158,7 @@ def async_setup_platform(p_type, p_config, disc_info=None): None, platform.get_scanner, hass, {DOMAIN: p_config}) elif hasattr(platform, 'async_setup_scanner'): setup = yield from platform.async_setup_scanner( - hass, p_config, tracker.async_see) + hass, p_config, tracker.see) elif hasattr(platform, 'setup_scanner'): setup = yield from hass.loop.run_in_executor( None, platform.setup_scanner, hass, p_config, tracker.see) diff --git a/homeassistant/components/device_tracker/trackr.py b/homeassistant/components/device_tracker/trackr.py deleted file mode 100644 index 2eb0def278f917..00000000000000 --- a/homeassistant/components/device_tracker/trackr.py +++ /dev/null @@ -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 - ) diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py index 9b0a2828394af8..24060bdfbcbf2c 100644 --- a/homeassistant/components/emulated_hue/hue_api.py +++ b/homeassistant/components/emulated_hue/hue_api.py @@ -91,7 +91,7 @@ def __init__(self, config): self.config = config @core.callback - def get(self, request, username, entity_id): + def get(self, request, username, entity_id=None): """Process a request to get the state of an individual light.""" hass = request.app['hass'] entity_id = self.config.number_to_entity_id(entity_id) diff --git a/homeassistant/components/group.py b/homeassistant/components/group.py index 25aa263d2621eb..0dfabdd8a35cf8 100644 --- a/homeassistant/components/group.py +++ b/homeassistant/components/group.py @@ -359,16 +359,14 @@ def start(self): """Start tracking members.""" run_callback_threadsafe(self.hass.loop, self.async_start).result() - @callback def async_start(self): """Start tracking members. This method must be run in the event loop. """ - if self._async_unsub_state_changed is None: - self._async_unsub_state_changed = async_track_state_change( - self.hass, self.tracking, self._async_state_changed_listener - ) + self._async_unsub_state_changed = async_track_state_change( + self.hass, self.tracking, self._state_changed_listener + ) def stop(self): """Unregister the group from Home Assistant.""" @@ -394,24 +392,20 @@ def async_remove(self): This method must be run in the event loop. """ + yield from super().async_remove() + if self._async_unsub_state_changed: self._async_unsub_state_changed() self._async_unsub_state_changed = None - yield from super().async_remove() - - @asyncio.coroutine - def _async_state_changed_listener(self, entity_id, old_state, new_state): + @callback + def _state_changed_listener(self, entity_id, old_state, new_state): """Respond to a member state changing. This method must be run in the event loop. """ - # removed - if self._async_unsub_state_changed is None: - return - self._async_update_group_state(new_state) - yield from self.async_update_ha_state() + self.hass.async_add_job(self.async_update_ha_state()) @property def _tracking_states(self): diff --git a/homeassistant/components/homematic.py b/homeassistant/components/homematic.py index 7f2f8b813a4bf4..004a0c6dbfbdb7 100644 --- a/homeassistant/components/homematic.py +++ b/homeassistant/components/homematic.py @@ -23,10 +23,10 @@ from homeassistant.util import Throttle DOMAIN = 'homematic' -REQUIREMENTS = ["pyhomematic==0.1.19"] +REQUIREMENTS = ["pyhomematic==0.1.18"] MIN_TIME_BETWEEN_UPDATE_HUB = timedelta(seconds=300) -SCAN_INTERVAL = timedelta(seconds=30) +MIN_TIME_BETWEEN_UPDATE_VAR = timedelta(seconds=30) DISCOVER_SWITCHES = 'homematic.switch' DISCOVER_LIGHTS = 'homematic.light' @@ -54,21 +54,19 @@ HM_DEVICE_TYPES = { DISCOVER_SWITCHES: [ 'Switch', 'SwitchPowermeter', 'IOSwitch', 'IPSwitch', - 'IPSwitchPowermeter', 'KeyMatic', 'HMWIOSwitch'], + 'IPSwitchPowermeter', 'KeyMatic'], DISCOVER_LIGHTS: ['Dimmer', 'KeyDimmer'], DISCOVER_SENSORS: [ - 'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion', 'MotionIP', + 'SwitchPowermeter', 'Motion', 'MotionV2', 'RemoteMotion', 'ThermostatWall', 'AreaThermostat', 'RotaryHandleSensor', 'WaterSensor', 'PowermeterGas', 'LuxSensor', 'WeatherSensor', 'WeatherStation', 'ThermostatWall2', 'TemperatureDiffSensor', - 'TemperatureSensor', 'CO2Sensor', 'IPSwitchPowermeter', 'HMWIOSwitch'], + 'TemperatureSensor', 'CO2Sensor', 'IPSwitchPowermeter'], DISCOVER_CLIMATE: [ - 'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2', - 'MAXWallThermostat'], + 'Thermostat', 'ThermostatWall', 'MAXThermostat', 'ThermostatWall2'], DISCOVER_BINARY_SENSORS: [ 'ShutterContact', 'Smoke', 'SmokeV2', 'Motion', 'MotionV2', - 'RemoteMotion', 'WeatherSensor', 'TiltSensor', 'IPShutterContact', - 'HMWIOSwitch'], + 'RemoteMotion', 'WeatherSensor', 'TiltSensor', 'IPShutterContact'], DISCOVER_COVER: ['Blind', 'KeyBlind'] } @@ -236,7 +234,7 @@ def setup(hass, config): """Setup the Homematic component.""" from pyhomematic import HMConnection - component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) + component = EntityComponent(_LOGGER, DOMAIN, hass) hass.data[DATA_DELAY] = config[DOMAIN].get(CONF_DELAY) hass.data[DATA_DEVINIT] = {} @@ -463,7 +461,9 @@ def _get_devices(hass, device_type, keys, proxy): _LOGGER.debug("Handling %s: %s", param, channels) for channel in channels: name = _create_ha_name( - name=device.NAME, channel=channel, param=param, + name=device.NAME, + channel=channel, + param=param, count=len(channels) ) device_dict = { @@ -623,6 +623,7 @@ def _update_hub_state(self): state = self._homematic.getServiceMessages(self._name) self._state = STATE_UNKNOWN if state is None else len(state) + @Throttle(MIN_TIME_BETWEEN_UPDATE_VAR) def _update_variables_state(self): """Retrive all variable data and update hmvariable states.""" if not self._use_variables: @@ -854,11 +855,11 @@ def _subscribe_homematic_events(self): # Set callbacks for channel in channels_to_sub: - _LOGGER.debug( - "Subscribe channel %s from %s", str(channel), self._name) - self._hmdevice.setEventCallback( - callback=self._hm_event_callback, bequeath=False, - channel=channel) + _LOGGER.debug("Subscribe channel %s from %s", + str(channel), self._name) + self._hmdevice.setEventCallback(callback=self._hm_event_callback, + bequeath=False, + channel=channel) def _load_data_from_hm(self): """Load first value from pyhomematic.""" diff --git a/homeassistant/components/http/__init__.py b/homeassistant/components/http/__init__.py index 2bb35dd8f3f947..e35b5f31d8fb70 100644 --- a/homeassistant/components/http/__init__.py +++ b/homeassistant/components/http/__init__.py @@ -32,7 +32,7 @@ KEY_USE_X_FORWARDED_FOR, KEY_TRUSTED_NETWORKS, KEY_BANS_ENABLED, KEY_LOGIN_THRESHOLD, KEY_DEVELOPMENT, KEY_AUTHENTICATED) -from .static import FILE_SENDER, CACHING_FILE_SENDER, staticresource_middleware +from .static import FILE_SENDER, GZIP_FILE_SENDER, staticresource_middleware from .util import get_real_ip DOMAIN = 'http' @@ -272,7 +272,7 @@ def register_static_path(self, url_root, path, cache_length=31): @asyncio.coroutine def serve_file(request): """Serve file from disk.""" - res = yield from CACHING_FILE_SENDER.send(request, filepath) + res = yield from GZIP_FILE_SENDER.send(request, filepath) return res # aiohttp supports regex matching for variables. Using that as temp diff --git a/homeassistant/components/http/static.py b/homeassistant/components/http/static.py index 6489144ec704c0..0bd68d6136e73c 100644 --- a/homeassistant/components/http/static.py +++ b/homeassistant/components/http/static.py @@ -1,40 +1,69 @@ """Static file handling for HTTP component.""" import asyncio +import mimetypes import re from aiohttp import hdrs from aiohttp.file_sender import FileSender from aiohttp.web_urldispatcher import StaticResource +from aiohttp.web_exceptions import HTTPNotModified + from .const import KEY_DEVELOPMENT _FINGERPRINT = re.compile(r'^(.+)-[a-z0-9]{32}\.(\w+)$', re.IGNORECASE) -class CachingFileSender(FileSender): - """FileSender class that caches output if not in dev mode.""" - - def __init__(self, *args, **kwargs): - """Initialize the hass file sender.""" - super().__init__(*args, **kwargs) - - orig_sendfile = self._sendfile +class GzipFileSender(FileSender): + """FileSender class capable of sending gzip version if available.""" - @asyncio.coroutine - def sendfile(request, resp, fobj, count): - """Sendfile that includes a cache header.""" - if not request.app[KEY_DEVELOPMENT]: - cache_time = 31 * 86400 # = 1 month - resp.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format( - cache_time) + # pylint: disable=invalid-name - yield from orig_sendfile(request, resp, fobj, count) + @asyncio.coroutine + def send(self, request, filepath): + """Send filepath to client using request.""" + gzip = False + if 'gzip' in request.headers[hdrs.ACCEPT_ENCODING]: + gzip_path = filepath.with_name(filepath.name + '.gz') + + 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]: + cache_time = 31 * 86400 # = 1 month + resp.headers[hdrs.CACHE_CONTROL] = "public, max-age={}".format( + cache_time) + + file_size = st.st_size + + resp.content_length = file_size + with filepath.open('rb') as f: + yield from self._sendfile(request, resp, f, file_size) - # Overwriting like this because __init__ can change implementation. - self._sendfile = sendfile + return resp +GZIP_FILE_SENDER = GzipFileSender() FILE_SENDER = FileSender() -CACHING_FILE_SENDER = CachingFileSender() @asyncio.coroutine @@ -48,7 +77,7 @@ def staticresource_middleware(app, handler): return handler # pylint: disable=protected-access - inst._file_sender = CACHING_FILE_SENDER + inst._file_sender = GZIP_FILE_SENDER @asyncio.coroutine def static_middleware_handler(request): diff --git a/homeassistant/components/light/flux_led.py b/homeassistant/components/light/flux_led.py index 22dd40b30efd2d..43c5ada9b8de76 100644 --- a/homeassistant/components/light/flux_led.py +++ b/homeassistant/components/light/flux_led.py @@ -17,7 +17,8 @@ PLATFORM_SCHEMA) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['flux_led==0.12'] +REQUIREMENTS = ['https://github.com/Danielhiversen/flux_led/archive/0.11.zip' + '#flux_led==0.11'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py index 8cfa7a587fb692..54b95deee4796e 100644 --- a/homeassistant/components/media_player/kodi.py +++ b/homeassistant/components/media_player/kodi.py @@ -21,7 +21,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['jsonrpc-async==0.2'] +REQUIREMENTS = ['jsonrpc-async==0.1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/media_player/nad.py b/homeassistant/components/media_player/nad.py deleted file mode 100644 index 0b8efda0e44e24..00000000000000 --- a/homeassistant/components/media_player/nad.py +++ /dev/null @@ -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') diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py index 05cbc5d0a807c2..df46fb5a03dd85 100644 --- a/homeassistant/components/script.py +++ b/homeassistant/components/script.py @@ -31,7 +31,6 @@ ATTR_VARIABLES = 'variables' ATTR_LAST_ACTION = 'last_action' -ATTR_LAST_TRIGGERED = 'last_triggered' ATTR_CAN_CANCEL = 'can_cancel' _LOGGER = logging.getLogger(__name__) @@ -156,7 +155,6 @@ def name(self): def state_attributes(self): """Return the state attributes.""" attrs = {} - attrs[ATTR_LAST_TRIGGERED] = self.script.last_triggered if self.script.can_cancel: attrs[ATTR_CAN_CANCEL] = self.script.can_cancel if self.script.last_action: diff --git a/homeassistant/components/sensor/dublin_bus_transport.py b/homeassistant/components/sensor/dublin_bus_transport.py deleted file mode 100644 index 10d2c2b39f0621..00000000000000 --- a/homeassistant/components/sensor/dublin_bus_transport.py +++ /dev/null @@ -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'}] diff --git a/homeassistant/components/sensor/gtfs.py b/homeassistant/components/sensor/gtfs.py index 2726f1f579f95c..5769860284cd74 100644 --- a/homeassistant/components/sensor/gtfs.py +++ b/homeassistant/components/sensor/gtfs.py @@ -37,7 +37,7 @@ vol.Required(CONF_ORIGIN): cv.string, vol.Required(CONF_DESTINATION): cv.string, vol.Required(CONF_DATA): cv.string, - vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, }) @@ -226,9 +226,9 @@ def update(self): self.destination) if not self._departure: self._state = 0 - self._attributes = {'Info': 'No more departures today'} + self._attributes = {'Info': 'No more bus today'} if self._name == '': - self._name = (self._custom_name or DEFAULT_NAME) + self._name = (self._custom_name or "GTFS Sensor") return self._state = self._departure['minutes_until_departure'] diff --git a/homeassistant/components/sensor/tcp.py b/homeassistant/components/sensor/tcp.py index 30ceba776e9e50..ab27e1e580fa97 100644 --- a/homeassistant/components/sensor/tcp.py +++ b/homeassistant/components/sensor/tcp.py @@ -16,6 +16,7 @@ CONF_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE) from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity import Entity +from homeassistant.helpers.template import Template import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -56,7 +57,7 @@ def __init__(self, hass, config): value_template = config.get(CONF_VALUE_TEMPLATE) if value_template is not None: - value_template.hass = hass + value_template = Template(value_template, hass) self._hass = hass self._config = { diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 0f731a51485059..01d0a6a15e331d 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -5,6 +5,7 @@ https://home-assistant.io/components/tts/ """ import asyncio +import functools import hashlib import logging import mimetypes @@ -246,6 +247,8 @@ def remove_files(): def async_register_engine(self, engine, provider, config): """Register a TTS provider.""" provider.hass = self.hass + if CONF_LANG in config: + provider.language = config.get(CONF_LANG) self.providers[engine] = provider @asyncio.coroutine @@ -254,16 +257,9 @@ def async_get_url(self, engine, message, cache=None, language=None): This method is a coroutine. """ - provider = self.providers[engine] - - language = language or provider.default_language - if language is None or \ - language not in provider.supported_languages: - raise HomeAssistantError("Not supported language {0}".format( - language)) - msg_hash = hashlib.sha1(bytes(message, 'utf-8')).hexdigest() - key = KEY_PATTERN.format(msg_hash, language, engine).lower() + language_key = language or self.providers[engine].language + key = KEY_PATTERN.format(msg_hash, language_key, engine).lower() use_cache = cache if cache is not None else self.use_cache # is speech allready in memory @@ -391,22 +387,13 @@ class Provider(object): """Represent a single provider.""" hass = None + language = None - @property - def default_language(self): - """Default language.""" - return None - - @property - def supported_languages(self): - """List of supported languages.""" - return None - - def get_tts_audio(self, message, language): + def get_tts_audio(self, message, language=None): """Load tts audio file from provider.""" raise NotImplementedError() - def async_get_tts_audio(self, message, language): + def async_get_tts_audio(self, message, language=None): """Load tts audio file from provider. Return a tuple of file extension and data as bytes. @@ -414,7 +401,8 @@ def async_get_tts_audio(self, message, language): This method must be run in the event loop and returns a coroutine. """ return self.hass.loop.run_in_executor( - None, self.get_tts_audio, message, language) + None, + functools.partial(self.get_tts_audio, message, language=language)) class TextToSpeechView(HomeAssistantView): diff --git a/homeassistant/components/tts/demo.py b/homeassistant/components/tts/demo.py index 88afa0643f2b71..68d49d58f78a14 100644 --- a/homeassistant/components/tts/demo.py +++ b/homeassistant/components/tts/demo.py @@ -6,50 +6,28 @@ """ import os -import voluptuous as vol - -from homeassistant.components.tts import Provider, PLATFORM_SCHEMA, CONF_LANG - -SUPPORT_LANGUAGES = [ - 'en', 'de' -] - -DEFAULT_LANG = 'en' - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_LANG, default=DEFAULT_LANG): vol.In(SUPPORT_LANGUAGES), -}) +from homeassistant.components.tts import Provider def get_engine(hass, config): """Setup Demo speech component.""" - return DemoProvider(config[CONF_LANG]) + return DemoProvider() class DemoProvider(Provider): """Demo speech api provider.""" - def __init__(self, lang): - """Initialize demo provider.""" - self._lang = lang - - @property - def default_language(self): - """Default language.""" - return self._lang - - @property - def supported_languages(self): - """List of supported languages.""" - return SUPPORT_LANGUAGES + def __init__(self): + """Initialize demo provider for TTS.""" + self.language = 'en' - def get_tts_audio(self, message, language): + def get_tts_audio(self, message, language=None): """Load TTS from demo.""" filename = os.path.join(os.path.dirname(__file__), "demo.mp3") try: with open(filename, 'rb') as voice: data = voice.read() except OSError: - return (None, None) + return return ("mp3", data) diff --git a/homeassistant/components/tts/google.py b/homeassistant/components/tts/google.py index dc03013d4f1a4f..e1bb4e5e4e5b4d 100644 --- a/homeassistant/components/tts/google.py +++ b/homeassistant/components/tts/google.py @@ -42,16 +42,15 @@ @asyncio.coroutine def async_get_engine(hass, config): """Setup Google speech component.""" - return GoogleProvider(hass, config[CONF_LANG]) + return GoogleProvider(hass) class GoogleProvider(Provider): """Google speech api provider.""" - def __init__(self, hass, lang): + def __init__(self, hass): """Init Google TTS service.""" self.hass = hass - self._lang = lang self.headers = { 'Referer': "http://translate.google.com/", 'User-Agent': ("Mozilla/5.0 (Windows NT 10.0; WOW64) " @@ -59,18 +58,8 @@ def __init__(self, hass, lang): "Chrome/47.0.2526.106 Safari/537.36") } - @property - def default_language(self): - """Default language.""" - return self._lang - - @property - def supported_languages(self): - """List of supported languages.""" - return SUPPORT_LANGUAGES - @asyncio.coroutine - def async_get_tts_audio(self, message, language): + def async_get_tts_audio(self, message, language=None): """Load TTS from google.""" from gtts_token import gtts_token @@ -78,6 +67,11 @@ def async_get_tts_audio(self, message, language): websession = async_get_clientsession(self.hass) message_parts = self._split_message_to_parts(message) + # If language is not specified or is not supported - use the language + # from the config. + if language not in SUPPORT_LANGUAGES: + language = self.language + data = b'' for idx, part in enumerate(message_parts): part_token = yield from self.hass.loop.run_in_executor( diff --git a/homeassistant/components/tts/picotts.py b/homeassistant/components/tts/picotts.py index 28db88c03b04d8..366973813a288d 100644 --- a/homeassistant/components/tts/picotts.py +++ b/homeassistant/components/tts/picotts.py @@ -29,31 +29,18 @@ def get_engine(hass, config): if shutil.which("pico2wave") is None: _LOGGER.error("'pico2wave' was not found") return False - return PicoProvider(config[CONF_LANG]) + return PicoProvider() class PicoProvider(Provider): """pico speech api provider.""" - def __init__(self, lang): - """Initialize pico provider.""" - self._lang = lang - - @property - def default_language(self): - """Default language.""" - return self._lang - - @property - def supported_languages(self): - """List of supported languages.""" - return SUPPORT_LANGUAGES - - def get_tts_audio(self, message, language): + def get_tts_audio(self, message, language=None): """Load TTS using pico2wave.""" + if language not in SUPPORT_LANGUAGES: + language = self.language with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmpf: fname = tmpf.name - cmd = ['pico2wave', '--wave', fname, '-l', language, message] subprocess.call(cmd) data = None @@ -65,7 +52,6 @@ def get_tts_audio(self, message, language): return (None, None) finally: os.remove(fname) - if data: return ("wav", data) return (None, None) diff --git a/homeassistant/components/tts/voicerss.py b/homeassistant/components/tts/voicerss.py index 2dda27b0c06728..688ae7f6e25221 100644 --- a/homeassistant/components/tts/voicerss.py +++ b/homeassistant/components/tts/voicerss.py @@ -93,34 +93,27 @@ class VoiceRSSProvider(Provider): def __init__(self, hass, conf): """Init VoiceRSS TTS service.""" self.hass = hass - self._extension = conf[CONF_CODEC] - self._lang = conf[CONF_LANG] - - self._form_data = { - 'key': conf[CONF_API_KEY], - 'hl': conf[CONF_LANG], - 'c': (conf[CONF_CODEC]).upper(), - 'f': conf[CONF_FORMAT], - } - - @property - def default_language(self): - """Default language.""" - return self._lang + self.extension = conf.get(CONF_CODEC) - @property - def supported_languages(self): - """List of supported languages.""" - return SUPPORT_LANGUAGES + self.form_data = { + 'key': conf.get(CONF_API_KEY), + 'hl': conf.get(CONF_LANG), + 'c': (conf.get(CONF_CODEC)).upper(), + 'f': conf.get(CONF_FORMAT), + } @asyncio.coroutine - def async_get_tts_audio(self, message, language): + def async_get_tts_audio(self, message, language=None): """Load TTS from voicerss.""" websession = async_get_clientsession(self.hass) - form_data = self._form_data.copy() + form_data = self.form_data.copy() form_data['src'] = message - form_data['hl'] = language + + # If language is specified and supported - use it instead of the + # language in the config. + if language in SUPPORT_LANGUAGES: + form_data['hl'] = language request = None try: @@ -148,4 +141,4 @@ def async_get_tts_audio(self, message, language): if request is not None: yield from request.release() - return (self._extension, data) + return (self.extension, data) diff --git a/homeassistant/components/updater.py b/homeassistant/components/updater.py index c8385c8aac0777..c05aedbb88832b 100644 --- a/homeassistant/components/updater.py +++ b/homeassistant/components/updater.py @@ -22,7 +22,7 @@ from homeassistant.const import ATTR_FRIENDLY_NAME from homeassistant.helpers import event -REQUIREMENTS = ['distro==1.0.2'] +REQUIREMENTS = ['distro==1.0.1'] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/wemo.py b/homeassistant/components/wemo.py index ba068905087f56..71bb2984c7e0bc 100644 --- a/homeassistant/components/wemo.py +++ b/homeassistant/components/wemo.py @@ -14,7 +14,7 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP -REQUIREMENTS = ['pywemo==0.4.9'] +REQUIREMENTS = ['pywemo==0.4.7'] DOMAIN = 'wemo' diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 46703d86450038..4d6a2b01df7a1e 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -46,7 +46,6 @@ def __init__(self, hass: HomeAssistant, sequence, name: str=None, self._change_listener = change_listener self._cur = -1 self.last_action = None - self.last_triggered = None self.can_cancel = any(CONF_DELAY in action for action in self.sequence) self._async_unsub_delay_listener = None @@ -69,7 +68,6 @@ def async_run(self, variables: Optional[Sequence]=None) -> None: This method is a coroutine. """ - self.last_triggered = date_util.utcnow() if self._cur == -1: self._log('Running script') self._cur = 0 diff --git a/requirements_all.txt b/requirements_all.txt old mode 100755 new mode 100644 index 5a4ef5a27366e1..6a59fffae2fa53 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -4,16 +4,16 @@ pyyaml>=3.11,<4 pytz>=2016.10 pip>=7.0.0 jinja2>=2.8 -voluptuous==0.9.3 +voluptuous==0.9.2 typing>=3,<4 -aiohttp==1.2 +aiohttp==1.1.6 async_timeout==1.1.0 # homeassistant.components.nuimo_controller --only-binary=all http://github.com/getSenic/nuimo-linux-python/archive/29fc42987f74d8090d0e2382e8f248ff5990b8c9.zip#nuimo==1.0.0 # homeassistant.components.bbb_gpio -# Adafruit_BBIO==1.0.0 +Adafruit_BBIO==1.0.0 # homeassistant.components.isy994 PyISY==1.0.7 @@ -90,7 +90,7 @@ denonavr==0.3.0 directpy==0.1 # homeassistant.components.updater -distro==1.0.2 +distro==1.0.1 # homeassistant.components.switch.digitalloggers dlipower==0.7.165 @@ -132,9 +132,6 @@ fitbit==0.2.3 # homeassistant.components.sensor.fixer fixerio==0.1.1 -# homeassistant.components.light.flux_led -flux_led==0.12 - # homeassistant.components.notify.free_mobile freesms==0.1.1 @@ -183,6 +180,9 @@ hikvision==0.4 # homeassistant.components.nest http://github.com/technicalpickles/python-nest/archive/e6c9d56a8df455d4d7746389811f2c1387e8cb33.zip#python-nest==3.0.3 +# homeassistant.components.light.flux_led +https://github.com/Danielhiversen/flux_led/archive/0.11.zip#flux_led==0.11 + # homeassistant.components.switch.tplink https://github.com/GadgetReactor/pyHS100/archive/45fc3548882628bcde3e3d365db341849457bef2.zip#pyHS100==0.2.2 @@ -224,9 +224,6 @@ https://github.com/jabesq/pybotvac/archive/v0.0.1.zip#pybotvac==0.0.1 # homeassistant.components.sensor.sabnzbd https://github.com/jamespcole/home-assistant-nzb-clients/archive/616cad59154092599278661af17e2a9f2cf5e2a9.zip#python-sabnzbd==0.1 -# homeassistant.components.media_player.nad -https://github.com/joopert/nad_receiver/archive/0.0.2.zip#nad_receiver==0.0.2 - # homeassistant.components.media_player.russound_rnet https://github.com/laf/russound/archive/0.1.6.zip#russound==0.1.6 @@ -278,7 +275,7 @@ insteon_hub==0.4.5 insteonlocal==0.39 # homeassistant.components.media_player.kodi -jsonrpc-async==0.2 +jsonrpc-async==0.1 # homeassistant.components.notify.kodi jsonrpc-requests==0.3 @@ -425,7 +422,7 @@ pyharmony==1.0.12 pyhik==0.0.7 # homeassistant.components.homematic -pyhomematic==0.1.19 +pyhomematic==0.1.18 # homeassistant.components.device_tracker.icloud pyicloud==0.9.1 @@ -514,9 +511,6 @@ python-vlc==1.1.2 # homeassistant.components.wink python-wink==0.11.0 -# homeassistant.components.device_tracker.trackr -pytrackr==0.0.5 - # homeassistant.components.device_tracker.unifi pyunifi==1.3 @@ -530,7 +524,7 @@ pyvera==0.2.21 pywebpush==0.6.1 # homeassistant.components.wemo -pywemo==0.4.9 +pywemo==0.4.7 # homeassistant.components.light.yeelight pyyeelight==1.0-beta diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py index 0231e0d51770f8..f4258ea825b02c 100755 --- a/script/gen_requirements_all.py +++ b/script/gen_requirements_all.py @@ -10,7 +10,6 @@ 'RPi.GPIO', 'rpi-rf', 'Adafruit_Python_DHT', - 'Adafruit_BBIO', 'fritzconnection', 'pybluez', 'bluepy', diff --git a/setup.py b/setup.py index 4f223eb9b8abf7..a62dbed80e8908 100755 --- a/setup.py +++ b/setup.py @@ -20,9 +20,9 @@ 'pytz>=2016.10', 'pip>=7.0.0', 'jinja2>=2.8', - 'voluptuous==0.9.3', + 'voluptuous==0.9.2', 'typing>=3,<4', - 'aiohttp==1.2', + 'aiohttp==1.1.6', 'async_timeout==1.1.0', ] diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py index 0b36b835cd5ad4..4aab6401939c44 100644 --- a/tests/components/emulated_hue/test_hue_api.py +++ b/tests/components/emulated_hue/test_hue_api.py @@ -1,9 +1,9 @@ """The tests for the emulated Hue component.""" -import asyncio import json +import unittest from unittest.mock import patch -import pytest +import requests from homeassistant import bootstrap, const, core import homeassistant.components as core_components @@ -12,12 +12,10 @@ ) from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.components.emulated_hue.hue_api import ( - HUE_API_STATE_ON, HUE_API_STATE_BRI, HueUsernameView, - HueAllLightsStateView, HueOneLightStateView, HueOneLightChangeView) -from homeassistant.components.emulated_hue import Config + HUE_API_STATE_ON, HUE_API_STATE_BRI) +from homeassistant.util.async import run_coroutine_threadsafe -from tests.common import ( - get_test_instance_port, mock_http_component_app) +from tests.common import get_test_instance_port, get_test_home_assistant HTTP_SERVER_PORT = get_test_instance_port() BRIDGE_SERVER_PORT = get_test_instance_port() @@ -26,38 +24,41 @@ JSON_HEADERS = {const.HTTP_HEADER_CONTENT_TYPE: const.CONTENT_TYPE_JSON} -@pytest.fixture -def hass_hue(loop, hass): - """Setup a hass instance for these tests.""" - # We need to do this to get access to homeassistant/turn_(on,off) - loop.run_until_complete( - core_components.async_setup(hass, {core.DOMAIN: {}})) +class TestEmulatedHueExposedByDefault(unittest.TestCase): + """Test class for emulated hue component.""" - loop.run_until_complete(bootstrap.async_setup_component( - hass, http.DOMAIN, - {http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}})) + @classmethod + def setUpClass(cls): + """Setup the class.""" + cls.hass = hass = get_test_home_assistant() - with patch('homeassistant.components' - '.emulated_hue.UPNPResponderThread'): - loop.run_until_complete( - bootstrap.async_setup_component(hass, emulated_hue.DOMAIN, { + # We need to do this to get access to homeassistant/turn_(on,off) + run_coroutine_threadsafe( + core_components.async_setup(hass, {core.DOMAIN: {}}), hass.loop + ).result() + + bootstrap.setup_component( + hass, http.DOMAIN, + {http.DOMAIN: {http.CONF_SERVER_PORT: HTTP_SERVER_PORT}}) + + with patch('homeassistant.components' + '.emulated_hue.UPNPResponderThread'): + bootstrap.setup_component(hass, emulated_hue.DOMAIN, { emulated_hue.DOMAIN: { emulated_hue.CONF_LISTEN_PORT: BRIDGE_SERVER_PORT, emulated_hue.CONF_EXPOSE_BY_DEFAULT: True } - })) + }) - loop.run_until_complete( - bootstrap.async_setup_component(hass, light.DOMAIN, { + bootstrap.setup_component(cls.hass, light.DOMAIN, { 'light': [ { 'platform': 'demo', } ] - })) + }) - loop.run_until_complete( - bootstrap.async_setup_component(hass, script.DOMAIN, { + bootstrap.setup_component(cls.hass, script.DOMAIN, { 'script': { 'set_kitchen_light': { 'sequence': [ @@ -72,354 +73,338 @@ def hass_hue(loop, hass): ] } } - })) + }) - loop.run_until_complete( - bootstrap.async_setup_component(hass, media_player.DOMAIN, { + bootstrap.setup_component(cls.hass, media_player.DOMAIN, { 'media_player': [ { 'platform': 'demo', } ] - })) - - # Kitchen light is explicitly excluded from being exposed - kitchen_light_entity = hass.states.get('light.kitchen_lights') - attrs = dict(kitchen_light_entity.attributes) - attrs[emulated_hue.ATTR_EMULATED_HUE] = False - hass.states.async_set( - kitchen_light_entity.entity_id, kitchen_light_entity.state, - attributes=attrs) - - # Expose the script - script_entity = hass.states.get('script.set_kitchen_light') - attrs = dict(script_entity.attributes) - attrs[emulated_hue.ATTR_EMULATED_HUE] = True - hass.states.async_set( - script_entity.entity_id, script_entity.state, attributes=attrs - ) - - return hass - - -@pytest.fixture -def hue_client(loop, hass_hue, test_client): - """Create web client for emulated hue api.""" - web_app = mock_http_component_app(hass_hue) - config = Config({'type': 'alexa'}) - - HueUsernameView().register(web_app.router) - HueAllLightsStateView(config).register(web_app.router) - HueOneLightStateView(config).register(web_app.router) - HueOneLightChangeView(config).register(web_app.router) - - return loop.run_until_complete(test_client(web_app)) - - -@asyncio.coroutine -def test_discover_lights(hue_client): - """Test the discovery of lights.""" - result = yield from hue_client.get('/api/username/lights') - - assert result.status == 200 - assert 'application/json' in result.headers['content-type'] - - result_json = yield from result.json() - - devices = set(val['uniqueid'] for val in result_json.values()) - - # Make sure the lights we added to the config are there - assert 'light.ceiling_lights' in devices - assert 'light.bed_light' in devices - assert 'script.set_kitchen_light' in devices - assert 'light.kitchen_lights' not in devices - assert 'media_player.living_room' in devices - assert 'media_player.bedroom' in devices - assert 'media_player.walkman' in devices - assert 'media_player.lounge_room' in devices - - -@asyncio.coroutine -def test_get_light_state(hass_hue, hue_client): - """Test the getting of light state.""" - # Turn office light on and set to 127 brightness - yield from hass_hue.services.async_call( - light.DOMAIN, const.SERVICE_TURN_ON, - { - const.ATTR_ENTITY_ID: 'light.ceiling_lights', - light.ATTR_BRIGHTNESS: 127 - }, - blocking=True) - - office_json = yield from perform_get_light_state( - hue_client, 'light.ceiling_lights', 200) - - assert office_json['state'][HUE_API_STATE_ON] is True - assert office_json['state'][HUE_API_STATE_BRI] == 127 - - # Check all lights view - result = yield from hue_client.get('/api/username/lights') - - assert result.status == 200 - assert 'application/json' in result.headers['content-type'] - - result_json = yield from result.json() - - assert 'light.ceiling_lights' in result_json - assert result_json['light.ceiling_lights']['state'][HUE_API_STATE_BRI] == \ - 127 - - # Turn bedroom light off - yield from hass_hue.services.async_call( - light.DOMAIN, const.SERVICE_TURN_OFF, - { - const.ATTR_ENTITY_ID: 'light.bed_light' - }, - blocking=True) - - bedroom_json = yield from perform_get_light_state( - hue_client, 'light.bed_light', 200) - - assert bedroom_json['state'][HUE_API_STATE_ON] is False - assert bedroom_json['state'][HUE_API_STATE_BRI] == 0 - - # Make sure kitchen light isn't accessible - yield from perform_get_light_state( - hue_client, 'light.kitchen_lights', 404) - - -@asyncio.coroutine -def test_put_light_state(hass_hue, hue_client): - """Test the seeting of light states.""" - yield from perform_put_test_on_ceiling_lights(hass_hue, hue_client) - - # Turn the bedroom light on first - yield from hass_hue.services.async_call( - light.DOMAIN, const.SERVICE_TURN_ON, - {const.ATTR_ENTITY_ID: 'light.bed_light', - light.ATTR_BRIGHTNESS: 153}, - blocking=True) - - bed_light = hass_hue.states.get('light.bed_light') - assert bed_light.state == STATE_ON - assert bed_light.attributes[light.ATTR_BRIGHTNESS] == 153 - - # Go through the API to turn it off - bedroom_result = yield from perform_put_light_state( - hass_hue, hue_client, - 'light.bed_light', False) - - bedroom_result_json = yield from bedroom_result.json() - - assert bedroom_result.status == 200 - assert 'application/json' in bedroom_result.headers['content-type'] - - assert len(bedroom_result_json) == 1 - - # Check to make sure the state changed - bed_light = hass_hue.states.get('light.bed_light') - assert bed_light.state == STATE_OFF - - # Make sure we can't change the kitchen light state - kitchen_result = yield from perform_put_light_state( - hass_hue, hue_client, - 'light.kitchen_light', True) - assert kitchen_result.status == 404 - - -@asyncio.coroutine -def test_put_light_state_script(hass_hue, hue_client): - """Test the setting of script variables.""" - # Turn the kitchen light off first - yield from hass_hue.services.async_call( - light.DOMAIN, const.SERVICE_TURN_OFF, - {const.ATTR_ENTITY_ID: 'light.kitchen_lights'}, - blocking=True) - - # Emulated hue converts 0-100% to 0-255. - level = 23 - brightness = round(level * 255 / 100) - - script_result = yield from perform_put_light_state( - hass_hue, hue_client, - 'script.set_kitchen_light', True, brightness) - - script_result_json = yield from script_result.json() - - assert script_result.status == 200 - assert len(script_result_json) == 2 - - kitchen_light = hass_hue.states.get('light.kitchen_lights') - assert kitchen_light.state == 'on' - assert kitchen_light.attributes[light.ATTR_BRIGHTNESS] == level - - -@asyncio.coroutine -def test_put_light_state_media_player(hass_hue, hue_client): - """Test turning on media player and setting volume.""" - # Turn the music player off first - yield from hass_hue.services.async_call( - media_player.DOMAIN, const.SERVICE_TURN_OFF, - {const.ATTR_ENTITY_ID: 'media_player.walkman'}, - blocking=True) - - # Emulated hue converts 0.0-1.0 to 0-255. - level = 0.25 - brightness = round(level * 255) - - mp_result = yield from perform_put_light_state( - hass_hue, hue_client, - 'media_player.walkman', True, brightness) - - mp_result_json = yield from mp_result.json() - - assert mp_result.status == 200 - assert len(mp_result_json) == 2 - - walkman = hass_hue.states.get('media_player.walkman') - assert walkman.state == 'playing' - assert walkman.attributes[media_player.ATTR_MEDIA_VOLUME_LEVEL] == level - - -# pylint: disable=invalid-name -@asyncio.coroutine -def test_put_with_form_urlencoded_content_type(hass_hue, hue_client): - """Test the form with urlencoded content.""" - # Needed for Alexa - yield from perform_put_test_on_ceiling_lights( - hass_hue, hue_client, 'application/x-www-form-urlencoded') - - # Make sure we fail gracefully when we can't parse the data - data = {'key1': 'value1', 'key2': 'value2'} - result = yield from hue_client.put( - '/api/username/lights/light.ceiling_lights/state', - headers={ - 'content-type': 'application/x-www-form-urlencoded' - }, - data=data, - ) - - assert result.status == 400 - - -@asyncio.coroutine -def test_entity_not_found(hue_client): - """Test for entity which are not found.""" - result = yield from hue_client.get( - '/api/username/lights/not.existant_entity') - - assert result.status == 404 - - result = yield from hue_client.put( - '/api/username/lights/not.existant_entity/state') - - assert result.status == 404 - - -@asyncio.coroutine -def test_allowed_methods(hue_client): - """Test the allowed methods.""" - result = yield from hue_client.get( - '/api/username/lights/light.ceiling_lights/state') - - assert result.status == 405 - - result = yield from hue_client.put( - '/api/username/lights/light.ceiling_lights') - - assert result.status == 405 - - result = yield from hue_client.put( - '/api/username/lights') - - assert result.status == 405 - - -@asyncio.coroutine -def test_proper_put_state_request(hue_client): - """Test the request to set the state.""" - # Test proper on value parsing - result = yield from hue_client.put( - '/api/username/lights/{}/state'.format( - 'light.ceiling_lights'), + }) + + cls.hass.start() + + # Kitchen light is explicitly excluded from being exposed + kitchen_light_entity = cls.hass.states.get('light.kitchen_lights') + attrs = dict(kitchen_light_entity.attributes) + attrs[emulated_hue.ATTR_EMULATED_HUE] = False + cls.hass.states.set( + kitchen_light_entity.entity_id, kitchen_light_entity.state, + attributes=attrs) + + # Expose the script + script_entity = cls.hass.states.get('script.set_kitchen_light') + attrs = dict(script_entity.attributes) + attrs[emulated_hue.ATTR_EMULATED_HUE] = True + cls.hass.states.set( + script_entity.entity_id, script_entity.state, attributes=attrs + ) + + @classmethod + def tearDownClass(cls): + """Stop the class.""" + cls.hass.stop() + + def test_discover_lights(self): + """Test the discovery of lights.""" + result = requests.get( + BRIDGE_URL_BASE.format('/api/username/lights'), timeout=5) + + self.assertEqual(result.status_code, 200) + self.assertTrue('application/json' in result.headers['content-type']) + + result_json = result.json() + + # Make sure the lights we added to the config are there + self.assertTrue('light.ceiling_lights' in result_json) + self.assertTrue('light.bed_light' in result_json) + self.assertTrue('script.set_kitchen_light' in result_json) + self.assertTrue('light.kitchen_lights' not in result_json) + self.assertTrue('media_player.living_room' in result_json) + self.assertTrue('media_player.bedroom' in result_json) + self.assertTrue('media_player.walkman' in result_json) + self.assertTrue('media_player.lounge_room' in result_json) + + def test_get_light_state(self): + """Test the getting of light state.""" + # Turn office light on and set to 127 brightness + self.hass.services.call( + light.DOMAIN, const.SERVICE_TURN_ON, + { + const.ATTR_ENTITY_ID: 'light.ceiling_lights', + light.ATTR_BRIGHTNESS: 127 + }, + blocking=True) + + office_json = self.perform_get_light_state('light.ceiling_lights', 200) + + self.assertEqual(office_json['state'][HUE_API_STATE_ON], True) + self.assertEqual(office_json['state'][HUE_API_STATE_BRI], 127) + + # Check all lights view + result = requests.get( + BRIDGE_URL_BASE.format('/api/username/lights'), timeout=5) + + self.assertEqual(result.status_code, 200) + self.assertTrue('application/json' in result.headers['content-type']) + + result_json = result.json() + + self.assertTrue('light.ceiling_lights' in result_json) + self.assertEqual( + result_json['light.ceiling_lights']['state'][HUE_API_STATE_BRI], + 127, + ) + + # Turn bedroom light off + self.hass.services.call( + light.DOMAIN, const.SERVICE_TURN_OFF, + { + const.ATTR_ENTITY_ID: 'light.bed_light' + }, + blocking=True) + + bedroom_json = self.perform_get_light_state('light.bed_light', 200) + + self.assertEqual(bedroom_json['state'][HUE_API_STATE_ON], False) + self.assertEqual(bedroom_json['state'][HUE_API_STATE_BRI], 0) + + # Make sure kitchen light isn't accessible + kitchen_url = '/api/username/lights/{}'.format('light.kitchen_lights') + kitchen_result = requests.get( + BRIDGE_URL_BASE.format(kitchen_url), timeout=5) + + self.assertEqual(kitchen_result.status_code, 404) + + def test_put_light_state(self): + """Test the seeting of light states.""" + self.perform_put_test_on_ceiling_lights() + + # Turn the bedroom light on first + self.hass.services.call( + light.DOMAIN, const.SERVICE_TURN_ON, + {const.ATTR_ENTITY_ID: 'light.bed_light', + light.ATTR_BRIGHTNESS: 153}, + blocking=True) + + bed_light = self.hass.states.get('light.bed_light') + self.assertEqual(bed_light.state, STATE_ON) + self.assertEqual(bed_light.attributes[light.ATTR_BRIGHTNESS], 153) + + # Go through the API to turn it off + bedroom_result = self.perform_put_light_state( + 'light.bed_light', False) + + bedroom_result_json = bedroom_result.json() + + self.assertEqual(bedroom_result.status_code, 200) + self.assertTrue( + 'application/json' in bedroom_result.headers['content-type']) + + self.assertEqual(len(bedroom_result_json), 1) + + # Check to make sure the state changed + bed_light = self.hass.states.get('light.bed_light') + self.assertEqual(bed_light.state, STATE_OFF) + + # Make sure we can't change the kitchen light state + kitchen_result = self.perform_put_light_state( + 'light.kitchen_light', True) + self.assertEqual(kitchen_result.status_code, 404) + + def test_put_light_state_script(self): + """Test the setting of script variables.""" + # Turn the kitchen light off first + self.hass.services.call( + light.DOMAIN, const.SERVICE_TURN_OFF, + {const.ATTR_ENTITY_ID: 'light.kitchen_lights'}, + blocking=True) + + # Emulated hue converts 0-100% to 0-255. + level = 23 + brightness = round(level * 255 / 100) + + script_result = self.perform_put_light_state( + 'script.set_kitchen_light', True, brightness) + + script_result_json = script_result.json() + + self.assertEqual(script_result.status_code, 200) + self.assertEqual(len(script_result_json), 2) + + kitchen_light = self.hass.states.get('light.kitchen_lights') + self.assertEqual(kitchen_light.state, 'on') + self.assertEqual( + kitchen_light.attributes[light.ATTR_BRIGHTNESS], + level) + + def test_put_light_state_media_player(self): + """Test turning on media player and setting volume.""" + # Turn the music player off first + self.hass.services.call( + media_player.DOMAIN, const.SERVICE_TURN_OFF, + {const.ATTR_ENTITY_ID: 'media_player.walkman'}, + blocking=True) + + # Emulated hue converts 0.0-1.0 to 0-255. + level = 0.25 + brightness = round(level * 255) + + mp_result = self.perform_put_light_state( + 'media_player.walkman', True, brightness) + + mp_result_json = mp_result.json() + + self.assertEqual(mp_result.status_code, 200) + self.assertEqual(len(mp_result_json), 2) + + walkman = self.hass.states.get('media_player.walkman') + self.assertEqual(walkman.state, 'playing') + self.assertEqual( + walkman.attributes[media_player.ATTR_MEDIA_VOLUME_LEVEL], + level) + + # pylint: disable=invalid-name + def test_put_with_form_urlencoded_content_type(self): + """Test the form with urlencoded content.""" + # Needed for Alexa + self.perform_put_test_on_ceiling_lights( + 'application/x-www-form-urlencoded') + + # Make sure we fail gracefully when we can't parse the data + data = {'key1': 'value1', 'key2': 'value2'} + result = requests.put( + BRIDGE_URL_BASE.format( + '/api/username/lights/{}/state'.format( + 'light.ceiling_lights')), data=data) + + self.assertEqual(result.status_code, 400) + + def test_entity_not_found(self): + """Test for entity which are not found.""" + result = requests.get( + BRIDGE_URL_BASE.format( + '/api/username/lights/{}'.format("not.existant_entity")), + timeout=5) + + self.assertEqual(result.status_code, 404) + + result = requests.put( + BRIDGE_URL_BASE.format( + '/api/username/lights/{}/state'.format("non.existant_entity")), + timeout=5) + + self.assertEqual(result.status_code, 404) + + def test_allowed_methods(self): + """Test the allowed methods.""" + result = requests.get( + BRIDGE_URL_BASE.format( + '/api/username/lights/{}/state'.format( + "light.ceiling_lights"))) + + self.assertEqual(result.status_code, 405) + + result = requests.put( + BRIDGE_URL_BASE.format( + '/api/username/lights/{}'.format("light.ceiling_lights")), + data={'key1': 'value1'}) + + self.assertEqual(result.status_code, 405) + + result = requests.put( + BRIDGE_URL_BASE.format('/api/username/lights'), + data={'key1': 'value1'}) + + self.assertEqual(result.status_code, 405) + + def test_proper_put_state_request(self): + """Test the request to set the state.""" + # Test proper on value parsing + result = requests.put( + BRIDGE_URL_BASE.format( + '/api/username/lights/{}/state'.format( + 'light.ceiling_lights')), data=json.dumps({HUE_API_STATE_ON: 1234})) - assert result.status == 400 - - # Test proper brightness value parsing - result = yield from hue_client.put( - '/api/username/lights/{}/state'.format( - 'light.ceiling_lights'), - data=json.dumps({ - HUE_API_STATE_ON: True, - HUE_API_STATE_BRI: 'Hello world!' - })) - - assert result.status == 400 + self.assertEqual(result.status_code, 400) + # Test proper brightness value parsing + result = requests.put( + BRIDGE_URL_BASE.format( + '/api/username/lights/{}/state'.format( + 'light.ceiling_lights')), data=json.dumps({ + HUE_API_STATE_ON: True, + HUE_API_STATE_BRI: 'Hello world!' + })) -# pylint: disable=invalid-name -def perform_put_test_on_ceiling_lights(hass_hue, hue_client, - content_type='application/json'): - """Test the setting of a light.""" - # Turn the office light off first - yield from hass_hue.services.async_call( - light.DOMAIN, const.SERVICE_TURN_OFF, - {const.ATTR_ENTITY_ID: 'light.ceiling_lights'}, - blocking=True) + self.assertEqual(result.status_code, 400) - ceiling_lights = hass_hue.states.get('light.ceiling_lights') - assert ceiling_lights.state == STATE_OFF + # pylint: disable=invalid-name + def perform_put_test_on_ceiling_lights(self, + content_type='application/json'): + """Test the setting of a light.""" + # Turn the office light off first + self.hass.services.call( + light.DOMAIN, const.SERVICE_TURN_OFF, + {const.ATTR_ENTITY_ID: 'light.ceiling_lights'}, + blocking=True) - # Go through the API to turn it on - office_result = yield from perform_put_light_state( - hass_hue, hue_client, - 'light.ceiling_lights', True, 56, content_type) + ceiling_lights = self.hass.states.get('light.ceiling_lights') + self.assertEqual(ceiling_lights.state, STATE_OFF) - assert office_result.status == 200 - assert 'application/json' in office_result.headers['content-type'] + # Go through the API to turn it on + office_result = self.perform_put_light_state( + 'light.ceiling_lights', True, 56, content_type) - office_result_json = yield from office_result.json() + office_result_json = office_result.json() - assert len(office_result_json) == 2 + self.assertEqual(office_result.status_code, 200) + self.assertTrue( + 'application/json' in office_result.headers['content-type']) - # Check to make sure the state changed - ceiling_lights = hass_hue.states.get('light.ceiling_lights') - assert ceiling_lights.state == STATE_ON - assert ceiling_lights.attributes[light.ATTR_BRIGHTNESS] == 56 + self.assertEqual(len(office_result_json), 2) + # Check to make sure the state changed + ceiling_lights = self.hass.states.get('light.ceiling_lights') + self.assertEqual(ceiling_lights.state, STATE_ON) + self.assertEqual(ceiling_lights.attributes[light.ATTR_BRIGHTNESS], 56) -@asyncio.coroutine -def perform_get_light_state(client, entity_id, expected_status): - """Test the gettting of a light state.""" - result = yield from client.get('/api/username/lights/{}'.format(entity_id)) + def perform_get_light_state(self, entity_id, expected_status): + """Test the gettting of a light state.""" + result = requests.get( + BRIDGE_URL_BASE.format( + '/api/username/lights/{}'.format(entity_id)), timeout=5) - assert result.status == expected_status + self.assertEqual(result.status_code, expected_status) - if expected_status == 200: - assert 'application/json' in result.headers['content-type'] + if expected_status == 200: + 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 + def perform_put_light_state(self, entity_id, is_on, brightness=None, + content_type='application/json'): + """Test the setting of a light state.""" + url = BRIDGE_URL_BASE.format( + '/api/username/lights/{}/state'.format(entity_id)) -@asyncio.coroutine -def perform_put_light_state(hass_hue, client, entity_id, is_on, - brightness=None, content_type='application/json'): - """Test the setting of a light state.""" - req_headers = {'Content-Type': content_type} + req_headers = {'Content-Type': content_type} - data = {HUE_API_STATE_ON: is_on} + data = {HUE_API_STATE_ON: is_on} - if brightness is not None: - data[HUE_API_STATE_BRI] = brightness + if brightness is not None: + data[HUE_API_STATE_BRI] = brightness - result = yield from client.put( - '/api/username/lights/{}/state'.format(entity_id), headers=req_headers, - data=json.dumps(data).encode()) + result = requests.put( + url, data=json.dumps(data), timeout=5, headers=req_headers) - # Wait until state change is complete before continuing - yield from hass_hue.async_block_till_done() + # Wait until state change is complete before continuing + self.hass.block_till_done() - return result + return result diff --git a/tests/components/sensor/test_tcp.py b/tests/components/sensor/test_tcp.py index fe6fa44b020c9d..d12eccccc63c5c 100644 --- a/tests/components/sensor/test_tcp.py +++ b/tests/components/sensor/test_tcp.py @@ -9,7 +9,6 @@ from homeassistant.bootstrap import setup_component from homeassistant.components.sensor import tcp from homeassistant.helpers.entity import Entity -from homeassistant.helpers.template import Template TEST_CONFIG = { 'sensor': { @@ -20,7 +19,7 @@ tcp.CONF_TIMEOUT: tcp.DEFAULT_TIMEOUT + 1, tcp.CONF_PAYLOAD: 'test_payload', tcp.CONF_UNIT_OF_MEASUREMENT: 'test_unit', - tcp.CONF_VALUE_TEMPLATE: Template('test_template'), + tcp.CONF_VALUE_TEMPLATE: 'test_template', tcp.CONF_VALUE_ON: 'test_on', tcp.CONF_BUFFER_SIZE: tcp.DEFAULT_BUFFER_SIZE + 1 }, @@ -253,7 +252,7 @@ def test_update_renders_value_in_template(self, mock_select, mock_socket): mock_socket = mock_socket().__enter__() mock_socket.recv.return_value = test_value.encode() config = copy(TEST_CONFIG['sensor']) - config[tcp.CONF_VALUE_TEMPLATE] = Template('{{ value }} {{ 1+1 }}') + config[tcp.CONF_VALUE_TEMPLATE] = '{{ value }} {{ 1+1 }}' sensor = tcp.TcpSensor(self.hass, config) assert sensor._state == '%s 2' % test_value @@ -266,6 +265,6 @@ def test_update_returns_if_template_render_fails( mock_socket = mock_socket().__enter__() mock_socket.recv.return_value = test_value.encode() config = copy(TEST_CONFIG['sensor']) - config[tcp.CONF_VALUE_TEMPLATE] = Template("{{ this won't work") + config[tcp.CONF_VALUE_TEMPLATE] = "{{ this won't work" sensor = tcp.TcpSensor(self.hass, config) assert sensor.update() is None diff --git a/tests/components/tts/test_init.py b/tests/components/tts/test_init.py index 715b98c4740a82..553813953132f5 100644 --- a/tests/components/tts/test_init.py +++ b/tests/components/tts/test_init.py @@ -22,7 +22,7 @@ class TestTTS(object): def setup_method(self): """Setup things to be run when tests are started.""" self.hass = get_test_home_assistant() - self.demo_provider = DemoProvider('en') + self.demo_provider = DemoProvider() self.default_tts_cache = self.hass.config.path(tts.DEFAULT_CACHE_DIR) def teardown_method(self): @@ -95,7 +95,7 @@ def test_setup_component_and_test_service_with_config_language(self): config = { tts.DOMAIN: { 'platform': 'demo', - 'language': 'de' + 'language': 'lang' } } @@ -111,23 +111,11 @@ def test_setup_component_and_test_service_with_config_language(self): assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC assert calls[0].data[ATTR_MEDIA_CONTENT_ID].find( "/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" - "_de_demo.mp3") \ + "_lang_demo.mp3") \ != -1 assert os.path.isfile(os.path.join( self.default_tts_cache, - "265944c108cbb00b2a621be5930513e03a0bb2cd_de_demo.mp3")) - - def test_setup_component_and_test_service_with_wrong_conf_language(self): - """Setup the demo platform and call service with wrong config.""" - config = { - tts.DOMAIN: { - 'platform': 'demo', - 'language': 'ru' - } - } - - with assert_setup_component(0, tts.DOMAIN): - setup_component(self.hass, tts.DOMAIN, config) + "265944c108cbb00b2a621be5930513e03a0bb2cd_lang_demo.mp3")) def test_setup_component_and_test_service_with_service_language(self): """Setup the demo platform and call service.""" @@ -144,7 +132,7 @@ def test_setup_component_and_test_service_with_service_language(self): self.hass.services.call(tts.DOMAIN, 'demo_say', { tts.ATTR_MESSAGE: "I person is on front of your door.", - tts.ATTR_LANGUAGE: "de", + tts.ATTR_LANGUAGE: "lang", }) self.hass.block_till_done() @@ -152,33 +140,9 @@ def test_setup_component_and_test_service_with_service_language(self): assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC assert calls[0].data[ATTR_MEDIA_CONTENT_ID].find( "/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd" - "_de_demo.mp3") \ + "_lang_demo.mp3") \ != -1 assert os.path.isfile(os.path.join( - self.default_tts_cache, - "265944c108cbb00b2a621be5930513e03a0bb2cd_de_demo.mp3")) - - def test_setup_component_test_service_with_wrong_service_language(self): - """Setup the demo platform and call service.""" - calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) - - config = { - tts.DOMAIN: { - 'platform': 'demo', - } - } - - with assert_setup_component(1, tts.DOMAIN): - setup_component(self.hass, tts.DOMAIN, config) - - self.hass.services.call(tts.DOMAIN, 'demo_say', { - tts.ATTR_MESSAGE: "I person is on front of your door.", - tts.ATTR_LANGUAGE: "lang", - }) - self.hass.block_till_done() - - assert len(calls) == 0 - assert not os.path.isfile(os.path.join( self.default_tts_cache, "265944c108cbb00b2a621be5930513e03a0bb2cd_lang_demo.mp3")) @@ -234,7 +198,7 @@ def test_setup_component_and_test_service_with_receive_voice(self): assert len(calls) == 1 req = requests.get(calls[0].data[ATTR_MEDIA_CONTENT_ID]) - _, demo_data = self.demo_provider.get_tts_audio("bla", 'en') + _, demo_data = self.demo_provider.get_tts_audio("bla") assert req.status_code == 200 assert req.content == demo_data @@ -355,7 +319,7 @@ def test_setup_component_test_with_cache_dir(self): """Setup demo platform with cache and call service without cache.""" calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) - _, demo_data = self.demo_provider.get_tts_audio("bla", 'en') + _, demo_data = self.demo_provider.get_tts_audio("bla") cache_file = os.path.join( self.default_tts_cache, "265944c108cbb00b2a621be5930513e03a0bb2cd_en_demo.mp3") @@ -375,7 +339,7 @@ def test_setup_component_test_with_cache_dir(self): setup_component(self.hass, tts.DOMAIN, config) with patch('homeassistant.components.tts.demo.DemoProvider.' - 'get_tts_audio', return_value=(None, None)): + 'get_tts_audio', return_value=None): self.hass.services.call(tts.DOMAIN, 'demo_say', { tts.ATTR_MESSAGE: "I person is on front of your door.", }) @@ -388,7 +352,7 @@ def test_setup_component_test_with_cache_dir(self): != -1 @patch('homeassistant.components.tts.demo.DemoProvider.get_tts_audio', - return_value=(None, None)) + return_value=None) def test_setup_component_test_with_error_on_get_tts(self, tts_mock): """Setup demo platform with wrong get_tts_audio.""" calls = mock_service(self.hass, DOMAIN_MP, SERVICE_PLAY_MEDIA) @@ -411,7 +375,7 @@ def test_setup_component_test_with_error_on_get_tts(self, tts_mock): def test_setup_component_load_cache_retrieve_without_mem_cache(self): """Setup component and load cache and get without mem cache.""" - _, demo_data = self.demo_provider.get_tts_audio("bla", 'en') + _, demo_data = self.demo_provider.get_tts_audio("bla") cache_file = os.path.join( self.default_tts_cache, "265944c108cbb00b2a621be5930513e03a0bb2cd_en_demo.mp3") diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 6eee484097b1d7..8787ff7b514652 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -353,22 +353,3 @@ def record_event(event): script_obj.run() self.hass.block_till_done() assert len(script_obj._config_cache) == 2 - - def test_last_triggered(self): - """Test the last_triggered.""" - event = 'test_event' - - script_obj = script.Script(self.hass, cv.SCRIPT_SCHEMA([ - {'event': event}, - {'delay': {'seconds': 5}}, - {'event': event}])) - - assert script_obj.last_triggered is None - - time = dt_util.utcnow() - with mock.patch('homeassistant.helpers.script.date_util.utcnow', - return_value=time): - script_obj.run() - self.hass.block_till_done() - - assert script_obj.last_triggered == time