Merge branch 'feature/otbr-example' into 'master'

openthread: add platform UDP and border router example

See merge request espressif/esp-idf!13885
This commit is contained in:
Shu Chen 2021-06-21 08:33:15 +00:00
commit 30524406f0
19 changed files with 1178 additions and 17 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
*

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 <string.h>
#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;
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "esp_ot_br.c"
INCLUDE_DIRS ".")

View File

@ -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

View File

@ -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 := .

View File

@ -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 <stdio.h>
#include <string.h>
#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);
}

View File

@ -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,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1200K,
6 ot_storage, data, 0x3a, , 0x2000,

View File

@ -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