Een tijd geleden had ik een aantal RFID-tags gekocht om wat mee te experimenteren. Ze lagen tot nu toe ongebruikt in een laatje tussen de andere componenten. Dit weekend kreeg ik ineens het idee om die dingen eens ‘nuttig’ in te zetten. Ik had namelijk een probleem.
Ik heb een groot aantal elektronicacomponenten en -modules, netjes opgeruimd in allerlei kastjes met daarin een zwik aan laatjes. Maar waar ligt nou dat moduletje dat ik nodig heb? Vaak in het allerlaatste laatje waarin ik zoek, of toch in het eerste omdat ik weer eens niet goed gekeken heb! Dus …. aan de slag om dit probleem op te lossen … mét de RFID-tags! En om ook eens gebruik te maken van AI werd de hulp ingeroepen van (gratis) Gemini.
Dat ging uiteindelijk erg soepel. Ik heb wat moeite moeten doen om de juiste prompts te formuleren; prompts die vooral sturing geven aan wat ik zelf voor ogen had.

De uitgebreide prompt voor Gemini. Eigenlijk had ik met deze vraag moeten beginnen, maar voordat ik zover was heb ik eerst allerlei deelvragen gesteld. Zo heb ik bijvoorbeeld eerder de database met de twee tabellen laten opmaken.
Ik wil een systeem opzetten waarmee ik componenten in lades kan bijhouden. Daarvoor heb ik een MariaDB database “voorraadbeheer” op een Synology NAS staan (ip-adres 192.168.2.X) met twee tabellen “lades” en “componenten”. De lades hebben elk een RFID-tag. De tags moeten uitgezen worden door een ESP32 C3 super mini verbonden met een RC522: RST op GPIO10, MISO op GPIO2, MOSI op GPIO7, SCK op GPIO6 en SDA (SS) op GPIO 21. Schrijf een Arduino programma voor de ESP32 die code van de tag over WiFi verstuurt naar een pc. Op die pc (IP adres 192.168.2.42) moet een Python programma draaien die de RFID-code van de ESP32via WiFi ontvangt en waar voor die tagcode gegevens kunnnen worden aangevuld met meerdere gegevens (o.a. naam component, aantal, enz) van meerdere componenten die in die lades bewaard zullen worden. Deze gegevens moeten vervolgens doorgegeven worden naar de database. Schrijf ook hiervoor de Python code.
Het antwoord van Gemini was leuk.
Wat een mooi en praktisch project! Om dit te realiseren, gaan we het opsplitsen in drie delen: de voorbereiding van de database, de Arduino-code voor de ESP32-C3, en de Python-server voor je pc.
Voor het opzetten van de database heb ik op mijn NAS (DS_Synology) MariaDB (een MySQL-variant), PHP8.2 en phpMyAdmin geïnstalleerd en een database ‘voorraad’ met een gebruiker ‘root’ aangemaakt.
Gemini kwam met de SQL-code om de tabellen aan te maken.
CREATE TABLE lades (
id INT AUTO_INCREMENT PRIMARY KEY,
rfid_tag VARCHAR(50) UNIQUE NOT NULL
);
CREATE TABLE componenten (
id INT AUTO_INCREMENT PRIMARY KEY,
lade_id INT,
naam VARCHAR(255) NOT NULL,
aantal INT NOT NULL DEFAULT 0,
FOREIGN KEY (lade_id) REFERENCES lades(id) ON DELETE CASCADE
);

Hieronder zie je twee uitvoeringen van de 215NFC RFID-chip. Van de linker plakbare versie (diameter 22mm) had ik 50 stuks, ze kostten me €0,07 per stuk. Van de rechter, een plastic schijfje van ca. 25mm had ik er 20, ze waren €0,24 per stuk.
Daarnaast had ik ook nog 10 stuks van de duurdere plakbare NXP NTAG213 gekocht, met een diameter van 6mm een stuk kleiner, maar ook duurder, nl. €0,81 per stuk. De RF522-module bleek deze laatste chips niet te kunnen lezen! Van de plastic-schijfjes had ik er dus maar 20, te weinig voor de 4×6 = 24 laatjes. Dus de keuze voor de goedkoopste plakkertjes was hierdoor snel gemaakt.


Om de ESP32 te laten werken als scanner van de RFID-chips had ik eerst een werkende versie van de scanner gemaakt om na te gaan dat e.e.a. zou werken met de aansluitingen met de RF522-module (de lezer-module) en zodoende de pinnummers in de Prompt voor te kauwen.
Vervolgens kwam Gemini met de volgende Arduino-code.
#include <WiFi.h>
#include <SPI.h>
#include <MFRC522.h>
// Pin configuratie (ESP32-C3 Super Mini)
#define RST_PIN 10
#define SS_PIN 21
#define SCK_PIN 6
#define MISO_PIN 2
#define MOSI_PIN 7
// WiFi & Netwerk configuratie
const char* ssid = "VUL_HIER_JE_WIFI_NAAM_IN";
const char* password = "VUL_HIER_JE_WIFI_WACHTWOORD_IN";
const char* host = "192.168.2.X"; // IP van de PC
const uint16_t port = 5000; // Poort waar Python naar luistert
MFRC522 mfrc522(SS_PIN, RST_PIN);
void setup() {
Serial.begin(115200);
// Initialiseer custom SPI pinnen
SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, SS_PIN);
// Initialiseer RFID lezer
mfrc522.PCD_Init();
Serial.println("RFID lezer gereed.");
// Maak verbinding met WiFi
WiFi.begin(ssid, password);
Serial.print("Verbinden met WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi verbonden!");
Serial.print("IP Adres: ");
Serial.println(WiFi.localIP());
}
void loop() {
// Kijk of er een nieuwe kaart is
if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) {
return;
}
// Lees de UID van de tag
String uid = "";
for (byte i = 0; i < mfrc522.uid.size; i++) {
uid += String(mfrc522.uid.uidByte[i] < 0x10 ? "0" : "");
uid += String(mfrc522.uid.uidByte[i], HEX);
}
uid.toUpperCase();
Serial.println("Tag gelezen: " + uid);
// Stuur de tag via TCP naar de PC
WiFiClient client;
if (client.connect(host, port)) {
client.print(uid);
client.stop();
Serial.println("Tag succesvol verzonden naar PC.");
} else {
Serial.println("Kan geen verbinding maken met de PC.");
}
// Voorkom dat dezelfde tag direct tig keer wordt verzonden
delay(2000);
}
Op de pc moest de MySQL-connector geïnstalleerd worden om Python met de database te kunnen verbinden.
install mysql-connector-python
Voor de pc kwam Gemini met de volgende Python code.
import socket
import mysql.connector
from mysql.connector import Error
# Database configuratie
DB_HOST = "192.168.2.X" # IP van je Synology NAS
DB_USER = "JOUW_DB_GEBRUIKERSNAAM"
DB_PASS = "JOUW_DB_WACHTWOORD"
DB_NAME = "voorraadbeheer"
# Server configuratie
HOST = "0.0.0.0" # Luistert naar alle binnenkomende verbindingen
PORT = 5000
def beheer_lade(rfid_tag):
try:
# Maak verbinding met MariaDB
connection = mysql.connector.connect(
host=DB_HOST,
user=DB_USER,
password=DB_PASS,
database=DB_NAME
)
if connection.is_connected():
cursor = connection.cursor()
# Zorg dat de lade in de database staat (INSERT IGNORE werkt dankzij UNIQUE constraint)
cursor.execute("INSERT IGNORE INTO lades (rfid_tag) VALUES (%s)", (rfid_tag,))
connection.commit()
# Haal het lade_id op
cursor.execute("SELECT id FROM lades WHERE rfid_tag = %s", (rfid_tag,))
lade_id = cursor.fetchone()[0]
print(f"\n{'='*40}")
print(f"LADE GESELECTEERD | RFID: {rfid_tag} (ID: {lade_id})")
print("Voer de componenten in voor deze lade.")
print("Laat de naam leeg en druk op Enter om af te sluiten.")
print(f"{'='*40}")
while True:
naam = input("Naam van component: ").strip()
if not naam:
print("Invoer voor deze lade afgerond.")
break
aantal_invoer = input("Aantal: ").strip()
aantal = int(aantal_invoer) if aantal_invoer.isdigit() else 0
# Voeg component toe aan de lade
sql = "INSERT INTO componenten (lade_id, naam, aantal) VALUES (%s, %s, %s)"
cursor.execute(sql, (lade_id, naam, aantal))
connection.commit()
print(f" --> Toegevoegd: {aantal}x '{naam}'")
except Error as e:
print(f"Fout tijdens database operatie: {e}")
finally:
if 'connection' in locals() and connection.is_connected():
cursor.close()
connection.close()
def start_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# Zorg dat de poort snel hergebruikt kan worden
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen()
print(f"Python Server gestart. Wacht op RFID-tags via poort {PORT}...")
while True:
conn, addr = s.accept()
with conn:
data = conn.recv(1024)
if data:
rfid_tag = data.decode('utf-8').strip()
print(f"\n[!] BERICHT VAN ESP32: Nieuwe RFID tag ontvangen: {rfid_tag}")
beheer_lade(rfid_tag)
print("\nServer staat weer in de wachtstand...")
if __name__ == "__main__":
start_server()

Het Python-programma wacht tot er een tag gelezen wordt …

… en vraag vervolgens om invoer van componentnaam en aantal van dat component.

Het toevoegen van componenten gaat door totdat je i.p.v. een naam een <enter> indrukt.

Wanneer je een nieuwe tag leest (voor de volgende lade) worden de ingevoerde componenten toegevoegd aan dat laatje en in de database gezet. Zo kan je de inhoud van alle lades dus registreren.
Dat is natuurlijk mooi, maar je wilt natuurlijk ook een geregistreerd component weer terugvinden! Daarvoor heb ik Gemini opnieuw een vraag voorgelegd.
Alles werk! Naast bovenstaand systeem wil ik een ander python programma hebben dat van een bepaald component aangeeft in welke lade (in een kast met een raster van 4×6 = 24 laatjes) die zich bevindt. Hiervoor heb ik een ESP32 C3 Super mini (of evt. een Wemos Mini D1) uitgerust met twee rijtjes LED’s: een van 4 (die de kolommen aangeven) en een van 6 (die de rijen aangeven). Wanneer een gezocht component zich in laatje 1 zit moet LED 1 van beide LED-rijtjes aangaan, voor laatje 2 LED 2 van de eerste rij en LED 1 van de 2e rij. Enzovoorts.
Ik heb dus een programma nodig voor op de ESP32 C3 Super mini (of evt. een Wemos Mini D1) voor het aansturen van de LEDs op het kastje en een Python programma (grafisch UI) waarin ik kan opgeven welk component ik zoek en dan via WiFi doorgeeft welke LED’s aan moeten o.b.v. de juiste lade. Handig zou zij wanneer ik een overzicht kan krijgen vanuit de database, welke componenten er geregistreerd zijn, zodat ik daaruit kan kiezen.
De reactie van Gemini was weer erg grappig!
Wat een geweldige uitbreiding van je project! Dit maakt het systeem helemaal compleet (“pick-to-light” principe).
Ik had een tweede ESP32 C3 genomen en daar twee LED-printjes aan verbonden.

Gemini: We wijzen 4 pinnen toe aan de kolommen en 6 pinnen aan de rijen. We gebruiken hiervoor een simpele webserver. Als Python de URL http://<ESP-IP>/?col=2&row=1 aanroept, springen de juiste LED’s aan.
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "VUL_HIER_JE_WIFI_NAAM_IN";
const char* password = "VUL_HIER_JE_WIFI_WACHTWOORD_IN";
WebServer server(80);
// Pin configuratie (ESP32-C3 Super Mini)
int colPins[4] = {0, 1, 2, 3}; // Kolommen (1 t/m 4)
int rowPins[6] = {4, 5, 6, 7, 10, 21}; // Rijen (1 t/m 6)
void setup() {
Serial.begin(115200);
// Zet alle pinnen als OUTPUT en standaard UIT (LOW)
for (int i = 0; i < 4; i++) {
pinMode(colPins[i], OUTPUT);
digitalWrite(colPins[i], LOW);
}
for (int i = 0; i < 6; i++) {
pinMode(rowPins[i], OUTPUT);
digitalWrite(rowPins[i], LOW);
}
// Verbind met WiFi
WiFi.begin(ssid, password);
Serial.print("Verbinden met WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi verbonden!");
Serial.print("IP Adres van deze ESP32 is: ");
Serial.println(WiFi.localIP()); // NOTEER DIT IP ADRES!
// Webserver route: http://<ip>/?col=X&row=Y
server.on("/", []() {
if (server.hasArg("col") && server.hasArg("row")) {
int col = server.arg("col").toInt();
int row = server.arg("row").toInt();
// Zet eerst alle LED's weer uit
for (int i = 0; i < 4; i++) digitalWrite(colPins[i], LOW);
for (int i = 0; i < 6; i++) digitalWrite(rowPins[i], LOW);
// Zet de juiste LED's aan (vergeet niet -1 te doen voor de array index)
if (col >= 1 && col <= 4) digitalWrite(colPins[col - 1], HIGH);
if (row >= 1 && row <= 6) digitalWrite(rowPins[row - 1], HIGH);
Serial.printf("Lade aangeduid! Kolom: %d, Rij: %d\n", col, row);
server.send(200, "text/plain", "LEDs succesvol geupdate.");
} else {
server.send(400, "text/plain", "Fout: 'col' of 'row' ontbreekt in de URL.");
}
});
server.begin();
Serial.println("Webserver gestart.");
}
void loop() {
server.handleClient();
}
De Python-code die je laat zoeken naar het gewenste component en vervolgens de juiste ladenummer doorgeeft aan de ESP met de LEDjes bij de kast.
import tkinter as tk
from tkinter import messagebox
import mysql.connector
import urllib.request
# --- CONFIGURATIE ---
DB_HOST = "192.168.2.29"
DB_USER = "voorraad"
DB_PASS = "JOUW_DB_WACHTWOORD"
DB_NAME = "Voorraadbeheer"
ESP_IP = "192.168.2.XXX" # Vul hier het IP adres in van je TWEEDE ESP32 (LED-aansturing)
class VoorraadApp:
def __init__(self, root):
self.root = root
self.root.title("Component Zoeker - Pick to Light")
self.root.geometry("400x500")
self.componenten_lijst = [] # Hier slaan we de data in op
# UI Elementen opbouwen
tk.Label(root, text="Zoek component:", font=("Arial", 12)).pack(pady=10)
# Zoekbalk
self.search_var = tk.StringVar()
self.search_var.trace("w", self.update_listbox) # Voer functie uit bij typen
self.search_entry = tk.Entry(root, textvariable=self.search_var, font=("Arial", 12), width=30)
self.search_entry.pack()
# Listbox (overzicht)
self.listbox = tk.Listbox(root, font=("Arial", 11), width=40, height=15)
self.listbox.pack(pady=10)
# Knop
self.btn_zoek = tk.Button(root, text="Toon lade met LED's", bg="green", fg="white", font=("Arial", 12, "bold"), command=self.stuur_naar_esp)
self.btn_zoek.pack(pady=10)
# Laad initiële data
self.laad_data_uit_db()
def laad_data_uit_db(self):
try:
conn = mysql.connector.connect(
host=DB_HOST, user=DB_USER, password=DB_PASS, database=DB_NAME
)
cursor = conn.cursor()
# Haal componenten op met bijbehorende lade_id
cursor.execute("""
SELECT c.naam, c.aantal, l.id
FROM componenten c
JOIN lades l ON c.lade_id = l.id
ORDER BY c.naam ASC
""")
self.componenten_lijst = cursor.fetchall()
self.update_listbox() # Vul de lijst op het scherm
cursor.close()
conn.close()
except mysql.connector.Error as err:
messagebox.showerror("Database Fout", f"Kan geen data ophalen:\n{err}")
def update_listbox(self, *args):
# Maak lijst leeg
self.listbox.delete(0, tk.END)
zoekterm = self.search_var.get().lower()
# Vul lijst op basis van zoekterm
for comp in self.componenten_lijst:
naam, aantal, lade_id = comp
if zoekterm in naam.lower():
weergave = f"{naam} ({aantal} stuks) - Lade {lade_id}"
self.listbox.insert(tk.END, weergave)
def stuur_naar_esp(self):
selectie = self.listbox.curselection()
if not selectie:
messagebox.showwarning("Let op", "Selecteer eerst een component uit de lijst!")
return
gekozen_tekst = self.listbox.get(selectie[0])
# Zoek het originele record op om het exacte lade_id te krijgen
lade_id = None
for comp in self.componenten_lijst:
naam, aantal, lid = comp
if f"{naam} ({aantal} stuks) - Lade {lid}" == gekozen_tekst:
lade_id = lid
break
if lade_id:
self.bereken_en_stuur(lade_id)
def bereken_en_stuur(self, lade_id):
# Bereken kolom (1-4) en rij (1-6) op basis van een 24-vakjes grid.
# Let op: dit werkt perfect als lade_id 1 t/m 24 is.
vak_nummer = lade_id - 1
kolom = (vak_nummer % 4) + 1
rij = (vak_nummer // 4) + 1
print(f"Geselecteerd lade_id: {lade_id} -> Kolom: {kolom}, Rij: {rij}")
# Stuur het HTTP request naar de ESP32
url = f"http://{ESP_IP}/?col={kolom}&row={rij}"
try:
urllib.request.urlopen(url, timeout=3)
except Exception as e:
messagebox.showerror("Netwerk Fout", f"Kan de ESP32 niet bereiken op {ESP_IP}.\nFout: {e}")
if __name__ == "__main__":
root = tk.Tk()
app = VoorraadApp(root)
root.mainloop()
Wanneer je dit programma start krijg je onderstaand venster te zien (tkinter). Wanneer je de “L” ingeeft krijg je alleen de componenten te zien die beginnen met “L”. Handig wanneer de database flink vol zit!

Na selecteren van (in dit voorbeeld) Arduino Nano, die blijkbaar in lade 4 zitten, geef je door te drukken op de knop <Toon lade met LEDs>de informatie door aan de ESP die de juiste LEDs doet oplichten. In dit geval de 4e LED van de kolommen-LEDs en de 1e van de rijen-LEDs. Het werkt gewoon! 🙂
Het enige dat ik nog gevraagd heb is om de LEDs na een bepaalde tijd zonder activiteit (bijvoorbeeld 5 minuten) de LEDs te doven. Hiervoor kwam Gemini met de volgende code.
#include <WiFi.h>
#include <WebServer.h>
const char* ssid = "VUL_HIER_JE_WIFI_NAAM_IN";
const char* password = "VUL_HIER_JE_WIFI_WACHTWOORD_IN";
WebServer server(80);
// Pin configuratie (ESP32-C3 Super Mini)
int colPins[4] = {0, 1, 2, 3}; // Kolommen (1 t/m 4)
int rowPins[6] = {4, 5, 6, 7, 10, 21}; // Rijen (1 t/m 6)
// --- TIMER VARIABELEN ---
unsigned long ledAanTijd = 0; // Slaat op wanneer de LEDs aan zijn gegaan
const unsigned long ledTijdslimiet = 300000; // 5 minuten in milliseconden (5 * 60 * 1000)
bool ledsZijnAan = false; // Houdt bij of de LEDs momenteel branden
void setup() {
Serial.begin(115200);
// Zet alle pinnen als OUTPUT en standaard UIT (LOW)
for (int i = 0; i < 4; i++) {
pinMode(colPins[i], OUTPUT);
digitalWrite(colPins[i], LOW);
}
for (int i = 0; i < 6; i++) {
pinMode(rowPins[i], OUTPUT);
digitalWrite(rowPins[i], LOW);
}
// Verbind met WiFi
WiFi.begin(ssid, password);
Serial.print("Verbinden met WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi verbonden!");
Serial.print("IP Adres van deze ESP32 is: ");
Serial.println(WiFi.localIP());
// Webserver route: http://<ip>/?col=X&row=Y
server.on("/", []() {
if (server.hasArg("col") && server.hasArg("row")) {
int col = server.arg("col").toInt();
int row = server.arg("row").toInt();
// Zet eerst alle LED's weer uit (reset)
for (int i = 0; i < 4; i++) digitalWrite(colPins[i], LOW);
for (int i = 0; i < 6; i++) digitalWrite(rowPins[i], LOW);
// Zet de juiste LED's aan
if (col >= 1 && col <= 4) digitalWrite(colPins[col - 1], HIGH);
if (row >= 1 && row <= 6) digitalWrite(rowPins[row - 1], HIGH);
// Start de timer!
ledAanTijd = millis();
ledsZijnAan = true;
Serial.printf("Lade aangeduid! Kolom: %d, Rij: %d. Timer gestart voor 5 minuten.\n", col, row);
server.send(200, "text/plain", "LEDs succesvol geupdate en timer gestart.");
} else {
server.send(400, "text/plain", "Fout: 'col' of 'row' ontbreekt in de URL.");
}
});
server.begin();
Serial.println("Webserver gestart.");
}
void loop() {
// Blijf luisteren naar binnenkomende verzoeken
server.handleClient();
// --- TIMER CONTROLE ---
// Als de LEDs aan staan EN de verstreken tijd is groter dan de tijdslimiet (5 min)
if (ledsZijnAan && (millis() - ledAanTijd >= ledTijdslimiet)) {
// Zet alle LEDs uit
for (int i = 0; i < 4; i++) digitalWrite(colPins[i], LOW);
for (int i = 0; i < 6; i++) digitalWrite(rowPins[i], LOW);
ledsZijnAan = false; // Reset de status
Serial.println("5 minuten verstreken: LEDs zijn automatisch uitgeschakeld.");
}
}
Zo heb je een heel professioneel voorraadbeheer opgezet, geweldig! Zou je er zonder AI ook uitgekomen zijn? Zo ja, heb je enig idee hoeveel tijd dat je gekost zou hebben?
Zonder AI?
Een week of langer. Misschien was ik er helemaal niet uitgekomen.
Ziet er leuk uit.
Thanks, JBH.