Objavljeno:

Merjenje temperature z RaspberryPi

RaspberryPi lahko uporabimo tudi kot preprost senzorski sistem. Najbolj preprosto ga uporabimo za merilec temperature, ki nam omogoča, da si trenutno temperaturo ter zgodovino meritev ogledamo preko spletnega vmesnika.

V tokratnem prispevku si bomo pogledali kako s cenenim senzorjem DS18B20, ki ga na spletu kupimo že za manj kot evro (in sicer na kitajskih spletnih trgovinah, pri nas in v ZDA je okrog 15-krat dražji!), ter s pomočjo RaspberryPi in programskega jezika Python, izdelamo preprost spletni vmesnik ki prikazuje povprečno temperaturo v vsakem urnem intervalu.

Graf meritve temperature.

Graf meritve temperature.

Strojni del obsega dva temperaturna senzorja, za osnovo programskega dela pa smo vzeli programsko kodo objavljeno  na spletni strani Adafruit, ki pa smo jo nekoliko predelali.

RaspberryPi z dvema temperaturnima senzorjema.

RaspberryPi z dvema temperaturnima senzorjema.

Naša aplikacija podatke zajema iz več senzorjev, ki jih lahko tudi poimenujemo, podatke pa zapisuje v PostgreSQL bazo. Podatkov o temperaturi ne izpisuje zgolj na zaslon, pač pa iz njih zna narisati tudi graf, ki si ga lahko ogledamo preko spletnega vmesnika (tudi na mobilnem telefonu). Pa začnimo!

Strojni del

Senzorji DS18B20 imajo vgrajen tim. 1-wire serijski vmesnik, kar pomeni, da lahko več senzorjev priključimo na isti vhod, vsak posamezen senzor pa se nato računalniku identificira s svojo identifikacijsko številko.

Vezava senzorjev je preprosta. Vsak senzor ima namreč tri žice in če imamo več senzorjev žice iste barve najprej povežemo med seboj. Nato sledi priklop na RaspberryPi. Rdeča žice povežemo na 3.3V, modre na GND, rumene pa na podatkovni PIN. Privzeto je le-ta GPIO4 (PIN 07), lahko pa to programsko spremenimo.

Zelo pomembno pa je, da med rdečo in rumeno žičko vežemo upor, in sicer katerikoli med 4,7K Ohmi in 10K Ohmi. Za samo vezavo lahko uporabimo tim. protoboard, na katerega prispajkamo žice.

Vezava senzorjev.

Vezava senzorjev.

Zdaj v RaspberryPi vključimo podporo za OneWire protokol.

Odpremo nastavitveno datoteko:

sudo nano /boot/config.txt

Vanjo dodamo:

dtoverlay=w1-gpio

Privzeta nastavitev je, da je kot podatkovni PIN uporabljen GPIO4. To lahko spremenimo z ukazom:

dtoverlay=w1-gpio,gpiopin=4

Sledi ponovni zagon naprave:

sudo reboot

Po zagonu naložimo ustrezne jedrne module:

sudo modprobe w1-gpio
sudo modprobe w1-therm

Sedaj si že lahko ogledamo kje v sistemu se nahajajo senzorji:

cd /sys/bus/w1/devices
ls

Dobimo izpis podimenikov. Podimeniki, ki se začenjajo z “28-” so v bistvu naši senzorji.

Primer:

28-0416800b61ff 28-041680116aff

Sedaj vstopimo v posamezen podimenik in si ogledamo vsebino datoteke w1_slave:

cd 28-0416800b61ff
cat w1_slave

Dobimo izpis, iz katerega lahko preberemo trenutno temperaturo na senzorju:

73 01 4b 46 7f ff 0c 10 85 : crc=85 YES
73 01 4b 46 7f ff 0c 10 85 t=23187

Če se prva vrstica konča z YES, je na koncu druge vrstice temperatura. V našem primeru je to 23,184 °C. Da, senzorji vrnejo temperaturo na tri decimalke natančno, čeprav je njihova nazivna natančnost pol stopinje.

Zajem podatkov v bazo

Ker želimo podatke iz senzorjev zajemati v bazo, bomo najprej PostgreSQL, nato pa namestili Pythonovo knjižnico Psycopg2:

sudo apt-get install python-psycopg2

Nato v PostgreSQL bazi ustvarimo uporabnika senzor:

sudo su
su - postgres

CREATE USER senzor WITH PASSWORD 'senzor';
CREATE DATABASE senzor WITH OWNER senzor;
GRANT ALL PRIVILEGES ON DATABASE senzor to senzor;

Zdaj se iz uporabniškega računa privzetega uporabnika (pi) povežemo na bazo:

psql -d senzor -h 127.0.0.1 -U senzor -W

…in ustvarimo ustrezno tabelo:

create table temperatura (datumcas timestamptz, ime_senzorja text, temperatura decimal(16,3));

Python aplikacija za zajem podatkov v bazo

Za zajem podatkov iz temperaturnih senzorjev uporabimo spodnjo Python aplikacijo. Mimogrede, zelo pomembno je, da na napravi, ki bo zajemala podatke (RaspberryPi) nastavimo točen čas, po možnosti samodejno sinhroniziran s časovnimi strežniki!

Iz same aplikacije je dokaj jasno razvidno kaj počne. Preden jo zaženemo, moramo v seznam device_id vnesti vse ID-je senzorjev in naša imena zanje (npr. zunanji, notranji,…), pri povezavi na bazo moramo vnesti pravo ime baze, uporabniško ime in geslo, s parametrom time.sleep(30) pa nastavimo, da branje iz senzorjev poteka na 30 sekund – to lahko poljubno spreminjamo (pravzaprav branje poteka na približno 30 sekund, pozorni bralci boste opazili manjši časovni zamik opazili še v funkciji read_temp).

Python aplikacija temperatura.py, ki jo lahko prilagodimo svojim potrebam:

# coding: utf-8

import sys
import psycopg2
import os
import glob
import time
from time import gmtime, strftime

# Priklop: rdeca na 3.3V, modra na GND, rumena na GPIO4 (PIN 07).
# Vir: https://learn.adafruit.com/adafruits-raspberry-pi-lesson-11-ds18b20-temperature-sensing/software

os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')

# Vnesemo ID-je naprav in njihove oznake, ki jih določimo sami.
# Seznam priključenih naprav vidimo z ukazom: ls /sys/bus/w1/devices/. Začnejo se z "28-".
device_id = [
    ['28-041680116aff', 'rdeči'],
    ['28-0416800b61ff', 'rumeni']
]

base_dir = '/sys/bus/w1/devices/'

# Preberemo podatke iz datoteke /sys/bus/w1/devices/<ID_senzorja>/w1_slave
def read_temp_raw(device_name):
    f = open(os.path.join(base_dir, device_name, 'w1_slave'), 'r')
    lines = f.readlines()
    f.close()
    return lines

# Preberemo temperaturo.
def read_temp(device_name):
    lines = read_temp_raw(device_name)
    while lines[0].strip()[-3:] != 'YES':
        time.sleep(0.2)
        lines = read_temp_raw(device_name)
    equals_pos = lines[1].find('t=')
    if equals_pos != -1:
        temp_string = lines[1][equals_pos+2:]
        temp_c = float(temp_string) / 1000.0
    return temp_c

# Odpremo povezavo z bazo.
db_con = psycopg2.connect(database='senzor', host='127.0.0.1', user='senzor', password='senzor')
cursor = db_con.cursor()

# Nastavimo časovni pas.
query = "set timezone to 'utc';"
cursor.execute(query)
db_con.commit()

# V zanki beremo podatke o temperaturi iz vsakega senzorja.
while True:
    for device in device_id:
        datumcas = strftime("%Y-%m-%d %H:%M:%S", gmtime())
        ime_senzorja = device[1]
        temperatura = read_temp(device[0])
        # Prebrane podatke izpišemo na zaslon.
        print "Datum: %s, ime senzorja: %s, izmerjena temperatura: %s stopinj Celzija." % (datumcas, ime_senzorja, round(temperatura,1))
        # Prebrane podatke zapišemo v bazo. 
        query = "INSERT INTO temperatura (datumcas, ime_senzorja, temperatura) VALUES (%s, %s, %s);"
        data = (datumcas, ime_senzorja, temperatura)
        cursor.execute(query, data)
        db_con.commit()
    # Počakamo 30 sekund, nato sprožimo ponovno branje iz senzorjev.
    time.sleep(30)

# Zapremo povezavo na bazo.
if db_con: 
    db_con.close()

Če aplikacijo sedaj zaženemo, dobimo približno takle izpis:

python temperatura.py

Datum: 2017-03-01 15:28:50, ime senzorja: rdeči, izmerjena temperatura: 33.7 stopinj Celzija.
Datum: 2017-03-01 15:28:51, ime senzorja: rumeni, izmerjena temperatura: 36.4 stopinj Celzija.
...

Python aplikacija za zajem podatkov v bazo

Ker želimo, da se aplikacija temperatura.py zažene ob vsakem zagonu sistema samodejno, oziroma, da se, če iz kakršnegakoli razloga preneha delovati, ponovno samodejno zažene nazaj, namestimo aplikacijo Supervisor:

sudo apt-get install supervisor

Ustvarimo nastavitveno datoteko:

sudo nano /etc/supervisor/conf.d/temperatura.conf

Vanjo zapišemo:

[program:temperatura]
command = python /home/pi/temperatura.py
autostart=true
autorestart=true

Sedaj posodobimo Supervisor nastavitve:

sudo supervisorctl update

…in pogledamo status aplikacije:

sudo supervisorctl status
temperatura RUNNING pid 730, uptime 5 days, 1:03:48

Če sedaj aplikacijo temperatura.py ročno ubijemo:

sudo killall -9 python

…bi jo moral Supervisor ponovno sam zagnati. To preverimo v dnevniški datoteki:

cat /var/log/supervisor/supervisord.log
2017-02-13 15:42:44,301 INFO exited: temperatura (terminated by SIGKILL; not expected)
2017-02-13 15:42:45,315 INFO spawned: 'temperatura' with pid 5707
2017-02-13 15:42:46,332 INFO success: temperatura entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)

Sistem torej deluje. Podatki se zajemajo na 30 sekund in zapisujejo v bazo, zdaj je na vrsti analiza in risanje grafov.

Branje podatkov in risanje grafov

Za risanje grafov bomo prav tako uporabili Python, v njem pa knjižnico Bokeh. Najprej jo namestimo:

sudo pip install bokeh

Za risanje uporabimo aplikacijo, ki jo poimenujemo tempgraf.py. Pomembnejši parameter v aplikaciji je output_file(“graf.html”, title=’Meritev temperature’), s katerim nastavimo v katero HTML datoteko se izriše graf. Namesto v HTML lahko podatke izrisujemo tudi v SVG ali PNG datoteko.

Aplikacija:

# coding: utf-8

import sys
import psycopg2
import string
import datetime
from decimal import Decimal
import time
from time import strftime, localtime
import os
from bokeh.plotting import figure, show, output_file
from bokeh.models import PrintfTickFormatter
from bokeh.models import ColumnDataSource, LabelSet, Label
from math import pi

# Odpremo povezavo z bazo.
db_con = psycopg2.connect(database='senzor', host='10.10.5.54', user='senzor', password='senzor')
cursor = db_con.cursor()

# Nastavimo lokalni časovni pas.
query = "set timezone to 'Europe/Ljubljana';"
cursor.execute(query)
db_con.commit()

# Iz baze preberemo podatke o povprečni temperaturi v zadnjih 2 minutah.
query = "select avg(temperatura)::numeric(3,1) from temperatura where datumcas > (now() - INTERVAL '2 minutes');"
cursor.execute(query)
db_con.commit()
avg_temp = cursor.fetchall()

# Iz baze preberemo podatke o povprečni urni temperaturi na vseh senzorjih.
query = """
 select date_trunc('hour', datumcas) as interv_start, date_trunc('hour', datumcas) + interval '1 hours' as interv_end, avg(temperatura)
 from temperatura
 where (datumcas >= current_date and datumcas < (current_date + interval '1 day')) group by date_trunc('hour', datumcas)
 order by interv_start;"""
cursor.execute(query)
db_con.commit()
avg_hourly_temp = cursor.fetchall()

# Iz baze preberemo podatke o povprečni urni temperaturi na rdečem senzorju.
query = """
 select date_trunc('hour', datumcas) as interv_start, date_trunc('hour', datumcas) + interval '1 hours' as interv_end, avg(temperatura)
 from temperatura
 where (datumcas >= current_date and datumcas < (current_date + interval '1 day')) and (ime_senzorja = 'rdeči') group by date_trunc('hour', datumcas)
 order by interv_start;"""
cursor.execute(query)
db_con.commit()
avg_hourly_temp_red = cursor.fetchall()


# Iz baze preberemo podatke o povprečni urni temperaturi na rumenem senzorju.
query = """
 select date_trunc('hour', datumcas) as interv_start, date_trunc('hour', datumcas) + interval '1 hours' as interv_end, avg(temperatura)
 from temperatura
 where (datumcas >= current_date and datumcas < (current_date + interval '1 day')) and (ime_senzorja = 'rumeni') group by date_trunc('hour', datumcas)
 order by interv_start;"""
cursor.execute(query)
db_con.commit()
avg_hourly_temp_yellow = cursor.fetchall()

# Izpišemo podatke o temperaturi v ukazno vrstico.
print "PRIKAZ TEMPERATURE"

print "Trenutna temperatura je: %.1f" % (avg_temp[0])

print "Povprečna temperatura na vseh senzorjih po urah na današnji dan:"
for reading in avg_hourly_temp:
 print "Med %s in %s uro je bila povprečna temperatura %s stopinj C." % (reading[0].strftime("%H"), reading[1].strftime("%H"), round(reading[2],1))

# Pripravimo podatke za risanje grafa.
x = [row[0].hour for row in avg_hourly_temp]
y = [row[2] for row in avg_hourly_temp]
y_labels = [u'%s °C' % (round(row[2],1)) for row in avg_hourly_temp]

y_red = [row[2] for row in avg_hourly_temp_red]
y_yellow = [row[2] for row in avg_hourly_temp_yellow]

danasnji_datum = reading[0].strftime("%-d. %-m. %Y")

# Graf naj se shrani v HTML datoteko.
output_file("graf.html", title='Meritev temperature')

# Oznake x osi.
labels = ['od %dh do %dh' % (i, i+1) for i in x]

# Osnovni parametri grafa.
p = figure(plot_width=900, plot_height=400, tools="save", x_range=(labels), y_range=(round(min(y),0)-2,round(max(y),0)+5), x_axis_location='below')

# Ne izrisemo Bokeh logota.
p.toolbar.logo = None
# Če želimo, sploh ne izrišemo Bokeh orodne vrstice.
# p.toolbar_location = None

# Naslov grafa.
p.title.text_font_size = '16pt'
p.title.text_font = 'helvetica'
p.title.text = u'Temperatura na dan %s' % danasnji_datum

# Os x.
p.xaxis.axis_label='ura'
p.xaxis.axis_label_text_font_size = "10pt"
p.xaxis.axis_label_text_font_style = "normal"
p.xaxis[0].formatter = PrintfTickFormatter(format='%s')
p.xaxis.major_label_orientation = pi/2
# OPOMBA: zaradi napake ukaz "p.xaxis.major_label_orientation = horizontal" ne deluje (Bug #5914)!

# Os y.
p.yaxis.axis_label='temperatura'
p.yaxis.axis_label_text_font_size = "12pt"
p.yaxis.axis_label_text_font_style = "normal"
p.yaxis[0].formatter = PrintfTickFormatter(format='%s °C')

# Izris histograma.
p.vbar(x=x, width=0.7, bottom=0, top=y, color="firebrick")

# Izris črte s krogci za vsak senzor posebej.
p.line(x, y_red, line_width=2, line_color="red", legend="rdeči senzor")
p.circle(x, y_red, line_width=2, line_color="red", fill_color="red", size=4, legend="rdeči senzor")

p.line(x, y_yellow, line_width=2, line_color="yellow", legend="rumeni senzor")
p.circle(x, y_yellow, line_width=2, line_color="yellow", fill_color="yellow", size=4, legend="rumeni senzor")

# Priprava in izris nalepk (label) za prikaz temperatur nad stolpci histograma.
source = ColumnDataSource(data=dict(ure=x, avg_temperatura=y, vrednost_temperature=y_labels))

temperature_readings = LabelSet(x='ure', y='avg_temperatura', text='vrednost_temperature', level='glyph', x_offset=-15, y_offset=8, text_color='red', text_font_size='9pt', text_font="helvetica", source=source, render_mode='canvas')

p.add_layout(temperature_readings)

# Priprava in izris napisa o trenutni temperaturi.
trenutna_temperatura = "Temperatura ob %s je bila %.1f °C." % (strftime("%H:%M", localtime()), avg_temp[0][0])
current_temp = Label(x=70, y=120, x_units='screen', y_units='screen',
 text=trenutna_temperatura, render_mode='css',
 text_color='red', text_font_size='9pt', text_font="helvetica",
 border_line_color='white', border_line_alpha=1.0,
 background_fill_color='white', background_fill_alpha=0.8)

p.add_layout(current_temp)

# Izrišemo graf.
show(p)

# Zapremo povezavo na bazo.
if db_con: 
 db_con.close()

Ko aplikacijo poženemo, dobimo približno takle izpis:

python tempgraf.py

PRIKAZ TEMPERATURE
Trenutna temperatura je: 33.2
Povprečna temperatura na vseh senzorjih po urah na današnji dan:
Med 00 in 01 uro je bila povprečna temperatura 23.2 stopinj C.
Med 01 in 02 uro je bila povprečna temperatura 23.3 stopinj C.
Med 02 in 03 uro je bila povprečna temperatura 23.2 stopinj C.
Med 03 in 04 uro je bila povprečna temperatura 23.0 stopinj C.
Med 04 in 05 uro je bila povprečna temperatura 22.2 stopinj C.
Med 05 in 06 uro je bila povprečna temperatura 23.9 stopinj C.
Med 06 in 07 uro je bila povprečna temperatura 23.9 stopinj C.
Med 07 in 08 uro je bila povprečna temperatura 23.4 stopinj C.
Med 08 in 09 uro je bila povprečna temperatura 23.3 stopinj C.
Med 09 in 10 uro je bila povprečna temperatura 23.7 stopinj C.
Med 10 in 11 uro je bila povprečna temperatura 23.8 stopinj C.
Med 11 in 12 uro je bila povprečna temperatura 24.2 stopinj C.
Med 12 in 13 uro je bila povprečna temperatura 24.8 stopinj C.
Med 13 in 14 uro je bila povprečna temperatura 24.9 stopinj C.
Med 14 in 15 uro je bila povprečna temperatura 25.2 stopinj C.
Med 15 in 16 uro je bila povprečna temperatura 28.6 stopinj C.
Med 16 in 17 uro je bila povprečna temperatura 31.8 stopinj C.

Prikaz temperature preko spletne aplikacije

Za konec si bomo ogledali še, kako graf temperature prikažemo preko spletnega strežnika. Tako si lahko graf temperature ogledamo preko računalnika ali mobilnega telefona. Zaradi varnosti je dostop do našega sistema dostopen zgolj preko OpenVPN omrežja.

Najprej namestimo spletni strežnik Apache:

sudo apt-get install apache2

Nastavimo, da ima uporabnik pi ustrezne pravice dostopa:

sudo chown -R pi:www-data /var/www
sudo chmod g+s /var/www

Nastavimo še, da je Python aplikacija za izris grafa izvršilna:

chmod +x tempgraf.py

Nato v crontabu nastavimo, da se aplikacija izvede vsako 1, 15, 31 in 45 minuto:

crontab -e

Vpišemo:

1,15,31,45 * * * * /usr/bin/python /home/pi/tempgraf.py

V Python aplikaciji tempgraf.py popravimo še lokacijo izhodne HTML datoteke:

output_file("/var/www/html/index.html", title='Meritev temperature')

S tem je stvar končana.

Do grafa sedaj lahko dostopamo preko VPN omrežja:

Prikaz grafa temperature preko VPN.

Prikaz grafa temperature preko VPN.

Objavljena programska koda je dovolj povedna, da lahko zajem, obdelavo podatkov in njihov prikaz dokaj enostavno prilagajamo svojim potrebam. Z nekaj malega znanja si lahko nastavimo tudi e-poštne ali SMS alarme, ki se prožijo, če temperatura pade pod določeno vrednost ali jo preseže. S pomočjo relejnih stikal pa si lahko vzpostavimo tudi krmiljenje grelnih naprav in s tem pričnemo prve korake proti pametni hiši.

Kategorije: Informacijska tehnologija, Odprta koda
Ključne besede: Raspberry Pi, senzorski sistemi