diff --git a/libraries/OpenThread/examples/Native/SimpleThreadNetwork/LeaderNode/LeaderNode.ino b/libraries/OpenThread/examples/Native/SimpleThreadNetwork/LeaderNode/LeaderNode.ino index dfea9776838..b3c4091e1dc 100644 --- a/libraries/OpenThread/examples/Native/SimpleThreadNetwork/LeaderNode/LeaderNode.ino +++ b/libraries/OpenThread/examples/Native/SimpleThreadNetwork/LeaderNode/LeaderNode.ino @@ -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 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); } diff --git a/libraries/OpenThread/examples/Native/SimpleThreadNetwork/RouterNode/RouterNode.ino b/libraries/OpenThread/examples/Native/SimpleThreadNetwork/RouterNode/RouterNode.ino index 5ffa535ad51..a8959792f5b 100644 --- a/libraries/OpenThread/examples/Native/SimpleThreadNetwork/RouterNode/RouterNode.ino +++ b/libraries/OpenThread/examples/Native/SimpleThreadNetwork/RouterNode/RouterNode.ino @@ -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); } diff --git a/libraries/OpenThread/keywords.txt b/libraries/OpenThread/keywords.txt index b62c2c23ddc..99821ce401c 100644 --- a/libraries/OpenThread/keywords.txt +++ b/libraries/OpenThread/keywords.txt @@ -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) diff --git a/libraries/OpenThread/src/OThread.cpp b/libraries/OpenThread/src/OThread.cpp index 7714d870cce..87b748d104d 100644 --- a/libraries/OpenThread/src/OThread.cpp +++ b/libraries/OpenThread/src/OThread.cpp @@ -2,6 +2,8 @@ #if SOC_IEEE802154_SUPPORTED #if CONFIG_OPENTHREAD_ENABLED +#include "IPAddress.h" +#include #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 + } + + // Clean up in reverse order of initialization + if (openthread_netif != NULL) { esp_netif_destroy(openthread_netif); - esp_vfs_eventfd_unregister(); + 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 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 OpenThread::getAllMulticastAddresses() const { + // Populate cache if empty + if (mCachedMulticastAddresses.empty()) { + populateMulticastAddressCache(); + } + + return mCachedMulticastAddresses; // Return copy of cached vector +} + OpenThread OThread; #endif /* CONFIG_OPENTHREAD_ENABLED */ diff --git a/libraries/OpenThread/src/OThread.h b/libraries/OpenThread/src/OThread.h index 359d581bb9d..6e21b854574 100644 --- a/libraries/OpenThread/src/OThread.h +++ b/libraries/OpenThread/src/OThread.h @@ -25,6 +25,8 @@ #include #include #include +#include "IPAddress.h" +#include typedef enum { OT_ROLE_DISABLED = 0, ///< The Thread stack is disabled. @@ -96,9 +98,68 @@ class OpenThread { // 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 getAllUnicastAddresses() const; + + size_t getMulticastAddressCount() const; + IPAddress getMulticastAddress(size_t index) const; + std::vector 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 mCachedUnicastAddresses; + mutable std::vector mCachedMulticastAddresses; + + // Internal cache management + void populateUnicastAddressCache() const; + void populateMulticastAddressCache() const; }; extern OpenThread OThread;