构建一个分布式空气质量监测网络
我本科学的是生态学,主修环境科学。多年来,我一直在研究生态系统,阅读政府机构发布的空气质量报告,并依赖一些无法独立验证的数据。
到了某个时刻,这让我感到足够困扰,以至于我采取了行动。
我不想要一个仪表盘,我想要一个网络。不是在阳台上安装一个传感器,而是要在不同地点设置多个站点——城市、乡村、靠近采石场、靠近农场的地方——所有站点都向同一个数据库发送数据,并且这些数据可以相互比较。
第一个站点于2024年4月在拉多夫利察附近的小村庄兰科沃上线。到2025年初,该系统已发展成我未曾完全规划的规模:覆盖斯洛文尼亚7个户外活跃站点、一台位于Synology NAS上的InfluxDB中央实例、Grafana仪表板,以及一个将所有内容连接起来的Tailscale VPN。
这就是那个项目。
什么是AirVibe
AirVibe 是一个分布式空气质量监测网络。每个站点均采用 Raspberry Pi Zero 2W,配备 Pimoroni Enviro+ HAT 和 PMS5003 激光粒子计数器,置于辐射屏蔽罩内并安装于户外。所有站点每15秒通过 Tailscale VPN 向中央 InfluxDB 2 数据库发送一次数据,Grafana 实时可视化这些数据。
硬件 — 一站式
每个户外站点使用:
•树莓派Zero 2W — 主要计算
•Pimoroni Enviro+ HAT — 温度、湿度、压力、光照、噪音、气体(MICS6814)
•Plantower PMS5003 — 粒物浓度:PM1、PM2.5、PM10
•屏幕(辐射屏蔽)——防风雨外壳,气流通道
•USB电源 — 标准5V
Enviro+ 直接连接到 Pi Zero 的 GPIO 接口。PMS5003 通过扁平电缆连接至 HAT 的 PM 接口。整个装置可安装在 100 毫米的辐射屏蔽罩内,该屏蔽罩与专业气象站所使用的类型相同,现提供套件形式。
Pi Zero 2W 被选中是因为其体积小巧、功耗低(约1–2瓦),并且足以支持每15秒运行一次的Python数据记录循环。该设备没有屏幕、没有键盘,外壳仅包含保护罩。整个系统无显示器,完全通过Tailscale的SSH进行管理。
网络架构
这正是AirVibe与大多数单传感器项目不同的地方。
我需要解决的问题是:远程站点位于不同位置,使用不同的网络服务提供商。有些站点背后是未设置端口转发的路由器,另一些则被阻止了特定移动运营商的接入。我需要一种方法,让所有站点都能可靠地访问中央 InfluxDB,同时不将其暴露给互联网。
解决方案:Tailscale。
每个站点都运行着Tailscale。中央的Raspberry Pi 5(baseRapi)充当代理服务器——它接收来自Tailscale IP地址的InfluxDB写入请求,并通过iptables NAT将数据转发至本地网络中的Synology NAS。从每个远程站点的角度来看,向InfluxDB写入数据仅相当于向一个私有IP地址发送HTTP POST请求。无需暴露任何凭据,NAS上也没有开放端口。
Bash
所有节点的 Tailscale 密钥过期设置为“永不过期”——这是一堂惨痛的教训。过期的密钥看起来和硬件故障完全一样:站点似乎已失效,日志停止,Pi 本身没有任何错误提示。在意识到这一点之前,我丢失了数天的数据。
7个活跃站点
•aq-off — 卢布尔雅那(EIMV网站)— 城市户外
•aq-oms — 卢布尔雅那环境监测站 — 户外,官方环境监测网站
•aq-lan — 拉科沃 — 户外、乡村;首站,2024年4月
•aq-kg1 — 卡姆纳戈里察(农场)——户外,乡村/农业
•aq-kg2 — 卡姆纳戈里察(采石场)——户外,工业附近
•aq-mp — 波尔霍夫格拉代茨 — 户外,郊区/森林边缘
•aq-po — 持久的 — 户外,最成熟的站点
Ljubljana OMS站点位于一个官方环境监测点,这引发了一个有趣的问题:低成本的光学颗粒计数器与专业参考仪器的实际差异有多大?虽然比较并非主要目标,但当准备查看时,相关数据将会提供。
软件 — 数据流水线
每个站点都以 systemd 服务的形式运行一个 Python 脚本(重启=始终)。该脚本每 15 秒循环一次:
•读取Enviro+(温度、湿度、压力、气体传感器)
•阅读 PMS5003(PM1、PM2.5、PM10)
•健康检查:如果PM2.5 > 2000,则丢弃(传感器错误或启动残留)
•通过 Tailscale 使用行协议向 InfluxDB 2 写入数据
Python
本地缓存并重新发送:如果 InfluxDB 无法访问(网络中断、Tailscale 重启),脚本会将测量数据写入本地的 JSON 文件。在下次成功连接后,它会重新发送缓存的数据点;如果再次失败,则将数据点移至隔离文件夹。这可防止在连接中断期间造成数据丢失。
时钟守护程序:在启动时,脚本会检查系统时间是否合理后再进行写入。树莓派Zero没有实时时钟(RTC),因此在断电后冷启动时,时间会从纪元开始,直到NTP同步为止。使用1970年作为时间戳写入数据会导致InfluxDB的时间序列损坏。时钟守护程序会将脚本置于等待循环中,直到时间有效为止。
Python
systemd 服务(/etc/systemd/system/aq_station.service):
这
After=time-sync.target 确保服务在 NTP 准备就绪之前不会启动。EnvironmentFile 将所有凭据和配置都保留在脚本之外。
最成熟的车站:波斯托伊纳(aq-po)
并非所有站点都同等重要。Postojna 是最成熟的——我所建立的,也是引领其他所有站点发展的核心。
它具备我在运营网络14个月期间所开发的全部可靠性功能:
•无限循环,重启=始终
•所有配置的环境文件
本地缓存 + 重新发送 + 隔离
••时钟防护装置(前后)
•_safe_write_point() 是对 InfluxDB 写入操作的封装
•normalize_fields_to_float() — 防止 InfluxDB 中字段类型冲突
•PMS5003 逻辑检查
•在 systemd 中的 time-sync.target
•已启用监控
当我部署新站点时,会按照波斯托伊纳标准进行建设。当旧站点出现停机故障时,我会将其升级至波斯托伊纳标准。这正是14个月生产故障所告诉我们的经验教训。
一个常见模式:Pico W + socat 代理
两个站点——卡姆纳戈里察(采石场)和卢布尔雅那OMS——使用Raspberry Pi Pico W进行传感器读取。Pico W性能不错且价格低廉,但无法连接Tailscale网络。这意味着我需要一种方法,将Pico W的数据直接传入InfluxDB,而无需通过VPN访问。
解决方案:socat TCP 代理。
Pico W 向伴侣设备 Zero 的本地 IP 发送数据。Zero 运行一个 socat 监听器,通过 Tailscale 将数据隧道传输至 InfluxDB。两个小型电路板各负责一项任务——虽然不够优雅,但非常可靠。
Bash
这并不优雅,但确实有效,它让我明白,限制会促使我找到原本无法想到的创造性解决方案。
Grafana 仪表板
所有数据都存储在 InfluxDB 2(Flux 查询)中,并在 Grafana 中进行可视化。每个站点都有独立的桶。仪表板包含:
•Geomap — 斯洛文尼亚地图上的所有站点,实时显示PM2.5数值
•时间序列——各站点的PM1、PM2.5、PM10、温度、湿度
•24小时平均值——一目了然的每日暴露情况
•风向玫瑰图(Plotly)——OMS站点的风向和风速
•气体传感器——标记为未校准(MICS6814 需要校准以获取绝对值)
我学到的东西
1. 第一个难题并非传感器本身,而是网络。让一台树莓派读取传感器是一个教程问题,而让7台树莓派分布在7个位置,能够持续数月可靠地写入一个数据库,无需人工干预,则是一个系统层面的问题。Tailscale解决了网络层,systemd配合Restart=always解决了进程层,本地缓存则解决了数据完整性层。每一层的优化都耗费了大量时间。
2. 时间处理起来出人意料地困难。Raspberry Pi Zero 没有实时时钟。每次断电后,它都会从1970年重新启动。如果你的 systemd 服务在 NTP 同步之前启动,就会得到一个漂亮的连续时间序列,其中包含从1970年1月1日开始的时间戳数据。InfluxDB 可以接受这种数据。但你的图表将无法正常恢复。时钟守护程序是我添加的最后一件,也是最重要的部分。
3. Tailscale 密钥过期会毁掉你的一天。默认情况下,Tailscale 密钥有效期为180天。密钥会静默过期,站点会显示为离线状态。你认为是硬件故障,于是前往现场检查,发现 Pi 正常运行。请将所有节点的密钥过期设置为“永不过期”,并记录在文档中。再次阅读一遍。
4. 切勿删除 InfluxDB 中的数据以解决字段类型冲突。如果某个字段被意外写成字符串而非浮点数,InfluxDB 将拒绝所有后续使用相同字段名的写入操作。这时很容易想删除该测量并重新开始,但请不要这样做。应创建一个新的数据桶。你现有的数据是真实有效的,无法恢复。
5. 参考标准比覆盖范围更重要。我有7个站点,其中一些比其他站点更可靠。波斯托伊纳站是我的参考基准——所有我信任的功能都首先在该站进行测试。当我部署新站点或升级旧站点时,都会朝着这一标准进行建设。拥有一个可靠且已验证的参考站点,比拥有7个普通站点更有用。
6. 可持续的现场部署是一种设计约束。我尽可能通过电动自行车、火车或步行前往站点。这既源于个人价值观,也出于实际考虑——这意味着我会仔细权衡哪些问题需要亲自到现场处理,哪些可以远程解决。一个需要频繁现场维护的站点,说明其设计已经失败。我为远程管理能力所做的每项改进(如Tailscale SSH、监控守护进程、systemd自动重启、智能插座实现远程重启),都能降低网络的碳足迹。
本文编译自hackster.io





