基于树莓派4B+华为云IOT设计的智能家居控制系统【玩转华为云】
一、设计需求
【1】前言
智能家居是指通过现代化的信息技术手段,将家居设备和家庭环境互相连接,实现智能化控制和管理的一种家居生活方式。随着现代科技的不断发展和普及,越来越多的人开始将智能家居系统作为提高生活质量和便捷性的必备装备。
本次设计实现了一个基于树莓派的智能家居系统,可以对家庭环境进行实时监测和控制,提高居家安全性和舒适度。该系统采用了多种传感器和模块,包括温湿度传感器、烟雾传感器、火焰传感器、光敏传感器、雨滴传感器、LED灯光控制模块、继电器控制模块等,实现了对家庭环境的多项监测和控制功能。同时,通过服务器实现了数据的存储和可视化,方便管理员对环境数据进行分析和管理。该系统采用了HTTP和MQTT两种协议进行通信(连接华为云IOT云平台),支持有线和无线组合通信方式,可以满足不同场景下的需求。
使用华为云IOT平台可以提高智能家居系统的可靠性、易用性和可扩展性,同时还能够降低系统的成本和实现快速部署。华为云IOT平台采用了分布式架构和多层安全防护机制,提供了丰富的开发工具和API,支持多种协议和接口,能够满足不同规模和需求的物联网应用。在智能家居系统中,使用华为云IOT平台可以实现设备接入、数据采集、数据存储和可视化等功能,同时还能够方便地与其他系统进行集成,提高系统的整体性能和用户体验。
本文将介绍该系统的整体框架和各个模块的功能,同时也将介绍系统的设计思路和实现过程。同时,本文也将介绍如何搭建华为云物联网服务器以及如何设计、使用手机客户端或电脑端实时查看家庭环境监测数据。希望本文能够为读者提供有关智能家居系统设计的参考和启示,同时也能够促进智能家居技术的发展和应用。
【2】需求总结
本次设计主要以树莓派为主要的硬件开发平台,以Raspbian操作系统为软件开发平台。主板树莓派通过HTTP协议和MQTT协议分别与上位机和下位机通信,搭建的服务器用来储存数据和日志记录。搭配有线和无线组合通信方式设计出一套完整的智能家居系统。 通过对智能家居系统的整体需求分析,利用温湿度传感器、MQ-2烟雾气敏传感器、火焰传感器、光敏传感器以及雨滴模块传感器采集环境中的数据构成家庭环境监控系统,通过传感器采集环境数据并实时上传至手机客户端或者电脑端可以实时查看家庭环境监测数据。最后根据功能需求设计出整个系统的框架。
该设计系统主要包括控制和终端两个部分 。主温湿度检测模块、烟雾检测模块、蜂鸣器报警模块、LED灯光控制模块、继电器控制模块、满足智能家居的日常需求。同时,为了便于用户实现对采集到的数据进行分析和操作,系统需要加入数据存储部分。
(1)树莓派为硬件中心用于控制整个系统的运行。
(2)温度模块:通过通过温湿度传感器和继电器配合实现对家庭空调和加湿器的控制。
(3)烟雾检测模块,利用烟雾检测传感器:同时提示用户加以检查,确保安全(打开窗户、打开喷淋水阀)
(4)继电器控制模模块:主要是为了用户根据实时情况完成对家中电器设备的操作。
(5)蜂鸣器模块:用于当出现异常数据时,报警使用。
(6)数据存储模块:实现数据的传输,将数据传输到服务端,便于管理员分析管理。
(7)服务端:实现数据可视化,显示各个模块的信息。
(8)服务器采用华为云物联网服务器
最终实现的功能:
整个项目分为3个部分: 1. 树莓派硬件端(智能家居硬件端) 2. 华为云物联网服务器(web网页端) 3. Android手机APP(手机端)
运行流程:
(1)树莓派硬件端通过WIFI连接到华为云物联网平台上传采集的传感器数据,在web网页上可以看到设备上传的数据。
(2)手机APP通过华为云物联网平台的接口获取设备上传的数据在页面上实时显示出来。
(3)手机APP点击页面上的控制按钮,可以向华为云服务器发送指令,服务器再将指令转发给树莓派设备,树莓派设备收到数据之后完成对硬件设备的控制。
【3】开发工具的选择
上位机的开发选择Qt框架,编程语言采用C++;Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。Qt能轻松创建具有原生C++性能的连接设备、用户界面(UI)和应用程序。它功能强大且结构紧凑,拥有直观的工具和库。
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是有点不适应界面的。
二、硬件选型
【1】 树莓派开发板
【2】TFT卡-烧写系统使用
【3】0.5米网线-远程登录
【4】MQ2烟雾传感器
MQ2传感器对氨气、硫化物、苯系蒸汽的灵敏度高,对烟雾和其它有害的监测也很理想。这种传感器可检测多种有害气体,是一款适合多种应用的低成本传感器。
MQ-2型烟雾传感器属于二氧化锡半导体气敏材料,属于表面离子式N型半导体。处于200~3000摄氏度时,二氧化锡表面吸附空气中的氧,形成氧的负离子吸附,使半导体中的电子密度减少,从面使其电阻值增加。当与烟雾接触时,如果晶粒间界处的势垒收到烟雾的调至面变化,就会引起表面导电率的变化。利用这一点就可以获得这种烟雾存在的信息烟雾浓度越大导电率越大,输出电阻越低,则输出的模拟信号就越大。
(1)引脚说明:
VCC:电源正极接口,可外接3.3~5v供电电源 GND:电源负极接口,可外接电源负极或地线(GND) DO:数字信号输出接口(0和1),可外接单片机的GPIO AO:模拟信号输出接口,可外接单片的ADC采样通道
(2)硬件连接:
模块引脚 | GPIO |
---|---|
VCC | 3.3V / 5V |
GND | GND |
DO | NC(空) |
AD | PA1 |
用杜邦线把模块的VCC和GND分别与单片机的3.3V(或5V)和GND连接; 把DO与单片机的其中一个GPIO连接; 把AD与单片机的其中一个ADC采样通道连接。
注:传感器通电后,需要先预热约60s后测量的数据才稳定。通电后传感器会出现正常的轻度发热现象,因为内部有电热丝。
(3)烟雾检测
当可燃气体浓度小于指定的阈值时,DO输出高电平,大于指定的阈值时则输出低电平。
(4)阈值调节
模块中蓝色的电位器是用于调节阀值,顺时针旋转,阈值会越大,逆时针越小。
(5)使用AO接口
与DO不同,AO会输出模拟信号,因此需要与单片机的ADC采样通道连接。单片机可以通过此模拟信号来获取可燃气体浓度大小。
【5】DHT11温湿度传感器
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有可靠性与卓越的长期稳定性,成本低、相对湿度和温度测量、快响应、抗干扰能力强、信号传输距离长、数字信号输出、精确校准。传感器包括一个电容式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。可用于暖通空调、除湿器、测试及检测设备、消费品、汽车、自动控制、数据记录器、气象站、家电、湿度调节器、医疗、其他相关湿度检测控制。
特点如下:
1、可以检测周围环境的湿度
2、可以检测周围环境的温度
3、湿度测量范围:20%-95%(0度-50度范围)湿度测量误差:+-5%4、温度测量范围:o度-50度温度测量误差:+-2度
4、工作电压3.3V-5V
5、输出形式数字输出
【6】洞洞板
【7】母对母杜邦线(两排)
作用: 连接模块与单片机。
【8】继电器(2个)
【9】雨滴检测模块
接上5V电源电源灯亮,感应板上没有水滴时,DO输出为高电平,开关指示灯灭;
滴上一滴水,DO输出为低电平,开关指示灯亮;刷掉上面的水滴,又恢复到,输出高电平状态。
AO模拟输出,可以连接单片机的AD口检测滴在上面的雨量大小。
DO TTL数字输出也可以连接单片机检测是否有雨。
【10】火焰检测传感器
【11】光敏电阻
【12】蜂鸣器模块
【13】 LED灯模块
三、部署华为云物联网平台
华为云官网: https://www.huaweicloud.com/
打开官网,搜索物联网,就能快速找到 设备接入IoTDA
。
3.1 物联网平台介绍
华为云物联网平台(IoT 设备接入云服务)提供海量设备的接入和管理能力,将物理设备联接到云,支撑设备数据采集上云和云端下发命令给设备进行远程控制,配合华为云其他产品,帮助我们快速构筑物联网解决方案。
使用物联网平台构建一个完整的物联网解决方案主要包括3部分:物联网平台、业务应用和设备。
物联网平台作为连接业务应用和设备的中间层,屏蔽了各种复杂的设备接口,实现设备的快速接入;同时提供强大的开放能力,支撑行业用户构建各种物联网解决方案。
设备可以通过固网、2G/3G/4G/5G、NB-IoT、Wifi等多种网络接入物联网平台,并使用LWM2M/CoAP、MQTT、HTTPS协议将业务数据上报到平台,平台也可以将控制命令下发给设备。
业务应用通过调用物联网平台提供的API,实现设备数据采集、命令下发、设备管理等业务场景。
3.2 开通物联网服务
地址: https://www.huaweicloud.com/product/iothub.html
进来默认会提示开通标准版,在2023的1月1号年之后没有基础版了。
开通之后,点击总览
,查看接入信息。 我们当前设备准备采用MQTT协议接入华为云平台,这里可以看到MQTT协议的地址和端口号等信息。
总结:
端口号: MQTT (1883)| MQTTS (8883)
接入地址: df8fe4bb66.st1.iotda-device.cn-north-4.myhuaweicloud.com
根据域名地址得到IP地址信息:
Microsoft Windows [版本 10.0.19044.2604]
(c) Microsoft Corporation。保留所有权利。
C:\Users\11266>ping df8fe4bb66.st1.iotda-device.cn-north-4.myhuaweicloud.com
正在 Ping df8fe4bb66.st1.iotda-device.cn-north-4.myhuaweicloud.com [117.78.5.125] 具有 32 字节的数据:
来自 117.78.5.125 的回复: 字节=32 时间=40ms TTL=30
来自 117.78.5.125 的回复: 字节=32 时间=40ms TTL=30
来自 117.78.5.125 的回复: 字节=32 时间=39ms TTL=30
来自 117.78.5.125 的回复: 字节=32 时间=39ms TTL=30
117.78.5.125 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 39ms,最长 = 40ms,平均 = 39ms
MQTT协议接入端口号有两个,1883是非加密端口,8883是证书加密端口,单片机无法加载证书,所以使用1883端口比较合适。 接下来的ESP8266就采用1883端口连接华为云物联网平台。
3.3 创建产品
(1)创建产品
(2)填写产品信息
根据自己产品名字填写,下面的设备类型选择自定义类型。
(3)产品创建成功
(4)添加自定义模型
产品创建完成之后,点击进入产品详情页面,翻到最下面可以看到模型定义。
模型简单来说: 就是存放设备上传到云平台的数据。比如:环境温度、环境湿度、环境光照强度、土壤湿度、扇热风扇、灌溉抽水马达等等,我们都可以单独创建一个模型保存。
当前设备需要与云平台交互的属性如下: 接下来就按照下面的属性创建 华为云平台的模型。
DHT11_T 环境温度
DHT11_H 环境湿度
MQ2 烟雾浓度检测
water 雨滴检测
flame 火焰检测
light 光强检测
LED1 LED1控制
LED2 LED2控制
LED3 LED3控制
先点击自定义模型。
再创建一个服务ID。
接着点击新增属性。
DHT11_T 环境温度
DHT11_H 环境湿度
MQ2 烟雾浓度
water 雨滴检测
flame 火焰检测
light 光强检测
LED灯控制
3盏LED灯。
全部添加完毕之后。
3.4 添加设备
产品是属于上层的抽象模型,接下来在产品模型下添加实际的设备。添加的设备最终需要与真实的设备关联在一起,完成数据交互。
(1)注册设备
(2)根据自己的设备填写
(3)保存设备信息
创建完毕之后,点击保存并关闭,得到创建的设备密匙信息。该信息在后续生成MQTT三元组的时候需要使用。
当前设备的信息如下:
{
"device_id": "64000697352830580e48df07_dev1",
"secret": "12345678"
}
(4) 设备创建完成
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
业务流程:
(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
对于设备而言,一般会订阅平台下发消息给设备 这个主题。
设备想接收平台下发的消息,就需要订阅平台下发消息给设备 的主题,订阅后,平台下发消息给设备,设备就会收到消息。
如果设备想要知道平台下发的消息,需要订阅上面图片里标注的主题。
以当前设备为例,最终订阅主题的格式如下:
$oc/devices/{device_id}/sys/messages/down
最终的格式:
$oc/devices/64000697352830580e48df07_dev1/sys/messages/down
(4)主题发布格式
对于设备来说,主题发布表示向云平台上传数据,将最新的传感器数据,设备状态上传到云平台。
这个操作称为:属性上报。
帮助文档地址:https://support.huaweicloud.com/usermanual-iothub/iot_06_v5_3010.html
根据帮助文档的介绍, 当前设备发布主题,上报属性的格式总结如下:
发布的主题格式:
$oc/devices/{device_id}/sys/properties/report
最终的格式:
$oc/devices/64000697352830580e48df07_dev1/sys/properties/report
发布主题时,需要上传数据,这个数据格式是JSON格式。
上传的JSON数据格式如下:
{
"services": [
{
"service_id": <填服务ID>,
"properties": {
"<填属性名称1>": <填属性值>,
"<填属性名称2>": <填属性值>,
..........
}
}
]
}
根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。
根据这个格式,组合一次上传的属性数据:
{"services": [{"service_id": "stm32","properties":{"DHT11_T":18,"DHT11_H":80,"MQ2":1,"water":1,"flame":1,"light":0,"LED1":0,"LED2":0,"LED3":0}}]}
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
MQTT协议的端口支持1883和8883,它们的区别是:8883 是加密端口更加安全。但是单片机上使用比较困难,所以当前的设备是采用1883端口进连接的。
根据上面的域名和端口号,得到下面的IP地址和端口号信息: 如果设备支持填写域名可以直接填域名,不支持就直接填写IP地址。 (IP地址就是域名解析得到的)
华为云的MQTT服务器地址:117.78.5.125
华为云的MQTT端口号:1883
(2)生成MQTT三元组
华为云提供了一个在线工具,用来生成MQTT鉴权三元组: https://iot-tool.obs-website.cn-north-4.myhuaweicloud.com/
打开这个工具,填入设备的信息(也就是刚才创建完设备之后保存的信息),点击生成,就可以得到MQTT的登录信息了。
下面是打开的页面:
填入设备的信息: (上面两行就是设备创建完成之后保存得到的)
直接得到三元组信息。
得到三元组之后,设备端通过MQTT协议登录鉴权的时候,填入参数即可。
DeviceId 64000697352830580e48df07_dev1
DeviceSecret 12345678
ClientId 64000697352830580e48df07_dev1_0_0_2023030206
Username 64000697352830580e48df07_dev1
Password a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449
3.7 模拟设备登录测试
经过上面的步骤介绍,已经创建了产品,设备,数据模型,得到MQTT登录信息。 接下来就用MQTT客户端软件模拟真实的设备来登录平台。测试与服务器通信是否正常。
(1)填入登录信息
打开MQTT客户端软件,对号填入相关信息(就是上面的文本介绍)。然后,点击登录,订阅主题,发布主题。
(2)打开网页查看
完成上面的操作之后,打开华为云网页后台,可以看到设备已经在线了。
点击详情页面,可以看到上传的数据:
如果想查看历史数据,点击这里:
到此,云平台的部署已经完成,设备已经可以正常上传数据了。
(3)MQTT登录测试参数总结
IP地址:117.78.5.125
端口号:1883
DeviceId 64000697352830580e48df07_dev1
DeviceSecret 12345678
ClientId 64000697352830580e48df07_dev1_0_0_2023030206
Username 64000697352830580e48df07_dev1
Password a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449
订阅主题:$oc/devices/64000697352830580e48df07_dev1/sys/messages/down
发布主题:$oc/devices/64000697352830580e48df07_dev1/sys/properties/report
发布的消息:{"services": [{"service_id": "stm32","properties":{"DHT11_T":18,"DHT11_H":80,"MQ2":1,"water":1,"flame":1,"light":0,"LED1":0,"LED2":0,"LED3":0}}]}
四、上位机开发
为了方便查看设备上传的数据,对设备进行远程控制,接下来利用Qt开发一款Android和windows系统的上位机。
使用华为云平台提供的API接口获取设备上传的数据,也可以给设备下发指令,控制设备。
为了方便查看设备上传的数据,对设备进行远程控制,接下来利用Qt开发一款Android和windows系统的上位机。
使用华为云平台提供的API接口获取设备上传的数据,也可以给设备下发指令,控制设备。
4.1 Qt开发环境安装
Qt的中文官网: https://www.qt.io/zh-cn/
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
编译器即可,其他的不管默认就行,直接点击下一步继续安装。
说明: 我这里只是介绍PC端的环境搭建(这个比较简单)。 Android的开发环境比较麻烦,可以去我的博客里看详细文章。
选择MinGW 32-bit 编译器:
4.2 创建IAM账户
创建一个IAM账户,因为接下来开发上位机,需要使用云平台的API接口,这些接口都需要token进行鉴权。简单来说,就是身份的认证。 调用接口获取Token时,就需要填写IAM账号信息。所以,接下来演示一下过程。
地址: https://console.huaweicloud.com/iam/?region=cn-north-4#/iam/users
获取Token时,除了AIM账号外,还需要项目凭证:
22ef548bdacc41a9ade0b3c83bcc3b21
鼠标放在左上角头像上,在下拉菜单里选择统一身份认证
。
点击左上角创建用户
。
创建成功:
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
在线调试接口,可以请求影子接口,了解请求,与返回的数据格式。
设备影子接口返回的数据如下:
{
"device_id": "64000697352830580e48df07_dev1",
"shadow": [
{
"service_id": "stm32",
"desired": {
"properties": null,
"event_time": null
},
"reported": {
"properties": {
"DHT11_T": 18,
"DHT11_H": 85,
"MQ2": 1,
"water": 1,
"flame": 1,
"light": 0,
"LED1": 0,
"LED2": 0,
"LED3": 0
},
"event_time": "20230302T063135Z"
},
"version": 4
}
]
}
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客户端先登录设备 (这是同步命令,必须在线才能调试)
【2】点击调试
{"services":{"LED":1}}
【4】可以看到,MQTT客户端软件上已经收到了服务器下发的消息
由于是同步命令,服务器必须要收到设备的响应才能顺利完成一个流程,设备响应了服务器才能确定数据下发成功。
MQTT设备端如何响应呢?
设备响应格式说明:https://support.huaweicloud.com/api-iothub/iot_06_v5_3008.html
下面进行实操:
当服务器通过在线调试,发送指令下来之后,客户端将请求ID复制下来,添加到发布主题的格式里,再回复回去,服务器收到了响应,一次属性修改就完美完成了。
就是成功的状态:
下面是请求的总结: (响应服务器的修改设备属性请求)
上报主题的格式:$oc/devices/{device_id}/sys/properties/set/response/request_id=
$oc/devices/64000697352830580e48df07_dev1/sys/properties/set/response/request_id=
响应的数据:
{"result_code": 0,"result_desc": "success"}
4.5 设计上位机
前面2讲解了需要用的API接口,接下来就使用Qt设计上位机,设计界面,完成整体上位机的逻辑设计。
【1】新建Qt工程
选择工程路径,放在英文路径下。
创建完毕。
新建Android的模板:
【2】打开现有的Qt工程
如果想打开已经设计好的工程,可以在保存上位机源码的目录下,找到工程文件xxx.pro,双击打开工程,详细操作看下面截图红框。
【3】设计UI界面
打开UI设计师界面:
这是默认的界面:
下面是设计好的界面:
【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; //返回值读取的值
}
【5】通信交互代码
//解析反馈结果
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()<<"开始解析数据....";
int DHT11_T;// 环境温度
int DHT11_H;// 环境湿度
int MQ2;// 烟雾浓度检测
int water;// 雨滴检测
int flame;// 火焰检测
int light;// 光强检测
int LED1;// LED1控制
int LED2;// LED2控制
int LED3;// LED3控制
DHT11_T=properties.take("DHT11_T").toInt();
DHT11_H=properties.take("DHT11_H").toInt();
MQ2=properties.take("MQ2").toInt();
water=properties.take("water").toInt();
flame=properties.take("flame").toInt();
light=properties.take("light").toInt();
LED1=properties.take("LED1").toInt();
LED2=properties.take("LED2").toInt();
LED3=properties.take("LED3").toInt();
//环境温度
ui->DHT11_T->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->DHT11_T->setText(QString("%1℃").arg(DHT11_T));
//环境湿度
ui->DHT11_H->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->DHT11_H->setText(QString("%1%").arg(DHT11_H));
//烟雾报警
if(MQ2>0)
{
ui->MQ2->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
//样式表
QString qss=QString("QLabel{"
"border-radius:0px;"
"color:#FFFFFF;"
"font: 18pt "Arial";"
"}");
ui->MQ2->setStyleSheet(qss);
ui->MQ2->setText(QString("正常"));
}
//正常
else
{
ui->MQ2->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
//样式表
QString qss=QString("QLabel{"
"border-radius:0px;"
"color:#FF0000;"
"font: 18pt "Arial";"
"}");
ui->MQ2->setStyleSheet(qss);
ui->MQ2->setText(QString("烟雾警告"));
}
//火焰检测报警
if(flame>0)
{
ui->flame->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
//样式表
QString qss=QString("QLabel{"
"border-radius:0px;"
"color:#FFFFFF;"
"font: 18pt "Arial";"
"}");
ui->flame->setStyleSheet(qss);
ui->flame->setText(QString("正常"));
}
//正常
else
{
ui->flame->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
//样式表
QString qss=QString("QLabel{"
"border-radius:0px;"
"color:#FF0000;"
"font: 18pt "Arial";"
"}");
ui->flame->setStyleSheet(qss);
ui->flame->setText(QString("火警警告"));
}
//雨滴检测报警
if(water>0)
{
ui->water->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->water->setText(QString("天晴"));
}
//正常
else
{
ui->water->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->water->setText(QString("下雨"));
}
//光强检测报警
if(light>0)
{
ui->light->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->light->setText(QString("强"));
}
//正常
else
{
ui->light->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->light->setText(QString("弱"));
}
//设置灯光开关的状态
ui->LED1->setChecked(LED1);
ui->LED2->setChecked(LED2);
ui->LED3->setChecked(LED3);
}
}
}
}
}
}
return;
}
}
【8】设置应用图标和应用名称
【9】编译工程代码
编译windows版本:
编译Android版本:
点击绿色小三角形之后,会弹出选择手机设备列表。需要提前将Android手机通过USB连接电脑的USB口,并且打开Android手机的USB调试选项,接下来选择设备,就可以编译安装到Android手机上。
【10】更新时间日期
//label_time
QDateTime current_date_time =QDateTime::currentDateTime();
QString current_date =current_date_time.toString("yyyy/MM/dd hh:mm:ss");
ui->label_time->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
ui->label_time->setText(current_date);
【11】 运行效果
下面是windows电脑的运行效果:
下面是Android手机的运行效果:
【12】源码目录
安装好Qt环境之后,双击打开即可。
注意: Qt工程需要放在英文路径下打开。
【13】可执行文件
如果不想编译源码,在资料目录下,打开windows上位机可执行文件,双击下面截图红框的程序即可。
也可以直接安装Android的安装包,在Android手机上操作。
【14】软件使用介绍
第一次打开软件需要点击更新Token按钮,如果在Android手机上每次启动都需要点击一次,暂时没有保存配置文件。
五、树莓派4B环境搭建
【1】硬件环境介绍
当前购买的树莓派开发板是4B型号,2GB内存,就买了一个主板,不带其他任何配件。
树莓派是什么?Raspberry Pi(中文名为“树莓派”,简写为RPi,或者RasPi/RPi)是为学生计算机编程教育而设计,只有信用卡大小的卡片式电脑,其系统基于Linux。
【2】资料下载
第一步,先将树莓派4B需要使用的资料下载下来。
4B资料 链接:https://pan.baidu.com/s/1GoMgDz1tUWLxHR6-z3LqCw 提取码:vfpe
【3】准备需要的配件
(1)准备一张至少32G的TFT卡,用来烧写系统。
(2)准备一个读卡器,方便插入TFT卡,好方便插入到电脑上拷贝系统
(3)树莓派主板一个
(4)一根网线(方便插路由器上与树莓派连接)
(5)一根type-C的电源线。用自己Android手机的数据线就行,拿手机充电器供电。
【4】准备烧写系统
(1)安装镜像烧写工具
(2)格式化SD卡
将TFT卡通过读卡器插入到电脑上,将TFT卡格式化。
(3)烧写系统
接下来准备烧写的系统是这一个系统: 将系统解压出来。
然后打开刚才安装好的镜像烧写工具,在软件中选择需要安装的 img(镜像)文件,“Device”下选择SD的盘符,然后选择“Write”,然后就开始安装系统了,根据你的SD速度,安装过程有快有慢。
注意:从网盘下载下来的镜像如果没有解压就先解压,释放出img文件。
下面是烧写的流程:
点击YES
,开始烧写。
烧写过程中:
安装结束后会弹出完成对话框,说明安装就完成了,如果不成功,需要关闭防火墙一类的软件,重新插入SD进行安装。
需要注意的是,安装完,windows系统下看到SD只有74MB了,这是正常现象,因为linux下的磁盘分区win下是看不到的。 烧录成功后windows系统可能会因为无法识别分区而提示格式化分区,此时千万不要格式化!不要格式化!不要格式化! 点击取消,然后弹出内存卡,插入到树莓派上。
至此,树莓派烧写成功。
【5】启动系统
(1)树莓派供电
由于我买的树莓派开发板不带电源线,就采用Android手机的充电线供电。 使用Type-C供电时,要求电源头的参数要求,电压是5V,电流是3A。
我的充电器是小米的120W有线快充,刚好满足要求。
(2)启动树莓派(以Type-C供电示例)
烧写完后把MicroSD卡直接插入树莓派的MicroSD卡插槽,如果有显示器就连接显示器,有DHMI线机也可以连接外接的显示器,有鼠标、键盘都可以插上去,就可以进入树莓派系统了。
但是,我这块板子就一个主板,什么都没有。就拿网线将树莓派的网口与路由器连接。
上电之后,开发板的指示灯会闪烁,说明已经启动。
(3)查看开发板的IP地址
现在板子没屏幕,想要连接板子,只能通过SSH远程登录的方式,当前烧写的这个系统默认开机就启动了SSH,所以只要知道开发板的IP地址就可以远程登录进去。
如何知道树莓派板子的IP地址? 方法很多,最简单是直接登录路由器的后台界面查看连接进入的设备。
我使用的小米路由器,登录后台,看到了树莓派的IP地址。
(4)SSH方式登录开发板
当前烧写系统的登录账号和密码如下:
账号:pi 密码:yahboom
打开SSH远程登录工具:PuTTY_0.67.0.0.exe
。
输入IP地址和端口号,点击open。
然后输入账号和密码。
输入用户名 pi
按下回车,然后再输入密码 yahboom
。 注意:Linux下为了保护隐私,输入密码是不可见的,你只需要正常输入,按下回车键确定 即可。
正常情况下,就登录成功了。
接下来看看联网情况。 ping一下百度测试互联网是否畅通,因为接下来要在线安装软件包。
ping www.baidu.com
可以看到网络没有问题。
提示: 按下 Ctrl + C
可以终止命令行。 这算是Linux基础。
【6】windows远程登录桌面
为了方便图形化方式开发,可以使用windows系统通过远程桌面登录树莓派,就可以看到界面了,不过需要先安装工具。
(1)安装xdrp
在树莓派的命令行终端输入命令:
sudo apt-get install xrdp
按下回车之后,会弹出确认窗口。输入 y
之后,按下回车,继续安装。
安装完毕:
(2)打开windows远程桌面
在windows电脑上打开运行命令的窗口,输入mstsc
来打开远程桌面。
打开远程桌面的窗口:
(3)连接树莓派远程桌面
打开远程桌面后,输入树莓派开发板的IP地址,点击连接。
如果弹出窗口,就选择是
。
接下来就进入到树莓派开发板的远程桌面的登录窗口了。
接下来输入面账号和密码。
账号:pi 密码:yahboom
输入后点击OK
按钮登录。
正常情况下,就顺利的进入树莓派的桌面了。接下来就可以进行远程桌面开发了。
【7】扩展树莓派SD卡可用空间
树莓派系统默认启动时,树莓派默认没有把整个存储空间拓展到整张卡中,如果需要使用整个SD卡,这时候可以通过人为的把存储空间拓展到整张卡上。
(1)查看内存使用情况
打开命令行终端,输入df -h
命令。
(2)扩展内存
<1> 打开树莓派命令行终端输入:
pi@raspberrypi:~ $ sudo raspi-config
<2> 在弹出的命令行里选择Advanced Options
<3> 选择第一个选项。
<4> 点击确定
<5> 点击右边的Finish
按钮保存退出。
确定之后,关闭界面,系统会自动重启,重启之后,使用df命令查看是否扩展成功(我这里插的是32G的SD卡)。
可以看到,我的系统已经扩展成功了,目前可以内存空间是19G。
【8】树莓派连接WIFI
(1)配置需要连接的WIFI
点击右上角的数据连接图标,打开WIFI列表,点击想要的WIFI进行连接。
输入密码:
连接成功后的效果:
(2)通过WIFI的IP地址登录远程桌面
在路由器的后台可以看到,目前树莓派连入了两个IP地址。接下来把网线拔掉,使用WIFI无线也可以直接连接无线桌面,这样就不用插网线了。
账号和密码:
账号:pi 密码:yahboom
六、硬件开发:树莓派点亮LED
【1】树莓派4B的引脚
GPIO是标准引脚,可以用来打开和关闭设备。例如,一个LED、一个蜂鸣器。
I2C(Inter-Integrated Circuit)引脚连接并与支持该协议(I2C协议)的硬件模块对话。
SPI(串行外设接口总线)引脚可用于连接和对话SPI设备
UART 通用异步接收/发送器)是用于与其他设备通信的串行引脚。
GND是用来接地的引脚。
【2】安装 WiringPi库
这里使用了 WiringPi 库,如果已经安装过可以跳过这一步。由于我当前使用的系统镜像已经默认安装了,所以就不再安装。
安装方式如下:
sudo apt-get update
#如果之前没有安装过 git,执行下面的命令安装一下
sudo apt-get install git-core
#下载 wiringPi
git clone https://github.com/WiringPi/WiringPi
#编译 wiringPi
cd WiringPi
sudo ./build
【3】点亮LED灯(python)
将LED灯接在GPIO18口上。
打开编辑器,编写代码:
打开编辑器后,写好代码,点击运行: 就可以看到LED灯在闪烁了。
示例代码:
#!/usr/bin/python
#-*-coding: utf-8 -*-
import RPi.GPIO as GPIO # 引入GPIO模块
from time import sleep # 引入time模块
GPIO.setmode(GPIO.BCM) # 使用BCM编号方式
GPIO.setup(18,GPIO.OUT) # 将GPIO18设置为输出模式
while True: # 无限循环
GPIO.output(18,GPIO.HIGH) # 将GPIO18设置为高电平,点亮LED
sleep(1) # 等待1秒钟
GPIO.output(18,GPIO.LOW) # 将GPIO18设置为低电平,熄灭LED
sleep(1) # 等待0.5秒钟
input() # 按下任意键退出
GPIO.cleanup() # 清理释放GPIO资源,将GPIO复位
【4】点亮LED灯(C语言)
使用C语言代码对树莓派的GPIO口操作时,需要先初始化。下面是2种初始化函数,对应不同的GPIO编码方式。
int wiringPiSetup (void) 返回:执行状态,-1表示失败 当使用这个函数初始化树莓派引脚时,程序使用的是wiringPi 引脚编号表。引脚的编号为 0~16
int wiringPiSetupGpio (void) 返回执行状态,-1表示失败 当使用这个函数初始化树莓派引脚时,程序中使用的是BCM GPIO 引脚编号表。
打开编辑器,编写C语言代码:
下面是C语言代码电路LED灯的代码:
#include <stdio.h>
#include <wiringPi.h>
#include <stdlib.h>
#include <string.h>
/*树莓派控制继电器高低电平亮灯*/
#define LED1 23
int main()
{
wiringPiSetupGpio();
pinMode(LED1,OUTPUT);
while(1)
{
digitalWrite(LED1,HIGH);
sleep(1);
digitalWrite(LED1,LOW);
sleep(1);
}
return 0;
}
写好代码后进行编译:
pi@raspberrypi:~/word $ gcc led.c -lwiringPi
运行代码:
pi@raspberrypi:~/word $ ./a.out
【5】GPIO口常用的控制函数(C语言)
1.void pinMode (int pin, int mode) ;
这个函数式设置pin脚的输入和输出模式以及PWM的输入和输出模式。在wiringPi中只有 pin 1 (BCM_GPIO 18)是支持PWM的输出的。
2.void digitalWrite (int pin, int value) ;
这个函数式用来设置pin脚的高低电平的,当我们写HIGH or LOW (1 or 0)的时候pin脚的mode必须为输出模式。
3.void digitalWriteByte (int value) ;
这个函数可以将8位字节写给8个GPIO pin脚,这是设置8个pin脚值的最快的方式。
4.void pwmWrite (int pin, int value) ;
写入的值必须是0-1024。输出一个值到PWM寄存器,控制PWM输出。pin只能是wiringPi 引脚编号下的1脚(BCM下的18脚)
5.int digitalRead (int pin) ;
这个函数是读取GPIO的pin脚的电平高低然后返回读取的值。
6.void pullUpDnControl (int pin, int pud) ;
这个函数是设置GPIO的pin脚是否接上拉电阻和下拉电阻的。函数的参数pud必须设置,当设置为PUD_OFF时表示没有上拉电阻和下拉电阻,当设置为PUD_DOWN 时为下拉电阻,设置为PUD_UP是为上拉电阻。
7. void analogWrite(int pin, int value)
pin:引脚
value:输出的模拟量
模拟量输出 树莓派的引脚本身是不支持AD转换的,也就是不能使用模拟量的API,
需要增加另外的模块
8. int analogRead (int pin)
pin:引脚
返回:引脚上读取的模拟量
模拟量输入 树莓派的引脚本身是不支持AD转换的,也就是不能使用模拟量的API,需要增加另外的模块
PWM的控制:
1.pwmSetMode (int mode) ;
这个函数是设置PWM的占空比模式一般为50%占空比和占空比可调模式。树莓派默认是50%占空比模式,我们可以通过PWM_MODE_BAL 和PWM_MODE_MS这两个参数来设置。
2.pwmSetRange (unsigned int range) ;
这个函数是设置PWM寄存器写入的范围,一般默认为1024.
3.pwmSetClock (int divisor) ;
这个函数的设置PWM时钟的分频因子。
时间控制函数:
1.void delay (unsigned int howLong)
这个是毫秒级的延时函数。
2.void delayMicroseconds (unsigned int howLong)
微秒级的延时函数
3. unsigned int millis (void)
这个函数返回 一个 从你的程序执行 wiringPiSetup 初始化函数(或者wiringPiSetupGpio ) 到 当前时间 经过的 毫秒数。
返回类型是unsigned int,最大可记录 大约49天的毫秒时长。
4. unsigned int micros (void)
这个函数返回 一个 从你的程序执行 wiringPiSetup 初始化函数(或者wiringPiSetupGpio ) 到 当前时间 经过的 微秒数。
返回类型是unsigned int,最大可记录 大约71分钟的时长。
关于中断的函数:
1.int waitForInterrupt (int pin, int timeOut) ;
这一个等待事件中断函数,timeout参数是毫秒级别的参数,当为-1时代表永远等待中断状态。如果发生错误返回值是-1,0表示超时,1表示成功中断。调用这个函数之前我们需要对GPIO进行初始化。
例如我们要设置GPIO 0为等待下降沿中断:
我们需要在运行程序之前先终端运行 gpio edge 0 falling
2.int wiringPiISR (int pin, int edgeType, void (*function)(void)) ;
这个函数是利用一个函数作为参数来获取在特定的GPIO pin脚的中断。edge_Type参数可以设定为 INT_EDGE_FALLING, INT_EDGE_RISING, INT_EDGE_BOTH or INT_EDGE_SETUP.
当中断发生的时候function将被调用,调用function之前会先清除相应的标志位,这就使得随后的中断不会被影响。
七、硬件开发:智能家居控制系统
【1】硬件连线
下面是传感器与树莓派之间的连线。
MQ2烟雾传感器:GPIO4
DHT11温湿度传感器:GPIO17
火焰检测传感器:GPIO18
光敏电阻:GPIO27
蜂鸣器:GPIO22
LED灯1:GPIO23
LED灯2:GPIO24
LED灯3:GPIO25
雨滴检测传感器:GPIO5
继电器模块:GPIO6
【2】DHT11温湿度读取代码
下面采用C语言方式读取DHT11温湿度传感器的数:
#include <wiringPi.h>
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char uint8;
typedef unsigned int uint16;
typedef unsigned long uint32;
#define HIGH_TIME 32
int pinNumber = 17;
uint32 databuf;
uint8 readSensorData(void)
{
uint8 crc;
uint8 i;
pinMode(pinNumber, OUTPUT); // set mode to output
digitalWrite(pinNumber, 0); // output a high level
delay(25);
digitalWrite(pinNumber, 1); // output a low level
pinMode(pinNumber, INPUT); // set mode to input
pullUpDnControl(pinNumber, PUD_UP);
delayMicroseconds(27);
if (digitalRead(pinNumber) == 0) //SENSOR ANS
{
while (!digitalRead(pinNumber))
; //wait to high
for (i = 0; i < 32; i++)
{
while (digitalRead(pinNumber))
; //data clock start
while (!digitalRead(pinNumber))
; //data start
delayMicroseconds(HIGH_TIME);
databuf *= 2;
if (digitalRead(pinNumber) == 1) //1
{
databuf++;
}
}
for (i = 0; i < 8; i++)
{
while (digitalRead(pinNumber))
; //data clock start
while (!digitalRead(pinNumber))
; //data start
delayMicroseconds(HIGH_TIME);
crc *= 2;
if (digitalRead(pinNumber) == 1) //1
{
crc++;
}
}
return 1;
}
else
{
return 0;
}
}
int main(void)
{
printf("PIN:%d\n", pinNumber);
if (-1 == wiringPiSetupGpio()) {
printf("Setup wiringPi failed!");
return 1;
}
pinMode(pinNumber, OUTPUT); // set mode to output
digitalWrite(pinNumber, 1); // output a high level
printf("Starting...\n");
while (1)
{
pinMode(pinNumber, OUTPUT); // set mode to output
digitalWrite(pinNumber, 1); // output a high level
delay(3000);
if (readSensorData())
{
printf("Sensor data read ok!\n");
printf("RH:%d.%d\n", (databuf >> 24) & 0xff, (databuf >> 16) & 0xff);
printf("TMP:%d.%d\n", (databuf >> 8) & 0xff, databuf & 0xff);
databuf = 0;
}
else
{
printf("Sensor dosent ans!\n");
databuf = 0;
}
}
return 0;
}
编译代码:
gcc -Wall -o dht11 dht11.c -lwiringPi -o app
运行代码:
pi@raspberrypi:~/word $ ./dht11
PIN:17
Starting...
Sensor dosent ans!
Sensor data read ok!
RH:57.8
TMP:18.7
【3】核心代码
通过MQTT协议连接华为云物联网平台,完成数据的上传和远程命令接收,控制模块完成。
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <poll.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <signal.h>
#include "mqtt.h"
#include "main.h"
#include <netdb.h>
#include <stdio.h>
#include <wiringPi.h>
#include <stdlib.h>
#include <string.h>
#define DeviceName "Smart_home"//设备名
#define ProductID "LA57WTHWL6"//产品ID
#define DeviceSceret "9JHiCQQcP9nuZlVDuQ2ZnQ=="//设备秘钥
//服务器IP
#define SERVER_IP "LA57WTHWL6.iotcloud.tencentdevices.com"
#define SERVER_PORT 1883 //端口号
//MQTT三元组
#define ClientID "64000697352830580e48df07_dev1_0_0_2023030206"
#define Username "64000697352830580e48df07_dev1"
#define Password "a695af9883c5d0e3817bc6971beeecadf8c7c595677c461b1fe75882ed2bf449"//密文
//订阅主题:
#define SET_TOPIC "$oc/devices/64000697352830580e48df07_dev1/sys/messages/down"//订阅
//发布主题:
#define POST_TOPIC "$oc/devices/64000697352830580e48df07_dev1/sys/properties/report"//发布
char mqtt_message[1024*1024];//上报数据缓存区
char request_id[100];
char mqtt_cmd_message[100];
char mqtt_cmd_data[100];
int sockfd;
/*获取平台下发数据*/
void *pth_work_func(void *arg)
{
char buff[1024];
int size=0;
int i=0;
while(1)
{
size=Client_GetData(buff);
printf("size=%d\r\n",size);
if(size<0)break;
for(i=0;i<size;i++)
{
printf("%#x ",buff[i]);
}
buff[size]='\0';
if(size>5)
{
printf("%s\r\n",buff+5);
if(strstr((char*)&buff[5],"properties/set/request_id"))
{
char *p=NULL;
p=strstr((char*)&buff[5],"request_id");
if(p)
{
//解析数据
//$oc/devices/6210e8acde9933029be8facf_dev1/sys/properties/get/request_id=5f359b5c-542f-460e-9f51-85e82150ff4a{"service_id":"gps"}
strncpy(request_id,p,47);
}
//上报数据
sprintf(mqtt_cmd_message,"{"result_code": 0,"result_desc": "success"}");
sprintf(mqtt_cmd_data,"$oc/devices/64000697352830580e48df07_dev1/sys/properties/set/response/%s",
request_id);
MQTT_PublishData(mqtt_cmd_data,mqtt_cmd_message,0);
printf("应答-发布主题:%s\r\n",mqtt_cmd_data);
printf("应答-发布数据:%s\r\n",mqtt_cmd_message);
}
if(strstr((char*)&buff[5],""LED1":1"))
{
digitalWrite(23,HIGH); //控制GPIO口输出高电平
}
if(strstr((char*)&buff[5],""LED1":0"))
{
digitalWrite(23,LOW); //控制GPIO口输出低电平
}
if(strstr((char*)&buff[5],""LED2":1"))
{
digitalWrite(24,HIGH); //控制GPIO口输出高电平
}
if(strstr((char*)&buff[5],""LED2":0"))
{
digitalWrite(24,LOW); //控制GPIO口输出低电平
}
if(strstr((char*)&buff[5],""LED3":1"))
{
digitalWrite(25,HIGH); //控制GPIO口输出高电平
}
if(strstr((char*)&buff[5],""LED3":0"))
{
digitalWrite(25,LOW); //控制GPIO口输出低电平
}
}
printf("\r\n");
}
}
/*信号处理函数*/
void signal_func(int sig)
{
//printf("捕获的信号:%d\n",sig);
if(sig==SIGALRM)
{
MQTT_SentHeart();//心跳包
alarm(5);
}
}
typedef unsigned char uint8;
typedef unsigned int uint16;
typedef unsigned long uint32;
#define HIGH_TIME 32
int pinNumber = 17;
uint32 databuf;
uint8 readSensorData(void)
{
uint8 crc;
uint8 i;
pinMode(pinNumber, OUTPUT); // set mode to output
digitalWrite(pinNumber, 0); // output a high level
delay(25);
digitalWrite(pinNumber, 1); // output a low level
pinMode(pinNumber, INPUT); // set mode to input
pullUpDnControl(pinNumber, PUD_UP);
delayMicroseconds(27);
if (digitalRead(pinNumber) == 0) //SENSOR ANS
{
while (!digitalRead(pinNumber))
; //wait to high
for (i = 0; i < 32; i++)
{
while (digitalRead(pinNumber))
; //data clock start
while (!digitalRead(pinNumber))
; //data start
delayMicroseconds(HIGH_TIME);
databuf *= 2;
if (digitalRead(pinNumber) == 1) //1
{
databuf++;
}
}
for (i = 0; i < 8; i++)
{
while (digitalRead(pinNumber))
; //data clock start
while (!digitalRead(pinNumber))
; //data start
delayMicroseconds(HIGH_TIME);
crc *= 2;
if (digitalRead(pinNumber) == 1) //1
{
crc++;
}
}
return 1;
}
else
{
return 0;
}
}
unsigned int DHT11_T;// 环境温度
unsigned int DHT11_H;// 环境湿度
int MQ2;// 烟雾浓度检测
int water;// 雨滴检测
int flame;// 火焰检测
int light;// 光强检测
int LED1;// LED1控制
int LED2;// LED2控制
int LED3;// LED3控制
/*
硬件连线:
MQ2烟雾传感器:GPIO4
DHT11温湿度传感器:GPIO17
火焰检测传感器:GPIO18
光敏电阻:GPIO27
蜂鸣器:GPIO22
LED灯1:GPIO23
LED灯2:GPIO24
LED灯3:GPIO25
雨滴检测传感器:GPIO5
继电器模块:GPIO6
*/
int main()
{
//初始化GPIO口
wiringPiSetupGpio(); //BCM编码格式
//配置GPIO口的模式
//输出模式
pinMode(23,OUTPUT);
pinMode(24,OUTPUT);
pinMode(25,OUTPUT);
pinMode(6,OUTPUT);
pinMode(22,OUTPUT);
//输入模式
pinMode(4,INPUT);
pinMode(18,INPUT);
pinMode(27,INPUT);
pinMode(5,INPUT);
//DHT11温湿度初始化
pinMode(pinNumber, OUTPUT); // set mode to output
digitalWrite(pinNumber, 1); // output a high level
int stat;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
printf("网络套接字打开失败\n");
return 0;
}
signal(SIGPIPE,SIG_IGN);/*忽略SIGPIPE信号*/
signal(SIGALRM,signal_func);/*闹钟信号*/
/*域名解析*/
struct hostent *hostent;
while(1)
{
hostent=gethostbyname(SERVER_IP);
if(hostent==NULL)
{
printf("域名解析失败\n");
sleep(1);
}
else break;
}
printf("主机名:%s\n",hostent->h_name);
printf("协议类型:%s\n",(hostent->h_addrtype == AF_INET)?"AF_INET":"AF_INET6");
printf("IP地址长度:%d\n",hostent->h_length);
char *ip;
for(int i=0;hostent->h_addr_list[i];i++)
{
ip=inet_ntoa(*(struct in_addr *)hostent->h_addr_list[i]);
printf("ip=%s\n",ip);
}
/*连接服务器*/
struct sockaddr_in addr;
addr.sin_family=AF_INET;//IPV4
addr.sin_port=htons(SERVER_PORT);/*端口号*/
addr.sin_addr.s_addr=inet_addr("117.78.5.125");//inet_addr(ip);//服务器IP
if(connect(sockfd, (struct sockaddr *)&addr,sizeof(struct sockaddr_in))==0)
{
printf("server connect ok\n");
MQTT_Init();
while(1)
{
/*登录服务器*/
if(MQTT_Connect(ClientID,Username,Password)==0)
{
break;
}
sleep(1);
printf("server connect ....\n");
}
printf("MQTT_Connect OK\r\n");
//订阅物联网平台数据
stat=MQTT_SubscribeTopic(SET_TOPIC,1,1);
if(stat)
{
close(sockfd);
printf("MQTT_SubscribeTopic ERROR\r\n");
exit(0);
}
printf("MQTT_SubscribeTopic ok\r\n");
/*创建线程*/
pthread_t id;
pthread_create(&id, NULL,pth_work_func,NULL);
pthread_detach(id);//设置分离属性
//发送心跳包
// alarm(5);//闹钟函数,时间到达会产生SIGALRM信号
while(1)
{
//读取DHT11温湿度数据
pinMode(pinNumber, OUTPUT); // set mode to output
digitalWrite(pinNumber, 1); // output a high level
delay(3000);
if (readSensorData())
{
printf("DHT11 Sensor data read ok!\n");
printf("RH:%d.%d\n", (databuf >> 24) & 0xff, (databuf >> 16) & 0xff);
printf("TMP:%d.%d\n", (databuf >> 8) & 0xff, databuf & 0xff);
//温度整数部分
DHT11_T=((databuf >> 24) & 0xff);
printf("DHT11_T:%d\r\n",DHT11_T);
//湿度整数部分
DHT11_H=((databuf >> 8) & 0xff);
printf("DHT11_H:%d\r\n",DHT11_H);
databuf = 0;
}
else
{
printf("Sensor dosent ans!\n");
databuf = 0;
}
//读取MQ2烟雾传感器状态
MQ2=digitalRead (4); //读取GPIO口电平状态
printf("MQ2:%d\r\n",MQ2);
//火警报警
if(MQ2==0)
{
digitalWrite(22,HIGH); //蜂鸣器响
}
else
{
digitalWrite(22,LOW); //蜂鸣器关
}
//读取火焰传感器状态
flame=digitalRead (18); //读取GPIO口电平状态
printf("flame:%d\r\n",flame);
//读取光敏传感器状态
light=digitalRead (27); //读取GPIO口电平状态
printf("light:%d\r\n",light);
//读取雨滴检测传感器状态
water=digitalRead (5); //读取GPIO口电平状态
printf("water:%d\r\n",water);
//下雨关窗
if(water==0)
{
digitalWrite(6,HIGH); //继电器开
}
else
{
digitalWrite(6,LOW); //蜂鸣器关
}
//读取LED1状态
LED1=digitalRead (23); //读取GPIO口电平状态
printf("LED1:%d\r\n",LED1);
//读取LED2状态
LED2=digitalRead (24); //读取GPIO口电平状态
printf("LED2:%d\r\n",LED2);
//读取LED3状态
LED3=digitalRead (25); //读取GPIO口电平状态
printf("LED3:%d\r\n",LED3);
//组合传感器状态数据
sprintf(mqtt_message,"{"services": [{"service_id": "stm32","properties":{"DHT11_T":%d,"DHT11_H":%d,"MQ2":%d,"water":%d,"flame":%d,"light":%d,"LED1":%d,"LED2":%d,"LED3":%d}}]}",DHT11_T,DHT11_H,MQ2,water,flame,light,LED1,LED2,LED3);//温度
//上报数据
MQTT_PublishData(POST_TOPIC,mqtt_message,0);
printf("MQTT_PublishData....\r\n");
sleep(2);
}
}
}
/*发送数据到服务器*/
int Client_SendData(u8 *buff,u32 len)
{
int cnt=0;
cnt=write(sockfd,buff,len);
if(cnt<0)return -1;
return 0;
}
/*获取服务器下发数据*/
int Client_GetData(u8 *buff)
{
int len=1024;
int size=0;
int stat=0;
struct pollfd fds={sockfd,POLLIN};
stat=poll(&fds, 1,-1);
size=read(sockfd,buff,len);
if(stat>0&&size<=0)
{
printf("服务器已经断开...\r\n");
}
return size;
}
- 点赞
- 收藏
- 关注作者
评论(0)