Skip to content

Commit 6d3632a

Browse files
committed
Validate device connectivity before creating a config entry (#5)
Both the bluetooth-confirm and user steps now probe the device with a single BLE connect (immediately disconnecting so entry setup can reconnect) before creating the entry. On failure the form is re-shown with a cannot_connect/unknown error instead of creating an entry that would just fail to set up.
1 parent cd3794e commit 6d3632a

2 files changed

Lines changed: 50 additions & 5 deletions

File tree

custom_components/run_chicken/config_flow.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66
from typing import TYPE_CHECKING, Any
77

88
import voluptuous as vol
9-
from homeassistant.components.bluetooth import async_discovered_service_info
9+
from bleak.exc import BleakError
10+
from homeassistant.components.bluetooth import (
11+
async_ble_device_from_address,
12+
async_discovered_service_info,
13+
)
1014
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
1115
from homeassistant.const import CONF_ADDRESS
1216

1317
from .const import DOMAIN, MANUFACTURER_ID
18+
from .run_chicken_ble import RunChickenDevice
1419

1520
if TYPE_CHECKING:
1621
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
@@ -45,23 +50,32 @@ async def async_step_bluetooth(self, discovery_info: BluetoothServiceInfoBleak)
4550
return await self.async_step_bluetooth_confirm()
4651

4752
async def async_step_bluetooth_confirm(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
48-
"""Confirm adding a device discovered over bluetooth."""
53+
"""Confirm adding a device discovered over bluetooth, verifying it is reachable."""
54+
errors: dict[str, str] = {}
4955
if user_input is not None:
50-
return self.async_create_entry(title=self._discovery_info.name, data={})
56+
error = await self._async_try_connect(self._discovery_info.address)
57+
if error is None:
58+
return self.async_create_entry(title=self._discovery_info.name, data={})
59+
errors["base"] = error
5160

5261
self._set_confirm_only()
5362
return self.async_show_form(
5463
step_id="bluetooth_confirm",
5564
description_placeholders={"name": self._discovery_info.name},
65+
errors=errors,
5666
)
5767

5868
async def async_step_user(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
5969
"""Show a list of discovered devices and let the user pick one."""
70+
errors: dict[str, str] = {}
6071
if user_input is not None:
6172
address = user_input[CONF_ADDRESS]
6273
await self.async_set_unique_id(address, raise_on_progress=False)
6374
self._abort_if_unique_id_configured()
64-
return self.async_create_entry(title=self._discovered_devices[address], data={})
75+
error = await self._async_try_connect(address)
76+
if error is None:
77+
return self.async_create_entry(title=self._discovered_devices.get(address, address), data={})
78+
errors["base"] = error
6579

6680
# Skip devices already set up or being set up in another in-progress flow.
6781
addresses_in_use = self._async_current_ids() | {
@@ -80,4 +94,31 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Con
8094
return self.async_abort(reason="no_devices_found")
8195

8296
schema = vol.Schema({vol.Required(CONF_ADDRESS): vol.In(self._discovered_devices)})
83-
return self.async_show_form(step_id="user", data_schema=schema)
97+
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
98+
99+
async def _async_try_connect(self, address: str) -> str | None:
100+
"""
101+
Probe connectivity to the device at ``address``.
102+
103+
Establishes a single BLE connection and immediately disconnects, leaving
104+
the device free for the entry setup to reconnect. Returns an error key
105+
(``cannot_connect``/``unknown``) on failure, or ``None`` on success.
106+
"""
107+
ble_device = async_ble_device_from_address(self.hass, address, connectable=True)
108+
if ble_device is None:
109+
_LOGGER.debug("No connectable BLE device found for %s", address)
110+
return "cannot_connect"
111+
112+
device = RunChickenDevice(ble_device)
113+
try:
114+
await device.ensure_client_connected()
115+
except (BleakError, TimeoutError):
116+
_LOGGER.debug("Could not connect to Run-Chicken device %s", address, exc_info=True)
117+
return "cannot_connect"
118+
except Exception:
119+
_LOGGER.exception("Unexpected error connecting to Run-Chicken device %s", address)
120+
return "unknown"
121+
finally:
122+
if device.client is not None:
123+
await device.client.disconnect()
124+
return None

custom_components/run_chicken/translations/en.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
"description": "Do you want to set up the Run-Chicken door {name}?"
1313
}
1414
},
15+
"error": {
16+
"cannot_connect": "Failed to connect. Make sure the door is powered on and within Bluetooth range, then try again.",
17+
"unknown": "An unexpected error occurred."
18+
},
1519
"abort": {
1620
"already_configured": "This device is already configured.",
1721
"not_run_chicken_device": "The discovered device is not a Run-Chicken door.",

0 commit comments

Comments
 (0)