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>
243 lines
9.1 KiB
Markdown
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`.
|