No description
  • C++ 53.3%
  • Kotlin 33.8%
  • Python 10.1%
  • HTML 1.3%
  • Shell 0.9%
  • Other 0.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Stefan Huber 0eb2c7dfe9 App 0.7.0: use without a stick; release 0.7.0
Start screen: "Use without a stick". The app then logs meals itself
(stick menu, barcodes, anything) with the firmware's Fat-Math and food
list (a test compares it with firmware/src/app.h), shows today's kcal,
uploads kcal-only days (stick=false). Pairing a stick later keeps the
log; unpairing keeps account, groups and scanned products. Leaderboard
upload is its own WorkManager job (12 h, needs network) while there's an
account. 3 new tests.

Docs: README/app README/server plan updated, release notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 16:02:07 +01:00
app App 0.7.0: use without a stick; release 0.7.0 2026-11-01 16:02:07 +01:00
docs App 0.7.0: use without a stick; release 0.7.0 2026-11-01 16:02:07 +01:00
firmware App 0.7.0: use without a stick; release 0.7.0 2026-11-01 16:02:07 +01:00
server App 0.7.0: use without a stick; release 0.7.0 2026-11-01 16:02:07 +01:00
tools README: Senpai pictures, rendered by the firmware's own drawing code 2026-11-01 15:41:58 +01:00
.gitignore Release script and notes for v0.2.0 2026-10-30 23:55:51 +01:00
README.md App 0.7.0: use without a stick; release 0.7.0 2026-11-01 16:02:07 +01:00

Sitbit 🍕🛋️

Ponytail-Senpai sipping coffee, ponytail swaying

The un-fitness tracker. Close your rings — by sitting inside them.

A semi-satirical persiflage of fitness trackers: instead of steps, active minutes and calories burned, we track sitting, naps and calories eaten (scientifically underestimated, so you can eat more). The mascot is Ponytail-Senpai, a chibi greybeard dev who compiled Gentoo in 1999 and hasn't stood up since.

Sitbit = Sit + bit. The name lives in firmware/src/config.h (APP_NAME).

Repository layout

Path What Status
firmware/ M5StickC Plus / Plus2 / StickS3 wristband firmware (PlatformIO, Arduino, M5Unified) works on the Plus2; StickS3 untested
app/ Android companion app (sync, settings, history, food, barcode scanner, firmware updates, leaderboard account) works
server/ Leaderboard + API at sitbit.fidefi.de (with GDPR in mind) groups + weekly leaderboard live
docs/ Protocol drafts, notes draft

Firmware (M5StickC Plus / Plus2 / StickS3)

Two builds: the StickC Plus and Plus2 have an ESP32, the StickS3 (their successor, the Plus2 is end-of-life) an ESP32-S3, and a firmware is compiled for one chip. So every release has two firmware files:

Stick PlatformIO env Release files
StickC Plus, Plus2 m5stickc-plus sitbit-firmware-<ver>-app.bin / -full.bin
StickS3 sticks3 sitbit-sticks3-firmware-<ver>-app.bin / -full.bin

The app picks the right one by the board the stick reports, and refuses an image built for the other chip (the stick's update check would too).

StickS3 status: compiles, not tested on hardware. Side-by-side comparison, what the firmware does per board and a checklist for the first stick: docs/hardware.md. In short:

  • BMI270 FIFO (light sleep like the Plus/Plus2), plus Bosch's on-chip step counter next to ours for comparison
  • no RTC chip listed: falls back to the ESP32's own clock
  • real speaker: synthesized sounds (sad trombone for salad, chomp, fanfare)
  • Couch Commander: learns your TV remote via the IR receiver and zaps from the wrist
  • navigation: B = next screen, hold B = previous (M5Stack documents a single click on the S3's power button as "power on / reset")
  • no programmable LED

The Plus and Plus2 share one build; M5Unified detects the board at boot. Differences: the Plus has an AXP192 power chip (current measurement via its coulomb counter, charge detection, 120 mAh), the Plus2 has no PMIC (battery voltage via ADC only, 200 mAh, PWR is a plain GPIO). On the Plus2 "charging" is guessed from the voltage trend (rises ≥ 50 mV → charging, drops ≥ 50 mV → not), so the pizza may take a minute or two to notice the cable.

Build & flash

pip install platformio      # or: uv tool install platformio
cd firmware
pio run -e m5stickc-plus -t upload   # StickC Plus / Plus2 (USB-C, /dev/ttyUSB0)
pio run -e sticks3 -t upload         # StickS3 (/dev/ttyACM0)
pio device monitor          # optional serial log (115200)

Without building: flash a release's full image once via USB, after that updates come through the app.

esptool.py --chip esp32   write_flash 0x0 sitbit-firmware-<ver>-full.bin           # Plus / Plus2
esptool.py --chip esp32s3 write_flash 0x0 sitbit-sticks3-firmware-<ver>-full.bin   # StickS3

Setting the time

  • Automatically: on the first boot after flashing, the clock is set from the build timestamp.
  • By hand: Settings → Set time (hold A). PWR moves to the next field (hour, minute, day, month, year, SAVE), A counts up (hold for fast) or presses SAVE, B cancels.
  • Via NTP: Settings → WiFi & NTP (hold A). If no network is configured yet, the stick opens its own setup WiFi and shows a QR code: scan it with your phone (or join Sitbit-XXXX with the password shown), and the setup page pops up (otherwise open 192.168.4.1). Pick your network, enter the password, choose a timezone, save. The stick then connects, gets the time from NTP and switches WiFi off again. On the WiFi screen: A = sync now, hold A = setup again, B/PWR = back.
  • Once WiFi is configured, the time is synced automatically at boot and once a day (retry after 6 h if it fails; skipped below 20 % battery unless charging). WiFi is only on for those few seconds.

The WiFi password is stored unencrypted in the ESP32's flash (NVS), like most hobby devices do. The setup network uses a fresh random WPA2 password each time and switches off after 5 minutes.

Controls

Button Click Hold
A (big front "M5") screen action secondary action; on the watch face: sudo make me a sandwich
B (side, top) previous screen –
PWR (side, bottom) next screen (6 s = hardware power off)

(Top/bottom in the default display orientation; flipping it with the Wrist setting swaps them physically.)

When the screen is off, any button just wakes it. Any button dismisses an alert.

Screens

  1. Watch face — time (12/24 h), date, the next alarm (or running timer), the three Fat Rings (kcal eaten / sitting / naps), and a scrolling ticker of wisdom (while(true) { sit(); }). Ponytail-Senpai lives his own life here: every 40–90 s he switches between coding (laptop, code scrolling in his glasses), gaming (handheld, tongue out, "COMBO!"), reading manga (page flips, "!?") and coffee, with a caption like rewrite in Rust or speedrun any%. A pokes him. He doesn't like that. Battery = pizza: 4 slices stuffed · 3 fed · 2 peckish · 1 HUNGRY · crumbs FEED ME (blinking). While charging the slices come back one by one (nom nom). Other screens show the classic icon with %.
  2. Step Budget — steps are a maximum, not a goal. The bar turns red as you approach your budget. Exceeding it: Segmentation fault (core dumped).
  3. Sit Counter — minutes sitting (any minute with < 6 steps), current and best couch streak.
  4. Nap Timer — A starts/stops a nap; ≥ 5 min counts. Goal: 3 naps / 90 min. Walking during a nap ends it ("sleepwalking does not count").
  5. Calorie Goal: EAT! — pick a food (hold A) and log it (A). Every calorie goes through Fat-Math™ (× 0.42), so you need to eat ~2.4× the goal. The real number is shown in tiny print with "(don't look)". Senpai reacts to what you eat: salad means 8 s of rage, an apple makes Senpai faint, cake is a party, Club-Mate and instant noodles mean compiling, popcorn means one more episode, ice cream is smug, protein shakes are suspicious. Works for names logged from the app too ("Apfelkuchen" is cake).
  6. Pizza Catch — tilt your wrist to move Senpai under falling pizza (+285 kcal each). Energy drinks give a 6 s caffeine rush: double points, everything faster, screen jitters. Dumbbells are exercise and cost a life, and so does every slice you let hit the floor. A starts; if the tilt goes the wrong way, hold A on the title screen to cycle the tilt axis (Y+/Y-/X+/X-). High score is saved. Virtual kcal don't count. Sadly.
  7. Timer — countdown presets: green tea 2:00, black tea 4:00, instant ramen 3:00, frozen pizza 12:00, drink in the freezer 30:00, power nap 20:00, plus a custom time. A selects, hold A starts. While running: A = +1 min, hold A = stop.
  8. Alarms — daily alarms with a label, up to 10. Defaults: Bedtime 02:30 (off), Breakfast 08:00, Lunch 12:00, Coffee & cake 15:30, Dinner 18:00. A selects, hold A edits (or adds a new one via "+ add alarm"). In the editor: PWR = next field, A = +1 (hold: fast) / press SAVE or DELETE, B = cancel. Labels: Alarm, Bedtime, Breakfast, Second breakfast, Lunch, Coffee & cake, Dinner, Midnight snack, Nap time, Pizza. When an alarm rings: A = snooze 9 min, any other button = off; it gives up after 60 s. The watch face shows the next alarm (or the running timer).
  9. Trophies — 24 stupid achievements (see below).
  10. Settings — set time, WiFi & NTP, phone app (pairing QR code), 12/24 h, language (EN/DE), theme, wrist (flip display), sound, brightness, screen timeout (5–60 s), raise to wake, step budget, kcal goal, reset today, power debug.
  11. Lab — hardware showcase / data dump. A = next page, hold A = the page's action. Each page switches its hardware on only while visible (the screen stays on for 60 s after the last button press here).
    • IMU: 3D model of the stick (roll/pitch from gravity + gyro, complementary filter; yaw from the gyro only, so it drifts), gyro rate, chip temperature. Gyro is on only on this page; steps keep counting. Hold A: zero yaw + recalibrate the gyro bias (keep still).
    • Mic (SPM1423 PDM, G0/G34): level in dBFS, spectrum 60 Hz–8 kHz, dominant frequency with note name (a crude tuner). Hold A: waveform. Jingles are muted meanwhile (mic and buzzer share the I2S unit).
    • Buzzer (G2): theremin. Hold A and tilt: 220 Hz–3.5 kHz.
    • LED + IR (Plus2: both on G19; Plus: LED G10, IR G9): off / breathe / strobe / IR beacon sending NEC frames (38 kHz via RMT, address 0x5B). Phone cameras show IR as purple.
    • LCD: color bars, gradients, 1 px lines, dead pixel hunt, backlight PWM sweep, frame rate.
    • Battery: voltage, raw ADC (Plus2: G38 sees half the battery voltage through a 1:2 divider, "BAT/2"), 4 h history graph (1 sample per minute), load test: rest vs. full backlight + LED + buzzer → voltage sag (on the Plus with the AXP192 also current → internal resistance).
    • RTC (BM8563 @0x51): time, raw registers, the VL flag (did the clock ever lose power), and how far the ESP32's own clock drifts against the RTC crystal since boot.
    • Touch: the ESP32's capacitive touch on the Grove pins G32/G33.
    • I2C: scan of the internal bus and the Grove port, with names of common M5 Units.
    • Bluetooth: BLE scanner. Devices heard in the last 30 s, sorted by signal strength, with a (very rough) distance guess and a label: the advertised name, or what the manufacturer data gives away (Apple FindMy tag / AirPods / iPhone, Samsung, Google, Xiaomi, ...). Phones rotate random addresses every ~15 min, so "seen" overcounts. The radio is on only while this page is visible. Hold A: clear the list.
    • System: chip, flash, heap, MAC, uptime, reset reason, build; hold A: WiFi scan.

Phone app (Bluetooth)

Settings → Phone app shows a QR code for the Android app (app/). Scan it in the app to pair; after that the app syncs (today, 60 days of history, meal log, achievements), sets the clock and time zone, and edits settings, alarms and WiFi. To save battery the stick is only reachable in short sync windows: while that screen is open, 60 s after boot, and 20 s at :00 and :30 of every hour. Hold A on the QR screen makes a new key and locks out all paired phones. Protocol: docs/ble-protocol.md.

Firmware updates (0.3.0+): the app's Device tab checks the forge's releases and installs new firmware over Bluetooth (or a .bin from a file, for dev builds). The stick writes it into its spare app slot, checks it, and restarts; a new firmware that doesn't run for 30 s is rolled back by the bootloader. Coming from 0.2.x, flash once via USB: 0.3.0 changes the flash layout to two app slots (settings and history are kept).

Snack nudges

Every now and then the watch suggests a snack with a soft chime and a little cookie pop-up: "How about a cookie?", "Need a refreshment?", "Chocolate is a vegetable. Technically." … To keep it charming rather than annoying:

  • random gaps of 1–2.5 h ("normal") or 2–4 h ("rare"), or off (Settings → Snack nudges)
  • never between 01:00 and 09:00, at most 8 per day
  • not within 45 min after you log food or 30 min after an alarm
  • never while you nap, play, edit something, or another alert/pop-up is on

Log food within 10 minutes of a nudge for the Peer Pressure achievement.

"Stop moving!" alerts

More than 40 steps per minute → flashing screen, anime speed lines, buzzer, red LED and an angry Senpai: "Your chair misses you." (3 min cooldown). Same for exceeding the step budget and for low battery ("FEED ME ELECTRONS — charging is the only cardio we allow").

Achievements

Hello, World! · Couch Potato · Kernel Compile · Greybeard · Ponytail Senpai · Works On My Couch · Don't Panic (step #42) · Segmentation Fault · Senpai Noticed · sudo sleep 20m · Stack Overflow (nap goal) · Second Breakfast · It's Over 9000! · sudo make me a sandwich · Charging Is Cardio · Low Battery Club · git commit at 3am · How To Exit Vim (leave Settings without changing anything) · Time Lord (NTP sync) · Pizza Party (25 slices in one game) · Caffeine Overflow (3 energy drinks in one game) · Five More Minutes (snooze the same alarm 3 times) · Peer Pressure (eat within 10 min of a snack nudge) · AFK (watch off-wrist for 8 h) · E.T. Phone Home (pair the app) · Patch Day (firmware update over the air) · Couch Commander (StickS3: zap the TV from your wrist).

Themes

Couch Classic · Neon Weeb · Greybeard Term(inal) · Pizza Mode

About the "anime GIFs"

The animations are drawn procedurally (sprite canvas, ~20 fps): Ponytail-Senpai blinks, sips coffee, sways his ponytail, sleeps with a snot bubble, eats pizza, gets an anger vein + steam when you move, and parties with sparkles when you unlock something. No third-party GIFs are bundled (licensing, and the 135×240 screen). A GIF → RGB565 converter for your own GIFs is on the roadmap.

Senpai's nine moods: idle, coding, eat, smug, sleep, gaming, party, reading, angry

These pictures are drawn by the firmware's own code (firmware/src/mascot.cpp), compiled on a PC with a stand-in for the display canvas and scaled up pixel-exact: python3 tools/senpai-render/make.py (needs a C++ compiler and the PlatformIO libraries once, for the stick's 5×7 font).

Power saving

  • Screen on: CPU at 80 MHz; 20 fps animations for 8 s after any interaction, then 5 fps. The screen dims 3 s before the timeout.
  • Screen off ("doze"): the MPU6886 samples the accelerometer at 25 Hz into its 1 KB FIFO (gyro switched off), the ESP32 light-sleeps for up to 4 s, wakes, processes the whole batch (steps, sitting, alerts) and sleeps again. Buttons A/B wake it instantly; PWR does too on the Plus2, on the Plus it goes through the AXP192 and is only noticed on the next timer wake (≤ 4 s).
  • Raise to wake (Settings, default off): detects turning the screen toward you (screen normal rises by ≥ 0.35 g within 2 s to ≥ 0.6 g and stays there 250 ms). The debug screen shows the live value (up0.83) and the number of detected raises (r3) for tuning. Needs the CPU to wake every 0.4 s instead of every 4 s, so it costs some battery.
  • Off-wrist / nightstand mode: if the accelerometer shows nothing but sensor noise (< 0.02 g peak-to-peak) for 10 minutes in a row, the watch is lying somewhere. It then ignores the IMU FIFO and only wakes every 15 s to check whether it was picked up (orientation changed by > 0.06 g); buttons still wake it instantly. Sitting minutes are paused (the couch streak is kept, and the 10 detection minutes are taken back), snack nudges and the low-battery alert wait, Senpai sleeps on the watch face. Alarms and timers still ring, so it works as a bedside alarm clock. Picking it up shows "Welcome back. Senpai kept your seat warm."
  • The speaker task only runs while a jingle plays.
  • If the IMU FIFO can't be set up, the firmware falls back to polling and stays awake (shown in red on the debug screen).

Measuring: Settings → Power debug (hold A). It shows battery voltage, the instantaneous current, the average current from the AXP192 coulomb counter (last minute and since boot; on the Plus2 instead the drain in %/h once the level dropped ≥ 3 % over ≥ 30 min), an estimated runtime, the share of time spent in light sleep, and the IMU FIFO status. To measure doze current: let the screen go off for 2+ minutes, then wake it — "avg 1m" covers the dozing minute. The same numbers are logged on the serial console every minute (pwr: DOZE avg -3.2 mA ...).

Known limitations of the mock-up

  • Step detection is a simple peak detector tuned by guesswork — needs real-world tuning (firmware/src/config.h).
  • Battery life is not measured on hardware yet; tuning values live in firmware/src/config.h.
  • The raise-to-wake thresholds are guesses and need tuning on a real wrist.
  • Fonts are ASCII only, so German uses ae/oe/ue for now.
  • No BLE yet; settings are local on the device.

Roadmap

  • Firmware mock-up: watch face, step budget, sit counter, nap timer, kcal goal, achievements, alerts, settings, battery
  • BLE GATT service (time sync, settings, stats export) — see docs/ble-protocol.md
  • Power saving: light sleep + IMU FIFO, adaptive frame rate, power debug screen
  • Off-wrist / nightstand mode (longer sleeps when not worn)
  • Mini game: Pizza Catch (tilt, energy drinks, dumbbells)
  • More mini games
  • GIF converter for custom animations (StickS3: full-screen animations from its 8 MB PSRAM)
  • StickS3 support: second build, BMI270 FIFO, clock without RTC chip, board-aware updates (untested on hardware, see docs/hardware.md)
  • StickS3 speaker: synthesized sounds (sad trombone, chomp, fanfare), Lab SOUNDS page
  • StickS3 Couch Commander: learn any IR remote, zap from the wrist
  • Steps: compare our detector with the BMI270's built-in counter on real walks, then use the better one (or both)
  • Battery: measure StickS3 runtime, use the M5PM1's low-power states
  • Mic: snore detector during naps ("Snore Lord" achievement)
  • Mic: scream-to-snooze alarm (snooze only if it's loud enough)
  • StickS3 USB keyboard (native USB): macro / panic button on hold A
  • Fridge mode (StickS3 magnetic back): on the fridge door, the IMU counts snack trips
  • Couch Commander on the Plus/Plus2 (transmitter only): codes learned on a StickS3 or sent from the app
  • Android app: QR pairing, settings, history, background sync
  • Firmware updates over Bluetooth (app → stick, with rollback)
  • Android app: barcode scanner (on the phone, camera + zxing) → kcal from Open Food Facts, for everything that comes in a package
  • Food photo → wildly underestimated kcal: small vision model via Ollama on the server (CPU only), see docs/food-vision-benchmark.md
  • Server: group leaderboard ("Laziest of the Week"), privacy-first, for stick owners and app-only users, see docs/server.md
  • Android app: groups (invite link/QR, weekly board, last week's winner), use without a stick
  • Trophy toast on the stick for last week's winner
  • Android app: steps from Health Connect for app-only users (no stick)