Merge pull request #23448 from home-assistant/rc

0.92.1
This commit is contained in:
Pascal Vizeli 2019-04-26 22:41:20 +02:00 committed by GitHub
commit ee107755f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 172 additions and 104 deletions

View File

@ -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

View File

@ -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,
})

View File

@ -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

View File

@ -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": [

View File

@ -3,8 +3,7 @@
"name": "Flux",
"documentation": "https://www.home-assistant.io/components/flux",
"requirements": [],
"dependencies": [
"light"
],
"dependencies": [],
"after_dependencies": ["light"],
"codeowners": []
}

View File

@ -3,7 +3,7 @@
"name": "Heos",
"documentation": "https://www.home-assistant.io/components/heos",
"requirements": [
"pyheos==0.4.0"
"pyheos==0.4.1"
],
"dependencies": [],
"codeowners": [

View File

@ -1,4 +1,5 @@
"""Denon HEOS Media Player."""
import asyncio
from functools import reduce, wraps
import logging
from operator import ior
@ -47,7 +48,7 @@ def log_command_error(command: str):
from pyheos import CommandError
try:
await func(*args, **kwargs)
except CommandError as ex:
except (CommandError, asyncio.TimeoutError, ConnectionError) as ex:
_LOGGER.error("Unable to %s: %s", command, ex)
return wrapper
return decorator
@ -85,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):

View File

@ -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))

View File

@ -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):

View File

@ -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:

View File

@ -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:

View File

@ -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():

View File

@ -0,0 +1,5 @@
"""Constants used by the Netatmo component."""
DOMAIN = 'netatmo'
DATA_NETATMO = 'netatmo'
DATA_NETATMO_AUTH = 'netatmo_auth'

View File

@ -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 \

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -398,8 +398,12 @@ def process_ha_config_upgrade(hass: HomeAssistant) -> None:
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', encoding='utf-8') 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__)

View File

@ -2,7 +2,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 92
PATCH_VERSION = '0'
PATCH_VERSION = '1'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5, 3)

View File

@ -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)

View File

@ -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

View File

@ -1073,7 +1073,7 @@ pygtt==1.1.2
pyhaversion==2.2.1
# homeassistant.components.heos
pyheos==0.4.0
pyheos==0.4.1
# homeassistant.components.hikvision
pyhik==0.2.2
@ -1650,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

View File

@ -217,7 +217,7 @@ pydeconz==54
pydispatcher==2.0.5
# homeassistant.components.heos
pyheos==0.4.0
pyheos==0.4.1
# homeassistant.components.homematic
pyhomematic==0.1.58

View File

@ -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)

View File

@ -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(

View File

@ -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

View File

@ -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')

View File

@ -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