Add Insteon lock and load controller devices (#75632)
parent
3b8650d053
commit
70731c0bc7
|
@ -44,6 +44,7 @@ INSTEON_PLATFORMS = [
|
|||
Platform.COVER,
|
||||
Platform.FAN,
|
||||
Platform.LIGHT,
|
||||
Platform.LOCK,
|
||||
Platform.SWITCH,
|
||||
]
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
"""Utility methods for the Insteon platform."""
|
||||
from pyinsteon.device_types import (
|
||||
AccessControl_Morningstar,
|
||||
ClimateControl_Thermostat,
|
||||
ClimateControl_WirelessThermostat,
|
||||
DimmableLightingControl,
|
||||
|
@ -12,6 +13,7 @@ from pyinsteon.device_types import (
|
|||
DimmableLightingControl_OutletLinc,
|
||||
DimmableLightingControl_SwitchLinc,
|
||||
DimmableLightingControl_ToggleLinc,
|
||||
EnergyManagement_LoadController,
|
||||
GeneralController_ControlLinc,
|
||||
GeneralController_MiniRemote_4,
|
||||
GeneralController_MiniRemote_8,
|
||||
|
@ -44,11 +46,13 @@ from homeassistant.components.climate import DOMAIN as CLIMATE
|
|||
from homeassistant.components.cover import DOMAIN as COVER
|
||||
from homeassistant.components.fan import DOMAIN as FAN
|
||||
from homeassistant.components.light import DOMAIN as LIGHT
|
||||
from homeassistant.components.lock import DOMAIN as LOCK
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH
|
||||
|
||||
from .const import ON_OFF_EVENTS
|
||||
|
||||
DEVICE_PLATFORM = {
|
||||
AccessControl_Morningstar: {LOCK: [1]},
|
||||
DimmableLightingControl: {LIGHT: [1], ON_OFF_EVENTS: [1]},
|
||||
DimmableLightingControl_DinRail: {LIGHT: [1], ON_OFF_EVENTS: [1]},
|
||||
DimmableLightingControl_FanLinc: {LIGHT: [1], FAN: [2], ON_OFF_EVENTS: [1, 2]},
|
||||
|
@ -67,6 +71,7 @@ DEVICE_PLATFORM = {
|
|||
DimmableLightingControl_OutletLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]},
|
||||
DimmableLightingControl_SwitchLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]},
|
||||
DimmableLightingControl_ToggleLinc: {LIGHT: [1], ON_OFF_EVENTS: [1]},
|
||||
EnergyManagement_LoadController: {SWITCH: [1], BINARY_SENSOR: [2]},
|
||||
GeneralController_ControlLinc: {ON_OFF_EVENTS: [1]},
|
||||
GeneralController_MiniRemote_4: {ON_OFF_EVENTS: range(1, 5)},
|
||||
GeneralController_MiniRemote_8: {ON_OFF_EVENTS: range(1, 9)},
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
"""Support for INSTEON locks."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import SIGNAL_ADD_ENTITIES
|
||||
from .insteon_entity import InsteonEntity
|
||||
from .utils import async_add_insteon_entities
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Insteon locks from a config entry."""
|
||||
|
||||
@callback
|
||||
def async_add_insteon_lock_entities(discovery_info=None):
|
||||
"""Add the Insteon entities for the platform."""
|
||||
async_add_insteon_entities(
|
||||
hass, LOCK_DOMAIN, InsteonLockEntity, async_add_entities, discovery_info
|
||||
)
|
||||
|
||||
signal = f"{SIGNAL_ADD_ENTITIES}_{LOCK_DOMAIN}"
|
||||
async_dispatcher_connect(hass, signal, async_add_insteon_lock_entities)
|
||||
async_add_insteon_lock_entities()
|
||||
|
||||
|
||||
class InsteonLockEntity(InsteonEntity, LockEntity):
|
||||
"""A Class for an Insteon lock entity."""
|
||||
|
||||
@property
|
||||
def is_locked(self) -> bool:
|
||||
"""Return the boolean response if the node is on."""
|
||||
return bool(self._insteon_device_group.value)
|
||||
|
||||
async def async_lock(self, **kwargs: Any) -> None:
|
||||
"""Lock the device."""
|
||||
await self._insteon_device.async_lock()
|
||||
|
||||
async def async_unlock(self, **kwargs: Any) -> None:
|
||||
"""Unlock the device."""
|
||||
await self._insteon_device.async_unlock()
|
|
@ -4,7 +4,7 @@
|
|||
"documentation": "https://www.home-assistant.io/integrations/insteon",
|
||||
"dependencies": ["http", "websocket_api"],
|
||||
"requirements": [
|
||||
"pyinsteon==1.1.3",
|
||||
"pyinsteon==1.2.0",
|
||||
"insteon-frontend-home-assistant==0.2.0"
|
||||
],
|
||||
"codeowners": ["@teharris1"],
|
||||
|
|
|
@ -1569,7 +1569,7 @@ pyialarm==2.2.0
|
|||
pyicloud==1.0.0
|
||||
|
||||
# homeassistant.components.insteon
|
||||
pyinsteon==1.1.3
|
||||
pyinsteon==1.2.0
|
||||
|
||||
# homeassistant.components.intesishome
|
||||
pyintesishome==1.8.0
|
||||
|
|
|
@ -1076,7 +1076,7 @@ pyialarm==2.2.0
|
|||
pyicloud==1.0.0
|
||||
|
||||
# homeassistant.components.insteon
|
||||
pyinsteon==1.1.3
|
||||
pyinsteon==1.2.0
|
||||
|
||||
# homeassistant.components.ipma
|
||||
pyipma==2.0.5
|
||||
|
|
|
@ -6,6 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
|
|||
from pyinsteon.address import Address
|
||||
from pyinsteon.constants import ALDBStatus, ResponseStatus
|
||||
from pyinsteon.device_types import (
|
||||
AccessControl_Morningstar,
|
||||
DimmableLightingControl_KeypadLinc_8,
|
||||
GeneralController_RemoteLinc,
|
||||
Hub,
|
||||
|
@ -59,12 +60,13 @@ class MockDevices:
|
|||
|
||||
async def async_load(self, *args, **kwargs):
|
||||
"""Load the mock devices."""
|
||||
if self._connected:
|
||||
if self._connected and not self._devices:
|
||||
addr0 = Address("AA.AA.AA")
|
||||
addr1 = Address("11.11.11")
|
||||
addr2 = Address("22.22.22")
|
||||
addr3 = Address("33.33.33")
|
||||
addr4 = Address("44.44.44")
|
||||
addr5 = Address("55.55.55")
|
||||
self._devices[addr0] = Hub(addr0, 0x03, 0x00, 0x00, "Hub AA.AA.AA", "0")
|
||||
self._devices[addr1] = MockSwitchLinc(
|
||||
addr1, 0x02, 0x00, 0x00, "Device 11.11.11", "1"
|
||||
|
@ -78,9 +80,12 @@ class MockDevices:
|
|||
self._devices[addr4] = SensorsActuators_IOLink(
|
||||
addr4, 0x07, 0x00, 0x00, "Device 44.44.44", "4"
|
||||
)
|
||||
self._devices[addr5] = AccessControl_Morningstar(
|
||||
addr5, 0x0F, 0x0A, 0x00, "Device 55.55.55", "5"
|
||||
)
|
||||
|
||||
for device in [
|
||||
self._devices[addr] for addr in [addr1, addr2, addr3, addr4]
|
||||
self._devices[addr] for addr in [addr1, addr2, addr3, addr4, addr5]
|
||||
]:
|
||||
device.async_read_config = AsyncMock()
|
||||
device.aldb.async_write = AsyncMock()
|
||||
|
@ -99,7 +104,9 @@ class MockDevices:
|
|||
return_value=ResponseStatus.SUCCESS
|
||||
)
|
||||
|
||||
for device in [self._devices[addr] for addr in [addr2, addr3, addr4]]:
|
||||
for device in [
|
||||
self._devices[addr] for addr in [addr2, addr3, addr4, addr5]
|
||||
]:
|
||||
device.async_status = AsyncMock()
|
||||
self._devices[addr1].async_status = AsyncMock(side_effect=AttributeError)
|
||||
self._devices[addr0].aldb.async_load = AsyncMock()
|
||||
|
@ -117,6 +124,12 @@ class MockDevices:
|
|||
return_value=ResponseStatus.FAILURE
|
||||
)
|
||||
|
||||
self._devices[addr5].async_lock = AsyncMock(
|
||||
return_value=ResponseStatus.SUCCESS
|
||||
)
|
||||
self._devices[addr5].async_unlock = AsyncMock(
|
||||
return_value=ResponseStatus.SUCCESS
|
||||
)
|
||||
self.modem = self._devices[addr0]
|
||||
self.modem.async_read_config = AsyncMock()
|
||||
|
||||
|
@ -155,6 +168,6 @@ class MockDevices:
|
|||
yield address
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
def subscribe(self, listener):
|
||||
def subscribe(self, listener, force_strong_ref=False):
|
||||
"""Mock the subscribe function."""
|
||||
subscribe_topic(listener, DEVICE_LIST_CHANGED)
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
"""Tests for the Insteon lock."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components import insteon
|
||||
from homeassistant.components.insteon import (
|
||||
DOMAIN,
|
||||
insteon_entity,
|
||||
utils as insteon_utils,
|
||||
)
|
||||
from homeassistant.components.lock import ( # SERVICE_LOCK,; SERVICE_UNLOCK,
|
||||
DOMAIN as LOCK_DOMAIN,
|
||||
)
|
||||
from homeassistant.const import ( # ATTR_ENTITY_ID,;
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
STATE_LOCKED,
|
||||
STATE_UNLOCKED,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .const import MOCK_USER_INPUT_PLM
|
||||
from .mock_devices import MockDevices
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
devices = MockDevices()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def lock_platform_only():
|
||||
"""Only setup the lock and required base platforms to speed up tests."""
|
||||
with patch(
|
||||
"homeassistant.components.insteon.INSTEON_PLATFORMS",
|
||||
(Platform.LOCK,),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def patch_setup_and_devices():
|
||||
"""Patch the Insteon setup process and devices."""
|
||||
with patch.object(insteon, "async_connect", new=mock_connection), patch.object(
|
||||
insteon, "async_close"
|
||||
), patch.object(insteon, "devices", devices), patch.object(
|
||||
insteon_utils, "devices", devices
|
||||
), patch.object(
|
||||
insteon_entity, "devices", devices
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
async def mock_connection(*args, **kwargs):
|
||||
"""Return a successful connection."""
|
||||
return True
|
||||
|
||||
|
||||
async def test_lock_lock(hass):
|
||||
"""Test locking an Insteon lock device."""
|
||||
|
||||
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT_PLM)
|
||||
config_entry.add_to_hass(hass)
|
||||
registry_entity = er.async_get(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
try:
|
||||
lock = registry_entity.async_get("lock.device_55_55_55_55_55_55")
|
||||
state = hass.states.get(lock.entity_id)
|
||||
assert state.state is STATE_UNLOCKED
|
||||
|
||||
# lock via UI
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN, "lock", {"entity_id": lock.entity_id}, blocking=True
|
||||
)
|
||||
assert devices["55.55.55"].async_lock.call_count == 1
|
||||
finally:
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
|
||||
async def test_lock_unlock(hass):
|
||||
"""Test locking an Insteon lock device."""
|
||||
|
||||
config_entry = MockConfigEntry(domain=DOMAIN, data=MOCK_USER_INPUT_PLM)
|
||||
config_entry.add_to_hass(hass)
|
||||
registry_entity = er.async_get(hass)
|
||||
|
||||
await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
devices["55.55.55"].groups[1].set_value(255)
|
||||
|
||||
try:
|
||||
lock = registry_entity.async_get("lock.device_55_55_55_55_55_55")
|
||||
state = hass.states.get(lock.entity_id)
|
||||
|
||||
assert state.state is STATE_LOCKED
|
||||
|
||||
# lock via UI
|
||||
await hass.services.async_call(
|
||||
LOCK_DOMAIN, "unlock", {"entity_id": lock.entity_id}, blocking=True
|
||||
)
|
||||
assert devices["55.55.55"].async_unlock.call_count == 1
|
||||
finally:
|
||||
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
|
||||
await hass.async_block_till_done()
|
Loading…
Reference in New Issue