Add support for outlets in HomeKit (#14628)
This commit is contained in:
parent
0a724a5473
commit
f5d74e07d5
7 changed files with 123 additions and 22 deletions
|
@ -12,7 +12,7 @@ import voluptuous as vol
|
||||||
import homeassistant.components.cover as cover
|
import homeassistant.components.cover as cover
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
|
ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES, ATTR_UNIT_OF_MEASUREMENT,
|
||||||
CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, DEVICE_CLASS_HUMIDITY,
|
CONF_IP_ADDRESS, CONF_NAME, CONF_PORT, CONF_TYPE, DEVICE_CLASS_HUMIDITY,
|
||||||
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE,
|
DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE,
|
||||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
|
||||||
TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
|
@ -23,7 +23,7 @@ from homeassistant.util.decorator import Registry
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, CONF_FILTER,
|
CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, CONF_FILTER,
|
||||||
DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO2, DEVICE_CLASS_PM25,
|
DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO2, DEVICE_CLASS_PM25,
|
||||||
DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START)
|
DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START, TYPE_OUTLET, TYPE_SWITCH)
|
||||||
from .util import (
|
from .util import (
|
||||||
show_setup_message, validate_entity_config, validate_media_player_features)
|
show_setup_message, validate_entity_config, validate_media_player_features)
|
||||||
|
|
||||||
|
@ -38,6 +38,8 @@ STATUS_RUNNING = 1
|
||||||
STATUS_STOPPED = 2
|
STATUS_STOPPED = 2
|
||||||
STATUS_WAIT = 3
|
STATUS_WAIT = 3
|
||||||
|
|
||||||
|
SWITCH_TYPES = {TYPE_OUTLET: 'Outlet',
|
||||||
|
TYPE_SWITCH: 'Switch'}
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema({
|
CONFIG_SCHEMA = vol.Schema({
|
||||||
DOMAIN: vol.All({
|
DOMAIN: vol.All({
|
||||||
|
@ -149,8 +151,11 @@ def get_accessory(hass, driver, state, aid, config):
|
||||||
elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ('lm', 'lx'):
|
elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ('lm', 'lx'):
|
||||||
a_type = 'LightSensor'
|
a_type = 'LightSensor'
|
||||||
|
|
||||||
elif state.domain in ('automation', 'input_boolean', 'remote', 'script',
|
elif state.domain == 'switch':
|
||||||
'switch'):
|
switch_type = config.get(CONF_TYPE, TYPE_SWITCH)
|
||||||
|
a_type = SWITCH_TYPES[switch_type]
|
||||||
|
|
||||||
|
elif state.domain in ('automation', 'input_boolean', 'remote', 'script'):
|
||||||
a_type = 'Switch'
|
a_type = 'Switch'
|
||||||
|
|
||||||
if a_type is None:
|
if a_type is None:
|
||||||
|
|
|
@ -31,6 +31,10 @@ BRIDGE_NAME = 'Home Assistant Bridge'
|
||||||
BRIDGE_SERIAL_NUMBER = 'homekit.bridge'
|
BRIDGE_SERIAL_NUMBER = 'homekit.bridge'
|
||||||
MANUFACTURER = 'Home Assistant'
|
MANUFACTURER = 'Home Assistant'
|
||||||
|
|
||||||
|
# #### Switch Types ####
|
||||||
|
TYPE_OUTLET = 'outlet'
|
||||||
|
TYPE_SWITCH = 'switch'
|
||||||
|
|
||||||
# #### Services ####
|
# #### Services ####
|
||||||
SERV_ACCESSORY_INFO = 'AccessoryInformation'
|
SERV_ACCESSORY_INFO = 'AccessoryInformation'
|
||||||
SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor'
|
SERV_AIR_QUALITY_SENSOR = 'AirQualitySensor'
|
||||||
|
@ -46,6 +50,7 @@ SERV_LIGHTBULB = 'Lightbulb'
|
||||||
SERV_LOCK = 'LockMechanism'
|
SERV_LOCK = 'LockMechanism'
|
||||||
SERV_MOTION_SENSOR = 'MotionSensor'
|
SERV_MOTION_SENSOR = 'MotionSensor'
|
||||||
SERV_OCCUPANCY_SENSOR = 'OccupancySensor'
|
SERV_OCCUPANCY_SENSOR = 'OccupancySensor'
|
||||||
|
SERV_OUTLET = 'Outlet'
|
||||||
SERV_SECURITY_SYSTEM = 'SecuritySystem'
|
SERV_SECURITY_SYSTEM = 'SecuritySystem'
|
||||||
SERV_SMOKE_SENSOR = 'SmokeSensor'
|
SERV_SMOKE_SENSOR = 'SmokeSensor'
|
||||||
SERV_SWITCH = 'Switch'
|
SERV_SWITCH = 'Switch'
|
||||||
|
@ -84,6 +89,7 @@ CHAR_MODEL = 'Model'
|
||||||
CHAR_MOTION_DETECTED = 'MotionDetected'
|
CHAR_MOTION_DETECTED = 'MotionDetected'
|
||||||
CHAR_NAME = 'Name'
|
CHAR_NAME = 'Name'
|
||||||
CHAR_OCCUPANCY_DETECTED = 'OccupancyDetected'
|
CHAR_OCCUPANCY_DETECTED = 'OccupancyDetected'
|
||||||
|
CHAR_OUTLET_IN_USE = 'OutletInUse'
|
||||||
CHAR_ON = 'On'
|
CHAR_ON = 'On'
|
||||||
CHAR_POSITION_STATE = 'PositionState'
|
CHAR_POSITION_STATE = 'PositionState'
|
||||||
CHAR_ROTATION_DIRECTION = 'RotationDirection'
|
CHAR_ROTATION_DIRECTION = 'RotationDirection'
|
||||||
|
|
|
@ -1,19 +1,54 @@
|
||||||
"""Class to hold all switch accessories."""
|
"""Class to hold all switch accessories."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyhap.const import CATEGORY_SWITCH
|
from pyhap.const import CATEGORY_OUTLET, CATEGORY_SWITCH
|
||||||
|
|
||||||
|
from homeassistant.components.switch import DOMAIN as SWITCH
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
|
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
|
|
||||||
from . import TYPES
|
from . import TYPES
|
||||||
from .accessories import HomeAccessory
|
from .accessories import HomeAccessory
|
||||||
from .const import SERV_SWITCH, CHAR_ON
|
from .const import CHAR_ON, CHAR_OUTLET_IN_USE, SERV_OUTLET, SERV_SWITCH
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@TYPES.register('Outlet')
|
||||||
|
class Outlet(HomeAccessory):
|
||||||
|
"""Generate an Outlet accessory."""
|
||||||
|
|
||||||
|
def __init__(self, *args):
|
||||||
|
"""Initialize an Outlet accessory object."""
|
||||||
|
super().__init__(*args, category=CATEGORY_OUTLET)
|
||||||
|
self.flag_target_state = False
|
||||||
|
|
||||||
|
serv_outlet = self.add_preload_service(SERV_OUTLET)
|
||||||
|
self.char_on = serv_outlet.configure_char(
|
||||||
|
CHAR_ON, value=False, setter_callback=self.set_state)
|
||||||
|
self.char_outlet_in_use = serv_outlet.configure_char(
|
||||||
|
CHAR_OUTLET_IN_USE, value=True)
|
||||||
|
|
||||||
|
def set_state(self, value):
|
||||||
|
"""Move switch state to value if call came from HomeKit."""
|
||||||
|
_LOGGER.debug('%s: Set switch state to %s',
|
||||||
|
self.entity_id, value)
|
||||||
|
self.flag_target_state = True
|
||||||
|
params = {ATTR_ENTITY_ID: self.entity_id}
|
||||||
|
service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
|
||||||
|
self.hass.services.call(SWITCH, service, params)
|
||||||
|
|
||||||
|
def update_state(self, new_state):
|
||||||
|
"""Update switch state after state changed."""
|
||||||
|
current_state = (new_state.state == STATE_ON)
|
||||||
|
if not self.flag_target_state:
|
||||||
|
_LOGGER.debug('%s: Set current state to %s',
|
||||||
|
self.entity_id, current_state)
|
||||||
|
self.char_on.set_value(current_state)
|
||||||
|
self.flag_target_state = False
|
||||||
|
|
||||||
|
|
||||||
@TYPES.register('Switch')
|
@TYPES.register('Switch')
|
||||||
class Switch(HomeAccessory):
|
class Switch(HomeAccessory):
|
||||||
"""Generate a Switch accessory."""
|
"""Generate a Switch accessory."""
|
||||||
|
|
|
@ -6,12 +6,13 @@ import voluptuous as vol
|
||||||
import homeassistant.components.media_player as media_player
|
import homeassistant.components.media_player as media_player
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, TEMP_CELSIUS)
|
ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_TYPE, TEMP_CELSIUS)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
import homeassistant.util.temperature as temp_util
|
import homeassistant.util.temperature as temp_util
|
||||||
from .const import (
|
from .const import (
|
||||||
CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF,
|
CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF,
|
||||||
FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE)
|
FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, TYPE_OUTLET,
|
||||||
|
TYPE_SWITCH)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -35,6 +36,11 @@ MEDIA_PLAYER_SCHEMA = vol.Schema({
|
||||||
FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE))),
|
FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE))),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
SWITCH_TYPE_SCHEMA = BASIC_INFO_SCHEMA.extend({
|
||||||
|
vol.Optional(CONF_TYPE, default=TYPE_SWITCH): vol.All(
|
||||||
|
cv.string, vol.In((TYPE_OUTLET, TYPE_SWITCH))),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
def validate_entity_config(values):
|
def validate_entity_config(values):
|
||||||
"""Validate config entry for CONF_ENTITY."""
|
"""Validate config entry for CONF_ENTITY."""
|
||||||
|
@ -62,6 +68,9 @@ def validate_entity_config(values):
|
||||||
feature_list[key] = params
|
feature_list[key] = params
|
||||||
config[CONF_FEATURE_LIST] = feature_list
|
config[CONF_FEATURE_LIST] = feature_list
|
||||||
|
|
||||||
|
elif domain == 'switch':
|
||||||
|
config = SWITCH_TYPE_SCHEMA(config)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
config = BASIC_INFO_SCHEMA(config)
|
config = BASIC_INFO_SCHEMA(config)
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,11 @@ import homeassistant.components.climate as climate
|
||||||
import homeassistant.components.media_player as media_player
|
import homeassistant.components.media_player as media_player
|
||||||
from homeassistant.components.homekit import get_accessory, TYPES
|
from homeassistant.components.homekit import get_accessory, TYPES
|
||||||
from homeassistant.components.homekit.const import (
|
from homeassistant.components.homekit.const import (
|
||||||
CONF_FEATURE_LIST, FEATURE_ON_OFF)
|
CONF_FEATURE_LIST, FEATURE_ON_OFF, TYPE_OUTLET, TYPE_SWITCH)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CODE, ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES,
|
ATTR_CODE, ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES,
|
||||||
ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_TYPE, TEMP_CELSIUS,
|
||||||
|
TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
|
|
||||||
def test_not_supported(caplog):
|
def test_not_supported(caplog):
|
||||||
|
@ -129,17 +130,19 @@ def test_type_sensors(type_name, entity_id, state, attrs):
|
||||||
assert mock_type.called
|
assert mock_type.called
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('type_name, entity_id, state, attrs', [
|
@pytest.mark.parametrize('type_name, entity_id, state, attrs, config', [
|
||||||
('Switch', 'automation.test', 'on', {}),
|
('Outlet', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_OUTLET}),
|
||||||
('Switch', 'input_boolean.test', 'on', {}),
|
('Switch', 'automation.test', 'on', {}, {}),
|
||||||
('Switch', 'remote.test', 'on', {}),
|
('Switch', 'input_boolean.test', 'on', {}, {}),
|
||||||
('Switch', 'script.test', 'on', {}),
|
('Switch', 'remote.test', 'on', {}, {}),
|
||||||
('Switch', 'switch.test', 'on', {}),
|
('Switch', 'script.test', 'on', {}, {}),
|
||||||
|
('Switch', 'switch.test', 'on', {}, {}),
|
||||||
|
('Switch', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_SWITCH}),
|
||||||
])
|
])
|
||||||
def test_type_switches(type_name, entity_id, state, attrs):
|
def test_type_switches(type_name, entity_id, state, attrs, config):
|
||||||
"""Test if switch types are associated correctly."""
|
"""Test if switch types are associated correctly."""
|
||||||
mock_type = Mock()
|
mock_type = Mock()
|
||||||
with patch.dict(TYPES, {type_name: mock_type}):
|
with patch.dict(TYPES, {type_name: mock_type}):
|
||||||
entity_state = State(entity_id, state, attrs)
|
entity_state = State(entity_id, state, attrs)
|
||||||
get_accessory(None, None, entity_state, 2, {})
|
get_accessory(None, None, entity_state, 2, config)
|
||||||
assert mock_type.called
|
assert mock_type.called
|
||||||
|
|
|
@ -2,12 +2,51 @@
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
from homeassistant.components.homekit.type_switches import Switch
|
from homeassistant.components.homekit.type_switches import Outlet, Switch
|
||||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
|
||||||
|
|
||||||
from tests.common import async_mock_service
|
from tests.common import async_mock_service
|
||||||
|
|
||||||
|
|
||||||
|
async def test_outlet_set_state(hass, hk_driver):
|
||||||
|
"""Test if Outlet accessory and HA are updated accordingly."""
|
||||||
|
entity_id = 'switch.outlet_test'
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, None)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
acc = Outlet(hass, hk_driver, 'Outlet', entity_id, 2, None)
|
||||||
|
await hass.async_add_job(acc.run)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert acc.aid == 2
|
||||||
|
assert acc.category == 7 # Outlet
|
||||||
|
|
||||||
|
assert acc.char_on.value is False
|
||||||
|
assert acc.char_outlet_in_use.value is True
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, STATE_ON)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert acc.char_on.value is True
|
||||||
|
|
||||||
|
hass.states.async_set(entity_id, STATE_OFF)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert acc.char_on.value is False
|
||||||
|
|
||||||
|
# Set from HomeKit
|
||||||
|
call_turn_on = async_mock_service(hass, 'switch', 'turn_on')
|
||||||
|
call_turn_off = async_mock_service(hass, 'switch', 'turn_off')
|
||||||
|
|
||||||
|
await hass.async_add_job(acc.char_on.client_update_value, True)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert call_turn_on
|
||||||
|
assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
|
||||||
|
|
||||||
|
await hass.async_add_job(acc.char_on.client_update_value, False)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert call_turn_off
|
||||||
|
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('entity_id', [
|
@pytest.mark.parametrize('entity_id', [
|
||||||
'automation.test',
|
'automation.test',
|
||||||
'input_boolean.test',
|
'input_boolean.test',
|
||||||
|
@ -23,6 +62,7 @@ async def test_switch_set_state(hass, hk_driver, entity_id):
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
acc = Switch(hass, hk_driver, 'Switch', entity_id, 2, None)
|
acc = Switch(hass, hk_driver, 'Switch', entity_id, 2, None)
|
||||||
await hass.async_add_job(acc.run)
|
await hass.async_add_job(acc.run)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert acc.aid == 2
|
assert acc.aid == 2
|
||||||
assert acc.category == 8 # Switch
|
assert acc.category == 8 # Switch
|
||||||
|
|
|
@ -5,7 +5,7 @@ import voluptuous as vol
|
||||||
from homeassistant.core import State
|
from homeassistant.core import State
|
||||||
from homeassistant.components.homekit.const import (
|
from homeassistant.components.homekit.const import (
|
||||||
CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF,
|
CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF,
|
||||||
FEATURE_PLAY_PAUSE)
|
FEATURE_PLAY_PAUSE, TYPE_OUTLET)
|
||||||
from homeassistant.components.homekit.util import (
|
from homeassistant.components.homekit.util import (
|
||||||
convert_to_float, density_to_air_quality, dismiss_setup_message,
|
convert_to_float, density_to_air_quality, dismiss_setup_message,
|
||||||
show_setup_message, temperature_to_homekit, temperature_to_states,
|
show_setup_message, temperature_to_homekit, temperature_to_states,
|
||||||
|
@ -15,7 +15,7 @@ from homeassistant.components.homekit.util import validate_entity_config \
|
||||||
from homeassistant.components.persistent_notification import (
|
from homeassistant.components.persistent_notification import (
|
||||||
ATTR_MESSAGE, ATTR_NOTIFICATION_ID, DOMAIN)
|
ATTR_MESSAGE, ATTR_NOTIFICATION_ID, DOMAIN)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, STATE_UNKNOWN,
|
ATTR_CODE, ATTR_SUPPORTED_FEATURES, CONF_NAME, CONF_TYPE, STATE_UNKNOWN,
|
||||||
TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
TEMP_CELSIUS, TEMP_FAHRENHEIT)
|
||||||
|
|
||||||
from tests.common import async_mock_service
|
from tests.common import async_mock_service
|
||||||
|
@ -30,7 +30,8 @@ def test_validate_entity_config():
|
||||||
{CONF_FEATURE: 'invalid_feature'}]}},
|
{CONF_FEATURE: 'invalid_feature'}]}},
|
||||||
{'media_player.test': {CONF_FEATURE_LIST: [
|
{'media_player.test': {CONF_FEATURE_LIST: [
|
||||||
{CONF_FEATURE: FEATURE_ON_OFF},
|
{CONF_FEATURE: FEATURE_ON_OFF},
|
||||||
{CONF_FEATURE: FEATURE_ON_OFF}]}}, ]
|
{CONF_FEATURE: FEATURE_ON_OFF}]}},
|
||||||
|
{'switch.test': {CONF_TYPE: 'invalid_type'}}]
|
||||||
|
|
||||||
for conf in configs:
|
for conf in configs:
|
||||||
with pytest.raises(vol.Invalid):
|
with pytest.raises(vol.Invalid):
|
||||||
|
@ -56,6 +57,8 @@ def test_validate_entity_config():
|
||||||
assert vec({'media_player.demo': config}) == \
|
assert vec({'media_player.demo': config}) == \
|
||||||
{'media_player.demo': {CONF_FEATURE_LIST:
|
{'media_player.demo': {CONF_FEATURE_LIST:
|
||||||
{FEATURE_ON_OFF: {}, FEATURE_PLAY_PAUSE: {}}}}
|
{FEATURE_ON_OFF: {}, FEATURE_PLAY_PAUSE: {}}}}
|
||||||
|
assert vec({'switch.demo': {CONF_TYPE: TYPE_OUTLET}}) == \
|
||||||
|
{'switch.demo': {CONF_TYPE: TYPE_OUTLET}}
|
||||||
|
|
||||||
|
|
||||||
def test_validate_media_player_features():
|
def test_validate_media_player_features():
|
||||||
|
|
Loading…
Add table
Reference in a new issue