espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
13.34k stars 7.36k forks source link

CoAP GET returns ASCII 30 or char '0' #10347

Open 97Cweb opened 5 days ago

97Cweb commented 5 days ago

Board

ESP32-C6 to ESP32-C6

Device Description

Waveshare ESP32-C6 dev board

Hardware Configuration

no

Version

latest development Release Candidate (RC-X)

IDE Name

Arduino IDE

Operating System

Windows 11

Flash frequency

80MHz

PSRAM enabled

yes

Upload speed

921600

Description

Cannot get a response from the Get command of coap. It returns 30 instead of a string

Sketch

2 Long sketches, need both parts, one is leader, the other is joiner

#include "OThreadCLI.h"
#include "OThreadCLI_Util.h"

#define OT_CHANNEL            "24"
#define OT_NETWORK_KEY        "00112233445566778899aabbccddeeff"
#define OT_MCAST_ADDR         "ff05::abcd"
#define OT_COAP_RESOURCE      "json"
#define USER_BUTTON           9

String joinerAddress = "";

const char *otSetupLeader[] = {
  // -- clear/disable all
  // stop CoAP
  "coap", "stop",
  // stop Thread
  "thread", "stop",
  // stop the interface
  "ifconfig", "down",
  // clear the dataset
  "dataset", "clear",
  // -- set dataset
  // create a new complete dataset with random data
  "dataset", "init new",
  // set the channel
  "dataset channel", OT_CHANNEL,
  // set the network key
  "dataset networkkey", OT_NETWORK_KEY,
  // commit the dataset
  "dataset", "commit active",
  // -- network start
  // start the interface
  "ifconfig", "up",
  // start the Thread network
  "thread", "start"
};

const char *otCoapHub[] = {
  // -- create a multicast IPv6 Address for this device
  "ipmaddr add", OT_MCAST_ADDR,
  // -- start and create a CoAP resource
  // start CoAP as server
  "coap", "start",
  // create a CoAP resource
  "coap resource", OT_COAP_RESOURCE,
  // set the CoAP resource initial value
  "coap set", "",
};

bool otDeviceSetup(const char **otSetupCmds, uint8_t nCmds1, const char **otCoapCmds, uint8_t nCmds2, ot_device_role_t expectedRole) {
  Serial.println("Starting OpenThread.");
  Serial.println("Running as HUB");
  uint8_t i;
  for (i = 0; i < nCmds1; i++) {
    if (!otExecCommand(otSetupCmds[i * 2], otSetupCmds[i * 2 + 1])) {
      break;
    }
  }
  if (i != nCmds1) {
    log_e("Sorry, OpenThread Network setup failed!");
    rgbLedWrite(RGB_BUILTIN, 255, 0, 0);  // RED ... failed!
    return false;
  }
  Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
  // wait for the expected Device Role to start
  uint8_t tries = 24;  // 24 x 2.5 sec = 1 min
  while (tries && otGetDeviceRole() != expectedRole) {
    Serial.print(".");
    delay(2500);
    tries--;
  }
  Serial.println();
  if (!tries) {
    log_e("Sorry, Device Role failed by timeout! Current Role: %s.", otGetStringDeviceRole());
    rgbLedWrite(RGB_BUILTIN, 255, 0, 0);  // RED ... failed!
    return false;
  }
  Serial.printf("Device is %s.\r\n", otGetStringDeviceRole());
  for (i = 0; i < nCmds2; i++) {
    if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
      break;
    }
  }
  if (i != nCmds2) {
    log_e("Sorry, OpenThread CoAP setup failed!");
    rgbLedWrite(RGB_BUILTIN, 0, 255, 0);  // RED ... failed!
    return false;
  }
  Serial.println("OpenThread setup done. Node is ready.");
  // all fine! LED goes Green
  rgbLedWrite(RGB_BUILTIN, 64, 0, 8);  // GREEN ... HUB is ready!
  return true;
}

void setupNode() {
  // tries to set the Thread Network node and only returns when succeeded
  bool startedCorrectly = false;
  while (!startedCorrectly) {
    startedCorrectly |=
      otDeviceSetup(otSetupLeader, sizeof(otSetupLeader) / sizeof(char *) / 2, otCoapHub, sizeof(otCoapHub) / sizeof(char *) / 2, OT_ROLE_LEADER);
    if (!startedCorrectly) {
      Serial.println("Setup Failed...\r\nTrying again...");
    }
  }
}

void hexToAscii(char* hexStr, char* asciiStr) {
  // Convert hex string to ASCII string
  int len = strlen(hexStr);
  for (int i = 0; i < len; i += 2) {
    // Convert each pair of hex digits to an ASCII character
    char hexChar[3] = { hexStr[i], hexStr[i + 1], '\0' };
    asciiStr[i / 2] = (char)strtol(hexChar, NULL, 16);
  }
  asciiStr[len / 2] = '\0';  // Null-terminate the result
}

// Send CoAP GET to the Joiner
bool sendCoapGet(String joinerAddress) {
  String coapMsg = "coap get ";
  coapMsg += joinerAddress;
  coapMsg += " ";
  coapMsg += OT_COAP_RESOURCE;
  coapMsg += " con";

  Serial.printf("Sending CoAP GET CMD: [%s]\n", coapMsg.c_str());
  OThreadCLI.println(coapMsg.c_str());

  char cliResp[1024];
  bool gotDone = false, gotConfirmation = false;
  uint8_t tries = 5;

  while (tries && !(gotDone && gotConfirmation)) {
    size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
    cliResp[len - 1] = '\0';
    Serial.printf("Try[%d]::MSG[%s]\n", tries, cliResp);
    if (strlen(cliResp)) {
      if (!strncmp(cliResp, "coap response from", 18)) {
        gotConfirmation = true;
        Serial.println("CoAP response received.");

      }
      if (!strncmp(cliResp, "Done", 4)) {
        gotDone = true;
        Serial.println("Command executed successfully.");
      }
      // Check for JSON payload in the response
      if (strstr(cliResp, "2.05 Content")) {
        String jsonResponse = String(cliResp).substring(String(cliResp).indexOf("Content") + 8);
        Serial.println("Received JSON response:");
        Serial.println(jsonResponse);
      }
    }
    tries--;
  }

  return gotDone && gotConfirmation;
}

// this function is used by the Hub mode to listen for CoAP frames from the joiner Nodes
void otCOAPListen() {
  // waits for the client to send a CoAP request
  char cliResp[2048] = {0};
  char asciiPayload[1024] = {0};  // Buffer for decoded payload
  size_t len = OThreadCLI.readBytesUntil('\0', cliResp, sizeof(cliResp));
  cliResp[len - 1] = '\0';
  if (strlen(cliResp)) {
    String sResp(cliResp);
    Serial.println(sResp);
    if (sResp.startsWith("coap request from") && sResp.indexOf("PUT") > 0) {
      // Assuming the hex payload starts after the "with payload:" part
      int payloadIndex = sResp.indexOf("with payload: ") + 14;  // Adjust as needed
      String hexPayload = sResp.substring(payloadIndex);

      // Convert the hex payload to ASCII
      hexPayload.toCharArray(cliResp, sizeof(cliResp));  // Convert to char array
      hexToAscii(cliResp, asciiPayload);  // Decode hex to ASCII

      // cliResp shall be something like:
      // "coap request from fd0c:94df:f1ae:b39a:ec47:ec6d:15e8:804a PUT with payload: 30"
      log_i("Msg[%s]", asciiPayload);
    }
  }
}

// Detect button press on GPIO 9
void checkUserButton() {
  static unsigned long lastPress = 0;
  const unsigned long debounceTime = 500;

  if (millis() - lastPress > debounceTime && digitalRead(USER_BUTTON) == LOW) {
    lastPress = millis();
    if (joinerAddress.length() > 0) {
      sendCoapGet(joinerAddress);  // Send GET request to the joiner
    } else {
      Serial.println("No Joiner address found! Provide address via Serial.");
    }
  }
}

void setup() {
  Serial.begin(115200);
  // LED starts RED, indicating not connected to Thread network.
  rgbLedWrite(RGB_BUILTIN, 64, 0, 0);
  OThreadCLI.begin(false);     // No AutoStart is necessary
  OThreadCLI.setTimeout(250);  // waits 250ms for the OpenThread CLI response
  setupNode();
  // LED goes Green when all is ready and Red when failed.

   pinMode(USER_BUTTON, INPUT_PULLUP);
}

void loop() {
  otCOAPListen();
  checkUserButton();
  // Read joiner address from Serial input
  if (Serial.available()) {
    joinerAddress = Serial.readStringUntil('\n');
    joinerAddress.trim();  // Remove extra spaces or newlines
    Serial.print("Joiner address set to: ");
    Serial.println(joinerAddress);
  }
  delay(10);
}

#include "OThreadCLI.h"
#include "OThreadCLI_Util.h"
#include <ArduinoJson.h>

#define USER_BUTTON       9  // C6/H2 Boot button
#define OT_CHANNEL        "24"
#define OT_NETWORK_KEY    "00112233445566778899aabbccddeeff"
#define OT_MCAST_ADDR     "ff05::abcd"
#define OT_COAP_RESOURCE  "json"

JsonDocument train;
String serializedJson = "";

const char *otSetupChild[] = {
  // -- clear/disable all
  // stop CoAP
  "coap", "stop",
  // stop Thread
  "thread", "stop",
  // stop the interface
  "ifconfig", "down",
  // clear the dataset
  "dataset", "clear",
  // -- set dataset
  // set the channel
  "dataset channel", OT_CHANNEL,
  // set the network key
  "dataset networkkey", OT_NETWORK_KEY,
  // commit the dataset
  "dataset", "commit active",
  // -- network start
  // start the interface
  "ifconfig", "up",
  // start the Thread network
  "thread", "start"
};

const char *otCoapJoiner[] = {
  // -- start CoAP as client
  "coap", "start",
  "coap resource", "json"
};

bool otDeviceSetup(
  const char **otSetupCmds, uint8_t nCmds1, const char **otCoapCmds, uint8_t nCmds2, ot_device_role_t expectedRole1, ot_device_role_t expectedRole2) {
  Serial.println("Starting OpenThread.");
  Serial.println("Running as Joiner");
  uint8_t i;
  for (i = 0; i < nCmds1; i++) {
    if (!otExecCommand(otSetupCmds[i * 2], otSetupCmds[i * 2 + 1])) {
      break;
    }
  }
  if (i != nCmds1) {
    log_e("Sorry, OpenThread Network setup failed!");
    rgbLedWrite(RGB_BUILTIN, 255, 0, 0);  // RED ... failed!
    return false;
  }
  Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
  // wait for the expected Device Role to start
  uint8_t tries = 24;  // 24 x 2.5 sec = 1 min
  while (tries && otGetDeviceRole() != expectedRole1 && otGetDeviceRole() != expectedRole2) {
    Serial.print(".");
    delay(2500);
    tries--;
  }
  Serial.println();
  if (!tries) {
    log_e("Sorry, Device Role failed by timeout! Current Role: %s.", otGetStringDeviceRole());
    rgbLedWrite(RGB_BUILTIN, 255, 0, 0);  // RED ... failed!
    return false;
  }
  Serial.printf("Device is %s.\r\n", otGetStringDeviceRole());
  for (i = 0; i < nCmds2; i++) {
    if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
      break;
    }
  }
  if (i != nCmds2) {
    log_e("Sorry, OpenThread CoAP setup failed!");
    rgbLedWrite(RGB_BUILTIN, 0, 255, 0);  // RED ... failed!
    return false;
  }
  Serial.println("OpenThread setup done. Node is ready.");
  // all fine! LED goes and stays Blue
  rgbLedWrite(RGB_BUILTIN, 0, 0, 64);  // BLUE ... Joiner is ready!
  return true;
}

void setupNode() {
  // tries to set the Thread Network node and only returns when succeeded
  bool startedCorrectly = false;
  while (!startedCorrectly) {
    startedCorrectly |= otDeviceSetup(
      otSetupChild, sizeof(otSetupChild) / sizeof(char *) / 2, otCoapJoiner, sizeof(otCoapJoiner) / sizeof(char *) / 2, OT_ROLE_CHILD, OT_ROLE_ROUTER);
    if (!startedCorrectly) {
      Serial.println("Setup Failed...\r\nTrying again...");
    }
  }
}

// Sends the CoAP frame to the json node
bool otCoapPUT() {
  bool gotDone = false, gotConfirmation = false;
  String coapMsg = "coap put ";
  coapMsg += OT_MCAST_ADDR;
  coapMsg += " ";
  coapMsg += OT_COAP_RESOURCE;
  coapMsg += " con ";
  coapMsg += serializedJson;

  // final command is "coap put ff05::abcd json con {<json>}"
  OThreadCLI.println(coapMsg.c_str());
  log_i("Send CLI CMD:[%s]", coapMsg.c_str());

  char cliResp[256];
  // waits for the CoAP confirmation and Done message for about 1.25 seconds
  // timeout is based on Stream::setTimeout()
  // Example of the expected confirmation response: "coap response from fdae:3289:1783:5c3f:fd84:c714:7e83:6122"
  uint8_t tries = 5;
  *cliResp = '\0';
  while (tries && !(gotDone && gotConfirmation)) {
    size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
    cliResp[len - 1] = '\0';
    log_d("Try[%d]::MSG[%s]", tries, cliResp);
    if (strlen(cliResp)) {
      if (!strncmp(cliResp, "coap response from", 18)) {
        gotConfirmation = true;
      }
      if (!strncmp(cliResp, "Done", 4)) {
        gotDone = true;
      }
    }
    tries--;
  }
  if (gotDone && gotConfirmation) {
    return true;
  }
  return false;
}

void hexToAscii(char* hexStr, char* asciiStr) {
  // Convert hex string to ASCII string
  int len = strlen(hexStr);
  for (int i = 0; i < len; i += 2) {
    // Convert each pair of hex digits to an ASCII character
    char hexChar[3] = { hexStr[i], hexStr[i + 1], '\0' };
    asciiStr[i / 2] = (char)strtol(hexChar, NULL, 16);
  }
  asciiStr[len / 2] = '\0';  // Null-terminate the result
}

// Handles incoming CoAP GET requests
void otCOAPListenForGET() {
  char cliResp[2048] = {0};
  char asciiPayload[1024] = {0};  // Buffer for decoded payload
  size_t len = OThreadCLI.readBytesUntil('\0', cliResp, sizeof(cliResp));
  cliResp[len - 1] = '\0';  // Null-terminate

  String sResp(cliResp);
  if(strlen(cliResp)){
    if (sResp.startsWith("coap request from") && sResp.indexOf("GET") > 0) {
      serializeJson(train,serializedJson);
      String coapResponse = "coap response 2.05 Content ";
      coapResponse += serializedJson;
      OThreadCLI.println(coapResponse.c_str());
      Serial.printf("Responded with JSON: %s\n", serializedJson.c_str());
    }
  }

}

// this function is used by the joiner mode to check the BOOT Button and send the user action to the joiner node
void checkUserButton() {
  static long unsigned int lastPress = 0;
  const long unsigned int debounceTime = 500;

  if (millis() > lastPress + debounceTime && digitalRead(USER_BUTTON) == LOW) {
    lastPress = millis();
    serializeJson(train,serializedJson);
    if (!otCoapPUT()) {  // failed: leader Node is not responding due to be off or unreachable
      // timeout from the CoAP PUT message... restart the node.
      rgbLedWrite(RGB_BUILTIN, 0, 255, 0);  // RED ... something failed!
      Serial.println("Resetting the Node as joiner... wait.");
      // start over...
      setupNode();
    }

  }
}

void setup() {
  Serial.begin(115200);
  // LED starts RED, indicating not connected to Thread network.
  rgbLedWrite(RGB_BUILTIN, 0, 255, 0);
  OThreadCLI.begin(false);     // No AutoStart is necessary
  OThreadCLI.setTimeout(250);  // waits 250ms for the OpenThread CLI response
  setupNode();
  // LED goes and keeps Blue when all is ready and Red when failed.
  pinMode(USER_BUTTON, INPUT_PULLUP);  // C6/H2 User Button

  train["id"] = 1;
}

void loop() {
  checkUserButton();
  otCOAPListenForGET();  // Continuously listen for GET requests
  delay(10);
}

Debug Message

Device is Leader.
OpenThread setup done. Node is ready.
0
Done

coap request from fdc7:388b:90bb:5a27:4611:3731:d16a:2515 PUT with payload: 7b226964223a317d
coap response sent

[ 49396][I][beeton_leader_v1.ino:184] otCOAPListen(): Msg[{"id":1}]
Joiner address set to: fdc7:388b:90bb:5a27:4611:3731:d16a:2515
Sending CoAP GET CMD: [coap get fdc7:388b:90bb:5a27:4611:3731:d16a:2515 json block-1024]
Try[5]::MSG[Done]
Command executed successfully.
Try[4]::MSG[coap response from fdc7:388b:90bb:5a27:4611:3731:d16a:2515 with payload: 30]
CoAP response received.
Sending CoAP GET CMD: [coap get fdc7:388b:90bb:5a27:4611:3731:d16a:2515 json block-1024]
Try[5]::MSG[Done]
Command executed successfully.
Try[4]::MSG[coap response from fdc7:388b:90bb:5a27:4611:3731:d16a:2515 with payload: 30]
CoAP response received.

[653707][I][beeton_joiner_v1.ino:114] otCoapPUT(): Send CLI CMD:[coap put ff05::abcd json con {"id":1}]
[653708][D][beeton_joiner_v1.ino:125] otCoapPUT(): Try[5]::MSG[Done]
[653726][D][beeton_joiner_v1.ino:125] otCoapPUT(): Try[4]::MSG[coap response from fdc7:388b:90bb:5a27:5085:7d23:e59c:18e8]
Responded with JSON: {"id":1}

Other Steps to Reproduce

press button on joiner to send via put a string, leader prints address. Copy address and paste back into leader serial terminal to populate the destination. Press button on leader to try and activate the GET for the joiner

I have checked existing issues, online documentation and the Troubleshooting Guide

97Cweb commented 4 days ago

found it finally, it is coap set to set the response that a get command gets. Could this please be added to documentation/instructions somewhere?

torntrousers commented 4 days ago

would you show the fixed code?

97Cweb commented 4 days ago
#include "OThreadCLI.h"
#include "OThreadCLI_Util.h"

#define OT_CHANNEL            "24"
#define OT_NETWORK_KEY        "00112233445566778899aabbccddeeff"
#define OT_MCAST_ADDR         "ff05::abcd"
#define OT_COAP_RESOURCE      "json"
#define USER_BUTTON           9

String joinerAddress = "";

const char *otSetupLeader[] = {
  // -- clear/disable all
  // stop CoAP
  "coap", "stop",
  // stop Thread
  "thread", "stop",
  // stop the interface
  "ifconfig", "down",
  // clear the dataset
  "dataset", "clear",
  // -- set dataset
  // create a new complete dataset with random data
  "dataset", "init new",
  // set the channel
  "dataset channel", OT_CHANNEL,
  // set the network key
  "dataset networkkey", OT_NETWORK_KEY,
  // commit the dataset
  "dataset", "commit active",
  // -- network start
  // start the interface
  "ifconfig", "up",
  // start the Thread network
  "thread", "start"
};

const char *otCoapHub[] = {
  // -- create a multicast IPv6 Address for this device
  "ipmaddr add", OT_MCAST_ADDR,
  // -- start and create a CoAP resource
  // start CoAP as server
  "coap", "start",
  // create a CoAP resource
  "coap resource", OT_COAP_RESOURCE,
  // set the CoAP resource initial value
  "coap set", "",
};

bool otDeviceSetup(const char **otSetupCmds, uint8_t nCmds1, const char **otCoapCmds, uint8_t nCmds2, ot_device_role_t expectedRole) {
  Serial.println("Starting OpenThread.");
  Serial.println("Running as HUB");
  uint8_t i;
  for (i = 0; i < nCmds1; i++) {
    if (!otExecCommand(otSetupCmds[i * 2], otSetupCmds[i * 2 + 1])) {
      break;
    }
  }
  if (i != nCmds1) {
    log_e("Sorry, OpenThread Network setup failed!");
    rgbLedWrite(RGB_BUILTIN, 255, 0, 0);  // RED ... failed!
    return false;
  }
  Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
  // wait for the expected Device Role to start
  uint8_t tries = 24;  // 24 x 2.5 sec = 1 min
  while (tries && otGetDeviceRole() != expectedRole) {
    Serial.print(".");
    delay(2500);
    tries--;
  }
  Serial.println();
  if (!tries) {
    log_e("Sorry, Device Role failed by timeout! Current Role: %s.", otGetStringDeviceRole());
    rgbLedWrite(RGB_BUILTIN, 255, 0, 0);  // RED ... failed!
    return false;
  }
  Serial.printf("Device is %s.\r\n", otGetStringDeviceRole());
  for (i = 0; i < nCmds2; i++) {
    if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
      break;
    }
  }
  if (i != nCmds2) {
    log_e("Sorry, OpenThread CoAP setup failed!");
    rgbLedWrite(RGB_BUILTIN, 0, 255, 0);  // RED ... failed!
    return false;
  }
  Serial.println("OpenThread setup done. Node is ready.");
  // all fine! LED goes Green
  rgbLedWrite(RGB_BUILTIN, 64, 0, 8);  // GREEN ... HUB is ready!
  return true;
}

void setupNode() {
  // tries to set the Thread Network node and only returns when succeeded
  bool startedCorrectly = false;
  while (!startedCorrectly) {
    startedCorrectly |=
      otDeviceSetup(otSetupLeader, sizeof(otSetupLeader) / sizeof(char *) / 2, otCoapHub, sizeof(otCoapHub) / sizeof(char *) / 2, OT_ROLE_LEADER);
    if (!startedCorrectly) {
      Serial.println("Setup Failed...\r\nTrying again...");
    }
  }
}

// Function to convert hex to ASCII
void hexToAscii(char* hexStr, char* asciiStr) {
  // Convert hex string to ASCII string
  int len = strlen(hexStr);
  for (int i = 0; i < len; i += 2) {
    // Convert each pair of hex digits to an ASCII character
    char hexChar[3] = { hexStr[i], hexStr[i + 1], '\0' };
    asciiStr[i / 2] = (char)strtol(hexChar, NULL, 16);
  }
  asciiStr[len / 2] = '\0';  // Null-terminate the result
}

// Function to extract and decode payload from CoAP response
void extractAndDecodePayload(String &sResp, char *asciiPayload) {
  int payloadIndex = sResp.indexOf("with payload: ") + 14;  // Adjust as needed
  String hexPayload = sResp.substring(payloadIndex);

  // Convert the hex payload to a char array
  char cliResp[1024];  // Temporary buffer
  hexPayload.toCharArray(cliResp, sizeof(cliResp));

  // Decode the hex payload to ASCII
  hexToAscii(cliResp, asciiPayload);

  // Log the decoded payload
  log_i("Decoded Payload: [%s]", asciiPayload);
}

bool sendCoapGet(String joinerAddress) {
  String coapMsg = "coap get ";
  coapMsg += joinerAddress;
  coapMsg += " ";
  coapMsg += OT_COAP_RESOURCE;
  coapMsg += " con";

  Serial.printf("Sending CoAP GET CMD: [%s]\n", coapMsg.c_str());
  OThreadCLI.println(coapMsg.c_str());

  char cliResp[1024];
  char asciiPayload[1024];  // Buffer for decoded payload
  bool gotDone = false, gotConfirmation = false;
  uint8_t tries = 5;

  while (tries && !(gotDone && gotConfirmation)) {
    size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
    cliResp[len - 1] = '\0';
    Serial.printf("Try[%d]::MSG[%s]\n", tries, cliResp);
    if (strlen(cliResp)) {
      if (!strncmp(cliResp, "coap response from", 18)) {
        gotConfirmation = true;
        Serial.println("CoAP response received.");
      }
      if (!strncmp(cliResp, "Done", 4)) {
        gotDone = true;
        Serial.println("Command executed successfully.");
      }
      // Check for JSON payload in the response
      if (strstr(cliResp, "payload")) {
        String sResp(cliResp);
        extractAndDecodePayload(sResp, asciiPayload);
      }
    }
    tries--;
  }

  return gotDone && gotConfirmation;
}

// Listen for CoAP frames from Joiner Nodes
void otCOAPListen() {
  char cliResp[2048] = {0};
  char asciiPayload[1024] = {0};  // Buffer for decoded payload
  size_t len = OThreadCLI.readBytesUntil('\0', cliResp, sizeof(cliResp));
  cliResp[len - 1] = '\0';
  if (strlen(cliResp)) {
    String sResp(cliResp);
    Serial.println(sResp);
    if (sResp.startsWith("coap request from") && sResp.indexOf("PUT") > 0) {
      extractAndDecodePayload(sResp, asciiPayload);
    }
  }
}
// Detect button press on GPIO 9
void checkUserButton() {
  static unsigned long lastPress = 0;
  const unsigned long debounceTime = 500;

  if (millis() - lastPress > debounceTime && digitalRead(USER_BUTTON) == LOW) {
    lastPress = millis();
    if (joinerAddress.length() > 0) {
      sendCoapGet(joinerAddress);  // Send GET request to the joiner
    } else {
      Serial.println("No Joiner address found! Provide address via Serial.");
    }
  }
}

void setup() {
  Serial.begin(115200);
  // LED starts RED, indicating not connected to Thread network.
  rgbLedWrite(RGB_BUILTIN, 64, 0, 0);
  OThreadCLI.begin(false);     // No AutoStart is necessary
  OThreadCLI.setTimeout(250);  // waits 250ms for the OpenThread CLI response
  setupNode();
  // LED goes Green when all is ready and Red when failed.

   pinMode(USER_BUTTON, INPUT_PULLUP);
}

void loop() {
  otCOAPListen();
  checkUserButton();
  // Read joiner address from Serial input
  if (Serial.available()) {
    joinerAddress = Serial.readStringUntil('\n');
    joinerAddress.trim();  // Remove extra spaces or newlines
    Serial.print("Joiner address set to: ");
    Serial.println(joinerAddress);
  }
  delay(10);
}
#include "OThreadCLI.h"
#include "OThreadCLI_Util.h"
#include <ArduinoJson.h>

#define USER_BUTTON       9  // C6/H2 Boot button
#define OT_CHANNEL        "24"
#define OT_NETWORK_KEY    "00112233445566778899aabbccddeeff"
#define OT_MCAST_ADDR     "ff05::abcd"
#define OT_COAP_RESOURCE  "json"

JsonDocument train;
String serializedJson = "";

const char *otSetupChild[] = {
  // -- clear/disable all
  // stop CoAP
  "coap", "stop",
  // stop Thread
  "thread", "stop",
  // stop the interface
  "ifconfig", "down",
  // clear the dataset
  "dataset", "clear",
  // -- set dataset
  // set the channel
  "dataset channel", OT_CHANNEL,
  // set the network key
  "dataset networkkey", OT_NETWORK_KEY,
  // commit the dataset
  "dataset", "commit active",
  // -- network start
  // start the interface
  "ifconfig", "up",
  // start the Thread network
  "thread", "start"
};

const char *otCoapJoiner[] = {
  // -- start CoAP as client
  "coap", "start",
  "coap resource", "json",
  "coap set", serializedJson.c_str()
};

bool otDeviceSetup(
  const char **otSetupCmds, uint8_t nCmds1, const char **otCoapCmds, uint8_t nCmds2, ot_device_role_t expectedRole1, ot_device_role_t expectedRole2) {
  Serial.println("Starting OpenThread.");
  Serial.println("Running as Joiner");
  uint8_t i;
  for (i = 0; i < nCmds1; i++) {
    if (!otExecCommand(otSetupCmds[i * 2], otSetupCmds[i * 2 + 1])) {
      break;
    }
  }
  if (i != nCmds1) {
    log_e("Sorry, OpenThread Network setup failed!");
    rgbLedWrite(RGB_BUILTIN, 255, 0, 0);  // RED ... failed!
    return false;
  }
  Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
  // wait for the expected Device Role to start
  uint8_t tries = 24;  // 24 x 2.5 sec = 1 min
  while (tries && otGetDeviceRole() != expectedRole1 && otGetDeviceRole() != expectedRole2) {
    Serial.print(".");
    delay(2500);
    tries--;
  }
  Serial.println();
  if (!tries) {
    log_e("Sorry, Device Role failed by timeout! Current Role: %s.", otGetStringDeviceRole());
    rgbLedWrite(RGB_BUILTIN, 255, 0, 0);  // RED ... failed!
    return false;
  }
  Serial.printf("Device is %s.\r\n", otGetStringDeviceRole());
  for (i = 0; i < nCmds2; i++) {
    if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
      break;
    }
  }
  if (i != nCmds2) {
    log_e("Sorry, OpenThread CoAP setup failed!");
    rgbLedWrite(RGB_BUILTIN, 0, 255, 0);  // RED ... failed!
    return false;
  }
  Serial.println("OpenThread setup done. Node is ready.");
  // all fine! LED goes and stays Blue
  rgbLedWrite(RGB_BUILTIN, 0, 0, 64);  // BLUE ... Joiner is ready!
  return true;
}

void setupNode() {
  // tries to set the Thread Network node and only returns when succeeded
  bool startedCorrectly = false;
  while (!startedCorrectly) {
    startedCorrectly |= otDeviceSetup(
      otSetupChild, sizeof(otSetupChild) / sizeof(char *) / 2, otCoapJoiner, sizeof(otCoapJoiner) / sizeof(char *) / 2, OT_ROLE_CHILD, OT_ROLE_ROUTER);
    if (!startedCorrectly) {
      Serial.println("Setup Failed...\r\nTrying again...");
    }
  }
}

// Sends the CoAP frame to the json node
bool otCoapPUT() {
  bool gotDone = false, gotConfirmation = false;
  String coapMsg = "coap put ";
  coapMsg += OT_MCAST_ADDR;
  coapMsg += " ";
  coapMsg += OT_COAP_RESOURCE;
  coapMsg += " con ";
  coapMsg += serializedJson;

  // final command is "coap put ff05::abcd json con {<json>}"
  OThreadCLI.println(coapMsg.c_str());
  log_i("Send CLI CMD:[%s]", coapMsg.c_str());

  char cliResp[256];
  // waits for the CoAP confirmation and Done message for about 1.25 seconds
  // timeout is based on Stream::setTimeout()
  // Example of the expected confirmation response: "coap response from fdae:3289:1783:5c3f:fd84:c714:7e83:6122"
  uint8_t tries = 5;
  *cliResp = '\0';
  while (tries && !(gotDone && gotConfirmation)) {
    size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
    cliResp[len - 1] = '\0';
    log_d("Try[%d]::MSG[%s]", tries, cliResp);
    if (strlen(cliResp)) {
      if (!strncmp(cliResp, "coap response from", 18)) {
        gotConfirmation = true;
      }
      if (!strncmp(cliResp, "Done", 4)) {
        gotDone = true;
      }
    }
    tries--;
  }
  if (gotDone && gotConfirmation) {
    return true;
  }
  return false;
}

void hexToAscii(char* hexStr, char* asciiStr) {
  // Convert hex string to ASCII string
  int len = strlen(hexStr);
  for (int i = 0; i < len; i += 2) {
    // Convert each pair of hex digits to an ASCII character
    char hexChar[3] = { hexStr[i], hexStr[i + 1], '\0' };
    asciiStr[i / 2] = (char)strtol(hexChar, NULL, 16);
  }
  asciiStr[len / 2] = '\0';  // Null-terminate the result
}

// this function is used by the joiner mode to check the BOOT Button and send the user action to the joiner node
void checkUserButton() {
  static long unsigned int lastPress = 0;
  const long unsigned int debounceTime = 500;

  if (millis() > lastPress + debounceTime && digitalRead(USER_BUTTON) == LOW) {
    lastPress = millis();
    serializeJson(train,serializedJson);
    if (!otCoapPUT()) {  // failed: leader Node is not responding due to be off or unreachable
      // timeout from the CoAP PUT message... restart the node.
      rgbLedWrite(RGB_BUILTIN, 0, 255, 0);  // RED ... something failed!
      Serial.println("Resetting the Node as joiner... wait.");
      // start over...
      setupNode();
    }

  }
}

void setup() {

  train["id"] = 1;
  serializeJson(train,serializedJson);
  Serial.begin(115200);
  // LED starts RED, indicating not connected to Thread network.
  rgbLedWrite(RGB_BUILTIN, 0, 255, 0);
  OThreadCLI.begin(false);     // No AutoStart is necessary
  OThreadCLI.setTimeout(250);  // waits 250ms for the OpenThread CLI response
  setupNode();
  // LED goes and keeps Blue when all is ready and Red when failed.
  pinMode(USER_BUTTON, INPUT_PULLUP);  // C6/H2 User Button

}

void loop() {
  checkUserButton();
  //otCOAPListenForGET();  // Continuously listen for GET requests
  delay(10);
}