Add support for multiple vera controller hubs (#33613)

This commit is contained in:
Robert Van Gorkom 2020-09-14 20:06:52 -07:00 committed by GitHub
parent 938e06c00e
commit 903afb62d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 323 additions and 149 deletions

View file

@ -2,6 +2,7 @@
import asyncio import asyncio
from collections import defaultdict from collections import defaultdict
import logging import logging
from typing import Type
import pyvera as veraApi import pyvera as veraApi
from requests.exceptions import RequestException from requests.exceptions import RequestException
@ -19,17 +20,25 @@ from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_STOP,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import convert, slugify from homeassistant.util import convert, slugify
from homeassistant.util.dt import utc_from_timestamp from homeassistant.util.dt import utc_from_timestamp
from .common import ControllerData, SubscriptionRegistry, get_configured_platforms from .common import (
ControllerData,
SubscriptionRegistry,
get_configured_platforms,
get_controller_data,
set_controller_data,
)
from .config_flow import fix_device_id_list, new_options from .config_flow import fix_device_id_list, new_options
from .const import ( from .const import (
ATTR_CURRENT_ENERGY_KWH, ATTR_CURRENT_ENERGY_KWH,
ATTR_CURRENT_POWER_W, ATTR_CURRENT_POWER_W,
CONF_CONTROLLER, CONF_CONTROLLER,
CONF_LEGACY_UNIQUE_ID,
DOMAIN, DOMAIN,
VERA_ID_FORMAT, VERA_ID_FORMAT,
) )
@ -54,6 +63,8 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass: HomeAssistant, base_config: dict) -> bool: async def async_setup(hass: HomeAssistant, base_config: dict) -> bool:
"""Set up for Vera controllers.""" """Set up for Vera controllers."""
hass.data[DOMAIN] = {}
config = base_config.get(DOMAIN) config = base_config.get(DOMAIN)
if not config: if not config:
@ -107,10 +118,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
all_devices = await hass.async_add_executor_job(controller.get_devices) all_devices = await hass.async_add_executor_job(controller.get_devices)
all_scenes = await hass.async_add_executor_job(controller.get_scenes) all_scenes = await hass.async_add_executor_job(controller.get_scenes)
except RequestException: except RequestException as exception:
# There was a network related error connecting to the Vera controller. # There was a network related error connecting to the Vera controller.
_LOGGER.exception("Error communicating with Vera API") _LOGGER.exception("Error communicating with Vera API")
return False raise ConfigEntryNotReady from exception
# Exclude devices unwanted by user. # Exclude devices unwanted by user.
devices = [device for device in all_devices if device.device_id not in exclude_ids] devices = [device for device in all_devices if device.device_id not in exclude_ids]
@ -118,20 +129,21 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
vera_devices = defaultdict(list) vera_devices = defaultdict(list)
for device in devices: for device in devices:
device_type = map_vera_device(device, light_ids) device_type = map_vera_device(device, light_ids)
if device_type is None: if device_type is not None:
continue vera_devices[device_type].append(device)
vera_devices[device_type].append(device)
vera_scenes = [] vera_scenes = []
for scene in all_scenes: for scene in all_scenes:
vera_scenes.append(scene) vera_scenes.append(scene)
controller_data = ControllerData( controller_data = ControllerData(
controller=controller, devices=vera_devices, scenes=vera_scenes controller=controller,
devices=vera_devices,
scenes=vera_scenes,
config_entry=config_entry,
) )
hass.data[DOMAIN] = controller_data set_controller_data(hass, config_entry, controller_data)
# Forward the config data to the necessary platforms. # Forward the config data to the necessary platforms.
for platform in get_configured_platforms(controller_data): for platform in get_configured_platforms(controller_data):
@ -144,7 +156,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Unload Withings config entry.""" """Unload Withings config entry."""
controller_data: ControllerData = hass.data[DOMAIN] controller_data: ControllerData = get_controller_data(hass, config_entry)
tasks = [ tasks = [
hass.config_entries.async_forward_entry_unload(config_entry, platform) hass.config_entries.async_forward_entry_unload(config_entry, platform)
@ -159,43 +171,52 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
def map_vera_device(vera_device, remap): def map_vera_device(vera_device, remap):
"""Map vera classes to Home Assistant types.""" """Map vera classes to Home Assistant types."""
if isinstance(vera_device, veraApi.VeraDimmer): type_map = {
return "light" veraApi.VeraDimmer: "light",
if isinstance(vera_device, veraApi.VeraBinarySensor): veraApi.VeraBinarySensor: "binary_sensor",
return "binary_sensor" veraApi.VeraSensor: "sensor",
if isinstance(vera_device, veraApi.VeraSensor): veraApi.VeraArmableDevice: "switch",
return "sensor" veraApi.VeraLock: "lock",
if isinstance(vera_device, veraApi.VeraArmableDevice): veraApi.VeraThermostat: "climate",
return "switch" veraApi.VeraCurtain: "cover",
if isinstance(vera_device, veraApi.VeraLock): veraApi.VeraSceneController: "sensor",
return "lock" veraApi.VeraSwitch: "switch",
if isinstance(vera_device, veraApi.VeraThermostat): }
return "climate"
if isinstance(vera_device, veraApi.VeraCurtain): def map_special_case(instance_class: Type, entity_type: str) -> str:
return "cover" if instance_class is veraApi.VeraSwitch and vera_device.device_id in remap:
if isinstance(vera_device, veraApi.VeraSceneController):
return "sensor"
if isinstance(vera_device, veraApi.VeraSwitch):
if vera_device.device_id in remap:
return "light" return "light"
return "switch" return entity_type
return None
return next(
iter(
map_special_case(instance_class, entity_type)
for instance_class, entity_type in type_map.items()
if isinstance(vera_device, instance_class)
),
None,
)
class VeraDevice(Entity): class VeraDevice(Entity):
"""Representation of a Vera device entity.""" """Representation of a Vera device entity."""
def __init__(self, vera_device, controller): def __init__(self, vera_device, controller_data: ControllerData):
"""Initialize the device.""" """Initialize the device."""
self.vera_device = vera_device self.vera_device = vera_device
self.controller = controller self.controller = controller_data.controller
self._name = self.vera_device.name self._name = self.vera_device.name
# Append device id to prevent name clashes in HA. # Append device id to prevent name clashes in HA.
self.vera_id = VERA_ID_FORMAT.format( self.vera_id = VERA_ID_FORMAT.format(
slugify(vera_device.name), vera_device.device_id slugify(vera_device.name), vera_device.vera_device_id
) )
if controller_data.config_entry.data.get(CONF_LEGACY_UNIQUE_ID):
self._unique_id = str(self.vera_device.vera_device_id)
else:
self._unique_id = f"vera_{controller_data.config_entry.unique_id}_{self.vera_device.vera_device_id}"
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Subscribe to updates.""" """Subscribe to updates."""
self.controller.register(self.vera_device, self._update_callback) self.controller.register(self.vera_device, self._update_callback)
@ -254,4 +275,4 @@ class VeraDevice(Entity):
The Vera assigns a unique and immutable ID number to each device. The Vera assigns a unique and immutable ID number to each device.
""" """
return str(self.vera_device.vera_device_id) return self._unique_id

View file

@ -12,7 +12,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from . import VeraDevice from . import VeraDevice
from .const import DOMAIN from .common import ControllerData, get_controller_data
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -23,10 +23,10 @@ async def async_setup_entry(
async_add_entities: Callable[[List[Entity], bool], None], async_add_entities: Callable[[List[Entity], bool], None],
) -> None: ) -> None:
"""Set up the sensor config entry.""" """Set up the sensor config entry."""
controller_data = hass.data[DOMAIN] controller_data = get_controller_data(hass, entry)
async_add_entities( async_add_entities(
[ [
VeraBinarySensor(device, controller_data.controller) VeraBinarySensor(device, controller_data)
for device in controller_data.devices.get(PLATFORM_DOMAIN) for device in controller_data.devices.get(PLATFORM_DOMAIN)
] ]
) )
@ -35,10 +35,10 @@ async def async_setup_entry(
class VeraBinarySensor(VeraDevice, BinarySensorEntity): class VeraBinarySensor(VeraDevice, BinarySensorEntity):
"""Representation of a Vera Binary Sensor.""" """Representation of a Vera Binary Sensor."""
def __init__(self, vera_device, controller): def __init__(self, vera_device, controller_data: ControllerData):
"""Initialize the binary_sensor.""" """Initialize the binary_sensor."""
self._state = False self._state = False
VeraDevice.__init__(self, vera_device, controller) VeraDevice.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property @property

View file

@ -24,7 +24,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import convert from homeassistant.util import convert
from . import VeraDevice from . import VeraDevice
from .const import DOMAIN from .common import ControllerData, get_controller_data
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -40,10 +40,10 @@ async def async_setup_entry(
async_add_entities: Callable[[List[Entity], bool], None], async_add_entities: Callable[[List[Entity], bool], None],
) -> None: ) -> None:
"""Set up the sensor config entry.""" """Set up the sensor config entry."""
controller_data = hass.data[DOMAIN] controller_data = get_controller_data(hass, entry)
async_add_entities( async_add_entities(
[ [
VeraThermostat(device, controller_data.controller) VeraThermostat(device, controller_data)
for device in controller_data.devices.get(PLATFORM_DOMAIN) for device in controller_data.devices.get(PLATFORM_DOMAIN)
] ]
) )
@ -52,9 +52,9 @@ async def async_setup_entry(
class VeraThermostat(VeraDevice, ClimateEntity): class VeraThermostat(VeraDevice, ClimateEntity):
"""Representation of a Vera Thermostat.""" """Representation of a Vera Thermostat."""
def __init__(self, vera_device, controller): def __init__(self, vera_device, controller_data: ControllerData):
"""Initialize the Vera device.""" """Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller) VeraDevice.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property @property

View file

@ -5,9 +5,12 @@ from typing import DefaultDict, List, NamedTuple, Set
import pyvera as pv import pyvera as pv
from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.event import call_later from homeassistant.helpers.event import call_later
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -17,6 +20,7 @@ class ControllerData(NamedTuple):
controller: pv.VeraController controller: pv.VeraController
devices: DefaultDict[str, List[pv.VeraDevice]] devices: DefaultDict[str, List[pv.VeraDevice]]
scenes: List[pv.VeraScene] scenes: List[pv.VeraScene]
config_entry: ConfigEntry
def get_configured_platforms(controller_data: ControllerData) -> Set[str]: def get_configured_platforms(controller_data: ControllerData) -> Set[str]:
@ -31,6 +35,20 @@ def get_configured_platforms(controller_data: ControllerData) -> Set[str]:
return set(platforms) return set(platforms)
def get_controller_data(
hass: HomeAssistant, config_entry: ConfigEntry
) -> ControllerData:
"""Get controller data from hass data."""
return hass.data[DOMAIN][config_entry.entry_id]
def set_controller_data(
hass: HomeAssistant, config_entry: ConfigEntry, data: ControllerData
) -> None:
"""Set controller data in hass data."""
hass.data[DOMAIN][config_entry.entry_id] = data
class SubscriptionRegistry(pv.AbstractSubscriptionRegistry): class SubscriptionRegistry(pv.AbstractSubscriptionRegistry):
"""Manages polling for data from vera.""" """Manages polling for data from vera."""

View file

@ -10,8 +10,13 @@ import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.entity_registry import EntityRegistry
from .const import CONF_CONTROLLER, DOMAIN from .const import ( # pylint: disable=unused-import
CONF_CONTROLLER,
CONF_LEGACY_UNIQUE_ID,
DOMAIN,
)
LIST_REGEX = re.compile("[^0-9]+") LIST_REGEX = re.compile("[^0-9]+")
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -92,15 +97,13 @@ class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input: dict = None): async def async_step_user(self, user_input: dict = None):
"""Handle user initiated flow.""" """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: if user_input is not None:
return await self.async_step_finish( return await self.async_step_finish(
{ {
**user_input, **user_input,
**options_data(user_input), **options_data(user_input),
**{CONF_SOURCE: config_entries.SOURCE_USER}, **{CONF_SOURCE: config_entries.SOURCE_USER},
**{CONF_LEGACY_UNIQUE_ID: False},
} }
) )
@ -113,8 +116,29 @@ class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_import(self, config: dict): async def async_step_import(self, config: dict):
"""Handle a flow initialized by import.""" """Handle a flow initialized by import."""
# If there are entities with the legacy unique_id, then this imported config
# should also use the legacy unique_id for entity creation.
entity_registry: EntityRegistry = (
await self.hass.helpers.entity_registry.async_get_registry()
)
use_legacy_unique_id = (
len(
[
entry
for entry in entity_registry.entities.values()
if entry.platform == DOMAIN and entry.unique_id.isdigit()
]
)
> 0
)
return await self.async_step_finish( return await self.async_step_finish(
{**config, **{CONF_SOURCE: config_entries.SOURCE_IMPORT}} {
**config,
**{CONF_SOURCE: config_entries.SOURCE_IMPORT},
**{CONF_LEGACY_UNIQUE_ID: use_legacy_unique_id},
}
) )
async def async_step_finish(self, config: dict): async def async_step_finish(self, config: dict):

View file

@ -2,6 +2,7 @@
DOMAIN = "vera" DOMAIN = "vera"
CONF_CONTROLLER = "vera_controller_url" CONF_CONTROLLER = "vera_controller_url"
CONF_LEGACY_UNIQUE_ID = "legacy_unique_id"
VERA_ID_FORMAT = "{}_{}" VERA_ID_FORMAT = "{}_{}"

View file

@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from . import VeraDevice from . import VeraDevice
from .const import DOMAIN from .common import ControllerData, get_controller_data
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -24,10 +24,10 @@ async def async_setup_entry(
async_add_entities: Callable[[List[Entity], bool], None], async_add_entities: Callable[[List[Entity], bool], None],
) -> None: ) -> None:
"""Set up the sensor config entry.""" """Set up the sensor config entry."""
controller_data = hass.data[DOMAIN] controller_data = get_controller_data(hass, entry)
async_add_entities( async_add_entities(
[ [
VeraCover(device, controller_data.controller) VeraCover(device, controller_data)
for device in controller_data.devices.get(PLATFORM_DOMAIN) for device in controller_data.devices.get(PLATFORM_DOMAIN)
] ]
) )
@ -36,9 +36,9 @@ async def async_setup_entry(
class VeraCover(VeraDevice, CoverEntity): class VeraCover(VeraDevice, CoverEntity):
"""Representation a Vera Cover.""" """Representation a Vera Cover."""
def __init__(self, vera_device, controller): def __init__(self, vera_device, controller_data: ControllerData):
"""Initialize the Vera device.""" """Initialize the Vera device."""
VeraDevice.__init__(self, vera_device, controller) VeraDevice.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property @property

View file

@ -17,7 +17,7 @@ from homeassistant.helpers.entity import Entity
import homeassistant.util.color as color_util import homeassistant.util.color as color_util
from . import VeraDevice from . import VeraDevice
from .const import DOMAIN from .common import ControllerData, get_controller_data
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,10 +28,10 @@ async def async_setup_entry(
async_add_entities: Callable[[List[Entity], bool], None], async_add_entities: Callable[[List[Entity], bool], None],
) -> None: ) -> None:
"""Set up the sensor config entry.""" """Set up the sensor config entry."""
controller_data = hass.data[DOMAIN] controller_data = get_controller_data(hass, entry)
async_add_entities( async_add_entities(
[ [
VeraLight(device, controller_data.controller) VeraLight(device, controller_data)
for device in controller_data.devices.get(PLATFORM_DOMAIN) for device in controller_data.devices.get(PLATFORM_DOMAIN)
] ]
) )
@ -40,12 +40,12 @@ async def async_setup_entry(
class VeraLight(VeraDevice, LightEntity): class VeraLight(VeraDevice, LightEntity):
"""Representation of a Vera Light, including dimmable.""" """Representation of a Vera Light, including dimmable."""
def __init__(self, vera_device, controller): def __init__(self, vera_device, controller_data: ControllerData):
"""Initialize the light.""" """Initialize the light."""
self._state = False self._state = False
self._color = None self._color = None
self._brightness = None self._brightness = None
VeraDevice.__init__(self, vera_device, controller) VeraDevice.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property @property

View file

@ -13,7 +13,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from . import VeraDevice from . import VeraDevice
from .const import DOMAIN from .common import ControllerData, get_controller_data
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -27,10 +27,10 @@ async def async_setup_entry(
async_add_entities: Callable[[List[Entity], bool], None], async_add_entities: Callable[[List[Entity], bool], None],
) -> None: ) -> None:
"""Set up the sensor config entry.""" """Set up the sensor config entry."""
controller_data = hass.data[DOMAIN] controller_data = get_controller_data(hass, entry)
async_add_entities( async_add_entities(
[ [
VeraLock(device, controller_data.controller) VeraLock(device, controller_data)
for device in controller_data.devices.get(PLATFORM_DOMAIN) for device in controller_data.devices.get(PLATFORM_DOMAIN)
] ]
) )
@ -39,10 +39,10 @@ async def async_setup_entry(
class VeraLock(VeraDevice, LockEntity): class VeraLock(VeraDevice, LockEntity):
"""Representation of a Vera lock.""" """Representation of a Vera lock."""
def __init__(self, vera_device, controller): def __init__(self, vera_device, controller_data: ControllerData):
"""Initialize the Vera device.""" """Initialize the Vera device."""
self._state = None self._state = None
VeraDevice.__init__(self, vera_device, controller) VeraDevice.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
def lock(self, **kwargs): def lock(self, **kwargs):

View file

@ -8,7 +8,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from homeassistant.util import slugify from homeassistant.util import slugify
from .const import DOMAIN, VERA_ID_FORMAT from .common import ControllerData, get_controller_data
from .const import VERA_ID_FORMAT
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -19,22 +20,19 @@ async def async_setup_entry(
async_add_entities: Callable[[List[Entity], bool], None], async_add_entities: Callable[[List[Entity], bool], None],
) -> None: ) -> None:
"""Set up the sensor config entry.""" """Set up the sensor config entry."""
controller_data = hass.data[DOMAIN] controller_data = get_controller_data(hass, entry)
async_add_entities( async_add_entities(
[ [VeraScene(device, controller_data) for device in controller_data.scenes]
VeraScene(device, controller_data.controller)
for device in controller_data.scenes
]
) )
class VeraScene(Scene): class VeraScene(Scene):
"""Representation of a Vera scene entity.""" """Representation of a Vera scene entity."""
def __init__(self, vera_scene, controller): def __init__(self, vera_scene, controller_data: ControllerData):
"""Initialize the scene.""" """Initialize the scene."""
self.vera_scene = vera_scene self.vera_scene = vera_scene
self.controller = controller self.controller = controller_data.controller
self._name = self.vera_scene.name self._name = self.vera_scene.name
# Append device id to prevent name clashes in HA. # Append device id to prevent name clashes in HA.

View file

@ -13,7 +13,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import convert from homeassistant.util import convert
from . import VeraDevice from . import VeraDevice
from .const import DOMAIN from .common import ControllerData, get_controller_data
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -26,10 +26,10 @@ async def async_setup_entry(
async_add_entities: Callable[[List[Entity], bool], None], async_add_entities: Callable[[List[Entity], bool], None],
) -> None: ) -> None:
"""Set up the sensor config entry.""" """Set up the sensor config entry."""
controller_data = hass.data[DOMAIN] controller_data = get_controller_data(hass, entry)
async_add_entities( async_add_entities(
[ [
VeraSensor(device, controller_data.controller) VeraSensor(device, controller_data)
for device in controller_data.devices.get(PLATFORM_DOMAIN) for device in controller_data.devices.get(PLATFORM_DOMAIN)
] ]
) )
@ -38,12 +38,12 @@ async def async_setup_entry(
class VeraSensor(VeraDevice, Entity): class VeraSensor(VeraDevice, Entity):
"""Representation of a Vera Sensor.""" """Representation of a Vera Sensor."""
def __init__(self, vera_device, controller): def __init__(self, vera_device, controller_data: ControllerData):
"""Initialize the sensor.""" """Initialize the sensor."""
self.current_value = None self.current_value = None
self._temperature_units = None self._temperature_units = None
self.last_changed_time = None self.last_changed_time = None
VeraDevice.__init__(self, vera_device, controller) VeraDevice.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
@property @property

View file

@ -1,7 +1,6 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "A controller is already configured.",
"cannot_connect": "Could not connect to controller with url {base_url}" "cannot_connect": "Could not connect to controller with url {base_url}"
}, },
"step": { "step": {

View file

@ -13,7 +13,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.util import convert from homeassistant.util import convert
from . import VeraDevice from . import VeraDevice
from .const import DOMAIN from .common import ControllerData, get_controller_data
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -24,10 +24,10 @@ async def async_setup_entry(
async_add_entities: Callable[[List[Entity], bool], None], async_add_entities: Callable[[List[Entity], bool], None],
) -> None: ) -> None:
"""Set up the sensor config entry.""" """Set up the sensor config entry."""
controller_data = hass.data[DOMAIN] controller_data = get_controller_data(hass, entry)
async_add_entities( async_add_entities(
[ [
VeraSwitch(device, controller_data.controller) VeraSwitch(device, controller_data)
for device in controller_data.devices.get(PLATFORM_DOMAIN) for device in controller_data.devices.get(PLATFORM_DOMAIN)
] ]
) )
@ -36,10 +36,10 @@ async def async_setup_entry(
class VeraSwitch(VeraDevice, SwitchEntity): class VeraSwitch(VeraDevice, SwitchEntity):
"""Representation of a Vera Switch.""" """Representation of a Vera Switch."""
def __init__(self, vera_device, controller): def __init__(self, vera_device, controller_data: ControllerData):
"""Initialize the Vera device.""" """Initialize the Vera device."""
self._state = False self._state = False
VeraDevice.__init__(self, vera_device, controller) VeraDevice.__init__(self, vera_device, controller_data)
self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id)
def turn_on(self, **kwargs): def turn_on(self, **kwargs):

View file

@ -1,10 +1,15 @@
"""Common code for tests.""" """Common code for tests."""
from enum import Enum
from typing import Callable, Dict, NamedTuple, Tuple from typing import Callable, Dict, NamedTuple, Tuple
import pyvera as pv import pyvera as pv
from homeassistant.components.vera.const import CONF_CONTROLLER, DOMAIN from homeassistant import config_entries
from homeassistant.components.vera.const import (
CONF_CONTROLLER,
CONF_LEGACY_UNIQUE_ID,
DOMAIN,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -24,7 +29,15 @@ class ControllerData(NamedTuple):
class ComponentData(NamedTuple): class ComponentData(NamedTuple):
"""Test data about the vera component.""" """Test data about the vera component."""
controller_data: ControllerData controller_data: Tuple[ControllerData]
class ConfigSource(Enum):
"""Source of configuration."""
FILE = "file"
CONFIG_FLOW = "config_flow"
CONFIG_ENTRY = "config_entry"
class ControllerConfig(NamedTuple): class ControllerConfig(NamedTuple):
@ -32,31 +45,34 @@ class ControllerConfig(NamedTuple):
config: Dict config: Dict
options: Dict options: Dict
config_from_file: bool config_source: ConfigSource
serial_number: str serial_number: str
devices: Tuple[pv.VeraDevice, ...] devices: Tuple[pv.VeraDevice, ...]
scenes: Tuple[pv.VeraScene, ...] scenes: Tuple[pv.VeraScene, ...]
setup_callback: SetupCallback setup_callback: SetupCallback
legacy_entity_unique_id: bool
def new_simple_controller_config( def new_simple_controller_config(
config: dict = None, config: dict = None,
options: dict = None, options: dict = None,
config_from_file=False, config_source=ConfigSource.CONFIG_FLOW,
serial_number="1111", serial_number="1111",
devices: Tuple[pv.VeraDevice, ...] = (), devices: Tuple[pv.VeraDevice, ...] = (),
scenes: Tuple[pv.VeraScene, ...] = (), scenes: Tuple[pv.VeraScene, ...] = (),
setup_callback: SetupCallback = None, setup_callback: SetupCallback = None,
legacy_entity_unique_id=False,
) -> ControllerConfig: ) -> ControllerConfig:
"""Create simple contorller config.""" """Create simple contorller config."""
return ControllerConfig( return ControllerConfig(
config=config or {CONF_CONTROLLER: "http://127.0.0.1:123"}, config=config or {CONF_CONTROLLER: "http://127.0.0.1:123"},
options=options, options=options,
config_from_file=config_from_file, config_source=config_source,
serial_number=serial_number, serial_number=serial_number,
devices=devices, devices=devices,
scenes=scenes, scenes=scenes,
setup_callback=setup_callback, setup_callback=setup_callback,
legacy_entity_unique_id=legacy_entity_unique_id,
) )
@ -68,14 +84,38 @@ class ComponentFactory:
self.vera_controller_class_mock = vera_controller_class_mock self.vera_controller_class_mock = vera_controller_class_mock
async def configure_component( async def configure_component(
self, hass: HomeAssistant, controller_config: ControllerConfig self,
hass: HomeAssistant,
controller_config: ControllerConfig = None,
controller_configs: Tuple[ControllerConfig] = (),
) -> ComponentData: ) -> ComponentData:
"""Configure the component with multiple specific mock data."""
configs = list(controller_configs)
if controller_config:
configs.append(controller_config)
return ComponentData(
controller_data=tuple(
[
await self._configure_component(hass, controller_config)
for controller_config in configs
]
)
)
async def _configure_component(
self, hass: HomeAssistant, controller_config: ControllerConfig
) -> ControllerData:
"""Configure the component with specific mock data.""" """Configure the component with specific mock data."""
component_config = { component_config = {
**(controller_config.config or {}), **(controller_config.config or {}),
**(controller_config.options or {}), **(controller_config.options or {}),
} }
if controller_config.legacy_entity_unique_id:
component_config[CONF_LEGACY_UNIQUE_ID] = True
controller = MagicMock(spec=pv.VeraController) # type: pv.VeraController controller = MagicMock(spec=pv.VeraController) # type: pv.VeraController
controller.base_url = component_config.get(CONF_CONTROLLER) controller.base_url = component_config.get(CONF_CONTROLLER)
controller.register = MagicMock() controller.register = MagicMock()
@ -101,7 +141,7 @@ class ComponentFactory:
hass_config = {} hass_config = {}
# Setup component through config file import. # Setup component through config file import.
if controller_config.config_from_file: if controller_config.config_source == ConfigSource.FILE:
hass_config[DOMAIN] = component_config hass_config[DOMAIN] = component_config
# Setup Home Assistant. # Setup Home Assistant.
@ -109,9 +149,21 @@ class ComponentFactory:
await hass.async_block_till_done() await hass.async_block_till_done()
# Setup component through config flow. # Setup component through config flow.
if not controller_config.config_from_file: if controller_config.config_source == ConfigSource.CONFIG_FLOW:
await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_USER},
data=component_config,
)
await hass.async_block_till_done()
# Setup component directly from config entry.
if controller_config.config_source == ConfigSource.CONFIG_ENTRY:
entry = MockConfigEntry( entry = MockConfigEntry(
domain=DOMAIN, data=component_config, options={}, unique_id="12345" domain=DOMAIN,
data=controller_config.config,
options=controller_config.options,
unique_id="12345",
) )
entry.add_to_hass(hass) entry.add_to_hass(hass)
@ -124,8 +176,4 @@ class ComponentFactory:
else None else None
) )
return ComponentData( return ControllerData(controller=controller, update_callback=update_callback)
controller_data=ControllerData(
controller=controller, update_callback=update_callback
)
)

View file

@ -14,7 +14,7 @@ async def test_binary_sensor(
"""Test function.""" """Test function."""
vera_device = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor vera_device = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device.device_id = 1 vera_device.device_id = 1
vera_device.vera_device_id = 1 vera_device.vera_device_id = vera_device.device_id
vera_device.name = "dev1" vera_device.name = "dev1"
vera_device.is_tripped = False vera_device.is_tripped = False
entity_id = "binary_sensor.dev1_1" entity_id = "binary_sensor.dev1_1"
@ -23,7 +23,7 @@ async def test_binary_sensor(
hass=hass, hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)), controller_config=new_simple_controller_config(devices=(vera_device,)),
) )
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
vera_device.is_tripped = False vera_device.is_tripped = False
update_callback(vera_device) update_callback(vera_device)

View file

@ -22,6 +22,7 @@ async def test_climate(
"""Test function.""" """Test function."""
vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat
vera_device.device_id = 1 vera_device.device_id = 1
vera_device.vera_device_id = vera_device.device_id
vera_device.name = "dev1" vera_device.name = "dev1"
vera_device.category = pv.CATEGORY_THERMOSTAT vera_device.category = pv.CATEGORY_THERMOSTAT
vera_device.power = 10 vera_device.power = 10
@ -34,7 +35,7 @@ async def test_climate(
hass=hass, hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)), controller_config=new_simple_controller_config(devices=(vera_device,)),
) )
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
assert hass.states.get(entity_id).state == HVAC_MODE_OFF assert hass.states.get(entity_id).state == HVAC_MODE_OFF
@ -131,6 +132,7 @@ async def test_climate_f(
"""Test function.""" """Test function."""
vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat vera_device = MagicMock(spec=pv.VeraThermostat) # type: pv.VeraThermostat
vera_device.device_id = 1 vera_device.device_id = 1
vera_device.vera_device_id = vera_device.device_id
vera_device.name = "dev1" vera_device.name = "dev1"
vera_device.category = pv.CATEGORY_THERMOSTAT vera_device.category = pv.CATEGORY_THERMOSTAT
vera_device.power = 10 vera_device.power = 10
@ -148,7 +150,7 @@ async def test_climate_f(
devices=(vera_device,), setup_callback=setup_callback devices=(vera_device,), setup_callback=setup_callback
), ),
) )
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
await hass.services.async_call( await hass.services.async_call(
"climate", "climate",

View file

@ -2,17 +2,13 @@
from requests.exceptions import RequestException from requests.exceptions import RequestException
from homeassistant import config_entries, data_entry_flow from homeassistant import config_entries, data_entry_flow
from homeassistant.components.vera import CONF_CONTROLLER, DOMAIN from homeassistant.components.vera import CONF_CONTROLLER, CONF_LEGACY_UNIQUE_ID, DOMAIN
from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE from homeassistant.const import CONF_EXCLUDE, CONF_LIGHTS, CONF_SOURCE
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import ( from homeassistant.data_entry_flow import RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_FORM
RESULT_TYPE_ABORT,
RESULT_TYPE_CREATE_ENTRY,
RESULT_TYPE_FORM,
)
from tests.async_mock import MagicMock, patch from tests.async_mock import MagicMock, patch
from tests.common import MockConfigEntry from tests.common import MockConfigEntry, mock_registry
async def test_async_step_user_success(hass: HomeAssistant) -> None: async def test_async_step_user_success(hass: HomeAssistant) -> None:
@ -44,6 +40,7 @@ async def test_async_step_user_success(hass: HomeAssistant) -> None:
CONF_SOURCE: config_entries.SOURCE_USER, CONF_SOURCE: config_entries.SOURCE_USER,
CONF_LIGHTS: [12, 13], CONF_LIGHTS: [12, 13],
CONF_EXCLUDE: [14, 15], CONF_EXCLUDE: [14, 15],
CONF_LEGACY_UNIQUE_ID: False,
} }
assert result["result"].unique_id == controller.serial_number assert result["result"].unique_id == controller.serial_number
@ -51,18 +48,6 @@ async def test_async_step_user_success(hass: HomeAssistant) -> None:
assert entries 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: async def test_async_step_import_success(hass: HomeAssistant) -> None:
"""Test import step success.""" """Test import step success."""
with patch("pyvera.VeraController") as vera_controller_class_mock: with patch("pyvera.VeraController") as vera_controller_class_mock:
@ -82,28 +67,40 @@ async def test_async_step_import_success(hass: HomeAssistant) -> None:
assert result["data"] == { assert result["data"] == {
CONF_CONTROLLER: "http://127.0.0.1:123", CONF_CONTROLLER: "http://127.0.0.1:123",
CONF_SOURCE: config_entries.SOURCE_IMPORT, CONF_SOURCE: config_entries.SOURCE_IMPORT,
CONF_LEGACY_UNIQUE_ID: False,
} }
assert result["result"].unique_id == controller.serial_number assert result["result"].unique_id == controller.serial_number
async def test_async_step_import_alredy_setup(hass: HomeAssistant) -> None: async def test_async_step_import_success_with_legacy_unique_id(
"""Test import step with entry already setup.""" hass: HomeAssistant,
entry = MockConfigEntry(domain=DOMAIN, data={}, options={}, unique_id="12345") ) -> None:
entry.add_to_hass(hass) """Test import step success with legacy unique id."""
entity_registry = mock_registry(hass)
entity_registry.async_get_or_create(
domain="switch", platform=DOMAIN, unique_id="12"
)
with patch("pyvera.VeraController") as vera_controller_class_mock: with patch("pyvera.VeraController") as vera_controller_class_mock:
controller = MagicMock() controller = MagicMock()
controller.refresh_data = MagicMock() controller.refresh_data = MagicMock()
controller.serial_number = "12345" controller.serial_number = "serial_number_1"
vera_controller_class_mock.return_value = controller vera_controller_class_mock.return_value = controller
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_IMPORT}, context={"source": config_entries.SOURCE_IMPORT},
data={CONF_CONTROLLER: "http://localhost:445"}, data={CONF_CONTROLLER: "http://127.0.0.1:123/"},
) )
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured" 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,
CONF_LEGACY_UNIQUE_ID: True,
}
assert result["result"].unique_id == controller.serial_number
async def test_async_step_finish_error(hass: HomeAssistant) -> None: async def test_async_step_finish_error(hass: HomeAssistant) -> None:

View file

@ -14,6 +14,7 @@ async def test_cover(
"""Test function.""" """Test function."""
vera_device = MagicMock(spec=pv.VeraCurtain) # type: pv.VeraCurtain vera_device = MagicMock(spec=pv.VeraCurtain) # type: pv.VeraCurtain
vera_device.device_id = 1 vera_device.device_id = 1
vera_device.vera_device_id = vera_device.device_id
vera_device.name = "dev1" vera_device.name = "dev1"
vera_device.category = pv.CATEGORY_CURTAIN vera_device.category = pv.CATEGORY_CURTAIN
vera_device.is_closed = False vera_device.is_closed = False
@ -24,7 +25,7 @@ async def test_cover(
hass=hass, hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)), controller_config=new_simple_controller_config(devices=(vera_device,)),
) )
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
assert hass.states.get(entity_id).state == "closed" assert hass.states.get(entity_id).state == "closed"
assert hass.states.get(entity_id).attributes["current_position"] == 0 assert hass.states.get(entity_id).attributes["current_position"] == 0

View file

@ -12,10 +12,10 @@ from homeassistant.components.vera import (
from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED from homeassistant.config_entries import ENTRY_STATE_NOT_LOADED
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from .common import ComponentFactory, new_simple_controller_config from .common import ComponentFactory, ConfigSource, new_simple_controller_config
from tests.async_mock import MagicMock from tests.async_mock import MagicMock
from tests.common import MockConfigEntry from tests.common import MockConfigEntry, mock_registry
async def test_init( async def test_init(
@ -24,7 +24,7 @@ async def test_init(
"""Test function.""" """Test function."""
vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device1.device_id = 1 vera_device1.device_id = 1
vera_device1.vera_device_id = 1 vera_device1.vera_device_id = vera_device1.device_id
vera_device1.name = "first_dev" vera_device1.name = "first_dev"
vera_device1.is_tripped = False vera_device1.is_tripped = False
entity1_id = "binary_sensor.first_dev_1" entity1_id = "binary_sensor.first_dev_1"
@ -33,7 +33,7 @@ async def test_init(
hass=hass, hass=hass,
controller_config=new_simple_controller_config( controller_config=new_simple_controller_config(
config={CONF_CONTROLLER: "http://127.0.0.1:111"}, config={CONF_CONTROLLER: "http://127.0.0.1:111"},
config_from_file=False, config_source=ConfigSource.CONFIG_FLOW,
serial_number="first_serial", serial_number="first_serial",
devices=(vera_device1,), devices=(vera_device1,),
), ),
@ -41,8 +41,8 @@ async def test_init(
entity_registry = await hass.helpers.entity_registry.async_get_registry() entity_registry = await hass.helpers.entity_registry.async_get_registry()
entry1 = entity_registry.async_get(entity1_id) entry1 = entity_registry.async_get(entity1_id)
assert entry1 assert entry1
assert entry1.unique_id == "vera_first_serial_1"
async def test_init_from_file( async def test_init_from_file(
@ -51,7 +51,7 @@ async def test_init_from_file(
"""Test function.""" """Test function."""
vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device1.device_id = 1 vera_device1.device_id = 1
vera_device1.vera_device_id = 1 vera_device1.vera_device_id = vera_device1.device_id
vera_device1.name = "first_dev" vera_device1.name = "first_dev"
vera_device1.is_tripped = False vera_device1.is_tripped = False
entity1_id = "binary_sensor.first_dev_1" entity1_id = "binary_sensor.first_dev_1"
@ -60,7 +60,7 @@ async def test_init_from_file(
hass=hass, hass=hass,
controller_config=new_simple_controller_config( controller_config=new_simple_controller_config(
config={CONF_CONTROLLER: "http://127.0.0.1:111"}, config={CONF_CONTROLLER: "http://127.0.0.1:111"},
config_from_file=True, config_source=ConfigSource.FILE,
serial_number="first_serial", serial_number="first_serial",
devices=(vera_device1,), devices=(vera_device1,),
), ),
@ -69,6 +69,62 @@ async def test_init_from_file(
entity_registry = await hass.helpers.entity_registry.async_get_registry() entity_registry = await hass.helpers.entity_registry.async_get_registry()
entry1 = entity_registry.async_get(entity1_id) entry1 = entity_registry.async_get(entity1_id)
assert entry1 assert entry1
assert entry1.unique_id == "vera_first_serial_1"
async def test_multiple_controllers_with_legacy_one(
hass: HomeAssistant, vera_component_factory: ComponentFactory
) -> None:
"""Test multiple controllers with one legacy controller."""
vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device1.device_id = 1
vera_device1.vera_device_id = vera_device1.device_id
vera_device1.name = "first_dev"
vera_device1.is_tripped = False
entity1_id = "binary_sensor.first_dev_1"
vera_device2 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device2.device_id = 2
vera_device2.vera_device_id = vera_device2.device_id
vera_device2.name = "second_dev"
vera_device2.is_tripped = False
entity2_id = "binary_sensor.second_dev_2"
# Add existing entity registry entry from previous setup.
entity_registry = mock_registry(hass)
entity_registry.async_get_or_create(
domain="switch", platform=DOMAIN, unique_id="12"
)
await vera_component_factory.configure_component(
hass=hass,
controller_config=new_simple_controller_config(
config={CONF_CONTROLLER: "http://127.0.0.1:111"},
config_source=ConfigSource.FILE,
serial_number="first_serial",
devices=(vera_device1,),
),
)
await vera_component_factory.configure_component(
hass=hass,
controller_config=new_simple_controller_config(
config={CONF_CONTROLLER: "http://127.0.0.1:222"},
config_source=ConfigSource.CONFIG_FLOW,
serial_number="second_serial",
devices=(vera_device2,),
),
)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entry1 = entity_registry.async_get(entity1_id)
assert entry1
assert entry1.unique_id == "1"
entry2 = entity_registry.async_get(entity2_id)
assert entry2
assert entry2.unique_id == "vera_second_serial_2"
async def test_unload( async def test_unload(
@ -77,7 +133,7 @@ async def test_unload(
"""Test function.""" """Test function."""
vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor vera_device1 = MagicMock(spec=pv.VeraBinarySensor) # type: pv.VeraBinarySensor
vera_device1.device_id = 1 vera_device1.device_id = 1
vera_device1.vera_device_id = 1 vera_device1.vera_device_id = vera_device1.device_id
vera_device1.name = "first_dev" vera_device1.name = "first_dev"
vera_device1.is_tripped = False vera_device1.is_tripped = False
@ -145,6 +201,7 @@ async def test_exclude_and_light_ids(
vera_device3 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch vera_device3 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch
vera_device3.device_id = 3 vera_device3.device_id = 3
vera_device3.vera_device_id = 3
vera_device3.name = "dev3" vera_device3.name = "dev3"
vera_device3.category = pv.CATEGORY_SWITCH vera_device3.category = pv.CATEGORY_SWITCH
vera_device3.is_switched_on = MagicMock(return_value=False) vera_device3.is_switched_on = MagicMock(return_value=False)
@ -152,6 +209,7 @@ async def test_exclude_and_light_ids(
vera_device4 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch vera_device4 = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch
vera_device4.device_id = 4 vera_device4.device_id = 4
vera_device4.vera_device_id = 4
vera_device4.name = "dev4" vera_device4.name = "dev4"
vera_device4.category = pv.CATEGORY_SWITCH vera_device4.category = pv.CATEGORY_SWITCH
vera_device4.is_switched_on = MagicMock(return_value=False) vera_device4.is_switched_on = MagicMock(return_value=False)
@ -160,6 +218,7 @@ async def test_exclude_and_light_ids(
component_data = await vera_component_factory.configure_component( component_data = await vera_component_factory.configure_component(
hass=hass, hass=hass,
controller_config=new_simple_controller_config( controller_config=new_simple_controller_config(
config_source=ConfigSource.CONFIG_ENTRY,
devices=(vera_device1, vera_device2, vera_device3, vera_device4), devices=(vera_device1, vera_device2, vera_device3, vera_device4),
config={**{CONF_CONTROLLER: "http://127.0.0.1:123"}, **options}, config={**{CONF_CONTROLLER: "http://127.0.0.1:123"}, **options},
), ),
@ -167,12 +226,10 @@ async def test_exclude_and_light_ids(
# Assert the entries were setup correctly. # Assert the entries were setup correctly.
config_entry = next(iter(hass.config_entries.async_entries(DOMAIN))) config_entry = next(iter(hass.config_entries.async_entries(DOMAIN)))
assert config_entry.options == { assert config_entry.options[CONF_LIGHTS] == [4, 10, 12]
CONF_LIGHTS: [4, 10, 12], assert config_entry.options[CONF_EXCLUDE] == [1]
CONF_EXCLUDE: [1],
}
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
update_callback(vera_device1) update_callback(vera_device1)
update_callback(vera_device2) update_callback(vera_device2)

View file

@ -15,6 +15,7 @@ async def test_light(
"""Test function.""" """Test function."""
vera_device = MagicMock(spec=pv.VeraDimmer) # type: pv.VeraDimmer vera_device = MagicMock(spec=pv.VeraDimmer) # type: pv.VeraDimmer
vera_device.device_id = 1 vera_device.device_id = 1
vera_device.vera_device_id = vera_device.device_id
vera_device.name = "dev1" vera_device.name = "dev1"
vera_device.category = pv.CATEGORY_DIMMER vera_device.category = pv.CATEGORY_DIMMER
vera_device.is_switched_on = MagicMock(return_value=False) vera_device.is_switched_on = MagicMock(return_value=False)
@ -27,7 +28,7 @@ async def test_light(
hass=hass, hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)), controller_config=new_simple_controller_config(devices=(vera_device,)),
) )
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
assert hass.states.get(entity_id).state == "off" assert hass.states.get(entity_id).state == "off"

View file

@ -15,6 +15,7 @@ async def test_lock(
"""Test function.""" """Test function."""
vera_device = MagicMock(spec=pv.VeraLock) # type: pv.VeraLock vera_device = MagicMock(spec=pv.VeraLock) # type: pv.VeraLock
vera_device.device_id = 1 vera_device.device_id = 1
vera_device.vera_device_id = vera_device.device_id
vera_device.name = "dev1" vera_device.name = "dev1"
vera_device.category = pv.CATEGORY_LOCK vera_device.category = pv.CATEGORY_LOCK
vera_device.is_locked = MagicMock(return_value=False) vera_device.is_locked = MagicMock(return_value=False)
@ -24,7 +25,7 @@ async def test_lock(
hass=hass, hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)), controller_config=new_simple_controller_config(devices=(vera_device,)),
) )
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
assert hass.states.get(entity_id).state == STATE_UNLOCKED assert hass.states.get(entity_id).state == STATE_UNLOCKED

View file

@ -14,6 +14,7 @@ async def test_scene(
"""Test function.""" """Test function."""
vera_scene = MagicMock(spec=pv.VeraScene) # type: pv.VeraScene vera_scene = MagicMock(spec=pv.VeraScene) # type: pv.VeraScene
vera_scene.scene_id = 1 vera_scene.scene_id = 1
vera_scene.vera_scene_id = vera_scene.scene_id
vera_scene.name = "dev1" vera_scene.name = "dev1"
entity_id = "scene.dev1_1" entity_id = "scene.dev1_1"

View file

@ -23,6 +23,7 @@ async def run_sensor_test(
"""Test generic sensor.""" """Test generic sensor."""
vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor
vera_device.device_id = 1 vera_device.device_id = 1
vera_device.vera_device_id = vera_device.device_id
vera_device.name = "dev1" vera_device.name = "dev1"
vera_device.category = category vera_device.category = category
setattr(vera_device, class_property, "33") setattr(vera_device, class_property, "33")
@ -34,7 +35,7 @@ async def run_sensor_test(
devices=(vera_device,), setup_callback=setup_callback devices=(vera_device,), setup_callback=setup_callback
), ),
) )
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
for (initial_value, state_value) in assert_states: for (initial_value, state_value) in assert_states:
setattr(vera_device, class_property, initial_value) setattr(vera_device, class_property, initial_value)
@ -175,6 +176,7 @@ async def test_scene_controller_sensor(
"""Test function.""" """Test function."""
vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor vera_device = MagicMock(spec=pv.VeraSensor) # type: pv.VeraSensor
vera_device.device_id = 1 vera_device.device_id = 1
vera_device.vera_device_id = vera_device.device_id
vera_device.name = "dev1" vera_device.name = "dev1"
vera_device.category = pv.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_id = MagicMock(return_value="id0")
@ -185,7 +187,7 @@ async def test_scene_controller_sensor(
hass=hass, hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)), controller_config=new_simple_controller_config(devices=(vera_device,)),
) )
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
vera_device.get_last_scene_time.return_value = "1111" vera_device.get_last_scene_time.return_value = "1111"
update_callback(vera_device) update_callback(vera_device)

View file

@ -14,6 +14,7 @@ async def test_switch(
"""Test function.""" """Test function."""
vera_device = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch vera_device = MagicMock(spec=pv.VeraSwitch) # type: pv.VeraSwitch
vera_device.device_id = 1 vera_device.device_id = 1
vera_device.vera_device_id = vera_device.device_id
vera_device.name = "dev1" vera_device.name = "dev1"
vera_device.category = pv.CATEGORY_SWITCH vera_device.category = pv.CATEGORY_SWITCH
vera_device.is_switched_on = MagicMock(return_value=False) vera_device.is_switched_on = MagicMock(return_value=False)
@ -21,9 +22,11 @@ async def test_switch(
component_data = await vera_component_factory.configure_component( component_data = await vera_component_factory.configure_component(
hass=hass, hass=hass,
controller_config=new_simple_controller_config(devices=(vera_device,)), controller_config=new_simple_controller_config(
devices=(vera_device,), legacy_entity_unique_id=False
),
) )
update_callback = component_data.controller_data.update_callback update_callback = component_data.controller_data[0].update_callback
assert hass.states.get(entity_id).state == "off" assert hass.states.get(entity_id).state == "off"