• 基于STM32+华为云IOT设计的智能衣柜


    一、项目介绍

    随着智能家居的发展,人们对于家居设备的智能化和远程控制需求越来越高。智能衣柜作为智能家居的一部分,可以提供衣物存储和保护的功能,并通过传感器和互联网技术实现对衣柜内部环境的监测和控制,为用户提供更好的使用体验。

    本项目基于STM32F103ZET6主控芯片设计了一个智能衣柜系统,主要功能包括温度和湿度的监测以及烘干控制。为了实现温湿度的监测,采用了DHT11传感器,可以准确地测量环境的温度和湿度。通过将传感器连接到STM32F103ZET6,可以实时获取衣柜内部的温湿度数据。

    为了实现烘干功能,系统使用加热丝和小风扇来制造热气并循环衣柜内部的空气,以去除湿气并防止衣物发霉。加热丝的控制采用继电器来控制加热丝的通断,从而控制烘干的开关。通过与STM32F103ZET6的连接,可以实现对加热丝和小风扇的控制。

    为了实现远程监控和控制,系统采用了ESP8266-WIFI模块将采集到的温湿度数据上传到华为云物联网平台。用户可以通过在Android手机上开发的Qt应用程序远程查看衣柜的实时温度和湿度,并设置湿度阀值。如果湿度超出阀值,系统会通过本地蜂鸣器报警和手机APP提示用户,以防止衣物发霉。此外,用户还可以通过手机APP远程控制衣柜的烘干系统,去除湿气,防止衣物发霉或出现霉味。

    整个系统通过将传感器、主控芯片、继电器、ESP8266-WIFI模块和手机APP进行集成,实现了智能衣柜的温湿度监测和远程控制功能,为用户提供了便捷、智能的衣物存储和保护解决方案。

    image-20230728132359697

    image-20230728132432875

    image-20230728132232626

    二、设计思路总结

    2.1 硬件选型

    在该项目中,以下是一些可能的硬件选型:

    【1】主控芯片:STM32F103ZET6,它是一款性能强大的32位ARM Cortex-M3微控制器,具有丰富的外设和存储器,适合用作智能衣柜系统的主控芯片。

    【2】温湿度传感器:DHT11,能够准确地测量环境的温度和湿度。

    【3】网络通信模块:ESP8266-WIFI模块,可以实现与互联网的连接,用于将采集到的温湿度数据上传到华为云物联网平台。

    【4】继电器:用于控制加热丝的通断,从而控制烘干的开关。选择合适的继电器型号和规格,以适应加热丝的电流和电压要求。

    【5】蜂鸣器:用于本地报警,当湿度超出阀值时发出报警声音。

    【5】Android手机:作为用户界面,通过Qt开发的Android手机APP实现远程查看和控制衣柜的温湿度以及烘干系统的开关控制。

    2.2 硬件设计

    • 使用STM32F103ZET6作为主控芯片,连接温湿度传感器DHT11,继电器,蜂鸣器和ESP8266-WIFI模块。
    • 将DHT11传感器连接到主控芯片的GPIO口,以实时获取衣柜内部的温湿度数据。
    • 通过继电器控制加热丝的通断,以控制烘干系统的开关。
    • 连接蜂鸣器到主控芯片的GPIO口,当湿度超出阀值时发出报警声音。
    • 将ESP8266-WIFI模块连接到主控芯片的串口,以实现与华为云物联网平台的通信。

    2.3 软件设计

    • 使用STM32的开发环境进行固件开发,编写相应的代码来实现温湿度传感器的读取、继电器的控制和蜂鸣器的报警功能。
    • 编写与ESP8266-WIFI模块通信的代码,实现将采集到的温湿度数据上传到华为云物联网平台。
    • 在华为云物联网平台上创建相应的设备和数据通道,以接收和存储来自智能衣柜的温湿度数据。
    • 开发基于Qt的Android手机APP,通过与华为云物联网平台的接口,实现远程查看衣柜的实时温湿度和控制烘干系统的开关。
    • 在手机APP上设置湿度阀值,当湿度超出阀值时,触发本地蜂鸣器报警和手机APP的提示功能。

    2.4 系统交互流程

    • STM32主控芯片读取DHT11传感器的温湿度数据。
    • 根据采集到的数据,控制继电器开关加热丝和小风扇,实现烘干功能。
    • 将温湿度数据通过ESP8266-WIFI模块上传到华为云物联网平台。
    • 用户通过Qt开发的Android手机APP远程访问华为云物联网平台,获取衣柜的实时温湿度数据。
    • 如果湿度超出设定的阀值,系统发出本地蜂鸣器报警和手机APP的提示,提醒用户。
    • 用户可以通过手机APP远程控制烘干系统的开关,去除湿气,防止衣物发霉或出现霉味。

    通过上述系统设计思路,实现了智能衣柜的温湿度监测和远程控制功能,提供了更智能、便捷的衣物存储和保护解决方案。

    三、部署华为云物联网平台

    华为云官网: https://www.huaweicloud.com/

    打开官网,搜索物联网,就能快速找到 设备接入IoTDA

    image-20221204193824815

    3.1 物联网平台介绍

    华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。

    使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。

    物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。

    设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。

    业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。

    img

    3.2 开通物联网服务

    地址: https://www.huaweicloud.com/product/iothub.html

    image-20221204194233414

    进来默认会提示开通标准版,在2023的1月1号年之后没有基础版了。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    image-20230313171325183

    开通之后,点击总览,查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    总结:

    端口号:   MQTT (1883)| MQTTS (8883)   
    接入地址: 7445c6bcd3.st1.iotda-app.cn-north-4.myhuaweicloud.com
    
    • 1
    • 2

    根据域名地址得到IP地址信息:

    Microsoft Windows [版本 10.0.19044.2728]
    (c) Microsoft Corporation。保留所有权利。
    
    C:\Users\11266>ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com
    
    正在 Ping 7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
    来自 117.78.5.125 的回复: 字节=32 时间=42ms TTL=30
    来自 117.78.5.125 的回复: 字节=32 时间=35ms TTL=30
    来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=30
    来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=30
    
    117.78.5.125 的 Ping 统计信息:
        数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
    往返行程的估计时间(以毫秒为单位):
        最短 = 35ms,最长 = 42ms,平均 = 37ms
    
    C:\Users\11266>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    image-20230321161044723

    MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。

    3.3 创建产品

    (1)创建产品

    点击产品页,再点击左上角创建产品。

    image-20230313171503547

    (2)填写产品信息

    根据自己产品名字填写。

    image-20230728130901496

    (3)产品创建成功

    image-20230728130915809

    (4)添加自定义模型

    产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。

    先点击自定义模型。

    image-20230313171815860

    再创建一个服务ID。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    接着点击新增属性。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    3.4 添加设备

    产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。

    (1)注册设备

    image-20230313173158883

    (2)根据自己的设备填写

    image-20230728131025569

    (3)保存设备信息

    创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。

    image-20230728131051244

    (4) 设备创建完成

    image-20230728131104843

    3.5 MQTT协议主题订阅与发布

    (1)MQTT协议介绍

    当前的设备是采用MQTT协议与华为云平台进行通信。

    MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务。MQTT是专门针对物联网开发的轻量级传输协议。MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景。目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统。

    MQTT是一种消息队列协议,使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合,相对于其他协议,开发更简单;MQTT协议是工作在TCP/IP协议上;由TCP/IP协议提供稳定的网络连接;所以,只要具备TCP协议栈的网络设备都可以使用MQTT协议。 本次设备采用的ESP8266就具备TCP协议栈,能够建立TCP连接,所以,配合STM32代码里封装的MQTT协议,就可以与华为云平台完成通信。

    华为云的MQTT协议接入帮助文档在这里: https://support.huaweicloud.com/devg-iothub/iot_02_2200.html

    img

    业务流程:

    img

    (2)华为云平台MQTT协议使用限制

    描述限制
    支持的MQTT协议版本3.1.1
    与标准MQTT协议的区别支持Qos 0和Qos 1支持Topic自定义不支持QoS2不支持will、retain msg
    MQTTS支持的安全等级采用TCP通道基础 + TLS协议(最高TLSv1.3版本)
    单帐号每秒最大MQTT连接请求数无限制
    单个设备每分钟支持的最大MQTT连接数1
    单个MQTT连接每秒的吞吐量,即带宽,包含直连设备和网关3KB/s
    MQTT单个发布消息最大长度,超过此大小的发布请求将被直接拒绝1MB
    MQTT连接心跳时间建议值心跳时间限定为30至1200秒,推荐设置为120秒
    产品是否支持自定义Topic支持
    消息发布与订阅设备只能对自己的Topic进行消息发布与订阅
    每个订阅请求的最大订阅数无限制

    (3)主题订阅格式

    帮助文档地址:https://support.huaweicloud.com/devg-iothub/iot_02_2200.html

    image-20221207153310037

    对于设备而言,一般会订阅平台下发消息给设备 这个主题。

    设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。

    如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。

    以当前设备为例,最终订阅主题的格式如下:
    $oc/devices/{device_id}/sys/messages/down
    
    最终的格式:
    $oc/devices/6419627e40773741f9fbdac7_dev1/sys/messages/down
    
    • 1
    • 2
    • 3
    • 4
    • 5

    (4)主题发布格式

    对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。

    这个操作称为:属性上报。

    帮助文档地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html

    image-20221207153637391

    根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:

    发布的主题格式:
    $oc/devices/{device_id}/sys/properties/report
     
    最终的格式:
    $oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/report
    发布主题时,需要上传数据,这个数据格式是JSON格式。
    
    上传的JSON数据格式如下:
    
    {
      "services": [
        {
          "service_id": <填服务ID>,
          "properties": {
            "<填属性名称1>": <填属性值>,
            "<填属性名称2>": <填属性值>,
            ..........
          }
        }
      ]
    }
    根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。
    
    根据这个格式,组合一次上传的属性数据:
    {"services": [{"service_id": "stm32","properties":{"DS18B20":18,"motor_water":1,"motor_oxygen":1,"temp_max":10,"water_hp":130,"motor_food":0,"time_food":0,"oxygen_food":3}}]}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    3.6 MQTT三元组

    MQTT协议登录需要填用户ID,设备ID,设备密码等信息,就像我们平时登录QQ,微信一样要输入账号密码才能登录。MQTT协议登录的这3个参数,一般称为MQTT三元组。

    接下来介绍,华为云平台的MQTT三元组参数如何得到。

    (1)MQTT服务器地址

    要登录MQTT服务器,首先记得先知道服务器的地址是多少,端口是多少。

    帮助文档地址:https://console.huaweicloud.com/iotdm/?region=cn-north-4#/dm-portal/home

    image-20230302135718882

    MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。

    根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)

    华为云的MQTT服务器地址:114.116.232.138
    域名:7445c6bcd3.st1.iotda-device.cn-north-4.myhuaweicloud.com
    华为云的MQTT端口号:1883
    
    • 1
    • 2
    • 3

    (2)生成MQTT三元组

    华为云提供了一个在线工具,用来生成MQTT鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/

    打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。

    下面是打开的页面:

    image-20221207154917230

    填入设备的信息: (上面两行就是设备创建完成之后保存得到的)

    直接得到三元组信息。

    image-20230321160708924

    image-20230321160718302

    得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。

    ClientId 6419627e40773741f9fbdac7_dev1_0_0_2023032108
    Username 6419627e40773741f9fbdac7_dev1
    Password 861ac9e6a579d36888b2aaf97714be7af6c77017b017162884592bd68b086a6e
    
    • 1
    • 2
    • 3

    3.7 模拟设备登录测试

    经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。

    (1)填入登录信息

    打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。

    image-20230321161132971

    (2)打开网页查看

    完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。

    点击详情页面,可以看到上传的数据。

    到此,云平台的部署已经完成,设备已经可以正常上传数据了。

    四、上位机开发

    为了方便查看设备上传的数据,对设备进行远程控制,接下来利用Qt开发一款Android和windows系统的上位机。

    使用华为云平台提供的API接口获取设备上传的数据,也可以给设备下发指令,控制设备。

    为了方便查看设备上传的数据,对设备进行远程控制,接下来利用Qt开发一款Android和windows系统的上位机。

    使用华为云平台提供的API接口获取设备上传的数据,也可以给设备下发指令,控制设备。

    4.1 Qt开发环境安装

    Qt的中文官网: https://www.qt.io/zh-cn/image-20221207160550486

    image-20221207160606892

    QT5.12.6的下载地址:https://download.qt.io/archive/qt/5.12/5.12.6

    打开下载链接后选择下面的版本进行下载:

    qt-opensource-windows-x86-5.12.6.exe 13-Nov-2019 07:28 3.7G Details

    软件安装时断网安装,否则会提示输入账户。

    安装的时候,第一个复选框里勾选一个mingw 32编译器即可,其他的不管默认就行,直接点击下一步继续安装。

    image-20221203151742653

    说明: 我这里只是介绍PC端的环境搭建(这个比较简单)。 Android的开发环境比较麻烦,可以去我的博客里看详细文章。

    选择MinGW 32-bit 编译器:

    image-20221203151750344

    4.2 创建IAM账户

    创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。

    地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users

    获取Token时,除了AIM账号外,还需要项目凭证:

    faa0973835ab409ab48182e2590f4ad3
    
    • 1

    image-20230321161348409

    image-20230321161414487

    鼠标点击自己昵称,点击统一身份认证。

    image-20230321161433209

    点击左上角创建用户

    image-20230321161450557

    image-20221207161209880

    image-20221207161308917

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    创建成功:

    image-20221212174359962

    image-20221212174412097

    image-20230321161557848

    4.3 获取影子数据

    帮助文档:https://support.huaweicloud.com/api-iothub/iot_06_v5_0079.html

    设备影子介绍:

    设备影子是一个用于存储和检索设备当前状态信息的JSON文档。
    每个设备有且只有一个设备影子,由设备ID唯一标识
    设备影子仅保存最近一次设备的上报数据和预期数据
    无论该设备是否在线,都可以通过该影子获取和设置设备的属性
    
    • 1
    • 2
    • 3
    • 4

    简单来说:设备影子就是保存,设备最新上传的一次数据。

    我们设计的软件里,如果想要获取设备的最新状态信息,就采用设备影子接口。

    如果对接口不熟悉,可以先进行在线调试:https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=ShowDeviceShadow

    在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。

    image-20230321161636567

    image-20230321161701419

    设备影子接口返回的数据如下:

    {
     "device_id": "6419627e40773741f9fbdac7_dev1",
     "shadow": [
      {
       "service_id": "stm32",
       "desired": {
        "properties": null,
        "event_time": null
       },
       "reported": {
        "properties": {
         "DS18B20": 18,
         "motor_water": 1,
         "motor_oxygen": 1,
         "temp_max": 10,
         "water_hp": 130,
         "motor_food": 0,
         "time_food": 0,
         "oxygen_food": 3
        },
        "event_time": "20230321T081126Z"
       },
       "version": 0
      }
     ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    4.4 修改设备属性

    地址: https://support.huaweicloud.com/api-iothub/iot_06_v5_0034.html

    接口说明

    设备的产品模型中定义了物联网平台可向设备下发的属性,应用服务器可调用此接口向指定设备下发属性。平台负责将属性以同步方式发送给设备,并将设备执行属性结果同步返回。
    
    • 1

    修改设备属性的接口,可以让服务器给设备下发指令,如果需要控制设备。

    在线调试地址:

    https://apiexplorer.developer.huaweicloud.com/apiexplorer/doc?product=IoTDA&api=UpdateProperties

    修改设备属性是属于同步命令,需要设备在线才可以进行调试,先使用MQTT客户端登录服务器,模拟设备上线。

    然后进行调试,测试数据远程下发给设备。

    【1】利用MQTT客户端先登录设备 (这是同步命令,必须在线才能调试)

    image-20230321161923007

    【2】点击调试

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    {"services":{"temp_max":100}}
    
    • 1

    【4】可以看到,MQTT客户端软件上已经收到了服务器下发的消息

    image-20230313175819901

    由于是同步命令,服务器必须要收到设备的响应才能顺利完成一个流程,设备响应了服务器才能确定数据下发成功。

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    MQTT设备端如何响应呢?

    设备响应格式说明:https://support.huaweicloud.com/api-iothub/iot_06_v5_3008.html

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    下面进行实操:

    当服务器通过在线调试,发送指令下来之后,客户端将请求ID复制下来,添加到发布主题的格式里,再回复回去,服务器收到了响应,一次属性修改就完美完成了。

    image-20230321162053263

    就是成功的状态:

    image-20230321162026282

    **下面是请求的总结: ** (响应服务器的修改设备属性请求)

    上报主题的格式:$oc/devices/{device_id}/sys/properties/set/response/request_id=
    
    $oc/devices/6419627e40773741f9fbdac7_dev1/sys/properties/set/response/request_id=
    
    响应的数据:
    {"result_code": 0,"result_desc": "success"}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.5 设计上位机

    前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。

    【1】新建Qt工程

    image-20230302144331541

    选择工程路径,放在英文路径下。

    image-20230321162532573

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    image-20230313180451177

    image-20230313180504518

    image-20230321162620141

    创建完毕。

    新建Android的模板:

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    image-20230321162741791

    image-20230321162812486

    【2】界面设计

    image-20230728132051169

    【4】代码设计:配置参数读取与保存

    /*
    功能: 保存数据到文件
    */
    void Widget::SaveDataToFile(QString text)
    {
        /*保存数据到文件,方便下次加载*/
        QString file;
        file=QCoreApplication::applicationDirPath()+"/"+ConfigFile;
        QFile filesrc(file);
        filesrc.open(QIODevice::WriteOnly);
        QDataStream out(&filesrc);
        out << text;  //序列化写字符串
        filesrc.flush();
        filesrc.close();
    }
    
    
    /*
    功能: 从文件读取数据
    */
    QString Widget::ReadDataFile(void)
    {
        //读取配置文件
        QString text,data;
        text=QCoreApplication::applicationDirPath()+"/"+ConfigFile;
    
        //判断文件是否存在
        if(QFile::exists(text))
        {
            QFile filenew(text);
            filenew.open(QIODevice::ReadOnly);
            QDataStream in(&filenew); // 从文件读取序列化数据
            in >> data; //提取写入的数据
            filenew.close();
        }
        return data; //返回值读取的值
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    【3】代码设计:云端数据解析

    //解析反馈结果
    void Widget::replyFinished(QNetworkReply *reply)
    {
        QString displayInfo;
    
        int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    
        //读取所有数据
        QByteArray replyData = reply->readAll();
    
        qDebug()<<"状态码:"<<statusCode;
        qDebug()<<"反馈的数据:"<<QString(replyData);
    
        //更新token
        if(function_select==3)
        {
            displayInfo="token 更新失败.";
            //读取HTTP响应头的数据
            QList<QNetworkReply::RawHeaderPair> RawHeader=reply->rawHeaderPairs();
            qDebug()<<"HTTP响应头数量:"<<RawHeader.size();
            for(int i=0;i<RawHeader.size();i++)
            {
                QString first=RawHeader.at(i).first;
                QString second=RawHeader.at(i).second;
                if(first=="X-Subject-Token")
                {
                    Token=second.toUtf8();
                    displayInfo="token 更新成功.";
    
                    //保存到文件
                    SaveDataToFile(Token);
                    break;
                }
            }
            QMessageBox::information(this,"提示",displayInfo,QMessageBox::Ok,QMessageBox::Ok);
            return;
        }
    
        //判断状态码
        if(200 != statusCode)
        {
            //解析数据
            QJsonParseError json_error;
            QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
            if(json_error.error == QJsonParseError::NoError)
            {
                //判断是否是对象,然后开始解析数据
                if(document.isObject())
                {
                    QString error_str="";
                    QJsonObject obj = document.object();
                    QString error_code;
                    //解析错误代码
                    if(obj.contains("error_code"))
                    {
                        error_code=obj.take("error_code").toString();
                        error_str+="错误代码:";
                        error_str+=error_code;
                        error_str+="\n";
                    }
                    if(obj.contains("error_msg"))
                    {
                        error_str+="错误消息:";
                        error_str+=obj.take("error_msg").toString();
                        error_str+="\n";
                    }
    
                    //显示错误代码
                    QMessageBox::information(this,"提示",error_str,QMessageBox::Ok,QMessageBox::Ok);
                }
             }
            return;
        }
    
        //设置属性
        if(function_select==12 || function_select==13)
        {
            //解析数据
            QJsonParseError json_error;
            QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
            if(json_error.error == QJsonParseError::NoError)
            {
                //判断是否是对象,然后开始解析数据
                if(document.isObject())
                {
                    QJsonObject obj = document.object();
                    if(obj.contains("response"))
                    {
                        QJsonObject obj1=obj.take("response").toObject();
                        int val=0;
                        QString success;
                        if(obj1.contains("result_code"))
                        {
                             val=obj1.take("result_code").toInt();
                        }
                        if(obj1.contains("result_desc"))
                        {
                             success=obj1.take("result_desc").toString();
                        }
    
                        if(val==0 && success =="success")
                        {
                            //显示状态
                            QMessageBox::information(this,"提示","远程命令操作完成.",QMessageBox::Ok,QMessageBox::Ok);
                            return;
                        }
                        else
                        {
                            //显示状态
                            QMessageBox::information(this,"提示","设备未正确回应.请检查设备网络.",QMessageBox::Ok,QMessageBox::Ok);
                            return;
                        }
                    }
                }
             }
        }
    
        //查询设备属性
        if(function_select==0)
        {
            //解析数据
            QJsonParseError json_error;
            QJsonDocument document = QJsonDocument::fromJson(replyData, &json_error);
            if(json_error.error == QJsonParseError::NoError)
            {
                //判断是否是对象,然后开始解析数据
                if(document.isObject())
                {
                    QJsonObject obj = document.object();
                    if(obj.contains("shadow"))
                    {
                        QJsonArray array=obj.take("shadow").toArray();
                        for(int i=0;i<array.size();i++)
                        {
                            QJsonObject obj2=array.at(i).toObject();
                            if(obj2.contains("reported"))
                            {
                                QJsonObject obj3=obj2.take("reported").toObject();
    
    
                                if(obj3.contains("properties"))
                                {
                                    QJsonObject properties=obj3.take("properties").toObject();
    
                                    qDebug()<<"开始解析数据....";
                                }
                            }
                        }
                    }
                }
             }
            return;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154

    五、代码实现

    5.1 ESP8266连接云平台实现代码

    以下是使用STM32F103ZET6和ESP8266连接华为云物联网平台,通过MQTT协议实现设备登录、主题订阅和主题发布的实现代码:

    #include "stm32f10x.h"
    #include "stdio.h"
    #include "string.h"
    
    // 定义ESP8266的串口
    USART_TypeDef* ESP_USARTx = USART1;
    
    // 定义MQTT服务器的地址和端口
    const char* MQTT_SERVER = "mqtt.eclipse.org";
    const int MQTT_PORT = 1883;
    
    // 定义设备ID和设备密码
    const char* DEVICE_ID = "your_device_id";
    const char* DEVICE_PASSWORD = "your_device_password";
    
    // 定义订阅的主题
    const char* SUBSCRIBE_TOPIC = "your_subscribe_topic";
    
    // 定义发布的主题
    const char* PUBLISH_TOPIC = "your_publish_topic";
    
    // 定义接收缓冲区和发送缓冲区的大小
    #define RX_BUFFER_SIZE 1024
    #define TX_BUFFER_SIZE 1024
    
    // 定义接收缓冲区和发送缓冲区
    char rxBuffer[RX_BUFFER_SIZE];
    char txBuffer[TX_BUFFER_SIZE];
    
    // 定义接收缓冲区的索引和标志位
    volatile uint16_t rxIndex = 0;
    volatile uint8_t rxComplete = 0;
    
    // 发送数据到ESP8266
    void ESP8266_SendData(const char* data) {
        sprintf(txBuffer, "%s\r\n", data);
        USART_SendData(ESP_USARTx, (uint16_t)'\r');
        USART_SendData(ESP_USARTx, (uint16_t)'\n');
        USART_SendData(ESP_USARTx, (uint16_t)'\r');
        USART_SendData(ESP_USARTx, (uint16_t)'\n');
        USART_SendData(ESP_USARTx, (uint16_t)'\r');
        USART_SendData(ESP_USARTx, (uint16_t)'\n');
        USART_SendData(ESP_USARTx, (uint16_t)'\r');
        USART_SendData(ESP_USARTx, (uint16_t)'\n');
        USART_SendData(ESP_USARTx, (uint16_t)'\r');
        USART_SendData(ESP_USARTx, (uint16_t)'\n');
        USART_SendData(ESP_USARTx, (uint16_t)'\r');
        USART_SendData(ESP_USARTx, (uint16_t)'\n');
        USART_SendData(ESP_USARTx, (uint16_t)'\r');
        USART_SendData(ESP_USARTx, (uint16_t)'\n');
        USART_SendData(ESP_USARTx, (uint16_t)'\r');
        USART_SendData(ESP_USARTx, (uint16_t)'\n');
    }
    
    // 从ESP8266接收数据
    void ESP8266_ReceiveData(uint16_t size) {
        while (size--) {
            rxBuffer[rxIndex++] = USART_ReceiveData(ESP_USARTx);
        }
        if (rxIndex >= RX_BUFFER_SIZE) {
            rxComplete = 1;
            rxIndex = 0;
        }
    }
    
    // 处理接收到的数据
    void ProcessReceivedData() {
        // TODO: 根据接收到的数据进行处理
    }
    
    // ESP8266串口中断处理函数
    void USART1_IRQHandler(void) {
        if (USART_GetITStatus(ESP_USARTx, USART_IT_RXNE) != RESET) {
            ESP8266_ReceiveData(1);
        }
    }
    
    // 连接到MQTT服务器
    void MQTT_Connect() {
        // 发送连接请求
        sprintf(txBuffer, "AT+CIPSTART=\"TCP\",\"%s\",%d\r\n", MQTT_SERVER, MQTT_PORT);
        ESP8266_SendData(txBuffer);
        // 等待连接成功
        while (!strstr(rxBuffer, "CONNECTED")) {
            if (rxComplete) {
                ProcessReceivedData();
                rxComplete = 0;
            }
        }
        // 发送MQTT连接请求
        sprintf(txBuffer, "AT+MQTTCONNECT=\"%s\",\"%s\"\r\n", DEVICE_ID, DEVICE_PASSWORD);
        ESP8266_SendData(txBuffer);
        // 等待连接成功
        while (!strstr(rxBuffer, "CONNECTED")) {
            if (rxComplete) {
                ProcessReceivedData();
                rxComplete = 0;
            }
        }
    }
    
    // 订阅主题
    void MQTT_Subscribe() {
        sprintf(txBuffer, "AT+MQTTSUBSCRIBE=\"%s\"\r\n", SUBSCRIBE_TOPIC);
        ESP8266_SendData(txBuffer);
    }
    
    // 发布消息
    void MQTT_Publish(const char* message) {
        sprintf(txBuffer, "AT+MQTTPUBLISH=\"%s\",\"%s\"\r\n", PUBLISH_TOPIC, message);
        ESP8266_SendData(txBuffer);
    }
    
    int main(void) {
        // 初始化ESP8266的串口
        USART_InitTypeDef USART_InitStructure;
        USART_InitStructure.USART_BaudRate = 115200;
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;
        USART_InitStructure.USART_StopBits = USART_StopBits_1;
        USART_InitStructure.USART_Parity = USART_Parity_No;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
        USART_Init(ESP_USARTx, &USART_InitStructure);
        USART_Cmd(ESP_USARTx, ENABLE);
        USART_ITConfig(ESP_USARTx, USART_IT_RXNE, ENABLE);
        NVIC_EnableIRQ(USART1_IRQn);
    
        // 连接到MQTT服务器
        MQTT_Connect();
    
        // 订阅主题
        MQTT_Subscribe();
    
        while (1) {
            if (rxComplete) {
                ProcessReceivedData();
                rxComplete = 0;
            }
            
            // TODO: 处理其他业务逻辑
            
            // 发布消息
            MQTT_Publish("Hello, MQTT!");
            
            // 延时一段时间
            delay_ms(1000);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148

    以上代码用于演示使用STM32F103ZET6和ESP8266连接华为云物联网平台,通过MQTT协议实现设备登录、主题订阅和主题发布的基本功能。

    5.2 ESP8266的MQTT协议指令

    ESP8266通过MQTT协议连接到服务器的相关AT指令主要有以下几个:

    【1】AT+CIPSTART:建立TCP连接

    • 功能:使用TCP协议连接到远程服务器
    • 用法:AT+CIPSTART=“TCP”,“<服务器地址>”,<服务器端口>
    • 示例:AT+CIPSTART=“TCP”,“mqtt.eclipse.org”,1883

    【2】AT+MQTTCONNECT:连接到MQTT服务器

    • 功能:使用MQTT协议连接到MQTT服务器
    • 用法:AT+MQTTCONNECT=“<设备ID>”,“<设备密码>”
    • 示例:AT+MQTTCONNECT=“your_device_id”,“your_device_password”

    【3】AT+MQTTPUBLISH:发布消息

    • 功能:向指定主题发布消息
    • 用法:AT+MQTTPUBLISH=“<主题>”,“<消息内容>”
    • 示例:AT+MQTTPUBLISH=“your_publish_topic”,“Hello, MQTT!”

    【4】AT+MQTTSUBSCRIBE:订阅主题

    • 功能:订阅指定的主题
    • 用法:AT+MQTTSUBSCRIBE=“<主题>”
    • 示例:AT+MQTTSUBSCRIBE=“your_subscribe_topic”

    【5】AT+CIPCLOSE:关闭TCP连接

    • 功能:关闭当前的TCP连接
    • 用法:AT+CIPCLOSE

    这些AT指令可以通过串口与ESP8266进行通信,实现与MQTT服务器的连接、消息发布和订阅等功能。通过这些指令,可以在嵌入式设备上实现与云端的通信和数据交换,从而实现物联网应用。

    5.3 读取DHT11传感器的温湿度数据

    以下是使用STM32F103ZET6读取DHT11传感器的温湿度数据的实现代码:

    #include "stm32f10x.h"
    #include "dht11.h"
    
    int main(void)
    {
        // 初始化DHT11传感器
        DHT11_Init();
    
        while (1)
        {
            // 读取DHT11传感器的温湿度数据
            DHT11_Result result = DHT11_Read();
    
            if (result.status == DHT11_OK)
            {
                // 温度数据
                uint8_t temperature = result.temperature;
                // 湿度数据
                uint8_t humidity = result.humidity;
    
                // 在这里进行温湿度数据的处理和使用
                // ...
    
                // 延时一段时间后再次读取
                DelayMs(2000);
            }
            else
            {
                // 读取失败,可以进行相应的错误处理
                // ...
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在主函数中,通过循环不断读取DHT11传感器的温湿度数据。如果读取成功,可以从result结构体中获取温度和湿度数据,并进行相应的处理。如果读取失败,可以根据需要进行错误处理。

    5.4 DHT11.c和DHT11.h代码

    dht11.h:

    #ifndef DHT11_H
    #define DHT11_H
    
    #include "stm32f10x.h"
    
    typedef struct
    {
        uint8_t status;      // 读取状态,0表示成功,其他表示失败
        uint8_t humidity;    // 湿度值
        uint8_t temperature; // 温度值
    } DHT11_Result;
    
    void DHT11_Init(void);
    DHT11_Result DHT11_Read(void);
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    dht11.c:

    #include "dht11.h"
    
    #define DHT11_PORT GPIOA
    #define DHT11_PIN GPIO_Pin_0
    
    static void DHT11_Delay(uint32_t us)
    {
        uint32_t count = us * 8;
        while (count--)
        {
            __NOP();
        }
    }
    
    static void DHT11_SetOutput(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
    }
    
    static void DHT11_SetInput(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
        GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
    }
    
    static uint8_t DHT11_ReadByte(void)
    {
        uint8_t byte = 0;
        for (uint8_t i = 0; i < 8; i++)
        {
            while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
            {
                // 等待低电平结束
            }
            DHT11_Delay(30);
            if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
            {
                byte |= (1 << (7 - i));
            }
            while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
            {
                // 等待高电平结束
            }
        }
        return byte;
    }
    
    void DHT11_Init(void)
    {
        GPIO_InitTypeDef GPIO_InitStructure;
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
        GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
    
        GPIO_SetBits(DHT11_PORT, DHT11_PIN);
    }
    
    DHT11_Result DHT11_Read(void)
    {
        DHT11_Result result;
        result.status = 1;
    
        DHT11_SetOutput();
        GPIO_ResetBits(DHT11_PORT, DHT11_PIN);
        DHT11_Delay(18000);
        GPIO_SetBits(DHT11_PORT, DHT11_PIN);
        DHT11_Delay(20);
        DHT11_SetInput();
    
        if (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
        {
            while (!GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
            {
                // 等待低电平结束
            }
            while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN))
            {
                // 等待高电平结束
            }
    
            uint8_t data[5];
            for (uint8_t i = 0; i < 5; i++)
            {
                data[i] = DHT11_ReadByte();
            }
    
            uint8_t sum = data[0] + data[1] + data[2] + data[3];
            if (sum == data[4])
            {
                result.status = 0;
                result.humidity = data[0];
                result.temperature = data[2];
            }
        }
    
        return result;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106

    dht11.h文件定义了DHT11传感器的初始化函数DHT11_Init()和读取函数DHT11_Read(),以及DHT11_Result结构体用于存储读取结果。

    dht11.c文件实现了DHT11传感器的初始化和读取函数。在初始化函数中,配置了DHT11引脚的GPIO模式和速度。在读取函数中,通过发送开始信号和接收数据的方式读取DHT11传感器的温湿度数据,并进行校验。

  • 相关阅读:
    mysql总结
    配电网重构|基于新颖的启发式算法SOE的随机(SDNR)配电网重构(Matlab代码实现)【算例33节点、84节点、119节点、136节点、417节点】
    JVM内存结构
    DVWA之SQL注入
    JVM优化案例实战-手动模拟Young GC
    Linux运维_Bash脚本_编译安装GTK+-3.24.41
    基于FPGA的图像RGB转HSV实现,包含testbench和MATLAB辅助验证程序
    java基础之继承[20]
    0029__时钟分频原理 - 时钟分频原理详解
    Element UI - v-infinite-scroll无限滚动组件
  • 原文地址:https://blog.csdn.net/xiaolong1126626497/article/details/132799904