基于STM32+华为云IOT设计的智能窗帘控制系统

一、项目背景

随着智能家居技术的不断发展,人们对于家居生活的需求也越来越高。智能窗帘作为智能家居领域的重要组成部分,为用户提供了更便捷、舒适的生活体验。本项目基于STM32主控芯片和华为云物联网平台,设计一款智能窗帘控制系统,以满足家庭和商业场所的需求。

在本项目中,选择了STM32F103ZET6作为主控芯片具有强大的处理能力和丰富的外设接口,适合用于物联网设备的控制和通信。通过与ESP8266-WIFI模块的连接,可以实现智能窗帘与华为云物联网平台的互联互通,实现远程控制和监测。

为了方便用户的操作和控制,使用Qt开发了Android手机APP和Windows上位机软件,用户可以通过这些应用程序进行窗帘的远程控制。同时,本地窗帘也支持手动控制,用户可以通过物理按钮或开关来操作窗帘的开关、升降等功能。

在智能化方面,引入了语音识别技术(LD3320模块),用户可以通过语音指令来控制窗帘的运行。这为用户提供了更加便捷、智能的控制方式,使得窗帘的操作更加自然和智能化。

除了远程控制和智能化功能,还引入了自动模式。在自动模式下,系统会根据环境条件进行智能判断和控制。例如,当检测到阳光强度超过设定阈值时,系统会自动关闭窗帘,以避免阳光直射室内;在晚上时,系统也会自动拉上窗帘,提供更好的隐私和安全性。

本智能窗帘控制系统基于STM32主控芯片和华为云物联网平台,结合语音识别、智能家居控制等功能,旨在为家庭和1商业场所提供便捷、舒适的智能化服务。通过远程控制、自动模式和智能化功能,用户可以实现对窗帘的灵活、智能的控制,提升生活质量和用户体验。

image-20230728112154869

image-20230728112455576

二、系统设计

2.1 硬件选型

在设计智能窗帘控制系统的硬件方案时,需要考虑主控芯片、通信模块和传感器等关键组件的选型。

以下是当前系统的具体硬件选型:

【1】主控芯片:采用STM32F103ZET6作为主控芯片。具备强大的处理能力和丰富的外设接口,适合用于物联网设备的控制和通信。可以驱动各种传感器和执行器,并与ESP8266-WIFI模块和LD3320语音识别模块进行通信。

扫描二维码关注公众号,回复: 17283762 查看本文章

【2】通信模块:选择了ESP8266-WIFI模块作为通信模块,用于连接华为云物联网平台。ESP8266-WIFI模块具有低功耗、高集成度和稳定的无线连接能力,能够实现智能窗帘与互联网的互联互通。

【3】光照传感器:采用BH1750光照传感器来检测光照强度。BH1750是一种数字式光强度传感器,能够准确测量环境光的强度。通过获取光照强度数据,系统可以根据设定的阈值来判断是否需要自动拉窗帘。

【4】语音识别模块:选择了LD3320语音识别模块,用于实现语音控制功能。LD3320是一种高性能语音识别芯片,能够实现对语音指令的识别和解析。通过语音识别模块,用户可以通过语音指令来控制窗帘的开合和模式切换。

【5】电机和驱动模块:选择了28BYJ40步进电机作为窗帘控制的电机,并使用ULN2003驱动模块来驱动电机。28BYJ40步进电机具有较高的精度和稳定性,适合用于窗帘的控制。ULN2003是一种高电压、高电流驱动芯片,能够提供足够的电流和电压来驱动步进电机。

【6】用户界面设备:采用Qt开发Android手机APP和Windows上位机来实现用户界面。通过这两个界面,用户可以进行远程控制窗帘的操作,包括开关窗帘、调整窗帘的开合程度和切换窗帘的工作模式。

2.2 设计思路

智能窗帘控制系统的软件设计主要包括主控程序、通信模块驱动、传感器驱动和用户界面等部分。

以下是系统软件设计的思路:

【1】主控程序:主控程序是系统的核心,负责控制窗帘的运行、处理传感器数据、与通信模块进行通信等。主控程序需要实现以下功能:

  • 初始化各个硬件模块,包括通信模块、传感器和电机驱动等。
  • 循环读取传感器数据,根据数据进行窗帘的控制和判断。
  • 处理用户的控制指令,包括远程控制指令和本地控制指令。
  • 与通信模块进行通信,实现与华为云物联网平台的互联互通。
  • 实现自动模式下的智能判断和控制逻辑。

【2】通信模块驱动:通信模块驱动负责与华为云物联网平台进行通信,实现远程控制和数据传输。通信模块驱动需要实现以下功能:

  • 初始化通信模块,建立与华为云物联网平台的连接。
  • 接收来自云平台的控制指令,解析指令内容。
  • 将传感器数据和窗帘状态等信息上传到云平台,实现实时监测和数据传输。

【3】传感器驱动:传感器驱动负责与光敏传感器、时间传感器等传感器进行交互,获取环境数据。传感器驱动需要实现以下功能:

  • 初始化传感器,配置传感器的工作模式和参数。
  • 定期读取传感器数据,包括光敏传感器的光强度和时间传感器的时间信息。
  • 将传感器数据传递给主控程序,供其进行判断和控制。

【4】用户界面:用户界面是用户与系统进行交互的界面,可以通过Android手机APP或Windows上位机软件实现。用户界面需要实现以下功能:

  • 显示窗帘的状态和实时数据,如光强度、时间。
  • 提供远程控制窗帘的功能,包括开关、升降和自动模式的切换。
  • 接收用户的控制指令,将指令传递给主控程序进行处理。

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

华为云官网: 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-20230313171311582

image-20230313171325183

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

image-20230321154943337

总结:

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

根据域名地址得到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>

image-20230321161044723

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

3.3 创建产品

(1)创建产品

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

image-20230313171503547

(2)填写产品信息

根据自己产品名字填写。

image-20230728110839855

(3)产品创建成功

image-20230728110854861

(4)添加自定义模型

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

先点击自定义模型。

image-20230313171815860

再创建一个服务ID。

image-20230321155733514

接着点击新增属性。

image-20230321155756735

3.4 添加设备

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

(1)注册设备

image-20230313173158883

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

image-20230728110745853

(3)保存设备信息

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

image-20230321160252288

(4) 设备创建完成

image-20230728110819739

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

(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}}]}

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

(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

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

image-20230321161348409

image-20230321161414487

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

image-20230321161433209

点击左上角创建用户

image-20230321161450557

image-20221207161209880

image-20221207161308917

image-20221207161327200

创建成功:

image-20221212174359962

image-20221212174412097

image-20230321161557848

4.3 获取影子数据

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

设备影子介绍:

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

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

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

如果对接口不熟悉,可以先进行在线调试: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
  }
 ]
}

4.4 修改设备属性

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

接口说明

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

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

在线调试地址:

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

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

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

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

image-20230321161923007

【2】点击调试

image-20230321161905033

{
    
    "services":{
    
    "temp_max":100}}

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

image-20230313175819901

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

image-20230321161941584

MQTT设备端如何响应呢?

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

image-20221203163532648

下面进行实操:

当服务器通过在线调试,发送指令下来之后,客户端将请求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"}

4.5 设计上位机

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

【1】新建Qt工程

image-20230302144331541

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

image-20230321162532573

image-20230313180428670

image-20230313180451177

image-20230313180504518

image-20230321162620141

创建完毕。

新建Android的模板:

image-20230321162657975

image-20230321162731010

image-20230321162741791

image-20230321162812486

【2】界面设计

image-20230728111857337

【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; //返回值读取的值
}

【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;
    }
}

五、代码实现

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);
    }
}

以上代码用于演示使用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.2 步进电机控制代码

以下是使用STM32F103ZET6单片机通过ULN2003驱动芯片控制28BYJ-48步进电机实现角度控制和速度控制的实现代码:

#include "stm32f10x.h"
#include "delay.h"

// 定义步进电机控制引脚
#define IN1_PIN GPIO_Pin_0
#define IN2_PIN GPIO_Pin_1
#define IN3_PIN GPIO_Pin_2
#define IN4_PIN GPIO_Pin_3
#define IN_PORT GPIOA

// 定义步进电机角度和速度参数
#define ANGLE_1 512 // 控制步进电机转动一圈的步数
#define SPEED_1 5   // 控制步进电机转动的速度

// 步进电机转动顺序
const uint8_t stepSequence[8] = {
    
    0x01, 0x03, 0x02, 0x06, 0x04, 0x0C, 0x08, 0x09};

// 步进电机当前角度和速度
volatile uint16_t currentAngle = 0;
volatile uint8_t currentSpeed = 0;

// 初始化步进电机控制引脚
void StepperMotor_Init() {
    
    
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitStructure.GPIO_Pin = IN1_PIN | IN2_PIN | IN3_PIN | IN4_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(IN_PORT, &GPIO_InitStructure);
}

// 控制步进电机转动一步
void StepperMotor_Step() {
    
    
    static uint8_t stepIndex = 0;
    GPIO_Write(IN_PORT, stepSequence[stepIndex]);
    stepIndex = (stepIndex + 1) % 8;
}

// 控制步进电机转动到指定角度
void StepperMotor_MoveToAngle(uint16_t targetAngle) {
    
    
    uint16_t steps = targetAngle - currentAngle;
    uint16_t absSteps = steps > 0 ? steps : -steps;
    uint8_t direction = steps > 0 ? 1 : -1;
    for (uint16_t i = 0; i < absSteps; i++) {
    
    
        StepperMotor_Step();
        delay_ms(2); // 控制步进电机转动的速度
    }
    currentAngle = targetAngle;
}

// 控制步进电机以指定速度连续转动
void StepperMotor_MoveWithSpeed(uint8_t speed) {
    
    
    currentSpeed = speed;
    while (1) {
    
    
        StepperMotor_Step();
        delay_ms(20 - currentSpeed); // 控制步进电机转动的速度
    }
}

int main(void) {
    
    
    // 初始化步进电机控制引脚
    StepperMotor_Init();
    
    // 控制步进电机转动到指定角度
    StepperMotor_MoveToAngle(ANGLE_1);
    
    // 控制步进电机以指定速度连续转动
    StepperMotor_MoveWithSpeed(SPEED_1);
    
    while (1) {
    
    
        // 主循环中可以添加其他逻辑代码
    }
}

5.3 LD3320识别代码

以下是使用STM32F103的串口2接收LD3320语音识别结果并进行判断控制的代码:

#include "stm32f10x.h"
#include <stdio.h>

// 定义LD3320串口通信引脚
#define LD3320_RX_PIN GPIO_Pin_2
#define LD3320_RX_PORT GPIOA
#define LD3320_USART USART2

// 定义接收缓冲区大小
#define BUFFER_SIZE 128

// 接收缓冲区
volatile char rxBuffer[BUFFER_SIZE];
volatile uint8_t rxIndex = 0;
volatile uint8_t rxComplete = 0;

// 初始化LD3320串口通信引脚
void LD3320_UART_Init() {
    
    
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
    
    GPIO_InitStructure.GPIO_Pin = LD3320_RX_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(LD3320_RX_PORT, &GPIO_InitStructure);
    
    USART_InitStructure.USART_BaudRate = 9600;
    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_Init(LD3320_USART, &USART_InitStructure);
    
    USART_ITConfig(LD3320_USART, USART_IT_RXNE, ENABLE);
    
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    USART_Cmd(LD3320_USART, ENABLE);
}

// 串口2中断处理函数
void USART2_IRQHandler() {
    
    
    if (USART_GetITStatus(LD3320_USART, USART_IT_RXNE) != RESET) {
    
    
        char data = USART_ReceiveData(LD3320_USART);
        if (rxIndex < BUFFER_SIZE - 1) {
    
    
            rxBuffer[rxIndex++] = data;
        }
        if (data == '\n') {
    
    
            rxComplete = 1;
        }
    }
}

// 处理接收到的LD3320识别结果
void ProcessLD3320Result() {
    
    
    // 在这里进行LD3320识别结果的判断和控制逻辑
    // 可以根据接收到的字符串进行判断,例如使用strcmp()函数进行比较
    // 示例:if (strcmp(rxBuffer, "ON") == 0) { // 执行打开操作 }
    
    // 清空接收缓冲区
    rxIndex = 0;
    rxComplete = 0;
}

int main(void) {
    
    
    // 初始化LD3320串口通信引脚
    LD3320_UART_Init();
    
    while (1) {
    
    
        if (rxComplete) {
    
    
            ProcessLD3320Result();
        }
    }
}

以上代码使用STM32F103的串口2接收LD3320语音识别结果并进行判断控制。

代码中使用了串口2的接收中断来接收LD3320的识别结果。在中断处理函数USART2_IRQHandler()中,将接收到的数据存储到接收缓冲区rxBuffer中,并通过检测换行符\n来判断一条完整的识别结果是否接收完成。当识别结果接收完成时,调用ProcessLD3320Result()函数进行识别结果的判断和控制逻辑处理。

ProcessLD3320Result()函数中,可以根据接收到的字符串进行判断和控制逻辑的实现。例如,使用字符串比较函数strcmp()来比较接收到的字符串与预设的控制命令是否匹配,从而执行相应的操作。在这个函数中,可以添加你需要的控制逻辑,例如打开或关闭某个设备,执行特定的动作等。

猜你喜欢

转载自blog.csdn.net/xiaolong1126626497/article/details/132799883