基于物联网的毕业设计任务书:从选题到系统架构的完整技术指南
1. 背景痛点:为什么你的 IoT 毕设总被导师打回?
做毕设时,很多同学把“物联网”当成一个上档次的关键词,却在任务书里写“用 Proteus 仿真 128 个节点”——导师一眼就能看出:这玩意儿根本跑不起来。下面几个坑,几乎年年有人踩:
- 过度依赖模拟器:Proteus、Tinkercad 只能验证电路逻辑,无法反映真实射频环境。天线怎么走线?TLS 握手会不会把 RAM 撑爆?仿真里永远给不了你答案。
- 忽略设备认证:把 MQTT 用户名/密码硬编码到固件里,一旦设备丢失,整条数据链路被人“白嫖”。答辩时被问“如果学生宿舍 Wi-Fi 密码改了怎么办”,只能沉默。
- “拍脑袋”技术栈:看到网上说 ESP32 跑 MQTT 很香,就写“本设计采用 ESP32”,结果任务书连“FreeRTOS 任务划分”都没提;真到开发阶段,发现单核 240 MHz 也扛不住 HTTPS 的 TLS 握手,直接原地爆炸。
- 架构图清一色“云-管-端”三层:看似高大上,却说不清“边”到底在哪。真到现场部署,发现 200 ms 的云端往返延迟让控制环路震荡,电机来回“抽搐”。
想顺利过关,任务书必须回答三个问题:① 真实场景是什么?② 数据怎么采上来?③ 采上来以后怎么存、怎么展示?下面给出一套“能跑、能改、能扩展”的最小可行模板,照着抄也能让导师点头。
2. 技术选型对比:MQTT vs HTTP,ESP32 vs Pi Pico
2.1 协议层:MQTT 还是 HTTP?
低功耗场景下,协议选型直接决定续航和实时性。
| 维度 | MQTT | HTTP |
|---|---|---|
| 连接开销 | 一次 TCP 长连接,心跳包 2 Byte | 每次请求都要 TCP 三次握手+TLS |
| 报文大小 | PUBLISH 最小 2 Byte 头 | 仅 HTTP 头就 ≥ 300 Byte |
| 离线缓存 | QoS1/2 支持本地持久化 | 需自己写缓存逻辑 |
| 代码复杂度 | 发布/订阅 2 个 API | 需封装 REST,解析 JSON |
| 功耗(ESP32 实测) | 浅睡 + 长连接 0.8 mA | 每次 HTTPS 唤醒 80 mA·s |
结论:只要 MCU RAM > 64 kB,优先 MQTT;HTTP 留给“一天上报一次”的极低速场景。
2.2 硬件层:ESP32 还是 Raspberry Pi Pico W?
| 维度 | ESP32-S3 | Pico W |
|---|---|---|
| 主频 | 240 MHz 双核 | 133 MHz 单核 |
| RAM | 512 kB + 8 MB PSRAM 可选 | 264 kB |
| 无线协议 | 2.4 GHz Wi-Fi/BLE | 2.4 GHz Wi-Fi |
| 深度睡眠电流 | 10 μA | 12 μA |
| 单价 | 25 元 | 18 元 |
结论:需要同时跑 TLS、OTA、边缘计算脚本 → ESP32;只采集+上报 → Pico W 足够,功耗还低 20%。
3. 核心实现:温湿度监测的端-边-云架构
3.1 系统总览
- 端:Pico W + SHT30,每 30 s 采集一次
- 边:ESP32-S3 做“边缘聚合”,负责 TLS 终止、设备 ID 白名单、本地缓存
- 云:EMQX + TDengine + Grafana,Web 端 3 秒刷新一次
3.2 数据流
- 传感器 → Pico W:I²C 读取温湿度,JSON 序列化
- Pico W → ESP32:UDP 广播(端口 9999),明文,节省电量
- ESP32 → EMQX:MQTT over TLS,Topic 命名规则
dt/{device_id}/telemetry - EMQX → TDengine:规则引擎直接写入,表结构自动创建
- TDengine → Grafana:SQL 查询,面板变量
$device_id
3.3 关键代码(MicroPython,Pico W 端)
# main.py - 遵循 Clean Code:单一职责、显式异常 import time import json import network from machine import Pin, I2C import urequests as requests SHT30_ADDR = 0x44 I2C = I2C(0, scl=Pin(1), sda=Pin(0), freq=100_000) def read_sht30() -> tuple[float, float]: I2C.writeto(SHT30_ADDR, b'\8\0x2C\x06') time.sleep_ms(15) raw = I2C.readfrom(SHT30_ADDR, 6) t = -45 + (175 * (raw[0] 8 | raw[1])) / 65535 h = 100 * (raw[3] 8 | raw[4]) / 65535 return round(t, 2), round(h, 2) def wifi_connect(ssid, pwd): sta = network.WLAN(network.STA_IF) sta.active(True) sta.connect(ssid, pwd) while not sta.isconnected(): time.sleep_ms(500) return sta.ifconfig()[0] def udp_broadcast(payload: bytes): import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.sendto(payload, ('255.255.255.255', 9999)) s.close() def main(): ip = wifi_connect('your_ssid', 'your_pwd') while True: t, h = read_sht30() msg = json.dumps({'id': 'pico_001', 't': t, 'h': h, 'ts': time.time()}) udp_broadcast(msg.encode()) time.sleep(30) if __name__ == '__main__': main()3.4 边缘聚合(ESP32-S3,Arduino)
#include <WiFi.h> #include <PubSubClient.h> #include <WiFiClientSecure.h> const char* ssid = "your_ssid"; const char* password = "your_pwd"; const char* mqtt_server = "emqx.xxx.com"; const int mqtt_port = 8883; const char* ca_crt = \ "-----BEGIN CERTIFICATE-----\n" \ "MIIDUTCCAjmgAwIBAgIJAPPYCjT3cEs9MA0GCSqGSIb3DQEBCwUAMD8xCzAJBgNV\n" \ ... // 省略 1 kB 的 CA "-----END CERTIFICATE-----\n"; WiFiClientSecure esp_tls; PubSubClient mqtt_client(esp_tls); void callback(char* topic, byte* payload, unsigned int len) { // 预留:下行控制指令解析 } void setup() { Serial.begin(115200); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) delay(500); esp_tls.setCACert(ca_crt); mqtt_client.setServer(mqtt_server, mqtt_port); mqtt_client.setCallback(callback); // 本地 UDP 监听 WiFiUDP udp; udp.begin(9999); } void loop() { if (!mqtt_client.connected()) { String client_id = "esp32_edge_" + String((uint32_t)ESP.getEfuseMac(), HEX); mqtt_client.connect(client_id.c_str(), "edge_user", "edge_pwd"); } mqtt_client.loop(); // 非阻塞读取 UDP int len = udp.parsePacket(); if (len) { char buf[512]; udp.read(buf, len); buf[len] = 0; // 简单鉴权:白名单 if (strstr(buf, "\"id\":\"pico_001\"")) { mqtt_client.publish("dt/pico_001/telemetry", buf); } } }4. 性能与安全考量:别让“小项目”变成“大事故”
冷启动延迟
Pico W 从main.py到连上 Wi-Fi 约 2.3 s;若把 TLS 握手也放上去,电池瞬间被拉崩。边缘节点专职做 TLS,终端只跑 UDP,能把唤醒电流降低 60%。消息幂等性
网络抖动会导致 MQTT QoS1 重传,TDengine 里出现重复行。可在 JSON 里加入uuid字段,写库时用INSERT ... USING STABLE TAGS (...) VALUES (...) ON DUPLICATE KEY UPDATE ts=ts,去重同时不丢最新值。固件 OTA 风险
ESP32 的 OTA 分区只有 2 个,若新固件启动失败会自动回滚,但 Pico W 没有双分区。升级前先把boot.py写成“双备份”:- 主程序
main.py校验失败 → 自动拷贝main.old并重启 - 云端下发升级包时,必须带版本号与 CRC32,否则拒绝刷写
- 主程序
5. 生产环境避坑指南:从“能跑”到“敢跑”
- 密钥管理:用 ESP32 的 efuse 烧录 256 bit 设备密钥,配合 EMC 加密,防止固件被 binwalk 直接读出密码。
- QoS 等级:遥测数据用 QoS0,控制指令用 QoS1;切忌全部 QoS2,否则 EMQX 的内存会随着客户端数线性爆炸。
- 日志分级:正式烧录关闭
Serial.println,改用log_d()宏,默认不编译进固件,节省 12 kB Flash。 - 看门狗:ESP32 硬件看门狗默认 5 s,若 TLS 握手偶尔 7 s 超时会被误杀,需在
esp_task_wdt_init(10)手动放宽。 - 数据类型:TDengine 对字符串长度敏感,把
float写成double会多占 4 Byte,一年 5000 万条记录就是 200 MB 冤枉钱。
6. 小结与延伸思考
一套“温湿度监测”看似小儿科,却能把端-边-云、TLS、幂等、OTA 全部串起来;把它写进任务书,导师既能看懂,你也真做得出来。下一步,不妨思考:
如果教室里再摆 20 个 Pico W,如何做到“即插即用”?
- 用 ESP-NOW 组 Mesh 替代 UDP 广播,省掉路由器
- 在边缘节点跑 TinyML,对温度序列做异常检测,再上报“事件”而非“原始值”
- 把 Grafana 换成 React + ECharts,通过 WebSocket 推流,实现真正的“实时大屏”
动手复现一遍,把遇到的每一个“坑”写成日志,你的毕设就不再是“纸上谈兵”,而是能部署在实验室、让师弟师妹继续用的“小产品”。祝你答辩顺利,代码不翻车!