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:
parent
9e55ccd98e
commit
d891ddfec7
25 changed files with 1503 additions and 0 deletions
|
|
@ -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)
|
||||
|
|
|
|||
72
libraries/OpenThread/README.md
Normal file
72
libraries/OpenThread/README.md
Normal 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.
|
||||
9
libraries/OpenThread/examples/COAP/coap_lamp/ci.json
Normal file
9
libraries/OpenThread/examples/COAP/coap_lamp/ci.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"targets": {
|
||||
"esp32": false,
|
||||
"esp32c2": false,
|
||||
"esp32c3": false,
|
||||
"esp32s2": false,
|
||||
"esp32s3": false
|
||||
}
|
||||
}
|
||||
149
libraries/OpenThread/examples/COAP/coap_lamp/coap_lamp.ino
Normal file
149
libraries/OpenThread/examples/COAP/coap_lamp/coap_lamp.ino
Normal 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);
|
||||
}
|
||||
9
libraries/OpenThread/examples/COAP/coap_switch/ci.json
Normal file
9
libraries/OpenThread/examples/COAP/coap_switch/ci.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"targets": {
|
||||
"esp32": false,
|
||||
"esp32c2": false,
|
||||
"esp32c3": false,
|
||||
"esp32s2": false,
|
||||
"esp32s3": false
|
||||
}
|
||||
}
|
||||
174
libraries/OpenThread/examples/COAP/coap_switch/coap_switch.ino
Normal file
174
libraries/OpenThread/examples/COAP/coap_switch/coap_switch.ino
Normal 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);
|
||||
}
|
||||
20
libraries/OpenThread/examples/SimpleCLI/SimpleCLI.ino
Normal file
20
libraries/OpenThread/examples/SimpleCLI/SimpleCLI.ino
Normal 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() {}
|
||||
9
libraries/OpenThread/examples/SimpleCLI/ci.json
Normal file
9
libraries/OpenThread/examples/SimpleCLI/ci.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"targets": {
|
||||
"esp32": false,
|
||||
"esp32c2": false,
|
||||
"esp32c3": false,
|
||||
"esp32s2": false,
|
||||
"esp32s3": false
|
||||
}
|
||||
}
|
||||
32
libraries/OpenThread/examples/SimpleNode/SimpleNode.ino
Normal file
32
libraries/OpenThread/examples/SimpleNode/SimpleNode.ino
Normal 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);
|
||||
}
|
||||
9
libraries/OpenThread/examples/SimpleNode/ci.json
Normal file
9
libraries/OpenThread/examples/SimpleNode/ci.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"targets": {
|
||||
"esp32": false,
|
||||
"esp32c2": false,
|
||||
"esp32c3": false,
|
||||
"esp32s2": false,
|
||||
"esp32s3": false
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"targets": {
|
||||
"esp32": false,
|
||||
"esp32c2": false,
|
||||
"esp32c3": false,
|
||||
"esp32s2": false,
|
||||
"esp32s3": false
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"targets": {
|
||||
"esp32": false,
|
||||
"esp32c2": false,
|
||||
"esp32c3": false,
|
||||
"esp32s2": false,
|
||||
"esp32s3": false
|
||||
}
|
||||
}
|
||||
41
libraries/OpenThread/examples/ThreadScan/ThreadScan.ino
Normal file
41
libraries/OpenThread/examples/ThreadScan/ThreadScan.ino
Normal 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);
|
||||
}
|
||||
9
libraries/OpenThread/examples/ThreadScan/ci.json
Normal file
9
libraries/OpenThread/examples/ThreadScan/ci.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"targets": {
|
||||
"esp32": false,
|
||||
"esp32c2": false,
|
||||
"esp32c3": false,
|
||||
"esp32s2": false,
|
||||
"esp32s3": false
|
||||
}
|
||||
}
|
||||
9
libraries/OpenThread/examples/onReceive/ci.json
Normal file
9
libraries/OpenThread/examples/onReceive/ci.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"targets": {
|
||||
"esp32": false,
|
||||
"esp32c2": false,
|
||||
"esp32c3": false,
|
||||
"esp32s2": false,
|
||||
"esp32s3": false
|
||||
}
|
||||
}
|
||||
37
libraries/OpenThread/examples/onReceive/onReceive.ino
Normal file
37
libraries/OpenThread/examples/onReceive/onReceive.ino
Normal 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);
|
||||
}
|
||||
59
libraries/OpenThread/helper_functions.md
Normal file
59
libraries/OpenThread/helper_functions.md
Normal 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.
|
||||
47
libraries/OpenThread/keywords.txt
Normal file
47
libraries/OpenThread/keywords.txt
Normal 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
|
||||
9
libraries/OpenThread/library.properties
Normal file
9
libraries/OpenThread/library.properties
Normal 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
|
||||
426
libraries/OpenThread/src/OThreadCLI.cpp
Normal file
426
libraries/OpenThread/src/OThreadCLI.cpp
Normal 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 */
|
||||
60
libraries/OpenThread/src/OThreadCLI.h
Normal file
60
libraries/OpenThread/src/OThreadCLI.h
Normal 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 */
|
||||
198
libraries/OpenThread/src/OThreadCLI_Util.cpp
Normal file
198
libraries/OpenThread/src/OThreadCLI_Util.cpp
Normal 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 */
|
||||
28
libraries/OpenThread/src/OThreadCLI_Util.h
Normal file
28
libraries/OpenThread/src/OThreadCLI_Util.h
Normal 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 */
|
||||
Loading…
Reference in a new issue