PolyMO: Een fysiek digitaal huisdier dat sterft als je te veel doomscrolt

Wanneer je te lang in een app verblijft die het huisdier "observeert", wordt het ziek. Het blijft ziek totdat je de telefoon weglegt. Ziekte betekent niet direct de dood, maar een ziek huisdier raakt sneller leeg dan een gezond huisdier. Als een huisdier te lang leeg blijft, sterft het definitief, waarna een reset vereist is die je expliciet moet aanvragen. De kern van de ervaring is dat jouw schermtijd de omgeving van het huisdier vormt, in plaats van een instelling binnen de software.

Je communiceert met het wezentje via spraak, waarop het antwoordt via zijn eigen luidspreker. Alles wat het huisdier zegt, wordt lokaal op je telefoon gegenereerd (on-device). Er is geen account en er is geen server.

Privacy en Beveiliging

Het systeem kan niet "naar huis bellen". Dit wordt bewezen door het feit dat de app geen enkele INTERNET-toestemming declareert. De volledige lijst met benodigde permissies is:

  • BLUETOOTH_CONNECT
  • BLUETOOTH_SCAN
  • RECORD_AUDIO
  • PACKAGEUSAGESTATS
  • POST_NOTIFICATIONS
  • RECEIVEBOOTCOMPLETED
  • FOREGROUND_SERVICE
  • FOREGROUNDSERVICECONNECTED_DEVICE

Er is een specifieke test die het AndroidManifest.xml uitleest en de build laat falen als er ooit INTERNET wordt toegevoegd. Voor een apparaat dat in je huis luistert en je meldingen kan lezen, is deze mechanische garantie essentieel.

Hoe het werkt

Het huisdier is het object waarmee je interactie hebt; de telefoon fungeert als de rekenkracht die het huisdier leent.

Interactie-stroom:

  • ESP32-S3 board: Bevat het AMOLED-gezicht, microfoon, luidspreker, touch-functionaliteit en de simulatie.
  • Android telefoon: Bevat Whisper (horen), llama.cpp (denken), Piper (stem) en de schermtijd-sensor.
  • Verbinding: Gebeurt via BLE (Bluetooth Low Energy) met Opus-audio en een specifiek protocol.

Wanneer je tegen het huisdier spreekt, streamt het board Opus-audio naar de telefoon. De telefoon transcribeert dit, vraagt een lokaal taalmodel om een antwoord in de stem van het huisdier, synthetiseert de spraak en streamt deze terug naar het huisdier. De telefoon heeft geen gebruikersinterface voor het praten met het huisdier; deze is enkel bedoeld voor de configuratie.

Het huisdier beheert zijn eigen leven. Honger, geluk, levensfase en overlijden worden gesimuleerd op het board, opgeslagen in het eigen flashgeheugen en getrackt door een eigen klok. De telefoon draagt enkel bij via schermtijd, taal en modellen. Als je de telefoon nooit meer verbindt, blijft het huisdier leven, maar wordt het stil.

Hardware

  • Board: Waveshare ESP32-S3 Touch AMOLED 1.8
  • Display: 1.8-inch 368×448 AMOLED, capacitieve touch
  • Oriëntatie: De UI draait geroteerd (448 breed, 368 hoog)
  • Audio: Ingebouwde microfoon en luidspreker
  • Verbinding: BLE naar een Android-telefoon

Zowel revisie V1 als V2 van het board worden ondersteund; de panel-driver (SH8601 of CO5300) wordt tijdens runtime gedetecteerd.

Documentatie

Het project bevat twee centrale documenten waarin beslissingen per sectienummer worden geciteerd:

  1. DESIGN.md: Bevat product- en UX-beslissingen. Hierin staan het state-model, de flows en de argumentatie. Voorbeelden zijn: waarom de schermtijd-toelating per sessie gaat in plaats van per dag, en waarom de score "verzadiging" (satiety) wordt genoemd in plaats van "honger" (om logische leesbaarheid in de code te garanderen).
  2. CLAUDE.md: Instructies voor het werken aan het project (geschreven voor Claude Code). Dit dient als conventiebestand met build-commando's, bekende valkuilen en de regel dat gezichten en persona's gegenereerd worden en niet handmatig bewerkt mogen worden.

Structuur van de Repository

  • pet-esp32/: ESP-IDF firmware (display, microfoon, luidspreker en simulatie).
  • android/: De app (BLE, Whisper, llama.cpp, Piper, schermtijd-tracking).
  • design-system/: Design tokens, gezichten en persona's (de 'source of truth').
  • tools/: Generatoren die gezichten en persona's omzetten in firmware- en app-code.
  • shared/: De wire-protocol header die door beide zijden wordt gedeeld.
  • hardware/: Printbare behuizingen (STL).
  • licences/: Volledige teksten voor meegeleverde lettertypen en GPL-3.0.
  • DESIGN.md: Product- en UX-beslissingen.
  • CLAUDE.md: Build-commando's en conventies.

Let op: De firmware en de app delen een protocol. Ze moeten synchroon worden gewijzigd. Het is aanbevolen om beide te flashen vanaf dezelfde commit om incompatibiliteit te voorkomen.

Bouwen (Building)

Firmware

. ~/esp/esp-idf/export.sh
cd pet-esp32
idf.py build
idf.py -p /dev/cu.usbmodem* flash

Let op: Het display kan na het flashen zwart blijven tot het board opnieuw is opgestart (USB-kabel trekken en opnieuw aansluiten).

Android app

De build vereist JDK 21. Een nieuwere JDK (zoals 25) zal leiden tot fouten zonder duidelijke oorzaak. Daarnaast is de Android SDK vereist via ANDROID_HOME of een sdk.dir regel in android/local.properties.

cd android
export ANDROID_HOME="$HOME/Library/Android/sdk"
JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home" \
./gradlew assembleDebug

Native dependencies

De app linkt naar llama.cpp, whisper.cpp, Piper, Opus en espeak-ng. Deze bevinden zich niet in de repository (circa 1.3 GB aan third-party source). De exacte commits zijn vastgelegd in NOTICE. Gebruik het volgende script om bronnen op te halen:

tools/fetch-natives.sh

Twee prebuilt shared libraries moeten handmatig worden geplaatst.

Tests

Er zijn 512 tests die geen apparaat vereisen:

cd android && ./gradlew testDebugUnitTest

Deze testen logica, WCAG-contrastverhoudingen, het verbod op raw .dp/.sp literals in UI-code, en de synchronisatie van gegenereerde assets.

Modellen

De app wordt zonder modellen geleverd. Je dient er drie zelf te importeren via de systeem-filepicker:

RolFormaatNotitie
Denken.ggufElk klein instruct-model dat llama.cpp kan laden
Horen.binEen Whisper-model
Stem.onnxEen Piper-stem

Generatie van Gezichten en Stemmen

De bronnen voor de gezichten en persona's bevinden zich in design-system/faces/.json en design-system/personas/.json. De firmware-tabellen, app-sets, notificatie-iconen en persona-prompts worden gegenereerd via:

  • tools/gen-faces.py
  • tools/gen-personas.py

Er zijn strikte designregels ingebouwd: een blij gezicht moet glimlachen, een dood gezicht heeft geen mond, en een persona mag geen vragen stellen of lijsten met apps hardop voorlezen.

Licenties

PolyMO maakt gebruik van drie licentielagen:

OnderdeelLicentieOpmerking
pet-esp32/ firmwareApache-2.0Schoon, linkt naar niets copyleft
android/ source, design-system/, tools/Apache-2.0Herbruikbaar onder Apache-voorwaarden
Uitgegeven APKGPL-3.0Linkt naar espeak-ng, waardoor het binary GPL erft

Waarom de APK verschilt: De native library van de app linkt naar espeak-ng (GPL-3.0-or-later) voor het omzetten van tekst naar fonemen. Omdat Apache-2.0 eenrichtingscompatibel is met GPL, blijft de broncode Apache-2.0, maar moet elk binary dat espeak-ng linkt, worden gedistribueerd onder GPL-3.0. De firmware is hiervan niet betrokken, omdat deze enkel reeds gesynthetiseerde audio via BLE ontvangt.