COAP协议 - arduino ESP32 M2M(端对端)通讯与代码详解

知识的价值不在于占有,而在于使用。这篇文章主要讲述COAP协议 - arduino ESP32 M2M(端对端)通讯与代码详解相关的知识,希望能为你提供帮助。
@TOC
前言最近我在研究 COAP 协议,在尝试使用 COAP 协议找了到了一个能在ESP32上用的coap-simple库,虽然库并不完善关于loop处理的部分应该是没写完,但是对于第一次接触COAP的朋友来说更容易理解,方便学习,需要的朋友可以去下面下载:
我之前使用 IOT PI 的 COAP 能和 PC node coap 通讯,但是因为 coap-simple 库不完善,正常的无法与 node coap 通讯,只能和同样使用这个库设备通讯,这次就来尝试 ESP32 之间的 M2M 通讯。
获取库使用 arduino IDE 就能下载到这个库:

COAP协议 - arduino ESP32 M2M(端对端)通讯与代码详解

文章图片

如果没有看到这个库,可以去首选项添加一下附加开发板管理器网址:
具体使用可以参考的我 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 到了需要的数据。
COAP协议 - arduino ESP32 M2M(端对端)通讯与代码详解

文章图片

COAP协议 - arduino ESP32 M2M(端对端)通讯与代码详解

文章图片

总结【COAP协议 - arduino ESP32 M2M(端对端)通讯与代码详解】感觉很怪?怪就对了,这个 demo 并不完善,只是这个库比较简单方便理解,同时有一个基本框架,看懂这个代码更容易理解 COAP 。

    推荐阅读