diff --git a/homeassistant/components/freedompro/__init__.py b/homeassistant/components/freedompro/__init__.py index 6b377f51560..311f1794866 100644 --- a/homeassistant/components/freedompro/__init__.py +++ b/homeassistant/components/freedompro/__init__.py @@ -14,7 +14,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["binary_sensor", "light", "sensor", "switch"] +PLATFORMS = ["binary_sensor", "light", "lock", "sensor", "switch"] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): diff --git a/homeassistant/components/freedompro/lock.py b/homeassistant/components/freedompro/lock.py new file mode 100644 index 00000000000..f3a689016f6 --- /dev/null +++ b/homeassistant/components/freedompro/lock.py @@ -0,0 +1,95 @@ +"""Support for Freedompro lock.""" +import json + +from pyfreedompro import put_state + +from homeassistant.components.lock import LockEntity +from homeassistant.const import CONF_API_KEY +from homeassistant.core import callback +from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import DOMAIN + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up Freedompro lock.""" + api_key = entry.data[CONF_API_KEY] + coordinator = hass.data[DOMAIN][entry.entry_id] + async_add_entities( + Device(hass, api_key, device, coordinator) + for device in coordinator.data + if device["type"] == "lock" + ) + + +class Device(CoordinatorEntity, LockEntity): + """Representation of an Freedompro lock.""" + + def __init__(self, hass, api_key, device, coordinator): + """Initialize the Freedompro lock.""" + super().__init__(coordinator) + self._hass = hass + self._session = aiohttp_client.async_get_clientsession(self._hass) + self._api_key = api_key + self._attr_name = device["name"] + self._attr_unique_id = device["uid"] + self._type = device["type"] + self._characteristics = device["characteristics"] + self._attr_device_info = { + "name": self.name, + "identifiers": { + (DOMAIN, self.unique_id), + }, + "model": self._type, + "manufacturer": "Freedompro", + } + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + device = next( + ( + device + for device in self.coordinator.data + if device["uid"] == self.unique_id + ), + None, + ) + if device is not None and "state" in device: + state = device["state"] + if "lock" in state: + if state["lock"] == 1: + self._attr_is_locked = True + else: + self._attr_is_locked = False + super()._handle_coordinator_update() + + async def async_added_to_hass(self) -> None: + """When entity is added to hass.""" + await super().async_added_to_hass() + self._handle_coordinator_update() + + async def async_lock(self, **kwargs): + """Async function to lock the lock.""" + payload = {"lock": 1} + payload = json.dumps(payload) + await put_state( + self._session, + self._api_key, + self.unique_id, + payload, + ) + await self.coordinator.async_request_refresh() + + async def async_unlock(self, **kwargs): + """Async function to unlock the lock.""" + payload = {"lock": 0} + payload = json.dumps(payload) + await put_state( + self._session, + self._api_key, + self.unique_id, + payload, + ) + await self.coordinator.async_request_refresh() diff --git a/tests/components/freedompro/test_lock.py b/tests/components/freedompro/test_lock.py new file mode 100644 index 00000000000..5c30909e081 --- /dev/null +++ b/tests/components/freedompro/test_lock.py @@ -0,0 +1,120 @@ +"""Tests for the Freedompro lock.""" +from datetime import timedelta +from unittest.mock import ANY, patch + +from homeassistant.components.lock import ( + DOMAIN as LOCK_DOMAIN, + SERVICE_LOCK, + SERVICE_UNLOCK, +) +from homeassistant.const import ATTR_ENTITY_ID, STATE_LOCKED, STATE_UNLOCKED +from homeassistant.helpers import device_registry as dr, entity_registry as er +from homeassistant.util.dt import utcnow + +from tests.common import async_fire_time_changed +from tests.components.freedompro.const import DEVICES_STATE + +uid = "2WRRJR6RCZQZSND8VP0YTO3YXCSOFPKBMW8T51TU-LQ*2VAS3HTWINNZ5N6HVEIPDJ6NX85P2-AM-GSYWUCNPU0" + + +async def test_lock_get_state(hass, init_integration): + """Test states of the lock.""" + init_integration + registry = er.async_get(hass) + registry_device = dr.async_get(hass) + + device = registry_device.async_get_device({("freedompro", uid)}) + assert device is not None + assert device.identifiers == {("freedompro", uid)} + assert device.manufacturer == "Freedompro" + assert device.name == "lock" + assert device.model == "lock" + + entity_id = "lock.lock" + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_UNLOCKED + assert state.attributes.get("friendly_name") == "lock" + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == uid + + get_states_response = list(DEVICES_STATE) + for state_response in get_states_response: + if state_response["uid"] == uid: + state_response["state"]["lock"] = 1 + with patch( + "homeassistant.components.freedompro.get_states", + return_value=get_states_response, + ): + async_fire_time_changed(hass, utcnow() + timedelta(hours=2)) + await hass.async_block_till_done() + + state = hass.states.get(entity_id) + assert state + assert state.attributes.get("friendly_name") == "lock" + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == uid + + assert state.state == STATE_LOCKED + + +async def test_lock_set_unlock(hass, init_integration): + """Test set on of the lock.""" + init_integration + registry = er.async_get(hass) + + entity_id = "lock.lock" + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_LOCKED + assert state.attributes.get("friendly_name") == "lock" + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == uid + + with patch("homeassistant.components.freedompro.lock.put_state") as mock_put_state: + assert await hass.services.async_call( + LOCK_DOMAIN, + SERVICE_UNLOCK, + {ATTR_ENTITY_ID: [entity_id]}, + blocking=True, + ) + mock_put_state.assert_called_once_with(ANY, ANY, ANY, '{"lock": 0}') + + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_LOCKED + + +async def test_lock_set_lock(hass, init_integration): + """Test set on of the lock.""" + init_integration + registry = er.async_get(hass) + + entity_id = "lock.lock" + state = hass.states.get(entity_id) + assert state + assert state.state == STATE_LOCKED + assert state.attributes.get("friendly_name") == "lock" + + entry = registry.async_get(entity_id) + assert entry + assert entry.unique_id == uid + + with patch("homeassistant.components.freedompro.lock.put_state") as mock_put_state: + assert await hass.services.async_call( + LOCK_DOMAIN, + SERVICE_LOCK, + {ATTR_ENTITY_ID: [entity_id]}, + blocking=True, + ) + mock_put_state.assert_called_once_with(ANY, ANY, ANY, '{"lock": 1}') + + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == STATE_LOCKED