// with cooldown period and messages in OLED eyes #include #include #include #include #include #define RST_PIN D3 #define SS_PIN D7 #define HALL_PIN D1 #define SERVO_PIN D6 #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 MFRC522 mfrc522(SS_PIN, RST_PIN); // OLED Displays Adafruit_SSD1306 display1(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); Adafruit_SSD1306 display2(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); const String AUTHORIZED_UIDS[] = { "045A100AD17780", "046063BA8A2190", "057441AF", "04762F7A985D80", "CB89533F", "B48A38AC", "54A8C4AB", "1D251D41010003", "1D815448010003", "0DB622C1", "1D2D5874000003", "FBB96059", "1DD56251000003", "D89813D7", //"6EB2D40D", //"3515E130" }; const String USER_NAMES[] = { "Mr Tom", "Bauka", "Tom's key", "Linur", "Magzhan", "Danial", "Olzhas", "Dima", "Hora", "Ilyas", "Erkanat", "German", "Sami", "Syrym", //"O Life", //"O Life" }; const int NUM_AUTHORIZED_CARDS = 14; const String SUPER_UIDS[] = { "6EB2D40D", "3515E130" }; const int NUM_SUPER_CARDS = 2; unsigned long lastDispenseTime[NUM_AUTHORIZED_CARDS] = {0}; const unsigned long COOLDOWN_MS = 12000; // 12 seconds const int LEDC_FREQUENCY = 50; const int LEDC_RESOLUTION = 16; const int PULSE_MIN_US = 2000; const int PULSE_STOP_US = 1500; uint32_t pulseUsToDuty(int us) { const float periodUs = 1000000.0f / LEDC_FREQUENCY; uint32_t maxDuty = (1 << LEDC_RESOLUTION) - 1; return (uint32_t)((us / periodUs) * maxDuty); } void setServoPulse(int us) { uint32_t duty = pulseUsToDuty(us); ledcWrite(SERVO_PIN, duty); } String uidToString() { String uid = ""; for (byte i = 0; i < mfrc522.uid.size; i++) { uid += (mfrc522.uid.uidByte[i] < 0x10 ? "0" : ""); uid += String(mfrc522.uid.uidByte[i], HEX); } uid.toUpperCase(); return uid; } bool isAuthorizedCard(String uid) { for (int i = 0; i < NUM_AUTHORIZED_CARDS; i++) { if (AUTHORIZED_UIDS[i] == uid) return true; } return false; } bool isSuperCard(String uid) { for (int i = 0; i < NUM_SUPER_CARDS; i++) { if (SUPER_UIDS[i] == uid) return true; } return false; } int findCardIndex(String uid) { for (int i = 0; i < NUM_AUTHORIZED_CARDS; i++) { if (AUTHORIZED_UIDS[i] == uid) return i; } return -1; } String getUserName(String uid) { int index = findCardIndex(uid); if (index != -1) { return USER_NAMES[index]; } return "Unknown"; } bool cooldownOK(String uid) { int index = findCardIndex(uid); if (index == -1) return false; return (lastDispenseTime[index] == 0 || millis() - lastDispenseTime[index] >= COOLDOWN_MS); } int getCooldownRemaining(String uid) { int index = findCardIndex(uid); if (index == -1 || lastDispenseTime[index] == 0) return 0; unsigned long elapsed = millis() - lastDispenseTime[index]; if (elapsed >= COOLDOWN_MS) return 0; return (COOLDOWN_MS - elapsed) / 1000; } void updateDisplays(String currentUID = "") { // Display 1: Status display1.clearDisplay(); display1.setTextColor(SSD1306_WHITE); display2.clearDisplay(); display2.setTextColor(SSD1306_WHITE); if (currentUID == "") {//no card detected display1.setTextSize(2); display1.setCursor(30, 30); display1.print(F("Kazakh")); display1.setCursor(35, 50); display1.print(F("Cocoa")); display2.setTextSize(2); display2.setCursor(40, 30); display2.print(F("Scan")); display2.setCursor(30, 50); display2.print(F("my ear!")); } else { String name = getUserName(currentUID); int remaining = getCooldownRemaining(currentUID); if (remaining > 0) { //user is recognized, but less than 2 minutes since already had candy display1.setTextSize(2); display1.setCursor(40, 30); display1.print("WAIT"); display1.setCursor(45, 50); display1.print(remaining); display1.println("sec"); display2.setTextSize(2); display2.setCursor(40, 30); display2.print(F("Card:")); display2.setTextSize(1); display2.setCursor(20, 55); display2.println(currentUID); } else { //User is not recognised display1.setTextSize(2); display1.setCursor(20, 30); display1.print("Who are"); display1.setCursor(30, 50); display1.print("you!?"); display2.setTextSize(2); display2.setCursor(25, 30); display2.print("Why I"); display2.setCursor(30, 50); display2.print("give?!"); } } display1.display(); display2.display(); } void setup() { Serial.begin(115200); delay(1000); // Init OLEDs if(!display1.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("Display1 failed")); for(;;); } if(!display2.begin(SSD1306_SWITCHCAPVCC, 0x3D)) { Serial.println(F("Display2 failed")); for(;;); } SPI.begin(); mfrc522.PCD_Init(); ledcAttach(SERVO_PIN, LEDC_FREQUENCY, LEDC_RESOLUTION); setServoPulse(PULSE_STOP_US); Serial.println("🍭 CANDY DISPENSER WITH OLED"); updateDisplays(); } void loop() { if (!mfrc522.PICC_IsNewCardPresent() || !mfrc522.PICC_ReadCardSerial()) { updateDisplays(); // Update displays continuously return; } String UID = uidToString(); Serial.print("Card: "); Serial.println(UID); if ((isAuthorizedCard(UID) && cooldownOK(UID)) || (isSuperCard(UID))) { Serial.println("✅ DISPENSING!"); String name = getUserName(UID); display1.clearDisplay(); display1.setTextSize(2); display1.setCursor(40, 30); display1.println("HELLO"); display1.setCursor(20, 50); display1.print(name); display1.println("!"); display1.display(); display2.clearDisplay(); display2.setTextSize(3); display2.setCursor(20, 30); display2.println("Enjoy!"); display2.display(); delay(500); unsigned long startTime = millis(); const unsigned long TIMEOUT_MS = 5000; while (millis() - startTime < TIMEOUT_MS) { int hallValue = analogRead(HALL_PIN); Serial.print("Hall: "); Serial.println(hallValue); if (hallValue < 2000) { setServoPulse(PULSE_STOP_US); Serial.println("🎉 DISPENSED!"); // Set cooldown int index = findCardIndex(UID); lastDispenseTime[index] = millis(); break; } setServoPulse(PULSE_MIN_US); delay(50); } if (millis() - startTime >= TIMEOUT_MS) { Serial.println("⚠️ TIMEOUT"); display1.clearDisplay(); display1.setTextSize(1); display1.setCursor(0, 25); display1.println(F("TIMEOUT")); display1.display(); delay(1500); } setServoPulse(PULSE_STOP_US); delay(500); } else { updateDisplays(UID); // Show card info int remaining = getCooldownRemaining(UID); if (remaining > 0) { Serial.print("Wait "); Serial.print(remaining); Serial.println("s"); } else { Serial.println("❌ NOT AUTHORIZED"); } } mfrc522.PICC_HaltA(); delay(500); updateDisplays(); }