# 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._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._log` | The whole log, for the calendar view | | `sensor._recent_activity` | Time of the latest entry, with the last 20 in its `entries` attribute | | `sensor._last_diaper` | Time of the last diaper, with status and amount as attributes | | `sensor._last_feeding` | Start of the last feed of any type, with its details | | `sensor._last_sleep` | Start of the last sleep, with `end` and `duration` attributes | | `sensor._diapers_today` | Count | | `sensor._feedings_today` | Count | | `sensor._sleep_today` | Minutes | | `sensor._bottle_volume_today` | Formula and pumped milk | | `sensor._temperature` | Latest reading | | `sensor._weight` | Latest measurement | | `sensor._length` | Latest measurement | | `sensor._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="") 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`.