Smart Lock System

Smart Lock System

Autor: Bogdan Dumitrescu

Grupa: 332CA

Email: bogdan.dumitrescu02@stud.acs.upb.ro

Introducere

Proiectul reprezinta sistem de securitate al casei care consta intr-un sistem inteligent de deschidere al unei usi. El poate fi montat pe orice usa pentru acces cu parola si in cazul incercarii de intrare a unei persoane straine, se va declansa o alarma si vor fi trimise informatii despre momentul in care s-a declansat, catre persoana care detine produsul.

Descriere generală

OLD->

Proiectul este format pe de-o parte dintr-o placa de dezvoltare Arduino Mega2560, la care este legat un keypad 4x4, un modul led RGB, un micro servomotor(pentru a deschide usa), un buzzer si un ecran LED cu interfata I2C(pentru a afisa diferite informatii).

Pe de alta parte, proiectul mai contine o alta placa de dezvoltare, ESP32-CAM, folosita atat pentru camera OV2640, cat si pentru modulul Wifi, pentru a trimite date la un server, care notifica userul. Aceasta va schimba informatii cu placa de dezvoltare arduino pentru acces de la distanta, a afisa mesaje pe display-ul led, sau pentru a declansa alarma.

Update ->

Citeste partea software pentru varianta finala + software

Arduino:

Probabil te intrebi de ce exista 3 componente de feedback(actuatori)(buzzer + led + display). Display-ul il voi folosi sa mai afisez mesaje de la server(acum are mai mult sens).
exemplu: Vad pe cineva la usa prin camera, afisez prin display mesajul: Nu sunt acasa

Acest arduino comunica cu un ESP32-CAM prin UART.

Pe esp voi vrea sa adaug niste senzori (deocamdata am unul de proximitate care nu merge din pacate, vreau sa mai am si unul de distanta si unul de sunet). La Esp voi conecta 2 senzori: unul ultrasonic si unul de detectare sunete.

De ce atatia senzori? Vreau sa ii folosesc pentru optimizari, practic la un moment dat vreau sa fie activi doar senzorii de pe esp + tastatura de pe arduino, adica sa fie in sleep procesoarele ca sa nu consume multa baterie.

Vreau sa se faca o poza si sa se trimita prin gmail cuiva o alarma, poate accesa pozele de pe un server(alt server nu de pe esp explic ulterior de ce).

Totodata pe esp mai am si o ruta de live_video, care returneaza un video efectiv cu ce se intampla in momentul respectiv.(modulul de wifi l-as dezactiva daca senzorii nu returneaza nimic ca nu are sens sa pornesti camera sa nu vezi nimic).

Am un alt server pentru ca am observat ca se incarca greu pozele pe esp, si daca se apeleaza prin alt server(adica daca am un buton de take_photo si dupa am view_photo), se vede mai fluid si mi se pare mai buna logica.

Vreau pe serverul ca pe acest server sa am si un feature de activate alarm/deactivate alarm si allow access de la distanta.

Pentru comunicarea esp-arduino prin UART, folosesc un level shifter , deoarece pinii gpio opereaza la 3.3V(maxim 3.6V), iar pinii arduino opereaza 5V

Hardware Design


Fiecare senzor/actuator are un link catre datasheet-ul acesteia.


Pinii sunt alesi astfel incat protocolul folosit pe actuator/senzor sa fie valid pentru fiecare. De exemplu, pentru rgb leds am ales pinii 10, 11, 12 care sunt pini PWM. Alti pini au fost alesi si aleator / am folosit pini analogici undeva unde puteau fi folositi pini digitali, dar nu am mai avut pini digitali pe placa, de exemplu, servomotorul este legat la un pin analogic, in loc de un pin PWM, dar reuseste sa isi indeplineasca sarcina si asa
Component Arduino Pin(s) Protocol/Communication Type
Keypad (Rows) 6, 7, 8, 9 Digital Input
Keypad (Columns) 2, 3, 4, 5 Digital Input
RGB LED (Red) 10 GPIO
RGB LED (Green) 11 GPIO
RGB LED (Blue) 12 GPIO
Active Buzzer 13 GPIO/Digital Output
LCD (I2C Address) I2C (A4 - SDA, A5 - SCL) I2C
Servo Motor 360 Continuous A0 GPIO
HC-SR04 Plus Trigger A2 GPIO
HC-SR04 Plus Echo A3 GPIO
RX 0 UART
TX 1 UART
Component ESP32-CAM Pin(s) Protocol/Communication Type
RX 3 UART
TX 1 UART
HC-SR04 Plus Trigger 13 GPIO
HC-SR04 Plus Echo 12 GPIO
Sound Sensor KY-037 AO 2 ADC
Sound Sensor KY-037 DO 4 GPIO

Hardware images

Hardware Image 1 Hardware Image 2 Hardware Image 3 Hardware Image 4
Schematic

Project Design Update

Prin "design" ma refer la rearanjarea/eliminarea senzorilor pe placi Project update

Am renuntat la senzorul de sunet deoarece nu era optim pentru distante mari, doar la apropiere detecta bine sunetele, oricat incercam sa il reglez din potentiometru, am ales in schimb sa maresc distanta de la senzorul ultrasonic ca sa detecteze mai din timp daca este cineva la usa

Software Design

Github Repo: https://github.com/dumibxd26/HouseSecuritySystem/tree/main

Explicarea codului de pe github

In repository exista 3 directoare:


- ArduinoLock - contine codul pentru placa de dezvoltare Arduino UNO

- ESPServer - contine codul pentru placa de dezvoltare ESP32-CAM

- CloudServer - contine codul pentru serverul .NET care interactioneaza cu serverul tinut pe ESP32-CAM

Arduino Lock:

In partea de setup, initalizam Seriala cu baud rate-ul 9600 pentru ca acesta este maximul recomandat pe arduino, prin intermediul acesteia vom comunica si cu comunica si cu ESP-ul, unde evident vom seta acelasi baud rate. Dupa aceea initalizam senzorii, pentru Keypad si LCD am ales sa folosesc clase pentru a implementa mai multe functii, si chiar initializarea. Initializarea LCD-ului consta in pornirea backlight-ului si afisarea pe ecran a mesajului "Enter password" pe prima linie. Initializarea tastaurii setarea pinilor de pe linii ca input si a activa rezistentele de pullup, iar coloanele ca output si high. Un semnal LOW pe o linie si o coloana simultan reprezinta apasarea unei taste. Intr-un final, initalizam pinii pentur fiecare senzor/actuator. Inital, dezactivam sistemul(il setam in modul idle si asteptam semnal de pe senzorul de proximitate pentru activare). In prima parte din loop, se verifica daca este detecatata o persoana la distanta de minim 30 metri, daca a fost detectata si placuta este oprita, aceaasta porneste(se asteapta semnale de la tastatura sau de la serverul extern). Daca este pornita de mai mult de 120 de secunde(2 min) si nu este in proces de folosire(alarma nu e pornita, o parola nu este scrisa, sau daca nu se reseteaza parola), placuta este dezactivata.

    void checkDistance()
{
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);

  duration = pulseIn(echoPin, HIGH);
  distance = duration * 0.034 / 2;

  if (distance > 0 && distance <= ACTIVATION_DISTANCE)
  {
    if (!systemActive)
    {
      systemActive = true;
      activationTime = millis();
      lcd.clear();
      lcd.printMessage(ONE_LINE_MESSAGE, "Enter Password:");
      lcd.backlight(); // Turn on the LCD backlight
      wakeUp();
    }
  }
  // Deactivate the system if the door is not opened within 120 seconds
  if (millis() - activationTime >= DEACTIVATION_TIME && !policeAlarmActive && systemActive && !resettingPassword && !distanceDetected && enteredPassword == "")
  {
    deactivateSystem();
  }

  if (distance > 0 && distance <= distanceThreshold)
  {
    distanceDetected = true;
    if (!previousDistanceDetected) // If it was not previously detected
    {
      Serial.println("start"); // Send message to ESP32-CAM
    }
  }
  else
  {
    distanceDetected = false;
    if (previousDistanceDetected) // If it was previously detected
    {
      Serial.println("stop"); // Send message to ESP32-CAM
    }
  }
  previousDistanceDetected = distanceDetected;
}
La detectarea proximitatii se trimite un semnal de "start" catre esp32

Dupa partea de verificare a distantei, urmeaza partea de asteptare mesaje pe Seriala, adica mesajele de la server care acceseaza placuta de la distanta, acestea sunt de tipul Aon(alarm on), Aoff(alarm off), newPassword(setare de parola de la distanta), allowAccess(daca vrei sa ii deschizi cuiva usa de la distanta, practic nu stie parola, dar il vezi prin camera si il recunosti, si ii dai voie sa intre).


    if (Serial.available() > 0)
  {
    String message = Serial.readString();
    // Serial.println(message);
    if (message.startsWith("Aon"))
    {
      lcd.clear();
      lcd.printMessage(ONE_LINE_MESSAGE, "Calling 911");
      policeAlarmActive = true;
      enteredPassword = "";
      attemptCount = 0;
    }
    else if (message.startsWith("Aoff"))
    {
      policeAlarmActive = false;
      noTone(buzzerPin);
      digitalWrite(buzzerPin, HIGH);
      setLedColor(false, false, false);
      lcd.clear();
      lcd.printMessage(ONE_LINE_MESSAGE, "Alarm Deactivated!");
      delay(1000);
      lcd.clear();
      lcd.printMessage(ONE_LINE_MESSAGE, "Enter Password:");
      enteredPassword = "";
    }
    else if (message.startsWith("newPassword"))
    {
      // noticed when writing to the lcd the password
      correctPassword = message.substring(message.indexOf("[") + 2, message.indexOf("]") - 1);
      Serial.println(message);
      lcd.clear();
      // lcd.printMessage(ONE_LINE_MESSAGE, "Password Changed");
      lcd.printMessage(TWO_LINE_MESSAGE, "Password Changed", correctPassword.c_str());
      enteredPassword = "";
      resettingPassword = false;
      enteredAdminPassword = 0;
      attemptCount = 0;
      setLedColor(false, false, false);
      delay(500);
      lcd.clear();
      lcd.printMessage(ONE_LINE_MESSAGE, "Enter Password:");
    }
    else if (message.startsWith("allowAccess"))
    {
      accessGranted();
    }
  }
    

Urmatoarea parte este verificarea daca alarma este activata, in cazul acela se asteapta scrierea parolei de administrator, care nu se afiseaza pe ecran, deoarece pe ecran se afiseaza doar mesajul "Calling 911", "infractroul" nestiind de existenta parolei de administrator si cum sa dezactivezi alarma

Intr-un final, este un handle pentru apasarea key-urilor, in momentul in care userul apasa o cheie aceasta este inregistrata, parola poate avea maxim 3 caractere in cazul actual, iar cand se ajunge la 3 caractere se verifica daca este corecta parola scrisa, daca se greseste parola de 3 ori, porneste alarma, daca parola este scrisa corect, porneste ledul rgb in cloarea verde, incepe o scurta melodie de deschidere a usii, iar servoul care tine locul unui zavor, se roteste 180 de grade(este un servo 360 continuu, deci practic se roteste 300 de milisecunde). Daca userul tine apasata tasta 'D' pentru 5 secunde, se intra in modul de resetare a parolei, ledul porneste in culoarea galben, se ateapta parola de admin si dupa aceea noua parola


    void handlePoliceAlarmKeypad(char key)
{
  if (key == 'D')
  {
    enteredPassword = "";
    lastTimeKeyPressed = millis();
    return;
  }

  if (key != keypad.getLastKeyPressed())
  {
    keypad.setLastKeyPressed(key);
    lastTimeKeyPressed = millis();
    enteredPassword += key;

    if (enteredPassword.length() == 3)
    {
      if (enteredPassword == adminPassword)
      {
        policeAlarmActive = false;
        noTone(buzzerPin);
        digitalWrite(buzzerPin, HIGH);
        setLedColor(false, false, false);
        lcd.clear();
        lcd.printMessage(ONE_LINE_MESSAGE, "Alarm Deactivated!");
        delay(1000);
        lcd.clear();
        lcd.printMessage(ONE_LINE_MESSAGE, "Enter Password:");
      }
      enteredPassword = "";
    }
    delay(250);
  }
}

ESPserver:

In partea de setup, OPRIM bluetooth-ul deoarece nu il folosim deloc, setam comunicarea prin seriala la baud rate 9600 pentru a comunica cu arduino-ul initializam camera si ne conectam la WiFi, totodata pornim serverul.
Esp32-CAM este dual core si pe core ruleaza WiFi + bluetooth, iar pe alt core celelalte functionalitati ale placutei. In partea de loop, doar verificam mesajele de pe seriala, de start si stop, care seteaza o variabila globala, daca placuta arduino este pornita sau oprita(daca este cineva la usa). Acea variabilala globala o vom folosi pentru a afisa mesaje pe serverul extern in cazul in care cineva este la usa, in momentul in care se detecteaza cineva la usa, se captureaza o imagine cu persoana respectiva si poate fi ulterior vizualizata.
Serverul serveste fie HTML static, fie are niste rute care doar returneaza date de pe esp(despre state-ul arduino-ul), catre serverul extern.
Rutele de pe esp sunt:
- "/" -> homepage, care tine butoane catre alte rute
- "/capture" -> in cazul esp-ului face poza si o afiseaza pe ecran
- "/live_video" -> se afiseaza un video live pe ecran
- "/activate si /deactivate_alarm" -> activeaza sau dezactiveaza alarma de pe arduino
- "/change_password" -> schimba parola de pe arduino
- "/allow_access" -> deschide usa de la distanta
- "/check_movement" -> returneaza daca este cineva la usa, practic returneaza variabila care se modifica in functe de comunicarea cu placuta arduino prin UART
Rutele pot fi impartite in doua tipuri, cele care comunica cu arduino, care tot ce fac este sa trimita un semnal prin serial a arduino, sau sa returneze daca exista semnale de la arduino, si rutele care tin de camera, /capture si /live_video.
Am ales ca protocol de comunicatie HTTP , deoarece este sateless, nu tine conexiunea pornita, ceea ce este perfect deoarece nu se fac apeluri atat de des la server si in teorie esp-ul ar consuma mai putina energie decat daca ar fi mereu conexiunea pornita penturu a astepta/trimite mesaje. Am apelat totusi la o optiune de long polling pentru a astepta state-ul arduino-ului, deoarece NU PUTEAM FACE esp-ul accesibil in internet la PM fair, astfel aveam de ales intre doua optiuni, fie sa fie serverul extern hostat undeva si esp-ul sa trimita mesaje la acesta, iar acesta sa nu poata initia comunicarea cu esp-ul la pm fair, fie sa fie ambele conectate local, iar esp-ul sa nu poata accesa serverul localhost de pe calculator, deoarece au "localhost" diferit ambele. Metoda eleganta era evident sa configurez port forwarding pe router si sa fac esp-ul accesibil in reteaua locala, iar serverului sa ii fac deploy, am reusit sa fac asta acasa(m-am chinuit sa hostez aplicatia de pe serverul extern pe azure, creand un pipeline care sincronizeaza un director dintr-un repo de pe github cu unul de pe azure pentru deploy), ca ulterior sa ma prind ca nu pot folosi asta la pm fair ;(. Proof:
proof azure

Cele mai relevante de explicat sunt rutele de camera, deoarece celelalte sunt mesaje de GET sau un mesaj POST pentru ruta de change password, deoarece parola este trimisa printr-un mesaj POST, si trebuie facuta o parsare a body-ului pentru a obtine obiectul care contine parola efectiva, ulterior datele aceseta fiind trimise la arduino.
/capture
-> Imaginea este facuta si ulterior trimisa in chunk-uri, un mesaj http neputand include evident, toata imaginea, am incercat initial sa trimit mesajele in format base64 dar mereu am avut probleme la parase, ar fi trebui sa fie mai lightweight, dar fie dura prea mult conversia, fie nu se parsa imaginea corect
/live_video
-> Filmarea este de fapt formata din mai multe poze care se trimite la interval de 100 de milisecunde(testat este cel mai bun pentru o trimitere corecta si o incarcare fluida). Ce se intampla: se trimit mesaje intr-un while, pana cand se iese de pe ruta, este un multipart response, deci se pot trimite in teorie raspunsuri la infinit.
Dar daca totul este intr-un while, cum se opreste while-ul pentru a nu bloca, evident, serverul?

       if (httpd_resp_send_chunk(req, part_buf, len) != ESP_OK || httpd_resp_send_chunk(req, (const char *)fb->buf, fb->len) != ESP_OK)
        {
            esp_camera_fb_return(fb);
            break;
        }
        
Daca nu primesc ACK de la browser la o trimitere de imagine, nu mai trimit imagine.
Ca optimizare, am modificat setarile camerei la o rezolutie mai mica in cazul filmarii, ca sa fie mai fluid, la imagini rezolutia este mai mare, si imaginea mai de calitate.

Cloud Server:

Scopul acestui server este unul foarte important, acela este o incarcare mai fluida a datelor IN CAZUL IMAGINILOR . Practic, orice metoda incercam sa gasesc, imaginile se trimiteau greu la browser si nu se incarcau fluid, si browsing-ul efectiv intre rute se face greu. Asa ca am ales sa folosesc alt server care salveaza imaginile local, si le trimite instant in momentul in care le cer, evident, deoarece ruleaza pe o masina de calcul mai puternica. In plus, ca sa nu mai dublez codul, logica HTML am adaugat-o doar aici, practic avem 7 butoane( cu scopul de la sine inteles)
- Capture Image
- View Image
- View Live Video (care da redirect la ruta te pe esp ca sa nu se mai treaca printr-un middleware, incetinind si mai mult trimiterea datelor)
- Activate Alarm
- Allow Access
- Change Password
Fiecare dintre aceste butoane fac redirect la o ruta de la care se poate intoarce pe pagina home.

Laboratoare folosite

- Laboratorul 0 GPIO -> pentru senzorul ultrasonic, active buzzer si modulul RGB
- Laboratorul 1 URAT -> pentru debug si comunicare ESP-ARDUINO
- Laboratorul 3 PWM -> pentru micro servo
- Laboratorul 6 I2C -> pentru modului Display cu I2C si comunicarea esp <-> camera

OPTIMIZARI

Facute:
- Dezactivare Bluetooth pe ESP deoarece nu il folosim
- Folosit http deoarece este stateless
- Folosit o rezolutie mai mica pentru video pentru a putea fi trimis datele mai fluid.
- Folosit cat mai putini senzori pe esp pentru a avea doar scopul de middleware intre serverul extern si arduino.
- Arduino UNO intra in modul idle si se dezactiveaza toti senzorii mai putin senzorul ultrasonic daca nu este folosit
- Evit folosirea delay-urilor pentru a nu bloca interceptarea datelor de la alti senzori sau functionarea corecta a ledurilor si a buzzerului.
Incercate:
- Am incercat sa pun placuta arduino in deep sleep(PWR_DOWN) si sa se activeze doar la primirea unui semnal de la senzorul ultrasonic, dar nu am reusit, de aceea am ales modul de idle, deoarece a fost singurul care functiona, chiar si cand incercam sa creez intreruperi doar pe pinul respectiv.
- Orice incercam pe esp32-cam, NU reuseam sa fac reconectarea la WiFi, nici nu are sens sa mai vorbesc de faptul ca nu functiona modul PWR_DOWN, deoarece nici macar o simpla reconectare la WiFi(WiFi-ul consumand foarte mult), am incercat orice functie de pe internet, conectare la mai multe access point-uri diferite, orice si nu reuseam, deci nu am reusit sa optimizez esp-ul din nefericire mai deloc. Foarte dezamagitor, avand in vedere ca prin modurile sale de sleep putea sa ajunga sa nu consume mai deloc curent(in deep sleep ~10uA)

Librarii folosite

- LiquidCrystal_I2C -> deoarece folosesc display cu un modul I2C si libraria aceasta are toate metodele de care am nevoie.
- Servo -> librarie pentru servomotor, problema este ca aceasta este gandita pentru un servo, care nu este continuu, dar functiile acesteia pot fi folosite si pe un servo continuu, numai ca: servo.rotate(90), nu roteste la 90 de grade ca pe un servo normal, acesta de fapt opreste servo-ul, rotate(0) il roteste invers ceasului de ceasornic iar rotate(180) il roteste in sensul acelor de ceasornic.
- Keypad> -> nu am mai folosit keypad.h, deoarece am implementat eu functiile care tin de apasarea unei taste, dupa logica mea de debouncing + un feature care nu lasa tastatura sa scrie incontinuu pana nu ridici degetul de pe buton.
- ESPAsyncWebServer -> librarie pentru serverul http
- esp32-camera -> librarie pentru camera de pe esp32-cam

Calibrarea senzorilor

Calibrarea fizica pe lcd-ul cu modul I2C, se face prin potentiometru, pana dispar patratele de pe ecran si pana se vad date pe ecran.
Calibrarea modului de sunet se face tot prin reglarea din potentiometru, ca sa i se seteze sensitivitatea.
Pe partea software, s-au setat pinii pentru fiecare prin digitalWrite(PIN, HIGH|LOW )

Concluzii

Pot spune ca am invatat multe din acest proiect si doresc sa mentionez mai jos toate problemele de care m-am lovit

-Debug-ul se face foarte greu pe un esp32-cam deoarece placuta trebuie montata si demontata mereu pentru fiecare update
-Ar fi fost mult mai optim sa folosesc doua placute la fel(esp32) pentru a face o comunicare mai optima, nu doar ca aduce un plus de fire prin prezenta unui level shifter, dar am avut de multe ori surpriza ca datele sa nu se trimita corect, sau foarte intarziat.
-Pe langa folosirea a doua placute la fel, puteam sa nu folosesc deloc un arduino, deoarece dupa o analiza la pret si performanta(comparat cu un esp32-CAm sau orice tip de esp(chiar si 8266), arduino are un procesor foarte slab, foarte putina memorie ram si este prea mare, fara sens, comunicarea se face doar prin URAT, sau alta metoda pe care am incercat-o, comunicarea a doi senzori, foarte absurd, dar putea fi folosit un senzor de sunet si un buzzer unul langa altul pentru a trimite mesaje(insane dar functioneaza ;) )
-A fost greu sa flash-uiesc doua board-uri in paralel, deoarece imi trebuia un driver pentru a flash-ui esp32-CAM -CH340-, care odata ce detecta alta placuta in afara de esp32-cam, trebuia reinstalat si restart la laptop.
-Problemele mentionate cu sleep modes
-Am avut de multe ori surpriza ca esp-ul sa functioneze cand il tineam conectat la dev board dar sa nu mearga cand il conectam la bradboard PSU, totusi... mergea cand il conectam direct la bateria 9V, deci m-am prins dupa o zi ca problema era la breadboard PSU, de aceea acum il tin direct conectat la board-ul de development, chiar daca arata oribil, altcumva nu functioneaza, in plus, nereusing sa rezolv cu modurile de sleep, s-ar fi consumat foarte repede bateria(~6h)
-Am cumparat o antena pentru esp32-cam pentru a avea conexiune mai buna la interent, inital avea, dar dupa, nu se mai conecta la wifi din cauzaei, fara motiv(inca ceva timp de debug)
Concluzie -> Chiar daca a fost fun, si mi-am dat seama ca acum pot face multe proiecte care sa ma ajute in casa pentru diferite lucruri, programarea pe microprocesoare este grea nu doar pentru ca, in primul rand, trebuie folosit un limbaj cat mai low level(precum C/C++), deci trebuie sa te lupti cu problemele de memorie, dar intervin si problemele hardware, care, daca nu ai indeajuns de multa experienta, nu poti avea ideea de la ce provin.

Download

"O arhivă (sau mai multe dacă este cazul) cu fişierele obţinute în urma realizării proiectului: surse, scheme, etc. Un fişier README, un ChangeLog, un script de compilare şi copiere automată pe uC crează întotdeauna o impresie bună ;-)"

Am folosit platformIO, librariile sunt deja salvate in fisierele platformio.ini, deci proiectul trebuie descarcate acelea si proiectul se va initializa automat

Cat despre serverul .Net, tot ce trebuie facut este sa fie instalat .Net 8(desi cred ca functioneaza si versiuni mai vechi precum .Net 5, sau chiar versiuni mai vechi de ASP.NET), dupa aceea sa fie rulata comanda dotnet run