Table of Contents

Snack Dispenser

Introducere

Acest dispenser inteligent de snack-uri poate oferi acces rapid și ușor la o varietate de gustări în cadrul spațiilor corporative, anumite sali de asteptare si chiar scoli sau universitati. Scopul acestui dispenser este de a îmbunătăți experiența angajaților sau studentilor în cadrul mediului de lucru, cu o mica gustare fara cost sau prea multa bataie de cap. Totusi, pentru a ne asigura ca ne limitam la un anumit numar de gustari pe user, foloseste un mecanism cu card ce acces sau un cod personal, astfel limitand de cat de multe ori poate folosi o anumita persoana dispozitivul pe un interval de timp prestabilit. Acesta poate fi chiar usor adaptat si pentru a fi folosit in alte modalitati, cum ar fi in locuri mai restranse (casa, office mic) in care eliminam sau setam un timp foarte mic sau chiar pentru animale daca setam activare automata la intervalele respective.

Descriere generală

Proiectul consta intr-un dispozitiv care funcționează doar pe baza unui card de acces si cod de acces. Daca oricare dintre acestea sunt acceptate, dispozitivul va oferi un snack / bomboana, dar acesta va avea un timp dupa in care nu va mai putea da alte bomboane. Are o tastatura pentru a schimba timpii si un ecran ca sa arate on / off si cat timp mai are pana poate da o alta bomboana.

Hardware Design

Componente:

Schema Electrica:

Design-ul circuitului:

Software Design

Bibliotecile folosite sunt:

Prezentare cod setup:

Pregatesc ledurile de la cititorul de card, leg servo motorul de pinul 12 si il aduc in pozitia de start. Declar intreruperile pe pinul 2 si 3 (INT0 si INT1) pentru cititorul de carduri si setez un timp de asteptare intre citirea altui card.

void setup() {
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(BEEP_BEEP, OUTPUT);
  digitalWrite(LED_RED, HIGH);
  digitalWrite(BEEP_BEEP, HIGH);
  digitalWrite(LED_GREEN, LOW);
  pinMode(2, INPUT);
  pinMode(3, INPUT);

  servo1.attach(12);

  servo1.write(0);

  Serial.begin(9600);
  Serial.println("RFID Readers");

  attachInterrupt(0, ISR_INT0, FALLING);
  attachInterrupt(1, ISR_INT1, FALLING);

  weigand_counter = WEIGAND_WAIT_TIME;
}

Aici am logica cititorului de card, cand exista biti in buffer-ul databits, adica bitCount > 0, stim ca am citit un card si s-au produs intreruperile necesare. In functie de numarul de biti cititi, prelucram facilityCode si cardCode si dupa intram in functia printBits(), unde afisam codul cardului si verificam daca este valid.

// Interrupt care apare când INTO devine LOW
void ISR_INT0() {
  bitCount++;
  flagDone = 0;
  weigand_counter = WEIGAND_WAIT_TIME;
}

// Interrupt care apare când INT1 devine LOW
void ISR_INT1() {
  databits[bitCount] = 1;
  bitCount++;
  flagDone = 0;
  weigand_counter = WEIGAND_WAIT_TIME;
}

void processCard() {
  unsigned char i;

  Serial.print("Read ");
  Serial.print(bitCount);
  Serial.print(" bits. ");

  if (bitCount == 35) {
    facilityCode = 0;
    cardCode = 0;
    for (i = 2; i < 14; i++) {
      facilityCode <<= 1;
      facilityCode |= databits[i];
    }
    for (i = 14; i < 34; i++) {
      cardCode <<= 1;
      cardCode |= databits[i];
    }
    printBits();
  } else if (bitCount == 26) {
    facilityCode = 0;
    cardCode = 0;
    for (i = 1; i < 9; i++) {
      facilityCode <<= 1;
      facilityCode |= databits[i];
    }
    for (i = 9; i < 25; i++) {
      cardCode <<= 1;
      cardCode |= databits[i];
    }
    printBits();
  } else if (bitCount == 37) {
    facilityCode = 0;
    cardCode = 0;
    for (i = 1; i < 19; i++) {
      facilityCode <<= 1;
      facilityCode |= databits[i];
    }
    for (i = 19; i < 37; i++) {
      cardCode <<= 1;
      cardCode |= databits[i];
    }
    printBits();
  }

  bitCount = 0;
  facilityCode = 0;
  cardCode = 0;
  for (i = 0; i < MAX_BITS; i++) {
    databits[i] = 0;
  }
}

Momentan avem doua carduri care sunt acceptate (38840 si 35270) daca timpul setat intre validari a trecut. Verificam asta salvand momentul in care am validat cardul si verificand daca diferenta dintre momentul curent si acela este mai mare decat timpul setat pentru card (waitTimeCard1 si waitTimeCard2, default setat la 0). Daca totul este in regula, apelam functia controlServos cu parametrul openTime, care reprezinta cat timp va sta deschisa trapa pentru snacks-uri.

void printBits() {
  Serial.print("FC = ");
  Serial.print(facilityCode);
  Serial.print(", CC = ");
  Serial.println(cardCode);

  digitalWrite(LED_RED, LOW);
  if (cardCode == 12345) {
    digitalWrite(LED_GREEN, HIGH);
  }
  delay(500);
  digitalWrite(LED_RED, HIGH);
  digitalWrite(LED_GREEN, LOW);

  digitalWrite(BEEP_BEEP, LOW);
  delay(500);
  digitalWrite(BEEP_BEEP, HIGH);
  delay(500);
  digitalWrite(BEEP_BEEP, LOW);
  delay(500);
  digitalWrite(BEEP_BEEP, HIGH);

  unsigned long currentMillis = millis();
  if (facilityCode == 38840 && cardCode == 35270) {
    if (currentMillis - lastAccessCard1 >= waitTimeCard1 * 1000) {
      controlServos(openTimeCard1);
      lastAccessCard1 = currentMillis;
    } else {
      Serial.println("Timpul de așteptare nu a expirat pentru cardul 1");
    }
  } else if (facilityCode == 38840 && cardCode == 35291) {
    if (currentMillis - lastAccessCard2 >= waitTimeCard2 * 1000) {
      controlServos(openTimeCard2);
      lastAccessCard2 = currentMillis;
    } else {
      Serial.println("Timpul de așteptare nu a expirat pentru cardul 2");
    }
  }
}

Functia controlServos este o functie simpla care tine trapa deschisa cu ajutorul motorului un anumit numar de secunde

void controlServos(unsigned long openTime) {
  servo1.write(90);
  delay(openTime);
  servo1.write(0);

Aici avem toata logica din spatele keypad-ului:

void handleKeypadInput(char key) {
  if (key == '*') {
    settingTime = true; // Inițierea setării timpului de așteptare
    inputBuffer = "";
    activeCard = 0;
    enteringTime = false;
    settingOpenTime = false;
  } else if (settingTime) {
    if (key == '#') {
      if (enteringTime || settingOpenTime) {
        float time = inputBuffer.toFloat();
        if (activeCard == 1) {
          if (settingOpenTime) {
            openTimeCard1 = time * 1000;
            Serial.print("Timp de deschidere pentru cardul 1 setat la: ");
            Serial.print(time);
            Serial.println(" secunde");
          } else {
            waitTimeCard1 = time;
            Serial.print("Timp de așteptare pentru cardul 1 setat la: ");
            Serial.print(time);
            Serial.println(" secunde");
          }
        } else if (activeCard == 2) {
          if (settingOpenTime) {
            openTimeCard2 = time * 1000;
            Serial.print("Timp de deschidere pentru cardul 2 setat la: ");
            Serial.print(time);
            Serial.println(" secunde");
          } else {
            waitTimeCard2 = time;
            Serial.print("Timp de așteptare pentru cardul 2 setat la: ");
            Serial.print(time);
            Serial.println(" secunde");
          }
        }
        settingTime = false;
        inputBuffer = "";
        activeCard = 0;
        enteringTime = false;
        settingOpenTime = false;
      }
    } else if (key == '1' || key == '2') {
      if (!enteringTime && !settingOpenTime) {
        activeCard = key - '0';
      } else {
        inputBuffer += key;
      }
    } else if (key == 'A') {
      enteringTime = true; // Intră în modul de setare a timpului de așteptare
    } else if (key == 'B') {
      settingOpenTime = true; // Intră în modul de setare a timpului de deschidere
    } else {
      if (enteringTime || settingOpenTime) {
        inputBuffer += key;
      }
    }
  }
}

Toate aceste functii sunt apelate in loop in functie de conditiile respective. Avem aici si logica timpului de asteptare pentru citirea cardurilor, astfel nu citim din greseala un card de mai multe ori.

void loop() {
  if (!flagDone) {
    if (--weigand_counter == 0)
      flagDone = 1;
  }

  if (bitCount > 0 && flagDone) {
    processCard();
  }

  char key = keypad.getKey();
  if (key) {
    Serial.print("Key pressed: ");
    Serial.println(key);
    handleKeypadInput(key);
  }
}

A doua varianta a codului include si un display I2C, nu l-am putut include in varianta initiala din cauza restrictiilor legate de memorie, deci a trebuit sa renunt la functionalitatile keypad-ului. Am afisat pe display ce era inainte asisat in serial monitor:

void setup() {
  Serial.begin(9600);
  Serial.println("Initializing...");

  // Inițializare ecran OLED
  Serial.println("Initializing OLED...");
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
    while (true);
  }
  Serial.println("OLED initialized.");
  display.display();
  delay(2000);
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);

  // Inițializare RFID
  pinMode(2, INPUT);
  pinMode(3, INPUT);

  attachInterrupt(0, ISR_INT0, FALLING);
  attachInterrupt(1, ISR_INT1, FALLING);

  weigand_counter = WEIGAND_WAIT_TIME;
  
  // Inițializare servomotoare
  Serial.println("Initializing Servos...");
  servo1.attach(12);
  servo1.write(0);
  Serial.println("Setup complete.");
}

void processCard() {
  unsigned char i;

  Serial.print("Read ");
  Serial.print(bitCount);
  Serial.print(" bits. ");
  display.clearDisplay();
  display.setCursor(0, 0);
  display.print("Read ");
  display.print(bitCount);
  display.print(" bits. ");
  display.display();
  
  ...

Rezultate Obţinute

Concluzii

A fost un proiect pentru care m-am documentat mult, dar a fost interesant sa invat si sa inteleg mai bine cum functioneaza si cum poti implementa si lega diverse lucruri cu ajutorul unui simplu arduino. A fost dificil sa inteleg exact cum sa leg si cum sa scriu codul pentru cititorul de card, dar ma bucur ca am perseverat si am reusit sa il folosesc cum am dorit. Ideea cu un singur “snack” nu a prea mers in practica, insa toata partea logica este exact cum mi-am imaginat-o de la inceput, iar gustarile tot curg, chiar daca in numar putin mai mare.

Download

snack_dispenser_pm.zip

Bibliografie/Resurse

https://app.diagrams.net/ (diagrama)

https://www.tinkercad.com/ (schemele electrice)

https://wokwi.com/

https://www.circuitbasics.com/how-to-set-up-a-keypad-on-an-arduino/

https://docs.arduino.cc/learn/electronics/servo-motors/

https://randomnerdtutorials.com/guide-for-oled-display-with-arduino/] (display oled) [[https://www.hackster.io/shakataganai/hid-prox-rfid-to-arduino-bd9b8a|https://www.hackster.io/shakataganai/hid-prox-rfid-to-arduino-bd9b8a (informatii card reader)