ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

物联网粉尘监测预警系统课程设计:从STM32采集到MQTT传输与可视化

物联网粉尘监测预警系统课程设计:从STM32采集到MQTT传输与可视化 简介基于物联网的作业场所粉尘危害监测预警系统是一套面向物联网、计算机、自动化等专业课程设计与毕业设计的完整项目资源源代码和文档说明均已整理齐全覆盖了系统方案设计、前端监测页面与业务逻辑实现。资源以Web端监测界面为核心压缩包共952个文件、仅5.06MB其中包含453张页面设计图、276个JavaScript逻辑脚本、82个CSS样式文件、60个HTML页面以及24个JSON数据配置另有地图、字体、文本说明、Markdown文档等辅助文件目录按功能划分方便从界面布局到数据请求逐层阅读。目前已有181人学习代码经过测试运行成功并配有课程设计说明与答辩评审平均96分的项目背景既适合作为课程作业、毕设或项目初期演示的素材基础也适合在理解源码后按监测点位、告警阈值、界面风格等需求二次开发。整体上对掌握物联网数据可视化、前端交互配置和完整项目打包交付方式具有直接的参考价值。1. 物联网粉尘监测预警系统一份课程设计源码先看它能不能真的预警在煤矿、建筑工地、面粉厂这类作业场所呼吸性粉尘是职业健康绕不开的威胁。很多课程设计把“物联网”“粉尘监测”“预警”这几个词摆在一起但真正能把传感器采集、无线传输、服务端存储、前端展示一条链路完整串起来的资源并不多。这套基于物联网的作业场所粉尘危害监测预警系统属于典型的物联网综合课程设计硬件端负责粉尘浓度采集服务端负责数据落库和阈值判断前端用 layui 搭出监测面板和预警记录。它自带源代码和文档说明代码已经跑通数据库脚本也齐全适合物联网、计科、自动化、电子信息等专业学生拿去当课程设计或者毕业设计的前置基础。第一次接手时我也踩了不少坑下文把复现步骤和参数边界讲清楚。2. 整体架构与数据链路传感器采集到预警推送中间经过了哪几个环节这套系统不是单机程序数据要从粉尘传感器一路流到浏览器页面。按物联网三段式划分感知层是传感器和单片机传输层是 WiFi 模块加 MQTT 协议平台层是后端服务、数据库和前端页面。资源里的源代码重点在平台层硬件端以示例代码和接线说明出现。理解数据链路后再动手才能知道启动后每一步应该看到什么现象而不是一启动就抓瞎。2.1 感知层STM32 读粉尘传感器模拟量到浓度值怎么换算常见课程设计选型有两种一种是用激光散射式数字粉尘传感器比如 PMS5003 这类直接通过串口输出 PM2.5、PM10 数值单片机只需要解析串口帧另一种是用红外模拟量传感器比如 GP2Y1010AU0F输出一个和粉尘浓度近似线性关系的电压。这个资源里更贴近后者的处理逻辑因为硬件端代码通常会出现 ADC 采样。模拟量传感器的换算公式并不复杂核心是先读 ADC 原始值再把原始值换成电压最后用电压减去零点电压后除以灵敏度。零点电压和灵敏度必须查传感器数据手册不同批次传感器会有偏差。课程设计阶段允许使用固定系数但答辩时要能说清楚“这个公式是从手册哪里来的”。#include stm32f1xx_hal.h // 假设 ADC 精度为 12 位参考电压 3.3V // 零点电压 V0 和灵敏度 K 根据传感器手册和实际标定得到 #define ADC_REF_VOLTAGE 3.3f #define ADC_RESOLUTION 4095.0f #define SENSOR_ZERO_V 0.60f #define SENSOR_SLOPE 0.20f // 单位V per mg/m^3仅示意 float read_dust_mg_per_m3(ADC_HandleTypeDef *hadc, uint32_t channel) { ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel channel; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_239CYCLES_5; HAL_ADC_ConfigChannel(hadc, sConfig); HAL_ADC_Start(hadc); HAL_ADC_PollForConversion(hadc, 100); uint16_t raw HAL_ADC_GetValue(hadc); HAL_ADC_Stop(hadc); // 原始值换算成电压再用线性公式换算为浓度 float vout raw * ADC_REF_VOLTAGE / ADC_RESOLUTION; float concentration (vout - SENSOR_ZERO_V) / SENSOR_SLOPE; if (concentration 0) concentration 0; return concentration; }这里最关键的是SENSOR_ZERO_V和SENSOR_SLOPE一个是无尘环境下的基础电压一个是电压随浓度的变化率。有的课程设计要求把 mg/m³ 换算成 μg/m³这时前端显示的数字会差 1000 倍很容易在答辩时被问出破绽。另一个是采样时间参数ADC_SAMPLETIME_239CYCLES_5表示用较长采样周期粉尘浓度本身是缓变量不需要高速采样但能明显减少读数的抖动。2.2 传输层ESP8266 通过 MQTT 上报主题和报文格式要先统一感知层拿到浓度数值后需要把它送到服务端。常见做法是 STM32 通过串口把数据交给 ESP8266ESP8266 连上 WiFi 后使用 MQTT 协议上报。MQTT 比 HTTP 轮询更适合这类传感器上报场景长连接开销小设备端断线后可以按固定间隔重连不需要服务端反向感知设备在不在线。设备端上报时最容易翻车的是 payload 结构。后端的解析代码写的是pm25设备端却发的是PM2.5或者把单位混在字段里后端 JSON 解析就会直接失败。我一般会先约定一个最小化报文设备 ID、PM2.5、PM10、温度字段名全部用小写驼峰单位统一在文档说明里写死。#include ESP8266WiFi.h #include PubSubClient.h const char* ssid your_wifi; const char* password your_password; const char* mqtt_server 192.168.1.100; // 后端所在服务器或本机 IP const int mqtt_port 1883; const char* client_id client_dust_01; const char* topic_dust dust/device01; WiFiClient espClient; PubSubClient mqtt(espClient); void publishDust(float pm25, float pm10, float temperature) { char payload[128]; snprintf(payload, sizeof(payload), {\deviceId\:\device01\,\pm25\:%.2f,\pm10\:%.2f,\temp\:%.1f}, pm25, pm10, temperature); mqtt.publish(topic_dust, payload); // QoS 用 0 即可课程设计不做补偿 } void reconnect() { while (!mqtt.connected()) { if (mqtt.connect(client_id)) { // 设备侧只上报不需要订阅任何主题 } else { delay(2000); } } }这段代码里mqtt_server要写服务端所在的局域网 IP不能写 localhost因为设备和服务端不是同一台机器。client_id也不能跟其他设备重复否则后面接入的设备会把前面设备的连接踢下线。QoS 用 0 在课程设计里够用如果以后接真实产线建议改成 QoS 1收到 PUBACK 后再清空本地待发送缓存。2.3 平台层后端订阅主题、数据落库与阈值判断服务端是整个系统的核心。它要做三件事订阅 MQTT 主题、把接收到的数据写入 MySQL、根据阈值判断是否需要生成预警记录。Java Web 后端常见的做法是继承某个 MQTT 回调接口在messageArrived里完成从 JSON 到实体类、从实体类到数据库表的转换。Component public class DustMqttHandler implements MqttCallback { Autowired private DustDataService dustDataService; Override public void messageArrived(String topic, MqttMessage message) throws Exception { String payload new String(message.getPayload(), StandardCharsets.UTF_8); // payload 示例: {deviceId:device01,pm25:88.5,...} DustData data JSON.parseObject(payload, DustData.class); // 1. 原始数据先落库保证历史曲线可查 dustDataService.insert(data); // 2. 读取该设备阈值并判断连续两次超标才触发预警 boolean over dustDataService.checkAndAlarm(data.getDeviceId(), data.getPm25()); if (over) { alarmService.createAlarm(data.getDeviceId(), data.getPm25()); } } }insert和checkAndAlarm是两条独立的职责。很多课程设计版本把两条逻辑混在一起数据还没入库就开始判断一旦判断逻辑抛异常原始数据也丢了。根据实际拆过的项目更稳妥的顺序是先 insert再判断判断时只读设备表里的阈值字段不把预警状态写在同一个事务里。这样即使报警逻辑出错也不会污染原始监测数据。数据库这边一般至少有三张表设备信息表、粉尘浓度记录表、预警记录表。设备信息表里存阈值参数浓度记录表是高频写入表预警记录表只在超标时增加。后面改阈值时不需要动代码直接更新设备表里的字段就行。3. 把源码跑起来环境版本、数据库导入和后端启动顺序拿到资源先别急着点启动先理清文件目录和文档说明。这类资源包通常包含一个后端工程、一个前端静态资源目录、一个 SQL 脚本目录以及一份 Word 或 PDF 格式的课程设计文档。搞清楚每部分对应的作用启动时才知道报错应该去哪边找原因。3.1 先看资源包结构源代码、SQL 脚本、设计文档分别在哪资源解压后的目录不一定完全一致但基本结构可以按功能划分。常见是这样路径内容你要做的事backend/Java 后端工程包含 controller、service、mapper导入 IDE 后配置 JDK 和 Mavenfrontend/layui 静态页面包含 css、js、html放到后端 static 或 webapp 目录sql/数据库初始化脚本建表语句创建数据库并导入脚本docs/需求分析、设计说明、答辩相关文档按实际项目内容修改前端目录里如果出现layui.css、skin.css、toast.css这一批文件说明页面是基于 layui 皮肤体系搭的。这些 css 是项目自带资源不用额外下载。运行时要注意资源引用路径这个在避坑章节里会专门讲。3.2 环境配置JDK、MySQL、IDE 的版本匹配规则课程设计项目往往不是最新技术栈版本匹配比功能本身更影响启动成功率。我拆过的类似资源里出现最多的是 JDK 版本过高导致编译失败或者 MySQL 8 驱动跟旧连接串配置不兼容。先检查本机环境java -version mvn -v mysql --version建议版本范围JDK 1.8 或 11Maven 3.6 以上MySQL 5.7 或 8.0IDE 用 IDEA 即可。如果用的是 JDK 17很多老项目会直接报cannot access class之类的依赖错误不是项目有问题是版本太新。后端依赖如果 Maven 拉不动可以配置国内镜像mirror idaliyunmaven/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror这段配置放在 Maven 的settings.xml的mirrors节点里。配置完镜像后首次构建要联网拉包时间较长别误以为卡死。等控制台出现BUILD SUCCESS后再继续下一步。3.3 数据库初始化与启动顺序先导表再启动后端再开前端数据库脚本是整个系统能跑起来的地基必须先做。命令行导入mysql -uroot -p sql/dust_monitor.sql如果脚本里没有CREATE DATABASE语句就手动先建库CREATE DATABASE dust_monitor DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;导入完成后修改后端配置。Spring Boot 项目通常改application.ymlSSM 项目则改jdbc.properties。配置内容核心是数据库连接串注意时区和 SSL 参数server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/dust_monitor?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 123456这段配置里serverTimezoneAsia/Shanghai是关键MySQL 8 不写这个参数会直接启动报错。characterEncodingutf8保证前端页面的中文不乱码useSSLfalse避免连接时证书校验导致的告警。用户名和密码改成你本机实际值。后端启动有两种方式。如果是 Spring Boot 打包目录直接用cd backend mvn spring-boot:run如果是传统 war 包项目需要把 war 丢到 Tomcat 的webapps目录下再启动 Tomcat。启动后先验证接口是否通再开页面curl http://localhost:8080/api/dust/latest?deviceIddevice01能返回 JSON 数据说明后端、数据库、MQTT 接收链路已经通了。此时再通过浏览器访问http://localhost:8080打开前端面板。前端页面不要直接用文件方式打开layui 的 css 资源路径是按 web 服务根目录写的直接点 html 文件会白屏。4. 预警阈值与可视化面板关键参数在哪改前端图形怎么接数据系统能跑起来只是第一步真正决定答辩观感的是预警规则是否合理、图表是否在动。这一章把阈值配置和前端可视化这两部分讲透。4.1 阈值参数放在数据库还是配置文件怎么改才生效阈值放在数据库表里而不是硬编码在 Java 类里是这类系统比较合理的设计。它允许你不动代码就调整报警线。资源里比较常见的设计是设备表带阈值字段或者单独有一张sys_config配置表。假设阈值存在device_info表中修改方式就是一条 SQLUPDATE device_info SET alarm_threshold_pm25 75.0 WHERE device_id device01;后端判断时从设备表读取这个字段public boolean checkThreshold(String deviceId, float pm25) { Float threshold deviceInfoMapper.getAlarmThreshold(deviceId); if (threshold null) { threshold 75.0f; // 默认阈值按 μg/m^3 约定 } return pm25 threshold; }这里必须注意单位。有的项目浓度单位用 μg/m³阈值写 75有的项目用 mg/m³阈值写 0.075。字段本身只存一个数字单位靠前后端约定。如果前端图表单位是 μg/m³后端判断也应该是 μg/m³中间不要做二次缩放否则会出现“曲线看着超标了却不报警”的玄学问题。4.2 ECharts 实时曲线的数据来源和刷新机制前端页面一般用 layui 搭框架用 ECharts 画实时曲线和柱状图。实时曲线的数据来源是后端的最新数据接口前端通过定时器拉取var chart echarts.init(document.getElementById(realTimeChart)); function loadRealTimeData() { $.get(/api/dust/latest?deviceIddevice01, function (resp) { if (resp.code 0) { var data resp.data; chart.setOption({ xAxis: { data: [data.timestamp] }, series: [{ data: [data.pm25] }] }); } }); } setInterval(loadRealTimeData, 5000);这个写法能用但存在一个常见问题setOption会全量重绘图表每 5 秒闪一下看久了眼睛不舒服。更顺滑的做法是维护一个数组把新值 push 进去超过一定数量后 shift 掉旧值再更新 series 数据。刷新间隔建议 3 到 5 秒太短对后端查询压力变大太长又看不出连续变化趋势。4.3 历史数据查询与统计报表的边界历史曲线是答辩时老师比较容易追问的部分它考验的是时间范围和查询条件有没有做对。资源里的历史查询接口一般长这样GET /api/dust/history?deviceIddevice01start2025-01-01 00:00:00end2025-01-01 23:59:59后端 Mapper 对应使用 BETWEEN 查询。这里有几个容易踩的边界第一条前端传的时间如果带毫秒后端解析要用LocalDateTime而不是Date否则时区会偏移 8 小时第二条如果一次查询的数据量特别大前端一次性渲染会卡崩可以在接口层做分页每页固定返回两百条前端滚动加载第三条预警记录表要有“已处理”状态字段否则页面上只能看历史告警没法继续做闭环。5. 复现与移植避坑清单启动失败、数据不动、样式丢失的三类问题这部分是实际拆项目和跑源码时最容易卡住人的地方。很多资源代码本身没大问题问题都出现在环境、路径和参数约定不一致上。下面按现象、原因、解决三个层次记录有同样问题可以直接对照。5.1 启动后端就报数据库连接失败现象后端启动时控制台抛Communications link failure或者提示Access denied for user程度轻一点的会一直打印Cannot create PoolableConnectionFactory。原因通常有三个数据库连接串里的库名写错、MySQL 用户名密码跟本地不一致、MySQL 8 驱动要求额外时区参数。解决方式分两步走先确认库存在再确认连接参数mysql -uroot -p -e SHOW DATABASES LIKE dust%如果库名没问题就回到 Spring 配置里检查serverTimezoneAsia/Shanghai是否加上。另外注意mysql-connector-java版本驱动 5.x 配 MySQL 8 会报ClassNotFoundException或时区错误换 8.x 驱动即可。这类问题属于环境类型跟项目代码逻辑无关先刷日志再动代码。5.2 MQTT 客户端连不上 broker现象设备端串口一直打印failed to connect后端日志里也看不到任何 MQTT 事件。原因大多是 broker 地址配置错误或者防火墙把 1883 端口拦了。有的资源里没有自带 broker需要自己装一个 EMQX 或 Mosquitto。先在本机验证 broker 是否通mosquitto_sub -h 192.168.1.100 -t #如果本机能收到数据说明 broker 正常问题在设备端网络。如果本机也收不到检查 broker 是否启动、端口是否被占用。另一个隐蔽问题是设备端配置的client_id与另一个客户端重名MQTT broker 会踢掉旧连接导致两边轮流掉线。调试时给每个客户端一个独一无二的client_id比如client_dust_01_xxx。5.3 前端页面加载出来但样式全乱现象浏览器能打开页面但看到的是一堆没有排版的列表和表单布局layui 的下拉框、弹窗全部无效。原因基本是静态资源路径不对。页面里引用的是/static/layui/css/layui.css如果项目部署在http://localhost:8080/dust-monitor/子目录下浏览器会去http://localhost:8080/static/...找资源前端页面实际在/dust-monitor/static/...自然 404。解决方式是让所有静态资源引用带上项目上下文路径!-- 错误写法 -- link relstylesheet href/static/layui/css/layui.css !-- 正确写法 -- link relstylesheet href${pageContext.request.contextPath}/static/layui/css/layui.css如果你用的是 Spring Boot 且部署在根路径直接配置server.servlet.context-path/也可行但不建议为了省事把项目强行挂到根目录会导致本地多项目联调时互相干扰。5.4 数值一直为 0或者报警像抽风一样刷屏现象前端曲线一直是 0或者一旦超阈值就连续生成几十条报警记录。前者的原因往往是设备端上报字段和后端解析字段不一致比如设备端发的PM25后端读的是pm25后者则说明预警逻辑缺少“连续判定”和“冷却时间”。解决方式先看后端打印的 MQTT payload 原始数据确认字段名、单位、数据类型是否完全匹配。然后检查预警判断逻辑比较合理的做法是连续 N 次超过阈值才触发并且同一设备在触发后进入 10 分钟冷却期避免单次毛刺导致报警风暴。int count dustDataService.countConsecutiveOver(deviceId, threshold, 3); if (count 3 alarmService.canAlarm(deviceId, 10)) { alarmService.sendAlarm(deviceId); }countConsecutiveOver表示最近连续几条记录超标canAlarm判断当前距离上次报警是否超过 10 分钟。这两个参数一个控制灵敏度一个控制报警频率课程设计里建议分别取 3 次和 10 分钟实际现场可以按粉尘波动程度调整。5.5 历史曲线时间差 8 小时现象设备上报时间正常页面曲线整体往后或往前偏移 8 小时。原因很简单服务器时区被设置成了 UTC而前端按北京时间显示。解决方式是 MySQL 连接串加serverTimezoneAsia/ShanghaiJava 服务端统一用LocalDateTime存储时间前端传入时间戳时用DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss)解析。改完这三处时间就对齐了。6. 从课程设计到毕设演示无硬件时如何造数、压测和讲解拿到这套源码时如果手上没有硬件不用慌。把模拟数据脚本跑起来同样能演示完整链路。我一般会优先写一个随机序列的 MQTT 发布脚本把浓度值做成“小范围波动加定时尖峰”这样既能看到实时曲线变化也能演示预警阈值触发过程。import json import time import random from paho.mqtt import client as mqtt_client broker 127.0.0.1 topic dust/device01 client mqtt_client.Client(python_simulator) client.connect(broker, 1883, 60) client.loop_start() while True: pm25 50 random.uniform(-5, 5) current_second int(time.time()) % 60 if current_second 0: pm25 120 # 每分钟制造一次超标尖峰用于触发预警 payload json.dumps({ deviceId: device01, pm25: round(pm25, 2), pm10: round(pm25 * 1.2, 2), temp: 25 }) client.publish(topic, payload, qos0) time.sleep(3)脚本前半部分用随机数模拟正常波动每分钟整点强制把 PM2.5 提到 120用来演示预警触发。注意这个值要高于正常波动、低于设备阈值否则要么报警一直不触发要么刚启动就误报。使用这个脚本时把broker改成你的 MQTT 服务地址topic改成资源里后端实际订阅的主题跑起来后打开前端页面就能看到曲线在一跳一跳地涨跌。演示时我一般准备两条路径先展示正常监测数据流动让页面曲线持续刷新再手动把阈值调低或者让脚本尖峰持续几秒触发一条预警记录然后切换到预警列表页面展示报警明细。这样回答“系统怎么验证”这个问题时手里有完整的数据链路不是只靠 PowerPoint 动画。从那以后我每次拿到类似的物联网课程设计资源都强制走一遍流程先确认 SQL 能导再启动后端看日志最后用模拟数据脚本穿透到前端页面。不要一上来就接硬件把设备端、服务端、前端三段链路分开验证能省掉很多“不知道是哪一段出问题”导致的熬夜。希望帮到你。本文还有配套的精品资源点击获取
返回列表