前言

最近我在钻研 COAP 协定,在尝试应用 COAP 协定找了到了一个能在ESP32上用的coap-simple库,尽管库并不欠缺对于loop解决的局部应该是没写完,然而对于第一次接触COAP的敌人来说更容易了解,不便学习,须要的敌人能够去上面下载:

https://github.com/hirotakast...

我之前应用 IOT PI 的 COAP 能和 PC node coap 通信,然而因为 coap-simple 库不欠缺,失常的无奈与 node coap 通信,只能和同样应用这个库设施通信,这次就来尝试 ESP32 之间的 M2M 通信。

获取库

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

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

https://github.com/espressif/...

具体应用能够参考的我 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 。