New OpenThread CLI Arduino Library for ESP32-C6 and ESP32-H2 (#9908)

* feat(OThread): Add Library

* fix(OpenThread): fixes file list in CMakeLists.txt

* fix(openthread): Fixes JSON CI Files

* fix(openthread): Fixes JSON CI Files

* fix(openthread): Include Openthread guarding

* fix(openthread): COAP parametrization

* fix(openthread): Include Openthread guarding

* fix(openthread): Improves commentaries and code

* fix(openthread): Improves code

* fix(openthread): Includes StreamString.h

* feat(openthread): New Scan Example

* feat(openthread): Improved Scan Example

* feat(openthread): README.md

Initial documentation for ESP3 Arduino OpenThread CLI API.

* feat(openthread): helper functions documentation

Create helper_functions.md for ESP32 Arduino OpenThread API

* fix(openthread): begin end

* feat(openthread): onReceice example

* fix(openthread): tx queue error

* fix(doc): fixing documentation apresentation

Fixes the documentation first paragraph in order to make it easier fore reading. It also displays in the very top which SoC are supported by the library.

* fix(doc): documentation format

* feat(openthread): commentary

* fix(openthread): Typo, start/stop console

* fix(openthread): library properties

* ci(pre-commit): Apply automatic fixes

* feat(openthread): formatting text

* ci(pre-commit): Apply automatic fixes

---------

Co-authored-by: Lucas Saavedra Vaz <32426024+lucasssvaz@users.noreply.github.com>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
Rodrigo Garcia 2024-06-24 13:26:42 -03:00 committed by GitHub
parent 9e55ccd98e
commit d891ddfec7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 1503 additions and 0 deletions

View file

@ -95,6 +95,7 @@ set(ARDUINO_ALL_LIBRARIES
LittleFS
NetBIOS
Network
OpenThread
PPP
Preferences
RainMaker
@ -158,6 +159,10 @@ set(ARDUINO_LIBRARY_LittleFS_SRCS libraries/LittleFS/src/LittleFS.cpp)
set(ARDUINO_LIBRARY_NetBIOS_SRCS libraries/NetBIOS/src/NetBIOS.cpp)
set(ARDUINO_LIBRARY_OpenThread_SRCS
libraries/OpenThread/src/OThreadCLI.cpp
libraries/OpenThread/src/OThreadCLI_Util.cpp)
set(ARDUINO_LIBRARY_PPP_SRCS
libraries/PPP/src/PPP.cpp
libraries/PPP/src/ppp.c)

View file

@ -0,0 +1,72 @@
| Supported Targets | ESP32-C6 | ESP32-H2 |
| ----------------- | -------- | -------- |
# ESP32 Arduino OpenThreadCLI
The `OpenThreadCLI` class is an Arduino API for interacting with the OpenThread Command Line Interface (CLI). It allows you to manage and configure the Thread stack using a command-line interface.
There is one main class called `OpenThreadCLI` and a global object used to operate OpenThread CLI, called `OThreadCLI`.\
Some [helper functions](helper_functions.md) were made available for working with the OpenThread CLI environment.
The available OpenThread Commands are documented in the [OpenThread CLI Reference Page](https://openthread.io/reference/cli/commands)
It is important to note that the current implementation can only be used with Espressif SoC that has support to IEEE 802.15.4, such as **ESP32-C6** and **ESP32-H2**.
Below are the details of the class:
## Class Definition
```cpp
class OpenThreadCLI : public Stream {
private:
static size_t setBuffer(xQueueHandle &queue, size_t len);
bool otStarted = false;
public:
OpenThreadCLI();
~OpenThreadCLI();
operator bool() const;
// Starts a task to read/write otStream. Default prompt is "ot> ". Set it to NULL to make it invisible.
void startConsole(Stream& otStream, bool echoback = true, const char* prompt = "ot> ");
void stopConsole();
void setPrompt(char* prompt); // Changes the console prompt. NULL is an empty prompt.
void setEchoBack(bool echoback); // Changes the console echoback option
void setStream(Stream& otStream); // Changes the console Stream object
void onReceive(OnReceiveCb_t func); // Called on a complete line of output from OT CLI, as OT Response
void begin(bool OThreadAutoStart = true);
void end();
// Default size is 256 bytes
size_t setTxBufferSize(size_t tx_queue_len);
// Default size is 1024 bytes
size_t setRxBufferSize(size_t rx_queue_len);
size_t write(uint8_t);
int available();
int read();
int peek();
void flush();
};
extern OpenThreadCLI OThreadCLI;
```
## Class Overview
- The `OpenThreadCLI` class inherits from the `Stream` class, making it compatible with Arduino's standard I/O functions.
- It provides methods for managing the OpenThread CLI, including starting and stopping the console, setting prompts, and handling received data.
- You can customize the console behavior by adjusting parameters such as echoback and buffer sizes.
## Public Methods
- `startConsole(Stream& otStream, bool echoback = true, const char* prompt = "ot> ")`: Starts the OpenThread console with the specified stream, echoback option, and prompt.
- `stopConsole()`: Stops the OpenThread console.
- `setPrompt(char* prompt)`: Changes the console prompt (set to NULL for an empty prompt).
- `setEchoBack(bool echoback)`: Changes the console echoback option.
- `setStream(Stream& otStream)`: Changes the console Stream object.
- `onReceive(OnReceiveCb_t func)`: Sets a callback function to handle complete lines of output from the OT CLI.
- `begin(bool OThreadAutoStart = true)`: Initializes the OpenThread stack (optional auto-start).
- `end()`: Deinitializes the OpenThread stack.
- `setTxBufferSize(size_t tx_queue_len)`: Sets the transmit buffer size (default is 256 bytes).
- `setRxBufferSize(size_t rx_queue_len)`: Sets the receive buffer size (default is 1024 bytes).
- `write(uint8_t)`, `available()`, `read()`, `peek()`, `flush()`: Standard Stream methods implementation for OpenThread CLI object.

View file

@ -0,0 +1,9 @@
{
"targets": {
"esp32": false,
"esp32c2": false,
"esp32c3": false,
"esp32s2": false,
"esp32s3": false
}
}

View file

@ -0,0 +1,149 @@
#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_NAME "Lamp"
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 *otCoapLamp[] = {
// -- 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_NAME,
// set the CoAP resource initial value
"coap set", "0"
};
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 Lamp (RGB LED) - use the other C6/H2 as a Switch");
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!");
neopixelWrite(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());
neopixelWrite(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!");
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.println("OpenThread setup done. Node is ready.");
// all fine! LED goes Green
neopixelWrite(RGB_BUILTIN, 0, 64, 8); // GREEN ... Lamp is ready!
return true;
}
void setupNode() {
// tries to set the Thread Network node and only returns when succeded
bool startedCorrectly = false;
while (!startedCorrectly) {
startedCorrectly |=
otDeviceSetup(otSetupLeader, sizeof(otSetupLeader) / sizeof(char *) / 2, otCoapLamp, sizeof(otCoapLamp) / sizeof(char *) / 2, OT_ROLE_LEADER);
if (!startedCorrectly) {
Serial.println("Setup Failed...\r\nTrying again...");
}
}
}
// this function is used by the Lamp mode to listen for CoAP frames from the Switch Node
void otCOAPListen() {
// waits for the client to send a CoAP request
char cliResp[256] = {0};
size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
cliResp[len - 1] = '\0';
if (strlen(cliResp)) {
String sResp(cliResp);
// cliResp shall be something like:
// "coap request from fd0c:94df:f1ae:b39a:ec47:ec6d:15e8:804a PUT with payload: 30"
// payload may be 30 or 31 (HEX) '0' or '1' (ASCII)
log_d("Msg[%s]", cliResp);
if (sResp.startsWith("coap request from") && sResp.indexOf("PUT") > 0) {
char payload = sResp.charAt(sResp.length() - 1); // last character in the payload
log_i("CoAP PUT [%s]\r\n", payload == '0' ? "OFF" : "ON");
if (payload == '0') {
for (int16_t c = 248; c > 16; c -= 8) {
neopixelWrite(RGB_BUILTIN, c, c, c); // ramp down
delay(5);
}
neopixelWrite(RGB_BUILTIN, 0, 0, 0); // Lamp Off
} else {
for (int16_t c = 16; c < 248; c += 8) {
neopixelWrite(RGB_BUILTIN, c, c, c); // ramp up
delay(5);
}
neopixelWrite(RGB_BUILTIN, 255, 255, 255); // Lamp On
}
}
}
}
void setup() {
Serial.begin(115200);
// LED starts RED, indicating not connected to Thread network.
neopixelWrite(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.
}
void loop() {
otCOAPListen();
delay(10);
}

View file

@ -0,0 +1,9 @@
{
"targets": {
"esp32": false,
"esp32c2": false,
"esp32c3": false,
"esp32s2": false,
"esp32s3": false
}
}

View file

@ -0,0 +1,174 @@
#include "OThreadCLI.h"
#include "OThreadCLI_Util.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_NAME "Lamp"
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 *otCoapSwitch[] = {
// -- start CoAP as client
"coap", "start"
};
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 Switch - use the BOOT button to toggle the other C6/H2 as a Lamp");
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!");
neopixelWrite(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());
neopixelWrite(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!");
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.println("OpenThread setup done. Node is ready.");
// all fine! LED goes and stays Blue
neopixelWrite(RGB_BUILTIN, 0, 0, 64); // BLUE ... Swtich is ready!
return true;
}
void setupNode() {
// tries to set the Thread Network node and only returns when succeded
bool startedCorrectly = false;
while (!startedCorrectly) {
startedCorrectly |= otDeviceSetup(
otSetupChild, sizeof(otSetupChild) / sizeof(char *) / 2, otCoapSwitch, sizeof(otCoapSwitch) / sizeof(char *) / 2, OT_ROLE_CHILD, OT_ROLE_ROUTER
);
if (!startedCorrectly) {
Serial.println("Setup Failed...\r\nTrying again...");
}
}
}
// Sends the CoAP frame to the Lamp node
bool otCoapPUT(bool lampState) {
bool gotDone = false, gotConfirmation = false;
String coapMsg = "coap put ";
coapMsg += OT_MCAST_ADDR;
coapMsg += " ";
coapMsg += OT_COAP_RESOURCE_NAME;
coapMsg += " con 0";
// final command is "coap put ff05::abcd Lamp con 1" or "coap put ff05::abcd Lamp con 0"
if (lampState) {
coapMsg[coapMsg.length() - 1] = '1';
}
OThreadCLI.println(coapMsg.c_str());
log_d("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;
}
// this fucntion is used by the Switch mode to check the BOOT Button and send the user action to the Lamp node
void checkUserButton() {
static long unsigned int lastPress = 0;
const long unsigned int debounceTime = 500;
static bool lastLampState = true; // first button press will turn the Lamp OFF from inital Green
pinMode(USER_BUTTON, INPUT_PULLUP); // C6/H2 User Button
if (millis() > lastPress + debounceTime && digitalRead(USER_BUTTON) == LOW) {
lastLampState = !lastLampState;
if (!otCoapPUT(lastLampState)) { // failed: Lamp Node is not responding due to be off or unreachable
// timeout from the CoAP PUT message... restart the node.
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... something failed!
Serial.println("Reseting the Node as Switch... wait.");
// start over...
setupNode();
}
lastPress = millis();
}
}
void setup() {
Serial.begin(115200);
// LED starts RED, indicating not connected to Thread network.
neopixelWrite(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 and keeps Blue when all is ready and Red when failed.
}
void loop() {
checkUserButton();
delay(10);
}

View file

@ -0,0 +1,20 @@
/*
* OpenThread.begin(false) will not automatically start a node in a Thread Network
* The user will need to start it manually using the OpenThread CLI commands
* Use the Serial Monitor to interact with the OpenThread CLI
*
* Type 'help' for a list of commands.
* Documentation: https://openthread.io/reference/cli/commands
*
*/
#include "OThreadCLI.h"
void setup() {
Serial.begin(115200);
OThreadCLI.begin(false); // No AutoStart - fresh start
Serial.println("OpenThread CLI started - type 'help' for a list of commands.");
OThreadCLI.startConsole(Serial);
}
void loop() {}

View file

@ -0,0 +1,9 @@
{
"targets": {
"esp32": false,
"esp32c2": false,
"esp32c3": false,
"esp32s2": false,
"esp32s3": false
}
}

View file

@ -0,0 +1,32 @@
/*
* OpenThread.begin() will automatically start a node in a Thread Network
* If NVS is empty, default configuration will be as follow:
*
* NETWORK_NAME "OpenThread-ESP"
* MESH_LOCAL_PREFIX "fd00:db8:a0:0::/64"
* NETWORK_CHANNEL 15
* NETWORK_PANID 0x1234
* NETWORK_EXTPANID "dead00beef00cafe"
* NETWORK_KEY "00112233445566778899aabbccddeeff"
* NETWORK_PSKC "104810e2315100afd6bc9215a6bfac53"
*
* If NVS has already a dataset information, it will load it from there.
*/
#include "OThreadCLI.h"
#include "OThreadCLI_Util.h"
// The first device to start Thread will be the Leader
// Next devices will be Router or Child
void setup() {
Serial.begin(115200);
OThreadCLI.begin(); // AutoStart using Thread default settings
otPrintNetworkInformation(Serial); // Print Current Thread Network Information
}
void loop() {
Serial.print("Thread Node State: ");
Serial.println(otGetStringDeviceRole());
delay(5000);
}

View file

@ -0,0 +1,9 @@
{
"targets": {
"esp32": false,
"esp32c2": false,
"esp32c3": false,
"esp32s2": false,
"esp32s3": false
}
}

View file

@ -0,0 +1,37 @@
/*
* OpenThread.begin(false) will not automatically start a node in a Thread Network
* A Leader node is the first device, that has a complete dataset, to start Thread
* A complete dataset is easily achieved by using the OpenThread CLI command "dataset init new"
*
* In order to allow other node to join the network,
* all of them shall use the same network master key
* The network master key is a 16-byte key that is used to secure the network
*
* Using the same channel will make the process faster
*
*/
#include "OThreadCLI.h"
#include "OThreadCLI_Util.h"
#define CLI_NETWORK_KEY "dataset networkkey 00112233445566778899aabbccddeeff"
#define CLI_NETWORK_CHANEL "dataset channel 24"
void setup() {
Serial.begin(115200);
OThreadCLI.begin(false); // No AutoStart - fresh start
Serial.println("Setting up OpenThread Node as Leader");
OThreadCLI.println("dataset init new");
OThreadCLI.println(CLI_NETWORK_KEY);
OThreadCLI.println(CLI_NETWORK_CHANEL);
OThreadCLI.println("dataset commit active");
OThreadCLI.println("ifconfig up");
OThreadCLI.println("thread start");
}
void loop() {
Serial.print("Thread Node State: ");
Serial.println(otGetStringDeviceRole());
delay(5000);
}

View file

@ -0,0 +1,9 @@
{
"targets": {
"esp32": false,
"esp32c2": false,
"esp32c3": false,
"esp32s2": false,
"esp32s3": false
}
}

View file

@ -0,0 +1,37 @@
/*
* OpenThread.begin(false) will not automatically start a node in a Thread Network
* A Router/Child node is the device that will join an existing Thread Network
*
* In order to allow this node to join the network,
* it shall use the same network master key as used by the Leader Node
* The network master key is a 16-byte key that is used to secure the network
*
* Using the same channel will make the process faster
*
*/
#include "OThreadCLI.h"
#include "OThreadCLI_Util.h"
#define CLI_NETWORK_KEY "dataset networkkey 00112233445566778899aabbccddeeff"
#define CLI_NETWORK_CHANEL "dataset channel 24"
void setup() {
Serial.begin(115200);
OThreadCLI.begin(false); // No AutoStart - fresh start
Serial.println("Setting up OpenThread Node as Router/Child");
Serial.println("Make sure the Leader Node is already running");
OThreadCLI.println("dataset clear");
OThreadCLI.println(CLI_NETWORK_KEY);
OThreadCLI.println(CLI_NETWORK_CHANEL);
OThreadCLI.println("dataset commit active");
OThreadCLI.println("ifconfig up");
OThreadCLI.println("thread start");
}
void loop() {
Serial.print("Thread Node State: ");
Serial.println(otGetStringDeviceRole());
delay(5000);
}

View file

@ -0,0 +1,9 @@
{
"targets": {
"esp32": false,
"esp32c2": false,
"esp32c3": false,
"esp32s2": false,
"esp32s3": false
}
}

View file

@ -0,0 +1,41 @@
/*
OpenThread.begin(true) will automatically start a node in a Thread Network
Full scanning requires the thread node to be at least in Child state.
This will scan the IEEE 802.14.5 devices in the local area using CLI "scan" command
As soon as this device turns into a Child, Router or Leader, it will be able to
scan for Local Thread Networks as well.
*/
#include "OThreadCLI.h"
#include "OThreadCLI_Util.h"
void setup() {
Serial.begin(115200);
OThreadCLI.begin(true); // For scanning, AutoStart must be active, any setup
OThreadCLI.setTimeout(100); // Set a timeout for the CLI response
Serial.println();
Serial.println("This sketch will continuously scan the Thread Local Network and all devices IEEE 802.15.4 compatible");
}
void loop() {
Serial.println();
Serial.println("Scanning for nearby IEEE 802.15.4 devices:");
// 802.15.4 Scan just needs a previous OThreadCLI.begin()
if (!otPrintRespCLI("scan", Serial, 3000)) {
Serial.println("Scan Failed...");
}
delay(5000);
if (otGetDeviceRole() < OT_ROLE_CHILD) {
Serial.println();
Serial.println("This device has not started Thread yet, bypassing Discovery Scan");
return;
}
Serial.println();
Serial.println("Scanning MLE Discover:");
if (!otPrintRespCLI("discover", Serial, 3000)) {
Serial.println("Discover Failed...");
}
delay(5000);
}

View file

@ -0,0 +1,9 @@
{
"targets": {
"esp32": false,
"esp32c2": false,
"esp32c3": false,
"esp32s2": false,
"esp32s3": false
}
}

View file

@ -0,0 +1,9 @@
{
"targets": {
"esp32": false,
"esp32c2": false,
"esp32c3": false,
"esp32s2": false,
"esp32s3": false
}
}

View file

@ -0,0 +1,37 @@
/*
OpenThread.begin() will automatically start a node in a Thread Network
This will demonstrate how to capture the CLI response in a callback function
The device state shall change from "disabled" to valid Thread states along time
*/
#include "OThreadCLI.h"
// reads all the lines sent by CLI, one by one
// ignores some lines that are just a sequence of \r\n
void otReceivedLine() {
String line = "";
while (OThreadCLI.available() > 0) {
char ch = OThreadCLI.read();
if (ch != '\r' && ch != '\n') {
line += ch;
}
}
// ignores empty lines, usually EOL sequence
if (line.length() > 0) {
Serial.print("OpenThread CLI RESP===> ");
Serial.println(line.c_str());
}
}
void setup() {
Serial.begin(115200);
OThreadCLI.begin(); // AutoStart
OThreadCLI.onReceive(otReceivedLine);
}
void loop() {
// sends the "state" command to the CLI every second
// the onReceive() Callback Function will read and process the response
OThreadCLI.println("state");
delay(1000);
}

View file

@ -0,0 +1,59 @@
# OpenThread Helper Functions and Types
The following helper functions and types are designed to simplify writing Arduino sketches for OpenThread.\
They provide useful utilities for managing OpenThread stack behavior and interacting with the Thread network.
### Enumerated Type: `ot_device_role_t`
This enumeration defines the possible roles of a Thread device within the network:
- `OT_ROLE_DISABLED`: The Thread stack is disabled.
- `OT_ROLE_DETACHED`: The device is not currently participating in a Thread network/partition.
- `OT_ROLE_CHILD`: The device operates as a Thread Child.
- `OT_ROLE_ROUTER`: The device operates as a Thread Router.
- `OT_ROLE_LEADER`: The device operates as a Thread Leader.
### Struct: `ot_cmd_return_t`
This structure represents the return status of an OpenThread CLI command:
- `errorCode`: An integer representing the error code (if any).
- `errorMessage`: A string containing an error message (if applicable).
### Function: `otGetDeviceRole()`
- Returns the current role of the device as an `ot_device_role_t` value.
### Function: `otGetStringDeviceRole()`
- Returns a human-readable string representation of the device role (e.g., "Child," "Router," etc.).
### Function: `otGetRespCmd(const char* cmd, char* resp = NULL, uint32_t respTimeout = 5000)`
- Executes an OpenThread CLI command and retrieves the response.
- Parameters:
- `cmd`: The OpenThread CLI command to execute.
- `resp`: Optional buffer to store the response (if provided).
- `respTimeout`: Timeout (in milliseconds) for waiting for the response.
### Function: `otExecCommand(const char* cmd, const char* arg, ot_cmd_return_t* returnCode = NULL)`
- Executes an OpenThread CLI command with an argument.
- Parameters:
- `cmd`: The OpenThread CLI command to execute.
- `arg`: The argument for the command.
- `returnCode`: Optional pointer to an `ot_cmd_return_t` structure to store the return status.
### Function: `otPrintRespCLI(const char* cmd, Stream& output, uint32_t respTimeout)`
- Executes an OpenThread CLI command and prints the response to the specified output stream.
- Parameters:
- `cmd`: The OpenThread CLI command to execute.
- `output`: The output stream (e.g., Serial) to print the response.
- `respTimeout`: Timeout (in milliseconds) for waiting for the response.
### Function: `otPrintNetworkInformation(Stream& output)`
- Prints information about the current Thread network to the specified output stream.
- Parameters:
- `output`: The output stream (e.g., Serial) to print the network information.

View file

@ -0,0 +1,47 @@
#######################################
# Syntax Coloring Map For OpenThread
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
OThreadCLI KEYWORD1
OpenThreadCLI KEYWORD1
ot_cmd_return_t KEYWORD1
ot_device_role_t KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
startConsole KEYWORD2
stopConsole KEYWORD2
setPrompt KEYWORD2
setEchoBack KEYWORD2
setStream KEYWORD2
onReceive KEYWORD2
begin KEYWORD2
setTxBufferSize KEYWORD2
setRxBufferSize KEYWORD2
write KEYWORD2
available KEYWORD2
read KEYWORD2
peek KEYWORD2
flush KEYWORD2
otGetDeviceRole KEYWORD2
otGetStringDeviceRole KEYWORD2
otGetRespCmd KEYWORD2
otExecCommand KEYWORD2
otPrintRespCLI KEYWORD2
otPrintNetworkInformation KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
OT_ROLE_DISABLED LITERAL1
OT_ROLE_DETACHED LITERAL1
OT_ROLE_CHILD LITERAL1
OT_ROLE_ROUTER LITERAL1
OT_ROLE_LEADER LITERAL1

View file

@ -0,0 +1,9 @@
name=OpenThread
version=1.0.0
author=Rodrigo Garcia | GitHub @SuGlider
maintainer=Rodrigo Garcia <Rodrigo.Garcia@espressif.com>
sentence=Library for OpenThread Network on ESP32.
paragraph=This library is a wrapper for OpenThread CLI. It provides a simple way to interact with OpenThread Network.
category=Communication
url=https://github.com/espressif/arduino-esp32/
architectures=esp32

View file

@ -0,0 +1,426 @@
#include "OThreadCLI.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
#include "Arduino.h"
#include "OThreadCLI.h"
#include "esp_err.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "esp_netif_types.h"
#include "esp_vfs_eventfd.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/queue.h"
#include "esp_netif.h"
#include "esp_netif_types.h"
static TaskHandle_t s_cli_task = NULL;
static TaskHandle_t s_console_cli_task = NULL;
static xQueueHandle rx_queue = NULL;
static xQueueHandle tx_queue = NULL;
static esp_openthread_platform_config_t ot_native_config;
static TaskHandle_t s_ot_task = NULL;
static esp_netif_t *openthread_netif = NULL;
#define OT_CLI_MAX_LINE_LENGTH 512
typedef struct {
Stream *cliStream;
bool echoback;
String prompt;
OnReceiveCb_t responseCallBack;
} ot_cli_console_t;
static ot_cli_console_t otConsole = {NULL, false, (const char *)NULL, NULL};
// process the CLI commands sent to the OpenThread stack
static void ot_cli_loop(void *context) {
String sTxString("");
while (true) {
if (tx_queue != NULL) {
uint8_t c;
if (xQueueReceive(tx_queue, &c, portMAX_DELAY)) {
// avoids sending a empty command, specially when the terminal send "\r\n" together
if (sTxString.length() > 0 && (c == '\r' || c == '\n')) {
esp_openthread_cli_input(sTxString.c_str());
xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
sTxString = "";
} else {
if (c == '\b' || c == 127) {
if (sTxString.length() > 0) {
sTxString.remove(sTxString.length() - 1);
}
} else {
// only allow printable characters
if (c > 31 && c < 127) {
sTxString += (char)c;
}
}
}
}
}
}
}
// process the CLI responses received from the OpenThread stack
static int ot_cli_output_callback(void *context, const char *format, va_list args) {
char prompt_check[3];
int ret = 0;
vsnprintf(prompt_check, sizeof(prompt_check), format, args);
if (!strncmp(prompt_check, "> ", sizeof(prompt_check))) {
if (s_cli_task) {
xTaskNotifyGive(s_cli_task);
}
if (s_console_cli_task) {
xTaskNotifyGive(s_console_cli_task);
}
} else {
char buf[OT_CLI_MAX_LINE_LENGTH];
ret = vsnprintf(buf, sizeof(buf), format, args);
if (ret) {
// store received data in the RX buffer
if (rx_queue != NULL) {
size_t freeSpace = uxQueueSpacesAvailable(rx_queue);
if (freeSpace < ret) {
// Drop the oldest data to make room for the new data
for (int i = 0; i < (ret - freeSpace); i++) {
uint8_t c;
xQueueReceive(rx_queue, &c, 0);
}
}
for (int i = 0; i < ret; i++) {
xQueueSend(rx_queue, &buf[i], 0);
}
// if there is a user callback function in place, it shall have the priority
// to process/consume the Stream data received from OpenThread CLI, which is available in its RX Buffer
if (otConsole.responseCallBack != NULL) {
otConsole.responseCallBack();
}
}
}
}
return ret;
}
// helper task to process CLI from a Stream (e.g. Serial)
static void ot_cli_console_worker(void *context) {
ot_cli_console_t *cli = (ot_cli_console_t *)context;
// prints the prompt as first action
if (cli->prompt && cli->echoback) {
cli->cliStream->print(cli->prompt.c_str());
}
// manages and synchronizes the Stream flow with OpenThread CLI response
char lastReadChar;
char c = '\n';
while (true) {
if (cli->cliStream->available() > 0) {
lastReadChar = c;
c = cli->cliStream->read();
// if EOL is received, it may contain a combination of '\n'
// and/or '\r' depending on the Host OS and Terminal used.
// remove all leading '\r' '\n'
if (c == '\r') {
c = '\n'; // just mark it as New Line
}
if (c == '\n' && lastReadChar == '\n') {
continue;
}
// echo it back to the console
if (cli->echoback) {
if (c == '\n') {
cli->cliStream->println(); // follows whatever is defined as EOL in Arduino
} else {
cli->cliStream->write(c);
}
}
// send it to be processed by Open Thread CLI
OThreadCLI.write(c);
// if EOL, it shall wait for the command to be processed in background
if (c == '\n' && lastReadChar != '\n') {
// wait for the OpenThread CLI to finish processing the command
xTaskNotifyWait(0, 0, NULL, portMAX_DELAY);
// read response from OpenThread CLI and send it to the Stream
while (OThreadCLI.available() > 0) {
char c = OThreadCLI.read();
// echo it back to the console
if (cli->echoback) {
if (c == '\n') {
cli->cliStream->println(); // follows whatever is defined as EOL in Arduino
} else {
cli->cliStream->write(c);
}
}
}
// print the prompt
if (cli->prompt && cli->echoback) {
cli->cliStream->printf(cli->prompt.c_str());
}
}
}
}
}
void OpenThreadCLI::setEchoBack(bool echoback) {
otConsole.echoback = echoback;
}
void OpenThreadCLI::setPrompt(char *prompt) {
otConsole.prompt = prompt; // NULL will make the prompt not visible
}
void OpenThreadCLI::setStream(Stream &otStream) {
otConsole.cliStream = &otStream;
}
void OpenThreadCLI::onReceive(OnReceiveCb_t func) {
otConsole.responseCallBack = func; // NULL will set it off
}
// Stream object shall be already started and configured before calling this function
void OpenThreadCLI::startConsole(Stream &otStream, bool echoback, const char *prompt) {
if (!otStarted) {
log_e("OpenThread CLI has not started. Please begin() it before starting the console.");
return;
}
if (s_console_cli_task == NULL) {
otConsole.cliStream = &otStream;
otConsole.echoback = echoback;
otConsole.prompt = prompt; // NULL will invalidate the String
// it will run in the same priority (1) as the Arduino setup()/loop() task
xTaskCreate(ot_cli_console_worker, "ot_cli_console", 4096, &otConsole, 1, &s_console_cli_task);
} else {
log_w("A console is already running. Please stop it before starting a new one.");
}
}
void OpenThreadCLI::stopConsole() {
if (s_console_cli_task) {
vTaskDelete(s_console_cli_task);
s_console_cli_task = NULL;
}
}
OpenThreadCLI::OpenThreadCLI() {
memset(&ot_native_config, 0, sizeof(esp_openthread_platform_config_t));
ot_native_config.radio_config.radio_mode = RADIO_MODE_NATIVE;
ot_native_config.host_config.host_connection_mode = HOST_CONNECTION_MODE_NONE;
ot_native_config.port_config.storage_partition_name = "nvs";
ot_native_config.port_config.netif_queue_size = 10;
ot_native_config.port_config.task_queue_size = 10;
//sTxString = "";
}
OpenThreadCLI::~OpenThreadCLI() {
end();
}
OpenThreadCLI::operator bool() const {
return otStarted;
}
static void ot_task_worker(void *aContext) {
esp_vfs_eventfd_config_t eventfd_config = {
.max_fds = 3,
};
bool err = false;
if (ESP_OK != esp_event_loop_create_default()) {
log_e("Failed to create OpentThread event loop");
err = true;
}
if (!err && ESP_OK != esp_netif_init()) {
log_e("Failed to initialize OpentThread netif");
err = true;
}
if (!err && ESP_OK != esp_vfs_eventfd_register(&eventfd_config)) {
log_e("Failed to register OpentThread eventfd");
err = true;
}
// Initialize the OpenThread stack
if (!err && ESP_OK != esp_openthread_init(&ot_native_config)) {
log_e("Failed to initialize OpenThread stack");
err = true;
}
if (!err) {
// Initialize the OpenThread cli
otCliInit(esp_openthread_get_instance(), ot_cli_output_callback, NULL);
// Initialize the esp_netif bindings
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
openthread_netif = esp_netif_new(&cfg);
}
if (!err && openthread_netif == NULL) {
log_e("Failed to create OpenThread esp_netif");
err = true;
}
if (!err && ESP_OK != esp_netif_attach(openthread_netif, esp_openthread_netif_glue_init(&ot_native_config))) {
log_e("Failed to attach OpenThread esp_netif");
err = true;
}
if (!err && ESP_OK != esp_netif_set_default_netif(openthread_netif)) {
log_e("Failed to set default OpenThread esp_netif");
err = true;
}
if (!err) {
// only returns in case there is an OpenThread Stack failure...
esp_openthread_launch_mainloop();
}
// Clean up
esp_openthread_netif_glue_deinit();
esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister();
vTaskDelete(NULL);
}
void OpenThreadCLI::begin(bool OThreadAutoStart) {
if (otStarted) {
log_w("OpenThread CLI already started. Please end() it before starting again.");
return;
}
xTaskCreate(ot_task_worker, "ot_main_loop", 10240, NULL, 20, &s_ot_task);
//RX Buffer default has 1024 bytes if not preset
if (rx_queue == NULL) {
if (!setRxBufferSize(1024)) {
log_e("HW CDC RX Buffer error");
}
}
//TX Buffer default has 256 bytes if not preset
if (tx_queue == NULL) {
if (!setTxBufferSize(256)) {
log_e("HW CDC RX Buffer error");
}
}
xTaskCreate(ot_cli_loop, "ot_cli", 4096, xTaskGetCurrentTaskHandle(), 2, &s_cli_task);
// starts Thread with default dataset from NVS or from IDF default settings
if (OThreadAutoStart) {
otOperationalDatasetTlvs dataset;
otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset);
// error = OT_ERROR_FAILED; // teste para forçar NULL dataset
if (error != OT_ERROR_NONE) {
log_i("Failed to get active NVS dataset from OpenThread");
} else {
log_i("Got active NVS dataset from OpenThread");
}
esp_err_t err = esp_openthread_auto_start((error == OT_ERROR_NONE) ? &dataset : NULL);
if (err != ESP_OK) {
log_i("Failed to AUTO start OpenThread");
} else {
log_i("AUTO start OpenThread done");
}
}
otStarted = true;
return;
}
void OpenThreadCLI::end() {
if (!otStarted) {
log_w("OpenThread CLI already stopped. Please begin() it before stopping again.");
return;
}
if (s_ot_task != NULL) {
vTaskDelete(s_ot_task);
// Clean up
esp_openthread_deinit();
esp_openthread_netif_glue_deinit();
esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister();
}
if (s_cli_task != NULL) {
vTaskDelete(s_cli_task);
}
if (s_console_cli_task != NULL) {
vTaskDelete(s_console_cli_task);
}
esp_event_loop_delete_default();
setRxBufferSize(0);
setTxBufferSize(0);
otStarted = false;
}
size_t OpenThreadCLI::write(uint8_t c) {
if (tx_queue == NULL) {
return 0;
}
if (xQueueSend(tx_queue, &c, 0) != pdPASS) {
return 0;
}
return 1;
}
size_t OpenThreadCLI::setBuffer(xQueueHandle &queue, size_t queue_len) {
if (queue) {
vQueueDelete(queue);
queue = NULL;
}
if (!queue_len) {
return 0;
}
queue = xQueueCreate(queue_len, sizeof(uint8_t));
if (!queue) {
return 0;
}
return queue_len;
}
size_t OpenThreadCLI::setTxBufferSize(size_t tx_queue_len) {
return setBuffer(tx_queue, tx_queue_len);
}
size_t OpenThreadCLI::setRxBufferSize(size_t rx_queue_len) {
return setBuffer(rx_queue, rx_queue_len);
}
int OpenThreadCLI::available(void) {
if (rx_queue == NULL) {
return -1;
}
return uxQueueMessagesWaiting(rx_queue);
}
int OpenThreadCLI::peek(void) {
if (rx_queue == NULL) {
return -1;
}
uint8_t c;
if (xQueuePeek(rx_queue, &c, 0)) {
return c;
}
return -1;
}
int OpenThreadCLI::read(void) {
if (rx_queue == NULL) {
return -1;
}
uint8_t c = 0;
if (xQueueReceive(rx_queue, &c, 0) == pdTRUE) {
return c;
}
return -1;
}
void OpenThreadCLI::flush() {
if (tx_queue == NULL) {
return;
}
// wait for the TX Queue to be empty
while (uxQueueMessagesWaiting(tx_queue));
}
OpenThreadCLI OThreadCLI;
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */

View file

@ -0,0 +1,60 @@
#pragma once
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
#include "esp_openthread.h"
#include "esp_openthread_cli.h"
#include "esp_openthread_lock.h"
#include "esp_openthread_netif_glue.h"
#include "esp_openthread_types.h"
#include "openthread/cli.h"
#include "openthread/instance.h"
#include "openthread/logging.h"
#include "openthread/tasklet.h"
#include "openthread/dataset_ftd.h"
#include "Arduino.h"
typedef std::function<void(void)> OnReceiveCb_t;
class OpenThreadCLI : public Stream {
private:
static size_t setBuffer(xQueueHandle &queue, size_t len);
bool otStarted = false;
public:
OpenThreadCLI();
~OpenThreadCLI();
// returns true if OpenThread CLI is running
operator bool() const;
// starts a task to read/write otStream. Default prompt is "ot> ". Set it to NULL to make it invisible.
void startConsole(Stream &otStream, bool echoback = true, const char *prompt = "ot> ");
void stopConsole();
void setPrompt(char *prompt); // changes the console prompt. NULL is an empty prompt.
void setEchoBack(bool echoback); // changes the console echoback option
void setStream(Stream &otStream); // changes the console Stream object
void onReceive(OnReceiveCb_t func); // called on a complete line of output from OT CLI, as OT Response
void begin(bool OThreadAutoStart = true);
void end();
// default size is 256 bytes
size_t setTxBufferSize(size_t tx_queue_len);
// default size is 1024 bytes
size_t setRxBufferSize(size_t rx_queue_len);
size_t write(uint8_t);
int available();
int read();
int peek();
void flush();
};
extern OpenThreadCLI OThreadCLI;
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */

View file

@ -0,0 +1,198 @@
#include "OThreadCLI.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
#include "OThreadCLI_Util.h"
#include <StreamString.h>
static const char *otRoleString[] = {
"Disabled", ///< The Thread stack is disabled.
"Detached", ///< Not currently participating in a Thread network/partition.
"Child", ///< The Thread Child role.
"Router", ///< The Thread Router role.
"Leader", ///< The Thread Leader role.
};
ot_device_role_t otGetDeviceRole() {
if (!OThreadCLI) {
return OT_ROLE_DISABLED;
}
otInstance *instance = esp_openthread_get_instance();
return (ot_device_role_t)otThreadGetDeviceRole(instance);
}
const char *otGetStringDeviceRole() {
return otRoleString[otGetDeviceRole()];
}
bool otGetRespCmd(const char *cmd, char *resp, uint32_t respTimeout) {
if (!OThreadCLI) {
return false;
}
StreamString cliRespAllLines;
char cliResp[256] = {0};
if (resp != NULL) {
*resp = '\0';
}
if (cmd == NULL) {
return true;
}
OThreadCLI.println(cmd);
log_d("CMD[%s]", cmd);
uint32_t timeout = millis() + respTimeout;
while (millis() < timeout) {
size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
// clip it on EOL
for (int i = 0; i < len; i++) {
if (cliResp[i] == '\r' || cliResp[i] == '\n') {
cliResp[i] = '\0';
}
}
log_d("Resp[%s]", cliResp);
if (strncmp(cliResp, "Done", 4) && strncmp(cliResp, "Error", 4)) {
cliRespAllLines += cliResp;
cliRespAllLines.println(); // Adds whatever EOL is for the OS
} else {
break;
}
}
if (!strncmp(cliResp, "Error", 4) || millis() > timeout) {
return false;
}
if (resp != NULL) {
strcpy(resp, cliRespAllLines.c_str());
}
return true;
}
bool otExecCommand(const char *cmd, const char *arg, ot_cmd_return_t *returnCode) {
if (!OThreadCLI) {
return false;
}
char cliResp[256] = {0};
if (cmd == NULL) {
return true;
}
if (arg == NULL) {
OThreadCLI.println(cmd);
} else {
OThreadCLI.print(cmd);
OThreadCLI.print(" ");
OThreadCLI.println(arg);
}
size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
// clip it on EOL
for (int i = 0; i < len; i++) {
if (cliResp[i] == '\r' || cliResp[i] == '\n') {
cliResp[i] = '\0';
}
}
log_d("CMD[%s %s] Resp[%s]", cmd, arg, cliResp);
// initial returnCode is success values
if (returnCode) {
returnCode->errorCode = 0;
returnCode->errorMessage = "Done";
}
if (!strncmp(cliResp, "Done", 4)) {
return true;
} else {
if (returnCode) {
// initial setting is a bad error message or it is something else...
// return -1 and the full returned message
returnCode->errorCode = -1;
returnCode->errorMessage = cliResp;
// parse cliResp looking for errorCode and errorMessage
// OT CLI error message format is "Error ##: msg\n" - Example:
//Error 35: InvalidCommand
//Error 7: InvalidArgs
char *i = cliResp;
char *m = cliResp;
while (*i && *i != ':') {
i++;
}
if (*i) {
*i = '\0';
m = i + 2; // message is 2 characters after ':'
while (i > cliResp && *i != ' ') {
i--; // search for ' ' before ":'
}
if (*i == ' ') {
i++; // move it forward to the number begining
returnCode->errorCode = atoi(i);
returnCode->errorMessage = m;
} // otherwise, it will keep the "bad error message" information
} // otherwise, it will keep the "bad error message" information
} // returnCode is NULL pointer
return false;
}
}
bool otPrintRespCLI(const char *cmd, Stream &output, uint32_t respTimeout) {
char cliResp[256] = {0};
if (cmd == NULL) {
return true;
}
OThreadCLI.println(cmd);
uint32_t timeout = millis() + respTimeout;
while (millis() < timeout) {
size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
if (cliResp[0] == '\0') {
// Straem has timed out and it should try again using parameter respTimeout
continue;
}
// clip it on EOL
for (int i = 0; i < len; i++) {
if (cliResp[i] == '\r' || cliResp[i] == '\n') {
cliResp[i] = '\0';
}
}
if (strncmp(cliResp, "Done", 4) && strncmp(cliResp, "Error", 4)) {
output.println(cliResp);
memset(cliResp, 0, sizeof(cliResp));
timeout = millis() + respTimeout; // renew timeout, line per line
} else {
break;
}
}
if (!strncmp(cliResp, "Error", 4) || millis() > timeout) {
return false;
}
return true;
}
void otPrintNetworkInformation(Stream &output) {
if (!OThreadCLI) {
return;
}
char resp[512];
output.println("Thread Setup:");
if (otGetRespCmd("state", resp)) {
output.printf("Node State: \t%s", resp);
}
if (otGetRespCmd("networkname", resp)) {
output.printf("Network Name: \t%s", resp);
}
if (otGetRespCmd("channel", resp)) {
output.printf("Channel: \t%s", resp);
}
if (otGetRespCmd("panid", resp)) {
output.printf("Pan ID: \t%s", resp);
}
if (otGetRespCmd("extpanid", resp)) {
output.printf("Ext Pan ID: \t%s", resp);
}
if (otGetRespCmd("networkkey", resp)) {
output.printf("Network Key: \t%s", resp);
}
if (otGetRespCmd("ipaddr", resp)) {
output.println("Node IP Addresses are:");
output.printf("%s", resp);
}
if (otGetRespCmd("ipmaddr", resp)) {
output.println("Node Multicast Addresses are:");
output.printf("%s", resp);
}
}
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */

View file

@ -0,0 +1,28 @@
#pragma once
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
typedef enum {
OT_ROLE_DISABLED = 0, ///< The Thread stack is disabled.
OT_ROLE_DETACHED = 1, ///< Not currently participating in a Thread network/partition.
OT_ROLE_CHILD = 2, ///< The Thread Child role.
OT_ROLE_ROUTER = 3, ///< The Thread Router role.
OT_ROLE_LEADER = 4, ///< The Thread Leader role.
} ot_device_role_t;
typedef struct {
int errorCode;
String errorMessage;
} ot_cmd_return_t;
ot_device_role_t otGetDeviceRole();
const char *otGetStringDeviceRole();
bool otGetRespCmd(const char *cmd, char *resp = NULL, uint32_t respTimeout = 5000);
bool otExecCommand(const char *cmd, const char *arg, ot_cmd_return_t *returnCode = NULL);
bool otPrintRespCLI(const char *cmd, Stream &output, uint32_t respTimeout);
void otPrintNetworkInformation(Stream &output);
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */