Die Knobelkiste ist Teil meines Semesterprojekts zum Thema Escape Rooms und (Spoiler Alert!) wer sie öffnet, findet darin einen Schlüssel.
Um die Box zu öffnen müssen jedoch mehrere Rätsel gelöst werden. Zunächst muss der Handschuh mit dem Control-Interface korrekt mit der Kiste verkabelt werden (nur 2 Kabel sind auch wirklich angeschlossen, der Rest läuft ins Leere). Dann wird der an einem Schlüsselbund befindliche Chip an den RFID-Sensor in der Kiste gehalten. Darauf leuchten zwei RGB-LEDs auf. Diese zeigen die Farben der textilen Buttons, die auf dem Handschuh gedrückt werden müssen, um den in der Kiste befindlichen Servo-Motor zu bewegen und die Kiste zu öffnen.
Zutaten für eine funktionierende Knobelkiste
Man nehme: 1 kleine Box, 1 Akku, 1 USB-Verteiler, 2 ESP32, 1 kleinen Servo-Motor, 2 RGB-LEDs, 1 RFID-Sensor und den dazu gehörigen Chip, leitfähiges Garn, 1 alten Handschuh, Textilreste, Nähutensilien und natürlich ganz viele Kabel und Schrauben.
Warum zwei ESP32?
Da sich die Bibliotheken für Servo-Motor und RFID-Sensor immer wieder verhakelt haben, habe ich kurzerhand einfach entschlossen, die beiden Codes auf zwei separate ESP32 zu laden, die über den USB-Verteiler beide am Akku angeschlossen sind.
Stromkreis Nr. 1: RFID-Sensor und LEDs
Ressourcen und Tutorials, die ich benutzt habe, um diesen Teil zum Laufen zu bringen:
BaldGuyDIY: ESP32: How to Setup An Analog Output for LED dimming, DC motor control, Etc.
Edi’s Techlab: RFID Reader RC533 mit ESPs und Arduinos – neuer upload | #Edis Techlab
BaldGuyDIY: ESP32: How to read RFID tags with a RFID Reader
RFID-Sensor und LEDs habe ich genauso angeschlossen wie im Tutorial und schließlich einfach die „Standard“-Codes aus den Bibliotheken angepasst.
#include <SPI.h> #include <MFRC522.h> #define RST_PIN 22 #define SS_PIN 5 MFRC522 mfrc522(SS_PIN, RST_PIN); // Create MFRC522 instance // led one setup int oneRedPin = 26; int oneRedChannel = 0; int oneGreenPin = 25; int oneGreenChannel = 1; int oneBluePin = 33; int oneBlueChannel = 2; int twoRedPin = 12; int twoRedChannel = 3; int twoGreenPin = 14; int twoGreenChannel = 4; int twoBluePin = 27; int twoBlueChannel = 5; int frequency = 1000; int resolution = 8; void setup() { // serial rfid setup Serial.begin(9600); // Initialize serial communications with the PC SPI.begin(); // Init SPI bus mfrc522.PCD_Init(); // Init MFRC522 Serial.println("Show your card to the reader..."); Serial.println(); // led one setup ledcSetup(oneRedChannel,frequency,resolution); ledcAttachPin(oneRedPin,oneRedChannel); ledcSetup(oneGreenChannel,frequency,resolution); ledcAttachPin(oneGreenPin,oneGreenChannel); ledcSetup(oneBlueChannel,frequency,resolution); ledcAttachPin(oneBluePin,oneBlueChannel); // led two setup ledcSetup(twoRedChannel,frequency,resolution); ledcAttachPin(twoRedPin,twoRedChannel); ledcSetup(twoGreenChannel,frequency,resolution); ledcAttachPin(twoGreenPin,twoGreenChannel); ledcSetup(twoBlueChannel,frequency,resolution); ledcAttachPin(twoBluePin,twoBlueChannel); } void loop() { // Reset the loop if no new card present on the sensor/reader. This saves the entire process when idle. Basically: Look for new cards. if ( ! mfrc522.PICC_IsNewCardPresent()) { return; } // Select one of the cards if ( ! mfrc522.PICC_ReadCardSerial()) { return; } //Show UID on serial monitor Serial.print("UID tag :"); String content= ""; byte letter; for (byte i = 0; i < mfrc522.uid.size; i++) { Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "); Serial.print(mfrc522.uid.uidByte[i], HEX); content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ")); content.concat(String(mfrc522.uid.uidByte[i], HEX)); } Serial.println(); Serial.print("Message : "); content.toUpperCase(); if (content.substring(1) == "CC 6A EF 8C") //change here the UID of the card/cards that you want to give access { Serial.println("Authorized access"); Serial.println(); ledcWrite(oneRedChannel, 255); ledcWrite(oneGreenChannel, 0); ledcWrite(oneBlueChannel, 120); ledcWrite(twoRedChannel, 0); ledcWrite(twoGreenChannel, 255); ledcWrite(twoBlueChannel, 0); delay(3000); } else { Serial.println(" Access denied"); ledcWrite(oneRedChannel, 0); ledcWrite(oneGreenChannel, 0); ledcWrite(oneBlueChannel, 0); ledcWrite(twoRedChannel, 0); ledcWrite(twoGreenChannel, 0); ledcWrite(twoBlueChannel, 0); delay(3000); } } // karte D7 46 59 F1 // chip CC 6A EF 8C
Stromkreis Nr. 2: Servo und Touch-Controls
Der Servo benötigt eine separate Stromversorgung, weil der ESP32 es nicht schafft, ihn mit Strom zu versorgen. Deshalb werden der Ground (GND, meist braunes Kabel) und die Stromversorgung (5V, meist rotes Kabel) vom Servo direkt an den Akku angeschlossen. Die Datenübertragung wird an einen GPIO-Pin vom ESP32 angeschlossen.
Für das textile Interface wurden die Schaltflächen auf dem Handschuh mit leitfähigem Garn durchsetzt. Dieses wurde in einem Schrumpfschlauch mit einem abisolierten Kabel verbunden. Das Kabel wurde schließlich an einen Touchpin des ESP32 angeschlossen.
Für diesen Teil habe ich das Tutorial Controlling Servos with ESP32 von BaldGuyDIY und TRXYS TOUCH HELPERS Code aus dem Projekt Textile Touch Matrix verwendet.
#include <ESP32Servo.h> Servo myservo; // create servo object to control a servo // 16 servo objects can be created on the ESP32 int pos = 0; // variable to store the servo position // Recommended PWM GPIO pins on the ESP32 include 2,4,12-19,21-23,25-27,32-33 // Possible PWM GPIO pins on the ESP32-S2: 0(used by on-board button),1-17,18(used by on-board LED),19-21,26,33-42 int servoPin = 2; // ---------------------------------- // ------- TRXYS TOUCH HELPERS ------ // ---------------------------------- // ( put this code PRIOR to your mainloop code or you will get nice errors :) ) // simple lerp helper function float return_lerp(float _s, int _target,int _time){ _s = _s + (( float(_target) - _s)/float(_time)); return _s; } class aTouch { private: bool prev_touch_state = false; byte pin; int smooth_time = 3; int trigger_threshold = 5; long ts = 0; public: int current_val = 0; int smoothed_val = 0; int diff_val = 0; bool is_triggered = false; bool on_pressed = false; bool on_released = false; bool is_holded = false; aTouch(byte pin) { this->pin = pin; } void readAndProcessInput() { // reset interaction states on_pressed = false; on_released = false; // directly read out values TWICE = BUGFIX for debouncing current_val = touchRead(pin); delayMicroseconds(10); current_val = touchRead(pin); //calculate smoothed input values smoothed_val = return_lerp(smoothed_val,current_val,smooth_time); // calc current differential sum of button diff_val = smoothed_val - current_val; // check if there is a noticable difference input values if( diff_val > trigger_threshold){ if(prev_touch_state == false){ is_triggered = true; prev_touch_state = is_triggered; on_pressed = true; ts = millis(); // set timestamp for hold routine } }else if( diff_val < trigger_threshold*-.4){ if(prev_touch_state == true){ is_triggered = false; prev_touch_state = is_triggered; on_released = true; } } // calculate timed holding function if( ts + 2500 < millis() && is_triggered){ is_holded = true; }else{ is_holded = false; } } void printAllMetadata() { // only use this on one touchinput only - it will skrew up else Serial.print (on_pressed); Serial.print(","); Serial.print (on_released); Serial.print(","); Serial.print (is_triggered); Serial.print(","); Serial.print (current_val); Serial.print(","); Serial.println(smoothed_val); } }; aTouch touch1(12); aTouch touch2(14); void setup() { // Allow allocation of all timers ESP32PWM::allocateTimer(0); ESP32PWM::allocateTimer(1); ESP32PWM::allocateTimer(2); ESP32PWM::allocateTimer(3); myservo.setPeriodHertz(50); // standard 50 hz servo myservo.attach(servoPin, 1000, 2000); // attaches the servo on pin 18 to the servo object // using default min/max of 1000us and 2000us // different servos may require different min/max settings // for an accurate 0 to 180 sweep Serial.begin(9600); // Initialize serial communications with the PC } void loop() { //myservo.write(0); touch1.readAndProcessInput(); touch2.readAndProcessInput(); if(touch1.is_triggered && touch2.is_triggered){ Serial.println("touched"); myservo.write(180); // tell servo to go to position 90 degrees } }
Finish
Dann wird das Ganze noch hübsch in einer Box verschraubt und fertig ist die Knobelkiste.
P.S.: Akku Laden und Fake-Kabel bzw. wild umherfliegende Lötverbindungen isolieren nicht vergessen.