智慧城市大气环境监测系统:从传感采集到平台展示的完整落地指南
简介这是一套智慧城市大气环境监测系统的前端项目资源核心基于Vue.js实现面向智慧城市、环境监测方向的开发者和学习者可用于理解大气数据可视化与监测面板的完整构建思路。压缩包共包含38个文件其中12个Vue组件构成主要页面5个JavaScript文件负责交互逻辑10张PNG图片用于界面图标与素材另有JSON配置、HTML入口、SCSS样式、Markdown说明等整体体积约7.05MB。目录结构清晰包含am-dashboard-master主控制面板、src源码目录、public静态资源、router路由、utils工具等模块运行维护方便。资源描述系统梳理了智慧城市、综合资源、人工智能等关键概念如何落地并覆盖从传感器数据采集、物联网传输、AI分析预测到Vue图表展示、自动预警的完整工作流程能够帮助读者快速建立环境监测系统全局认知。目前已有216人学习特别适合需要参考监测看板设计、搭建Dashboard项目的初中级Vue开发者也可作为课程设计或毕业设计的实用参考资料。1. 智慧城市大气环境监测系统.zip先说清楚它到底是什么做这类项目的人通常第一眼会问智慧城市大气环境监测系统到底是一个硬件项目还是一个软件项目我的答案是它两头都必须打通。单看名字它描述的是一套覆盖区域空气质量监测的完整系统在若干点位部署传感器终端把 PM2.5、PM10、温度、湿度这类数据以分钟级频率上报到平台落库后通过大屏和接口对外展示并在一氧化碳、二氧化氮等指标超限时给出告警。它面向的是园区、工地、区县网格化监测这类真实落地场景适合要交付项目、做 POC 验证或独立接单的从业者。压缩包里的代码可以改但链路骨架通常改不了采集、传输、存储、展示少一环都跑不起来。如果说有什么开场建议那就是别急着解压源码先拿着标题反推系统边界。真正让我在多个项目里翻车的从来不是某个传感器读不出数而是数据从现场设备走到大屏的那条路上每一段都各有一套自己的脾气。这篇文章就按这条链路把系统拆开讲清楚再把参数、代码和坑位一一摆出来。2. 系统整体拆解先看数据要过哪些关卡2.1 四层链路监测、传输、平台、展示各负其责我一般会把这类系统分成四层来看做项目时也按这四层去验收。监测层是数据源头包括粉尘传感器、气体传感器、温湿度探头和边缘网关。传感器负责把空气里的颗粒物浓度转换成电信号或数字信号网关负责把这些信号读出来整理成统一的报文。这一层的常见误区是以为传感器越贵越好实际是得看场景网格化监测密度高点位多单个点位承受不起几万块的设备成本但作为参考站或者考核站低成本传感器又扛不住长期漂移。所以先在成本和精度之间做一个明确定位再选型。常用的低成本方案是激光散射法测 PM2.5 和 PM10配合电化学传感器测 SO2、NO2、CO、O3要求高的站点才用 β 射线法但那种设备体积大、价格高不适合大规模布点。传输层解决的是“现场数据怎么到机房”。常见做法是网关通过串口或 Modbus 读取传感器再走 MQTT 或 HTTP 把数据推给平台。为什么不让传感器直接连服务器因为市面上传感器协议五花八门有的走 Modbus有的走主动上报帧让传感器直连平台平台侧要为每种传感器写一套解析逻辑改一个型号就要动服务端。用网关在边缘做一次协议转换平台侧只认一种标准报文后续换传感器型号成本全部留在网关端。这也是我把网关放进监测层而不是传输层的原因它既是设备也是边界。平台层包含消息接收端、数据库、API 服务三块。消息接收端负责接收上报数据做基本校验数据库负责落盘API 服务给大屏和第三方系统提供查询能力。点位少、数据量可控时MySQL 或 PostgreSQL 就够用点位超过几十个、数据又要长期保留时建议上时序数据库。展示层则是可视化大屏、地图点位和实时曲线技术本身不难难的是数据延迟和点位状态能不能在大屏上真实反映出来。层级常见实现适合场景需要留意的点监测层激光散射粉尘传感器 电化学气体模块 边缘网关网格化布点、园区、工地传感器一致性、零点漂移传输层MQTT / HTTP、4G / 有线点位分散、网络不稳定断网缓存、报文去重平台层Mosquitto / EMQX、MySQL / 时序库、API 服务几十到几百个点位时序聚合、保留策略展示层Web 大屏、地图、ECharts监控室、管理端按时序刷新的性能2.2 最小闭环方案先跑通两个点位再加规模拿到压缩包第一件事不是把代码全部跑起来而是先定一个最小闭环验证路径。我一般会部署两个真实点位、一台服务器和一个最简单的网页验证数据从设备到大屏的完整链路是通的再谈扩容的事。这个 POC 阶段建议固定用 MySQL 或 SQLite 起步不要一上来就上分布式时序集群那是给自己找麻烦。技术选型上默认组合是简单设备走串口或 Modbus 接入网关做协议转换后用 MQTT 上报平台端用 Mosquitto 收消息后端服务订阅并把数据写入数据库最后通过 API 给大屏。为什么选 MQTT 而不是 HTTP因为这类系统的数据是周期上报每个点位每分钟一条MQTT 长连接比 HTTP 短连接省资源而且地方政务类项目里网络质量参差不齐长连接配合断线重连机制比每次重建 HTTP 请求要稳定。HTTP 也不是没有位置我会把它用在“补传”场景后面第 4 章专门展开。部署顺序也有讲究。先手工往数据库里插一条测试数据确认 API 和大屏能显示再启动服务器收消息最后才接真实传感器。每一步都验证完再走下一步能让你从“不知道是哪一段坏了”的泥潭里脱身。很多人喜欢先把所有服务全部启动再看大屏有没有数据一旦没数据排查范围是整个系统效率极低。3. 监测终端落地传感器怎么选、采集程序怎么写3.1 传感器选型与单点验证监测终端最核心的部件是粉尘传感器和气体传感器。粉尘传感器多用激光散射法它在采样口吸入空气用激光照射空气中的颗粒物通过散射光强度推算浓度它的优点是体积小、响应快、便宜缺点是长期使用后风扇积灰会导致读数偏低。气体传感器分电化学和半导体两类电化学精度相对高、选择性好但需要预热时间刚上电的前几分钟数据不能用半导体传感器便宜但对温度湿度敏感不建议在考核场景使用。拿到设备后不要直接装到杆子上就完事。我一般会用一间封闭的小房间做单点验证把传感器和一台便携式检测仪放在同一个位置连续读取三十分钟对比两者的平均值和波动曲线。如果传感器读数明显偏低先考虑是不是进气口被遮挡或者采样风扇转速异常。还有一个很容易踩的坑不同厂家的“PM2.5 数据”可能不是一回事有的输出的是质量浓度有的输出的是计数浓度必须确认单位是 µg/m³量程是否覆盖现场可能出现的高浓度场景比如工地扬尘可能上千。这一步不确认后面所有分析都是白搭。安装位置同样影响数据质量。传感器采样口不能朝上否则除非下雨时进水否则灰尘堆积会让你后期反复被叫去现场清洗安装箱要有遮阳和通风结构避免阳光直射导致箱内温度过高。远离烟囱、食堂油烟口这类本地污染源还要避开强风直接吹向采样口的安装方位。这些都是血泪经验我在某园区项目里一套终端就装在空调外机排风口旁边pm10 数据天天爆表查了一个星期才发现是空调吹出来的干扰。3.2 编写采集程序从串口读出传感器并组装标准报文采集程序的职责很单一按周期从传感器读取原始值加上设备编号和时间戳组装成标准报文交给上层传输模块。它不应该直接包含 MQTT 逻辑也不应该包含数据库逻辑这样后续换传输协议或者换传感器驱动都不用改整条链。下面是一段从串口粉尘模块读取数据的示例代码通信格式因模块而异但套路是一致的# sensor_driver.py 传感器读取驱动示例 import serial import struct def read_dust(port: str): # 以某型激光粉尘传感器为例9600 波特率8 数据位1 停止位 ser serial.Serial(port, baudrate9600, timeout2) ser.flushInput() # 不同模块的查询帧不同以模块手册为准 ser.write(b\x42\x4d\x00\x00\x00\x10) buf ser.read(16) if len(buf) 16 or buf[0] ! 0x42 or buf[1] ! 0x4d: return None # 帧头不对直接丢弃 # 部分模块协议里 PM2.5 和 PM10 是 16 位无符号整数大端序 pm25 struct.unpack(H, buf[4:6])[0] pm10 struct.unpack(H, buf[6:8])[0] ser.close() return {pm25: pm25, pm10: pm10}这段代码做了三件关键的事指定串口参数、发送查询帧、校验帧头。串口参数里最容易错的是波特率不同传感器模块可能是 9600 也可能是 115200读出一堆乱码先查波特率。帧头校验一定要做因为串口通信链路本身没有可靠性保障突发干扰可能导致读到半个帧不校验就解析脏数据会直接进入上报链路。拿到原始读数后标准报文我习惯这样组织包含设备 ID、采集时间、各指标数值、信号质量和一条诊断状态字段。诊断状态字段很重要它记录的是传感器是否正常、网关温度是否过高等信息上线后排障全靠它。这个时间戳必须用网关本地时钟生成同时尽可能做网络校时否则断网期间积累的数据时间会对不上。3.3 时间戳与边缘缓存断网时数据不能丢大气监测系统的数据有一个特点丢失一个时间点事后无法补测。所以网关端必须在断网时先把数据缓存下来网络恢复后再补传。实现方式不复杂本地用 SQLite 建一张缓存表上报失败就把报文原样写进去后台一个定时任务负责把缓存中的数据交还传输模块。# cache.py 边缘缓存示例 import sqlite3 def append_to_cache(device_id: str, payload: dict): conn sqlite3.connect(/data/air_monitor/cache.db) conn.execute( INSERT INTO outbox (device_id, payload, created_at) VALUES (?, ?, ?), (device_id, json.dumps(payload), time.time()), ) conn.commit() conn.close()缓存表设计最需要注意的一点是每条数据要有独立的序列号推荐用时间戳加自增序号这样补传时服务端可以按序列号去重。有人会把缓存和本地数据库一体化用同一个 SQLite 文件这没问题但要小心存储空间被日志撑爆建议给缓存表做条数上限超过一万条就按时间清理优先保留最新的数据。断网久了缓存堆积是正常现象真正危险的是不做限制导致存储卡写满整个终端都罢工。4. 数据上行与平台承接MQTT 为主、HTTP 补传为辅4.1 MQTT 上报连接参数、主题设计与 QoS 选择MQTT 是这套系统的主干。在平台侧起一个 Mosquitto 或 EMQX 实例终端通过网关以固定频率发布数据到某个主题平台服务订阅这个主题再入库。主题的命名我一般分层设计例如city_air/{区域}/{点位}/data一个点位一条子主题方便按点位做权限控制和消息过滤。上报频率和参数设置很有讲究。我以 60 秒上报一次为例连接参数和发布代码可以这样写# mqtt_report.py MQTT 发布端参数设计示例 import paho.mqtt.client as mqtt import json, time client mqtt.Client(client_idair_001, clean_sessionTrue) client.username_pw_set(air_user, air_pass) # keepalive 必须大于上报周期的 2 倍避免误判掉线 client.connect(mqtt.center, 1883, keepalive120) while True: payload json.dumps({ device_id: air_001, ts: int(time.time()), pm25: 35.2, pm10: 68.4, temperature: 24.1, humidity: 46.8, status: 1 }) # QoS1 保证至少一次配合服务端 seq 去重 client.publish(city_air/district_01/air_001/data, payload, qos1) time.sleep(60)keepalive 参数这里经常有人设错。它表示客户端必须在这么长时间内至少给 Broker 发送一次消息否则 Broker 会认为连接已死并清除会话。如果把 keepalive 设成 30 秒而上报周期是 60 秒客户端又没有单独发送心跳包那 Broker 会周期性误判掉线终端就在“连接-断开-重连”里无限循环。所以 keepalive 一定要大于上报周期经验值是上报周期两倍以上。QoS 的选择和很多人的直觉相反。QoS 2 确实能保证不重不漏但一个点位 60 秒一条消息几百个点位消息并发不高真正的问题来自网络抖动导致的重复发布。QoS 1 在语义上是“至少一次”网络不好时会重复投递服务端必须按“设备 时间戳 序列号”做幂等处理。retain 参数可以按需开启适合存放下一条最新数据作为点位状态展示但如果开习惯了大屏轮询这个特性容易让人忘记数据是否过期。4.2 断网补传HTTP 方式的后备通道长时间断网后MQTT 离线客户端的消息积压能力有限缓冲区满了就会丢消息。所以我的方案里总留第二条路HTTP 补传。网关检测到 MQTT 连续多次发送失败后停止 MQTT 发布转而把缓存数据通过 HTTP 批量提交到平台的补传接口网络恢复后切回 MQTT。这条路不追求实时性但保证了数据的完整性。# http_backfill.py 本地缓存数据 HTTP 批量补传 import requests, sqlite3, json def backfill(): conn sqlite3.connect(/data/air_monitor/cache.db) rows conn.execute( SELECT device_id, payload FROM outbox ORDER BY created_at LIMIT 200 ).fetchall() for device_id, payload in rows: resp requests.post( http://center/api/v1/collect, jsonjson.loads(payload), headers{X-Device-Id: device_id}, timeout10 ) if resp.status_code 200: conn.execute( DELETE FROM outbox WHERE device_id? AND payload?, (device_id, payload) ) conn.commit() conn.close()补传接口必须支持批量提交和幂等去重。批量提交能减少 HTTP 连接次数一次传 200 条效率比逐条高一个量级幂等去重依靠服务端对同一设备 ID、同一时间戳、同一序列号的报文只做一次入库。这里有个细节补传的数据时间戳依然是设备时间服务端收下后要用本地接收时间再做一份 server_ts 存储这样即使设备时钟偏差大管理员也能区分“数据是什么时候测的”和“数据是什么时候到的”。4.3 平台侧承接入库表和查询 API 的最小实现服务端收到 MQTT 消息或 HTTP 补传后第一件事是解析出标准字段做基本校验比如 pm25 是否在 0-2000 的合理区间、时间是过去还是未来然后写入时序数据表。表结构我习惯这样设计字段类型说明device_idvarchar(32)设备编号seqbigint设备端递增序列号pm25decimal(6,1)PM2.5 浓度µg/m³pm10decimal(6,1)PM10 浓度µg/m³temperature / humiditydecimal(4,1)温湿度device_tsdatetime设备采集时间server_tsdatetime服务端接收时间device_id、device_ts、seq 三个字段组合起来加唯一索引这是整个系统防重的基础。只要这个索引在MQTT 重复发布、HTTP 重复补传都会被数据库挡掉不会污染统计结果。查询 API 方面最常用的接口是“按时间范围查单点位的均值”。因为大屏不需要原始分钟数据通常按小时或天粒度展示所以要区分原始表、小时聚合表、天聚合表。小时聚合可以用定时任务在前一天结束后统一计算比每次查询时现算快得多。点位多起来之后这一步如果不做数据库迟早会被查询拖垮。5. 大气环境监测系统的高频踩坑现象、原因与解决5.1 夜间恒零、白天有值不是传感器坏了现象白天数据正常一到夜间 PM2.5 和 PM10 读数全部变成 0 或者非常低次日早上又恢复。原因常规排查顺序有问题。我先查代码再查网络最后才发现是采样系统的问题。夜间湿度大采样管内结露颗粒物被水汽挡住进不了检测腔或者是采样风扇夜间停转设备进入了某种低功耗状态。解决在传感器采样口加装加热除湿装置并把“设备自检状态”上报字段加进报文里。我在后续项目里固定给终端加一个风扇转速回读值只要转速低于阈值平台自动标记该点位数据无效而不是把 0 值当作真实浓度展示。数据无效和真实低浓度必须能在平台侧区分开这是底线。5.2 读数越用越高零点标定后还会复发现象设备刚部署时和便携仪对比基本一致一个月后读数系统性偏高做一次零点标定后恢复但很快又飘上去。原因传感器内部有零点漂移尤其是激光粉尘传感器长时间工作后光敏元件性能退化或者风扇积灰导致采样流量变小。标定只是把当前状态拉回零点没有消除退化的根因。解决一是把标定周期写进运维制度通常一个月至少一次二是安装时尽量远离震动和高温环境三是保留每台设备的历史曲线监控其离群偏差一旦偏差持续增长超过 15%就安排现场维护而不是等到爆表才处理。数据漂移不可怕可怕的是没有定期校验的动作。5.3 设备周期性掉线重连Broker 和网络都查不出问题现象设备每隔十几分钟掉线一次掉线几秒后自动重连平台日志里全是 session take over 和 CONNECT/DISCONNECT 记录。原因MQTT 客户端设置的 keepalive 比实际上报周期短或者网络中间设备空闲超时把连接断掉。我在某个项目里遇到的情况是 4G 运营商侧对长连接有 90 秒无数据即断的策略而上报周期是 60 秒MQTT 数据报文和心跳刚好卡在临界点上。解决把上报周期和心跳机制分开设计keepalive 设成上报周期的三倍并且确保客户端在空闲时主动发送 PINGREQ 心跳。另外大批量设备同时断网恢复后瞬间重连会给 Broker 造成冲击要加入随机延时重连延时范围建议 5 到 30 秒。5.4 时间戳错乱曲线图排序后整段撕开现象单台设备上报的时间戳忽前忽后大屏按时间排序后曲线出现一段断崖式跳变数据对不上。原因设备端时钟没有做同步。网关在断电重启后 RTC 初始化失败时间回到出厂值等到凌晨校时才恢复正常。由于平台按 device_ts 排序错误时间戳的数据被排到了错误位置。解决一是网关启动时强制从平台获取时间并校准校时失败前不对外上报二是平台入库时以 server_ts 为准做水位线校验凡是设备时间落后于该设备当前最近一条数据时间超过一定阈值判为无效数据。时序系统里时间就是数据的骨架这层不做防护后面的聚合统计全是地基问题。5.5 数据量并不大查询却越来越慢现象设备只有十几个数据库里几十万条记录按点位查一天的小时均值要好几秒。原因原始表没做时间分区查询时全表扫描更常见的是大屏频繁调用小时聚合接口每次都现场对原始数据做聚合计算。几十万条数据在数据库层面其实很小但每次都聚合几万行再传给前端慢是必然的。解决按天做表分区或索引优化预先计算好小时均值表每整点后由定时任务算上一小时的聚合API 层再对结果做一次缓存30 秒到 1 分钟的缓存足够支撑大屏轮询。这套组合做完单点位一天的数据查询响应时间能压到几十毫秒级。6. 上线前的验证和交付习惯让系统从能跑变成可信6.1 用三天并行对比数据别只信“看起来正常”部署完成不代表数据可信。我养成的习惯是至少选一个点位把便携式检测仪和系统终端放在同一位置连续并行运行 48 到 72 小时然后对比两条曲线。看三个指标平均偏差是否在合理范围内、趋势是否一致、极端值是否同步出现。平均偏差在 15% 以内可以接受趋势一致说明传感器响应没问题。6.2 故意断电断网验证缓存补传是真的断网补传最怕只在代码里写了实际没生效。测试方法是直接拔掉网线或断开 4G 模块等 15 分钟再恢复然后去平台查这 15 分钟的数据是否一条不落地补齐。这个测试要在交付前做不要在验收时做。我在某项目交付时就吃过亏断网两小时后恢复缓存补传一次成功但数据全重复了。原因是服务端的唯一索引在补传时才暴露冲突所以验收前就把去重逻辑真正跑一遍。6.3 留下诊断接口让远程排障不再“玄学”每个点位除了数据接口还应该有一个状态接口返回最近一次上报时间、缓存队列长度、传感器状态、信号强度。排障时先看这个接口再决定是派人还是直接远程重启能省掉大量现场往返成本。这个接口也方便集成进大屏的运维模块让平台使用者自己就能判断点位是否离线。系统能不能交付不取决于大屏做得多漂亮而在于数据坏了你能不能发现、发现了能不能定位。回头看我做过的几个项目凡是上线后运维顺畅的都做到了两件事每台设备都有可远程查询的状态接口每一天都有定时校时和缓存检查。这些细节在写第一版业务代码时就要留位置后期补往往牵一发动全身。如果你正在参照这个方向做自己的系统可以从两个点位开始把采集、上报、补传、去重、聚合全部打通再往三五十个点位扩展——前期的每一步试错都会在后期规模化时兑现成稳定。希望帮到你。本文还有配套的精品资源点击获取