From 49a46f6236d33aba63241a43ef9ee83257630102 Mon Sep 17 00:00:00 2001 From: Ken Van Hoeylandt Date: Sat, 29 Mar 2025 10:45:34 +0100 Subject: [PATCH] ESP-NOW service (#259) Implemented Chat app with ESP-NOW service. The Chat app was originally created by @maxbrito500 and adapted by me. --- Tactility/Source/Tactility.cpp | 2 + Tactility/Source/app/chat/ChatApp.cpp | 179 ++++++++++++++ .../Include/Tactility/service/espnow/EspNow.h | 54 +++++ .../Tactility/service/espnow/EspNowService.h | 75 ++++++ .../Tactility/service/espnow/EspNowWifi.h | 11 + .../Source/TactilityHeadless.cpp | 6 + .../Source/service/espnow/EspNow.cpp | 80 +++++++ .../Source/service/espnow/EspNowService.cpp | 222 ++++++++++++++++++ .../Source/service/espnow/EspNowWifi.cpp | 106 +++++++++ 9 files changed, 735 insertions(+) create mode 100644 Tactility/Source/app/chat/ChatApp.cpp create mode 100644 TactilityHeadless/Include/Tactility/service/espnow/EspNow.h create mode 100644 TactilityHeadless/Private/Tactility/service/espnow/EspNowService.h create mode 100644 TactilityHeadless/Private/Tactility/service/espnow/EspNowWifi.h create mode 100644 TactilityHeadless/Source/service/espnow/EspNow.cpp create mode 100644 TactilityHeadless/Source/service/espnow/EspNowService.cpp create mode 100644 TactilityHeadless/Source/service/espnow/EspNowWifi.cpp diff --git a/Tactility/Source/Tactility.cpp b/Tactility/Source/Tactility.cpp index 8a8508ff..b14c3e2b 100644 --- a/Tactility/Source/Tactility.cpp +++ b/Tactility/Source/Tactility.cpp @@ -32,6 +32,7 @@ namespace service { namespace app { namespace alertdialog { extern const AppManifest manifest; } namespace applist { extern const AppManifest manifest; } + namespace chat { extern const AppManifest manifest; } namespace boot { extern const AppManifest manifest; } namespace display { extern const AppManifest manifest; } namespace files { extern const AppManifest manifest; } @@ -98,6 +99,7 @@ static void registerSystemApps() { #endif #ifdef ESP_PLATFORM + addApp(app::chat::manifest); addApp(app::crashdiagnostics::manifest); #endif diff --git a/Tactility/Source/app/chat/ChatApp.cpp b/Tactility/Source/app/chat/ChatApp.cpp new file mode 100644 index 00000000..f0463320 --- /dev/null +++ b/Tactility/Source/app/chat/ChatApp.cpp @@ -0,0 +1,179 @@ +#ifdef ESP_PLATFORM + +#include +#include +#include + +#include "Tactility/service/gui/Gui.h" +#include "Tactility/lvgl/LvglSync.h" + +#include +#include +#include +#include + +namespace tt::app::chat { + +constexpr const char* TAG = "ChatApp"; +constexpr const uint8_t BROADCAST_ADDRESS[ESP_NOW_ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + +class ChatApp : public App { + + lv_obj_t* msg_list = nullptr; + lv_obj_t* input_field = nullptr; + service::espnow::ReceiverSubscription receiveSubscription; + + void addMessage(const char* message) { + lv_obj_t* msg_label = lv_label_create(msg_list); + lv_label_set_text(msg_label, message); + lv_obj_set_width(msg_label, lv_pct(100)); + lv_label_set_long_mode(msg_label, LV_LABEL_LONG_WRAP); + lv_obj_set_style_text_align(msg_label, LV_TEXT_ALIGN_LEFT, 0); + lv_obj_set_style_pad_all(msg_label, 2, 0); + lv_obj_scroll_to_y(msg_list, lv_obj_get_scroll_y(msg_list) + 20, LV_ANIM_ON); + } + + static void onQuickSendClicked(lv_event_t* e) { + auto* self = static_cast(lv_event_get_user_data(e)); + auto* btn = static_cast(lv_event_get_target(e)); + const char* message = lv_label_get_text(lv_obj_get_child(btn, 0)); + + if (message) { + self->addMessage(message); + if (!service::espnow::send(BROADCAST_ADDRESS, (const uint8_t*)message, strlen(message))) { + TT_LOG_E(TAG, "Failed to send message"); + } + } + } + + static void onSendClicked(lv_event_t* e) { + auto* self = static_cast(lv_event_get_user_data(e)); + auto* msg = lv_textarea_get_text(self->input_field); + auto msg_len = strlen(msg); + + if (self->msg_list && msg && msg_len) { + self->addMessage(msg); + + if (!service::espnow::send(BROADCAST_ADDRESS, (const uint8_t*)msg, msg_len)) { + TT_LOG_E(TAG, "Failed to send message"); + } + + lv_textarea_set_text(self->input_field, ""); + } + } + + void onReceive(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length) { + // Append \0 to make it a string + char* buffer = (char*)malloc(length + 1); + memcpy(buffer, data, length); + buffer[length] = 0x00; + std::string message_prefixed = std::string("Received: ") + buffer; + + tt::lvgl::getSyncLock()->lock(); + addMessage(message_prefixed.c_str()); + tt::lvgl::getSyncLock()->unlock(); + + free(buffer); + } + +public: + + void onCreate(AppContext& appContext) override { + // TODO: Move this to a configuration screen/app + static const uint8_t key[ESP_NOW_KEY_LEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + auto config = service::espnow::EspNowConfig( + (uint8_t*)key, + service::espnow::Mode::Station, + 1, + false, + false + ); + + service::espnow::enable(config); + + receiveSubscription = service::espnow::subscribeReceiver([this](const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length) { + onReceive(receiveInfo, data, length); + }); + } + + void onDestroy(AppContext& appContext) override { + service::espnow::unsubscribeReceiver(receiveSubscription); + + if (service::espnow::isEnabled()) { + service::espnow::disable(); + } + } + + void onShow(AppContext& context, lv_obj_t* parent) override { + + // Create toolbar + auto* toolbar = tt::lvgl::toolbar_create(parent, context); + lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); + const int toolbar_height = lv_obj_get_height(toolbar); + + // Message list + msg_list = lv_list_create(parent); + lv_obj_set_size(msg_list, lv_pct(75), lv_pct(43)); + lv_obj_align(msg_list, LV_ALIGN_TOP_LEFT, 5, toolbar_height + 45); + lv_obj_set_style_bg_color(msg_list, lv_color_hex(0xEEEEEE), 0); + lv_obj_set_style_border_width(msg_list, 1, 0); + lv_obj_set_style_pad_all(msg_list, 5, 0); + + // Quick message panel + auto* quick_panel = lv_obj_create(parent); + lv_obj_set_size(quick_panel, lv_pct(20), lv_pct(58)); + lv_obj_align(quick_panel, LV_ALIGN_TOP_RIGHT, -5, toolbar_height + 15); // Adjusted to match + lv_obj_set_flex_flow(quick_panel, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(quick_panel, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_all(quick_panel, 5, 0); + + // Quick message buttons + const char* quick_msgs[] = {":-)", "+1", ":-("}; + for (size_t i = 0; i < sizeof(quick_msgs)/sizeof(quick_msgs[0]); i++) { + lv_obj_t* btn = lv_btn_create(quick_panel); + lv_obj_set_size(btn, lv_pct(75), 25); + lv_obj_add_event_cb(btn, onQuickSendClicked, LV_EVENT_CLICKED, this); + + lv_obj_t* label = lv_label_create(btn); + lv_label_set_text(label, quick_msgs[i]); + lv_obj_center(label); + } + + // Input panel + auto* input_panel = lv_obj_create(parent); + lv_obj_set_size(input_panel, lv_pct(95), 60); + lv_obj_align(input_panel, LV_ALIGN_BOTTOM_MID, 0, -5); + lv_obj_set_flex_flow(input_panel, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(input_panel, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_set_style_pad_all(input_panel, 5, 0); + + // Input field + input_field = lv_textarea_create(input_panel); + lv_obj_set_flex_grow(input_field, 1); + lv_obj_set_height(input_field, LV_PCT(100)); + lv_textarea_set_placeholder_text(input_field, "Type a message..."); + lv_textarea_set_one_line(input_field, true); + service::gui::keyboardAddTextArea(input_field); + + // Send button + auto* send_btn = lv_btn_create(input_panel); + lv_obj_set_size(send_btn, 80, LV_PCT(100)); + lv_obj_add_event_cb(send_btn, onSendClicked, LV_EVENT_CLICKED, this); + + auto* btn_label = lv_label_create(send_btn); + lv_label_set_text(btn_label, "Send"); + lv_obj_center(btn_label); + } + + ~ChatApp() override = default; +}; + +extern const AppManifest manifest = { + .id = "Chat", + .name = "Chat", + .createApp = create +}; + +} + +#endif diff --git a/TactilityHeadless/Include/Tactility/service/espnow/EspNow.h b/TactilityHeadless/Include/Tactility/service/espnow/EspNow.h new file mode 100644 index 00000000..0b9dcc5b --- /dev/null +++ b/TactilityHeadless/Include/Tactility/service/espnow/EspNow.h @@ -0,0 +1,54 @@ +#pragma once +#ifdef ESP_PLATFORM + +#include +#include +#include +#include + +namespace tt::service::espnow { + +typedef int ReceiverSubscription; +constexpr ReceiverSubscription NO_SUBSCRIPTION = -1; + +enum class Mode { + Station, + AccessPoint +}; + +struct EspNowConfig { + uint8_t masterKey[ESP_NOW_KEY_LEN]; + uint8_t address[ESP_NOW_ETH_ALEN]; + Mode mode; + uint8_t channel; + bool longRange; + bool encrypt; + + EspNowConfig( + uint8_t masterKey[16], + Mode mode, + uint8_t channel, + bool longRange, + bool encrypt + ) : mode(mode), channel(channel), longRange(longRange), encrypt(encrypt) { + memcpy((void*)this->masterKey, (void*)masterKey, 16); + } +}; + +void enable(const EspNowConfig& config); + +void disable(); + +bool isEnabled(); + +bool addPeer(const esp_now_peer_info_t& peer); + +bool send(const uint8_t* address, const uint8_t* buffer, size_t bufferLength); + +ReceiverSubscription subscribeReceiver(std::function onReceive); + +void unsubscribeReceiver(ReceiverSubscription subscription); + +} + +#endif // ESP_PLATFORM \ No newline at end of file diff --git a/TactilityHeadless/Private/Tactility/service/espnow/EspNowService.h b/TactilityHeadless/Private/Tactility/service/espnow/EspNowService.h new file mode 100644 index 00000000..7ee04e17 --- /dev/null +++ b/TactilityHeadless/Private/Tactility/service/espnow/EspNowService.h @@ -0,0 +1,75 @@ +#pragma once +#ifdef ESP_PLATFORM + +#include "Tactility/MessageQueue.h" +#include "Tactility/service/Service.h" +#include "Tactility/service/espnow/EspNow.h" + +#include + +#include + +namespace tt::service::espnow { + +class EspNowService final : public Service { + +private: + + struct ReceiverSubscriptionData { + ReceiverSubscription id; + std::function onReceive; + }; + + struct SendCallback { + uint8_t macAddress[ESP_NOW_ETH_ALEN]; + bool success; + }; + + Mutex mutex = Mutex(Mutex::Type::Recursive); + std::vector subscriptions; + ReceiverSubscription lastSubscriptionId = 0; + bool enabled = false; + + // Dispatcher calls this and forwards to non-static function + static void enableFromDispatcher(std::shared_ptr context); + void enableFromDispatcher(const EspNowConfig& config); + + static void disableFromDispatcher(std::shared_ptr context); + void disableFromDispatcher(); + + static void receiveCallback(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length); + void onReceive(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length); + +public: + + // region Overrides + + void onStart(ServiceContext& service) override; + void onStop(ServiceContext& service) override; + + // endregion Overrides + + // region Internal API + + void enable(const EspNowConfig& config); + + void disable(); + + bool isEnabled() const; + + bool addPeer(const esp_now_peer_info_t& peer); + + bool send(const uint8_t* address, const uint8_t* buffer, size_t bufferLength); + + ReceiverSubscription subscribeReceiver(std::function onReceive); + + void unsubscribeReceiver(ReceiverSubscription subscription); + + // region Internal API +}; + +std::shared_ptr findService(); + +} + +#endif // ESP_PLATFORM diff --git a/TactilityHeadless/Private/Tactility/service/espnow/EspNowWifi.h b/TactilityHeadless/Private/Tactility/service/espnow/EspNowWifi.h new file mode 100644 index 00000000..2b031f82 --- /dev/null +++ b/TactilityHeadless/Private/Tactility/service/espnow/EspNowWifi.h @@ -0,0 +1,11 @@ +#pragma once + +#include "Tactility/service/espnow/EspNow.h" + +namespace tt::service::espnow { + +bool initWifi(const EspNowConfig& config); + +bool deinitWifi(); + +} diff --git a/TactilityHeadless/Source/TactilityHeadless.cpp b/TactilityHeadless/Source/TactilityHeadless.cpp index 93bf8658..7b968f6e 100644 --- a/TactilityHeadless/Source/TactilityHeadless.cpp +++ b/TactilityHeadless/Source/TactilityHeadless.cpp @@ -19,6 +19,9 @@ namespace tt { namespace service::gps { extern const ServiceManifest manifest; } namespace service::wifi { extern const ServiceManifest manifest; } namespace service::sdcard { extern const ServiceManifest manifest; } +#ifdef ESP_PLATFORM +namespace service::espnow { extern const ServiceManifest manifest; } +#endif static Dispatcher mainDispatcher; @@ -29,6 +32,9 @@ static void registerAndStartSystemServices() { addService(service::gps::manifest); addService(service::sdcard::manifest); addService(service::wifi::manifest); +#ifdef ESP_PLATFORM + addService(service::espnow::manifest); +#endif } void initHeadless(const hal::Configuration& config) { diff --git a/TactilityHeadless/Source/service/espnow/EspNow.cpp b/TactilityHeadless/Source/service/espnow/EspNow.cpp new file mode 100644 index 00000000..7d09f7dd --- /dev/null +++ b/TactilityHeadless/Source/service/espnow/EspNow.cpp @@ -0,0 +1,80 @@ +#ifdef ESP_PLATFORM + +#include "Tactility/service/espnow/EspNow.h" +#include "Tactility/service/espnow/EspNowService.h" + +#include + +namespace tt::service::espnow { + +constexpr const char* TAG = "EspNow"; + +void enable(const EspNowConfig& config) { + auto service = findService(); + if (service != nullptr) { + service->enable(config); + } else { + TT_LOG_E(TAG, "Service not found"); + } +} + +void disable() { + auto service = findService(); + if (service != nullptr) { + service->disable(); + } else { + TT_LOG_E(TAG, "Service not found"); + } +} + +bool isEnabled() { + auto service = findService(); + if (service != nullptr) { + return service->isEnabled(); + } else { + return false; + } +} + +bool addPeer(const esp_now_peer_info_t& peer) { + auto service = findService(); + if (service != nullptr) { + return service->addPeer(peer); + } else { + TT_LOG_E(TAG, "Service not found"); + return false; + } +} + +bool send(const uint8_t* address, const uint8_t* buffer, size_t bufferLength) { + auto service = findService(); + if (service != nullptr) { + return service->send(address, buffer, bufferLength); + } else { + TT_LOG_E(TAG, "Service not found"); + return false; + } +} + +ReceiverSubscription subscribeReceiver(std::function onReceive) { + auto service = findService(); + if (service != nullptr) { + return service->subscribeReceiver(onReceive); + } else { + TT_LOG_E(TAG, "Service not found"); + return -1; + } +} + +void unsubscribeReceiver(ReceiverSubscription subscription) { + auto service = findService(); + if (service != nullptr) { + service->unsubscribeReceiver(subscription); + } else { + TT_LOG_E(TAG, "Service not found"); + } +} + +} + +#endif // ESP_PLATFORM diff --git a/TactilityHeadless/Source/service/espnow/EspNowService.cpp b/TactilityHeadless/Source/service/espnow/EspNowService.cpp new file mode 100644 index 00000000..7fc988b4 --- /dev/null +++ b/TactilityHeadless/Source/service/espnow/EspNowService.cpp @@ -0,0 +1,222 @@ +#ifdef ESP_PLATFORM + +#include "Tactility/service/espnow/EspNowService.h" +#include "Tactility/TactilityHeadless.h" +#include "Tactility/service/ServiceManifest.h" +#include "Tactility/service/ServiceRegistry.h" +#include "Tactility/service/espnow/EspNowWifi.h" +#include +#include +#include + +namespace tt::service::espnow { + +extern const ServiceManifest manifest; + +constexpr const char* TAG = "EspNowService"; +constexpr TickType_t MAX_DELAY = 1000U / portTICK_PERIOD_MS; +static uint8_t BROADCAST_MAC[ESP_NOW_ETH_ALEN]; + +constexpr bool isBroadcastAddress(uint8_t address[ESP_NOW_ETH_ALEN]) { return memcmp(address, BROADCAST_MAC, ESP_NOW_ETH_ALEN) == 0; } + +void EspNowService::onStart(ServiceContext& service) { + auto lock = mutex.asScopedLock(); + lock.lock(); + + memset(BROADCAST_MAC, 0xFF, sizeof(BROADCAST_MAC)); +} + +void EspNowService::onStop(ServiceContext& service) { + auto lock = mutex.asScopedLock(); + lock.lock(); + + if (isEnabled()) { + disable(); + } +} + +// region Enable + +void EspNowService::enable(const EspNowConfig& config) { + auto enable_context = std::make_shared(config); + getMainDispatcher().dispatch(enableFromDispatcher, enable_context); +} + +void EspNowService::enableFromDispatcher(std::shared_ptr context) { + auto service = findService(); + if (service == nullptr) { + TT_LOG_E(TAG, "Service not running"); + return; + } + + auto config = std::static_pointer_cast(context); + + service->enableFromDispatcher(*config); +} + +void EspNowService::enableFromDispatcher(const EspNowConfig& config) { + auto lock = mutex.asScopedLock(); + lock.lock(); + + if (enabled) { + return; + } + + if (!initWifi(config)) { + TT_LOG_E(TAG, "initWifi() failed"); + return; + } + + if (esp_now_init() != ESP_OK) { + TT_LOG_E(TAG, "esp_now_init() failed"); + return; + } + + if (esp_now_register_recv_cb(receiveCallback) != ESP_OK) { + TT_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) { + TT_LOG_E(TAG, "esp_now_set_pmk() failed"); + return; + } + + // Add default unencrypted broadcast peer + esp_now_peer_info_t broadcast_peer; + memset(&broadcast_peer, 0, sizeof(esp_now_peer_info_t)); + memcpy(broadcast_peer.peer_addr, BROADCAST_MAC, sizeof(BROADCAST_MAC)); + service::espnow::addPeer(broadcast_peer); + + enabled = true; +} + +// endregion Enable + +// region Disable + +void EspNowService::disable() { + getMainDispatcher().dispatch(disableFromDispatcher, nullptr); +} + +void EspNowService::disableFromDispatcher(TT_UNUSED std::shared_ptr context) { + auto service = findService(); + if (service == nullptr) { + TT_LOG_E(TAG, "Service not running"); + return; + } + + service->disableFromDispatcher(); +} + +void EspNowService::disableFromDispatcher() { + auto lock = mutex.asScopedLock(); + lock.lock(); + + if (!enabled) { + return; + } + + if (esp_now_deinit() != ESP_OK) { + TT_LOG_E(TAG, "esp_now_deinit() failed"); + } + + if (!deinitWifi()) { + TT_LOG_E(TAG, "deinitWifi() failed"); + } + + enabled = false; +} + +// region Disable + +// region Callbacks + +void EspNowService::receiveCallback(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length) { + auto service = findService(); + if (service == nullptr) { + TT_LOG_E(TAG, "Service not running"); + return; + } + service->onReceive(receiveInfo, data, length); +} + +void EspNowService::onReceive(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length) { + auto lock = mutex.asScopedLock(); + lock.lock(); + + TT_LOG_D(TAG, "Received %d bytes", length); + + for (const auto& item: subscriptions) { + item.onReceive(receiveInfo, data, length); + } +} + +// endregion Callbacks + +bool EspNowService::isEnabled() const { + auto lock = mutex.asScopedLock(); + lock.lock(); + return enabled; +} + +bool EspNowService::addPeer(const esp_now_peer_info_t& peer) { + if (esp_now_add_peer(&peer) != ESP_OK) { + TT_LOG_E(TAG, "Failed to add peer"); + return false; + } else { + TT_LOG_I(TAG, "Peer added"); + return true; + } +} + +bool EspNowService::send(const uint8_t* address, const uint8_t* buffer, size_t bufferLength) { + auto lock = mutex.asScopedLock(); + lock.lock(); + + if (!isEnabled()) { + return false; + } else { + return esp_now_send(address, buffer, bufferLength) == ESP_OK; + } +} + +ReceiverSubscription EspNowService::subscribeReceiver(std::function onReceive) { + auto lock = mutex.asScopedLock(); + lock.lock(); + + auto id = lastSubscriptionId++; + + subscriptions.push_back(ReceiverSubscriptionData { + .id = id, + .onReceive = onReceive + }); + + return id; +} + +void EspNowService::unsubscribeReceiver(tt::service::espnow::ReceiverSubscription subscriptionId) { + auto lock = mutex.asScopedLock(); + lock.lock(); + std::erase_if(subscriptions, [subscriptionId](auto& subscription) { return subscription.id == subscriptionId; }); +} + +std::shared_ptr findService() { + return std::static_pointer_cast( + service::findServiceById(manifest.id) + ); +} + +extern const ServiceManifest manifest = { + .id = "EspNow", + .createService = create +}; + +} + +#endif // ESP_PLATFORM diff --git a/TactilityHeadless/Source/service/espnow/EspNowWifi.cpp b/TactilityHeadless/Source/service/espnow/EspNowWifi.cpp new file mode 100644 index 00000000..30e4c423 --- /dev/null +++ b/TactilityHeadless/Source/service/espnow/EspNowWifi.cpp @@ -0,0 +1,106 @@ +#ifdef ESP_PLATFORM + +#include "Tactility/service/espnow/EspNow.h" +#include "Tactility/service/wifi/Wifi.h" +#include + +#include +#include + +namespace tt::service::espnow { + +constexpr const char* TAG = "EspNowService"; + +static bool disableWifiService() { + auto wifi_state = wifi::getRadioState(); + if (wifi_state != wifi::RadioState::Off && wifi_state != wifi::RadioState::OffPending) { + wifi::setEnabled(false); + } + + if (wifi::getRadioState() == wifi::RadioState::Off) { + return true; + } else { + TickType_t timeout_time = kernel::getTicks() + kernel::millisToTicks(2000); + while (kernel::getTicks() < timeout_time && wifi::getRadioState() != wifi::RadioState::Off) { + kernel::delayTicks(50); + } + + return wifi::getRadioState() == wifi::RadioState::Off; + } +} + +bool initWifi(const EspNowConfig& config) { + if (!disableWifiService()) { + TT_LOG_E(TAG, "Failed to disable wifi"); + return false; + } + + wifi_mode_t mode; + if (config.mode == Mode::Station) { + mode = wifi_mode_t::WIFI_MODE_STA; + } else { + mode = wifi_mode_t::WIFI_MODE_AP; + } + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + if (esp_wifi_init(&cfg) != ESP_OK) { + TT_LOG_E(TAG, "esp_wifi_init() failed"); + return false; + } + + if (esp_wifi_set_storage(WIFI_STORAGE_RAM) != ESP_OK) { + TT_LOG_E(TAG, "esp_wifi_set_storage() failed"); + return false; + } + + if (esp_wifi_set_mode(mode) != ESP_OK) { + TT_LOG_E(TAG, "esp_wifi_set_mode() failed"); + return false; + } + + if (esp_wifi_start() != ESP_OK) { + TT_LOG_E(TAG, "esp_wifi_start() failed"); + return false; + } + + if (esp_wifi_set_channel(config.channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) { + TT_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) { + TT_LOG_W(TAG, "esp_wifi_set_protocol() for long range failed"); + } + } + + return true; +} + +bool deinitWifi() { + if (esp_wifi_stop() != ESP_OK) { + TT_LOG_E(TAG, "Failed to stop radio"); + return false; + } + + if (esp_wifi_set_mode(WIFI_MODE_NULL) != ESP_OK) { + TT_LOG_E(TAG, "Failed to unset mode"); + } + + if (esp_wifi_deinit() != ESP_OK) { + TT_LOG_E(TAG, "Failed to deinit"); + } + + return true; +} + +} // namespace tt::service::espnow + +#endif // ESP_PLATFORM