mirror of
https://github.com/home-assistant/core.git
synced 2026-01-08 16:47:42 +01:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee107755f8 | ||
|
|
081a0290ba | ||
|
|
95ed8fb245 | ||
|
|
0d4858e296 | ||
|
|
f6a6be9a22 | ||
|
|
065b077369 | ||
|
|
1ec08ce243 | ||
|
|
46c955a501 | ||
|
|
c1429f5d80 | ||
|
|
ed16681b8e | ||
|
|
1ab03d9e15 | ||
|
|
ffcaeb4ef1 | ||
|
|
dd1e352d1d | ||
|
|
3bfb5b119a | ||
|
|
a269603e3b | ||
|
|
8ae2ce2299 | ||
|
|
700b8b2d0c | ||
|
|
806aba4a1a | ||
|
|
1128cf576f | ||
|
|
febdb72fb2 | ||
|
|
3c0146d382 | ||
|
|
9e76293141 | ||
|
|
6149c2877d | ||
|
|
2efe607b78 | ||
|
|
9fc271d178 | ||
|
|
06f76e8e97 | ||
|
|
d5bd8b9405 | ||
|
|
34c03109e5 | ||
|
|
df3ceb8d87 | ||
|
|
f514d44224 | ||
|
|
7fb0055a92 | ||
|
|
f81eeded90 | ||
|
|
6df31da180 | ||
|
|
364f5c8c02 | ||
|
|
85ac85c959 | ||
|
|
a2565ad3b4 | ||
|
|
6d31d56c03 |
@@ -355,7 +355,7 @@ async def _async_set_up_integrations(
|
||||
if stage_1_domains:
|
||||
await asyncio.gather(*[
|
||||
async_setup_component(hass, domain, config)
|
||||
for domain in logging_domains
|
||||
for domain in stage_1_domains
|
||||
])
|
||||
|
||||
# Load all integrations
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"domain": "aws_lambda",
|
||||
"name": "Aws lambda",
|
||||
"documentation": "https://www.home-assistant.io/components/aws_lambda",
|
||||
"requirements": [
|
||||
"boto3==1.9.16"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"domain": "aws_sns",
|
||||
"name": "Aws sns",
|
||||
"documentation": "https://www.home-assistant.io/components/aws_sns",
|
||||
"requirements": [
|
||||
"boto3==1.9.16"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"domain": "aws_sqs",
|
||||
"name": "Aws sqs",
|
||||
"documentation": "https://www.home-assistant.io/components/aws_sqs",
|
||||
"requirements": [
|
||||
"boto3==1.9.16"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
import asyncio
|
||||
from base64 import b64decode, b64encode
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
|
||||
from datetime import timedelta
|
||||
@@ -19,26 +18,22 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEFAULT_RETRY = 3
|
||||
|
||||
|
||||
def ipv4_address(value):
|
||||
"""Validate an ipv4 address."""
|
||||
regex = re.compile(r'^\d+\.\d+\.\d+\.\d+$')
|
||||
if not regex.match(value):
|
||||
raise vol.Invalid('Invalid Ipv4 address, expected a.b.c.d')
|
||||
return value
|
||||
|
||||
|
||||
def data_packet(value):
|
||||
"""Decode a data packet given for broadlink."""
|
||||
return b64decode(cv.string(value))
|
||||
value = cv.string(value)
|
||||
extra = len(value) % 4
|
||||
if extra > 0:
|
||||
value = value + ('=' * (4 - extra))
|
||||
return b64decode(value)
|
||||
|
||||
|
||||
SERVICE_SEND_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_HOST): ipv4_address,
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_PACKET): vol.All(cv.ensure_list, [data_packet])
|
||||
})
|
||||
|
||||
SERVICE_LEARN_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_HOST): ipv4_address,
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -106,6 +106,10 @@ class CloudClient(Interface):
|
||||
entity_config=google_conf.get(CONF_ENTITY_CONFIG),
|
||||
)
|
||||
|
||||
# Set it to the latest.
|
||||
self._google_config.secure_devices_pin = \
|
||||
self._prefs.google_secure_devices_pin
|
||||
|
||||
return self._google_config
|
||||
|
||||
@property
|
||||
|
||||
@@ -63,10 +63,10 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||
if not daikin_api:
|
||||
return False
|
||||
hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: daikin_api})
|
||||
await asyncio.wait([
|
||||
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||
for component in COMPONENT_TYPES
|
||||
])
|
||||
for component in COMPONENT_TYPES:
|
||||
hass.async_create_task(
|
||||
hass.config_entries.async_forward_entry_setup(
|
||||
entry, component))
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Ecovacs",
|
||||
"documentation": "https://www.home-assistant.io/components/ecovacs",
|
||||
"requirements": [
|
||||
"sucks==0.9.3"
|
||||
"sucks==0.9.4"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
||||
@@ -149,7 +149,8 @@ class RuntimeEntryData:
|
||||
|
||||
|
||||
def _attr_obj_from_dict(cls, **kwargs):
|
||||
return cls(**{key: kwargs[key] for key in attr.fields_dict(cls)})
|
||||
return cls(**{key: kwargs[key] for key in attr.fields_dict(cls)
|
||||
if key in kwargs})
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "ESPHome",
|
||||
"documentation": "https://www.home-assistant.io/components/esphome",
|
||||
"requirements": [
|
||||
"aioesphomeapi==2.0.0"
|
||||
"aioesphomeapi==2.0.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
"name": "Flux",
|
||||
"documentation": "https://www.home-assistant.io/components/flux",
|
||||
"requirements": [],
|
||||
"dependencies": [
|
||||
"light"
|
||||
],
|
||||
"dependencies": [],
|
||||
"after_dependencies": ["light"],
|
||||
"codeowners": []
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Home Assistant Frontend",
|
||||
"documentation": "https://www.home-assistant.io/components/frontend",
|
||||
"requirements": [
|
||||
"home-assistant-frontend==20190419.0"
|
||||
"home-assistant-frontend==20190424.0"
|
||||
],
|
||||
"dependencies": [
|
||||
"api",
|
||||
|
||||
@@ -34,6 +34,7 @@ DEFAULT_EXPOSE_BY_DEFAULT = True
|
||||
DEFAULT_EXPOSED_DOMAINS = [
|
||||
'climate', 'cover', 'fan', 'group', 'input_boolean', 'light',
|
||||
'media_player', 'scene', 'script', 'switch', 'vacuum', 'lock',
|
||||
'binary_sensor', 'sensor'
|
||||
]
|
||||
|
||||
PREFIX_TYPES = 'action.devices.types.'
|
||||
@@ -49,6 +50,7 @@ TYPE_BLINDS = PREFIX_TYPES + 'BLINDS'
|
||||
TYPE_GARAGE = PREFIX_TYPES + 'GARAGE'
|
||||
TYPE_OUTLET = PREFIX_TYPES + 'OUTLET'
|
||||
TYPE_SENSOR = PREFIX_TYPES + 'SENSOR'
|
||||
TYPE_DOOR = PREFIX_TYPES + 'DOOR'
|
||||
|
||||
SERVICE_REQUEST_SYNC = 'request_sync'
|
||||
HOMEGRAPH_URL = 'https://homegraph.googleapis.com/'
|
||||
@@ -93,9 +95,10 @@ DOMAIN_TO_GOOGLE_TYPES = {
|
||||
|
||||
DEVICE_CLASS_TO_GOOGLE_TYPES = {
|
||||
(cover.DOMAIN, cover.DEVICE_CLASS_GARAGE): TYPE_GARAGE,
|
||||
(cover.DOMAIN, cover.DEVICE_CLASS_DOOR): TYPE_DOOR,
|
||||
(switch.DOMAIN, switch.DEVICE_CLASS_SWITCH): TYPE_SWITCH,
|
||||
(switch.DOMAIN, switch.DEVICE_CLASS_OUTLET): TYPE_OUTLET,
|
||||
(binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_DOOR): TYPE_SENSOR,
|
||||
(binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_DOOR): TYPE_DOOR,
|
||||
(binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_GARAGE_DOOR):
|
||||
TYPE_SENSOR,
|
||||
(binary_sensor.DOMAIN, binary_sensor.DEVICE_CLASS_LOCK): TYPE_SENSOR,
|
||||
|
||||
@@ -79,7 +79,7 @@ def _register_panel(hass, addon, data):
|
||||
|
||||
Return coroutine.
|
||||
"""
|
||||
return hass.components.frontend.async_register_built_in_panel(
|
||||
return hass.components.panel_custom.async_register_panel(
|
||||
frontend_url_path=addon,
|
||||
webcomponent_name='hassio-main',
|
||||
sidebar_title=data[ATTR_TITLE],
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"requirements": [],
|
||||
"dependencies": [
|
||||
"http",
|
||||
"frontend",
|
||||
"panel_custom"
|
||||
],
|
||||
"codeowners": [
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Heos",
|
||||
"documentation": "https://www.home-assistant.io/components/heos",
|
||||
"requirements": [
|
||||
"pyheos==0.3.1"
|
||||
"pyheos==0.4.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Denon HEOS Media Player."""
|
||||
from functools import reduce
|
||||
import asyncio
|
||||
from functools import reduce, wraps
|
||||
import logging
|
||||
from operator import ior
|
||||
from typing import Sequence
|
||||
|
||||
@@ -21,6 +23,8 @@ BASE_SUPPORTED_FEATURES = SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_SET | \
|
||||
SUPPORT_VOLUME_STEP | SUPPORT_CLEAR_PLAYLIST | \
|
||||
SUPPORT_SHUFFLE_SET | SUPPORT_SELECT_SOURCE
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
@@ -36,6 +40,20 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry,
|
||||
async_add_entities(devices, True)
|
||||
|
||||
|
||||
def log_command_error(command: str):
|
||||
"""Return decorator that logs command failure."""
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
from pyheos import CommandError
|
||||
try:
|
||||
await func(*args, **kwargs)
|
||||
except (CommandError, asyncio.TimeoutError, ConnectionError) as ex:
|
||||
_LOGGER.error("Unable to %s: %s", command, ex)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
class HeosMediaPlayer(MediaPlayerDevice):
|
||||
"""The HEOS player."""
|
||||
|
||||
@@ -68,6 +86,13 @@ class HeosMediaPlayer(MediaPlayerDevice):
|
||||
|
||||
async def _heos_event(self, event):
|
||||
"""Handle connection event."""
|
||||
from pyheos import CommandError, const
|
||||
if event == const.EVENT_CONNECTED:
|
||||
try:
|
||||
await self._player.refresh()
|
||||
except (CommandError, asyncio.TimeoutError, ConnectionError) as ex:
|
||||
_LOGGER.error("Unable to refresh player %s: %s",
|
||||
self._player, ex)
|
||||
await self.async_update_ha_state(True)
|
||||
|
||||
async def _player_update(self, player_id, event):
|
||||
@@ -101,42 +126,52 @@ class HeosMediaPlayer(MediaPlayerDevice):
|
||||
self.hass.helpers.dispatcher.async_dispatcher_connect(
|
||||
SIGNAL_HEOS_SOURCES_UPDATED, self._sources_updated))
|
||||
|
||||
@log_command_error("clear playlist")
|
||||
async def async_clear_playlist(self):
|
||||
"""Clear players playlist."""
|
||||
await self._player.clear_queue()
|
||||
|
||||
@log_command_error("pause")
|
||||
async def async_media_pause(self):
|
||||
"""Send pause command."""
|
||||
await self._player.pause()
|
||||
|
||||
@log_command_error("play")
|
||||
async def async_media_play(self):
|
||||
"""Send play command."""
|
||||
await self._player.play()
|
||||
|
||||
@log_command_error("move to previous track")
|
||||
async def async_media_previous_track(self):
|
||||
"""Send previous track command."""
|
||||
await self._player.play_previous()
|
||||
|
||||
@log_command_error("move to next track")
|
||||
async def async_media_next_track(self):
|
||||
"""Send next track command."""
|
||||
await self._player.play_next()
|
||||
|
||||
@log_command_error("stop")
|
||||
async def async_media_stop(self):
|
||||
"""Send stop command."""
|
||||
await self._player.stop()
|
||||
|
||||
@log_command_error("set mute")
|
||||
async def async_mute_volume(self, mute):
|
||||
"""Mute the volume."""
|
||||
await self._player.set_mute(mute)
|
||||
|
||||
@log_command_error("select source")
|
||||
async def async_select_source(self, source):
|
||||
"""Select input source."""
|
||||
await self._source_manager.play_source(source, self._player)
|
||||
|
||||
@log_command_error("set shuffle")
|
||||
async def async_set_shuffle(self, shuffle):
|
||||
"""Enable/disable shuffle mode."""
|
||||
await self._player.set_play_mode(self._player.repeat, shuffle)
|
||||
|
||||
@log_command_error("set volume level")
|
||||
async def async_set_volume_level(self, volume):
|
||||
"""Set volume level, range 0..1."""
|
||||
await self._player.set_volume(int(volume * 100))
|
||||
|
||||
@@ -145,9 +145,14 @@ class HKDevice():
|
||||
|
||||
self.pairing = self.controller.pairings.get(self.hkid)
|
||||
if self.pairing is not None:
|
||||
pairing_file = os.path.join(
|
||||
pairing_dir = os.path.join(
|
||||
self.hass.config.path(),
|
||||
HOMEKIT_DIR,
|
||||
)
|
||||
if not os.path.exists(pairing_dir):
|
||||
os.makedirs(pairing_dir)
|
||||
pairing_file = os.path.join(
|
||||
pairing_dir,
|
||||
PAIRING_FILE,
|
||||
)
|
||||
self.controller.save_data(pairing_file)
|
||||
|
||||
@@ -27,12 +27,17 @@ class HueLightLevel(GenericHueGaugeSensorEntity):
|
||||
"""The light level sensor entity for a Hue motion sensor device."""
|
||||
|
||||
device_class = DEVICE_CLASS_ILLUMINANCE
|
||||
unit_of_measurement = "Lux"
|
||||
unit_of_measurement = "lx"
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self.sensor.lightlevel
|
||||
# https://developers.meethue.com/develop/hue-api/supported-devices/#clip_zll_lightlevel
|
||||
# Light level in 10000 log10 (lux) +1 measured by sensor. Logarithm
|
||||
# scale used because the human eye adjusts to light levels and small
|
||||
# changes at low lux levels are more noticeable than at high lux
|
||||
# levels.
|
||||
return 10 ** ((self.sensor.lightlevel - 1) / 10000)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
|
||||
9
homeassistant/components/input_datetime/services.yaml
Normal file
9
homeassistant/components/input_datetime/services.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
set_datetime:
|
||||
description: This can be used to dynamically set the date and/or time.
|
||||
fields:
|
||||
entity_id: {description: Entity id of the input datetime to set the new value.,
|
||||
example: input_datetime.test_date_time}
|
||||
date: {description: The target date the entity should be set to.,
|
||||
example: '"date": "2019-04-22"'}
|
||||
time: {description: The target time the entity should be set to.,
|
||||
example: '"time": "05:30:00"'}
|
||||
@@ -45,7 +45,8 @@ from .const import (
|
||||
SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY, SUPPORT_PLAY_MEDIA,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_SEEK, SUPPORT_SELECT_SOUND_MODE,
|
||||
SUPPORT_SELECT_SOURCE, SUPPORT_SHUFFLE_SET, SUPPORT_STOP, SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET)
|
||||
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_STEP)
|
||||
from .reproduce_state import async_reproduce_states # noqa
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -164,77 +165,77 @@ async def async_setup(hass, config):
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_TURN_ON, MEDIA_PLAYER_SCHEMA,
|
||||
'async_turn_on', SUPPORT_TURN_ON
|
||||
'async_turn_on', [SUPPORT_TURN_ON]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_TURN_OFF, MEDIA_PLAYER_SCHEMA,
|
||||
'async_turn_off', SUPPORT_TURN_OFF
|
||||
'async_turn_off', [SUPPORT_TURN_OFF]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_TOGGLE, MEDIA_PLAYER_SCHEMA,
|
||||
'async_toggle', SUPPORT_TURN_OFF | SUPPORT_TURN_ON
|
||||
'async_toggle', [SUPPORT_TURN_OFF | SUPPORT_TURN_ON]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_VOLUME_UP, MEDIA_PLAYER_SCHEMA,
|
||||
'async_volume_up', SUPPORT_VOLUME_SET
|
||||
'async_volume_up', [SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_VOLUME_DOWN, MEDIA_PLAYER_SCHEMA,
|
||||
'async_volume_down', SUPPORT_VOLUME_SET
|
||||
'async_volume_down', [SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_MEDIA_PLAY_PAUSE, MEDIA_PLAYER_SCHEMA,
|
||||
'async_media_play_pause', SUPPORT_PLAY | SUPPORT_PAUSE
|
||||
'async_media_play_pause', [SUPPORT_PLAY | SUPPORT_PAUSE]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_MEDIA_PLAY, MEDIA_PLAYER_SCHEMA,
|
||||
'async_media_play', SUPPORT_PLAY
|
||||
'async_media_play', [SUPPORT_PLAY]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_MEDIA_PAUSE, MEDIA_PLAYER_SCHEMA,
|
||||
'async_media_pause', SUPPORT_PAUSE
|
||||
'async_media_pause', [SUPPORT_PAUSE]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_MEDIA_STOP, MEDIA_PLAYER_SCHEMA,
|
||||
'async_media_stop', SUPPORT_STOP
|
||||
'async_media_stop', [SUPPORT_STOP]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_MEDIA_NEXT_TRACK, MEDIA_PLAYER_SCHEMA,
|
||||
'async_media_next_track', SUPPORT_NEXT_TRACK
|
||||
'async_media_next_track', [SUPPORT_NEXT_TRACK]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_MEDIA_PREVIOUS_TRACK, MEDIA_PLAYER_SCHEMA,
|
||||
'async_media_previous_track', SUPPORT_PREVIOUS_TRACK
|
||||
'async_media_previous_track', [SUPPORT_PREVIOUS_TRACK]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_CLEAR_PLAYLIST, MEDIA_PLAYER_SCHEMA,
|
||||
'async_clear_playlist', SUPPORT_CLEAR_PLAYLIST
|
||||
'async_clear_playlist', [SUPPORT_CLEAR_PLAYLIST]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_VOLUME_SET, MEDIA_PLAYER_SET_VOLUME_SCHEMA,
|
||||
lambda entity, call: entity.async_set_volume_level(
|
||||
volume=call.data[ATTR_MEDIA_VOLUME_LEVEL]),
|
||||
SUPPORT_VOLUME_SET
|
||||
[SUPPORT_VOLUME_SET]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_VOLUME_MUTE, MEDIA_PLAYER_MUTE_VOLUME_SCHEMA,
|
||||
lambda entity, call: entity.async_mute_volume(
|
||||
mute=call.data[ATTR_MEDIA_VOLUME_MUTED]),
|
||||
SUPPORT_VOLUME_MUTE
|
||||
[SUPPORT_VOLUME_MUTE]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_MEDIA_SEEK, MEDIA_PLAYER_MEDIA_SEEK_SCHEMA,
|
||||
lambda entity, call: entity.async_media_seek(
|
||||
position=call.data[ATTR_MEDIA_SEEK_POSITION]),
|
||||
SUPPORT_SEEK
|
||||
[SUPPORT_SEEK]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SELECT_SOURCE, MEDIA_PLAYER_SELECT_SOURCE_SCHEMA,
|
||||
'async_select_source', SUPPORT_SELECT_SOURCE
|
||||
'async_select_source', [SUPPORT_SELECT_SOURCE]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SELECT_SOUND_MODE, MEDIA_PLAYER_SELECT_SOUND_MODE_SCHEMA,
|
||||
'async_select_sound_mode', SUPPORT_SELECT_SOUND_MODE
|
||||
'async_select_sound_mode', [SUPPORT_SELECT_SOUND_MODE]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_PLAY_MEDIA, MEDIA_PLAYER_PLAY_MEDIA_SCHEMA,
|
||||
@@ -242,11 +243,11 @@ async def async_setup(hass, config):
|
||||
media_type=call.data[ATTR_MEDIA_CONTENT_TYPE],
|
||||
media_id=call.data[ATTR_MEDIA_CONTENT_ID],
|
||||
enqueue=call.data.get(ATTR_MEDIA_ENQUEUE)
|
||||
), SUPPORT_PLAY_MEDIA
|
||||
), [SUPPORT_PLAY_MEDIA]
|
||||
)
|
||||
component.async_register_entity_service(
|
||||
SERVICE_SHUFFLE_SET, MEDIA_PLAYER_SET_SHUFFLE_SCHEMA,
|
||||
'async_set_shuffle', SUPPORT_SHUFFLE_SET
|
||||
'async_set_shuffle', [SUPPORT_SHUFFLE_SET]
|
||||
)
|
||||
|
||||
return True
|
||||
@@ -686,7 +687,8 @@ class MediaPlayerDevice(Entity):
|
||||
await self.hass.async_add_job(self.volume_up)
|
||||
return
|
||||
|
||||
if self.volume_level < 1:
|
||||
if self.volume_level < 1 \
|
||||
and self.supported_features & SUPPORT_VOLUME_SET:
|
||||
await self.async_set_volume_level(min(1, self.volume_level + .1))
|
||||
|
||||
async def async_volume_down(self):
|
||||
@@ -699,7 +701,8 @@ class MediaPlayerDevice(Entity):
|
||||
await self.hass.async_add_job(self.volume_down)
|
||||
return
|
||||
|
||||
if self.volume_level > 0:
|
||||
if self.volume_level > 0 \
|
||||
and self.supported_features & SUPPORT_VOLUME_SET:
|
||||
await self.async_set_volume_level(
|
||||
max(0, self.volume_level - .1))
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ from homeassistant.helpers import discovery
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from .const import DOMAIN, DATA_NETATMO_AUTH
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_PERSONS = 'netatmo_persons'
|
||||
@@ -20,8 +22,6 @@ DATA_WEBHOOK_URL = 'netatmo_webhook_url'
|
||||
CONF_SECRET_KEY = 'secret_key'
|
||||
CONF_WEBHOOKS = 'webhooks'
|
||||
|
||||
DOMAIN = 'netatmo'
|
||||
|
||||
SERVICE_ADDWEBHOOK = 'addwebhook'
|
||||
SERVICE_DROPWEBHOOK = 'dropwebhook'
|
||||
|
||||
@@ -83,10 +83,9 @@ def setup(hass, config):
|
||||
"""Set up the Netatmo devices."""
|
||||
import pyatmo
|
||||
|
||||
global NETATMO_AUTH
|
||||
hass.data[DATA_PERSONS] = {}
|
||||
try:
|
||||
NETATMO_AUTH = pyatmo.ClientAuth(
|
||||
auth = pyatmo.ClientAuth(
|
||||
config[DOMAIN][CONF_API_KEY], config[DOMAIN][CONF_SECRET_KEY],
|
||||
config[DOMAIN][CONF_USERNAME], config[DOMAIN][CONF_PASSWORD],
|
||||
'read_station read_camera access_camera '
|
||||
@@ -96,6 +95,9 @@ def setup(hass, config):
|
||||
_LOGGER.error("Unable to connect to Netatmo API")
|
||||
return False
|
||||
|
||||
# Store config to be used during entry setup
|
||||
hass.data[DATA_NETATMO_AUTH] = auth
|
||||
|
||||
if config[DOMAIN][CONF_DISCOVERY]:
|
||||
for component in 'camera', 'sensor', 'binary_sensor', 'climate':
|
||||
discovery.load_platform(hass, component, DOMAIN, {}, config)
|
||||
@@ -107,7 +109,7 @@ def setup(hass, config):
|
||||
webhook_id)
|
||||
hass.components.webhook.async_register(
|
||||
DOMAIN, 'Netatmo', webhook_id, handle_webhook)
|
||||
NETATMO_AUTH.addwebhook(hass.data[DATA_WEBHOOK_URL])
|
||||
auth.addwebhook(hass.data[DATA_WEBHOOK_URL])
|
||||
hass.bus.listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP, dropwebhook)
|
||||
|
||||
@@ -117,7 +119,7 @@ def setup(hass, config):
|
||||
if url is None:
|
||||
url = hass.data[DATA_WEBHOOK_URL]
|
||||
_LOGGER.info("Adding webhook for URL: %s", url)
|
||||
NETATMO_AUTH.addwebhook(url)
|
||||
auth.addwebhook(url)
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_ADDWEBHOOK, _service_addwebhook,
|
||||
@@ -126,7 +128,7 @@ def setup(hass, config):
|
||||
def _service_dropwebhook(service):
|
||||
"""Service to drop webhooks during runtime."""
|
||||
_LOGGER.info("Dropping webhook")
|
||||
NETATMO_AUTH.dropwebhook()
|
||||
auth.dropwebhook()
|
||||
|
||||
hass.services.register(
|
||||
DOMAIN, SERVICE_DROPWEBHOOK, _service_dropwebhook,
|
||||
@@ -137,7 +139,8 @@ def setup(hass, config):
|
||||
|
||||
def dropwebhook(hass):
|
||||
"""Drop the webhook subscription."""
|
||||
NETATMO_AUTH.dropwebhook()
|
||||
auth = hass.data[DATA_NETATMO_AUTH]
|
||||
auth.dropwebhook()
|
||||
|
||||
|
||||
async def handle_webhook(hass, webhook_id, request):
|
||||
|
||||
@@ -8,7 +8,8 @@ from homeassistant.components.binary_sensor import (
|
||||
from homeassistant.const import CONF_TIMEOUT
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from . import CameraData, NETATMO_AUTH
|
||||
from .const import DATA_NETATMO_AUTH
|
||||
from . import CameraData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -59,8 +60,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
module_name = None
|
||||
|
||||
import pyatmo
|
||||
|
||||
auth = hass.data[DATA_NETATMO_AUTH]
|
||||
|
||||
try:
|
||||
data = CameraData(hass, NETATMO_AUTH, home)
|
||||
data = CameraData(hass, auth, home)
|
||||
if not data.get_camera_names():
|
||||
return None
|
||||
except pyatmo.NoDevice:
|
||||
|
||||
@@ -9,7 +9,8 @@ from homeassistant.components.camera import (
|
||||
from homeassistant.const import CONF_VERIFY_SSL
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from . import CameraData, NETATMO_AUTH
|
||||
from .const import DATA_NETATMO_AUTH
|
||||
from . import CameraData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -37,8 +38,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
verify_ssl = config.get(CONF_VERIFY_SSL, True)
|
||||
quality = config.get(CONF_QUALITY, DEFAULT_QUALITY)
|
||||
import pyatmo
|
||||
|
||||
auth = hass.data[DATA_NETATMO_AUTH]
|
||||
|
||||
try:
|
||||
data = CameraData(hass, NETATMO_AUTH, home)
|
||||
data = CameraData(hass, auth, home)
|
||||
for camera_name in data.get_camera_names():
|
||||
camera_type = data.get_camera_type(camera=camera_name, home=home)
|
||||
if CONF_CAMERAS in config:
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.const import (
|
||||
STATE_OFF, TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_NAME)
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
from . import NETATMO_AUTH
|
||||
from .const import DATA_NETATMO_AUTH
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -68,8 +68,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the NetAtmo Thermostat."""
|
||||
import pyatmo
|
||||
homes_conf = config.get(CONF_HOMES)
|
||||
|
||||
auth = hass.data[DATA_NETATMO_AUTH]
|
||||
|
||||
try:
|
||||
home_data = HomeData(NETATMO_AUTH)
|
||||
home_data = HomeData(auth)
|
||||
except pyatmo.NoDevice:
|
||||
return
|
||||
|
||||
@@ -88,7 +91,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
for home in homes:
|
||||
_LOGGER.debug("Setting up %s ...", home)
|
||||
try:
|
||||
room_data = ThermostatData(NETATMO_AUTH, home)
|
||||
room_data = ThermostatData(auth, home)
|
||||
except pyatmo.NoDevice:
|
||||
continue
|
||||
for room_id in room_data.get_room_ids():
|
||||
|
||||
5
homeassistant/components/netatmo/const.py
Normal file
5
homeassistant/components/netatmo/const.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Constants used by the Netatmo component."""
|
||||
DOMAIN = 'netatmo'
|
||||
|
||||
DATA_NETATMO = 'netatmo'
|
||||
DATA_NETATMO_AUTH = 'netatmo_auth'
|
||||
@@ -12,7 +12,7 @@ from homeassistant.const import (
|
||||
from homeassistant.helpers.entity import Entity
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from . import NETATMO_AUTH
|
||||
from .const import DATA_NETATMO_AUTH
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -68,23 +68,26 @@ MODULE_TYPE_INDOOR = 'NAModule4'
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the available Netatmo weather sensors."""
|
||||
dev = []
|
||||
auth = hass.data[DATA_NETATMO_AUTH]
|
||||
|
||||
if CONF_MODULES in config:
|
||||
manual_config(config, dev)
|
||||
manual_config(auth, config, dev)
|
||||
else:
|
||||
auto_config(config, dev)
|
||||
auto_config(auth, config, dev)
|
||||
|
||||
if dev:
|
||||
add_entities(dev, True)
|
||||
|
||||
|
||||
def manual_config(config, dev):
|
||||
def manual_config(auth, config, dev):
|
||||
"""Handle manual configuration."""
|
||||
import pyatmo
|
||||
|
||||
all_classes = all_product_classes()
|
||||
not_handled = {}
|
||||
|
||||
for data_class in all_classes:
|
||||
data = NetAtmoData(NETATMO_AUTH, data_class,
|
||||
data = NetAtmoData(auth, data_class,
|
||||
config.get(CONF_STATION))
|
||||
try:
|
||||
# Iterate each module
|
||||
@@ -107,12 +110,12 @@ def manual_config(config, dev):
|
||||
_LOGGER.error('Module name: "%s" not found', module_name)
|
||||
|
||||
|
||||
def auto_config(config, dev):
|
||||
def auto_config(auth, config, dev):
|
||||
"""Handle auto configuration."""
|
||||
import pyatmo
|
||||
|
||||
for data_class in all_product_classes():
|
||||
data = NetAtmoData(NETATMO_AUTH, data_class, config.get(CONF_STATION))
|
||||
data = NetAtmoData(auth, data_class, config.get(CONF_STATION))
|
||||
try:
|
||||
for module_name in data.get_module_names():
|
||||
for variable in \
|
||||
|
||||
@@ -88,7 +88,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry):
|
||||
await async_setup_webhook(hass, entry, session)
|
||||
client = MinutPointClient(hass, entry, session)
|
||||
hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: client})
|
||||
await client.update()
|
||||
hass.async_create_task(client.update())
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Skybell",
|
||||
"documentation": "https://www.home-assistant.io/components/skybell",
|
||||
"requirements": [
|
||||
"skybellpy==0.3.0"
|
||||
"skybellpy==0.4.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": []
|
||||
|
||||
@@ -5,11 +5,12 @@ import re
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import (
|
||||
MediaPlayerDevice, PLATFORM_SCHEMA)
|
||||
PLATFORM_SCHEMA, MediaPlayerDevice)
|
||||
from homeassistant.components.media_player.const import (
|
||||
DOMAIN, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE, SUPPORT_PLAY,
|
||||
SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF, SUPPORT_TURN_ON,
|
||||
SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_STEP)
|
||||
SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF,
|
||||
SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
|
||||
SUPPORT_VOLUME_STEP)
|
||||
from homeassistant.const import (
|
||||
CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_PAUSED, STATE_PLAYING,
|
||||
STATE_UNAVAILABLE)
|
||||
@@ -56,7 +57,7 @@ DEFAULT_PORT = 8090
|
||||
SUPPORT_SOUNDTOUCH = SUPPORT_PAUSE | SUPPORT_VOLUME_STEP | \
|
||||
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | \
|
||||
SUPPORT_NEXT_TRACK | SUPPORT_TURN_OFF | \
|
||||
SUPPORT_VOLUME_SET | SUPPORT_TURN_ON | SUPPORT_PLAY
|
||||
SUPPORT_VOLUME_SET | SUPPORT_TURN_ON | SUPPORT_PLAY | SUPPORT_PLAY_MEDIA
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Version",
|
||||
"documentation": "https://www.home-assistant.io/components/version",
|
||||
"requirements": [
|
||||
"pyhaversion==2.2.0"
|
||||
"pyhaversion==2.2.1"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
||||
@@ -47,10 +47,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the Zestimate sensor."""
|
||||
name = config.get(CONF_NAME)
|
||||
properties = config[CONF_ZPID]
|
||||
params = {'zws-id': config[CONF_API_KEY]}
|
||||
|
||||
sensors = []
|
||||
for zpid in properties:
|
||||
params = {'zws-id': config[CONF_API_KEY]}
|
||||
params['zpid'] = zpid
|
||||
sensors.append(ZestimateDataSensor(name, params))
|
||||
add_entities(sensors, True)
|
||||
@@ -113,12 +113,16 @@ class ZestimateDataSensor(Entity):
|
||||
return
|
||||
data = data_dict['response'][NAME]
|
||||
details = {}
|
||||
details[ATTR_AMOUNT] = data['amount']['#text']
|
||||
details[ATTR_CURRENCY] = data['amount']['@currency']
|
||||
details[ATTR_LAST_UPDATED] = data['last-updated']
|
||||
details[ATTR_CHANGE] = int(data['valueChange']['#text'])
|
||||
details[ATTR_VAL_HI] = int(data['valuationRange']['high']['#text'])
|
||||
details[ATTR_VAL_LOW] = int(data['valuationRange']['low']['#text'])
|
||||
if 'amount' in data and data['amount'] is not None:
|
||||
details[ATTR_AMOUNT] = data['amount']['#text']
|
||||
details[ATTR_CURRENCY] = data['amount']['@currency']
|
||||
if 'last-updated' in data and data['last-updated'] is not None:
|
||||
details[ATTR_LAST_UPDATED] = data['last-updated']
|
||||
if 'valueChange' in data and data['valueChange'] is not None:
|
||||
details[ATTR_CHANGE] = int(data['valueChange']['#text'])
|
||||
if 'valuationRange' in data and data['valuationRange'] is not None:
|
||||
details[ATTR_VAL_HI] = int(data['valuationRange']['high']['#text'])
|
||||
details[ATTR_VAL_LOW] = int(data['valuationRange']['low']['#text'])
|
||||
self.address = data_dict['response']['address']['street']
|
||||
self.data = details
|
||||
if self.data is not None:
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"requirements": [
|
||||
"bellows-homeassistant==0.7.2",
|
||||
"zha-quirks==0.0.8",
|
||||
"zigpy-deconz==0.1.3",
|
||||
"zigpy-homeassistant==0.3.1",
|
||||
"zigpy-xbee-homeassistant==0.1.3"
|
||||
"zigpy-deconz==0.1.4",
|
||||
"zigpy-homeassistant==0.3.2",
|
||||
"zigpy-xbee-homeassistant==0.2.0"
|
||||
],
|
||||
"dependencies": [],
|
||||
"codeowners": [
|
||||
|
||||
@@ -392,14 +392,18 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None:
|
||||
config_path = find_config_file(hass.config.config_dir)
|
||||
assert config_path is not None
|
||||
|
||||
with open(config_path, 'rt') as config_file:
|
||||
with open(config_path, 'rt', encoding='utf-8') as config_file:
|
||||
config_raw = config_file.read()
|
||||
|
||||
if TTS_PRE_92 in config_raw:
|
||||
_LOGGER.info("Migrating google tts to google_translate tts")
|
||||
config_raw = config_raw.replace(TTS_PRE_92, TTS_92)
|
||||
with open(config_path, 'wt') as config_file:
|
||||
config_file.write(config_raw)
|
||||
try:
|
||||
with open(config_path, 'wt', encoding='utf-8') as config_file:
|
||||
config_file.write(config_raw)
|
||||
except IOError:
|
||||
_LOGGER.exception("Migrating to google_translate tts failed")
|
||||
pass
|
||||
|
||||
with open(version_path, 'wt') as outp:
|
||||
outp.write(__version__)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""Constants used by Home Assistant components."""
|
||||
MAJOR_VERSION = 0
|
||||
MINOR_VERSION = 92
|
||||
PATCH_VERSION = '0b2'
|
||||
PATCH_VERSION = '1'
|
||||
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
|
||||
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
|
||||
REQUIRED_PYTHON_VER = (3, 5, 3)
|
||||
|
||||
@@ -327,7 +327,8 @@ async def _handle_service_platform_call(func, data, entities, context,
|
||||
|
||||
# Skip entities that don't have the required feature.
|
||||
if required_features is not None \
|
||||
and not entity.supported_features & required_features:
|
||||
and not any(entity.supported_features & feature_set
|
||||
for feature_set in required_features):
|
||||
continue
|
||||
|
||||
entity.async_set_context(context)
|
||||
|
||||
@@ -161,11 +161,13 @@ async def async_get_integration(hass: 'HomeAssistant', domain: str)\
|
||||
await int_or_evt.wait()
|
||||
int_or_evt = cache.get(domain, _UNDEF)
|
||||
|
||||
if int_or_evt is _UNDEF:
|
||||
pass
|
||||
elif int_or_evt is None:
|
||||
raise IntegrationNotFound(domain)
|
||||
else:
|
||||
# When we have waited and it's _UNDEF, it doesn't exist
|
||||
# We don't cache that it doesn't exist, or else people can't fix it
|
||||
# and then restart, because their config will never be valid.
|
||||
if int_or_evt is _UNDEF:
|
||||
raise IntegrationNotFound(domain)
|
||||
|
||||
if int_or_evt is not _UNDEF:
|
||||
return cast(Integration, int_or_evt)
|
||||
|
||||
event = cache[domain] = asyncio.Event()
|
||||
@@ -197,7 +199,12 @@ async def async_get_integration(hass: 'HomeAssistant', domain: str)\
|
||||
return integration
|
||||
|
||||
integration = Integration.resolve_legacy(hass, domain)
|
||||
cache[domain] = integration
|
||||
if integration is not None:
|
||||
cache[domain] = integration
|
||||
else:
|
||||
# Remove event from cache.
|
||||
cache.pop(domain)
|
||||
|
||||
event.set()
|
||||
|
||||
if not integration:
|
||||
|
||||
@@ -151,9 +151,12 @@ async def _async_setup_component(hass: core.HomeAssistant,
|
||||
if hasattr(component, 'async_setup'):
|
||||
result = await component.async_setup( # type: ignore
|
||||
hass, processed_config)
|
||||
else:
|
||||
elif hasattr(component, 'setup'):
|
||||
result = await hass.async_add_executor_job(
|
||||
component.setup, hass, processed_config) # type: ignore
|
||||
else:
|
||||
log_error("No setup function defined.")
|
||||
return False
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Error during setup of component %s", domain)
|
||||
async_notify_setup_error(hass, domain, True)
|
||||
@@ -176,7 +179,7 @@ async def _async_setup_component(hass: core.HomeAssistant,
|
||||
for entry in hass.config_entries.async_entries(domain):
|
||||
await entry.async_setup(hass, integration=integration)
|
||||
|
||||
hass.config.components.add(component.DOMAIN) # type: ignore
|
||||
hass.config.components.add(domain)
|
||||
|
||||
# Cleanup
|
||||
if domain in hass.data[DATA_SETUP]:
|
||||
@@ -184,7 +187,7 @@ async def _async_setup_component(hass: core.HomeAssistant,
|
||||
|
||||
hass.bus.async_fire(
|
||||
EVENT_COMPONENT_LOADED,
|
||||
{ATTR_COMPONENT: component.DOMAIN} # type: ignore
|
||||
{ATTR_COMPONENT: domain}
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@@ -112,7 +112,7 @@ aiobotocore==0.10.2
|
||||
aiodns==1.1.1
|
||||
|
||||
# homeassistant.components.esphome
|
||||
aioesphomeapi==2.0.0
|
||||
aioesphomeapi==2.0.1
|
||||
|
||||
# homeassistant.components.freebox
|
||||
aiofreepybox==0.0.8
|
||||
@@ -239,9 +239,6 @@ blockchain==1.4.4
|
||||
bomradarloop==0.1.2
|
||||
|
||||
# homeassistant.components.amazon_polly
|
||||
# homeassistant.components.aws_lambda
|
||||
# homeassistant.components.aws_sns
|
||||
# homeassistant.components.aws_sqs
|
||||
# homeassistant.components.route53
|
||||
boto3==1.9.16
|
||||
|
||||
@@ -551,7 +548,7 @@ hole==0.3.0
|
||||
holidays==0.9.10
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20190419.0
|
||||
home-assistant-frontend==20190424.0
|
||||
|
||||
# homeassistant.components.zwave
|
||||
homeassistant-pyozw==0.1.4
|
||||
@@ -1073,10 +1070,10 @@ pygtfs==0.1.5
|
||||
pygtt==1.1.2
|
||||
|
||||
# homeassistant.components.version
|
||||
pyhaversion==2.2.0
|
||||
pyhaversion==2.2.1
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==0.3.1
|
||||
pyheos==0.4.1
|
||||
|
||||
# homeassistant.components.hikvision
|
||||
pyhik==0.2.2
|
||||
@@ -1579,7 +1576,7 @@ simplisafe-python==3.4.1
|
||||
sisyphus-control==2.1
|
||||
|
||||
# homeassistant.components.skybell
|
||||
skybellpy==0.3.0
|
||||
skybellpy==0.4.0
|
||||
|
||||
# homeassistant.components.slack
|
||||
slacker==0.12.0
|
||||
@@ -1653,7 +1650,7 @@ steamodd==4.21
|
||||
stringcase==1.2.0
|
||||
|
||||
# homeassistant.components.ecovacs
|
||||
sucks==0.9.3
|
||||
sucks==0.9.4
|
||||
|
||||
# homeassistant.components.onvif
|
||||
suds-passworddigest-homeassistant==0.1.2a0.dev0
|
||||
@@ -1842,13 +1839,13 @@ zhong_hong_hvac==1.0.9
|
||||
ziggo-mediabox-xl==1.1.0
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-deconz==0.1.3
|
||||
zigpy-deconz==0.1.4
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-homeassistant==0.3.1
|
||||
zigpy-homeassistant==0.3.2
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-xbee-homeassistant==0.1.3
|
||||
zigpy-xbee-homeassistant==0.2.0
|
||||
|
||||
# homeassistant.components.zoneminder
|
||||
zm-py==0.3.3
|
||||
|
||||
@@ -136,7 +136,7 @@ hdate==0.8.7
|
||||
holidays==0.9.10
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20190419.0
|
||||
home-assistant-frontend==20190424.0
|
||||
|
||||
# homeassistant.components.homekit_controller
|
||||
homekit[IP]==0.13.0
|
||||
@@ -217,7 +217,7 @@ pydeconz==54
|
||||
pydispatcher==2.0.5
|
||||
|
||||
# homeassistant.components.heos
|
||||
pyheos==0.3.1
|
||||
pyheos==0.4.1
|
||||
|
||||
# homeassistant.components.homematic
|
||||
pyhomematic==0.1.58
|
||||
@@ -330,4 +330,4 @@ vultr==0.1.2
|
||||
wakeonlan==1.1.6
|
||||
|
||||
# homeassistant.components.zha
|
||||
zigpy-homeassistant==0.3.1
|
||||
zigpy-homeassistant==0.3.2
|
||||
|
||||
@@ -4,10 +4,9 @@ from base64 import b64decode
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.util.dt import utcnow
|
||||
from homeassistant.components.broadlink import async_setup_service
|
||||
from homeassistant.components.broadlink import async_setup_service, data_packet
|
||||
from homeassistant.components.broadlink.const import (
|
||||
DOMAIN, SERVICE_LEARN, SERVICE_SEND)
|
||||
|
||||
@@ -26,6 +25,13 @@ def dummy_broadlink():
|
||||
yield broadlink
|
||||
|
||||
|
||||
async def test_padding(hass):
|
||||
"""Verify that non padding strings are allowed."""
|
||||
assert data_packet('Jg') == b'&'
|
||||
assert data_packet('Jg=') == b'&'
|
||||
assert data_packet('Jg==') == b'&'
|
||||
|
||||
|
||||
async def test_send(hass):
|
||||
"""Test send service."""
|
||||
mock_device = MagicMock()
|
||||
@@ -100,18 +106,3 @@ async def test_learn_timeout(hass):
|
||||
assert mock_create.call_args == call(
|
||||
"No signal was received",
|
||||
title='Broadlink switch')
|
||||
|
||||
|
||||
async def test_ipv4():
|
||||
"""Test ipv4 parsing."""
|
||||
from homeassistant.components.broadlink import ipv4_address
|
||||
|
||||
schema = vol.Schema(ipv4_address)
|
||||
|
||||
for value in ('invalid', '1', '192', '192.168',
|
||||
'192.168.0', '192.168.0.A'):
|
||||
with pytest.raises(vol.MultipleInvalid):
|
||||
schema(value)
|
||||
|
||||
for value in ('192.168.0.1', '10.0.0.1'):
|
||||
schema(value)
|
||||
|
||||
@@ -13,6 +13,8 @@ from homeassistant.components.climate.const import (
|
||||
from homeassistant.components.google_assistant import (
|
||||
const, trait, helpers, smart_home as sh,
|
||||
EVENT_COMMAND_RECEIVED, EVENT_QUERY_RECEIVED, EVENT_SYNC_RECEIVED)
|
||||
from homeassistant.components.demo.binary_sensor import DemoBinarySensor
|
||||
from homeassistant.components.demo.cover import DemoCover
|
||||
from homeassistant.components.demo.light import DemoLight
|
||||
from homeassistant.components.demo.switch import DemoSwitch
|
||||
|
||||
@@ -598,6 +600,89 @@ async def test_device_class_switch(hass, device_class, google_type):
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_class,google_type", [
|
||||
('door', 'action.devices.types.DOOR'),
|
||||
('garage_door', 'action.devices.types.SENSOR'),
|
||||
('lock', 'action.devices.types.SENSOR'),
|
||||
('opening', 'action.devices.types.SENSOR'),
|
||||
('window', 'action.devices.types.SENSOR'),
|
||||
])
|
||||
async def test_device_class_binary_sensor(hass, device_class, google_type):
|
||||
"""Test that a binary entity syncs to the correct device type."""
|
||||
sensor = DemoBinarySensor(
|
||||
'Demo Sensor',
|
||||
state=False,
|
||||
device_class=device_class
|
||||
)
|
||||
sensor.hass = hass
|
||||
sensor.entity_id = 'binary_sensor.demo_sensor'
|
||||
await sensor.async_update_ha_state()
|
||||
|
||||
result = await sh.async_handle_message(
|
||||
hass, BASIC_CONFIG, 'test-agent',
|
||||
{
|
||||
"requestId": REQ_ID,
|
||||
"inputs": [{
|
||||
"intent": "action.devices.SYNC"
|
||||
}]
|
||||
})
|
||||
|
||||
assert result == {
|
||||
'requestId': REQ_ID,
|
||||
'payload': {
|
||||
'agentUserId': 'test-agent',
|
||||
'devices': [{
|
||||
'attributes': {'queryOnlyOpenClose': True},
|
||||
'id': 'binary_sensor.demo_sensor',
|
||||
'name': {'name': 'Demo Sensor'},
|
||||
'traits': ['action.devices.traits.OpenClose'],
|
||||
'type': google_type,
|
||||
'willReportState': False
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("device_class,google_type", [
|
||||
('non_existing_class', 'action.devices.types.BLINDS'),
|
||||
('door', 'action.devices.types.DOOR'),
|
||||
])
|
||||
async def test_device_class_cover(hass, device_class, google_type):
|
||||
"""Test that a binary entity syncs to the correct device type."""
|
||||
sensor = DemoCover(
|
||||
hass,
|
||||
'Demo Sensor',
|
||||
device_class=device_class
|
||||
)
|
||||
sensor.hass = hass
|
||||
sensor.entity_id = 'cover.demo_sensor'
|
||||
await sensor.async_update_ha_state()
|
||||
|
||||
result = await sh.async_handle_message(
|
||||
hass, BASIC_CONFIG, 'test-agent',
|
||||
{
|
||||
"requestId": REQ_ID,
|
||||
"inputs": [{
|
||||
"intent": "action.devices.SYNC"
|
||||
}]
|
||||
})
|
||||
|
||||
assert result == {
|
||||
'requestId': REQ_ID,
|
||||
'payload': {
|
||||
'agentUserId': 'test-agent',
|
||||
'devices': [{
|
||||
'attributes': {},
|
||||
'id': 'cover.demo_sensor',
|
||||
'name': {'name': 'Demo Sensor'},
|
||||
'traits': ['action.devices.traits.OpenClose'],
|
||||
'type': google_type,
|
||||
'willReportState': False
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async def test_query_disconnect(hass):
|
||||
"""Test a disconnect message."""
|
||||
result = await sh.async_handle_message(
|
||||
|
||||
@@ -44,7 +44,7 @@ def config_fixture():
|
||||
@pytest.fixture(name="players")
|
||||
def player_fixture(dispatcher):
|
||||
"""Create a mock HeosPlayer."""
|
||||
player = Mock(HeosPlayer, autospec=True)
|
||||
player = Mock(HeosPlayer)
|
||||
player.heos.dispatcher = dispatcher
|
||||
player.player_id = 1
|
||||
player.name = "Test Player"
|
||||
@@ -77,11 +77,11 @@ def player_fixture(dispatcher):
|
||||
@pytest.fixture(name="favorites")
|
||||
def favorites_fixture() -> Dict[int, HeosSource]:
|
||||
"""Create favorites fixture."""
|
||||
station = Mock(HeosSource, autospec=True)
|
||||
station = Mock(HeosSource)
|
||||
station.type = const.TYPE_STATION
|
||||
station.name = "Today's Hits Radio"
|
||||
station.media_id = '123456789'
|
||||
radio = Mock(HeosSource, autospec=True)
|
||||
radio = Mock(HeosSource)
|
||||
radio.type = const.TYPE_STATION
|
||||
radio.name = "Classical MPR (Classical Music)"
|
||||
radio.media_id = 's1234'
|
||||
@@ -94,7 +94,7 @@ def favorites_fixture() -> Dict[int, HeosSource]:
|
||||
@pytest.fixture(name="input_sources")
|
||||
def input_sources_fixture() -> Sequence[InputSource]:
|
||||
"""Create a set of input sources for testing."""
|
||||
source = Mock(InputSource, autospec=True)
|
||||
source = Mock(InputSource)
|
||||
source.player_id = 1
|
||||
source.input_name = const.INPUT_AUX_IN_1
|
||||
source.name = "HEOS Drive - Line In 1"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Tests for the Heos Media Player platform."""
|
||||
import asyncio
|
||||
|
||||
from pyheos import const
|
||||
from pyheos import const, CommandError
|
||||
|
||||
from homeassistant.components.heos import media_player
|
||||
from homeassistant.components.heos.const import (
|
||||
@@ -108,13 +108,40 @@ async def test_updates_start_from_signals(
|
||||
state = hass.states.get('media_player.test_player')
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Test heos events update
|
||||
|
||||
async def test_updates_from_connection_event(
|
||||
hass, config_entry, config, controller, input_sources, caplog):
|
||||
"""Tests player updates from connection event after connection failure."""
|
||||
# Connected
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
player.available = True
|
||||
player.heos.dispatcher.send(
|
||||
const.SIGNAL_HEOS_EVENT, const.EVENT_CONNECTED)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('media_player.test_player')
|
||||
assert state.state == STATE_PLAYING
|
||||
assert state.state == STATE_IDLE
|
||||
assert player.refresh.call_count == 1
|
||||
|
||||
# Connected handles refresh failure
|
||||
player.reset_mock()
|
||||
player.refresh.side_effect = CommandError(None, "Failure", 1)
|
||||
player.heos.dispatcher.send(
|
||||
const.SIGNAL_HEOS_EVENT, const.EVENT_CONNECTED)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('media_player.test_player')
|
||||
assert player.refresh.call_count == 1
|
||||
assert "Unable to refresh player" in caplog.text
|
||||
|
||||
# Disconnected
|
||||
player.reset_mock()
|
||||
player.available = False
|
||||
player.heos.dispatcher.send(
|
||||
const.SIGNAL_HEOS_EVENT, const.EVENT_DISCONNECTED)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get('media_player.test_player')
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
assert player.refresh.call_count == 0
|
||||
|
||||
|
||||
async def test_updates_from_sources_updated(
|
||||
@@ -162,67 +189,142 @@ async def test_updates_from_user_changed(
|
||||
assert state.attributes[ATTR_INPUT_SOURCE_LIST] == source_list
|
||||
|
||||
|
||||
async def test_services(hass, config_entry, config, controller):
|
||||
"""Tests player commands."""
|
||||
async def test_clear_playlist(hass, config_entry, config, controller, caplog):
|
||||
"""Test the clear playlist service."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# First pass completes successfully, second pass raises command error
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_CLEAR_PLAYLIST,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.clear_queue.call_count == 1
|
||||
player.clear_queue.reset_mock()
|
||||
player.clear_queue.side_effect = CommandError(None, "Failure", 1)
|
||||
assert "Unable to clear playlist: Failure (1)" in caplog.text
|
||||
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_CLEAR_PLAYLIST,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.clear_queue.call_count == 1
|
||||
|
||||
player.reset_mock()
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_PAUSE,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.pause.call_count == 1
|
||||
async def test_pause(hass, config_entry, config, controller, caplog):
|
||||
"""Test the pause service."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# First pass completes successfully, second pass raises command error
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_PAUSE,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.pause.call_count == 1
|
||||
player.pause.reset_mock()
|
||||
player.pause.side_effect = CommandError(None, "Failure", 1)
|
||||
assert "Unable to pause: Failure (1)" in caplog.text
|
||||
|
||||
player.reset_mock()
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_PLAY,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.play.call_count == 1
|
||||
|
||||
player.reset_mock()
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.play_previous.call_count == 1
|
||||
async def test_play(hass, config_entry, config, controller, caplog):
|
||||
"""Test the play service."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# First pass completes successfully, second pass raises command error
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_PLAY,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.play.call_count == 1
|
||||
player.play.reset_mock()
|
||||
player.play.side_effect = CommandError(None, "Failure", 1)
|
||||
assert "Unable to play: Failure (1)" in caplog.text
|
||||
|
||||
player.reset_mock()
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_NEXT_TRACK,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.play_next.call_count == 1
|
||||
|
||||
player.reset_mock()
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_STOP,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.stop.call_count == 1
|
||||
async def test_previous_track(hass, config_entry, config, controller, caplog):
|
||||
"""Test the previous track service."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# First pass completes successfully, second pass raises command error
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.play_previous.call_count == 1
|
||||
player.play_previous.reset_mock()
|
||||
player.play_previous.side_effect = CommandError(None, "Failure", 1)
|
||||
assert "Unable to move to previous track: Failure (1)" in caplog.text
|
||||
|
||||
player.reset_mock()
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_VOLUME_MUTE,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player',
|
||||
ATTR_MEDIA_VOLUME_MUTED: True}, blocking=True)
|
||||
player.set_mute.assert_called_once_with(True)
|
||||
|
||||
player.reset_mock()
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_SHUFFLE_SET,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player',
|
||||
ATTR_MEDIA_SHUFFLE: True}, blocking=True)
|
||||
player.set_play_mode.assert_called_once_with(player.repeat, True)
|
||||
async def test_next_track(hass, config_entry, config, controller, caplog):
|
||||
"""Test the next track service."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# First pass completes successfully, second pass raises command error
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_NEXT_TRACK,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.play_next.call_count == 1
|
||||
player.play_next.reset_mock()
|
||||
player.play_next.side_effect = CommandError(None, "Failure", 1)
|
||||
assert "Unable to move to next track: Failure (1)" in caplog.text
|
||||
|
||||
player.reset_mock()
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_VOLUME_SET,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player',
|
||||
ATTR_MEDIA_VOLUME_LEVEL: 1}, blocking=True)
|
||||
player.set_volume.assert_called_once_with(100)
|
||||
assert isinstance(player.set_volume.call_args[0][0], int)
|
||||
|
||||
async def test_stop(hass, config_entry, config, controller, caplog):
|
||||
"""Test the stop service."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# First pass completes successfully, second pass raises command error
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_MEDIA_STOP,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player'}, blocking=True)
|
||||
assert player.stop.call_count == 1
|
||||
player.stop.reset_mock()
|
||||
player.stop.side_effect = CommandError(None, "Failure", 1)
|
||||
assert "Unable to stop: Failure (1)" in caplog.text
|
||||
|
||||
|
||||
async def test_volume_mute(hass, config_entry, config, controller, caplog):
|
||||
"""Test the volume mute service."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# First pass completes successfully, second pass raises command error
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_VOLUME_MUTE,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player',
|
||||
ATTR_MEDIA_VOLUME_MUTED: True}, blocking=True)
|
||||
assert player.set_mute.call_count == 1
|
||||
player.set_mute.reset_mock()
|
||||
player.set_mute.side_effect = CommandError(None, "Failure", 1)
|
||||
assert "Unable to set mute: Failure (1)" in caplog.text
|
||||
|
||||
|
||||
async def test_shuffle_set(hass, config_entry, config, controller, caplog):
|
||||
"""Test the shuffle set service."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# First pass completes successfully, second pass raises command error
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_SHUFFLE_SET,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player',
|
||||
ATTR_MEDIA_SHUFFLE: True}, blocking=True)
|
||||
player.set_play_mode.assert_called_once_with(player.repeat, True)
|
||||
player.set_play_mode.reset_mock()
|
||||
player.set_play_mode.side_effect = CommandError(None, "Failure", 1)
|
||||
assert "Unable to set shuffle: Failure (1)" in caplog.text
|
||||
|
||||
|
||||
async def test_volume_set(hass, config_entry, config, controller, caplog):
|
||||
"""Test the volume set service."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# First pass completes successfully, second pass raises command error
|
||||
for _ in range(2):
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_VOLUME_SET,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player',
|
||||
ATTR_MEDIA_VOLUME_LEVEL: 1}, blocking=True)
|
||||
player.set_volume.assert_called_once_with(100)
|
||||
player.set_volume.reset_mock()
|
||||
player.set_volume.side_effect = CommandError(None, "Failure", 1)
|
||||
assert "Unable to set volume level: Failure (1)" in caplog.text
|
||||
|
||||
|
||||
async def test_select_favorite(
|
||||
@@ -270,6 +372,22 @@ async def test_select_radio_favorite(
|
||||
assert state.attributes[ATTR_INPUT_SOURCE] == favorite.name
|
||||
|
||||
|
||||
async def test_select_radio_favorite_command_error(
|
||||
hass, config_entry, config, controller, favorites, caplog):
|
||||
"""Tests command error loged when playing favorite."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
# Test set radio preset
|
||||
favorite = favorites[2]
|
||||
player.play_favorite.side_effect = CommandError(None, "Failure", 1)
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_SELECT_SOURCE,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player',
|
||||
ATTR_INPUT_SOURCE: favorite.name}, blocking=True)
|
||||
player.play_favorite.assert_called_once_with(2)
|
||||
assert "Unable to select source: Failure (1)" in caplog.text
|
||||
|
||||
|
||||
async def test_select_input_source(
|
||||
hass, config_entry, config, controller, input_sources):
|
||||
"""Tests selecting input source and state."""
|
||||
@@ -304,6 +422,21 @@ async def test_select_input_unknown(
|
||||
assert "Unknown source: Unknown" in caplog.text
|
||||
|
||||
|
||||
async def test_select_input_command_error(
|
||||
hass, config_entry, config, controller, caplog, input_sources):
|
||||
"""Tests selecting an unknown input."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
player = controller.players[1]
|
||||
input_source = input_sources[0]
|
||||
player.play_input_source.side_effect = CommandError(None, "Failure", 1)
|
||||
await hass.services.async_call(
|
||||
MEDIA_PLAYER_DOMAIN, SERVICE_SELECT_SOURCE,
|
||||
{ATTR_ENTITY_ID: 'media_player.test_player',
|
||||
ATTR_INPUT_SOURCE: input_source.name}, blocking=True)
|
||||
player.play_input_source.assert_called_once_with(input_source)
|
||||
assert "Unable to select source: Failure (1)" in caplog.text
|
||||
|
||||
|
||||
async def test_unload_config_entry(hass, config_entry, config, controller):
|
||||
"""Test the player is removed when the config entry is unloaded."""
|
||||
await setup_platform(hass, config_entry, config)
|
||||
|
||||
@@ -48,7 +48,7 @@ PRESENCE_SENSOR_1_PRESENT = {
|
||||
}
|
||||
LIGHT_LEVEL_SENSOR_1 = {
|
||||
"state": {
|
||||
"lightlevel": 0,
|
||||
"lightlevel": 1,
|
||||
"dark": True,
|
||||
"daylight": True,
|
||||
"lastupdated": "2019-01-01T01:00:00"
|
||||
@@ -141,7 +141,7 @@ PRESENCE_SENSOR_2_NOT_PRESENT = {
|
||||
}
|
||||
LIGHT_LEVEL_SENSOR_2 = {
|
||||
"state": {
|
||||
"lightlevel": 100,
|
||||
"lightlevel": 10001,
|
||||
"dark": True,
|
||||
"daylight": True,
|
||||
"lastupdated": "2019-01-01T01:00:00"
|
||||
@@ -234,7 +234,7 @@ PRESENCE_SENSOR_3_PRESENT = {
|
||||
}
|
||||
LIGHT_LEVEL_SENSOR_3 = {
|
||||
"state": {
|
||||
"lightlevel": 0,
|
||||
"lightlevel": 1,
|
||||
"dark": True,
|
||||
"daylight": True,
|
||||
"lastupdated": "2019-01-01T01:00:00"
|
||||
@@ -399,7 +399,7 @@ async def test_sensors(hass, mock_bridge):
|
||||
assert presence_sensor_1 is not None
|
||||
assert presence_sensor_1.state == 'on'
|
||||
assert light_level_sensor_1 is not None
|
||||
assert light_level_sensor_1.state == '0'
|
||||
assert light_level_sensor_1.state == '1.0'
|
||||
assert light_level_sensor_1.name == 'Living room sensor light level'
|
||||
assert temperature_sensor_1 is not None
|
||||
assert temperature_sensor_1.state == '17.75'
|
||||
@@ -414,7 +414,7 @@ async def test_sensors(hass, mock_bridge):
|
||||
assert presence_sensor_2 is not None
|
||||
assert presence_sensor_2.state == 'off'
|
||||
assert light_level_sensor_2 is not None
|
||||
assert light_level_sensor_2.state == '100'
|
||||
assert light_level_sensor_2.state == '10.0'
|
||||
assert light_level_sensor_2.name == 'Kitchen sensor light level'
|
||||
assert temperature_sensor_2 is not None
|
||||
assert temperature_sensor_2.state == '18.75'
|
||||
|
||||
@@ -29,6 +29,13 @@ class AsyncMediaPlayer(mp.MediaPlayerDevice):
|
||||
"""Volume level of the media player (0..1)."""
|
||||
return self._volume
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag media player features that are supported."""
|
||||
return mp.const.SUPPORT_VOLUME_SET | mp.const.SUPPORT_PLAY \
|
||||
| mp.const.SUPPORT_PAUSE | mp.const.SUPPORT_TURN_OFF \
|
||||
| mp.const.SUPPORT_TURN_ON
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_set_volume_level(self, volume):
|
||||
"""Set volume level, range 0..1."""
|
||||
@@ -74,6 +81,13 @@ class SyncMediaPlayer(mp.MediaPlayerDevice):
|
||||
"""Volume level of the media player (0..1)."""
|
||||
return self._volume
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag media player features that are supported."""
|
||||
return mp.const.SUPPORT_VOLUME_SET | mp.const.SUPPORT_VOLUME_STEP \
|
||||
| mp.const.SUPPORT_PLAY | mp.const.SUPPORT_PAUSE \
|
||||
| mp.const.SUPPORT_TURN_OFF | mp.const.SUPPORT_TURN_ON
|
||||
|
||||
def set_volume_level(self, volume):
|
||||
"""Set volume level, range 0..1."""
|
||||
self._volume = volume
|
||||
|
||||
@@ -372,7 +372,7 @@ class TestSoundtouchMediaPlayer(unittest.TestCase):
|
||||
mock.MagicMock())
|
||||
assert mocked_soundtouch_device.call_count == 1
|
||||
all_devices = self.hass.data[soundtouch.DATA_SOUNDTOUCH]
|
||||
assert all_devices[0].supported_features == 17853
|
||||
assert all_devices[0].supported_features == 18365
|
||||
|
||||
@mock.patch('libsoundtouch.device.SoundTouchDevice.power_off')
|
||||
@mock.patch('libsoundtouch.device.SoundTouchDevice.volume')
|
||||
|
||||
@@ -280,7 +280,7 @@ async def test_call_with_required_features(hass, mock_entities):
|
||||
Mock(entities=mock_entities)
|
||||
], test_service_mock, ha.ServiceCall('test_domain', 'test_service', {
|
||||
'entity_id': 'all'
|
||||
}), required_features=1)
|
||||
}), required_features=[1])
|
||||
assert len(mock_entities) == 2
|
||||
# Called once because only one of the entities had the required features
|
||||
assert test_service_mock.call_count == 1
|
||||
|
||||
@@ -318,7 +318,8 @@ class TestConfig(unittest.TestCase):
|
||||
ha_version = '0.92.0'
|
||||
|
||||
mock_open = mock.mock_open()
|
||||
with mock.patch('homeassistant.config.open', mock_open, create=True):
|
||||
with mock.patch('homeassistant.config.open', mock_open, create=True), \
|
||||
mock.patch.object(config_util, '__version__', '0.91.0'):
|
||||
opened_file = mock_open.return_value
|
||||
# pylint: disable=no-member
|
||||
opened_file.readline.return_value = ha_version
|
||||
@@ -326,7 +327,7 @@ class TestConfig(unittest.TestCase):
|
||||
config_util.process_ha_config_upgrade(self.hass)
|
||||
|
||||
assert opened_file.write.call_count == 1
|
||||
assert opened_file.write.call_args == mock.call(__version__)
|
||||
assert opened_file.write.call_args == mock.call('0.91.0')
|
||||
|
||||
def test_config_upgrade_same_version(self):
|
||||
"""Test no update of version on no upgrade."""
|
||||
|
||||
Reference in New Issue
Block a user