Autor: Cosmin-Răzvan Vancea (333CA)
Proiectul este compus din două componente software:
Codul Arduino a fost dezvoltat în Visual Micro.
Pentru realizarea proiectului am utilizat următoarele biblioteci 3rd-party:
/fire-alarm/net-config.ini
de pe cardul SDÎn plus am dezvoltat următoarele două mini-biblioteci pentru a ușura interacțiunea cu dispozitivele conectate:
AT+<cmd> <parametri>
și returnează un răspuns pe interfața serială. Biblioteca are rolul de a ascunde aceste detalii în spatele unei interfețe high-level (clasă C++), astfel nu va trebui să lucrez direct cu interfața serială. În plus, biblioteca oferă suport minimalist pentru trimiterea de cereri HTTP.ADCSRA
și ADMUX
).Mai jos sunt detaliate cele mai importante funcții din fișierul sursă principal: fire-alarm.ino.
Funcții de setup()
:
InitSDCard
: inițializează modulul pentru SD Card și citește datele de configurare din fișierul /fire-alarm/net-config.ini
InitWiFi
: așteaptă inițializarea modulului Wi-Fi și se conectează la AP-ul (router wireless) specificat în fișierul de configurare de către utilizatorBlinkForever
: în cazul în care se detectează o eroare la oricare pas de inițializare descris anterior, se apelează această funcție care blochează execuția codului și va seta LED-ul să emită o anumită culoare care semnalează eroarea întalnită.SetLEDColour
: setează culoarea LED-ului RGB. Acest LED este folosit pentru a semnala starea în care se află aparatul (ex: verde = inițializat + stare OK de funcționare)
În plus, în faza de setup
se activează întreruperile pe liniile INT0
(senzor fum) și INT1
(senzor flăcări) și se atașează câte un ISR care va fi executat la schimbarea valorilor.
Funcții de loop()
:
PostDataToServer
: se reconectează la AP (în cazul în care s-a pierdut conexiunea) și se postează un mesaj HTTP la endpoint-ul specificat în fișierul de configurare. Dacă nu se poate trimite mesajul, atunci se va semnala eroarea prin LED și se va reîncerca transmiterea pachetului la fiecare 10 secunde până se reușește. Spre deosebire de o eroare de inițializare, aici execuția codului nu se mai oprește.SendNotification
: se generează body-ul cererii HTTP POST în funcție de datele citite de la senzori, apoi se apelează PostDataToServer
pentru a trimite cererea.Pe langă semnalizarea prin LED, dispozitivul va scrie pe interfața serială mesaje de logging la fiecare pas important (în special: inițializare SD Card/Wi-Fi și trimiterea de mesaje HTTP). Acest lucru ajută la depanarea problemelor.
Rutine pentru întreruperi:
GasInterrupt
: apelată la schimbarea valorii de pe pinul digital asociat senzorului de fum. Dacă noua valoare este LOW
înseamnă că senzorul a detectat fum și se va inițializa AsyncADC
-ul definit anterior pentru a porni citirea de pe pinul analog asociat. Se va seta o variabilă globală pentru a marca evenimentul.FlameInterrupt
: apelată la schimbarea valorii de pe pinul digital asociat senzorului de flăcări. Dacă noua valoare este LOW
înseamnă că senzorul a detectat flăcări și se va marca faptul că s-au detectat flăcări într-o variabilă globalăDe asemenea, la detectarea de fum sau flăcări se va activa alarma sonoră. Aceasta se oprește când ambii senzori nu mai detectează nimic suspect.
După cum se observă, în rutinele de întreruperi nu se întamplă nimic excepțional, maxim se scrie în 1-2 variabile/regiștri. Codul care se ocupă cu notificarea se află în loop
. Aici la fiecare X secunde se verifică dacă senzorii au detectat ceva (se interoghează variabilele globale scrise în ISR), iar în caz afirmativ, se va citi valoarea senzorului de fum prin AsyncADC
și se vor trimite măsurătorile la server.
Motivul pentru care am optat să implementez și să folosesc AsyncADC
în loc de un simplu analogRead
în loop
este că de la momentul în care este declanșată întreruperea până la momentul în care se face polling în loop
pot trece multe secunde/minute, iar astfel senzorul poate să îmi returneze valori normale în momentul când eu le citesc în loop
. Folosind AsyncADC
mă asigur că în loop
voi avea acces la datele care erau citite acum Y secunde când s-a declanșat întreruperea.
Utilizatorul dispozitivului poate specifica datele de configurare fără a fi nevoit sa recompileze software-ul – doar trebuie să modifice un fișier de configurare de pe un SD Card! Fișierul trebuie plasat pe card la calea: /fire-alarm/net-config.ini
. Exemplu:
[wifi] ssid=HUAWEI-abcd ; nume rețea wireless pass=my-extremely-hard-to-guess-password ; parolă wireless [server] host=fire-alarm.example.com ; adresa serverului destinație port=7331 ; portul serverului destinație endpoint=/api/v1/measurement/add ; calea către endpoint-ul unde se vor posta măsurătorile guid=744A2B95-D130-40A1-94E3-D8719FEDCCCD ; identificator unic pentru fiecare dispozitiv "Fire Alarm"
Arduino UNO dispune de două spații de memorie diferite: 2K memorie SRAM și 32K memorie flash. Memoria SRAM mică limitează foarte mult dimensiunea datelor cu care pot lucra (inclusiv string-uri → acestea sunt stocate în SRAM, deci în cel mai bun caz aș putea avea maxim ~2000 de caractere fără să mai iau în considerare stack-ul și restul datelor din program).
Pentru a ocoli această limită, am ales să stochez datele constante (eg: string-uri) în memoria flash folosind keyword-ul PROGMEM oferit de AVR și să le aduc în SRAM doar când am nevoie să le prelucrez (eg: formatarea unei cereri HTTP/unui mesaj de eroare). Acest trick îmi permite să:
În plus, majoritatea funcțiilor care acceptă string-uri constante implementate de mine au două forme: (vezi ESP8266.h)
Software-ul pentru server este realizat în Flask. Practic este un web server care:
Notificarea pe telefon se face prin serviciul PushBullet. În interfața web, proprietarul poate asocia un cont de PushBullet dispozitivelor sale. Fiecare măsurătoare care depășește valorile normale va fi trimisă ca notificare contului PushBullet asociat.
Detaliile de implementare nu intră în materia cursului.
A fost o experiență productivă deoarece proiectul ales de mine a trebuit să fie gândit din două perspective diferite care trebuie să se întâlnească într-un punct comun: un sistem local ce colectează date de la senzori și un sistem remote ce primește aceste date, le prelucrează și le distribuie mai departe. Pe lângă implementarea efectivă, a fost nevoie de multă documentare în prealabil, iar astfel m-am familiarizat cu căutarea de informații în documentația oficială.