Home

Awesome

BabyPod

This repository is just for the CircuitPython code that runs on the hardware. See the babypod-hardware repository for the hardware setup and more general information about the project.

You need to install Baby Buddy for this to work. It can be installed on your local network or on the internet (AWS, etc.), so long as it's reachable through your Wi-Fi network. BabyPod can work offline, but not indefinitely; it's still intended to sync at least periodically, if not in real-time, with Baby Buddy.

Features

General

Offline support

In scenarios where you're away from your predefined Wi-Fi location, you can go offline. When you go offline, actions get buffered to JSON files on a microSD card, and when you go online, they get replayed. You should only go offline when you're forced to; otherwise, in the event the microSD card gets corrupted or there's some other issue, you could lose all the buffered actions you took while offline.

Successful events replays are deleted from the microSD card as each one is successfully replayed when going back online. If a specific event fails to play back, playback will stop at that point in history and subsequent events are kept on the microSD card, and the device stays offline.

Hardware requirements

For offline support to be available, the BabyPod must both have the following additional hardware which is assumed in the hardware build documentation:

You have two options for controlling power. They are mutually exclusive!

If you don't wire either if these power options, the BabyPod will stay on until the battery drains! If you wire both, then both will work, but it will be confusing because you can turn off the BabyPod with the hard power switch but it won't turn back on with soft power control.

If either the RTC isn't available or the microSD reader fails to initialize, offline support is disabled (the user isn't shown the option) and the BabyPod is forced to be online.

Real-time clock (RTC)

When online, the RTC gets set automatically using adafruit.io's time service in the following situations:

The Adafruit service is used instead of NTP because the former will autodetect your timezone. It is important that your local timezone match Baby Buddy's timezone or all your offline events will be off by several hours. The RTC cannot be set through the user interface. Instead, all syncing happens through the Adafruit service.

Remember that RTC devices need their own external power source, usually a button-cell battery like a CR1220 battery. Additionally, Adafruit warns users that the battery must be inserted into the breakout board even if it's dead or the device may behave unpredictably. However, the RTC battery will likely last for years.

A BabyPod can't work 100% offline in perpetuity. As a strict minimum, it must be online at least once to sync the RTC. More realistically, it needs to be online periodically to sync changes back to Baby Buddy or the BabyPod will be a mostly useless device.

Offline activation

Offline is activated:

Offline is deactivated (device goes back online):

Online vs. offline

The main differences between running online vs. offline are:

On the main menu, the bottom-right navigation shows a check if online and unchecked box if offline. That doesn't necessarily mean a positively confirmed connection to the server, just that the offline option is enabled or disabled.

Feedings

Diaper changes

Pumping

Tummy Time

Sleep

Options

User-configurability that persists across power cycles of:

Building and Deploying

settings.toml

You need a settings.toml at the root of the CIRCUITPY drive. The CircuitPython documentation describes the format of the file.

Here are the possible keys for settings.toml. Strings must be quoted and ints aren't.

KeyPurposeRequired?
CIRCUITPY_WIFI_SSID_DEFERYour Wi-Fi's SSID (network name)Yes, unless wifi.json is defined
CIRCUITPY_WIFI_PASSWORD_DEFERYour Wi-Fi's passwordYes, unless wifi.json is defined
CIRCUITPY_WIFI_INITIAL_CHANNELYour Wi-Fi's access point channel number, or 0 to autodetect with a slower startup penaltyNo
CIRCUITPY_WIFI_TIMEOUTWi-Fi connection timeout in seconds, or omit for a default of 10No
BABYBUDDY_BASE_URLBaby Buddy's API endpoint URL including trailing slash, like http://10.1.2.3/api/Yes
BABYBUDDY_AUTH_TOKENYour API user's authorization tokenYes
ADAFRUIT_AIO_USERNAMEYour adafruit.io API user's usernameYes, if your device has an RTC
ADAFRUIT_AIO_KEYYour adafruit.io API user's keyYes, if your device has an RTC
DEVICE_NAMEDevice name as it should appear in some notes posted to the API; defaults to "BabyPod"No
BACKLIGHT_COLOR_FULLBacklight color to use when just powered on or there's been recent user input, expressed as an int; defaults to 0xFFFFFF (white)No
BACKLIGHT_COLOR_DIMBacklight color to use when there hasn't been user input for a little while, expressed as an int; defaults to 0x808080 (white, but dimmer)No
BACKLIGHT_COLOR_ERRORBacklight color to use when showing an error message, expressed as an int; defaults to 0xFF0000 (red)No
BACKLIGHT_COLOR_SUCCESSBacklight color to use when showing a success message, expressed as an int; defaults to 0x00FF00 (green)No

Note the Wi-Fi related settings have a suffix of _DEFER. This is because you don't want CircuitPython connecting to Wi-Fi automatically as that precedes code.py starting and therefore the user doesn't get any startup feedback. Don't use the default CircuitPython Wi-Fi setting names!

Rather than defining the various Wi-Fi settings in settings.toml, you can instead put them in a file named /wifi.json that looks like this:

[
	{
		"ssid": "...",
		"password": ...",
		"channel": n
	},
	...
]

List the networks in order of connection preference. The channel number is optional; specify it to only connect on the given channel or omit it entirely for any channel. If both the values in settings.toml and the file /wifi.json are provided, then the former is attempted first, then the latter.

If you have multiple Wi-Fi networks defined, that implies you're using the BabyPod in multiple physical locations. That further implies that your Baby Buddy instance is accessible on the internet given a LAN address at home won't work when you're at work, for example, short of a VPN or other setup. Keep that in mind if you intend on using your BabyPod in multiple locations.

Requirements

These instructions assume you've already built a BabyPod per the instructions at the hardware repository, including copying a release to the CIRCUITPY drive along with setting up settings.toml. That is, you have a functioning BabyPod already, and now you want to change the code on it.

To make code changes, you need to do the following to build and deploy them.

  1. Clone BabyPod's software GitHub repository first: git clone https://github.com/skjdghsdjgsdj/babypod-software.git

  2. If there is a version of mpy-cross compatible with your version of CircuitPython available to download, you can use that. If not, compile your own mpy-cross executable and put it in your $PATH:

    1. Download and build CircuitPython 9, including building submodules. You have to do a full clone; you can't do git clone --depth or you'll miss tags and the build will fail. Be sure to use the exact same version that's flashed to the Feather.
    2. Build mpy-cross and put the resulting binary that ends up in circuitpython/mpy-cross/build/mpy-cross in your $PATH, like copying it to /usr/local/bin.
    3. You can delete the cloned circuitpython repository if you don't plan on building mpy-cross again or doing CircuitPython upgrades.
  3. Plug in the Feather to a USB port and verify the CIRCUITPY drive shows up. The power switch, if you have one wired across EN and GND, must be on. Some Feathers don't show up as local drives because they lack the USB support for it. In those cases, the build script won't work right and you'll have to copy files another way, which is usually by Wi-Fi or the serial console. Refer to that Feather's documentation for details.

If you update CircuitPython on the Feather, you will likely need to build a corresponding new mpy-cross.

macOS and Linux

  1. On Linux, edit build-and-deploy.py to point to the correct path for your CIRCUITPY drive. For now, it assumes you're on macOS. You may also need to edit /dev/tty.usbmodem* to point to the correct serial console for CircuitPython. Feel free to make the script less dumb and submit a pull request so others can build on Linux or macOS automatically without needing to edit the script.
  2. With the Feather plugged in and turned on, run build-and-deploy.py. This script will
    1. Run mpy-cross with optimizations on all .py files in the project, except for the entry point code.py.
    2. With each resulting .mpy compiled output, copy it to the Feather's lib/ directory.
    3. Copy code.py to the root of the Feather.
    4. Reboot the Feather.

The build script supports several arguments:

To set up a brand new BabyPod, all you should need to do is:

  1. Erase the flash then re-flash CircuitPython.
  2. Create a valid settings.toml.
  3. Run build-and-deploy.py --clean to build all the BabyPod files and also copy the necessary Adafruit modules.

Windows

I haven't tried, but you should be able to modify build-and-deploy.py with Windows paths. I'm not sure how you can programmatically reboot the Feather without a tty device, though.

Caveats

Manual reboot needed

Unlike CircuitPython's default behavior, the Feather won't reboot automatically when you copy a file to the CIRCUITPY drive. This is deliberate to avoid a storm of reboots as compiled files are copied to the Feather. Instead, you can reboot the Feather by:

Code design and architectural principles

General

Files

FilePurpose
api.pyConnectivity to Wi-Fi and Baby Buddy
battery_monitor.pyAbstraction of LC709203F and MAX17048 battery monitors with autoselection of the appropriate chip
build-and-deploy.pyBuild script that uses mpy-cross to compile the code and copy it to the CIRCUITPY drive
code.pyCircuitPython's entry point
devices.pyDependency injection of the various device abstractions instead of passing a million arguments around
external_rtc.pyAbstraction of the real-time clock (RTC)
flow.pyDrives the UX
lcd.pyAbstraction of the LCD text and backlight including defining special characters like arrows
nvram.pyPersists values in NVRAM across reboots
offline_event_queue.pyQueue that buffers up API requests that would happen if the device was online and serializes them to JSON on the microSD card
offline_state.pyStores some state needed for offline use to the microSD card, like last feeding that happened locally and when the RTC was last synced
periodic_chime.pyLogic for when to periodically nudge the user during feedings, tummy times, etc.
piezo.pyAbstraction of the piezo, including allowing playback of tones by name rather than specifying them externally
power_control.pyProvides soft shutdown and wakeup capability, if so enabled in settings.toml
sdcard.pyAbstraction of the microSD card reader
settings.pyUser-accessible settings backed by NVRAM values
ui_components.pyDefinition of various UI components, detailed below
user_input.pyAbstraction of the rotary encoder, which takes into account the 90° physical rotation when mounted in the enclosure
util.pyHelper methods, like a workaround for a CircuitPython bug that doesn't support UTC ISO-formatted timestamps

UI Components

ClassPurpose
UIComponentBase class
ActiveTimerA timer that counts up in real-time, including periodic piezo chimes
NumericSelectorInput for a float with upper/lower bounds and increment counts
VerticalMenuUser selection a single menu item from up to four options
VerticalCheckboxesLike VerticalMenu, but each item is preceded with a checkbox
BooleanPromptLike VerticalMenu, but allows for one selection of exactly two options and returns a boolean
ProgressBarShows a progress bar; unlike the other components, render_and_wait() doesn't block
ModalShows a message and a Dismiss button

Tips and Tricks

Feeding menu simplification

You can hide types of feeding options (breast milk, fortified breast milk, formula, and solid food) with a bitmask stored in NVRAM. If only one is enabled, the user isn't prompted at all for the food type.

The values are:

Food typeValue
Breast milk0x1
Fortified breast milk0x2
Formula0x4
Solid food0x8

Calculate the bitmask of the options you want by adding the values. For example, to only show the two types of breast milk, use 0x1 + 0x2, or to show just breast milk, use 0x1. Then, in the REPL serial console, store the value in NVRAM like this:

import nvram
nvram.NVRAMValues.ENABLED_FOOD_TYPES_MASK.write(value)

...where value is the bitmask. There's no user interface to do this for now, but being in NVRAM, this will persist across reboots.

Accessing the microSD card for debugging

Most obviously, you can always just remove the microSD card from your device, if it's accessible and powered off, then put it in your computer. But if that's not feasible, like you don't want to disassemble the BabyPod from its enclosure, you can access the microSD card via the REPL console. You also might be using a device with non-removable storage.

To do this:

If you are writing files this way, remember to flush file handles or your changes may not get persisted to the filesystem.

Known limitations and bugs

Please contribute and submit pull requests if you can help!

Wishlist

Please contribute and submit pull requests if you can help! But some of these things I'm not sure CircuitPython can do.