Add Blebox switch support (#35371)

* support BleBox switches

* fix tox py37 test failures

* refactor BleBox device class map
pull/35910/head
gadgetmobile 2020-05-15 23:48:17 +02:00 committed by GitHub
parent 064321c21e
commit 890013cecf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 437 additions and 7 deletions

View File

@ -17,7 +17,7 @@ from .const import DEFAULT_SETUP_TIMEOUT, DOMAIN, PRODUCT
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["cover", "sensor"]
PLATFORMS = ["cover", "sensor", "switch"]
PARALLEL_UPDATES = 0

View File

@ -9,6 +9,7 @@ from homeassistant.components.cover import (
STATE_OPEN,
STATE_OPENING,
)
from homeassistant.components.switch import DEVICE_CLASS_SWITCH
from homeassistant.const import DEVICE_CLASS_TEMPERATURE, TEMP_CELSIUS
DOMAIN = "blebox"
@ -26,6 +27,8 @@ BLEBOX_TO_HASS_DEVICE_CLASSES = {
"shutter": DEVICE_CLASS_SHUTTER,
"gatebox": DEVICE_CLASS_DOOR,
"gate": DEVICE_CLASS_GATE,
"relay": DEVICE_CLASS_SWITCH,
"temperature": DEVICE_CLASS_TEMPERATURE,
}
BLEBOX_TO_HASS_COVER_STATES = {
@ -43,7 +46,6 @@ BLEBOX_TO_HASS_COVER_STATES = {
}
BLEBOX_TO_UNIT_MAP = {"celsius": TEMP_CELSIUS}
BLEBOX_DEV_CLASS_MAP = {"temperature": DEVICE_CLASS_TEMPERATURE}
DEFAULT_HOST = "192.168.0.2"
DEFAULT_PORT = 80

View File

@ -3,7 +3,7 @@
from homeassistant.helpers.entity import Entity
from . import BleBoxEntity, create_blebox_entities
from .const import BLEBOX_DEV_CLASS_MAP, BLEBOX_TO_UNIT_MAP, DOMAIN, PRODUCT
from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, BLEBOX_TO_UNIT_MAP, DOMAIN, PRODUCT
async def async_setup_entry(hass, config_entry, async_add):
@ -30,4 +30,4 @@ class BleBoxSensorEntity(BleBoxEntity, Entity):
@property
def device_class(self):
"""Return the device class."""
return BLEBOX_DEV_CLASS_MAP[self._feature.device_class]
return BLEBOX_TO_HASS_DEVICE_CLASSES[self._feature.device_class]

View File

@ -0,0 +1,35 @@
"""BleBox switch implementation."""
from homeassistant.components.switch import SwitchDevice
from . import BleBoxEntity, create_blebox_entities
from .const import BLEBOX_TO_HASS_DEVICE_CLASSES, DOMAIN, PRODUCT
async def async_setup_entry(hass, config_entry, async_add):
"""Set up a BleBox switch entity."""
product = hass.data[DOMAIN][config_entry.entry_id][PRODUCT]
create_blebox_entities(product, async_add, BleBoxSwitchEntity, "switches")
return True
class BleBoxSwitchEntity(BleBoxEntity, SwitchDevice):
"""Representation of a BleBox switch feature."""
@property
def device_class(self):
"""Return the device class."""
return BLEBOX_TO_HASS_DEVICE_CLASSES[self._feature.device_class]
@property
def is_on(self):
"""Return whether switch is on."""
return self._feature.is_on
async def async_turn_on(self, **kwargs):
"""Turn on the switch."""
return await self._feature.async_turn_on()
async def async_turn_off(self, **kwargs):
"""Turn off the switch."""
return await self._feature.async_turn_off()

View File

@ -76,12 +76,19 @@ def feature(request):
return request.getfixturevalue(request.param)
async def async_setup_entity(hass, config, entity_id):
"""Return a configured entity with the given entity_id."""
async def async_setup_entities(hass, config, entity_ids):
"""Return configured entries with the given entity ids."""
config_entry = mock_config()
config_entry.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, config)
await hass.async_block_till_done()
entity_registry = await hass.helpers.entity_registry.async_get_registry()
return entity_registry.async_get(entity_id)
return [entity_registry.async_get(entity_id) for entity_id in entity_ids]
async def async_setup_entity(hass, config, entity_id):
"""Return a configured entry with the given entity_id."""
return (await async_setup_entities(hass, config, [entity_id]))[0]

View File

@ -0,0 +1,386 @@
"""Blebox switch tests."""
import logging
import blebox_uniapi
import pytest
from homeassistant.components.switch import DEVICE_CLASS_SWITCH
from homeassistant.const import (
ATTR_DEVICE_CLASS,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
)
from .conftest import (
async_setup_entities,
async_setup_entity,
mock_feature,
mock_only_feature,
setup_product_mock,
)
from tests.async_mock import AsyncMock, PropertyMock
@pytest.fixture(name="switchbox")
def switchbox_fixture():
"""Return a default switchBox switch entity mock."""
feature = mock_feature(
"switches",
blebox_uniapi.switch.Switch,
unique_id="BleBox-switchBox-1afe34e750b8-0.relay",
full_name="switchBox-0.relay",
device_class="relay",
is_on=False,
)
feature.async_update = AsyncMock()
product = feature.product
type(product).name = PropertyMock(return_value="My switch box")
type(product).model = PropertyMock(return_value="switchBox")
return (feature, "switch.switchbox_0_relay")
async def test_switchbox_init(switchbox, hass, config):
"""Test switch default state."""
feature_mock, entity_id = switchbox
feature_mock.async_update = AsyncMock()
entry = await async_setup_entity(hass, config, entity_id)
assert entry.unique_id == "BleBox-switchBox-1afe34e750b8-0.relay"
state = hass.states.get(entity_id)
assert state.name == "switchBox-0.relay"
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH
assert state.state == STATE_OFF
device_registry = await hass.helpers.device_registry.async_get_registry()
device = device_registry.async_get(entry.device_id)
assert device.name == "My switch box"
assert device.identifiers == {("blebox", "abcd0123ef5678")}
assert device.manufacturer == "BleBox"
assert device.model == "switchBox"
assert device.sw_version == "1.23"
async def test_switchbox_update_when_off(switchbox, hass, config):
"""Test switch updating when off."""
feature_mock, entity_id = switchbox
def initial_update():
feature_mock.is_on = False
feature_mock.async_update = AsyncMock(side_effect=initial_update)
await async_setup_entity(hass, config, entity_id)
state = hass.states.get(entity_id)
assert state.state == STATE_OFF
async def test_switchbox_update_when_on(switchbox, hass, config):
"""Test switch updating when on."""
feature_mock, entity_id = switchbox
def initial_update():
feature_mock.is_on = True
feature_mock.async_update = AsyncMock(side_effect=initial_update)
await async_setup_entity(hass, config, entity_id)
state = hass.states.get(entity_id)
assert state.state == STATE_ON
async def test_switchbox_on(switchbox, hass, config):
"""Test turning switch on."""
feature_mock, entity_id = switchbox
def initial_update():
feature_mock.is_on = False
feature_mock.async_update = AsyncMock(side_effect=initial_update)
await async_setup_entity(hass, config, entity_id)
feature_mock.async_update = AsyncMock()
def turn_on():
feature_mock.is_on = True
feature_mock.async_turn_on = AsyncMock(side_effect=turn_on)
await hass.services.async_call(
"switch", SERVICE_TURN_ON, {"entity_id": entity_id}, blocking=True,
)
state = hass.states.get(entity_id)
assert state.state == STATE_ON
async def test_switchbox_off(switchbox, hass, config):
"""Test turning switch off."""
feature_mock, entity_id = switchbox
def initial_update():
feature_mock.is_on = True
feature_mock.async_update = AsyncMock(side_effect=initial_update)
await async_setup_entity(hass, config, entity_id)
feature_mock.async_update = AsyncMock()
def turn_off():
feature_mock.is_on = False
feature_mock.async_turn_off = AsyncMock(side_effect=turn_off)
await hass.services.async_call(
"switch", SERVICE_TURN_OFF, {"entity_id": entity_id}, blocking=True,
)
state = hass.states.get(entity_id)
assert state.state == STATE_OFF
def relay_mock(relay_id=0):
"""Return a default switchBoxD switch entity mock."""
return mock_only_feature(
blebox_uniapi.switch.Switch,
unique_id=f"BleBox-switchBoxD-1afe34e750b8-{relay_id}.relay",
full_name=f"switchBoxD-{relay_id}.relay",
device_class="relay",
is_on=None,
)
@pytest.fixture(name="switchbox_d")
def switchbox_d_fixture():
"""Set up two mocked Switch features representing a switchBoxD."""
relay1 = relay_mock(0)
relay2 = relay_mock(1)
features = [relay1, relay2]
product = setup_product_mock("switches", features)
type(product).name = PropertyMock(return_value="My relays")
type(product).model = PropertyMock(return_value="switchBoxD")
type(product).brand = PropertyMock(return_value="BleBox")
type(product).firmware_version = PropertyMock(return_value="1.23")
type(product).unique_id = PropertyMock(return_value="abcd0123ef5678")
type(relay1).product = product
type(relay2).product = product
return (features, ["switch.switchboxd_0_relay", "switch.switchboxd_1_relay"])
async def test_switchbox_d_init(switchbox_d, hass, config):
"""Test switch default state."""
feature_mocks, entity_ids = switchbox_d
feature_mocks[0].async_update = AsyncMock()
feature_mocks[1].async_update = AsyncMock()
entries = await async_setup_entities(hass, config, entity_ids)
entry = entries[0]
assert entry.unique_id == "BleBox-switchBoxD-1afe34e750b8-0.relay"
state = hass.states.get(entity_ids[0])
assert state.name == "switchBoxD-0.relay"
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH
assert state.state == STATE_OFF # NOTE: should instead be STATE_UNKNOWN?
device_registry = await hass.helpers.device_registry.async_get_registry()
device = device_registry.async_get(entry.device_id)
assert device.name == "My relays"
assert device.identifiers == {("blebox", "abcd0123ef5678")}
assert device.manufacturer == "BleBox"
assert device.model == "switchBoxD"
assert device.sw_version == "1.23"
entry = entries[1]
assert entry.unique_id == "BleBox-switchBoxD-1afe34e750b8-1.relay"
state = hass.states.get(entity_ids[1])
assert state.name == "switchBoxD-1.relay"
assert state.attributes[ATTR_DEVICE_CLASS] == DEVICE_CLASS_SWITCH
assert state.state == STATE_OFF # NOTE: should instead be STATE_UNKNOWN?
device_registry = await hass.helpers.device_registry.async_get_registry()
device = device_registry.async_get(entry.device_id)
assert device.name == "My relays"
assert device.identifiers == {("blebox", "abcd0123ef5678")}
assert device.manufacturer == "BleBox"
assert device.model == "switchBoxD"
assert device.sw_version == "1.23"
async def test_switchbox_d_update_when_off(switchbox_d, hass, config):
"""Test switch updating when off."""
feature_mocks, entity_ids = switchbox_d
def initial_update0():
feature_mocks[0].is_on = False
feature_mocks[1].is_on = False
feature_mocks[0].async_update = AsyncMock(side_effect=initial_update0)
feature_mocks[1].async_update = AsyncMock()
await async_setup_entities(hass, config, entity_ids)
assert hass.states.get(entity_ids[0]).state == STATE_OFF
assert hass.states.get(entity_ids[1]).state == STATE_OFF
async def test_switchbox_d_update_when_second_off(switchbox_d, hass, config):
"""Test switch updating when off."""
feature_mocks, entity_ids = switchbox_d
def initial_update0():
feature_mocks[0].is_on = True
feature_mocks[1].is_on = False
feature_mocks[0].async_update = AsyncMock(side_effect=initial_update0)
feature_mocks[1].async_update = AsyncMock()
await async_setup_entities(hass, config, entity_ids)
assert hass.states.get(entity_ids[0]).state == STATE_ON
assert hass.states.get(entity_ids[1]).state == STATE_OFF
async def test_switchbox_d_turn_first_on(switchbox_d, hass, config):
"""Test turning switch on."""
feature_mocks, entity_ids = switchbox_d
def initial_update0():
feature_mocks[0].is_on = False
feature_mocks[1].is_on = False
feature_mocks[0].async_update = AsyncMock(side_effect=initial_update0)
feature_mocks[1].async_update = AsyncMock()
await async_setup_entities(hass, config, entity_ids)
feature_mocks[0].async_update = AsyncMock()
def turn_on0():
feature_mocks[0].is_on = True
feature_mocks[0].async_turn_on = AsyncMock(side_effect=turn_on0)
await hass.services.async_call(
"switch", SERVICE_TURN_ON, {"entity_id": entity_ids[0]}, blocking=True,
)
assert hass.states.get(entity_ids[0]).state == STATE_ON
assert hass.states.get(entity_ids[1]).state == STATE_OFF
async def test_switchbox_d_second_on(switchbox_d, hass, config):
"""Test turning switch on."""
feature_mocks, entity_ids = switchbox_d
def initial_update0():
feature_mocks[0].is_on = False
feature_mocks[1].is_on = False
feature_mocks[0].async_update = AsyncMock(side_effect=initial_update0)
feature_mocks[1].async_update = AsyncMock()
await async_setup_entities(hass, config, entity_ids)
feature_mocks[0].async_update = AsyncMock()
def turn_on1():
feature_mocks[1].is_on = True
feature_mocks[1].async_turn_on = AsyncMock(side_effect=turn_on1)
await hass.services.async_call(
"switch", SERVICE_TURN_ON, {"entity_id": entity_ids[1]}, blocking=True,
)
assert hass.states.get(entity_ids[0]).state == STATE_OFF
assert hass.states.get(entity_ids[1]).state == STATE_ON
async def test_switchbox_d_first_off(switchbox_d, hass, config):
"""Test turning switch on."""
feature_mocks, entity_ids = switchbox_d
def initial_update_any():
feature_mocks[0].is_on = True
feature_mocks[1].is_on = True
feature_mocks[0].async_update = AsyncMock(side_effect=initial_update_any)
feature_mocks[1].async_update = AsyncMock()
await async_setup_entities(hass, config, entity_ids)
feature_mocks[0].async_update = AsyncMock()
def turn_off0():
feature_mocks[0].is_on = False
feature_mocks[0].async_turn_off = AsyncMock(side_effect=turn_off0)
await hass.services.async_call(
"switch", SERVICE_TURN_OFF, {"entity_id": entity_ids[0]}, blocking=True,
)
assert hass.states.get(entity_ids[0]).state == STATE_OFF
assert hass.states.get(entity_ids[1]).state == STATE_ON
async def test_switchbox_d_second_off(switchbox_d, hass, config):
"""Test turning switch on."""
feature_mocks, entity_ids = switchbox_d
def initial_update_any():
feature_mocks[0].is_on = True
feature_mocks[1].is_on = True
feature_mocks[0].async_update = AsyncMock(side_effect=initial_update_any)
feature_mocks[1].async_update = AsyncMock()
await async_setup_entities(hass, config, entity_ids)
feature_mocks[0].async_update = AsyncMock()
def turn_off1():
feature_mocks[1].is_on = False
feature_mocks[1].async_turn_off = AsyncMock(side_effect=turn_off1)
await hass.services.async_call(
"switch", SERVICE_TURN_OFF, {"entity_id": entity_ids[1]}, blocking=True,
)
assert hass.states.get(entity_ids[0]).state == STATE_ON
assert hass.states.get(entity_ids[1]).state == STATE_OFF
ALL_SWITCH_FIXTURES = ["switchbox", "switchbox_d"]
@pytest.mark.parametrize("feature", ALL_SWITCH_FIXTURES, indirect=["feature"])
async def test_update_failure(feature, hass, config, caplog):
"""Test that update failures are logged."""
caplog.set_level(logging.ERROR)
feature_mock, entity_id = feature
if isinstance(feature_mock, list):
feature_mock[0].async_update = AsyncMock()
feature_mock[1].async_update = AsyncMock()
feature_mock = feature_mock[0]
entity_id = entity_id[0]
feature_mock.async_update = AsyncMock(side_effect=blebox_uniapi.error.ClientError)
await async_setup_entity(hass, config, entity_id)
assert f"Updating '{feature_mock.full_name}' failed: " in caplog.text