Add vera config entries support (#29880)

* Adding vera config entries support.

* Fixing lint error.

* Applying minimal changes necessary to get config entries working.

* Addressing PR feedback by further reducing the scope of the change.

* Addressing PR feedback.

* Fixing pyvera import to make it easier to patch.
Addressing PR feedback regarding creation of controller and scheduling of async config flow actions.

* Updating code owners file.

* Small fixes.

* Adding a user config flow step.

* Adding optional configs for user config flow.

* Updating strings to be more clear to the user.

* Adding options flow.
Fixing some PR feedback.

* Better handling of options.
PR feedback changes.

* Using config registry to update config options.

* Better managing config from file or config from UI
Disabling config through UI if config is provided from a file.
More tests to account for these adjustments.

* Address PR feedback.

* Fixing test, merging with master.

* Disabling all Vera UI for configs managed by configuration.yml.
Adding more tests.

* Updating config based on unique_id.
Addressing additional PR feedback.

* Rebasing off dev.
Addressing feedback.

* Addressing PR feedback.
This commit is contained in:
Robert Van Gorkom 2020-04-03 00:49:50 -07:00 committed by GitHub
parent aef06a3544
commit ae22b5187a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 876 additions and 294 deletions

View File

@ -409,6 +409,7 @@ homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velbus/* @Cereal2nd @brefra
homeassistant/components/velux/* @Julius2342
homeassistant/components/vera/* @vangorra
homeassistant/components/versasense/* @flamm3blemuff1n
homeassistant/components/version/* @fabaff
homeassistant/components/vesync/* @markperdue @webdjoe

View File

@ -1,4 +1,5 @@
"""Support for Vera devices."""
import asyncio
from collections import defaultdict
import logging
@ -6,6 +7,8 @@ import pyvera as veraApi
from requests.exceptions import RequestException
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ARMED,
ATTR_BATTERY_LEVEL,
@ -15,27 +18,24 @@ from homeassistant.const import (
CONF_LIGHTS,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import convert, slugify
from homeassistant.util.dt import utc_from_timestamp
from .common import ControllerData, get_configured_platforms
from .config_flow import new_options
from .const import (
ATTR_CURRENT_ENERGY_KWH,
ATTR_CURRENT_POWER_W,
CONF_CONTROLLER,
DOMAIN,
VERA_ID_FORMAT,
)
_LOGGER = logging.getLogger(__name__)
DOMAIN = "vera"
VERA_CONTROLLER = "vera_controller"
CONF_CONTROLLER = "vera_controller_url"
VERA_ID_FORMAT = "{}_{}"
ATTR_CURRENT_POWER_W = "current_power_w"
ATTR_CURRENT_ENERGY_KWH = "current_energy_kwh"
VERA_DEVICES = "vera_devices"
VERA_SCENES = "vera_scenes"
VERA_ID_LIST_SCHEMA = vol.Schema([int])
CONFIG_SCHEMA = vol.Schema(
@ -51,42 +51,53 @@ CONFIG_SCHEMA = vol.Schema(
extra=vol.ALLOW_EXTRA,
)
VERA_COMPONENTS = [
"binary_sensor",
"sensor",
"light",
"switch",
"lock",
"climate",
"cover",
"scene",
]
def setup(hass, base_config):
"""Set up for Vera devices."""
def stop_subscription(event):
"""Shutdown Vera subscriptions and subscription thread on exit."""
_LOGGER.info("Shutting down subscriptions")
hass.data[VERA_CONTROLLER].stop()
async def async_setup(hass: HomeAssistant, base_config: dict) -> bool:
"""Set up for Vera controllers."""
config = base_config.get(DOMAIN)
# Get Vera specific configuration.
base_url = config.get(CONF_CONTROLLER)
light_ids = config.get(CONF_LIGHTS)
exclude_ids = config.get(CONF_EXCLUDE)
if not config:
return True
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, data=config,
)
)
return True
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Do setup of vera."""
# Use options entered during initial config flow or provided from configuration.yml
if config_entry.data.get(CONF_LIGHTS) or config_entry.data.get(CONF_EXCLUDE):
hass.config_entries.async_update_entry(
entry=config_entry,
data=config_entry.data,
options=new_options(
config_entry.data.get(CONF_LIGHTS, []),
config_entry.data.get(CONF_EXCLUDE, []),
),
)
base_url = config_entry.data[CONF_CONTROLLER]
light_ids = config_entry.options.get(CONF_LIGHTS, [])
exclude_ids = config_entry.options.get(CONF_EXCLUDE, [])
# Initialize the Vera controller.
controller, _ = veraApi.init_controller(base_url)
hass.data[VERA_CONTROLLER] = controller
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_subscription)
controller = veraApi.VeraController(base_url)
controller.start()
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP,
lambda event: hass.async_add_executor_job(controller.stop),
)
try:
all_devices = controller.get_devices()
all_devices = await hass.async_add_executor_job(controller.get_devices)
all_scenes = controller.get_scenes()
all_scenes = await hass.async_add_executor_job(controller.get_scenes)
except RequestException:
# There was a network related error connecting to the Vera controller.
_LOGGER.exception("Error communicating with Vera API")
@ -102,15 +113,35 @@ def setup(hass, base_config):
continue
vera_devices[device_type].append(device)
hass.data[VERA_DEVICES] = vera_devices
vera_scenes = []
for scene in all_scenes:
vera_scenes.append(scene)
hass.data[VERA_SCENES] = vera_scenes
for component in VERA_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, base_config)
controller_data = ControllerData(
controller=controller, devices=vera_devices, scenes=vera_scenes
)
hass.data[DOMAIN] = controller_data
# Forward the config data to the necessary platforms.
for platform in get_configured_platforms(controller_data):
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, platform)
)
return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload Withings config entry."""
controller_data = hass.data[DOMAIN]
tasks = [
hass.config_entries.async_forward_entry_unload(config_entry, platform)
for platform in get_configured_platforms(controller_data)
]
await asyncio.gather(*tasks)
return True

View File

@ -1,21 +1,34 @@
"""Support for Vera binary sensors."""
import logging
from typing import Callable, List
from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorDevice
from homeassistant.components.binary_sensor import (
DOMAIN as PLATFORM_DOMAIN,
ENTITY_ID_FORMAT,
BinarySensorDevice,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
from . import VeraDevice
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Perform the setup for Vera controller devices."""
add_entities(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
"""Set up the sensor config entry."""
controller_data = hass.data[DOMAIN]
async_add_entities(
[
VeraBinarySensor(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]["binary_sensor"]
],
True,
VeraBinarySensor(device, controller_data.controller)
for device in controller_data.devices.get(PLATFORM_DOMAIN)
]
)

View File

@ -1,7 +1,12 @@
"""Support for Vera thermostats."""
import logging
from typing import Callable, List
from homeassistant.components.climate import ENTITY_ID_FORMAT, ClimateDevice
from homeassistant.components.climate import (
DOMAIN as PLATFORM_DOMAIN,
ENTITY_ID_FORMAT,
ClimateDevice,
)
from homeassistant.components.climate.const import (
FAN_AUTO,
FAN_ON,
@ -12,10 +17,14 @@ from homeassistant.components.climate.const import (
SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.util import convert
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
from . import VeraDevice
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@ -25,14 +34,18 @@ SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
SUPPORT_HVAC = [HVAC_MODE_COOL, HVAC_MODE_HEAT, HVAC_MODE_HEAT_COOL, HVAC_MODE_OFF]
def setup_platform(hass, config, add_entities_callback, discovery_info=None):
"""Set up of Vera thermostats."""
add_entities_callback(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
"""Set up the sensor config entry."""
controller_data = hass.data[DOMAIN]
async_add_entities(
[
VeraThermostat(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]["climate"]
],
True,
VeraThermostat(device, controller_data.controller)
for device in controller_data.devices.get(PLATFORM_DOMAIN)
]
)

View File

@ -0,0 +1,29 @@
"""Common vera code."""
import logging
from typing import DefaultDict, List, NamedTuple, Set
import pyvera as pv
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN
_LOGGER = logging.getLogger(__name__)
class ControllerData(NamedTuple):
"""Controller data."""
controller: pv.VeraController
devices: DefaultDict[str, List[pv.VeraDevice]]
scenes: List[pv.VeraScene]
def get_configured_platforms(controller_data: ControllerData) -> Set[str]:
"""Get configured platforms for a controller."""
platforms = []
for platform in controller_data.devices:
platforms.append(platform)
if controller_data.scenes:
platforms.append(SCENE_DOMAIN)
return set(platforms)

View File

@ -0,0 +1,130 @@
"""Config flow for Vera."""
import logging
import re
from typing import List, cast
import pyvera as pv
from requests.exceptions import RequestException
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE
from homeassistant.core import callback
from .const import CONF_CONTROLLER, DOMAIN
LIST_REGEX = re.compile("[^0-9]+")
_LOGGER = logging.getLogger(__name__)
def str_to_int_list(data: str) -> List[str]:
"""Convert a string to an int list."""
if isinstance(str, list):
return cast(List[str], data)
return [s for s in LIST_REGEX.split(data) if len(s) > 0]
def int_list_to_str(data: List[str]) -> str:
"""Convert an int list to a string."""
return " ".join([str(i) for i in data])
def new_options(lights: List[str], exclude: List[str]) -> dict:
"""Create a standard options object."""
return {CONF_LIGHTS: lights, CONF_EXCLUDE: exclude}
def options_schema(options: dict = None) -> dict:
"""Return options schema."""
options = options or {}
return {
vol.Optional(
CONF_LIGHTS, default=int_list_to_str(options.get(CONF_LIGHTS, [])),
): str,
vol.Optional(
CONF_EXCLUDE, default=int_list_to_str(options.get(CONF_EXCLUDE, [])),
): str,
}
def options_data(user_input: dict) -> dict:
"""Return options dict."""
return new_options(
str_to_int_list(user_input.get(CONF_LIGHTS, "")),
str_to_int_list(user_input.get(CONF_EXCLUDE, "")),
)
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Options for the component."""
def __init__(self, config_entry: config_entries.ConfigEntry):
"""Init object."""
self.config_entry = config_entry
async def async_step_init(self, user_input=None):
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=options_data(user_input),)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(options_schema(self.config_entry.options)),
)
class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
"""Vera config flow."""
@staticmethod
@callback
def async_get_options_flow(config_entry) -> OptionsFlowHandler:
"""Get the options flow."""
return OptionsFlowHandler(config_entry)
async def async_step_user(self, user_input: dict = None):
"""Handle user initiated flow."""
if self.hass.config_entries.async_entries(DOMAIN):
return self.async_abort(reason="already_configured")
if user_input is not None:
return await self.async_step_finish(
{
**user_input,
**options_data(user_input),
**{CONF_SOURCE: config_entries.SOURCE_USER},
}
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{**{vol.Required(CONF_CONTROLLER): str}, **options_schema()}
),
)
async def async_step_import(self, config: dict):
"""Handle a flow initialized by import."""
return await self.async_step_finish(
{**config, **{CONF_SOURCE: config_entries.SOURCE_IMPORT}}
)
async def async_step_finish(self, config: dict):
"""Validate and create config entry."""
base_url = config[CONF_CONTROLLER] = config[CONF_CONTROLLER].rstrip("/")
controller = pv.VeraController(base_url)
# Verify the controller is online and get the serial number.
try:
await self.hass.async_add_executor_job(controller.refresh_data)
except RequestException:
_LOGGER.error("Failed to connect to vera controller %s", base_url)
return self.async_abort(
reason="cannot_connect", description_placeholders={"base_url": base_url}
)
await self.async_set_unique_id(controller.serial_number)
self._abort_if_unique_id_configured(config)
return self.async_create_entry(title=base_url, data=config)

View File

@ -0,0 +1,11 @@
"""Vera constants."""
DOMAIN = "vera"
CONF_CONTROLLER = "vera_controller_url"
VERA_ID_FORMAT = "{}_{}"
ATTR_CURRENT_POWER_W = "current_power_w"
ATTR_CURRENT_ENERGY_KWH = "current_energy_kwh"
CONTROLLER_DATAS = "controller_datas"

View File

@ -1,21 +1,35 @@
"""Support for Vera cover - curtains, rollershutters etc."""
import logging
from typing import Callable, List
from homeassistant.components.cover import ATTR_POSITION, ENTITY_ID_FORMAT, CoverDevice
from homeassistant.components.cover import (
ATTR_POSITION,
DOMAIN as PLATFORM_DOMAIN,
ENTITY_ID_FORMAT,
CoverDevice,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
from . import VeraDevice
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Vera covers."""
add_entities(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
"""Set up the sensor config entry."""
controller_data = hass.data[DOMAIN]
async_add_entities(
[
VeraCover(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]["cover"]
],
True,
VeraCover(device, controller_data.controller)
for device in controller_data.devices.get(PLATFORM_DOMAIN)
]
)

View File

@ -1,29 +1,39 @@
"""Support for Vera lights."""
import logging
from typing import Callable, List
from homeassistant.components.light import (
ATTR_BRIGHTNESS,
ATTR_HS_COLOR,
DOMAIN as PLATFORM_DOMAIN,
ENTITY_ID_FORMAT,
SUPPORT_BRIGHTNESS,
SUPPORT_COLOR,
Light,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
import homeassistant.util.color as color_util
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
from . import VeraDevice
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Vera lights."""
add_entities(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
"""Set up the sensor config entry."""
controller_data = hass.data[DOMAIN]
async_add_entities(
[
VeraLight(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]["light"]
],
True,
VeraLight(device, controller_data.controller)
for device in controller_data.devices.get(PLATFORM_DOMAIN)
]
)

View File

@ -1,10 +1,19 @@
"""Support for Vera locks."""
import logging
from typing import Callable, List
from homeassistant.components.lock import ENTITY_ID_FORMAT, LockDevice
from homeassistant.components.lock import (
DOMAIN as PLATFORM_DOMAIN,
ENTITY_ID_FORMAT,
LockDevice,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
from . import VeraDevice
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@ -12,14 +21,18 @@ ATTR_LAST_USER_NAME = "changed_by_name"
ATTR_LOW_BATTERY = "low_battery"
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Find and return Vera locks."""
add_entities(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
"""Set up the sensor config entry."""
controller_data = hass.data[DOMAIN]
async_add_entities(
[
VeraLock(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]["lock"]
],
True,
VeraLock(device, controller_data.controller)
for device in controller_data.devices.get(PLATFORM_DOMAIN)
]
)

View File

@ -1,8 +1,11 @@
{
"domain": "vera",
"name": "Vera",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/vera",
"requirements": ["pyvera==0.3.7"],
"dependencies": [],
"codeowners": []
"codeowners": [
"@vangorra"
]
}

View File

@ -1,22 +1,30 @@
"""Support for Vera scenes."""
import logging
from typing import Callable, List
from homeassistant.components.scene import Scene
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify
from . import VERA_CONTROLLER, VERA_ID_FORMAT, VERA_SCENES
from .const import DOMAIN, VERA_ID_FORMAT
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Vera scenes."""
add_entities(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
"""Set up the sensor config entry."""
controller_data = hass.data[DOMAIN]
async_add_entities(
[
VeraScene(scene, hass.data[VERA_CONTROLLER])
for scene in hass.data[VERA_SCENES]
],
True,
VeraScene(device, controller_data.controller)
for device in controller_data.scenes
]
)

View File

@ -1,29 +1,37 @@
"""Support for Vera sensors."""
from datetime import timedelta
import logging
from typing import Callable, List
import pyvera as veraApi
from homeassistant.components.sensor import ENTITY_ID_FORMAT
from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN, ENTITY_ID_FORMAT
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, UNIT_PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.util import convert
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
from . import VeraDevice
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=5)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Vera controller devices."""
add_entities(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
"""Set up the sensor config entry."""
controller_data = hass.data[DOMAIN]
async_add_entities(
[
VeraSensor(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]["sensor"]
],
True,
VeraSensor(device, controller_data.controller)
for device in controller_data.devices.get(PLATFORM_DOMAIN)
]
)

View File

@ -0,0 +1,32 @@
{
"config": {
"title": "Vera",
"abort": {
"already_configured": "A controller is already configured.",
"cannot_connect": "Could not connect to controller with url {base_url}"
},
"step": {
"user": {
"title": "Setup Vera controller",
"description": "Provide a Vera controller url below. It should look like this: http://192.168.1.161:3480.",
"data": {
"vera_controller_url": "Controller URL",
"lights": "Vera switch device ids to treat as lights in Home Assistant.",
"exclude": "Vera device ids to exclude from Home Assistant."
}
}
}
},
"options": {
"step": {
"init": {
"title": "Vera controller options",
"description": "See the vera documentation for details on optional parameters: https://www.home-assistant.io/integrations/vera/. Note: Any changes here will need a restart to the home assistant server. To clear values, provide a space.",
"data": {
"lights": "Vera switch device ids to treat as lights in Home Assistant.",
"exclude": "Vera device ids to exclude from Home Assistant."
}
}
}
}
}

View File

@ -1,22 +1,35 @@
"""Support for Vera switches."""
import logging
from typing import Callable, List
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchDevice
from homeassistant.components.switch import (
DOMAIN as PLATFORM_DOMAIN,
ENTITY_ID_FORMAT,
SwitchDevice,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity
from homeassistant.util import convert
from . import VERA_CONTROLLER, VERA_DEVICES, VeraDevice
from . import VeraDevice
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Vera switches."""
add_entities(
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: Callable[[List[Entity], bool], None],
) -> None:
"""Set up the sensor config entry."""
controller_data = hass.data[DOMAIN]
async_add_entities(
[
VeraSwitch(device, hass.data[VERA_CONTROLLER])
for device in hass.data[VERA_DEVICES]["switch"]
],
True,
VeraSwitch(device, controller_data.controller)
for device in controller_data.devices.get(PLATFORM_DOMAIN)
]
)

View File

@ -122,6 +122,7 @@ FLOWS = [
"unifi",
"upnp",
"velbus",
"vera",
"vesync",
"vilfo",
"vizio",

View File

@ -1,47 +1,91 @@
"""Common code for tests."""
from typing import Callable, NamedTuple, Tuple
from typing import Callable, Dict, NamedTuple, Tuple
from mock import MagicMock
from pyvera import VeraController, VeraDevice, VeraScene
import pyvera as pv
from homeassistant.components.vera import CONF_CONTROLLER, DOMAIN
from homeassistant.components.vera.const import CONF_CONTROLLER, DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
SetupCallback = Callable[[pv.VeraController, dict], None]
class ControllerData(NamedTuple):
"""Test data about a specific vera controller."""
controller: pv.VeraController
update_callback: Callable
class ComponentData(NamedTuple):
"""Component data."""
"""Test data about the vera component."""
controller: VeraController
controller_data: ControllerData
class ControllerConfig(NamedTuple):
"""Test config for mocking a vera controller."""
config: Dict
options: Dict
config_from_file: bool
serial_number: str
devices: Tuple[pv.VeraDevice, ...]
scenes: Tuple[pv.VeraScene, ...]
setup_callback: SetupCallback
def new_simple_controller_config(
config: dict = None,
options: dict = None,
config_from_file=False,
serial_number="1111",
devices: Tuple[pv.VeraDevice, ...] = (),
scenes: Tuple[pv.VeraScene, ...] = (),
setup_callback: SetupCallback = None,
) -> ControllerConfig:
"""Create simple contorller config."""
return ControllerConfig(
config=config or {CONF_CONTROLLER: "http://127.0.0.1:123"},
options=options,
config_from_file=config_from_file,
serial_number=serial_number,
devices=devices,
scenes=scenes,
setup_callback=setup_callback,
)
class ComponentFactory:
"""Factory class."""
def __init__(self, init_controller_mock):
"""Initialize component factory."""
self.init_controller_mock = init_controller_mock
def __init__(self, vera_controller_class_mock):
"""Initialize the factory."""
self.vera_controller_class_mock = vera_controller_class_mock
async def configure_component(
self,
hass: HomeAssistant,
devices: Tuple[VeraDevice] = (),
scenes: Tuple[VeraScene] = (),
setup_callback: Callable[[VeraController], None] = None,
self, hass: HomeAssistant, controller_config: ControllerConfig
) -> ComponentData:
"""Configure the component with specific mock data."""
controller_url = "http://127.0.0.1:123"
hass_config = {
DOMAIN: {CONF_CONTROLLER: controller_url},
component_config = {
**(controller_config.config or {}),
**(controller_config.options or {}),
}
controller = MagicMock(spec=VeraController) # type: VeraController
controller.base_url = controller_url
controller = MagicMock(spec=pv.VeraController) # type: pv.VeraController
controller.base_url = component_config.get(CONF_CONTROLLER)
controller.register = MagicMock()
controller.get_devices = MagicMock(return_value=devices or ())
controller.get_scenes = MagicMock(return_value=scenes or ())
controller.start = MagicMock()
controller.stop = MagicMock()
controller.refresh_data = MagicMock()
controller.temperature_units = "C"
controller.serial_number = controller_config.serial_number
controller.get_devices = MagicMock(return_value=controller_config.devices)
controller.get_scenes = MagicMock(return_value=controller_config.scenes)
for vera_obj in controller.get_devices() + controller.get_scenes():
vera_obj.vera_controller = controller
@ -49,17 +93,39 @@ class ComponentFactory:
controller.get_devices.reset_mock()
controller.get_scenes.reset_mock()
if setup_callback:
setup_callback(controller, hass_config)
if controller_config.setup_callback:
controller_config.setup_callback(controller)
def init_controller(base_url: str) -> list:
nonlocal controller
return [controller, True]
self.vera_controller_class_mock.return_value = controller
self.init_controller_mock.side_effect = init_controller
hass_config = {}
# Setup component through config file import.
if controller_config.config_from_file:
hass_config[DOMAIN] = component_config
# Setup Home Assistant.
assert await async_setup_component(hass, DOMAIN, hass_config)
await hass.async_block_till_done()
return ComponentData(controller=controller)
# Setup component through config flow.
if not controller_config.config_from_file:
entry = MockConfigEntry(
domain=DOMAIN, data=component_config, options={}, unique_id="12345"
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
update_callback = (
controller.register.call_args_list[0][0][1]
if controller.register.call_args_list
else None
)
return ComponentData(
controller_data=ControllerData(
controller=controller, update_callback=update_callback
)
)

View File

@ -9,5 +9,5 @@ from .common import ComponentFactory
@pytest.fixture()
def vera_component_factory():
"""Return a factory for initializing the vera component."""
with patch("pyvera.init_controller") as init_controller_mock:
yield ComponentFactory(init_controller_mock)
with patch("pyvera.VeraController") as vera_controller_class_mock:
yield ComponentFactory(vera_controller_class_mock)

View File

@ -1,38 +1,36 @@
"""Vera tests."""
from unittest.mock import MagicMock
from pyvera import VeraBinarySensor
import pyvera as pv
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from .common import ComponentFactory, new_simple_controller_config
async def test_binary_sensor(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device = MagicMock(spec=VeraBinarySensor) # type: VeraBinarySensor
vera_device = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device.device_id = 1
vera_device.vera_device_id = 1
vera_device.name = "dev1"
vera_device.is_tripped = False
entity_id = "binary_sensor.dev1_1"
component_data = await vera_component_factory.configure_component(
hass=hass, devices=(vera_device,)
hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)),
)
controller = component_data.controller
update_callback = controller.register.call_args_list[0][0][1]
update_callback = component_data.controller_data.update_callback
vera_device.is_tripped = False
update_callback(vera_device)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "off"
controller.register.reset_mock()
vera_device.is_tripped = True
update_callback(vera_device)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "on"
controller.register.reset_mock()

View File

@ -1,7 +1,7 @@
"""Vera tests."""
from unittest.mock import MagicMock
from pyvera import CATEGORY_THERMOSTAT, VeraController, VeraThermostat
import pyvera as pv
from homeassistant.components.climate.const import (
FAN_AUTO,
@ -13,17 +13,17 @@ from homeassistant.components.climate.const import (
)
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from .common import ComponentFactory, new_simple_controller_config
async def test_climate(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device = MagicMock(spec=VeraThermostat) # type: VeraThermostat
vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat
vera_device.device_id = 1
vera_device.name = "dev1"
vera_device.category = CATEGORY_THERMOSTAT
vera_device.category = pv.CATEGORY_THERMOSTAT
vera_device.power = 10
vera_device.get_current_temperature.return_value = 71
vera_device.get_hvac_mode.return_value = "Off"
@ -31,10 +31,10 @@ async def test_climate(
entity_id = "climate.dev1_1"
component_data = await vera_component_factory.configure_component(
hass=hass, devices=(vera_device,),
hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)),
)
controller = component_data.controller
update_callback = controller.register.call_args_list[0][0][1]
update_callback = component_data.controller_data.update_callback
assert hass.states.get(entity_id).state == HVAC_MODE_OFF
@ -123,24 +123,26 @@ async def test_climate_f(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device = MagicMock(spec=VeraThermostat) # type: VeraThermostat
vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat
vera_device.device_id = 1
vera_device.name = "dev1"
vera_device.category = CATEGORY_THERMOSTAT
vera_device.category = pv.CATEGORY_THERMOSTAT
vera_device.power = 10
vera_device.get_current_temperature.return_value = 71
vera_device.get_hvac_mode.return_value = "Off"
vera_device.get_current_goal_temperature.return_value = 72
entity_id = "climate.dev1_1"
def setup_callback(controller: VeraController, hass_config: dict) -> None:
def setup_callback(controller: pv.VeraController) -> None:
controller.temperature_units = "F"
component_data = await vera_component_factory.configure_component(
hass=hass, devices=(vera_device,), setup_callback=setup_callback
hass=hass,
controller_config=new_simple_controller_config(
devices=(vera_device,), setup_callback=setup_callback
),
)
controller = component_data.controller
update_callback = controller.register.call_args_list[0][0][1]
update_callback = component_data.controller_data.update_callback
await hass.services.async_call(
"climate", "set_temperature", {"entity_id": entity_id, "temperature": 30},

View File

@ -0,0 +1,159 @@
"""Vera tests."""
from unittest.mock import MagicMock
from mock import patch
from requests.exceptions import RequestException
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.vera import CONF_CONTROLLER, DOMAIN
from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import (
RESULT_TYPE_ABORT,
RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_FORM,
)
from tests.common import MockConfigEntry
async def test_async_step_user_success(hass: HomeAssistant) -> None:
"""Test user step success."""
with patch("pyvera.VeraController") as vera_controller_class_mock:
controller = MagicMock()
controller.refresh_data = MagicMock()
controller.serial_number = "serial_number_0"
vera_controller_class_mock.return_value = controller
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_FORM
assert result["step_id"] == config_entries.SOURCE_USER
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={
CONF_CONTROLLER: "http://127.0.0.1:123/",
CONF_LIGHTS: "12 13",
CONF_EXCLUDE: "14 15",
},
)
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "http://127.0.0.1:123"
assert result["data"] == {
CONF_CONTROLLER: "http://127.0.0.1:123",
CONF_SOURCE: config_entries.SOURCE_USER,
CONF_LIGHTS: ["12", "13"],
CONF_EXCLUDE: ["14", "15"],
}
assert result["result"].unique_id == controller.serial_number
entries = hass.config_entries.async_entries(DOMAIN)
assert entries
async def test_async_step_user_already_configured(hass: HomeAssistant) -> None:
"""Test user step with entry already configured."""
entry = MockConfigEntry(domain=DOMAIN, data={}, options={}, unique_id="12345")
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
async def test_async_step_import_success(hass: HomeAssistant) -> None:
"""Test import step success."""
with patch("pyvera.VeraController") as vera_controller_class_mock:
controller = MagicMock()
controller.refresh_data = MagicMock()
controller.serial_number = "serial_number_1"
vera_controller_class_mock.return_value = controller
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={CONF_CONTROLLER: "http://127.0.0.1:123/"},
)
assert result["type"] == RESULT_TYPE_CREATE_ENTRY
assert result["title"] == "http://127.0.0.1:123"
assert result["data"] == {
CONF_CONTROLLER: "http://127.0.0.1:123",
CONF_SOURCE: config_entries.SOURCE_IMPORT,
}
assert result["result"].unique_id == controller.serial_number
async def test_async_step_import_alredy_setup(hass: HomeAssistant) -> None:
"""Test import step with entry already setup."""
entry = MockConfigEntry(domain=DOMAIN, data={}, options={}, unique_id="12345")
entry.add_to_hass(hass)
with patch("pyvera.VeraController") as vera_controller_class_mock:
controller = MagicMock()
controller.refresh_data = MagicMock()
controller.serial_number = "12345"
vera_controller_class_mock.return_value = controller
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={CONF_CONTROLLER: "http://localhost:445"},
)
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured"
async def test_async_step_finish_error(hass: HomeAssistant) -> None:
"""Test finish step with error."""
with patch("pyvera.VeraController") as vera_controller_class_mock:
controller = MagicMock()
controller.refresh_data = MagicMock(side_effect=RequestException())
vera_controller_class_mock.return_value = controller
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={CONF_CONTROLLER: "http://127.0.0.1:123/"},
)
assert result["type"] == "abort"
assert result["reason"] == "cannot_connect"
assert result["description_placeholders"] == {
"base_url": "http://127.0.0.1:123"
}
async def test_options(hass):
"""Test updating options."""
base_url = "http://127.0.0.1/"
entry = MockConfigEntry(
domain=DOMAIN,
title=base_url,
data={CONF_CONTROLLER: "http://127.0.0.1/"},
options={CONF_LIGHTS: [1, 2, 3]},
)
entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(
entry.entry_id, context={"source": "test"}, data=None
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
user_input={
CONF_LIGHTS: "1,2;3 4 5_6bb7",
CONF_EXCLUDE: "8,9;10 11 12_13bb14",
},
)
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["data"] == {
CONF_LIGHTS: ["1", "2", "3", "4", "5", "6", "7"],
CONF_EXCLUDE: ["8", "9", "10", "11", "12", "13", "14"],
}

View File

@ -1,30 +1,30 @@
"""Vera tests."""
from unittest.mock import MagicMock
from pyvera import CATEGORY_CURTAIN, VeraCurtain
import pyvera as pv
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from .common import ComponentFactory, new_simple_controller_config
async def test_cover(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device = MagicMock(spec=VeraCurtain) # type: VeraCurtain
vera_device = MagicMock(spec=pv.VeraCurtain) # type: pv.VeraCurtain
vera_device.device_id = 1
vera_device.name = "dev1"
vera_device.category = CATEGORY_CURTAIN
vera_device.category = pv.CATEGORY_CURTAIN
vera_device.is_closed = False
vera_device.get_level.return_value = 0
entity_id = "cover.dev1_1"
component_data = await vera_component_factory.configure_component(
hass=hass, devices=(vera_device,),
hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)),
)
controller = component_data.controller
update_callback = controller.register.call_args_list[0][0][1]
update_callback = component_data.controller_data.update_callback
assert hass.states.get(entity_id).state == "closed"
assert hass.states.get(entity_id).attributes["current_position"] == 0

View File

@ -1,78 +1,112 @@
"""Vera tests."""
from unittest.mock import MagicMock
from asynctest import MagicMock
import pyvera as pv
from requests.exceptions import RequestException
from pyvera import (
VeraArmableDevice,
VeraBinarySensor,
VeraController,
VeraCurtain,
VeraDevice,
VeraDimmer,
VeraLock,
VeraScene,
VeraSceneController,
VeraSensor,
VeraSwitch,
VeraThermostat,
)
from homeassistant.components.vera import (
CONF_EXCLUDE,
CONF_LIGHTS,
DOMAIN,
VERA_DEVICES,
)
from homeassistant.components.vera import CONF_CONTROLLER, DOMAIN
from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from .common import ComponentFactory, new_simple_controller_config
def new_vera_device(cls, device_id: int) -> VeraDevice:
"""Create new mocked vera device.."""
vera_device = MagicMock(spec=cls) # type: VeraDevice
vera_device.device_id = device_id
vera_device.name = f"dev${device_id}"
return vera_device
def assert_hass_vera_devices(hass: HomeAssistant, platform: str, arr_len: int) -> None:
"""Assert vera devices are present.."""
assert hass.data[VERA_DEVICES][platform]
assert len(hass.data[VERA_DEVICES][platform]) == arr_len
from tests.common import MockConfigEntry
async def test_init(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
def setup_callback(controller: VeraController, hass_config: dict) -> None:
hass_config[DOMAIN][CONF_EXCLUDE] = [11]
hass_config[DOMAIN][CONF_LIGHTS] = [10]
vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device1.device_id = 1
vera_device1.vera_device_id = 1
vera_device1.name = "first_dev"
vera_device1.is_tripped = False
entity1_id = "binary_sensor.first_dev_1"
await vera_component_factory.configure_component(
hass=hass,
devices=(
new_vera_device(VeraDimmer, 1),
new_vera_device(VeraBinarySensor, 2),
new_vera_device(VeraSensor, 3),
new_vera_device(VeraArmableDevice, 4),
new_vera_device(VeraLock, 5),
new_vera_device(VeraThermostat, 6),
new_vera_device(VeraCurtain, 7),
new_vera_device(VeraSceneController, 8),
new_vera_device(VeraSwitch, 9),
new_vera_device(VeraSwitch, 10),
new_vera_device(VeraSwitch, 11),
controller_config=new_simple_controller_config(
config={CONF_CONTROLLER: "http://127.0.0.1:111"},
config_from_file=False,
serial_number="first_serial",
devices=(vera_device1,),
),
scenes=(MagicMock(spec=VeraScene),),
setup_callback=setup_callback,
)
assert_hass_vera_devices(hass, "light", 2)
assert_hass_vera_devices(hass, "binary_sensor", 1)
assert_hass_vera_devices(hass, "sensor", 2)
assert_hass_vera_devices(hass, "switch", 2)
assert_hass_vera_devices(hass, "lock", 1)
assert_hass_vera_devices(hass, "climate", 1)
assert_hass_vera_devices(hass, "cover", 1)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entry1 = entity_registry.async_get(entity1_id)
assert entry1
async def test_init_from_file(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device1.device_id = 1
vera_device1.vera_device_id = 1
vera_device1.name = "first_dev"
vera_device1.is_tripped = False
entity1_id = "binary_sensor.first_dev_1"
await vera_component_factory.configure_component(
hass=hass,
controller_config=new_simple_controller_config(
config={CONF_CONTROLLER: "http://127.0.0.1:111"},
config_from_file=True,
serial_number="first_serial",
devices=(vera_device1,),
),
)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entry1 = entity_registry.async_get(entity1_id)
assert entry1
async def test_unload(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device1.device_id = 1
vera_device1.vera_device_id = 1
vera_device1.name = "first_dev"
vera_device1.is_tripped = False
await vera_component_factory.configure_component(
hass=hass, controller_config=new_simple_controller_config()
)
entries = hass.config_entries.async_entries(DOMAIN)
assert entries
for config_entry in entries:
assert await hass.config_entries.async_unload(config_entry.entry_id)
assert config_entry.state == ENTRY_STATE_NOT_LOADED
async def test_async_setup_entry_error(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
def setup_callback(controller: pv.VeraController) -> None:
controller.get_devices.side_effect = RequestException()
controller.get_scenes.side_effect = RequestException()
await vera_component_factory.configure_component(
hass=hass,
controller_config=new_simple_controller_config(setup_callback=setup_callback),
)
entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_CONTROLLER: "http://127.0.0.1"},
options={},
unique_id="12345",
)
entry.add_to_hass(hass)
assert not await hass.config_entries.async_setup(entry.entry_id)

View File

@ -1,22 +1,22 @@
"""Vera tests."""
from unittest.mock import MagicMock
from pyvera import CATEGORY_DIMMER, VeraDimmer
import pyvera as pv
from homeassistant.components.light import ATTR_BRIGHTNESS, ATTR_HS_COLOR
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from .common import ComponentFactory, new_simple_controller_config
async def test_light(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device = MagicMock(spec=VeraDimmer) # type: VeraDimmer
vera_device = MagicMock(spec=pv.VeraDimmer) # type: pv.VeraDimmer
vera_device.device_id = 1
vera_device.name = "dev1"
vera_device.category = CATEGORY_DIMMER
vera_device.category = pv.CATEGORY_DIMMER
vera_device.is_switched_on = MagicMock(return_value=False)
vera_device.get_brightness = MagicMock(return_value=0)
vera_device.get_color = MagicMock(return_value=[0, 0, 0])
@ -24,10 +24,10 @@ async def test_light(
entity_id = "light.dev1_1"
component_data = await vera_component_factory.configure_component(
hass=hass, devices=(vera_device,),
hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)),
)
controller = component_data.controller
update_callback = controller.register.call_args_list[0][0][1]
update_callback = component_data.controller_data.update_callback
assert hass.states.get(entity_id).state == "off"

View File

@ -1,30 +1,30 @@
"""Vera tests."""
from unittest.mock import MagicMock
from pyvera import CATEGORY_LOCK, VeraLock
import pyvera as pv
from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from .common import ComponentFactory, new_simple_controller_config
async def test_lock(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device = MagicMock(spec=VeraLock) # type: VeraLock
vera_device = MagicMock(spec=pv.VeraLock) # type: pv.VeraLock
vera_device.device_id = 1
vera_device.name = "dev1"
vera_device.category = CATEGORY_LOCK
vera_device.category = pv.CATEGORY_LOCK
vera_device.is_locked = MagicMock(return_value=False)
entity_id = "lock.dev1_1"
component_data = await vera_component_factory.configure_component(
hass=hass, devices=(vera_device,),
hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)),
)
controller = component_data.controller
update_callback = controller.register.call_args_list[0][0][1]
update_callback = component_data.controller_data.update_callback
assert hass.states.get(entity_id).state == STATE_UNLOCKED

View File

@ -1,24 +1,24 @@
"""Vera tests."""
from unittest.mock import MagicMock
from pyvera import VeraScene
import pyvera as pv
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from .common import ComponentFactory, new_simple_controller_config
async def test_scene(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_scene = MagicMock(spec=VeraScene) # type: VeraScene
vera_scene = MagicMock(spec=pv.VeraScene) # type: pv.VeraScene
vera_scene.scene_id = 1
vera_scene.name = "dev1"
entity_id = "scene.dev1_1"
await vera_component_factory.configure_component(
hass=hass, scenes=(vera_scene,),
hass=hass, controller_config=new_simple_controller_config(scenes=(vera_scene,)),
)
await hass.services.async_call(

View File

@ -2,21 +2,12 @@
from typing import Any, Callable, Tuple
from unittest.mock import MagicMock
from pyvera import (
CATEGORY_HUMIDITY_SENSOR,
CATEGORY_LIGHT_SENSOR,
CATEGORY_POWER_METER,
CATEGORY_SCENE_CONTROLLER,
CATEGORY_TEMPERATURE_SENSOR,
CATEGORY_UV_SENSOR,
VeraController,
VeraSensor,
)
import pyvera as pv
from homeassistant.const import UNIT_PERCENTAGE
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from .common import ComponentFactory, new_simple_controller_config
async def run_sensor_test(
@ -26,10 +17,10 @@ async def run_sensor_test(
class_property: str,
assert_states: Tuple[Tuple[Any, Any]],
assert_unit_of_measurement: str = None,
setup_callback: Callable[[VeraController], None] = None,
setup_callback: Callable[[pv.VeraController], None] = None,
) -> None:
"""Test generic sensor."""
vera_device = MagicMock(spec=VeraSensor) # type: VeraSensor
vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor
vera_device.device_id = 1
vera_device.name = "dev1"
vera_device.category = category
@ -37,10 +28,12 @@ async def run_sensor_test(
entity_id = "sensor.dev1_1"
component_data = await vera_component_factory.configure_component(
hass=hass, devices=(vera_device,), setup_callback=setup_callback
hass=hass,
controller_config=new_simple_controller_config(
devices=(vera_device,), setup_callback=setup_callback
),
)
controller = component_data.controller
update_callback = controller.register.call_args_list[0][0][1]
update_callback = component_data.controller_data.update_callback
for (initial_value, state_value) in assert_states:
setattr(vera_device, class_property, initial_value)
@ -57,13 +50,13 @@ async def test_temperature_sensor_f(
) -> None:
"""Test function."""
def setup_callback(controller: VeraController, hass_config: dict) -> None:
def setup_callback(controller: pv.VeraController) -> None:
controller.temperature_units = "F"
await run_sensor_test(
hass=hass,
vera_component_factory=vera_component_factory,
category=CATEGORY_TEMPERATURE_SENSOR,
category=pv.CATEGORY_TEMPERATURE_SENSOR,
class_property="temperature",
assert_states=(("33", "1"), ("44", "7")),
setup_callback=setup_callback,
@ -77,7 +70,7 @@ async def test_temperature_sensor_c(
await run_sensor_test(
hass=hass,
vera_component_factory=vera_component_factory,
category=CATEGORY_TEMPERATURE_SENSOR,
category=pv.CATEGORY_TEMPERATURE_SENSOR,
class_property="temperature",
assert_states=(("33", "33"), ("44", "44")),
)
@ -90,7 +83,7 @@ async def test_light_sensor(
await run_sensor_test(
hass=hass,
vera_component_factory=vera_component_factory,
category=CATEGORY_LIGHT_SENSOR,
category=pv.CATEGORY_LIGHT_SENSOR,
class_property="light",
assert_states=(("12", "12"), ("13", "13")),
assert_unit_of_measurement="lx",
@ -104,7 +97,7 @@ async def test_uv_sensor(
await run_sensor_test(
hass=hass,
vera_component_factory=vera_component_factory,
category=CATEGORY_UV_SENSOR,
category=pv.CATEGORY_UV_SENSOR,
class_property="light",
assert_states=(("12", "12"), ("13", "13")),
assert_unit_of_measurement="level",
@ -118,7 +111,7 @@ async def test_humidity_sensor(
await run_sensor_test(
hass=hass,
vera_component_factory=vera_component_factory,
category=CATEGORY_HUMIDITY_SENSOR,
category=pv.CATEGORY_HUMIDITY_SENSOR,
class_property="humidity",
assert_states=(("12", "12"), ("13", "13")),
assert_unit_of_measurement=UNIT_PERCENTAGE,
@ -132,7 +125,7 @@ async def test_power_meter_sensor(
await run_sensor_test(
hass=hass,
vera_component_factory=vera_component_factory,
category=CATEGORY_POWER_METER,
category=pv.CATEGORY_POWER_METER,
class_property="power",
assert_states=(("12", "12"), ("13", "13")),
assert_unit_of_measurement="watts",
@ -144,7 +137,7 @@ async def test_trippable_sensor(
) -> None:
"""Test function."""
def setup_callback(controller: VeraController, hass_config: dict) -> None:
def setup_callback(controller: pv.VeraController) -> None:
controller.get_devices()[0].is_trippable = True
await run_sensor_test(
@ -162,7 +155,7 @@ async def test_unknown_sensor(
) -> None:
"""Test function."""
def setup_callback(controller: VeraController, hass_config: dict) -> None:
def setup_callback(controller: pv.VeraController) -> None:
controller.get_devices()[0].is_trippable = False
await run_sensor_test(
@ -179,21 +172,21 @@ async def test_scene_controller_sensor(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device = MagicMock(spec=VeraSensor) # type: VeraSensor
vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor
vera_device.device_id = 1
vera_device.name = "dev1"
vera_device.category = CATEGORY_SCENE_CONTROLLER
vera_device.category = pv.CATEGORY_SCENE_CONTROLLER
vera_device.get_last_scene_id = MagicMock(return_value="id0")
vera_device.get_last_scene_time = MagicMock(return_value="0000")
entity_id = "sensor.dev1_1"
component_data = await vera_component_factory.configure_component(
hass=hass, devices=(vera_device,)
hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)),
)
controller = component_data.controller
update_callback = controller.register.call_args_list[0][0][1]
update_callback = component_data.controller_data.update_callback
vera_device.get_last_scene_time = "1111"
vera_device.get_last_scene_time.return_value = "1111"
update_callback(vera_device)
await hass.async_block_till_done()
assert hass.states.get(entity_id).state == "id0"

View File

@ -1,29 +1,29 @@
"""Vera tests."""
from unittest.mock import MagicMock
from pyvera import CATEGORY_SWITCH, VeraSwitch
import pyvera as pv
from homeassistant.core import HomeAssistant
from .common import ComponentFactory
from .common import ComponentFactory, new_simple_controller_config
async def test_switch(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test function."""
vera_device = MagicMock(spec=VeraSwitch) # type: VeraSwitch
vera_device = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch
vera_device.device_id = 1
vera_device.name = "dev1"
vera_device.category = CATEGORY_SWITCH
vera_device.category = pv.CATEGORY_SWITCH
vera_device.is_switched_on = MagicMock(return_value=False)
entity_id = "switch.dev1_1"
component_data = await vera_component_factory.configure_component(
hass=hass, devices=(vera_device,),
hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)),
)
controller = component_data.controller
update_callback = controller.register.call_args_list[0][0][1]
update_callback = component_data.controller_data.update_callback
assert hass.states.get(entity_id).state == "off"