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

news2024/12/28 20:26:19

一、项目背景

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

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

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

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

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

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

image-20230728112154869

image-20230728112455576

二、系统设计

2.1 硬件选型

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

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

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

【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()来比较接收到的字符串与预设的控制命令是否匹配,从而执行相应的操作。在这个函数中,可以添加你需要的控制逻辑,例如打开或关闭某个设备,执行特定的动作等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1005749.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习记忆——宫殿篇——记忆宫殿——记忆桩——知识讲解

类比 假设这些桩子好比不同的交通工具&#xff0c;每一种交通工具都可以助我们到达目的地&#xff0c;那举现在就根据你的时间以及现实情况&#xff0c;选择最合适自己的交通工具即可&#xff0c;重点在于你要熟悉每种交通工具的用途不区别。桩子也是如此&#xff0c;把所有的桩…

pat多项式求和

idea 权重记得也是浮点数&#xff0c;否则2&#xff0c;5测试点不过 solution #include <stdio.h> int main(){int n ;double x0, ans 0, power 1;scanf("%d%lf", &n, &x0);double a[n1];for(int i 0; i < n; i)scanf("%lf", a i)…

Docker+jenkinsPipeline运行实现python自动化测试(超详细)

一、实现思路 在 Linux 服务器安装 docker创建 jenkins 容器jenkins 中创建 pipeline 项目根据自动化项目依赖包构建 python 镜像(构建自动化 python 环境)运行新的 python 容器&#xff0c;执行 jenkins 从仓库中拉下来的自动化项目执行完成之后删除容器 二、环境准备 Linu…

Java基础入门·多线程·线程池ThreadPool篇

前言 特点分析 线程池ThreadPool 销毁线程池 Executor类 ​​​​​​​ ​​​​​​​ ​​​​​​​ Callable接口 线程池使用 ​​​​​​​…

全面详解Maven的配置文件pom.xml(含常用plugin)

系列文章目录 手把手教你maven的安装与配置(windows) 全面详解Maven的配置文件pom.xml&#xff08;含常用plugin&#xff09; 系列文章目录一、什么是pom.xml二、pom.xml的结构三、项目的基本信息1.modules2.parent3.scm4.properties 四、项目的依赖列表1.dependency2.reposit…

【Cocos Creator 3.5实现赛车游戏】10.实现汽车节点的运动逻辑

转载知识星球 | 深度连接铁杆粉丝&#xff0c;运营高品质社群&#xff0c;知识变现的工具 项目地址&#xff1a;赛车小游戏-基于Cocos Creator 3.5版本实现: 课程的源码&#xff0c;基于Cocos Creator 3.5版本实现 上一节的学习后&#xff0c;您已经完成了对汽车节点的控制逻…

数字IC设计之时序分析基础概念汇总

1 时钟Clock 理想的时钟模型是一个占空比为50%且周期固定的方波。时钟是FPGA中同步电路逻辑运行的一个基准。理想的时钟信号如下图: 2 时钟抖动Clock Jitter 理想的时钟信号是完美的方波&#xff0c;但是实际的方波是存在一些时钟抖动的。那么什么是时钟抖动呢?时钟抖动&#…

(2)数据库mongodb 终端 和 vscode创建数据库 数据导入导出

可视化工具&#xff1a; Robo 3T | Free, open-source MongoDB GUI (formerly Robomongo) mongodb安装官网&#xff1a;MongoDB: The Developer Data Platform | MongoDB 文档&#xff1a;安装 MongoDB - MongoDB-CN-Manual (mongoing.com) 配置环境变量&#xff1a; 是为了扩…

【图论】有向图的强连通分量

算法提高课笔记&#xff08;本篇未更新完 还有俩例题&#xff09; 文章目录 理论基础SCC板子 例题受欢迎的牛题意思路代码 学校网络题意思路代码 理论基础 什么是连通分量&#xff1f; 对于一个有向图&#xff0c;分量中任意两点u&#xff0c;v&#xff0c;必然可以从u走到v…

跑步运动耳机哪个牌子好、推荐几款专业跑步耳机

跑步是一项简单的运动&#xff0c;只要交替迈左右腿就能进行。然而&#xff0c;跑步也可能会变得单调乏味。即使是意志坚定、热爱跑步的人&#xff0c;在这漫长的过程中也会感到乏味&#xff0c;更不用说像你我这样的普通跑者了。音乐能够让跑步这项运动变得有趣起来&#xff0…

前端自适应瀑布流布局

JS案例自适应瀑布流 &#x1f31f;效果预览 &#x1f31f;什么是瀑布流 &#x1f31f;制作思路 &#x1f31f;具体实现 页面结构 js代码实现 &#x1f31f;写在最后 &#x1f31f;效果预览 前端自适应瀑布流效果预览 &#x1f31f;什么是瀑布流 瀑布流&#xff0c;又…

prize_p1

文章目录 解题过程代码审计思路问题解决数组绕过preg_match__destruct的触发修改phar文件以及签名phar://支持的后缀 题解方法一&#xff08;数组绕过&#xff09;方法二&#xff08;gzip绕过&#xff09; 解题过程 源代码 <META http-equiv"Content-Type" conte…

3D人体生成名人堂

一、马普所认知系统实验室。 Perceiving Systems - Max Planck Institute for Intelligent SystemsUsing computer vision, computer graphics, and machine learning, we teach computers to see people and understand their behavior in complex 3D scenes. We are located…

《Prometheus 监控实践:从零到英雄》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e;猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f6e0;️ 全栈技术 Full Stack: &#x1f4da…

【服务器 | 宝塔】宝塔面板卸载重装教程:清理删除宝塔面板并重新开始

宝塔面板卸载重装怎么操作?我们很多用户可能安装宝塔之后会经常看一下有没有新版本&#xff0c;如果有新版直接右上角 宝塔面板卸载重装怎么操作?我们很多用户可能安装宝塔之后会经常看一下有没有新版本&#xff0c;如果有新版直接右上角”更新”升级一下版本就可以了&#…

1-4 AUTOSAR方法论

总目录——AUTOSAR入门详解AUTOSAR入门详解目录汇总&#xff1a;待续中。。。https://xianfan.blog.csdn.net/article/details/132818463 目录 一、前言 二、方法论 三、单个ECU开发流程 一、前言 汽车生产供应链上有以下角色&#xff1a;OEM、TIER1、TIER2&#xff0c;其主…

提示msvcr120.dll丢失怎样修复呢?全面分析msvcr120.dll解决方法

在计算机使用过程中&#xff0c;可能会遇到 msvcr120.dll 丢失的问题。msvcr120.dll 是 Microsoft Visual C Redistributable 的一部分&#xff0c;是一个动态链接库文件&#xff08;DLL&#xff09;&#xff0c;用于支持运行在 Windows 操作系统上使用了 Microsoft Visual C 2…

9.12|day 5|day 44 |完全背包| 518. 零钱兑换 II | 377. 组合总和 Ⅳ

● 完全背包 主要是看清01背包和完全背包的区别 //01背包 for(int i 0;i<weight.size();i){ for(int j bagWeight;j>weight[i];j--){dp[j] Math.max(dp[j],dp[j-weight[i]]value[i]); } } //完全背包 for(int i 0;i<weight.size();i){for(int j weight[i];j<…

数据结构——排序算法——冒泡排序

冒泡排序1 void swap(vector<int> arr, int i, int j) {int temp arr[i];arr[i] arr[j];arr[j] temp;}void bubbleSort1(vector<int> arr) {for (int i 0; i < arr.size() - 1; i){for (int j 0; j < arr.size() - 1 - i; j){if (arr[j] > arr[j 1…

c++ 学习 之 常函数 和 常对象

前言 常函数 成员函数后加 const 我们可以称这个函数为 常函数 常函数内不可以修改成员属性 成员属性声明时加关键字 mutable 后&#xff0c;在常函数中依然可以修改 常对象 常对象 声明对象前加 const 称该对象为常对象 常对象只能调用常函数 正文 常函数 class Person…