mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(openthread): support multipan feature for host device
This commit is contained in:
@@ -533,6 +533,15 @@ menu "OpenThread"
|
||||
OpenThread esp_netif, attaches the esp_netif glue, and enables
|
||||
lwIP-specific DNS/netif handling.
|
||||
|
||||
config OPENTHREAD_MULTIPAN_HOST_ENABLE
|
||||
bool "Enable Multipan Host"
|
||||
depends on OPENTHREAD_RADIO_SPINEL_UART
|
||||
default n
|
||||
help
|
||||
Enable MULTI-PAN host support over the UART radio spinel interface.
|
||||
The host talks to a multipan RCP and demultiplexes Spinel frames by
|
||||
interface ID.
|
||||
|
||||
config OPENTHREAD_MULTIPAN_RCP_ENABLE
|
||||
bool "Enable Multipan RCP"
|
||||
depends on (OPENTHREAD_RADIO && IEEE802154_MULTI_PAN_ENABLE)
|
||||
@@ -543,11 +552,11 @@ menu "OpenThread"
|
||||
|
||||
config OPENTHREAD_MULTIPLE_INTERFACES_COUNT
|
||||
int "The number of multiple interfaces"
|
||||
depends on OPENTHREAD_MULTIPAN_RCP_ENABLE
|
||||
depends on OPENTHREAD_MULTIPAN_HOST_ENABLE || OPENTHREAD_MULTIPAN_RCP_ENABLE
|
||||
default 2
|
||||
range 2 3
|
||||
help
|
||||
Number of multiple interfaces.
|
||||
Number of Spinel interfaces used by the multipan host or RCP.
|
||||
|
||||
endmenu
|
||||
|
||||
|
||||
60
components/openthread/include/esp_radio_spinel_multipan.h
Normal file
60
components/openthread/include/esp_radio_spinel_multipan.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_openthread_types.h"
|
||||
#include "esp_radio_spinel.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Multipan host radio backend.
|
||||
*
|
||||
* One physical UART RCP link is owned by this module. Incoming spinel frames
|
||||
* are fanned out into per-client queues.
|
||||
*/
|
||||
typedef struct {
|
||||
esp_openthread_radio_mode_t radio_mode; /*!< Must be RADIO_MODE_UART_RCP */
|
||||
esp_radio_spinel_uart_config_t radio_uart_config; /*!< UART RCP */
|
||||
} esp_radio_spinel_multipan_radio_config_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize the shared RCP backend.
|
||||
*
|
||||
* Currently only `RADIO_MODE_UART_RCP` is supported. This function is expected
|
||||
* to be called once; a second call is rejected. Call this before starting the
|
||||
* OpenThread or Zigbee stacks so they can attach as clients.
|
||||
*
|
||||
* @param[in] radio_config Radio backend configuration. Must not be NULL.
|
||||
* `radio_mode` must be `RADIO_MODE_UART_RCP`.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_ARG if `radio_config` is NULL
|
||||
* - ESP_ERR_INVALID_STATE if the backend is already initialized
|
||||
* - ESP_ERR_NOT_SUPPORTED if `radio_mode` is not `RADIO_MODE_UART_RCP`
|
||||
* - ESP_ERR_NO_MEM if a lock, queue, or task cannot be created
|
||||
* - ESP_FAIL on other failures
|
||||
*/
|
||||
esp_err_t esp_radio_spinel_multipan_init(const esp_radio_spinel_multipan_radio_config_t *radio_config);
|
||||
|
||||
/**
|
||||
* @brief Tear down the backend. All client fds must already be closed.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_STATE if the backend is not initialized, or if any
|
||||
* client is still open
|
||||
*/
|
||||
esp_err_t esp_radio_spinel_multipan_deinit(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#if CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE
|
||||
#define ESP_RADIO_SPINEL_IID_LIST_LEN 2
|
||||
#define ESP_RADIO_SPINEL_HOST_INTERFACE_COUNT CONFIG_OPENTHREAD_MULTIPLE_INTERFACES_COUNT
|
||||
#else
|
||||
#define ESP_RADIO_SPINEL_IID_LIST_LEN 1
|
||||
#endif
|
||||
@@ -7,8 +7,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esp_radio_spinel.h"
|
||||
#include "esp_radio_spinel_uart_transport.hpp"
|
||||
#include "lib/spinel/spinel_interface.hpp"
|
||||
#include "lib/hdlc/hdlc.hpp"
|
||||
#include "openthread/error.h"
|
||||
#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART
|
||||
#include "esp_openthread_types.h"
|
||||
@@ -18,7 +18,11 @@ namespace esp {
|
||||
namespace radio_spinel {
|
||||
|
||||
/**
|
||||
* This class defines an UART interface to the Radio Co-processor (RCP).
|
||||
* This class defines a UART spinel interface to the Radio Co-processor (RCP).
|
||||
*
|
||||
* HDLC and UART I/O are owned by the transport layer. This class only waits on
|
||||
* the transport wait fd. Complete spinel frames are delivered by the transport
|
||||
* into the RxFrameBuffer bound at Init, via the receive callback.
|
||||
*
|
||||
*/
|
||||
class UartSpinelInterface : public ot::Spinel::SpinelInterface {
|
||||
@@ -65,7 +69,7 @@ public:
|
||||
* @retval OT_ERROR_NONE Successfully encoded and sent the spinel frame.
|
||||
* @retval OT_ERROR_BUSY Failed due to another operation is on going.
|
||||
* @retval OT_ERROR_NO_BUFS Insufficient buffer space available to encode the frame.
|
||||
* @retval OT_ERROR_FAILED Failed to call the SPI driver to send the frame.
|
||||
* @retval OT_ERROR_FAILED Failed to call the UART driver to send the frame.
|
||||
*
|
||||
*/
|
||||
otError SendFrame(const uint8_t *aFrame, uint16_t aLength);
|
||||
@@ -73,10 +77,10 @@ public:
|
||||
/**
|
||||
* Waits for receiving part or all of spinel frame within specified interval.
|
||||
*
|
||||
* @param[in] aTimeout The timeout value in microseconds.
|
||||
* @param[in] aTimeoutUs The timeout value in microseconds.
|
||||
*
|
||||
* @retval OT_ERROR_NONE Part or all of spinel frame is received.
|
||||
* @retval OT_ERROR_RESPONSE_TIMEOUT No spinel frame is received within @p aTimeout.
|
||||
* @retval OT_ERROR_RESPONSE_TIMEOUT No spinel frame is received within @p aTimeoutUs.
|
||||
*
|
||||
*/
|
||||
otError WaitForFrame(uint64_t aTimeoutUs);
|
||||
@@ -138,80 +142,69 @@ public:
|
||||
otError ResetConnection(void) { return OT_ERROR_NONE; }
|
||||
|
||||
/**
|
||||
* @brief This method enable the HDLC interface.
|
||||
* Enable the spinel UART transport.
|
||||
*
|
||||
* Multipan: allocates a free spinel IID. Use GetIid() after Enable succeeds.
|
||||
*
|
||||
* @param[in] radio_uart_config UART configuration.
|
||||
* @param[in] hooks Optional UART init/deinit hooks. May be nullptr.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NO_MEM if allocation has failed
|
||||
* - ESP_ERROR on failure
|
||||
* - ESP_ERR_INVALID_STATE if already enabled
|
||||
* - ESP_ERR_NO_MEM if a multipan host slot or rx queue cannot be allocated
|
||||
* - ESP_FAIL on failure
|
||||
*/
|
||||
esp_err_t Enable(const esp_radio_spinel_uart_config_t &radio_uart_config);
|
||||
esp_err_t Enable(const esp_radio_spinel_uart_config_t &radio_uart_config,
|
||||
const esp_radio_spinel_uart_transport_hooks_t *hooks = nullptr);
|
||||
#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART
|
||||
esp_err_t Enable(const esp_openthread_uart_config_t &radio_uart_config);
|
||||
/**
|
||||
* Enable the spinel UART transport using OpenThread UART config.
|
||||
*
|
||||
* @param[in] radio_uart_config OpenThread UART configuration.
|
||||
* @param[in] hooks Optional UART init/deinit hooks. May be nullptr.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_INVALID_STATE if already enabled
|
||||
* - ESP_ERR_NO_MEM if a multipan host slot or rx queue cannot be allocated
|
||||
* - ESP_FAIL on failure
|
||||
*/
|
||||
esp_err_t Enable(const esp_openthread_uart_config_t &radio_uart_config,
|
||||
const esp_radio_spinel_uart_transport_hooks_t *hooks = nullptr);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief This method disable the HDLC interface.
|
||||
* Disable the spinel UART transport.
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_FAIL on failure
|
||||
*/
|
||||
esp_err_t Disable(void);
|
||||
|
||||
void RegisterUartInitHandler(esp_radio_spinel_uart_init_handler handler)
|
||||
{
|
||||
if (mUartInitHandler != NULL) {
|
||||
ESP_LOGW(ESP_SPINEL_LOG_TAG, "UartInitHandler already registered, will overwrite (prev=%p, new=%p)", mUartInitHandler, handler);
|
||||
}
|
||||
mUartInitHandler = handler;
|
||||
}
|
||||
|
||||
void RegisterUartDeinitHandler(esp_radio_spinel_uart_deinit_handler handler) { mUartDeinitHandler = handler; }
|
||||
/**
|
||||
* Returns the allocated spinel IID.
|
||||
*
|
||||
* Valid after Enable() succeeds. Returns -1 if the interface is not enabled.
|
||||
*
|
||||
*/
|
||||
int8_t GetIid(void) const { return m_iid; }
|
||||
|
||||
private:
|
||||
|
||||
enum {
|
||||
/**
|
||||
* Maximum wait time in Milliseconds for socket to become writable (see `SendFrame`).
|
||||
*
|
||||
*/
|
||||
kMaxWaitTime = 2000,
|
||||
};
|
||||
|
||||
esp_err_t InitUart(const esp_radio_spinel_uart_config_t &radio_uart_config);
|
||||
|
||||
esp_err_t DeinitUart(void);
|
||||
|
||||
int TryReadAndDecode(void);
|
||||
|
||||
otError WaitForWritable(void);
|
||||
|
||||
otError Write(const uint8_t *frame, uint16_t length);
|
||||
|
||||
esp_err_t TryRecoverUart(void);
|
||||
|
||||
static void HandleHdlcFrame(void *context, otError error);
|
||||
void HandleHdlcFrame(otError error);
|
||||
int TryReadSpinel(void);
|
||||
|
||||
ReceiveFrameCallback m_receiver_frame_callback;
|
||||
void *m_receiver_frame_context;
|
||||
RxFrameBuffer *m_receive_frame_buffer;
|
||||
|
||||
ot::Hdlc::Decoder m_hdlc_decoder;
|
||||
uint8_t *m_uart_rx_buffer;
|
||||
|
||||
esp_radio_spinel_uart_config_t m_uart_config;
|
||||
int m_uart_fd;
|
||||
|
||||
int m_wait_fd;
|
||||
int8_t m_iid;
|
||||
otRcpInterfaceMetrics mInterfaceMetrics;
|
||||
esp_radio_spinel_rcp_failure_handler mRcpFailureHandler;
|
||||
|
||||
// Non-copyable, intentionally not implemented.
|
||||
UartSpinelInterface(const UartSpinelInterface &);
|
||||
UartSpinelInterface &operator=(const UartSpinelInterface &);
|
||||
|
||||
esp_radio_spinel_rcp_failure_handler mRcpFailureHandler;
|
||||
esp_radio_spinel_uart_init_handler mUartInitHandler;
|
||||
esp_radio_spinel_uart_deinit_handler mUartDeinitHandler;
|
||||
|
||||
ot::Spinel::FrameBuffer<kMaxFrameSize> encoder_buffer;
|
||||
};
|
||||
|
||||
} // namespace radio_spinel
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_radio_spinel.h"
|
||||
#include "lib/spinel/spinel_interface.hpp"
|
||||
|
||||
/**
|
||||
* Optional UART open/close hooks. Used by the non-multipan transport.
|
||||
* The multipan transport ignores them (UART is owned by esp_radio_spinel_multipan_init()).
|
||||
*/
|
||||
typedef struct {
|
||||
esp_radio_spinel_uart_init_handler uart_init; /* May be NULL. */
|
||||
esp_radio_spinel_uart_deinit_handler uart_deinit; /* May be NULL. */
|
||||
} esp_radio_spinel_uart_transport_hooks_t;
|
||||
|
||||
/**
|
||||
* Open the UART spinel transport.
|
||||
*
|
||||
* Non-multipan: opens the real UART and returns that fd as wait_fd. @p iid is set to 0.
|
||||
* Multipan: allocates a free IID slot and returns an eventfd as wait_fd.
|
||||
*
|
||||
* @param[in] config UART config. Non-multipan uses it to open the port.
|
||||
* Multipan may ignore it if esp_radio_spinel_multipan_init() already ran.
|
||||
* @param[in] hooks Optional UART init/deinit hooks. May be NULL.
|
||||
* @param[out] iid Allocated spinel interface id.
|
||||
* @param[out] wait_fd Fd to select for a readable spinel frame.
|
||||
*
|
||||
* @return ESP_OK on success, an error code otherwise.
|
||||
*/
|
||||
esp_err_t esp_radio_spinel_uart_transport_open(const esp_radio_spinel_uart_config_t *config,
|
||||
const esp_radio_spinel_uart_transport_hooks_t *hooks, int8_t *iid,
|
||||
int *wait_fd);
|
||||
|
||||
/**
|
||||
* Close the transport opened for @p iid.
|
||||
*/
|
||||
esp_err_t esp_radio_spinel_uart_transport_close(int8_t iid);
|
||||
|
||||
/**
|
||||
* Bind the RadioSpinel receive buffer and callback for @p iid.
|
||||
*
|
||||
* Called from UartSpinelInterface::Init (and Enable if Init already ran).
|
||||
* Non-multipan attaches the buffer to the HDLC decoder.
|
||||
* Multipan stores them on the host slot and invokes the callback from read().
|
||||
*/
|
||||
esp_err_t esp_radio_spinel_uart_transport_bind_rx(int8_t iid,
|
||||
ot::Spinel::SpinelInterface::ReceiveFrameCallback callback,
|
||||
void *context,
|
||||
ot::Spinel::SpinelInterface::RxFrameBuffer *frame_buffer);
|
||||
|
||||
/**
|
||||
* Undo bind_rx for @p iid.
|
||||
*
|
||||
* Non-multipan detaches the HDLC decoder from the RxFrameBuffer.
|
||||
* Multipan clears the host slot's buffer and callback.
|
||||
* Safe to call if nothing is bound.
|
||||
*/
|
||||
void esp_radio_spinel_uart_transport_unbind_rx(int8_t iid);
|
||||
|
||||
/**
|
||||
* Drain the transport for @p iid.
|
||||
*
|
||||
* Non-multipan: reads UART and HDLC-decodes into the bound RxFrameBuffer.
|
||||
* Complete frames are delivered via the callback registered with bind_rx.
|
||||
* Multipan: dequeues pending spinel frames, copies each into the bound
|
||||
* RxFrameBuffer, then invokes the same callback.
|
||||
*
|
||||
* @return 0 on success (including no complete frame), -1 on error.
|
||||
*/
|
||||
int esp_radio_spinel_uart_transport_read(int8_t iid);
|
||||
|
||||
/**
|
||||
* Write one complete spinel frame (HDLC-encoded internally).
|
||||
*
|
||||
* @return @p len on success, -1 on error.
|
||||
*/
|
||||
ssize_t esp_radio_spinel_uart_transport_write(int8_t iid, const void *buf, size_t len);
|
||||
|
||||
/**
|
||||
* Re-install the UART after a bus error. Client wait fds stay valid.
|
||||
*/
|
||||
esp_err_t esp_radio_spinel_uart_transport_recover(int8_t iid);
|
||||
|
||||
/**
|
||||
* UART baud rate in bits/second.
|
||||
*/
|
||||
uint32_t esp_radio_spinel_uart_transport_get_bus_speed(int8_t iid);
|
||||
@@ -469,6 +469,49 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE
|
||||
/**
|
||||
* @def OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID
|
||||
*
|
||||
* Define broadcast IID for spinel frames dedicated to all hosts in multipan configuration.
|
||||
*/
|
||||
#ifdef OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID
|
||||
#error `OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID` is redefined.
|
||||
#endif
|
||||
#define OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID SPINEL_HEADER_IID(CONFIG_OPENTHREAD_MULTIPLE_INTERFACES_COUNT)
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_SPINEL_CONFIG_MAX_INTERFACE_ID
|
||||
*
|
||||
* Specifies the maximum number of Spinel interface IDs.
|
||||
*/
|
||||
#ifdef OPENTHREAD_SPINEL_CONFIG_MAX_INTERFACE_ID
|
||||
#error `OPENTHREAD_SPINEL_CONFIG_MAX_INTERFACE_ID` is redefined.
|
||||
#endif
|
||||
#define OPENTHREAD_SPINEL_CONFIG_MAX_INTERFACE_ID 2
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE
|
||||
*
|
||||
* Enables compilation of vendor specific code for Spinel
|
||||
*/
|
||||
#ifdef OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE
|
||||
#error `OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE` is redefined.
|
||||
#endif
|
||||
#define OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE 1
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
|
||||
*
|
||||
* Define to 1 to enable multipan RCP support.
|
||||
*/
|
||||
#ifdef OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
|
||||
#error `OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE` is redefined.
|
||||
#endif
|
||||
#define OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE 1
|
||||
|
||||
#endif // CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE
|
||||
|
||||
/*----The following options set fixed default values but can be overridden by the user header file.----*/
|
||||
|
||||
#if CONFIG_OPENTHREAD_BORDER_ROUTER
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#elif CONFIG_OPENTHREAD_RADIO_SPINEL_CUSTOM
|
||||
#include "esp_radio_spinel_custom.hpp"
|
||||
#endif
|
||||
#include "esp_radio_spinel_host.h"
|
||||
#include "openthread-core-config.h"
|
||||
#include "lib/spinel/radio_spinel.hpp"
|
||||
#include "lib/spinel/spinel.h"
|
||||
@@ -109,9 +110,6 @@ esp_err_t esp_openthread_radio_init(const esp_openthread_platform_config_t *conf
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, OT_PLAT_LOG_TAG, "incoming config is NULL");
|
||||
|
||||
spinel_iid_t iidList[ot::Spinel::kSpinelHeaderMaxNumIid];
|
||||
iidList[0] = 0;
|
||||
|
||||
ot::Spinel::RadioSpinelCallbacks callbacks;
|
||||
memset(&callbacks, 0, sizeof(callbacks));
|
||||
callbacks.mEnergyScanDone = otPlatRadioEnergyScanDone;
|
||||
@@ -144,9 +142,17 @@ esp_err_t esp_openthread_radio_init(const esp_openthread_platform_config_t *conf
|
||||
#elif CONFIG_OPENTHREAD_RADIO_SPINEL_CUSTOM // CONFIG_OPENTHREAD_RADIO_SPINEL_CUSTOM
|
||||
ESP_RETURN_ON_ERROR(s_spinel_interface.GetSpinelInterface().Enable(config->radio_config.radio_transport_config), OT_PLAT_LOG_TAG,
|
||||
"Spinel custom transport init failed");
|
||||
#endif
|
||||
#if CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE
|
||||
spinel_iid_t iidList[ESP_RADIO_SPINEL_IID_LIST_LEN] = {
|
||||
static_cast<spinel_iid_t>(s_spinel_interface.GetSpinelInterface().GetIid()),
|
||||
static_cast<spinel_iid_t>(SPINEL_HEADER_GET_IID(OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID)),
|
||||
};
|
||||
#else
|
||||
spinel_iid_t iidList[ESP_RADIO_SPINEL_IID_LIST_LEN] = {0};
|
||||
#endif
|
||||
s_spinel_driver.SetCoprocessorResetFailureCallback(ot_spinel_coprocessor_reset_failure_callback, esp_openthread_get_instance());
|
||||
s_spinel_driver.Init(s_spinel_interface.GetSpinelInterface(), true, iidList, ot::Spinel::kSpinelHeaderMaxNumIid);
|
||||
s_spinel_driver.Init(s_spinel_interface.GetSpinelInterface(), true, iidList, ESP_RADIO_SPINEL_IID_LIST_LEN);
|
||||
if (strlen(s_internal_rcp_version) > 0) {
|
||||
const char *running_rcp_version = s_spinel_driver.GetVersion();
|
||||
if (strcmp(s_internal_rcp_version, running_rcp_version) != 0) {
|
||||
|
||||
@@ -12,8 +12,10 @@
|
||||
#include "esp_radio_spinel.h"
|
||||
#include "esp_radio_spinel_platform.h"
|
||||
#include "esp_radio_spinel_adapter.hpp"
|
||||
#include "esp_radio_spinel_host.h"
|
||||
#include "esp_spinel_ncp_vendor_macro.h"
|
||||
#include "esp_radio_spinel_uart_interface.hpp"
|
||||
#include "esp_radio_spinel_uart_transport.hpp"
|
||||
#include "spinel_driver.hpp"
|
||||
#include "openthread/link.h"
|
||||
|
||||
@@ -21,9 +23,11 @@
|
||||
#define SPINEL_VENDOR_PROPERTY_BIT_COORDINATOR BIT(1)
|
||||
// Must match ot::Spinel::SpinelDriver::kVersionStringSize (private, spinel_driver.hpp).
|
||||
#define ESP_RADIO_SPINEL_RCP_VERSION_MAX_SIZE 128
|
||||
static esp_ieee802154_pending_mode_t s_spinel_vendor_property_pendingmode[ot::Spinel::kSpinelHeaderMaxNumIid] = {ESP_IEEE802154_AUTO_PENDING_DISABLE};
|
||||
static bool s_spinel_vendor_property_coordinator[ot::Spinel::kSpinelHeaderMaxNumIid] = {false};
|
||||
static uint64_t s_spinel_vendor_property_mask[ot::Spinel::kSpinelHeaderMaxNumIid] = {0};
|
||||
/* This file currently only supports Zigbee; OpenThread uses its own radio spinel path. */
|
||||
#define ESP_RADIO_SPINEL_INTERFACE_COUNT 1
|
||||
static esp_ieee802154_pending_mode_t s_spinel_vendor_property_pendingmode[ESP_RADIO_SPINEL_INTERFACE_COUNT] = {ESP_IEEE802154_AUTO_PENDING_DISABLE};
|
||||
static bool s_spinel_vendor_property_coordinator[ESP_RADIO_SPINEL_INTERFACE_COUNT] = {false};
|
||||
static uint64_t s_spinel_vendor_property_mask[ESP_RADIO_SPINEL_INTERFACE_COUNT] = {0};
|
||||
|
||||
using ot::Spinel::RadioSpinel;
|
||||
using ot::Spinel::RadioSpinelCallbacks;
|
||||
@@ -32,9 +36,9 @@ using esp::radio_spinel::UartSpinelInterface;
|
||||
using ot::Spinel::SpinelDriver;
|
||||
|
||||
static SpinelInterfaceAdapter<UartSpinelInterface> s_spinel_interface;
|
||||
static RadioSpinel s_radio[ot::Spinel::kSpinelHeaderMaxNumIid];
|
||||
static esp_radio_spinel_callbacks_t s_esp_radio_spinel_callbacks[ot::Spinel::kSpinelHeaderMaxNumIid];
|
||||
static SpinelDriver s_spinel_driver[ot::Spinel::kSpinelHeaderMaxNumIid];
|
||||
static RadioSpinel s_radio[ESP_RADIO_SPINEL_INTERFACE_COUNT];
|
||||
static esp_radio_spinel_callbacks_t s_esp_radio_spinel_callbacks[ESP_RADIO_SPINEL_INTERFACE_COUNT];
|
||||
static SpinelDriver s_spinel_driver[ESP_RADIO_SPINEL_INTERFACE_COUNT];
|
||||
otRadioFrame s_transmit_frame;
|
||||
|
||||
static otRadioCaps s_radio_caps = (OT_RADIO_CAPS_ENERGY_SCAN |
|
||||
@@ -216,24 +220,32 @@ esp_err_t esp_radio_spinel_uart_interface_enable(const esp_radio_spinel_uart_con
|
||||
esp_radio_spinel_uart_deinit_handler aUartDeinitHandler,
|
||||
esp_radio_spinel_idx_t idx)
|
||||
{
|
||||
s_spinel_interface.GetSpinelInterface().RegisterUartInitHandler(aUartInitHandler);
|
||||
s_spinel_interface.GetSpinelInterface().RegisterUartDeinitHandler(aUartDeinitHandler);
|
||||
ESP_RETURN_ON_FALSE(radio_uart_config != nullptr, ESP_ERR_INVALID_ARG, ESP_SPINEL_LOG_TAG, "radio_uart_config can not be NULL");
|
||||
ESP_RETURN_ON_ERROR(s_spinel_interface.GetSpinelInterface().Enable(*radio_uart_config), ESP_SPINEL_LOG_TAG,
|
||||
"Spinel UART interface failed to enable");
|
||||
esp_radio_spinel_uart_transport_hooks_t hooks = {
|
||||
.uart_init = aUartInitHandler,
|
||||
.uart_deinit = aUartDeinitHandler,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(s_spinel_interface.GetSpinelInterface().Enable(*radio_uart_config, &hooks),
|
||||
ESP_SPINEL_LOG_TAG, "Spinel UART interface failed to enable");
|
||||
ESP_LOGI(ESP_SPINEL_LOG_TAG, "Spinel UART interface has been successfully enabled");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void esp_radio_spinel_init(esp_radio_spinel_idx_t idx)
|
||||
{
|
||||
spinel_iid_t iidList[ot::Spinel::kSpinelHeaderMaxNumIid];
|
||||
otInstance *instance = get_instance_from_index(idx);
|
||||
|
||||
// Multipan is not currently supported
|
||||
iidList[0] = 0;
|
||||
#if CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE
|
||||
spinel_iid_t iidList[ESP_RADIO_SPINEL_IID_LIST_LEN] = {
|
||||
static_cast<spinel_iid_t>(s_spinel_interface.GetSpinelInterface().GetIid()),
|
||||
static_cast<spinel_iid_t>(SPINEL_HEADER_GET_IID(OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID)),
|
||||
};
|
||||
#else
|
||||
spinel_iid_t iidList[ESP_RADIO_SPINEL_IID_LIST_LEN] = {0};
|
||||
#endif
|
||||
|
||||
s_spinel_driver[idx].SetCoprocessorResetFailureCallback(radio_spinel_coprocessor_reset_failure_callback, instance);
|
||||
s_spinel_driver[idx].Init(s_spinel_interface.GetSpinelInterface(), true, iidList, ot::Spinel::kSpinelHeaderMaxNumIid);
|
||||
s_spinel_driver[idx].Init(s_spinel_interface.GetSpinelInterface(), true, iidList, ESP_RADIO_SPINEL_IID_LIST_LEN);
|
||||
s_radio[idx].SetCompatibilityErrorCallback(radio_spinel_compatibility_error_callback, instance);
|
||||
s_radio[idx].Init(/*skip_rcp_compatibility_check=*/false, /*reset_radio=*/true, &s_spinel_driver[idx], s_radio_caps, false);
|
||||
s_radio[idx].SetVendorRestorePropertiesCallback(esp_radio_spinel_restore_vendor_properities, instance);
|
||||
@@ -283,6 +295,7 @@ esp_err_t esp_radio_spinel_transmit(uint8_t *frame, uint8_t channel, bool cca, e
|
||||
s_transmit_frame.mPsdu = frame + 1;
|
||||
s_transmit_frame.mInfo.mTxInfo.mCsmaCaEnabled = cca;
|
||||
s_transmit_frame.mInfo.mTxInfo.mMaxCsmaBackoffs = CONFIG_OPENTHREAD_SPINEL_MAC_MAX_CSMA_BACKOFFS_DIRECT;
|
||||
s_transmit_frame.mInfo.mTxInfo.mMaxFrameRetries = 15;
|
||||
s_transmit_frame.mChannel = channel;
|
||||
s_transmit_frame.mInfo.mTxInfo.mRxChannelAfterTxDone = channel;
|
||||
return (s_radio[idx].Transmit(s_transmit_frame) == OT_ERROR_NONE) ? ESP_OK : ESP_FAIL;
|
||||
@@ -406,7 +419,8 @@ esp_err_t esp_radio_spinel_set_rcp_ready(esp_radio_spinel_idx_t idx)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// TZ-1261
|
||||
// TZ-1261: SPINEL_ONLY has no OpenThread MAC; FTD/MTD already provide this API.
|
||||
#if !CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE
|
||||
uint32_t otLinkGetFrameCounter(otInstance *aInstance)
|
||||
{
|
||||
esp_radio_spinel_idx_t idx = get_index_from_instance(aInstance);
|
||||
@@ -418,6 +432,7 @@ __attribute__((weak)) uint32_t esp_radio_spinel_extern_get_frame_counter(esp_rad
|
||||
ESP_LOGW(ESP_SPINEL_LOG_TAG, "None function to get frame counter");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace ot {
|
||||
namespace Spinel {
|
||||
|
||||
@@ -5,109 +5,84 @@
|
||||
*/
|
||||
|
||||
#include "esp_radio_spinel_uart_interface.hpp"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "esp_check.h"
|
||||
#include "esp_openthread_common_macro.h"
|
||||
#include "openthread/platform/time.h"
|
||||
#include "hdlc.hpp"
|
||||
#include "common/code_utils.hpp"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "driver/uart.h"
|
||||
#include "driver/uart_vfs.h"
|
||||
#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART
|
||||
#include "esp_openthread_types.h"
|
||||
#endif
|
||||
#include "esp_radio_spinel_uart_transport.hpp"
|
||||
|
||||
namespace esp {
|
||||
namespace radio_spinel {
|
||||
|
||||
static esp_err_t RadioSpinelUartInitPort(const esp_radio_spinel_uart_config_t *config)
|
||||
{
|
||||
char uart_path[16];
|
||||
snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", config->port);
|
||||
bool is_uart_registered = (access(uart_path, F_OK) == 0);
|
||||
if (!is_uart_registered) {
|
||||
// Register UART VFS devices before opening /dev/uart/x if not already present.
|
||||
uart_vfs_dev_register();
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_ERROR(uart_param_config(config->port, &config->uart_config), ESP_SPINEL_LOG_TAG,
|
||||
"uart_param_config failed");
|
||||
ESP_RETURN_ON_ERROR(
|
||||
uart_set_pin(config->port, config->tx_pin, config->rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE),
|
||||
ESP_SPINEL_LOG_TAG, "uart_set_pin failed");
|
||||
ESP_RETURN_ON_ERROR(uart_driver_install(config->port, CONFIG_OPENTHREAD_SPINEL_UART_DRIVER_BUFFER_SIZE, 0, 0, NULL, 0),
|
||||
ESP_SPINEL_LOG_TAG, "uart_driver_install failed");
|
||||
uart_vfs_dev_use_driver(config->port);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
UartSpinelInterface::UartSpinelInterface(void)
|
||||
: m_receiver_frame_callback(nullptr)
|
||||
, m_receiver_frame_context(nullptr)
|
||||
, m_receive_frame_buffer(nullptr)
|
||||
, m_uart_rx_buffer(nullptr)
|
||||
, m_uart_fd(-1)
|
||||
, m_wait_fd(-1)
|
||||
, m_iid(-1)
|
||||
, mRcpFailureHandler(nullptr)
|
||||
, mUartInitHandler(nullptr)
|
||||
, mUartDeinitHandler(nullptr)
|
||||
{
|
||||
memset(&mInterfaceMetrics, 0, sizeof(mInterfaceMetrics));
|
||||
}
|
||||
|
||||
UartSpinelInterface::~UartSpinelInterface(void)
|
||||
{
|
||||
// Ensure UART resources are released even if caller forgets Disable().
|
||||
Disable();
|
||||
Deinit();
|
||||
}
|
||||
|
||||
otError UartSpinelInterface::Init(ReceiveFrameCallback aCallback, void *aCallbackContext, RxFrameBuffer &aFrameBuffer)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
m_receiver_frame_callback = aCallback;
|
||||
m_receiver_frame_context = aCallbackContext;
|
||||
m_receive_frame_buffer = &aFrameBuffer;
|
||||
m_hdlc_decoder.Init(aFrameBuffer, HandleHdlcFrame, this);
|
||||
|
||||
return error;
|
||||
if (m_iid < 0 ||
|
||||
esp_radio_spinel_uart_transport_bind_rx(m_iid, aCallback, aCallbackContext, m_receive_frame_buffer) != ESP_OK) {
|
||||
return OT_ERROR_FAILED;
|
||||
}
|
||||
return OT_ERROR_NONE;
|
||||
}
|
||||
|
||||
void UartSpinelInterface::Deinit(void)
|
||||
{
|
||||
esp_radio_spinel_uart_transport_unbind_rx(m_iid);
|
||||
m_receiver_frame_callback = nullptr;
|
||||
m_receiver_frame_context = nullptr;
|
||||
m_receive_frame_buffer = nullptr;
|
||||
}
|
||||
|
||||
esp_err_t UartSpinelInterface::Enable(const esp_radio_spinel_uart_config_t &radio_uart_config)
|
||||
esp_err_t UartSpinelInterface::Enable(const esp_radio_spinel_uart_config_t &radio_uart_config,
|
||||
const esp_radio_spinel_uart_transport_hooks_t *hooks)
|
||||
{
|
||||
esp_err_t error = ESP_OK;
|
||||
|
||||
if (m_uart_fd != -1 || m_uart_rx_buffer != NULL) {
|
||||
if (m_wait_fd != -1) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
m_uart_rx_buffer = static_cast<uint8_t *>(heap_caps_calloc(1, kMaxFrameSize, MALLOC_CAP_8BIT));
|
||||
if (m_uart_rx_buffer == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
ESP_RETURN_ON_ERROR(esp_radio_spinel_uart_transport_open(&radio_uart_config, hooks, &m_iid, &m_wait_fd),
|
||||
ESP_SPINEL_LOG_TAG, "spinel UART transport open failed");
|
||||
if (m_receive_frame_buffer != nullptr) {
|
||||
esp_err_t err = esp_radio_spinel_uart_transport_bind_rx(
|
||||
m_iid, m_receiver_frame_callback, m_receiver_frame_context, m_receive_frame_buffer);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(ESP_SPINEL_LOG_TAG, "spinel UART transport bind failed");
|
||||
(void)esp_radio_spinel_uart_transport_close(m_iid);
|
||||
m_wait_fd = -1;
|
||||
m_iid = -1;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
error = InitUart(radio_uart_config);
|
||||
if (error == ESP_OK) {
|
||||
ESP_LOGI(ESP_SPINEL_LOG_TAG, "spinel UART interface initialization completed");
|
||||
} else {
|
||||
heap_caps_free(m_uart_rx_buffer);
|
||||
m_uart_rx_buffer = NULL;
|
||||
}
|
||||
return error;
|
||||
ESP_LOGI(ESP_SPINEL_LOG_TAG, "spinel UART interface initialization completed, iid=%d", m_iid);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if CONFIG_OPENTHREAD_RADIO_SPINEL_UART
|
||||
// NOTE: This overload bridges config types; keep field-wise copy and avoid storing references/pointers to the input to prevent potential lifetime-related memory risks.
|
||||
esp_err_t UartSpinelInterface::Enable(const esp_openthread_uart_config_t &radio_uart_config)
|
||||
esp_err_t UartSpinelInterface::Enable(const esp_openthread_uart_config_t &radio_uart_config,
|
||||
const esp_radio_spinel_uart_transport_hooks_t *hooks)
|
||||
{
|
||||
esp_radio_spinel_uart_config_t spinel_uart_config = {
|
||||
.port = radio_uart_config.port,
|
||||
@@ -115,143 +90,54 @@ esp_err_t UartSpinelInterface::Enable(const esp_openthread_uart_config_t &radio_
|
||||
.rx_pin = radio_uart_config.rx_pin,
|
||||
.tx_pin = radio_uart_config.tx_pin,
|
||||
};
|
||||
return Enable(spinel_uart_config);
|
||||
return Enable(spinel_uart_config, hooks);
|
||||
}
|
||||
#endif
|
||||
|
||||
esp_err_t UartSpinelInterface::Disable(void)
|
||||
{
|
||||
if (m_uart_rx_buffer) {
|
||||
heap_caps_free(m_uart_rx_buffer);
|
||||
}
|
||||
m_uart_rx_buffer = NULL;
|
||||
|
||||
if (m_uart_fd == -1) {
|
||||
if (m_wait_fd == -1) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
return DeinitUart();
|
||||
esp_err_t err = esp_radio_spinel_uart_transport_close(m_iid);
|
||||
m_wait_fd = -1;
|
||||
m_iid = -1;
|
||||
return err;
|
||||
}
|
||||
|
||||
otError UartSpinelInterface::SendFrame(const uint8_t *frame, uint16_t length)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
encoder_buffer.Clear();
|
||||
ot::Hdlc::Encoder hdlc_encoder(encoder_buffer);
|
||||
|
||||
SuccessOrExit(error = hdlc_encoder.BeginFrame());
|
||||
SuccessOrExit(error = hdlc_encoder.Encode(frame, length));
|
||||
SuccessOrExit(error = hdlc_encoder.EndFrame());
|
||||
|
||||
SuccessOrExit(error = Write(encoder_buffer.GetFrame(), encoder_buffer.GetLength()));
|
||||
|
||||
exit:
|
||||
if (error != OT_ERROR_NONE) {
|
||||
ESP_LOGE(ESP_SPINEL_LOG_TAG, "send radio frame failed");
|
||||
} else {
|
||||
ssize_t rval = esp_radio_spinel_uart_transport_write(m_iid, frame, length);
|
||||
if (rval == static_cast<ssize_t>(length)) {
|
||||
ESP_LOGD(ESP_SPINEL_LOG_TAG, "sent radio frame");
|
||||
return OT_ERROR_NONE;
|
||||
}
|
||||
|
||||
return error;
|
||||
ESP_LOGE(ESP_SPINEL_LOG_TAG, "send radio frame failed");
|
||||
ESP_ERROR_CHECK(esp_radio_spinel_uart_transport_recover(m_iid));
|
||||
return OT_ERROR_FAILED;
|
||||
}
|
||||
|
||||
void UartSpinelInterface::Process(const void *aMainloopContext)
|
||||
int UartSpinelInterface::TryReadSpinel(void)
|
||||
{
|
||||
if (FD_ISSET(m_uart_fd, &((esp_radio_spinel_mainloop_context_t *)aMainloopContext)->read_fds)) {
|
||||
ESP_LOGD(ESP_SPINEL_LOG_TAG, "radio uart read event");
|
||||
TryReadAndDecode();
|
||||
}
|
||||
}
|
||||
|
||||
int UartSpinelInterface::TryReadAndDecode(void)
|
||||
{
|
||||
uint8_t buffer[UART_HW_FIFO_LEN(m_uart_config.port)];
|
||||
ssize_t rval;
|
||||
|
||||
do {
|
||||
rval = read(m_uart_fd, buffer, sizeof(buffer));
|
||||
if (rval > 0) {
|
||||
m_hdlc_decoder.Decode(buffer, static_cast<uint16_t>(rval));
|
||||
int rval = esp_radio_spinel_uart_transport_read(m_iid);
|
||||
if (rval < 0) {
|
||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
return 0;
|
||||
}
|
||||
} while (rval > 0);
|
||||
|
||||
if ((rval < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK)) {
|
||||
ESP_ERROR_CHECK(TryRecoverUart());
|
||||
ESP_ERROR_CHECK(esp_radio_spinel_uart_transport_recover(m_iid));
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
otError UartSpinelInterface::WaitForWritable(void)
|
||||
void UartSpinelInterface::Process(const void *aMainloopContext)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
struct timeval timeout = {kMaxWaitTime / MS_PER_S, (kMaxWaitTime % MS_PER_S) * US_PER_MS};
|
||||
uint64_t now = otPlatTimeGet();
|
||||
uint64_t end = now + kMaxWaitTime * US_PER_MS;
|
||||
fd_set write_fds;
|
||||
fd_set error_fds;
|
||||
int rval;
|
||||
|
||||
while (true) {
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(m_uart_fd, &write_fds);
|
||||
FD_SET(m_uart_fd, &error_fds);
|
||||
|
||||
rval = select(m_uart_fd + 1, NULL, &write_fds, &error_fds, &timeout);
|
||||
|
||||
if (rval > 0) {
|
||||
if (FD_ISSET(m_uart_fd, &write_fds)) {
|
||||
ExitNow();
|
||||
} else if (FD_ISSET(m_uart_fd, &error_fds)) {
|
||||
ExitNow(error = OT_ERROR_FAILED);
|
||||
}
|
||||
} else if ((rval < 0) && (errno != EINTR)) {
|
||||
ESP_ERROR_CHECK(TryRecoverUart());
|
||||
ExitNow(error = OT_ERROR_FAILED);
|
||||
}
|
||||
|
||||
now = otPlatTimeGet();
|
||||
|
||||
if (end > now) {
|
||||
uint64_t remain = end - now;
|
||||
|
||||
timeout.tv_sec = static_cast<time_t>(remain / 1000000);
|
||||
timeout.tv_usec = static_cast<suseconds_t>(remain % 1000000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
if (m_wait_fd < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
error = OT_ERROR_FAILED;
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError UartSpinelInterface::Write(const uint8_t *aFrame, uint16_t length)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
while (length) {
|
||||
ssize_t rval;
|
||||
|
||||
rval = write(m_uart_fd, aFrame, length);
|
||||
|
||||
if (rval > 0) {
|
||||
assert(rval <= length);
|
||||
length -= static_cast<uint16_t>(rval);
|
||||
aFrame += static_cast<uint16_t>(rval);
|
||||
continue;
|
||||
} else if (rval < 0) {
|
||||
ESP_ERROR_CHECK(TryRecoverUart());
|
||||
ExitNow(error = OT_ERROR_FAILED);
|
||||
}
|
||||
|
||||
SuccessOrExit(error = WaitForWritable());
|
||||
if (FD_ISSET(m_wait_fd, &((esp_radio_spinel_mainloop_context_t *)aMainloopContext)->read_fds)) {
|
||||
ESP_LOGD(ESP_SPINEL_LOG_TAG, "radio spinel read event");
|
||||
TryReadSpinel();
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError UartSpinelInterface::WaitForFrame(uint64_t timeout_us)
|
||||
@@ -262,27 +148,31 @@ otError UartSpinelInterface::WaitForFrame(uint64_t timeout_us)
|
||||
fd_set error_fds;
|
||||
int rval;
|
||||
|
||||
if (m_wait_fd < 0) {
|
||||
return OT_ERROR_FAILED;
|
||||
}
|
||||
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(m_uart_fd, &read_fds);
|
||||
FD_SET(m_uart_fd, &error_fds);
|
||||
FD_SET(m_wait_fd, &read_fds);
|
||||
FD_SET(m_wait_fd, &error_fds);
|
||||
|
||||
timeout.tv_sec = static_cast<time_t>(timeout_us / US_PER_S);
|
||||
timeout.tv_usec = static_cast<suseconds_t>(timeout_us % US_PER_S);
|
||||
|
||||
rval = select(m_uart_fd + 1, &read_fds, NULL, &error_fds, &timeout);
|
||||
rval = select(m_wait_fd + 1, &read_fds, NULL, &error_fds, &timeout);
|
||||
|
||||
if (rval > 0) {
|
||||
if (FD_ISSET(m_uart_fd, &read_fds)) {
|
||||
TryReadAndDecode();
|
||||
} else if (FD_ISSET(m_uart_fd, &error_fds)) {
|
||||
ESP_ERROR_CHECK(TryRecoverUart());
|
||||
if (FD_ISSET(m_wait_fd, &read_fds)) {
|
||||
TryReadSpinel();
|
||||
} else if (FD_ISSET(m_wait_fd, &error_fds)) {
|
||||
ESP_ERROR_CHECK(esp_radio_spinel_uart_transport_recover(m_iid));
|
||||
ExitNow(error = OT_ERROR_FAILED);
|
||||
}
|
||||
} else if (rval == 0) {
|
||||
ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT);
|
||||
} else {
|
||||
ESP_ERROR_CHECK(TryRecoverUart());
|
||||
ESP_ERROR_CHECK(esp_radio_spinel_uart_transport_recover(m_iid));
|
||||
ExitNow(error = OT_ERROR_FAILED);
|
||||
}
|
||||
|
||||
@@ -290,76 +180,10 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
void UartSpinelInterface::HandleHdlcFrame(void *context, otError error)
|
||||
{
|
||||
static_cast<UartSpinelInterface *>(context)->HandleHdlcFrame(error);
|
||||
}
|
||||
|
||||
void UartSpinelInterface::HandleHdlcFrame(otError error)
|
||||
{
|
||||
if (error == OT_ERROR_NONE) {
|
||||
ESP_LOGD(ESP_SPINEL_LOG_TAG, "received hdlc radio frame");
|
||||
m_receiver_frame_callback(m_receiver_frame_context);
|
||||
} else {
|
||||
ESP_LOGE(ESP_SPINEL_LOG_TAG, "dropping radio frame: %s", otThreadErrorToString(error));
|
||||
m_receive_frame_buffer->DiscardFrame();
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t UartSpinelInterface::InitUart(const esp_radio_spinel_uart_config_t &radio_uart_config)
|
||||
{
|
||||
if (mUartInitHandler) {
|
||||
m_uart_config = radio_uart_config;
|
||||
return mUartInitHandler(&m_uart_config, &m_uart_fd);
|
||||
} else {
|
||||
char uart_path[16];
|
||||
esp_err_t err = ESP_OK;
|
||||
|
||||
m_uart_config = radio_uart_config;
|
||||
ESP_RETURN_ON_ERROR(RadioSpinelUartInitPort(&radio_uart_config), ESP_SPINEL_LOG_TAG,
|
||||
"RadioSpinelUartInitPort failed");
|
||||
// We have a driver now installed so set up the read/write functions to use driver also.
|
||||
uart_vfs_dev_port_set_tx_line_endings(m_uart_config.port, ESP_LINE_ENDINGS_LF);
|
||||
uart_vfs_dev_port_set_rx_line_endings(m_uart_config.port, ESP_LINE_ENDINGS_LF);
|
||||
|
||||
snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", radio_uart_config.port);
|
||||
m_uart_fd = open(uart_path, O_RDWR | O_NONBLOCK);
|
||||
|
||||
if (m_uart_fd < 0) {
|
||||
err = uart_driver_delete(m_uart_config.port);
|
||||
ESP_RETURN_ON_ERROR(err, ESP_SPINEL_LOG_TAG, "uart_driver_delete failed after open");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t UartSpinelInterface::DeinitUart(void)
|
||||
{
|
||||
if (mUartDeinitHandler) {
|
||||
return mUartDeinitHandler(&m_uart_config, &m_uart_fd);
|
||||
} else {
|
||||
if (m_uart_fd != -1) {
|
||||
close(m_uart_fd);
|
||||
m_uart_fd = -1;
|
||||
return uart_driver_delete(m_uart_config.port);
|
||||
} else {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t UartSpinelInterface::TryRecoverUart(void)
|
||||
{
|
||||
ESP_RETURN_ON_ERROR(DeinitUart(), ESP_SPINEL_LOG_TAG, "DeInitUart failed");
|
||||
ESP_RETURN_ON_ERROR(InitUart(m_uart_config), ESP_SPINEL_LOG_TAG, "InitUart failed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
otError UartSpinelInterface::HardwareReset(void)
|
||||
{
|
||||
if (mRcpFailureHandler) {
|
||||
TryRecoverUart();
|
||||
ESP_ERROR_CHECK(esp_radio_spinel_uart_transport_recover(m_iid));
|
||||
mRcpFailureHandler();
|
||||
}
|
||||
return OT_ERROR_NONE;
|
||||
@@ -367,17 +191,20 @@ otError UartSpinelInterface::HardwareReset(void)
|
||||
|
||||
void UartSpinelInterface::UpdateFdSet(void *aMainloopContext)
|
||||
{
|
||||
// Register only READ events for radio UART and always wait
|
||||
// for a radio WRITE to complete.
|
||||
FD_SET(m_uart_fd, &((esp_radio_spinel_mainloop_context_t *)aMainloopContext)->read_fds);
|
||||
if (m_uart_fd > ((esp_radio_spinel_mainloop_context_t *)aMainloopContext)->max_fd) {
|
||||
((esp_radio_spinel_mainloop_context_t *)aMainloopContext)->max_fd = m_uart_fd;
|
||||
if (m_wait_fd < 0) {
|
||||
return;
|
||||
}
|
||||
auto *ctx = static_cast<esp_radio_spinel_mainloop_context_t *>(aMainloopContext);
|
||||
FD_SET(m_wait_fd, &ctx->read_fds);
|
||||
if (m_wait_fd > ctx->max_fd) {
|
||||
ctx->max_fd = m_wait_fd;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t UartSpinelInterface::GetBusSpeed(void) const
|
||||
{
|
||||
return m_uart_config.uart_config.baud_rate;
|
||||
return esp_radio_spinel_uart_transport_get_bus_speed(m_iid);
|
||||
}
|
||||
|
||||
} // namespace radio_spinel
|
||||
} // namespace esp
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_radio_spinel_uart_transport.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "driver/uart.h"
|
||||
#include "driver/uart_vfs.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_openthread_common_macro.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "hdlc.hpp"
|
||||
#include "lib/spinel/spinel_interface.hpp"
|
||||
#include "openthread/error.h"
|
||||
#include "openthread/platform/time.h"
|
||||
|
||||
static const char *TAG = ESP_SPINEL_LOG_TAG;
|
||||
|
||||
static constexpr uint16_t kMaxFrameSize = ot::Spinel::SpinelInterface::kMaxFrameSize;
|
||||
static constexpr uint32_t kMaxWaitTimeMs = 2000;
|
||||
|
||||
static esp_radio_spinel_uart_config_t s_uart_config;
|
||||
static esp_radio_spinel_uart_transport_hooks_t s_hooks;
|
||||
static int s_uart_fd = -1;
|
||||
static int *s_wait_fd_out = nullptr;
|
||||
|
||||
static ot::Spinel::FrameBuffer<kMaxFrameSize> s_encoder_buffer;
|
||||
static ot::Hdlc::Decoder s_hdlc_decoder;
|
||||
static ot::Spinel::SpinelInterface::RxFrameBuffer *s_rx_buffer = nullptr;
|
||||
static ot::Spinel::SpinelInterface::ReceiveFrameCallback s_rx_callback = nullptr;
|
||||
static void *s_rx_context = nullptr;
|
||||
|
||||
static void handle_hdlc_frame(void *context, otError error)
|
||||
{
|
||||
(void)context;
|
||||
if (error == OT_ERROR_NONE) {
|
||||
if (s_rx_callback != nullptr) {
|
||||
ESP_LOGD(TAG, "received spinel frame");
|
||||
s_rx_callback(s_rx_context);
|
||||
} else if (s_rx_buffer != nullptr) {
|
||||
s_rx_buffer->DiscardFrame();
|
||||
}
|
||||
return;
|
||||
}
|
||||
ESP_LOGE(TAG, "dropping radio frame: %s", otThreadErrorToString(error));
|
||||
if (s_rx_buffer != nullptr) {
|
||||
s_rx_buffer->DiscardFrame();
|
||||
}
|
||||
}
|
||||
|
||||
static void decoder_attach(void)
|
||||
{
|
||||
if (s_rx_buffer == nullptr) {
|
||||
return;
|
||||
}
|
||||
s_hdlc_decoder.Init(*s_rx_buffer, handle_hdlc_frame, nullptr);
|
||||
}
|
||||
|
||||
static esp_err_t uart_init_port(const esp_radio_spinel_uart_config_t *config)
|
||||
{
|
||||
char uart_path[16];
|
||||
snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", config->port);
|
||||
bool is_uart_registered = (access(uart_path, F_OK) == 0);
|
||||
if (!is_uart_registered) {
|
||||
uart_vfs_dev_register();
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_ERROR(uart_param_config(config->port, &config->uart_config), TAG, "uart_param_config failed");
|
||||
ESP_RETURN_ON_ERROR(
|
||||
uart_set_pin(config->port, config->tx_pin, config->rx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE), TAG,
|
||||
"uart_set_pin failed");
|
||||
ESP_RETURN_ON_ERROR(uart_driver_install(config->port, CONFIG_OPENTHREAD_SPINEL_UART_DRIVER_BUFFER_SIZE, 0, 0, NULL, 0),
|
||||
TAG, "uart_driver_install failed");
|
||||
uart_vfs_dev_use_driver(config->port);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t uart_open(void)
|
||||
{
|
||||
if (s_hooks.uart_init) {
|
||||
return s_hooks.uart_init(&s_uart_config, &s_uart_fd);
|
||||
}
|
||||
|
||||
char uart_path[16];
|
||||
ESP_RETURN_ON_ERROR(uart_init_port(&s_uart_config), TAG, "uart_init_port failed");
|
||||
uart_vfs_dev_port_set_tx_line_endings(s_uart_config.port, ESP_LINE_ENDINGS_LF);
|
||||
uart_vfs_dev_port_set_rx_line_endings(s_uart_config.port, ESP_LINE_ENDINGS_LF);
|
||||
|
||||
snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", s_uart_config.port);
|
||||
s_uart_fd = open(uart_path, O_RDWR | O_NONBLOCK);
|
||||
if (s_uart_fd < 0) {
|
||||
(void)uart_driver_delete(s_uart_config.port);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t uart_close(void)
|
||||
{
|
||||
if (s_hooks.uart_deinit) {
|
||||
return s_hooks.uart_deinit(&s_uart_config, &s_uart_fd);
|
||||
}
|
||||
if (s_uart_fd == -1) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
close(s_uart_fd);
|
||||
s_uart_fd = -1;
|
||||
return uart_driver_delete(s_uart_config.port);
|
||||
}
|
||||
|
||||
static void publish_wait_fd(void)
|
||||
{
|
||||
if (s_wait_fd_out) {
|
||||
*s_wait_fd_out = s_uart_fd;
|
||||
}
|
||||
}
|
||||
|
||||
static otError wait_for_writable(void)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
struct timeval timeout = {static_cast<time_t>(kMaxWaitTimeMs / MS_PER_S),
|
||||
static_cast<suseconds_t>((kMaxWaitTimeMs % MS_PER_S) * US_PER_MS)};
|
||||
uint64_t now = otPlatTimeGet();
|
||||
uint64_t end = now + static_cast<uint64_t>(kMaxWaitTimeMs) * US_PER_MS;
|
||||
|
||||
while (true) {
|
||||
fd_set write_fds;
|
||||
fd_set error_fds;
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(s_uart_fd, &write_fds);
|
||||
FD_SET(s_uart_fd, &error_fds);
|
||||
|
||||
int rval = select(s_uart_fd + 1, NULL, &write_fds, &error_fds, &timeout);
|
||||
if (rval > 0) {
|
||||
if (FD_ISSET(s_uart_fd, &write_fds)) {
|
||||
ExitNow();
|
||||
}
|
||||
if (FD_ISSET(s_uart_fd, &error_fds)) {
|
||||
ExitNow(error = OT_ERROR_FAILED);
|
||||
}
|
||||
} else if ((rval < 0) && (errno != EINTR)) {
|
||||
ExitNow(error = OT_ERROR_FAILED);
|
||||
}
|
||||
|
||||
now = otPlatTimeGet();
|
||||
if (end > now) {
|
||||
uint64_t remain = end - now;
|
||||
timeout.tv_sec = static_cast<time_t>(remain / 1000000);
|
||||
timeout.tv_usec = static_cast<suseconds_t>(remain % 1000000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
error = OT_ERROR_FAILED;
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
static otError uart_write_bytes(const uint8_t *frame, uint16_t length)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
while (length) {
|
||||
ssize_t rval = write(s_uart_fd, frame, length);
|
||||
if (rval > 0) {
|
||||
assert(rval <= length);
|
||||
length -= static_cast<uint16_t>(rval);
|
||||
frame += static_cast<uint16_t>(rval);
|
||||
continue;
|
||||
}
|
||||
if (rval < 0) {
|
||||
ExitNow(error = OT_ERROR_FAILED);
|
||||
}
|
||||
SuccessOrExit(error = wait_for_writable());
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_uart_transport_open(const esp_radio_spinel_uart_config_t *config,
|
||||
const esp_radio_spinel_uart_transport_hooks_t *hooks, int8_t *iid,
|
||||
int *wait_fd)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(config != nullptr && wait_fd != nullptr && iid != nullptr, ESP_ERR_INVALID_ARG, TAG,
|
||||
"invalid transport open args");
|
||||
ESP_RETURN_ON_FALSE(s_uart_fd == -1, ESP_ERR_INVALID_STATE, TAG, "uart transport already open");
|
||||
|
||||
*wait_fd = -1;
|
||||
s_uart_config = *config;
|
||||
memset(&s_hooks, 0, sizeof(s_hooks));
|
||||
if (hooks) {
|
||||
s_hooks = *hooks;
|
||||
}
|
||||
s_wait_fd_out = wait_fd;
|
||||
|
||||
ESP_RETURN_ON_ERROR(uart_open(), TAG, "uart open failed");
|
||||
*iid = 0;
|
||||
publish_wait_fd();
|
||||
ESP_LOGI(TAG, "spinel UART transport initialization completed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_uart_transport_close(int8_t iid)
|
||||
{
|
||||
(void)iid;
|
||||
esp_err_t err = ESP_OK;
|
||||
if (s_uart_fd != -1 || s_hooks.uart_deinit) {
|
||||
err = uart_close();
|
||||
}
|
||||
publish_wait_fd();
|
||||
s_wait_fd_out = nullptr;
|
||||
return err;
|
||||
}
|
||||
|
||||
int esp_radio_spinel_uart_transport_read(int8_t iid)
|
||||
{
|
||||
(void)iid;
|
||||
if (s_uart_fd < 0 || s_rx_buffer == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t buffer[UART_HW_FIFO_LEN(s_uart_config.port)];
|
||||
ssize_t rval = 0;
|
||||
do {
|
||||
rval = read(s_uart_fd, buffer, sizeof(buffer));
|
||||
if (rval > 0) {
|
||||
s_hdlc_decoder.Decode(buffer, static_cast<uint16_t>(rval));
|
||||
}
|
||||
} while (rval > 0);
|
||||
|
||||
if ((rval < 0) && (errno != EAGAIN) && (errno != EWOULDBLOCK)) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t esp_radio_spinel_uart_transport_write(int8_t iid, const void *buf, size_t len)
|
||||
{
|
||||
(void)iid;
|
||||
ESP_RETURN_ON_FALSE(buf != nullptr, (errno = EINVAL, -1), TAG, "invalid write buffer");
|
||||
ESP_RETURN_ON_FALSE(s_uart_fd >= 0, (errno = EBADF, -1), TAG, "uart transport is not open");
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (len > kMaxFrameSize) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
otError error = OT_ERROR_NONE;
|
||||
s_encoder_buffer.Clear();
|
||||
ot::Hdlc::Encoder hdlc_encoder(s_encoder_buffer);
|
||||
SuccessOrExit(error = hdlc_encoder.BeginFrame());
|
||||
SuccessOrExit(error = hdlc_encoder.Encode(static_cast<const uint8_t *>(buf), static_cast<uint16_t>(len)));
|
||||
SuccessOrExit(error = hdlc_encoder.EndFrame());
|
||||
SuccessOrExit(error = uart_write_bytes(s_encoder_buffer.GetFrame(), s_encoder_buffer.GetLength()));
|
||||
return static_cast<ssize_t>(len);
|
||||
|
||||
exit:
|
||||
return -1;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_uart_transport_recover(int8_t iid)
|
||||
{
|
||||
(void)iid;
|
||||
s_hdlc_decoder.Reset();
|
||||
ESP_RETURN_ON_ERROR(uart_close(), TAG, "uart close failed during recover");
|
||||
ESP_RETURN_ON_ERROR(uart_open(), TAG, "uart open failed during recover");
|
||||
decoder_attach();
|
||||
publish_wait_fd();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint32_t esp_radio_spinel_uart_transport_get_bus_speed(int8_t iid)
|
||||
{
|
||||
(void)iid;
|
||||
return s_uart_config.uart_config.baud_rate;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_uart_transport_bind_rx(int8_t iid,
|
||||
ot::Spinel::SpinelInterface::ReceiveFrameCallback callback,
|
||||
void *context,
|
||||
ot::Spinel::SpinelInterface::RxFrameBuffer *frame_buffer)
|
||||
{
|
||||
(void)iid;
|
||||
ESP_RETURN_ON_FALSE(frame_buffer != nullptr, ESP_ERR_INVALID_ARG, TAG, "invalid rx buffer");
|
||||
s_rx_callback = callback;
|
||||
s_rx_context = context;
|
||||
s_rx_buffer = frame_buffer;
|
||||
s_hdlc_decoder.Reset();
|
||||
decoder_attach();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void esp_radio_spinel_uart_transport_unbind_rx(int8_t iid)
|
||||
{
|
||||
(void)iid;
|
||||
s_rx_callback = nullptr;
|
||||
s_rx_context = nullptr;
|
||||
s_rx_buffer = nullptr;
|
||||
s_hdlc_decoder.Reset();
|
||||
}
|
||||
@@ -0,0 +1,650 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "driver/uart.h"
|
||||
#include "driver/uart_vfs.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_openthread_common_macro.h"
|
||||
#include "esp_radio_spinel_host.h"
|
||||
#include "esp_radio_spinel_multipan.h"
|
||||
#include "esp_radio_spinel_uart_transport.hpp"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "esp_vfs_eventfd.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
#include "hdlc.hpp"
|
||||
#include "lib/spinel/spinel.h"
|
||||
#include "lib/spinel/spinel_interface.hpp"
|
||||
#include "openthread/error.h"
|
||||
#include "openthread/platform/time.h"
|
||||
|
||||
static const char *TAG = "spinel_multipan";
|
||||
|
||||
#define MULTIPAN_RX_QUEUE_DEPTH 8
|
||||
#define MULTIPAN_WORKER_STACK_SIZE 8192
|
||||
#define MULTIPAN_WORKER_PRIORITY 6
|
||||
#define MULTIPAN_WRITE_TIMEOUT_MS 2000
|
||||
|
||||
static constexpr uint16_t kMaxSpinelFrame = ot::Spinel::SpinelInterface::kMaxFrameSize;
|
||||
|
||||
typedef struct {
|
||||
uint16_t len;
|
||||
uint8_t buf[kMaxSpinelFrame];
|
||||
} multipan_frame_msg_t;
|
||||
|
||||
typedef struct {
|
||||
bool in_use;
|
||||
int wait_fd;
|
||||
QueueHandle_t rx_queue;
|
||||
uint32_t drop_count;
|
||||
ot::Spinel::SpinelInterface::RxFrameBuffer *rx_buffer;
|
||||
ot::Spinel::SpinelInterface::ReceiveFrameCallback callback;
|
||||
void *callback_context;
|
||||
} multipan_slot_t;
|
||||
|
||||
static SemaphoreHandle_t s_uart_tx_lock = NULL;
|
||||
static SemaphoreHandle_t s_slots_lock = NULL;
|
||||
static bool s_multipan_inited = false;
|
||||
|
||||
static esp_radio_spinel_uart_config_t s_uart_config = {};
|
||||
|
||||
static int s_uart_port = -1;
|
||||
static int s_uart_fd = -1;
|
||||
static int s_uart_rx_stop_fd = -1;
|
||||
static TaskHandle_t s_uart_rx_task = NULL;
|
||||
static SemaphoreHandle_t s_uart_rx_exit_sem = NULL;
|
||||
|
||||
static ot::Spinel::FrameBuffer<kMaxSpinelFrame> s_hdlc_frame_buffer;
|
||||
static ot::Spinel::FrameBuffer<kMaxSpinelFrame> s_encoder_buffer;
|
||||
static ot::Hdlc::Decoder s_hdlc_decoder;
|
||||
|
||||
static uint8_t s_registered_count = 0;
|
||||
static multipan_slot_t s_slots[ESP_RADIO_SPINEL_HOST_INTERFACE_COUNT];
|
||||
|
||||
static bool iid_valid(int8_t iid)
|
||||
{
|
||||
return iid >= 0 && iid < ESP_RADIO_SPINEL_HOST_INTERFACE_COUNT;
|
||||
}
|
||||
|
||||
static esp_err_t wait_uart_writable(int uart_fd, uint32_t timeout_ms)
|
||||
{
|
||||
struct timeval timeout = {
|
||||
static_cast<time_t>(timeout_ms / MS_PER_S),
|
||||
static_cast<suseconds_t>((timeout_ms % MS_PER_S) * US_PER_MS),
|
||||
};
|
||||
uint64_t now = otPlatTimeGet();
|
||||
uint64_t end = now + static_cast<uint64_t>(timeout_ms) * US_PER_MS;
|
||||
|
||||
while (true) {
|
||||
fd_set write_fds;
|
||||
fd_set error_fds;
|
||||
FD_ZERO(&write_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(uart_fd, &write_fds);
|
||||
FD_SET(uart_fd, &error_fds);
|
||||
|
||||
int rval = select(uart_fd + 1, NULL, &write_fds, &error_fds, &timeout);
|
||||
if (rval > 0) {
|
||||
if (FD_ISSET(uart_fd, &write_fds)) {
|
||||
return ESP_OK;
|
||||
}
|
||||
if (FD_ISSET(uart_fd, &error_fds)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
} else if ((rval < 0) && (errno != EINTR)) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
now = otPlatTimeGet();
|
||||
if (end <= now) {
|
||||
break;
|
||||
}
|
||||
uint64_t remain = end - now;
|
||||
timeout.tv_sec = static_cast<time_t>(remain / US_PER_S);
|
||||
timeout.tv_usec = static_cast<suseconds_t>(remain % US_PER_S);
|
||||
}
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
static esp_err_t multipan_uart_open(void)
|
||||
{
|
||||
char uart_path[32];
|
||||
snprintf(uart_path, sizeof(uart_path), "/dev/uart/%d", s_uart_config.port);
|
||||
|
||||
bool is_uart_registered = (access(uart_path, F_OK) == 0);
|
||||
if (!is_uart_registered) {
|
||||
uart_vfs_dev_register();
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_ERROR(uart_param_config(s_uart_config.port, &s_uart_config.uart_config), TAG,
|
||||
"uart_param_config failed");
|
||||
ESP_RETURN_ON_ERROR(
|
||||
uart_set_pin(s_uart_config.port, s_uart_config.tx_pin, s_uart_config.rx_pin, UART_PIN_NO_CHANGE,
|
||||
UART_PIN_NO_CHANGE),
|
||||
TAG, "uart_set_pin failed");
|
||||
ESP_RETURN_ON_ERROR(uart_driver_install(s_uart_config.port, CONFIG_OPENTHREAD_SPINEL_UART_DRIVER_BUFFER_SIZE, 0, 0,
|
||||
NULL, 0),
|
||||
TAG, "uart_driver_install failed");
|
||||
uart_vfs_dev_use_driver(s_uart_config.port);
|
||||
uart_vfs_dev_port_set_tx_line_endings(s_uart_config.port, ESP_LINE_ENDINGS_LF);
|
||||
uart_vfs_dev_port_set_rx_line_endings(s_uart_config.port, ESP_LINE_ENDINGS_LF);
|
||||
|
||||
s_uart_fd = open(uart_path, O_RDWR | O_NONBLOCK);
|
||||
if (s_uart_fd < 0) {
|
||||
(void)uart_driver_delete(s_uart_config.port);
|
||||
ESP_LOGE(TAG, "open UART failed: %s", uart_path);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
s_uart_port = s_uart_config.port;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void multipan_uart_close(void)
|
||||
{
|
||||
if (s_uart_fd >= 0) {
|
||||
int uart_fd = s_uart_fd;
|
||||
s_uart_fd = -1;
|
||||
close(uart_fd);
|
||||
}
|
||||
if (s_uart_port >= 0) {
|
||||
(void)uart_driver_delete(static_cast<uart_port_t>(s_uart_port));
|
||||
s_uart_port = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t multipan_uart_write(const uint8_t *data, size_t size)
|
||||
{
|
||||
int uart_fd = s_uart_fd;
|
||||
ESP_RETURN_ON_FALSE(uart_fd >= 0, (errno = EIO, -1), TAG, "UART is not open");
|
||||
|
||||
const uint8_t *bytes = data;
|
||||
size_t remaining = size;
|
||||
uint64_t start_us = otPlatTimeGet();
|
||||
|
||||
while (remaining > 0) {
|
||||
ssize_t written = write(uart_fd, bytes, remaining);
|
||||
if (written > 0) {
|
||||
remaining -= static_cast<size_t>(written);
|
||||
bytes += written;
|
||||
continue;
|
||||
}
|
||||
if (written < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint64_t now = otPlatTimeGet();
|
||||
uint64_t elapsed_ms = (now - start_us) / US_PER_MS;
|
||||
ESP_RETURN_ON_FALSE(elapsed_ms < MULTIPAN_WRITE_TIMEOUT_MS, (errno = EIO, -1), TAG, "UART write timeout");
|
||||
ESP_RETURN_ON_FALSE(wait_uart_writable(uart_fd, MULTIPAN_WRITE_TIMEOUT_MS - static_cast<uint32_t>(elapsed_ms)) ==
|
||||
ESP_OK,
|
||||
(errno = EIO, -1), TAG, "UART not writable");
|
||||
}
|
||||
return static_cast<ssize_t>(size);
|
||||
}
|
||||
|
||||
static void multipan_signal_slot(multipan_slot_t *slot)
|
||||
{
|
||||
if (slot->wait_fd < 0) {
|
||||
return;
|
||||
}
|
||||
uint64_t one = 1;
|
||||
ssize_t w = write(slot->wait_fd, &one, sizeof(one));
|
||||
(void)w;
|
||||
}
|
||||
|
||||
static void multipan_enqueue_frame(int idx, const uint8_t *frame, uint16_t len)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(idx >= 0 && idx < ESP_RADIO_SPINEL_HOST_INTERFACE_COUNT, , TAG, "invalid client idx %d", idx);
|
||||
|
||||
multipan_slot_t *slot = &s_slots[idx];
|
||||
multipan_frame_msg_t msg = {};
|
||||
msg.len = len;
|
||||
memcpy(msg.buf, frame, len);
|
||||
|
||||
xSemaphoreTake(s_slots_lock, portMAX_DELAY);
|
||||
if (!slot->in_use || slot->rx_queue == NULL) {
|
||||
xSemaphoreGive(s_slots_lock);
|
||||
return;
|
||||
}
|
||||
if (xQueueSend(slot->rx_queue, &msg, 0) == pdTRUE) {
|
||||
multipan_signal_slot(slot);
|
||||
} else {
|
||||
slot->drop_count++;
|
||||
ESP_LOGW(TAG, "client %d rx queue full, dropped frame (drops=%lu)", idx, (unsigned long)slot->drop_count);
|
||||
}
|
||||
xSemaphoreGive(s_slots_lock);
|
||||
}
|
||||
|
||||
static void multipan_fanout_spinel_frame(const uint8_t *frame, uint16_t len, otError error)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(error == OT_ERROR_NONE, , TAG, "dropped invalid HDLC frame, error=%d", static_cast<int>(error));
|
||||
ESP_RETURN_ON_FALSE(len > 0 && len <= kMaxSpinelFrame, , TAG, "dropped decoded frame with invalid length=%u", len);
|
||||
|
||||
spinel_iid_t iid = SPINEL_HEADER_GET_IID(frame[0]);
|
||||
if (iid == SPINEL_HEADER_GET_IID(OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID)) {
|
||||
for (int i = 0; i < ESP_RADIO_SPINEL_HOST_INTERFACE_COUNT; i++) {
|
||||
multipan_enqueue_frame(i, frame, len);
|
||||
}
|
||||
} else if (iid < ESP_RADIO_SPINEL_HOST_INTERFACE_COUNT) {
|
||||
multipan_enqueue_frame(iid, frame, len);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "dropped frame with unsupported iid=%u", (unsigned)iid);
|
||||
}
|
||||
}
|
||||
|
||||
static void multipan_hdlc_frame_handler(void *context, otError error)
|
||||
{
|
||||
(void)context;
|
||||
multipan_fanout_spinel_frame(s_hdlc_frame_buffer.GetFrame(), s_hdlc_frame_buffer.GetLength(), error);
|
||||
s_hdlc_frame_buffer.Clear();
|
||||
}
|
||||
|
||||
static void multipan_uart_rx_task(void *context)
|
||||
{
|
||||
(void)context;
|
||||
uint8_t uart_rx_buf[256];
|
||||
const int uart_fd = s_uart_fd;
|
||||
const int stop_fd = s_uart_rx_stop_fd;
|
||||
|
||||
while (true) {
|
||||
if (stop_fd < 0 || uart_fd < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
fd_set read_fds;
|
||||
fd_set error_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_ZERO(&error_fds);
|
||||
FD_SET(uart_fd, &read_fds);
|
||||
FD_SET(uart_fd, &error_fds);
|
||||
FD_SET(stop_fd, &read_fds);
|
||||
int max_fd = (uart_fd > stop_fd) ? uart_fd : stop_fd;
|
||||
|
||||
int ret = select(max_fd + 1, &read_fds, NULL, &error_fds, NULL);
|
||||
if (ret <= 0) {
|
||||
continue;
|
||||
}
|
||||
if (FD_ISSET(stop_fd, &read_fds)) {
|
||||
uint64_t stop_signal = 0;
|
||||
(void)read(stop_fd, &stop_signal, sizeof(stop_signal));
|
||||
break;
|
||||
}
|
||||
if (FD_ISSET(uart_fd, &error_fds)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ssize_t read_len = 0;
|
||||
do {
|
||||
read_len = read(uart_fd, uart_rx_buf, sizeof(uart_rx_buf));
|
||||
if (read_len <= 0) {
|
||||
break;
|
||||
}
|
||||
s_hdlc_decoder.Decode(uart_rx_buf, static_cast<uint16_t>(read_len));
|
||||
} while (read_len > 0);
|
||||
}
|
||||
|
||||
s_uart_rx_task = NULL;
|
||||
if (s_uart_rx_exit_sem != NULL) {
|
||||
xSemaphoreGive(s_uart_rx_exit_sem);
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static void multipan_uart_rx_stop(void)
|
||||
{
|
||||
if (s_uart_rx_task == NULL) {
|
||||
return;
|
||||
}
|
||||
uint64_t stop_signal = 1;
|
||||
(void)write(s_uart_rx_stop_fd, &stop_signal, sizeof(stop_signal));
|
||||
(void)xSemaphoreTake(s_uart_rx_exit_sem, portMAX_DELAY);
|
||||
}
|
||||
|
||||
static void multipan_locks_deinit(void)
|
||||
{
|
||||
if (s_uart_tx_lock != NULL) {
|
||||
vSemaphoreDelete(s_uart_tx_lock);
|
||||
s_uart_tx_lock = NULL;
|
||||
}
|
||||
if (s_slots_lock != NULL) {
|
||||
vSemaphoreDelete(s_slots_lock);
|
||||
s_slots_lock = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t multipan_locks_init(void)
|
||||
{
|
||||
s_uart_tx_lock = xSemaphoreCreateMutex();
|
||||
ESP_RETURN_ON_FALSE(s_uart_tx_lock != NULL, ESP_ERR_NO_MEM, TAG, "failed to create UART TX mutex");
|
||||
s_slots_lock = xSemaphoreCreateMutex();
|
||||
if (s_slots_lock == NULL) {
|
||||
vSemaphoreDelete(s_uart_tx_lock);
|
||||
s_uart_tx_lock = NULL;
|
||||
ESP_LOGE(TAG, "failed to create slots mutex");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void multipan_rx_deinit(void)
|
||||
{
|
||||
multipan_uart_rx_stop();
|
||||
if (s_uart_rx_stop_fd >= 0) {
|
||||
close(s_uart_rx_stop_fd);
|
||||
s_uart_rx_stop_fd = -1;
|
||||
}
|
||||
if (s_uart_rx_exit_sem != NULL) {
|
||||
vSemaphoreDelete(s_uart_rx_exit_sem);
|
||||
s_uart_rx_exit_sem = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t multipan_rx_init(void)
|
||||
{
|
||||
s_uart_rx_exit_sem = xSemaphoreCreateBinary();
|
||||
ESP_RETURN_ON_FALSE(s_uart_rx_exit_sem != NULL, ESP_ERR_NO_MEM, TAG, "failed to create worker exit sem");
|
||||
s_uart_rx_stop_fd = eventfd(0, 0);
|
||||
ESP_RETURN_ON_FALSE(s_uart_rx_stop_fd >= 0, ESP_FAIL, TAG, "failed to create worker stop fd");
|
||||
|
||||
s_hdlc_frame_buffer.Clear();
|
||||
s_hdlc_decoder.Init(s_hdlc_frame_buffer, multipan_hdlc_frame_handler, NULL);
|
||||
|
||||
s_uart_rx_task = NULL;
|
||||
ESP_RETURN_ON_FALSE(xTaskCreate(multipan_uart_rx_task, "ot_mpan_uart", MULTIPAN_WORKER_STACK_SIZE, NULL,
|
||||
MULTIPAN_WORKER_PRIORITY, &s_uart_rx_task) == pdPASS,
|
||||
ESP_ERR_NO_MEM, TAG, "failed to create multipan worker");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void multipan_slots_reset(void)
|
||||
{
|
||||
for (int i = 0; i < ESP_RADIO_SPINEL_HOST_INTERFACE_COUNT; i++) {
|
||||
s_slots[i].wait_fd = -1;
|
||||
s_slots[i].rx_queue = NULL;
|
||||
s_slots[i].in_use = false;
|
||||
s_slots[i].drop_count = 0;
|
||||
s_slots[i].rx_buffer = nullptr;
|
||||
s_slots[i].callback = nullptr;
|
||||
s_slots[i].callback_context = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static void multipan_slot_release(int idx)
|
||||
{
|
||||
multipan_slot_t *slot = &s_slots[idx];
|
||||
if (slot->wait_fd >= 0) {
|
||||
close(slot->wait_fd);
|
||||
slot->wait_fd = -1;
|
||||
}
|
||||
if (slot->rx_queue != NULL) {
|
||||
vQueueDelete(slot->rx_queue);
|
||||
slot->rx_queue = NULL;
|
||||
}
|
||||
slot->in_use = false;
|
||||
slot->drop_count = 0;
|
||||
slot->rx_buffer = nullptr;
|
||||
slot->callback = nullptr;
|
||||
slot->callback_context = nullptr;
|
||||
if (s_registered_count > 0) {
|
||||
s_registered_count--;
|
||||
}
|
||||
}
|
||||
|
||||
static void multipan_host_channel_deinit(QueueHandle_t *rx_queue, int *event_fd)
|
||||
{
|
||||
if (event_fd != NULL && *event_fd >= 0) {
|
||||
close(*event_fd);
|
||||
*event_fd = -1;
|
||||
}
|
||||
if (rx_queue != NULL && *rx_queue != NULL) {
|
||||
vQueueDelete(*rx_queue);
|
||||
*rx_queue = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t multipan_host_channel_init(QueueHandle_t *rx_queue, int *event_fd)
|
||||
{
|
||||
*rx_queue = xQueueCreate(MULTIPAN_RX_QUEUE_DEPTH, sizeof(multipan_frame_msg_t));
|
||||
ESP_RETURN_ON_FALSE(*rx_queue != NULL, ESP_ERR_NO_MEM, TAG, "failed to create rx queue");
|
||||
*event_fd = eventfd(0, 0);
|
||||
if (*event_fd < 0) {
|
||||
vQueueDelete(*rx_queue);
|
||||
*rx_queue = NULL;
|
||||
ESP_LOGE(TAG, "failed to create client eventfd");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t multipan_slot_claim(QueueHandle_t rx_queue, int event_fd, int8_t *iid)
|
||||
{
|
||||
int8_t assigned = -1;
|
||||
|
||||
for (int8_t i = 0; i < ESP_RADIO_SPINEL_HOST_INTERFACE_COUNT; i++) {
|
||||
if (!s_slots[i].in_use) {
|
||||
assigned = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ESP_RETURN_ON_FALSE(assigned >= 0, ESP_ERR_NO_MEM, TAG, "no free spinel iid");
|
||||
|
||||
s_slots[assigned].in_use = true;
|
||||
s_slots[assigned].wait_fd = event_fd;
|
||||
s_slots[assigned].rx_queue = rx_queue;
|
||||
s_slots[assigned].drop_count = 0;
|
||||
s_slots[assigned].rx_buffer = nullptr;
|
||||
s_slots[assigned].callback = nullptr;
|
||||
s_slots[assigned].callback_context = nullptr;
|
||||
s_registered_count++;
|
||||
*iid = assigned;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_multipan_init(const esp_radio_spinel_multipan_radio_config_t *radio_config)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
ESP_RETURN_ON_FALSE(!s_multipan_inited, ESP_ERR_INVALID_STATE, TAG, "multipan already initialized");
|
||||
ESP_RETURN_ON_FALSE(radio_config != NULL, ESP_ERR_INVALID_ARG, TAG, "invalid radio config");
|
||||
ESP_RETURN_ON_FALSE(radio_config->radio_mode == RADIO_MODE_UART_RCP, ESP_ERR_NOT_SUPPORTED, TAG,
|
||||
"only RADIO_MODE_UART_RCP is currently supported");
|
||||
|
||||
ESP_GOTO_ON_ERROR(multipan_locks_init(), fail, TAG, "failed to init locks");
|
||||
|
||||
s_uart_config = radio_config->radio_uart_config;
|
||||
ESP_GOTO_ON_ERROR(multipan_uart_open(), fail, TAG, "failed to open UART");
|
||||
ESP_GOTO_ON_ERROR(multipan_rx_init(), fail, TAG, "failed to start RX task");
|
||||
multipan_slots_reset();
|
||||
|
||||
s_multipan_inited = true;
|
||||
return ESP_OK;
|
||||
|
||||
fail:
|
||||
multipan_rx_deinit();
|
||||
multipan_uart_close();
|
||||
memset(&s_uart_config, 0, sizeof(s_uart_config));
|
||||
multipan_locks_deinit();
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_multipan_deinit(void)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(s_multipan_inited, ESP_ERR_INVALID_STATE, TAG, "multipan not initialized");
|
||||
ESP_RETURN_ON_FALSE(s_registered_count == 0, ESP_ERR_INVALID_STATE, TAG,
|
||||
"deinit rejected: %u client(s) still open", s_registered_count);
|
||||
|
||||
s_multipan_inited = false;
|
||||
multipan_slots_reset();
|
||||
multipan_rx_deinit();
|
||||
multipan_uart_close();
|
||||
memset(&s_uart_config, 0, sizeof(s_uart_config));
|
||||
multipan_locks_deinit();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_uart_transport_open(const esp_radio_spinel_uart_config_t *config,
|
||||
const esp_radio_spinel_uart_transport_hooks_t *hooks, int8_t *iid,
|
||||
int *wait_fd)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
QueueHandle_t rx_queue = NULL;
|
||||
int event_fd = -1;
|
||||
|
||||
(void)hooks;
|
||||
(void)config;
|
||||
|
||||
ESP_RETURN_ON_FALSE(wait_fd != NULL && iid != NULL, ESP_ERR_INVALID_ARG, TAG, "invalid transport open args");
|
||||
ESP_RETURN_ON_FALSE(s_multipan_inited, ESP_ERR_INVALID_STATE, TAG, "multipan not initialized");
|
||||
|
||||
ESP_GOTO_ON_ERROR(multipan_host_channel_init(&rx_queue, &event_fd), fail, TAG, "failed to init host channel");
|
||||
xSemaphoreTake(s_slots_lock, portMAX_DELAY);
|
||||
ret = multipan_slot_claim(rx_queue, event_fd, iid);
|
||||
xSemaphoreGive(s_slots_lock);
|
||||
ESP_GOTO_ON_ERROR(ret, fail, TAG, "failed to claim spinel slot");
|
||||
|
||||
*wait_fd = event_fd;
|
||||
ESP_LOGI(TAG, "allocated spinel iid=%d", *iid);
|
||||
return ESP_OK;
|
||||
|
||||
fail:
|
||||
multipan_host_channel_deinit(&rx_queue, &event_fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_uart_transport_close(int8_t iid)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(iid_valid(iid), ESP_ERR_INVALID_ARG, TAG, "invalid iid");
|
||||
if (!s_multipan_inited) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
xSemaphoreTake(s_slots_lock, portMAX_DELAY);
|
||||
if (s_slots[iid].in_use) {
|
||||
multipan_slot_release(iid);
|
||||
}
|
||||
xSemaphoreGive(s_slots_lock);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int esp_radio_spinel_uart_transport_read(int8_t iid)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(iid_valid(iid), (errno = EINVAL, -1), TAG, "invalid iid");
|
||||
|
||||
multipan_slot_t *slot = &s_slots[iid];
|
||||
ESP_RETURN_ON_FALSE(slot->in_use && slot->wait_fd >= 0 && slot->rx_queue != NULL, (errno = EBADF, -1), TAG, "iid is not open");
|
||||
|
||||
uint64_t v = 0;
|
||||
(void)read(slot->wait_fd, &v, sizeof(v));
|
||||
|
||||
multipan_frame_msg_t msg = {};
|
||||
while (xQueueReceive(slot->rx_queue, &msg, 0) == pdTRUE) {
|
||||
if (slot->rx_buffer == nullptr || slot->callback == nullptr) {
|
||||
continue;
|
||||
}
|
||||
uint16_t max_len = slot->rx_buffer->GetFrameMaxLength();
|
||||
if (msg.len == 0 || msg.len > max_len) {
|
||||
ESP_LOGW(TAG, "dropping spinel frame: len=%u max=%u", msg.len, max_len);
|
||||
continue;
|
||||
}
|
||||
memcpy(slot->rx_buffer->GetFrame(), msg.buf, msg.len);
|
||||
otError error = slot->rx_buffer->SetLength(msg.len);
|
||||
if (error != OT_ERROR_NONE) {
|
||||
ESP_LOGE(TAG, "dropping spinel frame: %s", otThreadErrorToString(error));
|
||||
slot->rx_buffer->DiscardFrame();
|
||||
continue;
|
||||
}
|
||||
ESP_LOGD(TAG, "received spinel frame iid=%d", iid);
|
||||
slot->callback(slot->callback_context);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t esp_radio_spinel_uart_transport_write(int8_t iid, const void *buf, size_t len)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(iid_valid(iid) && buf != NULL, (errno = EINVAL, -1), TAG, "invalid write args");
|
||||
if (len == 0) {
|
||||
return 0;
|
||||
}
|
||||
if (len > kMaxSpinelFrame) {
|
||||
errno = EMSGSIZE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ESP_RETURN_ON_FALSE(s_slots[iid].in_use, (errno = EBADF, -1), TAG, "iid is not open");
|
||||
|
||||
otError error = OT_ERROR_NONE;
|
||||
ssize_t ret = -1;
|
||||
|
||||
xSemaphoreTake(s_uart_tx_lock, portMAX_DELAY);
|
||||
s_encoder_buffer.Clear();
|
||||
ot::Hdlc::Encoder hdlc_encoder(s_encoder_buffer);
|
||||
SuccessOrExit(error = hdlc_encoder.BeginFrame());
|
||||
SuccessOrExit(error = hdlc_encoder.Encode(static_cast<const uint8_t *>(buf), static_cast<uint16_t>(len)));
|
||||
SuccessOrExit(error = hdlc_encoder.EndFrame());
|
||||
ESP_GOTO_ON_FALSE(multipan_uart_write(s_encoder_buffer.GetFrame(), s_encoder_buffer.GetLength()) ==
|
||||
static_cast<ssize_t>(s_encoder_buffer.GetLength()),
|
||||
-1, exit, TAG, "UART write failed");
|
||||
ret = static_cast<ssize_t>(len);
|
||||
|
||||
exit:
|
||||
xSemaphoreGive(s_uart_tx_lock);
|
||||
if (error != OT_ERROR_NONE && ret < 0) {
|
||||
errno = EIO;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_uart_transport_recover(int8_t iid)
|
||||
{
|
||||
/* UART and HDLC belong to the RX task, shared by all hosts. Re-installing
|
||||
* the port from one iid would drop the other. Host wait_fd is an eventfd
|
||||
* and stays valid. RCP-level recovery is left to RadioSpinel. */
|
||||
(void)iid;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
uint32_t esp_radio_spinel_uart_transport_get_bus_speed(int8_t iid)
|
||||
{
|
||||
(void)iid;
|
||||
return s_uart_config.uart_config.baud_rate;
|
||||
}
|
||||
|
||||
esp_err_t esp_radio_spinel_uart_transport_bind_rx(int8_t iid,
|
||||
ot::Spinel::SpinelInterface::ReceiveFrameCallback callback,
|
||||
void *context,
|
||||
ot::Spinel::SpinelInterface::RxFrameBuffer *frame_buffer)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(iid_valid(iid), ESP_ERR_INVALID_ARG, TAG, "invalid iid");
|
||||
ESP_RETURN_ON_FALSE(frame_buffer != nullptr, ESP_ERR_INVALID_ARG, TAG, "invalid rx buffer");
|
||||
ESP_RETURN_ON_FALSE(s_slots[iid].in_use, ESP_ERR_INVALID_STATE, TAG, "iid is not open");
|
||||
|
||||
s_slots[iid].rx_buffer = frame_buffer;
|
||||
s_slots[iid].callback = callback;
|
||||
s_slots[iid].callback_context = context;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void esp_radio_spinel_uart_transport_unbind_rx(int8_t iid)
|
||||
{
|
||||
if (!iid_valid(iid)) {
|
||||
return;
|
||||
}
|
||||
s_slots[iid].rx_buffer = nullptr;
|
||||
s_slots[iid].callback = nullptr;
|
||||
s_slots[iid].callback_context = nullptr;
|
||||
}
|
||||
@@ -35,6 +35,14 @@ set(exclude_srcs
|
||||
"openthread/src/core/api/random_crypto_api.cpp"
|
||||
)
|
||||
|
||||
# SRC_DIRS is not recursive. Multipan host lives under src/spinel_multipan/.
|
||||
# Shared UartSpinelInterface is used for both single-stack and multipan.
|
||||
# Only one uart transport implementation is linked.
|
||||
if(CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE)
|
||||
list(APPEND src_dirs "src/spinel_multipan")
|
||||
list(APPEND exclude_srcs "src/spinel/esp_radio_spinel_uart_transport.cpp")
|
||||
endif()
|
||||
|
||||
# CLI sources
|
||||
if(CONFIG_OPENTHREAD_CLI)
|
||||
list(APPEND src_dirs
|
||||
@@ -51,16 +59,23 @@ if(CONFIG_OPENTHREAD_RADIO_NATIVE)
|
||||
"src/port/esp_spi_spinel_interface.cpp"
|
||||
"src/spinel/esp_radio_spinel.cpp"
|
||||
"src/spinel/esp_radio_spinel_uart_interface.cpp"
|
||||
"src/spinel/esp_radio_spinel_uart_transport.cpp"
|
||||
"src/spinel/esp_radio_spinel_custom.cpp")
|
||||
elseif(CONFIG_OPENTHREAD_RADIO_SPINEL_UART OR
|
||||
CONFIG_OPENTHREAD_RADIO_SPINEL_SPI OR
|
||||
CONFIG_OPENTHREAD_RADIO_SPINEL_CUSTOM)
|
||||
list(APPEND exclude_srcs
|
||||
"src/port/esp_openthread_radio.c"
|
||||
"src/port/esp_openthread_sleep.c"
|
||||
"src/spinel/esp_radio_spinel.cpp")
|
||||
"src/port/esp_openthread_sleep.c")
|
||||
# OT uses esp_openthread_radio_spinel.cpp. Zigbee still needs
|
||||
# esp_radio_spinel.cpp when the two stacks share one RCP.
|
||||
if(NOT CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE)
|
||||
list(APPEND exclude_srcs "src/spinel/esp_radio_spinel.cpp")
|
||||
endif()
|
||||
if(CONFIG_OPENTHREAD_RADIO_SPINEL_SPI OR CONFIG_OPENTHREAD_RADIO_SPINEL_CUSTOM)
|
||||
list(APPEND exclude_srcs "src/spinel/esp_radio_spinel_uart_interface.cpp")
|
||||
list(APPEND exclude_srcs
|
||||
"src/spinel/esp_radio_spinel_uart_interface.cpp"
|
||||
"src/spinel/esp_radio_spinel_uart_transport.cpp")
|
||||
endif()
|
||||
if(CONFIG_OPENTHREAD_RADIO_SPINEL_UART OR CONFIG_OPENTHREAD_RADIO_SPINEL_CUSTOM)
|
||||
list(APPEND exclude_srcs "src/port/esp_spi_spinel_interface.cpp")
|
||||
@@ -74,6 +89,7 @@ elseif(CONFIG_OPENTHREAD_RADIO_154_NONE)
|
||||
"src/port/esp_spi_spinel_interface.cpp"
|
||||
"src/spinel/esp_radio_spinel.cpp"
|
||||
"src/spinel/esp_radio_spinel_uart_interface.cpp"
|
||||
"src/spinel/esp_radio_spinel_uart_transport.cpp"
|
||||
"src/spinel/esp_radio_spinel_custom.cpp"
|
||||
"src/port/esp_openthread_radio.c"
|
||||
"src/port/esp_openthread_sleep.c")
|
||||
|
||||
@@ -19,6 +19,7 @@ set(spinel_srcs
|
||||
# ESP spinel sources
|
||||
src/spinel/esp_radio_spinel.cpp
|
||||
src/spinel/esp_radio_spinel_uart_interface.cpp
|
||||
src/spinel/esp_radio_spinel_uart_transport.cpp
|
||||
# ESP port (minimal)
|
||||
src/port/esp_openthread_alarm.c
|
||||
src/port/esp_openthread_logging.c
|
||||
|
||||
@@ -23,6 +23,8 @@ Application Examples
|
||||
|
||||
- :example:`openthread/ot_sleepy_device/light_sleep` demonstrates Thread Light-sleep function.
|
||||
|
||||
- :example:`openthread/ot_zb_multipan` demonstrates running OpenThread and Zigbee side by side on a host, sharing one 802.15.4 RCP via multipan.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
|
||||
@@ -23,6 +23,8 @@ Thread
|
||||
|
||||
- :example:`openthread/ot_sleepy_device/light_sleep` 演示了 Thread 浅睡眠功能。
|
||||
|
||||
- :example:`openthread/ot_zb_multipan` 演示了如何在同一块主机上同时运行 OpenThread 和 Zigbee,并通过 multipan 共用一颗 802.15.4 RCP。
|
||||
|
||||
API参考
|
||||
-------------
|
||||
|
||||
|
||||
@@ -84,3 +84,12 @@ examples/openthread/ot_trel:
|
||||
- if: IDF_TARGET not in ["esp32c6", "esp32s3"]
|
||||
reason: only test on esp32c6 and esp32s3
|
||||
<<: *openthread_dependencies
|
||||
|
||||
examples/openthread/ot_zb_multipan:
|
||||
enable:
|
||||
- if: SOC_WIFI_SUPPORTED == 1
|
||||
disable:
|
||||
- if: IDF_TARGET in ["esp32c2", "esp32s2", "esp32s31", "esp32c61"]
|
||||
temporary: true
|
||||
reason: OpenThread + Zigbee multipan example does not support esp32c2, esp32s2, esp32s31 and esp32c61 yet
|
||||
<<: *openthread_dependencies
|
||||
|
||||
7
examples/openthread/ot_zb_multipan/CMakeLists.txt
Normal file
7
examples/openthread/ot_zb_multipan/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# 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.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ot_zb_multipan)
|
||||
81
examples/openthread/ot_zb_multipan/README.md
Normal file
81
examples/openthread/ot_zb_multipan/README.md
Normal file
@@ -0,0 +1,81 @@
|
||||
| Supported Targets | ESP32 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- |
|
||||
|
||||
# OpenThread + Zigbee Multipan Example
|
||||
|
||||
This example starts the OpenThread and Zigbee stacks side-by-side on a single host SoC, sharing one 802.15.4 RCP over UART. The host runs both stacks; the RCP firmware multiplexes them via Spinel IIDs.
|
||||
|
||||
## What It Demonstrates
|
||||
|
||||
- Shared RCP backend initialization via `esp_radio_spinel_multipan_init()` before either stack starts. This opens the UART RCP once; OpenThread and Zigbee then attach as clients.
|
||||
- OpenThread stack startup via `esp_openthread_start()`
|
||||
- Zigbee 2.0 stack startup via `esp_zigbee_init()` + `esp_zigbee_start()`
|
||||
- Shared UART-RCP usage with OpenThread multipan (`CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE=y`)
|
||||
- Auto-start of the OpenThread Border Router on top of multipan
|
||||
|
||||
## Hardware Setup
|
||||
|
||||
You need two chips:
|
||||
|
||||
- Host chip running this example
|
||||
- 802.15.4 RCP chip running `examples/openthread/ot_rcp`, built with:
|
||||
- `CONFIG_IEEE802154_MULTI_PAN_ENABLE=y`
|
||||
- `CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE=y`
|
||||
|
||||
Default UART wiring:
|
||||
|
||||
| Host pin | RCP pin |
|
||||
| -------- | ------- |
|
||||
| `GPIO4` | `TX` |
|
||||
| `GPIO5` | `RX` |
|
||||
| `GND` | `GND` |
|
||||
|
||||
The current RCP cannot receive on two 802.15.4 channels at once, so Zigbee and OpenThread must use the same channel. This example forms Zigbee on channel 13. Shared UART pins/port/baud and OT/ZB defaults are in [`main/esp_multipan_config.h`](main/esp_multipan_config.h).
|
||||
|
||||
## Build and Flash
|
||||
|
||||
Flash the RCP first, then this example on the host:
|
||||
|
||||
```bash
|
||||
# RCP — enable CONFIG_IEEE802154_MULTI_PAN_ENABLE and CONFIG_OPENTHREAD_MULTIPAN_RCP_ENABLE
|
||||
cd $IDF_PATH/examples/openthread/ot_rcp
|
||||
idf.py set-target <rcp-target>
|
||||
idf.py menuconfig
|
||||
# Component config → IEEE 802.15.4 → Enable multi-pan feature for frame filter
|
||||
# Component config → OpenThread → Thread Core Features → Enable Multipan RCP
|
||||
idf.py build flash
|
||||
|
||||
# Host
|
||||
cd $IDF_PATH/examples/openthread/ot_zb_multipan
|
||||
idf.py set-target <your-target>
|
||||
idf.py build flash monitor
|
||||
```
|
||||
|
||||
## Default Behaviour
|
||||
|
||||
`sdkconfig.defaults` ships with:
|
||||
|
||||
- `CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE=y`
|
||||
- `CONFIG_OPENTHREAD_BORDER_ROUTER=y`
|
||||
- `CONFIG_ZB_ZCZR=y` (Zigbee coordinator/router)
|
||||
- `CONFIG_ZB_RADIO_SPINEL_UART=y` (Zigbee uses the shared UART RCP)
|
||||
|
||||
On boot you should see, in order:
|
||||
|
||||
1. `OpenThread stack started`.
|
||||
2. `Zigbee stack started` followed by `Zigbee network formed: pan_id=...`.
|
||||
|
||||
## Testing Zigbee Join
|
||||
|
||||
Flash another ESP32 (capable of 802.15.4) with `examples/zigbee/light_sample/HA_on_off_light`. After this host forms the Zigbee network and opens the network for 180 s, the light sample should join automatically.
|
||||
|
||||
Then bring up Wi-Fi and Thread the usual way:
|
||||
|
||||
```
|
||||
> ot wifi connect -s <ssid> -p <password>
|
||||
> ot dataset init new
|
||||
> ot dataset channel 13
|
||||
> ot dataset commit active
|
||||
> ot ifconfig up
|
||||
> ot thread start
|
||||
```
|
||||
4
examples/openthread/ot_zb_multipan/main/CMakeLists.txt
Normal file
4
examples/openthread/ot_zb_multipan/main/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "ot_zb_multipan.c"
|
||||
PRIV_REQUIRES openthread esp_netif nvs_flash vfs esp_timer
|
||||
esp_driver_uart mdns esp_wifi protocol_examples_common
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "driver/uart.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_openthread_types.h"
|
||||
#include "esp_zigbee.h"
|
||||
|
||||
#define ESP_ZIGBEE_ERROR_CHECK(err) ESP_ERROR_CHECK(esp_zigbee_err_to_esp(err))
|
||||
|
||||
/* Shared multipan-RCP UART parameters: keep OT and ZB in sync from a single
|
||||
* place to avoid drift between the two side-by-side configurations. */
|
||||
#define OT_ZB_SHARED_UART_PORT 1
|
||||
#define OT_ZB_SHARED_UART_BAUD 460800
|
||||
#define OT_ZB_SHARED_UART_RX_PIN 4
|
||||
#define OT_ZB_SHARED_UART_TX_PIN 5
|
||||
|
||||
#define ESP_ZIGBEE_PRIMARY_CHANNEL_MASK (1U << 13)
|
||||
#define ESP_ZIGBEE_SECONDARY_CHANNEL_MASK 0x07FFF800
|
||||
#define ESP_ZIGBEE_CUSTOM_GATEWAY_EP_ID 1
|
||||
#define ESP_ZIGBEE_MAX_CHILDREN 10
|
||||
#define ESP_ZIGBEE_STORAGE_PARTITION_NAME "nvs"
|
||||
|
||||
#define ESP_MANUFACTURER_NAME "\x09""ESPRESSIF"
|
||||
#define ESP_MODEL_IDENTIFIER "\x0e""OT_ZB_MULTIPAN"
|
||||
|
||||
#define ESP_MULTIPAN_DEFAULT_UART_RCP_CONFIG() \
|
||||
{ \
|
||||
.port = OT_ZB_SHARED_UART_PORT, \
|
||||
.uart_config = \
|
||||
{ \
|
||||
.baud_rate = OT_ZB_SHARED_UART_BAUD, \
|
||||
.data_bits = UART_DATA_8_BITS, \
|
||||
.parity = UART_PARITY_DISABLE, \
|
||||
.stop_bits = UART_STOP_BITS_1, \
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE, \
|
||||
.rx_flow_ctrl_thresh = 0, \
|
||||
.source_clk = UART_SCLK_DEFAULT, \
|
||||
}, \
|
||||
.rx_pin = OT_ZB_SHARED_UART_RX_PIN, \
|
||||
.tx_pin = OT_ZB_SHARED_UART_TX_PIN, \
|
||||
}
|
||||
|
||||
#define ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG() \
|
||||
{ \
|
||||
.radio_mode = RADIO_MODE_UART_RCP, \
|
||||
.radio_uart_config = ESP_MULTIPAN_DEFAULT_UART_RCP_CONFIG(), \
|
||||
}
|
||||
|
||||
#define ESP_OPENTHREAD_DEFAULT_HOST_CONFIG() \
|
||||
{ \
|
||||
.host_connection_mode = HOST_CONNECTION_MODE_NONE, \
|
||||
}
|
||||
|
||||
#define ESP_OPENTHREAD_DEFAULT_PORT_CONFIG() \
|
||||
{ \
|
||||
.storage_partition_name = "nvs", \
|
||||
.netif_queue_size = 10, \
|
||||
.task_queue_size = 10, \
|
||||
}
|
||||
|
||||
#define ESP_ZIGBEE_ZC_CONFIG() \
|
||||
{ \
|
||||
.device_type = EZB_NWK_DEVICE_TYPE_COORDINATOR, \
|
||||
.install_code_policy = false, \
|
||||
.zczr_config = { \
|
||||
.max_children = ESP_ZIGBEE_MAX_CHILDREN, \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define ESP_ZIGBEE_PLATFORM_CONFIG() \
|
||||
{ \
|
||||
.storage_partition_name = ESP_ZIGBEE_STORAGE_PARTITION_NAME, \
|
||||
.radio_config = { \
|
||||
.radio_mode = ESP_ZIGBEE_RADIO_MODE_UART_RCP, \
|
||||
.radio_uart_config = ESP_MULTIPAN_DEFAULT_UART_RCP_CONFIG(), \
|
||||
}, \
|
||||
}
|
||||
|
||||
#define ESP_ZIGBEE_DEFAULT_CONFIG() \
|
||||
{ \
|
||||
.device_config = ESP_ZIGBEE_ZC_CONFIG(), \
|
||||
.platform_config = ESP_ZIGBEE_PLATFORM_CONFIG(), \
|
||||
}
|
||||
15
examples/openthread/ot_zb_multipan/main/idf_component.yml
Normal file
15
examples/openthread/ot_zb_multipan/main/idf_component.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
espressif/esp_ot_cli_extension:
|
||||
version: "~2.0.0"
|
||||
espressif/mdns: "^1.0.3"
|
||||
espressif/esp-zigbee-lib: "~2.0.0"
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: ">=5.2"
|
||||
protocol_examples_common:
|
||||
path: ${IDF_PATH}/examples/common_components/protocol_examples_common
|
||||
ot_examples_common:
|
||||
path: ${IDF_PATH}/examples/openthread/ot_common_components/ot_examples_common
|
||||
ot_examples_br:
|
||||
path: ${IDF_PATH}/examples/openthread/ot_common_components/ot_examples_br
|
||||
221
examples/openthread/ot_zb_multipan/main/ot_zb_multipan.c
Normal file
221
examples/openthread/ot_zb_multipan/main/ot_zb_multipan.c
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_check.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_openthread.h"
|
||||
#include "esp_openthread_lock.h"
|
||||
#include "esp_radio_spinel_multipan.h"
|
||||
#include "esp_openthread_netif_glue.h"
|
||||
#include "esp_openthread_spinel.h"
|
||||
#include "esp_openthread_types.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_vfs_eventfd.h"
|
||||
#include "esp_zigbee.h"
|
||||
#include "ezbee/zha.h"
|
||||
#include "mdns.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "ot_examples_common.h"
|
||||
#include "esp_multipan_config.h"
|
||||
#if CONFIG_OPENTHREAD_BORDER_ROUTER && CONFIG_OPENTHREAD_NETWORK_AUTO_START
|
||||
#include "ot_examples_br.h"
|
||||
#endif
|
||||
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
#include "esp_ot_cli_extension.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "ot_zb_multipan";
|
||||
|
||||
static esp_timer_handle_t s_zb_retry_timer;
|
||||
static uint8_t s_zb_retry_mode;
|
||||
|
||||
static void zb_retry_timer_cb(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
if (esp_zigbee_lock_acquire(portMAX_DELAY)) {
|
||||
(void)ezb_bdb_start_top_level_commissioning(s_zb_retry_mode);
|
||||
esp_zigbee_lock_release();
|
||||
}
|
||||
}
|
||||
|
||||
static void zb_schedule_commissioning(uint8_t mode, uint32_t delay_ms)
|
||||
{
|
||||
s_zb_retry_mode = mode;
|
||||
if (s_zb_retry_timer == NULL) {
|
||||
const esp_timer_create_args_t args = {
|
||||
.callback = zb_retry_timer_cb,
|
||||
.name = "zb_retry",
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&args, &s_zb_retry_timer));
|
||||
} else {
|
||||
esp_timer_stop(s_zb_retry_timer);
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_timer_start_once(s_zb_retry_timer, (uint64_t)delay_ms * 1000));
|
||||
}
|
||||
|
||||
static bool zb_app_signal_handler(const ezb_app_signal_t *app_signal)
|
||||
{
|
||||
ezb_app_signal_type_t signal_type = ezb_app_signal_get_type(app_signal);
|
||||
|
||||
switch (signal_type) {
|
||||
case EZB_ZDO_SIGNAL_SKIP_STARTUP:
|
||||
ESP_LOGI(TAG, "Initialize Zigbee stack");
|
||||
ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_INITIALIZATION);
|
||||
break;
|
||||
case EZB_BDB_SIGNAL_DEVICE_FIRST_START:
|
||||
case EZB_BDB_SIGNAL_DEVICE_REBOOT: {
|
||||
ezb_bdb_comm_status_t status = *((ezb_bdb_comm_status_t *)ezb_app_signal_get_params(app_signal));
|
||||
if (status == EZB_BDB_STATUS_SUCCESS) {
|
||||
ESP_LOGI(TAG, "Zigbee started (%s factory-new)", ezb_bdb_is_factory_new() ? "" : "not");
|
||||
if (ezb_bdb_is_factory_new()) {
|
||||
ESP_ZIGBEE_ERROR_CHECK(ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_FORMATION));
|
||||
} else {
|
||||
ESP_ZIGBEE_ERROR_CHECK(ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_STEERING));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Zigbee start failed: status(0x%02x)", status);
|
||||
zb_schedule_commissioning(EZB_BDB_MODE_INITIALIZATION, 1000);
|
||||
}
|
||||
} break;
|
||||
case EZB_BDB_SIGNAL_FORMATION: {
|
||||
ezb_bdb_comm_status_t status = *((ezb_bdb_comm_status_t *)ezb_app_signal_get_params(app_signal));
|
||||
if (status == EZB_BDB_STATUS_SUCCESS) {
|
||||
ezb_extpanid_t extended_pan_id;
|
||||
ezb_nwk_get_extended_panid(&extended_pan_id);
|
||||
ESP_LOGI(TAG, "Zigbee network formed: pan_id=0x%04hx ext=0x%llx channel=%d",
|
||||
ezb_nwk_get_panid(), extended_pan_id.u64, ezb_nwk_get_current_channel());
|
||||
ezb_bdb_start_top_level_commissioning(EZB_BDB_MODE_NETWORK_STEERING);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Zigbee formation retry: status(0x%02x)", status);
|
||||
zb_schedule_commissioning(EZB_BDB_MODE_NETWORK_FORMATION, 1000);
|
||||
}
|
||||
} break;
|
||||
case EZB_BDB_SIGNAL_STEERING: {
|
||||
ezb_bdb_comm_status_t status = *((ezb_bdb_comm_status_t *)ezb_app_signal_get_params(app_signal));
|
||||
if (status == EZB_BDB_STATUS_SUCCESS) {
|
||||
ESP_LOGI(TAG, "Zigbee network steering completed");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Zigbee steering failed: status(0x%02x)", status);
|
||||
}
|
||||
} break;
|
||||
case EZB_ZDO_SIGNAL_DEVICE_ANNCE: {
|
||||
const ezb_zdo_signal_device_annce_params_t *dev_annce_params = ezb_app_signal_get_params(app_signal);
|
||||
ESP_LOGI(TAG, "Zigbee device announced: short=0x%04hx", dev_annce_params->short_addr);
|
||||
} break;
|
||||
case EZB_NWK_SIGNAL_PERMIT_JOIN_STATUS: {
|
||||
uint8_t duration = *(uint8_t *)ezb_app_signal_get_params(app_signal);
|
||||
if (duration) {
|
||||
ESP_LOGI(TAG, "Zigbee network open for %d seconds", duration);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Zigbee network closed");
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "Zigbee signal: %s (0x%x)", ezb_app_signal_to_string(signal_type), signal_type);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static esp_err_t zb_create_gateway_device(void)
|
||||
{
|
||||
ezb_af_device_desc_t dev_desc = ezb_af_create_device_desc();
|
||||
ezb_zha_custom_gateway_config_t gateway_cfg = EZB_ZHA_CUSTOM_GATEWAY_CONFIG();
|
||||
ezb_af_ep_desc_t ep_desc = ezb_zha_create_custom_gateway(ESP_ZIGBEE_CUSTOM_GATEWAY_EP_ID, &gateway_cfg);
|
||||
ezb_zcl_cluster_desc_t basic_desc = ezb_af_endpoint_get_cluster_desc(ep_desc, EZB_ZCL_CLUSTER_ID_BASIC, EZB_ZCL_CLUSTER_SERVER);
|
||||
|
||||
ESP_ZIGBEE_ERROR_CHECK(
|
||||
ezb_zcl_basic_cluster_desc_add_attr(basic_desc, EZB_ZCL_ATTR_BASIC_MANUFACTURER_NAME_ID, (void *)ESP_MANUFACTURER_NAME));
|
||||
ESP_ZIGBEE_ERROR_CHECK(
|
||||
ezb_zcl_basic_cluster_desc_add_attr(basic_desc, EZB_ZCL_ATTR_BASIC_MODEL_IDENTIFIER_ID, (void *)ESP_MODEL_IDENTIFIER));
|
||||
ESP_ZIGBEE_ERROR_CHECK(ezb_af_device_add_endpoint_desc(dev_desc, ep_desc));
|
||||
ESP_ZIGBEE_ERROR_CHECK(ezb_af_device_desc_register(dev_desc));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void zigbee_task(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
esp_zigbee_config_t zb_config = ESP_ZIGBEE_DEFAULT_CONFIG();
|
||||
|
||||
ESP_ERROR_CHECK(esp_zigbee_init(&zb_config));
|
||||
ezb_aps_secur_enable_distributed_security(false);
|
||||
ESP_ZIGBEE_ERROR_CHECK(ezb_bdb_set_primary_channel_set(ESP_ZIGBEE_PRIMARY_CHANNEL_MASK));
|
||||
ESP_ZIGBEE_ERROR_CHECK(ezb_bdb_set_secondary_channel_set(ESP_ZIGBEE_SECONDARY_CHANNEL_MASK));
|
||||
ESP_ZIGBEE_ERROR_CHECK(ezb_app_signal_add_handler(zb_app_signal_handler));
|
||||
ESP_ERROR_CHECK(zb_create_gateway_device());
|
||||
ESP_ERROR_CHECK(esp_zigbee_start(false));
|
||||
ESP_LOGI(TAG, "Zigbee stack started");
|
||||
esp_zigbee_launch_mainloop();
|
||||
esp_zigbee_deinit();
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Used eventfds:
|
||||
* 1: lwip netif
|
||||
* 1: OpenThread task queue
|
||||
* 1: OpenThread border router
|
||||
* 1: Zigbee task queue
|
||||
* 1: multipan UART RX worker stop fd
|
||||
* 2: one per multipan client (OT + ZB)
|
||||
* +1 when TREL is enabled */
|
||||
size_t max_eventfd = 7;
|
||||
#if CONFIG_OPENTHREAD_RADIO_TREL
|
||||
max_eventfd++;
|
||||
#endif
|
||||
esp_vfs_eventfd_config_t eventfd_config = {
|
||||
.max_fds = max_eventfd,
|
||||
};
|
||||
|
||||
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));
|
||||
ESP_ERROR_CHECK(mdns_init());
|
||||
ESP_ERROR_CHECK(mdns_hostname_set("esp-ot-zb-multipan"));
|
||||
static const esp_radio_spinel_multipan_radio_config_t multipan_radio_config = {
|
||||
.radio_mode = RADIO_MODE_UART_RCP,
|
||||
.radio_uart_config = ESP_MULTIPAN_DEFAULT_UART_RCP_CONFIG(),
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_radio_spinel_multipan_init(&multipan_radio_config));
|
||||
|
||||
#if CONFIG_OPENTHREAD_CLI
|
||||
ot_console_start();
|
||||
ot_register_external_commands();
|
||||
#endif
|
||||
|
||||
BaseType_t zb_task_ret = xTaskCreate(zigbee_task, "zigbee_task", 8192, NULL, 5, NULL);
|
||||
ESP_ERROR_CHECK(zb_task_ret == pdPASS ? ESP_OK : ESP_ERR_NO_MEM);
|
||||
|
||||
static esp_openthread_config_t ot_config = {
|
||||
.netif_config = ESP_NETIF_DEFAULT_OPENTHREAD(),
|
||||
.platform_config = {
|
||||
.radio_config = ESP_OPENTHREAD_DEFAULT_RADIO_CONFIG(),
|
||||
.host_config = ESP_OPENTHREAD_DEFAULT_HOST_CONFIG(),
|
||||
.port_config = ESP_OPENTHREAD_DEFAULT_PORT_CONFIG(),
|
||||
},
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_openthread_start(&ot_config));
|
||||
ESP_LOGI(TAG, "OpenThread stack started");
|
||||
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
esp_cli_custom_command_init();
|
||||
#endif
|
||||
#if CONFIG_OPENTHREAD_BORDER_ROUTER && CONFIG_OPENTHREAD_NETWORK_AUTO_START
|
||||
ESP_ERROR_CHECK(esp_openthread_border_router_start());
|
||||
#elif CONFIG_OPENTHREAD_NETWORK_AUTO_START
|
||||
ot_network_auto_start();
|
||||
#endif
|
||||
}
|
||||
5
examples/openthread/ot_zb_multipan/partitions.csv
Normal file
5
examples/openthread/ot_zb_multipan/partitions.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
# 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, , 0x6000,
|
||||
phy_init, data, phy, , 0x1000,
|
||||
factory, app, factory, , 3000K,
|
||||
|
72
examples/openthread/ot_zb_multipan/sdkconfig.defaults
Normal file
72
examples/openthread/ot_zb_multipan/sdkconfig.defaults
Normal file
@@ -0,0 +1,72 @@
|
||||
#
|
||||
# Partition Table
|
||||
#
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_MD5=y
|
||||
# end of Partition Table
|
||||
|
||||
#
|
||||
# mbedTLS
|
||||
#
|
||||
CONFIG_MBEDTLS_SSL_PROTO_DTLS=y
|
||||
CONFIG_MBEDTLS_KEY_EXCHANGE_ECJPAKE=y
|
||||
CONFIG_MBEDTLS_ECJPAKE_C=y
|
||||
# end of mbedTLS
|
||||
|
||||
#
|
||||
# OpenThread
|
||||
#
|
||||
CONFIG_OPENTHREAD_ENABLED=y
|
||||
CONFIG_OPENTHREAD_BORDER_ROUTER=y
|
||||
CONFIG_OPENTHREAD_RADIO_SPINEL_UART=y
|
||||
CONFIG_OPENTHREAD_SPINEL_ONLY=y
|
||||
CONFIG_OPENTHREAD_MULTIPAN_HOST_ENABLE=y
|
||||
CONFIG_OPENTHREAD_MULTIPLE_INTERFACES_COUNT=2
|
||||
CONFIG_OPENTHREAD_TASK_SIZE=10240
|
||||
CONFIG_OPENTHREAD_CONSOLE_ENABLE=n
|
||||
# end of OpenThread
|
||||
|
||||
#
|
||||
# Zigbee
|
||||
#
|
||||
CONFIG_ZB_ENABLED=y
|
||||
CONFIG_ZB_ZCZR=y
|
||||
CONFIG_ZB_RADIO_SPINEL_UART=y
|
||||
# end of Zigbee
|
||||
|
||||
#
|
||||
# lwIP
|
||||
#
|
||||
CONFIG_LWIP_IPV6_NUM_ADDRESSES=12
|
||||
CONFIG_LWIP_MULTICAST_PING=y
|
||||
CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
|
||||
CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y
|
||||
CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y
|
||||
CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM=y
|
||||
CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM=y
|
||||
CONFIG_LWIP_IPV6_AUTOCONFIG=y
|
||||
# end of lwIP
|
||||
|
||||
#
|
||||
# mDNS
|
||||
#
|
||||
CONFIG_MDNS_MULTIPLE_INSTANCE=y
|
||||
# end of mDNS
|
||||
|
||||
# Example connect
|
||||
CONFIG_EXAMPLE_CONNECT_WIFI=y
|
||||
CONFIG_EXAMPLE_CONNECT_THREAD=n
|
||||
|
||||
#
|
||||
# ESP System Settings
|
||||
#
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144
|
||||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
|
||||
# end of ESP System Settings
|
||||
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
Reference in New Issue
Block a user