基于树莓派与多传感器搭建低成本空气质量监测系统

📅 2026/8/19 4:53:58
基于树莓派与多传感器搭建低成本空气质量监测系统
1. 项目缘起为什么要在树莓派上折腾空气质量监测几年前我因为家里新装修对室内空气质量特别上心。市面上的专业检测仪要么价格昂贵要么功能单一数据还只能存在设备里没法远程查看。当时手头正好有几个闲置的树莓派一个想法就冒了出来能不能用树莓派自己搭一个空气质量监测站它应该能实时监测多种参数数据能存下来做长期分析最好还能通过网页或者手机随时查看成本还得可控。这个想法驱动我开始了这个“树莓派空气质量监测器”的项目。经过几轮迭代它现在已经能稳定监测PM2.5、PM10、温湿度、二氧化碳CO2和总挥发性有机物TVOC等多个关键指标。整个系统搭建下来硬件成本可以控制在几百元以内远低于市面上的多合一商用设备更重要的是你获得了数据的完全掌控权和无限的定制可能。无论是放在书房、客厅还是办公室、工作坊它都能为你提供一份关于呼吸环境的“数据报告”。2. 核心传感器选型与硬件连接方案搭建监测器的第一步也是最重要的一步就是选择靠谱的传感器。空气质量包含的指标很多我们需要根据关注点来组合传感器。下面是我经过实际测试和筛选后推荐的一套高性价比、易集成的方案。2.1 传感器模块详解1. PMS5003ST 激光粉尘传感器这是整个系统的核心用于监测PM2.5和PM10。我选择PMS5003ST而不仅仅是PMS5003是因为它集成了温湿度传感器一举两得。它采用激光散射原理精度和响应速度对于家庭环境来说完全足够。其UART串口输出方式使得与树莓派的连接非常直接。2. SCD30 二氧化碳传感器室内CO2浓度是衡量通风状况和人员密集度的关键指标浓度过高会直接导致困倦、注意力下降。SCD30采用NDIR非分散红外技术是目前民用级精度和稳定性最好的CO2传感器之一。它通过I2C接口通信功耗和体积都控制得很好。3. SGP30 气体传感器这款传感器用于检测TVOC总挥发性有机物和等效二氧化碳eCO2。TVOC涵盖了甲醛、苯等多种有害气体是衡量装修污染和化学品挥发的重要综合指标。SGP30同样使用I2C接口它内部有强大的算法和自动基线校准功能能提供相对可靠的趋势数据。注意SGP30检测的是TVOC总量和基于VOC推算的eCO2其eCO2值在通风良好的环境下可能与SCD30测得的真实CO2有差异。两者配合使用SCD30看真实CO2SGP30看有机污染物信息更全面。4. BME280 温湿度气压传感器可选如果你选用的是不带温湿度的PMS5003那么就需要额外增加一个温湿度传感器。BME280精度高还附带气压测量性价比突出。如果已经用了PMS5003ST这个传感器可以省去。2.2 硬件连接与供电考量树莓派的GPIO引脚有限需要合理规划。我推荐使用树莓派4B或更新的型号它们的处理能力和接口都更充裕。连接示意图如下传感器模块接口类型树莓派 GPIO 引脚功能说明PMS5003STUART (TX/RX)GPIO 14 (TX), GPIO 15 (RX)串口通信接收颗粒物及温湿度数据SCD30I2C (SDA/SCL)GPIO 2 (SDA), GPIO 3 (SCL)I2C通信需外部上拉电阻通常模块已集成SGP30I2C (SDA/SCL)GPIO 2 (SDA), GPIO 3 (SCL)与SCD30共享I2C总线地址不同可并联BME280 (可选)I2C (SDA/SCL)GPIO 2 (SDA), GPIO 3 (SCL)共享I2C总线实际接线操作要点电源统一所有传感器和树莓派最好由一个稳定的5V/2A以上的电源适配器供电。避免使用树莓派USB口为多个传感器供电可能导致电压不稳或电流不足。电平转换PMS5003ST的工作电压是5V而树莓派GPIO的逻辑电平是3.3V。虽然很多经验表明其TX输出信号3.3V可以直接连接树莓派的RX3.3V耐受但为了绝对安全建议使用一个简单的电平转换模块如TXS0108E或分压电路保护树莓派昂贵的核心板。I2C总线SCD30、SGP30、BME280可以并联在同一组I2C总线上SDA和SCL。每个设备都有唯一的I2C地址不会冲突。接线时确保SDA和SCL线路上各有1个4.7kΩ的上拉电阻到3.3V大多数传感器模块板载了这些电阻如果连接多个设备后通信不稳定可以检查或添加上拉电阻。物理固定粉尘传感器需要空气流通不要把它完全密封在盒子里。可以设计一个带有通风孔的外壳同时防止大的异物进入。其他气体传感器也应避免被直接风吹或阳光直射。3. 系统软件环境搭建与驱动配置硬件连接好后我们需要在树莓派上构建一个能够读取所有传感器、处理数据并可能提供服务的软件环境。我选择使用Python作为主控语言因为它有丰富的库支持开发效率高。3.1 操作系统与基础设置首先为树莓派安装最新的Raspberry Pi OS Lite无桌面版或带有桌面的版本。安装完成后进行基础配置# 更新系统 sudo apt update sudo apt upgrade -y # 启用I2C和串口接口 sudo raspi-config在raspi-config工具中依次找到Interface Options-I2C- 选择Yes启用。Interface Options-Serial Port- 当询问“是否要登录shell通过串口访问”时选择No然后询问“是否启用串口硬件”时选择Yes。这样我们禁用了串口控制台释放了GPIO14/15供PMS5003ST使用。重启树莓派使设置生效。3.2 Python环境与核心库安装建议使用虚拟环境来管理项目依赖避免污染系统Python环境。# 安装Python3虚拟环境工具 sudo apt install python3-venv python3-pip -y # 创建项目目录并进入 mkdir ~/air_quality_monitor cd ~/air_quality_monitor # 创建虚拟环境 python3 -m venv venv # 激活虚拟环境 source venv/bin/activate # 激活后命令行提示符前会出现 (venv) # 安装核心Python库 pip install pyserial # 用于串口通信读取PMS5003ST pip install smbus2 # 用于I2C通信比smbus更活跃 pip install adafruit-circuitpython-scd30 # SCD30官方库 pip install adafruit-circuitpython-sgp30 # SGP30官方库 pip install adafruit-circuitpython-bme280 # BME280库如选用 pip install pandas # 用于数据结构化处理和存储 pip install flask # 用于创建简单的Web数据接口可选3.3 传感器驱动与数据读取脚本接下来我们编写一个Python脚本来测试并读取所有传感器的数据。这个脚本将是后续所有功能的基础。#!/usr/bin/env python3 # -*- coding: utf-8 -*- # sensor_reader.py import serial import time import board import busio import adafruit_scd30 import adafruit_sgp30 # import adafruit_bme280 (可选) class AirQualityMonitor: def __init__(self): self.pm_data {} self.scd30_data {} self.sgp30_data {} self._init_pms5003st() self._init_i2c_sensors() def _init_pms5003st(self): 初始化PMS5003ST粉尘传感器 # 串口设备名可能是 /dev/ttyAMA0 或 /dev/serial0树莓派4B之后通常是后者 try: self.pm_serial serial.Serial(/dev/serial0, baudrate9600, timeout2.0) print(PMS5003ST串口初始化成功。) except serial.SerialException as e: print(f无法打开串口: {e}) self.pm_serial None def _init_i2c_sensors(self): 初始化所有I2C传感器 try: i2c busio.I2C(board.SCL, board.SDA) self.scd30 adafruit_scd30.SCD30(i2c) # SCD30需要一点时间启动且连续读取间隔建议大于2秒 print(SCD30初始化成功。) self.sgp30 adafruit_sgp30.Adafruit_SGP30(i2c) # SGP30首次使用需要初始基线通常运行一段时间后会自动校准 print(SGP30初始化成功。) # 打印SGP30的基线值初始为0 print(fSGP30初始基线: eCO2{self.sgp30.baseline_eCO2}, TVOC{self.sgp30.baseline_TVOC}) # 如果需要BME280取消下面注释 # self.bme280 adafruit_bme280.Adafruit_BME280_I2C(i2c) # print(BME280初始化成功。) except Exception as e: print(fI2C传感器初始化失败: {e}) self.scd30 None self.sgp30 None def _read_pms5003st(self): 读取并解析PMS5003ST数据帧 if not self.pm_serial: return None # PMS5003ST数据帧以0x42, 0x4D开头长度32字节 while self.pm_serial.in_waiting 32: # 寻找帧头 if self.pm_serial.read() b\x42: if self.pm_serial.read() b\x4d: # 读取后续30字节帧头已读2字节 frame self.pm_serial.read(30) # 简单的校验和验证此处省略详细校验代码实际应用建议加上 # 解析数据 (数据格式参考传感器手册) pm1_0 (frame[4] 8) | frame[5] # PM1.0 pm2_5 (frame[6] 8) | frame[7] # PM2.5 pm10 (frame[8] 8) | frame[9] # PM10 temperature ((frame[24] 8) | frame[25]) / 10.0 # 温度 humidity ((frame[26] 8) | frame[27]) / 10.0 # 湿度 self.pm_data { pm1_0: pm1_0, pm2_5: pm2_5, pm10: pm10, temp_pm: temperature, humi_pm: humidity } return self.pm_data return None def _read_scd30(self): 读取SCD30数据 if not self.scd30: return None # 检查是否有新数据可用 if self.scd30.data_available: self.scd30_data { co2: self.scd30.CO2, # 单位: ppm temp_scd: self.scd30.temperature, # 单位: 摄氏度 humi_scd: self.scd30.relative_humidity # 单位: % } return self.scd30_data return None def _read_sgp30(self): 读取SGP30数据 if not self.sgp30: return None self.sgp30_data { eco2: self.sgp30.eCO2, # 等效CO2单位: ppm tvoc: self.sgp30.TVOC # 总挥发性有机物单位: ppb } # SGP30需要定期读取以维持内部算法建议每秒读取一次 # 每运行一段时间可以保存其基线值到文件下次启动时导入以加快校准 return self.sgp30_data def read_all(self): 一次性读取所有传感器数据 data {} pm self._read_pms5003st() if pm: data.update(pm) scd self._read_scd30() if scd: data.update(scd) sgp self._read_sgp30() if sgp: data.update(sgp) # 可选读取BME280 # if hasattr(self, bme280): # data[temp_bme] self.bme280.temperature # data[humi_bme] self.bme280.humidity # data[pressure] self.bme280.pressure return data if __name__ __main__: monitor AirQualityMonitor() print(开始读取传感器数据按CtrlC终止...) try: while True: all_data monitor.read_all() if all_data: print(f\r数据: {all_data}, end, flushTrue) time.sleep(2) # 每2秒读取一次 except KeyboardInterrupt: print(\n程序终止。)运行这个脚本 (python sensor_reader.py)如果一切连接正确你应该能在终端看到不断刷新的传感器数据字典。这是万里长征第一步证明你的硬件和基础驱动是通的。4. 数据持久化、可视化与告警系统构建仅仅在终端里看数据是远远不够的。我们需要将数据存储下来以便分析长期趋势并通过更友好的方式呈现甚至在异常时发出提醒。4.1 使用SQLite进行本地数据存储对于这种频率不高每分钟几次且单点部署的项目SQLite数据库是轻量且完美的选择。它无需安装额外的数据库服务一个文件搞定。# database_logger.py import sqlite3 import time from datetime import datetime import json class DataLogger: def __init__(self, db_pathair_quality.db): self.db_path db_path self._init_db() def _init_db(self): 初始化数据库创建数据表 conn sqlite3.connect(self.db_path) cursor conn.cursor() # 创建主表包含所有可能的传感器字段 cursor.execute( CREATE TABLE IF NOT EXISTS sensor_data ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME NOT NULL, pm1_0 REAL, pm2_5 REAL, pm10 REAL, temp_pm REAL, humi_pm REAL, co2 REAL, temp_scd REAL, humi_scd REAL, eco2 REAL, tvoc REAL, -- 可选BME280字段 temp_bme REAL, humi_bme REAL, pressure REAL ) ) # 创建索引以加速按时间查询 cursor.execute(CREATE INDEX IF NOT EXISTS idx_timestamp ON sensor_data (timestamp)) conn.commit() conn.close() print(f数据库初始化完成: {self.db_path}) def log_data(self, data_dict): 将一条数据记录插入数据库 if not data_dict: return False # 添加当前时间戳 data_dict[timestamp] datetime.now().isoformat() # 准备列名和值 columns [] placeholders [] values [] for key, value in data_dict.items(): columns.append(key) placeholders.append(?) values.append(value) sql fINSERT INTO sensor_data ({, .join(columns)}) VALUES ({, .join(placeholders)}) try: conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute(sql, values) conn.commit() conn.close() return True except sqlite3.Error as e: print(f数据库插入错误: {e}) return False # 在主循环中使用 if __name__ __main__: from sensor_reader import AirQualityMonitor import time monitor AirQualityMonitor() logger DataLogger() print(开始监测并记录数据...) try: while True: data monitor.read_all() if data: success logger.log_data(data) if success: print(f[{data.get(timestamp)}] 数据记录成功。) else: print(数据记录失败。) time.sleep(60) # 每分钟记录一次数据避免数据库过快膨胀 except KeyboardInterrupt: print(数据记录服务停止。)这个记录器会每分钟将数据存入SQLite数据库。对于长期运行你可以考虑增加一些优化比如使用连接池或者每小时将数据聚合后存入另一张“摘要表”以减少主表大小。4.2 使用Grafana打造专业级仪表盘在本地存储了数据我们如何展示一个强大的选择是Grafana。它可以通过插件轻松连接SQLite并构建出非常美观、专业的监控仪表盘。第一步安装Grafana在树莓派上安装Grafana相对简单。# 添加Grafana的APT仓库 sudo apt-get install -y apt-transport-https software-properties-common sudo add-apt-repository deb https://packages.grafana.com/oss/deb stable main wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add - # 安装Grafana sudo apt-get update sudo apt-get install grafana # 启动Grafana服务并设置开机自启 sudo systemctl enable grafana-server sudo systemctl start grafana-server安装完成后在电脑浏览器访问http://你的树莓派IP:3000默认用户名和密码都是admin首次登录会要求修改密码。第二步安装SQLite数据源插件Grafana默认不支持SQLite需要安装一个社区插件。# 在树莓派上执行 sudo grafana-cli plugins install frser-sqlite-datasource sudo systemctl restart grafana-server重启后在Grafana的Configuration-Data Sources里就能添加SQLite类型的数据源了。配置时指定数据库文件的绝对路径如/home/pi/air_quality_monitor/air_quality.db。第三步创建仪表盘新建一个Dashboard。添加一个Panel选择Visualization为Time series。在查询编辑器里选择SQLite数据源编写SQL查询例如SELECT datetime(timestamp) as time, pm2_5, pm10 FROM sensor_data WHERE $__timeFilter(timestamp) ORDER BY time$__timeFilter(timestamp)是Grafana的宏会自动替换为当前仪表盘选择的时间范围。配置图表的标题、Y轴单位如µg/m³、颜色等。重复添加面板分别展示CO2、TVOC、温湿度等曲线。你还可以设置阈值告警线例如PM2.5超过75µg/m³显示为橙色超过150显示为红色让图表一目了然。Grafana的强大在于你可以把多个相关指标放在一起对比比如同时观察CO2上升和开窗温度变化的关系。4.3 实现简单的阈值告警与通知除了在图表上看我们还需要在空气质量变差时主动获知。一个简单的方法是编写一个Python脚本定期检查最新数据并通过各种方式发送通知。方案一本地声音或LED提示最简单如果你的树莓派连接了扬声器或LED可以直接控制它们。# simple_alert.py import sqlite3 from datetime import datetime, timedelta import os def check_and_alert(db_pathair_quality.db): conn sqlite3.connect(db_path) cursor conn.cursor() # 获取最近5分钟内的平均PM2.5 five_min_ago (datetime.now() - timedelta(minutes5)).isoformat() cursor.execute( SELECT AVG(pm2_5) as avg_pm25 FROM sensor_data WHERE timestamp ? AND pm2_5 IS NOT NULL , (five_min_ago,)) result cursor.fetchone() conn.close() avg_pm25 result[0] if result[0] else 0 alert_message None if avg_pm25 150: alert_message f严重警告平均PM2.5浓度过高: {avg_pm25:.1f} µg/m³ # 可以在这里触发更强烈的警报比如播放警报音 # os.system(mpg123 /path/to/alert_sound.mp3 ) elif avg_pm25 75: alert_message f警告平均PM2.5浓度偏高: {avg_pm25:.1f} µg/m³ # 触发温和提醒 # os.system(mpg123 /path/to/notice_sound.mp3 ) if alert_message: print(f[{datetime.now()}] {alert_message}) # 这里可以扩展为发送网络通知 if __name__ __main__: check_and_alert()然后使用cron定时任务每5分钟执行一次这个脚本crontab -e添加*/5 * * * * /home/pi/air_quality_monitor/venv/bin/python /home/pi/air_quality_monitor/simple_alert.py方案二推送通知到手机更实用我们可以使用如Pushover、Telegram Bot、企业微信机器人、Bark等免费服务将警报推送到手机。以Telegram Bot为例在Telegram上找BotFather创建一个新的Bot获得一个API Token。给你的Bot发送一条消息然后通过https://api.telegram.org/botYourBOTToken/getUpdates获取你的Chat ID。安装Python请求库pip install requests修改告警脚本import requests def send_telegram_alert(message, bot_token, chat_id): 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, timeout5) if response.status_code 200: print(Telegram通知发送成功。) else: print(f发送失败: {response.text}) except Exception as e: print(f发送请求时出错: {e}) # 在check_and_alert函数中调用 if alert_message: send_telegram_alert(alert_message, YOUR_BOT_TOKEN, YOUR_CHAT_ID)这样当空气质量超标时你的手机就能立刻收到消息提醒你该开窗通风或者开启空气净化器了。5. 系统优化、长期运行与常见问题排查让这个监测站7x24小时稳定运行并确保数据准确可靠还需要一些优化和排错经验。5.1 系统服务化与开机自启我们不能总开着终端运行Python脚本。最好的方式是将它做成一个系统服务。创建一个服务文件sudo nano /etc/systemd/system/airquality.service[Unit] DescriptionAir Quality Monitor Service Afternetwork.target [Service] Typesimple Userpi WorkingDirectory/home/pi/air_quality_monitor EnvironmentPATH/home/pi/air_quality_monitor/venv/bin ExecStart/home/pi/air_quality_monitor/venv/bin/python /home/pi/air_quality_monitor/main_service.py Restartalways RestartSec10 StandardOutputsyslog StandardErrorsyslog SyslogIdentifierairquality [Install] WantedBymulti-user.target这里的main_service.py是你整合了数据读取、记录和告警逻辑的主程序。然后启用并启动服务sudo systemctl daemon-reload sudo systemctl enable airquality.service sudo systemctl start airquality.service # 查看状态和日志 sudo systemctl status airquality.service sudo journalctl -u airquality.service -f5.2 传感器校准与数据可靠性保障传感器尤其是气体传感器需要正确的校准才能保证长期数据的可信度。SCD30的校准SCD30支持手动强制校准FCR和自动自校准ASC。ASC默认开启它假设设备每周至少有一次暴露在400ppm室外新鲜空气环境持续至少20分钟。如果你的设备长期在室内高浓度环境下运行ASC可能导致读数偏低。建议如果条件允许定期如每两周将设备放到通风良好的室外避免阳光直射和雨水运行一小时让ASC正常工作。除非你有已知浓度的标准气体否则不要轻易使用FCR。SGP30的基线管理SGP30的算法严重依赖基线值baseline。它会在运行中自动学习并更新基线。断电后基线会丢失重新上电需要约12小时才能重新稳定。关键技巧定期例如每天将sgp30.baseline_eCO2和sgp30.baseline_TVOC这两个属性值保存到文件中。当下次启动时读取文件中的值并赋给sgp30.set_iaq_baseline(eCO2_baseline, TVOC_baseline)。这可以极大缩短启动后的稳定时间并保持数据的一致性。PMS5003ST的维护激光粉尘传感器怕潮、怕油污、怕积尘。长期运行后风扇和激光腔内部可能会积灰导致读数偏高或不稳定。维护建议每3-6个月用压缩气罐或吹气球从进气口和出气口轻轻吹气清理表面浮尘。切勿用水或酒精直接冲洗传感器内部。如果设备在厨房等重油烟环境附近使用维护周期需要缩短。5.3 常见问题与排查思路I2C设备找不到IOError: [Errno 121] Remote I/O error检查接线确认SDA、SCL线没有接反、接触不良。检查上拉电阻用sudo i2cdetect -y 1命令扫描I2C总线。如果看不到设备地址SCD30通常是0x61SGP30是0x58可能是总线电平问题。尝试在SDA和SCL线上各加一个4.7kΩ电阻上拉到3.3V。检查电源确保传感器供电充足尤其是SCD30启动电流较大。串口读取不到PMS5003ST数据确认串口启用再次检查raspi-config中串口控制台是否已禁用硬件串口是否已启用。确认设备名尝试将/dev/serial0改为/dev/ttyAMA0。检查电平转换如果使用了电平转换模块确认方向是否正确5V端接传感器3.3V端接树莓派。逻辑分析仪/示波器如果有条件可以抓一下串口TX线上的波形看是否有数据发出。数据偶尔跳变或出现异常值电源干扰这是最常见的原因。确保使用优质的电源适配器并考虑在树莓派电源输入端并联一个大电容如470uF以平滑电压。传感器供电线尽量短。软件去抖动在代码中对读取的数据进行简单的滑动平均滤波。例如记录最近5次的有效读数取中位数或平均值作为输出可以滤除偶发的尖峰。物理环境检查传感器是否靠近空调出风口、风扇、门窗缝隙这些地方的气流和温湿度变化剧烈会影响读数。将设备放置在能代表房间整体状况的位置离墙、人体、电器一定距离。树莓派CPU占用率过高优化读取频率没有必要每秒读取所有传感器。PM2.5和气体变化相对较慢可以设置为每10-30秒读取一次。SCD30的官方建议读取间隔也是大于2秒即可。避免忙等待在主循环中使用time.sleep()让出CPU时间。检查日志如果数据库文件变得非常大查询和插入可能会变慢。可以考虑定期将旧数据归档或转移到其他存储。这个项目从简单的想法出发最终搭建起一个功能完整、可长期运行的个性化环境监测站。整个过程涉及了硬件选型、嵌入式编程、数据存储、可视化和系统运维等多个环节是一个非常好的全栈实践。最让我有成就感的是通过它我真正“看见”了家里的空气变化规律做饭时PM2.5的飙升、夜间关窗后CO2的缓慢爬升、开启净化器后污染物浓度的下降曲线。数据不再冰冷它成了感知环境的延伸。你可以在此基础上继续扩展比如增加甲醛传感器如SEN0465、接入智能家居平台Home Assistant实现自动化联动超标自动开净化器甚至将多个节点的数据汇总到云端做地图展示。希望这份详细的指南和踩坑经验能帮你顺利搭建起属于自己的那一份“空气感知力”。