做智能家居控制系统最怕的不是硬件没接对而是链路太长不知道哪一段出了问题。这篇博文不聊概念直接落地一套能跑通的三端方案STM32采集数据、经华为云IoT转发、Qt桌面端实时监控控制。整套系统做下来你会发现采集-上云-展示-遥控其实没有想象中那么玄乎真正的门槛在于把每一层的边界划清楚再把联调时那些细碎的问题一个个磨掉。适合的人群我直接说了有STM32基础、想往物联网全栈方向走的朋友或者正在做毕业设计/个人项目、需要一套完整可演示的智能家居原型的人。这篇内容不会只丢一个官方Demo让你自己看我会把设备端上报、云端鉴权、客户端订阅这条主链路从头到尾拆给你看包括那些文档里不会写的坑。1. 三层架构各管一段先从职责划分说起1.1 为什么非要三端协作而不是本地一把梭很多人一开始的想法是STM32接个WiFi模块自己搭个网页或者写个上位机不就行了吗确实能行但那叫局域网遥控不叫智能家居系统。一旦你出门在外手机和家里设备不在同一网段这套东西就废了。你需要的是一台中间人——设备端把状态发上去应用端从上面拉下来两边不需要知道对方在哪个网络角落。这就是云平台存在的价值。另外还有一个实际原因嵌入式设备的内存和Flash非常有限。STM32老老实实做采集、解析、控制这些实时性要求高的活就行别让它去存历史数据、算统计曲线、管多设备联动。云端和应用端干这些事更合适。所以三层职责其实很清晰端该管的不该管的STM32设备端传感器采集、GPIO控制、断线重连、执行命令历史数据存储、复杂界面、业务逻辑华为云IoT设备鉴权、消息路由、产品模型、规则联动传感器驱动、硬件控制Qt客户端数据可视化、用户交互、命令下发底层采集、网络透传1.2 数据流转的一条完整链路这套系统的数据流动很简单但每段用的协议和技术都不一样我把主链路画在脑子里给你描述一遍传感器温湿度→ STM32读取 → 串口送ESP8266 → Wi-Fi → 华为云IoT的MQTT Broker → Qt客户端订阅到消息更新界面。反过来你在Qt界面上点一下开灯命令就顺着同一根管道反向流回去Qt发布命令到MQTT Topic → 华为云IoT转发给设备 → STM32收到JSON → 解析后把GPIO拉高灯亮。关键在于这条链路上有两处翻译官。一处是STM32把传感器数据变成JSON字符串另一处是Qt把界面操作变成MQTT消息。华为云IoT在这里面只做一件事——按Topic和产品模型路由消息它不关心你的数据内容是温度还是湿度。1.3 华为云IoT在这条链里的独特价值很多初学者会问我直接用公共的MQTT Broker不就行了比如公共测试服务器为什么非得选华为云IoT我的回答是公共Broker只解决了消息中转问题但没解决设备管理和安全问题。你在公共Broker上发消息谁都能订阅你的Topic没有任何权限控制。而华为云IoT这类平台提供了一套完整的设备生命周期管理设备注册、密钥鉴权、Topic权限隔离、产品模型校验、消息跟踪。设备端要连上来必须带着合法的设备ID和签名密码否则直接拒绝连接。这对智能家居这种私密场景来说太重要了。另外平台还有个很实在的功能——消息跟踪。联调的时候设备上不来、消息丢了去平台的消息跟踪页面一看就知道问题在哪一段这个后面我会展开说。2. 设备端这一摊STM32采集上报与命令响应的完整逻辑2.1 硬件选型与接线里的几个坑我用的主控是STM32F407其实F103也完全够用主要看你手头有什么。传感器选的是DHT22温湿度一体精度比DHT11好不少。网络模块用ESP8266做TCP透传继电器模块控制灯和风扇。接线这里要提醒几个容易出问题的地方DHT22的数据引脚要接一个4.7k到10k的上拉电阻不然单总线通信很容易不稳定。继电器模块千万别直接从STM32引脚取电驱动GPIO只是信号脚要给继电器模块单独供5V而且地线要和STM32共地。ESP8266的供电最好用独立的3.3V稳压模块直接从开发板3.3V引脚取电可能导致WiFi发射时电流不足表现为时不时重启。判断供电问题有个笨办法但很有效看ESP8266板载指示灯。如果一连WiFi就闪、或者系统运行几分钟后串口打印乱码先怀疑电源不要怀疑代码。2.2 DHT22采集温湿度的关键代码逻辑DHT22用单总线协议时序要求比较严格。核心过程是主机先把总线拉低至少18ms告诉DHT22我要读数了然后释放总线DHT22会拉低80us响应再拉高80us之后开始输出40bit数据。// DHT22读取核心片段基于HAL库 uint8_t dht22_read(float *temperature, float *humidity) { uint8_t data[5] {0}; // 1. 主机拉低总线触发一次采集 HAL_GPIO_WritePin(DHT_GPIO_Port, DHT_Pin, GPIO_PIN_RESET); HAL_Delay(20); HAL_GPIO_WritePin(DHT_GPIO_Port, DHT_Pin, GPIO_PIN_SET); // 2. 等待从机响应先低后高 // 注意这里要用超时计数避免DHT22没接好时死循环 uint16_t timeout 0; while (HAL_GPIO_ReadPin(DHT_GPIO_Port, DHT_Pin) GPIO_PIN_RESET) { if (timeout 1000) return 0; // 超时退出 } // 3. 循环读取40bit数据每一位以高电平持续时间区分0和1 for (int i 0; i 40; i) { while (HAL_GPIO_ReadPin(DHT_GPIO_Port, DHT_Pin) GPIO_PIN_SET); while (HAL_GPIO_ReadPin(DHT_GPIO_Port, DHT_Pin) GPIO_PIN_RESET); delay_us(40); if (HAL_GPIO_ReadPin(DHT_GPIO_Port, DHT_Pin) GPIO_PIN_SET) { data[i / 8] | (0x80 (i % 8)); } } *humidity ((data[0] 8) | data[1]) / 10.0f; *temperature ((data[2] 8) | data[3]) / 10.0f; // data[4] 是校验和建议校验一下再使用 return (data[4] ((data[0] data[1] data[2] data[3]) 0xFF)); }一个实操提醒读DHT22要给它足够的空闲时间两次采集之间最好间隔2秒以上。你如果做1秒上报一次会发现偶尔读出来的数据是0或者跳变很大不是代码写错了而是传感器本身还没来得及完成下一次测量。2.3 让ESP8266做TCP透传再手写轻量MQTT报文关于WiFi模块网上主流的做法是用ESP8266直接刷MQTT固件然后用AT指令连MQTT。但这很容易踩到固件版本的坑不同厂家的AT固件支持的MQTT指令不一样有的根本不带MQTT指令。我的做法更省心——让ESP8266退化为一个纯粹的TCP透传通道MQTT协议报文由STM32自己拼。你要做的事情就两步先给ESP8266发AT指令连WiFi再发ATCIPSTART建TCP连接到华为云IoT的MQTT端口。之后所有打进串口透传通道的字节流ESP8266都会原样发到云端云端回来的字节也会原样送到串口。STM32直接往这个通道里写MQTT报文就行。MQTT的CONNECT报文结构不复杂你自己拼完全可行固定头: 0x10CONNECT类型 剩余长度 可变头: 协议名MQTT、协议级别0x04、连接标志0xC2、保活时间 有效载荷: ClientId、Username、Password三个字段依次拼接连接标志0xC2表示启用Clean Session同时带了Username和Password。这三个字段的具体值就要按照平台的鉴权规则生成了下一章节单独讲。这里要特别小心剩余长度字段的编码规则。MQTT里长度字段是变长的值小于128时直接写一个字节大于等于128时要分多个字节编码。我最初图省事直接写长度发了几次发现云端根本不理我抓包一看报文长度字段编错了。2.4 设备如何解析云端下发的命令设备端要做的另一件重要的事就是订阅命令Topic。华为云IoT的命令下发的Topic格式类似$oc/devices/{设备ID}/sys/commands/#设备订阅这个通配主题就能收到平台转发下来的命令消息。收到命令后我接的是一个JSON格式的负载结构类似于{command_name:setSwitch,paras:{switch:1}}STM32处理JSON有现成的库比如cJSON。但要注意内存问题F407还好如果你用F103这种小内存芯片解析嵌套JSON时malloc容易碎片化建议用静态内存池或者干脆写一个轻量级的键值查找函数因为命令格式固定没必要引一个重量级解析器。我实际生产的处理代码是直接做字符串匹配的// 收到了命令负载后先判断命令名 if (strstr(payload, \command_name\:\setSwitch\)) { if (strstr(payload, \switch\:1)) { HAL_GPIO_WritePin(LIGHT_Port, LIGHT_Pin, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(LIGHT_Port, LIGHT_Pin, GPIO_PIN_RESET); } }这种方式看起来不够优雅但胜在稳定可靠不依赖动态内存。执行完命令后设备一定要把最新的开关状态作为属性上报一次这样Qt客户端和云端才能知道命令已经执行成功。这个反馈闭环很多人会漏掉导致界面操作了但状态一直不刷新。2.5 断线重连策略别让设备掉线即阵亡设备端联网最现实的问题就是WiFi飘了或者云端连接被断开。我建议做三级重连机制第一级WiFi连接断开时ESP8266会返回WIFI DISCONNECTSTM32收到这个提示后先重新发ATCWJAP连WiFi。第二级WiFi在但TCP断开重发ATCIPSTART。第三级TCP还在但MQTT连接断了云端主动关闭需要重新发CONNECT报文。每级之间加一个递增退避延迟比如5秒、10秒、30秒避免设备疯狂重连把云端入口打挂。重连成功之后立即补发一次当前状态温湿度、开关状态一起上报。这是让客户端快速恢复显示的关键不然设备重连了Qt界面还停留在旧状态上。3. 华为云IoT侧的接线操作产品模型、设备注册与鉴权规则3.1 产品模型这样设计后续才不用返工华为云IoT里你要先创建产品然后在这个产品下定义产品模型——也就是设备上报数据的格式标准。模型其实就是一套JSON描述告诉平台你的设备有哪些服务、服务下有哪些属性。我设计的模型是这样子的服务IDsensor承载温湿度属性属性temperature数据类型float单位°C属性humidity数据类型float单位%服务IDcontrol承载开关控制属性switch数据类型int取值0或1命令setSwitch参数switchint类型这里有个实际经验属性命名不要用中文不要用包含特殊字符的名字。曾经我把一个属性定义成开关状态平台端校验倒是过了但Qt解析和手写JSON时编码问题一堆最后全部改成英文才消停。另一个建议是在设计模型阶段就把数据上报周期想清楚。你说平台只负责转发但实际上平台会校验上报数据是否符合模型定义。上报JSON里如果多带了模型里没有定义的字段有些平台策略下会拒绝这次上报。所以上报字段要克制模型里有什么就报什么宁可少加不要乱加。3.2 设备注册与MQTT鉴权参数的生成规则这是整个项目里最容易卡住的地方值得仔细讲。在平台设备页面注册一个设备后你会拿到一个设备ID和一个密钥。设备ID通常是一串很长的编码密钥则是用来生成密码的种子。MQTT连接时三个关键字段这样填ClientId直接填设备IDUsername也是设备ID没错两个字段值一样Password格式是{timestamp}|{signature}其中timestamp是当前UTC时间戳秒级signature是对timestamp字符串做HMAC-SHA256计算密钥就是设备密钥结果转成十六进制小写字符串这个过程我在Qt端用了一段代码验证过后面会贴完整实现。这里要强调的是timestamp必须是当前的UTC秒数不是本地时间更不能是固定值。如果你设备端没有做时间同步拿一个几天前的时间去签名云端校验会直接失败。另外我说一下用设备端生成密码的注意事项。STM32本身没有时钟芯片的话建议在上报MQTT之前用一个简单方式获取时间在连接WiFi后发一个HTTP请求到任意时间API解析返回的时间戳。因为每次重连都要生成新密码所以时间同步这件事要放在系统启动阶段做一次同时维护一个软件计数器做粗略补时。这个方法不百分百精确但只要偏差在几分钟内鉴权就能过。3.3 不建规则引擎也能完成数据流转很多人会问Qt客户端要拿设备数据是不是必须在华为云IoT上创建规则引擎把数据转发出去其实不用。规则引擎通常是让你把数据再流转到第三方系统或者存储服务而我们这套架构中Qt客户端本身就是MQTT客户端它可以直接订阅设备上报数据的Topic。也就是说Qt要做的事和STM32一样都是连接同一个MQTT Broker只不过Qt订阅的是设备上报的Topic。这样云端就是一个消息中枢不需要额外配置任何规则。只有当你要做温度过高自动发短信告警这种跨系统联动时才需要去规则引擎里配置流转动作。在你不需要规则引擎的时候就别折腾它。每多一个环节联调就多一层不确定性。先把最简链路跑通后续再往上面加联动逻辑这是我一贯的做法。3.4 用平台日志定位问题消息跟踪怎么看联调的时候最怕消息发出去石沉大海。华为云IoT控制台里有一个设备消息跟踪功能打开它之后你可以实时看到这个设备上行和下行的每条消息包括连接鉴权、属性上报、命令下发全部都有记录。这个工具怎么用当你怀疑消息是不是丢了的时候先打开消息跟踪然后重启设备让设备重连并上报。如果日志里能看到CONNECT成功记录说明鉴权过了如果能看到属性上报记录说明STM32那段没问题。接下来Qt订阅不到那就是客户端这边的问题。如果日志里什么都看不到说明消息根本没到云问题出在ESP8266透传段或者报文格式错。我可以负责任地说整个联调过程中80%的时间开销都花在判断问题出在哪一段上面。消息跟踪能帮你把这个判断时间缩短到几分钟。4. Qt客户端的订阅与下发界面三角色的实现细节4.1 选型为什么用QMQTT而不是手动拼MQTT报文Qt客户端这边我不建议你像我前面设备端那样手拼MQTT报文桌面端的资源足够富余直接用现成库就好。我最终选的是QMQTT这个第三方开源库理由有三个第一它是纯Qt实现底层走Qt的Socket跨平台不用改代码。第二API非常简单连上、订阅、收到消息回调三个函数就能跑起来。第三支持TLS加密连接通过setSSLConfiguration传证书配置就行。如果你不想用第三方库Qt官方其实有一个Qt MQTT模块但它没有直接集成到标准Qt发行版里需要自己拉源码编译配置成本比较高。个人项目或者毕设完全没必要在这上面浪费时间QMQTT足够稳。4.2 登录云端的签名函数Qt里的HMAC-SHA256登录华为云IoTQt端同样要生成MQTT三件套ClientId、Username、Password。前两个直接填设备IDPassword按timestamp|HMAC_SHA256(secret, timestamp)生成。Qt的QMessageAuthenticationCode类正好可以做HMAC计算代码非常短#include QMessageAuthenticationCode #include QCryptographicHash #include QDateTime QByteArray buildMqttPassword(const QByteArray deviceSecret) { // 取UTC秒级时间戳注意是UTC不是本地时间 QByteArray timestamp QByteArray::number(QDateTime::currentDateTimeUtc().toSecsSinceEpoch()); // HMAC-SHA256密钥是设备密钥消息是timestamp字符串 QByteArray signature QMessageAuthenticationCode::hash( timestamp, deviceSecret, QCryptographicHash::Sha256).toHex(); return timestamp | signature; }拿到密码后连接MQTT的配置代码长这样QMQTT::Client *client new QMQTT::Client(); // 接入地址以平台控制台“设备接入”页面显示为准 client-setHost(QHostAddress(hostIp)); client-setPort(8883); client-setClientId(deviceId); client-setUsername(deviceId); client-setPassword(buildMqttPassword(deviceSecret)); // 启用TLS证书可以从平台文档区下载CA文件 QSslConfiguration sslConfig QSslConfiguration::defaultConfiguration(); sslConfig.setCaCertificates(QSslCertificate::fromPath(ca.pem)); client-setSSLConfiguration(sslConfig); client-connectToHost();TLS证书那个坑我必须提一句某些平台上如果你不加载CA证书Qt会报ssl handshake failed。第一次遇到时我一度怀疑是端口不对后来才发现是证书链没配好。华为云IoT的接入端口下发的CA证书要与域名严格匹配别随便拿一个通用CA文件凑合。4.3 订阅上报topic温度显示与状态刷新的实现连接成功后Qt要立即订阅设备上报消息的Topic。格式大致是$oc/devices/{设备ID}/sys/properties/report注意这个Topic只有这一个设备能收到所以你订阅时要把设备ID替换成你自己注册的那个。QMQTT收到消息后会触发received信号你在槽函数里把QMQTT::Message的payload取出来就是一段JSON。下面是我解析上报数据的核心逻辑void MainWindow::onMessageReceived(const QMQTT::Message msg) { QJsonDocument doc QJsonDocument::fromJson(msg.payload()); if (doc.isNull()) return; QJsonObject root doc.object(); QJsonObject services root.value(services).toObject(); QJsonObject sensorData services.value(sensor).toObject(); double temperature sensorData.value(temperature).toDouble(); double humidity sensorData.value(humidity).toDouble(); ui-lcdTemperature-display(temperature); ui-lcdHumidity-display(humidity); // control服务里的switch状态同步刷新开关按钮 int switchState services.value(control).toObject().value(switch).toInt(); ui-btnLight-setChecked(switchState 1); }界面刷新有个小技巧不要每收到一条消息就直接重画曲线图高频上报时会发卡。我是在界面上放了两个液晶数字显示组件做实时刷新同时用一个环形存储维护最近几十条温度数据只每秒触发一次曲线更新。这样界面响应流畅又不会丢掉中间数据。4.4 下发命令并形成控制闭环Qt要控制设备的灯和风扇本质是向命令Topic发布一条消息。华为云IoT的命令下发Topic一般是$oc/devices/{设备ID}/sys/commands/request_id{自定义字符串}发布时Topic里要带一个请求ID云端会把它关联到命令响应上。这一步代码如下QString commandTopic QString($oc/devices/%1/sys/commands/request_id%2) .arg(deviceId) .arg(QUuid::createUuid().toString(QUuid::WithoutBraces)); QJsonObject cmd; cmd.insert(command_name, setSwitch); QJsonObject paras; paras.insert(switch, on ? 1 : 0); cmd.insert(paras, paras); QMQTT::Message msg(0, commandTopic.toUtf8(), QJsonDocument(cmd).toJson(QJsonDocument::Compact)); client-publish(msg);关键点来了命令发出去之后界面按钮不要立刻置灰或者立刻切换状态。因为命令是否真的执行成功要以设备上报的状态为准。我的做法是点击按钮后按钮进入等待状态等onMessageReceived里收到switch属性变化后才把按钮状态定死。这套闭环设计避免了界面显示开了实际设备没动的尴尬局面也让你在远程控制时心里有底。4.5 跨平台编译需要提前处理的动态库问题用qmake工程时QMQTT库要提前编成动态库或者静态库。如果你只在自己电脑上跑动态库省事如果打算拷给朋友用或者部署到另一台机器建议编译静态库版本不然缺个dll/so文件就起不来。跨平台还有一个隐藏问题Windows上Qt默认编辑器是UTF-8但某些编译器对中文代码文件支持不好JSON里的中文字符串容易乱码。我的经验是代码文件统一用UTF-8 without BOM并且JSON解析结果不要直接用来做字符串比较而是用数值字段传递参数值比如switch就用0/1不要用开/关。这个设计避免了平台编码差异带来的一堆破事。5. 联调中真实踩过的坑从鉴权失败到消息重复5.1 最典型的坑设备鉴权反复失败这个坑我猜90%的人都会踩。设备连上WiFi了TCP也通了CONNECT报文也发出去了结果云端秒断日志里显示鉴权失败。根因通常是Password生成里的timestamp问题。有三个可能一是设备本地时钟没同步离当前UTC时间差太久二是签名算法写错了不是标准HMAC-SHA256三是timestamp和signature之间的分隔符用错了字符应该是竖线|。排查方法很简单在Qt端用同样的设备ID和密钥生成一次密码然后用MQTT客户端软件连一次试试能连上就说明算法没问题问题在STM32端的时间或拼装逻辑。把两边的生成函数放一起对比很快就能定位。5.2 第二个高频坑属性上报JSON格式对不上设备上报的数据平台会做格式校验不符合产品模型的就报错。我遇到的情况是产品模型里temperature定义的是float而我上报时因为格式化字符串用的%d把温度值截断成整数了。平台日志里能看到类似数据类型不匹配的提示。另一个相关问题是serviceId拼错。模型里服务叫sensor上报JSON里写成了SENSOR大小写对不上直接被拒。JSON里的serviceId和属性名必须和产品模型定义完全一致一个字符都不能差。写代码前先去平台把模型敲定复制名字过来用不要手敲这是最稳的做法。5.3 QoS 1重复投递灯闪两下的灵异事件联调时遇到过这样一个诡异问题我在Qt界面上点了一下开灯灯确实亮了但紧接着又闪了一下。一开始以为继电器抖动排查半天发现是消息重复。原因在QoS级别。MQTT的QoS 1语义是至少送达一次Broker在没收到PUBACK时会重复投递消息。我用的QMQTT库连接时没有显式设置QoS默认可能用了QoS 1而且我们的设备端没有回PUBACK导致Broker把命令消息又投了一遍。解决办法有两个方向一是客户端发布消息时显式指定QoS 0在智能家居场景下偶尔丢一条命令影响不大但重复命令的副作用更讨厌二是设备端收到命令后如果本地状态已经和目标状态一致就直接忽略这次命令不再执行。我最终采用了双保险一方面发布设成QoS 0另一方面设备端做了一次状态去重判断。5.4 乱码与粘包串口透传的经典问题ESP8266透传模式下STM32通过串口往ESP8266发数据。这里有个经典问题如果你在两段MQTT报文之间停留时间太短或者串口波特率不匹配ESP8266会把两段报文连着发出去设备端拆包就乱套了。我踩过的坑更初级——ESP8266 AT指令的回显没关掉。发一条ATCIPMODE1模块会把这条指令原样回显这个回显内容混进了透传数据流里导致云端收到一堆乱码。正确做法是把ATE0关闭回显指令先发了再进透传模式。还有一点透传模式下若要让模块退出透传需要发但这个需要在发送前后各加1秒无数据时间不然模块不识别。我调试时老是退不出透传模式折腾了很久才想起这个时序。5.5 一整套排查链路分层测别上来就怀疑云联调排错一定要按层来我总结的顺序是第一层本地验证用MQTT客户端软件比如常见MQTT测试工具连接同一个云端地址用同样的设备ID和密码。能连上说明云端配置和鉴权算法没问题连不上看报错去改密码生成代码。第二层模组透传验证用串口调试助手直接给ESP8266发MQTT报文看云端能不能收到。这一层隔离了STM32的问题能收到说明TCP透传没问题。第三层设备端验证检查STM32串口发出的报文和第二步手动发的内容比对逐字节看差异。第四层应用端验证如果设备上报正常但Qt收不到用MQTT测试工具订阅同一个Topic试试能收到就是Qt代码问题收不到就是主题订阅路径或者权限问题。这套链路走下来任何问题最多半小时就能定位到具体层。我最怕的就是一上来把所有日志打满满屏幕串口数据反而看不出问题在哪。6. 打通之后还能怎么玩影子策略与后续扩展6.1 设备影子到底解决什么问题基本链路打通之后我再推荐一个平台的高频用法——设备影子。它本质是云端给设备状态做了一个缓存副本。设备离线时影子保存最后一次上报的状态设备重新上线后可以立即拉取影子拿到最新状态。这对智能家居的实际意义是如果有一个回家模式或者离家模式的联动逻辑会在你还没到家时就把空调开了、热水器开了。这些命令设备当时收不到但你不想漏掉就可以把目标状态写到影子或者下发到命令队列里等设备上线后自动执行。我在扩展阶段做的是Qt端增加了一块场景面板一键下发回家模式实际上就是连续发几条命令并记录在影子里。设备每次重连成功先向平台请求一次影子内容把里面未执行的命令补执行一遍。这个体验比单纯的实时控制好得多。6.2 平台规则触发的场景联动如果你不愿意让Qt一直在线盯着可以把一些简单的联动逻辑放到云端规则引擎里。比如温度连续三次上报超过30度云端自动给设备下发风扇开启命令。这样即使电脑没开系统自己也知道降温。规则引擎配置的时候最容易让人困惑的是触发条件和执行动作的字段映射你需要先建立一条数据转发规则把上报数据作为消息源然后配置条件判断。我第一次配置时把温度的字段路径写错了导致条件永远不满足。后来通过平台日志逐步比对才发现应该是sensor.temperature而不是data.temperature这类路径。建议配置完先用测试数据触发一次确认逻辑生效再依赖它。6.3 从远程开关走向场景自动化整套系统走到这其实已经不是一个作业了而是一个可以实际住着用的智能家居雏形。我最后给一个扩展路线参考第一次升级把设备端从单设备扩展成多设备——再接个门磁、人体红外一个STM32带多个传感器完全没问题。第二次升级把Qt端加上定时策略和联动策略比如晚上十点自动关灯、湿度低于阈值自动开加湿器。第三次升级把告警消息推到手机上可以用平台的推送功能不需要自己再写App。每一步的核心逻辑都没有变设备采集、云端路由、应用消费。变的无非是设备数量和界面复杂度。所以我把这篇博文定位为打底把三层之间最关键的鉴权、Topic、JSON这几个环节讲清楚剩下的功能扩展都是锦上添花。最后分享一个小技巧联调阶段建议你在Qt界面上留一个原始消息显示的调试框每次收到的MQTT原始JSON原样打印出来。这看起来很丑但它比任何断点调试都管用——当设备行为和预期不一致时你能看到最底层的消息是什么而不是看一堆经过处理的变量值。等系统稳定运行两周以后再把这个调试框隐藏掉不迟。