ESP-NOW service (#259)
Implemented Chat app with ESP-NOW service. The Chat app was originally created by @maxbrito500 and adapted by me.
This commit is contained in:
parent
358954f8be
commit
49a46f6236
@ -32,6 +32,7 @@ namespace service {
|
|||||||
namespace app {
|
namespace app {
|
||||||
namespace alertdialog { extern const AppManifest manifest; }
|
namespace alertdialog { extern const AppManifest manifest; }
|
||||||
namespace applist { extern const AppManifest manifest; }
|
namespace applist { extern const AppManifest manifest; }
|
||||||
|
namespace chat { extern const AppManifest manifest; }
|
||||||
namespace boot { extern const AppManifest manifest; }
|
namespace boot { extern const AppManifest manifest; }
|
||||||
namespace display { extern const AppManifest manifest; }
|
namespace display { extern const AppManifest manifest; }
|
||||||
namespace files { extern const AppManifest manifest; }
|
namespace files { extern const AppManifest manifest; }
|
||||||
@ -98,6 +99,7 @@ static void registerSystemApps() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
|
addApp(app::chat::manifest);
|
||||||
addApp(app::crashdiagnostics::manifest);
|
addApp(app::crashdiagnostics::manifest);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
179
Tactility/Source/app/chat/ChatApp.cpp
Normal file
179
Tactility/Source/app/chat/ChatApp.cpp
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
|
||||||
|
#include <Tactility/app/AppManifest.h>
|
||||||
|
#include <Tactility/lvgl/Toolbar.h>
|
||||||
|
#include <Tactility/service/espnow/EspNow.h>
|
||||||
|
|
||||||
|
#include "Tactility/service/gui/Gui.h"
|
||||||
|
#include "Tactility/lvgl/LvglSync.h"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <esp_wifi.h>
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
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<ChatApp*>(lv_event_get_user_data(e));
|
||||||
|
auto* btn = static_cast<lv_obj_t*>(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<ChatApp*>(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<ChatApp>
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
54
TactilityHeadless/Include/Tactility/service/espnow/EspNow.h
Normal file
54
TactilityHeadless/Include/Tactility/service/espnow/EspNow.h
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <esp_now.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
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<void(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length)> onReceive);
|
||||||
|
|
||||||
|
void unsubscribeReceiver(ReceiverSubscription subscription);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ESP_PLATFORM
|
||||||
@ -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 <Tactility/Mutex.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace tt::service::espnow {
|
||||||
|
|
||||||
|
class EspNowService final : public Service {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
struct ReceiverSubscriptionData {
|
||||||
|
ReceiverSubscription id;
|
||||||
|
std::function<void(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length)> onReceive;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SendCallback {
|
||||||
|
uint8_t macAddress[ESP_NOW_ETH_ALEN];
|
||||||
|
bool success;
|
||||||
|
};
|
||||||
|
|
||||||
|
Mutex mutex = Mutex(Mutex::Type::Recursive);
|
||||||
|
std::vector<ReceiverSubscriptionData> subscriptions;
|
||||||
|
ReceiverSubscription lastSubscriptionId = 0;
|
||||||
|
bool enabled = false;
|
||||||
|
|
||||||
|
// Dispatcher calls this and forwards to non-static function
|
||||||
|
static void enableFromDispatcher(std::shared_ptr<void> context);
|
||||||
|
void enableFromDispatcher(const EspNowConfig& config);
|
||||||
|
|
||||||
|
static void disableFromDispatcher(std::shared_ptr<void> 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<void(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length)> onReceive);
|
||||||
|
|
||||||
|
void unsubscribeReceiver(ReceiverSubscription subscription);
|
||||||
|
|
||||||
|
// region Internal API
|
||||||
|
};
|
||||||
|
|
||||||
|
std::shared_ptr<EspNowService> findService();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ESP_PLATFORM
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tactility/service/espnow/EspNow.h"
|
||||||
|
|
||||||
|
namespace tt::service::espnow {
|
||||||
|
|
||||||
|
bool initWifi(const EspNowConfig& config);
|
||||||
|
|
||||||
|
bool deinitWifi();
|
||||||
|
|
||||||
|
}
|
||||||
@ -19,6 +19,9 @@ namespace tt {
|
|||||||
namespace service::gps { extern const ServiceManifest manifest; }
|
namespace service::gps { extern const ServiceManifest manifest; }
|
||||||
namespace service::wifi { extern const ServiceManifest manifest; }
|
namespace service::wifi { extern const ServiceManifest manifest; }
|
||||||
namespace service::sdcard { 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;
|
static Dispatcher mainDispatcher;
|
||||||
|
|
||||||
@ -29,6 +32,9 @@ static void registerAndStartSystemServices() {
|
|||||||
addService(service::gps::manifest);
|
addService(service::gps::manifest);
|
||||||
addService(service::sdcard::manifest);
|
addService(service::sdcard::manifest);
|
||||||
addService(service::wifi::manifest);
|
addService(service::wifi::manifest);
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
addService(service::espnow::manifest);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void initHeadless(const hal::Configuration& config) {
|
void initHeadless(const hal::Configuration& config) {
|
||||||
|
|||||||
80
TactilityHeadless/Source/service/espnow/EspNow.cpp
Normal file
80
TactilityHeadless/Source/service/espnow/EspNow.cpp
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
|
||||||
|
#include "Tactility/service/espnow/EspNow.h"
|
||||||
|
#include "Tactility/service/espnow/EspNowService.h"
|
||||||
|
|
||||||
|
#include <Tactility/Log.h>
|
||||||
|
|
||||||
|
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<void(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length)> 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
|
||||||
222
TactilityHeadless/Source/service/espnow/EspNowService.cpp
Normal file
222
TactilityHeadless/Source/service/espnow/EspNowService.cpp
Normal file
@ -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 <cstring>
|
||||||
|
#include <esp_now.h>
|
||||||
|
#include <esp_random.h>
|
||||||
|
|
||||||
|
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<EspNowConfig>(config);
|
||||||
|
getMainDispatcher().dispatch(enableFromDispatcher, enable_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspNowService::enableFromDispatcher(std::shared_ptr<void> context) {
|
||||||
|
auto service = findService();
|
||||||
|
if (service == nullptr) {
|
||||||
|
TT_LOG_E(TAG, "Service not running");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto config = std::static_pointer_cast<EspNowConfig>(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<void> 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<void(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length)> 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<EspNowService> findService() {
|
||||||
|
return std::static_pointer_cast<EspNowService>(
|
||||||
|
service::findServiceById(manifest.id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const ServiceManifest manifest = {
|
||||||
|
.id = "EspNow",
|
||||||
|
.createService = create<EspNowService>
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // ESP_PLATFORM
|
||||||
106
TactilityHeadless/Source/service/espnow/EspNowWifi.cpp
Normal file
106
TactilityHeadless/Source/service/espnow/EspNowWifi.cpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
|
||||||
|
#include "Tactility/service/espnow/EspNow.h"
|
||||||
|
#include "Tactility/service/wifi/Wifi.h"
|
||||||
|
#include <Tactility/Log.h>
|
||||||
|
|
||||||
|
#include <esp_now.h>
|
||||||
|
#include <esp_wifi.h>
|
||||||
|
|
||||||
|
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
|
||||||
Loading…
x
Reference in New Issue
Block a user