Sistem de monitorizare a parametrilor ambientali prin internet [308858]

Universitatea „Politehnica” [anonimizat] a parametrilor ambientali prin internet

Proiect de diplomă

prezentat ca cerință parțială pentru obținerea titlului de

Inginer în domeniul Calculatoare și Tehnologia Informației

programul de studii de licență

Ingineria Informației (CTI – INF)

Conducător științific Absolvent

Ș.L.Dr.Ing. [anonimizat]

2018

Lista figurilor și tabelelor

Lista figurilor

Figura 1.1 Schema bloc a sistemului de monitorizare a parametrilor ambientali 15

Figura 2.1 Dispunerea pinilor microcontrolerului ATmega328P 18

Figura 2.2 Placa de dezvoltare Arduino Uno Rev3 19

Figura 3.1 Fotorezistorul GL5528 22

Figura 3.2 Senzorul DHT22 23

Figura 3.3 Senzorul MPL115A2 24

Figura 3.4 Modul de funcționare al senzorului MPL115A2 24

Figura 3.5 Dispunerea pinilor senzorului MPL115A2 25

Figura 4.1 Arduino Ethernet Shield W5100 27

Figura 4.2 Poziția unui SGBD în cadrul unei aplicații software 29

Figura 5.1 Montarea în circuit a fotorezistorului 34

Figura 5.2 Montarea în circuit a senzorului DHT22 34

Figura 5.3 Montarea în circuit a senzorului MPL115A2 35

Figura 5.4 [anonimizat] 36

Figura 5.5 [anonimizat] 36

Figura 6.1 Organizarea registrului ADMUX 38

Figura 6.2 Organizarea registrului ADCSRA 38

Figura 6.3 Semnalul de start pentru senzorul DHT22 39

Figura 6.4 Semnalul de răspuns al senzorul DHT22 39

Figura 6.5 Semnalele pentru transmisia biților senzorului DHT22 40

Figura 6.6 Organizarea registrului TWCR 42

Figura 6.7 Harta memoriei senzorului MPL115A2 44

Figura 6.8 Structura bazei de date 49

Figura 6.9 Pagina principală a interfeței 53

Figura 6.10 Diagrama temperaturii medii zilnice 55

Figura 6.11 Pagina de configurare a interfeței 56

Lista tabelelor

Tabelul 3.1 Specificațiile tehnice ale fotorezistorului GL5528 22

Tabelul 3.2 Specificațiile tehnice ale senzorului DHT22 23

Tabelul 3.3 Specificațiile tehnice ale senzorului MPL115A2 26

Listă acronime

ADC (Analog-Digital Converter) – [anonimizat] (Asynchronous JavaScript and XML) – tehnică de transfer al datelor în javascript

CC (Curent continuu)

CISC (Complex Instruction Set Computer) – calculator cu set complex de instrucțiuni

DHCP (Dynamic Host Configuration Protocol) – protocol de alocare dinamică a datelor de configurare în rețea

HTTP (Hypertext Transfer Protocol) – protocol de transmisie a datelor

IP (Internet Protocol Address) – adresa unui dispozitiv în internet

JSON (JavaScript Object Notation) – format de reprezentare a datelor

MIME (Multipurpose Internet Mail Extensions) – extensii cu scop general pentru poșta electronică

NTC (Negative Temperature Coefficient) – coeficient de temperatură negativ

RAM (Random Access Memory) – memorie cu acces aleator

RISC (Reduced Instruction Set Computer) – calculator cu set redus de instrucțiuni

SCL (Serial Clock Line) – linia de ceas serial

SDA (Serial Data Line) – linia de date seriale

SGBD (Sistem de gestionare al bazelor de date)

SPI (Serial Peripheral Interface) – interfață serială periferică

TWI (Two-Wire Interface) – interfață de comunicație cu două fire

URL (Uniform Resource Locator) – localizator uniform de resurse

USB (Universal Serial Bus) – magistrală serială universală

Introducere

Omul a căutat dintotdeauna modalități prin care să își ușureze și să își eficientizeze munca dar și pentru a își face viața mai confortabilă. Datorită inteligenței și capacității sale de inovație, acesta a conceput tehnologii ce îi permit să monitorizeze și să controleze anumite activități care nu se află în imediata sa apropiere.

Progresul tehnologic ne permite să discutăm astăzi despre senzori, dispozitive care conferă mașinilor simțuri asemănătoare omului. Prin intermediul lor, se pot măsura diferite mărimi fizice sau chimice ale mediului.

Sistemele de monitorizare a senzorilor pot avea nenumărate aplicații printre care: monitorizarea poluării aerului, detecția de gaze periculoase, semnalizarea defectelor în rețele mari (rețeaua electrică). Deasemenea, se pot dezvolta sisteme inteligente, care pot lua anumite decizii pe baza parametrilor măsurați de senzori (de exemplu, o casă inteligentă, capabilă să controleze aparatele de aer condiționat pentru a menține temperatura constantă).

Datorită numărului mare de aplicații pe care aceste sisteme le pot avea, mi-am propus ca prin intermediul acestui proiect să ofer o soluție pentru monitorizarea parametrilor temperatură, umiditate, lumină și presiune atmosferică ce salvează măsurătorile într-o bază de date și le pune la dispoziția utilizatorului printr-o interfață web.

CAPITOLUL 1
Conceptul aplicației

O bună parte din inovațiile recente se axează pe controlul și monitorizarea de la distanță a unor diferite activități. Nevoia de monitorizare a mediului înconjurător duce la necesitatea proiectării și realizării unui modul electronic capabil să măsoare anumiți parametrii ambientali (de exemplu, temperatura, umiditatea etc).

În cadrul lucrării, se va descrie în detaliu etapa de proiectare și etapa de realizare a unui sistem de monitorizare a parametrilor ambientali capabil să ofere informații despre temperatură, lumină, umiditate și presiune atmosferică. În scopul utilizării de la distanță, datele achiziționate de la senzori vor fi stocate într-o bază de date și afișate în cadrul unei interfețe de tip web.

Componentele modulului:

Placă de dezvoltare Arduino Uno Rev3

Modul Ethernet

Modul pentru măsurarea temperaturii și a umidității

Senzor de presiune atmosferică

Fotorezistor

Figura 1.1 Schema bloc a sistemului de monitorizare a parametrilor ambientali

CAPITOLUL 2
Microcontroler

2.1 Noțiuni generale

Un microprocesor reprezintă un circuit integrat ce conține toate unitățile funcționale specifice unei structuri de procesor de uz general. Acesta reprezintă Unitatea Centrală de Prelucrare a unui sistem programabil și este utilizat pentru construcția calculatoarelor de uz general. [1]

În mod uzual, un microcontroler reprezintă o încapsulare a unui microprocesor, a unei memorii și a unui sistem de intrare/ieșire realizat. Așa cum sugerează și numele, acestea sunt destinate aplicațiilor de control și nu calculului de uz general.

Microcontrolerele pot avea diferite tipuri de microprocesoare, în funcție de performanțele dorite. În prezent, se pot găsi microcontrolere ce utilizează microprocesoare pe 8, 16 sau pe 32 de biți.

În funcție de setul de instrucțiuni, microprocesoarele din componența microcontrolerelor pot fi de două feluri:

CISC (Complex Instruction Set Computer): presupune un set complex de instrucțiuni, cu multe moduri de adresare, în care instrucțiunile pot să dureze mai multe intervale de ceas;

RISC (Reduced Instruction Set Computer): presupune un set redus de instrucțiuni, cu puține moduri de adresare, în care fiecare instrucțiune durează un singur interval de ceas;

În prezent, foarte multe microprocesoarelor folosesc o combinație între CISC și RISC cu scopul de combina calitățile celor doua arhitecturi și a obține performanțe cât mai bune.

2.2 Microcontrolerul ATmega328P

Familia de microcontrolere AVR de la firma Atmel reprezintă o colecție de microcontrolere cu un consum redus de putere, caracterizate de o arhitectură de tip RISC modificată. Arhitectura AVR a fost concepută ca o arhitectura de tip RISC în care setul de instrucțiuni a fost mărit cu scopul de a reduce lungimea codului necesar programării. Astfel, au fost introduse instrucțiuni caracteristice arhitecturii CISC, fară a afecta însă viteza de execuție și consumul de putere. [2]

Microcontrolerul folosit în acest proiect este ATmega328P, un membru al familiei AVR, pe 8 biti, ce posedă următorele caracteristici: [3]

131 de instrucțiuni

32 de registre generale pe 8 biți

32 kiloocteți de memorie flash

2 kiloocteți de memorie RAM

23 de pini de I/O

convertor Analog-Digital pe 10 biți

Figura 2.1 Dispunerea pinilor microcontrolerului ATmega328P Sursa: [3]

2.2 Placa de dezvoltare Arduino Uno

Arduino Uno este o placă de dezvoltare ce conține, pe lângă microcontrolerul ATmega328P, o interfață USB ce permite conectarea la un calculator, un stabilizator de tensiune ce permite alimentarea plăcii la o tensiune de 7-12 volți, un cristal de cuarț de 16 Mhz, leduri pentru semnalizarea comunicației seriale și alte componente utile (de exemplu, buton de reset, porturi etc).

Figura 2.2 Placa de dezvoltare Arduino Uno Rev3

CAPITOLUL 3
Senzori

3.1 Noțiuni generale

Dicționarele din prima parte a anilor '70 nu cuprind cuvântul "senzor". Acesta a apărut odată cu dezvoltarea microelectronicii, împreună cu alte noțiuni de mare impact, cum ar fi cele de „microprocesor” și „microcontroler”. Denumirea provine din cuvântul latin „sensus”, care înseamană simț și este folosit pentru a desemna capacitățile organelor de simț ale organismelor vii, de a colecta și prelucra informații din mediul înconjurător și a le transmite creierului. [4]

Senzorii sunt dispozitive tehnice capabile să măsoare anumite mărimi fizice sau chimice și să le transforme într-un semnal electric care ulterior poate fi citit și interpretat.

Clasificarea senzorilor se poate face după mai multe criterii: [5]

După modul de variație a mărimii de ieșire

senzori analogici

senzori digitali

După modul de obținere a energiei necesare formării semnalului

senzori activi

senzori pasivi

După forma caracteristicii statice

senzori cu caracteristică liniară

senzori cu caracteristică neliniară

După mărimea fizică masurată

senzori de temperatură

senzori de presiune

senzori de forță

senzori de deplasare

3.2 Fotorezistor

Fotorezistor este combinația de cuvinte „foton”, adică particule de lumină, și „rezistor”. Așa cum spune și numele său, este un dispozitiv sau putem spune un rezistor dependent de intensitatea luminii, fată de care rezistența sa variază invers proporțional. [6] Din punct de vedere al construcției, acesta este format dintr-un strat de material semiconductor depus pe un grătar metalic, fixat pe o placa izolatoare.

Printre avantajele folosirii fotorezistoarelor se remarcă dimensiunea redusă a acestuia, facându-l ușor de integrat in circuitele electronice dar și costul de fabricație redus datorat materialelor folosite (de obicei, sulfură de cadmiu). În schimb, timpul său de răspuns este relativ mare (între 10 – 100 ms, în funcție de materialul din care este confecționat).

Când este conectat într-un circuit electric, intensitatea curentului ce trece prin fotorezistor crește proporțional cu scăderea rezistenței electrice, deci proporțional cu creșterea intensității fluxului luminos.

În cadrul proiectului am folosit fotorezistorul GL5528 care este caracterizat de următoarele specificații tehnice:

Tabelul 3.1 Specificațiile tehnice ale fotorezistorului GL5528 Sursa: [7]

Figura 3.1 Fotorezistorul GL5528

3.3 Senzorul de temperatură și umiditate DHT22

Dispozitivul DHT22, cunoscut și sub denumirea de AM2302, este un modul electronic capabil să măsoare temperatura și umiditatea relativă a mediului. Pentru a își îndeplini scopul, acesta include un senzor capacitiv de umiditate, un termistor cu coeficient de temperatură negativ (NTC) și un circuit care face conversia semnalelor din analog în digital.

Specificațiile tehnice ale dispozitivului:

Tabelul 3.2 Specificațiile tehnice ale senzorului DHT22 Sursa: [8]

Figura 3.2 Senzorul DHT22 Sursa: [8]

Din punct de vedere al timpului de răspuns, se observă ca acest dispozitiv este foarte lent (2 secunde), însă pentru acest proiect este o componentă ideală datorită prețului scăzut și faptului că sistemul de monitorizare nu necesită achiziționarea datelor la intervale scurte de timp.

Comunicația cu microcontrolerul se realizează digital printr-un singur fir. La primirea semnalului de start din partea microcontrolerului, senzorul trimite 40 de biți reprezentând valorile măsurate și un octet folosit pentru verificarea corectitudinii valorilor recepționate.

3.4 Senzorul de presiune MPL115A2

Senzorul MPL115A2 produs de compania NXP Semiconductors, reprezintă un dispozitiv electronic capabil să măsoare presiunea având ca referință vidul.

În condiții de funcționare la o temperatură cuprinsă între -20șC și 85șC, precizia de măsurare a presiunii este 1kPa (vezi tabelul 3.3), o valoare suficient de bună pentru această aplicație. [9]

Figura 3.3 Senzorul MPL115A2 Sursa: [10]

Pentru a comunica cu un microcontroler, senzorul dispune de o interfață I²C, care presupune o comunicație serială sincronă cu frecvență maximă de 400kHz. Acest tip de comunicație permite transmiterea de mesaje în mod bidirecțional între mai multe dispozitive folosind doar două semnale denumite SDA (Serial Data Line/Linia de date seriale) și SCL (Serial Clock Line/Linia de ceas serial).

Figura 3.4 Modul de funcționare al senzorului MPL115A2

După cum se poate observa și în figura 3.4, modul de funcționare al senzorului MPL115A2 se poate împărți în cinci etape:

La momentul inițial, senzorul eliberează regiștrii asociați interfeței I²C și setează pinii SDA (Serial Data Line / Linia de date seriale) și SCL (Serial Clock line / Linia de ceas serial) în regim de impedanță înaltă.

Se citesc coeficienții de calibrare, necesari pentru calculul ulterior al presiunii. Aceștia se citesc o singură dată deoarece reprezintă niște constante care au fost calculate în fabrică și stocate în memoria senzorului.

Se trimite senzorului comanda de începere a conversiei. După ce acesta a terminat de măsurat presiunea și temperatura, valorile se pun în regiștrii corespunzători iar senzorul întră într-o stare de așteptare.

Se citesc valorile presiunii și temperaturii apoi se calculează valoarea presiunii compensate adică valoarea presiunii ținând cont de schimbările de temperatură și de liniaritate a senzorului. Din această etapă se poate da comanda începerii unei noi conversii pentru a face o nouă măsurătoare.

Dacă senzorul va sta inactiv o perioada lungă de timp, în scopul reducerii consumului de curent, acesta poate fi oprit, conectând pinul SHDN (vezi figura 3.5) la masă.

Figura 3.5 Dispunerea pinilor senzorului MPL115A2

Acest dispozitiv are încorporat și un senzor de temperatură însă acesta a fost conceput doar pentru a oferi valori necesare pentru calculul presiunii compensate și nu pentru a măsura temperatura mediului. După cum se poate observa în tabelul 3.3, fabricantul nu a oferit informații tehnice despre acest senzor.

Tabelul 3.3 Specificațiile tehnice ale senzorului MPL115A2 Sursa: [9]

Din punct de vedere al adresării, senzorul MPL115A2 poate fi identificat în cadrul unei comunicații de tip I²C după adresa pe 7 biți 1100000 sau 0x60 în format hexazecimal.

CAPITOLUL 4
Transmisia datelor

4.1 Modulul Arduino Ethernet Shield W5100

Modulul Arduino Ethernet Shield W5100 reprezintă un dispozitiv proiectat special pentru a fi utilizat împreună cu placa de dezvoltare Arduino Uno care permite conectarea cu ușurintă a acesteia la rețea. După cum se poate observa în figura 4.1, acesta se poziționează deasupra plăcii de dezvoltare și îi prelungește porturile în aceeași dispunere pentru a permite conectarea unui alt modul. De aici vine și denumirea de „scut” sau în limba engleză „shield”.

Figura 4.1 Arduino Ethernet Shield W5100

La baza modulului stă controlerul W5100 produs de firma WIZnet, care oferă o implementare a stivei de rețea pentru o conexiune de până la 100Mbps. Pe lăngă acesta, pe placă se mai remarcă un conector pentru cablul de rețea, un slot pentru un card de memorie și un set de led-uri cu rolul de a semnaliza diferite stări sau acțiuni ale modulului cum ar fi: [11]

PWR: indică alimentarea plăcii

LINK: indică prezența unei conexiuni de rețea

100M: indică prezența unei conexiuni de 100Mbps

RX: indică recepția datelor

TX: indică transmisia datelor

Comunicația dintre shield-ul Ethernet și placa de dezvoltare se realizează pe baza protocolului SPI (Serial Peripheral Interface / Interfață serială periferică).

4.2 Serverul web Apache

Apache reprezintă serverul web dominant din prezent, ocupând un loc cheie în infrastructura internetului. În principiu, rolul său este să traducă un URL determinând astfel locația unui fișier și să trimită înapoi prin internet conținutul acelui fișier având la bază protocolul HTTP. Numele de URL este un acronim provenit de la Uniform Resource Locator și reprezintă modul unic de a localiza resursele în internet.[12]

Pentru a se obține o dinamică a resurselor și pentru a putea îndeplini acțiuni complexe, serverul web apache se poate folosi împreuna cu un limbaj de programare sau de scripting, spre exemplu PHP (Hypertext Preprocessor). Aceste limbaje permit deasemenea interacțiunea paginilor web cu sisteme de gestionare a bazelor de date dar și cu alte aplicații din cadrul sistemului.

Protocolul HTTP (Hypertext Transfer Protocol) este protocolul cel mai des utilizat în accesarea datelor prin internet. Toate navigatoarele web, serverele și aplicațiile care au legături cu internetul comunică folosind acest protocol, capabil să asigure integritatea datelor, garantând utilizatorilor și dezvoltatorilor de software că informațiile transmise vor ajunge nealterate la destinație.

Comunicația folosind protocolul HTTP este de tip tranzacțional. O tranzacție HTTP constă într-o cerere trimisă de către client către server și un răspuns din partea serverului către client. Protocolul definește mai multe tipuri de cereri pentru a indica ce acțiune să se realizeze asupra resurselor indicate. Printre acestea se numără: [13]

GET: trimite resursele indicate de la server către client

POST: trimite informații de la client către server

DELETE: șterge anume resurse de pe server

PUT: stochează datele de la client într-un fișier indicat de pe server

4.3 Sistemul de gestionare al bazelor de date MariaDB

Un sistem de gestionare al bazelor de date, pe scurt SGBD, reprezintă totalitatea programelor necesare pentru crearea, interogarea și organizarea bazelor de date. Acesta se interpune între utilizatori, aplicație și baza de date, după modelul descris în figura 4.2.

Figura 4.2 Poziția unui SGBD în cadrul unei aplicații software

MariaDB este o ramificație a SGBD-ului MySQL care include funcționalitățile acestuia dar nu numai. Acest sistem folosește organizarea datelor conform modelului relațional, în care structura de bază a datelor este aceea de relație. Legăturile dintre relații se realizează cu ajutorul unor chei primare și chei străine.

Pentru controlul și interogarea bazelor de date se folosește limbajul SQL (Structured Query Language), care este specific manipulării datelor în sistemele de gestionare a bazelor de date relaționale.

În cadrul acestui proiect am ales să folosesc MariaDB pentru a stoca datele primite de la senzori datorită performațelor mai bune în execuția interogărilor spre deosebire de MySQL.

4.4 Transmisia datelor

Transmisia datelor colectate de la senzori către serverul web în scopul de a fi prelucrate, stocate și puse la dispoziția utilizatorului, se va realiza printr-o tranzacție HTTP de tip POST. În urma acesteia, serverul va răspunde ca și în cazul cererii de tip GET, adică va returna resursa specificată. Pentru transmisia valorilor temperaturii, umidității, intenității luminii și presiunii, un exemplu de formă minimală a cererii HTTP de tip POST este următorul:

Structura cererii de tip POST:

Pe primul rând trebuie să se specifice tipul cererii (în cazul nostru POST) urmat de locația resursei vizate și de versiunea HTTP folosită. Versiunea HTTP/1.1 presupune o conexiune persistentă, adică într-o conexiune se pot realiza mai multe tranzacții (mai multe perechi de cerere-raspuns). În cazul nostru vom folosi doar o singură tranzacție pe conexiune iar pentru a semnaliza acest lucru vom seta parametrul „Connection” (în limba română conexiune) la valoarea „close” (în limba română închis). Acest lucru semnifică închiderea conexiunii în momentul finalizării tranzacției (rândul cu numârul 3).

Adresa serverului trebuie să fie specificată prin intermediul parametrului „HOST” (rândul cu numărul 2).

Conținutul cererii trebuie obligatoriu însoțit de o etichetă denumită „tip MIME” care are rolul de a indica formatul datelor. MIME (Multipurpose Internet Mail Extensions / Extensii cu scop general pentru poșta electronică) este un mod standardizat de a descrie tipul și formatul unui fișier transmis prin internet. Acesta a fost conceput inițial pentru a rezolva problemele de transfer ale mesajelor între sistemele de poștă electronică și ulterior adoptat de HTTP. [13] Eticheta „application/x-www-form-urlencoded” (rândul cu numârul 4) este folosită în general pentru formularele html. Aceasta presupune ca fiecare câmp din formular să fie de forma „nume_câmp=valoare” poziționate unul după altul și separate prin caracterul „&”. [14]

Pe rândul 5 este specificată dimensiunea în octeți a informațiilor transmise.

Pentru a indica începerea informațiilor de transmis se lasă un rând liber (rândul cu numârul 6) iar pe rândul 7 se regăsesc datele în formatul indicat de tipul MIME.

În urma acestei cereri, serverul web va returna un răspuns pe care îl vom prezenta în cele ce urmează.

Structura răspunsului:

Pe primul rând din răspunsul serverului web se regăsește versiunea de HTTP folosită și un cod de stare ce indică dacă tranzacția s-a finalizat cu succes sau nu. Codul „200 OK” semnifică finalizarea în condiții normale a tranzacției. [13]

Data și ora expedierii pachetului se regăsesc pe rândul al doilea, urmate de detalii despre configurația serverului pe rândul următor.

La fel ca în cazul cererii, descrisă pe pagina anterioară, se pot identifica parametrii „Content-Length”, „Connection” și „Content-Type” pe rândurile 4, 5 și 6.

Pe rândul 8 se regăsește mesajul returnat de server în urma cererii. Acest mesaj este de forma „per=numar” și semnifică valoarea în secunde a intervalului de timp dintre cererile efectuate de dispozitiv către serverul web.

CAPITOLUL 5
Implementarea hardware

5.1 Noțiuni introductive

Construcția dispozitivului care înglobează senzorii necesari pentru această aplicație presupune mai multe etape:

Proiectarea schemei electrice;

Proiectarea cablajului imprimat corespunzător schemei electrice;

Realizarea practică a cablajului imprimat;

Montajul componentelor.

Pentru proiectarea schemei electrice și a cablajului imprimat, am folosit programul EAGLE al companiei Autodesk. Acesta reprezintă un ansamblu de programe interconectate ce permite proiectarea și simularea unui circuit electronic, proiectarea de cablaje imprimate și generarea fișierelor necesare fabricării.

5.2 Proiectarea schemei electrice

Pentru a funcționa în mod corect, senzorii prezentați în capitolul 3 necesită să fie însoțiți de anumite componente electronice. În continuare, voi prezenta conectarea în circuitul electric a fiecărui senzor.

Fotorezistorul

În funcție de intensitatea luminii mediului, fotorezistorul își modifică rezistența conducând astfel la modificări ale valorii curentului electric. Pentru a putea interpreta valorile oferite de acest dispozitiv, am inserat în circuit un rezistor (figura 5.1). Variația curentului electric va produce modificări ale tensiunii pe acest rezistor, deci măsurând această tensiune, putem prelua valorile oferite de fotorezistor. În acest scop, între senzor și rezistor am conectat pinul analogic A0 al plăcii de dezvoltare.

Acest divizor rezistiv va funcționa după formula următoare, unde este valoarea tensiunii măsurate la pinul microcontrolerului A1 iar este valoarea rezistenței fotorezistorului.

Figura 5.1 Montarea în circuit a fotorezistorului

Senzorul de temperatură și umiditate DHT22

Senzorul DHT22 dispune de 4 pini dintre care se folosesc doar 3, deoarece conform fabricantului pinul cu numărul 3 nu trebuie conectat.[8] Între pinul digital de date și pinul de alimentare este necesară plasarea unei rezistențe. Aceasta are rolul de a menține o tensiune pe linia de date atunci cănd aceasta nu este folosită.

Figura 5.2 Montarea în circuit a senzorului DHT22

Pentru colectarea datelor de la senzor, pinul de date este legat la pinul digital 6 din portul D al plăcii de dezvoltare.

Senzorul de presiune MPL115A2

Ca și în cazul senzorului DHT22, fabricantul acestui dispozitiv impune utilizarea unor rezistențe pentru ridicarea tensiunii pe liniile digitale SCL și SDA.[9] Pinii denumiți *SHDN și *RST au rolul de a permite oprirea sau resetarea dispozitivului. Steluța (*) semnifică faptul că aceștia își îndeplinesc sarcinile atunci cand sunt conectați la masă. În cadrul acestei aplicații vor fi conectați la tensiunea de alimentare a senzorului prin intermediul unei rezistențe deoarece nu vor fi folosiți.

Figura 5.3 Montarea în circuit a senzorului MPL115A2

Condensatorul C1 este plasat în circuit cu rolul de a proteja senzorul împotriva unor eventuale perturbații ale tensiunii de alimentare.

Schema electrică completă se regăsește în anexa 1.

5.1 Proiectarea și fabricarea circuitului imprimat

Pentru a păstra un aspect fizic plăcut și pentru a permite conectarea ulterioară a unor alte module la placa de dezvoltare Arduino Uno, am proiectat circuitul imprimat sub forma unui scut (shield). Dimensiunile și forma acestuia sunt identice cu cele ale plăcii de dezvoltare. Porturile de tip „mamă” permit accesul unui alt modul la pinii microcontrolerului.

După verificarea traseelor și a tuturor componentelor folosind opțiunea „Design Rule Check” (Verificarea Regulilor de Proiectare) din programul EAGLE, fișierele generate au fost trimise fabricantului, care pe baza lor a realizat traseele, găurile, padurile componentelor și forma plăcuței electronice. În figura 5.4 se poate vedea plăcuța, așa cum a sosit din fabrică.

Mai multe imagini ale circuitului imprimat și ale plăcii se găsesc în anexa 2.

Figura 5.4 Placa electronică – față

Figura 5.5 Placa electronică – spate

CAPITOLUL 6
Implementarea software

6.1 Programarea și configurarea microcontrolerului

Pentru a scrie codul necesar funcționării dispozitivului, am folosit programul dedicat programării în limbajul C, Eclipse C împreună cu o extensie ce permite conectarea acestuia la placa de dezvoltare Arduino.

Pentru a ușura munca programatorilor, compania Arduino a dezvoltat o multitudine de biblioteci. Pentru a putea fi utilizate, acestea necesită o anumită configurație a registrelor microcontrolerului.

Pentru început, activăm întreruperile prin intermediul funcției „sei()”.

Anumite funcții din librariile Arduino necesită întârzieri generate de funcția „delay()”. Aceasta folosește timerul 0 al microcontrolerului. Setarea primilor doi biți din registrul TCCR0B, folosind comanda „TCCR0B |= 0b11” produce activarea timerului și funcționarea sa cu o frecvență de 250kHz. Pentru a activa întreruperile generate de depășirea timerului 0, setăm bitul denumit TOIE din registrul TIMSK0 prin intermediul comenzii „TIMSK0 |= (1 << TOIE0) ”.

6.2 Colectarea și prelucrarea datelor de la senzori

6.2.1 Fotorezistorul

Pentru a interpreta valoarea intensității luminii oferită de fotorezistor, trebuie să convertim semnalul analogic în semnal digital. În acest scop, trebuie să configurăm convertorul analog-digital (ADC) al microcontrolerului.

Valoarea rezultată este calculată în funcție de numărul maxim de valori pe care îl poate identifica ADC-ul adică 1024 și tensiunea de referință (VREF), după următoarea formulă:

Vom configura registrul ADMUX pentru a selecta tensiunea de alimentare (5V) ca tensiune de referință pentru ADC (setând bitul REFS0) și pinul reprezentat prin variabila „pinLDR” ca intrare în convertor (folosind primii 4 biți denumiți MUX3, MUX2, MUX1 și MUX0).

ADMUX = (1 << REFS0);

ADMUX |= pinLDR;

Figura 6.1 Organizarea registrului ADMUX Sursa: [3]

Convertorul analog-digital dispune de un generator propriu de ceas, a cărui frecvență trebuie configurată prin intermediul primilor 3 biți din registrul ADCSRA (APDS0, ADPS1 și ADPS2). Linia de cod „ADCSRA |= 0b111” setează frecvența generatorului de ceas la valoarea de 125kHz.

Figura 6.2 Organizarea registrului ADCSRA Sursa: [3]

Conform documentației oferite de fabricantul microcontrolerului ATmega328, o conversie a ADC-ului durează în mod normal 13 cicluri de ceas însă prima conversie după pornirea convertorului durează 25 de cicluri de ceas din cauza inițializării circuitelor analogice.

Pentru a activa convertorul trebuie setat bitul ADEN (bitul 7) folosind următoarea linie de cod: „ADCSRA |= (1 << ADEN) ”.

În momentul apelării funcției „readLdr”, se pornește conversia setând bitul ADSC și se așteaptă modificarea valorii sale în 0, semnificând finalizarea conversiei.

void readLdr() {

ADCSRA |= (1 << ADSC);

while ((ADCSRA & (1 << ADSC)));

_delay_ms(100);

lightIntensity = scale(ADC);

}

Valoarea obținută în urma conversiei este apoi scalată prin intermediul funcției „scale” pentru a obține o valoare procentuală a intensității luminii pe baza următoarei formule de calcul:

6.2.2 Senzorul de temperatură și umiditate DHT22

Comunicația cu senzorul de temperatură și umiditate DHT22 se realizează bidirecțional printr-un singur fir de date. Pentru a comanda senzorul să convertească și să trimită datele către microcontroler, trebuie să construim un semnal de start conform documentației dispozitivului.

Figura 6.3 Semnalul de start pentru senzorul DHT22

Pentru generarea acestui semnal, trebuie să parcurgem următorii pași:

Configurăm corespunzător registrul DDRD pentru ca pinul definit de variabila „pinDHT22” să fie pin de ieșire.

Setăm valoarea pinului pe 0 logic.

Așteptăm cel puțin 1 milisecundă.

Setăm valoarea pinului pe 1 logic.

Așteptăm între 20 și 40 de microsecunde.

Implementarea software a acestor pași este următoarea:

DDRD |= (1 << pinDHT22);

PORTD &= ~(1 << pinDHT22);

_delay_ms(2);

PORTD |= (1 << pinDHT22);

_delay_us(30);

La primirea acestui semnal, senzorul va întoarce un semnal de răspuns, semn că a înțeles semnalul de start și că urmează să inițieze conversia și transmisia datelor.

Figura 6.4 Semnalul de răspuns al senzorul DHT22

Pentru a verifica semnalul de răspuns, trebuie să schimbăm modul de funcționare al pinului „pinDHT22” din ieșire în intrare și să așteptăm apariția consecutivă a celor două valori ale semnalului.

//setăm pinul pinDHT22 ca intrare

DDRD &= ~(1 << pinDHT22);

//așteptăm valoarea 0

while (PIND & (1 << pinDHT22));

//așteptăm terminarea valorii 0

while ((PIND & (1 << pinDHT22)) == 0);

//așteptăm terminarea palierului 1

while (PIND & (1 << pinDHT22));

Imediat după acest semnal de răspuns, senzorul va trimite către microcontroler 40 de biți de date organizați astfel: primii 16 pentru valoarea presiunii, următorii 16 pentru valoarea temperaturii iar ultimii 8 reprezintă suma primilor 32. Transmisia unui bit este formată dintr-un semnal de valoare 0 logic și un semnal de valoare 1 logic a cărui durată determină valoarea bitului transmis.

Figura 6.5 Semnalele pentru transmisia biților senzorului DHT22

Pentru a implementa software recepția celor 5 octeți de date vom folosi o buclă de tip „for” cu 5 iterații. În cadrul fiecărei interații se citesc câte 8 biți, identificați în funcție de durata semnalului.

for (int i = 0; i < 5; i++) {

data[i] = 0;

for (int j = 0; j < 8; j++) {

while ((PIND & (1 << pinDHT22)) == 0); //cât timp primim semnal 0 așteptăm

_delay_us(60); //am primit semnalul 1, așteptăm 60 us

if (PIND & (1 << pinDHT22)) // daca după 60us semnalul este tot 1 atunci am primit bitul 1

data[i] = (data[i] << 1) | (0x01); //adaugăm un bit de 1 în data[i]

else

//altfel am primit bit de 0

data[i] = (data[i] << 1); //adaugam bit de 0 în data[i]

while (PIND & (1 << pinDHT22)); //asteptăm să se termine semnalul 1

}

}

În continuare, vom verifica corectitudinea datelor primite, comparând ultimul octet de date primit de la senzor cu ultimii 8 biți ai sumei celorlalți biți recepționați. În cazul în care aceste două valori nu sunt identice înseamnă ca cel puțin un bit a fost citit greșit.

După validarea sumei vom prelucra datele pentru a putea fi interpretate și transmise către serverul web folosind următoarele linii de cod:

if (((data[0] + data[1] + data[2] + data[3]) & 0xff) == data[4]) { //verificăm suma biților

//construim valorile celor doi parametrii măsurați

humidity = (data[0] << 8) | (data[1] & 0xff);

temperature = (data[2] << 8) | (data[3] & 0xff);

//verificăm dacă cel mai semnificativ bit din data[2] este 1

//în acest caz temperatura este negativă

if ((data[2] & 0b10000000) == 0b10000000) {

data[2] &= 0b01111111;

temperature = (data[2] << 8) | (data[3] & 0xff);

temperature = -temperature;

}

humidity = humidity / 10;

temperature = temperature / 10;

}

6.2.3 Senzorul de presiune MPL115A2

Pentru a comunica cu alte dispozitive prin protocolul I2C, microcontrolerul ATmega328 dispune de o interfață denumită Two-Wire Serial Interface (în limba română Interfață Serială cu două fire) și prescurtat TWI. Aceasta permite conectarea unui număr maxim de 128 de dispozitive diferite folosind două linii de date bidirecționale denumite SDA (Serial Data Line/Linia de date seriale) și SCL (Serial Clock Line/Linia de ceas serial).

Pentru a controla interfața, am structurat liniile de cod corespunzătoare acesteia în următoarele funcții:

Funcția de inițializare a interfeței

void i2cInit()

{

TWBR = 0x88;

TWSR = 0x00;

TWCR = 1 << TWEN;

}

Registrul TWBR are rolul de a indica frecvența de lucru a interfeței, de aici și denumirea Two-Wire Serial Interface Bit Rate (în limba română, Rata de bit a Interfeței Seriale cu două fire). Setând registrul TWBR la valoarea 0x88 și registrul TWSR la valoarea 0x00 (se obține un factor de scalare egal cu 1), pe baza formulei de calcul următoare obținem o frecvență de 55kHz.

Registrul TWCR reprezintă registrul de control al interfeței. Prin intermediul acestui registru se activează interfața TWI, se verifică dacă sarcina curentă s-a terminat (bitul TWINT) și se pot genera condițiile de start și de stop (TWSTA și TWSTO). Organizarea sa este descrisă în figura următoare:

Figura 6.6 Organizarea registrului TWCR Sursa: [3]

Ultima linie de cod din interiorul acestei funcții setează bitul TWEN activând astfel interfața.

Funcția de start

void i2cStart()

{

TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTA);

while (!(TWCR & (1 << TWINT)));

}

Pentru a genera condiția de start setăm bitul TWSTA și bitul TWINT din registrul TWCR apoi așteptăm finalizarea acțiunii semnalată de apariția valorii de 0 în bitul TWINT. În timpul execuției acestei sarcini, interfața verifică daca magistrala nu este ocupată de un alt dispozitiv și preia controlul acesteia. Daca magistrala nu este liberă, se așteaptă o condiție de stop și apoi se preia controlul magistralei.

Funcția de transmisie a mesajelor

void i2cWrite(char msg)

{

TWDR = msg;

TWCR = (1 << TWINT) | (1 << TWEN);

while (!(TWCR & (1 << TWINT)));

}

Pentru a comanda interfața să transmită un anumit mesaj trebuie să plasăm mesajul în registrul TWDR și să setăm bitul TWINT.

Funcția de recepție a mesajelor

int i2cRead()

{

TWCR = (1 << TWINT) | (1 << TWEN);

while (!(TWCR & (1 << TWINT)));

return TWDR;

}

Pentru a recepționa un mesaj trebuie doar să setăm bitul TWINT.

Funcția de stop

void i2cStop() {

TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);

}

Condiția de stop se generează prin setarea bitului TWSTO și a bitului TWINT. Aceasta are rolul de a semnala celorlalte dispozitive legate la magistrala I2C că pot să o folosească.

Pentru a își începe activitatea, senzorul MPL115A2 trebuie să primească un mesaj ce conține doar valoarea adresei sale adică 0x60. În protocolul I2C, adresarea trebuie să conțină obligatoriu un bit care semnifică intenția dispozitivului master. Adică valoarea 0 pentru scriere și valoarea 1 pentru citire. Astfel, în cazul de față vom folosi adresa 0xC0 pentru scriere și adresa 0xC1 pentru citire.

i2cInit();

i2cStart();

i2cWrite(0xC0);

i2cStop();

Următorul pas în utilizarea dispozitivului este citirea coeficienților de calibrare. Această operațiune se execută o singură dată pentru ca aceste valori sunt constante. Consultând harta de memorie a senzorului (din figura 6.7) se observă că trebuie îi cerem dispozitivului toate valorile registrilor începand de la adresa 0x04 și până la registrul 0x0B.

Figura 6.7 Harta memoriei senzorului MPL115A2 Sursa: [9]

Implementarea software a citirii acestor parametrii constă în următoarea buclă. În fiecare iterație se solicită și se primește un coeficient de calibrare, stocați apoi în vectorul „data2”.

for (int i = 0; i < 8; i++) {

i2cStart();

i2cWrite(0xC0);

i2cWrite(4 + i);

i2cStop();

i2cStart();

i2cWrite(0xC1);

data2[i] = i2cRead();

i2cStop();

}

Valorile recepționate trebuie să fie prelucrate conform specificațiilor din documentația senzorului [9] pentru a putea construi valorile coeficienților.

Coeficientul a1 are 3 părți zecimale, deci împărțim valoarea sa la 2^3 adică 8.

a1 = ((data2[0] * 256.0) + data2[1]);

a1 = a1 / 8;

Coeficientul b1 are 13 părți zecimale, deci împărțim valoarea sa la 2^13 adică 8192.

b1 = ((data2[2] * 256) + data2[3]);

b1 = b1 / 8192;

Coeficientul b2 are 14 părți zecimale, deci împărțim valoarea sa la 2^13 adică 16384.

b2 = ((data2[4] * 256) + data2[5]);

b2 = b2 / 16384;

Coeficientul c12 este deplasat la dreapta cu 2 biți și are 22 de părți zecimale, deci împărțim valoarea sa la 2^13 adică 16384.

c12 = ((data2[6] * 256.0 + data2[7]) >> 2);

c12 = c12 / 4194304;

Pentru a porni conversia presiunii și a temperaturii, senzorul trebuie să primească mesajele 0x12 și 0x00 consecutiv astfel:

i2cStart();

i2cWrite(0xC0);

i2cWrite(0x12);

i2cWrite(0x00);

i2cStop();

Apoi urmează citirea valorilor brute ale presiunii și temperaturii și calcularea valorilor compensate cu ajutorul coeficienților de calibrare. Senzorul returnează aceste valori la primirea mesajului 0x00.

i2cStart();

i2cWrite(0xC0);

i2cWrite(0x00);

i2cStop();

for (int i = 0; i < 4; i++) {

i2cStart();

i2cWrite(0xC1);

data2[i] = i2cRead();

i2cStop();

}

Construim valorile brute ale presiunii și temperaturii, amâdouă pe câte 10 biți.

int pres = ((data2[0] * 256) + (data2[1] & 0xC0)) / 64;

int temp = ((data2[2] * 256) + (data2[3] & 0xC0)) / 64;

În final, calculăm valoarea presiunii ținând cont de temperatură și de coeficienții de calibrare astfel:

float presComp = a0 + (b1 + c12 * temp) * pres + b2 * temp;

pressure = (65.0 / 1023.0) * presComp + 50.0;

6.3 Transmiterea datelor către serverul web

Pentru controlul modulului Arduino Ethernet Shield vom utiliza biblioteca Ethernet disponibilă în colecția de biblioteci Arduino. Prin intermediul acesteia, modulul poate fi configurat să funcționeze ca un client sau ca un server. În cadrul acestei aplicații vom utiliza doar modul de client.

Pentru a stabili conexiunea cu routerul la care este conectat modulul vom folosi funcția „begin”. Atunci cand acestă funcție primește ca parametru doar adresa mac a dispozitivului, execuția sa va conduce la obținerea automată a unei adrese IP folosind protocolul DHCP (Dynamic Host Configuration Protocol). Acesta are rolul de a aloca dinamic adrese IP și alte informații de configurație de rețea. [15]

În cazul în care operațiunea reușește, funcția „begin” va returna valoarea 0. Astfel putem să depistăm o eventuală eroare.

if (Ethernet.begin(mac) == 0) {

Serial.println("Eroare configurare prin DHCP");

} else {

Serial.println("Retea configurata!");

}

În continuare, vom stabili conexiunea cu serverul web, și vom construi pachetul de tip HTTP.

if (client.connect(server, 80)) {

//stinge led

PORTD &= ~(1 << pinLED);

client.println("POST " + dir + " HTTP/1.1");

client.println("Host: " + String(server) + "");

client.println("User-Agent: Arduino/1.0");

client.println("Connection: close");

client.println("Content-Type: application/x-www-form-urlencoded;");

client.print("Content-Length: ");

client.println(values.length());

client.println();

client.println(values);

}

După transmisia acestui mesaj, ne așteptăm la un răspuns din partea serverului. Astfel, introducem următoarea buclă de aștepare:

while (client.connected() && !client.available())

_delay_ms(1);

În corpul mesajului de răspuns, pe lângă directivele HTTP vom găsi valoarea perioadei de transmitere a datelor colectate de la senzori sub forma „per=valoare”. Această valoare trebuie să o identificăm și să o stocăm într-o variabilă. În acest scop, vom parcurge fiecare caracter recepționat până când vom identifica grupul de litere „per=”. Toate caracterele ce urmează după acesta până la finalul mesajului vor reprezenta valoarea perioadei.

Fiecare caracter preluat îl vom stoca în variabila „temp1” și îl vom supune verificărilor. Variabila „h” va reprezenta lungimea întregului mesaj.

int ctrl = 0;

int h = client.available(); // lungimea raspunsului

for (int i = 0; i < h; i++) { //parcurgem fiecare caracter al raspunsului

temp1 = (char) client.read(); //caracterul citit

if (ctrl == 1) {

periodTemp += temp1;

}

if (temp1 == "p") { //daca identificam "per=", tot ce urmeaza dupa reprezinta perioada

temp1 = (char) client.read();

i++;

if (temp1 == "e") {

temp1 = (char) client.read();

i++;

if (temp1 == "r") {

temp1 = (char) client.read();

i++;

if (temp1 == "=") {

ctrl = 1;

}

}

}

}

}

În cazul citirii eronate a valorii perioadei, funcția care convertește variabila periodTemp în tipul de date Int va returna valoarea 0. Vom trata această situație fixând valoarea perioadei la 2 secunde.

period = periodTemp.toInt();

if(period == 0) { //in cazul unei erori, setam perioada la 3 secunde

period = 3;

}

În acest moment, comunicația necesară cu serverul web s-a încheiat așa că vom opri clientul HTTP prin intermediul funcției „stop”.

client.stop();

Codul sursă complet se regăsește în anexa 3.

6.4 Interfața web

Pentru ca datele colectate de la senzori să poată fi interpretate și întelese de utilizator, am decis că este necesară proiectarea și realizarea unei interfețe web capabilă să afișeze informațiile sub forma unor diagrame. Datele de la senzori dar și anumiți parametrii de configurare vor fi stocați într-o bază de date a cărei structură este descrisă în figura următoare.

Figura 6.8 Structura bazei de date

Codul necesar realizări structurii bazei de date:

CREATE TABLE `data` (

`id` bigint(20) UNSIGNED NOT NULL,

`light_intensity` float NOT NULL,

`temperature` float NOT NULL,

`humidity` float NOT NULL,

`pressure` float NOT NULL,

`date` datetime DEFAULT CURRENT_TIMESTAMP

)

CREATE TABLE `settings` (

`id` int(11) UNSIGNED NOT NULL,

`period` int(11) NOT NULL DEFAULT '0',

`includeZero` tinyint(1) NOT NULL DEFAULT '0',

`save_on_change` tinyint(1) NOT NULL DEFAULT '0'

)

Pentru conectarea și interogarea bazei de date am folosit extensia PHP „mysqli”. Următoarele linii de cod au rolul de a face conectarea la baza de date. Acestea vor fi incluse în toate fișierele în care se fac interogări ale bazei de date.

$db = new mysqli($dbhost, $dbuser, $dbpass, $dbname);

if (mysqli_connect_errno()) {

die('Conectarea la baza de date a esuat!');

}

Microcontrolerul a fost configurat să transmită valorile senzorilor sub forma unei cereri HTTP de tip post, după cum am prezentat în capitolele anterioare. Pentru a primi aceste date, am realizat un script (fișierul conn.php) capabil să recunoască valorile și să le introducă în baza de date.

Inițial, scriptul verifică dacă a primit parametrii prin metoda POST. Dacă nu a primit nimic atunci va returna o pagină goală. Parametrii preluați îi stocam într-un vector pentru a fi ulterior folosiți în interogarea bazei de date.

if(!empty($_POST)) {

$value[0] = $_POST['lightIntensity'];

$value[1] = $_POST['humidity'];

$value[2] = $_POST['temperature'];

$value[3] = $_POST['pressure'];

Câmpul „save_on_change” din relația „settings” poate fi setat de utilizator din pagina de setări a interfeței. Dacă acest câmp este setat la valoarea 1, datele unei măsurători efectuate de senzori vor fi introduse în baza de date doar dacă cel puțin unul din senzori a oferit o valoare distinctă față de valorile din măsurătoarea anterioară. Daca valoarea câmpului este 0 atunci nu va fi nici o restricție în introducerea valorilor în baza de date.

Pentru a îndeplini aceste condiții trebuie să preluăm valorile ultimei măsurători din relația „data” prin următoarele linii de cod:

$result = $db->query("SELECT * from data ORDER by id DESC LIMIT 0,1");

$obj = $result->fetch_object();

Apelul funcției „fetch_object” returnează tuplul rezultat în urma interogării sub forma unui obiect.

În continuare, vom verifica valorile anterioare în comparație cu cele curente dacă este cazul, și vom realiza introduce măsurătorile în baza de date.

if (!$settings->save_on_change ||

($value[0] != $obj->light_intensity ||

$value[1] != $obj->humidity ||

$value[2] != $obj->temperature ||

$value[3] != $obj->pressure))

if ($result =

$db->prepare("INSERT INTO data

(light_intensity, humidity, temperature, pressure)

VALUES (?,?,?,?)"))

{

$result->bind_param("dddd", $value[0], $value[1], $value[2], $value[3]);

$result->execute();

$result->close();

}

}

În final, returnăm dispozitivului valoarea perioadei de transmisie a măsurătorilor sub forma „per=valoare”.

echo 'per='.$settings->period;

Pentru construcția paginilor am utilizat biblioteca Bootstrap. Aceasta reprezintă o colecție de componente html, css și javascript ce au rolul de a ușura dezvoltarea părții de front-end.

Generarea diagramelor ce reprezintă evoluția valorii unui anumit senzor în timp se face folosind biblioteca javascript CanvasJS a companiei Fenopix. Aceasta reprezintă o colecție de funcții responsabile cu generarea mai multor tipuri de diagrame.

Pentru a nu repeta bucăți mari de cod, am împărțit părțile comune ale paginilor adică partea de sus respectiv partea de jos în două fișiere denumite „header.php” și „footer.php”, pe care le vom include în toate paginile folosind comanda „include”.

Vom analiza în continuare fiecare fișier.

index.php

Această pagină reprezintă panoul principal al interfeței și are rolul de a prezenta utilizatorului ultimele valori înregistrate dar și detalii legate de configurația interfeței.

Biblioteca jQuery reprezintă o colecție de funcții realizate în limbajul javascript destinate să ușureze dezvoltarea scripturilor. Vom folosi această bibliotecă pentru a prelua și afișa ultimele măsurători din baza de date fără a fi nevoie ca utilizatorul să reîmprospăteze pagina folosind următoarea funcție:

$.ajax({

url: 'getAjaxData.php?type=0',

success: function(result) {

data = JSON.parse(result);

}});

Valorile preluate vor fi afișate în cadrul anumitor elemente html identificate.

$('#monitor-temperature-value').html(data.temperature + '°C');

$('#monitor-light_intensity-value').html(data.light_intensity + '%');

$('#monitor-humidity-value').html(data.humidity + '%');

$('#monitor-pressure-value').html(data.pressure + 'kPa');

$('#dataInterval').html(data.dataInterval);

$('#dataRows').html(data.dataRows);

Diagramele vor fi generate inițial fără nici o valoare, urmând să fie adaugate valori pe măsură ce ele sunt preluate. Un exemplu de inițializare și adăugare de valori într-o diagramă îl constituie următoarele linii de cod:

var chartLight = new CanvasJS.Chart("chartLightContainer", {

axisY: {

includeZero: <?=$settings->includeZero?>,

suffix: '%'

},

axisX:{

valueFormatString:"HH:mm:ss",

labelAngle: -50

},

toolTip:{

content: "{x} -> {y}%"

},

data: [{

type: "spline",

xValueFormatString: "DD MMM YYYY HH:mm:ss",

dataPoints: dpsLight

}]

});

dpsLight.push({

x: curDate,

y: data.light_intensity

});

Figura 6.9 Pagina principală a interfeței

getAjaxData.php

Acest fișier este accesat doar din codul javascript responsabil cu popularea diagramelor cu valori din baza de date. În interiorul său se găsesc patru tipuri de interogări ale bazei de date astfel. Una dintre ele oferă datele necesare paginii principale iar celelalte sunt dedicate celor trei tipuri de diagrame: valorile dintr-o zi, medie zilnică, medie lunară.

ChartGenerator.php

Pentru fiecare dintre cei patru parametrii măsurați (temperatură, umiditate, presiune, intensitatea luminii) avem de construit câte 3 diagrame deci în total 12. Am ales varianta de a construi o clasă prin intermediul căreia să generez aceste diagrame. Clasa conține câte o funcție pentru fiecare tip de diagramă, o funcție care generează meniul de deasupra diagramei și încă o funcție de inițializare ce stabilește numele și unitățile de măsură.

class ChartGenerator

{

private $parameter;

private $suffix;

private $title;

public function init($param) {…}

private function renderMenu($mode) {…}

public function renderChartByDay(){…}

public function renderChartDay(){…}

public function renderChartMonth(){…}

chart.php

În acest fișier vom face instanțierea obiectelor din clasa „ChartGenerator” în funcție de parametrul dorit și tipul de diagramă. Pentru a comunica acesta două informații, am stabilit o regulă de rescriere a URL-urilor în fișierul „htaccess”.

RewriteRule ^chart/[A-Za-z-_]+/[A-Za-z-]+$ chart.php

Această regulă semnifică faptul că vom ajunge la pagina „chart.php” cu orice URL de forma /chart/(litere mari, litere mici + _)/(litere mari, litere mici). În cele două spații se vor regăsi valorile parametrilor necesari pentru generarea diagramei.

Pentru a prelua aceste valori, vom „sparge” URL-ul în funcție de delimitatorul „/” folosind funcția „explode”. Apoi vom instanția câte un obiect al clasei pe baza acestor informații.

$url = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

$url = explode("/", $url);

$mode = $url[sizeof($url) – 1];

$parameter = $url[sizeof($url) – 2];

if ($mode == "byday") {

$chart = new ChartGenerator();

$chart -> init($parameter);

$chart -> renderChartByDay();

} elseif ($mode == "day") {

$chart = new ChartGenerator();

$chart -> init($parameter);

$chart -> renderChartDay();

} elseif ($mode == "month") {

$chart = new ChartGenerator();

$chart -> init($parameter);

$chart -> renderChartMonth();

} else {

echo '<br/> Tipul de diagramă este incorect!';

}

Pentru a permite selecția perioadei de timp din cadrul fiecărei diagrame, am utilizat biblioteca jQueryUI. Aceasta reprezintă un set de componente dedicate realizării unei interfețe ușor de utilizat. În cadrul acestei aplicații vom folosi componenta denumită DatePicker (în română, Selector de dată) care este practic un calendar ce permite selecția unei date.

Inițializarea acestei componente javascript asupra unui câmp se face prin intermediul următoarelor linii de cod, pe baza indentificatorului câmpului.

$("#dateStart").datepicker();

$("#dateStart").datepicker("option", "dateFormat", 'dd-mm-yy');

$('#dateStart').datepicker("setDate", "valoarea inițială");

Un exemplu de diagramă generată folosind clasa ChartGenerator.php este prezentat în figura 6.10.

Figura 6.10 Diagrama temperaturii medii zilnice

settings.php

Aceast fișier generează o pagină web care conține două formulare. Unul din acestea îi permite utilizatorului să modifice parametrii de configurare ai interfeței iar celălalt permite ștergerea măsurătorilor din baza de date în funcție de vechimea lor.

Figura 6.11 Pagina de configurare a interfeței

Codul sursă complet al interfeței web se regăsește în anexa 4.

Concluzii

DE COMPLETAT

Bibliografie

[1] Mihai Romanca, Microprocesoare și microcontrolere, Editura Universității Transilvania, Brașov, 2015, paginile 6-7;

[2] ATMEL: Microcontrolere AVR RISC, http://electronica-azi.ro/2001/03/22/atmel-microcontrolere-avr-risc/, accesat la data 04.06.2018;

[3] ATmega328P Datasheet, http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf, accesat la data 04.06.2018;

[4] Bazele Sistemelor Mecatronice, http://webbut.unitbv.ro/Carti%20on-line/BSM/BSM/capitol4.pdf, accesat la data 05.06.2018;

[5] Clasificarea Senzorilor și a Traductoarelor, https://www.scribd.com/document/291971871/Clasificarea-Senzorilor-Si-a-Traductoarelor, accesat la data 05.06.2018;

[6] Fotorezistorul, http://schemaelectrica.blogspot.com/2017/11/fotorezistorul.html, accesat la data 05.06.2018;

[7] Cadmium Sulphide (CdS) Photo Resistor, http://roboromania.ro/datasheet/GL5528-photo-resistor-senzor-roboromania.pdf, accesat la data 05.06.2018;

[8] DHT22, https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT22.pdf, accesat la data 06.06.2018;

[9] MPL115A2, Miniature I2C Digital Barometer, http://cache.freescale.com/files/sensors/doc/data_sheet/MPL115A2.pdf, accesat la data 19.06.2018;

[10] MPL115A2 Freescale Semiconductor, http://elcodis.com/parts/2221575/MPL115A2.html, accesat la data 19.06.2018;

[11] Arduino Ethernet Shield, https://www.mouser.com/catalog/specsheets/A000056_DATASHEET.pdf, accesat la data 20.06.2018;

[12] Ben Laurie & Peter Laurie, Apache: The Definitive Guide, 3rd Edition, O'Reilly Media, Sebastopol California, 2009, pagina 16;

[13] David Gourley, Brian Totty, Marjorie Sayer, Anshu Aggarwal, Sailu Reddy, HTTP: The Definitive Guide, O'Reilly Media, Sebastopol California, 2002, paginile 11,47,89;

[14] The HTTP POST Method, https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST, accesat la 22.06.2018;

[15] James F. Kurose, Keith W. Ross, Computer Networking: A Top-Down Approach (6th Edition), Pearson, Amherst Massachusetts, 2012, pagina 495;

Anexe

Anexa 1 Schema electrică proiectată în EAGLE

Anexa 2 Cablajul imprimat

Anexa 3 Codul sursă – microcontroler

#include <util/delay.h>

#include <Ethernet.h>

#include <utility/w5100.h>

//Configurare

int pinLDR = 1;

int pinLED = 7;

int pinDHT22 = 6;

byte mac[] = { 0xAA, 0xAA, 0xBC, 0xDE, 0xEF, 0xFF }; //

char server[] = "192.168.1.204";

String dir = "/conn.php";

float lightIntensity;

float humidity;

float temperature;

float pressure;

float cTemp;

String periodTemp;

String temp1;

uint8_t data[4];

int period;

EthernetClient client;

void connectToServer(float lightIntensity, float humidity, float temperature, float pressure);

float scale(float x);

void delay_s(int t);

void i2cInit();

void i2cStart();

void i2cWrite(char msg);

int i2cRead();

void i2cStop();

void readMplCoef();

void readMpl();

void readDHT22();

void readLdr();

int data2[8];

double a0 = 0.0, b1 = 0.0, b2 = 0.0, c12 = 0.0;

int main(void) {

//activeaza intreruperile

sei();

//configuram si activam timer 0.

//setam frecventa ceasului clk/64 = 250kHz ->pt fctii din arduino

TCCR0B |= 0b11;

// activam intrerupere overflow pentru timer0

TIMSK0 |= (1 << TOIE0);

ADMUX = (1 << REFS0); //selecteaza AVCC ca referinta

ADMUX |= pinLDR;//selectez pinul de intrare in adc

ADCSRA |= 0b111; //prescaleaza cu 128 (16MHz/128 = 125kHz)

ADCSRA |= (1 << ADEN); //activeaza ADC

Ethernet.begin(mac);

_delay_ms(2000);

W5100.setRetransmissionCount(2);

//aprinde led

DDRD |= (1 << pinLED); //setam pinul pinLED ca iesire

PORTD |= (1 << pinLED);

//Trimite comanda de start catre MPL115A2

i2cInit();

i2cStart();

i2cWrite(0xC0);

i2cStop();

_delay_ms(3500);

readMplCoef();

for (;;) {

readLdr();

readDHT22();

readMpl();

//Trimite valorile

connectToServer(lightIntensity, humidity, temperature, pressure);

Ethernet.maintain();

delay_s(period); //Delay minim 2 secunde (dht)

}

}

void connectToServer(float lightIntensity, float humidity, float temperature, float pressure) {

//Construim mesajul

String values = "lightIntensity=";

values += lightIntensity;

values += "&humidity=";

values += humidity;

values += "&temperature=";

values += temperature;

values += "&pressure=";

values += pressure;

if (client.connect(server, 80)) {

//stinge led

PORTD &= ~(1 << pinLED);

client.println("POST " + dir + " HTTP/1.1");

client.println("Host: " + String(server) + "");

client.println("User-Agent: Arduino/1.0");

client.println("Connection: close");

client.println("Content-Type: application/x-www-form-urlencoded;");

client.print("Content-Length: ");

client.println(values.length());

client.println();

client.println(values);

} else {

//aprinde led

PORTD |= (1 << pinLED);

}

// Asteptam raspunsul de la serverul web

while (client.connected() && !client.available())

_delay_ms(1);

int ctrl = 0;

int h = client.available(); // lungimea raspunsului

for (int i = 0; i < h; i++) { //parcurgem fiecare caracter al raspunsului

temp1 = (char) client.read(); //caracterul citit

if (ctrl == 1) {

periodTemp += temp1;

}

if (temp1 == "p") { //daca identificam "per=", tot ce urmeaza dupa reprezinta perioada de achizitie

temp1 = (char) client.read();

i++;

if (temp1 == "e") {

temp1 = (char) client.read();

i++;

if (temp1 == "r") {

temp1 = (char) client.read();

i++;

if (temp1 == "=") {

ctrl = 1;

}

}

}

}

}

period = periodTemp.toInt();

if(period == 0) { //in cazul unei erori, setam perioada la 3 secunde

period = 2;

}

client.stop();

periodTemp = "";

return;

}

float scale(float x) {

return x*100/1023;

}

void delay_s(int t)

{

while (t>0)

{

t–;

_delay_ms(1000);

}

}

void i2cInit() {

TWBR = 0x88; //setam frecventa 100kHz

TWSR = 0x00;

TWCR = 1 << TWEN; //activare interfata TWI

}

void i2cStart() {

TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTA); //Trimite conditia de start

while (!(TWCR & (1 << TWINT))); //Asteapta finalizarea

}

void i2cWrite(char msg) {

TWDR = msg;

TWCR = (1 << TWINT) | (1 << TWEN);

while (!(TWCR & (1 << TWINT))); //Asteapta finalizarea

}

int i2cRead() {

TWCR = (1 << TWINT) | (1 << TWEN);

while (!(TWCR & (1 << TWINT))); //Asteapta finalizarea

return TWDR;

}

void i2cStop() {

TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);

}

void readMplCoef() {

for (int i = 0; i < 8; i++) {

i2cStart();

i2cWrite(0xC0);

i2cWrite(4 + i);

i2cStop();

i2cStart();

i2cWrite(0xC1);

data2[i] = i2cRead();

i2cStop();

}

a0 = ((data2[0] * 256.0) + data2[1]);

a0 = a0 / 8.0;

b1 = ((data2[2] * 256) + data2[3]);

b1 = b1 / 8192;

b2 = ((data2[4] * 256) + data2[5]);

b2 = b2 / 16384;

c12 = (((data2[6] * 256) + data2[7]) >> 2);

c12 = c12 / 4194304;

}

void readMpl() {

//cerem si citim presiunea si temperatura din MPL115A2

i2cStart();

i2cWrite(0xC0);

i2cWrite(0x12);

i2cWrite(0x00);

i2cStop();

_delay_ms(300);

i2cStart();

i2cWrite(0xC0);

i2cWrite(0x00);

i2cStop();

for (int i = 0; i < 4; i++) {

i2cStart();

i2cWrite(0xC1);

data2[i] = i2cRead();

i2cStop();

}

int pres = ((data2[0] * 256) + (data2[1] & 0xC0)) / 64;

int temp = ((data2[2] * 256) + (data2[3] & 0xC0)) / 64;

if (temp < 0) {

temp = temp * (-1);

}

if (pres < 0) {

pres = pres * (-1);

}

//calculam valoarea compensata a presiunii

float presComp = a0 + (b1 + c12 * temp) * pres + b2 * temp;

pressure = (65.0 / 1023.0) * presComp + 50.0;

}

void readDHT22() {

//Trimitem semnal de start catre DHT22

DDRD |= (1 << pinDHT22); //setam pinul pinDHT22 din port D ca output (1)

PORTD &= ~(1 << pinDHT22); //setam iesirea pe 0

_delay_ms(2); // semnal LOW pt > 1ms

PORTD |= (1 << pinDHT22); //setam iesirea pe 1

_delay_us(30); // semnal HIGH pt 20-40 us

//Asteptam respunsul senzorului (semnal LOW pt 80us si semnal HIGH pt 80us)

DDRD &= ~(1 << pinDHT22); //setam pinul pinDHT22 din port D ca input (0)

while (PIND & (1 << pinDHT22)); // asteptam semnalul LOW din partea senzorului

while ((PIND & (1 << pinDHT22)) == 0); // asteptam terminarea semnalului LOW

while (PIND & (1 << pinDHT22)); //asteptam terminarea semnalului HIGH

for (int i = 0; i < 5; i++) {

data[i] = 0;

for (int j = 0; j < 8; j++) {

while ((PIND & (1 << pinDHT22)) == 0); //cat timp primim semnal LOW asteptam

_delay_us(60); //am primit semnalul HIGH, asteptam 60 us

if (PIND & (1 << pinDHT22)) // daca dupa 60us semnalul este tot HIGH atunci am primit bit 1 (el tine 70us)

data[i] = (data[i] << 1) | (0x01);

else //altfel am primit bit de 0 (pt 0, semnalul sta HIGH 26~28us)

data[i] = (data[i] << 1);

while (PIND & (1 << pinDHT22)); //asteptam sa se termine semnalul HIGH

}

}

if (((data[0] + data[1] + data[2] + data[3]) & 0xff) == data[4]) { //verificam suma bitilor

humidity = (data[0] << 8) | (data[1] & 0xff);

temperature = (data[2] << 8) | (data[3] & 0xff);

//verificam daca primul cel mai semnificativ bit din data[2] este 1

//in acest caz temperatura este negativa

if ((data[2] & 0b10000000) == 0b10000000) {

data[2] &= 0b01111111;

temperature = (data[2] << 8) | (data[3] & 0xff);

temperature = -temperature;

}

humidity = humidity / 10;

temperature = temperature / 10;

}

}

void readLdr() {

ADCSRA |= (1 << ADSC); // porneste conversia pentru LDR

//ADSC se face 0 la terminarea conversiei

while ((ADCSRA & (1 << ADSC)));

_delay_ms(100);

//Scalam valoarea intensitatii luminii intre 0 si 100

lightIntensity = scale(ADC);

}

Anexa 4 Codul sursă – interfață web

header.php

<!DOCTYPE html>

<html>

<head>

<meta name="viewport" content="width=device-width, initial-scale=1">

<link rel="stylesheet" href="<?=URL ?>/includes/bootstrap/css/bootstrap-sand.min.css">

<script src="<?=URL ?>/includes/jquery-3.3.1.min.js"></script>

<script src="<?=URL ?>/includes/bootstrap/js/bootstrap.min.js"></script>

<script src="<?=URL ?>/includes/script.js"></script>

<link rel="stylesheet" href="<?=URL ?>/includes/fontawesome/css/fontawesome-all.css">

<link rel="stylesheet" href="<?=URL ?>/includes/style.css">

<script src="<?=URL ?>/includes/jquery-ui/jquery-ui.js"></script>

<link rel="stylesheet" href="<?=URL ?>/includes/jquery-ui/jquery-ui.css">

</head>

<body>

<header>

<nav class="navbar navbar-expand-lg navbar-light navbar-dark bg-primary fixed-top">

<div class="container">

<a class="navbar-brand" href="<?=URL ?>/"><i class="far fa-chart-bar"></i></a>

<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">

<span class="navbar-toggler-icon"></span>

</button>

<div class="collapse navbar-collapse" id="navbarSupportedContent" >

<ul class="navbar-nav mr-auto">

<li class="nav-item <?=($page === 'index' ? 'active' : '') ?>">

<a class="nav-link" href="<?=URL ?>/">Panou</a>

</li>

<li class="nav-item dropdown <?=($page === 'chart' ? 'active' : '') ?>">

<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">

Diagrame</a>

<div class="dropdown-menu" aria-labelledby="navbarDropdown">

<a class="dropdown-item" href="<?=URL ?>/chart/temperature/byday">Temperatură</a>

<a class="dropdown-item" href="<?=URL ?>/chart/light_intensity/byday">Intensitatea luminii</a>

<a class="dropdown-item" href="<?=URL ?>/chart/humidity/byday">Umiditate</a>

<a class="dropdown-item" href="<?=URL ?>/chart/pressure/byday">Presiune</a>

</div>

</li>

<li class="nav-item <?=($page === 'settings' ? 'active' : '') ?>">

<a class="nav-link" href="<?=URL ?>/settings">Setări</a>

</li>

</ul>

</div></div>

</nav>

</header>

<div class="container">

<div class="row">

<div class="col-lg-12 monitor-tab-container">

<div class="monitor-tab">

footer.php

</div></div></div></div></div>

<script src="<?=URL ?>/includes/canvasjs/canvasjs.min.js"></script>

</body>

</html>

.htaccess

RewriteEngine On

RewriteRule ^chart/[A-Za-z-_]+/[A-Za-z-]+$ chart.php

RewriteRule ^settings$ settings.php

chart.php

<?php

require_once('maincore.php');

require_once('ChartGenerator.php');

$page = 'chart';

include 'templates/header.php';

$url = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

$url = explode("/", $url);$mode = $url[sizeof($url) – 1];

$parameter = $url[sizeof($url) – 2];

if ($mode == "byday") {

$chart = new ChartGenerator();

$chart -> init($parameter);

$chart -> renderChartByDay();

} elseif ($mode == "day") {

$chart = new ChartGenerator();

$chart -> init($parameter);

$chart -> renderChartDay();

} elseif ($mode == "month") {

$chart = new ChartGenerator();

$chart -> init($parameter);

$chart -> renderChartMonth();

} else {

echo '<br/> Tipul de diagramă este incorect!';

}

include "templates/footer.php";

?>

chartGenerator.php

<?php

class ChartGenerator

{

private $parameter;

private $suffix;

private $title;

public function init($param)

{

$this->parameter = $param;

switch ($this->parameter) {

case "temperature":

$this->suffix = "°C";

$this->title = "Temperatură";

break;

case "light_intensity":

$this->suffix = "%";

$this->title = "Intensitatea luminii";

break;

case "humidity":

$this->suffix = "%";

$this->title = "Umiditate";

break;

case "pressure":

$this->suffix = "kPa";

$this->title = "Presiune";

break;

default:

die('<br/>Parametru inexistent!');

break;

}

}

private function renderMenu($mode) {

?>

<div class="btn-group" role="group">

<a href="<?=URL?>/chart/<?=$this->parameter?>/byday" class="btn btn-<?=($mode === 'byday' ? 'primary active' : 'secondary') ?> btn-lg" role="button" >Valori pe zi</a>

<a href="<?=URL?>/chart/<?=$this->parameter?>/day" class="btn btn-<?=($mode === 'day' ? 'primary active' : 'secondary') ?> btn-lg " role="button" >Medie zilnică</a>

<a href="<?=URL?>/chart/<?=$this->parameter?>/month" class="btn btn-<?=($mode === 'month' ? 'primary active' : 'secondary') ?> btn-lg" role="button" >Medie lunară</a>

</div>

<?php

}

public function renderChartByDay()

{

Global $settings;

if (isset($_GET['dateToday'])) {

$dateToday = $_GET['dateToday'];

} else {

$dateToday = date("d-m-Y", strtotime("now"));

}

$dateTodayJ = explode('-', $dateToday);

$dateTodayJ = $dateTodayJ[2] . ',' . ($dateTodayJ[1] – 1) . ',' . $dateTodayJ[0];

?>

<script>

window.onload = function () {

var dps = []; // dataPoints

CanvasJS.addCultureInfo("ro",

{ decimalSeparator: ",", digitGroupSeparator: ".",months: ["Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie", "Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"],

shortMonths: ["Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sept", "Oct", "Noi", "Dec"]

});

var chart = new CanvasJS.Chart("chartContainer", {

culture: "ro",

axisY: {

includeZero: <?=$settings->includeZero?>,

suffix: '<?=$this->suffix?>'

},

axisX: { valueFormatString: " HH:mm:ss",

labelAngle: -50

},

toolTip: {

content: "{x} -> {y}<?=$this->suffix?>"

},

data: [{

type: "spline",

lineThickness: 2,

showInLegend: false,

xValueFormatString: "DD MMMM YYYY HH:mm:ss",

dataPoints: dps

}]

});

var obj;

$.ajax({

url: '<?=URL ?>/getAjaxData.php?type=1&param=<?=$this->parameter?>&dateToday=<?=$dateToday ?>',

dataType: "html",

success: function (result) {

obj = JSON.parse(result);

chart.render();

$.each(obj, function (i, item) {

dps.push({

x: convertDatetime(item.date),

y: item.<?=$this->parameter?>

});

});

chart.render();

},

complete: function () {

}

});

$('#dateToday').datepicker();

$("#dateToday").datepicker("option", "dateFormat", 'dd-mm-yy'); $('#dateToday').datepicker("setDate", new Date(<?=$dateTodayJ?>));

}

</script>

<div class="container" style="margin-top:20px;">

<div class="chartTitle" style="text-align: center">

<?=$this->title?>

</div>

<?php

$this->renderMenu("byday");

?>

<div id="chartContainer" style="height: 300px; width: 100%;"></div>

<form method="get" action="">

<p>Data: <input type="text" name="dateToday" id="dateToday" autocomplete="off"></p>

<button type="submit" class="btn btn-success">Afișare</button>

</form>

</div>

<?php

}

public function renderChartDay()

{

if (isset($_GET['dateStart']) && isset($_GET['dateEnd'])) {

$dateStart = $_GET['dateStart'];

$dateEnd = $_GET['dateEnd'];

} else {

$dateStart = date("d-m-Y", strtotime("-7 day"));

$dateEnd = date("d-m-Y");

}

$dateStartJ = explode('-', $dateStart);

$dateEndJ = explode('-', $dateEnd);

$dateStartJ = $dateStartJ[2] . ',' . ($dateStartJ[1] – 1) . ',' . $dateStartJ[0];

$dateEndJ = $dateEndJ[2] . ',' . ($dateEndJ[1] – 1) . ',' . $dateEndJ[0];

?>

<script>

var dps = [];

window.onload = function () {

CanvasJS.addCultureInfo("ro",

{

decimalSeparator: ",",

digitGroupSeparator: ".",months: ["Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie", "Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"],shortMonths: ["Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sept", "Oct", "Noi", "Dec"]

});

var chart = new CanvasJS.Chart("chartContainer", {

culture: "ro",

animationEnabled: true,

axisY: {

suffix: "<?=$this->suffix?>"

},

axisX: {

valueFormatString: "DD MM YYYY",

labelAngle: -50

},

toolTip: {

content: "{x} -> {y}<?=$this->suffix?>"

},

data: [{

xValueFormatString: "DD MMMM YYYY",

type: "column",

showInLegend: false,

legendMarkerColor: "grey",

dataPoints: dps

}]

});

chart.render();

var obj;

$.ajax({

url: '<?=URL ?>/getAjaxData.php?type=2&param=<?=$this->parameter?>&dateStart=<?=$dateStart ?>&dateEnd=<?=$dateEnd?>',

dataType: "html",

success: function (result) {

obj = JSON.parse(result);

chart.render();

$.each(obj, function (i, item) {

dps.push({

x: convertDatetime(item.date),

y: item.<?=$this->parameter?>

});

});

chart.render();

},

complete: function () {}

});

$("#dateStart").datepicker();

$("#dateEnd").datepicker();

$("#dateStart").datepicker("option", "dateFormat", 'dd-mm-yy');

$("#dateEnd").datepicker("option", "dateFormat", 'dd-mm-yy');

$('#dateStart').datepicker("setDate", new Date(<?=$dateStartJ?>));

$('#dateEnd').datepicker("setDate", new Date(<?=$dateEndJ?>));

}

</script>

<div class="container" style="margin-top:20px;">

<div class="chartTitle" style="text-align: center">

<?=$this->title?>

</div>

<?php

$this->renderMenu("day");

?>

<div id="chartContainer" style="height: 300px; width: 100%;"></div>

<form method="get" action="">

<p>De la data: <input type="text" name="dateStart" id="dateStart" autocomplete="off"></p>

<p>Până la data: <input type="text" name="dateEnd" id="dateEnd" autocomplete="off"></p>

<button type="submit" class="btn btn-success">Afișare</button>

</form>

</div>

<?php

}

public function renderChartMonth()

{

if (isset($_GET['dateStart']) && isset($_GET['dateEnd'])) {

$dateStart = $_GET['dateStart'];

$dateEnd = $_GET['dateEnd'];

} else {

$dateStart = date("01-m-Y");

$dateEnd = date("01-m-Y", strtotime("+1 month"));

}

$dateStartJ = explode('-', $dateStart);

$dateEndJ = explode('-', $dateEnd);

$dateStartJ = $dateStartJ[2] . ',' . ($dateStartJ[1] – 1) . ',' . $dateStartJ[0];

$dateEndJ = $dateEndJ[2] . ',' . ($dateEndJ[1] – 1) . ',' . $dateEndJ[0];

?>

<script>

var dps = [];

window.onload = function () {

CanvasJS.addCultureInfo("ro",

{

decimalSeparator: ",",

digitGroupSeparator: ".",months: ["Ianuarie", "Februarie", "Martie", "Aprilie", "Mai", "Iunie", "Iulie", "August", "Septembrie", "Octombrie", "Noiembrie", "Decembrie"],

shortMonths: ["Ian", "Feb", "Mar", "Apr", "Mai", "Iun", "Iul", "Aug", "Sept", "Oct", "Noi", "Dec"]

});

var chart = new CanvasJS.Chart("chartContainer", {

culture: "ro",

animationEnabled: true,

axisY: {

suffix: "<?=$this->suffix?>"

},

axisX: {

valueFormatString: "MMM YYYY",

labelAngle: -50

},

toolTip: {

content: "{x} -> {y}<?=$this->suffix?>"

},

data: [{

xValueFormatString: "MMMM YYYY",

type: "column",

showInLegend: false,

legendMarkerColor: "grey",

dataPoints: dps

}]

});

chart.render();

var obj;

$.ajax({

url: '<?=URL ?>/getAjaxData.php?type=3&param=<?=$this->parameter?>&dateStart=<?=$dateStart ?>&dateEnd=<?=$dateEnd?>',

dataType: "html",

success: function (result) {

obj = JSON.parse(result);

chart.render();

$.each(obj, function (i, item) {

dps.push({

x: convertDatetime(item.date),

y: item.<?=$this->parameter?>

});

});

chart.render();

},

complete: function () {}

}); $('#dateStart').datepicker({

changeMonth: true,

changeYear: true,

dateFormat: 'MM yy',

showButtonPanel: true,

onClose: function (dateText, inst) {

var month = $("#ui-datepicker-div .ui-datepicker-month :selected").val();

var year = $("#ui-datepicker-div .ui-datepicker-year :selected").val();

$(this).datepicker('setDate', new Date(year, month, 1));

}

});

$('#dateEnd').datepicker({

changeMonth: true,

changeYear: true,

dateFormat: 'MM yy',

showButtonPanel: true,

onClose: function (dateText, inst) {

var month = $("#ui-datepicker-div .ui-datepicker-month :selected").val();

var year = $("#ui-datepicker-div .ui-datepicker-year :selected").val();

$(this).datepicker('setDate', new Date(year, month, 1));

}

});

$("#dateStart").datepicker("option", "dateFormat", 'dd-mm-yy');

$("#dateEnd").datepicker("option", "dateFormat", 'dd-mm-yy');

$('#dateStart').datepicker("setDate", new Date(<?=$dateStartJ?>));

$('#dateEnd').datepicker("setDate", new Date(<?=$dateEndJ?>));

}

</script>

<style>

.ui-datepicker-calendar {

display: none;

}

</style>

<div class="container" style="margin-top:20px;">

<div class="chartTitle" style="text-align: center">

<?=$this->title?>

</div>

<?php

$this->renderMenu("month");

?>

<div id="chartContainer" style="height: 300px; width: 100%;"></div>

<form method="get" action="">

<p>De la data: <input type="text" name="dateStart" id="dateStart" autocomplete="off"></p>

<p>Până la data: <input type="text" name="dateEnd" id="dateEnd" autocomplete="off"></p>

<button type="submit" class="btn btn-success">Afișare</button>

</form>

</div>

<?php

}

}

conn.php

<?php

require_once('maincore.php');

if(!empty($_POST)) {

$value[0] = $_POST['lightIntensity'];

$value[1] = $_POST['humidity'];

$value[2] = $_POST['temperature'];

$value[3] = $_POST['pressure'];

$result = $db->query("SELECT * from data ORDER by id DESC LIMIT 0,1");

$obj = $result->fetch_object();

if ($value[3] < "120") {

if (!$settings->save_on_change || ($value[0] != $obj->light_intensity || $value[1] != $obj->humidity || $value[2] != $obj->temperature || $value[3] != $obj->pressure))

if ($result = $db->prepare("INSERT INTO data (light_intensity,humidity,temperature,pressure) VALUES (?,?,?,?)")

) {

$result->bind_param("dddd", $value[0], $value[1], $value[2], $value[3]);

$result->execute();

$result->close();

}

}

echo 'per='.$settings->period;

}

?>

getAjaxData.php

<?php

require_once('maincore.php');

if(isset($_GET['type'])) {

$type = $_GET['type'];

} else {

$type = 0;

}

if($type == 0) {

$result = $db->query("SELECT COUNT(*) from data");

$dataRows = $result->fetch_row()[0];

$result = $db->query("SELECT * from data ORDER by id DESC LIMIT 0,1");

$obj = $result->fetch_object();

if($settings->period < 60) {

$dataInterval = $settings->period." secunde";

} else {

$dataInterval = ($settings->period/60)." minute";

}

echo '{"humidity": '.$obj->humidity.', "temperature": '.$obj->temperature.', "light_intensity": '.$obj->light_intensity.', "pressure": '.$obj->pressure.', "date" : "'.$obj->date.'", "dataRows": "'.number_format($dataRows).'", "dataInterval": "'.$dataInterval.'"}';

}

// valori pe zi

if($type == 1) {

$param = $_GET['param'];

header('Content-Type: application/json');

$data_points = array();

$result = $db->query("SELECT * from data

where date >= '".convertDate($_GET['dateToday'])."'

AND date <= '".convertDate($_GET['dateToday'])." 23:59:59'

");

while ($obj = $result->fetch_object()) {

$point = array($param => $obj->$param, "date" => $obj->date);

array_push($data_points, $point);

}

echo json_encode($data_points, JSON_NUMERIC_CHECK);

}

// media pe zile

if($type == 2) {

$param = $_GET['param'];

header('Content-Type: application/json');

$data_points = array();

$result = $db->query("SELECT AVG( ".$param." ) as ".$param." , date FROM data

where date >= '".convertDate($_GET['dateStart'])."'

AND date <= '".convertDate($_GET['dateEnd'])." 23:59:59'

GROUP BY DATE( date ) ");

while ($obj = $result->fetch_object()) {

$point = array($param => round($obj->$param,2) , "date" => $obj->date);

array_push($data_points, $point);

}

echo json_encode($data_points, JSON_NUMERIC_CHECK);

}

// media pe luni

if($type == 3) {

$param = $_GET['param'];

header('Content-Type: application/json');

$data_points = array();

$result = $db->query("SELECT AVG( ".$param." ) as ".$param." , date FROM data

where date >= '".convertDate($_GET['dateStart'])."'

AND date <= '".convertDate($_GET['dateEnd'])." 23:59:59'

GROUP BY YEAR(date), MONTH(date) ");

while ($obj = $result->fetch_object()) {

$point = array($param => round($obj->$param,2) , "date" => $obj->date);

array_push($data_points, $point);

}

echo json_encode($data_points, JSON_NUMERIC_CHECK);

}

function convertDate($rodate) {

$rodate = explode('-', $rodate);

return $rodate[2].'-'.$rodate[1].'-'.$rodate[0];

}

• index.php

<?php

require_once('maincore.php');

$page = "index";

include 'templates/header.php';

?>

<span class="fas fa-long-arrow-alt-up" style="display:none;"></span>

<span class="fas fa-long-arrow-alt-down" style="display:none;"></span>

<?php

echo '

<div class="row monitor-dash">

<div class="col-xs-3 monitor-dash-item-temp">

<div class="text-center">

<span class="fa fa-thermometer-empty monitor-dash-item-icon"></span><br/> <br/>

<span class="fas fa-long-arrow-alt-down hide-item" id="monitor-temperature-indicator"></span>

<span class="monitor-dash-item-value" id="monitor-temperature-value"></span>

</div>

</div>

<div class="col-xs-3 monitor-dash-item-light">

<div class="text-center">

<span class="far fa-lightbulb monitor-dash-item-icon"></span>

<br/> <br/>

<span class="fas fa-long-arrow-alt-down hide-item" id="monitor-light_intensity-indicator"></span>

<span class="monitor-dash-item-value" id="monitor-light_intensity-value"></span>

</div>

</div>

<div class="col-xs-3 monitor-dash-item-hum">

<div class="text-center">

<span class="fas fa-tint monitor-dash-item-icon"></span>

<br/> <br/>

<span class="fas fa-long-arrow-alt-down hide-item" id="monitor-humidity-indicator"></span>

<span class="monitor-dash-item-value" id="monitor-humidity-value"></span>

</div>

</div>

<div class="col-xs-3 monitor-dash-item-pressure">

<div class="text-center">

<span class="fas fa-tachometer-alt monitor-dash-item-icon"></span>

<br/> <br/>

<span class="fas fa-long-arrow-alt-down hide-item" id="monitor-pressure-indicator"></span>

<span class="monitor-dash-item-value" id="monitor-pressure-value"></span>

</div>

</div>

</div>

';

echo '

<div class="row dashboard-cards">';

if($settings->includeZero) {

$includeZero = "Da";

} else {

$includeZero = "Nu";

}

echo '<div class="col-lg-6">

<div class="card">

<div class="card-header">

Informații

</div>

<div class="card-body">

<ul class="list-group">

<li class="list-group-item">Numărul de măsurători <span class="float-right badge badge-pill badge-primary" id="dataRows"></span></li>

<li class="list-group-item">Perioada de achiziție <span class="float-right badge badge-pill badge-danger" id="dataInterval"></span></li>

<li class="list-group-item">Ultima achiziție <span class="float-right badge badge-pill badge-info" id="updateTime"></span> </li>

<li class="list-group-item">Include punctul 0 în diagrame <span class="float-right badge badge-pill badge-warning">'.$includeZero.'</span> </li>

</ul>

</div>

</div>

</div>';

echo '<div class="col-lg-6">

<div class="card">

<div class="card-header">Temperatură</div>

<div class="panel-body">

<div id="chartTempContainer" style="height: 300px; width: 100%;"></div>

</div>

</div>

</div>';

echo '<div class="col-lg-6">

<div class="card">

<div class="card-header">

Intensitatea luminii

</div>

<div>

<div id="chartLightContainer" style="height: 300px; width: 100%;"></div>

</div>

</div>

</div>';

echo '<div class="col-lg-6">

<div class="card">

<div class="card-header">

Umiditate

</div>

<div>

<div id="chartHumidityContainer" style="height: 300px; width: 100%;"></div>

</div>

</div>

</div>';

echo '<div class="col-lg-6">

<div class="card">

<div class="card-header">

Presiune

</div>

<div>

<div id="chartPressureContainer" style="height: 300px; width: 100%;"></div>

</div>

</div>

</div>';

echo '</div>'; //end row

?>

<script>

function setIndicator(old, newv, comp) {

if (old < newv) {

$('#monitor-'+ comp +'-indicator').attr("class","fas fa-long-arrow-alt-up");

$('#monitor-'+ comp +'-indicator').css('color', '#1eff38');

} else if (old > newv) {

$('#monitor-'+ comp +'-indicator').attr("class","fas fa-long-arrow-alt-down");

$('#monitor-'+ comp +'-indicator').css('color', 'red');

} else if (old = newv) {

$('#monitor-'+ comp +'-indicator').attr("class","fas fa-long-arrow-alt-down hide-item");

}

}

var oldTemperature = 0;

var oldLightIntensity = 0;

var oldHumidity = 0;

var oldPressure = 0;

var curDate;

window.onload = function () {

var dataLength = 1000;

var dpsTemp = []; // dataPoints Temperature

var dpsLight = []; // dataPoints Light

var dpsHumidity = []; //dataPoints Humidity

var dpsPressure = []; //dataPoints Humidity

var chartTemp = new CanvasJS.Chart("chartTempContainer", {

axisY: {

includeZero: <?=$settings->includeZero?>,

suffix: '°C'

},

axisX:{

valueFormatString:"HH:mm:ss",

labelAngle: -50

},

toolTip:{

content: "{x} -> {y}°C"

},

data: [{

type: "spline",

xValueFormatString: "DD MMM YYYY HH:mm:ss",

dataPoints: dpsTemp

}]

});

var chartLight = new CanvasJS.Chart("chartLightContainer", {

axisY: {

includeZero: <?=$settings->includeZero?>,

suffix: '%'

},

axisX:{

valueFormatString:"HH:mm:ss",

labelAngle: -50

},

toolTip:{

content: "{x} -> {y}%"

},

data: [{

type: "spline",

xValueFormatString: "DD MMM YYYY HH:mm:ss",

dataPoints: dpsLight

}]

});

var chartHumidity = new CanvasJS.Chart("chartHumidityContainer", {

axisY: {

includeZero: <?=$settings->includeZero?>,

suffix: '%'

},

axisX:{

valueFormatString:"HH:mm:ss",

labelAngle: -50

},

toolTip:{

content: "{x} -> {y}%"

},

data: [{

type: "spline",

xValueFormatString: "DD MMM YYYY HH:mm:ss",

dataPoints: dpsHumidity

}]

});

var chartPressure = new CanvasJS.Chart("chartPressureContainer", {

axisY: {

includeZero: <?=$settings->includeZero?>,

suffix: 'kPa'

},

axisX:{

valueFormatString:"HH:mm:ss",

labelAngle: -50

},

toolTip:{

content: "{x} -> {y} kPa"

},

data: [{

type: "spline",

xValueFormatString: "DD MMM YYYY HH:mm:ss",

dataPoints: dpsPressure

}]

});

(function worker() {

$.ajax({

url: 'getAjaxData.php?type=0',

success: function(result) {

data = JSON.parse(result);

curDate = convertDatetime(data.date);

$('#monitor-temperature-value').html(data.temperature + '°C');

$('#monitor-light_intensity-value').html(data.light_intensity + '%');

$('#monitor-humidity-value').html(data.humidity + '%');

$('#monitor-pressure-value').html(data.pressure + 'kPa');

$('#dataInterval').html(data.dataInterval);

$('#dataRows').html(data.dataRows);

$('#updateTime').html(convertDatetimeShort(data.date));

if (oldHumidity !== 0 && oldTemperature !== 0 && oldLightIntensity !== 0 && oldLightIntensity !== 0) {

setIndicator(oldTemperature, data.temperature, "temperature");

setIndicator(oldLightIntensity, data.light_intensity, "light_intensity");

setIndicator(oldHumidity, data.humidity, "humidity");

setIndicator(oldPressure, data.pressure, "pressure");

}

dpsTemp.push({

x: curDate,

y: data.temperature

});

dpsLight.push({

x: curDate,

y: data.light_intensity

});

dpsHumidity.push({

x: curDate,

y: data.humidity

});

dpsPressure.push({

x: curDate,

y: data.pressure

});

if (dpsTemp.length > dataLength) {

dpsTemp.shift();

}

if (dpsLight.length > dataLength) {

dpsLight.shift();

}

if (dpsHumidity.length > dataLength) {

dpsHumidity.shift();

}

if (dpsPressure.length > dataLength) {

dpsPressure.shift();

}

chartTemp.render();

chartLight.render();

chartHumidity.render();

chartPressure.render();

oldTemperature = data.temperature;

oldLightIntensity = data.light_intensity;

oldHumidity = data.humidity;

oldPressure = data.pressure;

},

complete: function() {

// Schedule the next request when the current one's complete

setTimeout(worker, 5000);

}

});

})();

}

</script>

<?php

include 'templates/footer.php';

?>

maincore.php

<?php

$dbhost='localhost';

$dbuser='root';

$dbpass='';

$dbname='licenta';

define("URL", "http://".$_SERVER['SERVER_NAME']);

$db = new mysqli($dbhost, $dbuser, $dbpass, $dbname);

if (mysqli_connect_errno()) {

die('Conectarea la baza de date a esuat!');

}

//Global settings

$result = $db->query("SELECT * from settings where id=1");

$settings = $result->fetch_object();

settings.php

<?php

require_once('maincore.php');

$page = "settings";

include 'templates/header.php';

echo '<div style="margin-top:50px;"> </div>';

if(isset($_POST['includeZero'])) {

if ($result = $db->query("UPDATE settings SET includeZero='".$_POST['includeZero']."', period='".$_POST['period']."', save_on_change='".$_POST['save_on_change']."' WHERE id=1")) {

echo '<div class="alert alert-success" role="alert">Modificările au fost salvate !</div>';

}

$result = $db->query("SELECT * from settings where id=1");

$settings = $result->fetch_object();

}

if(isset($_POST['delete'])) {

if ($result = $db->query("DELETE FROM data WHERE date < DATE_SUB(NOW(), INTERVAL ".$_POST['delete']." MONTH);")) {

echo '<div class="alert alert-danger" role="alert">Datele au fost șterse!</div>';

}

}

echo '<h3>Setări</h3>

<div class="row" >

<div class="col-md-6">

<form action="" method="post">

<div class="form-group">

<label for="includeZero">Include punctul 0 în diagrame</label>

<select class="form-control" id="includeZero" name="includeZero">

<option'.($settings->includeZero ? '' : ' selected="selected"').' value="0">Nu</option>

<option'.(!$settings->includeZero ? '' : ' selected="selected"').' value="1">Da</option>

</select>

</div>

<div class="form-group">

<label for="period">Perioada de achiziție</label>

<select class="form-control" id="period" name="period">

<option'.($settings->period == 2 ? ' selected="selected"' : '').' value="5">5 secunde</option>

<option'.($settings->period == 10 ? ' selected="selected"' : '').' value="10">10 secunde</option>

<option'.($settings->period == 300 ? ' selected="selected"' : '').' value="300">5 minute</option>

<option'.($settings->period == 600 ? ' selected="selected"' : '').' value="600">10 minute</option>

<option'.($settings->period == 1800 ? ' selected="selected"' : '').' value="1800">30 minute</option>

<option'.($settings->period == 3600 ? ' selected="selected"' : '').' value="3600">60 minute</option>

</select>

<small id="periodHelp" class="form-text text-muted">Intevalul de timp la care dispozitivul trimite date către serverul web</small>

</div>

<div class="form-group">

<label for="save_on_change">Înregistrează doar date diferite</label>

<select class="form-control" id="save_on_change" name="save_on_change">

<option'.($settings->save_on_change ? '' : ' selected="selected"').' value="0">Nu</option>

<option'.(!$settings->save_on_change ? '' : ' selected="selected"').' value="1">Da</option>

</select>

<small id="periodHelp" class="form-text text-muted">Acest parametru permite înregistrarea datelor de la senzori doar dacă s-a depistat o diferență față de valoarea anterioară</small>

</div>

<button type="submit" class="btn btn-primary">Salvează</button>

</form>

</div>

<div class="col-md-6">

<form action="" method="post">

<div class="form-group">

<label for="delete">Sterge datele mai vechi de</label>

<select class="form-control" id="delete" name="delete">

<option value="1">1 lună</option>

<option value="2">2 luni</option>

<option value="3">3 luni</option>

<option value="4">4 luni</option>

<option value="5">5 luni</option>

<option value="6">6 luni</option>

<option value="7">7 luni</option>

<option value="8">8 luni</option>

<option value="9">9 luni</option>

<option value="10">10 luni</option>

<option value="11">11 luni</option>

<option value="12">12 luni</option>

</select>

</div>

<button type="submit" class="btn btn-danger">Șterge</button>

</form>

</div>

</div>';

include 'templates/footer.php';

?>

Similar Posts