ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

树莓派环境监控实战:从传感器选型到Web可视化与报警系统

2026/8/20 1:23:38 拓冰建站 浏览量
树莓派环境监控实战:从传感器选型到Web可视化与报警系统 1. 项目缘起为什么用树莓派做温度监控几年前我负责一个存放精密仪器的仓库环境监控项目。当时市面上现成的温湿度记录仪要么功能单一、数据导出麻烦要么价格昂贵且无法远程实时查看。一个偶然的机会我看到角落里吃灰的树莓派突然想到这玩意儿不就是一台微型电脑吗它有USB口、GPIO引脚能联网还能跑Python为什么不能让它来干这个活这个想法催生了我的第一个树莓派温度监控项目。从最初简陋的脚本到后来稳定运行数年的完整系统我踩过不少坑也积累了大量实战经验。今天我就把这个从零到一的完整过程分享出来。无论你是想监控家里的温室、鱼缸、服务器机柜还是像我一样有小型仓储或实验室的监控需求这个基于树莓派的方案都极具性价比和灵活性。它不仅能实时显示数据还能记录历史、设置报警甚至通过网页远程访问核心成本可能还不到一张红票子指百元人民币。2. 核心硬件选型与连接不止于一个传感器很多人一提到温度监控第一反应就是买个DS18B20传感器接上。这没错但它只是起点。一个可靠的监控系统硬件选型决定了系统的稳定性、精度和扩展性。2.1 传感器篇DS18B20、DHT22与BME280的抉择首先你需要一个“感觉器官”。市面上常见的有三种我逐一分析其优劣。DS18B20这是最经典的数字温度传感器采用独特的“单总线”协议。它的优点是抗干扰能力强、精度较高±0.5°C、价格极其低廉而且支持“一线多挂”即一根数据线可以串联多个传感器每个有唯一ID非常适合需要监测多点如机房不同机架的场景。它的缺点是只能测温度。接线时它有三根线VCC3.3V、GND、DATA。DATA引脚需要接一个4.7kΩ的上拉电阻到3.3V这是稳定通信的关键很多新手会忽略这点导致读取失败。DHT22 (AM2302)这是一个温湿度二合一传感器。它能同时提供温度和湿度数据湿度精度±2%RH温度精度±0.5°C。它使用单线双向串行接口通信协议比DS18B20稍复杂。它的优点是集成度高、性价比较好。缺点是响应速度较慢约2秒一次、长距离通信稳定性稍弱。对于需要同时关注湿度的应用如温室、家居它是很好的选择。BME280这是“毕业款”传感器。它由博世生产能同时测量温度、湿度、气压。它精度高、稳定性好采用I2C或SPI接口通信速度快且可靠。I2C接口只需连接SDA、SCL、VCC、GND四根线树莓派原生支持无需额外上拉电阻板载通常已有。它的缺点是价格是前两者的数倍。如果你的项目预算充足或者需要气压数据例如用于天气预报或高度估算BME280是毋庸置疑的最佳选择。我的选择建议对于纯温度监控DS18B20足矣需要温湿度且预算有限选DHT22追求高精度、高稳定性和气压数据直接上BME280。我自己的长期运行系统最终都换成了BME280。2.2 树莓派型号与供电稳定性的基石传感器选好了我们来看看“大脑”。任何一款树莓派Zero, 3B, 4B都能胜任此工作。我的建议是树莓派Zero W/WH性价比之王体积小巧自带Wi-Fi和蓝牙功耗极低。适合固定安装、对性能要求不高的场景。注意它的GPIO是排针需要焊接或使用夹子。树莓派4B性能过剩但接口丰富。如果你未来可能在同一设备上扩展摄像头、运行更复杂的Web服务4B是更稳妥的选择。它的功耗和发热也更高。供电是树莓派稳定运行的第一生命线。务必使用官方推荐或质量可靠的5V/3A电源适配器。劣质电源导致的电压不稳是树莓派无故重启、SD卡损坏的罪魁祸首。对于需要7x24小时运行的项目电源上不要省钱。2.3 电路连接实战以BME280 (I2C) 和 DS18B20为例理论说完我们动手连接。假设我们使用BME280和DS18B20两种传感器实现温度和温湿度的冗余监测。BME280 (I2C接口) 连接确保树莓派已关机。将BME280的VIN引脚连接到树莓派的3.3V引脚例如物理引脚1。将BME280的GND引脚连接到树莓派的GND引脚例如物理引脚6。将BME280的SCL引脚连接到树莓派的GPIO3 (SCL)引脚物理引脚5。将BME280的SDA引脚连接到树莓派的GPIO2 (SDA)引脚物理引脚3。 连接完毕无需额外电阻。DS18B20 (单总线) 连接将DS18B20的VDD红线连接到树莓派的3.3V引脚。将DS18B20的GND黑线连接到树莓派的GND引脚。将DS18B20的DQ数据线通常是黄线或白线连接到树莓派的GPIO4物理引脚7。这是一个常用引脚系统驱动对其有良好支持。关键一步在DQ数据线和3.3V电源线之间连接一个4.7kΩ的电阻。这个上拉电阻确保数据线在空闲时处于高电平是通信稳定的必要条件。连接好后硬件部分就准备好了。接下来我们让树莓派“认识”这些传感器。3. 系统软件配置从驱动到Python环境硬件通电后树莓派并不会自动识别传感器。我们需要在操作系统中进行一些配置。3.1 启用接口与安装驱动首先通过SSH或直接连接屏幕键盘登录到你的树莓派系统推荐使用Raspberry Pi OS Lite版本以减少资源占用。启用I2C接口为BME280运行sudo raspi-config。选择Interface Options-I2C。当询问是否启用ARM I2C接口时选择Yes。退出raspi-config并重启树莓派sudo reboot。启用单总线接口为DS18B20编辑启动配置文件sudo nano /boot/config.txt在文件末尾添加一行dtoverlayw1-gpio如果你使用的不是GPIO4默认例如使用GPIO17则需要改为dtoverlayw1-gpio,gpiopin17保存CtrlX然后按Y回车并重启。重启后我们可以验证传感器是否被识别。验证BME280安装I2C工具sudo apt install i2c-tools -y扫描I2C设备sudo i2cdetect -y 1如果BME280连接正常你会看到类似76或77的地址出现在输出表格中具体地址取决于传感器的SDO引脚电平。验证DS18B20加载内核模块sudo modprobe w1-gpio和sudo modprobe w1-therm。进入设备目录cd /sys/bus/w1/devices/列出设备ls你应该能看到一个以28-开头的长文件夹名例如28-01131a1e0eff这就是你的DS18B20。进入该文件夹cat w1_slave即可看到原始温度数据。3.2 Python环境与库安装我们的核心逻辑将用Python编写因为它拥有丰富的库和极高的开发效率。更新系统并安装Python3及pip通常已预装sudo apt update sudo apt upgrade -y sudo apt install python3-pip python3-venv -y为项目创建独立的虚拟环境强烈推荐避免污染系统Python环境mkdir ~/temperature_tracker cd ~/temperature_tracker python3 -m venv venv source venv/bin/activate看到命令行提示符前出现(venv)即表示激活成功。安装必要的Python库对于BME280 (I2C)pip install RPi.bme280或pip install adafruit-circuitpython-bme280Adafruit的库功能更全。对于DHT22pip install Adafruit_DHT。对于DS18B20我们可以直接读取系统文件但使用w1thermsensor库更简单pip install w1thermsensor。通用工具库pip install schedule用于定时任务、pip install flask如果要做Web界面。避坑提示安装Adafruit_DHT或某些需要编译的库时可能会报错缺少开发工具。请先运行sudo apt install python3-dev libgpiod2安装依赖。如果使用Adafruit的BME280库可能还需要sudo apt install libgpiod2。4. 核心数据采集脚本编写稳定与精准之道硬件就绪环境配好现在我们来编写核心的数据采集脚本。我将展示一个同时读取BME280和DS18B20的健壮版脚本。4.1 单次数据读取函数我们首先编写读取单个传感器的函数并加入异常处理。这是系统稳定的关键。# sensor_reader.py import time import logging from datetime import datetime # 配置日志便于调试和记录错误 logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) def read_bme280(): 读取BME280传感器的温度、湿度、气压数据。 返回字典失败返回None。 try: # 使用Adafruit库示例 import board import adafruit_bme280 # 创建I2C对象 i2c board.I2C() # 使用板载I2C bme280 adafruit_bme280.Adafruit_BME280_I2C(i2c) # 可设置补偿参数根据海拔等 # bme280.sea_level_pressure 1013.25 # 读取数据 temperature bme280.temperature # 摄氏度 humidity bme280.relative_humidity # 百分比 pressure bme280.pressure # 百帕 # 简单数据合理性校验 if -40 temperature 85 and 0 humidity 100: return { sensor: BME280, temp_c: round(temperature, 2), humidity: round(humidity, 2), pressure_hpa: round(pressure, 2), timestamp: datetime.now().isoformat() } else: logger.warning(fBME280读数异常: temp{temperature}, humidity{humidity}) return None except Exception as e: logger.error(f读取BME280时发生错误: {e}, exc_infoTrue) return None def read_ds18b20(): 读取DS18B20传感器的温度数据。 返回字典失败返回None。 try: from w1thermsensor import W1ThermSensor, SensorNotReadyError # 初始化传感器默认查找第一个 sensor W1ThermSensor() temperature sensor.get_temperature() # 摄氏度 # 数据校验 if -55 temperature 125: return { sensor: DS18B20, temp_c: round(temperature, 2), timestamp: datetime.now().isoformat() } else: logger.warning(fDS18B20读数异常: temp{temperature}) return None except SensorNotReadyError: logger.warning(DS18B20传感器未就绪可能正在转换温度。) return None except Exception as e: logger.error(f读取DS18B20时发生错误: {e}, exc_infoTrue) return None4.2 数据持久化从CSV到SQLite数据读出来需要保存。对于轻量级应用CSV文件最简单对于需要查询历史、数据量大的应用SQLite数据库更合适。方案一CSV文件存储import csv import os CSV_FILE temperature_data.csv def save_to_csv(data_dict): 将单次读取的数据字典追加到CSV文件 file_exists os.path.isfile(CSV_FILE) # 定义CSV文件的列头 fieldnames [timestamp, sensor, temp_c, humidity, pressure_hpa] with open(CSV_FILE, a, newline) as csvfile: writer csv.DictWriter(csvfile, fieldnamesfieldnames) # 如果文件不存在写入表头 if not file_exists: writer.writeheader() # 写入数据行缺失的字段会自动留空 writer.writerow(data_dict) logger.info(f数据已保存至CSV: {data_dict})方案二SQLite数据库存储推荐import sqlite3 from contextlib import contextmanager DB_FILE temperature.db contextmanager def get_db_connection(): 数据库连接上下文管理器自动处理连接和关闭 conn sqlite3.connect(DB_FILE) conn.row_factory sqlite3.Row # 允许以字典形式访问行 try: yield conn conn.commit() except Exception as e: conn.rollback() logger.error(f数据库操作失败: {e}) raise finally: conn.close() def init_database(): 初始化数据库创建表如果不存在 with get_db_connection() as conn: cursor conn.cursor() cursor.execute( CREATE TABLE IF NOT EXISTS sensor_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, sensor_type TEXT NOT NULL, temperature_c REAL, humidity REAL, pressure_hpa REAL ) ) # 为常用查询创建索引大幅提升查询速度 cursor.execute(CREATE INDEX IF NOT EXISTS idx_timestamp ON sensor_data(timestamp)) cursor.execute(CREATE INDEX IF NOT EXISTS idx_sensor ON sensor_data(sensor_type)) logger.info(数据库表初始化完成。) def save_to_db(data_dict): 将数据保存到SQLite数据库 with get_db_connection() as conn: cursor conn.cursor() cursor.execute( INSERT INTO sensor_data (sensor_type, temperature_c, humidity, pressure_hpa) VALUES (?, ?, ?, ?) , ( data_dict.get(sensor), data_dict.get(temp_c), data_dict.get(humidity), data_dict.get(pressure_hpa) )) logger.debug(f数据已插入数据库: {data_dict})经验之谈我强烈推荐使用SQLite。它几乎零配置单个文件易于备份并且支持复杂的SQL查询。当你有几百万条记录时CSV文件的读写和查询效率会变得非常低下。使用索引后SQLite的查询速度依然很快。4.3 主循环与定时任务我们需要一个主程序来周期性地读取传感器并保存数据。这里使用schedule库来管理定时任务它比简单的time.sleep更清晰。# main.py import schedule import time from sensor_reader import read_bme280, read_ds18b20 from data_logger import save_to_db, init_database def job(): 定时执行的任务读取所有传感器并保存 logger.info(--- 开始执行数据采集任务 ---) # 读取BME280 bme_data read_bme280() if bme_data: save_to_db(bme_data) else: logger.error(BME280数据读取失败本次跳过。) # 读取DS18B20 (作为冗余或对比) ds18_data read_ds18b20() if ds18_data: save_to_db(ds18_data) # 可以在这里添加简单的数据比对逻辑如果两个传感器温差过大则报警 if bme_data and ds18_data: temp_diff abs(bme_data[temp_c] - ds18_data[temp_c]) if temp_diff 2.0: # 假设温差超过2度视为异常 logger.warning(f传感器间温差过大BME280: {bme_data[temp_c]}°C, DS18B20: {ds18_data[temp_c]}°C) logger.info(--- 数据采集任务完成 ---) if __name__ __main__: # 初始化数据库 init_database() # 设置定时任务每5分钟执行一次 schedule.every(5).minutes.do(job) # 立即执行一次 job() logger.info(温度监控服务已启动每5分钟采集一次数据。按 CtrlC 停止。) # 主循环 try: while True: schedule.run_pending() time.sleep(1) # 每秒检查一次是否有任务需要执行 except KeyboardInterrupt: logger.info(服务被用户中断。) except Exception as e: logger.critical(f服务运行发生严重错误: {e}, exc_infoTrue)现在运行python main.py你的树莓派温度监控系统就开始工作了数据会按照设定间隔稳定地存入SQLite数据库。5. 数据可视化与报警让数据产生价值光有数据还不够我们需要直观地看到趋势并在异常时得到通知。5.1 构建简易Web仪表盘Flask Chart.js我们可以用Flask快速搭建一个本地网页用Chart.js绘制曲线图。# web_dashboard.py from flask import Flask, render_template, jsonify import sqlite3 from datetime import datetime, timedelta app Flask(__name__) DB_FILE temperature.db def get_recent_data(hours24, sensor_typeBME280): 从数据库获取最近N小时指定传感器的数据 conn sqlite3.connect(DB_FILE) conn.row_factory sqlite3.Row cursor conn.cursor() since_time (datetime.now() - timedelta(hourshours)).strftime(%Y-%m-%d %H:%M:%S) cursor.execute( SELECT timestamp, temperature_c, humidity, pressure_hpa FROM sensor_data WHERE sensor_type ? AND timestamp ? ORDER BY timestamp ASC , (sensor_type, since_time)) rows cursor.fetchall() conn.close() # 转换为列表字典 data [dict(row) for row in rows] return data app.route(/) def index(): 主页面展示图表 return render_template(dashboard.html) # 需要创建一个HTML模板 app.route(/api/data/recent) def api_recent_data(): API接口返回JSON格式的近期数据 data get_recent_data(hours24, sensor_typeBME280) return jsonify(data) app.route(/api/current) def api_current(): API接口返回最新的数据点 conn sqlite3.connect(DB_FILE) conn.row_factory sqlite3.Row cursor conn.cursor() cursor.execute( SELECT * FROM sensor_data WHERE sensor_type BME280 ORDER BY timestamp DESC LIMIT 1 ) row cursor.fetchone() conn.close() if row: return jsonify(dict(row)) else: return jsonify({error: No data found}), 404 if __name__ __main__: # 在树莓派上host0.0.0.0允许同一网络下的其他设备访问 app.run(host0.0.0.0, port5000, debugFalse) # 生产环境务必设置debugFalse对应的HTML模板 (templates/dashboard.html) 可以使用Chart.js来绘图这里提供一个极简示例框架!DOCTYPE html html head title树莓派温度监控仪表盘/title script srchttps://cdn.jsdelivr.net/npm/chart.js/script stylebody { font-family: sans-serif; margin: 20px; } .card { border: 1px solid #ccc; padding: 15px; margin-bottom: 20px; border-radius: 5px; }/style /head body h1️ 环境监控实时仪表盘/h1 div classcard h3当前状态/h3 div idcurrentStatus加载中.../div /div div classcard h3过去24小时温度趋势/h3 canvas idtempChart width800 height300/canvas /div div classcard h3过去24小时湿度趋势/h3 canvas idhumidityChart width800 height300/canvas /div script // 获取当前数据 fetch(/api/current) .then(response response.json()) .then(data { if(!data.error) { document.getElementById(currentStatus).innerHTML pstrong温度:/strong ${data.temperature_c} °C/p pstrong湿度:/strong ${data.humidity} %/p pstrong气压:/strong ${data.pressure_hpa} hPa/p pstrong更新时间:/strong ${new Date(data.timestamp).toLocaleString()}/p ; } }); // 获取历史数据并绘制图表 fetch(/api/data/recent) .then(response response.json()) .then(data { const labels data.map(d new Date(d.timestamp).toLocaleTimeString()); const temps data.map(d d.temperature_c); const humids data.map(d d.humidity); // 绘制温度图表 new Chart(document.getElementById(tempChart).getContext(2d), { type: line, data: { labels: labels, datasets: [{ label: 温度 (°C), data: temps, borderColor: rgb(255, 99, 132), tension: 0.1 }] } }); // 绘制湿度图表 new Chart(document.getElementById(humidityChart).getContext(2d), { type: line, data: { labels: labels, datasets: [{ label: 湿度 (%), data: humids, borderColor: rgb(54, 162, 235), tension: 0.1 }] } }); }); /script /body /html运行python web_dashboard.py然后在同一局域网的电脑或手机上访问http://[你的树莓派IP]:5000就能看到实时图表了。5.2 实现阈值报警邮件/Telegram通知监控的最终目的是在出问题时及时知道。我们可以修改主程序在读取数据后加入判断逻辑。邮件报警示例使用SMTP# alert_sender.py import smtplib from email.mime.text import MIMEText from email.header import Header import logging logger logging.getLogger(__name__) class EmailAlerter: def __init__(self, smtp_server, smtp_port, sender_email, sender_password): self.smtp_server smtp_server self.smtp_port smtp_port self.sender_email sender_email self.sender_password sender_password def send_alert(self, recipient_email, subject, body): 发送报警邮件 message MIMEText(body, plain, utf-8) message[From] Header(f树莓派监控系统 {self.sender_email}, utf-8) message[To] Header(recipient_email, utf-8) message[Subject] Header(subject, utf-8) try: with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server: server.login(self.sender_email, self.sender_password) server.sendmail(self.sender_email, [recipient_email], message.as_string()) logger.info(f报警邮件已发送至 {recipient_email}) return True except Exception as e: logger.error(f发送报警邮件失败: {e}) return False # 在主程序中使用 # 初始化报警器 alerter EmailAlerter( smtp_serversmtp.example.com, # 例如QQ邮箱smtp.qq.com smtp_port465, sender_emailyour_emailexample.com, sender_passwordyour_authorization_code # 注意是授权码不是邮箱密码 ) # 在数据采集任务中判断 def check_and_alert(temp, humidity): alert_messages [] if temp 30: # 温度超过30度报警 alert_messages.append(f温度过高: {temp}°C) if humidity 80: # 湿度过高报警 alert_messages.append(f湿度过高: {humidity}%) if alert_messages: subject ⚠️ 环境监控报警 body \n.join(alert_messages) f\n\n时间: {datetime.now()} alerter.send_alert(your_recipientexample.com, subject, body)Telegram Bot报警更推荐实时性更强通过BotFather创建一个Telegram Bot获取Token。与你的Bot对话然后通过https://api.telegram.org/botYourBOTToken/getUpdates获取你的Chat ID。安装python-telegram-bot库pip install python-telegram-bot编写发送函数import requests def send_telegram_alert(bot_token, chat_id, message): url fhttps://api.telegram.org/bot{bot_token}/sendMessage payload { chat_id: chat_id, text: message, parse_mode: HTML } try: response requests.post(url, jsonpayload) response.raise_for_status() logger.info(Telegram报警发送成功) except Exception as e: logger.error(f发送Telegram报警失败: {e})将报警判断逻辑集成到主程序的job()函数中你的监控系统就具备了“感知-记录-展示-预警”的完整能力。6. 系统优化与长期运行指南让一个脚本运行几天和稳定运行几年是两回事。以下是确保项目长期可靠的关键点。6.1 将脚本设置为系统服务开机自启我们不应该在SSH终端里用python main.py来运行服务终端一关就没了。应该将其设置为系统服务。创建服务文件sudo nano /etc/systemd/system/temperature-tracker.service写入以下内容根据你的实际路径修改[Unit] DescriptionTemperature Tracker Service Afternetwork.target [Service] Typesimple Userpi WorkingDirectory/home/pi/temperature_tracker EnvironmentPATH/home/pi/temperature_tracker/venv/bin ExecStart/home/pi/temperature_tracker/venv/bin/python /home/pi/temperature_tracker/main.py Restarton-failure RestartSec10s StandardOutputjournal StandardErrorjournal [Install] WantedBymulti-user.targetUserpi: 以pi用户运行。WorkingDirectory: 项目根目录。Environment: 指定虚拟环境的PATH确保使用虚拟环境中的Python和库。ExecStart: 启动命令。Restarton-failure: 服务失败时自动重启。RestartSec10s: 重启前等待10秒。启用并启动服务sudo systemctl daemon-reload sudo systemctl enable temperature-tracker.service # 启用开机自启 sudo systemctl start temperature-tracker.service # 立即启动 sudo systemctl status temperature-tracker.service # 查看状态现在你的监控服务就像系统守护进程一样运行即使树莓派重启也会自动启动。6.2 数据管理与维护日志轮转应用日志如Python的logging模块输出可能会撑满磁盘。可以配置logrotate。创建/etc/logrotate.d/temperature-tracker/home/pi/temperature_tracker/*.log { daily missingok rotate 7 compress delaycompress notifempty create 644 pi pi }数据库备份定期备份SQLite文件。一个简单的cron任务即可0 2 * * * cp /home/pi/temperature_tracker/temperature.db /home/pi/backups/temperature.db.$(date \%Y\%m\%d)SD卡保护树莓派的SD卡有写入寿命。可以通过将日志、数据库挂载到RAM磁盘tmpfs来减少写入但重启会丢失数据。更稳妥的做法是使用高品质、高耐久度的工业级SD卡或USB SSD并确保系统在意外断电时能安全关机可以配置UPS或只读挂载根分区。6.3 常见故障排查传感器读取返回None或报错检查接线确保VCC、GND、数据线连接正确且牢固。对于DS18B20务必检查4.7kΩ上拉电阻。检查接口是否启用再次运行sudo raspi-config确认I2C和1-Wire已启用。检查电源传感器供电不足会导致读数不稳定。确保使用树莓派的3.3V引脚而非5V除非传感器明确支持5V。检查传感器地址对于I2C设备用i2cdetect -y 1确认地址。对于DS18B20检查/sys/bus/w1/devices/下是否有设备文件夹。Web界面无法访问检查Flask服务是否运行sudo netstat -tlnp | grep :5000检查防火墙树莓派默认无防火墙但如果你设置了需开放5000端口。检查IP地址确保你访问的是树莓派正确的局域网IPhostname -I查看。服务自动停止查看服务日志sudo journalctl -u temperature-tracker.service -f检查Python脚本异常日志通常会输出错误信息。常见原因是数据库文件权限问题、传感器偶尔断开导致库抛出异常等。确保你的代码有完善的异常捕获和日志记录。这个项目从简单的连线开始逐步构建了一个具备数据采集、存储、可视化、报警和系统化运维的完整监控方案。它最吸引人的地方在于其极高的自定义空间——你可以轻松地增加更多传感器如水质PH传感器、光照强度传感器可以将数据上传到云端如InfluxDB Grafana甚至可以联动智能插座实现自动控温。希望这份详尽的指南能帮你少走弯路成功搭建属于自己的树莓派温度监控系统。