Skip to content

Commit

Permalink
clean up and improve user step
Browse files Browse the repository at this point in the history
  • Loading branch information
TheMicDiet committed Jun 14, 2024
1 parent 8513ccb commit 9947a92
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 125 deletions.
173 changes: 58 additions & 115 deletions custom_components/chihiros/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,70 +5,23 @@
import logging
from typing import Any

import voluptuous as vol
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
import voluptuous as vol # type: ignore
from homeassistant.components.bluetooth import (
BluetoothServiceInfoBleak,
async_discovered_service_info,
)
from homeassistant.config_entries import ConfigFlow
from homeassistant.const import CONF_ADDRESS
from homeassistant.data_entry_flow import FlowResult

from .chihiros_led_control.device import BaseDevice, get_model_class_from_name
from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

# TODO adjust the data schema to the data that you need
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Optional(CONF_PASSWORD): str,
}
)

ADDITIONAL_DISCOVERY_TIMEOUT = 60


class PlaceholderHub:
"""Placeholder class to make tests pass.
TODO Remove this placeholder class and replace with things from your PyPI package.
"""

def __init__(self, host: str) -> None:
"""Initialize."""
self.host = host

async def authenticate(self, username: str, password: str) -> bool:
"""Test if we can authenticate with the host."""
return True


async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
# TODO validate the data can be used to set up a connection.

# If your PyPI package is not built with async, pass your methods
# to the executor:
# await hass.async_add_executor_job(
# your_validate_func, data[CONF_USERNAME], data[CONF_PASSWORD]
# )

# hub = PlaceholderHub(data[CONF_HOST])

# if not await hub.authenticate(data[CONF_USERNAME], data[CONF_PASSWORD]):
# raise InvalidAuth

# If you cannot connect:
# throw CannotConnect
# If the authentication is wrong:
# InvalidAuth

# Return info that you want to store in the config entry.
return {"password": data.get(CONF_PASSWORD)}


class ChihirosConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for chihiros."""

Expand All @@ -78,61 +31,30 @@ def __init__(self) -> None:
"""Initialize the config flow."""
self._discovery_info: BluetoothServiceInfoBleak | None = None
self._discovered_device: BaseDevice | None = None
self._discovered_devices: dict[str, str] = {}

# async def _async_wait_for_full_advertisement(
# # self, discovery_info: BluetoothServiceInfoBleak, device: DeviceData
# self,
# discovery_info: BluetoothServiceInfoBleak,
# device,
# ) -> BluetoothServiceInfoBleak:
# """Wait for the full advertisement.
#
# Sometimes the first advertisement we receive is blank or incomplete.
# """
# if device.supported(discovery_info):
# return discovery_info
# return await async_process_advertisements(
# self.hass,
# device.supported,
# {"address": discovery_info.address},
# BluetoothScanningMode.ACTIVE,
# ADDITIONAL_DISCOVERY_TIMEOUT,
# )
self._discovered_devices: dict[str, BluetoothServiceInfoBleak] = {}

async def async_step_bluetooth(
self, discovery_info: BluetoothServiceInfoBleak
) -> ConfigFlowResult:
) -> FlowResult:
"""Handle the bluetooth discovery step."""
await self.async_set_unique_id(discovery_info.address)
self._abort_if_unique_id_configured()
model_class = get_model_class_from_name(discovery_info.name)
device = model_class(discovery_info.device)
# try:
# self._discovery_info = await self._async_wait_for_full_advertisement(
# discovery_info, device
# )
# except TimeoutError:
# return self.async_abort(reason="not_supported")
self._discovery_info = discovery_info
self._discovered_device = device
return await self.async_step_bluetooth_confirm()

async def async_step_bluetooth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
) -> FlowResult:
"""Confirm discovery."""
assert self._discovered_device is not None
device = self._discovered_device
assert self._discovery_info is not None
discovery_info = self._discovery_info
title = device.name or discovery_info.name
if user_input is not None:
if not user_input.get(CONF_PASSWORD):
return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
)
return self.async_create_entry(title=title, data={})

self._set_confirm_only()
Expand All @@ -144,33 +66,54 @@ async def async_step_bluetooth_confirm(

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
) -> FlowResult:
"""Handle the user step to pick discovered device."""
errors: dict[str, str] = {}
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except InvalidAuth:
errors["base"] = "invalid_auth"
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
assert self._discovered_device is not None
return self.async_create_entry(
title=self._discovered_device.name, data=info
)

if user_input is not None:
address = user_input[CONF_ADDRESS]
discovery_info = self._discovered_devices[address]
await self.async_set_unique_id(
discovery_info.address, raise_on_progress=False
)
self._abort_if_unique_id_configured()
model_class = get_model_class_from_name(discovery_info.name)
device = model_class(discovery_info.device)

self._discovery_info = discovery_info
self._discovered_device = device
title = device.name or discovery_info.name
return self.async_create_entry(
title=title, data={CONF_ADDRESS: discovery_info.address}
)

if discovery := self._discovery_info:
self._discovered_devices[discovery.address] = discovery
else:
current_addresses = self._async_current_ids()
for discovery in async_discovered_service_info(self.hass):
if (
discovery.address in current_addresses
or discovery.address in self._discovered_devices
):
continue
self._discovered_devices[discovery.address] = discovery

if not self._discovered_devices:
return self.async_abort(reason="no_devices_found")

data_schema = vol.Schema(
{
vol.Required(CONF_ADDRESS): vol.In(
{
service_info.address: (
f"{service_info.name} ({service_info.address})"
)
for service_info in self._discovered_devices.values()
}
),
}
)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
step_id="user", data_schema=data_schema, errors=errors
)


class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""


class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""
9 changes: 0 additions & 9 deletions custom_components/chihiros/light.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,15 +88,6 @@ def __init__(
name=self._device.name,
)

# @property
# def data(self) -> dict[str, Any]:
# """Return coordinator data for this entity.
#
# TODO: Seems useless
# """
# _LOGGER.debug("Called data: %s", self.name)
# return self.coordinator.data

async def async_added_to_hass(self) -> None:
"""Handle entity about to be added to hass event."""
_LOGGER.debug("Called async_added_to_hass: %s", self.name)
Expand Down
2 changes: 1 addition & 1 deletion custom_components/chihiros/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@
"requirements": [
"typer[all]==0.9.0"
],
"version": "0.2.1"
"version": "0.2.3"
}
1 change: 1 addition & 0 deletions info.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ _Component to integrate with [Chihiros][chihiros_aquatic_studio] lights._

Tested devices:
* Tiny Terrarium Egg
* LED A2

{% if not installed %}
## Installation
Expand Down

0 comments on commit 9947a92

Please sign in to comment.