1. 项目概述:用树莓派打造一个温度追踪器
最近在整理工作室的设备,发现一个老款的树莓派4B闲置着,琢磨着给它找个活儿干。正好,工作室里有个小温室,里面养了些对温度比较敏感的植物,还有一台3D打印机,打印ABS材料时对腔体温度也有要求。手动拿温度计去测,不仅麻烦,数据还不连续。于是,我就想,能不能用这个树莓派做个持续工作的温度追踪器?这个想法听起来简单,但真做起来,从传感器选型、数据采集、到后端存储和前段展示,每一步都有不少门道。
这个“Temperature Tracker using Raspberry Pi”项目,本质上是一个物联网(IoT)数据采集与监控系统。它的核心功能是让树莓派这个微型电脑,连接上一个或多个温度传感器,以设定的频率(比如每分钟一次)读取环境温度数据,然后将这些数据保存下来,并通过一个网页界面实时展示和历史查询。它解决的痛点就是低成本、自动化、可远程访问的持续环境监测。无论是想监控家里的地下室是否潮湿阴冷,还是想记录鱼缸的水温变化,甚至是观测服务器机柜的内部温度,这个方案都特别合适。你不需要是个电子或编程专家,只要跟着步骤一步步来,花上一个下午的时间,就能拥有一个属于自己的、24小时不间断工作的温度监控站。
2. 核心硬件选型与电路连接解析
2.1 传感器选型:DS18B20 vs DHT11/DHT22
做温度监测,第一步就是挑传感器。市面上常见的、适合树莓派的有两类:单总线的DS18B20和数字温湿度一体的DHT11/DHT22。
DS18B20是我这次项目的首选,原因有几个。首先,它的精度足够高,典型精度在±0.5°C,对于绝大多数环境监测场景完全够用。其次,它采用独特的“单总线”协议,只需要一根数据线(外加电源和地线)就能与树莓派通信,这意味着你可以用树莓派的一个GPIO引脚,通过并联的方式连接多个传感器,给不同房间或位置编号,实现多点监测,布线非常简洁。最后,它本身是防水的,有封装好的探头版本,可以直接扔进水里测水温,应用场景更广。
DHT11/DHT22这类传感器能同时提供温度和湿度数据,对于需要温湿度共同监测的场景(如温室、仓库)是很好的选择。DHT22的精度和量程比DHT11更好。但它们使用的是自定义的单线通信协议,对时序要求严格,编程稍复杂,且响应速度不如DS18B20,在需要快速或高密度采样的场景下可能不太合适。
注意:如果你只需要温度数据,且可能有多点监测需求,DS18B20是更清晰、更可靠的选择。如果需要温湿度,且采样频率不高(如每分钟一次),DHT22是性价比之选。
2.2 所需物料清单与电路连接
硬件部分非常简单,除了树莓派(任何型号均可,推荐3B+或4B,性能更充裕),你还需要:
- DS18B20温度传感器(建议购买已焊接好引线和上拉电阻的模块,省事)。
- 4.7kΩ - 10kΩ的电阻一枚(用于单总线的上拉,如果模块已集成则不需要)。
- 母对母杜邦线若干(用于连接)。
- 一个面包板(非必须,但方便测试和扩展)。
连接电路是电子项目的“Hello World”。树莓派有40个GPIO针脚,我们需要用到3.3V电源、接地(GND)和一个数据引脚。
接线步骤(以DS18B20为例):
- 电源:将传感器的VCC(或红色线)连接到树莓派的3.3V引脚(例如物理引脚1)。切记不要接到5V引脚,虽然DS18B20兼容5V,但为了保险和统一,使用3.3V更安全。
- 接地:将传感器的GND(或黑色线)连接到树莓派的任意GND引脚(例如物理引脚6)。
- 数据:将传感器的DATA(或黄色线)连接到树莓派的一个GPIO引脚,例如我习惯用GPIO4,它对应物理引脚7。
- 上拉电阻:在传感器的DATA线和3.3V电源线之间,连接一个4.7kΩ的电阻。这个电阻至关重要,它用于在总线空闲时将数据线稳定在高电平,确保通信可靠。很多成品模块已经集成了这个电阻。
连接好后,你的电路看起来应该非常简洁:三根线从传感器出来,分别接到树莓派的3.3V、GND和GPIO4,然后在GPIO4和3.3V之间多了一个电阻(如果模块没有的话)。
2.3 树莓派系统准备与基础配置
在连接硬件之前,我们需要确保树莓派的系统就绪。我使用的是官方的Raspberry Pi OS(以前叫Raspbian),用SD卡刷好系统后,首次启动完成基础设置。
有几个关键的系统配置需要提前做:
- 启用SSH和VNC:为了方便后续无头(无显示器)操作,在系统首选项的“Raspberry Pi Configuration”工具中,找到“Interfaces”标签页,将SSH和VNC都启用。这样你就可以用另一台电脑通过命令行或远程桌面来控制树莓派了。
- 更新系统:打开终端,首先运行
sudo apt update和sudo apt upgrade -y,确保所有软件包都是最新的。一个更新的系统能避免很多奇怪的兼容性问题。 - 启用单总线(1-Wire)接口:这是让DS18B20工作的关键。同样在“Raspberry Pi Configuration”的“Interfaces”标签页,找到“1-Wire”选项,将其启用。你也可以通过命令行操作:
sudo raspi-config,然后依次选择Interface Options->1-Wire->Yes,完成后重启。
重启后,你可以验证1-Wire是否启用成功。在终端输入lsmod | grep w1,如果看到w1_gpio和w1_therm模块,就说明驱动加载成功了。
3. 软件架构设计与核心组件部署
一个完整的温度追踪器,不仅仅是读一下传感器数据。我们需要一个稳定、可扩展的软件架构来负责数据采集、存储和展示。我设计的架构分为三层:数据采集层、数据服务层和数据展示层。
3.1 数据采集层:Python脚本与定时任务
采集层的工作是用Python脚本与DS18B20传感器通信,读取温度值。得益于Linux内核的支持,DS18B20的数据会以文件形式出现在/sys/bus/w1/devices/目录下。每个传感器都有一个唯一的ID(如28-xxxx),对应一个文件夹,里面的w1_slave文件就包含了温度数据。
我写了一个简单的Python脚本read_temperature.py:
import os import time import glob from datetime import datetime # 1-Wire设备的基础路径 base_dir = '/sys/bus/w1/devices/' # 找到所有以“28-”开头的设备文件夹,即DS18B20传感器 device_folders = glob.glob(base_dir + '28-*') def read_temp_raw(device_file): """读取传感器原始数据文件""" with open(device_file, 'r') as f: lines = f.readlines() return lines def read_temp(device_folder): """从原始数据中解析出摄氏度温度值""" device_file = device_folder + '/w1_slave' lines = read_temp_raw(device_file) # 检查数据是否有效(CRC校验) while lines[0].strip()[-3:] != 'YES': time.sleep(0.2) lines = read_temp_raw(device_file) 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 return None if __name__ == '__main__': for device_folder in device_folders: sensor_id = device_folder.split('/')[-1] # 提取传感器ID temperature = read_temp(device_folder) if temperature is not None: timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 打印到控制台,后续会改为写入数据库 print(f"{timestamp} - Sensor {sensor_id}: {temperature:.2f}°C") else: print(f"Failed to read from sensor {sensor_id}")这个脚本的核心逻辑是定位传感器、读取文件、校验数据、解析温度。现在它只是打印出来,我们需要让它定期运行。这里我使用Linux系统自带的Cron定时任务。在终端输入crontab -e,然后在文件末尾添加一行:
*/5 * * * * /usr/bin/python3 /home/pi/temperature_tracker/read_temperature.py >> /home/pi/temperature.log 2>&1这行配置的意思是:每5分钟,用python3运行一次我们的脚本,并将输出(包括错误信息)追加到temperature.log日志文件中。这样,数据采集的自动化就完成了。
3.2 数据服务层:轻量级数据库选型与表设计
将数据打印到日志文件不是长久之计,不利于查询和分析。我们需要一个数据库。对于树莓派这种资源有限的设备,SQLite是绝佳选择。它无需安装独立的数据库服务,整个数据库就是一个文件,零配置,通过Python标准库sqlite3就能直接操作。
我们在采集脚本里加入数据库操作。首先,创建一个数据库和表:
import sqlite3 DB_PATH = '/home/pi/temperature_data.db' def init_database(): conn = sqlite3.connect(DB_PATH) c = conn.cursor() # 创建数据表,包含时间戳、传感器ID和温度值 c.execute('''CREATE TABLE IF NOT EXISTS temperature_readings (timestamp DATETIME, sensor_id TEXT, temperature REAL)''') # 创建索引以提高按时间和传感器查询的速度 c.execute('CREATE INDEX IF NOT EXISTS idx_time ON temperature_readings(timestamp)') c.execute('CREATE INDEX IF NOT EXISTS idx_sensor ON temperature_readings(sensor_id)') conn.commit() conn.close()然后,修改主循环,将读取到的数据插入数据库:
def save_to_database(sensor_id, temperature): conn = sqlite3.connect(DB_PATH) c = conn.cursor() timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') c.execute("INSERT INTO temperature_readings VALUES (?, ?, ?)", (timestamp, sensor_id, temperature)) conn.commit() conn.close() # 在主循环中调用 # temperature = read_temp(device_folder) # save_to_database(sensor_id, temperature)使用SQLite,我们几乎不增加系统负担,就能实现数据的结构化存储。表结构设计简单明了:时间、传感器ID、温度值。加上索引后,即使数据积累到几十万条,查询速度也依然很快。
3.3 数据展示层:使用Flask构建Web仪表盘
数据存好了,我们还需要一个直观的方式来看。在树莓派上搭建一个轻量的Web服务器是最佳选择。Python的Flask框架简单易用,非常适合这个任务。
首先安装Flask:pip install flask。然后创建一个新的Python文件app.py:
from flask import Flask, render_template, jsonify import sqlite3 from datetime import datetime, timedelta import json app = Flask(__name__) DB_PATH = '/home/pi/temperature_data.db' @app.route('/') def index(): """主页面,展示图表""" return render_template('index.html') @app.route('/api/current') def get_current_temp(): """API接口:获取所有传感器的最新温度""" conn = sqlite3.connect(DB_PATH) c = conn.cursor() # 获取每个传感器最近的一条记录 c.execute('''SELECT sensor_id, temperature, timestamp FROM temperature_readings WHERE timestamp = (SELECT MAX(timestamp) FROM temperature_readings AS tr2 WHERE tr2.sensor_id = temperature_readings.sensor_id) ORDER BY sensor_id''') data = [{'sensor_id': row[0], 'temperature': row[1], 'timestamp': row[2]} for row in c.fetchall()] conn.close() return jsonify(data) @app.route('/api/history/<sensor_id>') def get_history(sensor_id): """API接口:获取指定传感器过去24小时的数据,用于绘图""" conn = sqlite3.connect(DB_PATH) c = conn.cursor() time_threshold = (datetime.now() - timedelta(hours=24)).strftime('%Y-%m-%d %H:%M:%S') c.execute('''SELECT timestamp, temperature FROM temperature_readings WHERE sensor_id = ? AND timestamp > ? ORDER BY timestamp''', (sensor_id, time_threshold)) data = [{'time': row[0], 'temperature': row[1]} for row in c.fetchall()] conn.close() return jsonify(data) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False) # 生产环境务必设置debug=False这个Flask应用提供了两个核心API:/api/current返回当前最新温度,/api/history/<sensor_id>返回某个传感器过去24小时的历史数据。前端页面通过调用这些API来获取JSON格式的数据。
4. 前端可视化与系统集成实战
4.1 使用Chart.js绘制动态温度曲线
有了后端API,前端页面就负责把数据漂亮地展示出来。我在项目根目录下创建了一个templates文件夹,在里面新建index.html。为了绘制曲线图,我选择了Chart.js这个轻量而强大的JavaScript图表库。
index.html的核心部分如下:
<!DOCTYPE html> <html> <head> <title>树莓派温度监控仪表盘</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <style> body { font-family: sans-serif; margin: 20px; } .dashboard { display: flex; flex-wrap: wrap; } .sensor-card { border: 1px solid #ccc; border-radius: 5px; padding: 15px; margin: 10px; min-width: 300px; } .current-temp { font-size: 2em; font-weight: bold; } .chart-container { width: 100%; height: 300px; } </style> </head> <body> <h1>🌡️ 树莓派温度监控系统</h1> <div id="currentDashboard" class="dashboard"> <!-- 这里会动态插入每个传感器的当前温度卡片 --> </div> <div id="historyCharts" class="dashboard"> <!-- 这里会动态插入每个传感器的历史曲线图 --> </div> <script> // 1. 获取并显示当前温度 fetch('/api/current') .then(response => response.json()) .then(data => { const dashboard = document.getElementById('currentDashboard'); dashboard.innerHTML = ''; // 清空 data.forEach(sensor => { const card = document.createElement('div'); card.className = 'sensor-card'; card.innerHTML = ` <h3>传感器: ${sensor.sensor_id}</h3> <p>最新温度: <span class="current-temp">${sensor.temperature.toFixed(2)}°C</span></p> <p>更新时间: ${sensor.timestamp}</p> `; dashboard.appendChild(card); }); }); // 2. 获取并绘制历史曲线 // 假设我们已知传感器ID,或者可以从第一个API响应中获取 const sensorIds = ['28-xxxxxxxxxxxx', '28-xxxxxxxxxxxy']; // 这里需要替换成你的实际传感器ID sensorIds.forEach(sensorId => { fetch(`/api/history/${sensorId}`) .then(response => response.json()) .then(data => { const container = document.getElementById('historyCharts'); const chartDiv = document.createElement('div'); chartDiv.className = 'sensor-card'; chartDiv.innerHTML = `<h3>${sensorId} - 24小时趋势</h3><canvas id="chart-${sensorId}"></canvas>`; container.appendChild(chartDiv); const ctx = document.getElementById(`chart-${sensorId}`).getContext('2d'); new Chart(ctx, { type: 'line', data: { labels: data.map(d => d.time.substring(11, 16)), // 只取时分 datasets: [{ label: '温度 (°C)', data: data.map(d => d.temperature), borderColor: 'rgb(75, 192, 192)', tension: 0.1 }] }, options: { responsive: true, maintainAspectRatio: false } }); }); }); </script> </body> </html>这个页面会先向/api/current请求数据,动态生成显示当前温度的卡片。然后,针对每个已知的传感器ID(这里需要你手动替换成实际的ID),请求其24小时历史数据,并用Chart.js绘制出漂亮的折线图。图表是交互式的,可以缩放、平移,方便查看细节。
4.2 系统服务化与开机自启动
我们不希望每次重启树莓派后,都要手动去启动Flask应用和Cron任务(Cron本身是系统服务,无需额外处理)。我们需要将Flask应用设置为系统服务,实现开机自启和后台稳定运行。
创建一个系统服务文件:sudo nano /etc/systemd/system/temperature-tracker.service
[Unit] Description=Temperature Tracker Web Interface After=network.target [Service] User=pi WorkingDirectory=/home/pi/temperature_tracker ExecStart=/usr/bin/python3 /home/pi/temperature_tracker/app.py Restart=always RestartSec=10 StandardOutput=syslog StandardError=syslog SyslogIdentifier=temp_tracker [Install] WantedBy=multi-user.target这个配置文件告诉系统:以用户pi的身份,在指定目录下运行我们的app.py。如果程序意外崩溃,系统会在10秒后自动重启它(Restart=always)。
保存后,执行以下命令启用并启动服务:
sudo systemctl daemon-reload # 重新加载服务配置 sudo systemctl enable temperature-tracker.service # 启用开机自启 sudo systemctl start temperature-tracker.service # 立即启动服务 sudo systemctl status temperature-tracker.service # 检查服务状态看到状态显示为“active (running)”就大功告成了。现在,你的温度追踪Web界面已经作为一个守护进程在后台运行,即使树莓派重启也会自动拉起。
4.3 内网穿透与远程访问配置
到目前为止,我们的仪表盘只能在家庭局域网内通过http://树莓派IP:5000访问。如果你想在外面(比如在公司)也能查看家里的温度,就需要内网穿透。
这里我使用Cloudflare Tunnel(以前叫 Argo Tunnel)作为例子,因为它免费、安全且配置相对简单。首先,你需要在Cloudflare上注册账号,并添加你的域名(可以是一个免费的子域名)。
- 在树莓派上安装Cloudflare的客户端:
sudo apt install cloudflared - 运行认证命令:
cloudflared tunnel login,它会打开一个浏览器页面让你授权。 - 创建隧道:
cloudflared tunnel create temperature-tracker,记下返回的隧道ID(一个UUID)。 - 创建配置文件:在
~/.cloudflared/config.yml中写入:tunnel: <你的隧道ID> credentials-file: /home/pi/.cloudflared/<隧道ID>.json ingress: - hostname: temp.yourdomain.com # 你的子域名 service: http://localhost:5000 - service: http_status:404 - 在Cloudflare DNS面板,为你指定的子域名(如
temp)创建一条CNAME记录,指向<隧道ID>.cfargotunnel.com。 - 运行隧道:
cloudflared tunnel run temperature-tracker。为了让它也在后台运行,同样可以将其配置为系统服务。
配置成功后,你就可以通过https://temp.yourdomain.com这个地址,在任何有网络的地方访问你的温度监控仪表盘了。
5. 项目优化、问题排查与扩展思路
5.1 性能优化与数据管理
随着系统长时间运行,数据库会越来越大。虽然SQLite很轻量,但无限制增长也会影响查询效率。我们需要一个数据清理策略。
一个常见的做法是只保留一定时间内的详细数据,比如过去30天。对于更早的数据,可以按小时或天进行聚合(计算平均值、最大值、最小值),然后删除原始数据,只保留聚合结果。这可以通过一个额外的Cron任务来实现:
# 编辑crontab: crontab -e # 每天凌晨3点执行数据清理和聚合脚本 0 3 * * * /usr/bin/python3 /home/pi/temperature_tracker/cleanup_db.pycleanup_db.py脚本的内容可以包括:
- 删除超过30天的原始数据。
- 将30天前、昨天之前的数据,按传感器ID和小时进行聚合,存入另一张
temperature_summary表。 - 在Flask应用中,可以为“历史数据”API增加一个参数,比如
?period=week时查询汇总表,period=day时查询详细表。
5.2 常见问题与排查技巧
在实际部署中,你可能会遇到以下问题:
传感器读数为85°C或-127°C等异常值:
- 原因:这是DS18B20的典型错误值,通常表示通信失败或传感器接触不良。
- 排查:首先检查物理连接,确保杜邦线插紧,没有虚接。然后检查上拉电阻是否连接正确(4.7kΩ电阻是否接在数据线和3.3V之间)。最后,在终端输入
ls /sys/bus/w1/devices/,看是否能列出你的传感器(28-开头的文件夹)。如果看不到,说明1-Wire驱动未正确加载或传感器未识别,尝试重启树莓派或重新插拔传感器。
Flask网页无法访问:
- 原因:防火墙阻止了5000端口,或者Flask服务没有运行。
- 排查:首先
sudo systemctl status temperature-tracker.service查看服务状态。如果是inactive,检查app.py是否有语法错误,或者日志文件(sudo journalctl -u temperature-tracker.service)中的错误信息。如果服务是active,则检查防火墙:sudo ufw status。如果防火墙开启,需要允许5000端口:sudo ufw allow 5000。
网页图表不显示或数据为空:
- 原因:前端JavaScript无法从后端API获取到数据,可能是API路径错误、CORS问题(如果前后端分离部署)或数据库为空。
- 排查:打开浏览器的开发者工具(F12),切换到“网络”(Network)标签页,刷新页面,查看对
/api/current等接口的请求是否成功(状态码应为200)。如果失败,查看控制台(Console)是否有错误信息。确保Flask应用运行在正确的host (0.0.0.0) 和端口上。
系统运行一段时间后变卡:
- 原因:可能是内存或CPU占用过高。树莓派资源有限,如果同时运行太多服务,或者Python脚本有内存泄漏,会导致问题。
- 排查:使用
htop命令查看系统资源占用。检查Cron任务是否过于频繁(不要低于1分钟一次),Flask的debug模式是否在生产环境中被意外开启(会非常耗资源)。
5.3 功能扩展与进阶玩法
基础系统搭建完成后,你可以根据需求进行丰富扩展:
- 多传感器与位置映射:连接多个DS18B20,在数据库中不仅记录传感器ID,还记录一个“位置”字段(如“客厅”、“鱼缸”、“机房机柜上”)。在前端展示时,用位置名称代替难记的ID。
- 阈值告警:在数据采集脚本中,加入逻辑判断。如果某个传感器的温度超过或低于预设的阈值,就触发告警。告警方式可以很多样:
- 发送电子邮件:使用Python的
smtplib库。 - 发送App推送:使用Pushover、Bark等服务。
- 发送短信:使用Twilio等API(通常收费)。
- 控制继电器:如果温度过高,自动控制一个连接在树莓派上的继电器,打开风扇或关闭加热器,实现简单的闭环控制。
- 发送电子邮件:使用Python的
- 数据导出与分析:在Flask应用中增加一个路由,允许用户以CSV格式导出选定时间范围的数据,方便用Excel或专业软件进行更深入的分析。
- 集成到智能家居平台:如果你使用Home Assistant,可以将树莓派上的温度数据通过MQTT协议发布出去。在采集脚本中,使用
paho-mqtt库,将读取到的温度值发布到像home/sensor/temperature/room1这样的主题,Home Assistant就能自动发现并集成这个传感器,实现更复杂的自动化联动。
这个项目就像一颗种子,从最简单的读取一个温度值开始,可以生长出各种有趣的应用。它的价值不在于用了多高深的技术,而在于提供了一个完整的、可落地的物联网原型,让你亲手触摸到从物理信号到数字数据,再到可视化图表和网络服务的全过程。