通过arduino采集室内室外温湿度数据,通过串口以发送json数据的形式,将采集到的数据传递给树莓派,树莓派中有一个服务,获取到来自串口的数据,将数据写入rrd文件。树莓派上再制作一个脚本,每5分钟生成图形,放在http服务器目录下,以便通过浏览器查看。
项目用到的材料清单如下
必备材料:
1、树莓派一块
2、欧西亚(Oregon Scientific)RTGR328N室外传感器一台。(欧西亚家用气象站的室外温度湿度传感器,后面会有简单的说明)
3、Arduino nano一块
4、RXB60 433MHz超外差接收模块一块(也使用过那种很便宜的超再生模块,似乎抗干扰能力很差,后来换用这种模块后就稳定多了)
5、连接线若干
6、433MHz弹簧天线一根(RXB60接收模块本身不含天线,需要自己在模块ANT针上连接一根天线)
7、170孔面包板两块(实际上这次使用两块170孔面包板比使用400孔面包板更为紧凑、空间利用率更高)
8、DHT11或者DHT22温湿度模块(主要用来获取室内温湿度,这里我用的是DHT22)
软件方面:
1、树莓派最好用debian或者ubuntu的系统
2、安装rrdtool和python3,
3、再装个nginx或者apache之类的http服务器,主要是为了通过网页查看rrdtool出的图。
为什么要使用欧西亚的温湿度传感器?
最初是为了利用家里现有的国产易美特气象站433MHz室外温湿度传感器来得到室外温湿度数据,也测试过另一款很便宜的标识为UPM的洋垃圾433Mhz温湿度传感器,发现对于我这个并不精通无线电这方面的门外汉来说,解码这些传感器数据困难很大,在研究过程中发现欧西亚气象站在外国使用量很大(因为欧西亚就是家用气象站的鼻祖嘛),研究的朋友也很多,有现成且很成熟的解码方案,遂在某鱼上低价入手一台二手的用来研究。有朋友会问了,为什么不用arduino、ESP8266\ESP32自己做一台蓝牙、WIFI或者433Mhz的室外传感器呢?我只是觉得像欧西亚这种成品传感器有它独特的优点:现成的可以在恶劣室外环境下工作的外壳(主要是保护主要器件防水、防尘、传感器有专门的开口)、433Mhz相对功耗比较低、使用干电池比较方便也能更好适应室外恶劣的环境,最后,最重要的一个有点就是拿来就用(我的这个传感器型号是RTGR328N,需要两节5号电池,有电波钟功能,只是频率和国内不同,需要改造才能使用)。
为什么不直接使用树莓派连接433Mhz接收器和DHT22传感器?
原因很简单,因为arduino有现成的欧西亚传感器解码代码,我们的原则是尽量拿来就用。
Arduino nano传感器连接:
天线部分,因为原来就是直接找了一根铜连接线插在面包板上,感觉总是拖个小尾巴,干脆就在某宝买了2.54转1.27的小电路板和弹簧天线,将天线焊在电路板1.27的孔内,2.54孔位焊上排针,做成了如下图的天线:
Arduino代码
欧西亚传感器解码部分来自于https://github.com/rene-d/oregon,在此基础上,我做了些修改,加入了读取DHT22传感器数据的代码,以及改为通过串口以json格式发送数据。
使用到的arduino库有ArduinoJson和DHT sensor library两个库。
// oregon.ino // rene-d 01/2019 #include "DHT.h" #include <ArduinoJson.h> #include "Oregon.h" #include "decode.h" #define DHTPIN 3 // Digital pin connected to the DHT sensor // Define pin where is 433Mhz receiver (here, pin 2) #define MHZ_RECEIVER_PIN 2 //#define DHTTYPE DHT11 // DHT 11 #define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321 //#define DHTTYPE DHT21 // DHT 21 (AM2301) DHT dht(DHTPIN, DHTTYPE); void setup() {
Serial.begin(); //DHTxx dht.begin(); Serial.println(">>>>Arduino-Oregon RTGR328N Example<<<<"); //Serial.println("Setup started"); memset(&sensor_clock, 0, sizeof(sensor_clock)); sensor_clock.day = 1; sensor_clock.month = 1; pinMode(LED_BUILTIN, OUTPUT); // Setup received data attachInterrupt(digitalPinToInterrupt(MHZ_RECEIVER_PIN), ext_int_1, CHANGE); //Serial.println("Setup completed"); //Serial.println("Begin receive 433MHz Oregon RTGR328N sensors..."); } void loop() {
//------------------------------------------ // Start process new data from Oregon sensors //------------------------------------------ noInterrupts(); // Disable interrupts word p = pulse; pulse = 0; interrupts(); // Enable interrupts if (p != 0) {
if (orscV2.nextPulse(p)) {
// Decode Hex Data once byte length; const byte *osdata = DataToDecoder(orscV2, length); print_hexa(osdata, length); // Serial.println(F("Outdoor:")); oregon_decode(osdata, length); float h = dht.readHumidity(); float t = dht.readTemperature(); if (isnan(h) || isnan(t)) {
Serial.println(F("Failed to read from DHT sensor!")); return; } float hic = dht.computeHeatIndex(t, h, false); // Serial.println(F("Indoor:")); // Serial.print(F("Humidity: ")); // Serial.print(h); // Serial.print(F("% Temperature: ")); // Serial.print(t); // Serial.print(F("°C ")); // Serial.print(F("Heat index: ")); // Serial.print(hic); // Serial.println(F("°C")); StaticJsonDocument<200> doc; doc["name"] = "RTGR328N"; JsonObject data = doc.createNestedObject("data"); JsonObject data_indoor = data.createNestedObject("indoor"); data_indoor["temperature"] = t; data_indoor["humidity"] = h; data_indoor["heat-index"] = hic; JsonObject data_outdoor = data.createNestedObject("outdoor"); data_outdoor["humidity"] = sensor_info.hum; data_outdoor["temperature"] = sensor_info.temp; data_outdoor["channel"] = sensor_info.channel; data_outdoor["id"] = sensor_info.id; data_outdoor["time"] = date_time; data_outdoor["bat"] = sensor_info.bat; data_outdoor["RAW"] = raw_result; serializeJson(doc, Serial); //Serial.println(); //serializeJsonPretty(doc, Serial); } } update_clock(); if (Serial.available() > 0) {
// read the incoming byte: byte incomingByte = Serial.read(); if (incomingByte == 't') {
// print the current time print_hexa(&incomingByte, 0); } } } int days_in_month(byte month, byte year) {
static byte days[] = {
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; if (month == 2) {
// we will never reach 2100 if (year % 4 == 0) return 29; } return days[month]; } void update_clock() {
unsigned long now = millis(); unsigned long o = now - sensor_clock.now; if (o >= 1000) {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); sensor_clock.now = now; while (o >= 1000) {
// advance the clock by one second o -= 1000; // dirty code done dirt cheap... sensor_clock.second++; if (sensor_clock.second >= 60) {
sensor_clock.second = 0; sensor_clock.minute++; if (sensor_clock.minute >= 60) {
sensor_clock.minute = 0; sensor_clock.hour++; if (sensor_clock.hour >= 24) {
sensor_clock.hour = 0; sensor_clock.day++; if (sensor_clock.day > days_in_month(sensor_clock.month, sensor_clock.year)) {
sensor_clock.day = 1; sensor_clock.month++; if (sensor_clock.month > 12) {
sensor_clock.month = 1; sensor_clock.year++; } } } } } } } } void print_hexa(const byte *data, byte length) {
raw_result = ""; //Serial.println(); // print the sensor/board clock char buf[32]; snprintf(buf, 32, "%04u/%02u/%02u %02u:%02u:%02u.%03lu", 2000 + sensor_clock.year, sensor_clock.month, sensor_clock.day, sensor_clock.hour, sensor_clock.minute, sensor_clock.second, millis() - sensor_clock.now); date_time = buf; //Serial.println(buf); // print data in byte order, that's NOT the nibble order //for (byte i = 0; i < length; ++i) //{
// Serial.print(data[i] >> 4, HEX); // Serial.print(data[i] & 0x0F, HEX); //} //Serial.println(); for (byte i = 0; i < length; ++i) {
raw_result += tohex(data[i] >> 4); raw_result += tohex(data[i] & 0x0F); } }
decode.h文件主要是修改了原来直接输出到串口的部分代码,变更为将数据存储在全局变量中,以便最后统一输出为json格式。
// decode.h // rene-d 01/2019 // internal clock, set from OS sensor struct {
byte year, month, day; byte hour, minute, second; unsigned long now; } sensor_clock; struct {
byte id, channel; float temp; byte hum, bat; } sensor_info; String raw_result = ""; String date_time = ""; // // get a nibble (a half byte) // byte nibble(const byte *osdata, byte n) {
if (n & 1) {
return osdata[n / 2] >> 4; } else {
return osdata[n / 2] & 0xf; } } // // calculate the packet checksum (sum of all nibbles) // bool checksum(const byte *osdata, byte last, byte check) {
byte calc = 0; for (byte i = 1; i <= last; i++) {
calc += nibble(osdata, i); } return (check == calc); } // // print a buffer in nibble order, with nibbles packed by field // void print_nibbles(const byte *osdata, size_t len, const char *def) {
static const char digits[] = "0ABCDEF"; char hexa[128]; size_t i = 0; size_t j = 0; size_t k = 0; char c, n; n = def[0]; if (n) n -= '0'; c = n; while (i < len * 2 && j < sizeof(hexa) - 3) {
hexa[j++] = digits[nibble(osdata, i++)]; if (c > 0) {
c--; if (c == 0) {
// reverse last def[k] chars if (n > 1) {
for (char z = 0; z < n / 2; ++z) {
c = hexa[j - 1 - z]; hexa[j - 1 - z] = hexa[j - n + z]; hexa[j - n + z] = c; } } hexa[j++] = ' '; n = def[++k]; if (n) n -= '0'; c = n; } } } hexa[j] = 0; Serial.print("data: "); Serial.println(hexa); } // // message 3EA8 or 3EC8 : clock // void decode_date_time(const byte *osdata, size_t len) {
if (len < 12) return; byte crc = osdata[11]; bool ok = checksum(osdata, 21, crc); byte channel = (osdata[2] >> 4); byte rolling_code = osdata[3]; int year = (osdata[9] >> 4) + 10 * (osdata[10] & 0xf); int month = (osdata[8] >> 4); int day = (osdata[7] >> 4) + 10 * (osdata[8] & 0xf); int hour = (osdata[6] >> 4) + 10 * (osdata[7] & 0xf); int minute = (osdata[5] >> 4) + 10 * (osdata[6] & 0xf); int second = (osdata[4] >> 4) + 10 * (osdata[5] & 0xf); channel = nibble(osdata, 5); #ifdef ARDUINO if (!ok) Serial.println(" bad crc"); if (ok && (nibble(osdata, 8) & 2) != 0) {
// update the sensor clock sensor_clock.now = millis(); sensor_clock.year = year; sensor_clock.month = month; sensor_clock.day = day; sensor_clock.hour = hour; sensor_clock.minute = minute; sensor_clock.second = second; Serial.println("update sensor clock!"); } //Serial.print(" id: "); //Serial.println(rolling_code); //Serial.print(" channel: "); //Serial.println(channel); char buf[100]; snprintf(buf, sizeof(buf), " date: 20%02d/%02d/%02d", year, month, day); //Serial.println(buf); snprintf(buf, sizeof(buf), " time: %02d:%02d:%02d", hour, minute, second); //Serial.println(buf); #else print_nibbles(osdata, len, "212"); char buf[80]; snprintf(buf, sizeof(buf), "channel=%d crc=$%02X %s id=%d channel=%d state=%d clock=20%02d/%02d/%02d %02d:%02d:%02d", channel, crc, ok ? "OK" : "KO", rolling_code, channel, nibble(osdata, 8), year, month, day, hour, minute, second); Serial.println(buf); static const char *label[] = {
"alwaysA", // 0 b0 A=1010 - not part of the message "id_msg", // 1 b0 "id_msg", // 2 b1 "id_msg", // 3 b1 "id_msg", // 4 b2 "channel", // 5 b2 "rolling code", // 6 b3 "rolling code", // 7 b3 "clock state", // 8 b4 0,4,8: date is invalid, 2 or 6: date is valid "second (units)", // 9 b4 "second (tens)", // 10 b5 "minute (unit)", // 11 b5 "minute (tens)", // 12 b6 "hour (units)", // 13 b6 "hour (tens)", // 14 b7 "day (units)", // 15 b7 "day (tens)", // 16 b8 "month", // 17 b8 "day of week", // 18 b9 "year (units)", // 19 b9 "year (tens)", // 20 b10 "?", // 21 b10 "crc", // 22 b11 "crc", // 23 b11 }; for (int i = 0; i < 24; ++i) {
snprintf(buf, sizeof(buf), " nibble %2d : %X %s", i, nibble(osdata, i), label[i]); Serial.println(buf); } #endif } // // message 3CCx or 02D1: temperature humidity // void decode_temp_hygro(const byte *osdata, size_t len) {
if (len < 9) return; byte crc = osdata[8]; bool ok = checksum(osdata, 15, crc); // checksum = nibbles 1-15, result is nibbles 17..16 byte channel = (osdata[2] >> 4); byte rolling_code = osdata[3]; int temp = ((osdata[5] >> 4) * 100) + ((osdata[5] & 0x0F) * 10) + ((osdata[4] >> 4)); if (osdata[6] & 0x08) temp = -temp; byte hum = ((osdata[7] & 0x0F) * 10) + (osdata[6] >> 4); // byte BatteryLevel = (osdata[4] & 0x4) ? 10 : 90; // Serial.println("Oregon battery level: " + String(BatteryLevel)); // byte bat = osdata[4] & 0x4; // byte bat = osdata[4] & 0x0F; #ifdef ARDUINO if (!ok) Serial.println(" bad crc"); //Serial.print(" id: ["); //Serial.print(rolling_code); //Serial.print("] channel: ["); //Serial.print(channel); //Serial.print("] temperature: ["); //Serial.print(temp / 10.); //Serial.print("] humidity: ["); //Serial.print(hum); //Serial.print("] bat: ["); //Serial.print(BatteryLevel); // Serial.print(bat); //if ((bat & 4) != 0) //{
// Serial.println(" low]"); //} //else //{
// Serial.println(" ok]"); //} sensor_info.id = rolling_code; sensor_info.channel = channel; //sensor_info.temp = temp; sensor_info.temp = temp / 10.; sensor_info.hum = hum; sensor_info.bat = BatteryLevel; #else print_nibbles(osdata, len, ""); char buf[80]; snprintf(buf, sizeof(buf), "channel=%d crc=$%02X %s id=%d temp=%.1lf°C hum=%d%% bat=%d", channel, crc, ok ? "OK" : "KO", rolling_code, temp / 10., hum, bat); Serial.println(buf); static const char *label[] = {
"alwaysA", // 0 b0 lsb always A=1010 "id_msg", // 1 b0 msb "id_msg", // 2 b1 lsb "id_msg", // 3 b1 msb "id_msg", // 4 b2 lsb "channel", // 5 b2 msb "rolling code", // 6 b3 lsb "rolling code", // 7 b3 msb "battery", // 8 b4 lsb bit 3=1 => low? "temperature (tenths)", // 9 b4 msb "temperature (units)", // 10 b5 lsb "temperature (tens)", // 11 b5 msb "temperature (sign)", // 12 b6 lsb "humidity (units)", // 13 b6 msb "humidity (tens)", // 14 b7 lsb "comfort", // 15 b7 msb comfort ??? (according to RFLink) 0: normal, 4: comfortable, 8: dry, C: wet "crc", // 16 b8 lsb "crc", // 17 b8 msb }; for (int i = 0; i < 18; ++i) {
snprintf(buf, sizeof(buf), " nibble %2d : %X %s", i, nibble(osdata, i), label[i]); Serial.println(buf); } #endif } void oregon_decode(const byte *osdata, size_t len) {
// we need 2 bytes at least // - the preamble A // - the ID on four nibbles if (len < 3) return; uint16_t id = (((uint16_t)nibble(osdata, 4)) << 12) + (((uint16_t)nibble(osdata, 3)) << 8) + (((uint16_t)nibble(osdata, 2)) << 4) + (((uint16_t)nibble(osdata, 1))); #ifndef ARDUINO char buf[32]; snprintf(buf, 32, "message: %04X len=%zu", id, len); Serial.println(buf); #endif if ((id & 0xFFF0) == 0x3CC0 || id == 0x02D1) {
decode_temp_hygro(osdata, len); } else if (id == 0x3EA8 || id == 0x3EC8) {
decode_date_time(osdata, len); } else {
#ifndef ARDUINO print_nibbles(osdata, len, "1412"); Serial.println("UNKNOWN"); #endif } } String tohex(int n) {
if (n > 15) {
return "0"; } String result = ""; char _16[] = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; result = _16[n]; return result; }
建立一个rrd文件:
rrdtool create test2.rrd --step 300 DS:in_temp:GAUGE:600:U:U DS:in_hum:GAUGE:600:U:U DS:in_hic:GAUGE:600:U:U DS:out_temp:GAUGE:600:U:U DS:out_hum:GAUGE:600:U:U DS:out_hic:GAUGE:600:U:U RRA:AVERAGE:0.5:1:600 RRA:AVERAGE:0.5:6:700 RRA:AVERAGE:0.5:24:775 RRA:AVERAGE:0.5:288:797 RRA:MAX:0.5:1:600 RRA:MAX:0.5:6:700 RRA:MAX:0.5:24:775 RRA:MAX:0.5:288:797
树莓派上首先要自己写一个服务,在后台读取USB 串口传过来的json数据,写入rrd文件。
首先,在/home/ubuntu/目录下新建service_read_sensor.py文件,内容如下,主要作用是
service文件调用的
#!/usr/bin/python3 import serial import threading import json import time import math import rrdtool import sys DATA = "" # 读取的数据 NOEND = True # 是否读取结束 sensor_data = "" def calc_heat_index(T, RH): '''NOAA计算体感温度 参数为气温(摄氏度)和相对湿度(0~100或者0~1)''' if RH < 1: RH *= 100 T = 1.8 * T + 32 HI = 0.5 * (T + 61 + (T - 68) * 1.2 + RH * 0.094) if HI >= 80: # 如果不小于 80华氏度 则用完整公式重新计算 HI = -42.379 + 2.0 * T + 10. * RH - . * T * RH \ - .00 * T * T - .0 * RH * RH + .00 * T * T * RH \ + .00085282 * T * RH * RH - .00000199 * T * T * RH * RH if RH < 13 and 80 < T < 112: ADJUSTMENT = (13 - RH) / 4 * math.sqrt((17 - abs(T - 95)) / 17) HI -= ADJUSTMENT elif RH > 85 and 80 < T < 87: ADJUSTMENT = (RH - 85) * (87 - T) / 50 HI += ADJUSTMENT return round((HI - 32) / 1.8, 2) # 读数据的本体 def read_data(ser): #global DATA, NOEND, sensor_data global DATA, NOEND, sensor_data,logfile # 循环接收数据(此为死循环,可用线程实现) while NOEND: if ser.in_waiting: #logfile = open("\tmp\read_serial.log", "a") #DATA = ser.read(ser.in_waiting).decode("gbk") DATA = ser.readline().decode("gbk") logfile.write("\n["+time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())+"]receive:"+DATA+"\n") logfile.write("\n["+time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())+"]json.loads:") try: sensor_data = json.loads(DATA) except ValueError: logfile.write("error for json\n") else: logfile.write(str(sensor_data)+"\n") update=rrdtool.updatev('/home/ubuntu/test2.rrd','N:%s:%s:%s:%s:%s:%s' % (str(sensor_data['data']['indoor']['temperature']),str(sensor_data['data']['indoor']['humidity']),str(sensor_data['data']['indoor']['heat-index']),str(sensor_data['data']['outdoor']['temperature']),str(sensor_data['data']['outdoor']['humidity']),str(calc_heat_index(sensor_data['data']['outdoor']['temperature'],sensor_data['data']['outdoor']['humidity'])))) logfile.write("\n["+time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())+"]rrdtool update:"+str(update)+"\n") logfile.flush() #logfile.close() # 打开串口 def open_seri(portx, bps, timeout): global logfile ret = False try: # 打开串口,并得到串口对象 ser = serial.Serial(portx, bps, timeout=timeout) # 判断是否成功打开 if(ser.is_open): ret = True th = threading.Thread(target=read_data, args=(ser,)) # 创建一个子线程去等待读数据 th.start() except Exception as e: #logfile = open("\tmp\open_serial.log", "a") logfile.write("\n["+time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())+"]open serial error:"+e+"\n") logfile.flush() #logfile.close() return ser, ret # 关闭串口 def close_seri(ser): global NOEND NOEND = False ser.close() # 写数据 def write_to_seri(ser, text): res = ser.write(text.encode("gbk")) # 写 return res # 读数据 def read_from_seri(): global DATA data = DATA DATA = "" #清空当次读取 return data if __name__ == "__main__": logfile = open("/tmp/read_serial.log", "a") ser, ret = open_seri('/dev/ttyUSB0', , 1) while True: time.sleep(2) logfile.close()
在/lib/systemd/system目录下新建read_serial_sensor.service文件,内容如下:
[Unit] Description=Read Serial Sensor Service After=multi-user.target Conflicts=getty@tty1.service [Service] Type=simple ExecStart=/usr/bin/python3 /home/ubuntu/service_read_sensor.py StandardInput=tty-force [Install] WantedBy=multi-user.target
sudo systemctl start read_serial_sensor.service sudo systemctl status read_serial_sensor.service sudo systemctl enable read_serial_sensor.service
另外再写一个定时执行的脚本,每5分钟生成一次rrd图,图片放在/usr/local/nginx/html/images/目录下,这是我的nginx根目录下的一个图片目录。
#!/usr/bin/env python3 import sys import time import rrdtool rrdtool.graph( '/usr/local/nginx/html/images/temp_test-1day.png', '--title','室内室外温度与体感温度24小时曲线图', '--vertical-label','temp °C', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','-10', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_temp:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_temp:AVERAGE', 'DEF:c=/home/ubuntu/test2.rrd:in_hic:AVERAGE', 'DEF:d=/home/ubuntu/test2.rrd:out_hic:AVERAGE', 'LINE1:a#1E90FFFF:室内温度', 'GPRINT:a:LAST: Current\:%2.2lf°C', 'GPRINT:a:AVERAGE:Average\:%2.2lf°C', 'GPRINT:a:MAX:Maximum\:%2.2lf°C', 'LINE1:c#87CEEBFF:室内体感', 'GPRINT:c:LAST: Current\:%2.2lf°C', 'GPRINT:c:AVERAGE:Average\:%2.2lf°C', 'GPRINT:c:MAX:Maximum\:%2.2lf°C', 'LINE1:b#00FF7FFF:室外温度', 'GPRINT:b:LAST:Current\:%2.2lf°C', 'GPRINT:b:AVERAGE:Average\:%2.2lf°C', 'GPRINT:b:MAX:Maximum\:%2.2lf°C', 'LINE1:d#66CD00FF:室外体感', 'GPRINT:d:LAST:Current\:%2.2lf°C', 'GPRINT:d:AVERAGE:Average\:%2.2lf°C', 'GPRINT:d:MAX:Maximum\:%2.2lf°C') rrdtool.graph( '/usr/local/nginx/html/images/temp_test-1week.png', '--start','end-1w', '--end','00:00', '--title','室内室外温度', '--vertical-label','temp °C', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','-10', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_temp:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_temp:AVERAGE', 'LINE1:a#00CF00FF:indoor temp', 'GPRINT:a:LAST: Current\:%2.2lf°C', 'GPRINT:a:AVERAGE:Average\:%2.2lf°C', 'GPRINT:a:MAX:Maximum\:%2.2lf°C\\n', 'LINE1:b#002A97FF:outdoor temp', 'GPRINT:b:LAST:Current\:%2.2lf°C', 'GPRINT:b:AVERAGE:Average\:%2.2lf°C', 'GPRINT:b:MAX:Maximum\:%2.2lf°C') rrdtool.graph( '/usr/local/nginx/html/images/temp_test-1month.png', '--start','end-1m', '--end','00:00', '--title','室内室外温度', '--vertical-label','temp °C', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','-10', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_temp:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_temp:AVERAGE', 'LINE1:a#00CF00FF:indoor temp', 'GPRINT:a:LAST: Current\:%2.2lf°C', 'GPRINT:a:AVERAGE:Average\:%2.2lf°C', 'GPRINT:a:MAX:Maximum\:%2.2lf°C\\n', 'LINE1:b#002A97FF:outdoor temp', 'GPRINT:b:LAST:Current\:%2.2lf°C', 'GPRINT:b:AVERAGE:Average\:%2.2lf°C', 'GPRINT:b:MAX:Maximum\:%2.2lf°C') rrdtool.graph( '/usr/local/nginx/html/images/temp_test-1year.png', '--start','end-1y', '--end','00:00', '--title','室内室外温度', '--vertical-label','temp °C', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','-10', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_temp:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_temp:AVERAGE', 'LINE1:a#00CF00FF:indoor temp', 'GPRINT:a:LAST: Current\:%2.2lf°C', 'GPRINT:a:AVERAGE:Average\:%2.2lf°C', 'GPRINT:a:MAX:Maximum\:%2.2lf°C\\n', 'LINE1:b#002A97FF:outdoor temp', 'GPRINT:b:LAST:Current\:%2.2lf°C', 'GPRINT:b:AVERAGE:Average\:%2.2lf°C', 'GPRINT:b:MAX:Maximum\:%2.2lf°C') rrdtool.graph( '/usr/local/nginx/html/images/hum_test-1day.png', '--title','室内室外湿度', '--vertical-label','hum %', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','0', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_hum:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_hum:AVERAGE', 'LINE1:a#00CF00FF:indoor hum', 'GPRINT:a:LAST: Current\:%3.2lf%%', 'GPRINT:a:AVERAGE:Average\:%3.2lf%%', 'GPRINT:a:MAX:Maximum\:%3.2lf%%\\n', 'LINE1:b#002A97FF:outdoor hum', 'GPRINT:b:LAST:Current\:%3.2lf%%', 'GPRINT:b:AVERAGE:Average\:%3.2lf%%', 'GPRINT:b:MAX:Maximum\:%3.2lf%%') rrdtool.graph( '/usr/local/nginx/html/images/hum_test-1week.png', '--start','end-1w', '--end','00:00', '--title','室内室外湿度', '--vertical-label','hum %', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','0', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_hum:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_hum:AVERAGE', 'LINE1:a#00CF00FF:indoor hum', 'GPRINT:a:LAST: Current\:%3.2lf%%', 'GPRINT:a:AVERAGE:Average\:%3.2lf%%', 'GPRINT:a:MAX:Maximum\:%3.2lf%%\\n', 'LINE1:b#002A97FF:outdoor hum', 'GPRINT:b:LAST:Current\:%3.2lf%%', 'GPRINT:b:AVERAGE:Average\:%3.2lf%%', 'GPRINT:b:MAX:Maximum\:%3.2lf%%') rrdtool.graph( '/usr/local/nginx/html/images/hum_test-1month.png', '--start','end-1m', '--end','00:00', '--title','室内室外湿度', '--vertical-label','hum %', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','0', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_hum:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_hum:AVERAGE', 'LINE1:a#00CF00FF:indoor hum', 'GPRINT:a:LAST: Current\:%3.2lf%%', 'GPRINT:a:AVERAGE:Average\:%3.2lf%%', 'GPRINT:a:MAX:Maximum\:%3.2lf%%\\n', 'LINE1:b#002A97FF:outdoor hum', 'GPRINT:b:LAST:Current\:%3.2lf%%', 'GPRINT:b:AVERAGE:Average\:%3.2lf%%', 'GPRINT:b:MAX:Maximum\:%3.2lf%%') rrdtool.graph( '/usr/local/nginx/html/images/hum_test-1year.png', '--start','end-1y', '--end','00:00', '--title','室内室外湿度', '--vertical-label','hum %', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','0', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_hum:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_hum:AVERAGE', 'LINE1:a#00CF00FF:indoor hum', 'GPRINT:a:LAST: Current\:%3.2lf%%', 'GPRINT:a:AVERAGE:Average\:%3.2lf%%', 'GPRINT:a:MAX:Maximum\:%3.2lf%%\\n', 'LINE1:b#002A97FF:outdoor hum', 'GPRINT:b:LAST:Current\:%3.2lf%%', 'GPRINT:b:AVERAGE:Average\:%3.2lf%%', 'GPRINT:b:MAX:Maximum\:%3.2lf%%') rrdtool.graph( '/usr/local/nginx/html/images/hic_test-1day.png', '--title','室内室外体感温度', '--vertical-label','hic °C', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','-10', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_hic:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_hic:AVERAGE', 'LINE1:a#00CF00FF:indoor hic', 'GPRINT:a:LAST: Current\:%2.2lf°C', 'GPRINT:a:AVERAGE:Average\:%2.2lf°C', 'GPRINT:a:MAX:Maximum\:%2.2lf°C\\n', 'LINE1:b#002A97FF:outdoor hic', 'GPRINT:b:LAST:Current\:%2.2lf°C', 'GPRINT:b:AVERAGE:Average\:%2.2lf°C', 'GPRINT:b:MAX:Maximum\:%2.2lf°C') rrdtool.graph( '/usr/local/nginx/html/images/hic_test-1week.png', '--start','end-1w', '--end','00:00', '--title','室内室外体感温度', '--vertical-label','hic °C', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','-10', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_hic:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_hic:AVERAGE', 'LINE1:a#00CF00FF:indoor hic', 'GPRINT:a:LAST: Current\:%2.2lf°C', 'GPRINT:a:AVERAGE:Average\:%2.2lf°C', 'GPRINT:a:MAX:Maximum\:%2.2lf°C\\n', 'LINE1:b#002A97FF:outdoor hic', 'GPRINT:b:LAST:Current\:%2.2lf°C', 'GPRINT:b:AVERAGE:Average\:%2.2lf°C', 'GPRINT:b:MAX:Maximum\:%2.2lf°C') rrdtool.graph( '/usr/local/nginx/html/images/hic_test-1month.png', '--start','end-1m', '--end','00:00', '--title','室内室外体感温度', '--vertical-label','hic °C', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','-10', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_hic:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_hic:AVERAGE', 'LINE1:a#00CF00FF:indoor hic', 'GPRINT:a:LAST: Current\:%2.2lf°C', 'GPRINT:a:AVERAGE:Average\:%2.2lf°C', 'GPRINT:a:MAX:Maximum\:%2.2lf°C\\n', 'LINE1:b#002A97FF:outdoor hic', 'GPRINT:b:LAST:Current\:%2.2lf°C', 'GPRINT:b:AVERAGE:Average\:%2.2lf°C', 'GPRINT:b:MAX:Maximum\:%2.2lf°C') rrdtool.graph( '/usr/local/nginx/html/images/hic_test-1year.png', '--start','end-1y', '--end','00:00', '--title','室内室外体感温度', '--vertical-label','hic °C', '--alt-autoscale-max', '--slope-mode', '--watermark',time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()), '--lower-limit','-10', '--font','TITLE:10:', '--font','AXIS:7:', '--font','LEGEND:8:', '--font','UNIT:7:', '--font','WATERMARK:7:', 'DEF:a=/home/ubuntu/test2.rrd:in_hic:AVERAGE', 'DEF:b=/home/ubuntu/test2.rrd:out_hic:AVERAGE', 'LINE1:a#00CF00FF:indoor hic', 'GPRINT:a:LAST: Current\:%2.2lf°C', 'GPRINT:a:AVERAGE:Average\:%2.2lf°C', 'GPRINT:a:MAX:Maximum\:%2.2lf°C\\n', 'LINE1:b#002A97FF:outdoor hic', 'GPRINT:b:LAST:Current\:%2.2lf°C', 'GPRINT:b:AVERAGE:Average\:%2.2lf°C', 'GPRINT:b:MAX:Maximum\:%2.2lf°C')
在系统cron中添加一个任务,每5分钟执行一次脚本,生成rrd图。
*/5 * * * * sudo python3 /home/ubuntu/rrdtool_graph.py >> /tmp/temp_hum_rrdtool_graph.log 2>&1
在nginx的html根目录下新建index.html文件,将生成的rrd图放到html页面中,这样就可以通过网页查看曲线图了。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> <meta http-equiv="Expires" content="0"> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-control" content="no-cache"> <meta http-equiv="Cache" content="no-cache"> <meta http-equiv="refresh" content="300"> <title>温度湿度监控</title> </head> <body> <table> <tr> <td> <img src="images/temp_test-1day.png" alt="1day"> 1day </br> <img src="images/temp_test-1week.png" alt="1week"> 1week </br> <img src="images/temp_test-1month.png" alt="1month"> 1month </br> <img src="images/temp_test-1year.png" alt="1year"> 1year </td> <td> <img src="images/hum_test-1day.png" alt="1day"> 1day </br> <img src="images/hum_test-1week.png" alt="1week"> 1week </br> <img src="images/hum_test-1month.png" alt="1month"> 1month </br> <img src="images/hum_test-1year.png" alt="1year"> 1year </td> <td> <img src="images/hic_test-1day.png" alt="1day"> 1day </br> <img src="images/hic_test-1week.png" alt="1week"> 1week </br> <img src="images/hic_test-1month.png" alt="1month"> 1month </br> <img src="images/hic_test-1year.png" alt="1year"> 1year </td> </tr> </table> </body> </html>
节目预告:
下一篇文章我们将把这个系统进行改造,使用博世bme280传感器来代替DHT22传感器,通过bme280传感器我们也可以得到大气压数据。开发板改为ESP8266,将原来串口输出的数据改为wifi连接,将数据以mqtt形式发布到mqtt broker,使用domoticz来订阅这个主题,以达到将传感器数据传入domoticz的目的。
今天的文章
树莓派温度实时温度采集_树莓派温度实时温度采集分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/60602.html