Files
arneandClaude Opus 5.5 821e4f5d1c Point the project at its Gitea repository
Set the manifest's links and code owner to git.asw.io/arne/ha-babytracker.
HACS can only install from GitHub, so the README now leads with a by-hand
install and describes a GitHub mirror for HACS users. Drop the HACS check
from the workflow because it cannot pass on Gitea.

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

243 lines
9.1 KiB
Markdown

# Baby Tracker for Home Assistant
An unofficial integration that connects Home Assistant to the
sync group of the Baby Tracker app (`com.nighp.babytracker_android`). You can see your
baby's log in Home Assistant and add to it, and entries sync with the app on your other
devices.
It also contains a [Python client](#python-client) for the sync API, which was reverse
engineered from version 4.53 of the Android app.
> **Unofficial and partly unverified.** Nothing here comes from the app's developer, and
> the server could change without notice. Reading has been checked against the real
> server, but **uploading has not**. See [Your first test](#your-first-test), and back up
> your data first.
## Requirements
- Home Assistant 2025.1 or newer.
- A sync group, set up in the app's sync settings. Its email and password are what you
sign in with.
## Install
### By hand
From a terminal on the Home Assistant machine, in your configuration folder (usually
`/config`), run this and then restart Home Assistant. Run it again to update.
```bash
mkdir -p custom_components && rm -rf custom_components/babytracker && curl -fsSL https://git.asw.io/arne/ha-babytracker/archive/main.tar.gz | tar xz --strip-components=2 -C custom_components ha-babytracker/custom_components/babytracker
```
You can also copy `custom_components/babytracker` over yourself, for example with the
Samba or File editor add-ons.
### With HACS
HACS can only install from GitHub, so it can't use the repository at `git.asw.io` directly.
To use HACS, mirror this repository to a public GitHub repository, which Gitea can do
automatically under **Settings → Repository → Mirror settings**. Then:
1. In HACS, open the menu and choose **Custom repositories**.
2. Add the **GitHub** URL, with the type **Integration**.
3. Find **Baby Tracker** in HACS, download it, and restart Home Assistant.
### Set it up
Go to **Settings → Devices & services → Add integration → Baby Tracker** and sign in.
Home Assistant joins the group as one more device, named "Home Assistant". Remove the
integration and that device stays listed on the account, because the app has no call
that removes a device.
## Your first test
The first upload is the one thing that has never been tried against the real server, so
start with something easy to remove:
1. Go to **Developer tools → Actions** and run **Baby Tracker: Log journal entry** with
the text `Home Assistant test`.
2. Check that it appears in the app on your phone, after the app has synced.
3. Restart Home Assistant and check that it still appears, once, on `sensor.<baby>_recent_activity`.
4. Delete it in the app. Within about five minutes it should disappear from Home Assistant.
If any step fails, stop uploading and open an issue with what you saw.
## What you get
Each baby becomes a device with these entities.
| Entity | Shows |
|---|---|
| `calendar.<baby>_log` | The whole log, for the calendar view |
| `sensor.<baby>_recent_activity` | Time of the latest entry, with the last 20 in its `entries` attribute |
| `sensor.<baby>_last_diaper` | Time of the last diaper, with status and amount as attributes |
| `sensor.<baby>_last_feeding` | Start of the last feed of any type, with its details |
| `sensor.<baby>_last_sleep` | Start of the last sleep, with `end` and `duration` attributes |
| `sensor.<baby>_diapers_today` | Count |
| `sensor.<baby>_feedings_today` | Count |
| `sensor.<baby>_sleep_today` | Minutes |
| `sensor.<baby>_bottle_volume_today` | Formula and pumped milk |
| `sensor.<baby>_temperature` | Latest reading |
| `sensor.<baby>_weight` | Latest measurement |
| `sensor.<baby>_length` | Latest measurement |
| `sensor.<baby>_head_circumference` | Latest measurement |
Entries count towards the day they started on, so a sleep that runs past midnight counts
for the day it began. Entries made on other devices appear within about five minutes.
## Adding entries
Each of these actions takes optional `baby`, `time` and `note` fields.
| Action | Fields |
|---|---|
| `babytracker.log_diaper` | `status` (wet, poopy, mixed, dry), `amount` (light, normal, heavy, overflow) |
| `babytracker.log_sleep` | `duration` in minutes |
| `babytracker.log_bottle` | `amount`, `kind` (formula, pumped), `unit` (ml, oz) |
| `babytracker.log_nursing` | `left_minutes`, `right_minutes`, `both_minutes`, `finish_side` (left, right) |
| `babytracker.log_temperature` | `value`, `unit` (C, F) |
| `babytracker.log_growth` | `weight`, `length`, `head`, `units` (metric, english) |
| `babytracker.log_bath` | none |
| `babytracker.log_joy` | none |
| `babytracker.log_journal` | `note` is the entry's text, and is required |
- **`baby`**: the baby's device or their name. Leave it out if the account has one baby.
- **`time`**: when the activity started. Leave it out and the activity is taken to have
just finished, so a 30-minute sleep is logged as starting 30 minutes ago.
- **Units**: these default to your Home Assistant unit system.
- **Failures**: if the upload fails, for example with no internet, the action reports an
error and nothing is saved or retried.
- **Response**: each action can return the new entry's `id`.
## Dashboard examples
Buttons to log a diaper in one tap:
```yaml
type: horizontal-stack
cards:
- type: button
name: Wet
icon: mdi:water
tap_action:
action: perform-action
perform_action: babytracker.log_diaper
data:
status: wet
- type: button
name: Poopy
icon: mdi:emoticon-poop
tap_action:
action: perform-action
perform_action: babytracker.log_diaper
data:
status: poopy
```
The log as a list, using a Markdown card. Replace `sam` with your baby's name:
```yaml
type: markdown
title: Recent activity
content: >
{% for entry in state_attr('sensor.sam_recent_activity', 'entries') or [] %}
- **{{ as_timestamp(entry.time) | timestamp_custom('%H:%M') }}** {{ entry.summary }}{{ ' — ' ~ entry.note if entry.note }}
{% endfor %}
```
A reminder when the last feed was over three hours ago:
```yaml
triggers:
- trigger: template
value_template: >
{{ now() - states('sensor.sam_last_feeding') | as_datetime > timedelta(hours=3) }}
actions:
- action: notify.notify
data:
message: "Last feed was over three hours ago"
```
## Limitations
- **Shown but not addable**: medication, vaccines, milestones, solid foods and other
activities. These pick from lists kept in the app. They do appear on the calendar and
in recent activity.
- **Not shown**: pumping sessions, because they don't belong to a baby. Photos too.
- **No editing or deleting** from Home Assistant. Use the app.
- **Startup**: the integration keeps the log in memory and downloads it again each time
it starts, so large accounts take longer to load.
- **Group resets**: if you reset the group in the app, reload the integration.
- **Password**: Home Assistant stores it unencrypted in its `.storage` folder, as it does
for most integrations. The server has no separate tokens.
## Python client
The library works without Home Assistant, and lives inside the integration folder because
HACS installs only that folder.
```bash
pip install -e .
```
The server has no way to query records. It stores a **database snapshot** (the seed) and
an **append-only log per device**. `Replica` combines them into a local copy.
```python
from babytracker_api import (
BabyTrackerClient, DiaperStatus, Replica, diaper, extract_seed_db,
formula, open_seed_db, read_cursors, sleep,
)
# Keep device_uuid stable between runs. A new value is a new device on the account.
client = BabyTrackerClient("you@example.com", "password", device_uuid="<a saved uuid4>")
client.login()
# Build a local copy from the seed.
extract_seed_db(client.download_seed("seed.zip"), "seed.db")
db = open_seed_db("seed.db")
replica = Replica.from_seed(db)
cursors = read_cursors(db)
# Add what happened since. Save `cursors` to continue from there next time.
# include_own is needed here because the seed predates this device's own uploads.
for device, item in client.pull(cursors, include_own=True):
replica.apply(item.op, item.object)
baby = next(iter(replica.babies.values()))
for entry in replica.entries(baby.id):
print(entry.time, entry.summary)
# Add records. These need the baby's full record.
client.add(diaper(baby.record, DiaperStatus.POOPY))
client.add(formula(baby.record, 120)) # ml; english=True for fl oz
client.add(sleep(baby.record, minutes=45, note="nap"))
# Change or remove a record. Records are plain dicts.
record = sleep(baby.record, minutes=30)
client.add(record)
client.update({**record, "duration": 40})
client.delete(record)
```
To check it against your account without changing anything:
```bash
BABYTRACKER_EMAIL=you@example.com BABYTRACKER_PASSWORD=... python3 examples/smoke_test.py
```
See [docs/API.md](docs/API.md) for endpoints, record formats and open questions.
## Development
```bash
python3 -m unittest discover -s tests
```
The tests use a mock server written from the same analysis of the app. The integration's
tests run against small stand-ins for Home Assistant's APIs in `tests/ha_stubs`, so they
don't show that it works in real Home Assistant. The workflow in `.github/workflows`, which
Gitea Actions also reads, adds Home Assistant's `hassfest`.