基于STM32设计的通信机房空调与新风系统联动装置【玩转华为云】

举报
DS小龙哥 发表于 2024/10/31 21:34:16 2024/10/31
【摘要】 本项目的设计思路主要围绕提高通信机房环境管理的智能化和自动化水平展开。鉴于机房内设备众多且运行负荷大的特点,环境温湿度的实时监测显得尤为重要。选择了DHT11温湿度传感器来持续采集环境数据,确保能够及时反映机房内的温度和湿度变化情况。

一、前言

1.1 项目介绍

【1】项目开发背景

随着信息技术的发展,数据中心和通信机房作为信息处理的核心枢纽,在各行各业中扮演着越来越重要的角色。然而,由于设备密集且持续运行,机房内的温度和湿度控制成为保证设备正常运行的关键因素之一。传统的机房环境管理方式往往依赖于定期的人工巡检和手动调节,这种方式不仅效率低下,而且容易出现管理疏漏,导致设备过热或湿度过高,进而影响设备的稳定性和使用寿命。

针对这一现状,本项目提出了一种基于STM32微控制器的通信机房空调与新风系统联动装置的设计方案。通过集成环境温湿度传感器、风扇控制模块以及4G通信模块,实现了对机房环境的实时监测和智能调控。特别是在数据传输方面,借助华为云IOT物联网云平台的强大功能,不仅可以远程监控机房内的环境变化,还能及时调整空调和新风系统的运行策略,确保机房环境始终处于最佳状态。同时,考虑到实际应用中的便捷性,本装置还提供了本地控制功能,用户可以通过直观的OLED显示屏了解当前环境状态,并通过简单的按键操作来调整设置,增强了用户体验。

通过这样的设计,本项目不仅解决了传统机房管理中存在的问题,还极大地提高了机房环境管理的智能化水平,为机房管理人员提供了一个高效、可靠、易于操作的解决方案,进一步保障了通信设备的稳定运行,降低了运维成本。

华为云IoT平台是华为提供的一站式物联网(Internet of Things, IoT)服务解决方案,旨在帮助企业和开发者快速构建安全可靠的物联网应用。该平台基于云计算技术,集成了设备接入、数据处理、业务逻辑定制、数据分析等核心功能,支持大规模设备的连接与管理,以及高效的数据交换与处理。

在设备接入方面,华为云IoT平台提供了丰富的协议支持,包括MQTT、CoAP、HTTP等标准协议,同时也支持自定义协议,确保不同类型的设备能够轻松接入云端。通过安全认证机制,如设备证书、密钥等方式,保障了设备与平台之间的通信安全。

对于数据处理,华为云IoT平台具备强大的规则引擎,可以对收集到的设备数据进行实时处理和分析,根据预设的条件触发相应的动作,比如将处理后的数据转发至其他系统或调用API接口实现业务联动。此外,平台还支持数据存储,方便用户对历史数据进行查询和分析。

在业务逻辑定制上,华为云IoT平台允许用户根据实际需求灵活配置业务流程,比如设置告警阈值、定义设备间的互动模式等,极大地提高了应用开发的灵活性和效率。同时,平台也提供了丰富的API接口,便于第三方系统的集成和扩展。

安全性是华为云IoT平台设计的重要考量之一。除了前面提到的设备接入安全措施外,平台还采用了多重防护策略,如数据加密传输、访问控制、安全审计等,确保整个物联网系统的稳定运行和用户数据的安全。

华为云IoT平台还注重用户体验,提供了直观易用的管理界面,让非专业技术人员也能轻松完成设备管理和监控工作。无论是小规模的试验项目还是大规模的商业部署,华为云IoT平台都能提供全面的支持和服务,助力客户加速物联网应用的落地实施。

image-20240904161939733


image-20240904161823706




【2】设计实现的功能

(1)实时采集环境参数:系统配备了DHT11温湿度传感器,能够连续不断地获取机房内的环境温度和湿度数据。

(2)风扇通风散热控制:通过继电器模块控制两个5V直流电机小风扇的启停,以达到通风散热的效果,确保机房内部温度维持在安全范围内。

(3)数据远程上传:利用合宙Air724UG 4G模块,将环境监测数据上传至华为云IOT物联网云平台,实现数据的云端存储与管理。

(4)远程监控与控制:用户可以通过Windows客户端应用程序实时查看机房内的温度、湿度、风扇状态等信息,并能远程设置温度湿度阈值,选择运行模式(自动/手动),以及远程控制风扇的开关。

(5)本地控制功能:在没有网络连接的情况下,用户依然可以通过设备上的按键来控制风扇的开关状态,以及切换设备的运行模式。

(6)OLED显示屏信息展示:装置上安装了一个0.96寸的OLED显示屏,用于显示两页信息:第一页显示环境温度、湿度、温度和湿度阈值;第二页显示设备运行模式和风扇的开关状态。


【3】项目硬件模块组成

(1)主控模块:采用STM32F103RCT6微控制器作为核心处理器,负责处理各种传感器数据、控制输出信号以及与外部设备的通信。

(2)环境监测模块:使用DHT11温湿度传感器来实时采集机房内的温度和湿度数据,为后续的环境控制提供依据。

(3)显示模块:配置了一块0.96寸的OLED显示屏,通过SPI协议接口与主控芯片相连,用于显示当前环境的温度、湿度、设定阈值以及设备的运行状态。

(4)风扇控制模块:包括两个5V直流电机的小风扇,通过继电器控制风扇的启动和停止,实现通风散热功能。

(5)通信模块:采用合宙Air724UG 4G模块,支持全网通4G网络,用于将采集到的数据上传至华为云IOT物联网云平台,同时也支持接收来自云端的控制指令。

(6)人机交互模块:包括若干个按键,允许用户进行本地控制,如设置温度湿度阈值、切换设备运行模式以及控制风扇的开关。

(7)电源模块:系统使用一个5V 2A的稳压模块来提供稳定的电力供应,确保各个模块的正常工作。

这些硬件模块相互协作,共同构成了一个完整的智能通信机房空调与新风系统联动装置,能够实现环境参数的实时监测、数据的远程传输与控制、以及本地的人机交互等功能。


【4】需求总结

项目名称: 基于STM32设计的通信机房空调与新风系统联动装置

支持的功能:
(1) 能够实时采集环境温度与湿度。
(2) 能够控制2个风扇进行通风扇热。
(3) 设备端的数据通过4G网络上传到华为云IOT物联网云平台。
(4) 能够通过Windows大屏远程查看设备上传的温度、湿度,风扇开关状态,设备运行的模式。
(5) 能够通过Windows大屏远程设置温度和湿度阀值,设置运行模式(自动模式和手动模式),以及控制风扇的开启和关闭。
(6) 本地设备也可以通过按键控制风扇的开关,以及切换设备运行模式。
(7) 本地设备带了一个0.96寸的OLED显示屏,一共显示2个页面。 第一个页面显示: 可以显示环境的温度、环境的湿度、温度阀值、湿度阀值。 第二个页面显示: 设备运行模式、风扇的开关状态。

硬件选型:
(1)主控芯片选择STM32F103RCT6
(2)OLED显示屏选择SPI协议接口的0.96寸OLED显示屏。
(3)4G模块采用合宙的Air724UG 4G模块,支持全网通4G网络。
(4)环境温湿度检测采用DHT11模块。
(5)风扇采用5V直流电机的小风扇,通过继电器控制风扇的开和关。
(6)系统电源采用5V 2A的稳压模块进行供电,提供稳定电源。


1.2 设计思路

本项目的设计思路主要围绕提高通信机房环境管理的智能化和自动化水平展开。鉴于机房内设备众多且运行负荷大的特点,环境温湿度的实时监测显得尤为重要。选择了DHT11温湿度传感器来持续采集环境数据,确保能够及时反映机房内的温度和湿度变化情况。

为了有效控制机房内的温度,设计中引入了风扇控制模块。通过继电器模块来驱动5V直流电机的小风扇,可以根据采集到的温度数据自动或手动控制风扇的开关,以此来调节机房内的温度,防止设备因过热而损坏。考虑到远程监控的需求,选用了合宙Air724UG 4G模块,通过4G网络将环境数据上传至华为云IOT物联网云平台,使得管理者能够在任何地点通过Windows客户端实时查看机房状态,并作出相应的调整。

在本地控制方面,为了便于现场工作人员的操作,设计中加入了按键控制功能。通过简单的按键输入,可以实现风扇开关的控制以及设备运行模式的切换。为了使用户能够直观地了解当前机房的状态,项目中还配备了一块0.96寸的OLED显示屏,用以显示环境参数、设置阈值及设备运行模式等重要信息。

系统整体采用STM32F103RCT6作为主控芯片,该芯片具备足够的性能来处理传感器数据、执行控制逻辑以及管理与其他模块之间的通信。为了保证系统的稳定运行,采用了5V 2A的稳压电源模块来提供可靠的电力供应。

本项目的设计思路是在充分理解通信机房环境管理需求的基础上,结合现代物联网技术和智能控制手段,构建一个集数据采集、分析处理、远程监控与本地控制于一体的智能化管理系统,以提高机房环境管理的效率和可靠性。


1.3 系统功能总结

功能类别 描述
环境监测 实时采集机房内的温度和湿度数据,使用DHT11模块。
风扇控制 通过继电器模块控制两个5V直流风扇的启停,实现通风散热。
数据传输 利用合宙Air724UG 4G模块将环境数据上传至华为云IOT物联网云平台。
远程监控 通过Windows客户端远程查看温度、湿度、风扇状态及设备运行模式。
远程设置 允许用户远程设置温度和湿度阈值,切换运行模式(自动/手动),控制风扇开关。
本地控制 提供按键输入功能,实现风扇开关控制及设备运行模式切换。
显示信息 OLED显示屏显示环境参数、设置阈值、设备运行模式及风扇状态。
电源管理 采用5V 2A稳压电源模块,确保系统稳定运行。


1.4 开发工具的选择

【1】设备端开发

STM32的编程语言选择C语言,C语言执行效率高,大学里主学的C语言,C语言编译出来的可执行文件最接近于机器码,汇编语言执行效率最高,但是汇编的移植性比较差,目前在一些操作系统内核里还有一些低配的单片机使用的较多,平常的单片机编程还是以C语言为主。C语言的执行效率仅次于汇编,语法理解简单、代码通用性强,也支持跨平台,在嵌入式底层、单片机编程里用的非常多,当前的设计就是采用C语言开发。

开发工具选择Keil,keil是一家世界领先的嵌入式微控制器软件开发商,在2015年,keil被ARM公司收购。因为当前芯片选择的是STM32F103系列,STMF103是属于ARM公司的芯片构架、Cortex-M3内核系列的芯片,所以使用Kile来开发STM32是有先天优势的,而keil在各大高校使用的也非常多,很多教科书里都是以keil来教学,开发51单片机、STM32单片机等等。目前作为MCU芯片开发的软件也不只是keil一家独大,IAR在MCU微处理器开发领域里也使用的非常多,IAR扩展性更强,也支持STM32开发,也支持其他芯片,比如:CC2530,51单片机的开发。从软件的使用上来讲,IAR比keil更加简洁,功能相对少一些。如果之前使用过keil,而且使用频率较多,已经习惯再使用IAR是有点不适应界面的。

image-20221210225339928


【2】上位机开发

上位机的开发选择Qt框架,编程语言采用C++;Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。Qt能轻松创建具有原生C++性能的连接设备、用户界面(UI)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。

image-20230218001243591

image-20230218001219105




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

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

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

image-20221204193824815


2.1 物联网平台介绍

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

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

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

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

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

img



2.2 开通物联网服务

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

image-20221204194233414


点击立即创建

image-20240117134653452


正在创建标准版实例,需要等待片刻。

image-20240117134729401


创建完成之后,点击实例名称。 可以看到标准版实例的设备接入端口和地址。

image-20240425180759670



在上面也能看到 免费单元的限制。

image-20240425180817704



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

image-20240425180845461


总结:

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

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

打开Windows电脑的命令行控制台终端,使用ping 命令。ping一下即可。

Microsoft Windows [版本 10.0.19045.4170]
(c) Microsoft Corporation。保留所有权利。

C:\Users\11266>ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com

正在 Ping ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
来自 117.78.5.125 的回复: 字节=32 时间=35ms TTL=93
来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=93
来自 117.78.5.125 的回复: 字节=32 时间=36ms TTL=93
来自 117.78.5.125 的回复: 字节=32 时间=39ms TTL=93

117.78.5.125 的 Ping 统计信息:
    数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
    最短 = 35ms,最长 = 39ms,平均 = 36ms

C:\Users\11266>

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


2.3 创建产品

(1)创建产品

image-20230109164412041


(2)填写产品信息

根据自己产品名字填写,下面的设备类型选择自定义类型。

image-20240612094809689


(3)产品创建成功

image-20240612095148945


创建完成之后点击查看详情。

image-20240612095134263


(4)添加自定义模型

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

模型简单来说: 就是存放设备上传到云平台的数据。

你可以根据自己的产品进行创建。

比如:

烟雾可以叫  MQ2
温度可以叫  Temperature
湿度可以叫  humidity
火焰可以叫  flame
其他的传感器自己用单词简写命名即可。 这就是你的单片机设备端上传到服务器的数据名字。



先点击自定义模型。

image-20240612095517900


再创建一个服务ID。

image-20240612095542749


接着点击新增属性。

image-20240612095648815


image-20240612095711898



2.4 添加设备

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

(1)注册设备

image-20240425181935561


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

image-20240612100115167


(3)保存设备信息

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

image-20240612100128061



(4)设备创建完成

image-20240612100147232



(5)设备详情

image-20240612100202960



image-20240612100217236




2.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/663cb18871d845632a0912e7_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/663cb18871d845632a0912e7_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。

上传的JSON数据格式如下:

{
  "services": [
    {
      "service_id": <填服务ID>,
      "properties": {
        "<填属性名称1>": <填属性值>,
        "<填属性名称2>": <填属性值>,
        ..........
      }
    }
  ]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。

根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}


2.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-20240509193207359


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

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

华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883


如何得到IP地址?如何域名转IP? 打开Windows的命令行输入以下命令。

ping  ad635970a1.st1.iotda-device.cn-north-4.myhuaweicloud.com

image-20240425182610048



(2)生成MQTT三元组

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

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

下面是打开的页面:

image-20240425183025893


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

直接得到三元组信息。

image-20240509193310020


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

ClientId  663cb18871d845632a0912e7_dev1_0_0_2024050911
Username  663cb18871d845632a0912e7_dev1
Password  71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237



2.7 模拟设备登录测试

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

(1)填入登录信息

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

image-20240509193457358



(2)打开网页查看

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

image-20240612100508790


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

image-20240612100529581


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


(3)MQTT登录测试参数总结

MQTT服务器:  117.78.5.125
MQTT端口号:  183

//物联网服务器的设备信息
#define MQTT_ClientID "663cb18871d845632a0912e7_dev1_0_0_2024050911"
#define MQTT_UserName "663cb18871d845632a0912e7_dev1"
#define MQTT_PassWord "71b82deae83e80f04c4269b5bbce3b2fc7c13f610948fe210ce18650909ac237"

//订阅与发布的主题
#define SET_TOPIC  "$oc/devices/663cb18871d845632a0912e7_dev1/sys/messages/down"  //订阅
#define POST_TOPIC "$oc/devices/663cb18871d845632a0912e7_dev1/sys/properties/report"  //发布


发布的数据:
{"services": [{"service_id": "stm32","properties":{"DHT11_T":30,"DHT11_H":10,"BH1750":1,"MQ135":0}}]}


2.8 创建IAM账户

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

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


【1】获取项目凭证 点击左上角用户名,选择下拉菜单里的我的凭证

image-20240509193646253


image-20240509193701262


项目凭证:

28add376c01e4a61ac8b621c714bf459



【2】创建IAM用户

鼠标放在左上角头像上,在下拉菜单里选择统一身份认证

image-20240509193729078



点击左上角创建用户

image-20240509193744287



image-20240314153208692




image-20240314153228359

image-20240314153258229



创建成功:

image-20240314153315444



【3】创建完成

image-20240509193828289


用户信息如下:

主用户名  l19504562721
IAM用户  ds_abc
密码     DS12345678



2.9 获取影子数据

帮助文档: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-20240509194152229



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

{
 "device_id": "663cb18871d845632a0912e7_dev1",
 "shadow": [
  {
   "service_id": "stm32",
   "desired": {
    "properties": null,
    "event_time": null
   },
   "reported": {
    "properties": {
     "DHT11_T": 18,
     "DHT11_H": 90,
     "BH1750": 38,
     "MQ135": 70
    },
    "event_time": "20240509T113448Z"
   },
   "version": 3
  }
 ]
}



调试成功之后,可以得到访问影子数据的真实链接,接下来的代码开发中,就采用Qt写代码访问此链接,获取影子数据,完成上位机开发。

image-20240509194214716


链接如下:

https://ad635970a1.st1.iotda-app.cn-north-4.myhuaweicloud.com:443/v5/iot/28add376c01e4a61ac8b621c714bf459/devices/663cb18871d845632a0912e7_dev1/shadow





三、上位机开发

为了方便查看设备上传的数据,接下来利用Qt开发一款Android手机APP 和 Windows上位机。

使用华为云平台提供的API接口获取设备上传的数据,进行可视化显示,以及远程控制设备。

3.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

或者去网盘里下载:https://pan.quark.cn/s/145a9b3f7f53


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

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

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

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

image-20221203151742653


选择MinGW 32-bit 编译器: (一定要看清楚了)

image-20221203151750344


说明: 我这里只是介绍PC端,也就是Windows系统下的Qt环境搭建。 Android的开发环境比较麻烦,如果想学习Android开发,想编译Android程序的APP,需要自己去搭建Android环境。


也可以看下面这篇文章,不过这个文章是在Qt开发专栏里付费的,需要订阅专栏才可以看。 如果不想付费看,也可以自行找其他教程,自己搭建好必须的环境就行了

Android环境搭建的博客链接: https://blog.csdn.net/xiaolong1126626497/article/details/117254453


3.2 新建上位机工程

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


【1】新建工程

image-20240117144052547


【2】设置项目的名称。

image-20240509195711965



【3】选择编译系统

image-20240117144239681


【4】选择默认继承的类

image-20240117144302275


【5】选择编译器

image-20240314162137170



【6】点击完成

image-20240117144354252


【7】工程创建完成

image-20230421094133333



3.3 设计UI界面与工程配置

【1】打开UI文件

image-20230421094815236


打开默认的界面如下:

image-20240425194845233



【2】开始设计界面

根据自己需求设计界面。

image-20240904160948734



3.5 编译Windows上位机

点击软件左下角的绿色三角形按钮进行编译运行。

image-20240509202031739

编译之后的效果:

image-20240903163952415



3.6 配置Android环境

如果想编译Android手机APP,必须要先自己配置好自己的Android环境。(搭建环境的过程可以自行百度搜索学习)


然后才可以进行下面的步骤。


【1】选择Android编译器

image-20240425232651515


image-20240509202408776


【2】创建Android配置文件


image-20240117144604025


image-20240117144635052


image-20240117144652014


创建完成。

【3】配置Android图标与名称

image-20240612100947190


【3】编译Android上位机

Qt本身是跨平台的,直接选择Android的编译器,就可以将程序编译到Android平台。

然后点击构建。

image-20240509202534407


成功之后,在目录下可以看到生成的apk文件,也就是Android手机的安装包,电脑端使用QQ发送给手机QQ,手机登录QQ接收,就能直接安装。


生成的apk的目录在哪里呢? 编译完成之后,在控制台会输出APK文件的路径。

知道目录在哪里之后,在Windows的文件资源管理器里,找到路径,具体看下图,找到生成的apk文件。

image-20240509202712295


D:/linux-share-dir/QT/build-app_Huawei_Eco_tracking-Android_for_arm64_v8a_Clang_Qt_5_12_6_for_Android_ARM64_v8a-Release/android-build//build/outputs/apk/debug/android-build-debug.apk


四、STM32代码开发

4.1 MQTT协议设计

#include "MQTTClient.h"
#include <string.h>
#include <stdlib.h>

#if !defined(_WINDOWS)
	#include <sys/time.h>
  	#include <sys/socket.h>
	#include <unistd.h>
  	#include <errno.h>
#else
#include <winsock2.h>
#include <ws2tcpip.h>
#define MAXHOSTNAMELEN 256
#define EAGAIN WSAEWOULDBLOCK
#define EINTR WSAEINTR
#define EINPROGRESS WSAEINPROGRESS
#define EWOULDBLOCK WSAEWOULDBLOCK
#define ENOTCONN WSAENOTCONN
#define ECONNRESET WSAECONNRESET
#endif

#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))


char* topics[] =  {"TopicA", "TopicA/B", "Topic/C", "TopicA/C", "/TopicA"};
char* wildtopics[] = {"TopicA/+", "+/C", "#", "/#", "/+", "+/+", "TopicA/#"};
char* nosubscribe_topics[] = {"nosubscribe",};

struct Options
{
	char* connection;         /**< connection to system under test. */
	char* clientid1;
	char* clientid2;
	char* username;
	char* password;
	int verbose;
	int MQTTVersion;
	int iterations;
	int run_dollar_topics_test;
	int run_subscribe_failure_test;
} options =
{
	"tcp://localhost:1883",
	"myclientid",
	"myclientid2",
	NULL,
	NULL,
	0,
	MQTTVERSION_3_1_1,
	1,
	0,
	0,
};


void usage(void)
{
	printf("options:\n  connection, clientid1, clientid2, username, password, MQTTversion, iterations, verbose\n");
	exit(EXIT_FAILURE);
}

void getopts(int argc, char** argv)
{
	int count = 1;
	
	while (count < argc)
	{
		if (strcmp(argv[count], "--dollar_topics_test") == 0 || strcmp(argv[count], "--$") == 0)
		{
			options.run_dollar_topics_test = 1;
			printf("Running $ topics test\n");
		}
		else if (strcmp(argv[count], "--subscribe_failure_test") == 0 || strcmp(argv[count], "-s") == 0)
		{
			options.run_subscribe_failure_test = 1;
			printf("Running subscribe failure test\n");
		}
		else if (strcmp(argv[count], "--connection") == 0)
		{
			if (++count < argc)
			{
				options.connection = argv[count];
				printf("Setting connection to %s\n", options.connection);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--clientid1") == 0)
		{
			if (++count < argc)
			{
				options.clientid1 = argv[count];
				printf("Setting clientid1 to %s\n", options.clientid1);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--clientid2") == 0)
		{
			if (++count < argc)
			{
				options.clientid2 = argv[count];
				printf("Setting clientid2 to %s\n", options.clientid2);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--username") == 0)
		{
			if (++count < argc)
			{
				options.username = argv[count];
				printf("Setting username to %s\n", options.username);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--password") == 0)
		{
			if (++count < argc)
			{
				options.password = argv[count];
				printf("Setting password to %s\n", options.password);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--MQTTversion") == 0)
		{
			if (++count < argc)
			{
				options.MQTTVersion = atoi(argv[count]);
				printf("Setting MQTT version to %d\n", options.MQTTVersion);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--iterations") == 0)
		{
			if (++count < argc)
			{
				options.iterations = atoi(argv[count]);
				printf("Setting iterations to %d\n", options.iterations);
			}
			else
				usage();
		}
		else if (strcmp(argv[count], "--verbose") == 0)
		{
			options.verbose = 1;
			printf("\nSetting verbose on\n");
		}
		count++;
	}
}


#if defined(_WIN32) || defined(_WINDOWS)
#define msleep Sleep
#define START_TIME_TYPE DWORD
static DWORD start_time = 0;
START_TIME_TYPE start_clock(void)
{
	return GetTickCount();
}
#elif defined(AIX)
#define mqsleep sleep
#define START_TIME_TYPE struct timespec
START_TIME_TYPE start_clock(void)
{
	static struct timespec start;
	clock_gettime(CLOCK_REALTIME, &start);
	return start;
}
#else
#define msleep(A) usleep(A*1000)
#define START_TIME_TYPE struct timeval
/* TODO - unused - remove? static struct timeval start_time; */
START_TIME_TYPE start_clock(void)
{
	struct timeval start_time;
	gettimeofday(&start_time, NULL);
	return start_time;
}
#endif

#define LOGA_DEBUG 0
#define LOGA_INFO 1
#include <stdarg.h>
#include <time.h>
#include <sys/timeb.h>
void MyLog(int LOGA_level, char* format, ...)
{
	static char msg_buf[256];
	va_list args;
#if defined(_WIN32) || defined(_WINDOWS)
	struct timeb ts;
#else
	struct timeval ts;
#endif
	struct tm timeinfo;

	if (LOGA_level == LOGA_DEBUG && options.verbose == 0)
	  return;

#if defined(_WIN32) || defined(_WINDOWS)
	ftime(&ts);
	localtime_s(&timeinfo, &ts.time);
#else
	gettimeofday(&ts, NULL);
	localtime_r(&ts.tv_sec, &timeinfo);
#endif
	strftime(msg_buf, 80, "%Y%m%d %H%M%S", &timeinfo);

#if defined(_WIN32) || defined(_WINDOWS)
	sprintf(&msg_buf[strlen(msg_buf)], ".%.3hu ", ts.millitm);
#else
	sprintf(&msg_buf[strlen(msg_buf)], ".%.3lu ", ts.tv_usec / 1000L);
#endif

	va_start(args, format);
	vsnprintf(&msg_buf[strlen(msg_buf)], sizeof(msg_buf) - strlen(msg_buf), format, args);
	va_end(args);

	printf("%s\n", msg_buf);
	fflush(stdout);
}

int tests = 0;
int failures = 0;


void myassert(char* filename, int lineno, char* description, int value, char* format, ...)
{
	++tests;
	if (!value)
	{
		int count;
		va_list args;

		++failures;
		printf("Assertion failed, file %s, line %d, description: %s\n", filename, lineno, description);

		va_start(args, format);
		count = vprintf(format, args);
		va_end(args);
		if (count)
			printf("\n");

		//cur_output += sprintf(cur_output, "<failure type=\"%s\">file %s, line %d </failure>\n", 
        //               description, filename, lineno);
	}
    else
    	MyLog(LOGA_DEBUG, "Assertion succeeded, file %s, line %d, description: %s", filename, lineno, description);  
}


#define assert(a, b, c, d) myassert(__FILE__, __LINE__, a, b, c, d)
#define assert1(a, b, c, d, e) myassert(__FILE__, __LINE__, a, b, c, d, e)

typedef struct
{
	char* topicName;
	int topicLen;
	MQTTClient_message* m;
} messageStruct;

messageStruct messagesArrived[1000];
int messageCount = 0;

int messageArrived(void* context, char* topicName, int topicLen, MQTTClient_message* m)
{
	messagesArrived[messageCount].topicName = topicName;
	messagesArrived[messageCount].topicLen = topicLen;
	messagesArrived[messageCount++].m = m;
	MyLog(LOGA_DEBUG, "Callback: %d message received on topic %s is %.*s.",
					messageCount, topicName, m->payloadlen, (char*)(m->payload));
	return 1;
}


void clearMessages(void)
{
	int i;

	for (i = 0; i < messageCount; ++i)
	{
		MQTTClient_free(messagesArrived[i].topicName);
		MQTTClient_freeMessage(&messagesArrived[i].m);
	}
	messageCount = 0;
}

void cleanup(void)
{
	// clean all client state
	char* clientids[] = {options.clientid1, options.clientid2};
	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Cleaning up");

	opts.keepAliveInterval = 20;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;
	
	for (i = 0; i < 2; ++i)
	{
		rc = MQTTClient_create(&aclient, options.connection, clientids[i], MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
		assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

		rc = MQTTClient_connect(aclient, &opts);
		assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

		rc = MQTTClient_disconnect(aclient, 100);
		assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

		MQTTClient_destroy(&aclient);
	}

	// clean retained messages 
	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, "#", 0);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	msleep(2000); // wait for all retained messages to arrive

	rc = MQTTClient_unsubscribe(aclient, "#");
	assert("Good rc from unsubscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	for (i = 0; i < messageCount; ++i)
	{
		if (messagesArrived[i].m->retained)
		{
			MyLog(LOGA_INFO, "Deleting retained message for topic %s", (char*)messagesArrived[i].topicName);
			rc = MQTTClient_publish(aclient, messagesArrived[i].topicName, 0, "", 0, 1, NULL);
			assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
		}
	}

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&aclient);

	clearMessages();

	MyLog(LOGA_INFO, "Finished cleaning up");
}

 
int basic_test(void)
{
	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Starting basic test");

	tests = failures = 0;

	opts.keepAliveInterval = 20;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, topics[0], 0);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[0], 5, "qos 0", 0, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[0], 5, "qos 1", 1, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[0], 5, "qos 2", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	msleep(1000);

	rc = MQTTClient_disconnect(aclient, 10000);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	assert("3 Messages received", messageCount == 3, "messageCount was %d", messageCount);
	clearMessages();

	/*opts.MQTTVersion = MQTTVERSION_3_1;
	rc = MQTTClient_connect(aclient, &opts); // should fail - wrong protocol version
	assert("Bad rc from connect", rc == MQTTCLIENT_FAILURE, "rc was %d", rc);*/
	
	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Basic test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}



int offline_message_queueing_test(void)
{
	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;
	MQTTClient bclient;

	MyLog(LOGA_INFO, "Offline message queueing test");

	tests = failures = 0;

	opts.keepAliveInterval = 20;
	opts.cleansession = 0;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	opts.cleansession = 1;
	rc = MQTTClient_connect(bclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(bclient, topics[1], 5, "qos 0", 0, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(bclient, topics[2], 5, "qos 1", 1, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);	

	rc = MQTTClient_publish(bclient, topics[3], 5, "qos 2", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	msleep(2000);

	rc = MQTTClient_disconnect(bclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&bclient);

	opts.cleansession = 0;
	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000);  // receive the queued messages

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&aclient);

	assert("2 or 3 messages received", messageCount == 3 || messageCount == 2, "messageCount was %d", messageCount);

	MyLog(LOGA_INFO, "This server %s queueing QoS 0 messages for offline clients", (messageCount == 3) ? "is" : "is not");

	clearMessages();

	MyLog(LOGA_INFO, "Offline message queueing test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int retained_message_test(void)
{ 
	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Retained message test");

	tests = failures = 0;

	opts.keepAliveInterval = 20;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount);
  
    // set retained messages
	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[1], 5, "qos 0", 0, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[2], 5, "qos 1", 1, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);	

	rc = MQTTClient_publish(aclient, topics[3], 5, "qos 2", 2, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000);

	rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(2000);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	assert("3 messages received", messageCount == 3, "messageCount was %d", messageCount);

	for (i = 0; i < messageCount; ++i)
	{
		assert("messages should be retained", messagesArrived[i].m->retained, "retained was %d", 
			messagesArrived[i].m->retained);
		MQTTClient_free(messagesArrived[i].topicName);
		MQTTClient_freeMessage(&messagesArrived[i].m);
	}
	messageCount = 0;

    // clear retained messages
	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[1], 0, "", 0, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[2], 0, "", 1, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);	

	rc = MQTTClient_publish(aclient, topics[3], 0, "", 2, 1, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(200); // wait for QoS 2 exchange to be completed
	rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(200);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	assert("0 messages received", messageCount == 0, "messageCount was %d", messageCount);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Retained message test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}

#define SOCKET_ERROR -1

int test6_socket_error(char* aString, int sock)
{
#if defined(_WIN32)
	int errno;
#endif

#if defined(_WIN32)
	errno = WSAGetLastError();
#endif
	if (errno != EINTR && errno != EAGAIN && errno != EINPROGRESS && errno != EWOULDBLOCK)
	{
		if (strcmp(aString, "shutdown") != 0 || (errno != ENOTCONN && errno != ECONNRESET))
			printf("Socket error %d in %s for socket %d", errno, aString, sock);
	}
	return errno;
}

int test6_socket_close(int socket)
{
	int rc;

#if defined(_WIN32)
	if (shutdown(socket, SD_BOTH) == SOCKET_ERROR)
		test6_socket_error("shutdown", socket);
	if ((rc = closesocket(socket)) == SOCKET_ERROR)
		test6_socket_error("close", socket);
#else
	if (shutdown(socket, SHUT_RDWR) == SOCKET_ERROR)
		test6_socket_error("shutdown", socket);
	if ((rc = close(socket)) == SOCKET_ERROR)
		test6_socket_error("close", socket);
#endif
	return rc;
}

typedef struct
{
	int socket;
	time_t lastContact;
#if defined(OPENSSL)
	SSL* ssl;
	SSL_CTX* ctx;
#endif
} networkHandles;


typedef struct
{
	char* clientID;					/**< the string id of the client */
	char* username;					/**< MQTT v3.1 user name */
	char* password;					/**< MQTT v3.1 password */
	unsigned int cleansession : 1;	/**< MQTT clean session flag */
	unsigned int connected : 1;		/**< whether it is currently connected */
	unsigned int good : 1; 			/**< if we have an error on the socket we turn this off */
	unsigned int ping_outstanding : 1;
	int connect_state : 4;
	networkHandles net;
/* ... */
} Clients;


typedef struct
{
	char* serverURI;
	Clients* c;
	MQTTClient_connectionLost* cl;
	MQTTClient_messageArrived* ma;
	MQTTClient_deliveryComplete* dc;
	void* context;

	int connect_sem;
	int rc; /* getsockopt return code in connect */
	int connack_sem;
	int suback_sem;
	int unsuback_sem;
	void* pack;
} MQTTClients;

 
int will_message_test(void)
{
	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient_willOptions wopts =  MQTTClient_willOptions_initializer;
	MQTTClient aclient, bclient;

	MyLog(LOGA_INFO, "Will message test");

	tests = failures = 0;

	opts.keepAliveInterval = 2;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	opts.will = &wopts;
	opts.will->message = "client not disconnected";
	opts.will->qos = 1;
	opts.will->retained = 0;
	opts.will->topicName = topics[2];

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	opts.keepAliveInterval = 20;
	opts.will = NULL;
	rc = MQTTClient_connect(bclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(bclient, topics[2], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(100);

   	test6_socket_close(((MQTTClients*)aclient)->c->net.socket); 

	while (messageCount == 0 && ++count < 10)
    	msleep(1000);

	rc = MQTTClient_disconnect(bclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&bclient);

	assert("will message received", messageCount == 1, "messageCount was %d", messageCount);

	rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Will message test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int overlapping_subscriptions_test(void)
{
  /* overlapping subscriptions. When there is more than one matching subscription for the same client for a topic,
   the server may send back one message with the highest QoS of any matching subscription, or one message for
   each subscription with a matching QoS. */

	int i, rc;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;
	char* topicList[] = {wildtopics[6], wildtopics[0]};
	int qosList[] = {2, 1};

	MyLog(LOGA_INFO, "Starting overlapping subscriptions test");

	clearMessages();
	tests = failures = 0;

	opts.keepAliveInterval = 20;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribeMany(aclient, 2, topicList, qosList);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[3], strlen("overlapping topic filters") + 1, 
                "overlapping topic filters", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000);

	assert("1 or 2 messages received", messageCount == 1 || messageCount == 2, "messageCount was %d", messageCount);

    if (messageCount == 1)
	{
		MyLog(LOGA_INFO, "This server is publishing one message for all matching overlapping subscriptions, not one for each.");
		assert("QoS should be 2", messagesArrived[0].m->qos == 2, "QoS was %d", messagesArrived[0].m->qos);
	}
    else
	{
		MyLog(LOGA_INFO, "This server is publishing one message per each matching overlapping subscription.");
		assert1("QoSs should be 1 and 2", 
			(messagesArrived[0].m->qos == 2 && messagesArrived[1].m->qos == 1) ||
      		(messagesArrived[0].m->qos == 1 && messagesArrived[1].m->qos == 2),
		"QoSs were %d %d", messagesArrived[0].m->qos, messagesArrived[1].m->qos);
	}

	rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Overlapping subscription test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int keepalive_test(void)
{
	/* keepalive processing.  We should be kicked off by the server if we don't send or receive any data, and don't send
	any pings either. */

	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient_willOptions wopts =  MQTTClient_willOptions_initializer;
	MQTTClient aclient, bclient;

	MyLog(LOGA_INFO, "Starting keepalive test");

	tests = failures = 0;
	clearMessages();

	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	opts.will = &wopts;
	opts.will->message = "keepalive expiry";
	opts.will->qos = 1;
	opts.will->retained = 0;
	opts.will->topicName = topics[4];

	opts.keepAliveInterval = 20;
	rc = MQTTClient_create(&bclient, options.connection, options.clientid2, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_setCallbacks(bclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(bclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(bclient, topics[4], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	opts.keepAliveInterval = 2;
	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	while (messageCount == 0 && ++count < 20)
    	msleep(1000);

	rc = MQTTClient_disconnect(bclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	assert("Should have will message", messageCount == 1, "messageCount was %d", messageCount);

	rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Keepalive test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}



int redelivery_on_reconnect_test(void)
{
	/* redelivery on reconnect. When a QoS 1 or 2 exchange has not been completed, the server should retry the 
	 appropriate MQTT packets */

	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Starting redelivery on reconnect test");

	tests = failures = 0;
	clearMessages();

	opts.keepAliveInterval = 0;
	opts.cleansession = 0;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, wildtopics[6], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
	MQTTClient_yield();

    // no background processing because no callback has been set
	rc = MQTTClient_publish(aclient, topics[1], 6, "qos 1", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, topics[3], 6, "qos 2", 2, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_disconnect(aclient, 0);

	assert("No messages should have been received yet", messageCount == 0, "messageCount was %d", messageCount);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	while (messageCount < 2 && ++count < 5)
		msleep(1000);

	assert("Should have 2 messages", messageCount == 2, "messageCount was %d", messageCount);

	rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Redelivery on reconnect test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}



int zero_length_clientid_test(void)
{
	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;

	MyLog(LOGA_INFO, "Starting zero length clientid test");

	tests = failures = 0;
	clearMessages();

	opts.keepAliveInterval = 0;
	opts.cleansession = 0;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, "", MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("rc 2 from connect", rc == 2, "rc was %d", rc); // this should always fail

	opts.cleansession = 1;
	rc = MQTTClient_connect(aclient, &opts);
	assert("Connack rc should be 0 or 2", rc == MQTTCLIENT_SUCCESS || rc == 2, "rc was %d", rc);

	MyLog(LOGA_INFO, "This server %s support zero length clientids", (rc == 2) ? "does not" : "does");

	if (rc == MQTTCLIENT_SUCCESS)
		rc = MQTTClient_disconnect(aclient, 100);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Zero length clientid test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int dollar_topics_test(void)
{
  /* $ topics. The specification says that a topic filter which starts with a wildcard does not match topic names that
   	begin with a $.  Publishing to a topic which starts with a $ may not be allowed on some servers (which is entirely valid),
   	so this test will not work and should be omitted in that case.
	*/
	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;
	char dollartopic[20];

	MyLog(LOGA_INFO, "Starting $ topics test");

	sprintf(dollartopic, "$%s", topics[1]);
  
    clearMessages();

	opts.keepAliveInterval = 5;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_subscribe(aclient, wildtopics[5], 2);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000); // wait for any retained messages, hopefully
    clearMessages();

	rc = MQTTClient_publish(aclient, topics[1], 20, "not sent to dollar topic", 1, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_publish(aclient, dollartopic, 20, "sent to dollar topic", 1, 0, NULL);
	assert("Good rc from publish", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

    msleep(1000);
	assert("Should have 1 message", messageCount == 1, "messageCount was %d", messageCount);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "$ topics test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int subscribe_failure_test(void)
{
  /* Subscribe failure.  A new feature of MQTT 3.1.1 is the ability to send back negative reponses to subscribe
   requests.  One way of doing this is to subscribe to a topic which is not allowed to be subscribed to.
  */
	int i, rc, count = 0;
	MQTTClient_connectOptions opts = MQTTClient_connectOptions_initializer;
	MQTTClient aclient;
	int subqos = 2;

	MyLog(LOGA_INFO, "Starting subscribe failure test");
  
    clearMessages();

	opts.keepAliveInterval = 5;
	opts.cleansession = 1;
	opts.username = options.username;
	opts.password = options.password;
	opts.MQTTVersion = options.MQTTVersion;

	rc = MQTTClient_create(&aclient, options.connection, options.clientid1, MQTTCLIENT_PERSISTENCE_DEFAULT, NULL);
	assert("good rc from create",  rc == MQTTCLIENT_SUCCESS, "rc was %d\n", rc);

	rc = MQTTClient_setCallbacks(aclient, NULL, NULL, messageArrived, NULL);
	assert("Good rc from setCallbacks", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	rc = MQTTClient_connect(aclient, &opts);
	assert("Good rc from connect", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
 
	rc = MQTTClient_subscribeMany(aclient, 1, &nosubscribe_topics[0], &subqos);
	assert("Good rc from subscribe", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);
	assert("0x80 rc from subscribe", subqos == 0x80, "subqos was %d", subqos);

	rc = MQTTClient_disconnect(aclient, 100);
	assert("Disconnect successful", rc == MQTTCLIENT_SUCCESS, "rc was %d", rc);

	MQTTClient_destroy(&aclient);

	MyLog(LOGA_INFO, "Subscribe failure test %s", (failures == 0) ?  "succeeded" : "failed");
	return failures;
}


int main(int argc, char** argv)
{
	int i;
	int all_failures = 0;

	getopts(argc, argv);

	for (i = 0; i < options.iterations; ++i)
	{
		cleanup();
		all_failures += basic_test() + 
						offline_message_queueing_test() +
		                retained_message_test() +
		                will_message_test() +
		                overlapping_subscriptions_test() +
		                keepalive_test() +
						redelivery_on_reconnect_test() + 
						zero_length_clientid_test();

		if (options.run_dollar_topics_test)
			all_failures += dollar_topics_test();
		
		if (options.run_subscribe_failure_test)
			all_failures += subscribe_failure_test();
	}

	MyLog(LOGA_INFO, "Test suite %s", (all_failures == 0) ?  "succeeded" : "failed");
}


4.2 OLED显示屏驱动代码

#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"  	 
#include "delay.h"

//OLED模式设置
//0: 4线串行模式  (模块的BS1,BS2均接GND)
//1: 并行8080模式 (模块的BS1,BS2均接VCC)
#define OLED_MODE 	1 
		    						  
//---------------------------OLED端口定义--------------------------  					   
#define OLED_CS  PDout(6)
#define OLED_RST PGout(15) 	
#define OLED_RS  PDout(3)
#define OLED_WR  PGout(14)		  
#define OLED_RD  PGout(13)	   
//PC0~7,作为数据线
#define DATAOUT(x) GPIOC->ODR=(GPIOC->ODR&0xff00)|(x&0x00FF); //输出
  
//使用4线串行接口时使用 
#define OLED_SCLK PCout(0)
#define OLED_SDIN PCout(1)
		     
#define OLED_CMD  0	//写命令
#define OLED_DATA 1	//写数据
//OLED控制用函数
void OLED_WR_Byte(u8 dat,u8 cmd);	    
void OLED_Display_On(void);
void OLED_Display_Off(void);
void OLED_Refresh_Gram(void);  		    
void OLED_Init(void);
void OLED_Clear(void);
void OLED_DrawPoint(u8 x,u8 y,u8 t);
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size);	



//OLED的显存
//存放格式如下.
//[0]0 1 2 3 ... 127	
//[1]0 1 2 3 ... 127	
//[2]0 1 2 3 ... 127	
//[3]0 1 2 3 ... 127	
//[4]0 1 2 3 ... 127	
//[5]0 1 2 3 ... 127	
//[6]0 1 2 3 ... 127	
//[7]0 1 2 3 ... 127 		   
u8 OLED_GRAM[128][8];	 

//更新显存到LCD		 
void OLED_Refresh_Gram(void)
{
	u8 i,n;		    
	for(i=0;i<8;i++)  
	{  
		OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)
		OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址
		OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   
		for(n=0;n<128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA); 
	}   
}
#if OLED_MODE==1	//8080并口 
//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{
	DATAOUT(dat);	    
 	OLED_RS=cmd;
	OLED_CS=0;	   
	OLED_WR=0;	 
	OLED_WR=1;
	OLED_CS=1;	  
	OLED_RS=1;	 
} 	    	    
#else
//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{	
	u8 i;			  
	OLED_RS=cmd; //写命令 
	OLED_CS=0;		  
	for(i=0;i<8;i++)
	{			  
		OLED_SCLK=0;
		if(dat&0x80)OLED_SDIN=1;
		else OLED_SDIN=0;
		OLED_SCLK=1;
		dat<<=1;   
	}				 
	OLED_CS=1;		  
	OLED_RS=1;   	  
} 
#endif
	  	  
//开启OLED显示    
void OLED_Display_On(void)
{
	OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
	OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ON
	OLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}
//关闭OLED显示     
void OLED_Display_Off(void)
{
	OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令
	OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFF
	OLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}		   			 
//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!	  
void OLED_Clear(void)  
{  
	u8 i,n;  
	for(i=0;i<8;i++)for(n=0;n<128;n++)OLED_GRAM[n][i]=0X00;  
	OLED_Refresh_Gram();//更新显示
}
//画点 
//x:0~127
//y:0~63
//t:1 填充 0,清空				   
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{
	u8 pos,bx,temp=0;
	if(x>127||y>63)return;//超出范围了.
	pos=7-y/8;
	bx=y%8;
	temp=1<<(7-bx);
	if(t)OLED_GRAM[x][pos]|=temp;
	else OLED_GRAM[x][pos]&=~temp;	    
}
//x1,y1,x2,y2 填充区域的对角坐标
//确保x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63	 	 
//dot:0,清空;1,填充	  
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot)  
{  
	u8 x,y;  
	for(x=x1;x<=x2;x++)
	{
		for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot);
	}													    
	OLED_Refresh_Gram();//更新显示
}
//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示				 
//size:选择字体 12/16/24
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{      			    
	u8 temp,t,t1;
	u8 y0=y;
	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字体一个字符对应点阵集所占的字节数
	chr=chr-' ';//得到偏移后的值		 
    for(t=0;t<csize;t++)
    {   
		if(size==12)temp=asc2_1206[chr][t]; 	 	//调用1206字体
		else if(size==16)temp=asc2_1608[chr][t];	//调用1608字体
		else if(size==24)temp=asc2_2412[chr][t];	//调用2412字体
		else return;								//没有的字库
        for(t1=0;t1<8;t1++)
		{
			if(temp&0x80)OLED_DrawPoint(x,y,mode);
			else OLED_DrawPoint(x,y,!mode);
			temp<<=1;
			y++;
			if((y-y0)==size)
			{
				y=y0;
				x++;
				break;
			}
		}  	 
    }          
}
//m^n函数
u32 mypow(u8 m,u8 n)
{
	u32 result=1;	 
	while(n--)result*=m;    
	return result;
}				  
//显示2个数字
//x,y :起点坐标	 
//len :数字的位数
//size:字体大小
//mode:模式	0,填充模式;1,叠加模式
//num:数值(0~4294967295);	 		  
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
{         	
	u8 t,temp;
	u8 enshow=0;						   
	for(t=0;t<len;t++)
	{
		temp=(num/mypow(10,len-t-1))%10;
		if(enshow==0&&t<(len-1))
		{
			if(temp==0)
			{
				OLED_ShowChar(x+(size/2)*t,y,' ',size,1);
				continue;
			}else enshow=1; 
		 	 
		}
	 	OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1); 
	}
} 
//显示字符串
//x,y:起点坐标  
//size:字体大小 
//*p:字符串起始地址 
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size)
{	
    while((*p<='~')&&(*p>=' '))//判断是不是非法字符!
    {       
        if(x>(128-(size/2))){x=0;y+=size;}
        if(y>(64-size)){y=x=0;OLED_Clear();}
        OLED_ShowChar(x,y,*p,size,1);	 
        x+=size/2;
        p++;
    }  
	
}	
//初始化SSD1306					    
void OLED_Init(void)
{ 	 	 			 	 				 
	RCC->APB2ENR|=1<<4;    //使能PORTC时钟 
	RCC->APB2ENR|=1<<5;    //使能PORTD时钟 
	RCC->APB2ENR|=1<<8;    //使能PORTG时钟

  	GPIOD->CRL&=0XF0FF0FFF;//PD3,6 推挽输出 
  	GPIOD->CRL|=0X03003000;	 
  	GPIOD->ODR|=1<<3;
  	GPIOD->ODR|=1<<6;	    
#if OLED_MODE==1			//8080并口模式 
	GPIOC->CRL=0X33333333; 	//PC0~7 OUT
	GPIOC->ODR|=0X00FF;
		
  	GPIOG->CRH&=0X000FFFFF;	//PG13,14,15 OUT
  	GPIOG->CRH|=0X33300000;	 
	GPIOG->ODR|=7<<13; 
#else						//4线SPI模式
	GPIOC->CRL&=0XFFFFFF00; //PC0,1 OUT
	GPIOC->CRL|=0X00000033;	    
 	GPIOC->ODR|=3<<0;

 	GPIOG->CRH&=0X0FFFFFFF;	//RST	   
 	GPIOG->CRH|=0X30000000;	 
	GPIOG->ODR|=1<<15;
#endif									  
	OLED_CS=1;
	OLED_RS=1;	 
	
	OLED_RST=0;
	delay_ms(100);
	OLED_RST=1; 
					  
	OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示
	OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率
	OLED_WR_Byte(80,OLED_CMD);   //[3:0],分频因子;[7:4],震荡频率
	OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数
	OLED_WR_Byte(0X3F,OLED_CMD); //默认0X3F(1/64) 
	OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移
	OLED_WR_Byte(0X00,OLED_CMD); //默认为0

	OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数.
													    
	OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置
	OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭
	OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式
	OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
	OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127;
	OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
	OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置
	OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置
		 
	OLED_WR_Byte(0x81,OLED_CMD); //对比度设置
	OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮)
	OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期
	OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;
	OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH 电压倍率
	OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;

	OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
	OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示	    						   
	OLED_WR_Byte(0xAF,OLED_CMD); //开启显示	 
	OLED_Clear();
}  


4.3 DHT11温湿度模块驱动代码

#include "dht11.h"
#include "delay.h"
//IO方向设置
#define DHT11_IO_IN()  {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=8<<12;}
#define DHT11_IO_OUT() {GPIOG->CRH&=0XFFFF0FFF;GPIOG->CRH|=3<<12;}
////IO操作函数											   
#define	DHT11_DQ_OUT PGout(11) //数据端口	PG11 
#define	DHT11_DQ_IN  PGin(11)  //数据端口	PG11


u8 DHT11_Init(void);		//初始化DHT11
u8 DHT11_Read_Data(u8 *temp,u8 *humi);//读取温湿度
u8 DHT11_Read_Byte(void);	//读出一个字节
u8 DHT11_Read_Bit(void);	//读出一个位
u8 DHT11_Check(void);		//检测是否存在DHT11
void DHT11_Rst(void);		//复位DHT11    


//复位DHT11
void DHT11_Rst(void)	   
{                 
	DHT11_IO_OUT(); 	//SET OUTPUT
    DHT11_DQ_OUT=0; 	//拉低DQ
    delay_ms(20);    	//拉低至少18ms
    DHT11_DQ_OUT=1; 	//DQ=1 
	delay_us(30);     	//主机拉高20~40us
}
//等待DHT11的回应
//返回1:未检测到DHT11的存在
//返回0:存在
u8 DHT11_Check(void) 	   
{   
	u8 retry=0;
	DHT11_IO_IN();//SET INPUT	 
    while (DHT11_DQ_IN&&retry<100)//DHT11会拉低40~80us
	{
		retry++;
		delay_us(1);
	};	 
	if(retry>=100)return 1;
	else retry=0;
    while (!DHT11_DQ_IN&&retry<100)//DHT11拉低后会再次拉高40~80us
	{
		retry++;
		delay_us(1);
	};
	if(retry>=100)return 1;	    
	return 0;
}
//从DHT11读取一个位
//返回值:1/0
u8 DHT11_Read_Bit(void) 			 
{
 	u8 retry=0;
	while(DHT11_DQ_IN&&retry<100)//等待变为低电平
	{
		retry++;
		delay_us(1);
	}
	retry=0;
	while(!DHT11_DQ_IN&&retry<100)//等待变高电平
	{
		retry++;
		delay_us(1);
	}
	delay_us(40);//等待40us
	if(DHT11_DQ_IN)return 1;
	else return 0;		   
}
//从DHT11读取一个字节
//返回值:读到的数据
u8 DHT11_Read_Byte(void)    
{        
    u8 i,dat;
    dat=0;
	for (i=0;i<8;i++) 
	{
   		dat<<=1; 
	    dat|=DHT11_Read_Bit();
    }						    
    return dat;
}
//从DHT11读取一次数据
//temp:温度值(范围:0~50°)
//humi:湿度值(范围:20%~90%)
//返回值:0,正常;1,读取失败
u8 DHT11_Read_Data(u8 *temp,u8 *humi)    
{        
 	u8 buf[5];
	u8 i;
	DHT11_Rst();
	if(DHT11_Check()==0)
	{
		for(i=0;i<5;i++)//读取40位数据
		{
			buf[i]=DHT11_Read_Byte();
		}
		if((buf[0]+buf[1]+buf[2]+buf[3])==buf[4])
		{
			*humi=buf[0];
			*temp=buf[2];
		}
	}else return 1;
	return 0;	    
}
//初始化DHT11的IO口 DQ 同时检测DHT11的存在
//返回1:不存在
//返回0:存在    	 
u8 DHT11_Init(void)
{
	RCC->APB2ENR|=1<<8;    //使能PORTG口时钟 
	GPIOG->CRH&=0XFFFF0FFF;//PORTG.11 推挽输出
	GPIOG->CRH|=0X00003000;
	GPIOG->ODR|=1<<11;      //输出1				    
	DHT11_Rst();
	return DHT11_Check();
}



五、总结

本项目设计一种基于STM32微控制器的智能通信机房空调与新风系统联动装置,以提升通信机房的环境管理效率和自动化水平。该装置能够实时监测机房内的环境温度和湿度,并根据预设阈值自动控制风扇进行散热,从而保持机房内部环境的适宜条件。系统利用合宙Air724UG 4G模块将采集到的数据上传至华为云IOT物联网云平台,支持远程监控及控制功能。通过Windows客户端界面,管理员可以实时查看机房内环境参数,调整设定值,并控制风扇的工作状态。此外,本地操作也得到了考虑,通过简单的按键输入即可完成风扇控制和工作模式的切换。为了方便现场人员查看当前状态,设备还配备了一块0.96寸的OLED显示屏,用于显示环境参数、设置值及运行状态等信息。整个系统由一个5V 2A的稳压电源模块供电,确保了系统的稳定性和可靠性。此项目的实施有助于提高通信机房的智能化管理水平,减少人工干预,降低维护成本。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。