ESP-NOW bridge for P4 (#573)

This commit is contained in:
Shadowtrance
2026-07-19 17:23:20 +10:00
committed by GitHub
parent bd30aa046a
commit 2d768ef3a1
40 changed files with 1343 additions and 182 deletions
@@ -4,7 +4,7 @@
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include "ChatState.h"
#include "ChatView.h"
@@ -43,4 +43,4 @@ public:
} // namespace tt::app::chat
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -4,7 +4,7 @@
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <cstddef>
#include <cstdint>
@@ -94,4 +94,4 @@ size_t getMaxMessageLength(size_t nicknameLen, size_t targetLen);
} // namespace tt::app::chat
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -4,7 +4,7 @@
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <array>
#include <cstdint>
@@ -28,4 +28,4 @@ bool settingsFileExists();
} // namespace tt::app::chat
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -4,7 +4,7 @@
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <Tactility/RecursiveMutex.h>
@@ -56,4 +56,4 @@ public:
} // namespace tt::app::chat
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -4,7 +4,7 @@
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include "ChatState.h"
#include "ChatSettings.h"
@@ -76,4 +76,4 @@ public:
} // namespace tt::app::chat
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -0,0 +1,32 @@
#pragma once
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <Tactility/service/espnow/EspNow.h>
namespace tt::service::espnow::backend {
/**
* Bring up the backend (WiFi if needed + ESP-NOW init + recv/send callbacks + PMK).
* Native backend calls esp_now_* directly; hosted backend bridges over esp_hosted's
* custom RPC channel to the co-processor. Either way, recvCallback is invoked with
* the same signature ESP-NOW itself uses.
*/
bool init(const EspNowConfig& config, esp_now_recv_cb_t recvCallback);
bool deinit();
bool addPeer(const esp_now_peer_info_t& peer);
bool send(const uint8_t* address, const uint8_t* buffer, size_t bufferLength);
/** Returns 0 if not initialized. */
uint32_t getVersion();
}
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -0,0 +1,33 @@
#pragma once
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <cstdint>
namespace tt::service::espnow::backend {
/**
* The esp_hosted SDIO transport to the co-processor is brought up by esp_hosted_init()
* (auto-called at process startup via a constructor attribute - always runs regardless of
* WiFi/BT state), but the actual slave handshake only happens via esp_hosted_connect_to_slave(),
* which today is only called as a side effect of the WiFi or BT stack starting
* (esp32_wifi.cpp / vhci_drv.c). Nothing in Tactility's boot sequence calls it on its own, so
* without this, ESP-NOW-over-bridge would require WiFi or BT to already be enabled - unlike
* native ESP32 chips where ESP-NOW works standalone.
*
* This actively triggers the slave connect (safe to call even if already connected - the
* underlying transport_drv_reconfigure() checks is_transport_tx_ready() first and returns
* immediately if so) and then polls until the link is confirmed up or the timeout expires.
*
* @param timeoutMs how long to poll after triggering the connect before giving up
* @return true once the transport is confirmed up, false on timeout
*/
bool waitForHostedTransport(uint32_t timeoutMs);
}
#endif // CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -4,7 +4,7 @@
#include <sdkconfig.h>
#endif
#ifdef CONFIG_ESP_WIFI_ENABLED
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
#include <Tactility/service/Service.h>
#include <Tactility/service/espnow/EspNow.h>
@@ -75,4 +75,4 @@ std::shared_ptr<EspNowService> findService();
}
#endif // ESP_PLATFORM
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
@@ -1,19 +0,0 @@
#pragma once
#ifdef ESP_PLATFORM
#include <sdkconfig.h>
#endif
#ifdef CONFIG_ESP_WIFI_ENABLED
#include "Tactility/service/espnow/EspNow.h"
namespace tt::service::espnow {
bool initWifi(const EspNowConfig& config);
bool deinitWifi();
}
#endif
@@ -0,0 +1,122 @@
/*
* SPDX-FileCopyrightText: 2026 Tactility
*
* SPDX-License-Identifier: Apache-2.0
*
* Wire format for bridging ESP-NOW across esp-hosted-mcu's custom RPC
* channel (esp_hosted_send_custom_data / esp_hosted_register_custom_callback).
* Shared verbatim between slave (runs real esp_now_*) and host (P4, no radio).
*
* Kept in sync by hand with the copy in the esp-hosted-mcu fork at
* common/espnow_bridge/esp_hosted_espnow_bridge_proto.h (Shadowtrance/esp-hosted-mcu,
* branch feature/espnow-bridge) — this is a wire-format contract between two repos.
*/
#ifndef __ESP_HOSTED_ESPNOW_BRIDGE_PROTO_H
#define __ESP_HOSTED_ESPNOW_BRIDGE_PROTO_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
/* msg_id namespace: any uint32_t except 0xFFFFFFFF (reserved by esp_hosted). */
#define ESPNOW_BRIDGE_MSG_BASE 0xE500U
#define ESPNOW_BRIDGE_REQ_INIT (ESPNOW_BRIDGE_MSG_BASE + 0)
#define ESPNOW_BRIDGE_RESP_INIT (ESPNOW_BRIDGE_MSG_BASE + 1)
#define ESPNOW_BRIDGE_REQ_DEINIT (ESPNOW_BRIDGE_MSG_BASE + 2)
#define ESPNOW_BRIDGE_RESP_DEINIT (ESPNOW_BRIDGE_MSG_BASE + 3)
#define ESPNOW_BRIDGE_REQ_ADD_PEER (ESPNOW_BRIDGE_MSG_BASE + 4)
#define ESPNOW_BRIDGE_RESP_ADD_PEER (ESPNOW_BRIDGE_MSG_BASE + 5)
#define ESPNOW_BRIDGE_REQ_SEND (ESPNOW_BRIDGE_MSG_BASE + 6)
#define ESPNOW_BRIDGE_RESP_SEND (ESPNOW_BRIDGE_MSG_BASE + 7)
#define ESPNOW_BRIDGE_EVT_RECV (ESPNOW_BRIDGE_MSG_BASE + 8)
#define ESPNOW_BRIDGE_EVT_SEND_STATUS (ESPNOW_BRIDGE_MSG_BASE + 9)
#define ESPNOW_BRIDGE_ETH_ALEN 6
#define ESPNOW_BRIDGE_KEY_LEN 16
/* ESP-NOW v2.0 payload limit (ESP_NOW_MAX_DATA_LEN_V2 in esp_now.h). The slave reports its
* actual negotiated esp_now_get_version() back to the host in RESP_INIT; hosts talking to a
* v1.0-only slave still work, they just never send payloads over 250B (native ESP-NOW callers
* enforce that themselves based on the reported version, same as the non-bridged backend). */
#define ESPNOW_BRIDGE_MAX_DATA_LEN 1470
/* req_init mode: matches tt::service::espnow::Mode / WIFI_MODE_STA vs WIFI_MODE_AP on the slave */
#define ESPNOW_BRIDGE_MODE_STATION 0U
#define ESPNOW_BRIDGE_MODE_ACCESS_POINT 1U
/* req: ESPNOW_BRIDGE_REQ_INIT */
typedef struct __attribute__((packed)) {
uint32_t txn_id; /* echoed back verbatim in the matching resp_status_t/resp_init_t so a
* response arriving after its request already timed out (e.g. a slow
* REQ_INIT retry) can't be mistaken for the answer to a newer request
* of the same type - see EspNowBackendHosted.cpp's doRequest(). */
uint8_t pmk[ESPNOW_BRIDGE_KEY_LEN];
uint8_t channel; /* 0 = use current STA/AP channel */
uint8_t long_range; /* bool as uint8_t: fixed 1-byte width for this hand-synced wire struct */
uint8_t mode; /* ESPNOW_BRIDGE_MODE_STATION / ESPNOW_BRIDGE_MODE_ACCESS_POINT: which WiFi
* mode+interface the slave should bring up and register ESP-NOW against */
} espnow_bridge_req_init_t;
/* req: ESPNOW_BRIDGE_REQ_DEINIT */
typedef struct __attribute__((packed)) {
uint32_t txn_id; /* see espnow_bridge_req_init_t::txn_id */
} espnow_bridge_req_deinit_t;
/* resp: ESPNOW_BRIDGE_RESP_DEINIT / RESP_ADD_PEER / RESP_SEND */
typedef struct __attribute__((packed)) {
uint32_t txn_id; /* copied from the request this responds to */
int32_t esp_err; /* raw esp_err_t from the slave-side call */
} espnow_bridge_resp_status_t;
/* resp: ESPNOW_BRIDGE_RESP_INIT */
typedef struct __attribute__((packed)) {
uint32_t txn_id; /* copied from the request this responds to */
int32_t esp_err; /* raw esp_err_t from the slave-side esp_now_init() call */
uint32_t espnow_version; /* esp_now_get_version() result, 0 if esp_err != ESP_OK */
} espnow_bridge_resp_init_t;
/* req: ESPNOW_BRIDGE_REQ_ADD_PEER */
typedef struct __attribute__((packed)) {
uint32_t txn_id; /* see espnow_bridge_req_init_t::txn_id */
uint8_t peer_addr[ESPNOW_BRIDGE_ETH_ALEN];
uint8_t lmk[ESPNOW_BRIDGE_KEY_LEN];
uint8_t channel;
uint8_t encrypt; /* bool as uint8_t: fixed 1-byte width for this hand-synced wire struct */
uint8_t ifidx; /* ESPNOW_BRIDGE_MODE_STATION / ESPNOW_BRIDGE_MODE_ACCESS_POINT: which
* interface (WIFI_IF_STA / WIFI_IF_AP) to bind the peer to on the slave,
* matching esp_now_peer_info_t::ifidx on native */
} espnow_bridge_req_add_peer_t;
/* req: ESPNOW_BRIDGE_REQ_SEND */
typedef struct __attribute__((packed)) {
uint32_t txn_id; /* see espnow_bridge_req_init_t::txn_id */
uint8_t dest_addr[ESPNOW_BRIDGE_ETH_ALEN];
uint8_t broadcast; /* bool as uint8_t; true: dest_addr ignored, esp_now_send(NULL, ...) */
uint16_t data_len;
uint8_t data[ESPNOW_BRIDGE_MAX_DATA_LEN];
} espnow_bridge_req_send_t;
/* event: ESPNOW_BRIDGE_EVT_RECV (slave-initiated, unsolicited) */
typedef struct __attribute__((packed)) {
uint8_t src_addr[ESPNOW_BRIDGE_ETH_ALEN];
uint8_t des_addr[ESPNOW_BRIDGE_ETH_ALEN];
int8_t rssi;
uint8_t channel;
uint16_t data_len;
uint8_t data[ESPNOW_BRIDGE_MAX_DATA_LEN];
} espnow_bridge_evt_recv_t;
/* event: ESPNOW_BRIDGE_EVT_SEND_STATUS (slave-initiated, unsolicited) */
typedef struct __attribute__((packed)) {
uint8_t peer_addr[ESPNOW_BRIDGE_ETH_ALEN];
uint8_t success; /* bool as uint8_t: fixed 1-byte width for this hand-synced wire struct */
} espnow_bridge_evt_send_status_t;
#ifdef __cplusplus
}
#endif
#endif /* __ESP_HOSTED_ESPNOW_BRIDGE_PROTO_H */