diff --git a/components/openthread/Kconfig b/components/openthread/Kconfig index 018df2fe3c..da042ceac7 100644 --- a/components/openthread/Kconfig +++ b/components/openthread/Kconfig @@ -58,6 +58,13 @@ menu "OpenThread" Select this option to enable Joiner in OpenThread. This allows a device to join the Thread network with a pre-shared key using the Thread commissioning protocol. + config OPENTHREAD_BORDER_ROUTER + bool "Enable Border Router" + depends on OPENTHREAD_ENABLED + default n + help + Select this option to enable border router features in OpenThread. + config OPENTHREAD_PARTITION_NAME string "The partition for OpenThread to store its network data" depends on OPENTHREAD_ENABLED @@ -72,4 +79,11 @@ menu "OpenThread" help The size of the packet queue for OpenThread lwIP network interface. + config OPENTHREAD_TASK_QUEUE_SIZE + int "The size of the OpenThread task queue" + depends on OPENTHREAD_ENABLED + default 10 + help + The size of the OpenThread task queue. + endmenu diff --git a/components/openthread/include/esp_openthread_border_router.h b/components/openthread/include/esp_openthread_border_router.h new file mode 100644 index 0000000000..aef20df628 --- /dev/null +++ b/components/openthread/include/esp_openthread_border_router.h @@ -0,0 +1,51 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO 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 "esp_netif.h" +#include "esp_netif_types.h" +#include "esp_openthread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initializes the border router features of OpenThread. + * + * @note Calling this function will make the device behave as an OpenThread + * border router. Kconfig option CONFIG_OPENTHREAD_BORDER_ROUTER is required. + * + * @param[in] backbone_netif The backbone network interface (WiFi or ethernet) + * + * @return + * - ESP_OK on success + * - ESP_ERR_NOT_SUPPORTED if feature not supported + * + */ +esp_err_t esp_openthread_border_router_init(esp_netif_t *backbone_netif); + +/** + * @brief Gets the backbone interface of OpenThread border router. + * + * @return + * The backbone interface or NULL if border router not initialized. + * + */ +esp_netif_t *esp_openthread_get_backbone_netif(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/openthread/include/esp_openthread_netif_glue.h b/components/openthread/include/esp_openthread_netif_glue.h index 319362a172..61bd1ee1a7 100644 --- a/components/openthread/include/esp_openthread_netif_glue.h +++ b/components/openthread/include/esp_openthread_netif_glue.h @@ -15,6 +15,7 @@ #pragma once #include "esp_err.h" +#include "esp_netif.h" #include "esp_openthread_types.h" #include "openthread/instance.h" @@ -38,6 +39,15 @@ void *esp_openthread_netif_glue_init(void); */ void esp_openthread_netif_glue_deinit(void); +/** + * @brief This function acquires the OpenThread netif. + * + * @return + * The OpenThread netif or NULL if not initialzied. + * + */ +esp_netif_t *esp_openthread_get_netif(void); + #ifdef __cplusplus } #endif diff --git a/components/openthread/include/openthread-core-esp32x-config.h b/components/openthread/include/openthread-core-esp32x-config.h index ff17008bb2..8d00379d16 100644 --- a/components/openthread/include/openthread-core-esp32x-config.h +++ b/components/openthread/include/openthread-core-esp32x-config.h @@ -72,23 +72,23 @@ #endif #endif -#define OPENTHREAD_CONFIG_LOG_API 1 -#define OPENTHREAD_CONFIG_LOG_ARP 1 -#define OPENTHREAD_CONFIG_LOG_BBR 1 -#define OPENTHREAD_CONFIG_LOG_CLI 1 -#define OPENTHREAD_CONFIG_LOG_COAP 1 -#define OPENTHREAD_CONFIG_LOG_DUA 1 -#define OPENTHREAD_CONFIG_LOG_ICMP 1 -#define OPENTHREAD_CONFIG_LOG_IP6 1 -#define OPENTHREAD_CONFIG_LOG_MAC 1 -#define OPENTHREAD_CONFIG_LOG_MEM 1 -#define OPENTHREAD_CONFIG_LOG_MESHCOP 1 -#define OPENTHREAD_CONFIG_LOG_MLE 1 -#define OPENTHREAD_CONFIG_LOG_MLR 1 -#define OPENTHREAD_CONFIG_LOG_NETDATA 1 -#define OPENTHREAD_CONFIG_LOG_NETDIAG 1 -#define OPENTHREAD_CONFIG_LOG_PKT_DUMP 1 -#define OPENTHREAD_CONFIG_LOG_PLATFORM 1 +#define OPENTHREAD_CONFIG_LOG_API 1 +#define OPENTHREAD_CONFIG_LOG_ARP 1 +#define OPENTHREAD_CONFIG_LOG_BBR 1 +#define OPENTHREAD_CONFIG_LOG_CLI 1 +#define OPENTHREAD_CONFIG_LOG_COAP 1 +#define OPENTHREAD_CONFIG_LOG_DUA 1 +#define OPENTHREAD_CONFIG_LOG_ICMP 1 +#define OPENTHREAD_CONFIG_LOG_IP6 1 +#define OPENTHREAD_CONFIG_LOG_MAC 1 +#define OPENTHREAD_CONFIG_LOG_MEM 1 +#define OPENTHREAD_CONFIG_LOG_MESHCOP 1 +#define OPENTHREAD_CONFIG_LOG_MLE 1 +#define OPENTHREAD_CONFIG_LOG_MLR 1 +#define OPENTHREAD_CONFIG_LOG_NETDATA 1 +#define OPENTHREAD_CONFIG_LOG_NETDIAG 1 +#define OPENTHREAD_CONFIG_LOG_PKT_DUMP 1 +#define OPENTHREAD_CONFIG_LOG_PLATFORM 1 /** * @def OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS @@ -115,6 +115,40 @@ #define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 1 #endif +/** + * @def OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE + * + * Define to 1 to enable platform UDP support. + * + */ +#ifndef OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE +#define OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE 1 +#endif + +/** + * @def OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE + * + * Define to 1 to enable platform NETIF support. + * + */ +#ifndef OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE +#define OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE 1 +#endif + +#if CONFIG_OPENTHREAD_BORDER_ROUTER + +/** + * @def OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE + * + * Define to 1 to enable Border Agent support. + * + */ +#ifndef OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE +#define OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE 1 +#endif + +#endif // CONFIG_OPENTHREAD_BORDER_ROUTER + /** * @def OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE * diff --git a/components/openthread/port/esp_openthread_border_router.c b/components/openthread/port/esp_openthread_border_router.c new file mode 100644 index 0000000000..4f219efae4 --- /dev/null +++ b/components/openthread/port/esp_openthread_border_router.c @@ -0,0 +1,34 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO 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 "esp_openthread_border_router.h" +#include "esp_err.h" + +static esp_netif_t *s_backbone_netif = NULL; + +esp_err_t esp_openthread_border_router_init(esp_netif_t *backbone_if) +{ +#if CONFIG_OPENTHREAD_BORDER_ROUTER + s_backbone_netif = backbone_if; + + return ESP_OK; +#else + return ESP_ERR_NOT_SUPPORTED; +#endif +} + +esp_netif_t *esp_openthread_get_backbone_netif(void) +{ + return s_backbone_netif; +} diff --git a/components/openthread/port/esp_openthread_netif_glue.c b/components/openthread/port/esp_openthread_netif_glue.c index f9d6bed7dd..1fe5bea4a3 100644 --- a/components/openthread/port/esp_openthread_netif_glue.c +++ b/components/openthread/port/esp_openthread_netif_glue.c @@ -51,6 +51,7 @@ static esp_openthread_netif_glue_t s_openthread_netif_glue = { ESP_EVENT_DEFINE_BASE(OPENTHREAD_EVENT); static QueueHandle_t s_packet_queue; +static esp_netif_t *s_openthread_netif; #define NETIF_OUTPUT_SIGNAL 1 @@ -265,6 +266,7 @@ static esp_err_t openthread_netif_post_attach(esp_netif_t *esp_netif, void *args otLogInfoPlat("OpenThread attached to netif"); esp_err_t error = register_openthread_event_handlers(esp_netif); + s_openthread_netif = esp_netif; if (error == ESP_OK) { error = esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_START, NULL, 0, 0); } @@ -323,6 +325,7 @@ void esp_openthread_netif_glue_deinit(void) if (esp_event_post(OPENTHREAD_EVENT, OPENTHREAD_EVENT_STOP, NULL, 0, 0) != ESP_OK) { otLogCritPlat("Failed to stop OpenThread netif"); } + s_openthread_netif = NULL; unregister_openthread_event_handlers(); } @@ -343,3 +346,8 @@ esp_err_t esp_openthread_netif_glue_process(otInstance *instance, const esp_open } return ESP_OK; } + +esp_netif_t *esp_openthread_get_netif(void) +{ + return s_openthread_netif; +} diff --git a/components/openthread/port/esp_openthread_platform.cpp b/components/openthread/port/esp_openthread_platform.cpp index a5e736f8e5..49838ed7d4 100644 --- a/components/openthread/port/esp_openthread_platform.cpp +++ b/components/openthread/port/esp_openthread_platform.cpp @@ -22,6 +22,7 @@ #include "esp_openthread_netif_glue.h" #include "esp_openthread_netif_glue_priv.h" #include "esp_openthread_radio_uart.h" +#include "esp_openthread_task_queue.h" #include "esp_openthread_types.h" #include "esp_openthread_uart.h" #include "common/code_utils.hpp" @@ -53,6 +54,7 @@ esp_err_t esp_openthread_platform_init(const esp_openthread_platform_config_t *c if (config->host_config.host_connection_mode == HOST_CONNECTION_MODE_UART) { ESP_GOTO_ON_ERROR(esp_openthread_uart_init(config), exit, OT_PLAT_LOG_TAG, "esp_openthread_uart_init failed"); } + ESP_GOTO_ON_ERROR(esp_openthread_task_queue_init(), exit, OT_PLAT_LOG_TAG, "esp_openthread_task_queue_init failed"); ESP_GOTO_ON_ERROR(esp_openthread_radio_init(config), exit, OT_PLAT_LOG_TAG, "esp_openthread_radio_init failed"); exit: @@ -73,6 +75,7 @@ esp_err_t esp_openthread_platform_deinit(void) ESP_RETURN_ON_FALSE(s_openthread_platform_initialized, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, "OpenThread platform not initialized"); + esp_openthread_task_queue_deinit(); esp_openthread_radio_deinit(); if (s_platform_config.host_config.host_connection_mode == HOST_CONNECTION_MODE_UART) { esp_openthread_uart_deinit(); @@ -90,6 +93,7 @@ void esp_openthread_platform_update(esp_openthread_mainloop_context_t *mainloop) } esp_openthread_radio_update(mainloop); esp_openthread_netif_glue_update(mainloop); + esp_openthread_task_queue_update(mainloop); } esp_err_t esp_openthread_platform_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop) @@ -99,5 +103,6 @@ esp_err_t esp_openthread_platform_process(otInstance *instance, const esp_openth } esp_openthread_radio_process(instance, mainloop); esp_openthread_alarm_process(instance); + esp_openthread_task_queue_process(instance, mainloop); return esp_openthread_netif_glue_process(instance, mainloop); } diff --git a/components/openthread/port/esp_openthread_task_queue.c b/components/openthread/port/esp_openthread_task_queue.c new file mode 100644 index 0000000000..344ce1ab7c --- /dev/null +++ b/components/openthread/port/esp_openthread_task_queue.c @@ -0,0 +1,102 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO 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 "esp_check.h" +#include "esp_err.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_task_queue.h" +#include "esp_vfs.h" +#include "esp_vfs_eventfd.h" +#include "common/logging.hpp" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" + +static QueueHandle_t s_task_queue = NULL; +static int s_task_queue_event_fd = -1; + +typedef struct { + esp_openthread_task_t task; + void *arg; +} task_storage_t; + +esp_err_t esp_openthread_task_queue_init(void) +{ + s_task_queue_event_fd = eventfd(0, EFD_SUPPORT_ISR); + ESP_RETURN_ON_FALSE(s_task_queue_event_fd >= 0, ESP_FAIL, OT_PLAT_LOG_TAG, + "Failed to create OpenThread task queue event fd"); + s_task_queue = xQueueCreate(CONFIG_OPENTHREAD_TASK_QUEUE_SIZE, sizeof(task_storage_t)); + ESP_RETURN_ON_FALSE(s_task_queue != NULL, ESP_ERR_NO_MEM, OT_PLAT_LOG_TAG, + "Failed to create OpenThread task queue"); + return ESP_OK; +} + +esp_err_t esp_openthread_task_queue_post(esp_openthread_task_t task, void *arg) +{ + task_storage_t task_storage = { + .task = task, + .arg = arg, + }; + uint64_t val = 1; + ssize_t ret; + + ESP_RETURN_ON_FALSE(xQueueSend(s_task_queue, &task_storage, portMAX_DELAY), ESP_FAIL, OT_PLAT_LOG_TAG, + "Failed to post task to OpenThread task queue"); + ret = write(s_task_queue_event_fd, &val, sizeof(val)); + assert(ret == sizeof(val)); + + return ESP_OK; +} + +void esp_openthread_task_queue_update(esp_openthread_mainloop_context_t *mainloop) +{ + if (s_task_queue_event_fd >= 0) { + FD_SET(s_task_queue_event_fd, &mainloop->read_fds); + if (s_task_queue_event_fd > mainloop->max_fd) { + mainloop->max_fd = s_task_queue_event_fd; + } + } +} + +esp_err_t esp_openthread_task_queue_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop) +{ + task_storage_t task_storage; + + if (FD_ISSET(s_task_queue_event_fd, &mainloop->read_fds)) { + uint64_t val; + ssize_t ret = read(s_task_queue_event_fd, &val, sizeof(val)); + assert(ret == sizeof(val)); + } + + ESP_RETURN_ON_FALSE(s_task_queue != NULL, ESP_ERR_INVALID_STATE, OT_PLAT_LOG_TAG, + "OpenThread task queue not initialized"); + while (xQueueReceive(s_task_queue, &task_storage, 0) == pdTRUE) { + task_storage.task(task_storage.arg); + } + + return ESP_OK; +} + +esp_err_t esp_openthread_task_queue_deinit(void) +{ + if (s_task_queue) { + vQueueDelete(s_task_queue); + s_task_queue = NULL; + } + if (s_task_queue_event_fd >= 0) { + close(s_task_queue_event_fd); + s_task_queue_event_fd = -1; + } + + return ESP_OK; +} diff --git a/components/openthread/port/esp_openthread_udp.c b/components/openthread/port/esp_openthread_udp.c new file mode 100644 index 0000000000..55f347ecbd --- /dev/null +++ b/components/openthread/port/esp_openthread_udp.c @@ -0,0 +1,360 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO 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 + +#include "esp_check.h" +#include "esp_err.h" +#include "esp_netif.h" +#include "esp_openthread.h" +#include "esp_openthread_border_router.h" +#include "esp_openthread_common_macro.h" +#include "esp_openthread_lock.h" +#include "esp_openthread_netif_glue.h" +#include "esp_openthread_task_queue.h" +#include "common/code_utils.hpp" +#include "common/logging.hpp" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "lwip/ip6.h" +#include "lwip/ip6_addr.h" +#include "lwip/ip_addr.h" +#include "lwip/pbuf.h" +#include "lwip/prot/ip4.h" +#include "lwip/tcpip.h" +#include "lwip/udp.h" +#include "openthread/error.h" +#include "openthread/platform/udp.h" + +typedef struct { + otUdpSocket *socket; + struct pbuf *recv_buf; + ip_addr_t addr; + uint16_t port; + uint8_t hop_limit; + bool is_host_interface; +} udp_recv_task_t; + +typedef struct { + TaskHandle_t source_task; + otUdpSocket *socket; + struct udp_pcb *pcb_ret; +} udp_new_task_t; + +typedef struct { + TaskHandle_t source_task; + struct udp_pcb *pcb; + ip_addr_t addr; + uint16_t port; + err_t ret; +} udp_bind_connect_task_t; + +typedef struct { + TaskHandle_t source_task; + struct udp_pcb *pcb; + uint8_t netif_index; +} udp_bind_netif_task_t; + +typedef struct { + struct udp_pcb *pcb; + otMessage *message; + ip_addr_t addr; + uint16_t port; + bool multicast_loop; + uint8_t hop_limit; + uint8_t netif_index; +} udp_send_task_t; + +static void wait_for_task_notification(void) +{ + esp_openthread_lock_release(); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + esp_openthread_lock_acquire(portMAX_DELAY); +} + +static ip_addr_t map_openthread_addr_to_lwip_addr(const otIp6Address *address) +{ + ip_addr_t addr; + + memcpy(ip_2_ip6(&addr)->addr, address->mFields.m8, sizeof(ip_2_ip6(&addr)->addr)); + if (ip6_addr_isipv4mappedipv6(ip_2_ip6(&addr))) { + unmap_ipv4_mapped_ipv6(ip_2_ip4(&addr), ip_2_ip6(&addr)); + addr.type = IPADDR_TYPE_V4; + } else { + addr.type = IPADDR_TYPE_V6; +#if LWIP_IPV6_SCOPES + addr.u_addr.ip6.zone = IP6_NO_ZONE; +#endif + } + return addr; +} + +static void udp_recv_task(void *ctx) +{ + udp_recv_task_t *task = (udp_recv_task_t *)ctx; + + otMessageInfo message_info; + otMessage *message = NULL; + otMessageSettings msg_settings = {.mLinkSecurityEnabled = false, .mPriority = OT_MESSAGE_PRIORITY_NORMAL}; + struct pbuf *recv_buf = task->recv_buf; + uint8_t *data_buf = (uint8_t *)recv_buf->payload; + uint8_t *data_buf_to_free = NULL; + + message_info.mSockPort = 0; + memset(&message_info.mSockAddr, 0, sizeof(message_info.mSockAddr)); + message_info.mHopLimit = task->hop_limit; + message_info.mPeerPort = task->port; + if (task->addr.type == IPADDR_TYPE_V4) { + ip4_2_ipv4_mapped_ipv6(ip_2_ip6(&task->addr), ip_2_ip4(&task->addr)); + } + memcpy(&message_info.mPeerAddr, ip_2_ip6(&task->addr)->addr, sizeof(message_info.mPeerAddr)); + + if (recv_buf->next != NULL) { + data_buf = (uint8_t *)malloc(recv_buf->tot_len); + if (data_buf != NULL) { + data_buf_to_free = data_buf; + pbuf_copy_partial(recv_buf, data_buf, recv_buf->tot_len, 0); + } + } + VerifyOrExit(data_buf != NULL, + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to allocate data buf when receiving OpenThread plat UDP")); + message = otUdpNewMessage(esp_openthread_get_instance(), &msg_settings); + VerifyOrExit(message != NULL, + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to allocate OpenThread message when receiving OpenThread plat UDP")); + VerifyOrExit(otMessageAppend(message, data_buf, recv_buf->tot_len) == OT_ERROR_NONE, + ESP_LOGE(OT_PLAT_LOG_TAG, "Failed to copy OpenThread message when receiving OpenThread plat UDP")); + task->socket->mHandler(task->socket->mContext, message, &message_info); + otMessageFree(message); + +exit: + free(task); + if (data_buf_to_free) { + free(data_buf_to_free); + } + pbuf_free(recv_buf); + return; +} + +static void handle_udp_recv(void *ctx, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr, uint16_t port) +{ + udp_recv_task_t *task = (udp_recv_task_t *)malloc(sizeof(udp_recv_task_t)); + const struct ip6_hdr *ip6_hdr = ip6_current_header(); + const struct ip_hdr *ip4_hdr = ip4_current_header(); + struct netif *source_netif = ip_current_netif(); + + if (task == NULL) { + otLogCritPlat("Failed to allocate recv task when receiving OpenThread plat UDP"); + } + task->socket = (otUdpSocket *)ctx; + task->recv_buf = p; + task->addr = *addr; + task->port = port; + task->hop_limit = (addr->type == IPADDR_TYPE_V6) ? IP6H_HOPLIM(ip6_hdr) : IPH_TTL(ip4_hdr); + task->is_host_interface = + (netif_get_index(source_netif) == esp_netif_get_netif_impl_index(esp_openthread_get_backbone_netif())); + + if (esp_openthread_task_queue_post(udp_recv_task, task) != ESP_OK) { + free(task); + } +} + +static void udp_new_task(void *ctx) +{ + udp_new_task_t *task = (udp_new_task_t *)ctx; + + task->pcb_ret = udp_new(); + udp_recv(task->pcb_ret, handle_udp_recv, task->socket); + xTaskNotifyGive(task->source_task); +} + +otError otPlatUdpSocket(otUdpSocket *udp_socket) +{ + otError error = OT_ERROR_NONE; + + udp_new_task_t task = {.source_task = xTaskGetCurrentTaskHandle(), .socket = udp_socket}; + tcpip_callback(udp_new_task, &task); + wait_for_task_notification(); + VerifyOrExit(task.pcb_ret != NULL, error = OT_ERROR_FAILED); + udp_socket->mHandle = task.pcb_ret; + +exit: + return error; +} + +static void udp_close_task(void *ctx) +{ + struct udp_pcb *pcb = (struct udp_pcb *)ctx; + + udp_remove(pcb); +} + +otError otPlatUdpClose(otUdpSocket *udp_socket) +{ + struct udp_pcb *pcb = (struct udp_pcb *)udp_socket->mHandle; + + if (pcb) { + tcpip_callback(udp_close_task, pcb); + } + + return OT_ERROR_NONE; +} + +static void udp_bind_task(void *ctx) +{ + udp_bind_connect_task_t *task = (udp_bind_connect_task_t *)ctx; + + task->ret = udp_bind(task->pcb, &task->addr, task->port); + xTaskNotifyGive(task->source_task); +} + +otError otPlatUdpBind(otUdpSocket *udp_socket) +{ + udp_bind_connect_task_t task = { + .source_task = xTaskGetCurrentTaskHandle(), + .pcb = (struct udp_pcb *)udp_socket->mHandle, + .port = udp_socket->mSockName.mPort, + }; + ESP_LOGI(OT_PLAT_LOG_TAG, "Platform UDP bound to port %d", udp_socket->mSockName.mPort); + + task.addr.type = IPADDR_TYPE_ANY; + memcpy(ip_2_ip6(&task.addr)->addr, udp_socket->mSockName.mAddress.mFields.m8, sizeof(ip_2_ip6(&task.addr)->addr)); + tcpip_callback(udp_bind_task, &task); + wait_for_task_notification(); + + return task.ret == ERR_OK ? OT_ERROR_NONE : OT_ERROR_FAILED; +} + +static void udp_bind_netif_task(void *ctx) +{ + udp_bind_netif_task_t *task = (udp_bind_netif_task_t *)ctx; + + task->netif_index = task->netif_index; + xTaskNotifyGive(task->source_task); +} + +static uint8_t get_netif_index(otNetifIdentifier netif_identifier) +{ + switch (netif_identifier) { + case OT_NETIF_UNSPECIFIED: + return NETIF_NO_INDEX; + case OT_NETIF_THREAD: + return esp_netif_get_netif_impl_index(esp_openthread_get_netif()); + case OT_NETIF_BACKBONE: + return esp_netif_get_netif_impl_index(esp_openthread_get_backbone_netif()); + default: + return NETIF_NO_INDEX; + } +} + +otError otPlatUdpBindToNetif(otUdpSocket *udp_socket, otNetifIdentifier netif_identifier) +{ + udp_bind_netif_task_t task = { + .source_task = xTaskGetCurrentTaskHandle(), + .pcb = (struct udp_pcb *)udp_socket->mHandle, + .netif_index = get_netif_index(netif_identifier), + }; + + tcpip_callback(udp_bind_netif_task, &task); + wait_for_task_notification(); + + return OT_ERROR_NONE; +} + +static void udp_connect_task(void *ctx) +{ + udp_bind_connect_task_t *task = (udp_bind_connect_task_t *)ctx; + + task->ret = udp_connect(task->pcb, &task->addr, task->port); + xTaskNotifyGive(task->source_task); +} + +otError otPlatUdpConnect(otUdpSocket *udp_socket) +{ + udp_bind_connect_task_t task = { + .source_task = xTaskGetCurrentTaskHandle(), + .pcb = (struct udp_pcb *)udp_socket->mHandle, + .port = udp_socket->mPeerName.mPort, + }; + + task.addr = map_openthread_addr_to_lwip_addr(&udp_socket->mPeerName.mAddress); + tcpip_callback(udp_connect_task, &task); + wait_for_task_notification(); + + return task.ret == ERR_OK ? OT_ERROR_NONE : OT_ERROR_FAILED; +} + +static bool is_link_local(const otIp6Address *address) +{ + return address->mFields.m8[0] == 0xfe && address->mFields.m8[1] == 0x80; +} + +static bool is_multicast(const otIp6Address *address) +{ + return address->mFields.m8[0] == 0xff; +} + +static void udp_send_task(void *ctx) +{ + udp_send_task_t *task = (udp_send_task_t *)ctx; + struct pbuf *send_buf = NULL; + uint16_t len = otMessageGetLength(task->message); + + task->pcb->ttl = task->hop_limit; + task->pcb->netif_idx = task->netif_index; +#if LWIP_IPV6_SCOPES + if (task->addr.type == IPADDR_TYPE_V6) { + ip_2_ip6(&task->addr)->zone = task->netif_index; + } +#endif + task->pcb->flags = (task->pcb->flags & (~UDP_FLAGS_MULTICAST_LOOP)); + if (task->multicast_loop) { + task->pcb->flags |= UDP_FLAGS_MULTICAST_LOOP; + } + send_buf = pbuf_alloc(PBUF_TRANSPORT, len, PBUF_RAM); + otMessageRead(task->message, 0, send_buf->payload, len); + VerifyOrExit(send_buf != NULL); + udp_sendto(task->pcb, send_buf, &task->addr, task->port); + +exit: + if (send_buf) { + pbuf_free(send_buf); + } + esp_openthread_lock_acquire(portMAX_DELAY); + otMessageFree(task->message); + esp_openthread_lock_release(); + free(task); +} + +otError otPlatUdpSend(otUdpSocket *udp_socket, otMessage *message, const otMessageInfo *message_info) +{ + udp_send_task_t *task = (udp_send_task_t *)malloc(sizeof(udp_send_task_t)); + otError error = OT_ERROR_NONE; + + VerifyOrExit(task != NULL, error = OT_ERROR_NO_BUFS); + task->pcb = (struct udp_pcb *)udp_socket->mHandle; + task->message = message; + task->port = message_info->mPeerPort; + task->multicast_loop = message_info->mMulticastLoop; + task->hop_limit = message_info->mHopLimit; + task->netif_index = NETIF_NO_INDEX; + task->addr = map_openthread_addr_to_lwip_addr(&message_info->mPeerAddr); + + if (is_link_local(&message_info->mPeerAddr) || is_multicast(&message_info->mPeerAddr)) { + task->netif_index = get_netif_index(message_info->mIsHostInterface ? OT_NETIF_BACKBONE : OT_NETIF_THREAD); + } + tcpip_callback(udp_send_task, task); + +exit: + return error; +} diff --git a/components/openthread/private_include/esp_openthread_task_queue.h b/components/openthread/private_include/esp_openthread_task_queue.h new file mode 100644 index 0000000000..662a42710d --- /dev/null +++ b/components/openthread/private_include/esp_openthread_task_queue.h @@ -0,0 +1,87 @@ +// Copyright 2021 Espressif Systems (Shanghai) CO 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 "esp_err.h" +#include "esp_openthread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +* @brief OpenThread task declaration +* +*/ +typedef void (*esp_openthread_task_t)(void *); + +/** +* @brief This function allocs and initializes the OpenThread task queue. +* +* @return +* - ESP_OK on success +* - ESP_ERR_NO_MEM on queue allocation failure +* - ESP_FAIL on other failures +* +*/ +esp_err_t esp_openthread_task_queue_init(void); + +/** +* @brief This function posts a task to the OpenThread task queue. +* +* @param[in] task The task to execute. +* @param[in] arg The context argument to be passed to the task. +* +* @return +* - ESP_OK +* - ESP_FAIL +* +*/ +esp_err_t esp_openthread_task_queue_post(esp_openthread_task_t task, void *arg); + +/** +* @brief This function updates the task queue inner fd to the main loop. +* +* @param[inout] mainloop The main loop context. +* +*/ +void esp_openthread_task_queue_update(esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief This function drives the execution of the task queue. + * + * @param[in] instance The OpenThread instance. + * @param[in] mainloop The main loop context. + * + * @return + * - ESP_OK + * - ESP_FAIL + * + */ +esp_err_t esp_openthread_task_queue_process(otInstance *instance, const esp_openthread_mainloop_context_t *mainloop); + +/** + * @brief This function deinitializes the task queue. + * + * @return + * - ESP_OK + * - ESP_FAIL + * + */ +esp_err_t esp_openthread_task_queue_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/openthread/ot_br/CMakeLists.txt b/examples/openthread/ot_br/CMakeLists.txt new file mode 100644 index 0000000000..b9b1ee9995 --- /dev/null +++ b/examples/openthread/ot_br/CMakeLists.txt @@ -0,0 +1,10 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +# (Not part of the boilerplate) +# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection. +set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(otbr_esp) diff --git a/examples/openthread/ot_br/Makefile b/examples/openthread/ot_br/Makefile new file mode 100644 index 0000000000..4cbaed78c2 --- /dev/null +++ b/examples/openthread/ot_br/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := otbr_esp + +EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common + +include $(IDF_PATH)/make/project.mk diff --git a/examples/openthread/ot_br/README.md b/examples/openthread/ot_br/README.md new file mode 100644 index 0000000000..a85d8438f3 --- /dev/null +++ b/examples/openthread/ot_br/README.md @@ -0,0 +1,148 @@ +# OpenThread command line example + +## Overview + +This example demonstrates an [OpenThread border router](https://openthread.io/guides/border-router). + +## How to use example + +### Hardware connection + +To run this example, it's used to use an DevKit C board and connect PIN4 and PIN5 to the UART TX and RX port of another 15.4 capable radio co-processor ([RCP](https://openthread.io/platforms/co-processor?hl=en)) + +### Configure the project + +``` +idf.py menuconfig +``` + +You need to configure the `CONFIG_EXAMPLE_WIFI_SSID` and `CONFIG_EXAMPLE_WIFI_PASSWORD` with your access point's ssid and psk. + +### Build, Flash, and Run + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT build flash monitor +``` + +## Example Output + +```bash +I (2729) esp_netif_handlers: example_connect: sta ip: 192.168.1.100, mask: 255.255.255.0, gw: 192.168.1.1 +I (2729) example_connect: Got IPv4 event: Interface "example_connect: sta" address: 192.168.1.100 +I (3729) example_connect: Got IPv6 event: Interface "example_connect: sta" address: fe80:0000:0000:0000:266f:28ff:fe80:2920, type: ESP_IP6_ADDR_IS_LINK_LOCAL +I (3729) example_connect: Connected to example_connect: sta +I (3739) example_connect: - IPv4 address: 192.168.1.100 +I (3739) example_connect: - IPv6 address: fe80:0000:0000:0000:266f:28ff:fe80:2920, type: ESP_IP6_ADDR_IS_LINK_LOCAL + +...... + + +I(8139) OPENTHREAD:[INFO]-MLE-----: AttachState ParentReqReeds -> Idle +I(8139) OPENTHREAD:[NOTE]-MLE-----: Allocate router id 50 +I(8139) OPENTHREAD:[NOTE]-MLE-----: RLOC16 fffe -> c800 +I(8159) OPENTHREAD:[NOTE]-MLE-----: Role Detached -> Leader +``` + +The device will automatically connect to the configured WiFi and Thread network and act as the border router. + +## Using the border agent feature + +You need to ot-commissioner on the host machine and another Thread end device running OpenThread cli. + +You can find the guide to build and run ot-commissioner [here](https://openthread.io/guides/commissioner/build). + +Make sure to configure the same PSKc as the one in sdkconfig in ot-commisioner's config file `non-ccm-config.json` + +### Connect the commissioner to the border router + +Note that the target address `192.168.1.100` shall match the actual WiFi IP address of the device. + +``` bash +$ commissioner-cli /usr/local/etc/commissioner/non-ccm-config.json +> start 192.168.1.100 49154 +[done] +> active +true +[done] +``` + +You can also verify the commissioner connection from the border router's log: + +``` +I(59709) OPENTHREAD:[INFO]-MESH-CP-: DTLS started +I(65469) OPENTHREAD:[INFO]-MESH-CP-: Commissioner connected +I(65479) OPENTHREAD:[INFO]-MESH-CP-: Forwarded request to leader on c/lp +I(65489) OPENTHREAD:[INFO]-MESH-CP-: received petition +I(65489) OPENTHREAD:[INFO]-MESH-CP-: sent petition response +I(65489) OPENTHREAD:[INFO]-MESH-CP-: commissioner accepted: session ID=3077, ALOC=fd04:b642:9ba9:fcdc:0:ff:fe00:fc35 +I(65499) OPENTHREAD:[INFO]-MESH-CP-: Sent to commissioner +I(65509) OPENTHREAD:[INFO]-CORE----: Notifier: StateChanged (0x00000201) [Ip6+ NetData] +I(65529) OPENTHREAD:[INFO]-BBR-----: PBBR state: None +I(65539) OPENTHREAD:[INFO]-BBR-----: Domain Prefix: ::/0, state: None +I(65559) OPENTHREAD:[INFO]-MESH-CP-: Forwarded request to leader on c/ag +W(65559) OPENTHREAD:[WARN]-MESH-CP-: Failed to notify commissioner on ProxyRx (c/ur): DestinationAddressFiltered +I(65579) OPENTHREAD:[INFO]-MESH-CP-: sent active dataset get response to fd04:b642:9ba9:fcdc:0:ff:fe00:c800 +W(65579) OPENTHREAD:[WARN]-MESH-CP-: Failed to notify commissioner on ProxyRx (c/ur): DestinationAddressFiltered +I(65589) OPENTHREAD:[INFO]-MESH-CP-: Sent to commissioner +I(65629) OPENTHREAD:[INFO]-MESH-CP-: Forwarded request to leader on c/ag +W(65629) OPENTHREAD:[WARN]-MESH-CP-: Failed to notify commissioner on ProxyRx (c/ur): DestinationAddressFiltered +I(65649) OPENTHREAD:[INFO]-MESH-CP-: sent active dataset get response to fd04:b642:9ba9:fcdc:0:ff:fe00:c800 +W(65649) OPENTHREAD:[WARN]-MESH-CP-: Failed to notify commissioner on ProxyRx (c/ur): DestinationAddressFiltered +I(65659) OPENTHREAD:[INFO]-MESH-CP-: Sent to commissioner +I(65689) OPENTHREAD:[INFO]-MESH-CP-: Proxy transmit sent to fd04:b642:9ba9:fcdc:0:ff:fe00:fc00 +W(65689) OPENTHREAD:[WARN]-MESH-CP-: Failed to notify commissioner on ProxyRx (c/ur): DestinationAddressFiltered +I(65699) OPENTHREAD:[INFO]-MESH-CP-: sent pending dataset get response to fd04:b642:9ba9:fcdc:0:ff:fe00:fc35 +I(65709) OPENTHREAD:[INFO]-MESH-CP-: Sent to commissioner on c/ur +I(65749) OPENTHREAD:[INFO]-MESH-CP-: Proxy transmit sent to fd04:b642:9ba9:fcdc:0:ff:fe00:fc00 +W(65749) OPENTHREAD:[WARN]-MESH-CP-: Failed to notify commissioner on ProxyRx (c/ur): DestinationAddressFiltered +I(65759) OPENTHREAD:[INFO]-MESH-CP-: sent commissioning dataset set response +I(65769) OPENTHREAD:[INFO]-MESH-CP-: Sent to commissioner on c/ur +I(65769) OPENTHREAD:[INFO]-CORE----: Notifier: StateChanged (0x00000200) [NetData] +I(65789) OPENTHREAD:[INFO]-BBR-----: PBBR state: None + +``` + +### Commission the joiner + +In the OT commissioner cli, run: +``` bash +> joiner enableall meshcop J01NU5 +[done] +> +``` + +In the joining device's cli, run: + +```bash +> ifconfig up +Done +> joiner start J01NU5 +Done +> Join success! +> thread start +Done +``` + +You can also find these log lines in the border router: + +``` +I(531219) OPENTHREAD:[INFO]-MESH-CP-: Received relay transmit +I(531229) OPENTHREAD:[INFO]-MESH-CP-: Received kek +I(531279) OPENTHREAD:[INFO]-MAC-----: Sent IPv6 UDP msg, len:85, chksum:14a0, to:92335c4b320830fb, sec:no, prio:net +I(531279) OPENTHREAD:[INFO]-MAC-----: src:[fe80:0:0:0:ac2f:720a:6fe4:c837]:1000 +I(531289) OPENTHREAD:[INFO]-MAC-----: dst:[fe80:0:0:0:9033:5c4b:3208:30fb]:1000 +I(531299) OPENTHREAD:[INFO]-MESH-CP-: Sending JOIN_ENT.ntf +I(531299) OPENTHREAD:[INFO]-MESH-CP-: Sent joiner entrust length = 161 + +...... + +I(552699) OPENTHREAD:[INFO]-MLE-----: Receive Child ID Request (fe80:0:0:0:8434:c5ec:fe9f:c088) +I(552729) OPENTHREAD:[INFO]-CORE----: [settings] Added ChildInfo {rloc:0xc801, extaddr:8634c5ecfe9fc088, timeout:240, mode:0x0f, version:3} +I(552729) OPENTHREAD:[INFO]-MLE-----: Send Child ID Response (fe80:0:0:0:8434:c5ec:fe9f:c088,0xc801) +I(552739) OPENTHREAD:[INFO]-CORE----: Notifier: StateChanged (0x00000400) [Child+] +I(552749) OPENTHREAD:[INFO]-UTIL----: Starting Child Supervision +``` + +The device has now joined the same Thread network based on the key set by the commissioner. diff --git a/examples/openthread/ot_br/main/CMakeLists.txt b/examples/openthread/ot_br/main/CMakeLists.txt new file mode 100644 index 0000000000..83b4e7608e --- /dev/null +++ b/examples/openthread/ot_br/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "esp_ot_br.c" + INCLUDE_DIRS ".") diff --git a/examples/openthread/ot_br/main/Kconfig.projbuild b/examples/openthread/ot_br/main/Kconfig.projbuild new file mode 100644 index 0000000000..cf056d4dea --- /dev/null +++ b/examples/openthread/ot_br/main/Kconfig.projbuild @@ -0,0 +1,41 @@ +menu "Example Configuration" + + config OPENTHREAD_NETWORK_NAME + string "OpenThread network name" + default "OpenThread" + help + The OpenThread network name for example to use + + config OPENTHREAD_NETWORK_CHANNEL + int "OpenThread network channel" + range 11 26 + default 15 + help + The OpenThread network channel to use + + config OPENTHREAD_NETWORK_PANID + hex "OpenThread network pan id" + range 0 0x1234 + default 0x1234 + help + The OpenThread network pan id to use + + config OPENTHREAD_NETWORK_EXTPANID + string "OpenThread extended pan id" + default "dead00beef00cafe" + help + The OpenThread network extended pan id in hex string format + + config OPENTHREAD_NETWORK_MASTERKEY + string "OpenThread master key" + default "00112233445566778899aabbccddeeff" + help + The OpenThread network master key in hex string format + + config OPENTHREAD_NETWORK_PSKC + string "OpenThread pre-shared commissioner key" + default "104810e2315100afd6bc9215a6bfac53" + help + The OpenThread pre-shared commissioner key in hex string format + +endmenu diff --git a/examples/openthread/ot_br/main/component.mk b/examples/openthread/ot_br/main/component.mk new file mode 100644 index 0000000000..b928c2f543 --- /dev/null +++ b/examples/openthread/ot_br/main/component.mk @@ -0,0 +1,8 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_ADD_INCLUDEDIRS := . + +COMPONENT_PRIV_INCLUDEDIRS := . diff --git a/examples/openthread/ot_br/main/esp_ot_br.c b/examples/openthread/ot_br/main/esp_ot_br.c new file mode 100644 index 0000000000..7627869c88 --- /dev/null +++ b/examples/openthread/ot_br/main/esp_ot_br.c @@ -0,0 +1,191 @@ +// Copyright 2021 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 +#include + +#include "esp_check.h" +#include "esp_err.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_netif.h" +#include "esp_netif_ip_addr.h" +#include "esp_netif_net_stack.h" +#include "esp_openthread.h" +#include "esp_openthread_border_router.h" +#include "esp_openthread_defaults.h" +#include "esp_openthread_lock.h" +#include "esp_openthread_netif_glue.h" +#include "esp_openthread_types.h" +#include "esp_vfs_eventfd.h" +#include "esp_wifi.h" +#include "nvs_flash.h" +#include "protocol_examples_common.h" +#include "sdkconfig.h" +#include "driver/uart.h" +#include "freertos/FreeRTOS.h" +#include "freertos/portmacro.h" +#include "freertos/task.h" +#include "hal/uart_types.h" +#include "openthread/border_router.h" +#include "openthread/cli.h" +#include "openthread/dataset.h" +#include "openthread/dataset_ftd.h" +#include "openthread/dataset_updater.h" +#include "openthread/error.h" +#include "openthread/instance.h" +#include "openthread/ip6.h" +#include "openthread/tasklet.h" +#include "openthread/thread.h" +#include "openthread/thread_ftd.h" + +#define TAG "esp_ot_br" + +static int hex_digit_to_int(char hex) +{ + if ('A' <= hex && hex <= 'F') { + return 10 + hex - 'A'; + } + if ('a' <= hex && hex <= 'f') { + return 10 + hex - 'a'; + } + if ('0' <= hex && hex <= '9') { + return hex - '0'; + } + return -1; +} + +static size_t hex_string_to_binary(const char *hex_string, uint8_t *buf, size_t buf_size) +{ + int num_char = strlen(hex_string); + + if (num_char != buf_size * 2) { + return 0; + } + for (size_t i = 0; i < num_char; i += 2) { + int digit0 = hex_digit_to_int(hex_string[i]); + int digit1 = hex_digit_to_int(hex_string[i + 1]); + + if (digit0 < 0 || digit1 < 0) { + return 0; + } + buf[i / 2] = (digit0 << 4) + digit1; + } + + return buf_size; +} + +static void create_config_network(otInstance *instance) +{ + otOperationalDataset dataset; + uint16_t network_name_len = strnlen(CONFIG_OPENTHREAD_NETWORK_NAME, OT_NETWORK_NAME_MAX_SIZE + 1); + + assert(network_name_len <= OT_NETWORK_NAME_MAX_SIZE); + + if (otDatasetCreateNewNetwork(instance, &dataset) != OT_ERROR_NONE) { + ESP_LOGE(TAG, "Failed to create OpenThread network dataset."); + abort(); + } + dataset.mChannel = CONFIG_OPENTHREAD_NETWORK_CHANNEL; + dataset.mComponents.mIsChannelPresent = true; + dataset.mPanId = CONFIG_OPENTHREAD_NETWORK_PANID; + dataset.mComponents.mIsPanIdPresent = true; + memcpy(dataset.mNetworkName.m8, CONFIG_OPENTHREAD_NETWORK_NAME, network_name_len); + dataset.mComponents.mIsNetworkNamePresent = true; + if (hex_string_to_binary(CONFIG_OPENTHREAD_NETWORK_EXTPANID, dataset.mExtendedPanId.m8, + sizeof(dataset.mExtendedPanId.m8)) != sizeof(dataset.mExtendedPanId.m8)) { + ESP_LOGE(TAG, "Cannot convert OpenThread extended pan id. Please double-check your config."); + abort(); + } + dataset.mComponents.mIsExtendedPanIdPresent = true; + if (hex_string_to_binary(CONFIG_OPENTHREAD_NETWORK_MASTERKEY, dataset.mMasterKey.m8, + sizeof(dataset.mMasterKey.m8)) != sizeof(dataset.mMasterKey.m8)) { + ESP_LOGE(TAG, "Cannot convert OpenThread master key. Please double-check your config."); + abort(); + } + dataset.mComponents.mIsMasterKeyPresent = true; + if (hex_string_to_binary(CONFIG_OPENTHREAD_NETWORK_PSKC, dataset.mPskc.m8, sizeof(dataset.mPskc.m8)) != + sizeof(dataset.mPskc.m8)) { + ESP_LOGE(TAG, "Cannot convert OpenThread pre-shared commissioner key. Please double-check your config."); + abort(); + } + dataset.mComponents.mIsPskcPresent = true; + if (otDatasetSetActive(instance, &dataset) != OT_ERROR_NONE) { + ESP_LOGE(TAG, "Failed to set OpenThread active dataset."); + abort(); + } + if (otBorderRouterRegister(instance) != OT_ERROR_NONE) { + ESP_LOGE(TAG, "Failed to register border router."); + abort(); + } + if (otIp6SetEnabled(instance, true) != OT_ERROR_NONE) { + ESP_LOGE(TAG, "Failed to enable OpenThread IP6 link"); + abort(); + } + if (otThreadSetEnabled(instance, true) != OT_ERROR_NONE) { + ESP_LOGE(TAG, "Failed to enable OpenThread"); + abort(); + } + return; +} + +static void ot_task_worker(void *aContext) +{ + esp_openthread_platform_config_t config = { + .radio_config = ESP_OPENTHREAD_DEFAULT_RADIO_UART_RCP_CONFIG(4, 5), + .host_config = ESP_OPENTHREAD_DEFAULT_UART_HOST_CONFIG(), + }; + + esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD(); + esp_netif_t *openthread_netif = esp_netif_new(&cfg); + assert(openthread_netif != NULL); + + // Initialize the OpenThread stack + ESP_ERROR_CHECK(esp_openthread_init(&config)); + + // Initialize border routing features + ESP_ERROR_CHECK(esp_netif_attach(openthread_netif, esp_openthread_netif_glue_init())); + ESP_ERROR_CHECK(esp_openthread_border_router_init(get_example_netif())); + + esp_openthread_lock_acquire(portMAX_DELAY); + create_config_network(esp_openthread_get_instance()); + esp_openthread_lock_release(); + + // Run the main loop + esp_openthread_launch_mainloop(); + + // Clean up + esp_netif_destroy(openthread_netif); + esp_openthread_netif_glue_deinit(); + esp_vfs_eventfd_unregister(); + vTaskDelete(NULL); +} + +void app_main(void) +{ + // Used eventfds: + // * netif + // * task queue + esp_vfs_eventfd_config_t eventfd_config = { + .max_fds = 2, + }; + ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config)); + + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + ESP_ERROR_CHECK(example_connect()); + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); + xTaskCreate(ot_task_worker, "ot_br_main", 20480, xTaskGetCurrentTaskHandle(), 5, NULL); +} diff --git a/examples/openthread/ot_br/partitions.csv b/examples/openthread/ot_br/partitions.csv new file mode 100644 index 0000000000..ded8c6adfd --- /dev/null +++ b/examples/openthread/ot_br/partitions.csv @@ -0,0 +1,6 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 1200K, +ot_storage, data, 0x3a, , 0x2000, diff --git a/examples/openthread/ot_br/sdkconfig.defaults b/examples/openthread/ot_br/sdkconfig.defaults new file mode 100644 index 0000000000..e4331e4410 --- /dev/null +++ b/examples/openthread/ot_br/sdkconfig.defaults @@ -0,0 +1,40 @@ +# +# libsodium +# +CONFIG_LIBSODIUM_USE_MBEDTLS_SHA=y +# end of libsodium + +# +# Partition Table +# +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_OFFSET=0x8000 +CONFIG_PARTITION_TABLE_MD5=y +# end of Partition Table + +# +# mbedTLS +# + +CONFIG_MBEDTLS_CMAC_C=y +CONFIG_MBEDTLS_SSL_PROTO_DTLS=y +CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y +# end of TLS Key Exchange Methods + +CONFIG_MBEDTLS_ECJPAKE_C=y +# end of mbedTLS + +# +# OpenThread +# +CONFIG_OPENTHREAD_ENABLED=y +CONFIG_OPENTHREAD_BORDER_ROUTER=y +# end of OpenThread + +# +# lwIP +# +CONFIG_LWIP_IPV6_NUM_ADDRESSES=8 +# end of lwIP