本文分享自华为云社区《采用华为云 IOT 平台设计的高速公路多节点温度采集系统 (STM32+NBIOT)》,作者:DS 小龙哥。

  一、前言

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

  二、设计思路
(1)云平台选型:使用华为云物联网云平台。
(2)云数据存储: 使用 OBS 存储,存放设备上传的历史数据。
(3)设备选项:NBIOT 模块 + 温度采集模块,实现温度采集上报。
(4)数据可视化:采用华为云 IoT 应用侧接口,获取传感器设备上传到云端的数据,在本地设计界面进行可视化显示温度数据。
下面是温度数据可视化展示效果:
    v2-112c3c955cb443250f0e3cff6880b05e_720w.jpg
    v2-c1ad5ae1576f0ab2942171ae9baaf578_720w.jpg
本篇文章主要介绍设备上云的详细流程,介绍华为云物联网云端产品、设备创建流程,数据转存方式,应用侧开发接口等等。
  硬件选型:
(1)STM32 开发板: STM32F103C8T6
    v2-1f602c81809b2d75a7d0b8da19aedbac_720w.jpg
(2)NBIOT 模块 --BC26
BC26 模块是一款高性能、低功耗、多频段 LTE Cat NB1 无线通信模块。
    v2-8c9e4ec89f8f7a97ad9708cb0cf3b902_720w.jpg
(3)温度采集模块
pt100 是铂热电阻,它的阻值会随着温度的变化而改变。PT 后的 100 即表示它在 0℃时阻值为 100 欧姆,在 100℃时它的阻值约为 138.5 欧姆。其工作原理:当 PT100 在 0℃时,其电阻为 100 欧姆。它的电阻会随着温度的升高而上升,并且它的电阻会匀速增加。热电阻是一种常用于中低温的温度传感器。它的工作原理是基于电阻的热效应,即电阻的阻值随温度的变化而变化。铂金热敏电阻的热阻精度最高,具有抗振动、稳定性好、耐高压等特点。因此,它被制成各种标准温度计进行测量和校准。
    v2-84f485c29bd43cb369f79c5ea9af24c7_720w.jpg

  三、华为云 IOT 平台
  3.1 创建产品
官网地址: https://www.huaweicloud.com/
(1)设备接入 IOTDA
在产品页面找到 iot 物联网,选择设备接入 IOTDA
    v2-2d61dbe8ee6d16d7dde87e68a70be7cf_720w.jpg
设备接入服务(IoT Device Access)是华为云的物联网平台,提供海量设备连接上云、设备和云端双向消息通信、批量设备管理、远程控制和监控、OTA 升级、设备联动规则等能力,并可将设备数据灵活流转到华为云其他服务,帮助物联网行业用户快速完成设备联网及行业应用集成,基础版每月一百万条消息免费。
在页面上选择免费试用。
    v2-05be026274c503975310516974edc231_720w.jpg
点击后,会进入到设备接入控制台页面。
    v2-5946ce922cbabc722074997a9de24d53_720w.jpg
(2)设备接入地址
在基础版详情页面,点击右边的按需计费详情,可以查看物联网服务器接入的 IP 地址,端口号、接入方式。
    v2-84b292b2de3fdd442ad1c1bbb9f9c352_720w.jpg
    v2-724b005312e30bc84ad722ac79619297_720w.jpg
如果是设备接入,可以选择 MQTT 或者 MQTTS 协议,在单片机上只有 MQTT 协议验证比较方便,通过 MQTT 三元组即可完成设备连接,当前我这里的设备选择是 MQTT 协议连接华为云平台。
  
域名:a161a58a78.iot-mqtts.cn-north-4.myhuaweicloud.com
  • IP地址: 121.36.42.100
  • 端口号: 1883
  • 复制代码

    (3)创建产品
    在左边选项卡里点击产品,进入产品页面,点击右上角创建产品。
        v2-a37c29d33e6c788615fd08c9d320392a_720w.jpg
    根据自己的产品信息,填写表单:
        v2-b24282d61e60bb5951379eccc80b5fd8_720w.jpg
    (4)完成产品创建
        v2-9d13e59d50ef61f630b50485337bc33c_720w.jpg
    点击查看详情,可以进入到创建成功的产品页面。
        v2-05590aba12fda1811a93d71a7c6c86fc_720w.jpg
    (5)定义产品模型
    产品创建之后,接着需要在平台上构建一款设备的抽象模型,使平台理解该款设备支持的功能。
    比如:温度采集设备肯定会向云平台上传采集的温度信息;在产品模型里就可以定义一个温度的属性字段。
        v2-b891149a0f177650626e2301a016357a_720w.jpg
    选择下面的自定义模型。
        v2-c0261a05b9b53e392e2b249d48c91da1_720w.jpg
    添加服务 ID。
        v2-07680b54c83498f0775958c02311f2f0_720w.jpg
    添加属性。
        v2-c7ab3dacfe239add8ab862c76525a43e_720w.jpg
    新增属性字段。当前在多节点温度采集的设备里,主要是采集温度上传,这里就新增一个温度的属性。
        v2-2f369688f21359d3a8fc2cc9bf050b06_720w.jpg
    产品模型创建完成。
        v2-29b7afe5cedf55533e621d9bb495360e_720w.jpg
      3.2 创建设备
    (1)创建单个设备
    设备注册的方式有很多:
    【1】创建支持单个设备手动创建。
    【2】如果设备特别多可以选择批量注册。
    【3】通过 API 接口进行动态注册。
    当前为了演示流程,这里选择第一种方式,手动创建单个设备。
    在设备页面,选择所有设备选项,点击右边的注册设备按钮。
        v2-bc2951e17545dec51b7d8ff88cd85125_720w.jpg
    (2)单设备注册填写信息
    点击注册设备之后,会弹出一个表单填写信息。
    其中产品就选择刚才创建的产品,设备标识码这一项一般是填设备的 ID(设备的唯一标识符,方便绑定设备)。目前还没有对接硬件,我这里就填 dev1,方便接下来的测试。 下面的设备 ID 这一项如果不填,会自动生成,可以不管;最后输入密匙(这个密匙的作用:通过对每个设备进行身份验证,可以安全地将每个设备连接到平台,并且安全地管理这些设备),填好之后点击确定。
        v2-cca7f466326231b1bea77dea92caccac_720w.jpg
    (3)设备创建完成
    创建好之后,保存生成的设备 ID 和密匙。
        v2-dd62b16b099472ea587bf1dbd297fe65_720w.jpg
    得到的密匙和 ID 的格式文本如下:
      
    {
  •      "device_id": "6353a8163ec34a6d03c8dfe5_dev1",
  •      "secret": "12345678"
  • }
  • 复制代码

    这个设备密匙和 ID,后面生成 MQTT 登录参数时需要用到。
    (4)创建多个温度设备节点
    由于本项目是实现多节点温度上传到云平台,每个节点都是一个独立的温度采集设备,为了方便演示效果,还需要多创建几个设备。
    接下来的创建流程,和刚才第一个设备一样,这里就不再截图演示了。
    点击创建设备按钮,继续注册。
        v2-0240cec9965a58375012983339b79db3_720w.jpg
        v2-e686b44494529b5a63320e7c5cc0b67b_720w.jpg
    目前一共创建了 4 个设备,其中 main_dev 设备是用来作为显示终端,在本地用显示屏显示其他温度采集节点采集的温度信息。 剩下 3 个设备 dev1,dev2,dev3 是表示 3 个独立的温度采集节点。
        v2-0aceb950f50b1ee10e4b91e9ac907be1_720w.jpg
    这 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/
    工具打开的界面如下效果:
        v2-9185d8cd0b0b87ef9fa3d1001693a28f_720w.jpg
    前面两行就是填设备创建后生成的设备 ID 和设备密匙,填好之后,生成下面 3 行信息,生成的 3 行就是 MQTT 协议登录需要用的参数。
    按照格式分别生成 4 个设备的鉴权信息:
        v2-c5ea7aae4f73d17beab158a9ae223f72_720w.jpg
    得到的三元组如下:
      
    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 鉴权三元组。
        v2-3e73d22d278c7d80ea9eb2b83324b5a3_720w.jpg
    登录成功。
        v2-f3c4569d626c40a841d3dc99fb123911_720w.jpg
    然后打开云平台的控制台,查看设备在线情况:
        v2-f866534e0b070dc2f48fbfc9ef9df014_720w.jpg
    可以看到 dev1 已经在线了, 刚才模拟的设备就是 dev1。
    (3)主题订阅与发布
    目前设备已经成功登录,接下来要解决的问题就是数据传输问题了。
    MQTT 协议里要理解的两个概念就是主题订阅,主题发布。 设备上传数据到平台,属于 主题发布。 设备想要知道其他设备的数据或者云平台下发的指令,需要进行主题订阅。
    帮助文档的地址: https://support.huaweicloud.com/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,否则设备无法收到平台命令,应用下发命令的接口也会报超时。
    在产品页面,可以看到主题的格式,以及对于主题的用途:**
        v2-1f3dbd1187544a22ffd3d557a02104e5_720w.jpg
    【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 为例)
        v2-119230085d82abdc3eb22b7854e23b4a_720w.jpg
    打开云平台看到 dev1 已经在线。
        v2-79e47f56166d6ded5dbad45798a6336c_720w.jpg
    点击 dev1, 进去查看设备上传的数据。 可以看到,刚才上传的数据已经收到了。
        v2-33e07a85e623aee57072a6ddee9659fa_720w.jpg
    到此,设备上传数据到云平台已经完成。

      四、设备数据转存
    如果设备上传的数据需要进行保存,后续进行分析做其他用途,可以利用数据转发服务,让平台将设备上报数据推送给自己的应用服务器,由应用服务器进行保存;也可以选择让平台将设备上报数据转发给 OBS 对象存储服务,由 OBS 进行存储,进行永久保存,非常方便。如果存储在 OBS 里,自己设计应用侧界面时,也可以直接拉取 OBS 里的数据下来进行显示,处理,分析。
      4.1 创建 OSB 存储桶
    地址: https://www.huaweicloud.com/product/obs.html
    对象存储服务(Object Storage Service,OBS)是一个基于对象的存储服务,提供了海量、安全、高可靠、低成本的数据存储能力。
    (1)选择管理控制台
        v2-f47444f89edaea1b7d7c3e2119b6ff0a_720w.jpg
    (2)创建桶
        v2-db646e04709486c6901ccce6679ab384_720w.jpg
    填充桶信息: 我这里选择的是 华北 - 北京一
        v2-f704f614be0509af2d8bc906965e6925_720w.jpg
    我这里因为要长期使用,这里选择 1 年的购买权。
        v2-2bbb33b20ca5404fa77ae391eaefc55a_720w.jpg
    (3)创建成功
        v2-6f4bdd89e7cdfd2b429a787f917c1ab4_720w.jpg

      4.2 配置数据转发规则
    (1)创建规则

    选择左侧导航栏的规则>数据转发,单击右上角的创建规则。
        v2-f4b057b48ee943874269b392d94a4dd1_720w.jpg
    填充规则转发的信息:
        v2-feaedaeb02c0cd4b55cb680ba561e66f_720w.jpg
        v2-e991973e7963b02a24f1e68bbe8324e5_720w.jpg
    (2)设置转发目标
    单击添加,设置转发目标。
    这里的区域选择 -- 华北-北京一。 因为前面的 OBS 桶创建的时候,设置区域设置的是北京一,然后点击授权。
        v2-074415d6aea2bea6ac1979c3fb586b35_720w.jpg
        v2-2fce480e20bbe1a1b611e57b79c35945_720w.jpg
        v2-47a2517f2ea35c3da75e6eef052d4bd1_720w.jpg
    授权之后,选择刚才创建的 OBS 桶,设置存储数据的目录和文件名字。
    存储的数据可以直接转存 JSON 数据到 OBS 存储桶,也可以存放成 CSV 文件到存储桶。
    如果选择存储 JSON,就是直接将设备上传的数据存放到 OBS 存储里,如果选择存储 CSV 文件,可以自己选择需要存储的字段。 下面我演示一下存储成 CSV 文件时如何进行设置。 (选择 JSON 文件不需要进行任何设置,直接将设备上传的数据 JSON 存储进去了)。
        v2-3e3ee6c21fd888883bac196602db2f88_720w.jpg
    下面设置转发的字段: (如果提示没有授权,点击授权即可)
    这个转发字段就是表示需要存放的数据是那些,对于路灯而言,肯定是需要存储上报的温度、湿度、电量、光照强度的属性的。 这些属性在创建产品的时候设置,设备上报的也是这些属性。
    这是设备上传一次云平台的完整 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
  • 复制代码

    下面是我设置好,转发存储的字段:
        v2-d29eba3432ce1c010a83950e9f626cf9_720w.jpg
    确定后,点击设置完成。
        v2-0a17be4873550bb4567bf38c9943897e_720w.jpg
    (3)测试规则
    设置好之后,在设置转发目标的页面点击测试。
        v2-10ef78fc00c685adca8389079ffe0f8d_720w.jpg
    输入数据模板,然后点击连通性测试。如果测试结果显示成功,说明整体流程没有问题了。
        v2-100c3076d8bd536dfde3b658f362d756_720w.jpg
    (4)启用规则
    设置完成后,点击启用规则。
        v2-bae323c13bf78030a643811835444bd7_720w.jpg
        v2-6a2a630935dbb284cd1435651a18c9d2_720w.jpg
    (5)数据上报测试
    为了验证转发规则是否生效,接下来使用 MQTT 客户端多上报几次数据到云平台。
        v2-de0934f789900f82a0657f611ca29296_720w.jpg
    上传之后,打开 OBS 存储桶的控制台页面。打开转发规则存储的 OBS 桶。
        v2-5ea6ace72d9d0e00d8d68963953a6efe_720w.jpg
    找到存储数据的文件,点击下载。
        v2-675040607d9b63ac02cc2d77a10d7672_720w.jpg
    下载下来,打开可以看到存储的数据:
        v2-5308fbfcb032c3331d7a0b9bab407d5f_720w.jpg
    到此,数据转发,存储已经成功了。

      五、应用侧 - 可视化大屏开发
    对于数据的可视化显示,华为云提供了(Data Lake Visualization)一站式数据可视化平台,数据可视化服务 (DLV) 可以从 OBS 文件读取数据呈现为可视化报表,实现数据的可视化显示。
    下面是华为云的 DLV 大屏从 OBS 读取数据显示的流程:
        v2-712a1543d30dadd98481aedf4cc2dbaf_720w.jpg
    当前我这里的需求是需要在本地自己设计界面显示数据,没有采用华为云的 DLV 大屏,如果自己本地软件需要显示设备上传的数据,就需要使用华为云物联网的平台的应用侧 API 接口,读取设备上传的数据进行,本地进行显示;如果需要历史数据,可以读取 OBS 存储桶里存储的数据进行显示。
      5.1 应用侧接口
    帮助文档地址: https://support.huaweicloud.com/usermanual-iothub/iot_01_0045.html
        v2-e7ccaaa0399095c5506b36d01578ec74_720w.jpg
      5.2 查询设备影子数据接口
    应用侧接口可以查询发送指令给设备查询属性,也可以读取设备的影子数据。
    【1】查询设备查询:这个是实时查询,相当于应用侧接口发送指令给在线设备,设备收到指令,将当前最新的数据再上传。这个需要保证设备在线,离线是无法调用的。
    【2】影子数据:影子数据相当于保存设备上传的最新一次数据。 读取读取影子设备数据,是不需要设备在线。
    (1)接口 URI 地址
        v2-795a16f7f0e14f1da6399bc543cefc4a_720w.jpg
    (2)请求参数说明
        v2-108f427cea8c131e52baec7aff8c5b0b_720w.jpg
    (3)响应参数
        v2-1f84c06282a2a2aab95a6c7c0f7090ef_720w.jpg
    (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)错误码
        v2-502fed6a0672cae8df45a15e5ad70e5d_720w.jpg
      5.3 接口调试
    在线调试地址:https://apiexplorer.developer.huaweicloud.com/apiexplorer/debug?product=IoTDA&api=ShowDeviceShadow
    下面是调试影子 数据查询接口,查询设备影子数据: 右边返回的是设备上传的最新数据。
        v2-a5a054f1c2e091686120e168d22ec5b8_720w.jpg

      5.4 接口总结
      
    请求地址:
  • 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 帐户

    鼠标悬停在右上角的用户名称上,弹出下拉框,选择统一身份认证。
        v2-450d6bc2211f02829b2c6796d81ef37b_720w.jpg
    (2)选择创建用户
        v2-ab8492b09df24ddb56b4c00224ff87d5_720w.jpg
        v2-87173dfbe3acab3c5541bdffabd8ddb2_720w.jpg
        v2-adcc5c37ee6a7e3f4e41046bfa84766c_720w.jpg
        v2-aec9e70ff55508fb58d9ff0c2897b14a_720w.jpg
    (3)使用调试接口测试获取 oken
    调试接口地址: https://apiexplorer.developer.huaweicloud.com/apiexplorer/debug?product=IAM&api=KeystoneCreateUserTokenByPassword
        v2-b3b7976decaceafb12ef12254626c314_720w.jpg
    右边响应头里的 X-Subject-Token 就是获取的 token。
        v2-a75ff0f5801daee7df8e49ad16f16487_720w.jpg
    (4)上面的这些账户名称从哪里获取?
        v2-d4ca66057496495f35fa92f4f6c3a02a_720w.jpg
        v2-f1b1b2fe45f448b9981fa3b759af3e01_720w.jpg
    (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 界面设计最终效果
        v2-6da30f978b2fd4d5eefb105fe8f6de65_720w.jpg

      六、硬件部分
      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进行网络传输 第七章 - 一致性目标