Een los AMOLED-schermpje van een paar tientjes dat je bol.com-omzet, je Shopify-omzet, je advertentiekosten en je Claude Code-verbruik laat zien. Claude Code bouwt het; jij plakt de prompts.
een avond bouwen · 16 min lezen · voor ondernemers die met Claude Code werken
Een los AMOLED-schermpje van een paar tientjes, naast je laptop, dat je bol.com-omzet, je Shopify-omzet, je advertentiekosten en je Claude Code-verbruik laat zien. Je schrijft geen regel code: Claude Code bouwt de firmware en de koppelingen, jij plakt de prompts en zegt wat erop moet.
Dit document is het complete draaiboek. De prompts staan er letterlijk in, inclusief de valkuilen die anders een avond kosten.
Wat er aan het eind op je bureau staat: een schermpje met een menu van apps, dat elke minuut ververst. Vier stuks om mee te beginnen.
Twee routes naar hetzelfde schermpje. De eerste is sneller, de tweede staat los van je bureau.
| Waar | Wat je krijgt | Prijs | Levertijd |
|---|---|---|---|
| Amazon | Het bordje zelf. Hangt aan de USB-kabel van je Mac, en die kabel voedt hem ook | rond de 40 euro | een paar dagen |
| AliExpress | De uitvoering met accu erbij, dus draadloos neer te zetten. Wel opladen | rond de 50 euro | twee tot drie weken |
Waar je op let bij het bestellen: het formaat moet 2.16 zijn en er hoort Touch bij te staan. Waveshare maakt deze reeks in meerdere maten, met en zonder aanraakscherm; de kleinere hebben een andere driver, waardoor de prompts hieronder niet meer kloppen.
Kom je toch uit bij de uitvoering zonder aanraakscherm, dan werkt dit draaiboek nog steeds. De startprompt schrijft voor dat je met de twee knoppen door alle schermen moet kunnen, juist voor het geval aanraken hapert. Je mist alleen het tikken.
De Amazon-link is een affiliate-link: koop je het schermpje daarmee, dan krijgen wij een kleine vergoeding van Amazon. Voor jou verandert de prijs niet, en we raden het ding aan omdat we er zelf mee werken.
Het is een Waveshare ESP32-S3-Touch-AMOLED-2.16: een compleet apparaatje waar scherm, aanraakglas, wifi, bluetooth en een accu-aansluiting al op zitten. Geen soldeerwerk, geen losse onderdelen.
| Onderdeel | Wat het is |
|---|---|
| Scherm | 2,16 inch AMOLED. Zwart is hier echt zwart, en daarom leest een donkere interface zo rustig |
| Aanraaklaag | CST9220. Je tikt en scrolt zoals op een telefoon |
| Bluetooth LE | De verbinding met je Mac. Geen wifi-netwerk, geen server, geen cloud |
| Knoppen | Twee stuks, altijd de terugval als aanraken hapert. Bewust niet weggelaten |
Verder nodig:
Twee dingen moeten kloppen voordat Claude Code aan de slag kan: een plek voor het project, en een bouwomgeving voor het schermpje. Die laatste heeft een eigen Python nodig, want de toolchain voor deze chip draait niet op de Python die standaard op je Mac staat.
mkdir -p ~/projects/bureaumonitor && cd ~/projects/bureaumonitor
brew install python@3.12
python3.12 -m venv ~/.pio-venv312
~/.pio-venv312/bin/pip install platformio
node -v # 20 of hoger; anders: brew install node
Sluit het schermpje met de USB-kabel aan en controleer dat je Mac hem ziet. Verschijnt er niets, probeer dan een andere kabel voordat je verder gaat.
ls /dev/cu.usbmodem*
/dev/cu.usbmodem21101
Start daarna Claude Code in die map met claude.
Dit is de belangrijkste stap. De prompt hieronder beschrijft het apparaat, de architectuur en de valkuilen. Plak hem in Claude Code en laat hem lopen. Hij bouwt de firmware, de achtergrondservice op je Mac en de bluetooth-koppeling ertussen.
Ik heb een Waveshare ESP32-S3-Touch-AMOLED-2.16 op USB aangesloten. Bouw hiervoor
een bureaumonitor: een schermpje met een menu van apps, gevoed door een klein
Node-programma op deze Mac dat elke minuut data over Bluetooth LE stuurt.
Hardware (staat in de Waveshare-documentatie, verifieer het daar):
- CO5300 AMOLED 480x480 via QSPI, touch CST9220 op I2C, twee knoppen.
- Gebruik alleen Waveshare's eigen documentatie en voorbeelden voor de pinout en
de drivers. Zoek de juiste GPIO-nummers daar op, gok ze niet.
Firmware:
- PlatformIO met het pioarduino-platform (arduino-esp32 core 3.x). Dat vereist
Python >= 3.10, dus gebruik ~/.pio-venv312/bin/pio. Zoek de actuele
platform-release op en zet die in platformio.ini.
- Arduino_GFX voor het scherm, LVGL 9 voor de interface.
- Een BLE GATT-peripheral met een eigen service-UUID en een schrijfbare
characteristic die platte JSON ontvangt.
- Een startscherm met app-iconen (rooster van tegels, label eronder, home-balk
onderin), en per app een eigen scherm met een titelbalk en een terugknop.
Tikken opent een app; de home-balk en de BOOT-knop gaan terug; de andere knop
stapt door naar het volgende scherm. Navigatie moet altijd ook zonder
aanraakscherm werken.
- Donkere interface: zwarte achtergrond, inhoud in afgeronde kaarten, per rij
label links en waarde rechts. Bak een eigen lettertype in via lv_font_conv;
de ingebouwde LVGL-fonts kennen geen euroteken.
Node-service op de Mac:
- TypeScript, draait onder pm2, pusht elke 60 seconden.
- Bluetooth via @abandonware/noble. macOS vraagt eenmalig toestemming voor
Bluetooth; leg in de README uit waar je die aanzet.
- Elke databron is een eigen module met een simpele functie die getallen
teruggeeft. Faalt een bron, dan stuur je -1, nooit 0.
Vier dingen die je moet respecteren, ze zijn duur betaald:
1. Een BLE write-without-response wordt op macOS rond 182 bytes STIL afgekapt.
Splits de data daarom in meerdere kleine frames en parse in de firmware per
sleutel, zodat elk frame op zichzelf geldig is. Een scherm erbij = een frame
erbij, nooit een frame groter maken.
2. Zet de BLE-init in setup() VOOR de LVGL-init. LVGL's geheugenpool is
statisch; is die te groot of te vroeg, dan start de radio niet meer op
("nimble host init failed") terwijl al het andere normaal lijkt. Houd de pool
klein (rond 72 kB) en bouw maar EEN scherm tegelijk: bouw een scherm bij het
openen en gooi het vorige weg.
3. Gebruik geen clip_corner en geen transform of opacity op een container.
LVGL rendert die dan via een tussenlaag die hier niet in het geheugen past;
het scherm blijft dan stil hangen. Zet LV_USE_LOG op warn zodat zulke
fouten zichtbaar zijn.
4. Het paneel adresseert kolommen per twee. Snap elk te hertekenen gebied naar
de volle schermbreedte en een even hoogte, anders staat de tekst scheef en
trilt hij bij elke update.
Bouw daarnaast twee hulpmiddelen:
- Seriële commando's over USB (115200) om te navigeren en te testen: home, next,
app <n>, info. Een regel met JSON wordt als datapakket verwerkt.
- Een "shot"-commando dat het scherm als base64 over serieel dumpt, plus een
Python-scriptje dat daar een PNG van maakt. Het paneel is niet uit te lezen,
dus dit is de enige manier om te controleren hoe het er echt uitziet.
Begin met een leeg scherm dat "hallo" toont en flash dat eerst, zodat we weten
dat de toolchain klopt. Daarna pas de rest.
Een schermpje met een leeg menu en een service op je Mac die nog niets zinnigs stuurt. De drie stappen hierna vullen dat. Elke app is steeds dezelfde drie onderdelen: een bron die getallen ophaalt, een frame dat ze verstuurt, en een scherm dat ze toont.
Claude Code werkt met twee vensters: een van vijf uur en een van zeven dagen. Hoe vol die zitten staat niet in een dashboard, maar in de antwoord-headers van een gewone API-aanroep. Eén minimale aanroep per minuut is genoeg om te weten waar je staat.
Dit werkt alleen met het token van je eigen Claude Code-installatie. Dat staat in je macOS-sleutelhanger en wordt door Claude Code zelf ververst. Met een losse API-sleutel krijg je andere limieten te zien, niet die van je abonnement.
Voeg een Claude-app toe aan de bureaumonitor.
De data: doe elke minuut de kleinst mogelijke /v1/messages-aanroep (max_tokens 1)
en lees het verbruik uit de ANTWOORD-HEADERS, niet uit de body. De headers
beginnen met anthropic-ratelimit-unified-: er zit een 5-uurs en een 7-daagse
benutting in, plus wanneer elk venster reset. Dump eerst eenmalig alle headers
van een echte aanroep en laat me zien welke je gaat gebruiken - namen kunnen
veranderen, dus baseer je op wat er echt terugkomt.
Voor de authenticatie: lees het OAuth-token uit de macOS-sleutelhanger
(het item dat Claude Code zelf aanmaakt) en lees het elke ronde opnieuw, zodat
een verversing vanzelf meekomt. Zet het token nergens in een bestand.
Is het token verlopen, log dan een duidelijke melding en sla die ronde over -
de andere apps moeten gewoon door blijven lopen.
Op het scherm: het percentage van beide vensters met een balk eronder, en hoe
lang het nog duurt tot elk venster reset. Daaronder het tempo: hoeveel procent
per uur je er nu doorheen gaat en hoe lang je op dat tempo nog hebt. Bereken dat
uit een klein rollend geheugen van de laatste twintig minuten; daalt het
percentage, dan is het venster gereset en begin je opnieuw.
bol heeft twee gescheiden koppelingen: één voor je verkopen en één voor je advertenties. Je maakt de sleutels aan in je verkoopaccount, onder de instellingen voor API's. Je hebt er twee nodig: een voor de Retailer API (bestellingen) en een voor de Advertiser API (kosten en toegeschreven omzet).
| Getal | Wat het is | Waar het vandaan komt |
|---|---|---|
| Omzet | Wat je in de periode verkocht hebt | Retailer API, bestellingen optellen |
| Advertentiekosten | Wat je in dezelfde periode uitgaf | Advertiser API, kosten per dag |
| TACOS | Kosten gedeeld door je totale omzet. Zegt hoeveel van alles wat je verkocht naar advertenties ging | Rekensom |
| ROAS | Omzet die aan advertenties toegeschreven is, gedeeld door de kosten | Rekensom |
Stuur de periodes alle drie mee. Het scherm kan niets terugvragen aan je Mac; het ontvangt alleen. Wil je kunnen wisselen tussen vandaag, deze week en deze maand, stuur dan alle drie de sets in aparte frames. Het schermpje bewaart ze en wisselt bij een tik direct, zonder te wachten op de volgende ronde.
Voeg een bol.com-app toe aan de bureaumonitor, met een periodekiezer.
Databron 1 - Retailer API (bestellingen): som van de verkopen per periode.
Databron 2 - Advertiser API (advertenties): kosten en toegeschreven omzet per dag.
Ik zet de client id en secret van beide in ~/.bureaumonitor/bol.json. Lees ze
daaruit, nooit uit de code. Haal eerst de officiële documentatie van beide API's
op, laat me zien welke endpoints je gaat gebruiken, en doe pas daarna een echte
aanroep waarvan je de ruwe uitvoer aan mij laat zien. Verzin geen veldnamen.
Bereken per periode (vandaag / 7 dagen / 30 dagen):
- omzet in hele euro's
- advertentiekosten in hele euro's
- TACOS = kosten / omzet, in tienden van een procent
- ROAS = toegeschreven omzet / kosten, in tienden. Reken die ALTIJD over
afgeronde dagen (dus zonder vandaag), want bol schrijft verkopen pas dagen
later aan een advertentie toe. Zet op het scherm bij welke periode het hoort.
- de verandering ten opzichte van dezelfde periode ervoor, in hele procenten.
Vergelijk vandaag met gisteren tot HETZELFDE tijdstip, anders staat de hele
ochtend in het rood zonder dat dat iets zegt.
Stuur elke periode als een eigen frame (denk aan de bytegrens per write).
Op het scherm: bovenaan de omzet groot met een gekleurd label voor de
verandering, daaronder drie rijen: advertentiekosten, TACOS, ROAS. Bovenin de
app een keuze tussen Vandaag / 7 dagen / 30 dagen; die wisselt lokaal tussen de
al ontvangen frames, dus zonder te wachten op de volgende ronde.
Lukt een aanroep niet, stuur dan -1 en toon een streepje. Een storing mag er
nooit uitzien als een omzet van nul.
Shopify is eenvoudiger dan bol: je maakt in je eigen winkelbeheer een custom app aan, geeft die leesrechten op bestellingen, en krijgt een token. Verder heb je niets nodig.
~/.bureaumonitor/shopify.json.Advertentiekosten kent Shopify niet, dus daar zijn twee werkbare routes voor. De Meta Marketing API of Google Ads API is het meeste werk, maar loopt daarna vanzelf en per dag exact. Eén regel in een bestandje met je budget per dag is verrassend goed genoeg zolang je budget stabiel is, en in vijf minuten geregeld.
Voeg een Shopify-app toe aan de bureaumonitor, met dezelfde periodekiezer als
bol.com (vandaag / 7 dagen / 30 dagen).
Omzet: via de Shopify Admin API, bestellingen per periode optellen. Shop-domein
en token staan in ~/.bureaumonitor/shopify.json; lees ze daaruit. Let op de
paginering van de API - haal alle pagina's op, niet alleen de eerste, anders
klopt de omzet stilletjes niet op een drukke dag.
Advertentiekosten: die kent Shopify niet. Maak er een aparte bron van met
dezelfde vorm als de andere, en lees hem uit ~/.bureaumonitor/adspend.json
(bedrag per dag). Staat dat bestand er niet, stuur dan -1 en laat TACOS en ROAS
een streepje zijn met de tekst "geen advertentiebron gekoppeld". Bouw het zo dat
ik die bron later kan vervangen door de Meta- of Google-Ads-API zonder de app of
het scherm aan te passen.
Hergebruik alles wat de bol-app al heeft: dezelfde berekeningen, dezelfde
schermopbouw, dezelfde omgang met ontbrekende data. Als er code te delen valt
tussen de twee verkoopkanalen, deel die dan echt in plaats van te kopiëren.
Hier wordt het leuk. Elke app is dezelfde drie stappen, en die zijn zo klein dat je in één gesprek van idee naar scherm gaat.
| Stap | Wat het is | Wat jij zegt |
|---|---|---|
| Bron | Een functie op je Mac die getallen teruggeeft. Een API, een database, een bestand, een script | "Haal X op uit Y" |
| Frame | Een eigen klein JSON-pakketje met korte sleutels dat over bluetooth gaat | "Stuur ze mee in een eigen frame" |
| App | Een scherm met een icoon in het menu, dat die getallen toont | "Toon het zo..." |
Ideeën die goed werken op zo'n schermpje:
De vuistregel: getallen waar je iets mee doet, niet getallen die je alleen leuk vindt. Een scherm dat je na een week niet meer aankijkt, heeft de verkeerde dingen erop staan.
Voeg een nieuwe app toe aan de bureaumonitor.
Naam: [bijvoorbeeld: Facturen]
Wat ik wil zien: [bijvoorbeeld: hoeveel er openstaat, hoeveel daarvan te laat
is, en het aantal facturen dat wacht]
Waar het vandaan komt: [bijvoorbeeld: de API van mijn boekhoudpakket / een CSV
in map X / een SQLite-database]
Het belangrijkste getal, dat groot bovenaan moet: [bijvoorbeeld: bedrag te laat]
Volg exact het patroon van de bestaande apps: een aparte bronmodule, een eigen
frame met korte sleutels binnen de bytegrens, en een app-bestand met een build-
en een tick-functie. Gebruik de bestaande bouwstenen voor kaarten en rijen, zodat
het scherm bij de rest past. Maak een icoon in dezelfde stijl als de andere.
Bij een storing -1 sturen en een streepje tonen, nooit een nul.
Laat me na het flashen een screenshot van het nieuwe scherm zien.
Deze staan ook in de startprompt, maar het helpt als jij ze herkent wanneer het misgaat. Ze hebben allemaal gemeen dat er geen foutmelding komt.
| Symptoom | Wat er aan de hand is |
|---|---|
| De helft van je data komt niet aan, of het scherm toont onzin | Je pakketje is te groot. macOS kapt een bluetooth-write rond 182 bytes af zonder iets te zeggen. Splitsen in kleinere frames |
| Bluetooth start niet op, de rest werkt prima | De interfacebibliotheek heeft het geheugen ingepikt dat de radio nodig had. Kleinere geheugenpool, en de radio eerst starten |
| Het scherm bevriest halverwege een nieuw scherm | Er zit een afgeronde clipping of een transformatie op een container. De grafische laag probeert dan een tussenbuffer die niet past |
| Tekst staat scheef en trilt bij elke update | Dit paneel adresseert kolommen per twee. Elk te hertekenen gebied moet op even pixels beginnen en eindigen |
| Losse tekens zijn lege blokjes | Het lettertype op het apparaat kent alleen de tekens die je erin bakt. Een euroteken moet je expliciet meenemen |
| Alles werkt, maar er komt nooit data aan | Bluetooth-toestemming in de systeeminstellingen van je Mac. Je ziet er niets van; het blijft gewoon stil |
| Je Mac ziet het apparaat niet op USB | Negen van de tien keer een laadkabel zonder datadraden |
De service op je Mac moet blijven lopen, ook na een herstart. Daar is pm2 voor: die start hem opnieuw als hij omvalt en na het opstarten van je Mac.
npm install -g pm2
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup # volg de regel die hij teruggeeft
pm2 logs bureaumonitor # meekijken
Twee dingen om te weten voor later. Staat op je Mac de schijfversleuteling aan zonder automatisch inloggen, dan draait er na een herstart niets tot iemand inlogt; het schermpje staat dan op "wachten op host". En verander je later iets aan de interface, laat Claude Code dan altijd een screenshot maken na het flashen. Het is de goedkoopste manier om te zien of het klopt.
Geen dashboard dat je moet openen, en geen melding die je stoort. Een klokje dat toevallig je bedrijf laat zien. Je kijkt ernaar als je toch al opkijkt, en de rest van de dag staat het er gewoon.
Loop je vast, of wil je dit met je eigen systemen doen in plaats van met de vier hierboven? Plan een gesprek; dan kijken we mee.
← Terug naar de kennisbank
In een vrijblijvend gesprek brengen we in kaart welke systemen jouw business nodig heeft.
Gratis 1-op-1 call met Job Lenselink