Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit d891ddf

Browse files
SuGliderlucasssvazpre-commit-ci-lite[bot]
authoredJun 24, 2024··
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 <[email protected]> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 9e55ccd commit d891ddf

File tree

25 files changed

+1503
-0
lines changed

25 files changed

+1503
-0
lines changed
 

‎CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ set(ARDUINO_ALL_LIBRARIES
9595
LittleFS
9696
NetBIOS
9797
Network
98+
OpenThread
9899
PPP
99100
Preferences
100101
RainMaker
@@ -158,6 +159,10 @@ set(ARDUINO_LIBRARY_LittleFS_SRCS libraries/LittleFS/src/LittleFS.cpp)
158159

159160
set(ARDUINO_LIBRARY_NetBIOS_SRCS libraries/NetBIOS/src/NetBIOS.cpp)
160161

162+
set(ARDUINO_LIBRARY_OpenThread_SRCS
163+
libraries/OpenThread/src/OThreadCLI.cpp
164+
libraries/OpenThread/src/OThreadCLI_Util.cpp)
165+
161166
set(ARDUINO_LIBRARY_PPP_SRCS
162167
libraries/PPP/src/PPP.cpp
163168
libraries/PPP/src/ppp.c)

‎libraries/OpenThread/README.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
| Supported Targets | ESP32-C6 | ESP32-H2 |
2+
| ----------------- | -------- | -------- |
3+
4+
# ESP32 Arduino OpenThreadCLI
5+
6+
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.
7+
8+
There is one main class called `OpenThreadCLI` and a global object used to operate OpenThread CLI, called `OThreadCLI`.\
9+
Some [helper functions](helper_functions.md) were made available for working with the OpenThread CLI environment.
10+
11+
The available OpenThread Commands are documented in the [OpenThread CLI Reference Page](https://openthread.io/reference/cli/commands)
12+
13+
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**.
14+
15+
Below are the details of the class:
16+
17+
## Class Definition
18+
19+
```cpp
20+
class OpenThreadCLI : public Stream {
21+
private:
22+
static size_t setBuffer(xQueueHandle &queue, size_t len);
23+
bool otStarted = false;
24+
25+
public:
26+
OpenThreadCLI();
27+
~OpenThreadCLI();
28+
operator bool() const;
29+
30+
// Starts a task to read/write otStream. Default prompt is "ot> ". Set it to NULL to make it invisible.
31+
void startConsole(Stream& otStream, bool echoback = true, const char* prompt = "ot> ");
32+
void stopConsole();
33+
void setPrompt(char* prompt); // Changes the console prompt. NULL is an empty prompt.
34+
void setEchoBack(bool echoback); // Changes the console echoback option
35+
void setStream(Stream& otStream); // Changes the console Stream object
36+
void onReceive(OnReceiveCb_t func); // Called on a complete line of output from OT CLI, as OT Response
37+
38+
void begin(bool OThreadAutoStart = true);
39+
void end();
40+
41+
// Default size is 256 bytes
42+
size_t setTxBufferSize(size_t tx_queue_len);
43+
// Default size is 1024 bytes
44+
size_t setRxBufferSize(size_t rx_queue_len);
45+
46+
size_t write(uint8_t);
47+
int available();
48+
int read();
49+
int peek();
50+
void flush();
51+
};
52+
53+
extern OpenThreadCLI OThreadCLI;
54+
```
55+
56+
## Class Overview
57+
- The `OpenThreadCLI` class inherits from the `Stream` class, making it compatible with Arduino's standard I/O functions.
58+
- It provides methods for managing the OpenThread CLI, including starting and stopping the console, setting prompts, and handling received data.
59+
- You can customize the console behavior by adjusting parameters such as echoback and buffer sizes.
60+
61+
## Public Methods
62+
- `startConsole(Stream& otStream, bool echoback = true, const char* prompt = "ot> ")`: Starts the OpenThread console with the specified stream, echoback option, and prompt.
63+
- `stopConsole()`: Stops the OpenThread console.
64+
- `setPrompt(char* prompt)`: Changes the console prompt (set to NULL for an empty prompt).
65+
- `setEchoBack(bool echoback)`: Changes the console echoback option.
66+
- `setStream(Stream& otStream)`: Changes the console Stream object.
67+
- `onReceive(OnReceiveCb_t func)`: Sets a callback function to handle complete lines of output from the OT CLI.
68+
- `begin(bool OThreadAutoStart = true)`: Initializes the OpenThread stack (optional auto-start).
69+
- `end()`: Deinitializes the OpenThread stack.
70+
- `setTxBufferSize(size_t tx_queue_len)`: Sets the transmit buffer size (default is 256 bytes).
71+
- `setRxBufferSize(size_t rx_queue_len)`: Sets the receive buffer size (default is 1024 bytes).
72+
- `write(uint8_t)`, `available()`, `read()`, `peek()`, `flush()`: Standard Stream methods implementation for OpenThread CLI object.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": {
3+
"esp32": false,
4+
"esp32c2": false,
5+
"esp32c3": false,
6+
"esp32s2": false,
7+
"esp32s3": false
8+
}
9+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
#include "OThreadCLI.h"
2+
#include "OThreadCLI_Util.h"
3+
4+
#define OT_CHANNEL "24"
5+
#define OT_NETWORK_KEY "00112233445566778899aabbccddeeff"
6+
#define OT_MCAST_ADDR "ff05::abcd"
7+
#define OT_COAP_RESOURCE_NAME "Lamp"
8+
9+
const char *otSetupLeader[] = {
10+
// -- clear/disable all
11+
// stop CoAP
12+
"coap", "stop",
13+
// stop Thread
14+
"thread", "stop",
15+
// stop the interface
16+
"ifconfig", "down",
17+
// clear the dataset
18+
"dataset", "clear",
19+
// -- set dataset
20+
// create a new complete dataset with random data
21+
"dataset", "init new",
22+
// set the channel
23+
"dataset channel", OT_CHANNEL,
24+
// set the network key
25+
"dataset networkkey", OT_NETWORK_KEY,
26+
// commit the dataset
27+
"dataset", "commit active",
28+
// -- network start
29+
// start the interface
30+
"ifconfig", "up",
31+
// start the Thread network
32+
"thread", "start"
33+
};
34+
35+
const char *otCoapLamp[] = {
36+
// -- create a multicast IPv6 Address for this device
37+
"ipmaddr add", OT_MCAST_ADDR,
38+
// -- start and create a CoAP resource
39+
// start CoAP as server
40+
"coap", "start",
41+
// create a CoAP resource
42+
"coap resource", OT_COAP_RESOURCE_NAME,
43+
// set the CoAP resource initial value
44+
"coap set", "0"
45+
};
46+
47+
bool otDeviceSetup(const char **otSetupCmds, uint8_t nCmds1, const char **otCoapCmds, uint8_t nCmds2, ot_device_role_t expectedRole) {
48+
Serial.println("Starting OpenThread.");
49+
Serial.println("Running as Lamp (RGB LED) - use the other C6/H2 as a Switch");
50+
uint8_t i;
51+
for (i = 0; i < nCmds1; i++) {
52+
if (!otExecCommand(otSetupCmds[i * 2], otSetupCmds[i * 2 + 1])) {
53+
break;
54+
}
55+
}
56+
if (i != nCmds1) {
57+
log_e("Sorry, OpenThread Network setup failed!");
58+
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
59+
return false;
60+
}
61+
Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
62+
// wait for the expected Device Role to start
63+
uint8_t tries = 24; // 24 x 2.5 sec = 1 min
64+
while (tries && otGetDeviceRole() != expectedRole) {
65+
Serial.print(".");
66+
delay(2500);
67+
tries--;
68+
}
69+
Serial.println();
70+
if (!tries) {
71+
log_e("Sorry, Device Role failed by timeout! Current Role: %s.", otGetStringDeviceRole());
72+
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
73+
return false;
74+
}
75+
Serial.printf("Device is %s.\r\n", otGetStringDeviceRole());
76+
for (i = 0; i < nCmds2; i++) {
77+
if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
78+
break;
79+
}
80+
}
81+
if (i != nCmds2) {
82+
log_e("Sorry, OpenThread CoAP setup failed!");
83+
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
84+
return false;
85+
}
86+
Serial.println("OpenThread setup done. Node is ready.");
87+
// all fine! LED goes Green
88+
neopixelWrite(RGB_BUILTIN, 0, 64, 8); // GREEN ... Lamp is ready!
89+
return true;
90+
}
91+
92+
void setupNode() {
93+
// tries to set the Thread Network node and only returns when succeded
94+
bool startedCorrectly = false;
95+
while (!startedCorrectly) {
96+
startedCorrectly |=
97+
otDeviceSetup(otSetupLeader, sizeof(otSetupLeader) / sizeof(char *) / 2, otCoapLamp, sizeof(otCoapLamp) / sizeof(char *) / 2, OT_ROLE_LEADER);
98+
if (!startedCorrectly) {
99+
Serial.println("Setup Failed...\r\nTrying again...");
100+
}
101+
}
102+
}
103+
104+
// this function is used by the Lamp mode to listen for CoAP frames from the Switch Node
105+
void otCOAPListen() {
106+
// waits for the client to send a CoAP request
107+
char cliResp[256] = {0};
108+
size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
109+
cliResp[len - 1] = '\0';
110+
if (strlen(cliResp)) {
111+
String sResp(cliResp);
112+
// cliResp shall be something like:
113+
// "coap request from fd0c:94df:f1ae:b39a:ec47:ec6d:15e8:804a PUT with payload: 30"
114+
// payload may be 30 or 31 (HEX) '0' or '1' (ASCII)
115+
log_d("Msg[%s]", cliResp);
116+
if (sResp.startsWith("coap request from") && sResp.indexOf("PUT") > 0) {
117+
char payload = sResp.charAt(sResp.length() - 1); // last character in the payload
118+
log_i("CoAP PUT [%s]\r\n", payload == '0' ? "OFF" : "ON");
119+
if (payload == '0') {
120+
for (int16_t c = 248; c > 16; c -= 8) {
121+
neopixelWrite(RGB_BUILTIN, c, c, c); // ramp down
122+
delay(5);
123+
}
124+
neopixelWrite(RGB_BUILTIN, 0, 0, 0); // Lamp Off
125+
} else {
126+
for (int16_t c = 16; c < 248; c += 8) {
127+
neopixelWrite(RGB_BUILTIN, c, c, c); // ramp up
128+
delay(5);
129+
}
130+
neopixelWrite(RGB_BUILTIN, 255, 255, 255); // Lamp On
131+
}
132+
}
133+
}
134+
}
135+
136+
void setup() {
137+
Serial.begin(115200);
138+
// LED starts RED, indicating not connected to Thread network.
139+
neopixelWrite(RGB_BUILTIN, 64, 0, 0);
140+
OThreadCLI.begin(false); // No AutoStart is necessary
141+
OThreadCLI.setTimeout(250); // waits 250ms for the OpenThread CLI response
142+
setupNode();
143+
// LED goes Green when all is ready and Red when failed.
144+
}
145+
146+
void loop() {
147+
otCOAPListen();
148+
delay(10);
149+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": {
3+
"esp32": false,
4+
"esp32c2": false,
5+
"esp32c3": false,
6+
"esp32s2": false,
7+
"esp32s3": false
8+
}
9+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#include "OThreadCLI.h"
2+
#include "OThreadCLI_Util.h"
3+
4+
#define USER_BUTTON 9 // C6/H2 Boot button
5+
#define OT_CHANNEL "24"
6+
#define OT_NETWORK_KEY "00112233445566778899aabbccddeeff"
7+
#define OT_MCAST_ADDR "ff05::abcd"
8+
#define OT_COAP_RESOURCE_NAME "Lamp"
9+
10+
const char *otSetupChild[] = {
11+
// -- clear/disable all
12+
// stop CoAP
13+
"coap", "stop",
14+
// stop Thread
15+
"thread", "stop",
16+
// stop the interface
17+
"ifconfig", "down",
18+
// clear the dataset
19+
"dataset", "clear",
20+
// -- set dataset
21+
// set the channel
22+
"dataset channel", OT_CHANNEL,
23+
// set the network key
24+
"dataset networkkey", OT_NETWORK_KEY,
25+
// commit the dataset
26+
"dataset", "commit active",
27+
// -- network start
28+
// start the interface
29+
"ifconfig", "up",
30+
// start the Thread network
31+
"thread", "start"
32+
};
33+
34+
const char *otCoapSwitch[] = {
35+
// -- start CoAP as client
36+
"coap", "start"
37+
};
38+
39+
bool otDeviceSetup(
40+
const char **otSetupCmds, uint8_t nCmds1, const char **otCoapCmds, uint8_t nCmds2, ot_device_role_t expectedRole1, ot_device_role_t expectedRole2
41+
) {
42+
Serial.println("Starting OpenThread.");
43+
Serial.println("Running as Switch - use the BOOT button to toggle the other C6/H2 as a Lamp");
44+
uint8_t i;
45+
for (i = 0; i < nCmds1; i++) {
46+
if (!otExecCommand(otSetupCmds[i * 2], otSetupCmds[i * 2 + 1])) {
47+
break;
48+
}
49+
}
50+
if (i != nCmds1) {
51+
log_e("Sorry, OpenThread Network setup failed!");
52+
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
53+
return false;
54+
}
55+
Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
56+
// wait for the expected Device Role to start
57+
uint8_t tries = 24; // 24 x 2.5 sec = 1 min
58+
while (tries && otGetDeviceRole() != expectedRole1 && otGetDeviceRole() != expectedRole2) {
59+
Serial.print(".");
60+
delay(2500);
61+
tries--;
62+
}
63+
Serial.println();
64+
if (!tries) {
65+
log_e("Sorry, Device Role failed by timeout! Current Role: %s.", otGetStringDeviceRole());
66+
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
67+
return false;
68+
}
69+
Serial.printf("Device is %s.\r\n", otGetStringDeviceRole());
70+
for (i = 0; i < nCmds2; i++) {
71+
if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
72+
break;
73+
}
74+
}
75+
if (i != nCmds2) {
76+
log_e("Sorry, OpenThread CoAP setup failed!");
77+
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
78+
return false;
79+
}
80+
Serial.println("OpenThread setup done. Node is ready.");
81+
// all fine! LED goes and stays Blue
82+
neopixelWrite(RGB_BUILTIN, 0, 0, 64); // BLUE ... Swtich is ready!
83+
return true;
84+
}
85+
86+
void setupNode() {
87+
// tries to set the Thread Network node and only returns when succeded
88+
bool startedCorrectly = false;
89+
while (!startedCorrectly) {
90+
startedCorrectly |= otDeviceSetup(
91+
otSetupChild, sizeof(otSetupChild) / sizeof(char *) / 2, otCoapSwitch, sizeof(otCoapSwitch) / sizeof(char *) / 2, OT_ROLE_CHILD, OT_ROLE_ROUTER
92+
);
93+
if (!startedCorrectly) {
94+
Serial.println("Setup Failed...\r\nTrying again...");
95+
}
96+
}
97+
}
98+
99+
// Sends the CoAP frame to the Lamp node
100+
bool otCoapPUT(bool lampState) {
101+
bool gotDone = false, gotConfirmation = false;
102+
String coapMsg = "coap put ";
103+
coapMsg += OT_MCAST_ADDR;
104+
coapMsg += " ";
105+
coapMsg += OT_COAP_RESOURCE_NAME;
106+
coapMsg += " con 0";
107+
108+
// final command is "coap put ff05::abcd Lamp con 1" or "coap put ff05::abcd Lamp con 0"
109+
if (lampState) {
110+
coapMsg[coapMsg.length() - 1] = '1';
111+
}
112+
OThreadCLI.println(coapMsg.c_str());
113+
log_d("Send CLI CMD:[%s]", coapMsg.c_str());
114+
115+
char cliResp[256];
116+
// waits for the CoAP confirmation and Done message for about 1.25 seconds
117+
// timeout is based on Stream::setTimeout()
118+
// Example of the expected confirmation response: "coap response from fdae:3289:1783:5c3f:fd84:c714:7e83:6122"
119+
uint8_t tries = 5;
120+
*cliResp = '\0';
121+
while (tries && !(gotDone && gotConfirmation)) {
122+
size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
123+
cliResp[len - 1] = '\0';
124+
log_d("Try[%d]::MSG[%s]", tries, cliResp);
125+
if (strlen(cliResp)) {
126+
if (!strncmp(cliResp, "coap response from", 18)) {
127+
gotConfirmation = true;
128+
}
129+
if (!strncmp(cliResp, "Done", 4)) {
130+
gotDone = true;
131+
}
132+
}
133+
tries--;
134+
}
135+
if (gotDone && gotConfirmation) {
136+
return true;
137+
}
138+
return false;
139+
}
140+
141+
// this fucntion is used by the Switch mode to check the BOOT Button and send the user action to the Lamp node
142+
void checkUserButton() {
143+
static long unsigned int lastPress = 0;
144+
const long unsigned int debounceTime = 500;
145+
static bool lastLampState = true; // first button press will turn the Lamp OFF from inital Green
146+
147+
pinMode(USER_BUTTON, INPUT_PULLUP); // C6/H2 User Button
148+
if (millis() > lastPress + debounceTime && digitalRead(USER_BUTTON) == LOW) {
149+
lastLampState = !lastLampState;
150+
if (!otCoapPUT(lastLampState)) { // failed: Lamp Node is not responding due to be off or unreachable
151+
// timeout from the CoAP PUT message... restart the node.
152+
neopixelWrite(RGB_BUILTIN, 255, 0, 0); // RED ... something failed!
153+
Serial.println("Reseting the Node as Switch... wait.");
154+
// start over...
155+
setupNode();
156+
}
157+
lastPress = millis();
158+
}
159+
}
160+
161+
void setup() {
162+
Serial.begin(115200);
163+
// LED starts RED, indicating not connected to Thread network.
164+
neopixelWrite(RGB_BUILTIN, 64, 0, 0);
165+
OThreadCLI.begin(false); // No AutoStart is necessary
166+
OThreadCLI.setTimeout(250); // waits 250ms for the OpenThread CLI response
167+
setupNode();
168+
// LED goes and keeps Blue when all is ready and Red when failed.
169+
}
170+
171+
void loop() {
172+
checkUserButton();
173+
delay(10);
174+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* OpenThread.begin(false) will not automatically start a node in a Thread Network
3+
* The user will need to start it manually using the OpenThread CLI commands
4+
* Use the Serial Monitor to interact with the OpenThread CLI
5+
*
6+
* Type 'help' for a list of commands.
7+
* Documentation: https://openthread.io/reference/cli/commands
8+
*
9+
*/
10+
11+
#include "OThreadCLI.h"
12+
13+
void setup() {
14+
Serial.begin(115200);
15+
OThreadCLI.begin(false); // No AutoStart - fresh start
16+
Serial.println("OpenThread CLI started - type 'help' for a list of commands.");
17+
OThreadCLI.startConsole(Serial);
18+
}
19+
20+
void loop() {}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": {
3+
"esp32": false,
4+
"esp32c2": false,
5+
"esp32c3": false,
6+
"esp32s2": false,
7+
"esp32s3": false
8+
}
9+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* OpenThread.begin() will automatically start a node in a Thread Network
3+
* If NVS is empty, default configuration will be as follow:
4+
*
5+
* NETWORK_NAME "OpenThread-ESP"
6+
* MESH_LOCAL_PREFIX "fd00:db8:a0:0::/64"
7+
* NETWORK_CHANNEL 15
8+
* NETWORK_PANID 0x1234
9+
* NETWORK_EXTPANID "dead00beef00cafe"
10+
* NETWORK_KEY "00112233445566778899aabbccddeeff"
11+
* NETWORK_PSKC "104810e2315100afd6bc9215a6bfac53"
12+
*
13+
* If NVS has already a dataset information, it will load it from there.
14+
*/
15+
16+
#include "OThreadCLI.h"
17+
#include "OThreadCLI_Util.h"
18+
19+
// The first device to start Thread will be the Leader
20+
// Next devices will be Router or Child
21+
22+
void setup() {
23+
Serial.begin(115200);
24+
OThreadCLI.begin(); // AutoStart using Thread default settings
25+
otPrintNetworkInformation(Serial); // Print Current Thread Network Information
26+
}
27+
28+
void loop() {
29+
Serial.print("Thread Node State: ");
30+
Serial.println(otGetStringDeviceRole());
31+
delay(5000);
32+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": {
3+
"esp32": false,
4+
"esp32c2": false,
5+
"esp32c3": false,
6+
"esp32s2": false,
7+
"esp32s3": false
8+
}
9+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* OpenThread.begin(false) will not automatically start a node in a Thread Network
3+
* A Leader node is the first device, that has a complete dataset, to start Thread
4+
* A complete dataset is easily achieved by using the OpenThread CLI command "dataset init new"
5+
*
6+
* In order to allow other node to join the network,
7+
* all of them shall use the same network master key
8+
* The network master key is a 16-byte key that is used to secure the network
9+
*
10+
* Using the same channel will make the process faster
11+
*
12+
*/
13+
14+
#include "OThreadCLI.h"
15+
#include "OThreadCLI_Util.h"
16+
17+
#define CLI_NETWORK_KEY "dataset networkkey 00112233445566778899aabbccddeeff"
18+
#define CLI_NETWORK_CHANEL "dataset channel 24"
19+
20+
void setup() {
21+
Serial.begin(115200);
22+
OThreadCLI.begin(false); // No AutoStart - fresh start
23+
Serial.println("Setting up OpenThread Node as Leader");
24+
25+
OThreadCLI.println("dataset init new");
26+
OThreadCLI.println(CLI_NETWORK_KEY);
27+
OThreadCLI.println(CLI_NETWORK_CHANEL);
28+
OThreadCLI.println("dataset commit active");
29+
OThreadCLI.println("ifconfig up");
30+
OThreadCLI.println("thread start");
31+
}
32+
33+
void loop() {
34+
Serial.print("Thread Node State: ");
35+
Serial.println(otGetStringDeviceRole());
36+
delay(5000);
37+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": {
3+
"esp32": false,
4+
"esp32c2": false,
5+
"esp32c3": false,
6+
"esp32s2": false,
7+
"esp32s3": false
8+
}
9+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* OpenThread.begin(false) will not automatically start a node in a Thread Network
3+
* A Router/Child node is the device that will join an existing Thread Network
4+
*
5+
* In order to allow this node to join the network,
6+
* it shall use the same network master key as used by the Leader Node
7+
* The network master key is a 16-byte key that is used to secure the network
8+
*
9+
* Using the same channel will make the process faster
10+
*
11+
*/
12+
13+
#include "OThreadCLI.h"
14+
#include "OThreadCLI_Util.h"
15+
16+
#define CLI_NETWORK_KEY "dataset networkkey 00112233445566778899aabbccddeeff"
17+
#define CLI_NETWORK_CHANEL "dataset channel 24"
18+
19+
void setup() {
20+
Serial.begin(115200);
21+
OThreadCLI.begin(false); // No AutoStart - fresh start
22+
Serial.println("Setting up OpenThread Node as Router/Child");
23+
Serial.println("Make sure the Leader Node is already running");
24+
25+
OThreadCLI.println("dataset clear");
26+
OThreadCLI.println(CLI_NETWORK_KEY);
27+
OThreadCLI.println(CLI_NETWORK_CHANEL);
28+
OThreadCLI.println("dataset commit active");
29+
OThreadCLI.println("ifconfig up");
30+
OThreadCLI.println("thread start");
31+
}
32+
33+
void loop() {
34+
Serial.print("Thread Node State: ");
35+
Serial.println(otGetStringDeviceRole());
36+
delay(5000);
37+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": {
3+
"esp32": false,
4+
"esp32c2": false,
5+
"esp32c3": false,
6+
"esp32s2": false,
7+
"esp32s3": false
8+
}
9+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
OpenThread.begin(true) will automatically start a node in a Thread Network
3+
Full scanning requires the thread node to be at least in Child state.
4+
5+
This will scan the IEEE 802.14.5 devices in the local area using CLI "scan" command
6+
As soon as this device turns into a Child, Router or Leader, it will be able to
7+
scan for Local Thread Networks as well.
8+
9+
*/
10+
11+
#include "OThreadCLI.h"
12+
#include "OThreadCLI_Util.h"
13+
14+
void setup() {
15+
Serial.begin(115200);
16+
OThreadCLI.begin(true); // For scanning, AutoStart must be active, any setup
17+
OThreadCLI.setTimeout(100); // Set a timeout for the CLI response
18+
Serial.println();
19+
Serial.println("This sketch will continuously scan the Thread Local Network and all devices IEEE 802.15.4 compatible");
20+
}
21+
22+
void loop() {
23+
Serial.println();
24+
Serial.println("Scanning for nearby IEEE 802.15.4 devices:");
25+
// 802.15.4 Scan just needs a previous OThreadCLI.begin()
26+
if (!otPrintRespCLI("scan", Serial, 3000)) {
27+
Serial.println("Scan Failed...");
28+
}
29+
delay(5000);
30+
if (otGetDeviceRole() < OT_ROLE_CHILD) {
31+
Serial.println();
32+
Serial.println("This device has not started Thread yet, bypassing Discovery Scan");
33+
return;
34+
}
35+
Serial.println();
36+
Serial.println("Scanning MLE Discover:");
37+
if (!otPrintRespCLI("discover", Serial, 3000)) {
38+
Serial.println("Discover Failed...");
39+
}
40+
delay(5000);
41+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": {
3+
"esp32": false,
4+
"esp32c2": false,
5+
"esp32c3": false,
6+
"esp32s2": false,
7+
"esp32s3": false
8+
}
9+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"targets": {
3+
"esp32": false,
4+
"esp32c2": false,
5+
"esp32c3": false,
6+
"esp32s2": false,
7+
"esp32s3": false
8+
}
9+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
OpenThread.begin() will automatically start a node in a Thread Network
3+
This will demonstrate how to capture the CLI response in a callback function
4+
The device state shall change from "disabled" to valid Thread states along time
5+
*/
6+
7+
#include "OThreadCLI.h"
8+
9+
// reads all the lines sent by CLI, one by one
10+
// ignores some lines that are just a sequence of \r\n
11+
void otReceivedLine() {
12+
String line = "";
13+
while (OThreadCLI.available() > 0) {
14+
char ch = OThreadCLI.read();
15+
if (ch != '\r' && ch != '\n') {
16+
line += ch;
17+
}
18+
}
19+
// ignores empty lines, usually EOL sequence
20+
if (line.length() > 0) {
21+
Serial.print("OpenThread CLI RESP===> ");
22+
Serial.println(line.c_str());
23+
}
24+
}
25+
26+
void setup() {
27+
Serial.begin(115200);
28+
OThreadCLI.begin(); // AutoStart
29+
OThreadCLI.onReceive(otReceivedLine);
30+
}
31+
32+
void loop() {
33+
// sends the "state" command to the CLI every second
34+
// the onReceive() Callback Function will read and process the response
35+
OThreadCLI.println("state");
36+
delay(1000);
37+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# OpenThread Helper Functions and Types
2+
3+
The following helper functions and types are designed to simplify writing Arduino sketches for OpenThread.\
4+
They provide useful utilities for managing OpenThread stack behavior and interacting with the Thread network.
5+
6+
### Enumerated Type: `ot_device_role_t`
7+
8+
This enumeration defines the possible roles of a Thread device within the network:
9+
10+
- `OT_ROLE_DISABLED`: The Thread stack is disabled.
11+
- `OT_ROLE_DETACHED`: The device is not currently participating in a Thread network/partition.
12+
- `OT_ROLE_CHILD`: The device operates as a Thread Child.
13+
- `OT_ROLE_ROUTER`: The device operates as a Thread Router.
14+
- `OT_ROLE_LEADER`: The device operates as a Thread Leader.
15+
16+
### Struct: `ot_cmd_return_t`
17+
18+
This structure represents the return status of an OpenThread CLI command:
19+
20+
- `errorCode`: An integer representing the error code (if any).
21+
- `errorMessage`: A string containing an error message (if applicable).
22+
23+
### Function: `otGetDeviceRole()`
24+
25+
- Returns the current role of the device as an `ot_device_role_t` value.
26+
27+
### Function: `otGetStringDeviceRole()`
28+
29+
- Returns a human-readable string representation of the device role (e.g., "Child," "Router," etc.).
30+
31+
### Function: `otGetRespCmd(const char* cmd, char* resp = NULL, uint32_t respTimeout = 5000)`
32+
33+
- Executes an OpenThread CLI command and retrieves the response.
34+
- Parameters:
35+
- `cmd`: The OpenThread CLI command to execute.
36+
- `resp`: Optional buffer to store the response (if provided).
37+
- `respTimeout`: Timeout (in milliseconds) for waiting for the response.
38+
39+
### Function: `otExecCommand(const char* cmd, const char* arg, ot_cmd_return_t* returnCode = NULL)`
40+
41+
- Executes an OpenThread CLI command with an argument.
42+
- Parameters:
43+
- `cmd`: The OpenThread CLI command to execute.
44+
- `arg`: The argument for the command.
45+
- `returnCode`: Optional pointer to an `ot_cmd_return_t` structure to store the return status.
46+
47+
### Function: `otPrintRespCLI(const char* cmd, Stream& output, uint32_t respTimeout)`
48+
49+
- Executes an OpenThread CLI command and prints the response to the specified output stream.
50+
- Parameters:
51+
- `cmd`: The OpenThread CLI command to execute.
52+
- `output`: The output stream (e.g., Serial) to print the response.
53+
- `respTimeout`: Timeout (in milliseconds) for waiting for the response.
54+
55+
### Function: `otPrintNetworkInformation(Stream& output)`
56+
57+
- Prints information about the current Thread network to the specified output stream.
58+
- Parameters:
59+
- `output`: The output stream (e.g., Serial) to print the network information.

‎libraries/OpenThread/keywords.txt

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#######################################
2+
# Syntax Coloring Map For OpenThread
3+
#######################################
4+
5+
#######################################
6+
# Datatypes (KEYWORD1)
7+
#######################################
8+
9+
OThreadCLI KEYWORD1
10+
OpenThreadCLI KEYWORD1
11+
ot_cmd_return_t KEYWORD1
12+
ot_device_role_t KEYWORD1
13+
14+
#######################################
15+
# Methods and Functions (KEYWORD2)
16+
#######################################
17+
18+
startConsole KEYWORD2
19+
stopConsole KEYWORD2
20+
setPrompt KEYWORD2
21+
setEchoBack KEYWORD2
22+
setStream KEYWORD2
23+
onReceive KEYWORD2
24+
begin KEYWORD2
25+
setTxBufferSize KEYWORD2
26+
setRxBufferSize KEYWORD2
27+
write KEYWORD2
28+
available KEYWORD2
29+
read KEYWORD2
30+
peek KEYWORD2
31+
flush KEYWORD2
32+
otGetDeviceRole KEYWORD2
33+
otGetStringDeviceRole KEYWORD2
34+
otGetRespCmd KEYWORD2
35+
otExecCommand KEYWORD2
36+
otPrintRespCLI KEYWORD2
37+
otPrintNetworkInformation KEYWORD2
38+
39+
#######################################
40+
# Constants (LITERAL1)
41+
#######################################
42+
43+
OT_ROLE_DISABLED LITERAL1
44+
OT_ROLE_DETACHED LITERAL1
45+
OT_ROLE_CHILD LITERAL1
46+
OT_ROLE_ROUTER LITERAL1
47+
OT_ROLE_LEADER LITERAL1
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
name=OpenThread
2+
version=1.0.0
3+
author=Rodrigo Garcia | GitHub @SuGlider
4+
maintainer=Rodrigo Garcia <Rodrigo.Garcia@espressif.com>
5+
sentence=Library for OpenThread Network on ESP32.
6+
paragraph=This library is a wrapper for OpenThread CLI. It provides a simple way to interact with OpenThread Network.
7+
category=Communication
8+
url=https://github.com/espressif/arduino-esp32/
9+
architectures=esp32

‎libraries/OpenThread/src/OThreadCLI.cpp

Lines changed: 426 additions & 0 deletions
Large diffs are not rendered by default.

‎libraries/OpenThread/src/OThreadCLI.h

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#pragma once
2+
#include "soc/soc_caps.h"
3+
#include "sdkconfig.h"
4+
#if SOC_IEEE802154_SUPPORTED
5+
#if CONFIG_OPENTHREAD_ENABLED
6+
7+
#include "esp_openthread.h"
8+
#include "esp_openthread_cli.h"
9+
#include "esp_openthread_lock.h"
10+
#include "esp_openthread_netif_glue.h"
11+
#include "esp_openthread_types.h"
12+
13+
#include "openthread/cli.h"
14+
#include "openthread/instance.h"
15+
#include "openthread/logging.h"
16+
#include "openthread/tasklet.h"
17+
#include "openthread/dataset_ftd.h"
18+
19+
#include "Arduino.h"
20+
21+
typedef std::function<void(void)> OnReceiveCb_t;
22+
23+
class OpenThreadCLI : public Stream {
24+
private:
25+
static size_t setBuffer(xQueueHandle &queue, size_t len);
26+
bool otStarted = false;
27+
28+
public:
29+
OpenThreadCLI();
30+
~OpenThreadCLI();
31+
// returns true if OpenThread CLI is running
32+
operator bool() const;
33+
34+
// starts a task to read/write otStream. Default prompt is "ot> ". Set it to NULL to make it invisible.
35+
void startConsole(Stream &otStream, bool echoback = true, const char *prompt = "ot> ");
36+
void stopConsole();
37+
void setPrompt(char *prompt); // changes the console prompt. NULL is an empty prompt.
38+
void setEchoBack(bool echoback); // changes the console echoback option
39+
void setStream(Stream &otStream); // changes the console Stream object
40+
void onReceive(OnReceiveCb_t func); // called on a complete line of output from OT CLI, as OT Response
41+
42+
void begin(bool OThreadAutoStart = true);
43+
void end();
44+
45+
// default size is 256 bytes
46+
size_t setTxBufferSize(size_t tx_queue_len);
47+
// default size is 1024 bytes
48+
size_t setRxBufferSize(size_t rx_queue_len);
49+
50+
size_t write(uint8_t);
51+
int available();
52+
int read();
53+
int peek();
54+
void flush();
55+
};
56+
57+
extern OpenThreadCLI OThreadCLI;
58+
59+
#endif /* CONFIG_OPENTHREAD_ENABLED */
60+
#endif /* SOC_IEEE802154_SUPPORTED */
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#include "OThreadCLI.h"
2+
#if SOC_IEEE802154_SUPPORTED
3+
#if CONFIG_OPENTHREAD_ENABLED
4+
5+
#include "OThreadCLI_Util.h"
6+
#include <StreamString.h>
7+
8+
static const char *otRoleString[] = {
9+
"Disabled", ///< The Thread stack is disabled.
10+
"Detached", ///< Not currently participating in a Thread network/partition.
11+
"Child", ///< The Thread Child role.
12+
"Router", ///< The Thread Router role.
13+
"Leader", ///< The Thread Leader role.
14+
};
15+
16+
ot_device_role_t otGetDeviceRole() {
17+
if (!OThreadCLI) {
18+
return OT_ROLE_DISABLED;
19+
}
20+
otInstance *instance = esp_openthread_get_instance();
21+
return (ot_device_role_t)otThreadGetDeviceRole(instance);
22+
}
23+
24+
const char *otGetStringDeviceRole() {
25+
return otRoleString[otGetDeviceRole()];
26+
}
27+
28+
bool otGetRespCmd(const char *cmd, char *resp, uint32_t respTimeout) {
29+
if (!OThreadCLI) {
30+
return false;
31+
}
32+
StreamString cliRespAllLines;
33+
char cliResp[256] = {0};
34+
if (resp != NULL) {
35+
*resp = '\0';
36+
}
37+
if (cmd == NULL) {
38+
return true;
39+
}
40+
OThreadCLI.println(cmd);
41+
log_d("CMD[%s]", cmd);
42+
uint32_t timeout = millis() + respTimeout;
43+
while (millis() < timeout) {
44+
size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
45+
// clip it on EOL
46+
for (int i = 0; i < len; i++) {
47+
if (cliResp[i] == '\r' || cliResp[i] == '\n') {
48+
cliResp[i] = '\0';
49+
}
50+
}
51+
log_d("Resp[%s]", cliResp);
52+
if (strncmp(cliResp, "Done", 4) && strncmp(cliResp, "Error", 4)) {
53+
cliRespAllLines += cliResp;
54+
cliRespAllLines.println(); // Adds whatever EOL is for the OS
55+
} else {
56+
break;
57+
}
58+
}
59+
if (!strncmp(cliResp, "Error", 4) || millis() > timeout) {
60+
return false;
61+
}
62+
if (resp != NULL) {
63+
strcpy(resp, cliRespAllLines.c_str());
64+
}
65+
return true;
66+
}
67+
68+
bool otExecCommand(const char *cmd, const char *arg, ot_cmd_return_t *returnCode) {
69+
if (!OThreadCLI) {
70+
return false;
71+
}
72+
char cliResp[256] = {0};
73+
if (cmd == NULL) {
74+
return true;
75+
}
76+
if (arg == NULL) {
77+
OThreadCLI.println(cmd);
78+
} else {
79+
OThreadCLI.print(cmd);
80+
OThreadCLI.print(" ");
81+
OThreadCLI.println(arg);
82+
}
83+
size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
84+
// clip it on EOL
85+
for (int i = 0; i < len; i++) {
86+
if (cliResp[i] == '\r' || cliResp[i] == '\n') {
87+
cliResp[i] = '\0';
88+
}
89+
}
90+
log_d("CMD[%s %s] Resp[%s]", cmd, arg, cliResp);
91+
// initial returnCode is success values
92+
if (returnCode) {
93+
returnCode->errorCode = 0;
94+
returnCode->errorMessage = "Done";
95+
}
96+
if (!strncmp(cliResp, "Done", 4)) {
97+
return true;
98+
} else {
99+
if (returnCode) {
100+
// initial setting is a bad error message or it is something else...
101+
// return -1 and the full returned message
102+
returnCode->errorCode = -1;
103+
returnCode->errorMessage = cliResp;
104+
// parse cliResp looking for errorCode and errorMessage
105+
// OT CLI error message format is "Error ##: msg\n" - Example:
106+
//Error 35: InvalidCommand
107+
//Error 7: InvalidArgs
108+
char *i = cliResp;
109+
char *m = cliResp;
110+
while (*i && *i != ':') {
111+
i++;
112+
}
113+
if (*i) {
114+
*i = '\0';
115+
m = i + 2; // message is 2 characters after ':'
116+
while (i > cliResp && *i != ' ') {
117+
i--; // search for ' ' before ":'
118+
}
119+
if (*i == ' ') {
120+
i++; // move it forward to the number begining
121+
returnCode->errorCode = atoi(i);
122+
returnCode->errorMessage = m;
123+
} // otherwise, it will keep the "bad error message" information
124+
} // otherwise, it will keep the "bad error message" information
125+
} // returnCode is NULL pointer
126+
return false;
127+
}
128+
}
129+
130+
bool otPrintRespCLI(const char *cmd, Stream &output, uint32_t respTimeout) {
131+
char cliResp[256] = {0};
132+
if (cmd == NULL) {
133+
return true;
134+
}
135+
OThreadCLI.println(cmd);
136+
uint32_t timeout = millis() + respTimeout;
137+
while (millis() < timeout) {
138+
size_t len = OThreadCLI.readBytesUntil('\n', cliResp, sizeof(cliResp));
139+
if (cliResp[0] == '\0') {
140+
// Straem has timed out and it should try again using parameter respTimeout
141+
continue;
142+
}
143+
// clip it on EOL
144+
for (int i = 0; i < len; i++) {
145+
if (cliResp[i] == '\r' || cliResp[i] == '\n') {
146+
cliResp[i] = '\0';
147+
}
148+
}
149+
if (strncmp(cliResp, "Done", 4) && strncmp(cliResp, "Error", 4)) {
150+
output.println(cliResp);
151+
memset(cliResp, 0, sizeof(cliResp));
152+
timeout = millis() + respTimeout; // renew timeout, line per line
153+
} else {
154+
break;
155+
}
156+
}
157+
if (!strncmp(cliResp, "Error", 4) || millis() > timeout) {
158+
return false;
159+
}
160+
return true;
161+
}
162+
163+
void otPrintNetworkInformation(Stream &output) {
164+
if (!OThreadCLI) {
165+
return;
166+
}
167+
char resp[512];
168+
output.println("Thread Setup:");
169+
if (otGetRespCmd("state", resp)) {
170+
output.printf("Node State: \t%s", resp);
171+
}
172+
if (otGetRespCmd("networkname", resp)) {
173+
output.printf("Network Name: \t%s", resp);
174+
}
175+
if (otGetRespCmd("channel", resp)) {
176+
output.printf("Channel: \t%s", resp);
177+
}
178+
if (otGetRespCmd("panid", resp)) {
179+
output.printf("Pan ID: \t%s", resp);
180+
}
181+
if (otGetRespCmd("extpanid", resp)) {
182+
output.printf("Ext Pan ID: \t%s", resp);
183+
}
184+
if (otGetRespCmd("networkkey", resp)) {
185+
output.printf("Network Key: \t%s", resp);
186+
}
187+
if (otGetRespCmd("ipaddr", resp)) {
188+
output.println("Node IP Addresses are:");
189+
output.printf("%s", resp);
190+
}
191+
if (otGetRespCmd("ipmaddr", resp)) {
192+
output.println("Node Multicast Addresses are:");
193+
output.printf("%s", resp);
194+
}
195+
}
196+
197+
#endif /* CONFIG_OPENTHREAD_ENABLED */
198+
#endif /* SOC_IEEE802154_SUPPORTED */
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#pragma once
2+
#include "soc/soc_caps.h"
3+
#include "sdkconfig.h"
4+
#if SOC_IEEE802154_SUPPORTED
5+
#if CONFIG_OPENTHREAD_ENABLED
6+
7+
typedef enum {
8+
OT_ROLE_DISABLED = 0, ///< The Thread stack is disabled.
9+
OT_ROLE_DETACHED = 1, ///< Not currently participating in a Thread network/partition.
10+
OT_ROLE_CHILD = 2, ///< The Thread Child role.
11+
OT_ROLE_ROUTER = 3, ///< The Thread Router role.
12+
OT_ROLE_LEADER = 4, ///< The Thread Leader role.
13+
} ot_device_role_t;
14+
15+
typedef struct {
16+
int errorCode;
17+
String errorMessage;
18+
} ot_cmd_return_t;
19+
20+
ot_device_role_t otGetDeviceRole();
21+
const char *otGetStringDeviceRole();
22+
bool otGetRespCmd(const char *cmd, char *resp = NULL, uint32_t respTimeout = 5000);
23+
bool otExecCommand(const char *cmd, const char *arg, ot_cmd_return_t *returnCode = NULL);
24+
bool otPrintRespCLI(const char *cmd, Stream &output, uint32_t respTimeout);
25+
void otPrintNetworkInformation(Stream &output);
26+
27+
#endif /* CONFIG_OPENTHREAD_ENABLED */
28+
#endif /* SOC_IEEE802154_SUPPORTED */

0 commit comments

Comments
 (0)
Please sign in to comment.