1 """Config flow for Motionblinds Bluetooth integration."""
3 from __future__
import annotations
7 from typing
import TYPE_CHECKING, Any
9 from bleak.backends.device
import BLEDevice
10 from motionblindsble.const
import DISPLAY_NAME, SETTING_DISCONNECT_TIME, MotionBlindType
11 import voluptuous
as vol
35 ERROR_COULD_NOT_FIND_MOTOR,
36 ERROR_INVALID_MAC_CODE,
37 ERROR_NO_BLUETOOTH_ADAPTER,
38 ERROR_NO_DEVICES_FOUND,
39 OPTION_DISCONNECT_TIME,
40 OPTION_PERMANENT_CONNECTION,
43 _LOGGER = logging.getLogger(__name__)
45 CONFIG_SCHEMA = vol.Schema({vol.Required(CONF_MAC_CODE): str})
49 """Handle a config flow for Motionblinds Bluetooth."""
54 """Initialize a ConfigFlow."""
55 self.
_discovery_info_discovery_info: BluetoothServiceInfoBleak | BLEDevice |
None =
None
56 self.
_mac_code_mac_code: str |
None =
None
57 self.
_blind_type_blind_type: MotionBlindType |
None =
None
60 self, discovery_info: BluetoothServiceInfoBleak
61 ) -> ConfigFlowResult:
62 """Handle the bluetooth discovery step."""
64 "Discovered Motionblinds bluetooth device: %s", discovery_info.as_dict()
72 self.context[
"title_placeholders"] = {
"name": self.
_display_name_display_name}
77 self, user_input: dict[str, Any] |
None =
None
78 ) -> ConfigFlowResult:
79 """Handle a flow initialized by the user."""
80 errors: dict[str, str] = {}
81 if user_input
is not None:
82 mac_code = user_input[CONF_MAC_CODE]
86 except NoBluetoothAdapter:
88 except NoDevicesFound:
90 except tuple(EXCEPTION_MAP.keys())
as e:
91 errors = {
"base": EXCEPTION_MAP.get(type(e),
str(type(e)))}
93 step_id=
"user", data_schema=CONFIG_SCHEMA, errors=errors
97 scanner_count = bluetooth.async_scanner_count(self.hass, connectable=
True)
99 _LOGGER.error(
"No bluetooth adapter found")
103 step_id=
"user", data_schema=CONFIG_SCHEMA, errors=errors
107 self, user_input: dict[str, Any] |
None =
None
108 ) -> ConfigFlowResult:
109 """Confirm a single device."""
110 if user_input
is not None:
128 data_schema=vol.Schema(
133 blind_type.name.lower()
134 for blind_type
in MotionBlindType
136 translation_key=CONF_BLIND_TYPE,
137 mode=SelectSelectorMode.DROPDOWN,
142 description_placeholders={
"display_name": self.
_display_name_display_name},
146 """Discover Motionblinds initialized by the user."""
148 _LOGGER.error(
"Invalid MAC code: %s", mac_code.upper())
151 scanner_count = bluetooth.async_scanner_count(self.hass, connectable=
True)
152 if not scanner_count:
153 _LOGGER.error(
"No bluetooth adapter found")
154 raise NoBluetoothAdapter
156 bleak_scanner = bluetooth.async_get_scanner(self.hass)
157 devices = await bleak_scanner.discover()
159 if len(devices) == 0:
160 _LOGGER.error(
"Could not find any bluetooth devices")
163 motion_device: BLEDevice |
None = next(
166 for device
in devices
169 and f
"MOTION_{mac_code.upper()}" in device.name
174 if motion_device
is None:
175 _LOGGER.error(
"Could not find a motor with MAC code: %s", mac_code.upper())
176 raise CouldNotFindMotor
178 await self.
async_set_unique_idasync_set_unique_id(motion_device.address, raise_on_progress=
False)
182 self.
_mac_code_mac_code = mac_code.upper()
188 config_entry: ConfigEntry,
190 """Create the options flow."""
195 """Handle an options flow for Motionblinds BLE."""
198 self, user_input: dict[str, Any] |
None =
None
199 ) -> ConfigFlowResult:
200 """Manage the options."""
201 if user_input
is not None:
206 data_schema=vol.Schema(
209 OPTION_PERMANENT_CONNECTION,
212 OPTION_PERMANENT_CONNECTION,
False
217 OPTION_DISCONNECT_TIME,
220 OPTION_DISCONNECT_TIME, SETTING_DISCONNECT_TIME
223 ): vol.All(vol.Coerce(int), vol.Range(min=0)),
230 """Validate the provided MAC address."""
232 mac_regex =
r"^[0-9A-Fa-f]{4}$"
233 return bool(re.match(mac_regex, data))
237 """Get the MAC address from the bluetooth local name."""
239 mac_regex =
r"^MOTION_([0-9A-Fa-f]{4})$"
240 match = re.search(mac_regex, data)
241 return str(match.group(1))
if match
else None
245 """Error to indicate no motor with that MAC code could be found."""
248 class InvalidMACCode(HomeAssistantError):
249 """Error to indicate the MAC code is invalid."""
253 """Error to indicate no bluetooth adapter could be found."""
257 """Error to indicate no bluetooth devices could be found."""
261 NoBluetoothAdapter: ERROR_NO_BLUETOOTH_ADAPTER,
262 NoDevicesFound: ERROR_NO_DEVICES_FOUND,
263 CouldNotFindMotor: ERROR_COULD_NOT_FIND_MOTOR,
264 InvalidMACCode: ERROR_INVALID_MAC_CODE,
ConfigFlowResult async_step_bluetooth(self, BluetoothServiceInfoBleak discovery_info)
ConfigFlowResult async_step_user(self, dict[str, Any]|None user_input=None)
OptionsFlow async_get_options_flow(ConfigEntry config_entry)
ConfigFlowResult async_step_confirm(self, dict[str, Any]|None user_input=None)
None async_discover_motionblind(self, str mac_code)
ConfigFlowResult async_step_init(self, dict[str, Any]|None user_input=None)
None _abort_if_unique_id_configured(self, dict[str, Any]|None updates=None, bool reload_on_update=True, *str error="already_configured")
ConfigEntry|None async_set_unique_id(self, str|None unique_id=None, *bool raise_on_progress=True)
ConfigFlowResult async_create_entry(self, *str title, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None, Mapping[str, Any]|None options=None)
ConfigFlowResult async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
ConfigFlowResult async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
ConfigEntry config_entry(self)
None config_entry(self, ConfigEntry value)
_FlowResultT async_show_form(self, *str|None step_id=None, vol.Schema|None data_schema=None, dict[str, str]|None errors=None, Mapping[str, str]|None description_placeholders=None, bool|None last_step=None, str|None preview=None)
_FlowResultT async_create_entry(self, *str|None title=None, Mapping[str, Any] data, str|None description=None, Mapping[str, str]|None description_placeholders=None)
_FlowResultT async_abort(self, *str reason, Mapping[str, str]|None description_placeholders=None)
str|None get_mac_from_local_name(str data)
bool is_valid_mac(str data)