智能井盖系统毕设实战:从传感器到MQTT告警全流程解析
发布时间:2026/9/13 5:02:36来源:尧图网络
简介智能井盖系统V1.0是一套面向毕业设计与课程作业的完整项目源码聚焦城市基础设施管理中井盖状态监测、异常报警与可视化监控场景适合计算机类、人工智能方向学生用于学习物联网与AI融合落地。压缩包共803个文件约11.19MB既有Java/PHP/ASP/JSP等后端服务代码也有JS/CSS/HTML等前端页面资源还包含PNG/JPG/GIF界面素材、配置文件、数据库及项目工程文件整体覆盖从数据采集、服务端处理到管理后台展示的主要环节。目前已有221人学习下载。通过阅读源码目录与核心模块可直观理解传感器数据上传、AI异常识别、报警推送、管理后台等链路的设计思路内容预览中的接口处理脚本、上传类与配置加载文件可作为梳理系统请求流程的切入口同时前端页面、接口代码与部署配置也能为复现系统、二次开发和撰写设计文档提供直接参考。1. 智能井盖系统V1.0一份能直接答辩的毕设骨架智能井盖系统V1.0.zip 这个命名很常见但压缩包里往往不是可运行项目而是“半成品实验报告”。真正的价值在于把井盖状态数字化井盖被非法开启、井下积水、可燃气体超标、设备掉线都需要在几秒内上报到后台。对毕设和课程作业来说评委会问三个问题你的数据从哪来走什么协议告警是怎么产生的这里不展示效果而是按“传感器 → 单片机 → MQTT → 后端 → 可视化”的主线给出每个环节的最小实现和参数依据。适合物联网、嵌入式、软件工程方向的学生以及想快速搭同类演示系统的开发者。2. 井盖采集端从传感器到单片机的最小实现智能井盖系统V1.0的采集端需要回答“测什么、怎么接、怎么读”。常见的方案会选四个状态量倾斜角度用角度传感器或倾斜开关、水浸状态接触式探头、环境温湿度DHT11、可燃气浓度MQ系列。如果选用MPU6050测倾角可以同时得到加速度和角速度比单纯开关更有答辩点。这里以ESP32作为主控理由是板载WiFi能直接走TCP/IPADC接口够用Arduino生态调试速度快。2.1 传感器选型与引脚分配先给出一份能跑通的引脚分配表不需要外扩板直接用开发板供电。传感器输出类型接ESP32引脚供电SW-520D倾斜开关数字量0/1GPIO43.3V水浸探头LM393数字量0/1GPIO53.3VDHT11温湿度单总线GPIO183.3VMQ-4甲烷传感器模拟量GPIO36ADC1_CH05V输出接ADC可选光敏电阻模拟量GPIO39ADC1_CH33.3V注意MQ-4的加热电阻需要5V供电模拟输出可以直接接ESP32的ADC输入如果模块带比较器则输出数字信号。DHT11尽量使用独立3.3V避免和感性负载共用电源。2.1.1 倾斜、水浸和温湿度的接线细节倾斜开关SW-520D本质是滚珠开关水平时导通倾斜超过15度后断开。为了读成数字量需要外接10kΩ上拉电阻到3.3V输出端接GPIO4读值为HIGH表示水平LOW表示倾斜。水浸探头上电后探头碰到水时输出低电平。DHT11只有三根线VCC、GND、DATADATA接GPIO18但DHT11对时序极其敏感代码里不要用delay阻塞太久。2.1.2 用ESP32读取第一组数据下面这段Arduino代码可以直接放进采集端工程先做数据裸读不做协议封装#include DHT.h #define TILT_PIN 4 #define WATER_PIN 5 #define DHT_PIN 18 #define MQ4_PIN 36 #define DHT_TYPE DHT11 DHT dht(DHT_PIN, DHT_TYPE); void setup() { Serial.begin(115200); pinMode(TILT_PIN, INPUT_PULLUP); // 内部上拉省掉外接电阻 pinMode(WATER_PIN, INPUT_PULLUP); dht.begin(); analogReadResolution(12); // 0-4095 analogSetAttenuation(ADC_0db); // 输入电压范围限制在1.1V左右若接分压电路 } void loop() { int tilt digitalRead(TILT_PIN); // LOW 倾斜 int water digitalRead(WATER_PIN); // LOW 有水 float h dht.readHumidity(); float t dht.readTemperature(); int mq4 analogRead(MQ4_PIN); // 原始ADC值未标定 Serial.printf({\tilt\:%d,\water\:%d,\hum\:%.1f,\temp\:%.1f,\gas\:%d}\n, tilt, water, h, t, mq4); delay(2000); }analogReadResolution(12)把ADC位数设为12位对应0~4095analogSetAttenuation(ADC_0db)将输入量程压缩到约1.1V如果直接用3.3V供电就需要改衰减档比如ADC_11db可扩到3.3V。delay(2000)在裸测时没问题但在后续上报逻辑里会让采集周期变成2秒不适合低功耗后面会改成定时唤醒。2.2 数据帧设计要点采集端读出的数据不能直接往服务器塞需要规定一个最小数据帧让后端只认自己人。常见做法是统一JSON结构并带上设备ID、时间戳和固件版本{ deviceId: WC-001, ts: 1738416000, tilt: 1, water: 0, humidity: 45.2, temperature: 23.1, gas: 980, bat: 3.86 }时间戳用Unix秒而不是带时区的字符串好处是后端排序、聚合省去解析bat是电量由ADC读取电池分压值换算。MCU裸测通过后再决定哪一部分数据由云端判定告警哪一部分只做展示。2.2.1 开关量的防抖处理倾斜开关和水浸探头在临界点会频繁跳动如果不处理后端会收到一串0/1交替的告警。最简单的办法是连续读5次值一致才认为有效int readStablePin(int pin, int samples) { int last digitalRead(pin); for (int i 0; i samples; i) { int cur digitalRead(pin); if (cur ! last) return -1; // 不稳定返回无效 delay(5); } return last; }samples5表示约25毫秒内电平不变才接受对井盖倾斜这种慢变化足够了。不要为了省时间把samples降为2否则弱振动环境下告警还会抖动。2.2.2 为什么模拟量建议用两个ADC通道MQ-4和光敏电阻分别占用GPIO36和GPIO39这两个通道都是ADC1不能和WiFi同时使用吗实际上ESP32的ADC2会被WiFi占用导致读值不稳定ADC1则不受影响。所以任何需要上报的模拟量都优先接在ADC1的通道上GPIO32~GPIO39都行。毕设里最容易翻车的点就是用了ADC2得到一堆互斥错误的读数却以为是传感器坏了。3. 井盖数据上云MQTT协议与断线补传策略智能井盖系统V1.0的传输层是整个项目的灵魂。很多同学在答辩时被问“为什么不用HTTP”答不出所以然。井盖数量多、上行流量小、偶尔断线用HTTP轮询会造成大量无效请求而MQTT基于发布/订阅一条连接可复用QoS能保证不丢包正好匹配这个场景。3.1 选择MQTT的动机和主题规划一个井盖就是“发布者”后端是“订阅者”。MQTT的Topic本质是UTF-8字符串按层级用/分隔。常规规划是把主题分为三组wellcover/{deviceId}/telemetry周期上报遥测数据温度、电量、倾角wellcover/{deviceId}/event突发告警事件非法开启、水浸、气体超标wellcover/{deviceId}/cmd云端下发指令重启、校准、修改采样周期各类权限应分开设备只允许发布/订阅与自己deviceId相关的前缀后端订阅所有wellcover/...。这可以通过EMQX的ACL实现。3.2 EMQX本地搭建与连接参数开发环境用EMQX比用Mosquitto更省事一是带Web控制台二是规则引擎对毕设加分。在Ubuntu服务器上安装社区版curl -s https://assets.emqx.com/scripts/install-emqx-deb.sh | sudo bash sudo apt-get install emqx -y sudo systemctl start emqx运行后18083端口是控制台1883是MQTT端口。开机自启用sudo systemctl enable emqx。连接参数有四个关键点Client ID必须全局唯一Keep Alive建议30~60秒Clean Session为false才能离线接收遗嘱消息QoS分为0/1/2毕设里遥测用0、告警用1就够了QoS2在高频率下会造成消息堆积。3.2.1 创建账号和ACL规则在控制台“Access Control”里创建用户iot-device和iot-backend。设备端连接时使用iot-device但只能发布wellcover/WC-001/#这样带前缀的主题。ACL规则可以写成{ rules: { publish: [wellcover/WC-001/telemetry, wellcover/WC-001/event], subscribe: [wellcover/WC-001/cmd] } }后端账号iot-backend订阅wellcover/#同时有权限向wellcover/WC-001/cmd发布指令。如果设备很多建议用{clientid}变量替换固定编号避免每条设备写死规则。3.2.2 遗嘱消息与断线检测井盖被撬走后可能直接断电服务器需要尽快感知。利用MQTT的LWT机制在连接时设置遗嘱主题wellcover/WC-001/event遗嘱内容为{type:OFFLINE}当设备异常断开时broker会替它发布这条消息。配合Keep Alive后端能在30秒左右收到掉线事件而不是等用户打开前端才发现。3.3 采集端MQTT发布代码在ESP32上使用PubSubClient库需要把WiFi连接和MQTT发布放到同一个loop里。下面是一段可用的发布函数同时演示了断线重连和简单本地缓存#include WiFi.h #include PubSubClient.h #include Preferences.h const char* ssid your-wifi; const char* password your-pass; const char* mqtt_server 192.168.1.10; const int mqtt_port 1883; const char* deviceId WC-001; WiFiClient espClient; PubSubClient mqtt(espClient); Preferences prefs; void publishTelemetry(String json) { String topic wellcover/ String(deviceId) /telemetry; mqtt.beginPublish(topic.c_str(), json.length(), false); mqtt.print(json); mqtt.endPublish(); } void checkMqtt() { if (!mqtt.connected()) { prefs.begin(buffer, false); String pending prefs.getString(pending, ); if (pending.length() 0 mqtt.connect(deviceId, iot-device, iot-pass)) { mqtt.publish(wellcover/ String(deviceId) /event, pending.c_str()); prefs.remove(pending); } prefs.end(); } mqtt.loop(); }beginPublish支持分片发送避免一次性把大字符串压进内存prefs库把断网期间的告警先写入NVS Flash等重连后补发。这里QoS用的默认0如果要保证告警不丢应该在发布时显式传QoS1但需要同时处理ACK回调否则会累积重复消息。下表汇总了采集端推荐参数参数推荐值说明Keep Alive30s心跳间隔短了耗电长了断线延迟QoS0遥测/ 1告警遥测可容忍丢失告警不能Retainfalse状态类主题可考虑true但需要定期清理Clean Sessionfalse设备让会话保留设备离线期间的遗嘱4. 后端告警与可视化让井盖状态能被看到智能井盖系统V1.0的应用层完成两件事根据主题解析出告警把告警和遥测数据落到数据库并展示。后端选Spring Boot是稳妥选择社区资料多答辩不会被难倒前端用VueECharts展示设备地图和实时趋势。4.1 Spring Boot集成MQTT订阅在pom.xml加入spring-integration-mqtt依赖然后在配置文件中声明MQTT连接参数spring: mqtt: url: tcp://192.168.1.10:1883 username: iot-backend password: iot-pass client-id: backend-server default-topic: wellcover/#注意default-topic必须以/分隔不能写成wellcover/*#表示多层通配符表示单层通配符。如果订阅的是wellcover//telemetry就只能接收所有井盖的遥测收不到事件。4.1.1 消息处理与实体映射写一个MqttMessageHandler对收到的消息先用deviceId找井盖再更新状态Component public class MqttMessageHandler implements MessageHandler { Autowired private WellcoverService service; Override public void handleMessage(Message? message) { String topic message.getHeaders().get(mqtt_receivedTopic).toString(); String payload new String((byte[]) message.getPayload(), StandardCharsets.UTF_8); // topic: wellcover/WC-001/telemetry Matcher m Pattern.compile(wellcover/([^/])/(.)).matcher(topic); if (m.matches()) { service.process(m.group(1), m.group(2), payload); } } }正则在这里比split(/)更稳妥因为topic最后的event或telemetry可能携带子路径。service.process里根据type决定走告警规则还是更新快照。4.1.2 告警规则怎么写告警规则不写在Controller里而是放在独立方法中便于测试。例如public void process(String deviceId, String type, String payload) { JsonObject data JsonParser.parseString(payload).getAsJsonObject(); int tilt data.get(tilt).getAsInt(); int gas data.get(gas).getAsInt(); boolean alarm (tilt 1) || (gas 1500) || waterLevel.equals(LOW); if (alarm) { createAlarm(deviceId, type, data); } updateSnapshot(deviceId, data); }气体阈值1500是经验值实际上要根据MQ4的标定曲线换算成PPM。答辩时可以说“阈值放在后端是为了避免设备端算力不足和误报堆积同时支持远程动态调整。”这是一个加分点。4.2 前端大屏展示与告警推送如果不想从零写一个重型前端可以用Vue 3 ECharts搭一个单页支持按井盖编号过滤。下面这段ECharts配置把后端API返回的井盖温度历史画成折线const chart echarts.init(document.getElementById(chart)); fetch(/api/wellcovers/WC-001/history) .then(res res.json()) .then(rows { chart.setOption({ xAxis: { type: time, data: rows.map(r r.ts * 1000) }, yAxis: { type: value }, series: [{ name: 温度, type: line, data: rows.map(r [r.ts * 1000, r.temperature]), smooth: true }] }); });时间戳乘以1000是因为ECharts的time轴按毫秒处理。rows来自后端/api/wellcovers/{id}/history接口接口里按ts升序返回。地图展示不是必选项如果要展示建议把井盖经纬度存到wellcover表再用scatter图层叠加不要在SQL里做空间计算。4.2.1 告警记录表设计为了支撑“哪些井盖出过事”后端至少要有两张表wellcover和alarm_record。alarm_record的字段如下字段名类型说明idBIGINT主键自增device_idVARCHAR(32)井盖编号alarm_typeVARCHAR(20)TILT / WATER / GAS / OFFLINEvalueVARCHAR(64)触发时的原始值tsINT UNSIGNED触发时间戳statusTINYINT0未处理1已确认告警接口必须保证幂等同一个deviceId在同一个时间戳重复上报后端不能生成两条记录。实现方式是在alarm_type device_id ts上加唯一索引否则MQTT重传会导致告警翻倍。4.2.2 告警推送规则毕设里最常见的推送是站内信或WebSocket但如果想突出工程能力可以加一个简单的通知接口当status0且时间超过2分钟未处理时调用钉钉或飞书机器人Webhook。这个设计能让评委看到你考虑过“事件闭环”而不是只做个展示页面。通知内容只需带上井盖编号、告警类型和触发时间不要传递完整JSON避免敏感信息泄漏。5. 调优三板斧测量精度、低功耗与异常排查智能井盖系统V1.0最常见的瑕疵是数据乱跳、电池掉电快、断线后重启丢事件。日常调这类系统只做三件事。5.1 一维卡尔曼滤波吃掉抖动倾斜开关的值比较干净但模拟量气体、水位容易受供电波动影响。最简单有效的滤波是一阶低通或一维卡尔曼。下面这段适用于传感器值的卡尔曼代码放在MCU端float kalmanFilter(float z, float x, float p, float q, float r) { p p q; float k p / (p r); x x k * (z - x); p (1 - k) * p; return x; }q是过程噪声r是测量噪声。对气体传感器q取0.01r取0.5能得到比较平滑的曲线。过滤后的值只参与告警判断原始值仍要保留方便答辩时展示“滤波前后对比”。5.2 低功耗定时唤醒参数井盖大多数时间是静止的没必要每秒上报。用ESP32的light_sleep模式每5分钟唤醒一次采集传感器然后立即发送esp_sleep_enable_timer_wakeup(5 * 60 * 1000 * 1000); // 单位是微秒 esp_light_sleep_start();唤醒后先等待ADC稳定再读传感器。WiFi重连是最大耗电项所以不要在每次唤醒都重新连WiFi如果已经断开应采用“尝试连接3次失败则进深度睡眠”的策略把监测周期放宽到10分钟。参数表如下场景心跳周期采样次数重连策略正常井盖300s2次取平均失败立即sleep告警状态5s1次失败通过备用链路上报5.3 用日志和MQTT X验证数据链路调通系统后最怕测评现场某个井盖不更新。一般会在后端写一个只带时间戳的调试接口比如GET /api/debug/pump手动触发一条模拟报文curl -X POST http://localhost:8080/api/debug/pump \ -H Content-Type: application/json \ -d {deviceId:WC-001,ts:1738416000,tilt:1,water:0,gas:900}这条命令绕过MQTT直接验证后端入库和告警展示是否正常。如果后端能看到告警说明问题在采集端或网络如果看不到就去EMQX控制台的“Topic”页签里看有没有设备上线。最后一步用MQTT X订阅wellcover//event手动触发井盖能看到事件即链路OK。提示不要把MQTT连接超时时间设成5秒这么短否则弱网环境下每次上报都会被判定掉线蜂鸣器响个不停。一般把连接超时设为15秒Keep Alive设为30秒超时时间应略大于Keep Alive两者是联动的。本文还有配套的精品资源点击获取
网站建设高端定制企业官网