Kaip jau rašiau ankščiau, prie savo RaspberryPi esu prisijungęs porą termometrų/higrometrų, kurių duomenis kas tam tikrą laiką saugau MySQL duomenų bazėje. Kaip man ir nepatiktų rašyti SQL užklausas, vis tiek norėtųsi namų „meteo stotelės” duomenis matyti gražiu, suprantamu, grafiniu formatu. Taip pat būtų visai smagu turėti galimybė pasiekti tuos duomenis iš bet kurios pasaulio vietos internetu. Žodžiu, prisižiūrėjęs pavyzdžių internete, nusprendžiau surinktus duomenis atvaizduoti internetinėje naršyklėje grafiku, skalėmis ir tekstu.
Visa duomenų atvaizdavimo sistema susideda iš vidinės (angl. back-end) ir išorinės (angl. front-end) dalių. Vidinė dalis – tai MySQL duomenų bazė ir spec. PHP skriptas, pagal tam tikrus kriterijus iš DB ištraukiantis duomenis ir gražinantis juos jSON formatu. Išorinė dalis – web puslapiai, vienokiu ar kitokiu būdu atvaizduojantys back-end’o sugeneruotus duomenis.
Back End
Kadangi iki šiol realios web programavimo patirties kaip ir neturėjau, nusprendžiau viską daryti kuo paprasčiau, t. y. neišradinėti dviračio ir naudoti tik gerai internete aprašytus sprendimus. Užbėgdamas už akių prasitarsiu, kad grafiniam duomenų atvaizdavimui pasirinkau HighCharts grafikus. HighCharts labai gerai draugauja su jSON, todėl būtent šis pasirinkimas ir lėmė back-end gražinamų duomenų struktūrą.
Po ilgo knibinėjimosi, gavosi štai toks data.php skriptas:
<?php $servername = "localhost"; $username = "USERNAME"; $password = "PASSWORD"; $dbname = "warehouse"; $con = mysql_connect($servername,$username,$password); if (!$con) { die('Could not connect: ' . mysql_error()); } mysql_select_db($dbname, $con); if (isset($_GET["getLast"])) { $sth = mysql_query("select UNIX_TIMESTAMP(date_format(d.d_actualdate, '%Y-%m-%d %H:%i'))*1000 as d_actualdate ,d.D_Temperature_1 d_temperature_1 ,d.D_Humidity_1 d_humidity_1 ,d.D_Temperature_2 d_temperature_2 ,d.D_Humidity_2 d_humidity_2 ,d.D_Temperature_3 d_temperature_3 from warehouse.duomenys d where d.D_ActualDate >= DATE_SUB(NOW(), INTERVAL 1 HOUR) order by 1 desc limit 1"); } else { if (isset($_GET["dateMin"],$_GET["dateMax"])) { $sth = mysql_query("SELECT UNIX_TIMESTAMP(date_format(date_add(d.d_actualdate, INTERVAL 2 HOUR), '%Y-%m-%d %H:%i'))*1000 as d_actualdate ,round(d.d_temperature_1,1) as d_temperature_1 ,round(d.d_temperature_2,1) as d_temperature_2 ,round(d.d_humidity_1,1) as d_humidity_1 ,round(d.d_humidity_2,1) as d_humidity_2 ,round(d.d_temperature_3,1) as d_temperature_3 from warehouse.duomenys d where d.d_actualdate >= '".$_GET["dateMin"]."' and d.d_actualdate <= '".$_GET["dateMax"]."' order by d.d_actualdate"); } else { $sth = mysql_query("SELECT UNIX_TIMESTAMP(date_format(date_add(d.d_actualdate, INTERVAL 2 HOUR), '%Y-%m-%d %H:00'))*1000 as d_actualdate ,round(avg(d.d_temperature_1),1) as d_temperature_1 ,round(avg(d.d_temperature_2),1) as d_temperature_2 ,round(avg(d.d_humidity_1),1) as d_humidity_1 ,round(avg(d.d_humidity_2),1) as d_humidity_2 ,round(avg(d.d_temperature_3),1) as d_temperature_3 from warehouse.duomenys d where d.d_actualdate >= DATE_SUB(date(NOW()), INTERVAL 1 WEEK) and d.d_actualdate < DATE_SUB(date(NOW()), INTERVAL 1 DAY) group by date_format(d.d_actualdate, '%Y-%m-%d %H') UNION SELECT UNIX_TIMESTAMP(date_format(date_add(d.d_actualdate, INTERVAL 2 HOUR), '%Y-%m-%d %H:%i'))*1000 as d_actualdate ,round(d.d_temperature_1,1) as d_temperature_1 ,round(d.d_temperature_2,1) as d_temperature_2 ,round(d.d_humidity_1,1) as d_humidity_1 ,round(d.d_humidity_2,1) as d_humidity_2 ,round(d.d_temperature_3,1) as d_temperature_3 from warehouse.duomenys d where d.d_actualdate >= DATE_SUB(date(NOW()), INTERVAL 1 DAY) order by 1"); } } $return_arr = array(); while($r = mysql_fetch_array($sth)) { $rows['at'] = $r['d_actualdate']; $rows['T1'] = $r['d_temperature_1']; $rows['T2'] = $r['d_temperature_2']; $rows['H1'] = $r['d_humidity_1']; $rows['H2'] = $r['d_humidity_2']; $rows['T3'] = $r['d_temperature_3']; array_push($return_arr,$rows); } $data = array('datapoints' => $return_arr); print json_encode($data, JSON_NUMERIC_CHECK); mysql_close($con); ?>
Į skriptą galima kreiptis trimis skirtingais būdais (paryškinimai kode):
- data.php?getLast – gražina paskutinės duomenų surinkimo iteracijos reikšmes.
- data.php?dateMin=2015-03-03&dateMax=2015-03-04 – gražina visus nurodytos datos intervalo duomenis.
- data.php – gražina paskutinių 7 dienų duomenis. Čia, tvarkingam duomenų atvaizdavimui, yra realizuotas šioks toks duomenų agregavimas. T. y. pirmų 6 dienų duomenys yra pateikiami valandos vidurkiais, o paskutinės dienos – visi duomenys (neagreguoti).
Visais atvejais, duomenys yra gražinami jSON’u, kurios struktūros pavyzdys yra pateiktas dešinėje. Duomenys yra gražinami „datapoints” masyve. Kiekviename iš masyvo elementų yra pateikiama duomenų data „at” JavaScript TimeStamp formatu ir konkrečios iteracijos jutiklių duomenys.
Duomenų surinkimo laiką specialiai gražinu js timestamp formatu, nes tik tokį laiko ir datos formatą supranta HighCharts grafikas, kitais atvejais (datą gražinant perskaitomu formatu, pav. 2015-03-02 15:32), HighCharts tai interpretuoja kaip kategorijos tekstą ir dalis šaunių HighCharts funkcijų tiesiog nebeveikia.
Front End
Grafikas
Kaip jau minėjau ankščiau, grafikui naudoju HighCharts. Ties HighCharts apsistojau dėl labai geros dokumentacijos, galybės pavyzdžių ir paprasto, interaktyvaus valdymo.
Grafiko kodas gavosi labai ilgas iš principo dėl to, kad pagal savo poreikius keičiau daug standartinių HighCharts nustatymų ir papildomai priprogramavau keletą funkcijų: suprogramavau grafike atvaizduojamų duomenų laiko rėžio nustatymą per jQuaryUI (by default grafikas užsikrauna paskutinių 7 dienų duomenis), pridėjau šviesaus paros laiko (saulėtekis – saulėlydis) atvaizdavimą, kurį radau čia, ir nustačiau automatini grafiko duomenų atnaujinimą.
Dalį grafiko kodo galite rasti mano JSFiddle, o visą grafiko kodą rasite atsisiuntimuose.
Skalės ir Tekstas
Nors grafikas ir yra labai patogus įrankis, tačiau juo gan sunku naudotis mobiliajame telefone. Iš principo, po ilgesnio naudojimosi, supratau, kad apskritai telefone mane labiau domina išvysti paskutinius, aktualius duomenis, o ne visos savaitės grafiką. Taip gimė dar pora puslapiukų, kuriuose atvaizduojama tik paskutinė informacija.
Pirmiausiai, pagaminau puslapiuką (gauges.php), kuriame DHT22 duomenys atvaizduojami skalėmis. Skalės sugeneruotos su HighCharts, duomenys į skales yra perduodami jSON’u, panaudojant data.php?getLast metodą.
getGaugeConfig = function(renderID, title, data_temp, data_hum){ var config = {}; //.....// config.title= { text: title }; //.....// return config; };
Šioje vietoje, manau, įdomiausia yra tai, kad tas pats skalių configas yra panaudotas dviem skirtingom skalėms atvaizduoti. Tai atlikau, sukūręs funkciją getGaugeConfig, per kurios argumentus į skalių configą perduodu reikiamus parametrus. Viršuje, pateiktas tik mažulytis funkcijos fragmentas, visą kodą rasite atsisiuntimuose.
Po ilgesnio naudojimosi „skalių puslapiu”, pastebėjau, kad labiau žiūriu ne į rodykles, bet į skaičius po jomis. Taip kilo idėja padaryti mažą paprastą puslapį, kuriame aktualūs duomenys būtų išvedami tiesiog tekstu (nuotrauka viršuje, dešinėje). Ant greitųjų „sumečiau” now.php, kurio išeities kodą rasite atsisiuntimuose.
Puslapis gavosi idiotiškai paprastas, bet žavus. Su „viewport” nustatymais ir šiek tiek bootstrap jį pritaikiau savo telefono ekranui. Duomenis puslapiui paruošiu tuo pačiu data.php?getLast, o iškoduotą jSON atvaizduoju su paprasčiausiu innerHTML. Tiesa, duomenų surinkimo laiką puslapyje formatavau su toLocaleFormat. Sunku patikėti, bet šios patogios funkcijos Chrome nepalaiko :/ Gerai, kad radau internete, kaip alternatyvią funkciją realizuoti pačiam:
Date.prototype.FormatDate = function(format) { var f = {y : this.getYear() + 1900,m : this.getMonth() + 1,d : this.getDate(),H : this.getHours(),M : this.getMinutes(),S : this.getSeconds()} for(k in f) format = format.replace('%' + k, f[k] < 10 ? "0" + f[k] : f[k]); return format; };
Atsisiuntimai
Visus aukščiau aprašytus skriptus rasite čia: dht22_duomenu_atvaizdavimas.zip
Naujausią kodo versiją rasite mano GitHub saugykloje!
Naudinga literatūra:
- http://blueflame-software.com/blog/using-highcharts-with-php-and-mysql
- http://blueflame-software.com/blog/how-to-create-dynamic-x-axis-data-using-json
- http://blueflame-software.com/blog/how-to-load-mysql-results-to-highcharts-using-json
- http://www.kliptu.com/free-script/dynamic-highchart-example-with-jquery-php-json
- http://www.boock.ch/meteo/tuto_mysql_highcharts/tuto_mysql_highcharts_04.php
- http://www.d3noob.org/2013/02/using-mysql-database-as-source-of-data.html
- http://stackoverflow.com/questions/11851122/blank-page-highchart-in-using-jquery-to-call-json-arrary/11852873#11852873
- http://stackoverflow.com/questions/13228081/highcharts-not-displaying-series-pushed-from-json
- http://williams.best.vwh.net/sunrise_sunset_algorithm.htm
- http://blog.tafkas.net/2012/11/26/charting-sunrise-and-sunset-in-highcharts
- http://jsfiddle.net/jlbriggs/FnhRV
- http://www.java2s.com/Tutorials/Javascript/jQuery_UI/DatePicker/Build_jQuery_UI_Datepicker_Select_a_Date_Range_in_JavaScript.htm
- http://javascript.ru/Date/toLocaleFormat#comment-16083