EPS01通过AT指令上云(ONENET)

​ 为了方便嵌入式开发,通常需要物联网进行一些应用上传云端进行类似数据检测的环节,因此学习物联网云端通信是很有必要的。

WIFI模块烧录固件

​ 为了能够成功和云端实现通信,我们就需要进行联网操作,那么不可避免的就需要用到WIFI模块,因此我们将采用ESP8266进行WIFI联网功能上云。

1.下载固件

​ 固件包在安信可官网AT固件汇总 | 安信可科技!在这里插入图片描述

2.烧录固件

​ 打开下载工具可以再官网下载,按照如图进行配置,下载即可

在这里插入图片描述

​ 下载完成后,我们可以进行AT测试,默认波特率为115200,进行测试,返回OK,则下载固件成功

在这里插入图片描述

云平台配置教程

​ 因为目前阿里云平台需要付费了,所以我们使用onenet云来实现物联网

​ 具体教程可以参考文档OneNET - 中国移动物联网开放平台,视频教程参考B站搜索中移物联网

​ 快速入门视频【新版OneNET云平台接入详解(数据流协议)】https://www.bilibili.com/video/BV1g21RYFEQX?vd_source=100abe451980ff9dd4a5647d880af0c5

​ 首先进入开发者中心,点击产品开发创建产品,其中产品是由多个设备组成的,因此我们还需要创建设备,点击设备管理进行添加创建
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

​ 其中产品的创建的配置要如图,使用数据流传输和WIFI联网

在这里插入图片描述

云端调试

​ 进行云端调试的目的是检测我们的WIFI模块能不能和云端进行正确的通信,我们使用mqtt.fx来进行测试,因为mqtt.fx最新版本收费,因此我们下载1.71版本MQTT客户端MQTT.fx下载和使用,mqttfx-1.7.1-windows-x64.exe -开发工具下载 -时代Java,与您同行!来进行免费的测试

​ 同时也可以使用国产的mqttx来测试中国移动物联网开放平台OneNET学习笔记(2)——设备接入测试(MQTT协议)OneNET Studio篇_onenet mqtt测试连接-CSDN博客

界面介绍

​ 主界面

在这里插入图片描述

​ 通用设置界面

在这里插入图片描述

​ 用户信息设置界面

在这里插入图片描述

​ 安全证书设置

在这里插入图片描述

​ 网络代理设置

在这里插入图片描述

​ 遗嘱设置

在这里插入图片描述

使用MQTT.FX测试通信

​ 具体调试方案参考与OneNET服务器连接初体验_token中et怎么获取-CSDN博客

​ 首先对mqtt.fx进行配置,需要配置的项如下

填入参数
Broker Address 接入地址studio-mqtt.heclouds.com
Broker Port 接入的端口
Client ID 设备名称
User Name 产品ID
Password 生成的token

在这里插入图片描述

生成token方法

​ 利用生成token的上位机来实现生成token,参数具体如下

参数 填入
res products/{pid}/devices/{device_name} 由产品ID+设备名称组成
et 时间戳 必须要大于当前时间(https://tool.lu/timestamp/)
key 产品的access_key

在这里插入图片描述

连接好后效果,mqtt.fx连接成功,同时云平台设备在线

在这里插入图片描述

在这里插入图片描述

向云平台发送数据

​ 博客参考:MQTT协议(新版)接入OneNET平台_通过mqtt协议连接onenet-CSDN博客

​ 文档参考:协议规范_开发者文档_OneNET

​ MQTT 通过定义一组特定的 topic(主题),让设备和云端能够互相通信。简单来说,设备可以通过这些 topic 发送消息(publish)或接收消息(subscribe),从而实现各种功能,比如上传数据、接收命令等。

​ 简而言之,topic 就像一个“地址”或“频道”,设备和云端通过它来交换消息。

数据点topic簇

​ 设备可以通过数据点 topic 簇上传数据存储并即时获取数据存储结果

  • 簇中topic 以 $sys/{pid}/{device-name}/dp 开头

  • 通过publish上传数据时,payload需要满足平台约定数据格式

  • 支持一次上报多条数据,支持设备自带时间戳上报

  • 即时通知数据处理结果(需订阅)

    系统topic 用途 QoS 可订阅 可发布
    $sys/{pid}/{device-name}/dp/post/json 设备上传数据点 0/1
    $sys/{pid}/{device-name}/dp/post/json/accepted 系统通知"设备上传数据点成功" 0
    $sys/{pid}/{device-name}/dp/post/json/rejected 系统通知"设备上传数据点失败" 0

topic格式:

//上传定义规则topic
$sys/{产品ID}/{设备名称}/dp/post/json
    
//订阅定义规则topic
$sys/{pid}/{device-name}/dp/post/json/+

先要订阅才可以,上传消息

具体流程图示

1.订阅

在这里插入图片描述

2.上传

在这里插入图片描述

数据上传格式

​ 数据的上传是以JISON格式上传,具体框架如下:

{
    "id": 设备ID,
    "dp": {
        "数据点名称1": [{"v":1}],
        "数据点名称2": [{"v":2}],
        ...
    }
}
//查看订阅面板,是否成功上传消息

eg:

{
    "id": 001,        
    "dp": {             
        "temperatrue": [{     
            "v": 20,       
        }],
        "power": [{     
            "v": 3.5      
        }]
    }
}
参数 类型 是否必填 说明
id int 消息ID,大于0的整数,数值范围为,4字节有符号数取值范围
dp object 数据点内容,key-value格式,key为数据流名称,value为list格式的一个或者多个数据点值
v - 数据点值,可以为int/float/string/object多种格式

​ 消息发送并处理完成后,平台通过系统 topic 向设备发送上传结果通知,若设备订阅了该 topic,则能收到该消息

场景 Topic 数据格式
成功 $sys/${pid}/${device-name}/dp/post/json/accepted {"id": 123}
失败:无法解析合法 ID $sys/${pid}/${device-name}/dp/post/json/rejected {"id": -1, "err_code": 98, "err_msg": "Illegal Data"}
失败:解析合法 ID 但有其他格式问题 $sys/${pid}/${device-name}/dp/post/json/rejected {"id": 123, "err_code": 98, "err_msg": "Illegal Data"}

说明:

  • ${pid}${device-name} 是产品 ID 和设备名称的占位符,需替换为实际值。
  • 在成功场景和失败但能解析合法 ID 的场景中,"id" 对应数据上报时的消息 ID。
  • 失败场景中的 "err_code""err_msg" 提供错误详情,示例值为 98"Illegal Data"

云平台发送数据

​ 云平台下发命令也是通过topic簇的方式

​ 建立设备连接之后,获取设备命令前,必须先订阅系统命令topic

系统命令簇
//订阅
$sys/{pid}/{device-name}/cmd/request/{cmdid}
//具体订阅可以是下面两种方式都可以
$sys/{pid}/{device-name}/cmd/request/+		
$sys/{pid}/{device-name}/cmd/#

//发送回答
$sys/{pid}/{device-name}/cmd/response/{cmdid}

参数介绍

参数 介绍
{pid} 产品id
{device-name} 设备名称
{cmdid} 接收到的命令码(eg:2e15b771-a1c9-4205-87e4-0570b898757d)
具体流程图示

1.下发命令

在这里插入图片描述

2.接收到命令

在这里插入图片描述

3.发送应答

在这里插入图片描述

4.云端接收到应答

在这里插入图片描述

云端数据接收格式
  • 命令请求body数据长度必须 小于1k
  • 设备应答时 payload 长度必须 小于1k

​ 当发送命令后收到返回应答

{
  "errno": 0,
  "error": "success",
  "data": {
    "cmd_uuid": "33ffeaa0-e5f1-49d6-a626-ff",
    "cmd_resp": "YWJjZA=="
  }
}

其中应答消息为YWJjZA==,但是这是Base64格式,需要转换成对应的格式去解码

返回参数说明

参数名称 类型 说明
errno int 错误码
error string 错误描述
cmd_uuid string 命令ID
cmd_resp string 设备应答内容,base64编码格式

返回错误码

错误码 错误描述 说明
12 device not found 设备不存在
13 device not online 设备不在线
15 sync cmd timeout 设备命令应答超时

eg:

{
    "errno": 15,
    "error": "sync cmd timeout"
}

MDK测试

​ 参考链接:ESP8266/01s AT指令连接OneNET MQTT篇上报和下发数据_onenet mqtt 命令下发-CSDN博客

​ 我们首先是要建立起ESP8266和ONENET云端的连接,直接使用一下AT指令即可

AT指令

​ 参考链接:MQTT AT 命令集 - ESP32 - — ESP-AT 用户指南 latest 文档

​ AT指令提供了一套统一的命令格式和响应机制。AT指令将复杂的操作封装成了简单的命令,便于直接简单上手操作

下面简单介绍一些常用的AT指令:

基础AT指令:

AT指令 响应 解释
AT OK 测试 AT 启动
AT+RST OK 重启模块

Wi-Fi AT 命令集

AT指令 响应 解释
AT+CWJAP=“WIFI NAME”,“SSID” OK及连接信息 连接对应的WIFI
AT+CWJAP? OK及AP信息 查询AP信息
AT+CWQAP OK 断开AP连接
AT+CWDHCP=1,1 OK 启动DHCP,自动分配IP地址
AT+CWMODE=x OK x: 0: 无 Wi-Fi 模式,并且关闭 Wi-Fi RF 1: Station 模式 2: SoftAP 模式 3: SoftAP+Station 模式

MQTT AT指令

AT指令 响应 解释
AT+MQTTUSERCFG=,,<“client_id”>,<“username”>,<“password”>,<cert_key_ID>,<CA_ID>,<“path”> OK 设置MQTT用户属性
AT+MQTTCONN=,<“host”>,, OK 连接MQTT端口
AT+MQTTSUB=,<“topic”>, OK MQTT订阅主题(支持最多订阅10个主题)
AT+MQTTPUB=,<“topic”>,<“data”>,, OK 发布MQTT消息字符串格式,最长度256字节
AT+MQTTPUBRAW=,<“topic”>,,, OK及> 发送MQTT消息,当接收到>时直接发送数据

连接ONENET云端

​ 具体的实现步骤应该如下所示:

1.确保ESP8266处于可通信状态:
AT
解释:发送AT测试指令以检查模块是否响应。

2.复位ESP8266模块:
AT+RST 
解释:发送AT+RST复位指令以确保模块从稳定状态开始。

3.设置ESP8266为Station模式:
AT+CWMODE=1 
解释:发送AT+CWMODE=1设置模块为station模式,这样才能连接到WiFi网络。

4.启动DHCP服务:
AT+CWDHCP=1,1
解释:发送AT+CWDHCP=1,1以启动DHCP服务,使得ESP8266可以从路由器获取IP地址。

5.断开WiFi网络:
AT+CWQAP
解释:目的是防止当ESP8266自动上电连接到WiFi,会卡在连接到WiFi。

5.连接到WiFi网络:
AT+CWJAP="Redmi K60 Ultra","ohb5201314.."
解释:连接到指定的WiFi网络

6.(重点)配置MQTT用户信息:
AT+MQTTUSERCFG=0,1,"test","8Oe8528R03","version=2018-10-31&res=products%2F8Oe8528R03%2Fdevices%2Ftest&et=1772891021&method=md5&sign=fcetEfkSS29c1F4hazKhYw%3D%3D",0,0,"
解释:配置MQTT连接的用户名和密码等信息,这里包含了产品的ID、设备的ID以及签名等认证信息。 需要修改其中的设备名称 产品ID TOKEN

7.建立MQTT连接:
AT+MQTTCONN=0,"mqtts.heclouds.com",1883,1
解释:连接到OneNET的MQTT服务器,其中"mqtts.heclouds.com"是服务器地址,1883是端口号。

具体代码实现:

#include "esp8266.h"
#include "bsp_sys.h"

//WIFI名称与密码
#define ESP8266_WIFI_INFO    "AT+CWJAP=\"Redmi K60 Ultra\",\"5201314..\"\r\n"
//MQTT用户信息 只需要修改设备名称和产品ID以及token
#define MQTT_USER_CFG "AT+MQTTUSERCFG=0,1,\"test\",\"8Oe8528R03\",\"version=2018-10-31&res=products%%2F8Oe8528R03%%2Fdevices%%2Ftest&et=1772891021&method=md5&sign=fcetEfkSS29c1F4hazKhYw%%3D%%3D\",0,0,\"\"\r\n"

unsigned char esp8266_buf[512];
unsigned short esp8266_cnt = 0, esp8266_cntPre = 0;

//==========================================================
//  函数名称: ESP8266_Clear
//  函数功能: 清空缓存
//  入口参数:  无
//  返回参数:  无
//  说明:
//==========================================================
void ESP8266_Clear(void)
{
    memset(esp8266_buf, 0, sizeof(esp8266_buf));
    esp8266_cnt = 0;
}

//==========================================================
//  函数名称: ESP8266_WaitRecive
//  函数功能: 等待接收完成
//  入口参数:  无
//  返回参数:  REV_OK-接收完成    REV_WAIT-接收超时未完成
//  说明:  循环调用检测是否接收完成
//==========================================================
_Bool ESP8266_WaitRecive(void)
{
    if(esp8266_cnt == 0)
        return REV_WAIT;

    if(esp8266_cnt == esp8266_cntPre)
    {
        esp8266_cnt = 0;
        return REV_OK;
    }
    esp8266_cntPre = esp8266_cnt;
    return REV_WAIT;
}

//==========================================================
//  函数名称: ESP8266_SendCmd
//  函数功能: 发送命令
//  入口参数:  cmd:命令
//             res:需要检查的返回指令
//  返回参数:  0-成功 1-失败
//  说明:
//==========================================================
_Bool ESP8266_SendCmd(char *cmd, char *res)
{
    unsigned char timeOut = 250;
    uart_printf(USART2, cmd);
    while(timeOut--)
    {
        if(ESP8266_WaitRecive() == REV_OK)
        {
            if(strstr((const char *)esp8266_buf, res) != NULL)
            {
                ESP8266_Clear();
                return 0;
            }
        }
        Delay_Ms(20);
    }
    return 1;
}

//==========================================================
//  函数名称: ESP8266_Init
//  函数功能: 初始化ESP8266模块,包括:
//            1. AT测试命令
//            2. 复位模块(AT+RST)
//            3. 设置Station模式(AT+CWMODE=1)
//            4. 启动DHCP(AT+CWDHCP=1,1)
//            5. 连接WiFi(AT+CWJAP)
//            6. 配置MQTT用户信息(AT+MQTTUSERCFG)
//            7. 建立MQTT连接(AT+MQTTCONN)
//  入口参数:  无
//  返回参数:  无
//  说明:
//==========================================================
void ESP8266_Init(void)
{
    ESP8266_Clear();

    // 1. AT 测试通信
    printf("1. AT\r\n");
    while(ESP8266_SendCmd("AT\r\n", "OK"))
        Delay_Ms(500);

    // 2. 复位模块
    printf("2. RST\r\n");
    while(ESP8266_SendCmd("AT+RST\r\n", "OK"))
        Delay_Ms(500);

    // 3. 设置Station模式
    printf("3. CWMODE\r\n");
    while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
        Delay_Ms(500);

    // 4. 启动DHCP服务
    printf("4. CWDHCP\r\n");
    while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
        Delay_Ms(500);

    // 5. 断开WiFi连接
    printf("5. CWQAP\r\n");
    while(ESP8266_SendCmd("AT+CWQAP\r\n", "OK"))
        Delay_Ms(500);

    // 6. 连接WiFi网络
    printf("6. CWJAP\r\n");
    while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
        Delay_Ms(500);


    // 7. 配置MQTT用户信息
    printf("7. MQTTUSERCFG\r\n");
    while(ESP8266_SendCmd(MQTT_USER_CFG, "OK"))
    Delay_Ms(500);


    // 8. 建立MQTT连接
    printf("8. MQTTCONN\r\n");
    while(ESP8266_SendCmd("AT+MQTTCONN=0,\"mqtts.heclouds.com\",1883,1\r\n", "OK"))
        Delay_Ms(500);

    printf("ESP8266 Init OK\r\n");
}

向ONENET云端发送数据

​ 我们发送消息首先需要去订阅相应的主题,然后使用相应的AT指令去发送,具体步骤如下:

1.数据收发订阅主题:
AT+MQTTSUB=0,"$sys/8Oe8528R03/test/dp/post/json/+",1
和AT+MQTTSUB=0,"$sys/8Oe8528R03/test/dp/post/json",1
解释:订阅相应的主题以接收来自OneNET平台的消息。
前主题用于接收平台对设备上报属性的响应。
后主题用于接收平台对设备属性设置的命令。


2.上传数据(使用两条数据发送格式):
AT+MQTTPUBRAW=0,"$sys/8Oe8528R03/test/dp/post/json",44,0,0
{"id": 123, "dp": {"temp": [{"v": 27.0}]}}
解释:上传数据到OneNET平台
前者指出要发送的主题以及对应的字节数(包含\r\n)
后者为需要发送的数据,当接收到>就可以开始发送

​ **注意:**在上传数据时,不知道为何,我使用AT+MQTTPUB一条指令发送就会失败,如果要使用这个AT+MQTTPUB的话请注意将数据中的某一些字符转义

#include "bsp_sys.h"
#include "onenet.h"
#include "esp8266.h"

//用于订阅的宏
#define PROID           "产品"
#define DEVICE_NAME     "设备命"

//==========================================================
//  函数名称: OneNET_Subscribe
//  函数功能: 订阅主题
//  入口参数:  无
//  返回参数:  无
//==========================================================
void OneNET_Subscribe()
{
    uint8_t sub1_buf[64];
    uint8_t sub2_buf[64];
    uint8_t sub3_buf[64];
    //订阅数据接收topic
    sprintf(sub1_buf, "AT+MQTTSUB=0,\"$sys/%s/%s/dp/post/json/+\",1\r\n", PROID, DEVICE_NAME);
    while(ESP8266_SendCmd(sub1_buf, "OK"))
        Delay_Ms(500);
    //订阅数据发送topic
    sprintf(sub2_buf, "AT+MQTTSUB=0,\"$sys/%s/%s/dp/post/json\",1\r\n", PROID, DEVICE_NAME);
    while(ESP8266_SendCmd(sub2_buf,"OK"))
        Delay_Ms(500);
    //订阅命令接收topic
    sprintf(sub3_buf, "AT+MQTTSUB=0,\"$sys/%s/%s/cmd/#\",1\r\n", PROID, DEVICE_NAME);
    while(ESP8266_SendCmd(sub3_buf,"OK"))
        Delay_Ms(500);

    printf("OneNET_Subscribe Ok\r\n");
}

//==========================================================
//  函数名称: OneNet_SendData
//  函数功能: 单数据上传数据
//  入口参数:  数据名称,数据值
//  返回参数:  无
//==========================================================
void OneNet_SendData(uint8_t*data_name,float value)
{
    uint8_t data_length;
    uint8_t send_buf1[64];
    uint8_t send_buf2[64];
    ESP8266_Clear();                                // 清空接收缓存
    data_length=sprintf(send_buf1, "{\"id\": 123, \"dp\": {\"%s\": [{\"v\": %.2f}]}}\r\n",data_name,value);
    sprintf(send_buf2, "AT+MQTTPUBRAW=0,\"$sys/%s/%s/dp/post/json\",%d,0,0\r\n", PROID, DEVICE_NAME,data_length);
    while(ESP8266_SendCmd(send_buf2,">"))
        Delay_Ms(250);
    if(ESP8266_SendCmd(send_buf1,"accepted")==0)
        printf("Send Success\r\n");
}

接收云端命令

接收流程

​ 接收云端下发的命令也必须要进行topic的订阅,同时当接收到云端的命令后必须要即时返回以便云端确认收到命令,具体步骤如下:

1.命令收发订阅主题
AT+MQTTSUB=0,"$sys/8Oe8528R03/test/cmd/#",1或者
AT+MQTTSUB=0,"$sys/8Oe8528R03/test/cmd/request/+",1
解释:订阅onenet云的命令接收

2.回复消息
AT+MQTTPUB=0,"$sys/8Oe8528R03/test/cmd/response/{cmdid}","{回复的消息}",0,0
解释:当接收到命令时需要回复,云端才判定为发送成功

​ 主要涉及到的是解析命令以及返回确认收到,以下为ATXCOM测试接收返回数据

//云端下发 This is cmd
+MQTTSUBRECV:0,"$sys/8Oe8528R03/test/cmd/request/59c91b1b-4cb3-4e89-9168-34736fdbe55e",9,Thisiscmd
//返回接收到命令
AT+MQTTPUB=0,"$sys/8Oe8528R03/test/cmd/response/59c91b1b-4cb3-4e89-9168-34736fdbe55e","OK",0,0
    
//云端接收到返回指令
OK
+MQTTSUBRECV:0,"$sys/8Oe8528R03/test/cmd/response/59c91b1b-4cb3-4e89-9168-34736fdbe55e/accepted",0,

​ 以下为底层代码

//==========================================================
//  函数名称: OneNet_ReceiveCmd
//  函数功能:接收下发的命令
//  入口参数:  接收命令串
//  返回参数:  无
//==========================================================
// MQTT 订阅消息处理函数
void OneNet_ReceiveCmd(uint8_t*cmd)
{
    Delay_Ms(100);

    if(ESP8266_WaitRecive() == REV_OK)
    {
        if(strstr((const char *)esp8266_buf, "+MQTTSUBRECV") != NULL)     // 检查是否是 MQTT 订阅接收到的消息
        {

//            printf("MQTT message received: %s\n", esp8266_buf);        // 测试信息

            if(strstr((const char *)esp8266_buf, "/cmd/request/") != NULL)  //进一步检测是否为接收命令
            {
                char *payload_start = NULL;
                char *comma_pos = strrchr((const char *)esp8266_buf, ',');  //寻找最后一个逗号,后面即为命令
                if(comma_pos)
                {
                    payload_start = comma_pos + 1;
                    strcpy((char *)cmd, payload_start);     //拷贝命令
//                    printf("Command: %s\n", payload_start);       //测试接收到的命令

                    // 提取topic
                    char *topic_start = strchr((const char *)esp8266_buf, '"') + 1;
                    char *topic_end = strchr(topic_start, '"');
                    int topic_len = topic_end - topic_start;
                    char topic[150] = {0};
                    strncpy(topic, topic_start, topic_len);

                    // 提取请求 ID
                    char *req_id_start = strstr(topic, "/request/") + 9;
                    char req_id[50] = {0};
                    strcpy(req_id, req_id_start);

                    // 构建响应 AT 命令
                    char response_cmd[200] = {0};
                    sprintf(response_cmd, "AT+MQTTPUB=0,\"$sys/%s/%s/cmd/response/%s\",\"OK\",0,0\r\n", PROID,DEVICE_NAME,req_id);

//                    printf("Sending response: %s\n", response_cmd);   //测试响应指令
                    while(ESP8266_SendCmd(response_cmd, "OK"))  // 复用您现有的发送命令函数
                    Delay_Ms(100);
                    printf("ACK Ok\r\n");
                }
            }
        }
        ESP8266_Clear();  // 假设您有这个清除缓冲区的函数
    }
}
知识点补充

在对数据进行解析时,有几个常用的处理函数:

​ 首先是sscanf函数

int sscanf(const char *str, const char *format, ...);
//str为搜索的字符串,format为指定如何解析字符串的格式控制字符串
//可以用于提取指定格式内的数据
//对于该函数可以直接替换我底层中的使用strchr等函数操作实现更加简单的提取
//返回成功读取的项目数

​ 其次是strchr函数

char *strchr(const char *str, int c);
//str为搜索的字符串,搜索的字符c
//该函数功能是可以实现查找第一次出现的位置,通常可以用于验证字符和查找字符
//返回该字符指针

​ 最后是strrchr函数

char *strchr(const char *str, int c);
//str为搜索的字符串,搜索的字符c
//该函数功能是查找最后一次出现字符的位置,通常可以用于验证字符和查找字符
//返回该字符指针
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐