mirror of
https://github.com/home-assistant/core.git
synced 2025-07-02 19:07:27 +00:00

* Update switchbot to be local push * fixes * fixes * fixes * fixes * adjust * cover is not assumed anymore * cleanups * adjust * adjust * add missing cover * import compat * fixes * uses lower * uses lower * bleak users upper case addresses * fixes * bump * keep conf_mac and deprecated options for rollback * reuse coordinator * adjust * move around * move around * move around * move around * refactor fixes * compat with DataUpdateCoordinator * fix available * Update homeassistant/components/bluetooth/passive_update_processor.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/bluetooth/update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Split bluetooth coordinator into PassiveBluetoothDataUpdateCoordinator and PassiveBluetoothProcessorCoordinator The PassiveBluetoothDataUpdateCoordinator is now used to replace instances of DataUpdateCoordinator where the data is coming from bluetooth advertisements, and the integration may also mix in active updates The PassiveBluetoothProcessorCoordinator is used for integrations that want to process each bluetooth advertisement with multiple processors which can be dispatched to individual platforms or areas or the integration as it chooes * change connections * reduce code churn to reduce review overhead * reduce code churn to reduce review overhead * Update homeassistant/components/bluetooth/passive_update_coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * add basic test * add basic test * complete coverage * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/coordinator.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * Update homeassistant/components/switchbot/__init__.py Co-authored-by: Martin Hjelmare <marhje52@gmail.com> * lint Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
166 lines
5.9 KiB
Python
166 lines
5.9 KiB
Python
"""Config flow for Switchbot."""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any, cast
|
|
|
|
from switchbot import SwitchBotAdvertisement, parse_advertisement_data
|
|
import voluptuous as vol
|
|
|
|
from homeassistant.components.bluetooth import (
|
|
BluetoothServiceInfoBleak,
|
|
async_discovered_service_info,
|
|
)
|
|
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
|
|
from homeassistant.const import CONF_ADDRESS, CONF_NAME, CONF_PASSWORD, CONF_SENSOR_TYPE
|
|
from homeassistant.core import callback
|
|
from homeassistant.data_entry_flow import FlowResult
|
|
from homeassistant.helpers.service_info.bluetooth import BluetoothServiceInfo
|
|
|
|
from .const import (
|
|
CONF_RETRY_COUNT,
|
|
CONF_RETRY_TIMEOUT,
|
|
DEFAULT_RETRY_COUNT,
|
|
DEFAULT_RETRY_TIMEOUT,
|
|
DOMAIN,
|
|
SUPPORTED_MODEL_TYPES,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
def format_unique_id(address: str) -> str:
|
|
"""Format the unique ID for a switchbot."""
|
|
return address.replace(":", "").lower()
|
|
|
|
|
|
class SwitchbotConfigFlow(ConfigFlow, domain=DOMAIN):
|
|
"""Handle a config flow for Switchbot."""
|
|
|
|
VERSION = 1
|
|
|
|
@staticmethod
|
|
@callback
|
|
def async_get_options_flow(
|
|
config_entry: ConfigEntry,
|
|
) -> SwitchbotOptionsFlowHandler:
|
|
"""Get the options flow for this handler."""
|
|
return SwitchbotOptionsFlowHandler(config_entry)
|
|
|
|
def __init__(self):
|
|
"""Initialize the config flow."""
|
|
self._discovered_adv: SwitchBotAdvertisement | None = None
|
|
self._discovered_advs: dict[str, SwitchBotAdvertisement] = {}
|
|
|
|
async def async_step_bluetooth(
|
|
self, discovery_info: BluetoothServiceInfo
|
|
) -> FlowResult:
|
|
"""Handle the bluetooth discovery step."""
|
|
_LOGGER.debug("Discovered bluetooth device: %s", discovery_info)
|
|
await self.async_set_unique_id(format_unique_id(discovery_info.address))
|
|
self._abort_if_unique_id_configured()
|
|
discovery_info_bleak = cast(BluetoothServiceInfoBleak, discovery_info)
|
|
parsed = parse_advertisement_data(
|
|
discovery_info_bleak.device, discovery_info_bleak.advertisement
|
|
)
|
|
if not parsed or parsed.data.get("modelName") not in SUPPORTED_MODEL_TYPES:
|
|
return self.async_abort(reason="not_supported")
|
|
self._discovered_adv = parsed
|
|
data = parsed.data
|
|
self.context["title_placeholders"] = {
|
|
"name": data["modelName"],
|
|
"address": discovery_info.address,
|
|
}
|
|
return await self.async_step_user()
|
|
|
|
async def async_step_user(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> FlowResult:
|
|
"""Handle the user step to pick discovered device."""
|
|
errors: dict[str, str] = {}
|
|
|
|
if user_input is not None:
|
|
address = user_input[CONF_ADDRESS]
|
|
await self.async_set_unique_id(
|
|
format_unique_id(address), raise_on_progress=False
|
|
)
|
|
self._abort_if_unique_id_configured()
|
|
user_input[CONF_SENSOR_TYPE] = SUPPORTED_MODEL_TYPES[
|
|
self._discovered_advs[address].data["modelName"]
|
|
]
|
|
return self.async_create_entry(title=user_input[CONF_NAME], data=user_input)
|
|
|
|
if discovery := self._discovered_adv:
|
|
self._discovered_advs[discovery.address] = discovery
|
|
else:
|
|
current_addresses = self._async_current_ids()
|
|
for discovery_info in async_discovered_service_info(self.hass):
|
|
address = discovery_info.address
|
|
if (
|
|
format_unique_id(address) in current_addresses
|
|
or address in self._discovered_advs
|
|
):
|
|
continue
|
|
parsed = parse_advertisement_data(
|
|
discovery_info.device, discovery_info.advertisement
|
|
)
|
|
if parsed and parsed.data.get("modelName") in SUPPORTED_MODEL_TYPES:
|
|
self._discovered_advs[address] = parsed
|
|
|
|
if not self._discovered_advs:
|
|
return self.async_abort(reason="no_unconfigured_devices")
|
|
|
|
data_schema = vol.Schema(
|
|
{
|
|
vol.Required(CONF_ADDRESS): vol.In(
|
|
{
|
|
address: f"{parsed.data['modelName']} ({address})"
|
|
for address, parsed in self._discovered_advs.items()
|
|
}
|
|
),
|
|
vol.Required(CONF_NAME): str,
|
|
vol.Optional(CONF_PASSWORD): str,
|
|
}
|
|
)
|
|
return self.async_show_form(
|
|
step_id="user", data_schema=data_schema, errors=errors
|
|
)
|
|
|
|
|
|
class SwitchbotOptionsFlowHandler(OptionsFlow):
|
|
"""Handle Switchbot options."""
|
|
|
|
def __init__(self, config_entry: ConfigEntry) -> None:
|
|
"""Initialize options flow."""
|
|
self.config_entry = config_entry
|
|
|
|
async def async_step_init(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> FlowResult:
|
|
"""Manage Switchbot options."""
|
|
if user_input is not None:
|
|
# Update common entity options for all other entities.
|
|
for entry in self.hass.config_entries.async_entries(DOMAIN):
|
|
if entry.unique_id != self.config_entry.unique_id:
|
|
self.hass.config_entries.async_update_entry(
|
|
entry, options=user_input
|
|
)
|
|
return self.async_create_entry(title="", data=user_input)
|
|
|
|
options = {
|
|
vol.Optional(
|
|
CONF_RETRY_COUNT,
|
|
default=self.config_entry.options.get(
|
|
CONF_RETRY_COUNT, DEFAULT_RETRY_COUNT
|
|
),
|
|
): int,
|
|
vol.Optional(
|
|
CONF_RETRY_TIMEOUT,
|
|
default=self.config_entry.options.get(
|
|
CONF_RETRY_TIMEOUT, DEFAULT_RETRY_TIMEOUT
|
|
),
|
|
): int,
|
|
}
|
|
|
|
return self.async_show_form(step_id="init", data_schema=vol.Schema(options))
|