ESP-NOW bridge for P4 (#573)
This commit is contained in:
@@ -39,6 +39,9 @@ sdkconfig.CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8
|
|||||||
sdkconfig.CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15
|
sdkconfig.CONFIG_ESP_HOSTED_SDIO_GPIO_RESET_SLAVE=15
|
||||||
# Fixes recent changes to esp_hosted
|
# Fixes recent changes to esp_hosted
|
||||||
sdkconfig.CONFIG_ESP_HOSTED_USE_MEMPOOL=n
|
sdkconfig.CONFIG_ESP_HOSTED_USE_MEMPOOL=n
|
||||||
|
# ESP-NOW bridge (EspNowBackendHosted.cpp) registers 6 custom-RPC handlers (4 resp + 2 event); default cap is 3
|
||||||
|
sdkconfig.CONFIG_ESP_HOSTED_ENABLE_PEER_DATA_TRANSFER=y
|
||||||
|
sdkconfig.CONFIG_ESP_HOSTED_MAX_CUSTOM_MSG_HANDLERS=8
|
||||||
# Performance: larger L2 cache reduces PSRAM stalls for draw/DPI buffers
|
# Performance: larger L2 cache reduces PSRAM stalls for draw/DPI buffers
|
||||||
sdkconfig.CONFIG_CACHE_L2_CACHE_256KB=y
|
sdkconfig.CONFIG_CACHE_L2_CACHE_256KB=y
|
||||||
# Performance: use P4's PPA (pixel processing accelerator for rotation)
|
# Performance: use P4's PPA (pixel processing accelerator for rotation)
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct FirmwareOps;
|
||||||
|
|
||||||
|
/** @return the esp_hosted co-processor FirmwareOps implementation. Only declared/linked when
|
||||||
|
* CONFIG_SLAVE_SOC_WIFI_SUPPORTED - callers (esp32_wifi.cpp) must guard with the same #if. */
|
||||||
|
const FirmwareOps* esp32_esp_hosted_ota_get_ops();
|
||||||
@@ -0,0 +1,254 @@
|
|||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||||
|
|
||||||
|
#include <tactility/drivers/esp32_esp_hosted_ota.h>
|
||||||
|
#include <tactility/drivers/wifi.h>
|
||||||
|
#include <tactility/error_esp32.h>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#include <esp_hosted.h>
|
||||||
|
extern "C" {
|
||||||
|
#include <esp_hosted_ota.h>
|
||||||
|
}
|
||||||
|
#include <esp_hosted_api_types.h>
|
||||||
|
#include <esp_hosted_event.h>
|
||||||
|
#include <esp_event.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/event_groups.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#define TAG "esp32_esp_hosted_ota"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr EventBits_t CP_INIT_BIT = BIT0;
|
||||||
|
|
||||||
|
// An event group (not a binary semaphore) because multiple callers can wait concurrently - a
|
||||||
|
// binary semaphore only wakes one waiter per give(), which would leave the other(s) blocked
|
||||||
|
// until a second CP_INIT event that may never come.
|
||||||
|
EventGroupHandle_t cpInitEventGroup = nullptr;
|
||||||
|
esp_event_handler_instance_t cpInitHandlerInstance = nullptr;
|
||||||
|
std::atomic<bool> handlerRegistered{false};
|
||||||
|
|
||||||
|
// Monotonically bumped on every CP_INIT (including ones after a slave reset/reboot). Callers
|
||||||
|
// snapshot this before triggering a (re)connect and wait for it to advance past that snapshot,
|
||||||
|
// so a CP_INIT_BIT left set from a boot that predates the call can't be mistaken for readiness of
|
||||||
|
// the *current* RPC layer.
|
||||||
|
std::atomic<uint32_t> cpInitGeneration{0};
|
||||||
|
|
||||||
|
void onCpInitEvent(void* /*arg*/, esp_event_base_t /*base*/, int32_t /*id*/, void* /*data*/) {
|
||||||
|
cpInitGeneration.fetch_add(1);
|
||||||
|
if (cpInitEventGroup != nullptr) {
|
||||||
|
xEventGroupSetBits(cpInitEventGroup, CP_INIT_BIT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Thread-safe lazy init: the first caller through wins the race, so concurrent callers can't
|
||||||
|
* both try to create the event group/register the handler at once. */
|
||||||
|
bool ensureHandlerRegistered() {
|
||||||
|
if (handlerRegistered) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
static std::atomic<bool> initializing{false};
|
||||||
|
bool expected = false;
|
||||||
|
if (!initializing.compare_exchange_strong(expected, true)) {
|
||||||
|
while (!handlerRegistered && initializing) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
return handlerRegistered;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cpInitEventGroup == nullptr) {
|
||||||
|
cpInitEventGroup = xEventGroupCreate();
|
||||||
|
}
|
||||||
|
if (cpInitEventGroup == nullptr) {
|
||||||
|
LOG_E(TAG, "xEventGroupCreate() failed");
|
||||||
|
initializing = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (cpInitHandlerInstance == nullptr) {
|
||||||
|
esp_err_t err = esp_event_handler_instance_register(ESP_HOSTED_EVENT, ESP_HOSTED_EVENT_CP_INIT,
|
||||||
|
onCpInitEvent, nullptr, &cpInitHandlerInstance);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
LOG_E(TAG, "esp_event_handler_instance_register() failed: %d", err);
|
||||||
|
initializing = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlerRegistered = true;
|
||||||
|
initializing = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool waitReady(void* /*ctx*/, uint32_t timeoutMs) {
|
||||||
|
esp_hosted_coprocessor_fwver_t probeVersion = {};
|
||||||
|
if (esp_hosted_get_coprocessor_fwversion(&probeVersion) == ESP_OK) {
|
||||||
|
// Already up (e.g. WiFi/BT brought it up earlier this boot).
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the event handler *before* triggering the connect, so we can't miss the
|
||||||
|
// ESP_HOSTED_EVENT_CP_INIT event firing in the window between triggering and waiting.
|
||||||
|
if (!ensureHandlerRegistered()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot the generation before triggering (re)connect - a CP_INIT that already happened
|
||||||
|
// (e.g. from a boot before a slave reset) doesn't count as readiness for this call; only a
|
||||||
|
// CP_INIT observed after this point (generation advances past the snapshot) does.
|
||||||
|
uint32_t generationBeforeConnect = cpInitGeneration.load();
|
||||||
|
|
||||||
|
// Nothing in Tactility's boot sequence calls esp_hosted_connect_to_slave() on its own
|
||||||
|
// (only WiFi/BT starting does today, as a side effect) - trigger it ourselves so ESP-NOW
|
||||||
|
// and OTA can work standalone, matching how ESP-NOW works on native (non-hosted) chips.
|
||||||
|
if (esp_hosted_connect_to_slave() != ESP_OK) {
|
||||||
|
LOG_W(TAG, "esp_hosted_connect_to_slave() returned an error - will still wait for CP_INIT in case it's async");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the co-processor's own RPC layer to finish booting (ESP_HOSTED_EVENT_CP_INIT,
|
||||||
|
// logged upstream as "Coprocessor Boot-up") - this is later than the transport/SDIO link
|
||||||
|
// simply being up, and firing a custom RPC request before this point corrupts the RPC
|
||||||
|
// channel for subsequent real calls (observed: esp_wifi_init() failing until reboot).
|
||||||
|
// vTaskSetTimeOutState()/xTaskCheckForTimeOut() track elapsed ticks from a snapshot rather
|
||||||
|
// than comparing against a fixed deadline tick count, so this is correct across a tick-count
|
||||||
|
// wraparound (a fixed "now + timeout" deadline can wrap past TickType_t's max and compare as
|
||||||
|
// already-elapsed on the very next check).
|
||||||
|
TimeOut_t timeoutState;
|
||||||
|
vTaskSetTimeOutState(&timeoutState);
|
||||||
|
TickType_t remaining = pdMS_TO_TICKS(timeoutMs);
|
||||||
|
while (true) {
|
||||||
|
if (cpInitGeneration.load() != generationBeforeConnect) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xTaskCheckForTimeOut(&timeoutState, &remaining) == pdTRUE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdTRUE: consume the bit so a stale (pre-existing) signal doesn't let a *later* call
|
||||||
|
// short-circuit past its own fresh wait. Safe for concurrent waiters because every waiter
|
||||||
|
// re-checks cpInitGeneration (not just the bit) both before and after waking - FreeRTOS
|
||||||
|
// delivers a set to all currently-blocked waiters before any auto-clear happens, so a
|
||||||
|
// waiter still genuinely waiting for a not-yet-arrived CP_INIT simply loops again.
|
||||||
|
EventBits_t bits = xEventGroupWaitBits(cpInitEventGroup, CP_INIT_BIT, pdTRUE, pdFALSE, remaining);
|
||||||
|
if ((bits & CP_INIT_BIT) == 0) {
|
||||||
|
return false; // timed out
|
||||||
|
}
|
||||||
|
if (cpInitGeneration.load() != generationBeforeConnect) {
|
||||||
|
return true; // genuinely new CP_INIT since this call's connect trigger
|
||||||
|
}
|
||||||
|
// Otherwise: consumed a stale bit - loop back and re-wait.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t getInfo(void* /*ctx*/, FirmwareInfo* info) {
|
||||||
|
if (info == nullptr) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
esp_hosted_coprocessor_fwver_t version = {};
|
||||||
|
esp_err_t err = esp_hosted_get_coprocessor_fwversion(&version);
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
return esp_err_to_error(err);
|
||||||
|
}
|
||||||
|
info->fw_major = version.major1;
|
||||||
|
info->fw_minor = version.minor1;
|
||||||
|
info->fw_patch = version.patch1;
|
||||||
|
|
||||||
|
// Chip identification is best-effort: report the version even if this fails (e.g. a slave
|
||||||
|
// firmware old enough to lack the esp_hosted_get_cp_info() RPC).
|
||||||
|
info->name[0] = '\0';
|
||||||
|
info->hw_id = 0;
|
||||||
|
uint32_t chipId = 0;
|
||||||
|
if (::esp_hosted_get_cp_info(&chipId, info->name, sizeof(info->name)) == ESP_OK) {
|
||||||
|
info->hw_id = chipId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// esp_hosted's OTA API is a single global stream (esp_hosted_slave_ota_begin/write/end/activate
|
||||||
|
// take no handle/context - there's exactly one co-processor). FirmwareUpdateHandle is an
|
||||||
|
// intentionally-incomplete/opaque type (see wifi.h) so it can't be instantiated directly - this
|
||||||
|
// sentinel object just gives begin()/write()/finish()/abort() a distinct, non-null address to
|
||||||
|
// pass around and check against, matching the generic FirmwareOps shape.
|
||||||
|
int otaHandleSentinelStorage = 0;
|
||||||
|
FirmwareUpdateHandle* otaHandleSentinel = reinterpret_cast<FirmwareUpdateHandle*>(&otaHandleSentinelStorage);
|
||||||
|
|
||||||
|
// Guards against two overlapping OTA sessions (concurrent begin() calls) and against a stale
|
||||||
|
// handle from a finished/aborted session still being accepted by write()/finish()/abort() - the
|
||||||
|
// sentinel address alone can't distinguish "the one active session" from "a session that already
|
||||||
|
// ended", since it's always the same pointer.
|
||||||
|
std::atomic<bool> otaSessionActive{false};
|
||||||
|
|
||||||
|
error_t beginUpdate(void* /*ctx*/, const FirmwareUpdateRequest* /*req*/, FirmwareUpdateHandle** handle) {
|
||||||
|
if (handle == nullptr) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
bool expected = false;
|
||||||
|
if (!otaSessionActive.compare_exchange_strong(expected, true)) {
|
||||||
|
return ERROR_RESOURCE_BUSY;
|
||||||
|
}
|
||||||
|
esp_err_t err = ::esp_hosted_slave_ota_begin();
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
otaSessionActive = false;
|
||||||
|
return esp_err_to_error(err);
|
||||||
|
}
|
||||||
|
*handle = otaHandleSentinel;
|
||||||
|
return ERROR_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t writeUpdate(FirmwareUpdateHandle* handle, const void* data, size_t len) {
|
||||||
|
if (handle != otaHandleSentinel || !otaSessionActive.load()) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
return esp_err_to_error(::esp_hosted_slave_ota_write(
|
||||||
|
const_cast<uint8_t*>(static_cast<const uint8_t*>(data)), static_cast<uint32_t>(len)));
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t finishUpdate(FirmwareUpdateHandle* handle) {
|
||||||
|
if (handle != otaHandleSentinel || !otaSessionActive.load()) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
otaSessionActive = false;
|
||||||
|
return esp_err_to_error(::esp_hosted_slave_ota_end());
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t abortUpdate(FirmwareUpdateHandle* handle) {
|
||||||
|
if (handle != otaHandleSentinel || !otaSessionActive.load()) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
otaSessionActive = false;
|
||||||
|
// esp_hosted has no distinct abort call - end() is the only way to close out a begin(),
|
||||||
|
// successful or not (matches how the previous single-stream app code always called
|
||||||
|
// esp_hosted_slave_ota_end() on both the success and failure paths).
|
||||||
|
return esp_err_to_error(::esp_hosted_slave_ota_end());
|
||||||
|
}
|
||||||
|
|
||||||
|
error_t activate(void* /*ctx*/) {
|
||||||
|
return esp_err_to_error(::esp_hosted_slave_ota_activate());
|
||||||
|
}
|
||||||
|
|
||||||
|
const FirmwareOps firmwareOps = {
|
||||||
|
.wait_ready = waitReady,
|
||||||
|
.get_info = getInfo,
|
||||||
|
.begin = beginUpdate,
|
||||||
|
.write = writeUpdate,
|
||||||
|
.finish = finishUpdate,
|
||||||
|
.abort = abortUpdate,
|
||||||
|
.activate = activate,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
const FirmwareOps* esp32_esp_hosted_ota_get_ops() {
|
||||||
|
return &firmwareOps;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
@@ -17,6 +17,10 @@
|
|||||||
#include <tactility/error_esp32.h>
|
#include <tactility/error_esp32.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||||
|
#include <tactility/drivers/esp32_esp_hosted_ota.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <new>
|
#include <new>
|
||||||
@@ -489,6 +493,24 @@ error_t api_remove_event_callback(Device* device, WifiEventCallback callback) {
|
|||||||
return ERROR_NOT_FOUND;
|
return ERROR_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error_t api_get_firmware_ops(Device* /*device*/, const FirmwareOps** ops, void** ctx) {
|
||||||
|
// ops/ctx are caller-supplied output pointers, reachable from external (ELF) apps via
|
||||||
|
// wifi_get_firmware_ops() - validate at this API boundary rather than trusting the caller.
|
||||||
|
if (ops == nullptr || ctx == nullptr) {
|
||||||
|
return ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||||
|
// Only meaningful on a hosted board (P4+C6/C5 etc.) - this wifi device is backed by a real
|
||||||
|
// co-processor with its own updatable firmware there. On a native (non-hosted) chip, this
|
||||||
|
// device's "radio" is the chip's own built-in WiFi, nothing to update via this interface.
|
||||||
|
*ops = esp32_esp_hosted_ota_get_ops();
|
||||||
|
*ctx = nullptr; // esp32_esp_hosted_ota's FirmwareOps functions are all singleton/global, no per-call ctx needed
|
||||||
|
return ERROR_NONE;
|
||||||
|
#else
|
||||||
|
return ERROR_NOT_SUPPORTED;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
const WifiApi esp32_wifi_api = {
|
const WifiApi esp32_wifi_api = {
|
||||||
.get_radio_state = api_get_radio_state,
|
.get_radio_state = api_get_radio_state,
|
||||||
.get_station_state = api_get_station_state,
|
.get_station_state = api_get_station_state,
|
||||||
@@ -502,7 +524,8 @@ const WifiApi esp32_wifi_api = {
|
|||||||
.station_disconnect = api_station_disconnect,
|
.station_disconnect = api_station_disconnect,
|
||||||
.station_get_rssi = api_station_get_rssi,
|
.station_get_rssi = api_station_get_rssi,
|
||||||
.add_event_callback = api_add_event_callback,
|
.add_event_callback = api_add_event_callback,
|
||||||
.remove_event_callback = api_remove_event_callback
|
.remove_event_callback = api_remove_event_callback,
|
||||||
|
.get_firmware_ops = api_get_firmware_ops
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---- Driver lifecycle ----
|
// ---- Driver lifecycle ----
|
||||||
|
|||||||
@@ -39,6 +39,11 @@ if (DEFINED ENV{ESP_IDF_VERSION})
|
|||||||
list(APPEND REQUIRES_LIST esp_tinyusb)
|
list(APPEND REQUIRES_LIST esp_tinyusb)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
if ("${IDF_TARGET}" STREQUAL "esp32p4")
|
||||||
|
# EspNowBackendHosted.cpp needs esp_hosted.h / esp_hosted_misc.h.
|
||||||
|
list(APPEND REQUIRES_LIST espressif__esp_hosted)
|
||||||
|
endif ()
|
||||||
|
|
||||||
else ()
|
else ()
|
||||||
|
|
||||||
list(APPEND REQUIRES_LIST
|
list(APPEND REQUIRES_LIST
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <Tactility/app/App.h>
|
||||||
|
#include <Tactility/Bundle.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace tt::app::fileselection {
|
namespace tt::app::fileselection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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 <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -66,4 +66,4 @@ size_t getMaxDataLength();
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
@@ -83,6 +83,17 @@ void disconnect();
|
|||||||
/** @return true if the connection isn't unencrypted. */
|
/** @return true if the connection isn't unencrypted. */
|
||||||
bool isConnectionSecure();
|
bool isConnectionSecure();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Suspend or resume the periodic background auto-connect scan.
|
||||||
|
* @param[in] paused when true, the auto-connect timer stops issuing scans until resumed.
|
||||||
|
* Intended for narrow, short-lived windows where any WiFi/co-processor traffic would be
|
||||||
|
* unsafe (e.g. a known co-processor reboot in progress) - callers must resume when done.
|
||||||
|
* Independent of the internal connect()/disconnect() auto-connect pause bookkeeping: only
|
||||||
|
* a matching setAutoScanPaused(false) clears this, it is never cleared implicitly by a
|
||||||
|
* connection succeeding/failing or the radio being enabled.
|
||||||
|
*/
|
||||||
|
void setAutoScanPaused(bool paused);
|
||||||
|
|
||||||
/** @return the RSSI value (negative number) or return 1 when not connected. */
|
/** @return the RSSI value (negative number) or return 1 when not connected. */
|
||||||
int getRssi();
|
int getRssi();
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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 "ChatState.h"
|
||||||
#include "ChatView.h"
|
#include "ChatView.h"
|
||||||
@@ -43,4 +43,4 @@ public:
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // 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>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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 <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -94,4 +94,4 @@ size_t getMaxMessageLength(size_t nicknameLen, size_t targetLen);
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // 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>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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 <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -28,4 +28,4 @@ bool settingsFileExists();
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // 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>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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>
|
#include <Tactility/RecursiveMutex.h>
|
||||||
|
|
||||||
@@ -56,4 +56,4 @@ public:
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // 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>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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 "ChatState.h"
|
||||||
#include "ChatSettings.h"
|
#include "ChatSettings.h"
|
||||||
@@ -76,4 +76,4 @@ public:
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // 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>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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/Service.h>
|
||||||
#include <Tactility/service/espnow/EspNow.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 */
|
||||||
@@ -80,7 +80,7 @@ namespace service {
|
|||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
namespace development { extern const ServiceManifest manifest; }
|
namespace development { extern const ServiceManifest manifest; }
|
||||||
#endif
|
#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)
|
||||||
namespace espnow { extern const ServiceManifest manifest; }
|
namespace espnow { extern const ServiceManifest manifest; }
|
||||||
#endif
|
#endif
|
||||||
// Secondary (UI)
|
// Secondary (UI)
|
||||||
@@ -161,7 +161,7 @@ namespace app {
|
|||||||
namespace screenshot { extern const AppManifest manifest; }
|
namespace screenshot { extern const AppManifest manifest; }
|
||||||
#endif
|
#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)
|
||||||
namespace chat { extern const AppManifest manifest; }
|
namespace chat { extern const AppManifest manifest; }
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -229,7 +229,7 @@ static void registerInternalApps() {
|
|||||||
addAppManifest(app::screenshot::manifest);
|
addAppManifest(app::screenshot::manifest);
|
||||||
#endif
|
#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)
|
||||||
addAppManifest(app::chat::manifest);
|
addAppManifest(app::chat::manifest);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -328,7 +328,7 @@ static void registerAndStartPrimaryServices() {
|
|||||||
addService(service::development::manifest);
|
addService(service::development::manifest);
|
||||||
#endif
|
#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)
|
||||||
addService(service::espnow::manifest);
|
addService(service::espnow::manifest);
|
||||||
#endif
|
#endif
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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/app/chat/ChatAppPrivate.h>
|
#include <Tactility/app/chat/ChatAppPrivate.h>
|
||||||
#include <Tactility/app/chat/ChatProtocol.h>
|
#include <Tactility/app/chat/ChatProtocol.h>
|
||||||
@@ -187,4 +187,4 @@ extern const AppManifest manifest = {
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // namespace tt::app::chat
|
||||||
|
|
||||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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/app/chat/ChatProtocol.h>
|
#include <Tactility/app/chat/ChatProtocol.h>
|
||||||
#include <Tactility/service/espnow/EspNow.h>
|
#include <Tactility/service/espnow/EspNow.h>
|
||||||
@@ -171,4 +171,4 @@ size_t getMaxMessageLength(size_t nicknameLen, size_t targetLen) {
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // namespace tt::app::chat
|
||||||
|
|
||||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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/app/chat/ChatSettings.h>
|
#include <Tactility/app/chat/ChatSettings.h>
|
||||||
#include <Tactility/app/chat/ChatProtocol.h>
|
#include <Tactility/app/chat/ChatProtocol.h>
|
||||||
@@ -201,4 +201,4 @@ bool settingsFileExists() {
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // namespace tt::app::chat
|
||||||
|
|
||||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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/app/chat/ChatState.h>
|
#include <Tactility/app/chat/ChatState.h>
|
||||||
|
|
||||||
@@ -57,4 +57,4 @@ std::vector<StoredMessage> ChatState::getFilteredMessages() const {
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // namespace tt::app::chat
|
||||||
|
|
||||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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/app/chat/ChatView.h>
|
#include <Tactility/app/chat/ChatView.h>
|
||||||
#include <Tactility/app/chat/ChatAppPrivate.h>
|
#include <Tactility/app/chat/ChatAppPrivate.h>
|
||||||
@@ -296,4 +296,4 @@ void ChatView::onChannelCancel(lv_event_t* e) {
|
|||||||
|
|
||||||
} // namespace tt::app::chat
|
} // namespace tt::app::chat
|
||||||
|
|
||||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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/service/espnow/EspNow.h>
|
#include <Tactility/service/espnow/EspNow.h>
|
||||||
#include <Tactility/service/espnow/EspNowService.h>
|
#include <Tactility/service/espnow/EspNowService.h>
|
||||||
@@ -97,4 +97,4 @@ size_t getMaxDataLength() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
|
|||||||
@@ -0,0 +1,313 @@
|
|||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||||
|
|
||||||
|
#include <Tactility/service/espnow/EspNowBackend.h>
|
||||||
|
#include <Tactility/service/espnow/EspNowHostedTransport.h>
|
||||||
|
#include <Tactility/service/espnow/esp_hosted_espnow_bridge_proto.h>
|
||||||
|
|
||||||
|
// esp_hosted_misc.h lacks its own extern "C" guard, so its declarations get C++ name-mangled
|
||||||
|
// when included from a .cpp file, causing "undefined reference" at link time against the
|
||||||
|
// library's plain-C symbols (esp_hosted_send_custom_data/register_custom_callback).
|
||||||
|
extern "C" {
|
||||||
|
#include <esp_hosted_misc.h>
|
||||||
|
}
|
||||||
|
#include <esp_now.h>
|
||||||
|
|
||||||
|
#include <Tactility/Semaphore.h>
|
||||||
|
#include <Tactility/RecursiveMutex.h>
|
||||||
|
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
namespace tt::service::espnow::backend {
|
||||||
|
|
||||||
|
constexpr auto* TAG = "EspNowBackendHosted";
|
||||||
|
constexpr TickType_t REQUEST_TIMEOUT = 2000U / portTICK_PERIOD_MS;
|
||||||
|
|
||||||
|
static RecursiveMutex requestMutex;
|
||||||
|
static Semaphore responseSemaphore(1, 0);
|
||||||
|
// Callbacks (onRespGeneric/onRespInit) run from esp_hosted's own dispatch task, concurrently
|
||||||
|
// with doRequest() running on the caller's task - std::atomic instead of requestMutex here
|
||||||
|
// because taking requestMutex from the callback would deadlock: doRequest() holds it for its
|
||||||
|
// entire wait, including while blocked in responseSemaphore.acquire() waiting on the very
|
||||||
|
// callback that would need the lock.
|
||||||
|
static std::atomic<esp_err_t> lastResponseErr{ESP_FAIL};
|
||||||
|
static std::atomic<bool> waitingForResponse{false};
|
||||||
|
// The txn_id doRequest() is currently waiting a response for. Response handlers only accept a
|
||||||
|
// response (release the semaphore) if its txn_id matches - otherwise a delayed response to a
|
||||||
|
// prior, already-timed-out request (e.g. a REQ_INIT retry's earlier attempt) can't be mistaken
|
||||||
|
// for the answer to whatever request is active now, even a later request of the same type.
|
||||||
|
static std::atomic<uint32_t> activeTxnId{0};
|
||||||
|
static std::atomic<uint32_t> nextTxnId{1};
|
||||||
|
|
||||||
|
// Written by init()/deinit() on the caller's task, read by onEvtRecv() on esp_hosted's dispatch
|
||||||
|
// task - atomic so a callback removal during deinit() can't race a concurrent RX event dispatch
|
||||||
|
// (the dispatch task must see either the old callback or nullptr, never a torn/stale pointer).
|
||||||
|
static std::atomic<esp_now_recv_cb_t> userRecvCallback{nullptr};
|
||||||
|
static uint32_t espnowVersion = 0;
|
||||||
|
|
||||||
|
static std::atomic<uint32_t> lastReportedInitVersion{0};
|
||||||
|
|
||||||
|
static void onRespGeneric(uint32_t msgId, const uint8_t* data, size_t dataLen, void* ctx) {
|
||||||
|
(void)msgId; (void)ctx;
|
||||||
|
if (dataLen != sizeof(espnow_bridge_resp_status_t)) {
|
||||||
|
LOG_E(TAG, "Malformed response (%zu bytes)", dataLen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* resp = reinterpret_cast<const espnow_bridge_resp_status_t*>(data);
|
||||||
|
if (!waitingForResponse || resp->txn_id != activeTxnId.load()) {
|
||||||
|
return; // stale response for a request that's no longer (or never was) being waited on
|
||||||
|
}
|
||||||
|
lastResponseErr = static_cast<esp_err_t>(resp->esp_err);
|
||||||
|
responseSemaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onRespInit(uint32_t msgId, const uint8_t* data, size_t dataLen, void* ctx) {
|
||||||
|
(void)msgId; (void)ctx;
|
||||||
|
if (dataLen != sizeof(espnow_bridge_resp_init_t)) {
|
||||||
|
LOG_E(TAG, "Malformed RESP_INIT (%zu bytes)", dataLen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* resp = reinterpret_cast<const espnow_bridge_resp_init_t*>(data);
|
||||||
|
if (!waitingForResponse || resp->txn_id != activeTxnId.load()) {
|
||||||
|
return; // stale response for a request that's no longer (or never was) being waited on
|
||||||
|
}
|
||||||
|
lastResponseErr = static_cast<esp_err_t>(resp->esp_err);
|
||||||
|
lastReportedInitVersion = resp->espnow_version;
|
||||||
|
responseSemaphore.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onEvtRecv(uint32_t msgId, const uint8_t* data, size_t dataLen, void* ctx) {
|
||||||
|
(void)msgId; (void)ctx;
|
||||||
|
if (dataLen != sizeof(espnow_bridge_evt_recv_t)) {
|
||||||
|
LOG_E(TAG, "Malformed RX event (%zu bytes)", dataLen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
esp_now_recv_cb_t recvCallback = userRecvCallback.load();
|
||||||
|
if (recvCallback == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* evt = reinterpret_cast<const espnow_bridge_evt_recv_t*>(data);
|
||||||
|
if (evt->data_len > ESPNOW_BRIDGE_MAX_DATA_LEN) {
|
||||||
|
LOG_E(TAG, "Malformed RX event: data_len %u exceeds buffer capacity", (unsigned)evt->data_len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a transient esp_now_recv_info_t matching the shape native ESP-NOW delivers.
|
||||||
|
wifi_pkt_rx_ctrl_t rxCtrl = {};
|
||||||
|
rxCtrl.rssi = evt->rssi;
|
||||||
|
rxCtrl.channel = evt->channel;
|
||||||
|
|
||||||
|
uint8_t srcAddr[ESPNOW_BRIDGE_ETH_ALEN];
|
||||||
|
uint8_t desAddr[ESPNOW_BRIDGE_ETH_ALEN];
|
||||||
|
memcpy(srcAddr, evt->src_addr, ESPNOW_BRIDGE_ETH_ALEN);
|
||||||
|
memcpy(desAddr, evt->des_addr, ESPNOW_BRIDGE_ETH_ALEN);
|
||||||
|
|
||||||
|
esp_now_recv_info_t recvInfo = {};
|
||||||
|
recvInfo.src_addr = srcAddr;
|
||||||
|
recvInfo.des_addr = desAddr;
|
||||||
|
recvInfo.rx_ctrl = &rxCtrl;
|
||||||
|
|
||||||
|
recvCallback(&recvInfo, evt->data, evt->data_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onEvtSendStatus(uint32_t msgId, const uint8_t* data, size_t dataLen, void* ctx) {
|
||||||
|
(void)msgId; (void)ctx;
|
||||||
|
if (dataLen != sizeof(espnow_bridge_evt_send_status_t)) {
|
||||||
|
LOG_E(TAG, "Malformed send-status event (%zu bytes)", dataLen);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto* evt = reinterpret_cast<const espnow_bridge_evt_send_status_t*>(data);
|
||||||
|
if (!evt->success) {
|
||||||
|
LOG_W(TAG, "Peer send reported failure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sends a request and blocks (holding requestMutex) until the matching response arrives or
|
||||||
|
* times out. Every request struct's first field is `uint32_t txn_id`; doRequest() stamps a
|
||||||
|
* fresh transaction id directly into that leading 4 bytes of reqData (memcpy rather than a
|
||||||
|
* `uint32_t*` into the packed struct, which trips -Waddress-of-packed-member even though the
|
||||||
|
* first field of every one of these structs is always naturally aligned) before sending - the
|
||||||
|
* matching response handler (onRespGeneric/onRespInit) only accepts a response whose txn_id
|
||||||
|
* equals this call's, so a late response to an earlier, already-timed-out request (e.g. a
|
||||||
|
* REQ_INIT retry) can't be mistaken for the answer to this request. */
|
||||||
|
static esp_err_t doRequest(uint32_t reqMsgId, uint8_t* reqData, size_t reqDataLen) {
|
||||||
|
auto lock = requestMutex.asScopedLock();
|
||||||
|
lock.lock();
|
||||||
|
|
||||||
|
// Drain any stale token left by a response that arrived after a previous request's
|
||||||
|
// doRequest() call already timed out - without this, that late release would let this new
|
||||||
|
// request's acquire() below return immediately with a stale lastResponseErr/version instead
|
||||||
|
// of actually waiting for its own response.
|
||||||
|
while (responseSemaphore.acquire(0)) {}
|
||||||
|
|
||||||
|
uint32_t txnId = nextTxnId.fetch_add(1);
|
||||||
|
memcpy(reqData, &txnId, sizeof(txnId));
|
||||||
|
activeTxnId = txnId;
|
||||||
|
|
||||||
|
waitingForResponse = true;
|
||||||
|
esp_err_t sendErr = esp_hosted_send_custom_data(reqMsgId, reqData, reqDataLen);
|
||||||
|
if (sendErr != ESP_OK) {
|
||||||
|
waitingForResponse = false;
|
||||||
|
LOG_E(TAG, "esp_hosted_send_custom_data() failed for req 0x%lx: %d", (unsigned long)reqMsgId, sendErr);
|
||||||
|
return sendErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gotResponse = responseSemaphore.acquire(REQUEST_TIMEOUT);
|
||||||
|
waitingForResponse = false;
|
||||||
|
if (!gotResponse) {
|
||||||
|
LOG_E(TAG, "Timed out waiting for response to req 0x%lx", (unsigned long)reqMsgId);
|
||||||
|
return ESP_ERR_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastResponseErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool registerCallbacksOnce() {
|
||||||
|
static bool registered = false;
|
||||||
|
if (registered) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool allOk = true;
|
||||||
|
allOk &= esp_hosted_register_custom_callback(ESPNOW_BRIDGE_RESP_INIT, onRespInit, nullptr) == ESP_OK;
|
||||||
|
allOk &= esp_hosted_register_custom_callback(ESPNOW_BRIDGE_RESP_DEINIT, onRespGeneric, nullptr) == ESP_OK;
|
||||||
|
allOk &= esp_hosted_register_custom_callback(ESPNOW_BRIDGE_RESP_ADD_PEER, onRespGeneric, nullptr) == ESP_OK;
|
||||||
|
allOk &= esp_hosted_register_custom_callback(ESPNOW_BRIDGE_RESP_SEND, onRespGeneric, nullptr) == ESP_OK;
|
||||||
|
allOk &= esp_hosted_register_custom_callback(ESPNOW_BRIDGE_EVT_RECV, onEvtRecv, nullptr) == ESP_OK;
|
||||||
|
allOk &= esp_hosted_register_custom_callback(ESPNOW_BRIDGE_EVT_SEND_STATUS, onEvtSendStatus, nullptr) == ESP_OK;
|
||||||
|
|
||||||
|
if (!allOk) {
|
||||||
|
LOG_E(TAG, "Failed to register one or more custom callbacks");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
registered = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool init(const EspNowConfig& config, esp_now_recv_cb_t recvCallback) {
|
||||||
|
constexpr uint32_t HOSTED_TRANSPORT_WAIT_TIMEOUT_MS = 10000;
|
||||||
|
if (!waitForHostedTransport(HOSTED_TRANSPORT_WAIT_TIMEOUT_MS)) {
|
||||||
|
LOG_E(TAG, "esp_hosted transport didn't come up within %" PRIu32 "ms - is WiFi enabled?",
|
||||||
|
HOSTED_TRANSPORT_WAIT_TIMEOUT_MS);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!registerCallbacksOnce()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
userRecvCallback = recvCallback;
|
||||||
|
|
||||||
|
espnow_bridge_req_init_t req = {};
|
||||||
|
memcpy(req.pmk, config.masterKey, ESPNOW_BRIDGE_KEY_LEN);
|
||||||
|
req.channel = config.channel;
|
||||||
|
req.long_range = config.longRange;
|
||||||
|
req.mode = (config.mode == Mode::AccessPoint) ? ESPNOW_BRIDGE_MODE_ACCESS_POINT : ESPNOW_BRIDGE_MODE_STATION;
|
||||||
|
|
||||||
|
// The slave registers its custom RPC handlers (slave_espnow_bridge_init()) partway through
|
||||||
|
// its own app_main(), which isn't gated on - and can run later than - the CP_INIT event
|
||||||
|
// waitForHostedTransport() already waited for. So the very first REQ_INIT after a cold
|
||||||
|
// boot can still race ahead of the slave being ready to handle it, timing out once even
|
||||||
|
// though the transport itself is fine. Retry a few times with a short backoff before
|
||||||
|
// giving up - the slave finishes registering shortly after CP_INIT in practice.
|
||||||
|
constexpr int MAX_INIT_ATTEMPTS = 4;
|
||||||
|
constexpr TickType_t INIT_RETRY_DELAY = 500U / portTICK_PERIOD_MS;
|
||||||
|
|
||||||
|
esp_err_t err = ESP_FAIL;
|
||||||
|
for (int attempt = 1; attempt <= MAX_INIT_ATTEMPTS; attempt++) {
|
||||||
|
err = doRequest(ESPNOW_BRIDGE_REQ_INIT, reinterpret_cast<uint8_t*>(&req), sizeof(req));
|
||||||
|
if (err == ESP_OK) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (attempt < MAX_INIT_ATTEMPTS) {
|
||||||
|
LOG_W(TAG, "REQ_INIT attempt %d/%d failed (%d) - retrying, slave may still be booting",
|
||||||
|
attempt, MAX_INIT_ATTEMPTS, err);
|
||||||
|
vTaskDelay(INIT_RETRY_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Remote esp_now_init() failed after %d attempts: %d", MAX_INIT_ATTEMPTS, err);
|
||||||
|
userRecvCallback = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
espnowVersion = lastReportedInitVersion;
|
||||||
|
if (espnowVersion != 0) {
|
||||||
|
LOG_I(TAG, "Co-processor ESP-NOW version: %u.0", (unsigned)espnowVersion);
|
||||||
|
} else {
|
||||||
|
LOG_W(TAG, "Co-processor didn't report an ESP-NOW version - assuming v1.0");
|
||||||
|
espnowVersion = 1;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deinit() {
|
||||||
|
espnow_bridge_req_deinit_t req = {};
|
||||||
|
esp_err_t err = doRequest(ESPNOW_BRIDGE_REQ_DEINIT, reinterpret_cast<uint8_t*>(&req), sizeof(req));
|
||||||
|
userRecvCallback = nullptr;
|
||||||
|
espnowVersion = 0;
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Remote esp_now_deinit() failed: %d", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool addPeer(const esp_now_peer_info_t& peer) {
|
||||||
|
espnow_bridge_req_add_peer_t req = {};
|
||||||
|
memcpy(req.peer_addr, peer.peer_addr, ESPNOW_BRIDGE_ETH_ALEN);
|
||||||
|
memcpy(req.lmk, peer.lmk, ESPNOW_BRIDGE_KEY_LEN);
|
||||||
|
req.channel = peer.channel;
|
||||||
|
req.encrypt = peer.encrypt;
|
||||||
|
req.ifidx = (peer.ifidx == WIFI_IF_AP) ? ESPNOW_BRIDGE_MODE_ACCESS_POINT : ESPNOW_BRIDGE_MODE_STATION;
|
||||||
|
|
||||||
|
esp_err_t err = doRequest(ESPNOW_BRIDGE_REQ_ADD_PEER, reinterpret_cast<uint8_t*>(&req), sizeof(req));
|
||||||
|
if (err != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Remote esp_now_add_peer() failed: %d", err);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send(const uint8_t* address, const uint8_t* buffer, size_t bufferLength) {
|
||||||
|
if (bufferLength > ESPNOW_BRIDGE_MAX_DATA_LEN) {
|
||||||
|
LOG_E(TAG, "Payload too large for bridge (%zu bytes)", bufferLength);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
espnow_bridge_req_send_t req = {};
|
||||||
|
req.broadcast = (address == nullptr);
|
||||||
|
if (!req.broadcast) {
|
||||||
|
memcpy(req.dest_addr, address, ESPNOW_BRIDGE_ETH_ALEN);
|
||||||
|
}
|
||||||
|
req.data_len = static_cast<uint16_t>(bufferLength);
|
||||||
|
memcpy(req.data, buffer, bufferLength);
|
||||||
|
|
||||||
|
// Must send the full fixed-size struct, not just header + payload: the slave's on_req_send
|
||||||
|
// validates data_len against sizeof(espnow_bridge_req_send_t) exactly, so a truncated wire
|
||||||
|
// length was always rejected as ESP_ERR_INVALID_SIZE (this made every send fail while
|
||||||
|
// receive still worked fine, since RX events aren't size-truncated the same way).
|
||||||
|
esp_err_t err = doRequest(ESPNOW_BRIDGE_REQ_SEND, reinterpret_cast<uint8_t*>(&req), sizeof(req));
|
||||||
|
return err == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getVersion() {
|
||||||
|
return espnowVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||||
|
|
||||||
|
#include <Tactility/service/espnow/EspNowBackend.h>
|
||||||
|
#include <Tactility/service/wifi/Wifi.h>
|
||||||
|
|
||||||
|
#include <esp_now.h>
|
||||||
|
#include <esp_wifi.h>
|
||||||
|
|
||||||
|
#include <tactility/log.h>
|
||||||
|
|
||||||
|
namespace tt::service::espnow::backend {
|
||||||
|
|
||||||
|
constexpr auto* TAG = "EspNowBackendNative";
|
||||||
|
static bool wifiStartedByEspNow = false;
|
||||||
|
static bool longRangeProtocolApplied = false;
|
||||||
|
static wifi_interface_t longRangeInterface = WIFI_IF_STA;
|
||||||
|
static uint8_t savedProtocolBitmap = 0;
|
||||||
|
|
||||||
|
static bool deinitWifi();
|
||||||
|
|
||||||
|
static bool initWifi(const EspNowConfig& config) {
|
||||||
|
auto wifi_state = wifi::getRadioState();
|
||||||
|
bool wifi_already_running = (wifi_state != wifi::RadioState::Off && wifi_state != wifi::RadioState::OffPending);
|
||||||
|
|
||||||
|
wifi_mode_t mode;
|
||||||
|
if (config.mode == Mode::Station) {
|
||||||
|
mode = wifi_mode_t::WIFI_MODE_STA;
|
||||||
|
} else {
|
||||||
|
mode = wifi_mode_t::WIFI_MODE_AP;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only initialize WiFi if it's not already running; ESP-NOW coexists with STA mode
|
||||||
|
wifiStartedByEspNow = !wifi_already_running;
|
||||||
|
if (wifiStartedByEspNow) {
|
||||||
|
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||||
|
if (esp_wifi_init(&cfg) != ESP_OK) {
|
||||||
|
LOG_E(TAG,"esp_wifi_init() failed");
|
||||||
|
wifiStartedByEspNow = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_wifi_set_storage(WIFI_STORAGE_RAM) != ESP_OK) {
|
||||||
|
LOG_E(TAG,"esp_wifi_set_storage() failed");
|
||||||
|
esp_wifi_deinit();
|
||||||
|
wifiStartedByEspNow = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_wifi_set_mode(mode) != ESP_OK) {
|
||||||
|
LOG_E(TAG,"esp_wifi_set_mode() failed");
|
||||||
|
esp_wifi_deinit();
|
||||||
|
wifiStartedByEspNow = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_wifi_start() != ESP_OK) {
|
||||||
|
LOG_E(TAG,"esp_wifi_start() failed");
|
||||||
|
esp_wifi_deinit();
|
||||||
|
wifiStartedByEspNow = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.channel != 0 && esp_wifi_set_channel(config.channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
|
||||||
|
LOG_E(TAG,"esp_wifi_set_channel() failed");
|
||||||
|
deinitWifi();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (config.channel != 0 &&
|
||||||
|
wifi_state != wifi::RadioState::ConnectionActive && wifi_state != wifi::RadioState::ConnectionPending) {
|
||||||
|
// WifiService already owns the radio but isn't associated to an AP: an unassociated STA's
|
||||||
|
// operating channel is otherwise left undefined/wherever it last scanned to, which silently
|
||||||
|
// breaks ESP-NOW (esp_now_send() reports success but nothing reaches a peer sitting on a
|
||||||
|
// different channel) - only skip this when actually connected/connecting, where the STA is
|
||||||
|
// already locked to the AP's channel and forcing a different one would disrupt that link.
|
||||||
|
if (esp_wifi_set_channel(config.channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
|
||||||
|
LOG_W(TAG, "esp_wifi_set_channel() failed on externally managed radio - ESP-NOW may not reach peers");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.longRange) {
|
||||||
|
wifi_interface_t wifi_interface = (config.mode == Mode::Station) ? WIFI_IF_STA : WIFI_IF_AP;
|
||||||
|
|
||||||
|
// Preserve whatever protocol mask the interface already had (e.g. set by WifiService if
|
||||||
|
// it owns this interface) so it can be restored in deinitWifi() - esp_wifi_set_protocol()
|
||||||
|
// below otherwise permanently stomps it, even when ESP-NOW doesn't own the radio. Skip
|
||||||
|
// applying long-range at all if the snapshot fails: without a saved mask to restore,
|
||||||
|
// changing the protocol would permanently alter an externally-managed interface with no
|
||||||
|
// way to undo it later.
|
||||||
|
uint8_t previousBitmap = 0;
|
||||||
|
bool hadPreviousBitmap = esp_wifi_get_protocol(wifi_interface, &previousBitmap) == ESP_OK;
|
||||||
|
|
||||||
|
if (!hadPreviousBitmap) {
|
||||||
|
LOG_W(TAG, "esp_wifi_get_protocol() failed - skipping long-range (can't safely restore protocol mask later)");
|
||||||
|
} else if (esp_wifi_set_protocol(wifi_interface, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR) != ESP_OK) {
|
||||||
|
LOG_W(TAG,"esp_wifi_set_protocol() for long range failed");
|
||||||
|
} else {
|
||||||
|
longRangeProtocolApplied = true;
|
||||||
|
longRangeInterface = wifi_interface;
|
||||||
|
savedProtocolBitmap = previousBitmap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_I(TAG, "WiFi initialized for ESP-NOW (wifi already running: %s)", wifi_already_running ? "yes" : "no");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool deinitWifi() {
|
||||||
|
// Restore the interface's protocol mask before either stopping WiFi or handing the radio
|
||||||
|
// back to WifiService - covers both paths, since an externally managed radio (wifiStartedByEspNow
|
||||||
|
// == false) is the case that actually needs its prior protocol bits put back.
|
||||||
|
if (longRangeProtocolApplied) {
|
||||||
|
if (esp_wifi_set_protocol(longRangeInterface, savedProtocolBitmap) != ESP_OK) {
|
||||||
|
LOG_W(TAG, "Failed to restore prior WiFi protocol bitmap");
|
||||||
|
}
|
||||||
|
longRangeProtocolApplied = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wifiStartedByEspNow) {
|
||||||
|
esp_wifi_stop();
|
||||||
|
esp_wifi_deinit();
|
||||||
|
wifiStartedByEspNow = false;
|
||||||
|
LOG_I(TAG, "WiFi stopped (was started by ESP-NOW)");
|
||||||
|
} else {
|
||||||
|
LOG_I(TAG, "WiFi left running (managed by WiFi service)");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t espnowVersion = 0;
|
||||||
|
|
||||||
|
bool init(const EspNowConfig& config, esp_now_recv_cb_t recvCallback) {
|
||||||
|
if (!initWifi(config)) {
|
||||||
|
LOG_E(TAG, "initWifi() failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_now_init() != ESP_OK) {
|
||||||
|
LOG_E(TAG, "esp_now_init() failed");
|
||||||
|
deinitWifi();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_now_register_recv_cb(recvCallback) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "esp_now_register_recv_cb() failed");
|
||||||
|
esp_now_deinit();
|
||||||
|
deinitWifi();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (esp_now_set_pmk(config.masterKey) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "esp_now_set_pmk() failed");
|
||||||
|
esp_now_deinit();
|
||||||
|
deinitWifi();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
espnowVersion = 0;
|
||||||
|
if (esp_now_get_version(&espnowVersion) == ESP_OK) {
|
||||||
|
LOG_I(TAG, "ESP-NOW version: %u.0", (unsigned)espnowVersion);
|
||||||
|
} else {
|
||||||
|
LOG_W(TAG, "Failed to get ESP-NOW version");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deinit() {
|
||||||
|
bool success = true;
|
||||||
|
|
||||||
|
if (esp_now_deinit() != ESP_OK) {
|
||||||
|
LOG_E(TAG, "esp_now_deinit() failed");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!deinitWifi()) {
|
||||||
|
LOG_E(TAG, "deinitWifi() failed");
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
espnowVersion = 0;
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool addPeer(const esp_now_peer_info_t& peer) {
|
||||||
|
if (esp_now_add_peer(&peer) != ESP_OK) {
|
||||||
|
LOG_E(TAG, "Failed to add peer");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
LOG_I(TAG, "Peer added");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send(const uint8_t* address, const uint8_t* buffer, size_t bufferLength) {
|
||||||
|
return esp_now_send(address, buffer, bufferLength) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getVersion() {
|
||||||
|
return espnowVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
#include <sdkconfig.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||||
|
|
||||||
|
#include <Tactility/service/espnow/EspNowHostedTransport.h>
|
||||||
|
|
||||||
|
#include <tactility/drivers/wifi.h>
|
||||||
|
|
||||||
|
namespace tt::service::espnow::backend {
|
||||||
|
|
||||||
|
// Thin wrapper: the actual CP_INIT wait/generation-tracking logic lives in the kernel driver
|
||||||
|
// (Platforms/platform-esp32/source/drivers/esp32_esp_hosted_ota.cpp, reached via the generic
|
||||||
|
// FirmwareOps::wait_ready() interface in tactility/drivers/wifi.h) so it's a single source of
|
||||||
|
// truth shared with any other caller (e.g. an external OTA app), instead of being duplicated
|
||||||
|
// here.
|
||||||
|
bool waitForHostedTransport(uint32_t timeoutMs) {
|
||||||
|
Device* device = wifi_find_first_registered_device();
|
||||||
|
if (device == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const FirmwareOps* ops = nullptr;
|
||||||
|
void* ctx = nullptr;
|
||||||
|
if (wifi_get_firmware_ops(device, &ops, &ctx) != ERROR_NONE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ops->wait_ready(ctx, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
#include <sdkconfig.h>
|
#include <sdkconfig.h>
|
||||||
#endif
|
#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/Tactility.h>
|
#include <Tactility/Tactility.h>
|
||||||
#include <Tactility/service/espnow/EspNowService.h>
|
#include <Tactility/service/espnow/EspNowService.h>
|
||||||
#include <Tactility/service/ServiceManifest.h>
|
#include <Tactility/service/ServiceManifest.h>
|
||||||
#include <Tactility/service/ServiceRegistration.h>
|
#include <Tactility/service/ServiceRegistration.h>
|
||||||
#include <Tactility/service/espnow/EspNowWifi.h>
|
#include <Tactility/service/espnow/EspNowBackend.h>
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <esp_now.h>
|
#include <esp_now.h>
|
||||||
@@ -60,33 +60,13 @@ void EspNowService::enableFromDispatcher(const EspNowConfig& config) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!initWifi(config)) {
|
if (!backend::init(config, receiveCallback)) {
|
||||||
LOG_E(TAG,"initWifi() failed");
|
LOG_E(TAG, "backend::init() failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (esp_now_init() != ESP_OK) {
|
espnowVersion = backend::getVersion();
|
||||||
LOG_E(TAG,"esp_now_init() failed");
|
if (espnowVersion != 0) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_now_register_recv_cb(receiveCallback) != ESP_OK) {
|
|
||||||
LOG_E(TAG,"esp_now_register_recv_cb() failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//#if CONFIG_ESPNOW_ENABLE_POWER_SAVE
|
|
||||||
// ESP_ERROR_CHECK( esp_now_set_wake_window(CONFIG_ESPNOW_WAKE_WINDOW) );
|
|
||||||
// ESP_ERROR_CHECK( esp_wifi_connectionless_module_set_wake_interval(CONFIG_ESPNOW_WAKE_INTERVAL) );
|
|
||||||
//#endif
|
|
||||||
|
|
||||||
if (esp_now_set_pmk(config.masterKey) != ESP_OK) {
|
|
||||||
LOG_E(TAG,"esp_now_set_pmk() failed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
espnowVersion = 0;
|
|
||||||
if (esp_now_get_version(&espnowVersion) == ESP_OK) {
|
|
||||||
LOG_I(TAG, "ESP-NOW version: %u.0", (unsigned)espnowVersion);
|
LOG_I(TAG, "ESP-NOW version: %u.0", (unsigned)espnowVersion);
|
||||||
} else {
|
} else {
|
||||||
LOG_W(TAG, "Failed to get ESP-NOW version");
|
LOG_W(TAG, "Failed to get ESP-NOW version");
|
||||||
@@ -119,12 +99,8 @@ void EspNowService::disableFromDispatcher() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (esp_now_deinit() != ESP_OK) {
|
if (!backend::deinit()) {
|
||||||
LOG_E(TAG,"esp_now_deinit() failed");
|
LOG_E(TAG, "backend::deinit() failed");
|
||||||
}
|
|
||||||
|
|
||||||
if (!deinitWifi()) {
|
|
||||||
LOG_E(TAG,"deinitWifi() failed");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
espnowVersion = 0;
|
espnowVersion = 0;
|
||||||
@@ -164,7 +140,7 @@ bool EspNowService::isEnabled() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool EspNowService::addPeer(const esp_now_peer_info_t& peer) {
|
bool EspNowService::addPeer(const esp_now_peer_info_t& peer) {
|
||||||
if (esp_now_add_peer(&peer) != ESP_OK) {
|
if (!backend::addPeer(peer)) {
|
||||||
LOG_E(TAG,"Failed to add peer");
|
LOG_E(TAG,"Failed to add peer");
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
@@ -180,7 +156,7 @@ bool EspNowService::send(const uint8_t* address, const uint8_t* buffer, size_t b
|
|||||||
if (!isEnabled()) {
|
if (!isEnabled()) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return esp_now_send(address, buffer, bufferLength) == ESP_OK;
|
return backend::send(address, buffer, bufferLength);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,4 +199,4 @@ extern const ServiceManifest manifest = {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
#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>
|
|
||||||
#include <Tactility/service/wifi/Wifi.h>
|
|
||||||
|
|
||||||
#include <esp_now.h>
|
|
||||||
#include <esp_wifi.h>
|
|
||||||
|
|
||||||
#include <tactility/log.h>
|
|
||||||
|
|
||||||
namespace tt::service::espnow {
|
|
||||||
|
|
||||||
constexpr auto* TAG = "EspNowService";
|
|
||||||
static bool wifiStartedByEspNow = false;
|
|
||||||
|
|
||||||
bool initWifi(const EspNowConfig& config) {
|
|
||||||
auto wifi_state = wifi::getRadioState();
|
|
||||||
bool wifi_already_running = (wifi_state != wifi::RadioState::Off && wifi_state != wifi::RadioState::OffPending);
|
|
||||||
|
|
||||||
wifi_mode_t mode;
|
|
||||||
if (config.mode == Mode::Station) {
|
|
||||||
mode = wifi_mode_t::WIFI_MODE_STA;
|
|
||||||
} else {
|
|
||||||
mode = wifi_mode_t::WIFI_MODE_AP;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only initialize WiFi if it's not already running; ESP-NOW coexists with STA mode
|
|
||||||
wifiStartedByEspNow = !wifi_already_running;
|
|
||||||
if (wifiStartedByEspNow) {
|
|
||||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
|
||||||
if (esp_wifi_init(&cfg) != ESP_OK) {
|
|
||||||
LOG_E(TAG,"esp_wifi_init() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_wifi_set_storage(WIFI_STORAGE_RAM) != ESP_OK) {
|
|
||||||
LOG_E(TAG,"esp_wifi_set_storage() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_wifi_set_mode(mode) != ESP_OK) {
|
|
||||||
LOG_E(TAG,"esp_wifi_set_mode() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_wifi_start() != ESP_OK) {
|
|
||||||
LOG_E(TAG,"esp_wifi_start() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_wifi_set_channel(config.channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) {
|
|
||||||
LOG_E(TAG,"esp_wifi_set_channel() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.longRange) {
|
|
||||||
wifi_interface_t wifi_interface;
|
|
||||||
if (config.mode == Mode::Station) {
|
|
||||||
wifi_interface = WIFI_IF_STA;
|
|
||||||
} else {
|
|
||||||
wifi_interface = WIFI_IF_AP;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (esp_wifi_set_protocol(wifi_interface, WIFI_PROTOCOL_11B | WIFI_PROTOCOL_11G | WIFI_PROTOCOL_11N | WIFI_PROTOCOL_LR) != ESP_OK) {
|
|
||||||
LOG_W(TAG,"esp_wifi_set_protocol() for long range failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LOG_I(TAG, "WiFi initialized for ESP-NOW (wifi already running: %s)", wifi_already_running ? "yes" : "no");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool deinitWifi() {
|
|
||||||
if (wifiStartedByEspNow) {
|
|
||||||
esp_wifi_stop();
|
|
||||||
esp_wifi_deinit();
|
|
||||||
wifiStartedByEspNow = false;
|
|
||||||
LOG_I(TAG, "WiFi stopped (was started by ESP-NOW)");
|
|
||||||
} else {
|
|
||||||
LOG_I(TAG, "WiFi left running (managed by WiFi service)");
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace tt::service::espnow
|
|
||||||
|
|
||||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
|
||||||
@@ -17,8 +17,10 @@
|
|||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/wifi.h>
|
#include <tactility/drivers/wifi.h>
|
||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
#include <tactility/wifi_auto_scan.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
namespace tt::service::wifi {
|
namespace tt::service::wifi {
|
||||||
|
|
||||||
@@ -63,7 +65,14 @@ struct WifiServiceState {
|
|||||||
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
|
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
|
||||||
RecursiveMutex mutex;
|
RecursiveMutex mutex;
|
||||||
bool secureConnection = false;
|
bool secureConnection = false;
|
||||||
|
// Internal: set by connect()/disconnect() while a manual attempt is in flight, cleared on
|
||||||
|
// connection success/failure. Distinct from externalScanPause below - the two must not
|
||||||
|
// clobber each other, otherwise a caller's explicit pause (e.g. AutoScanPauseGuard during a
|
||||||
|
// co-processor OTA) can be silently cleared by an unrelated connect/disconnect finishing.
|
||||||
bool pauseAutoConnect = false;
|
bool pauseAutoConnect = false;
|
||||||
|
// External: only setAutoScanPaused() may set/clear this. Read alongside pauseAutoConnect to
|
||||||
|
// gate scan scheduling (both must be false to scan).
|
||||||
|
std::atomic<bool> externalScanPause{false};
|
||||||
bool connectionTargetRemember = false;
|
bool connectionTargetRemember = false;
|
||||||
settings::WifiApSettings connectionTarget;
|
settings::WifiApSettings connectionTarget;
|
||||||
uint16_t scanRecordLimit = TT_WIFI_SCAN_RECORD_LIMIT;
|
uint16_t scanRecordLimit = TT_WIFI_SCAN_RECORD_LIMIT;
|
||||||
@@ -224,11 +233,12 @@ bool findAutoConnectAp(settings::WifiApSettings& out) {
|
|||||||
|
|
||||||
void dispatchAutoConnect() {
|
void dispatchAutoConnect() {
|
||||||
LOG_I(TAG, "dispatchAutoConnect()");
|
LOG_I(TAG, "dispatchAutoConnect()");
|
||||||
if (state.pauseAutoConnect) {
|
if (state.pauseAutoConnect || state.externalScanPause.load()) {
|
||||||
// A manual disconnect() or an in-progress manual connect() has paused
|
// A manual disconnect() or an in-progress manual connect() has paused
|
||||||
// auto-connect. This is called on every SCAN_FINISHED, not just the
|
// auto-connect, or a caller (e.g. AutoScanPauseGuard) has externally paused it.
|
||||||
// auto-connect timer's own scans (e.g. WifiManage re-scans on show),
|
// This is called on every SCAN_FINISHED, not just the auto-connect timer's own
|
||||||
// so it must honor the pause instead of reconnecting unconditionally.
|
// scans (e.g. WifiManage re-scans on show), so it must honor the pause instead of
|
||||||
|
// reconnecting unconditionally.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
RadioState radio_state = getRadioState();
|
RadioState radio_state = getRadioState();
|
||||||
@@ -249,7 +259,8 @@ void dispatchAutoConnect() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool shouldScanForAutoConnect() {
|
bool shouldScanForAutoConnect() {
|
||||||
bool radio_scannable = getRadioState() == RadioState::On && !isScanning() && !state.pauseAutoConnect;
|
bool radio_scannable = getRadioState() == RadioState::On && !isScanning() &&
|
||||||
|
!state.pauseAutoConnect && !state.externalScanPause.load();
|
||||||
if (!radio_scannable) return false;
|
if (!radio_scannable) return false;
|
||||||
|
|
||||||
TickType_t current_time = kernel::getTicks();
|
TickType_t current_time = kernel::getTicks();
|
||||||
@@ -328,6 +339,11 @@ void onWifiDeviceEvent(Device* /*device*/, void* /*context*/, ::WifiEvent event)
|
|||||||
publish(event);
|
publish(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void autoScanSetPaused(bool paused) {
|
||||||
|
LOG_I(TAG, "autoScanSetPaused(%d)", (int)paused);
|
||||||
|
state.externalScanPause = paused;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// region Public functions
|
// region Public functions
|
||||||
@@ -416,6 +432,10 @@ void disconnect() {
|
|||||||
getMainDispatcher().dispatch([] { dispatchDisconnect(); });
|
getMainDispatcher().dispatch([] { dispatchDisconnect(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAutoScanPaused(bool paused) {
|
||||||
|
autoScanSetPaused(paused);
|
||||||
|
}
|
||||||
|
|
||||||
void setScanRecords(uint16_t records) {
|
void setScanRecords(uint16_t records) {
|
||||||
LOG_I(TAG, "setScanRecords(%u)", records);
|
LOG_I(TAG, "setScanRecords(%u)", records);
|
||||||
if (!started) return;
|
if (!started) return;
|
||||||
@@ -479,6 +499,8 @@ public:
|
|||||||
bool onStart(ServiceContext& /*service*/) override {
|
bool onStart(ServiceContext& /*service*/) override {
|
||||||
check(!started);
|
check(!started);
|
||||||
|
|
||||||
|
wifi_auto_scan_set_paused_function(autoScanSetPaused);
|
||||||
|
|
||||||
state.device = wifi_find_first_registered_device();
|
state.device = wifi_find_first_registered_device();
|
||||||
if (state.device == nullptr) {
|
if (state.device == nullptr) {
|
||||||
LOG_W(TAG, "No WiFi device found");
|
LOG_W(TAG, "No WiFi device found");
|
||||||
@@ -515,6 +537,8 @@ public:
|
|||||||
state.secureConnection = false;
|
state.secureConnection = false;
|
||||||
state.pauseAutoConnect = false;
|
state.pauseAutoConnect = false;
|
||||||
state.device = nullptr;
|
state.device = nullptr;
|
||||||
|
|
||||||
|
wifi_auto_scan_set_paused_function(nullptr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tt_app.h"
|
||||||
|
#include "tt_bundle.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a file selection dialog that allows the user to select an existing file.
|
||||||
|
* @return the launch ID of the dialog, which can be compared in onResult to identify the source
|
||||||
|
*/
|
||||||
|
AppLaunchId tt_app_fileselection_start_for_existing_file();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a file selection dialog that allows the user to select a new or existing file.
|
||||||
|
* @return the launch ID of the dialog, which can be compared in onResult to identify the source
|
||||||
|
*/
|
||||||
|
AppLaunchId tt_app_fileselection_start_for_existing_or_new_file();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param[in] handle the result bundle passed to onResult
|
||||||
|
* @param[out] buffer the buffer to store the selected path in
|
||||||
|
* @param[in] bufferSize the size of the buffer (must include room for the null terminator)
|
||||||
|
* @return true if a path was selected and written to buffer, false otherwise
|
||||||
|
*/
|
||||||
|
bool tt_app_fileselection_get_result_path(BundleHandle handle, char* buffer, uint32_t bufferSize);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#include "tt_app_fileselection.h"
|
||||||
|
#include <Tactility/app/App.h>
|
||||||
|
#include <Tactility/app/fileselection/FileSelection.h>
|
||||||
|
#include <Tactility/Bundle.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
AppLaunchId tt_app_fileselection_start_for_existing_file() {
|
||||||
|
return tt::app::fileselection::startForExistingFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLaunchId tt_app_fileselection_start_for_existing_or_new_file() {
|
||||||
|
return tt::app::fileselection::startForExistingOrNewFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tt_app_fileselection_get_result_path(BundleHandle handle, char* buffer, uint32_t bufferSize) {
|
||||||
|
auto path = tt::app::fileselection::getResultPath(*(tt::Bundle*)handle);
|
||||||
|
if (path.empty() || bufferSize == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strncpy(buffer, path.c_str(), bufferSize - 1);
|
||||||
|
buffer[bufferSize - 1] = '\0';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "tt_app.h"
|
#include "tt_app.h"
|
||||||
#include "tt_app_alertdialog.h"
|
#include "tt_app_alertdialog.h"
|
||||||
|
#include "tt_app_fileselection.h"
|
||||||
#include "tt_app_selectiondialog.h"
|
#include "tt_app_selectiondialog.h"
|
||||||
#include "tt_bundle.h"
|
#include "tt_bundle.h"
|
||||||
#include "tt_gps.h"
|
#include "tt_gps.h"
|
||||||
@@ -41,6 +42,7 @@
|
|||||||
#include <esp_netif.h>
|
#include <esp_netif.h>
|
||||||
#include <esp_heap_caps.h>
|
#include <esp_heap_caps.h>
|
||||||
#include <esp_timer.h>
|
#include <esp_timer.h>
|
||||||
|
#include <esp_system.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <lwip/sockets.h>
|
#include <lwip/sockets.h>
|
||||||
#include <lwip/netdb.h>
|
#include <lwip/netdb.h>
|
||||||
@@ -274,6 +276,9 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(tt_app_get_parameters),
|
ESP_ELFSYM_EXPORT(tt_app_get_parameters),
|
||||||
ESP_ELFSYM_EXPORT(tt_app_set_result),
|
ESP_ELFSYM_EXPORT(tt_app_set_result),
|
||||||
ESP_ELFSYM_EXPORT(tt_app_has_result),
|
ESP_ELFSYM_EXPORT(tt_app_has_result),
|
||||||
|
ESP_ELFSYM_EXPORT(tt_app_fileselection_start_for_existing_file),
|
||||||
|
ESP_ELFSYM_EXPORT(tt_app_fileselection_start_for_existing_or_new_file),
|
||||||
|
ESP_ELFSYM_EXPORT(tt_app_fileselection_get_result_path),
|
||||||
ESP_ELFSYM_EXPORT(tt_app_selectiondialog_start),
|
ESP_ELFSYM_EXPORT(tt_app_selectiondialog_start),
|
||||||
ESP_ELFSYM_EXPORT(tt_app_selectiondialog_get_result_index),
|
ESP_ELFSYM_EXPORT(tt_app_selectiondialog_get_result_index),
|
||||||
ESP_ELFSYM_EXPORT(tt_app_alertdialog_start),
|
ESP_ELFSYM_EXPORT(tt_app_alertdialog_start),
|
||||||
@@ -460,6 +465,8 @@ const esp_elfsym main_symbols[] {
|
|||||||
ESP_ELFSYM_EXPORT(ppa_do_scale_rotate_mirror),
|
ESP_ELFSYM_EXPORT(ppa_do_scale_rotate_mirror),
|
||||||
// esp_cache.h
|
// esp_cache.h
|
||||||
ESP_ELFSYM_EXPORT(esp_cache_msync),
|
ESP_ELFSYM_EXPORT(esp_cache_msync),
|
||||||
|
// esp_system.h
|
||||||
|
ESP_ELFSYM_EXPORT(esp_restart),
|
||||||
#endif
|
#endif
|
||||||
// delimiter
|
// delimiter
|
||||||
ESP_ELFSYM_END
|
ESP_ELFSYM_END
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
|
#include <tactility/firmware/firmware.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
@@ -196,6 +197,15 @@ struct WifiApi {
|
|||||||
* @return ERROR_NONE on success
|
* @return ERROR_NONE on success
|
||||||
*/
|
*/
|
||||||
error_t (*remove_event_callback)(struct Device* device, WifiEventCallback callback);
|
error_t (*remove_event_callback)(struct Device* device, WifiEventCallback callback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this device's co-processor firmware update interface, if it has one.
|
||||||
|
* @param[in] device the wifi device
|
||||||
|
* @param[out] ops filled in with the FirmwareOps vtable and its ctx, if supported
|
||||||
|
* @return ERROR_NONE on success, ERROR_NOT_SUPPORTED if this device has no updatable
|
||||||
|
* co-processor (ops is left untouched in that case)
|
||||||
|
*/
|
||||||
|
error_t (*get_firmware_ops)(struct Device* device, const struct FirmwareOps** ops, void** ctx);
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct DeviceType WIFI_TYPE;
|
extern const struct DeviceType WIFI_TYPE;
|
||||||
@@ -216,6 +226,7 @@ error_t wifi_station_disconnect(struct Device* device);
|
|||||||
error_t wifi_station_get_rssi(struct Device* device, int32_t* rssi);
|
error_t wifi_station_get_rssi(struct Device* device, int32_t* rssi);
|
||||||
error_t wifi_add_event_callback(struct Device* device, void* callback_context, WifiEventCallback callback);
|
error_t wifi_add_event_callback(struct Device* device, void* callback_context, WifiEventCallback callback);
|
||||||
error_t wifi_remove_event_callback(struct Device* device, WifiEventCallback callback);
|
error_t wifi_remove_event_callback(struct Device* device, WifiEventCallback callback);
|
||||||
|
error_t wifi_get_firmware_ops(struct Device* device, const struct FirmwareOps** ops, void** ctx);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <tactility/error.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generic co-processor/companion-radio firmware info & update interface.
|
||||||
|
*
|
||||||
|
* Not tied to any particular driver: some devices are backed by a separate co-processor with its
|
||||||
|
* own updatable firmware (e.g. esp-hosted-mcu's P4+C6/C5 pairing, reachable via
|
||||||
|
* WifiApi::get_firmware_ops() in tactility/drivers/wifi.h); most aren't. A driver with no such
|
||||||
|
* co-processor returns ERROR_NOT_SUPPORTED from whatever function exposes this interface
|
||||||
|
* (leaving its output params untouched) - callers must check the result before using any
|
||||||
|
* FirmwareOps function.
|
||||||
|
*/
|
||||||
|
struct FirmwareInfo {
|
||||||
|
char name[32]; // human-readable target name, e.g. "esp32c6" - empty if unavailable
|
||||||
|
uint32_t hw_id; // raw hardware/chip id, implementation-defined
|
||||||
|
uint32_t fw_major;
|
||||||
|
uint32_t fw_minor;
|
||||||
|
uint32_t fw_patch;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FirmwareUpdateRequest {
|
||||||
|
size_t image_size;
|
||||||
|
uint32_t flags;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FirmwareUpdateHandle;
|
||||||
|
|
||||||
|
struct FirmwareOps {
|
||||||
|
/**
|
||||||
|
* Wait for the co-processor to be ready to accept firmware info/update calls.
|
||||||
|
* @param[in] ctx opaque context, passed to every other FirmwareOps function
|
||||||
|
* @param[in] timeout_ms how long to wait
|
||||||
|
* @return true if ready, false on timeout
|
||||||
|
*/
|
||||||
|
bool (*wait_ready)(void* ctx, uint32_t timeout_ms);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the co-processor's current firmware/hardware info.
|
||||||
|
* @return ERROR_NONE on success, or an error code (e.g. not ready)
|
||||||
|
*/
|
||||||
|
error_t (*get_info)(void* ctx, struct FirmwareInfo* info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Begin a firmware update, filling in *handle for use with write()/finish()/abort().
|
||||||
|
* @return ERROR_NONE on success, or an error code
|
||||||
|
*/
|
||||||
|
error_t (*begin)(void* ctx, const struct FirmwareUpdateRequest* req, struct FirmwareUpdateHandle** handle);
|
||||||
|
|
||||||
|
/** Write a chunk of firmware image data. @return ERROR_NONE on success, or an error code */
|
||||||
|
error_t (*write)(struct FirmwareUpdateHandle* handle, const void* data, size_t len);
|
||||||
|
|
||||||
|
/** Finalize a successful update. @return ERROR_NONE on success, or an error code */
|
||||||
|
error_t (*finish)(struct FirmwareUpdateHandle* handle);
|
||||||
|
|
||||||
|
/** Abort an in-progress update (e.g. after a write() failure). */
|
||||||
|
error_t (*abort)(struct FirmwareUpdateHandle* handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate the most recently finish()ed firmware, causing the co-processor to reboot into it.
|
||||||
|
* @note Not guaranteed to be supported by every co-processor firmware version - callers should
|
||||||
|
* check get_info()'s reported version against their own known-good threshold before calling
|
||||||
|
* this, and fall back to treating the update as complete after finish() otherwise.
|
||||||
|
*/
|
||||||
|
error_t (*activate)(void* ctx);
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Suspend or resume WifiService's periodic background auto-connect scan.
|
||||||
|
*
|
||||||
|
* Not a device operation - see tactility/drivers/wifi.h for the actual radio API. This exists
|
||||||
|
* for narrow, short-lived windows where any WiFi/co-processor traffic would be unsafe (e.g. a
|
||||||
|
* known co-processor reboot in progress during an OTA update) - callers must resume when done.
|
||||||
|
* Independent of WifiService's own internal connect()/disconnect() pause bookkeeping: it is
|
||||||
|
* never cleared implicitly by a connection succeeding/failing or the radio being enabled, only a
|
||||||
|
* matching wifi_auto_scan_set_paused(false) clears it.
|
||||||
|
*
|
||||||
|
* The real implementation lives in the Tactility WiFi service
|
||||||
|
* (Tactility/Source/service/wifi/Wifi.cpp), a layer above TactilityKernel - TactilityKernel
|
||||||
|
* can't call up into it directly (and mustn't link against it: TactilityKernelTests links
|
||||||
|
* TactilityKernel alone, without Tactility). Tactility registers its implementation at startup
|
||||||
|
* via wifi_auto_scan_set_paused_function(); until then (or on a build that never links Tactility, e.g. a
|
||||||
|
* bare-kernel target) this is a no-op, same as WifiApi::get_firmware_ops() returning
|
||||||
|
* ERROR_NOT_SUPPORTED when nothing is registered.
|
||||||
|
*
|
||||||
|
* @param[in] paused when true, the auto-connect timer stops issuing scans until resumed
|
||||||
|
*/
|
||||||
|
void wifi_auto_scan_set_paused(bool paused);
|
||||||
|
|
||||||
|
/** @brief Register the real implementation. Called once by the Tactility WiFi service. */
|
||||||
|
void wifi_auto_scan_set_paused_function(void (*set_paused)(bool paused));
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -68,6 +68,14 @@ error_t wifi_remove_event_callback(struct Device* device, WifiEventCallback call
|
|||||||
return WIFI_API(device)->remove_event_callback(device, callback);
|
return WIFI_API(device)->remove_event_callback(device, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error_t wifi_get_firmware_ops(struct Device* device, const struct FirmwareOps** ops, void** ctx) {
|
||||||
|
auto* api = WIFI_API(device);
|
||||||
|
if (api->get_firmware_ops == nullptr) {
|
||||||
|
return ERROR_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
return api->get_firmware_ops(device, ops, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
const struct DeviceType WIFI_TYPE = {
|
const struct DeviceType WIFI_TYPE = {
|
||||||
.name = "wifi"
|
.name = "wifi"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include <tactility/error.h>
|
#include <tactility/error.h>
|
||||||
#include <tactility/filesystem/file_system.h>
|
#include <tactility/filesystem/file_system.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
#include <tactility/wifi_auto_scan.h>
|
||||||
#include <tactility/service/service_instance.h>
|
#include <tactility/service/service_instance.h>
|
||||||
#include <tactility/service/service_manager.h>
|
#include <tactility/service/service_manager.h>
|
||||||
#include <tactility/service/service_paths.h>
|
#include <tactility/service/service_paths.h>
|
||||||
@@ -326,7 +327,10 @@ const struct ModuleSymbol KERNEL_SYMBOLS[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(wifi_station_get_rssi),
|
DEFINE_MODULE_SYMBOL(wifi_station_get_rssi),
|
||||||
DEFINE_MODULE_SYMBOL(wifi_add_event_callback),
|
DEFINE_MODULE_SYMBOL(wifi_add_event_callback),
|
||||||
DEFINE_MODULE_SYMBOL(wifi_remove_event_callback),
|
DEFINE_MODULE_SYMBOL(wifi_remove_event_callback),
|
||||||
|
DEFINE_MODULE_SYMBOL(wifi_get_firmware_ops),
|
||||||
DEFINE_MODULE_SYMBOL(WIFI_TYPE),
|
DEFINE_MODULE_SYMBOL(WIFI_TYPE),
|
||||||
|
// wifi_auto_scan
|
||||||
|
DEFINE_MODULE_SYMBOL(wifi_auto_scan_set_paused),
|
||||||
// drivers/usb_host_hid
|
// drivers/usb_host_hid
|
||||||
DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected),
|
DEFINE_MODULE_SYMBOL(usb_host_hid_is_connected),
|
||||||
DEFINE_MODULE_SYMBOL(usb_host_hid_subscribe),
|
DEFINE_MODULE_SYMBOL(usb_host_hid_subscribe),
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#include <tactility/wifi_auto_scan.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
static void (*registeredSetPaused)(bool paused) = NULL;
|
||||||
|
|
||||||
|
void wifi_auto_scan_set_paused_function(void (*set_paused)(bool paused)) {
|
||||||
|
registeredSetPaused = set_paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
void wifi_auto_scan_set_paused(bool paused) {
|
||||||
|
if (registeredSetPaused != NULL) {
|
||||||
|
registeredSetPaused(paused);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user