feat(openthread): native API extension (#11598)

* feat(openthread): native API extension

* fix(openthread): wrong return type and parameter

* fix(openthread): wrong field reference

* fix(openthread): CR/LF fix

* feat(openthread): print leader RLOC information

* feat(openthread): code improvements

* ci(pre-commit): Apply automatic fixes

---------

Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
This commit is contained in:
Sugar Glider 2025-07-16 13:53:36 -03:00 committed by GitHub
parent 82d56bc679
commit 6015fd73e0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 566 additions and 26 deletions

View file

@ -3,6 +3,9 @@
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);
@ -27,8 +30,84 @@ void setup() {
}
void loop() {
// Print network information every 5 seconds
Serial.println("==============================================");
threadLeaderNode.otPrintNetworkInformation(Serial);
// 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);
}

View file

@ -22,8 +22,63 @@ void setup() {
}
void loop() {
// Print network information every 5 seconds
Serial.println("==============================================");
threadChildNode.otPrintNetworkInformation(Serial);
// 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);
}

View file

@ -13,6 +13,7 @@ OpenThread KEYWORD1
DataSet KEYWORD1
ot_cmd_return_t KEYWORD1
ot_device_role_t KEYWORD1
OnReceiveCb_t KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
@ -59,6 +60,23 @@ 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)

View file

@ -2,6 +2,8 @@
#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"
@ -132,16 +134,29 @@ const otOperationalDataset &DataSet::getDataset() const {
}
void DataSet::setNetworkName(const char *name) {
strncpy(mDataset.mNetworkName.m8, name, sizeof(mDataset.mNetworkName.m8));
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;
}
@ -181,10 +196,18 @@ void DataSet::apply(otInstance *instance) {
}
// OpenThread Implementation
bool OpenThread::otStarted = false;
bool OpenThread::otStarted;
otInstance *OpenThread::mInstance;
DataSet OpenThread::mCurrentDataset;
otNetworkKey OpenThread::mNetworkKey;
otInstance *OpenThread::mInstance = nullptr;
OpenThread::OpenThread() {}
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();
@ -214,13 +237,7 @@ void OpenThread::begin(bool OThreadAutoStart) {
return;
}
log_d("OpenThread task created successfully");
// 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;
}
// starts Thread with default dataset from NVS or from IDF default settings
if (OThreadAutoStart) {
otOperationalDatasetTlvs dataset;
@ -238,23 +255,46 @@ void OpenThread::begin(bool OThreadAutoStart) {
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
esp_openthread_deinit();
esp_openthread_netif_glue_deinit();
#if CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM
ot_lwip_netif = NULL;
#endif
esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister();
}
// 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() {
@ -262,6 +302,7 @@ void OpenThread::start() {
log_w("Error: OpenThread instance not initialized");
return;
}
clearAllAddressCache(); // Clear cache when starting network
otThreadSetEnabled(mInstance, true);
log_d("Thread network started");
}
@ -271,6 +312,7 @@ void OpenThread::stop() {
log_w("Error: OpenThread instance not initialized");
return;
}
clearAllAddressCache(); // Clear cache when stopping network
otThreadSetEnabled(mInstance, false);
log_d("Thread network stopped");
}
@ -285,6 +327,7 @@ void OpenThread::networkInterfaceUp() {
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");
}
@ -312,6 +355,7 @@ void OpenThread::commitDataSet(const DataSet &dataset) {
log_e("Error: Failed to commit dataset (error code: %d)\n", error);
return;
}
clearAllAddressCache(); // Clear cache when dataset changes
log_d("Dataset committed successfully");
}
@ -360,6 +404,289 @@ void OpenThread::otPrintNetworkInformation(Stream &output) {
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 */

View file

@ -25,6 +25,8 @@
#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.
@ -96,9 +98,68 @@ public:
// 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;
DataSet mCurrentDataSet;
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;
};
extern OpenThread OThread;