Files
ha-babytracker/custom_components/babytracker/config_flow.py
T
arneandClaude Opus 5.5 6466f002e0 Add Baby Tracker client and Home Assistant integration
Python client for the Baby Tracker sync server, reverse engineered from
the Android app (4.53), and a HACS-installable Home Assistant integration
built on it. Each baby gets a log calendar and summary sensors, and actions
add entries that sync back to the group.

Reading has been run against the real server. Uploading and running inside
real Home Assistant have not.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 10:04:39 +00:00

89 lines
3.7 KiB
Python

"""Config flow for Baby Tracker."""
from __future__ import annotations
import logging
import uuid
from typing import Any, Mapping
import requests
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.helpers.selector import TextSelector, TextSelectorConfig, TextSelectorType
from .babytracker_api import AccountStateError, AuthenticationError, BabyTrackerClient, BabyTrackerError
from .compat import vol
from .const import CONF_DEVICE_UUID, DEVICE_NAME, DOMAIN
_LOGGER = logging.getLogger(__name__)
_PASSWORD_SELECTOR = TextSelector(TextSelectorConfig(type=TextSelectorType.PASSWORD))
USER_SCHEMA = vol.Schema(
{
vol.Required(CONF_EMAIL): TextSelector(TextSelectorConfig(type=TextSelectorType.EMAIL)),
vol.Required(CONF_PASSWORD): _PASSWORD_SELECTOR,
}
)
REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): _PASSWORD_SELECTOR})
class BabyTrackerConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
async def _async_login(self, email: str, password: str, device_uuid: str) -> tuple[int | None, dict[str, str]]:
client = BabyTrackerClient(
email, password, device_uuid=device_uuid, device_name=DEVICE_NAME, device_os_info=DEVICE_NAME
)
try:
return await self.hass.async_add_executor_job(client.login), {}
except AuthenticationError:
return None, {"base": "invalid_auth"}
except AccountStateError:
return None, {"base": "account_state"}
except (BabyTrackerError, requests.RequestException):
return None, {"base": "cannot_connect"}
except Exception:
_LOGGER.exception("Unexpected error logging in")
return None, {"base": "unknown"}
async def async_step_user(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
errors: dict[str, str] = {}
if user_input is not None:
# This ID stays with the entry for good. The server treats each new
# value as a new device on the account.
device_uuid = str(uuid.uuid4())
account_id, errors = await self._async_login(
user_input[CONF_EMAIL], user_input[CONF_PASSWORD], device_uuid
)
if not errors:
await self.async_set_unique_id(str(account_id))
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=user_input[CONF_EMAIL],
data={**user_input, CONF_DEVICE_UUID: device_uuid},
)
return self.async_show_form(step_id="user", data_schema=USER_SCHEMA, errors=errors)
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> ConfigFlowResult:
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
errors: dict[str, str] = {}
entry = self._get_reauth_entry()
if user_input is not None:
account_id, errors = await self._async_login(
entry.data[CONF_EMAIL], user_input[CONF_PASSWORD], entry.data[CONF_DEVICE_UUID]
)
if not errors:
await self.async_set_unique_id(str(account_id))
self._abort_if_unique_id_mismatch(reason="wrong_account")
return self.async_update_reload_and_abort(entry, data_updates=user_input)
return self.async_show_form(
step_id="reauth_confirm",
data_schema=REAUTH_SCHEMA,
errors=errors,
description_placeholders={"email": entry.data[CONF_EMAIL]},
)