ESP-NOW bridge for P4 (#573)
This commit is contained in:
@@ -80,7 +80,7 @@ namespace service {
|
||||
#ifdef ESP_PLATFORM
|
||||
namespace development { extern const ServiceManifest manifest; }
|
||||
#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; }
|
||||
#endif
|
||||
// Secondary (UI)
|
||||
@@ -161,7 +161,7 @@ namespace app {
|
||||
namespace screenshot { extern const AppManifest manifest; }
|
||||
#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; }
|
||||
#endif
|
||||
}
|
||||
@@ -229,7 +229,7 @@ static void registerInternalApps() {
|
||||
addAppManifest(app::screenshot::manifest);
|
||||
#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);
|
||||
#endif
|
||||
|
||||
@@ -328,7 +328,7 @@ static void registerAndStartPrimaryServices() {
|
||||
addService(service::development::manifest);
|
||||
#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);
|
||||
#endif
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
|
||||
#include <Tactility/app/chat/ChatAppPrivate.h>
|
||||
#include <Tactility/app/chat/ChatProtocol.h>
|
||||
@@ -187,4 +187,4 @@ extern const AppManifest manifest = {
|
||||
|
||||
} // 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>
|
||||
#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/service/espnow/EspNow.h>
|
||||
@@ -171,4 +171,4 @@ size_t getMaxMessageLength(size_t nicknameLen, size_t targetLen) {
|
||||
|
||||
} // namespace tt::app::chat
|
||||
|
||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
|
||||
#include <Tactility/app/chat/ChatSettings.h>
|
||||
#include <Tactility/app/chat/ChatProtocol.h>
|
||||
@@ -201,4 +201,4 @@ bool settingsFileExists() {
|
||||
|
||||
} // namespace tt::app::chat
|
||||
|
||||
#endif // CONFIG_SOC_WIFI_SUPPORTED && !CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||
#endif // CONFIG_SOC_WIFI_SUPPORTED || CONFIG_SLAVE_SOC_WIFI_SUPPORTED
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#include <sdkconfig.h>
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) && !defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
#if defined(CONFIG_SOC_WIFI_SUPPORTED) || defined(CONFIG_SLAVE_SOC_WIFI_SUPPORTED)
|
||||
|
||||
#include <Tactility/app/chat/ChatState.h>
|
||||
|
||||
@@ -57,4 +57,4 @@ std::vector<StoredMessage> ChatState::getFilteredMessages() const {
|
||||
|
||||
} // 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>
|
||||
#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/ChatAppPrivate.h>
|
||||
@@ -296,4 +296,4 @@ void ChatView::onChannelCancel(lv_event_t* e) {
|
||||
|
||||
} // 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>
|
||||
#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/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>
|
||||
#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/service/espnow/EspNowService.h>
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
#include <Tactility/service/espnow/EspNowWifi.h>
|
||||
#include <Tactility/service/espnow/EspNowBackend.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <esp_now.h>
|
||||
@@ -60,33 +60,13 @@ void EspNowService::enableFromDispatcher(const EspNowConfig& config) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!initWifi(config)) {
|
||||
LOG_E(TAG,"initWifi() failed");
|
||||
if (!backend::init(config, receiveCallback)) {
|
||||
LOG_E(TAG, "backend::init() failed");
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_now_init() != ESP_OK) {
|
||||
LOG_E(TAG,"esp_now_init() failed");
|
||||
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) {
|
||||
espnowVersion = backend::getVersion();
|
||||
if (espnowVersion != 0) {
|
||||
LOG_I(TAG, "ESP-NOW version: %u.0", (unsigned)espnowVersion);
|
||||
} else {
|
||||
LOG_W(TAG, "Failed to get ESP-NOW version");
|
||||
@@ -119,12 +99,8 @@ void EspNowService::disableFromDispatcher() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (esp_now_deinit() != ESP_OK) {
|
||||
LOG_E(TAG,"esp_now_deinit() failed");
|
||||
}
|
||||
|
||||
if (!deinitWifi()) {
|
||||
LOG_E(TAG,"deinitWifi() failed");
|
||||
if (!backend::deinit()) {
|
||||
LOG_E(TAG, "backend::deinit() failed");
|
||||
}
|
||||
|
||||
espnowVersion = 0;
|
||||
@@ -164,7 +140,7 @@ bool EspNowService::isEnabled() const {
|
||||
}
|
||||
|
||||
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");
|
||||
return false;
|
||||
} else {
|
||||
@@ -180,7 +156,7 @@ bool EspNowService::send(const uint8_t* address, const uint8_t* buffer, size_t b
|
||||
if (!isEnabled()) {
|
||||
return false;
|
||||
} 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/drivers/wifi.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/wifi_auto_scan.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
|
||||
namespace tt::service::wifi {
|
||||
|
||||
@@ -63,7 +65,14 @@ struct WifiServiceState {
|
||||
std::shared_ptr<PubSub<WifiEvent>> pubsub = std::make_shared<PubSub<WifiEvent>>();
|
||||
RecursiveMutex mutex;
|
||||
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;
|
||||
// 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;
|
||||
settings::WifiApSettings connectionTarget;
|
||||
uint16_t scanRecordLimit = TT_WIFI_SCAN_RECORD_LIMIT;
|
||||
@@ -224,11 +233,12 @@ bool findAutoConnectAp(settings::WifiApSettings& out) {
|
||||
|
||||
void 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
|
||||
// auto-connect. This is called on every SCAN_FINISHED, not just the
|
||||
// auto-connect timer's own scans (e.g. WifiManage re-scans on show),
|
||||
// so it must honor the pause instead of reconnecting unconditionally.
|
||||
// auto-connect, or a caller (e.g. AutoScanPauseGuard) has externally paused it.
|
||||
// This is called on every SCAN_FINISHED, not just the auto-connect timer's own
|
||||
// scans (e.g. WifiManage re-scans on show), so it must honor the pause instead of
|
||||
// reconnecting unconditionally.
|
||||
return;
|
||||
}
|
||||
RadioState radio_state = getRadioState();
|
||||
@@ -249,7 +259,8 @@ void dispatchAutoConnect() {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
TickType_t current_time = kernel::getTicks();
|
||||
@@ -328,6 +339,11 @@ void onWifiDeviceEvent(Device* /*device*/, void* /*context*/, ::WifiEvent event)
|
||||
publish(event);
|
||||
}
|
||||
|
||||
void autoScanSetPaused(bool paused) {
|
||||
LOG_I(TAG, "autoScanSetPaused(%d)", (int)paused);
|
||||
state.externalScanPause = paused;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// region Public functions
|
||||
@@ -416,6 +432,10 @@ void disconnect() {
|
||||
getMainDispatcher().dispatch([] { dispatchDisconnect(); });
|
||||
}
|
||||
|
||||
void setAutoScanPaused(bool paused) {
|
||||
autoScanSetPaused(paused);
|
||||
}
|
||||
|
||||
void setScanRecords(uint16_t records) {
|
||||
LOG_I(TAG, "setScanRecords(%u)", records);
|
||||
if (!started) return;
|
||||
@@ -479,6 +499,8 @@ public:
|
||||
bool onStart(ServiceContext& /*service*/) override {
|
||||
check(!started);
|
||||
|
||||
wifi_auto_scan_set_paused_function(autoScanSetPaused);
|
||||
|
||||
state.device = wifi_find_first_registered_device();
|
||||
if (state.device == nullptr) {
|
||||
LOG_W(TAG, "No WiFi device found");
|
||||
@@ -515,6 +537,8 @@ public:
|
||||
state.secureConnection = false;
|
||||
state.pauseAutoConnect = false;
|
||||
state.device = nullptr;
|
||||
|
||||
wifi_auto_scan_set_paused_function(nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user