前言

最近我在研究 COAP 协议,在尝试使用 COAP 协议找了到了一个能在ESP32上用的coap-simple库,虽然库并不完善关于loop处理的部分应该是没写完,但是对于第一次接触COAP的朋友来说更容易理解,方便学习,需要的朋友可以去下面下载:

https://github.com/hirotakaster/CoAP-simple-library

我之前使用 IOT PI 的 COAP 能和 PC node coap 通讯,但是因为 coap-simple 库不完善,正常的无法与 node coap 通讯,只能和同样使用这个库设备通讯,这次就来尝试 ESP32 之间的 M2M 通讯。

获取库

使用 arduino IDE 就能下载到这个库:



如果没有看到这个库,可以去首选项添加一下附加开发板管理器网址:

https://github.com/espressif/arduino-esp32/releases/download/1.0.5/package_esp32_index.json

具体使用可以参考的我 arduino 超详细的开发入门指导 或者直接通过我上面发的 GitHub 网址下载。

代码解析

以下代码为了方便讲解,可能经过了调换了顺序或者裁剪。

这个 demo 是客户端、服务端一体的,只需要注册对应的回调函数就行。

初始化部分

这部分包括了设备初始化,协议初始化等部分,重点在服务器/客户端的回调函数部分。和 SDDC 官方demo类似,在这注册回调函数之后,通过对应的端点找到对应的回调函数。

#include <WiFi.h>
#include <WiFiUdp.h>
#include <coap-simple.h> void setup() {
Serial.begin(115200); WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
} Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP()); // LED State
pinMode(9, OUTPUT);
digitalWrite(9, HIGH);
LEDSTATE = true; // 添加服务器url端点.
// 可以添加多个端点url.
// coap.server(callback_switch, "switch");
// coap.server(callback_env, "env/temp");
// coap.server(callback_env, "env/humidity");
Serial.println("Setup Callback Light");
// 其实就是注册服务器处理回调函数
// 将处理函数指针与url添加到 uri.add 中
coap.server(callback_light, "light"); // 注册客户端响应的回调函数。
// this endpoint is single callback.
Serial.println("Setup Response Callback");
// 很上面一样,其实就是把回调函数指针注册到resp里
coap.response(callback_response); // 使用默认端口5683 启动 coap server/client
coap.start();
} void loop() {
// 作为客户端时向coap服务器发送GET或PUT coap请求.
// 可以发送给另外一个 ESP32
// msgid = coap.put(IPAddress(192, 168, 128, 101), 5683, "light", "0");
// msgid = coap.get(IPAddress(192, 168, 128, 101), 5683, "light"); delay(1000);
coap.loop();
}

回调函数

// CoAP 服务器端点 URL ,对客户端发过来的命令进行处理并且回应
void callback_light(CoapPacket &packet, IPAddress ip, int port)
{
// 这是一个模拟控灯的回调函数,通过接收的命令
Serial.println("[Light] ON/OFF");
Serial.println(packet.messageid); // 发送响应
char p[packet.payloadlen + 1];
memcpy(p, packet.payload, packet.payloadlen);
p[packet.payloadlen] = NULL; String message(p); if (message.equals("0"))
LEDSTATE = false;
else if(message.equals("1"))
LEDSTATE = true; if (LEDSTATE) {
digitalWrite(9, HIGH) ;
Serial.println("[Light] ON"); coap.sendResponse(ip, port, packet.messageid, "1");
} else {
digitalWrite(9, LOW) ;
Serial.println("[Light] OFF");
coap.sendResponse(ip, port, packet.messageid, "0");
}
} // CoAP客户端响应回调
void callback_response(CoapPacket &packet, IPAddress ip, int port)
{
Serial.println("[Coap Response got]"); char p[packet.payloadlen + 1];
memcpy(p, packet.payload, packet.payloadlen);
p[packet.payloadlen] = NULL; Serial.println(p);
}

库代码

报文结构定义:

// 确定消息类型,在 coap 消息层
typedef enum {
COAP_CON = 0, // 可靠传输
COAP_NONCON = 1, // 不可靠传输
COAP_ACK = 2, // 回复
COAP_RESET = 3 // 报文异常后的被动重发请求
} COAP_TYPE;
// 命令执行的动作,在请求/响应层
typedef enum {
COAP_GET = 1,
COAP_POST = 2, // 主动的重发命令
COAP_PUT = 3,
COAP_DELETE = 4
} COAP_METHOD;
// 响应码,相当于函数返回值或者err码之类的,在请求/响应层
typedef enum {
COAP_CREATED = RESPONSE_CODE(2, 1),
COAP_DELETED = RESPONSE_CODE(2, 2),
COAP_VALID = RESPONSE_CODE(2, 3),
COAP_CHANGED = RESPONSE_CODE(2, 4),
COAP_CONTENT = RESPONSE_CODE(2, 5),
COAP_BAD_REQUEST = RESPONSE_CODE(4, 0),
COAP_UNAUTHORIZED = RESPONSE_CODE(4, 1),
COAP_BAD_OPTION = RESPONSE_CODE(4, 2),
COAP_FORBIDDEN = RESPONSE_CODE(4, 3),
COAP_NOT_FOUNT = RESPONSE_CODE(4, 4),
COAP_METHOD_NOT_ALLOWD = RESPONSE_CODE(4, 5),
COAP_NOT_ACCEPTABLE = RESPONSE_CODE(4, 6),
COAP_PRECONDITION_FAILED = RESPONSE_CODE(4, 12),
COAP_REQUEST_ENTITY_TOO_LARGE = RESPONSE_CODE(4, 13),
COAP_UNSUPPORTED_CONTENT_FORMAT = RESPONSE_CODE(4, 15),
COAP_INTERNAL_SERVER_ERROR = RESPONSE_CODE(5, 0),
COAP_NOT_IMPLEMENTED = RESPONSE_CODE(5, 1),
COAP_BAD_GATEWAY = RESPONSE_CODE(5, 2),
COAP_SERVICE_UNAVALIABLE = RESPONSE_CODE(5, 3),
COAP_GATEWAY_TIMEOUT = RESPONSE_CODE(5, 4),
COAP_PROXYING_NOT_SUPPORTED = RESPONSE_CODE(5, 5)
} COAP_RESPONSE_CODE;
// Option 编号 ,在 coap 消息层
typedef enum {
COAP_IF_MATCH = 1,
COAP_URI_HOST = 3,
COAP_E_TAG = 4,
COAP_IF_NONE_MATCH = 5,
COAP_URI_PORT = 7,
COAP_LOCATION_PATH = 8,
COAP_URI_PATH = 11,
COAP_CONTENT_FORMAT = 12,
COAP_MAX_AGE = 14,
COAP_URI_QUERY = 15,
COAP_ACCEPT = 17,
COAP_LOCATION_QUERY = 20,
COAP_PROXY_URI = 35,
COAP_PROXY_SCHEME = 39
} COAP_OPTION_NUMBER;
// 内容类型和 Accept 用于表示CoAP负载的媒体格式
typedef enum {
COAP_NONE = -1,
COAP_TEXT_PLAIN = 0,
COAP_APPLICATION_LINK_FORMAT = 40,
COAP_APPLICATION_XML = 41,
COAP_APPLICATION_OCTET_STREAM = 42,
COAP_APPLICATION_EXI = 47,
COAP_APPLICATION_JSON = 50,
COAP_APPLICATION_CBOR = 60
} COAP_CONTENT_TYPE; class CoapOption {
public:
uint8_t number;
uint8_t length;
uint8_t *buffer;
}; class CoapPacket {
public:
uint8_t type = 0;
uint8_t code = 0;
const uint8_t *token = NULL;
uint8_t tokenlen = 0;
const uint8_t *payload = NULL;
size_t payloadlen = 0;
uint16_t messageid = 0;
uint8_t optionnum = 0;
CoapOption options[COAP_MAX_OPTION_NUM]; void addOption(uint8_t number, uint8_t length, uint8_t *opt_payload);
};

组包发送:

在这里填写包的UDP需要地址,端口,端点等路径相关信息以及 COAP 请求/响应层的信息

uint16_t Coap::send(IPAddress ip, int port, const char *url, COAP_TYPE type, COAP_METHOD method, const uint8_t *token, uint8_t tokenlen, const uint8_t *payload, size_t payloadlen, COAP_CONTENT_TYPE content_type) {

    // make packet
CoapPacket packet; packet.type = type;
packet.code = method;
packet.token = token;
packet.tokenlen = tokenlen;
packet.payload = payload;
packet.payloadlen = payloadlen;
packet.optionnum = 0;
packet.messageid = rand(); // use URI_HOST UIR_PATH
char ipaddress[16] = "";
sprintf(ipaddress, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
packet.addOption(COAP_URI_HOST, strlen(ipaddress), (uint8_t *)ipaddress); // parse url
int idx = 0;
for (int i = 0; i < strlen(url); i++) {
if (url[i] == '/') {
packet.addOption(COAP_URI_PATH, i-idx, (uint8_t *)(url + idx));
idx = i + 1;
}
} if (idx <= strlen(url)) {
packet.addOption(COAP_URI_PATH, strlen(url)-idx, (uint8_t *)(url + idx));
} // if Content-Format option
uint8_t optionBuffer[2] {0};
if (content_type != COAP_NONE) {
optionBuffer[0] = ((uint16_t)content_type & 0xFF00) >> 8;
optionBuffer[1] = ((uint16_t)content_type & 0x00FF) ;
packet.addOption(COAP_CONTENT_FORMAT, 2, optionBuffer);
} // send packet
return this->sendPacket(packet, ip, port);
}

在这里的组装 coap 包消息层的数据

uint16_t Coap::sendPacket(CoapPacket &packet, IPAddress ip, int port) {
uint8_t buffer[COAP_BUF_MAX_SIZE];
uint8_t *p = buffer;
uint16_t running_delta = 0;
uint16_t packetSize = 0; // 制作coap包基头
*p = 0x01 << 6;
*p |= (packet.type & 0x03) << 4;
*p++ |= (packet.tokenlen & 0x0F);
*p++ = packet.code;
*p++ = (packet.messageid >> 8);
*p++ = (packet.messageid & 0xFF);
p = buffer + COAP_HEADER_SIZE;
packetSize += 4; // make token
if (packet.token != NULL && packet.tokenlen <= 0x0F) {
memcpy(p, packet.token, packet.tokenlen);
p += packet.tokenlen;
packetSize += packet.tokenlen;
} // make option header
for (int i = 0; i < packet.optionnum; i++) {
uint32_t optdelta;
uint8_t len, delta; if (packetSize + 5 + packet.options[i].length >= COAP_BUF_MAX_SIZE) {
return 0;
}
optdelta = packet.options[i].number - running_delta;
COAP_OPTION_DELTA(optdelta, &delta);
COAP_OPTION_DELTA((uint32_t)packet.options[i].length, &len); *p++ = (0xFF & (delta << 4 | len));
if (delta == 13) {
*p++ = (optdelta - 13);
packetSize++;
} else if (delta == 14) {
*p++ = ((optdelta - 269) >> 8);
*p++ = (0xFF & (optdelta - 269));
packetSize+=2;
} if (len == 13) {
*p++ = (packet.options[i].length - 13);
packetSize++;
} else if (len == 14) {
*p++ = (packet.options[i].length >> 8);
*p++ = (0xFF & (packet.options[i].length - 269));
packetSize+=2;
} memcpy(p, packet.options[i].buffer, packet.options[i].length);
p += packet.options[i].length;
packetSize += packet.options[i].length + 1;
running_delta = packet.options[i].number;
} // make payload
if (packet.payloadlen > 0) {
if ((packetSize + 1 + packet.payloadlen) >= COAP_BUF_MAX_SIZE) {
return 0;
}
*p++ = 0xFF;
memcpy(p, packet.payload, packet.payloadlen);
packetSize += 1 + packet.payloadlen;
} _udp->beginPacket(ip, port);
_udp->write(buffer, packetSize);
_udp->endPacket(); return packet.messageid;
}

因为这个库解包的loop部分没做完所以这里就先不说了

结果展示

COAP 客户端发送了ID 为20125,24157,12868的三个消息,然后服务器端返回了这三个消息,并带上了数据,客户端也got 到了需要的数据。



总结

感觉很怪?怪就对了,这个 demo 并不完善,只是这个库比较简单方便理解,同时有一个基本框架,看懂这个代码更容易理解 COAP 。

COAP协议 - arduino ESP32 M2M(端对端)通讯与代码详解的更多相关文章

  1. MQTT协议 - arduino ESP32 通过精灵一号 MQTT Broker 进行通讯的代码详解

    前言 之前研究了一段时间的 COAP 协议结果爱智那边没有测试工具,然后 arduino 也没有找到合适的库,我懒癌发作也懒得修这库,就只能非常尴尬先暂时放一放了.不过我在 爱智APP -> 设 ...

  2. 理论经典:TCP协议的3次握手与4次挥手过程详解

    1.前言 尽管TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务.TCP提供一种面向连接的.可靠的字节流服务. 面向连接意味着两个使用TCP的应用(通常是一个客户和一 ...

  3. TCP协议的3次握手与4次挥手过程详解

    1.前言 尽管TCP和UDP都使用相同的网络层(IP),TCP却向应用层提供与UDP完全不同的服务.TCP提供一种面向连接的.可靠的字节流服务. 面向连接意味着两个使用TCP的应用(通常是一个客户和一 ...

  4. 新手入门:史上最全Web端即时通讯技术原理详解

    前言 有关IM(InstantMessaging)聊天应用(如:微信,QQ).消息推送技术(如:现今移动端APP标配的消息推送模块)等即时通讯应用场景下,大多数都是桌面应用程序或者native应用较为 ...

  5. Web端即时通讯技术原理详解

    前言 有关IM(InstantMessaging)聊天应用(如:微信,QQ).消息推送技术(如:现今移动端APP标配的消息推送模块)等即时通讯应用场景下,大多数都是桌面应用程序或者native应用较为 ...

  6. centos 6.5环境利用iscsi搭建SAN网络存储服务及服务端target和客户端initiator配置详解

    一.简介 iSCSI(internet SCSI)技术由IBM公司研究开发,是一个供硬件设备使用的.可以在IP协议的上层运行的SCSI指令集,这种指令集合可以实现在IP网络上运行SCSI协议,使其能够 ...

  7. PHP-Socket服务端客户端发送接收通信实例详解

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://fighter.blog.51cto.com/1318618/1533957 So ...

  8. 新手入门贴:史上最全Web端即时通讯技术原理详解

    关于IM(InstantMessaging)即时通信类软件(如微信,QQ),大多数都是桌面应用程序或者native应用较为流行,而网上关于原生IM或桌面IM软件类的通信原理介绍也较多,此处不再赘述.而 ...

  9. 四、frp内网穿透服务端frps.ini各配置参数详解

    [必须]标识头[common]是不可或缺的部分 [必须]服务器IPbind_addr = 0.0.0.00.0.0.0为服务器全局所有IP可用,假如你的服务器有多个IP则可以这样做,或者填写为指定其中 ...

随机推荐

  1. Bzoj P2212 [Poi2011]Tree Rotations | 线段树合并

    题目链接 通过观察与思考,我们可以发现,交换一个结点的两棵子树,只对这两棵子树内的节点的逆序对个数有影响,对这两棵子树以外的节点是没有影响的.嗯,然后呢?(っ•̀ω•́)っ 然后,我们就可以对于每一个 ...

  2. hdu 3635 Dragon Balls(并查集)

    题意: N个城市,每个城市有一个龙珠. 两个操作: 1.T A B:A城市的所有龙珠转移到B城市. 2.Q A:输出第A颗龙珠所在的城市,这个城市里所有的龙珠个数,第A颗龙珠总共到目前为止被转移了多少 ...

  3. Centos 7 局域网 yum 源搭建

    一.需求及实现方式介绍: 需求:现在各个软件版本更新迭代很快,在我们部署一套集群(比如:openstack)后,如果过一段时间想扩展集群时发现软件版本早已迭代更新,安装后导致和现有环境或多或少不兼容, ...

  4. 用STM32定时器中断产生PWM控制步进电机

    控制步进电机可以使用PWM.定时器中断.延时,这里用的就是定时器中断来让它转动. 一.硬件部分1.使用的硬件板子用的是正点原子的STM32F103 mini板,驱动器是DM420(DM420驱动器资料 ...

  5. win10各版本激活码

    win + x进入Power shell(管理员),依次输入 Win10专业版 slmgr.vbs /upk slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX slmg ...

  6. linux下安装Git并生成SSH key

    系统:contens7.4 1.下载源码解压 wget https://github.com/git/git/archive/v2.3.0.zip unzip v2.3.0.zip cd git-2. ...

  7. java中使用Process执行linux命令

    代码如下 BufferedReader reader = null; String cmd = "netstat -anp|grep :8080";//命令中有管道符 | 需要如下 ...

  8. Python 官方研讨会:彻底移除 GIL 真的可行么?

    作者:Łukasz Langa 译者:豌豆花下猫,来源:Python猫 原文:https://lukasz.langa.pl/5d044f91-49c1-4170-aed1-62b6763e6ad0 ...

  9. Django笔记&教程 1-1 一 新建项目

    Django 自学笔记兼学习教程第1章第1节--一 新建项目 点击查看教程总目录 1- 命令行新建Django项目 新建项目命令(project_name处为项目名) django-admin sta ...

  10. ES6--ES12笔记整理(1)

    一.let const 五个共同特点 不允许重复声明 块级作用域 不存在变量提升 不影响作用域链 暂时性死区---在代码块内,使用let/const命令声明变量之前,该变量都是不可用的.这在语法上,称 ...