-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for virtual locks (#103)
- Loading branch information
Showing
12 changed files
with
190 additions
and
40 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
"""Module for Virtual locks.""" | ||
|
||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass, field | ||
import logging | ||
from typing import TypedDict | ||
|
||
from homeassistant.exceptions import HomeAssistantError | ||
from homeassistant.helpers.storage import Store | ||
|
||
from ..const import DOMAIN | ||
from ._base import BaseLock | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
class CodeSlotData(TypedDict): | ||
"""Type for code slot data.""" | ||
|
||
code: int | str | ||
name: str | None | ||
|
||
|
||
@dataclass(repr=False, eq=False) | ||
class VirtualLock(BaseLock): | ||
"""Class to represent Virtual lock.""" | ||
|
||
_store: Store[dict[str, CodeSlotData]] = field(init=False, repr=False) | ||
_data: dict[str, CodeSlotData] = field(default_factory=dict, init=False, repr=False) | ||
|
||
@property | ||
def domain(self) -> str: | ||
"""Return integration domain.""" | ||
return "virtual" | ||
|
||
async def async_setup(self) -> None: | ||
"""Set up lock.""" | ||
self._store = Store( | ||
self.hass, 1, f"{self.domain}_{DOMAIN}_{self.lock.entity_id}" | ||
) | ||
await self.async_hard_refresh_codes() | ||
|
||
async def async_unload(self, remove_permanently: bool) -> None: | ||
"""Unload lock.""" | ||
if remove_permanently: | ||
await self._store.async_remove() | ||
else: | ||
await self._store.async_save(self._data) | ||
|
||
async def async_is_connection_up(self) -> bool: | ||
"""Return whether connection to lock is up.""" | ||
return True | ||
|
||
async def async_hard_refresh_codes(self) -> None: | ||
""" | ||
Perform hard refresh of all codes. | ||
Needed for integrations where usercodes are cached and may get out of sync with | ||
the lock. | ||
""" | ||
self._data = data if (data := await self._store.async_load()) else {} | ||
|
||
async def async_set_usercode( | ||
self, code_slot: int, usercode: int | str, name: str | None = None | ||
) -> None: | ||
"""Set a usercode on a code slot.""" | ||
self._data[str(code_slot)] = CodeSlotData(code=usercode, name=name) | ||
|
||
async def async_clear_usercode(self, code_slot: int) -> None: | ||
"""Clear a usercode on a code slot.""" | ||
if str(code_slot) not in self._data: | ||
raise HomeAssistantError(f"Code slot {code_slot} not found") | ||
self._data.pop(str(code_slot)) | ||
|
||
async def async_get_usercodes(self) -> dict[int, int | str]: | ||
"""Get dictionary of code slots and usercodes.""" | ||
return { | ||
int(slot_num): code_slot["code"] | ||
for slot_num, code_slot in self._data.items() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
"""Virtual lock tests.""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
"""Test the Virtual lock platform.""" | ||
|
||
from datetime import timedelta | ||
|
||
import pytest | ||
from pytest_homeassistant_custom_component.common import MockConfigEntry | ||
|
||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import HomeAssistantError | ||
from homeassistant.helpers import device_registry as dr, entity_registry as er | ||
|
||
from custom_components.lock_code_manager.providers.virtual import VirtualLock | ||
|
||
|
||
async def test_door_lock(hass: HomeAssistant): | ||
"""Test a lock entity.""" | ||
lock = VirtualLock( | ||
hass, | ||
dr.async_get(hass), | ||
er.async_get(hass), | ||
MockConfigEntry(), | ||
er.RegistryEntry("lock.test", "blah", "blah"), | ||
) | ||
assert await lock.async_setup() is None | ||
assert lock.usercode_scan_interval == timedelta(minutes=1) | ||
assert lock.domain == "virtual" | ||
assert await lock.async_is_connection_up() | ||
assert lock._data == {} | ||
await lock.async_hard_refresh_codes() | ||
assert lock._data == {} | ||
# we should not be able to clear a usercode that does not exist | ||
with pytest.raises(HomeAssistantError): | ||
await lock.async_clear_usercode(1) | ||
|
||
# we should be able to set a usercode and see it in the data | ||
await lock.async_set_usercode(1, 1, "test") | ||
assert lock._data["1"] == {"code": 1, "name": "test"} | ||
await lock.async_get_usercodes() | ||
assert lock._data["1"] == {"code": 1, "name": "test"} | ||
|
||
# if we unload without removing permanently, the data should be saved | ||
assert await lock.async_unload(False) is None | ||
assert await lock.async_setup() is None | ||
assert lock._data["1"] == {"code": 1, "name": "test"} | ||
|
||
# we can clear a valid usercode | ||
await lock.async_set_usercode(2, 2, "test2") | ||
assert lock._data["2"] == {"code": 2, "name": "test2"} | ||
await lock.async_clear_usercode(2) | ||
assert "2" not in lock._data | ||
|
||
# if we unload with removing permanently, the data should be removed | ||
assert await lock.async_unload(True) is None | ||
assert await lock.async_setup() is None | ||
assert not lock._data |