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>
This commit is contained in:
arneandClaude Opus 5.5 committed 2026-11-04 10:04:39 +00:00
commit 6466f002e0
51 files changed
+5199

No files matched your search

Whitespace-only changes.
@@ -0,0 +1,22 @@
from datetime import datetime as datetime_sys
import voluptuous
def string(value):
if value is None:
raise voluptuous.Invalid("string value is None")
return str(value)
def datetime(value):
if isinstance(value, datetime_sys):
return value
try:
return datetime_sys.fromisoformat(value)
except (TypeError, ValueError) as err:
raise voluptuous.Invalid("invalid datetime") from err
def config_entry_only_config_schema(domain):
return lambda config: config
@@ -0,0 +1,30 @@
from dataclasses import dataclass
from enum import Enum
class DeviceEntryType(Enum):
SERVICE = "service"
def DeviceInfo(**kwargs):
return kwargs
@dataclass
class DeviceEntry:
identifiers: set
class DeviceRegistry:
def __init__(self):
self.devices = {}
def async_get(self, device_id):
return self.devices.get(device_id)
REGISTRY = DeviceRegistry()
def async_get(hass):
return REGISTRY
@@ -0,0 +1,18 @@
from enum import Enum
class TextSelectorType(str, Enum):
EMAIL = "email"
PASSWORD = "password"
def TextSelectorConfig(**kwargs):
return kwargs
class TextSelector:
def __init__(self, config=None):
self.config = config
def __call__(self, value):
return str(value)
@@ -0,0 +1 @@
ConfigType = dict
@@ -0,0 +1,60 @@
from typing import Generic, TypeVar
from ..exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady, HomeAssistantError
_T = TypeVar("_T")
class UpdateFailed(HomeAssistantError):
pass
class DataUpdateCoordinator(Generic[_T]):
def __init__(self, hass, logger, *, config_entry=None, name=None, update_interval=None):
self.hass = hass
self.logger = logger
self.config_entry = config_entry
self.name = name
self.update_interval = update_interval
self.data = None
self.last_update_success = True
self.last_exception = None
self._listeners = []
def async_add_listener(self, listener):
self._listeners.append(listener)
return lambda: self._listeners.remove(listener)
def _notify(self):
for listener in list(self._listeners):
listener()
async def async_refresh(self):
try:
self.data = await self._async_update_data()
except UpdateFailed as err:
self.last_update_success = False
self.last_exception = err
else:
self.last_update_success = True
self._notify()
async def async_config_entry_first_refresh(self):
try:
self.data = await self._async_update_data()
except UpdateFailed as err:
raise ConfigEntryNotReady(str(err)) from err
def async_set_updated_data(self, data):
self.data = data
self.last_update_success = True
self._notify()
class CoordinatorEntity(Generic[_T]):
def __init__(self, coordinator):
self.coordinator = coordinator
@property
def available(self):
return self.coordinator.last_update_success