This commit is contained in:
2026-05-22 21:52:50 +03:00
commit be7c60e4dd
1854 changed files with 583428 additions and 0 deletions
+261
View File
@@ -0,0 +1,261 @@
| Supported Targets | ESP32-C6 | ESP32-H2 |
| ----------------- | -------- | -------- |
# General View
This Arduino OpenThread Library allows using ESP OpenThread implementation using CLI and/or Native OpenThread API.
The Library implements 3 C++ Classes:
- `OThread` Class for Native OpenThread API
- `OThreadCLI` Class for CLI OpenThread API
- `DataSet` Class for OpenThread dataset manipulation using Native `OThread` Class
# ESP32 Arduino OpenThread Native
The `OThread` class provides methods for managing the OpenThread instance and controlling the Thread network. It allows you to initialize, start, stop, and manage the Thread network using native OpenThread APIs.
## Class Definition
```cpp
class OpenThread {
public:
static bool otStarted; // Indicates whether the OpenThread stack is running.
// Get the current Thread device role (e.g., Leader, Router, Child, etc.).
static ot_device_role_t otGetDeviceRole();
// Get the current Thread device role as a string.
static const char *otGetStringDeviceRole();
// Print network information (e.g., network name, channel, PAN ID) to the specified stream.
static void otPrintNetworkInformation(Stream &output);
OpenThread();
~OpenThread();
// Returns true if the OpenThread stack is running.
operator bool() const;
// Initialize the OpenThread stack.
static void begin(bool OThreadAutoStart = true);
// Deinitialize the OpenThread stack.
static void end();
// Start the Thread network.
void start();
// Stop the Thread network.
void stop();
// Bring up the Thread network interface (equivalent to "ifconfig up").
void networkInterfaceUp();
// Bring down the Thread network interface (equivalent to "ifconfig down").
void networkInterfaceDown();
// Commit a dataset to the OpenThread instance.
void commitDataSet(const DataSet &dataset);
private:
static otInstance *mInstance; // Pointer to the OpenThread instance.
DataSet mCurrentDataSet; // Current dataset being used by the OpenThread instance.
};
extern OpenThread OThread;
```
## Class Overview
The `OThread` class provides a simple and intuitive interface for managing the OpenThread stack and Thread network. It abstracts the complexity of the OpenThread APIs and provides Arduino-style methods for common operations.
## Public Methods
### Initialization and Deinitialization
- `begin(bool OThreadAutoStart = true)`: Initializes the OpenThread stack. If `OThreadAutoStart` is `true`, the Thread network will start automatically using NVS data.
- `end()`: Deinitializes the OpenThread stack and releases resources.
### Thread Network Control
- `start()`: Starts the Thread network. This is equivalent to the CLI command "thread start".
- `stop()`: Stops the Thread network. This is equivalent to the CLI command "thread stop".
### Network Interface Control
- `networkInterfaceUp()`: Brings up the Thread network interface. This is equivalent to the CLI command "ifconfig up".
- `networkInterfaceDown()`: Brings down the Thread network interface. This is equivalent to the CLI command "ifconfig down".
### Dataset Management
- `commitDataSet(const DataSet &dataset)`: Commits a dataset to the OpenThread instance. This is used to configure the Thread network with specific parameters (e.g., network name, channel, PAN ID).
### Network Information
- `otGetDeviceRole()`: Returns the current Thread device role as an `ot_device_role_t` enum (e.g., `OT_ROLE_LEADER`, `OT_ROLE_ROUTER`).
- `otGetStringDeviceRole()`: Returns the current Thread device role as a string (e.g., "Leader", "Router").
- `otPrintNetworkInformation(Stream &output)`: Prints the current network information (e.g., network name, channel, PAN ID) to the specified stream.
## Key Features
- **Initialization and Cleanup**: Easily initialize and deinitialize the OpenThread stack.
- **Network Control**: Start and stop the Thread network with simple method calls.
- **Dataset Management**: Configure the Thread network using the `DataSet` class and commit it to the OpenThread instance.
- **Network Information**: Retrieve and print the current network information and device role.
## Notes
- The `OThread` class is designed to simplify the use of OpenThread APIs in Arduino sketches.
- It works seamlessly with the DataSet class for managing Thread network configurations.
- Ensure that the OpenThread stack is initialized (`OThread.begin()`) before calling other methods.
This documentation provides a comprehensive overview of the `OThread` class, its methods, and example usage. It is designed to help developers quickly integrate OpenThread functionality into their Arduino projects.
# DataSet Class
The `DataSet` class provides a structured way to manage and configure Thread network datasets using native OpenThread APIs. It allows you to set and retrieve network parameters such as the network name, channel, PAN ID, and more. The `DataSet` class works seamlessly with the `OThread` class to apply these configurations to the OpenThread instance.
## Class Definition
```cpp
class DataSet {
public:
DataSet();
void clear();
void initNew();
const otOperationalDataset &getDataset() const;
// Setters
void setNetworkName(const char *name);
void setExtendedPanId(const uint8_t *extPanId);
void setNetworkKey(const uint8_t *key);
void setChannel(uint8_t channel);
void setPanId(uint16_t panId);
// Getters
const char *getNetworkName() const;
const uint8_t *getExtendedPanId() const;
const uint8_t *getNetworkKey() const;
uint8_t getChannel() const;
uint16_t getPanId() const;
// Apply the dataset to the OpenThread instance
void apply(otInstance *instance);
private:
otOperationalDataset mDataset; // Internal representation of the dataset
};
```
## Class Overview
The DataSet` class simplifies the management of Thread network datasets by providing intuitive methods for setting, retrieving, and applying network parameters. It abstracts the complexity of the OpenThread dataset APIs and provides Arduino-style methods for common operations.
## Public Methods
### Initialization
- `DataSet()`: Constructor that initializes an empty dataset.
- `void clear()`: Clears the dataset, resetting all fields to their default values.
- `void initNew()`: Initializes a new dataset with default values (equivalent to the CLI command dataset init new).
### Setters
- `void setNetworkName(const char *name)`: Sets the network name.
- `void setExtendedPanId(const uint8_t *extPanId)`: Sets the extended PAN ID.
- `void setNetworkKey(const uint8_t *key)`: Sets the network key.
- `void setChannel(uint8_t channel)`: Sets the channel.
- `void setPanId(uint16_t panId)`: Sets the PAN ID.
### Getters
- `const char *getNetworkName() const`: Retrieves the network name.
- `const uint8_t *getExtendedPanId() const`: Retrieves the extended PAN ID.
- `const uint8_t *getNetworkKey() const`: Retrieves the network key.
- `uint8_t getChannel() const`: Retrieves the channel.
- `uint16_t getPanId() const`: Retrieves the PAN ID.
### Dataset Application
- `void apply(otInstance *instance)`: Applies the dataset to the specified OpenThread instance.
## Key Features
- **Dataset Initialization**: Easily initialize a new dataset with default values using initNew().
- **Custom Configuration**: Set custom network parameters such as the network name, channel, and PAN ID using setter methods.
- **Dataset Application**: Apply the configured dataset to the OpenThread instance using apply().
** Notes
- The `DataSet` class is designed to work seamlessly with the `OThread` class for managing Thread network configurations.
- Ensure that the OpenThread stack is initialized (`OThread.begin()`) before applying a dataset.
- The initNew()`` method provides default values for the dataset, which can be customized using the setter methods.
This documentation provides a comprehensive overview of the `DataSet` class, its methods, and example usage. It is designed to help developers easily manage Thread network configurations in their Arduino projects.
# OpenThreadCLI Class
The `OpenThreadCLI` class is an Arduino API for interacting with the OpenThread Command Line Interface (CLI). It allows you to send commands to the OpenThread stack and receive responses. This class is designed to simplify the use of OpenThread CLI commands in Arduino sketches.
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(QueueHandle_t &queue, size_t len);
static bool otCLIStarted = 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
### Initialization and Deinitialization
- `begin()`: Initializes the OpenThread stack (optional auto-start).
- `end()`: Deinitializes the OpenThread stack and releases resources.
### Console Management
- `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.
### Buffer Management
- `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).
### Stream Methods
- `write(uint8_t)`: Writes a byte to the CLI.
- `available()`: Returns the number of bytes available to read.
- `read()`: Reads a byte from the CLI.
- `peek()`: Returns the next byte without removing it from the buffer.
- `flush()`: Flushes the CLI buffer.
## Key Features
- **Arduino Stream Compatibility**: Inherits from the Stream class, making it compatible with Arduino's standard I/O functions.
- **Customizable Console**: Allows customization of the CLI prompt, echoback behavior, and buffer sizes.
- **Callback Support**: Provides a callback mechanism to handle CLI responses asynchronously.
- **Seamless Integration**: Designed to work seamlessly with the OThread and DataSet classes
## Notes
- The `OThreadCLI` class is designed to simplify the use of OpenThread CLI commands in Arduino sketches.
- It works seamlessly with the `OThread` and `DataSet` classes for managing Thread networks.
- Ensure that the OpenThread stack is initialized (`OThreadCLI.begin()`) before starting the CLI console.
This documentation provides a comprehensive overview of the `OThreadCLI` class, its methods, and example usage. It is designed to help developers easily integrate OpenThread CLI functionality into their Arduino projects.
@@ -0,0 +1,165 @@
# OpenThread CoAP Lamp Example
This example demonstrates how to create a CoAP (Constrained Application Protocol) server on a Thread network that controls an RGB LED lamp.\
The application acts as a CoAP resource server that receives PUT requests to turn the lamp on or off, demonstrating Thread-based IoT device communication.
## Supported Targets
| SoC | Thread | RGB LED | Status |
| --- | ------ | ------- | ------ |
| ESP32-H2 | ✅ | Required | Fully supported |
| ESP32-C6 | ✅ | Required | Fully supported |
| ESP32-C5 | ✅ | Required | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example requires a companion CoAP Switch device (coap_switch example) to control the lamp.
- The lamp device acts as a Leader node and CoAP server.
## Features
- CoAP server implementation on Thread network
- RGB LED control with smooth fade in/out transitions
- Leader node configuration using CLI Helper Functions API
- CoAP resource creation and management
- Multicast IPv6 address for CoAP communication
- Automatic network setup with retry mechanism
- Visual status indication using RGB LED (Red = failed, Green = ready)
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- RGB LED (built-in RGB LED or external RGB LED)
- USB cable for Serial communication
- A CoAP Switch device (coap_switch example) to control the lamp
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
Before uploading the sketch, you can modify the network and CoAP configuration:
```cpp
#define OT_CHANNEL "24"
#define OT_NETWORK_KEY "00112233445566778899aabbccddeeff"
#define OT_MCAST_ADDR "ff05::abcd"
#define OT_COAP_RESOURCE_NAME "Lamp"
```
**Important:**
- The network key and channel must match the Switch device configuration
- The multicast address and resource name must match the Switch device
- The network key must be a 32-character hexadecimal string (16 bytes)
- The channel must be between 11 and 26 (IEEE 802.15.4 channels)
## Building and Flashing
1. Open the `coap_lamp.ino` sketch in the Arduino IDE.
2. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
3. Connect your ESP32 board to your computer via USB.
4. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
Starting OpenThread.
Running as Lamp (RGB LED) - use the other C6/H2 as a Switch
OpenThread started.
Waiting for activating correct Device Role.
........
Device is Leader.
OpenThread setup done. Node is ready.
```
The RGB LED will turn **green** when the device is ready to receive CoAP commands.
## Using the Device
### Lamp Device Setup
The lamp device automatically:
1. Configures itself as a Thread Leader node
2. Creates a CoAP server
3. Registers a CoAP resource named "Lamp"
4. Sets up a multicast IPv6 address for CoAP communication
5. Waits for CoAP PUT requests from the Switch device
### CoAP Resource
The lamp exposes a CoAP resource that accepts:
- **PUT with payload "0"**: Turns the lamp OFF (fades to black)
- **PUT with payload "1"**: Turns the lamp ON (fades to white)
### Visual Status Indication
The RGB LED provides visual feedback:
- **Red**: Setup failed or error occurred
- **Green**: Device is ready and waiting for CoAP commands
- **White/Black**: Lamp state (controlled by CoAP commands)
### Working with Switch Device
1. Start the Lamp device first (this example)
2. Start the Switch device (coap_switch example) with matching network key and channel
3. Press the button on the Switch device to toggle the lamp
4. The lamp will fade in/out smoothly when toggled
## Code Structure
The coap_lamp example consists of the following main components:
1. **`otDeviceSetup()` function**:
- Configures the device as a Leader node using CLI Helper Functions
- Sets up CoAP server and resource
- Waits for device to become Leader
- Returns success/failure status
2. **`setupNode()` function**:
- Retries setup until successful
- Calls `otDeviceSetup()` with Leader role configuration
3. **`otCOAPListen()` function**:
- Listens for CoAP requests from the Switch device
- Parses CoAP PUT requests
- Controls RGB LED based on payload (0 = OFF, 1 = ON)
- Implements smooth fade transitions
4. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin(false)`
- Initializes OpenThread CLI
- Sets CLI timeout
- Calls `setupNode()` to configure the device
5. **`loop()`**:
- Continuously calls `otCOAPListen()` to process incoming CoAP requests
- Small delay for responsiveness
## Troubleshooting
- **LED stays red**: Setup failed. Check Serial Monitor for error messages. Verify network configuration.
- **Lamp not responding to switch**: Ensure both devices use the same network key, channel, multicast address, and resource name. Check that Switch device is running.
- **Device not becoming Leader**: Clear NVS or ensure this is the first device started. Check network configuration.
- **CoAP requests not received**: Verify multicast address matches between Lamp and Switch devices. Check Thread network connectivity.
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread CLI Helper Functions API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_cli.html)
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
- [CoAP Protocol](https://coap.technology/)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,164 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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!");
rgbLedWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
// wait for the expected Device Role to start
uint8_t tries = 24; // 24 x 2.5 sec = 1 min
while (tries && OThread.otGetDeviceRole() != expectedRole) {
Serial.print(".");
delay(2500);
tries--;
}
Serial.println();
if (!tries) {
log_e("Sorry, Device Role failed by timeout! Current Role: %s.", OThread.otGetStringDeviceRole());
rgbLedWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.printf("Device is %s.\r\n", OThread.otGetStringDeviceRole());
for (i = 0; i < nCmds2; i++) {
if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
break;
}
}
if (i != nCmds2) {
log_e("Sorry, OpenThread CoAP setup failed!");
rgbLedWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.println("OpenThread setup done. Node is ready.");
// all fine! LED goes Green
rgbLedWrite(RGB_BUILTIN, 0, 64, 8); // GREEN ... Lamp is ready!
return true;
}
void setupNode() {
// tries to set the Thread Network node and only returns when succeeded
bool startedCorrectly = false;
while (!startedCorrectly) {
startedCorrectly |=
otDeviceSetup(otSetupLeader, sizeof(otSetupLeader) / sizeof(char *) / 2, 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) {
rgbLedWrite(RGB_BUILTIN, c, c, c); // ramp down
delay(5);
}
rgbLedWrite(RGB_BUILTIN, 0, 0, 0); // Lamp Off
} else {
for (int16_t c = 16; c < 248; c += 8) {
rgbLedWrite(RGB_BUILTIN, c, c, c); // ramp up
delay(5);
}
rgbLedWrite(RGB_BUILTIN, 255, 255, 255); // Lamp On
}
}
}
}
void setup() {
Serial.begin(115200);
// LED starts RED, indicating not connected to Thread network.
rgbLedWrite(RGB_BUILTIN, 64, 0, 0);
OThread.begin(false); // No AutoStart is necessary
OThreadCLI.begin();
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);
}
@@ -0,0 +1,176 @@
# OpenThread CoAP Switch Example
This example demonstrates how to create a CoAP (Constrained Application Protocol) client on a Thread network that controls a remote CoAP server (lamp).\
The application acts as a CoAP client that sends PUT requests to a lamp device, demonstrating Thread-based IoT device control.
## Supported Targets
| SoC | Thread | Button | Status |
| --- | ------ | ------ | ------ |
| ESP32-H2 | ✅ | Required | Fully supported |
| ESP32-C6 | ✅ | Required | Fully supported |
| ESP32-C5 | ✅ | Required | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example requires a companion CoAP Lamp device (coap_lamp example) to control.
- The switch device joins the network as a Router or Child node and acts as a CoAP client.
## Features
- CoAP client implementation on Thread network
- Button-based control of remote lamp device
- Router/Child node configuration using CLI Helper Functions API
- CoAP PUT request sending with confirmation
- Automatic network join with retry mechanism
- Visual status indication using RGB LED (Red = failed, Blue = ready)
- Button debouncing for reliable input
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- User button (BOOT button or external button)
- RGB LED for status indication (optional, but recommended)
- USB cable for Serial communication
- A CoAP Lamp device (coap_lamp example) must be running first
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
Before uploading the sketch, you can modify the network and CoAP configuration:
```cpp
#define USER_BUTTON 9 // C6/H2 Boot button (change if needed)
#define OT_CHANNEL "24"
#define OT_NETWORK_KEY "00112233445566778899aabbccddeeff"
#define OT_MCAST_ADDR "ff05::abcd"
#define OT_COAP_RESOURCE_NAME "Lamp"
```
**Important:**
- The network key and channel **must match** the Lamp device configuration
- The multicast address and resource name **must match** the Lamp device
- The network key must be a 32-character hexadecimal string (16 bytes)
- The channel must be between 11 and 26 (IEEE 802.15.4 channels)
- **Start the Lamp device first** before starting this Switch device
## Building and Flashing
1. **First, start the Lamp device** using the coap_lamp example
2. Open the `coap_switch.ino` sketch in the Arduino IDE.
3. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
4. Connect your ESP32 board to your computer via USB.
5. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
Starting OpenThread.
Running as Switch - use the BOOT button to toggle the other C6/H2 as a Lamp
OpenThread started.
Waiting for activating correct Device Role.
........
Device is Router.
OpenThread setup done. Node is ready.
```
The RGB LED will turn **blue** when the device is ready to send CoAP commands.
## Using the Device
### Switch Device Setup
The switch device automatically:
1. Joins the existing Thread network (created by the Lamp Leader)
2. Configures itself as a Router or Child node
3. Creates a CoAP client
4. Waits for button presses to send CoAP commands
### Button Control
- **Press the button**: Toggles the lamp state (ON/OFF)
- The switch sends CoAP PUT requests to the lamp:
- Payload "1" = Turn lamp ON
- Payload "0" = Turn lamp OFF
### Visual Status Indication
The RGB LED provides visual feedback:
- **Red**: Setup failed or CoAP request failed
- **Blue**: Device is ready and can send CoAP commands
- **Red (after button press)**: CoAP request failed, device will restart setup
### Working with Lamp Device
1. Start the Lamp device first (coap_lamp example)
2. Start this Switch device with matching network key and channel
3. Wait for Switch device to join the network (LED turns blue)
4. Press the button on the Switch device
5. The lamp on the other device should toggle ON/OFF
## Code Structure
The coap_switch example consists of the following main components:
1. **`otDeviceSetup()` function**:
- Configures the device to join an existing network using CLI Helper Functions
- Sets up CoAP client
- Waits for device to become Router or Child
- Returns success/failure status
2. **`setupNode()` function**:
- Retries setup until successful
- Calls `otDeviceSetup()` with Router/Child role configuration
3. **`otCoapPUT()` function**:
- Sends CoAP PUT request to the lamp device
- Waits for CoAP confirmation response
- Returns success/failure status
- Uses CLI Helper Functions to send commands and read responses
4. **`checkUserButton()` function**:
- Monitors button state with debouncing
- Toggles lamp state on button press
- Calls `otCoapPUT()` to send commands
- Restarts setup if CoAP request fails
5. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin(false)`
- Initializes OpenThread CLI
- Sets CLI timeout
- Calls `setupNode()` to configure the device
6. **`loop()`**:
- Continuously calls `checkUserButton()` to monitor button input
- Small delay for responsiveness
## Troubleshooting
- **LED stays red**: Setup failed. Check Serial Monitor for error messages. Verify network configuration matches Lamp device.
- **Button press doesn't toggle lamp**: Ensure Lamp device is running and both devices are on the same Thread network. Check that network key, channel, multicast address, and resource name match.
- **Device not joining network**: Ensure Lamp device (Leader) is running first. Verify network key and channel match exactly.
- **CoAP request timeout**: Check Thread network connectivity. Verify multicast address and resource name match the Lamp device. Ensure Lamp device is responding.
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread CLI Helper Functions API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_cli.html)
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
- [CoAP Protocol](https://coap.technology/)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,189 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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!");
rgbLedWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.println("OpenThread started.\r\nWaiting for activating correct Device Role.");
// wait for the expected Device Role to start
uint8_t tries = 24; // 24 x 2.5 sec = 1 min
while (tries && OThread.otGetDeviceRole() != expectedRole1 && OThread.otGetDeviceRole() != expectedRole2) {
Serial.print(".");
delay(2500);
tries--;
}
Serial.println();
if (!tries) {
log_e("Sorry, Device Role failed by timeout! Current Role: %s.", OThread.otGetStringDeviceRole());
rgbLedWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.printf("Device is %s.\r\n", OThread.otGetStringDeviceRole());
for (i = 0; i < nCmds2; i++) {
if (!otExecCommand(otCoapCmds[i * 2], otCoapCmds[i * 2 + 1])) {
break;
}
}
if (i != nCmds2) {
log_e("Sorry, OpenThread CoAP setup failed!");
rgbLedWrite(RGB_BUILTIN, 255, 0, 0); // RED ... failed!
return false;
}
Serial.println("OpenThread setup done. Node is ready.");
// all fine! LED goes and stays Blue
rgbLedWrite(RGB_BUILTIN, 0, 0, 64); // BLUE ... Switch is ready!
return true;
}
void setupNode() {
// tries to set the Thread Network node and only returns when succeeded
bool startedCorrectly = false;
while (!startedCorrectly) {
startedCorrectly |= otDeviceSetup(
otSetupChild, sizeof(otSetupChild) / sizeof(char *) / 2, 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 function 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 initial 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.
rgbLedWrite(RGB_BUILTIN, 255, 0, 0); // RED ... something failed!
Serial.println("Resetting the Node as Switch... wait.");
// start over...
setupNode();
}
lastPress = millis();
}
}
void setup() {
Serial.begin(115200);
// LED starts RED, indicating not connected to Thread network.
rgbLedWrite(RGB_BUILTIN, 64, 0, 0);
OThread.begin(false); // No AutoStart is necessary
OThreadCLI.begin();
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);
}
@@ -0,0 +1,166 @@
# OpenThread Simple CLI Example
This example demonstrates how to use the OpenThread CLI (Command-Line Interface) for interactive control of a Thread network node using an ESP32 SoC microcontroller.\
The application provides an interactive console where you can manually configure and control the Thread network using OpenThread CLI commands.
## Supported Targets
| SoC | Thread | Status |
| --- | ------ | ------ |
| ESP32-H2 | ✅ | Fully supported |
| ESP32-C6 | ✅ | Fully supported |
| ESP32-C5 | ✅ | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example uses `OpenThread.begin(false)` which does not automatically start a Thread network, allowing you to manually configure it using CLI commands.
## Features
- Interactive OpenThread CLI console via Serial Monitor
- Manual Thread network configuration using CLI commands
- Full control over Thread network parameters (network name, channel, PAN ID, network key, etc.)
- Support for all OpenThread CLI commands
- Useful for learning OpenThread CLI and debugging Thread networks
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- USB cable for Serial communication
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
No configuration is required before uploading the sketch. The example starts with a fresh Thread stack that is not automatically started, allowing you to configure it manually using CLI commands.
## Building and Flashing
1. Open the `SimpleCLI.ino` sketch in the Arduino IDE.
2. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
3. Connect your ESP32 board to your computer via USB.
4. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
OpenThread CLI started - type 'help' for a list of commands.
ot>
```
The `ot> ` prompt indicates that the OpenThread CLI console is ready. You can now type OpenThread CLI commands directly.
## Using the Device
### Interactive CLI Commands
Type OpenThread CLI commands in the Serial Monitor. Some useful commands to get started:
**Get help:**
```
help
```
**Initialize a new dataset:**
```
dataset init new
```
**Set network parameters:**
```
dataset networkname MyThreadNetwork
dataset channel 15
dataset networkkey 00112233445566778899aabbccddeeff
```
**Commit and start the network:**
```
dataset commit active
ifconfig up
thread start
```
**Check device state:**
```
state
```
**View network information:**
```
networkname
channel
panid
ipaddr
```
**For a complete list of OpenThread CLI commands, refer to the** [OpenThread CLI Reference](https://openthread.io/reference/cli).
### Example Workflow
1. **Initialize a new Thread network (Leader):**
```
dataset init new
dataset networkname MyNetwork
dataset channel 15
dataset networkkey 00112233445566778899aabbccddeeff
dataset commit active
ifconfig up
thread start
```
2. **Join an existing network (Router/Child):**
```
dataset clear
dataset networkkey 00112233445566778899aabbccddeeff
dataset channel 15
dataset commit active
ifconfig up
thread start
```
3. **Check network status:**
```
state
networkname
ipaddr
```
## Code Structure
The SimpleCLI example consists of the following main components:
1. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin(false)` (no auto-start)
- Initializes OpenThread CLI
- Starts the interactive CLI console on Serial
2. **`loop()`**:
- Empty - all interaction happens through the CLI console
## Troubleshooting
- **CLI not responding**: Ensure Serial Monitor is set to 115200 baud and "Both NL & CR" line ending
- **Commands not working**: Make sure OpenThread stack is initialized (check for "OpenThread CLI started" message)
- **Network not starting**: Verify that you've committed the dataset and started the interface before starting Thread
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread CLI Helper Functions API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_cli.html)
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,35 @@
// Copyright 2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* 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);
OThread.begin(false); // No AutoStart - fresh start
OThreadCLI.begin();
Serial.println("OpenThread CLI started - type 'help' for a list of commands.");
OThreadCLI.startConsole(Serial);
}
void loop() {}
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,131 @@
# OpenThread Simple Node Example
This example demonstrates how to create a simple OpenThread node that automatically starts in a Thread network using default settings.\
The application automatically initializes a Thread network with default parameters and displays the current device role and network information.
## Supported Targets
| SoC | Thread | Status |
| --- | ------ | ------ |
| ESP32-H2 | ✅ | Fully supported |
| ESP32-C6 | ✅ | Fully supported |
| ESP32-C5 | ✅ | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example uses `OpenThread.begin()` which automatically starts a Thread network using default settings or previously stored NVS dataset information.
## Features
- Automatic Thread network initialization with default settings
- Automatic device role assignment (first device becomes Leader, subsequent devices become Router or Child)
- Network information display using CLI Helper Functions API
- Periodic device role monitoring
- Support for persistent network configuration via NVS
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- USB cable for Serial communication
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
No configuration is required before uploading the sketch. The example uses default Thread network parameters:
- **Network Name**: "OpenThread-ESP"
- **Mesh Local Prefix**: "fd00:db8:a0:0::/64"
- **Channel**: 15
- **PAN ID**: 0x1234
- **Extended PAN ID**: "dead00beef00cafe"
- **Network Key**: "00112233445566778899aabbccddeeff"
- **PSKc**: "104810e2315100afd6bc9215a6bfac53"
**Note:** If NVS (Non-Volatile Storage) already contains dataset information, it will be loaded from there instead of using defaults.
## Building and Flashing
1. Open the `SimpleNode.ino` sketch in the Arduino IDE.
2. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
3. Connect your ESP32 board to your computer via USB.
4. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
OpenThread Network Information:
Role: Leader
RLOC16: 0x0000
Network Name: OpenThread-ESP
Channel: 15
PAN ID: 0x1234
Extended PAN ID: dead00beef00cafe
Network Key: 00112233445566778899aabbccddeeff
Mesh Local EID: fd00:db8:a0:0:0:ff:fe00:0
Leader RLOC: fd00:db8:a0:0:0:ff:fe00:0
Node RLOC: fd00:db8:a0:0:0:ff:fe00:0
Thread Node State: Leader
Thread Node State: Leader
...
```
The first device to start will become the **Leader**. Subsequent devices will automatically join as **Router** or **Child** nodes.
## Using the Device
### Network Behavior
- **First device**: Automatically becomes the Leader and creates a new Thread network
- **Subsequent devices**: Automatically join the existing network as Router or Child nodes
- **Device role**: Displayed every 5 seconds in the Serial Monitor
### Multiple Devices
To create a multi-device Thread network:
1. Flash the first device - it will become the Leader
2. Flash additional devices - they will automatically join the network
3. All devices must use the same network key and channel (defaults are used if NVS is empty)
## Code Structure
The SimpleNode example consists of the following main components:
1. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin()` (auto-start with default settings)
- Initializes OpenThread CLI
- Prints current Thread network information using `OpenThread.otPrintNetworkInformation()`
2. **`loop()`**:
- Periodically displays the current device role using `OpenThread.otGetStringDeviceRole()`
- Updates every 5 seconds
## Troubleshooting
- **Device not joining network**: Ensure all devices use the same network key and channel. Check that the Leader node is running first.
- **Role stuck as "Detached"**: Wait a few seconds for the device to join the network. Verify network key and channel match the Leader.
- **No network information displayed**: Check that OpenThread stack initialized successfully (look for network information in setup)
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread CLI Helper Functions API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_cli.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,47 @@
// Copyright 2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* 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);
OThread.begin(); // AutoStart using Thread default settings
OThreadCLI.begin();
OThread.otPrintNetworkInformation(Serial); // Print Current Thread Network Information
}
void loop() {
Serial.print("Thread Node State: ");
Serial.println(OThread.otGetStringDeviceRole());
delay(5000);
}
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,80 @@
// Copyright 2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "OThreadCLI.h"
#include "OThreadCLI_Util.h"
// Leader node shall use the same Network Key and channel
#define CLI_NETWORK_KEY "00112233445566778899aabbccddeeff"
#define CLI_NETWORK_CHANNEL "24"
bool otStatus = true;
void setup() {
Serial.begin(115200);
OThread.begin(false); // No AutoStart - fresh start
OThreadCLI.begin();
Serial.println("Setting up OpenThread Node as Router/Child");
Serial.println("Make sure the Leader Node is already running");
otStatus &= otExecCommand("dataset", "clear");
otStatus &= otExecCommand("dataset networkkey", CLI_NETWORK_KEY);
otStatus &= otExecCommand("dataset channel", CLI_NETWORK_CHANNEL);
otStatus &= otExecCommand("dataset", "commit active");
otStatus &= otExecCommand("ifconfig", "up");
otStatus &= otExecCommand("thread", "start");
if (!otStatus) {
Serial.println("\r\n\t===> Failed starting Thread Network!");
return;
}
// wait for the node to enter in the router state
uint32_t timeout = millis() + 90000; // waits 90 seconds to
while (OThread.otGetDeviceRole() != OT_ROLE_CHILD && OThread.otGetDeviceRole() != OT_ROLE_ROUTER) {
Serial.print(".");
if (millis() > timeout) {
Serial.println("\r\n\t===> Timeout! Failed.");
otStatus = false;
break;
}
delay(500);
}
if (otStatus) {
// print the PanID using 2 methods
// CLI
char resp[256];
if (otGetRespCmd("panid", resp)) {
Serial.printf("\r\nPanID[using CLI]: %s\r\n", resp);
} else {
Serial.printf("\r\nPanID[using CLI]: FAILED!\r\n");
}
// OpenThread API
Serial.printf("PanID[using OT API]: 0x%x\r\n", (uint16_t)otLinkGetPanId(esp_openthread_get_instance()));
}
Serial.println("\r\n");
}
void loop() {
if (otStatus) {
Serial.println("Thread NetworkInformation: ");
Serial.println("---------------------------");
OThread.otPrintNetworkInformation(Serial);
Serial.println("---------------------------");
} else {
Serial.println("Some OpenThread operation has failed...");
}
delay(10000);
}
@@ -0,0 +1,163 @@
# OpenThread Extended Router Node Example (CLI)
This example demonstrates how to create an OpenThread Router or Child node that joins an existing Thread network, with extended functionality showing both CLI Helper Functions API and native OpenThread API usage.\
The example shows how to retrieve network information using both methods and demonstrates error handling.
## Supported Targets
| SoC | Thread | Status |
| --- | ------ | ------ |
| ESP32-H2 | ✅ | Fully supported |
| ESP32-C6 | ✅ | Fully supported |
| ESP32-C5 | ✅ | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example uses `OpenThread.begin(false)` which does not automatically start a Thread network, allowing manual configuration.
- **Important:** A Leader node must be running before starting this Router/Child node.
## Features
- Manual Router/Child node configuration using CLI Helper Functions API
- Joins an existing Thread network created by a Leader node
- Demonstrates both CLI Helper Functions API (`otGetRespCmd()`) and native OpenThread API (`otLinkGetPanId()`) usage
- Network information display using both API methods
- Error handling and timeout management
- Comprehensive network status monitoring
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- USB cable for Serial communication
- A Leader node must be running on the same network
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
Before uploading the sketch, configure the network parameters to match the Leader node:
```cpp
#define CLI_NETWORK_KEY "00112233445566778899aabbccddeeff"
#define CLI_NETWORK_CHANNEL "24"
```
**Important:**
- The network key **must match** the Leader node's network key
- The channel **must match** the Leader node's channel
- The network key must be a 32-character hexadecimal string (16 bytes)
- The channel must be between 11 and 26 (IEEE 802.15.4 channels)
## Building and Flashing
1. **First, start the Leader node** using the LeaderNode example
2. Open the `ExtendedRouterNode.ino` sketch in the Arduino IDE.
3. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
4. Connect your ESP32 board to your computer via USB.
5. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
Setting up OpenThread Node as Router/Child
Make sure the Leader Node is already running
PanID[using CLI]: 0x1234
PanID[using OT API]: 0x1234
Thread NetworkInformation:
---------------------------
Role: Router
RLOC16: 0xfc00
Network Name: OpenThread-ESP
Channel: 24
PAN ID: 0x1234
Extended PAN ID: dead00beef00cafe
Network Key: 00112233445566778899aabbccddeeff
Mesh Local EID: fd00:db8:a0:0:0:ff:fe00:fc00
Leader RLOC: fd00:db8:a0:0:0:ff:fe00:0
Node RLOC: fd00:db8:a0:0:0:ff:fe00:fc00
---------------------------
```
## Using the Device
### Extended Router/Child Node Setup
The Extended Router/Child node is automatically configured in `setup()` using the following sequence:
1. **Clear dataset**: Clears any existing dataset
2. **Set network key**: Configures the network security key (must match Leader)
3. **Set channel**: Configures the IEEE 802.15.4 channel (must match Leader)
4. **Commit dataset**: Applies the dataset to the active configuration
5. **Start interface**: Brings up the network interface
6. **Start Thread**: Starts the Thread network and joins the existing network
7. **Wait for role**: Waits up to 90 seconds for the device to become Router or Child
### Dual API Demonstration
This example demonstrates two ways to access OpenThread information:
1. **CLI Helper Functions API**: Uses `otGetRespCmd("panid", resp)` to get PAN ID via CLI
2. **Native OpenThread API**: Uses `otLinkGetPanId(esp_openthread_get_instance())` to get PAN ID directly
Both methods should return the same value, demonstrating API equivalence.
### Network Information
Once the device joins the network, the `loop()` function displays comprehensive network information using `OpenThread.otPrintNetworkInformation()`, including:
- Device role
- RLOC16
- Network name
- Channel
- PAN ID and Extended PAN ID
- Network key
- IPv6 addresses (Mesh Local EID, Leader RLOC, Node RLOC)
## Code Structure
The ExtendedRouterNode example consists of the following main components:
1. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin(false)` (no auto-start)
- Initializes OpenThread CLI
- Configures the device to join an existing network using CLI Helper Functions:
- `otExecCommand()` - Executes CLI commands with error handling
- Commands: "dataset clear", "dataset networkkey", "dataset channel", "dataset commit active", "ifconfig up", "thread start"
- Waits for device to become Router or Child (with 90-second timeout)
- Demonstrates dual API usage for getting PAN ID
2. **`loop()`**:
- Displays comprehensive network information using `OpenThread.otPrintNetworkInformation()`
- Updates every 10 seconds
- Shows error message if setup failed
## Troubleshooting
- **Device not joining network**: Ensure the Leader node is running first. Verify network key and channel match the Leader exactly.
- **Timeout error**: The device waits 90 seconds to join. If timeout occurs, check network key and channel match the Leader.
- **Network key/channel mismatch**: Double-check that both Leader and Router/Child nodes use identical network key and channel values.
- **Setup failed message**: Check Serial Monitor for specific error messages. Verify Leader node is running and within range.
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread CLI Helper Functions API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_cli.html)
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,102 @@
// Copyright 2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
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_CHANNEL "dataset channel 24"
otInstance *aInstance = NULL;
void setup() {
Serial.begin(115200);
OThread.begin(false); // No AutoStart - fresh start
OThreadCLI.begin();
Serial.println();
Serial.println("Setting up OpenThread Node as Leader");
aInstance = esp_openthread_get_instance();
OThreadCLI.println("dataset init new");
OThreadCLI.println(CLI_NETWORK_KEY);
OThreadCLI.println(CLI_NETWORK_CHANNEL);
OThreadCLI.println("dataset commit active");
OThreadCLI.println("ifconfig up");
OThreadCLI.println("thread start");
}
void loop() {
Serial.println("=============================================");
Serial.print("Thread Node State: ");
Serial.println(OThread.otGetStringDeviceRole());
// Native OpenThread API calls:
// wait until the node become Child or Router
if (OThread.otGetDeviceRole() == OT_ROLE_LEADER) {
// Network Name
const char *networkName = otThreadGetNetworkName(aInstance);
Serial.printf("Network Name: %s\r\n", networkName);
// Channel
uint8_t channel = otLinkGetChannel(aInstance);
Serial.printf("Channel: %d\r\n", channel);
// PAN ID
uint16_t panId = otLinkGetPanId(aInstance);
Serial.printf("PanID: 0x%04x\r\n", panId);
// Extended PAN ID
const otExtendedPanId *extPanId = otThreadGetExtendedPanId(aInstance);
Serial.printf("Extended PAN ID: ");
for (int i = 0; i < OT_EXT_PAN_ID_SIZE; i++) {
Serial.printf("%02x", extPanId->m8[i]);
}
Serial.println();
// Network Key
otNetworkKey networkKey;
otThreadGetNetworkKey(aInstance, &networkKey);
Serial.printf("Network Key: ");
for (int i = 0; i < OT_NETWORK_KEY_SIZE; i++) {
Serial.printf("%02x", networkKey.m8[i]);
}
Serial.println();
// IP Addresses
char buf[OT_IP6_ADDRESS_STRING_SIZE];
const otNetifAddress *address = otIp6GetUnicastAddresses(aInstance);
while (address != NULL) {
otIp6AddressToString(&address->mAddress, buf, sizeof(buf));
Serial.printf("IP Address: %s\r\n", buf);
address = address->mNext;
}
// Multicast IP Addresses
const otNetifMulticastAddress *mAddress = otIp6GetMulticastAddresses(aInstance);
while (mAddress != NULL) {
otIp6AddressToString(&mAddress->mAddress, buf, sizeof(buf));
printf("Multicast IP Address: %s\n", buf);
mAddress = mAddress->mNext;
}
}
delay(5000);
}
@@ -0,0 +1,150 @@
# OpenThread Leader Node Example (CLI)
This example demonstrates how to create an OpenThread Leader node using the CLI Helper Functions API.\
The Leader node is the first device in a Thread network that manages the network and assigns router IDs. This example shows how to configure a Leader node manually using OpenThread CLI commands.
## Supported Targets
| SoC | Thread | Status |
| --- | ------ | ------ |
| ESP32-H2 | ✅ | Fully supported |
| ESP32-C6 | ✅ | Fully supported |
| ESP32-C5 | ✅ | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example uses `OpenThread.begin(false)` which does not automatically start a Thread network, allowing manual configuration.
## Features
- Manual Leader node configuration using CLI Helper Functions API
- Complete dataset initialization with network key and channel
- Network information display using native OpenThread API calls
- Demonstrates both CLI Helper Functions and native OpenThread API usage
- IPv6 address listing (unicast and multicast)
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- USB cable for Serial communication
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
Before uploading the sketch, you can modify the network configuration:
```cpp
#define CLI_NETWORK_KEY "dataset networkkey 00112233445566778899aabbccddeeff"
#define CLI_NETWORK_CHANNEL "dataset channel 24"
```
**Important:**
- The network key must be a 32-character hexadecimal string (16 bytes)
- The channel must be between 11 and 26 (IEEE 802.15.4 channels)
- All devices in the same network must use the same network key and channel
## Building and Flashing
1. Open the `LeaderNode.ino` sketch in the Arduino IDE.
2. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
3. Connect your ESP32 board to your computer via USB.
4. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
Setting up OpenThread Node as Leader
=============================================
Thread Node State: Leader
Network Name: OpenThread-ESP
Channel: 24
PanID: 0x1234
Extended PAN ID: dead00beef00cafe
Network Key: 00112233445566778899aabbccddeeff
IP Address: fd00:db8:a0:0:0:ff:fe00:0
Multicast IP Address: ff02::1
Multicast IP Address: ff03::1
...
```
## Using the Device
### Leader Node Setup
The Leader node is automatically configured in `setup()` using the following sequence:
1. **Initialize new dataset**: Creates a complete dataset with random values
2. **Set network key**: Configures the network security key
3. **Set channel**: Configures the IEEE 802.15.4 channel
4. **Commit dataset**: Applies the dataset to the active configuration
5. **Start interface**: Brings up the network interface
6. **Start Thread**: Starts the Thread network
### Network Information
Once the device becomes a Leader, the `loop()` function displays:
- Device role (Leader)
- Network name
- Channel
- PAN ID and Extended PAN ID
- Network key
- All IPv6 addresses (unicast and multicast)
### Joining Other Devices
To join other devices to this network:
1. Use the same network key and channel in the Router/Child node examples
2. Start the Leader node first
3. Then start the Router/Child nodes
## Code Structure
The LeaderNode example consists of the following main components:
1. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin(false)` (no auto-start)
- Initializes OpenThread CLI
- Configures the device as a Leader using CLI Helper Functions:
- `OThreadCLI.println()` - Sends CLI commands
- Commands: "dataset init new", "dataset networkkey", "dataset channel", "dataset commit active", "ifconfig up", "thread start"
2. **`loop()`**:
- Checks if device role is Leader using `OpenThread.otGetDeviceRole()`
- Displays network information using native OpenThread API calls:
- `otThreadGetNetworkName()` - Network name
- `otLinkGetChannel()` - Channel
- `otLinkGetPanId()` - PAN ID
- `otThreadGetExtendedPanId()` - Extended PAN ID
- `otThreadGetNetworkKey()` - Network key
- `otIp6GetUnicastAddresses()` - Unicast IPv6 addresses
- `otIp6GetMulticastAddresses()` - Multicast IPv6 addresses
- Updates every 5 seconds
## Troubleshooting
- **Device not becoming Leader**: Ensure this is the first device started, or clear NVS to start fresh
- **Network key/channel mismatch**: Verify all devices use the same network key and channel
- **No network information**: Wait for the device to become Leader (may take a few seconds)
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread CLI Helper Functions API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_cli.html)
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,162 @@
# OpenThread Router/Child Node Example (CLI)
This example demonstrates how to create an OpenThread Router or Child node that joins an existing Thread network using the CLI Helper Functions API.\
The Router/Child node joins a network created by a Leader node and can route messages or operate as an end device.
## Supported Targets
| SoC | Thread | Status |
| --- | ------ | ------ |
| ESP32-H2 | ✅ | Fully supported |
| ESP32-C6 | ✅ | Fully supported |
| ESP32-C5 | ✅ | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example uses `OpenThread.begin(false)` which does not automatically start a Thread network, allowing manual configuration.
- **Important:** A Leader node must be running before starting this Router/Child node.
## Features
- Manual Router/Child node configuration using CLI Helper Functions API
- Joins an existing Thread network created by a Leader node
- Network information display using native OpenThread API calls
- Demonstrates both CLI Helper Functions and native OpenThread API usage
- IPv6 address listing (unicast and multicast)
- Automatic role assignment (Router or Child based on network conditions)
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- USB cable for Serial communication
- A Leader node must be running on the same network
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
Before uploading the sketch, configure the network parameters to match the Leader node:
```cpp
#define CLI_NETWORK_KEY "dataset networkkey 00112233445566778899aabbccddeeff"
#define CLI_NETWORK_CHANNEL "dataset channel 24"
```
**Important:**
- The network key **must match** the Leader node's network key
- The channel **must match** the Leader node's channel
- The network key must be a 32-character hexadecimal string (16 bytes)
- The channel must be between 11 and 26 (IEEE 802.15.4 channels)
## Building and Flashing
1. **First, start the Leader node** using the LeaderNode example
2. Open the `RouterNode.ino` sketch in the Arduino IDE.
3. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
4. Connect your ESP32 board to your computer via USB.
5. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
Setting up OpenThread Node as Router/Child
Make sure the Leader Node is already running
=============================================
Thread Node State: Router
Network Name: OpenThread-ESP
Channel: 24
PanID: 0x1234
Extended PAN ID: dead00beef00cafe
Network Key: 00112233445566778899aabbccddeeff
IP Address: fd00:db8:a0:0:0:ff:fe00:fc00
Multicast IP Address: ff02::1
Multicast IP Address: ff03::1
...
```
The device will join as either a **Router** (if network needs more routers) or **Child** (end device).
## Using the Device
### Router/Child Node Setup
The Router/Child node is automatically configured in `setup()` using the following sequence:
1. **Clear dataset**: Clears any existing dataset
2. **Set network key**: Configures the network security key (must match Leader)
3. **Set channel**: Configures the IEEE 802.15.4 channel (must match Leader)
4. **Commit dataset**: Applies the dataset to the active configuration
5. **Start interface**: Brings up the network interface
6. **Start Thread**: Starts the Thread network and joins the existing network
### Network Information
Once the device joins the network (as Router or Child), the `loop()` function displays:
- Device role (Router or Child)
- Network name (from the Leader)
- Channel
- PAN ID and Extended PAN ID
- Network key
- All IPv6 addresses (unicast and multicast)
### Multi-Device Network
To create a multi-device Thread network:
1. Start the Leader node first (using LeaderNode example)
2. Start Router/Child nodes (using this example)
3. All devices will form a mesh network
4. Routers extend network range and route messages
5. Children are end devices that can sleep
## Code Structure
The RouterNode example consists of the following main components:
1. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin(false)` (no auto-start)
- Initializes OpenThread CLI
- Configures the device to join an existing network using CLI Helper Functions:
- `OThreadCLI.println()` - Sends CLI commands
- Commands: "dataset clear", "dataset networkkey", "dataset channel", "dataset commit active", "ifconfig up", "thread start"
2. **`loop()`**:
- Checks if device role is Router or Child using `OpenThread.otGetDeviceRole()`
- Displays network information using native OpenThread API calls:
- `otThreadGetNetworkName()` - Network name
- `otLinkGetChannel()` - Channel
- `otLinkGetPanId()` - PAN ID
- `otThreadGetExtendedPanId()` - Extended PAN ID
- `otThreadGetNetworkKey()` - Network key
- `otIp6GetUnicastAddresses()` - Unicast IPv6 addresses
- `otIp6GetMulticastAddresses()` - Multicast IPv6 addresses
- Updates every 5 seconds
## Troubleshooting
- **Device not joining network**: Ensure the Leader node is running first. Verify network key and channel match the Leader exactly.
- **Role stuck as "Detached"**: Wait a few seconds for the device to join. Check that network key and channel match the Leader.
- **Network key/channel mismatch**: Double-check that both Leader and Router/Child nodes use identical network key and channel values.
- **No network information**: Wait for the device to join the network (may take 10-30 seconds)
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread CLI Helper Functions API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_cli.html)
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,102 @@
// Copyright 2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
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_CHANNEL "dataset channel 24"
otInstance *aInstance = NULL;
void setup() {
Serial.begin(115200);
OThread.begin(false); // No AutoStart - fresh start
OThreadCLI.begin();
Serial.println();
Serial.println("Setting up OpenThread Node as Router/Child");
Serial.println("Make sure the Leader Node is already running");
aInstance = esp_openthread_get_instance();
OThreadCLI.println("dataset clear");
OThreadCLI.println(CLI_NETWORK_KEY);
OThreadCLI.println(CLI_NETWORK_CHANNEL);
OThreadCLI.println("dataset commit active");
OThreadCLI.println("ifconfig up");
OThreadCLI.println("thread start");
}
void loop() {
Serial.println("=============================================");
Serial.print("Thread Node State: ");
Serial.println(OThread.otGetStringDeviceRole());
// Native OpenThread API calls:
// wait until the node become Child or Router
if (OThread.otGetDeviceRole() == OT_ROLE_CHILD || OThread.otGetDeviceRole() == OT_ROLE_ROUTER) {
// Network Name
const char *networkName = otThreadGetNetworkName(aInstance);
Serial.printf("Network Name: %s\r\n", networkName);
// Channel
uint8_t channel = otLinkGetChannel(aInstance);
Serial.printf("Channel: %d\r\n", channel);
// PAN ID
uint16_t panId = otLinkGetPanId(aInstance);
Serial.printf("PanID: 0x%04x\r\n", panId);
// Extended PAN ID
const otExtendedPanId *extPanId = otThreadGetExtendedPanId(aInstance);
Serial.printf("Extended PAN ID: ");
for (int i = 0; i < OT_EXT_PAN_ID_SIZE; i++) {
Serial.printf("%02x", extPanId->m8[i]);
}
Serial.println();
// Network Key
otNetworkKey networkKey;
otThreadGetNetworkKey(aInstance, &networkKey);
Serial.printf("Network Key: ");
for (int i = 0; i < OT_NETWORK_KEY_SIZE; i++) {
Serial.printf("%02x", networkKey.m8[i]);
}
Serial.println();
// IP Addresses
char buf[OT_IP6_ADDRESS_STRING_SIZE];
const otNetifAddress *address = otIp6GetUnicastAddresses(aInstance);
while (address != NULL) {
otIp6AddressToString(&address->mAddress, buf, sizeof(buf));
Serial.printf("IP Address: %s\r\n", buf);
address = address->mNext;
}
// Multicast IP Addresses
const otNetifMulticastAddress *mAddress = otIp6GetMulticastAddresses(aInstance);
while (mAddress != NULL) {
otIp6AddressToString(&mAddress->mAddress, buf, sizeof(buf));
printf("Multicast IP Address: %s\n", buf);
mAddress = mAddress->mNext;
}
}
delay(5000);
}
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,137 @@
# OpenThread Thread Scan Example
This example demonstrates how to scan for IEEE 802.15.4 devices and Thread networks using OpenThread CLI commands.\
The application continuously scans for nearby devices and Thread networks, showing both raw IEEE 802.15.4 scans and Thread-specific discovery scans.
## Supported Targets
| SoC | Thread | Status |
| --- | ------ | ------ |
| ESP32-H2 | ✅ | Fully supported |
| ESP32-C6 | ✅ | Fully supported |
| ESP32-C5 | ✅ | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example uses `OpenThread.begin(true)` which automatically starts a Thread network, required for Thread discovery scans.
## Features
- IEEE 802.15.4 device scanning (works even when Thread is not started)
- Thread network discovery scanning (requires device to be in Child, Router, or Leader state)
- Continuous scanning with configurable intervals
- Demonstrates CLI Helper Functions API for scanning
- Useful for network discovery and debugging
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- USB cable for Serial communication
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
No configuration is required before uploading the sketch. The example automatically starts a Thread network for discovery scanning.
## Building and Flashing
1. Open the `ThreadScan.ino` sketch in the Arduino IDE.
2. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
3. Connect your ESP32 board to your computer via USB.
4. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
This sketch will continuously scan the Thread Local Network and all devices IEEE 802.15.4 compatible
Scanning for nearby IEEE 802.15.4 devices:
| J | Network Name | Extended PAN | PAN | MAC Address | Ch | dBm | LQI |
+---+------------------+------------------+------+------------------+----+-----+-----+
| 0 | OpenThread-ESP | dead00beef00cafe | 1234 | 1234567890abcdef | 24 | -45 | 255 |
Done
Scanning MLE Discover:
| J | Network Name | Extended PAN | PAN | MAC Address | Ch | dBm | LQI |
+---+------------------+------------------+------+------------------+----+-----+-----+
| 0 | OpenThread-ESP | dead00beef00cafe | 1234 | 1234567890abcdef | 24 | -45 | 255 |
Done
```
## Using the Device
### Scanning Behavior
The example performs two types of scans:
1. **IEEE 802.15.4 Scan**:
- Scans for all IEEE 802.15.4 compatible devices in the area
- Works even when the device is not part of a Thread network
- Shows devices on all channels
2. **Thread Discovery Scan (MLE Discover)**:
- Scans for Thread networks specifically
- Only works when the device is in Child, Router, or Leader state
- Shows Thread networks with their network names and parameters
### Scan Results
The scan results show:
- **J**: Joinable flag (1 = can join, 0 = cannot join)
- **Network Name**: Thread network name
- **Extended PAN**: Extended PAN ID
- **PAN**: PAN ID
- **MAC Address**: Device MAC address
- **Ch**: Channel
- **dBm**: Signal strength in dBm
- **LQI**: Link Quality Indicator
### Continuous Scanning
The example continuously scans:
- IEEE 802.15.4 scan every 5 seconds
- Thread discovery scan every 5 seconds (only if device is in Thread network)
## Code Structure
The ThreadScan example consists of the following main components:
1. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin(true)` (auto-start required for discovery)
- Initializes OpenThread CLI
- Sets CLI timeout to 100 ms using `OThreadCLI.setTimeout()`
2. **`loop()`**:
- Performs IEEE 802.15.4 scan using `otPrintRespCLI("scan", Serial, 3000)`
- Checks if device is in Thread network (Child, Router, or Leader)
- If in network, performs Thread discovery scan using `otPrintRespCLI("discover", Serial, 3000)`
- Waits 5 seconds between scan cycles
## Troubleshooting
- **No scan results**: Ensure there are Thread devices nearby. Check that other devices are running and on the same channel.
- **Discovery scan not working**: Wait for the device to join a Thread network (should become Child, Router, or Leader)
- **Scan timeout**: Increase the timeout value in `otPrintRespCLI()` if scans are taking longer
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread CLI Helper Functions API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_cli.html)
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,56 @@
// Copyright 2025 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
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);
OThread.begin(true); // For scanning, AutoStart must be active, any setup
OThreadCLI.begin();
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 (OThread.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);
}
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,130 @@
# OpenThread CLI onReceive Callback Example
This example demonstrates how to use the OpenThread CLI callback mechanism to capture and process CLI responses asynchronously.\
The application shows how to set up a callback function that processes CLI responses line by line, allowing non-blocking CLI interaction.
## Supported Targets
| SoC | Thread | Status |
| --- | ------ | ------ |
| ESP32-H2 | ✅ | Fully supported |
| ESP32-C6 | ✅ | Fully supported |
| ESP32-C5 | ✅ | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example uses `OpenThread.begin()` which automatically starts a Thread network with default settings.
## Features
- CLI response callback using `OpenThreadCLI.onReceive()`
- Asynchronous CLI response processing
- Non-blocking CLI command execution
- Demonstrates callback-based CLI interaction pattern
- Automatic Thread network startup with default settings
- Device role monitoring via CLI
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- USB cable for Serial communication
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
No configuration is required before uploading the sketch. The example automatically starts a Thread network with default settings.
## Building and Flashing
1. Open the `onReceive.ino` sketch in the Arduino IDE.
2. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
3. Connect your ESP32 board to your computer via USB.
4. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
OpenThread CLI RESP===> disabled
OpenThread CLI RESP===> disabled
OpenThread CLI RESP===> detached
OpenThread CLI RESP===> child
OpenThread CLI RESP===> router
OpenThread CLI RESP===> router
...
```
The callback function processes each line of CLI response, showing the device state transitions from "disabled" to "detached" to "child" to "router" (or "leader").
## Using the Device
### Callback Mechanism
The example demonstrates the callback-based CLI interaction pattern:
1. **Callback Registration**: `OThreadCLI.onReceive(otReceivedLine)` registers a callback function
2. **Command Execution**: `OThreadCLI.println("state")` sends CLI commands
3. **Response Processing**: The callback function `otReceivedLine()` processes responses asynchronously
4. **Non-blocking**: The main loop continues while CLI responses are processed in the callback
### Device State Monitoring
The example continuously monitors the device state:
- Sends "state" command every second
- Callback processes the response
- Shows state transitions as the device joins the Thread network
### Customizing the Callback
You can modify the `otReceivedLine()` function to:
- Parse specific CLI responses
- Extract data from CLI output
- Trigger actions based on CLI responses
- Filter or process specific response patterns
## Code Structure
The onReceive example consists of the following main components:
1. **`otReceivedLine()` callback function**:
- Reads all available data from OpenThread CLI
- Filters out empty lines (EOL sequences)
- Prints non-empty lines with a prefix
2. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin()` (auto-start)
- Initializes OpenThread CLI
- Registers the callback function using `OThreadCLI.onReceive(otReceivedLine)`
3. **`loop()`**:
- Sends "state" CLI command every second using `OThreadCLI.println("state")`
- The callback function processes the response asynchronously
- Non-blocking operation allows other tasks to run
## Troubleshooting
- **No callback responses**: Ensure the callback is registered in setup. Check that OpenThread CLI is initialized.
- **Empty lines in output**: The callback filters empty lines, which is normal behavior
- **State not changing**: Wait for the device to join the Thread network. First device becomes Leader, subsequent devices become Router or Child.
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread CLI Helper Functions API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_cli.html)
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,52 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
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);
OThread.begin(); // AutoStart
OThreadCLI.begin();
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);
}
@@ -0,0 +1,113 @@
#include "OThread.h"
OpenThread threadLeaderNode;
DataSet dataset;
// Track last known device role for state change detection
ot_device_role_t lastKnownRole = OT_ROLE_DISABLED;
void setup() {
Serial.begin(115200);
// Start OpenThread Stack - false for not using NVS dataset information
threadLeaderNode.begin(false);
// Create a new Thread Network Dataset for a Leader Node
dataset.initNew();
// Configure the dataset
dataset.setNetworkName("ESP_OpenThread");
uint8_t extPanId[OT_EXT_PAN_ID_SIZE] = {0xDE, 0xAD, 0x00, 0xBE, 0xEF, 0x00, 0xCA, 0xFE};
dataset.setExtendedPanId(extPanId);
uint8_t networkKey[OT_NETWORK_KEY_SIZE] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
dataset.setNetworkKey(networkKey);
dataset.setChannel(15);
dataset.setPanId(0x1234);
// Apply the dataset and start the network
threadLeaderNode.commitDataSet(dataset);
threadLeaderNode.networkInterfaceUp();
threadLeaderNode.start();
}
void loop() {
// Get current device role
ot_device_role_t currentRole = threadLeaderNode.otGetDeviceRole();
// Only print network information when not detached
if (currentRole != OT_ROLE_DETACHED && currentRole != OT_ROLE_DISABLED) {
Serial.println("==============================================");
Serial.println("OpenThread Network Information:");
// Basic network information
Serial.printf("Role: %s\r\n", threadLeaderNode.otGetStringDeviceRole());
Serial.printf("RLOC16: 0x%04x\r\n", threadLeaderNode.getRloc16());
Serial.printf("Network Name: %s\r\n", threadLeaderNode.getNetworkName().c_str());
Serial.printf("Channel: %d\r\n", threadLeaderNode.getChannel());
Serial.printf("PAN ID: 0x%04x\r\n", threadLeaderNode.getPanId());
// Extended PAN ID
const uint8_t *extPanId = threadLeaderNode.getExtendedPanId();
if (extPanId) {
Serial.print("Extended PAN ID: ");
for (int i = 0; i < OT_EXT_PAN_ID_SIZE; i++) {
Serial.printf("%02x", extPanId[i]);
}
Serial.println();
}
// Network Key
const uint8_t *networkKey = threadLeaderNode.getNetworkKey();
if (networkKey) {
Serial.print("Network Key: ");
for (int i = 0; i < OT_NETWORK_KEY_SIZE; i++) {
Serial.printf("%02x", networkKey[i]);
}
Serial.println();
}
// Mesh Local EID
IPAddress meshLocalEid = threadLeaderNode.getMeshLocalEid();
Serial.printf("Mesh Local EID: %s\r\n", meshLocalEid.toString().c_str());
// Leader RLOC
IPAddress leaderRloc = threadLeaderNode.getLeaderRloc();
Serial.printf("Leader RLOC: %s\r\n", leaderRloc.toString().c_str());
// Node RLOC
IPAddress nodeRloc = threadLeaderNode.getRloc();
Serial.printf("Node RLOC: %s\r\n", nodeRloc.toString().c_str());
// Demonstrate address listing with two different methods:
// Method 1: Unicast addresses using counting API (individual access)
Serial.println("\r\n--- Unicast Addresses (Using Count + Index API) ---");
size_t unicastCount = threadLeaderNode.getUnicastAddressCount();
for (size_t i = 0; i < unicastCount; i++) {
IPAddress addr = threadLeaderNode.getUnicastAddress(i);
Serial.printf(" [%zu]: %s\r\n", i, addr.toString().c_str());
}
// Method 2: Multicast addresses using std::vector (bulk access)
Serial.println("\r\n--- Multicast Addresses (Using std::vector API) ---");
std::vector<IPAddress> allMulticast = threadLeaderNode.getAllMulticastAddresses();
for (size_t i = 0; i < allMulticast.size(); i++) {
Serial.printf(" [%zu]: %s\r\n", i, allMulticast[i].toString().c_str());
}
// Check for role change and clear cache if needed (only when active)
if (currentRole != lastKnownRole) {
Serial.printf(
"Role changed from %s to %s - clearing address cache\r\n", (lastKnownRole < 5) ? otRoleString[lastKnownRole] : "Unknown",
threadLeaderNode.otGetStringDeviceRole()
);
threadLeaderNode.clearAllAddressCache();
lastKnownRole = currentRole;
}
} else {
Serial.printf("Thread Node Status: %s - Waiting for thread network start...\r\n", threadLeaderNode.otGetStringDeviceRole());
// Update role tracking even when detached/disabled, but don't clear cache
lastKnownRole = currentRole;
}
delay(5000);
}
@@ -0,0 +1,183 @@
# OpenThread Leader Node Example (Native API)
This example demonstrates how to create an OpenThread Leader node using the Classes API (native OpenThread API).\
The Leader node is the first device in a Thread network that manages the network and assigns router IDs. This example shows how to configure a Leader node using the `OpenThread` and `DataSet` classes.
## Supported Targets
| SoC | Thread | Status |
| --- | ------ | ------ |
| ESP32-H2 | ✅ | Fully supported |
| ESP32-C6 | ✅ | Fully supported |
| ESP32-C5 | ✅ | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example uses the Classes API (`OpenThread` and `DataSet` classes) instead of CLI Helper Functions.
- This example uses `OpenThread.begin(false)` which does not use NVS dataset information, allowing fresh configuration.
## Features
- Leader node configuration using Classes API
- Dataset creation and configuration using `DataSet` class
- Network information display using `OpenThread` class methods
- IPv6 address management (unicast and multicast)
- Address cache management on role changes
- Comprehensive network status monitoring
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- USB cable for Serial communication
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
Before uploading the sketch, you can modify the network configuration:
```cpp
dataset.setNetworkName("ESP_OpenThread");
dataset.setChannel(15);
dataset.setPanId(0x1234);
uint8_t networkKey[OT_NETWORK_KEY_SIZE] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
dataset.setNetworkKey(networkKey);
```
**Important:**
- The network key must be a 16-byte array
- The channel must be between 11 and 26 (IEEE 802.15.4 channels)
- All devices in the same network must use the same network key and channel
- Extended PAN ID should be unique for your network
## Building and Flashing
1. Open the `LeaderNode.ino` sketch in the Arduino IDE.
2. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
3. Connect your ESP32 board to your computer via USB.
4. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
==============================================
OpenThread Network Information:
Role: Leader
RLOC16: 0x0000
Network Name: ESP_OpenThread
Channel: 15
PAN ID: 0x1234
Extended PAN ID: dead00beef00cafe
Network Key: 00112233445566778899aabbccddeeff
Mesh Local EID: fd00:db8:a0:0:0:ff:fe00:0
Leader RLOC: fd00:db8:a0:0:0:ff:fe00:0
Node RLOC: fd00:db8:a0:0:0:ff:fe00:0
--- Unicast Addresses (Using Count + Index API) ---
[0]: fd00:db8:a0:0:0:ff:fe00:0
[1]: fe80:0:0:0:0:ff:fe00:0
--- Multicast Addresses (Using std::vector API) ---
[0]: ff02::1
[1]: ff03::1
[2]: ff03::fc
...
```
## Using the Device
### Leader Node Setup
The Leader node is automatically configured in `setup()` using the Classes API:
1. **Initialize OpenThread**: `threadLeaderNode.begin(false)` - Starts OpenThread stack without using NVS
2. **Create dataset**: `dataset.initNew()` - Creates a new complete dataset
3. **Configure dataset**: Sets network name, extended PAN ID, network key, channel, and PAN ID
4. **Apply dataset**: `threadLeaderNode.commitDataSet(dataset)` - Applies the dataset
5. **Start network**: `threadLeaderNode.networkInterfaceUp()` and `threadLeaderNode.start()` - Starts the Thread network
### Network Information
The `loop()` function displays comprehensive network information using Classes API methods:
- Device role and RLOC16
- Network name, channel, PAN ID
- Extended PAN ID and network key
- IPv6 addresses (Mesh Local EID, Leader RLOC, Node RLOC)
- Unicast addresses (using count + index API)
- Multicast addresses (using std::vector API)
### Address Cache Management
The example demonstrates address cache management:
- Clears address cache when device role changes
- Tracks role changes to optimize address resolution
### Joining Other Devices
To join other devices to this network:
1. Use the same network key and channel in the Router/Child node examples
2. Start the Leader node first
3. Then start the Router/Child nodes
## Code Structure
The LeaderNode example consists of the following main components:
1. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin(false)`
- Creates and configures a `DataSet` object:
- `dataset.initNew()` - Initialize new dataset
- `dataset.setNetworkName()` - Set network name
- `dataset.setExtendedPanId()` - Set extended PAN ID
- `dataset.setNetworkKey()` - Set network key
- `dataset.setChannel()` - Set channel
- `dataset.setPanId()` - Set PAN ID
- Applies dataset: `threadLeaderNode.commitDataSet(dataset)`
- Starts network: `threadLeaderNode.networkInterfaceUp()` and `threadLeaderNode.start()`
2. **`loop()`**:
- Gets current device role using `threadLeaderNode.otGetDeviceRole()`
- Displays network information using Classes API methods:
- `threadLeaderNode.otGetStringDeviceRole()` - Device role as string
- `threadLeaderNode.getRloc16()` - RLOC16
- `threadLeaderNode.getNetworkName()` - Network name
- `threadLeaderNode.getChannel()` - Channel
- `threadLeaderNode.getPanId()` - PAN ID
- `threadLeaderNode.getExtendedPanId()` - Extended PAN ID
- `threadLeaderNode.getNetworkKey()` - Network key
- `threadLeaderNode.getMeshLocalEid()` - Mesh Local EID
- `threadLeaderNode.getLeaderRloc()` - Leader RLOC
- `threadLeaderNode.getRloc()` - Node RLOC
- `threadLeaderNode.getUnicastAddressCount()` and `threadLeaderNode.getUnicastAddress(i)` - Unicast addresses
- `threadLeaderNode.getAllMulticastAddresses()` - Multicast addresses
- Manages address cache on role changes
- Updates every 5 seconds
## Troubleshooting
- **Device not becoming Leader**: Ensure this is the first device started, or clear NVS to start fresh
- **Network key/channel mismatch**: Verify all devices use the same network key and channel
- **No network information**: Wait for the device to become Leader (may take a few seconds)
- **Address cache issues**: The example automatically clears cache on role changes
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Dataset API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_dataset.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
@@ -0,0 +1,174 @@
# OpenThread Router/Child Node Example (Native API)
This example demonstrates how to create an OpenThread Router or Child node that joins an existing Thread network using the Classes API (native OpenThread API).\
The Router/Child node joins a network created by a Leader node and can route messages or operate as an end device. This example shows how to configure a Router/Child node using the `OpenThread` and `DataSet` classes.
## Supported Targets
| SoC | Thread | Status |
| --- | ------ | ------ |
| ESP32-H2 | ✅ | Fully supported |
| ESP32-C6 | ✅ | Fully supported |
| ESP32-C5 | ✅ | Fully supported |
### Note on Thread Support:
- Thread support must be enabled in the ESP-IDF configuration (`CONFIG_OPENTHREAD_ENABLED`). This is done automatically when using the ESP32 Arduino OpenThread library.
- This example uses the Classes API (`OpenThread` and `DataSet` classes) instead of CLI Helper Functions.
- This example uses `OpenThread.begin(false)` which does not use NVS dataset information, allowing fresh configuration.
- **Important:** A Leader node must be running before starting this Router/Child node.
## Features
- Router/Child node configuration using Classes API
- Dataset configuration using `DataSet` class
- Joins an existing Thread network created by a Leader node
- Network information display using `OpenThread` class methods
- Active dataset retrieval and display
- Comprehensive network status monitoring
## Hardware Requirements
- ESP32 compatible development board with Thread support (ESP32-H2, ESP32-C6, or ESP32-C5)
- USB cable for Serial communication
- A Leader node must be running on the same network
## Software Setup
### Prerequisites
1. Install the Arduino IDE (2.0 or newer recommended)
2. Install ESP32 Arduino Core with OpenThread support
3. ESP32 Arduino libraries:
- `OpenThread`
### Configuration
Before uploading the sketch, configure the network parameters to match the Leader node:
```cpp
uint8_t networkKey[OT_NETWORK_KEY_SIZE] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
dataset.setNetworkKey(networkKey);
```
**Important:**
- The network key **must match** the Leader node's network key exactly
- The network key must be a 16-byte array
- Only the network key is required to join (other parameters are learned from the Leader)
- **Start the Leader node first** before starting this Router/Child node
## Building and Flashing
1. **First, start the Leader node** using the LeaderNode example (Native API)
2. Open the `RouterNode.ino` sketch in the Arduino IDE.
3. Select your ESP32 board from the **Tools > Board** menu (ESP32-H2, ESP32-C6, or ESP32-C5).
4. Connect your ESP32 board to your computer via USB.
5. Click the **Upload** button to compile and flash the sketch.
## Expected Output
Once the sketch is running, open the Serial Monitor at a baud rate of **115200**. You should see output similar to the following:
```
==============================================
OpenThread Network Information (Active Dataset):
Role: Router
RLOC16: 0xfc00
Network Name: ESP_OpenThread
Channel: 15
PAN ID: 0x1234
Extended PAN ID: dead00beef00cafe
Network Key: 00112233445566778899aabbccddeeff
Mesh Local EID: fd00:db8:a0:0:0:ff:fe00:fc00
Node RLOC: fd00:db8:a0:0:0:ff:fe00:fc00
Leader RLOC: fd00:db8:a0:0:0:ff:fe00:0
```
The device will join as either a **Router** (if network needs more routers) or **Child** (end device).
## Using the Device
### Router/Child Node Setup
The Router/Child node is automatically configured in `setup()` using the Classes API:
1. **Initialize OpenThread**: `threadChildNode.begin(false)` - Starts OpenThread stack without using NVS
2. **Clear dataset**: `dataset.clear()` - Clears any existing dataset
3. **Configure dataset**: Sets only the network key (must match Leader)
4. **Apply dataset**: `threadChildNode.commitDataSet(dataset)` - Applies the dataset
5. **Start network**: `threadChildNode.networkInterfaceUp()` and `threadChildNode.start()` - Starts the Thread network and joins existing network
### Network Information
The `loop()` function displays network information using Classes API methods:
- Device role and RLOC16
- Active dataset information (retrieved using `threadChildNode.getCurrentDataSet()`):
- Network name, channel, PAN ID
- Extended PAN ID and network key
- Runtime information:
- Mesh Local EID, Node RLOC, Leader RLOC
### Active Dataset Retrieval
This example demonstrates how to retrieve the active dataset:
- Uses `threadChildNode.getCurrentDataSet()` to get the current active dataset
- Displays dataset parameters that were learned from the Leader node
- Shows that only the network key needs to be configured to join
### Multi-Device Network
To create a multi-device Thread network:
1. Start the Leader node first (using Native API LeaderNode example)
2. Start Router/Child nodes (using this example)
3. All devices will form a mesh network
4. Routers extend network range and route messages
5. Children are end devices that can sleep
## Code Structure
The RouterNode example consists of the following main components:
1. **`setup()`**:
- Initializes Serial communication
- Starts OpenThread stack with `OpenThread.begin(false)`
- Creates and configures a `DataSet` object:
- `dataset.clear()` - Clear existing dataset
- `dataset.setNetworkKey()` - Set network key (must match Leader)
- Applies dataset: `threadChildNode.commitDataSet(dataset)`
- Starts network: `threadChildNode.networkInterfaceUp()` and `threadChildNode.start()`
2. **`loop()`**:
- Gets current device role using `threadChildNode.otGetDeviceRole()`
- Retrieves active dataset using `threadChildNode.getCurrentDataSet()`
- Displays network information using Classes API methods:
- `threadChildNode.otGetStringDeviceRole()` - Device role as string
- `threadChildNode.getRloc16()` - RLOC16
- `activeDataset.getNetworkName()` - Network name from dataset
- `activeDataset.getChannel()` - Channel from dataset
- `activeDataset.getPanId()` - PAN ID from dataset
- `activeDataset.getExtendedPanId()` - Extended PAN ID from dataset
- `activeDataset.getNetworkKey()` - Network key from dataset
- `threadChildNode.getMeshLocalEid()` - Mesh Local EID
- `threadChildNode.getRloc()` - Node RLOC
- `threadChildNode.getLeaderRloc()` - Leader RLOC
- Updates every 5 seconds
## Troubleshooting
- **Device not joining network**: Ensure the Leader node is running first. Verify network key matches the Leader exactly.
- **Role stuck as "Detached"**: Wait a few seconds for the device to join. Check that network key matches the Leader.
- **Network key mismatch**: Double-check that both Leader and Router/Child nodes use identical network key values.
- **No network information**: Wait for the device to join the network (may take 10-30 seconds)
- **Active dataset empty**: Ensure device has successfully joined the network before checking dataset
- **No serial output**: Check baudrate (115200) and USB connection
## Related Documentation
- [OpenThread Core API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_core.html)
- [OpenThread Dataset API](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread_dataset.html)
- [OpenThread Overview](https://docs.espressif.com/projects/arduino-esp32/en/latest/openthread/openthread.html)
## License
This example is licensed under the Apache License, Version 2.0.
@@ -0,0 +1,84 @@
#include "OThread.h"
OpenThread threadChildNode;
DataSet dataset;
void setup() {
Serial.begin(115200);
// Start OpenThread Stack - false for not using NVS dataset information
threadChildNode.begin(false);
// clear dataset
dataset.clear();
// Configure the dataset with the same Network Key of the Leader Node
uint8_t networkKey[OT_NETWORK_KEY_SIZE] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
dataset.setNetworkKey(networkKey);
// Apply the dataset and start the network
threadChildNode.commitDataSet(dataset);
threadChildNode.networkInterfaceUp();
threadChildNode.start();
}
void loop() {
// Get current device role
ot_device_role_t currentRole = threadChildNode.otGetDeviceRole();
// Only print detailed network information when node is active
if (currentRole != OT_ROLE_DETACHED && currentRole != OT_ROLE_DISABLED) {
Serial.println("==============================================");
Serial.println("OpenThread Network Information (Active Dataset):");
// Get and display the current active dataset
const DataSet &activeDataset = threadChildNode.getCurrentDataSet();
Serial.printf("Role: %s\r\n", threadChildNode.otGetStringDeviceRole());
Serial.printf("RLOC16: 0x%04x\r\n", threadChildNode.getRloc16());
// Dataset information
Serial.printf("Network Name: %s\r\n", activeDataset.getNetworkName());
Serial.printf("Channel: %d\r\n", activeDataset.getChannel());
Serial.printf("PAN ID: 0x%04x\r\n", activeDataset.getPanId());
// Extended PAN ID from dataset
const uint8_t *extPanId = activeDataset.getExtendedPanId();
if (extPanId) {
Serial.print("Extended PAN ID: ");
for (int i = 0; i < OT_EXT_PAN_ID_SIZE; i++) {
Serial.printf("%02x", extPanId[i]);
}
Serial.println();
}
// Network Key from dataset
const uint8_t *networkKey = activeDataset.getNetworkKey();
if (networkKey) {
Serial.print("Network Key: ");
for (int i = 0; i < OT_NETWORK_KEY_SIZE; i++) {
Serial.printf("%02x", networkKey[i]);
}
Serial.println();
}
// Additional runtime information
IPAddress meshLocalEid = threadChildNode.getMeshLocalEid();
Serial.printf("Mesh Local EID: %s\r\n", meshLocalEid.toString().c_str());
IPAddress nodeRloc = threadChildNode.getRloc();
Serial.printf("Node RLOC: %s\r\n", nodeRloc.toString().c_str());
IPAddress leaderRloc = threadChildNode.getLeaderRloc();
Serial.printf("Leader RLOC: %s\r\n", leaderRloc.toString().c_str());
Serial.println();
} else {
Serial.println("==============================================");
Serial.printf("Thread Node Status: %s - Waiting for thread network start...\r\n", threadChildNode.otGetStringDeviceRole());
Serial.println();
}
delay(5000);
}
@@ -0,0 +1,3 @@
requires:
- CONFIG_OPENTHREAD_ENABLED=y
- CONFIG_SOC_IEEE802154_SUPPORTED=y
+59
View File
@@ -0,0 +1,59 @@
# OpenThread Helper Functions and Types
The following helper functions and types are designed to simplify writing Arduino sketches for OpenThread.\
They provide useful utilities for managing OpenThread stack behavior and interacting with the Thread network.
### Enumerated Type: `ot_device_role_t`
This enumeration defines the possible roles of a Thread device within the network:
- `OT_ROLE_DISABLED`: The Thread stack is disabled.
- `OT_ROLE_DETACHED`: The device is not currently participating in a Thread network/partition.
- `OT_ROLE_CHILD`: The device operates as a Thread Child.
- `OT_ROLE_ROUTER`: The device operates as a Thread Router.
- `OT_ROLE_LEADER`: The device operates as a Thread Leader.
### Struct: `ot_cmd_return_t`
This structure represents the return status of an OpenThread CLI command:
- `errorCode`: An integer representing the error code (if any).
- `errorMessage`: A string containing an error message (if applicable).
### Function: `otGetDeviceRole()`
- Returns the current role of the device as an `ot_device_role_t` value.
### Function: `otGetStringDeviceRole()`
- Returns a human-readable string representation of the device role (e.g., "Child," "Router," etc.).
### Function: `otGetRespCmd(const char* cmd, char* resp = NULL, uint32_t respTimeout = 5000)`
- Executes an OpenThread CLI command and retrieves the response.
- Parameters:
- `cmd`: The OpenThread CLI command to execute.
- `resp`: Optional buffer to store the response (if provided).
- `respTimeout`: Timeout (in milliseconds) for waiting for the response.
### Function: `otExecCommand(const char* cmd, const char* arg, ot_cmd_return_t* returnCode = NULL)`
- Executes an OpenThread CLI command with an argument.
- Parameters:
- `cmd`: The OpenThread CLI command to execute.
- `arg`: The argument for the command.
- `returnCode`: Optional pointer to an `ot_cmd_return_t` structure to store the return status.
### Function: `otPrintRespCLI(const char* cmd, Stream& output, uint32_t respTimeout)`
- Executes an OpenThread CLI command and prints the response to the specified output stream.
- Parameters:
- `cmd`: The OpenThread CLI command to execute.
- `output`: The output stream (e.g., Serial) to print the response.
- `respTimeout`: Timeout (in milliseconds) for waiting for the response.
### Function: `otPrintNetworkInformation(Stream& output)`
- Prints information about the current Thread network to the specified output stream.
- Parameters:
- `output`: The output stream (e.g., Serial) to print the network information.
+91
View File
@@ -0,0 +1,91 @@
#######################################
# Syntax Coloring Map For OpenThread
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
OThreadCLI KEYWORD1
OThread KEYWORD1
OpenThreadCLI KEYWORD1
OpenThread KEYWORD1
DataSet KEYWORD1
ot_cmd_return_t KEYWORD1
ot_device_role_t KEYWORD1
OnReceiveCb_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
clear KEYWORD2
initNew KEYWORD2
getDataset KEYWORD2
setNetworkName KEYWORD2
getNetworkName KEYWORD2
setExtendedPanId KEYWORD2
getExtendedPanId KEYWORD2
setNetworkKey KEYWORD2
getNetworkKey KEYWORD2
setChannel KEYWORD2
getChannel KEYWORD2
setPanId KEYWORD2
getPanId KEYWORD2
apply KEYWORD2
otStarted KEYWORD2
otCLIStarted KEYWORD2
start KEYWORD2
stop KEYWORD2
networkInterfaceUp KEYWORD2
networkInterfaceDown KEYWORD2
commitDataSet KEYWORD2
getInstance KEYWORD2
getCurrentDataSet KEYWORD2
getMeshLocalPrefix KEYWORD2
getMeshLocalEid KEYWORD2
getLeaderRloc KEYWORD2
getRloc KEYWORD2
getRloc16 KEYWORD2
getUnicastAddressCount KEYWORD2
getUnicastAddress KEYWORD2
getAllUnicastAddresses KEYWORD2
getMulticastAddressCount KEYWORD2
getMulticastAddress KEYWORD2
getAllMulticastAddresses KEYWORD2
clearUnicastAddressCache KEYWORD2
clearMulticastAddressCache KEYWORD2
clearAllAddressCache KEYWORD2
end KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################
OT_ROLE_DISABLED LITERAL1
OT_ROLE_DETACHED LITERAL1
OT_ROLE_CHILD LITERAL1
OT_ROLE_ROUTER LITERAL1
OT_ROLE_LEADER LITERAL1
OT_EXT_PAN_ID_SIZE LITERAL1
OT_NETWORK_KEY_SIZE LITERAL1
+9
View File
@@ -0,0 +1,9 @@
name=OpenThread
version=3.3.7
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
+693
View File
@@ -0,0 +1,693 @@
#include "OThread.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
#include "IPAddress.h"
#include <vector>
#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_net_stack.h"
#include "esp_openthread_netif_glue.h"
#include "lwip/netif.h"
static esp_openthread_platform_config_t ot_native_config;
static esp_netif_t *openthread_netif = NULL;
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.
"Unknown", ///< Unknown role, not initialized or not started.
};
static TaskHandle_t s_ot_task = NULL;
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
static struct netif *ot_lwip_netif = NULL;
#endif
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
extern "C" int lwip_hook_ip6_input(struct pbuf *p, struct netif *inp) {
if (ot_lwip_netif && ot_lwip_netif == inp) {
return 0;
}
if (ip6_addr_isany_val(inp->ip6_addr[0].u_addr.ip6)) {
// We don't have an LL address -> eat this packet here, so it won't get accepted on input netif
pbuf_free(p);
return 1;
}
return 0;
}
#endif
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 esp_netif bindings
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
openthread_netif = esp_netif_new(&cfg);
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
// Get LwIP Netif
if (openthread_netif != NULL) {
ot_lwip_netif = (struct netif *)esp_netif_get_netif_impl(openthread_netif);
if (ot_lwip_netif == NULL) {
log_e("Failed to get OpenThread LwIP netif");
}
}
#endif
}
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);
}
// DataSet Implementation
DataSet::DataSet() {
memset(&mDataset, 0, sizeof(mDataset));
}
void DataSet::clear() {
memset(&mDataset, 0, sizeof(mDataset));
}
void DataSet::initNew() {
otInstance *mInstance = esp_openthread_get_instance();
if (!mInstance) {
log_e("OpenThread not started. Please begin() it before initializing a new dataset.");
return;
}
clear();
otDatasetCreateNewNetwork(mInstance, &mDataset);
}
const otOperationalDataset &DataSet::getDataset() const {
return mDataset;
}
void DataSet::setNetworkName(const char *name) {
if (!name) {
log_w("Network name is null");
return;
}
// char m8[OT_NETWORK_KEY_SIZE + 1] bytes space by definition
strncpy(mDataset.mNetworkName.m8, name, OT_NETWORK_KEY_SIZE);
mDataset.mComponents.mIsNetworkNamePresent = true;
}
void DataSet::setExtendedPanId(const uint8_t *extPanId) {
if (!extPanId) {
log_w("Extended PAN ID is null");
return;
}
memcpy(mDataset.mExtendedPanId.m8, extPanId, OT_EXT_PAN_ID_SIZE);
mDataset.mComponents.mIsExtendedPanIdPresent = true;
}
void DataSet::setNetworkKey(const uint8_t *key) {
if (!key) {
log_w("Network key is null");
return;
}
memcpy(mDataset.mNetworkKey.m8, key, OT_NETWORK_KEY_SIZE);
mDataset.mComponents.mIsNetworkKeyPresent = true;
}
void DataSet::setChannel(uint8_t channel) {
mDataset.mChannel = channel;
mDataset.mComponents.mIsChannelPresent = true;
}
void DataSet::setPanId(uint16_t panId) {
mDataset.mPanId = panId;
mDataset.mComponents.mIsPanIdPresent = true;
}
const char *DataSet::getNetworkName() const {
return mDataset.mNetworkName.m8;
}
const uint8_t *DataSet::getExtendedPanId() const {
return mDataset.mExtendedPanId.m8;
}
const uint8_t *DataSet::getNetworkKey() const {
return mDataset.mNetworkKey.m8;
}
uint8_t DataSet::getChannel() const {
return mDataset.mChannel;
}
uint16_t DataSet::getPanId() const {
return mDataset.mPanId;
}
void DataSet::apply(otInstance *instance) {
otDatasetSetActive(instance, &mDataset);
}
// OpenThread Implementation
bool OpenThread::otStarted;
otInstance *OpenThread::mInstance;
DataSet OpenThread::mCurrentDataset;
otNetworkKey OpenThread::mNetworkKey;
OpenThread::OpenThread() {
// static initialization (node data and stack starting information)
otStarted = false;
mCurrentDataset.clear(); // Initialize the current dataset
memset(&mNetworkKey, 0, sizeof(mNetworkKey)); // Initialize the network key
mInstance = nullptr;
}
OpenThread::~OpenThread() {
end();
}
OpenThread::operator bool() const {
return otStarted;
}
void OpenThread::begin(bool OThreadAutoStart) {
if (otStarted) {
log_w("OpenThread already started");
return;
}
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;
// Initialize OpenThread stack
xTaskCreate(ot_task_worker, "ot_main_loop", 10240, NULL, 20, &s_ot_task);
if (s_ot_task == NULL) {
log_e("Error: Failed to create OpenThread task");
return;
}
log_d("OpenThread task created successfully");
// 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");
}
}
// get the OpenThread instance that will be used for all operations
mInstance = esp_openthread_get_instance();
if (!mInstance) {
log_e("Error: Failed to initialize OpenThread instance");
end();
return;
}
otStarted = true;
}
void OpenThread::end() {
if (!otStarted) {
log_w("OpenThread already stopped");
return;
}
if (s_ot_task != NULL) {
vTaskDelete(s_ot_task);
s_ot_task = NULL;
}
// Clean up in reverse order of initialization
if (openthread_netif != NULL) {
esp_netif_destroy(openthread_netif);
openthread_netif = NULL;
}
esp_openthread_netif_glue_deinit();
esp_openthread_deinit();
esp_vfs_eventfd_unregister();
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
ot_lwip_netif = NULL;
#endif
mInstance = nullptr;
otStarted = false;
log_d("OpenThread ended successfully");
}
void OpenThread::start() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
clearAllAddressCache(); // Clear cache when starting network
otThreadSetEnabled(mInstance, true);
log_d("Thread network started");
}
void OpenThread::stop() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
clearAllAddressCache(); // Clear cache when stopping network
otThreadSetEnabled(mInstance, false);
log_d("Thread network stopped");
}
void OpenThread::networkInterfaceUp() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
// Enable the Thread interface (equivalent to CLI Command "ifconfig up")
otError error = otIp6SetEnabled(mInstance, true);
if (error != OT_ERROR_NONE) {
log_e("Error: Failed to enable Thread interface (error code: %d)\n", error);
}
clearAllAddressCache(); // Clear cache when interface comes up
log_d("OpenThread Network Interface is up");
}
void OpenThread::networkInterfaceDown() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
// Disable the Thread interface (equivalent to CLI Command "ifconfig down")
otError error = otIp6SetEnabled(mInstance, false);
if (error != OT_ERROR_NONE) {
log_e("Error: Failed to disable Thread interface (error code: %d)\n", error);
}
log_d("OpenThread Network Interface is down");
}
void OpenThread::commitDataSet(const DataSet &dataset) {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
// Commit the dataset as the active dataset
otError error = otDatasetSetActive(mInstance, &(dataset.getDataset()));
if (error != OT_ERROR_NONE) {
log_e("Error: Failed to commit dataset (error code: %d)\n", error);
return;
}
clearAllAddressCache(); // Clear cache when dataset changes
log_d("Dataset committed successfully");
}
ot_device_role_t OpenThread::otGetDeviceRole() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return OT_ROLE_DISABLED;
}
return (ot_device_role_t)otThreadGetDeviceRole(mInstance);
}
const char *OpenThread::otGetStringDeviceRole() {
return otRoleString[otGetDeviceRole()];
}
void OpenThread::otPrintNetworkInformation(Stream &output) {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return;
}
output.printf("Role: %s", otGetStringDeviceRole());
output.println();
output.printf("RLOC16: 0x%04x", otThreadGetRloc16(mInstance)); // RLOC16
output.println();
output.printf("Network Name: %s", otThreadGetNetworkName(mInstance));
output.println();
output.printf("Channel: %d", otLinkGetChannel(mInstance));
output.println();
output.printf("PAN ID: 0x%04x", otLinkGetPanId(mInstance));
output.println();
const otExtendedPanId *extPanId = otThreadGetExtendedPanId(mInstance);
output.print("Extended PAN ID: ");
for (int i = 0; i < OT_EXT_PAN_ID_SIZE; i++) {
output.printf("%02x", extPanId->m8[i]);
}
output.println();
otNetworkKey networkKey;
otThreadGetNetworkKey(mInstance, &networkKey);
output.print("Network Key: ");
for (int i = 0; i < OT_NETWORK_KEY_SIZE; i++) {
output.printf("%02x", networkKey.m8[i]);
}
output.println();
}
// Get the Node Network Name
String OpenThread::getNetworkName() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return String(); // Return empty String, not nullptr
}
const char *networkName = otThreadGetNetworkName(mInstance);
return networkName ? String(networkName) : String();
}
// Get the Node Extended PAN ID
const uint8_t *OpenThread::getExtendedPanId() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return nullptr;
}
const otExtendedPanId *extPanId = otThreadGetExtendedPanId(mInstance);
return extPanId ? extPanId->m8 : nullptr;
}
// Get the Node Network Key
const uint8_t *OpenThread::getNetworkKey() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return nullptr;
}
otThreadGetNetworkKey(mInstance, &mNetworkKey);
return mNetworkKey.m8;
}
// Get the Node Channel
uint8_t OpenThread::getChannel() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return 0;
}
return otLinkGetChannel(mInstance);
}
// Get the Node PAN ID
uint16_t OpenThread::getPanId() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return 0;
}
return otLinkGetPanId(mInstance);
}
// Get the OpenThread instance
otInstance *OpenThread::getInstance() {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return nullptr;
}
return mInstance;
}
// Get the current dataset
const DataSet &OpenThread::getCurrentDataSet() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
mCurrentDataset.clear();
return mCurrentDataset;
}
otOperationalDataset dataset;
otError error = otDatasetGetActive(mInstance, &dataset);
if (error == OT_ERROR_NONE) {
mCurrentDataset.clear();
if (dataset.mComponents.mIsNetworkNamePresent) {
mCurrentDataset.setNetworkName(dataset.mNetworkName.m8);
}
if (dataset.mComponents.mIsExtendedPanIdPresent) {
mCurrentDataset.setExtendedPanId(dataset.mExtendedPanId.m8);
}
if (dataset.mComponents.mIsNetworkKeyPresent) {
mCurrentDataset.setNetworkKey(dataset.mNetworkKey.m8);
}
if (dataset.mComponents.mIsChannelPresent) {
mCurrentDataset.setChannel(dataset.mChannel);
}
if (dataset.mComponents.mIsPanIdPresent) {
mCurrentDataset.setPanId(dataset.mPanId);
}
} else {
log_w("Failed to get active dataset (error: %d)", error);
mCurrentDataset.clear();
}
return mCurrentDataset;
}
// Get the Mesh Local Prefix
const otMeshLocalPrefix *OpenThread::getMeshLocalPrefix() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return nullptr;
}
return otThreadGetMeshLocalPrefix(mInstance);
}
// Get the Mesh-Local EID
IPAddress OpenThread::getMeshLocalEid() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return IPAddress(IPv6); // Return empty IPv6 address
}
const otIp6Address *otAddr = otThreadGetMeshLocalEid(mInstance);
if (!otAddr) {
log_w("Failed to get Mesh Local EID");
return IPAddress(IPv6);
}
return IPAddress(IPv6, otAddr->mFields.m8);
}
// Get the Thread Leader RLOC
IPAddress OpenThread::getLeaderRloc() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return IPAddress(IPv6); // Return empty IPv6 address
}
otIp6Address otAddr;
otError error = otThreadGetLeaderRloc(mInstance, &otAddr);
if (error != OT_ERROR_NONE) {
log_w("Failed to get Leader RLOC");
return IPAddress(IPv6);
}
return IPAddress(IPv6, otAddr.mFields.m8);
}
// Get the Node RLOC
IPAddress OpenThread::getRloc() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return IPAddress(IPv6); // Return empty IPv6 address
}
const otIp6Address *otAddr = otThreadGetRloc(mInstance);
if (!otAddr) {
log_w("Failed to get Node RLOC");
return IPAddress(IPv6);
}
return IPAddress(IPv6, otAddr->mFields.m8);
}
// Get the RLOC16 ID
uint16_t OpenThread::getRloc16() const {
if (!mInstance) {
log_w("Error: OpenThread instance not initialized");
return 0;
}
return otThreadGetRloc16(mInstance);
}
// Populate unicast address cache from OpenThread
void OpenThread::populateUnicastAddressCache() const {
if (!mInstance) {
return;
}
// Clear existing cache
mCachedUnicastAddresses.clear();
// Populate unicast addresses cache
const otNetifAddress *addr = otIp6GetUnicastAddresses(mInstance);
while (addr != nullptr) {
mCachedUnicastAddresses.push_back(IPAddress(IPv6, addr->mAddress.mFields.m8));
addr = addr->mNext;
}
log_d("Populated unicast address cache with %zu addresses", mCachedUnicastAddresses.size());
}
// Populate multicast address cache from OpenThread
void OpenThread::populateMulticastAddressCache() const {
if (!mInstance) {
return;
}
// Clear existing cache
mCachedMulticastAddresses.clear();
// Populate multicast addresses cache
const otNetifMulticastAddress *mAddr = otIp6GetMulticastAddresses(mInstance);
while (mAddr != nullptr) {
mCachedMulticastAddresses.push_back(IPAddress(IPv6, mAddr->mAddress.mFields.m8));
mAddr = mAddr->mNext;
}
log_d("Populated multicast address cache with %zu addresses", mCachedMulticastAddresses.size());
}
// Clear unicast address cache
void OpenThread::clearUnicastAddressCache() const {
mCachedUnicastAddresses.clear();
log_d("Cleared unicast address cache");
}
// Clear multicast address cache
void OpenThread::clearMulticastAddressCache() const {
mCachedMulticastAddresses.clear();
log_d("Cleared multicast address cache");
}
// Clear all address caches
void OpenThread::clearAllAddressCache() const {
mCachedUnicastAddresses.clear();
mCachedMulticastAddresses.clear();
log_d("Cleared all address caches");
}
// Get count of unicast addresses
size_t OpenThread::getUnicastAddressCount() const {
// Populate cache if empty
if (mCachedUnicastAddresses.empty()) {
populateUnicastAddressCache();
}
return mCachedUnicastAddresses.size();
}
// Get unicast address by index
IPAddress OpenThread::getUnicastAddress(size_t index) const {
// Populate cache if empty
if (mCachedUnicastAddresses.empty()) {
populateUnicastAddressCache();
}
if (index >= mCachedUnicastAddresses.size()) {
log_w("Unicast address index %zu out of range (max: %zu)", index, mCachedUnicastAddresses.size());
return IPAddress(IPv6);
}
return mCachedUnicastAddresses[index];
}
// Get all unicast addresses
std::vector<IPAddress> OpenThread::getAllUnicastAddresses() const {
// Populate cache if empty
if (mCachedUnicastAddresses.empty()) {
populateUnicastAddressCache();
}
return mCachedUnicastAddresses; // Return copy of cached vector
}
// Get count of multicast addresses
size_t OpenThread::getMulticastAddressCount() const {
// Populate cache if empty
if (mCachedMulticastAddresses.empty()) {
populateMulticastAddressCache();
}
return mCachedMulticastAddresses.size();
}
// Get multicast address by index
IPAddress OpenThread::getMulticastAddress(size_t index) const {
// Populate cache if empty
if (mCachedMulticastAddresses.empty()) {
populateMulticastAddressCache();
}
if (index >= mCachedMulticastAddresses.size()) {
log_w("Multicast address index %zu out of range (max: %zu)", index, mCachedMulticastAddresses.size());
return IPAddress(IPv6);
}
return mCachedMulticastAddresses[index];
}
// Get all multicast addresses
std::vector<IPAddress> OpenThread::getAllMulticastAddresses() const {
// Populate cache if empty
if (mCachedMulticastAddresses.empty()) {
populateMulticastAddressCache();
}
return mCachedMulticastAddresses; // Return copy of cached vector
}
OpenThread OThread;
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */
+170
View File
@@ -0,0 +1,170 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
#include <openthread/thread.h>
#include <openthread/link.h>
#include <openthread/netdata.h>
#include <openthread/ip6.h>
#include <openthread/dataset_ftd.h>
#include <esp_openthread.h>
#include <Arduino.h>
#include "IPAddress.h"
#include <vector>
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;
extern const char *otRoleString[];
class DataSet {
public:
DataSet();
void clear();
void initNew();
const otOperationalDataset &getDataset() const;
// Setters
void setNetworkName(const char *name);
void setExtendedPanId(const uint8_t *extPanId);
void setNetworkKey(const uint8_t *key);
void setChannel(uint8_t channel);
void setPanId(uint16_t panId);
// Getters
const char *getNetworkName() const;
const uint8_t *getExtendedPanId() const;
const uint8_t *getNetworkKey() const;
uint8_t getChannel() const;
uint16_t getPanId() const;
// Apply the dataset to the OpenThread instance
void apply(otInstance *instance);
private:
otOperationalDataset mDataset;
};
class OpenThread {
public:
static bool otStarted;
static ot_device_role_t otGetDeviceRole();
static const char *otGetStringDeviceRole();
static void otPrintNetworkInformation(Stream &output);
OpenThread();
~OpenThread();
// returns true if OpenThread Stack is running
operator bool() const;
// Initialize OpenThread
static void begin(bool OThreadAutoStart = true);
// Initialize OpenThread
static void end();
// Start the Thread network
void start();
// Stop the Thread network
void stop();
// Start Thread Network Interface
void networkInterfaceUp();
// Stop Thread Network Interface
void networkInterfaceDown();
// Set the dataset
void commitDataSet(const DataSet &dataset);
// Get the Node Network Name
String getNetworkName() const;
// Get the Node Extended PAN ID
const uint8_t *getExtendedPanId() const;
// Get the Node Network Key
const uint8_t *getNetworkKey() const;
// Get the Node Channel
uint8_t getChannel() const;
// Get the Node PAN ID
uint16_t getPanId() const;
// Get the OpenThread instance
otInstance *getInstance();
// Get the current dataset
const DataSet &getCurrentDataSet() const;
// Get the Mesh Local Prefix
const otMeshLocalPrefix *getMeshLocalPrefix() const;
// Get the Mesh-Local EID
IPAddress getMeshLocalEid() const;
// Get the Thread Leader RLOC
IPAddress getLeaderRloc() const;
// Get the Node RLOC
IPAddress getRloc() const;
// Get the RLOC16 ID
uint16_t getRloc16() const;
// Address management with caching
size_t getUnicastAddressCount() const;
IPAddress getUnicastAddress(size_t index) const;
std::vector<IPAddress> getAllUnicastAddresses() const;
size_t getMulticastAddressCount() const;
IPAddress getMulticastAddress(size_t index) const;
std::vector<IPAddress> getAllMulticastAddresses() const;
// Cache management
void clearUnicastAddressCache() const;
void clearMulticastAddressCache() const;
void clearAllAddressCache() const;
private:
static otInstance *mInstance;
static DataSet mCurrentDataset; // Current dataset being used by the OpenThread instance.
static otNetworkKey mNetworkKey; // Static storage to persist after function return
// Address caching for performance (user-controlled)
mutable std::vector<IPAddress> mCachedUnicastAddresses;
mutable std::vector<IPAddress> mCachedMulticastAddresses;
// Internal cache management
void populateUnicastAddressCache() const;
void populateMulticastAddressCache() const;
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_OPENTHREAD)
extern OpenThread OThread;
#endif
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */
+357
View File
@@ -0,0 +1,357 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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_net_stack.h"
#include "lwip/netif.h"
bool OpenThreadCLI::otCLIStarted = false;
static TaskHandle_t s_cli_task = NULL;
static TaskHandle_t s_console_cli_task = NULL;
static QueueHandle_t rx_queue = NULL;
static QueueHandle_t tx_queue = 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 = {nullptr, false, (const char *)nullptr, nullptr};
// 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 != nullptr) {
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; // nullptr can make the prompt not visible
}
void OpenThreadCLI::setStream(Stream &otStream) {
otConsole.cliStream = &otStream;
}
void OpenThreadCLI::onReceive(OnReceiveCb_t func) {
otConsole.responseCallBack = func; // nullptr 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 (!otCLIStarted) {
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; // nullptr 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() {
//sTxString = "";
}
OpenThreadCLI::~OpenThreadCLI() {
end();
}
OpenThreadCLI::operator bool() const {
return otCLIStarted;
}
void OpenThreadCLI::begin() {
if (otCLIStarted) {
log_w("OpenThread CLI already started. Please end() it before starting again.");
return;
}
if (!OpenThread::otStarted) {
log_w("OpenThread not started. Please begin() it before starting CLI.");
return;
}
//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);
// Initialize the OpenThread cli
otCliInit(esp_openthread_get_instance(), ot_cli_output_callback, NULL);
otCLIStarted = true;
return;
}
void OpenThreadCLI::end() {
if (!otCLIStarted) {
log_w("OpenThread CLI already stopped. Please begin() it before stopping again.");
return;
}
if (s_cli_task != NULL) {
vTaskDelete(s_cli_task);
s_cli_task = NULL;
}
stopConsole();
esp_event_loop_delete_default();
setRxBufferSize(0);
setTxBufferSize(0);
otCLIStarted = 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(QueueHandle_t &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 */
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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_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"
#include "OThread.h"
typedef std::function<void(void)> OnReceiveCb_t;
class OpenThreadCLI : public Stream {
private:
static size_t setBuffer(QueueHandle_t &queue, size_t len);
static bool otCLIStarted;
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();
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();
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_OPENTHREADCLI)
extern OpenThreadCLI OThreadCLI;
#endif
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */
@@ -0,0 +1,192 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "OThreadCLI.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
#include "OThreadCLI_Util.h"
#include <StreamString.h>
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 beginning
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 otCLIPrintNetworkInformation(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 */
@@ -0,0 +1,31 @@
// Copyright 2024 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#if SOC_IEEE802154_SUPPORTED
#if CONFIG_OPENTHREAD_ENABLED
typedef struct {
int errorCode;
String errorMessage;
} ot_cmd_return_t;
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);
#endif /* CONFIG_OPENTHREAD_ENABLED */
#endif /* SOC_IEEE802154_SUPPORTED */