14 September 2026 –
Soms begint een project met een apparaat waarvan ik vooral wil weten hoe het werkt. In dit geval is dat een kleine B23a draadloze thermische printer. Normaal gesproken installeer je de AiYin-app op je telefoon, maak je via Bluetooth verbinding en print je tekst, labels of afbeeldingen.
Dat werkt prima, maar voor mij wordt zo’n apparaat vooral interessant wanneer ik de originele software helemaal uit de keten haal. Kan ik rechtstreeks met de printer communiceren? Kan ik achterhalen welk protocol wordt gebruikt? En kan ik de printer uiteindelijk autonoom laten werken zonder telefoon of computer?
Ik pak het project bewust in twee stappen aan. Eerst ontwikkel ik op mijn laptop een werkende printerdriver in Python. Python geeft me de vrijheid om snel te experimenteren met Bluetooth Low Energy (BLE), services, characteristics, printercommando’s en rasterdata. Als die aansturing betrouwbaar werkt, port ik dezelfde functionaliteit naar C++ op een ESP32-C3.
Die tweede stap verandert het karakter van het project. De Python-versie is mijn ontwikkel- en reverse-engineeringomgeving. Met de ESP32-C3 ontstaat vervolgens een klein embedded platform dat zelfstandig alerts, notificaties en werkbonnen kan printen.

die normaal via een app op de telefoon wordt aangestuurd.
Eerst het doosje open
Zoals wel vaker begint het project met het verwijderen van een paar schroeven. Binnenin vind ik een thermische printkop, papiertransport met een klein motortje, een 3,7 volt Li-ion accu en een verrassend compacte printplaat met de complete besturing.

Ik haal de elektronica verder uit elkaar om te zien hoe de verschillende onderdelen met elkaar verbonden zijn. De thermische printkop en motor zijn via flexprints met de besturing verbonden. Onder de microscoop bekijk ik vervolgens printbanen, componenten en verschillende delen van de elektronica.


De hardware vertelt me hoe het apparaat fysiek is opgebouwd, maar nog niet welke data ik naar de printer moet sturen. Daarvoor moet ik naar de softwarekant.
De AiYin-app wordt mijn documentatie
Een officiële SDK of protocolbeschrijving heb ik niet. Ik gebruik daarom de bestaande software en de Bluetooth-communicatie als uitgangspunt voor mijn eigen onderzoek.
De printer meldt zich als:
B23a_7F36_BLE
In de app kan ik daarnaast het serienummer en firmwareversie V2.0.4 terugvinden.


Vervolgens gebruik ik de nRF BLE-browser om te bekijken welke services en characteristics het apparaat aanbiedt. Ik zoek daarbij vooral naar characteristics waarop ik kan schrijven en naar notifications waarmee de printer informatie terugstuurt.
Mijn exemplaar heeft BLE MAC-adres:
CC:99:17:57:7F:36
In mijn uiteindelijke driver gebruik ik service FF00:
0000ff00-0000-1000-8000-00805f9b34fb
Binnen deze service gebruik ik FF02 om data naar de printer te sturen en FF03 om notifications van de printer te ontvangen. Langzaam ontstaat daarmee mijn eigen documentatie van een printer waarvoor ik geen bruikbare programmeerhandleiding heb.
Eerst een werkende driver in Python
Ik begin bewust nog niet met de ESP32-C3. Eerst wil ik op mijn laptop aantonen dat ik de printer volledig vanuit mijn eigen software kan bedienen. Daarvoor gebruik ik Python met Bleak voor Bluetooth Low Energy. Python is voor dit soort onderzoek prettig omdat ik snel kan aanpassen en testen. Een paar bytes veranderen, het script opnieuw uitvoeren en direct kijken wat de printer doet. Een vereenvoudigd stukje van deze eerste driver ziet er zo uit:
import asyncio
from bleak import BleakClient
PRINTER = "CC:99:17:57:7F:36"
FF02 = "0000ff02-0000-1000-8000-00805f9b34fb"
FF03 = "0000ff03-0000-1000-8000-00805f9b34fb"
ready = asyncio.Event()
def notification_handler(sender, data):
if data == bytes([0x01, 0x01]):
ready.set()
async def send(client, data):
ready.clear()
await client.write_gatt_char(
FF02, data, response=False
)
try:
await asyncio.wait_for(
ready.wait(), timeout=0.15
)
except asyncio.TimeoutError:
pass
Via FF02 verstuur ik data. Tegelijkertijd luister ik via FF03 naar de printer. Wanneer daar 01 01 binnenkomt, kan mijn programma doorgaan met het versturen van de volgende data.

Met Python test ik stap voor stap welke BLE-commando’s de printer accepteert.
Het moment waarop het motortje voor het eerst vanuit mijn eigen Python-code begint te draaien, is een belangrijke mijlpaal. Het ziet er misschien niet spectaculair uit, maar de route klopt:
Python → BLE → eigen printercommando’s → papier.
Van commando’s naar pixels
De thermische printkop is 384 pixels breed. Omdat iedere pixel één bit vertegenwoordigt, bestaat één horizontale regel uit 384 / 8 = 48 bytes. Voor het versturen van rasterdata gebruik ik een commando dat begint met:
1D 76 30 00
Daarna volgen breedte, hoogte en de bitmapdata. Iedere groep van acht pixels wordt daarbij één byte. Een paar bytes versturen blijkt alleen iets anders dan een complete grafische bon betrouwbaar over BLE krijgen.
Niet sneller sturen dan de printer kan verwerken
Wanneer ik probeer grotere hoeveelheden rasterdata te versturen, ontstaan incomplete prints omdat ik de printer dan simpelweg zo snel mogelijk van te veel bytes voorzie. De notifications via FF03 blijken hiervoor de sleutel. De printer stuurt 01 01 terug wanneer hij weer verder kan. Ik deel de datastroom daarom op in kleinere stukken. In de ESP32-versie gebruik ik uiteindelijk chunks van maximaal 120 bytes, gecombineerd met een timeout van 150 milliseconden.
Conceptueel wordt de communicatie daarmee:
ESP32-C3 B23a
│ │
│── data ───────► FF02 ────────►│
│ │
│◄──────── FF03: 01 01 ─────────│
│ │
│── volgende data ─────────────►│
Met die flow control wordt het printen van grotere afbeeldingen betrouwbaar.
Nu pas: van Python naar C++
Op dit punt heb ik op mijn laptop een werkende Python-driver. Ik weet hoe ik verbinding moet maken, welke characteristics ik nodig heb, hoe de printercommando’s zijn opgebouwd en hoe ik rekening moet houden met de verwerkingssnelheid van de printer.
Nu pas port ik de driver naar C++ op een ESP32-C3.
Voor de embedded versie gebruik ik:
#include <Arduino.h>
#include <NimBLEDevice.h>
#include <Adafruit_GFX.h>
#include <qrcode.h>
Arduino.h vormt de basis, NimBLE-Arduino verzorgt BLE, Adafruit GFX gebruik ik voor de grafische opbouw en qrcode.h genereert de QR-code. De flow control die ik eerst in Python ontwikkel, komt vrijwel letterlijk terug in C++:
bool sendChunk(const uint8_t* data, size_t length) {
flowReady = false;
bool result = ff02->writeValue(
data, length, false
);
if (!result) return false;
unsigned long start = millis();
while (!flowReady) {
delay(1);
if (millis() - start > 150) {
break;
}
}
return true;
}
De callback van FF03 zet flowReady weer op true zodra 01 01 binnenkomt. Dezelfde logica die eerst op mijn laptop draait, zit nu dus in een microcontroller.

Zelf een complete bon renderen
Nu de ESP32-C3 zelfstandig kan printen, wil ik ook de complete bon op de microcontroller zelf opbouwen. Daarvoor gebruik ik GFXcanvas1 uit Adafruit GFX:
GFXcanvas1 canvas(
PRINTER_WIDTH,
CANVAS_HEIGHT
);
Op dit monochrome canvas teken ik teksten, lijnen, waarschuwingssymbolen, KPI-indicatoren, een trendgrafiek en een QR-code. De iconen zijn geen kant-en-klare afbeeldingen. Ik bouw ze met functies als drawLine(), fillRect(), drawCircle() en drawTriangle(). De QR-code wordt met qrcode.h gegenereerd en eveneens rechtstreeks op het canvas getekend. Voor het printen verwerk ik de afbeelding in stroken van 32 pixels hoog. Eén strook bevat 48 × 32 = 1536 bytes rasterdata. Met de acht bytes rasterheader komt een volledige tijdelijke buffer op ongeveer 1544 bytes. De complete route is daarmee:
Adafruit GFX → 1-bit canvas → rasterbuffer → BLE-chunks → FF02 → printer.
Fouten horen bij het porten
Tijdens het uitbreiden van de C++-code kom ik natuurlijk ook compilerfouten tegen. Bijvoorbeeld:
'canvas' was not declared in this scope
en:
'drawWarningIcon' was not declared in this scope
De oorzaak blijkt niet Adafruit GFX te zijn. Tijdens het aanpassen van de layout heb ik een deel van renderTicket() op de verkeerde plaats in de sketch geplaatst, waardoor objecten en functies worden gebruikt voordat ze bekend zijn. De oplossing is de code weer logisch structureren: eerst libraries en globale objecten, vervolgens callbacks en tekenfuncties en daarna pas het renderen en printen. Ook fysieke testprints zijn onderdeel van het debuggen. Een icoon dat op een beeldscherm prima lijkt, kan op thermisch papier ineens te klein zijn. Teksten moeten groter, de QR-code duidelijker en de layout moet vooral op het echte papier werken.
Van printerdriver naar Predictive Maintenanc-alert
De eerste echte toepassing wordt daarom geen simpele “Hello World”, maar een sample voor een ander project ‘Predictive Maintenance-alert’. De ESP32-C3 bouwt zelfstandig een bon met machine-ID, locatie, eventnummer en anomaly score. Daaronder komen KPI’s voor trillingen, temperatuur, stroom en toerental. Een grafiek toont de trend en onderaan staat een QR-code waarmee de papieren melding aan aanvullende digitale informatie kan worden gekoppeld. Met deze Predictive Maintenance-alert kan de machine operator meteen aan de slag. Vermeld is waar welke machine staat en wat er mogelijk aan de hand is. Actiegericht en helder als opdrachtbon.

zelfstandig gerenderd door de ESP32-C3 en via BLE naar de printer gestuurd.
Sensor in, papier uit
De ESP32-C3 heeft naast BLE ook WiFi. Daarmee kan ik het platform later koppelen aan bijvoorbeeld MQTT, een REST-interface, een machinecontroller of een andere IoT-omgeving. Een Anomaly Detection-model kan dan ergens in de keten afwijkend gedrag constateren. De ESP32-C3 ontvangt de melding, bouwt de bijbehorende bon op en print hem direct bij de machine. Geen AiYin-app, geen telefoon en uiteindelijk ook geen laptop meer.
Dat is ook wat ik leuk vind aan dit soort projecten. Ik begin met het openmaken van een goedkope consumentenprinter, kijk onder de microscoop naar de elektronica en analyseer de BLE-communicatie. Vervolgens schrijf ik eerst een werkende driver in Python op mijn laptop. Zodra ik daarmee het protocol begrijp, port ik dezelfde functionaliteit naar C++ op een ESP32-C3. Even later zit ik individuele bits in een rasterbuffer te schuiven, om vervolgens weer naar een papieren testprint te kijken omdat een icoontje twee millimeter groter moet.
Elektronica, reverse-engineering, Python, Bluetooth Low Energy, embedded C++, grafische vormgeving, IoT en data-analyse komen zo vanzelf in één project bij elkaar. En uiteindelijk blijft het resultaat erg tastbaar. De ESP32-C3 stuurt zijn laatste pakket naar de printer, het motortje begint te draaien en een paar seconden later ligt mijn eigen Predictive Maintenance-alert op tafel.
Sensor in, papier uit.
Dat is precies het soort omgeving dat ik graag bouw.
