openthread: move esp-idf specific logic into openthread_esp

This commit is contained in:
Mathieu Rene 2024-09-30 22:18:51 -04:00
parent 1432164a42
commit 06b10bfd90
4 changed files with 355 additions and 328 deletions

View file

@ -18,6 +18,9 @@
#include <cstring> #include <cstring>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#define TAG "openthread" #define TAG "openthread"
namespace esphome { namespace esphome {
@ -25,125 +28,29 @@ namespace openthread {
OpenThreadComponent *global_openthread_component = nullptr; OpenThreadComponent *global_openthread_component = nullptr;
OpenThreadComponent::OpenThreadComponent() { OpenThreadComponent::OpenThreadComponent() { global_openthread_component = this; }
global_openthread_component = this;
}
OpenThreadComponent::~OpenThreadComponent() { OpenThreadComponent::~OpenThreadComponent() {
auto lock = EspOpenThreadLockGuard::TryAcquire(100); auto lock = OpenThreadLockGuard::TryAcquire(100);
if (!lock) { if (!lock) {
ESP_LOGW(TAG, "Failed to acquire OpenThread lock in destructor, leaking memory"); ESP_LOGW(TAG, "Failed to acquire OpenThread lock in destructor, leaking memory");
return; return;
} }
otInstance *instance = esp_openthread_get_instance(); otInstance *instance = lock->get_instance();
otSrpClientClearHostAndServices(instance); otSrpClientClearHostAndServices(instance);
otSrpClientBuffersFreeAllServices(instance); otSrpClientBuffersFreeAllServices(instance);
global_openthread_component = nullptr; global_openthread_component = nullptr;
} }
void OpenThreadComponent::setup() {
ESP_LOGI("openthread", "Setting up OpenThread...");
// Used eventfds:
// * netif
// * ot task queue
// * radio driver
// TODO: Does anything else in esphome set this up?
esp_vfs_eventfd_config_t eventfd_config = {
.max_fds = 3,
};
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
xTaskCreate([](void* arg) {
static_cast<OpenThreadComponent*>(arg)->ot_main();
vTaskDelete(NULL);
}, "ot_main", 10240, this, 5, nullptr);
xTaskCreate([](void* arg) {
static_cast<OpenThreadComponent*>(arg)->srp_setup();
vTaskDelete(NULL);
}, "ot_srp_setup", 10240, this, 5, nullptr);
ESP_LOGI("openthread", "OpenThread started");
}
static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t *config)
{
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
esp_netif_t *netif = esp_netif_new(&cfg);
assert(netif != NULL);
ESP_ERROR_CHECK(esp_netif_attach(netif, esp_openthread_netif_glue_init(config)));
return netif;
}
void OpenThreadComponent::ot_main() {
esp_openthread_platform_config_t config = {
.radio_config = {
.radio_mode = RADIO_MODE_NATIVE,
.radio_uart_config = {},
},
.host_config = {
// There is a conflict between esphome's logger which also
// claims the usb serial jtag device.
// .host_connection_mode = HOST_CONNECTION_MODE_CLI_USB,
// .host_usb_config = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(),
},
.port_config = {
.storage_partition_name = "nvs",
.netif_queue_size = 10,
.task_queue_size = 10,
},
};
// Initialize the OpenThread stack
ESP_ERROR_CHECK(esp_openthread_init(&config));
#if CONFIG_OPENTHREAD_STATE_INDICATOR_ENABLE
ESP_ERROR_CHECK(esp_openthread_state_indicator_init(esp_openthread_get_instance()));
#endif
#if CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC
// The OpenThread log level directly matches ESP log level
(void)otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL);
#endif
// Initialize the OpenThread cli
#if CONFIG_OPENTHREAD_CLI
esp_openthread_cli_init();
#endif
esp_netif_t *openthread_netif;
// Initialize the esp_netif bindings
openthread_netif = init_openthread_netif(&config);
esp_netif_set_default_netif(openthread_netif);
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
esp_cli_custom_command_init();
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
// Run the main loop
#if CONFIG_OPENTHREAD_CLI
esp_openthread_cli_create_task();
#endif
ESP_LOGI(TAG, "Activating dataset...");
otOperationalDatasetTlvs dataset;
otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset);
ESP_ERROR_CHECK(esp_openthread_auto_start((error == OT_ERROR_NONE) ? &dataset : NULL));
esp_openthread_launch_mainloop();
// Clean up
esp_openthread_netif_glue_deinit();
esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister();
}
bool OpenThreadComponent::is_connected() { bool OpenThreadComponent::is_connected() {
otInstance *instance = esp_openthread_get_instance(); auto lock = OpenThreadLockGuard::TryAcquire(100);
if (!lock) {
ESP_LOGW(TAG, "Failed to acquire OpenThread lock in is_connected");
return false;
}
otInstance *instance = lock->get_instance();
if (instance == nullptr) { if (instance == nullptr) {
return false; return false;
} }
@ -154,52 +61,36 @@ bool OpenThreadComponent::is_connected() {
return role >= OT_DEVICE_ROLE_CHILD; return role >= OT_DEVICE_ROLE_CHILD;
} }
// TODO: This gets used by mqtt in order to register the device's IP. Likely it doesn't
// make sense to return thread-local addresses, since they can't be reached from outside the thread network.
// It could make more sense to return the off-mesh-routable address instead.
network::IPAddresses OpenThreadComponent::get_ip_addresses() {
network::IPAddresses addresses;
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
uint8_t count = 0;
esp_netif_t *netif = esp_netif_get_default_netif();
count = esp_netif_get_all_ip6(netif, if_ip6s);
assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
for (int i = 0; i < count; i++) {
addresses[i + 1] = network::IPAddress(&if_ip6s[i]);
}
return addresses;
}
// Gets the off-mesh routable address // Gets the off-mesh routable address
std::optional<otIp6Address> OpenThreadComponent::get_omr_address() { std::optional<otIp6Address> OpenThreadComponent::get_omr_address() {
auto lock = EspOpenThreadLockGuard::Acquire(); auto lock = OpenThreadLockGuard::Acquire();
return this->get_omr_address(lock); return this->get_omr_address(lock);
} }
std::optional<otIp6Address> OpenThreadComponent::get_omr_address(std::optional<EspOpenThreadLockGuard> &lock) { std::optional<otIp6Address> OpenThreadComponent::get_omr_address(std::optional<OpenThreadLockGuard> &lock) {
otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT; otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
otInstance *instance = nullptr; otInstance *instance = nullptr;
instance = esp_openthread_get_instance(); instance = lock->get_instance();
otBorderRouterConfig aConfig; otBorderRouterConfig aConfig;
while (otNetDataGetNextOnMeshPrefix(instance, &iterator, &aConfig) != OT_ERROR_NONE) { while (otNetDataGetNextOnMeshPrefix(instance, &iterator, &aConfig) != OT_ERROR_NONE) {
lock.reset(); lock.reset();
vTaskDelay(100); vTaskDelay(100);
lock = EspOpenThreadLockGuard::TryAcquire(portMAX_DELAY); lock = OpenThreadLockGuard::TryAcquire(portMAX_DELAY);
if (!lock) { if (!lock) {
ESP_LOGW("OT SRP", "Could not re-acquire lock"); ESP_LOGW("OT SRP", "Could not re-acquire lock");
return {}; return {};
} }
}; };
const otIp6Prefix * omrPrefix = &aConfig.mPrefix; const otIp6Prefix *omrPrefix = &aConfig.mPrefix;
char addressAsString[40]; char addressAsString[40];
otIp6PrefixToString(omrPrefix, addressAsString, 40); otIp6PrefixToString(omrPrefix, addressAsString, 40);
ESP_LOGW("OT SRP", "USING omr prefix %s", addressAsString); ESP_LOGW("OT SRP", "USING omr prefix %s", addressAsString);
const otNetifAddress *unicastAddrs = otIp6GetUnicastAddresses(instance); const otNetifAddress *unicastAddrs = otIp6GetUnicastAddresses(instance);
for (const otNetifAddress *addr = unicastAddrs; addr; addr = addr->mNext){ for (const otNetifAddress *addr = unicastAddrs; addr; addr = addr->mNext) {
const otIp6Address *localIp = &addr->mAddress; const otIp6Address *localIp = &addr->mAddress;
if (otIp6PrefixMatch(&omrPrefix->mPrefix, localIp)) { if (otIp6PrefixMatch(&omrPrefix->mPrefix, localIp)) {
otIp6AddressToString(localIp, addressAsString, 40); otIp6AddressToString(localIp, addressAsString, 40);
@ -211,11 +102,10 @@ std::optional<otIp6Address> OpenThreadComponent::get_omr_address(std::optional<E
return {}; return {};
} }
void OpenThreadComponent::srp_setup(){ void OpenThreadComponent::srp_setup() {
otError error; otError error;
otInstance *instance = nullptr; auto lock = OpenThreadLockGuard::Acquire();
auto lock = EspOpenThreadLockGuard::Acquire(); otInstance *instance = lock->get_instance();
instance = esp_openthread_get_instance();
// set the host name // set the host name
uint16_t size; uint16_t size;
@ -244,14 +134,15 @@ void OpenThreadComponent::srp_setup(){
memcpy(hostAddressArray, &*localIp, sizeof(localIp)); memcpy(hostAddressArray, &*localIp, sizeof(localIp));
error = otSrpClientSetHostAddresses(instance, hostAddressArray, 1); error = otSrpClientSetHostAddresses(instance, hostAddressArray, 1);
if (error != 0){ if (error != 0) {
ESP_LOGW("OT SRP", "Could not set ip address with srp server"); ESP_LOGW("OT SRP", "Could not set ip address with srp server");
return; return;
} }
// Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this component // Copy the mdns services to our local instance so that the c_str pointers remain valid for the lifetime of this
// component
this->mdns_services_ = this->mdns_->get_services(); this->mdns_services_ = this->mdns_->get_services();
for (const auto& service : this->mdns_services_) { for (const auto &service : this->mdns_services_) {
otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance); otSrpClientBuffersServiceEntry *entry = otSrpClientBuffersAllocateService(instance);
if (!entry) { if (!entry) {
ESP_LOGW("OT SRP", "Failed to allocate service entry"); ESP_LOGW("OT SRP", "Failed to allocate service entry");
@ -278,13 +169,14 @@ void OpenThreadComponent::srp_setup(){
// Set port // Set port
entry->mService.mPort = service.port; entry->mService.mPort = service.port;
otDnsTxtEntry *mTxtEntries = reinterpret_cast<otDnsTxtEntry*>(this->pool_alloc(sizeof(otDnsTxtEntry) * service.txt_records.size())); otDnsTxtEntry *mTxtEntries =
reinterpret_cast<otDnsTxtEntry *>(this->pool_alloc(sizeof(otDnsTxtEntry) * service.txt_records.size()));
// Set TXT records // Set TXT records
entry->mService.mNumTxtEntries = service.txt_records.size(); entry->mService.mNumTxtEntries = service.txt_records.size();
for (size_t i = 0; i < service.txt_records.size(); i++) { for (size_t i = 0; i < service.txt_records.size(); i++) {
const auto& txt = service.txt_records[i]; const auto &txt = service.txt_records[i];
mTxtEntries[i].mKey = txt.key.c_str(); mTxtEntries[i].mKey = txt.key.c_str();
mTxtEntries[i].mValue = reinterpret_cast<const uint8_t*>(txt.value.c_str()); mTxtEntries[i].mValue = reinterpret_cast<const uint8_t *>(txt.value.c_str());
mTxtEntries[i].mValueLength = txt.value.size(); mTxtEntries[i].mValueLength = txt.value.size();
} }
entry->mService.mTxtEntries = mTxtEntries; entry->mService.mTxtEntries = mTxtEntries;
@ -301,22 +193,16 @@ void OpenThreadComponent::srp_setup(){
} }
void *OpenThreadComponent::pool_alloc(size_t size) { void *OpenThreadComponent::pool_alloc(size_t size) {
uint8_t* ptr = new uint8_t[size]; uint8_t *ptr = new uint8_t[size];
if (ptr) { if (ptr) {
this->_memory_pool.emplace_back(std::unique_ptr<uint8_t[]>(ptr)); this->_memory_pool.emplace_back(std::unique_ptr<uint8_t[]>(ptr));
} }
return ptr; return ptr;
} }
void OpenThreadComponent::set_host_name(std::string host_name){ void OpenThreadComponent::set_host_name(std::string host_name) { this->host_name = host_name; }
this->host_name = host_name;
}
void OpenThreadComponent::set_mdns(esphome::mdns::MDNSComponent *mdns) {
this->mdns_ = mdns;
}
void OpenThreadComponent::set_mdns(esphome::mdns::MDNSComponent *mdns) { this->mdns_ = mdns; }
} // namespace openthread } // namespace openthread
} // namespace esphome } // namespace esphome

View file

@ -6,23 +6,20 @@
#include <openthread/thread.h> #include <openthread/thread.h>
#ifdef USE_ESP_IDF
#include "openthread_esp.h"
#endif
#include <vector> #include <vector>
#include <optional>
namespace esphome { namespace esphome {
namespace openthread { namespace openthread {
class OpenThreadLockGuard;
class OpenThreadComponent : public Component { class OpenThreadComponent : public Component {
public: public:
OpenThreadComponent(); OpenThreadComponent();
~OpenThreadComponent(); ~OpenThreadComponent();
void setup() override; void setup() override;
float get_setup_priority() const override { float get_setup_priority() const override { return setup_priority::WIFI; }
return setup_priority::WIFI;
}
void set_host_name(std::string host_name); void set_host_name(std::string host_name);
void set_mdns(esphome::mdns::MDNSComponent *mdns); void set_mdns(esphome::mdns::MDNSComponent *mdns);
@ -30,20 +27,37 @@ class OpenThreadComponent : public Component {
network::IPAddresses get_ip_addresses(); network::IPAddresses get_ip_addresses();
std::optional<otIp6Address> get_omr_address(); std::optional<otIp6Address> get_omr_address();
void ot_main(); void ot_main();
protected: protected:
void srp_setup(); void srp_setup();
std::optional<otIp6Address> get_omr_address(std::optional<EspOpenThreadLockGuard> &lock); std::optional<otIp6Address> get_omr_address(std::optional<OpenThreadLockGuard> &lock);
std::string host_name; std::string host_name;
void *pool_alloc(size_t size); void *pool_alloc(size_t size);
private: private:
// void platform_init();
esphome::mdns::MDNSComponent *mdns_{nullptr}; esphome::mdns::MDNSComponent *mdns_{nullptr};
std::vector<esphome::mdns::MDNSService> mdns_services_; std::vector<esphome::mdns::MDNSService> mdns_services_;
std::vector<std::unique_ptr<uint8_t[]>> _memory_pool; std::vector<std::unique_ptr<uint8_t[]>> _memory_pool;
}; };
extern OpenThreadComponent *global_openthread_component; extern OpenThreadComponent *global_openthread_component;
class OpenThreadLockGuard {
public:
static std::optional<OpenThreadLockGuard> TryAcquire(int delay);
static std::optional<OpenThreadLockGuard> Acquire();
~OpenThreadLockGuard();
// Returns the global openthread instance guarded by this lock
otInstance *get_instance();
private:
// Use a private constructor in order to force thehandling
// of acquisition failure
OpenThreadLockGuard() {}
};
} // namespace openthread } // namespace openthread
} // namespace esphome } // namespace esphome

View file

@ -0,0 +1,172 @@
// #ifdef USE_ESP_IDF
#include "openthread_esp.h"
#include "openthread.h"
#include <openthread/logging.h>
#include "esp_openthread.h"
#include "esp_openthread_lock.h"
#include "esp_log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esp_task_wdt.h"
#include "esp_openthread_cli.h"
#include "esp_openthread_netif_glue.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_vfs_eventfd.h"
#include "esp_netif.h"
#include "esp_netif_types.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define TAG "openthread"
namespace esphome {
namespace openthread {
void OpenThreadComponent::setup() {
ESP_LOGI("openthread", "Setting up OpenThread...");
// Used eventfds:
// * netif
// * ot task queue
// * radio driver
// TODO: Does anything else in esphome set this up?
esp_vfs_eventfd_config_t eventfd_config = {
.max_fds = 3,
};
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
xTaskCreate(
[](void *arg) {
static_cast<OpenThreadComponent *>(arg)->ot_main();
vTaskDelete(NULL);
},
"ot_main", 10240, this, 5, nullptr);
xTaskCreate(
[](void *arg) {
static_cast<OpenThreadComponent *>(arg)->srp_setup();
vTaskDelete(NULL);
},
"ot_srp_setup", 10240, this, 5, nullptr);
ESP_LOGI("openthread", "OpenThread started");
}
static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t *config) {
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
esp_netif_t *netif = esp_netif_new(&cfg);
assert(netif != NULL);
ESP_ERROR_CHECK(esp_netif_attach(netif, esp_openthread_netif_glue_init(config)));
return netif;
}
void OpenThreadComponent::ot_main() {
esp_openthread_platform_config_t config = {
.radio_config =
{
.radio_mode = RADIO_MODE_NATIVE,
.radio_uart_config = {},
},
.host_config =
{
// There is a conflict between esphome's logger which also
// claims the usb serial jtag device.
// .host_connection_mode = HOST_CONNECTION_MODE_CLI_USB,
// .host_usb_config = USB_SERIAL_JTAG_DRIVER_CONFIG_DEFAULT(),
},
.port_config =
{
.storage_partition_name = "nvs",
.netif_queue_size = 10,
.task_queue_size = 10,
},
};
// Initialize the OpenThread stack
ESP_ERROR_CHECK(esp_openthread_init(&config));
#if CONFIG_OPENTHREAD_STATE_INDICATOR_ENABLE
ESP_ERROR_CHECK(esp_openthread_state_indicator_init(esp_openthread_get_instance()));
#endif
#if CONFIG_OPENTHREAD_LOG_LEVEL_DYNAMIC
// The OpenThread log level directly matches ESP log level
(void) otLoggingSetLevel(CONFIG_LOG_DEFAULT_LEVEL);
#endif
// Initialize the OpenThread cli
#if CONFIG_OPENTHREAD_CLI
esp_openthread_cli_init();
#endif
esp_netif_t *openthread_netif;
// Initialize the esp_netif bindings
openthread_netif = init_openthread_netif(&config);
esp_netif_set_default_netif(openthread_netif);
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
esp_cli_custom_command_init();
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
// Run the main loop
#if CONFIG_OPENTHREAD_CLI
esp_openthread_cli_create_task();
#endif
ESP_LOGI(TAG, "Activating dataset...");
otOperationalDatasetTlvs dataset;
otError error = otDatasetGetActiveTlvs(esp_openthread_get_instance(), &dataset);
ESP_ERROR_CHECK(esp_openthread_auto_start((error == OT_ERROR_NONE) ? &dataset : NULL));
esp_openthread_launch_mainloop();
// Clean up
esp_openthread_netif_glue_deinit();
esp_netif_destroy(openthread_netif);
esp_vfs_eventfd_unregister();
}
// TODO: This gets used by mqtt in order to register the device's IP. Likely it doesn't
// make sense to return thread-local addresses, since they can't be reached from outside the thread network.
// It could make more sense to return the off-mesh-routable address instead.
network::IPAddresses OpenThreadComponent::get_ip_addresses() {
network::IPAddresses addresses;
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
uint8_t count = 0;
esp_netif_t *netif = esp_netif_get_default_netif();
count = esp_netif_get_all_ip6(netif, if_ip6s);
assert(count <= CONFIG_LWIP_IPV6_NUM_ADDRESSES);
for (int i = 0; i < count; i++) {
addresses[i + 1] = network::IPAddress(&if_ip6s[i]);
}
return addresses;
}
std::optional<OpenThreadLockGuard> OpenThreadLockGuard::TryAcquire(int delay) {
if (esp_openthread_lock_acquire(delay)) {
return OpenThreadLockGuard();
}
return {};
}
std::optional<OpenThreadLockGuard> OpenThreadLockGuard::Acquire() {
while (!esp_openthread_lock_acquire(100)) {
esp_task_wdt_reset();
}
return OpenThreadLockGuard();
}
otInstance *OpenThreadLockGuard::get_instance() { return esp_openthread_get_instance(); }
OpenThreadLockGuard::~OpenThreadLockGuard() { esp_openthread_lock_release(); }
} // namespace openthread
} // namespace esphome
// #endif

View file

@ -1,46 +1 @@
#pragma once #pragma once
#include <optional>
#include "esp_openthread.h"
#include "esp_openthread_lock.h"
#include "esp_log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
#include "esp_task_wdt.h"
#include "esp_openthread_cli.h"
#include "esp_openthread_netif_glue.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "esp_vfs_eventfd.h"
#include "esp_netif.h"
#include "esp_netif_types.h"
#include "esp_err.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
namespace esphome {
namespace openthread {
class EspOpenThreadLockGuard {
public:
static std::optional<EspOpenThreadLockGuard> TryAcquire(TickType_t delay) {
if (esp_openthread_lock_acquire(delay)) {
return EspOpenThreadLockGuard();
}
return {};
}
static std::optional<EspOpenThreadLockGuard> Acquire() {
while (!esp_openthread_lock_acquire(100)) {
esp_task_wdt_reset();
}
return EspOpenThreadLockGuard();
}
~EspOpenThreadLockGuard() { esp_openthread_lock_release(); }
private:
// Use a private constructor in order to force thehandling
// of acquisition failure
EspOpenThreadLockGuard() {}
};
}
}