基于树莓派的室内空气质量监测系统:从硬件选型到Web可视化全流程实践
1. 项目概述从想法到可落地的空气质量监测方案去年我接手了一个挺有意思的私人项目为一个小型联合办公空间搭建一套室内空气质量监测系统。起因很简单空间管理者发现午后常有成员抱怨犯困、注意力不集中他们怀疑是通风不足导致二氧化碳浓度升高。市面上成熟的商用监测设备要么功能单一要么价格昂贵且数据封闭。于是我决定基于手头闲置的树莓派搭配几个常见的传感器自己动手搭建一套开源、可定制且能实时查看数据的方案。这个项目的核心目标很明确低成本、可扩展、数据可视化。我们需要实时监测温度、湿度和二氧化碳CO2浓度这三个对室内环境舒适度和健康影响最直接的指标并将数据通过一个清晰的网页界面展示出来最好还能查看历史趋势。最终我选用Raspberry Pi 3B作为主控连接DS18B20、DHT22和MH-Z19B传感器后端用Python的Flask框架处理数据和逻辑前端用HTML/CSS/JavaScript配合Chart.js库绘制图表数据则存入MySQL数据库。整个系统搭建下来硬件成本完全可以控制在500元人民币以内软件部分则完全开源。无论你是物联网爱好者、嵌入式开发初学者还是仅仅想为自己家或工作室改善空气环境的技术控这个项目都提供了一个从硬件连接到软件部署的完整路径。它不仅是一套可用的监测工具更是一个理解传感器数据采集、嵌入式系统编程、Web前后端交互以及数据可视化的绝佳实践案例。2. 核心硬件选型与电路设计解析硬件是整个系统的基石选型直接决定了数据的准确性、系统的稳定性以及最终成本。我的选型思路主要围绕“够用、稳定、易得”三个原则展开。2.1 主控与传感器选型考量主控平台Raspberry Pi 3B选择树莓派3B而非更新型号如4B或更低型号如Zero W是基于一个平衡点考虑。3B拥有完整的40针GPIO接口、有线以太网口、Wi-Fi和蓝牙性能足够流畅运行一个轻量级的Linux系统、Python程序以及MySQL数据库。Zero W虽然更便宜小巧但其处理器和内存512MB在同时运行Web服务器和数据处理时可能会比较吃力尤其在需要绘制动态图表时。而4B性能固然更强但功耗和发热也更大对于需要7x24小时持续运行的环境监测设备来说3B的性价比和稳定性更合适。传感器三剑客温度、湿度、CO2温度传感器DS18B20我选择了DS18B20这款经典的“单总线”数字温度传感器。它最大的优点是采用独特的单总线协议只需要一根数据线加上电源和地线共三线即可与树莓派通信极大地简化了布线。其测量范围-55°C到125°C精度±0.5°C完全满足室内监测需求。更重要的是每个DS18B20都有全球唯一的64位ID这意味着你可以在同一条总线上挂载多个传感器方便未来扩展监测多个点位。温湿度一体传感器DHT22虽然DS18B20能测温度但为了同时获取湿度数据并作为一个数据交叉验证的参考我增加了DHT22。DHT22也是数字传感器输出校准过的数字信号湿度测量范围0-100%RH精度±2%RH温度测量范围-40~80°C精度±0.5°C。它使用单线双向串行接口接线同样简单。这里有一个注意事项DHT22的响应速度较慢两次测量之间需要至少2秒的间隔在编程时需要设置合理的读取频率避免频繁读取导致错误或程序阻塞。二氧化碳传感器MH-Z19B这是项目的核心传感器用于测量空气中的CO2浓度单位ppm。我选择MH-Z19B型号是因为它采用非色散红外NDIR原理这是测量CO2的主流且相对准确的方法比某些廉价的半导体式传感器稳定、抗干扰性强得多。其测量范围是0-5000ppm自带温度补偿并通过UART串口与树莓派通信使用简单。关键点MH-Z19B需要一段预热时间通常约3分钟才能输出稳定数据且建议每24小时进行一次自动校准ABC功能或将其置于400ppm的新鲜空气中进行手动校准以确保长期准确性。2.2 电路连接与供电方案所有传感器与树莓派的连接都需要通过面包板进行过渡和测试。树莓派的GPIO引脚工作电压是3.3V务必确认所有传感器兼容此电压否则需要电平转换模块。接线明细与原理DS18B20VCC红线接3.3V GND黑线接地 DATA黄线接GPIO4物理引脚7。必须在DATA线和3.3V之间连接一个4.7kΩ的上拉电阻这是单总线协议稳定通信所必需的。DHT22VCC接3.3V GND接地 DATA接GPIO17物理引脚11。同样建议在DATA线和3.3V之间连接一个5-10kΩ的上拉电阻尤其在导线较长时能增强信号稳定性。MH-Z19BVIN接树莓派的5V引脚物理引脚2或4 GND接地。其TX引脚发送端接树莓派的RXGPIO15物理引脚10 RX引脚接收端接树莓派的TXGPIO14物理引脚8。注意这里是交叉连接传感器的TX接树莓派的RX传感器的RX接树莓派的TX。供电设计树莓派3B满载时电流需求可达2.5A。我使用了一个输出为5V/3A的优质USB电源适配器为其供电。重要经验务必避免使用劣质或功率不足的电源这会导致树莓派运行不稳定、频繁重启甚至损坏SD卡。对于需要长期运行的设备供电的稳定性是第一要务。LCD显示屏可选我额外连接了一块16x2的I2C接口LCD屏用于本地实时显示数据。I2C接口仅需四根线VCC, GND, SDA, SCL节省GPIO资源。SDA接GPIO2物理引脚3SCL接GPIO3物理引脚5。这是一个很好的调试和本地查看工具但在以Web访问为主的场景下非必需。提示在连接任何线缆前请确保树莓派已断电。连接完成后最好用万用表通断档检查一下关键连接避免虚接或短路这是保护硬件的第一步。3. 软件环境搭建与核心代码实现硬件连接妥当后下一步就是让树莓派“活”起来赋予它数据采集、处理和展示的能力。这个过程主要分为系统与驱动配置、后端服务编写、前端界面开发三大部分。3.1 系统配置与传感器驱动启用首先为树莓派安装Raspberry Pi OS Lite无桌面版以节省资源。通过SSH远程登录进行配置是最高效的方式。1. 系统更新与必要工具安装sudo apt update sudo apt upgrade -y sudo apt install python3-pip python3-venv git -y2. 启用接口与驱动I2C用于LCD屏运行sudo raspi-config进入Interface Options-I2C选择启用。1-Wire用于DS18B20同样在raspi-config中启用1-Wire接口。串口用于MH-Z19B树莓派的硬件串口默认用于蓝牙。我们需要将其释放给GPIO使用。编辑/boot/config.txt文件在末尾添加enable_uart1 dtoverlaydisable-bt这行命令会禁用蓝牙并将硬件串口/dev/ttyAMA0分配给GPIO14/15。重启后MH-Z19B就可以连接到/dev/ttyAMA0了。3. 验证传感器连接DS18B20重启后输入ls /sys/bus/w1/devices/你应该能看到一个以“28-”开头的文件夹里面w1_slave文件的内容就包含了温度数据。DHT22需要通过Python库读取后续测试。MH-Z19B安装测试工具pip3 install mh-z19然后运行sudo python3 -m mh_z19如果看到返回的CO2浓度值说明串口通信正常。3.2 后端服务数据采集、存储与API提供后端是系统的大脑负责轮询传感器、处理数据、存入数据库并通过WebSocket或API提供给前端。我使用Python的Flask微框架因为它轻量、灵活且易于上手。1. 项目结构与虚拟环境在树莓派上创建一个项目目录并建立Python虚拟环境以隔离依赖。mkdir air_quality_monitor cd air_quality_monitor python3 -m venv venv source venv/bin/activate2. 安装Python依赖库pip install flask flask-cors flask-socketio mysql-connector-python gevent gevent-websocket pip install adafruit-circuitpython-dht # 用于DHT22 pip install mh-z19 # 用于MH-Z19B # DS18B20使用系统内置的1-wire接口无需额外安装库3. 数据库设计使用MariaDBMySQL的兼容分支创建数据库。设计遵循简单的规范化原则创建两张表CREATE DATABASE air_quality; USE air_quality; CREATE TABLE sensors ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL, type VARCHAR(50) NOT NULL, location VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE sensor_data ( id INT AUTO_INCREMENT PRIMARY KEY, sensor_id INT, temperature DECIMAL(5,2), humidity DECIMAL(5,2), co2 INT, recorded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (sensor_id) REFERENCES sensors(id) );sensors表记录传感器元信息sensor_data表存储具体的测量数据。这种设计便于未来增加更多监测点。4. 核心Python脚本解析创建一个app.py作为后端主程序。其核心逻辑如下传感器读取函数编写独立的函数来读取每个传感器并加入异常处理。例如读取DHT22时如果失败就重试几次避免因单次读取失败导致整个循环中断。import adafruit_dht import board import mh_z19 import time def read_dht22(pin): dhtDevice adafruit_dht.DHT22(pin, use_pulseioFalse) # 在某些系统上需要禁用pulseio for _ in range(3): # 重试3次 try: temperature dhtDevice.temperature humidity dhtDevice.humidity if temperature is not None and humidity is not None: return temperature, humidity except RuntimeError as error: time.sleep(2) # DHT22需要间隔 continue return None, None数据采集循环使用一个后台线程每10秒读取一次所有传感器数据并插入数据库。这里有个关键技巧将传感器读取和数据库写入操作分离。即使数据库暂时不可用采集的数据也可以先缓存起来避免数据丢失。import threading from datetime import datetime def collect_data(): while True: temp_ds18b20 read_ds18b20() temp_dht22, humidity read_dht22(board.D17) co2_value read_mhz19b() # 可以选择使用两个温度传感器的平均值 avg_temp (temp_ds18b20 temp_dht22) / 2 if None not in (temp_ds18b20, temp_dht22) else temp_dht22 or temp_ds18b20 # 插入数据库 save_to_db(avg_temp, humidity, co2_value) time.sleep(10)Flask与Socket.IOFlask处理HTTP请求如提供历史数据的APISocket.IO实现实时数据推送。from flask import Flask, jsonify from flask_socketio import SocketIO, emit import mysql.connector app Flask(__name__) socketio SocketIO(app, cors_allowed_origins*) app.route(/api/history) def get_history(): # 连接数据库查询最近N小时的数据以JSON格式返回 pass # 当有新的传感器数据存入时通过Socket.IO广播给所有连接的网页客户端 def save_to_db(temp, hum, co2): # ... 数据库插入逻辑 ... socketio.emit(new_data, {temperature: temp, humidity: hum, co2: co2})3.3 前端界面数据可视化与交互前端的目标是提供一个清晰、直观且响应式的数据看板。我使用纯HTML/CSS/JS开发利用Chart.js库来绘制图表。1. 页面结构创建index.html包含一个标题和简要说明。三个卡片区域分别用大字体显示温度、湿度、CO2的当前实时数值并使用颜色提示例如CO2超过1000ppm显示为橙色超过1500ppm显示为红色。一个标签页区域包含“实时曲线”和“历史趋势”两个标签页。“实时曲线”页内包含三个Canvas元素分别用于绘制三条随时间变化的折线图。“历史趋势”页提供时间范围选择器如最近1小时、6小时、24小时点击后从后端API获取数据并渲染图表。2. 实时数据更新使用Socket.IO的客户端库监听后端发出的new_data事件更新大数字显示并同时向三个实时图表追加新的数据点。const socket io(); socket.on(new_data, function(data) { // 更新温度、湿度、CO2的数值显示 document.getElementById(current-temp).innerText data.temperature.toFixed(1); // 更新实时图表 realTimeChart.data.labels.push(new Date().toLocaleTimeString()); realTimeChart.data.datasets[0].data.push(data.co2); realTimeChart.update(quiet); // 静默更新避免频繁动画消耗性能 });3. 历史数据获取为“历史趋势”页的按钮绑定点击事件触发对/api/history的AJAX请求获取对应时间段的JSON数据然后重新初始化Chart.js图表。function loadHistoryData(hours) { fetch(/api/history?hours${hours}) .then(response response.json()) .then(data { // 使用获取的数据重新绘制历史图表 renderHistoryChart(data); }); }4. 样式与响应式使用CSS Flexbox或Grid布局确保在手机、平板和电脑上都能有良好的显示效果。图表容器设置为width: 100%; height: 300px;。4. 系统集成、部署与优化当硬件、后端、前端都分别测试通过后就需要将它们整合成一个稳定、可长期运行的系统。4.1 系统服务化与开机自启我们不能依赖在SSH终端里手动运行python app.py。需要将后端程序设置为系统服务使其在树莓派启动时自动运行并在崩溃时尝试重启。创建系统服务文件sudo nano /etc/systemd/system/airquality.service写入以下配置[Unit] DescriptionAir Quality Monitor Flask Service Afternetwork.target mariadb.service [Service] Userpi WorkingDirectory/home/pi/air_quality_monitor EnvironmentPATH/home/pi/air_quality_monitor/venv/bin ExecStart/home/pi/air_quality_monitor/venv/bin/python app.py Restartalways RestartSec10 [Install] WantedBymulti-user.target关键参数解释Afternetwork.target mariadb.service确保服务在网络和数据库就绪后才启动。EnvironmentPATH...指定虚拟环境的路径确保服务使用我们安装的依赖包。Restartalways服务意外退出时自动重启。RestartSec10重启前等待10秒。启用并启动服务sudo systemctl daemon-reload sudo systemctl enable airquality.service sudo systemctl start airquality.service sudo systemctl status airquality.service # 检查运行状态4.2 Web服务器配置可选但推荐目前Flask内置的开发服务器运行在http://树莓派IP:5000。开发服务器性能较弱且不安全不适合生产环境。我们可以使用Nginx作为反向代理处理静态文件前端HTML/CSS/JS并将动态请求转发给后端的Flask应用通常运行在本地某个端口如127.0.0.1:8000。安装Nginxsudo apt install nginx -y配置Nginx站点编辑Nginx配置文件/etc/nginx/sites-available/airqualityserver { listen 80; server_name your_pi_ip_or_domain; # 填写树莓派的IP地址或域名 location / { # 首先尝试直接提供静态文件前端页面 root /home/pi/air_quality_monitor/static; try_files $uri $uri/ flaskapp; } location flaskapp { proxy_pass http://127.0.0.1:8000; # 转发给Gunicorn proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # Socket.IO支持 location /socket.io/ { proxy_pass http://127.0.0.1:8000/socket.io/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; } }使用Gunicorn替代Flask开发服务器在生产环境中使用Gunicorn这类WSGI服务器来运行Flask应用更稳定。pip install gunicorn # 在项目目录下使用Gunicorn启动应用绑定到本地8000端口 gunicorn --workers 2 --bind 127.0.0.1:8000 app:app同样可以将Gunicorn也配置为系统服务。4.3 机箱设计与制作为了让项目看起来更专业也为了保护内部电路一个定制的机箱是必要的。我选择用激光切割亚克力板来制作。设计使用Fusion 360或Inkscape等软件根据树莓派、面包板、传感器和屏幕的尺寸设计一个六面体盒子的展开图DXF或SVG格式。需要预留传感器开孔MH-Z19B的进气/出气孔、DHT22和DS18B20的感应窗口。屏幕开孔与LCD显示区域匹配。线缆孔电源线和网线如果使用的出口。散热孔在树莓派CPU上方位置开一些小孔。材料3mm厚的亚克力板是不错的选择兼顾强度和美观。也可以使用中密度纤维板MDF成本更低但不如亚克力通透。组装激光切割完成后使用亚克力胶水或卡扣结构进行组装。内部可以用尼龙柱或螺丝固定树莓派和面包板。注意机箱设计时务必确保CO2传感器的进气口不被遮挡且远离其他热源如树莓派CPU以免影响测量准确性。温湿度传感器也应避免被阳光直射或紧贴发热元件。5. 常见问题排查与维护心得在实际部署和长期运行中你肯定会遇到各种各样的问题。下面是我踩过坑后总结的一些典型问题及其解决方法。5.1 硬件与连接问题问题1DS18B20读取失败/sys/bus/w1/devices/目录下没有设备。排查首先检查raspi-config中1-Wire接口是否已启用。然后检查物理连接特别是那个4.7kΩ的上拉电阻是否接在了数据线和3.3V之间这是最常见的原因。用万用表测量数据线引脚电压在未通信时应在3.3V左右。解决确认接线无误后重启树莓派。如果仍不行尝试更换一个GPIO口需同时修改代码和/boot/config.txt中的dtoverlayw1-gpio,gpiopin参数。问题2DHT22经常读取失败或返回None。排查DHT22对时序要求严格。首先检查接线同样可以尝试增加一个上拉电阻。其次确保两次读取间隔大于2秒。检查代码中是否在读取失败后没有足够的延迟就立即重试。解决在读取函数中加入重试机制和足够的延迟time.sleep(2)。确保给DHT22供电的3.3V电源稳定。如果环境干扰大可以尝试缩短传感器到树莓派的导线长度或使用屏蔽线。问题3MH-Z19B返回的CO2值异常如始终为0、410或5000。排查始终为0或410可能是串口通信问题。使用ls -l /dev/ttyAMA0检查设备权限确保运行程序的用户如pi有读写权限crw-rw----。可以使用sudo chmod 666 /dev/ttyAMA0临时修改但最好将用户加入dialout组sudo usermod -a -G dialout pi。始终为5000可能是传感器处于预热期上电后头3分钟或发生了ABC自动校准干扰。MH-Z19B的ABC自动基线校准功能会假设传感器每周至少有一次暴露在400ppm新鲜空气如室外的环境中。如果长期在密闭高浓度环境运行ABC功能会导致读数逐渐偏低。解决对于通信问题检查/boot/config.txt中串口配置并确认TX/RX没有接反。对于ABC问题如果监测环境长期密闭建议关闭ABC功能。可以通过发送特定的串口指令实现具体指令需查阅MH-Z19B手册或者购买MH-Z19C型号它支持更灵活的校准设置。5.2 软件与数据问题问题4Web页面能打开但实时数据不更新。排查打开浏览器的开发者工具F12查看“网络”(Network)选项卡中的“WS”WebSocket或“获取”(Fetch)请求。确认Socket.IO连接是否建立成功状态码101。查看控制台(Console)是否有JavaScript错误。解决检查后端Socket.IO服务是否正常运行。确认前端连接的Socket.IO服务器地址和端口是否正确。如果使用了Nginx反向代理务必按照前面所述正确配置/socket.io/路径的转发。问题5历史数据图表不显示或数据不对。排查在浏览器中直接访问后端历史数据API例如http://树莓派IP:5000/api/history?hours24查看返回的JSON数据是否正常。检查后端查询数据库的SQL语句和时间过滤逻辑是否正确。解决确保数据库服务MariaDB正在运行。检查数据库连接参数主机、用户名、密码、数据库名。在Python代码中增加更详细的错误捕获和日志记录将异常信息写入文件便于排查。问题6树莓派运行一段时间后卡顿或重启。排查很可能是电源问题或散热问题。触摸树莓派芯片如果烫手说明散热不足。使用vcgencmd measure_temp命令查看核心温度超过80°C就需要警惕。解决为树莓派加装散热片甚至小型风扇。确保使用足额5V/3A的优质电源。检查SD卡剩余空间定期清理日志文件。对于7x24运行建议使用高质量、高耐用度的工业级SD卡或改用USB SSD启动。5.3 长期维护与优化建议数据备份定期备份MySQL数据库。可以使用mysqldump工具创建备份脚本并通过cron定时任务将备份文件发送到另一台电脑或云存储。日志记录为Python程序添加日志功能使用logging模块将错误信息、传感器读取失败记录等写入文件方便日后排查问题。设置报警阈值在后端代码中增加逻辑当CO2浓度连续超过设定阈值如1000ppm一定时间后可以通过发送电子邮件、Telegram消息或触发一个物理蜂鸣器的方式进行报警。功耗考虑如果希望部署在无固定电源的地方可以考虑使用树莓派Zero 2 W功耗更低搭配移动电源并优化软件减少不必要的进程延长续航。扩展性当前的数据库和代码结构已经考虑了多传感器扩展。你可以轻松地增加第二个、第三个监测节点使用额外的树莓派或ESP32等单片机将数据通过HTTP POST或MQTT协议发送到中心服务器这台树莓派进行汇总展示。这个项目从构思到稳定运行花费了我大约两周的业余时间。最大的收获不是做出了一个能用的工具而是在这个过程中将硬件、嵌入式Linux、网络通信、数据库、Web开发这些分散的知识点串联成了一个完整的解决方案。当你第一次在手机上打开网页看到自己房间里真实的温湿度和CO2曲线时那种感觉是无可替代的。它现在已经在那个联合办公空间默默运行了大半年成为了管理者调整通风策略的重要依据。如果你也正被室内空气问题困扰或者单纯想找一个有挑战又有成就感的物联网项目来练手不妨就从这里开始。