How to get sensor measurements (Temperature, Pressure and Humidity) into graphs
Requirements: Raspberry Pi with properly installed OS (Raspbian Buster) and a webserver (lighttpd with php 7.0 enabled)
- 1.) Prepare the system for use of sensors: I²C-Bus, Python modules, Adafruit-Installation
- 2.) install RRD Tool, create a database and appropriate graphs
- 3.) show graphs as dynamically updated images on a webpage
1.) Communication between the Raspberry Pi and bme-sensor uses in this example the I²C bus (or SPI). The necessary kernel modules are part of Raspbian. Add their names to the file /etc/modules:
i2c-bcm2708i2c-dev
In case, you have a blacklist, these entries in /etc/modprobe.d/raspi-blacklist.conf should be commented out:
#blacklist spi-bcm2708#blacklist i2c-bcm2708
Newer versions of Raspbian require the activation of the I²C bus. Enable this in your Raspi-config: Select Advanced Options - I2C from the menu and confirm the dialog box with Yes.
You may now install the required Python modules and tools for working with the I²C bus:
sudo apt-get install python-smbus i2c-tools
Get default user pi to be a member of the group i2c:
sudo adduser pi i2c
Now reboot the Raspberry Pi with the BME280-sensor attached. The kernel should load the I²C modules. As user pi verify that hardware and software are working properly with i2cdetect.
The output shows that communication with the I²C address 77 (or 0x76) is possible.
This address is permanently assigned to the BME280. Run this code and see the output as follows..
i2cdetect -y 1
You should see this picture in your console:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- 77
You should now install Python2. I installed both python-versions: python2 and python3. Also install the BME280-library:
sudo pip install adafruit-circuitpython-bme280
The following script (named: bme280.py) for readout sensor-data from BME280 is written in python2 and it is an adapted code from:
GitHub
To print out sensor data to terminal screen, you have to delete the comment signs (#) at the end of this script.
Then comment out the last part "insert data into round-robin-database".
#!/usr/bin/python
# Distributed with a free-will license.
# Use it any way you want, profit or free, provided it fits in the licenses of its associated works.
# BME280
# This code is designed to work with the BME280_I2CS I2C Mini Module available from ControlEverything.com.
# https://www.controleverything.com/content/Humidity?sku=BME280_I2CS#tabs-0-product_tabset-2
import smbus
import time
import os
import rrdtool
databaseFile = "/home/pi/py/bme280.rrd"
# Get I2C bus
bus = smbus.SMBus(1)
# BME280 address, 0x76(118)
# Read data back from 0x88(136), 24 bytes
b1 = bus.read_i2c_block_data(0x77, 0x88, 24)
# Convert the data
# Temp coefficients
dig_T1 = b1[1] * 256 + b1[0]
dig_T2 = b1[3] * 256 + b1[2]
if dig_T2 > 32767 :
dig_T2 -= 65536
dig_T3 = b1[5] * 256 + b1[4]
if dig_T3 > 32767 :
dig_T3 -= 65536
# Pressure coefficients
dig_P1 = b1[7] * 256 + b1[6]
dig_P2 = b1[9] * 256 + b1[8]
if dig_P2 > 32767 :
dig_P2 -= 65536
dig_P3 = b1[11] * 256 + b1[10]
if dig_P3 > 32767 :
dig_P3 -= 65536
dig_P4 = b1[13] * 256 + b1[12]
if dig_P4 > 32767 :
dig_P4 -= 65536
dig_P5 = b1[15] * 256 + b1[14]
if dig_P5 > 32767 :
dig_P5 -= 65536
dig_P6 = b1[17] * 256 + b1[16]
if dig_P6 > 32767 :
dig_P6 -= 65536
dig_P7 = b1[19] * 256 + b1[18]
if dig_P7 > 32767 :
dig_P7 -= 65536
dig_P8 = b1[21] * 256 + b1[20]
if dig_P8 > 32767 :
dig_P8 -= 65536
dig_P9 = b1[23] * 256 + b1[22]
if dig_P9 > 32767 :
dig_P9 -= 65536
# BME280 address, 0x77(118)
# Read data back from 0xA1(161), 1 byte
dig_H1 = bus.read_byte_data(0x77, 0xA1)
# BME280 address, 0x76(118)
# Read data back from 0xE1(225), 7 bytes
b1 = bus.read_i2c_block_data(0x77, 0xE1, 7)
# Convert the data
# Humidity coefficients
dig_H2 = b1[1] * 256 + b1[0]
if dig_H2 > 32767 :
dig_H2 -= 65536
dig_H3 = (b1[2] & 0xFF)
dig_H4 = (b1[3] * 16) + (b1[4] & 0xF)
if dig_H4 > 32767 :
dig_H4 -= 65536
dig_H5 = (b1[4] / 16) + (b1[5] * 16)
if dig_H5 > 32767 :
dig_H5 -= 65536
dig_H6 = b1[6]
if dig_H6 > 127 :
dig_H6 -= 256
# BME280 address, 0x76(118)
# Select control humidity register, 0xF2(242)
# 0x01(01) Humidity Oversampling = 1
bus.write_byte_data(0x77, 0xF2, 0x01)
# BME280 address, 0x76(118)
# Select Control measurement register, 0xF4(244)
# 0x27(39) Pressure and Temperature Oversampling rate = 1
# Normal mode
bus.write_byte_data(0x77, 0xF4, 0x27)
# BME280 address, 0x76(118)
# Select Configuration register, 0xF5(245)
# 0xA0(00) Stand_by time = 1000 ms
bus.write_byte_data(0x77, 0xF5, 0xA0)
time.sleep(0.5)
# BME280 address, 0x76(118)
# Read data back from 0xF7(247), 8 bytes
# Pressure MSB, Pressure LSB, Pressure xLSB, Temperature MSB, Temperature LSB
# Temperature xLSB, Humidity MSB, Humidity LSB
data = bus.read_i2c_block_data(0x77, 0xF7, 8)
# Convert pressure and temperature data to 19-bits
adc_p = ((data[0] * 65536) + (data[1] * 256) + (data[2] & 0xF0)) / 16
adc_t = ((data[3] * 65536) + (data[4] * 256) + (data[5] & 0xF0)) / 16
# Convert the humidity data
adc_h = data[6] * 256 + data[7]
# Temperature offset calculations
var1 = ((adc_t) / 16384.0 - (dig_T1) / 1024.0) * (dig_T2)
var2 = (((adc_t) / 131072.0 - (dig_T1) / 8192.0) * ((adc_t)/131072.0 - (dig_T1)/8192.0)) * (dig_T3)
t_fine = (var1 + var2)
cTemp = (var1 + var2) / 5120.0
fTemp = cTemp * 1.8 + 32
# Pressure offset calculations
var1 = (t_fine / 2.0) - 64000.0
var2 = var1 * var1 * (dig_P6) / 32768.0
var2 = var2 + var1 * (dig_P5) * 2.0
var2 = (var2 / 4.0) + ((dig_P4) * 65536.0)
var1 = ((dig_P3) * var1 * var1 / 524288.0 + ( dig_P2) * var1) / 524288.0
var1 = (1.0 + var1 / 32768.0) * (dig_P1)
p = 1048576.0 - adc_p
p = (p - (var2 / 4096.0)) * 6250.0 / var1
var1 = (dig_P9) * p * p / 2147483648.0
var2 = p * (dig_P8) / 32768.0
pressure = (p + (var1 + var2 + (dig_P7)) / 16.0) / 100
# Humidity offset calculations
var_H = ((t_fine) - 76800.0)
var_H = (adc_h - (dig_H4 * 64.0 + dig_H5 / 16384.0 * var_H)) * (dig_H2 / 65536.0 * (1.0 + dig_H6 / 67108864.0 * var_H * (1.0 + dig_H3 / 67108864.0 * var_H)))
humidity = var_H * (1.0 - dig_H1 * var_H / 524288.0)
if humidity > 100.0 :
humidity = 100.0
elif humidity < 0.0 :
humidity = 0.0
# Output data to screen
#print "Temperature in Celsius : %.2f C" %cTemp
#print "Temperature in Fahrenheit : %.2f F" %fTemp
#print "Pressure : %.2f hPa " %pressure
#print "Relative Humidity : %.2f %%" %humidity
# insert data into round-robin-database
data = "N:%.2f:%.2f:%.2f" % (cTemp,pressure,humidity)
rrdtool.update(
databaseFile,
data)
Run python bme280.py (with the code-modifications made: uncomment # Output data to screen and comment out # insert data into round-robin-database)
to check if your modified script prints out values to the console like below:
Temperature: 18.63 °C
Pressure: 1017.54 hPa
Humidity: 59.00 %
2.) To visualize atmospheric pressure a long-term recording of measured values into a database and a convenient way to create graphics, we install a round-robin database
with the OpenSource software RRDtool written by Tobias Oetiker. It holds high temporal resolution readings on only a certain number of days.
This is followed by an automatic consolidation of data so that only the daily average values are kept in the database.
After a long time, you can also override these values. Thus, the memory requirements of the database are kept into predetermined limits.
sudo apt-get install rrdtool python-rrdtool
Now create a database, that stores the temperature temp, atmospheric pressure at sea level psea and relative humidity.
It should store one value tuple per quarter hour (900 seconds). After ten days (= 960 values), a reduction takes place to one average,
minimum, and maximum value per day. The retention time of the daily values is ten years (= 3600 values).
rrdtool create bme280.rrd --step 900 \
DS:temp:GAUGE:1200:-40:80 \
DS:psea:GAUGE:1200:950:1050 \
DS:hum:GAUGE:1200:10:100 \
RRA:AVERAGE:0.5:1:960 \
RRA:MIN:0.5:96:3600 \
RRA:MAX:0.5:96:3600 \
RRA:AVERAGE:0.5:96:3600
Now change the above explained code modifications vice versa and extend the pythonscript (bme280.py) at the end to write measured values into the database.
Take care about your path: in my case script and database are in the same directory.
# insert data into round-robin-database
data = "N:%.2f:%.2f:%.2f" % (cTemp,pressure,humidity)
rrdtool.update(
databaseFile,
data)
To check, whether the script is actually writing data into the database use this command: rrdtool lastupdate bme280.rrd.
It returns the timestamp and the values of the last update of the database.
After a few hours of collecting data, you could create graphs with RRDtool.
The following code creates 12 graphics files with the air pressure, temperature and humidity data collected
within a daily, weekly, monthly and yearly timespan.
#!/bin/bash
RRDPATH="/home/pi/py/"
#create graph Luftdruck daily
rrdtool graph $RRDPATH/pressure_d.png \
-s 'now - 1 day' -e 'now' \
-v "[hPa]" \
-A -Y -X 0 \
DEF:psea=$RRDPATH/values.rrd:psea:AVERAGE \
LINE1:psea#3fe0db:"Air Pressure (QNH)"
# VDEF:psealast=psea:LAST \
# GPRINT:psea:LAST:"Now\:%4.2lf"
#create graph Temperatur daily
rrdtool graph $RRDPATH/temp_d.png \
-s 'now - 1 day' -e 'now' \
-v "[°C]" \
-A -Y -X 0 \
DEF:temp=$RRDPATH/values.rrd:temp:AVERAGE \
LINE1:temp#FF0000:"Air Temperature (OAT)"
# VDEF:templast=temp:LAST \
# GPRINT:temp:LAST:"Now\:%2.2lf"
#create graph Luftfeuchte daily
rrdtool graph $RRDPATH/hum_d.png \
-s 'now - 1 day' -e 'now' \
-v "[%]" \
-A -Y -X 0 \
DEF:hum=$RRDPATH/values.rrd:hum:AVERAGE \
LINE1:hum#cf38ca:"Rel Humidity (%)"
# VDEF:humlast=hum:LAST \
# GPRINT:hum:LAST:"Now\:%4.2lf"
#*********************************************
#create graph Luftdruck weekly
rrdtool graph $RRDPATH/pressure.png \
-s 'now -1w' -e 'now' \
-v "[hPa]" \
-A -Y -X o \
DEF:psea=$RRDPATH/values.rrd:psea:AVERAGE \
LINE1:psea#3fe0db:"Air Pressure (QNH)" \
GPRINT:psea:LAST:"Now\:%4.2lf" \
GPRINT:psea:MIN:"Min\:%4.2lf" \
GPRINT:psea:MAX:"Max\:%4.2lf\n"
#create graph Temperatur weekly
rrdtool graph $RRDPATH/temp.png \
-s 'now -1w' -e 'now' \
-v "[°C]" \
-A -Y -X o \
DEF:temp=$RRDPATH/values.rrd:temp:AVERAGE \
LINE1:temp#FF0000:"Air Temperature (OAT)" \
GPRINT:temp:LAST:"Now\:%2.2lf" \
GPRINT:temp:MIN:"Min\:%2.2lf" \
GPRINT:temp:MAX:"Max\:%2.2lf\n"
#create graph Luftfeuchte weekly
rrdtool graph $RRDPATH/hum.png \
-s 'now -1w' -e 'now' \
-v "[%]" \
-A -Y -X o \
DEF:hum=$RRDPATH/values.rrd:hum:AVERAGE \
LINE1:hum#cf38ca:"Rel Humidity (%)" \
GPRINT:hum:LAST:"Now\:%4.2lf" \
GPRINT:hum:MIN:"Min\:%4.2lf" \
GPRINT:hum:MAX:"Max\:%4.2lf\n"
#*********************************************
#create graph Luftdruck monthly
rrdtool graph $RRDPATH/pressure_m.png \
-s 'now - 1 month' -e 'now' \
-v "[hPa]" \
-A -Y -X 0 \
DEF:pseamin_m=$RRDPATH/values.rrd:psea:MIN \
DEF:pseamax_m=$RRDPATH/values.rrd:psea:MAX \
DEF:psea=$RRDPATH/values.rrd:psea:AVERAGE \
CDEF:psearanges=pseamax_m,pseamin_m,- \
LINE1:pseamin_m#3fe0db \
AREA:psearanges#8dadf588::STACK \
LINE1:pseamax_m#3fe0db \
LINE2:psea#3fe0db:Pressure
#create graph Temperatur monthly
rrdtool graph $RRDPATH/temp_m.png \
-s 'now - 1 month' -e 'now' \
-v "[°C]" \
-A -Y -X 0 \
DEF:tempmin_m=$RRDPATH/values.rrd:temp:MIN \
DEF:tempmax_m=$RRDPATH/values.rrd:temp:MAX \
DEF:temp=$RRDPATH/values.rrd:temp:AVERAGE \
CDEF:tempranges=tempmax_m,tempmin_m,- \
LINE1:tempmin_m#FF0000 \
AREA:tempranges#8dadf588::STACK \
LINE1:tempmax_m#FF0000 \
LINE2:temp#FF0000:Temperature
#create graph Luftfeuchtigkeit monthly
rrdtool graph $RRDPATH/hum_m.png \
-s 'now - 1 month' -e 'now' \
-v "[%]" \
-A -Y -X 0 \
DEF:hummin_m=$RRDPATH/values.rrd:hum:MIN \
DEF:hummax_m=$RRDPATH/values.rrd:hum:MAX \
DEF:hum=$RRDPATH/values.rrd:hum:AVERAGE \
CDEF:humranges=hummax_m,hummin_m,- \
LINE1:hummin_m#cf38ca \
AREA:humranges#ffb900::STACK \
LINE1:hummax_m#cf38ca \
LINE2:hum#cf38ca:Humidity
#**********************************************
#create graph Luftdruck yearly
rrdtool graph $RRDPATH/pressure_y.png \
-s 'now - 1 year' -e 'now' \
-v "[hPa]" \
-A -Y -X o \
DEF:psea_y=$RRDPATH/values.rrd:psea:AVERAGE \
LINE1:psea_y#3fe0db:"Air Pressure (QNH)" \
GPRINT:psea_y:LAST:"Now\:%4.2lf" \
GPRINT:psea_y:MIN:"Min\:%4.2lf" \
GPRINT:psea_y:MAX:"Max\:%4.2lf\n"
#create graph Temperatur yearly
rrdtool graph $RRDPATH/temp_y.png \
-s 'now - 1 year' -e 'now' \
-v "[°C]" \
-A -Y -X o \
DEF:temp_y=$RRDPATH/values.rrd:temp:AVERAGE \
LINE1:temp_y#FF0000:"Air Temperature (OAT)" \
GPRINT:temp_y:LAST:"Now\:%2.2lf" \
GPRINT:temp_y:MIN:"Min\:%2.2lf" \
GPRINT:temp_y:MAX:"Max\:%2.2lf\n"
#create graph Luftfeuchtigkeit yearly
rrdtool graph $RRDPATH/hum_y.png \
-s 'now - 1 year' -e 'now' \
-v "[%]" \
-A -Y -X o \
DEF:hum_y=$RRDPATH/values.rrd:hum:AVERAGE \
LINE1:hum_y#cf38ca:"Rel Humidity (%)" \
GPRINT:hum_y:LAST:"Now\:%4.2lf" \
GPRINT:hum_y:MIN:"Min\:%4.2lf" \
GPRINT:hum_y:MAX:"Max\:%4.2lf\n"
#copy to var/www/html/rrd/images/graphs
cp /home/pi/py/*.png /var/www/html/rrd/images/graphs
For long term operation you may delegate the script call to the scheduling service cron - here line 2 and 3 (type crontab -e to edit).
# m h dom mon dow command
*/5 * * * * cd /home/pi/py && python bme280.py
*/15 * * * * /home/pi/py/create_graph.sh
*/5 * * * * /home/pi/py/cputemp.sh
@reboot cd /usr/local/bin && sudo noip2
3.) Assumed you have a webserver like lighttpd installed on your system,
you can either copy the images into your web directory or you directly let RRDtool create the graphs in your web directory.
The second solution requires to adjust the $RRDPATH in the script above. For the first solution add the code below and change it with your path.
#copy to var/www/html/rrd/images/graphs
cp /home/pi/py/*.png /var/www/html/rrd/images/graphs
You`re done! Since the database will be updated every 15 minutes, wait a few hours until you can observe the diagram growing...