Project Overzicht
Technische specificaties en architectuur van het Interactieve Escape Dozen systeem
Concept
Vijf interactieve dozen met unieke sensor-uitdagingen. Spelers moeten alle sensoren correct activeren om escape codes te verkrijgen.
- 5 Arduino-gestuurde dozen
- 4 dozen met verschillende sensortypes
- 1 doos met een code-invoersysteem
- Volledige draadloze communicatie tussen de puzzels
Hardware
Gebaseerd op Arduino WiFi Kit 32 met verschillende sensoren voor een complete gebruikerservaring.
- Arduino WiFi Kit 32 controller
- OLED display voor gebruikersinterface
- Push-buttons voor code-invoer
- MPU6050 accelerometer
- LDR lichtsensor
- Microfoon
- Temperatuursensor
Software
Arduino code met state machine architectuur voor betrouwbare sensor verwerking en gebruikersinteractie.
- State machine-logica
- Sensorkalibratie
- OLED display-controle
- Interrupt-handling
- Webserver
Technische Schema's
Uitgebreide blokdiagrammen en circuits ontworpen met Fritzing
Blokschema Knoppen
Fritzing
Interactief knoppensysteem met OLED-display voor code-invoer, buzzer voor audio-feedback en webserver voor communicatie
Blokschema Beweging
Fritzing
Accelerometer-gebaseerd systeem voor bewegingsdetectie met MPU6050-sensor
Blokschema Temperatuur
Fritzing
Temperatuur-sensing systeem met automatische kalibratie en threshold-detectie
Blokschema Licht
Fritzing
LDR-gebaseerd lichtsensorsysteem voor ambient light-detectie
Blokschema Geluid
Fritzing
Microfoon-gebaseerd geluidssensorsysteem voor decibelniveau-detectie (>80dB)
Code Documentatie
Volledig gecommenteerde broncode met uitleg van algoritmen en implementatie
Knoppen Game Master (server_knoppen.ino)
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
#include <WebServer.h>
#include "WiFi.h"
#include "HT_SSD1306Wire.h"
// Display configuratie
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
SSD1306Wire display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
// Pin configuratie voor knoppen
const uint8_t digitPins[4] = { 1, 3, 5, 7 };
const uint8_t okPin = 20;
const int buzzer = 46;
const int8_t X_OFFSET = 5;
// Debounce variabelen
bool lastState[5];
uint32_t lastDebounceTime[5] = { 0, 0, 0, 0 };
const uint16_t DEBOUNCE_MS = 20;
// Game variabelen
typedef uint8_t byte;
byte digits[4] = { 1, 1, 1, 1 };
byte secret[4] = { 1, 2, 3, 4 };
// Netwerk configuratie
WebServer server(80);
const char *host = "1.1.1.1";
WiFiClient client;
bool isConnected = false;
// Display posities
const int X0 = 0;
const int Y_LINE1 = 10;
const int Y_LINE2 = 30;
const int Y_LINE3 = 50;
// Buzzer variabelen
bool buzzerActive = false;
uint8_t buzzCount = 0;
uint8_t buzzTarget = 0;
unsigned long lastBuzzTime = 0;
bool buzzerOn = false;
const uint16_t buzzInterval = 200;
// Functie voor het verwerken van inkomende data
void onDataReceived() {
String Payload = server.arg("data");
int commaIndex = Payload.indexOf('_');
if (commaIndex == -1) return;
int number = Payload.substring(0, commaIndex).toInt();
String text = Payload.substring(commaIndex + 1);
Serial.println("Extracted number: " + String(number));
Serial.println("Extracted string: " + text);
if (text == "complete" && number >= 0 && number < 4) {
// Start buzzer feedback
buzzTarget = secret[number];
buzzCount = 0;
buzzerActive = false;
buzzerOn = false;
noTone(buzzer);
delay(50);
buzzerActive = true;
lastBuzzTime = millis();
Serial.println("Starting non-blocking buzz for " + String(buzzTarget) + " times");
}
}
void setup() {
Serial.begin(115200);
Serial.println("Initializing game master");
Serial.begin(115200);
// Display initialiseren
display.init();
display.setFont(ArialMT_Plain_10);
display.clear();
display.display();
// Knoppen configureren als INPUT_PULLUP
for (uint8_t i = 0; i < 4; i++) {
pinMode(digitPins[i], INPUT_PULLUP);
lastState[i] = HIGH;
}
pinMode(okPin, INPUT_PULLUP);
lastState[4] = HIGH;
for (uint8_t i = 0; i < 4; i++) {
pinMode(digitPins[i], INPUT_PULLUP);
lastState[i] = HIGH;
}
pinMode(okPin, INPUT_PULLUP);
pinMode(buzzer, OUTPUT);
lastState[4] = HIGH;
u8g2.begin();
u8g2.setFont(u8g2_font_6x10_tf);
// Random geheime code genereren
for (uint8_t i = 0; i < 4; i++) {
secret[i] = random(1, 10);
}
// WiFi configureren als Access Point
WiFi.disconnect(true);
WiFi.mode(WIFI_MODE_APSTA);
WiFi.setAutoReconnect(true);
WiFi.waitForConnectResult(10000);
WiFi.softAPConfig(
IPAddress(1, 1, 1, 1), // IP
IPAddress(1, 1, 1, 1), // gateway
IPAddress(255, 255, 255, 0) // subnet
);
WiFi.softAP("super secret hackathon network", "you should not know this password");
server.on("/", HTTP_GET, onDataReceived);
server.begin();
isConnected = true;
}
void loop() {
// HTTP requests verwerken
server.handleClient();
String codeStr = "";
for (uint8_t i = 0; i < 4; i++) {
codeStr += String(secret[i]);
}
// Buzzer feedback verwerken
if (buzzerActive && millis() - lastBuzzTime >= buzzInterval) {
lastBuzzTime = millis();
if (buzzerOn) {
noTone(buzzer);
buzzerOn = false;
buzzCount++;
if (buzzCount >= buzzTarget) {
buzzerActive = false;
}
} else {
tone(buzzer, 2000);
buzzerOn = true;
}
}
// Display update
display.clear();
display.setFont(ArialMT_Plain_10);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(X0, Y_LINE1, "Voer code in:");
String s = "";
for (uint8_t i = 0; i < 4; i++) {
s += String(digits[i]);
s += " ";
}
display.drawString(X0, Y_LINE2, s);
display.drawString(X0, Y_LINE3, "Druk OK om te verzenden");
display.display();
// Knop input verwerken met debounce
for (uint8_t i = 0; i < 4; i++) {
bool reading = digitalRead(digitPins[i]);
if (reading != lastState[i] && (millis() - lastDebounceTime[i]) > DEBOUNCE_MS) {
lastDebounceTime[i] = millis();
if (reading == LOW) {
digits[i] = (digits[i] < 9) ? digits[i] + 1 : 1;
}
}
lastState[i] = reading;
}
// OK knop verwerking
bool readingOK = digitalRead(okPin);
if (readingOK != lastState[4] && (millis() - lastDebounceTime[4]) > DEBOUNCE_MS) {
lastDebounceTime[4] = millis();
if (readingOK == LOW) {
while (digitalRead(okPin) == LOW) delay(10);
// Code controleren
bool match = true;
for (uint8_t i = 0; i < 4; i++) {
if (digits[i] != secret[i]) { match = false; break; }
}
// Resultaat tonen
display.clear();
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.setFont(ArialMT_Plain_16);
display.drawString(64, 28, match ? "OK" : "FOUT");
display.display();
// Reset input
for (uint8_t i = 0; i < 4; i++) {
digits[i] = 1;
}
}
}
lastState[4] = readingOK;
}
Accelerometer (client_accelerometer.ino)
#include "Arduino.h"
#include "ESP8266WiFi.h"
#include <Wire.h>
#include "heltec.h"
#include <MPU6050.h>
MPU6050 mpu;
// Bewegingsdetectie variabelen
int shakeCount = 0;
bool canDetect = true;
unsigned long lastShakeTime = 0;
// Netwerk configuratie
const char *host = "1.1.1.1"; // IP van de game master server
WiFiClient client;
bool isConnected = false;
// Functie voor het tonen van tekst op de OLED display
void displayText(String text) {
Heltec.display->clear();
Heltec.display->drawString(0, 0, text);
Heltec.display->display();
}
// Display initialisatie
void setupDisplay() {
Heltec.display->init();
Heltec.display->clear();
Heltec.display->display();
}
void setup() {
Serial.begin(115200);
setupDisplay();
// WiFi verbinding configureren als client
WiFi.disconnect(true);
delay(100);
WiFi.mode(WIFI_STA); // Station mode, verbind als client
WiFi.setAutoReconnect(true);
// Statisch IP adres toewijzen
WiFi.config(
IPAddress(1, 1, 1, 2), // IP adres van deze doos
IPAddress(1, 1, 1, 1), // Gateway (game master)
IPAddress(255, 255, 255, 0) // Subnet mask
);
displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
WiFi.begin("super secret hackathon network", "you should not know this password");
// Wacht op WiFi verbinding met timeout
int count = 0;
while(WiFi.status() != WL_CONNECTED && count < 100) {
count++;
delay(500);
displayText("Connecting to AP...");
}
// Controleer verbindingsstatus
if(WiFi.status() == WL_CONNECTED) {
displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
isConnected = true;
} else {
displayText("Connecting...Failed");
return;
}
// MPU6050 accelerometer initialiseren
mpu.initialize();
if (!mpu.testConnection()) {
Serial.println("MPU6050 NIET gevonden!");
while (1);
} else {
Serial.println("MPU6050 verbonden.");
}
}
// HTTP GET request naar game master voor communicatie
void sendDataToHost(String message) {
if (client.connected()) {
client.stop();
}
if (!client.connect(host, 80)) {
Serial.println("Connect host failed!");
return;
}
Serial.println("host Connected!");
// Verstuur data als URL parameter
String getUrl = "/?data=";
getUrl += message;
client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
Serial.println("Get send");
client.stop();
Serial.println("Connection closed");
}
void loop() {
if (!isConnected) {
return;
}
// Lees accelerometer waarden (X, Y, Z as)
int16_t ax, ay, az;
mpu.getAcceleration(&ax, &ay, &az);
unsigned long now = millis();
// Detecteer schudbeweging op Z-as met threshold
if (canDetect && abs(az) > 22000) {
delay(100); // Debounce vertraging
// Hercontroleer accelerometer waarde
mpu.getAcceleration(&ax, &ay, &az);
// Valideer dat beweging is gestopt (terugkeer naar rustpositie)
if (abs(az) < 12000) {
shakeCount++;
Serial.print("Schud #");
Serial.println(shakeCount);
// Voorkom meerdere detecties door tijdelijke blokkering
canDetect = false;
lastShakeTime = now;
}
}
// Heractiveer detectie na korte pauze
if (!canDetect && now - lastShakeTime > 50) {
canDetect = true;
}
// Controleer of uitdaging voltooid is (3 schudden)
if (shakeCount >= 3) {
Serial.println("✅ 3 keer geschud!");
shakeCount = 0;
// Stuur voltooiing naar game master
sendDataToHost("1_complete");
delay(7500); // Pauze voor volgende uitdaging
}
delay(50); // Korte vertraging voor stabiele werking
}
Licht Sensor (client_licht.ino)
#include <Arduino.h>
#include <Wire.h>
#include "WiFi.h"
#include "HT_SSD1306Wire.h"
// Pin configuratie voor licht sensor
const int lichtSensorPin = 1;
// OLED display initialisatie
SSD1306Wire factory_display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
// Netwerk configuratie
const char *host = "1.1.1.1"; // IP van de game master server
WiFiClient client;
bool isConnected = false;
// Functie voor het tonen van tekst op de OLED display
void displayText(String text) {
factory_display.clear();
factory_display.drawString(0, 0, text);
factory_display.display();
}
// Display initialisatie
void setupDisplay() {
factory_display.init();
factory_display.clear();
factory_display.display();
}
void setup() {
Serial.begin(115200);
setupDisplay();
// WiFi verbinding configureren
WiFi.disconnect(true);
delay(100);
WiFi.mode(WIFI_STA); // Station mode, verbind als client
WiFi.setAutoReconnect(true);
// Statisch IP adres toewijzen
WiFi.config(
IPAddress(1, 1, 1, 3), // IP adres van deze doos
IPAddress(1, 1, 1, 1), // Gateway (game master)
IPAddress(255, 255, 255, 0) // Subnet mask
);
displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
WiFi.begin("super secret hackathon network", "you should not know this password");
// Wacht op WiFi verbinding met timeout
int count = 0;
while(WiFi.status() != WL_CONNECTED && count < 100) {
count++;
delay(500);
displayText("Connecting to AP...");
}
// Controleer verbindingsstatus
if(WiFi.status() == WL_CONNECTED) {
displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
isConnected = true;
} else {
displayText("Connecting...Failed");
}
// Configureer licht sensor pin als input
pinMode(lichtSensorPin, INPUT);
}
// HTTP GET request naar game master voor communicatie
void sendDataToHost(String message) {
if (client.connected()) {
client.stop();
}
if (!client.connect(host, 80)) {
Serial.println("Connect host failed!");
return;
}
Serial.println("host Connected!");
// Verstuur data als URL parameter
String getUrl = "/?data=";
getUrl += message;
client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
Serial.println("Get send");
client.stop();
Serial.println("Connection closed");
}
void loop() {
if (!isConnected) {
return;
}
// Lees digitale waarde van licht sensor
int sensorWaarde = digitalRead(lichtSensorPin);
// Controleer lichtintensiteit (HIGH = geen licht, LOW = licht gedetecteerd)
if (sensorWaarde == HIGH) {
delay(100); // Wacht bij onvoldoende licht
} else {
// Licht gedetecteerd - stuur voltooiing naar game master
sendDataToHost("2_complete");
delay(7500); // Pauze voor volgende uitdaging
}
}
Temperatuur (client_temperatuur.ino)
#include "Arduino.h"
#include "WiFi.h"
#include "HT_SSD1306Wire.h"
// Pin configuratie voor temperatuur sensor
const int temperatuurSensorPin = 1;
// OLED display initialisatie
SSD1306Wire factory_display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
// Netwerk configuratie
const char *host = "1.1.1.1";
WiFiClient client;
bool isConnected = false;
// Functie voor het tonen van tekst op de OLED display
void displayText(String text) {
factory_display.clear();
factory_display.drawString(0, 0, text);
factory_display.display();
}
// Display initialisatie
void setupDisplay() {
factory_display.init();
factory_display.clear();
factory_display.display();
}
void setup() {
Serial.begin(115200);
setupDisplay();
// WiFi verbinding configureren
WiFi.disconnect(true);
delay(100);
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
// Statisch IP adres toewijzen
WiFi.config(
IPAddress(1, 1, 1, 4), // IP adres van deze doos
IPAddress(1, 1, 1, 1), // Gateway (game master)
IPAddress(255, 255, 255, 0) // Subnet mask
);
displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
WiFi.begin("super secret hackathon network", "you should not know this password");
// Wacht op WiFi verbinding met timeout
int count = 0;
while(WiFi.status() != WL_CONNECTED && count < 100) {
count++;
delay(500);
displayText("Connecting to AP...");
}
// Controleer verbindingsstatus
if(WiFi.status() == WL_CONNECTED) {
displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
isConnected = true;
} else {
displayText("Connecting...Failed");
}
}
// Configuratie voor gemiddelde temperatuur berekening
const int aantalMetingen = 30;
float temperatuurMeting[aantalMetingen];
int metingIndex = 0;
// HTTP GET request naar game master voor communicatie
void sendDataToHost(String message) {
if (client.connected()) {
client.stop();
}
if (!client.connect(host, 80)) {
Serial.println("Connect host failed!");
return;
}
Serial.println("host Connected!");
// Verstuur data als URL parameter
String getUrl = "/?data=";
getUrl += message;
client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
Serial.println("Get send");
client.stop();
Serial.println("Connection closed");
}
void loop() {
if (!isConnected) {
return;
}
// Lees analoge waarde van temperatuur sensor
float sensorWaarde = analogRead(temperatuurSensorPin);
// Converteer ADC waarde naar voltage (0-5V bereik voor 12-bit ADC)
float voltage = sensorWaarde * (5 / 4095.0);
// Converteer voltage naar temperatuur in Celsius
float temperatureC = (voltage * 0.5) * 100.0;
// Sla meting op in ringbuffer voor gemiddelde berekening
temperatuurMeting[metingIndex] = temperatureC;
metingIndex = (metingIndex + 1) % aantalMetingen;
// Bereken gemiddelde temperatuur na volledige cyclus
if (metingIndex == 0) {
float sum = 0;
for (int i = 0; i < aantalMetingen; i++) {
sum += temperatuurMeting[i];
}
float averageTemp = sum / aantalMetingen;
Serial.print("Average Temperature: ");
Serial.println(averageTemp);
// Controleer of temperatuur drempel bereikt is (24°C)
if (averageTemp >= 24.00) {
sendDataToHost("3_complete");
delay(7500); // Pauze voor volgende uitdaging
}
}
delay(100); // Korte vertraging tussen metingen
}
Geluid (client_geluid.ino)
#include "WiFi.h"
#include "HT_SSD1306Wire.h"
// Pin configuratie voor geluid sensor
const int geluidSensorPin = 1;
// OLED display initialisatie
SSD1306Wire factory_display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
// Netwerk configuratie
const char *host = "1.1.1.1";
WiFiClient client;
bool isConnected = false;
// Functie voor het tonen van tekst op de OLED display
void displayText(String text) {
factory_display.clear();
factory_display.drawString(0, 0, text);
factory_display.display();
}
// Display initialisatie
void setupDisplay() {
factory_display.init();
factory_display.clear();
factory_display.display();
}
void setup() {
Serial.begin(115200);
setupDisplay();
// WiFi verbinding configureren
WiFi.disconnect(true);
delay(100);
WiFi.mode(WIFI_STA);
WiFi.setAutoReconnect(true);
// Statisch IP adres toewijzen
WiFi.config(
IPAddress(1, 1, 1, 5), // IP adres van deze doos (aangepast voor geluid)
IPAddress(1, 1, 1, 1), // Gateway (game master)
IPAddress(255, 255, 255, 0) // Subnet mask
);
displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
WiFi.begin("super secret hackathon network", "you should not know this password");
// Wacht op WiFi verbinding met timeout
int count = 0;
while(WiFi.status() != WL_CONNECTED && count < 100) {
count++;
delay(500);
displayText("Connecting to AP...");
}
// Controleer verbindingsstatus
if(WiFi.status() == WL_CONNECTED) {
displayText("Connected to AP\nCurrent IP: " + WiFi.localIP().toString());
isConnected = true;
} else {
displayText("Connecting...Failed");
}
// Configureer geluid sensor pin als input
pinMode(geluidSensorPin, INPUT);
}
// HTTP GET request naar game master voor communicatie
void sendDataToHost(String message) {
if (client.connected()) {
client.stop();
}
if (!client.connect(host, 80)) {
Serial.println("Connect host failed!");
return;
}
Serial.println("host Connected!");
// Verstuur data als URL parameter
String getUrl = "/?data=";
getUrl += message;
client.print(String("GET ") + getUrl + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
Serial.println("Get send");
client.stop();
Serial.println("Connection closed");
}
void loop() {
if (!isConnected) {
return;
}
// Lees digitale waarde van geluid sensor
int sensorWaarde = digitalRead(geluidSensorPin);
// Controleer geluidsniveau (HIGH = stil, LOW = geluid >80dB gedetecteerd)
if (sensorWaarde == HIGH) {
delay(100); // Wacht bij stilte
} else {
// Geluid boven drempel gedetecteerd - stuur voltooiing naar game master
sendDataToHost("4_complete");
delay(7500); // Pauze voor volgende uitdaging
}
}
Logica Schema's
Flowcharts die de programmalogica en beslissingsprocessen van elk systeem illustreren
System Overzicht
ArchitectuurCompleet systeemarchitectuur met Game Master, vier sensor-clients en WiFi-communicatieprotocol
Game Master (Knoppen)
Main ControllerGame Master-logica: WiFi AP-setup, web request-handling, knop-input met debounce en codevalidatie
Accelerometer Client
BewegingBewegingsdetectie met MPU6050: threshold-detectie, debounce-filtering en 3-shake validatie
Temperatuur Client
TemperatuurTemperatuurmeting: ADC-conversie, ring buffer-averaging (30 samples) en 24°C threshold-detectie
Licht Client
LichtLDR-lichtsensor: digitale pin-reading en directe lichtdetectie (LOW = licht aanwezig)
Geluid Client
AudioMicrofoon-geluidssensor: digitale threshold-detectie voor geluiden boven 80dB-niveau
Media Galerij
Foto's en video's van het Interactieve Escape Dozen project
Foto's
De eerste houten plaat
De houten platen voor de 5 dozen
Lichtsensor doos
Temperatuursensor doos
Accelerometer doos
Knoppen systeem met buzzer
Afgewerkte doos
Video's
Werking van de OLED-display
Eerste werkende puzzel met de accelerometer, met het afspelen van de bijhorende code
Puzzel met lichtdetectie, met het afspelen van de bijhorende code
Systeem voor het invoeren van de code