Avietė: DHT22 -> MySQL

IMG_7148

Viena pagrindinių priežasčių, kodėl aš iš viso pirkau RaspberryPi, buvo noras pagaliau prisiversti susidraugauti su linuxu ir kažkiek pramokti python, php, mysql, java, js, ir visokius kitokius šiandien aktualius velnius. Programuoti vien dėl programavimo yra tuščias laiko gaišimas, todėl iš karto reikia daryti kažką naudingo, o ne rašinėti visokius „hello world“. Vos tik nusipirkęs RPI, nusprendžiau pasidaryti kelių zonų termometrą su istorinių duomenų atvaizdavimo galimybe. Tam, kad spyris į užpakalį kažką daryti būtų kuo stipresnis, nusprendžiau viską programuoti pats. Sukurpiau tokį pradinį planą: Cron jobas kas 5 min. paleidžiai Python scriptą, kuris kelis kartus nuskaito DHT22 jutiklio duomenis, juos suvidurkina ir surašo į MySQL DB.


dht22
dht22 temperature and humidty sensor circuitry wiring

Kaip jau supratot, pasirinkau DHT22 oro temperatūros ir drėgmės jutiklius. Juos pasirinkau dėl pakankamai mažos kainos (~4$ perkant iš AliExpress), paprasto prijungimo (1-wire) ir, žinoma, dėl to, kad jau yra sukurta DHT22 duomenų nuskaitymo RPI biblioteka. Prie RPI DHT22 jutiklius prijungiau pagal viršuje dešinėje pateiktą schemą. Projekte naudoju du DHT22 jutiklius,  todėl dėl naudojamos DHT22 duomenų nuskaitymo bibliotekos apribojimų, jutiklius teko prijungti prie atskirtų RPI portų. Vieną iš jutiklių prijungiau prie GPIO19, kitą prie GPIO19. Iš principo, kur juos prijungti, skirtumo nėra. Man šie portai tiesiog buvo fiziškai patogiausi.

Atėjo laikas susidiegti DHT22 nuskaitymo biblioteką. Tam konsolėje įvykdome šią komandą:

git clone git://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code.git

Po to, jau kaip ir galime nuskaityti duomenis:

cd /home/Adafruit-Raspberry-Pi-Python-Code/Adafruit_DHT_Driver
sudo ./Adafruit_DHT 22 19

Adafruit_DHT driveris priima du nustatymus: jutiklio versiją (DHT11 ar DHT22) ir porto numerį, prie kurio jutiklis yra prijungtas. Kadangi bandau nuskaityti duomenis iš DHT22 jutiklio, prijungto prie GPIO19 posto, Adafruit_DHT driveriui padaviau šiuos nustatymus: „22 19„. Rezultatas štai toks:

dht22_s

DHT22 yra gan užsispyręs jutiklis, todėl Adafruit_DHT biblioteka ne visuomet sugeba iš jo nuskaityti duomenis. Jei biblioteka negrąžina temperatūros ir santykinės oro drėgmės reikšmių, į jutiklį reikia kreiptis dar bartą. Liuda, bet bent kartą iš penkių jutiklis tikrai negražins mus dominančių duomenų:

dht22_f

Benaršydamas internete, radau, kad Adrafuit_DHT driveris potencialiai gali „pakabinti“ visą sistemą. Potenciali bėda slypi šiame nekaltame driverio kodo gabaliukyje, kuris gali netyčia pereiti į amžiną ciklą:

while ( bcm2835_gpio_lev(pin) == 1 ) {
 usleep(1);
}

Protingi žmonės siūlo šią potencialią klaidą taisyti taip:

// wait for pin to drop?
int safety = 0;
int max_wait = 1000;
while ( bcm2835_gpio_lev(pin) == 1 ) {
  // if no answer, quit
  if ( safety > max_wait ) {
  return 2;}
usleep(1);
safety++;
}

Pasiklioviau patarimu ir perkompiliavau Adafruit_DHT driverį. Tiesa, teko kiek pavargti, nes norint perkompiliuoti Adafruit_DHT.c, reikėjo pirma atsisiųsti BCM2835 biblioteką, ją susikompiliuoti ir susidiegti. Taip padariau taip (pagal šį pavyzdį):

cd
mkdir -p work/bcm2835
cd work/bcm2835
wget http://www.open.com.au/mikem/bcm2835/bcm2835-1.36.tar.gz
tar xvfz bcm2835-1.36.tar.gz
cd ./bcm2835-1.36
./configure
make
sudo make install

Patį Adafruit_DHT.c perkompiliavau (žinoma, prieš tai atnaujinęs C kodo gabaliuką) su šia komanda:

sudo gcc Adafruit_DHT.c -l bcm2835 -std=gnu99 -o Adafruit_DHT

Na ką, atnaujintas driveris kaip ir veikia. Reikia pasiruošti MySQL duomenų bazę, į kurią rašysim duomenis. Susikūriau MySQL DB pavadinimu „warehouse“, kurioje sukūriau tokią duomenų lentelę:

CREATE TABLE IF NOT EXISTS `duomenys` (
`D_ID` mediumint(9) NOT NULL AUTO_INCREMENT,
`D_DATE_CREATED` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`D_ActualDate` datetime DEFAULT NULL,
`D_Temperature_1` float DEFAULT NULL,
`D_Humidity_1` float DEFAULT NULL,
`D_Temperature_2` float DEFAULT NULL,
`D_Humidity_2` float DEFAULT NULL,
`D_Info` varchar(255) COLLATE utf16_lithuanian_ci DEFAULT NULL,
`D_Comments` varchar(255) COLLATE utf16_lithuanian_ci DEFAULT NULL,
PRIMARY KEY (`D_ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf16 COLLATE=utf16_lithuanian_ci AUTO_INCREMENT=48 ;

Pagal kelis internetiniu pavyzdžius sukurpiau šiokį tokį Python skriptą, kuris nuskaito abiejų DHT22 jutiklių duomenis, juos suvidurkina ir kartu su laiko žyme, surašo į prieš tai sukurtą mysql lentelę:


#!/usr/bin/python
# -*- coding: utf-8 -*-

import MySQLdb as mdb
import sys
import arrow
import re
import subprocess
import os

import RPi.GPIO as GPIO

GPIO.setmode(GPIO.BCM) ## uses GPIO numbers NOT pin numbers
GPIO.setwarnings( False )
GPIO.setup(26, GPIO.OUT) ## 37 PIN / GPIO26 

os.chdir(os.path.abspath('/home/python/'))

#globals for temperature and humidity averaging
temp = 0
hum = 0

def read_dht22 ( PiPin ):
    success = 0
    output = subprocess.check_output(["./Adafruit_DHT", "2302", str(PiPin)])
    matches = re.search("Temp =\s+([-+]?[0-9.]+)", output)
    if (matches):
        global temp
        temp = matches.group(1)
        success = 1
        #print temp
    else:
        success = 0
        #print "fail"
    matches = re.search("Hum =\s+([0-9.]+)", output)
    if (matches):
        global hum
        hum = matches.group(1)
        #print hum
    else:
        success = 0
        #print "fail"
    return success

try:
    con = mdb.connect('localhost', 'USERNAME', 'PASSWORD', 'warehouse');

    cur = con.cursor()
    cur.execute("SELECT VERSION()")

    ver = cur.fetchone()

    sys_date = arrow.now().format('YYYY-MM-DD HH:mm:ss')

    
    print "Database version : %s " % ver
    print sys_date

    GPIO.output(26,True) #Turn LED ON, while reading DHT2 sensors
    
    count_1 = 0
    temperature_1 = 0
    humidity_1 = 0
    for x in range (0, 10):
        if (read_dht22(19)):
            count_1 = count_1 + 1
            temperature_1 = temperature_1 + float(temp)
            humidity_1 = humidity_1 + float(hum)
            #print "[19] Temp: %s Hum: %s Count: %s" %(temp,hum,count_1)
        else: count_1 = count_1 #print "echem.."

    if (count_1>0):
        temperature_1 = round(temperature_1/count_1,1)
        humidity_1=round(humidity_1/count_1,1)
    else: count_1=count_1
            
    print "[19] Temperature: %s Humidity: %s" %(temperature_1,humidity_1)    

    count_2 = 0
    temperature_2 = 0
    humidity_2 = 0
    for x in range (0, 10):
        if (read_dht22(13)):
            count_2 = count_2 + 1
            temperature_2 = temperature_2 + float(temp)
            humidity_2 = humidity_2 + float(hum)
            #print "[1] Temp: %s Hum: %s Count: %s" %(temp,hum,count_2)
        else: count_2 = count_2 #print "echem.."

    if (count_2>0):
        temperature_2 = round(temperature_2/count_2,1)
        humidity_2=round(humidity_2/count_2,1)
    else: count_2=count_2
            
    print "[13] Temperature: %s Humidity: %s" %(temperature_2,humidity_2)         

    if (count_1 > 0 and count_2 > 0):
            cur.execute("""INSERT INTO duomenys (D_ActualDate, D_Temperature_1, D_Humidity_1 ,D_Temperature_2, D_Humidity_2, D_Info)
            VALUES ( %s, %s,%s,%s,%s, 'CronJob')""",
            [sys_date,temperature_1,humidity_1,temperature_2,humidity_2])

    elif (count_1 == 0 and count_2 > 0):
            cur.execute("""INSERT INTO duomenys (D_ActualDate, D_Temperature_1, D_Humidity_1 ,D_Temperature_2, D_Humidity_2, D_Info)
            VALUES ( %s, NULL,NULL,%s,%s, 'CronJob')""",
            [sys_date,temperature_2,humidity_2])

    elif (count_1 > 0 and count_2 == 0):
            cur.execute("""INSERT INTO duomenys (D_ActualDate, D_Temperature_1, D_Humidity_1 ,D_Temperature_2, D_Humidity_2, D_Info)
            VALUES ( %s,%s,%s,NULL,NULL, 'CronJob')""",
            [sys_date,temperature_1,humidity_1])

    else :
            cur.execute("""INSERT INTO duomenys (D_ActualDate, D_Temperature_1, D_Humidity_1 ,D_Temperature_2, D_Humidity_2, D_Info)
            VALUES ( %s, NULL,NULL,NULL,NULL, 'CronJob')""",
            [sys_date])

                
    con.commit()

    GPIO.output(26,False)
    
except mdb.Error, e:
    if con:
        con.rollback()
        
    print "Error %d: %s" % (e.args[0],e.args[1])
    sys.exit(1)
    
finally:    
        
    if con:    
        con.close()

    GPIO.cleanup()
    

Tam, kad skriptas veiktų, reikia susidiegti MySQLdb modulį:

sudo apt-get install python-mysqldb

Skripte panaudojau ir Arrow laiko apskaitos biblioteką, kurią taip pat reikia susidiegti:

sudo apt-get install python-setuptools
sudo easy_install pip
sudo pip install arrow

Beje, nepamirškit skripte nurodyti savo mysql naudotojo vardo ir slaptažodžio 😉

Patogumo dėlei, paruoštą skirptą (jį pavadinau mysql_dht.py) ir perkompiliuotą Adafruit_DHT driverį nukopijavau į /home/python direktoriją. Jei naudojate kitą direktoriją, ją nurodykite skirpto eilutėje „os.chdir(os.path.abspath(‘/home/python/‘))“, kitaip skriptas neveiks.

Ar skriptas veikia, galima pasitikrinti tiesiai iš konsolės:

sudo python /home/python/mysql_dht.py

Turėtumėte gauti tokį rezultatą:

dht22_mysql

Jeigu gaunate tokią klaidą, įsitikinkite ar Adafruit_DHT driveris yra tikrai toje pačioje direktorijoje, kaip ir pats mysql_dht.py skirptas:

dht22_mysql_error

Jei skriptas veikia gerai, galima užkurti automatinį jo vykdymą. Tam panaudojau Cron:

sudo crontab -e

Atsidariusiame lange, pačioje apačioje įrašiau tokią eilutę:

0,5,10,15,20,25,30,35,40,45,50,55  * * * * sudo python /home/python/mysql_dht.py &

Išsaugojus failą, mūsų skriptas bus įvykdomas kas 5 minutes. Simbolis „&“ eilutės gale reiškia, kad procesas bus vykdomas fone ir netrukdys sistemos darbui.

mysql

Ar Cron jobas veikia, gerai parodo mysql duomenys. Jei duomenys tvarkingai atsigula į nurodytą lentelę, galima eiti gerti kavos 😀

Tinginiams:

Perkompiliuotą Adafruit_DHT driverį ir mano parašyta mysql_dht.py skriptą galite atsisiųsti iš čia: dht22_mysql.zip

Naujausią kodo versiją rasite mano GitHub saugykloje!

 

[update: 2014-12-04]
Patobulintas duomenų surinkimo kodas: dht22_mysql_2.zip

[update: 2015-01-20]
mysql_dht.py skripte buvo klaida, dėl kurios skirtpas nesugebėdavos „surinkti“ neigiamų oro temperatūrų. Klaida buvo šioje kodo eilutėje:

matches = re.search("Temp =\s+([0-9.]+)", output)

Klaidą ištaisiau papildęs re.search išraišką minuso simbolio paieška „[-+]?“:

matches = re.search("Temp =\s+([-+]?[0-9.]+)", output)

 

Naudinga literatūra:

Bonus: 

Norintiems, bet negalintiems prisijungti prie MySQL DB, kuri sukasi jūsų RPI, siūlau pasiskaityti čia: http://stackoverflow.com/questions/18733802/how-do-i-open-up-my-mysql-on-my-raspberry-pi-for-outside-remote-connections Man padėjo! 😉

17 komentarų

  1. Atgalinis pranešimas: Python: Statistinis klaidingų duomenų taškų eliminavimas | Paulius Bautrėnas

  2. kailis

    Sveikas Pauliau, kaip matau visai neblogai padirbeta, labai issamus tutorialas, didelis dekui Tau. Noriu truputi papildyti, jei turite savo pusltapi, tai galima patalpinti si koda, kad rodytu temperatura uzkrovus puslapi. Aisku, butu galima ir papildyti autorefreshu, bet kadangi nesu rimtas programuotojas, labiau man cia hobis po darbu, gal but kada ir imesiu toki koduka. P.s. skripta bandziau isdestyti pagal tavo Pauliau pateikta pavyzdine duomenu baze ‘warehouse’ ir lentele ‘duomenys’

    <?php
    error_reporting('E_ALL ^ E_NOTICE');
    mysql_connect('localhost','username','password') or die(mysql_error());
    mysql_select_db('warehouse') or die(mysql_error());
    $page=$_REQUEST['p'];
    $limit=1;
    if($page=='')
    {
    $page=1;
    $start=0;
    }
    else
    {
    $start=$limit*($page-1);
    }
    $query=mysql_query("select * from duomenys order by D_id desc limit 1;") or die(mysql_error());
    $tot=mysql_query("select * from duomenys") or die(mysql_error());
    $total=mysql_num_rows($tot);
    $num_page=ceil($total/$limit);
    echo'Last measurementTempHumidity';
    while($res=mysql_fetch_array($query))
    {
    echo''.$res['D_ActualDate'].'   '.$res['D_Temperature_1'].'°C    '.$res['D_Humidity_1'].'%';

    }
    echo'';

    ?>

    Atsakyti
  3. pauliusbau Įrašo autorius(-ė)

    Esu jau panašiai pasidaręs, tik daug papraščiau.. Nelabai supratau, kam šita vieta:
    $tot=mysql_query("select * from duomenys") or die(mysql_error());
    $total=mysql_num_rows($tot);

    Kam reikia traukti visus duomenis iš DB, norint suskaičiuoti, kiek yra duomenų eulučių? Manau, kad geriau būtų parašyti query, pav. select count(d_id) from warehouse.duomenys ir iškart gauti eilučių skaičių..

    Mano trumpat drūtas skriptas atrodo taip:
    < ?php $servername = "localhost"; $username = "USERNAME"; $password = "PASSWORD"; $dbname = "warehouse"; // Create connection $conn = new mysqli($servername, $username, $password, $dbname); // Check connection if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
    }

    $sql = "SELECT d_actualdate, d_temperature_1, d_humidity_1, d_temperature_2, d_humidity_2 from warehouse.duomenys d order by d.d_id desc limit 1";
    $result = $conn->query($sql);

    if ($result->num_rows > 0) {
    // output data of each row

    while($row = $result->fetch_assoc()) {

    echo "
    ". $row["d_actualdate"]. " - T1: ". $row["d_temperature_1"]. " H1: " . $row["d_humidity_1"]. " - T2: ". $row["d_temperature_2"]. " H2: " . $row["d_humidity_2"]."
    ";
    }
    } else {
    echo "0 results";
    }

    $conn->close();
    ?>

    Atsakyti
  4. kailis

    mano mintis buvo narsykleje duomenis patalpinti i lentele, kuri rodo daugiau nei vienos „linijos“ istraukima, beje RPi naudoju labiau relems kontroliuoti. Sensoriai tik papildomas pasizaidimas. Tikrai nesigincyju del kodo, jei yra daug paprasteniu budu tam atlikti tai tada liuks :), patirties pasidalijimas visada geras dalykas tobulejimui. Klausimas gal naudoji kazka panausaus i dinamini grafika temperaturai palyginti? kaip tik dabar delioju laisvu laiku. Jei pavyks sekmingai – pasidalinsiu…

    Atsakyti
    1. pauliusbau Įrašo autorius(-ė)

      Iš principo DHT22 ir jungiau tik su mintimi jų duomenis išsipiešti grafike. Šiaip ne taip surinkau tokį komplikuotą grafiką su „HighCharts“:
      HighCharts

      Ketinu apie tai artimiausiu metu parašyti postą, bet tikrai būtų pamatyti ir kitas alternatyvas 😉

      Atsakyti
  5. kailis

    tau super gavosi :), siaip su tai grafikais tikras galvos skausmas… as bandau su amcharts.com pavyzdziais dirbti, beja sunkiai sekasi 🙂 na nieko dar turiu kantrybes…

    Atsakyti
  6. pauliusbau Įrašo autorius(-ė)

    Aš tą grafiką prabėgomis gerą mėnesį kūriau ir vis dar tobulinu. Sėkmės su amcharts, nes jie tikrai labai gražiai atrodo. Bus įdomu pamatyti tavo rezultatą.

    Atsakyti
  7. kailis

    kaip ir turiu jau padares grafika pagal amchart pavydzi, dar neisbaigtas aisku, nzn kaip i tavo komentaru forma ikelti 🙂 prisegtifile:///home/audrius/Documents/grafikas.jpg

    Atsakyti
  8. pauliusbau Įrašo autorius(-ė)

    Gaila, bet taip paprastai paveikslėlio čia neįkelsi.. Pirma įkelk paveikslėlį į kokį http://imgur.com ar pan. ir tik tada į komentarą įkelk paveikslėlio nuorodą su atributais „img“ 😉

    Atsakyti
  9. kailis

    Galvoju, kad su dviem skaneriais iseitu daug geresnis projektukas, graviravimo plotas butu lygus 29×29 cm A4 formato lapui, vietoj 4x4cm DVDburnerio… Didziausias issukis tinkamai sukonfiguruoti motor stepperius.

    Atsakyti
  10. Atgalinis pranešimas: Avietė: DHT22 duomenų atvaizdavimas | Paulius Bautrėnas

Parašykite komentarą

El. pašto adresas nebus skelbiamas.