Add always connected option to Yale Access Bluetooth (#93224)
* Add always connected option to Yale Access Bluetooth If the lock does not support push updates via advertisements or you want lock operation to be more responsive, you can enable always connected mode. Always connected will cause the lock to stay connected to Home Assistant via Bluetooth, which will use more battery. * Update homeassistant/components/yalexs_ble/config_flow.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> --------- Co-authored-by: Martin Hjelmare <marhje52@gmail.com>pull/93242/head
parent
2eef2ed911
commit
763b898621
|
@ -19,7 +19,14 @@ from homeassistant.const import CONF_ADDRESS, EVENT_HOMEASSISTANT_STOP, Platform
|
|||
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
|
||||
from .const import CONF_KEY, CONF_LOCAL_NAME, CONF_SLOT, DEVICE_TIMEOUT, DOMAIN
|
||||
from .const import (
|
||||
CONF_ALWAYS_CONNECTED,
|
||||
CONF_KEY,
|
||||
CONF_LOCAL_NAME,
|
||||
CONF_SLOT,
|
||||
DEVICE_TIMEOUT,
|
||||
DOMAIN,
|
||||
)
|
||||
from .models import YaleXSBLEData
|
||||
from .util import async_find_existing_service_info, bluetooth_callback_matcher
|
||||
|
||||
|
@ -33,7 +40,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
key = entry.data[CONF_KEY]
|
||||
slot = entry.data[CONF_SLOT]
|
||||
has_unique_local_name = local_name_is_unique(local_name)
|
||||
push_lock = PushLock(local_name, address, None, key, slot)
|
||||
always_connected = entry.options.get(CONF_ALWAYS_CONNECTED, False)
|
||||
push_lock = PushLock(
|
||||
local_name, address, None, key, slot, always_connected=always_connected
|
||||
)
|
||||
id_ = local_name if has_unique_local_name else address
|
||||
push_lock.set_name(f"{entry.title} ({id_})")
|
||||
|
||||
|
@ -79,7 +89,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
) from ex
|
||||
|
||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = YaleXSBLEData(
|
||||
entry.title, push_lock
|
||||
entry.title, push_lock, always_connected
|
||||
)
|
||||
|
||||
@callback
|
||||
|
@ -115,7 +125,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Handle options update."""
|
||||
data: YaleXSBLEData = hass.data[DOMAIN][entry.entry_id]
|
||||
if entry.title != data.title:
|
||||
if entry.title != data.title or data.always_connected != entry.options.get(
|
||||
CONF_ALWAYS_CONNECTED
|
||||
):
|
||||
await hass.config_entries.async_reload(entry.entry_id)
|
||||
|
||||
|
||||
|
|
|
@ -23,10 +23,11 @@ from homeassistant.components.bluetooth import (
|
|||
async_discovered_service_info,
|
||||
)
|
||||
from homeassistant.const import CONF_ADDRESS
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.data_entry_flow import AbortFlow, FlowResult
|
||||
from homeassistant.helpers.typing import DiscoveryInfoType
|
||||
|
||||
from .const import CONF_KEY, CONF_LOCAL_NAME, CONF_SLOT, DOMAIN
|
||||
from .const import CONF_ALWAYS_CONNECTED, CONF_KEY, CONF_LOCAL_NAME, CONF_SLOT, DOMAIN
|
||||
from .util import async_find_existing_service_info, human_readable_name
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
@ -297,3 +298,46 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
data_schema=data_schema,
|
||||
errors=errors,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(
|
||||
config_entry: config_entries.ConfigEntry,
|
||||
) -> YaleXSBLEOptionsFlowHandler:
|
||||
"""Get the options flow for this handler."""
|
||||
return YaleXSBLEOptionsFlowHandler(config_entry)
|
||||
|
||||
|
||||
class YaleXSBLEOptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle YaleXSBLE options."""
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||
"""Initialize YaleXSBLE options flow."""
|
||||
self.entry = config_entry
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Manage the YaleXSBLE options."""
|
||||
return await self.async_step_device_options()
|
||||
|
||||
async def async_step_device_options(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> FlowResult:
|
||||
"""Manage the YaleXSBLE devices options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(
|
||||
data={CONF_ALWAYS_CONNECTED: user_input[CONF_ALWAYS_CONNECTED]},
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="device_options",
|
||||
data_schema=vol.Schema(
|
||||
{
|
||||
vol.Optional(
|
||||
CONF_ALWAYS_CONNECTED,
|
||||
default=self.entry.options.get(CONF_ALWAYS_CONNECTED, False),
|
||||
): bool,
|
||||
}
|
||||
),
|
||||
)
|
||||
|
|
|
@ -5,5 +5,6 @@ DOMAIN = "yalexs_ble"
|
|||
CONF_LOCAL_NAME = "local_name"
|
||||
CONF_KEY = "key"
|
||||
CONF_SLOT = "slot"
|
||||
CONF_ALWAYS_CONNECTED = "always_connected"
|
||||
|
||||
DEVICE_TIMEOUT = 55
|
||||
|
|
|
@ -12,3 +12,4 @@ class YaleXSBLEData:
|
|||
|
||||
title: str
|
||||
lock: PushLock
|
||||
always_connected: bool
|
||||
|
|
|
@ -35,5 +35,15 @@
|
|||
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"device_options": {
|
||||
"description": "If the lock does not support push updates via advertisements or you want lock operation to be more responsive, you can enable always connected mode. Always connected will cause the lock to stay connected to Home Assistant via Bluetooth, which will use more battery.",
|
||||
"data": {
|
||||
"always_connected": "Always connected"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
"""Test the Yale Access Bluetooth config flow."""
|
||||
import asyncio
|
||||
from unittest.mock import patch
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
from bleak import BleakError
|
||||
import pytest
|
||||
from yalexs_ble import AuthError
|
||||
from yalexs_ble import AuthError, DoorStatus, LockInfo, LockState, LockStatus
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.yalexs_ble.const import (
|
||||
CONF_ALWAYS_CONNECTED,
|
||||
CONF_KEY,
|
||||
CONF_LOCAL_NAME,
|
||||
CONF_SLOT,
|
||||
|
@ -27,6 +28,23 @@ from . import (
|
|||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
def _get_mock_push_lock():
|
||||
"""Return a mock PushLock."""
|
||||
mock_push_lock = Mock()
|
||||
mock_push_lock.start = AsyncMock()
|
||||
mock_push_lock.wait_for_first_update = AsyncMock()
|
||||
mock_push_lock.stop = AsyncMock()
|
||||
mock_push_lock.lock_state = LockState(
|
||||
LockStatus.UNLOCKED, DoorStatus.CLOSED, None, None
|
||||
)
|
||||
mock_push_lock.lock_status = LockStatus.UNLOCKED
|
||||
mock_push_lock.door_status = DoorStatus.CLOSED
|
||||
mock_push_lock.lock_info = LockInfo("Front Door", "M1XXX012LU", "1.0.0", "1.0.0")
|
||||
mock_push_lock.device_info = None
|
||||
mock_push_lock.address = YALE_ACCESS_LOCK_DISCOVERY_INFO.address
|
||||
return mock_push_lock
|
||||
|
||||
|
||||
@pytest.mark.parametrize("slot", [0, 1, 66])
|
||||
async def test_user_step_success(hass: HomeAssistant, slot: int) -> None:
|
||||
"""Test user step success path."""
|
||||
|
@ -947,3 +965,48 @@ async def test_reauth(hass: HomeAssistant) -> None:
|
|||
assert result3["type"] == FlowResultType.ABORT
|
||||
assert result3["reason"] == "reauth_successful"
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
|
||||
async def test_options(hass: HomeAssistant) -> None:
|
||||
"""Test options."""
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
data={
|
||||
CONF_LOCAL_NAME: YALE_ACCESS_LOCK_DISCOVERY_INFO.name,
|
||||
CONF_ADDRESS: YALE_ACCESS_LOCK_DISCOVERY_INFO.address,
|
||||
CONF_KEY: "2fd51b8621c6a139eaffbedcb846b60f",
|
||||
CONF_SLOT: 66,
|
||||
},
|
||||
unique_id=YALE_ACCESS_LOCK_DISCOVERY_INFO.address,
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.yalexs_ble.PushLock",
|
||||
return_value=_get_mock_push_lock(),
|
||||
):
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
result = await hass.config_entries.options.async_init(
|
||||
entry.entry_id,
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "device_options"
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.yalexs_ble.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result2 = await hass.config_entries.options.async_configure(
|
||||
result["flow_id"],
|
||||
{
|
||||
CONF_ALWAYS_CONNECTED: True,
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result2["type"] == FlowResultType.CREATE_ENTRY
|
||||
assert entry.options == {CONF_ALWAYS_CONNECTED: True}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
|
Loading…
Reference in New Issue