基于华为云IOT平台实现多节点温度采集(STM32+NBIOT)【华为云IoT端边云体验】

举报
DS小龙哥 发表于 2022/10/24 17:18:23 2022/10/24
【摘要】 当前的场景是,在高速公路上部署温度采集设备,在高速路地表安装温度检测传感器,检测当前路段的路面实际温度。一段高速路上有多个地点需要采集温度数据。 采集温度数据需要上传到云平台进行数据存储,并且通过可视化界面展示温度变化曲线,支持查询最近几天的温度信息。

一、前言

当前的场景是,在高速公路上部署温度采集设备,在高速路地表安装温度检测传感器,检测当前路段的路面实际温度。一段高速路上有多个地点需要采集温度数据。 采集温度数据需要上传到云平台进行数据存储,并且通过可视化界面展示温度变化曲线,支持查询最近几天的温度信息。

二、设计思路

(1)云平台选型:使用华为云物联网云平台。

(2)云数据存储: 使用OBS存储,存放设备上传的历史数据。

(3)设备选项:NBIOT模块+温度采集模块,实现温度采集上报。

(4)数据可视化:采用华为云IoT应用侧接口,获取传感器设备上传到云端的数据,在本地设计界面进行可视化显示温度数据。

下面是温度数据可视化展示效果:

image-20221024111639359

image-20221024111941580

本篇文章主要介绍设备上云的详细流程,介绍华为云物联网云端产品、设备创建流程,数据转存方式,应用侧开发接口等等。

硬件选型:

(1)STM32开发板: STM32F103C8T6

image-20221024110049871

(2)NBIOT模块--BC26

BC26模块是一款高性能、低功耗、多频段LTE Cat NB1无线通信模块。

image-20221024110217821

(3)温度采集模块

pt100是铂热电阻,它的阻值会随着温度的变化而改变。PT后的100即表示它在0℃时阻值为100欧姆,在100℃时它的阻值约为138.5欧姆。其工作原理:当PT100在0℃时,其电阻为100欧姆。它的电阻会随着温度的升高而上升,并且它的电阻会匀速增加。热电阻是一种常用于中低温的温度传感器。它的工作原理是基于电阻的热效应,即电阻的阻值随温度的变化而变化。铂金热敏电阻的热阻精度最高,具有抗振动、稳定性好、耐高压等特点。因此,它被制成各种标准温度计进行测量和校准。

image-20221024110324451

三、华为云IOT平台

3.1 创建产品

官网地址: https://www.huaweicloud.cn/

(1)设备接入IOTDA

在产品页面找到iot物联网,选择设备接入IOTDA

image-20221022160605043

设备接入服务(IoT Device Access)是华为云的物联网平台,提供海量设备连接上云、设备和云端双向消息通信、批量设备管理、远程控制和监控、OTA升级、设备联动规则等能力,并可将设备数据灵活流转到华为云其他服务,帮助物联网行业用户快速完成设备联网及行业应用集成,基础版每月一百万条消息免费。

在页面上选择免费试用。

image-20221022160818650

点击后,会进入到设备接入控制台页面。

image-20221022160840780

(2)设备接入地址

在基础版详情页面,点击右边的按需计费详情,可以查看物联网服务器接入的IP地址,端口号、接入方式。

image-20221022161034896

image-20221022161142778

如果是设备接入,可以选择MQTT或者MQTTS协议,在单片机上只有MQTT协议验证比较方便,通过MQTT三元组即可完成设备连接,当前我这里的设备选择是MQTT协议连接华为云平台。

 域名:a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com
 IP地址: 121.36.42.100
 端口号: 1883

(3)创建产品

在左边选项卡里点击产品,进入产品页面,点击右上角创建产品。

image-20221022161727914

根据自己的产品信息,填写表单:

image-20221022162136847

(4)完成产品创建

image-20221022162207193

点击查看详情,可以进入到创建成功的产品页面。

image-20221022162320973

(4)定义产品模型

产品创建之后,接着需要在平台上构建一款设备的抽象模型,使平台理解该款设备支持的功能。

比如:温度采集设备肯定会向云平台上传采集的温度信息;在产品模型里就可以定义一个温度的属性字段。

img

选择下面的自定义模型

image-20221022162843653

添加服务ID。

image-20221022163107049

添加属性。

image-20221022163147840

新增属性字段。当前在多节点温度采集的设备里,主要是采集温度上传,这里就新增一个温度的属性。

image-20221022163403660

产品模型创建完成。

image-20221022163609428

3.2 创建设备

(1)创建单个设备

设备注册的方式有很多:

【1】创建支持单个设备手动创建。

【2】如果设备特别多可以选择批量注册。

【3】通过API接口进行动态注册。

当前为了演示流程,这里选择第一种方式,手动创建单个设备。

在设备页面,选择所有设备选项,点击右边的注册设备按钮。

image-20221022164404032

(2)单设备注册填写信息

点击注册设备之后,会弹出一个表单填写信息。

其中产品就选择刚才创建的产品,设备标识码这一项一般是填设备的ID(设备的唯一标识符,方便绑定设备)。目前还没有对接硬件,我这里就填dev1,方便接下来的测试。 下面的设备ID这一项如果不填,会自动生成,可以不管;最后输入密匙(这个密匙的作用:通过对每个设备进行身份验证,可以安全地将每个设备连接到平台,并且安全地管理这些设备),填好之后点击确定。

image-20221022164903189

(3)设备创建完成

创建好之后,保存生成的设备ID和密匙。

image-20221022165440256

得到的密匙和ID的格式文本如下:

 {
     "device_id": "6353a8163ec34a6d03c8dfe5_dev1",
     "secret": "12345678"
 }

这个设备密匙和ID,后面生成MQTT登录参数时需要用到。

(4)创建多个温度设备节点

由于本项目是实现多节点温度上传到云平台,每个节点都是一个独立的温度采集设备,为了方便演示效果,还需要多创建几个设备。

接下来的创建流程,和刚才第一个设备一样,这里就不再截图演示了。

点击创建设备按钮,继续注册。

image-20221022165800302

image-20221022165853460

目前一共创建了4个设备,其中main_dev设备是用来作为显示终端,在本地用显示屏显示其他温度采集节点采集的温度信息。 剩下3个设备dev1dev2dev3是表示3个独立的温度采集节点。

image-20221022170226355

这4个设备的密匙和ID信息如下:

 {
     "device_id": "6353a8163ec34a6d03c8dfe5_dev1",
     "secret": "12345678"
 }
 ​
 ​
 {
     "device_id": "6353a8163ec34a6d03c8dfe5_dev2",
     "secret": "12345678"
 }
 ​
 ​
 {
     "device_id": "6353a8163ec34a6d03c8dfe5_dev3",
     "secret": "12345678"
 }
 ​
 ​
 {
     "device_id": "6353a8163ec34a6d03c8dfe5_main_dev",
     "secret": "12345678"
 }

3.3 模拟设备上云测试

目前设备创建之后,这些设备都还没有激活。接下来会使用MQTT客户端来模拟真实设备上云,上传温度数据。

这里的上云,包括数据交互都采用MQTT客户端来模拟实现,不涉及到实际的硬件,只要模拟能测试成功,并且能得到自己想要的结果,那硬件就没有问题了。

(1)生成MQTT鉴权三元组

设备要连接华为云平台的方式,在第一节创建产品的时候就已经介绍了,本次项目里的设备是采用MQTT协议接入云平台。 在完成设备模拟上云之前,需要先生成设备的MQTT协议鉴权三元组。

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

工具打开的界面如下效果:

image-20221022172642648

前面两行就是填设备创建后生成的设备ID和设备密匙,填好之后,生成下面3行信息,生成的3行就是MQTT协议登录需要用的参数。

按照格式分别生成4个设备的鉴权信息:

image-20221022172904948

得到的三元组如下:

 ClientId  6353a8163ec34a6d03c8dfe5_dev1_0_0_2022102209
 Username  6353a8163ec34a6d03c8dfe5_dev1
 Password  c58c45d514832b119b4302eafb3e74854849ca94079e9aed75efedf176e9c388
 ​
 ClientId  6353a8163ec34a6d03c8dfe5_dev2_0_0_2022102209
 Username  6353a8163ec34a6d03c8dfe5_dev2
 Password  c58c45d514832b119b4302eafb3e74854849ca94079e9aed75efedf176e9c388
 ​
 ClientId  6353a8163ec34a6d03c8dfe5_dev3_0_0_2022102209
 Username  6353a8163ec34a6d03c8dfe5_dev3
 Password  c58c45d514832b119b4302eafb3e74854849ca94079e9aed75efedf176e9c388
 ​
 ClientId  6353a8163ec34a6d03c8dfe5_main_dev_0_0_2022102209
 Username  6353a8163ec34a6d03c8dfe5_main_dev
 Password  c58c45d514832b119b4302eafb3e74854849ca94079e9aed75efedf176e9c388

(2)MQTT客户端模拟设备登录

得到MQTT三元组之后,接下来用MQTT客户端模拟设备登录云平台。

按照软件的输入框提示,输入对应的信息,点击登录。 其中的IP地址和端口号在第一小节的产品创建里就介绍过了。 后面输入的这3行就是上一步生成的MQTT鉴权三元组。

image-20221022173336679

登录成功。

image-20221022173358272

然后打开云平台的控制台,查看设备在线情况:

image-20221022173444173

可以看到dev1已经在线了, 刚才模拟的设备就是dev1。

(3)主题订阅与发布

目前设备已经成功登录,接下来要解决的问题就是数据传输问题了。

MQTT协议里要理解的两个概念就是主题订阅,主题发布。 设备上传数据到平台,属于 主题发布。 设备想要知道其他设备的数据或者云平台下发的指令,需要进行主题订阅。

帮助文档的地址: https://support.huaweicloud.cn/api-iothub/iot_06_v5_3002.html

MQTT消息由固定报头(Fixed header)、可变报头(Variable header)和有效载荷(Payload)三部分组成。

其中固定报头(Fixed header)和可变报头(Variable header)格式的填写请参考MQTT标准规范,有效载荷(Payload)的格式由应用定义,即设备和物联网平台之间自己定义。

常见MQTT消息类型主要有CONNECT、SUBSCRIBE、PUBLISH。

 CONNECT:指客户端请求和服务端连接。有效载荷(Payload)的主要参数,参考设备连接鉴权填写。
 SUBSCRIBE:指客户端订阅请求。有效载荷(Payload)中的主要参数“Topic name”,参考Topic定义中订阅者为设备的Topic。
 PUBLISH:平台发布消息。
 可变报头(Variable header)中的主要参数“Topic name”,指设备上报到物联网平台时发布者为设备的Topic。详细请参考Topic定义。
 有效载荷(Payload)中的主要参数为完整的数据上报和命令下发的消息内容,目前是一个JSON对象。
 ​
 上行Topic是指设备向平台发送请求,或上报数据,或回复响应。
 下行Topic是指平台向设备下发指令,或回复响应。
 设备与平台建立连接后,需要订阅下行Topic,否则无法收到平台下发的指令或回复的响应。应用侧接口的调用,需要设备侧的配合,例如应用侧下发命令,设备侧需要先订阅“平台命令下发”的下行Topic,否则设备无法收到平台命令,应用下发命令的接口也会报超时。

在产品页面,可以看到主题的格式,以及对于主题的用途:**

image-20221018145900244

【1】订阅主题

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

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

主题的格式如下:

 $oc/devices/{device_id}/sys/messages/down
     
 以dev1设备1为例,最终的格式:
 $oc/devices/6353a8163ec34a6d03c8dfe5_dev1/sys/messages/down

【2】发布主题

对于设备,发布主题,也就显示向云平台上传数据。

发布的主题格式如下:

 $oc/devices/{device_id}/sys/properties/report
 ​
 以dev1设备1为例最终的格式:
 $oc/devices/6353a8163ec34a6d03c8dfe5_dev1/sys/properties/report

发布主题时,需要上传数据,这个数据格式是JSON格式。

上传的JSON数据格式如下:

 {
   "services": [
     {
       "service_id": <填服务ID>,
       "properties": {
         "<填属性名称1>": <填属性值>,
         "<填属性名称2>": <填属性值>,
         ..........
       }
     }
   ]
 }

根据JSON格式,一次可以上传多个属性字段。 这个JSON格式里的,服务ID,属性字段名称,属性值类型,在前面创建产品的时候就已经介绍了,不记得可以翻到前面去查看。

根据这个格式,组合温度节点一次上传的数据:

 {"services": [{"service_id": "temp","properties":{"temp":24.6}}]}

(4)MQTT客户端模拟设备上报数据

打开MQTT客户端填入订阅主题,发布主题,和需要发布的数据,然后分别点击订阅主题按钮,发布主题按钮。 右边提示成功之后,就可以打开云平台看上传的效果了。 (这里是以dev1,设备1为例)

image-20221022204905869

打开云平台看到dev1已经在线。

image-20221022205118029

点击dev1,进去查看设备上传的数据。 可以看到,刚才上传的数据已经收到了。

image-20221022205215640

到此,设备上传数据到云平台已经完成。

四、设备数据转存

如果设备上传的数据需要进行保存,后续进行分析做其他用途,可以利用数据转发服务,让平台将设备上报数据推送给自己的应用服务器,由应用服务器进行保存;也可以选择让平台将设备上报数据转发给OBS对象存储服务,由OBS进行存储,进行永久保存,非常方便。如果存储在OBS里,自己设计应用侧界面时,也可以直接拉取OBS里的数据下来进行显示,处理,分析。

4.1 创建OSB存储桶

地址: https://www.huaweicloud.cn/product/obs.html

对象存储服务(Object Storage Service,OBS)是一个基于对象的存储服务,提供了海量、安全、高可靠、低成本的数据存储能力。

(1)选择管理控制台

image-20221018155125516

(2)创建桶

image-20221018155217538

填充桶信息: 我这里选择的是 华北-北京一

image-20221018155612331

我这里因为要长期使用,这里选择1年的购买权。

image-20221018155735502

(3)创建成功

image-20221018160339294

4.2 配置数据转发规则

(1)创建规则

选择左侧导航栏的规则>数据转发,单击右上角的创建规则

image-20221018153651626

填充规则转发的信息:

参数名 参数说明
规则名称 自定义,如: led_obs。
规则描述 自定义,如数据转发至OBS服务。
数据来源 选择“设备属性”。
触发事件 自动匹配“设备属性上报”。
资源空间 和上一步创建的产品所在的资源空间保持一致。

image-20221024092039739

(2)设置转发目标

单击添加,设置转发目标。

这里的区域选择--华北-北京一。 因为前面的OBS桶创建的时候,设置区域设置的是北京一,然后点击授权。

image-20221018154650041

image-20221018154706105

授权之后,选择刚才创建的OBS桶,设置存储数据的目录和文件名字。

存储的数据可以直接转存JSON数据到OBS存储桶,也可以存放成CSV文件到存储桶。

如果选择存储JSON,就是直接将设备上传的数据存放到OBS存储里,如果选择存储CSV文件,可以自己选择需要存储的字段。 下面我演示一下存储成CSV文件时如何进行设置。 (选择JSON文件不需要进行任何设置,直接将设备上传的数据JSON存储进去了)。

image-20221018161905064

下面设置转发的字段: (如果提示没有授权,点击授权即可)

这个转发字段就是表示需要存放的数据是那些,对于路灯而言,肯定是需要存储上报的温度、湿度、电量、光照强度的属性的。 这些属性在创建产品的时候设置,设备上报的也是这些属性。

这是设备上传一次云平台的完整JSON数据格式:

 {
   "resource": "device.property",
   "event": "report",
   "event_time": "20221018T131627Z",
   "request_id": "5ee95a0c-262d-43c3-8d31-af453f9952ef",
   "notify_data": {
     "header": {
       "app_id": "7211833377cf435c8c0580de390eedbe",
       "device_id": "634e3e423ec34a6d03c84bfb_1126626497",
       "node_id": "1126626497",
       "product_id": "634e3e423ec34a6d03c84bfb",
       "gateway_id": "634e3e423ec34a6d03c84bfb_1126626497"
     },
     "body": {
       "services": [
         {
           "service_id": "temp",
           "properties": {
             "temp": 28,
           },
           "event_time": "20221018T131627Z"
         }
       ]
     }
   }
 }

当前设备上传到云端服务器有一个温度属性字段temp,如果想转发设备上传的这个温度字段,就可以这样写:

 notify_data.body.services[0].properties.temp

后面的存储目标字段,为了好区分,直接填temp即可。

如果设备还上传了其他属性字段也想转发,按照上面格式设置即可。

如果存储数据时,想知道这个数据是那个产品,那个设备上传的,也可以将设备ID和产品ID转发存储起来:格式如下。

 notify_data.header.app_id  
 notify_data.header.device_id

下面是我设置好,转发存储的字段:

image-20221024092936800

确定后,点击设置完成。

image-20221018162100042

(3)测试规则

设置好之后,在设置转发目标的页面点击测试。

image-20221024093411314

输入数据模板,然后点击连通性测试。如果测试结果显示成功,说明整体流程没有问题了。

image-20221024093458702

(4)启用规则

设置完成后,点击启用规则。

image-20221018162138156

image-20221018162202629

(6)数据上报测试

为了验证转发规则是否生效,接下来使用MQTT客户端多上报几次数据到云平台。

image-20221024093313439

上传之后,打开OBS存储桶的控制台页面。打开转发规则存储的OBS桶。

image-20221018164241352

找到存储数据的文件,点击下载。

image-20221018164340102

下载下来,打开可以看到存储的数据:

image-20221024093812207

到此,数据转发,存储已经成功了。

五、应用侧-可视化大屏开发

对于数据的可视化显示,华为云提供了(Data Lake Visualization)一站式数据可视化平台,数据可视化服务(DLV)可以从OBS文件读取数据呈现为可视化报表,实现数据的可视化显示。

下面是华为云的DLV大屏从OBS读取数据显示的流程:

img

当前我这里的需求是需要在本地自己设计界面显示数据,没有采用华为云的DLV大屏,如果自己本地软件需要显示设备上传的数据,就需要使用华为云物联网的平台的应用侧API接口,读取设备上传的数据进行,本地进行显示;如果需要历史数据,可以读取OBS存储桶里存储的数据进行显示。

5.1 应用侧接口

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

image-20220113142020653

5.2 查询设备影子数据接口

应用侧接口可以查询发送指令给设备查询属性,也可以读取设备的影子数据。

【1】查询设备查询: 这个是实时查询,相当于应用侧接口发送指令给在线设备,设备收到指令,将当前最新的数据再上传。这个需要保证设备在线,离线是无法调用的。

【2】影子数据:影子数据相当于保存设备上传的最新一次数据。 读取读取影子设备数据,是不需要设备在线。

(1)接口URI地址

请求方法 GET
URI /v5/iot/{project_id}/devices/{device_id}/shadow
传输协议 HTTPS

(2)请求参数说明

名称 必选/可选 类型 位置 说明
X-Auth-Token 必选 String Header 参数说明:用户Token。通过调用IAM服务 获取IAM用户Token接口获取,接口返回的响应消息头中“X-Subject-Token”就是需要获取的用户Token。
Instance-Id 可选 String Header 参数说明:实例ID。物理多租下各实例的唯一标识,一般华为云租户无需携带该参数,仅在物理多租场景下从管理面访问API时需要携带该参数。
project_id 必选 String Path 参数说明:项目ID。获取方法请参见 获取项目ID
device_id 必选 String Path 参数说明:设备ID,用于唯一标识一个设备。在注册设备时直接指定,或者由物联网平台分配获得。由物联网平台分配时,生成规则为"product_id" + " " + "node_id"拼接而成。取值范围:长度不超过128,只允许字母、数字、下划线( )、连接符(-)的组合。

(3)响应参数

名称 类型 说明
device_id String 设备ID,用于唯一标识一个设备。在注册设备时直接指定,或者由物联网平台分配获得。由物联网平台分配时,生成规则为"product_id" + "_" + "node_id"拼接而成。
shadow List[DeviceShadowData]() 设备影子数据结构体。
 
名称 类型 说明
service_id String 设备的服务ID,在设备关联的产品模型中定义。
desired DeviceShadowProperties Object 用户最近一次对设备下发的预期数据,Json格式,里面是一个个键值对,每个键都是产品模型中属性的参数名(property_name)。
reported DeviceShadowProperties Object 设备最近一次上报的属性数据,Json格式,里面是一个个键值对,每个键都是产品模型中属性的参数名(property_name)。
version Long 设备影子的版本,携带该参数时平台会校验值必须等于当前影子版本,初始从0开始。
 
名称 类型 说明
properties Object 设备影子的属性数据,Json格式,里面是一个个键值对,每个键都是产品模型中属性的参数名(property_name),目前如样例所示只支持一层结构。注意:JSON结构的key当前不支持特殊字符:点(.)、dollar符号($)、空char(十六进制的ASCII码为00),key为以上特殊字符无法正常刷新设备影子
event_time String 事件操作时间,格式:yyyyMMdd'T'HHmmss'Z',如20151212T121212Z。

(4)请求示例

 GET https://{Endpoint}/v5/iot/{project_id}/devices/{device_id}/shadow
 Content-Type: application/json
 X-Auth-Token: ********
 Instance-Id: ********

(5)响应示例

Status Code: 200 OK

 Content-Type: application/json
 ​
 {
   "device_id" : "40fe3542-f4cc-4b6a-98c3-61a49ba1acd4",
   "shadow" : [ {
     "service_id" : "WaterMeter",
     "desired" : {
       "properties" : {
         "temperature" : "60"
       },
       "event_time" : "20151212T121212Z"
     },
     "reported" : {
       "properties" : {
         "temperature" : "60"
       },
       "event_time" : "20151212T121212Z"
     },
     "version" : 1
   } ]
 }

(6)错误码

HTTP状态码 错误码 错误码英文描述 错误码中文描述 处理建议
403 IOTDA.000021 Operation not allowed. User not found by IAM token or the authorized user has not subscribed IOTDA service. 没有找到IAM Token所对应的用户信息或该用户没有订阅设备接入服务(IOTDA) 请排查IAM Token所在用户是否订阅了设备接入服务(IOTDA)。
404 IOTDA.014000 The device does not exist. 设备不存在 请排查请求参数是否有误并确认是否有在平台注册该设备。
500 IOTDA.000020 Decrypt IAM token failed.

5.3 接口调试

在线调试地址:https://apiexplorer.developer.huaweicloud.cn/apiexplorer/debug?product=IoTDA&api=ShowDeviceShadow

下面是调试影子 数据查询接口,查询设备影子数据: 右边返回的是设备上传的最新数据。

image-20221024103028287

5.5 接口总结

 请求地址: 
 https://iotda.cn-north-4.myhuaweicloud.com/v5/iot/{project_id}/devices/{device_id}/shadow
 ​
 请求方式: GET
 ​
 请求头:
 {
  "X-Auth-Token": "这个需要自己获取",   
  "Content-Type": "application/json"
 }

5.5 如何获取X-Subject-Token

使用API访问华为云的所有服务接口,都需要填X-Subject-Token参数,下面介绍步骤:

(1)创建一个新的IAM帐户

鼠标悬停在右上角的用户名称上,弹出下拉框,选择统一身份认证。

image-20211220152929854

(2)选择创建用户

image-20211220153051946

image-20211220153226036

image-20211220153255718

image-20211220153311429

(3)使用调试接口测试获取oken

调试接口地址: https://apiexplorer.developer.huaweicloud.cn/apiexplorer/debug?product=IAM&api=KeystoneCreateUserTokenByPassword

image-20211220153749278

右边响应头里的X-Subject-Token就是获取的token。

image-20211220154523194

(4)上面的这些账户名称从哪里获取?

image-20211220153950194

image-20211220154123357

(5)请求地址和数据格式

获取X-Subject-Token请求的地址: https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens

请求头数据:

  {
   "User-Agent": "API Explorer",
   "X-Auth-Token": "******",
   "Content-Type": "application/json;charset=UTF-8"
  }

请求体数据:

  {
    "auth": {
      "identity": {
        "methods": [
          "password"
        ],
        "password": {
          "user": {
            "domain": {  
              "name": "xxxxx"  //这里填当前主账户名称
            },
            "name": "xxxx",  //这个新建的子账户名称
            "password": "xxxxx"    //这个是新建的子账户密码
          }
        }
      },
      "scope": {
        "project": {
          "name": "cn-north-4"
        }
      }
    }
  }

(6)代码实现

  /*
  功能: 获取token
  */
  void Widget::GetToken()
  {
      //表示获取token
      function_select=3;
  
      QString requestUrl;
      QNetworkRequest request;
  
      //设置请求地址
      QUrl url;
  
      //获取token请求地址
      requestUrl = QString("https://iam.%1.myhuaweicloud.com/v3/auth/tokens")
                   .arg(SERVER_ID);
  
      //自己创建的TCP服务器,测试用
      //requestUrl="http://10.0.0.6:8080";
  
      //设置数据提交格式
      request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json;charset=UTF-8"));
  
      //构造请求
      url.setUrl(requestUrl);
  
      request.setUrl(url);
  
      QString text =QString("{"auth":{"identity":{"methods":["password"],"password":"
      "{"user":{"domain": {"
      ""name":"%1"},"name": "%2","password": "%3"}}},"
      ""scope":{"project":{"name":"%4"}}}}")
              .arg(MAIN_USER)
              .arg(IAM_USER)
              .arg(IAM_PASSWORD)
              .arg(SERVER_ID);
  
      //发送请求
      manager->post(request, text.toUtf8());
  }

5.6 查询设备影子数据代码

(1)这是请求代码

 //查询设备属性
 void Widget::Get_device_properties()
 {
     //表示获取token
     function_select=0;
 ​
     QString requestUrl;
     QNetworkRequest request;
 ​
     //设置请求地址
     QUrl url;
 ​
     //获取token请求地址
     requestUrl = QString("https://iotda.%1.myhuaweicloud.com/v5/iot/%2/devices/%3/shadow")
                  .arg(SERVER_ID)
             .arg(PROJECT_ID)
             .arg(device_id);
 ​
     //设置数据提交格式
     request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
 ​
     //设置token
     request.setRawHeader("X-Auth-Token",Token);
 ​
     //构造请求
     url.setUrl(requestUrl);
 ​
     request.setUrl(url);
 ​
     //发送请求
     manager->get(request);
 }
 ​
 //更新设备属性
 void Widget::on_pushButton_update_device_clicked()
 {
     Get_device_properties();
 }

(2)这是请求一次返回的JSON数据

 {
   "device_id": "6353a8163ec34a6d03c8dfe5_dev1",
   "shadow": [
     {
       "service_id": "temp",
       "desired": {
         "properties": null,
         "event_time": null
       },
       "reported": {
         "properties": {
           "temp": 45.6
         },
         "event_time": "20221024T013607Z"
       },
       "version": 49
     }
   ]
 }

5.7 界面设计最终效果

image-20221024111929665

六、硬件部分

6.1 MQTT版本

注意:华为云、OneNet、腾讯IOT 等平台规定接入的MQTT协议版本必须是3.1.1 。

在BC26里,需要执行这行代码配置MQTT协议版本为3.1.1

 AT+QMTCFG="version",0,4

6.2 BC26上云配置代码

 #include "BC26.h"
 ​
 //BC26复位
 //引脚是PC0
 //高电平有效
 void BC26_Reset(void)
 {
     //开时钟
     RCC->APB2ENR|=1<<4;
 ​
     //配置GPIO口
     GPIOC->CRL&=0xFFFFFFF0;
     GPIOC->CRL|=0x00000003;
     
     //开始复位
     GPIOC->ODR|=1<<0;
     DelayMs(2000);
     GPIOC->ODR&=~(1<<0);
 }
 ​
 ​
 /*
 函数功能:向BC26模块发送指令
 函数参数:
                 char *cmd  发送的命令
               char *check_data 检测返回的数据
 返回值: 0表示成功 1表示失败
 */
 u8 BC26_SendCmd(char *cmd,char *check_data)
 {
    u16 i,j;
    for(i=0;i<5;i++) //测试的总次数
    {
       USART2_RX_FLAG=0;
       USART2_RX_CNT=0;
       memset(USART2_RX_BUFFER,0,sizeof(USART2_RX_BUFFER));
       USARTx_StringSend(USART2,cmd); //发送指令
       for(j=0;j<500;j++) //等待的时间(ms单位)
       {
           if(USART2_RX_FLAG)
           {
               USART2_RX_BUFFER[USART2_RX_CNT]='\0';
               if(strstr((char*)USART2_RX_BUFFER,check_data))
               {
                   return 0;
               }
               else break;
           }
           delay_ms(30); //一次的时间
       }
    }
    return 1;
 }
 ​
 ​
 ​
 ​
 //初始化BC26模块
 int BC26_Init(void)
 {
     if(BC26_SendCmd("AT\r\n","OK"))
     {
         USART1_Printf("BC26模块不存在.\r\n");
         return 1;
     }
     else 
     {
         USART1_Printf("BC26模块正常!\r\n");
     }
 ​
     if(BC26_SendCmd("AT+CIMI\r\n","OK"))
     {
         USART1_Printf("模块未插卡.\r\n");
         return 3;
     }
     else 
     {
         USART1_Printf("卡已经插好.\r\n");
     }
 ​
  
     if(BC26_SendCmd("AT+CGATT=1\r\n","OK"))
     {
         USART1_Printf("配置:网络激活失败.\r\n");
         return 3;
     }
     else 
     {
         USART1_Printf("配置:网络激活成功.\r\n");
     }
     
     if(BC26_SendCmd("AT+CGATT?\r\n","OK"))
     {
         USART1_Printf("状态:网络激活失败.\r\n");
         return 4;
     }
     else 
     {
         USART1_Printf("状态:网络激活成功.\r\n");
     }
     
     if(BC26_SendCmd("AT+CSQ\r\n","OK"))
     {
         USART1_Printf("查询信号质量失败.\r\n");
         return 5;
     }
     else 
     {
         USART1_Printf("信号质量:%s\r\n",USART2_RX_BUFFER);
     }
     
     
 //    if(BC26_SendCmd("AT+QGNSSC=1\r\n","OK"))
 //    {
 //        USART1_Printf("激活GPS定位失败.\r\n");
 //        return 6;
 //    }
 //    else 
 //    {
 //        USART1_Printf("激活GPS定位成功.\r\n");
 //    }
 //    
 //    if(BC26_SendCmd("AT+QGNSSAGPS=1\r\n","OK"))
 //    {
 //        USART1_Printf("开启AGPS定位失败.\r\n");
 //        return 6;
 //    }
 //    else 
 //    {
 //        USART1_Printf("开启AGPS定位成功.\r\n");
 //    }
 //    
 //    if(BC26_SendCmd("AT+CGPADDR=1\r\n","OK"))
 //    {
 //        USART1_Printf("激活GPRS场景失败.\r\n");
 //        return 7;
 //    }
 //    else 
 //    {
 //        USART1_Printf("激活GPRS场景成功.\r\n");
 //    }
 //    
 //    DelayMs(1000);
 //    DelayMs(1000);
     
     if(BC26_SendCmd("AT+CEREG?\r\n","+CEREG: 0,1"))
     {
         USART1_Printf("网络注册状态:失败.\r\n");
         return 9;
     }
     else 
     {
         USART1_Printf("网络注册状态:成功.\r\n");
     }
      
     return 0;
 }
 ​
 ​
 //发送使用的缓冲区
 char BC26_SEND_BUFF[500];
 ​
 //MQTT协议登录服务器
 int BC26_MQTT_Connect(void)
 {
     //1. 先关闭之前的连接
      USART1_Printf("正在关闭之前的连接...\r\n");
     BC26_SendCmd("AT+QMTCLOSE=0\r\n","OK");
     DelayMs(4000);
     
     //关闭服务
     BC26_SendCmd("AT+QMTCONN?\r\n","OK");
     DelayMs(4000);
     
     //2. 连接MQTT服务器
     USART1_Printf("正在连接MQTT服务器..\r\n");
     sprintf(BC26_SEND_BUFF,"AT+QMTOPEN=0,"%s",%s\r\n",MQTT_SERVER_ADDR,MQTT_SERVER_PORT);
     if(BC26_SendCmd(BC26_SEND_BUFF,"OK"))
     {
         USART1_Printf("MQTT服务器连接失败:%s\r\n",BC26_SEND_BUFF);
         return 1;
     }
     else 
     {
         USART1_Printf("MQTT服务器连接成功.\r\n");
     }
     DelayMs(3000);
     
     
     //3. 登录MQTT服务器
     USART1_Printf("正在登录MQTT服务器...\r\n");
     sprintf(BC26_SEND_BUFF,"AT+QMTCONN=0,"%s","%s","%s"\r\n",MQTT_CLIENT_ID,MQTT_USERNAME,MQTT_PASSWORD);
     if(BC26_SendCmd(BC26_SEND_BUFF,"OK"))
     {
         USART1_Printf("MQTT服务器登录失败:%s\r\n",BC26_SEND_BUFF);
         return 2;
     }
     else 
     {
         USART1_Printf("MQTT服务器登录成功.\r\n");
     }
     DelayMs(3000);
     
     //4. 订阅主题
     USART1_Printf("正在订阅主题...\r\n");
     sprintf(BC26_SEND_BUFF,"AT+QMTSUB=0,1,"%s",2\r\n",MQTT_TOPIC_SUB_GET);
     if(BC26_SendCmd(BC26_SEND_BUFF,"OK"))
     {
         USART1_Printf("MQTT主题订阅失败:%s\r\n",BC26_SEND_BUFF);
     }
     else 
     {
         USART1_Printf("MQTT主题订阅成功.\r\n");
     }
     return 0;
 }
 ​
 ​
 //MQTT发布主题
 int MQTT_PublishTheme(char *text)
 {
     char send_buf[3];
     sprintf(BC26_SEND_BUFF,"AT+QMTPUB=0,0,0,0,"%s"\r\n",MQTT_TOPIC_SUB_SET);
     if(BC26_SendCmd(BC26_SEND_BUFF,">"))
     {
         USART1_Printf("发布主题等待输入失败:%s\r\n",BC26_SEND_BUFF);
         return 1;
     }
     USARTx_StringSend(USART2,text);     //发送主题内容
     
     //发送结束符
     send_buf[0] = 0x1a;
     send_buf[1] = '\0';
     if(BC26_SendCmd(send_buf,"OK"))
     {
         USART1_Printf("发布主题内容失败:%s\r\n",BC26_SEND_BUFF);
         return 2; //发送结束符号
     }
     else
     {
         USART1_Printf("发布主题内容成功.\r\n");
     }
     
      USART1_Printf("发布主题内容:%s\r\n",text);
     return 0;
 }
 ​
 //MQTT响应应用层的属性请求
 int MQTT_SendAttribute(char *text,char *request_id)
 {
     char send_buf[3];
     sprintf(BC26_SEND_BUFF,"AT+QMTPUB=0,0,0,0,"%s%s"\r\n",MQTT_TOPIC_SUB_SET_RUN,request_id);
     if(BC26_SendCmd(BC26_SEND_BUFF,">"))
     {
         USART1_Printf("(响应)发布主题等待输入失败:%s\r\n",BC26_SEND_BUFF);
         return 1;
     }
     USARTx_StringSend(USART2,text);     //发送主题内容
     
     //发送结束符
     send_buf[0] = 0x1a;
     send_buf[1] = '\0';
     if(BC26_SendCmd(send_buf,"OK"))
     {
         USART1_Printf("(响应)发布主题内容失败:%s\r\n",BC26_SEND_BUFF);
         return 2; //发送结束符号
     }
     else
     {
         USART1_Printf("(响应)发布主题内容成功.\r\n");
     }
     
      USART1_Printf("(响应)发布主题内容:%s\r\n",text);
     return 0;
 }
 ​
 ​
 ​
 /*
 函数功能: 获取一次GPS经纬度数据
 函数参数:
         double *Longitude  :经度
         double *latitude   :纬度
 返回值: 0表示定位成功,1表示数据接收失败,2表示定位失败
 */
 u8 BC26_GetGPS_Data(double *Longitude,double *latitude)
 {
     /*1. 发送获取GPS数据的指令*/
     if(BC26_SendCmd("AT+QGNSSRD="NMEA/RMC"\r\n", "OK\r\n"))return 1;
     
     /*2. 对GPS数据进行解码*/
     if(GPS_GNRMC_Decoding((char *)USART2_RX_BUFFER,Longitude,latitude))return 2;
     
     //解码成功
     return 0; 
 }
 ​
 ​
 /*
 函数功能: 开启GPS功能
 返 回 值:0表示成功  1表示失败
 */
 u8 BC26_StartGPS(void)
 {
     //先判断GPS功能是否启动
     if(BC26_SendCmd("AT+QGNSSC?\r\n","+QGNSSC: 1")) 
     {
         //没有启动就启动GPS功能
         if(BC26_SendCmd("AT+QGNSSC=1\r\n","OK\r\n"))
         {
             USART1_Printf("开启GPS功能失败.\r\n");
             return 1;  //GPS功能启动失败
         }
         else
         {
             USART1_Printf("开启GPS功能成功.\r\n");
         }
     }
     return 0;
 }

6.3 MQTT 3.1协议介绍

地址: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html

 规范共分七章:
 第一章 - 介绍
 第二章 - MQTT控制包格式
 第三章 - MQTT控制包
 第四章 - 操作行为
 第五章 - 安全
 第六章 - 使用WebSocket进行网络传输
 第七章 - 一致性目标

七、总结

通过温度采集的项目,介绍了一个物联网设备如何上云,云平台如何配置,设备数据如何永久转存,应用侧API接口,应用侧界面发等知识点。如果没有硬件,也可以直接利用软件方式模拟设备上传,实现最终的效果。


【我的IoT端边云体验】有奖征文火热进行中:https://bbs.huaweicloud.cn/blogs/378687 此外,在(https://bbs.huaweicloud.cn/activity/suggestion.html)提出您的宝贵建议,标题以【云驻计划-定向征文】开头,还有机会赢取额外奖励。

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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