diff --git a/components/ESP-NOW/CMakeLists.txt b/components/ESP-NOW/CMakeLists.txt new file mode 100644 index 0000000..f666de7 --- /dev/null +++ b/components/ESP-NOW/CMakeLists.txt @@ -0,0 +1,15 @@ +idf_component_register( + SRCS + "ESP-NOW.c" + INCLUDE_DIRS + "." + REQUIRES + "SystemK" + "esp_wifi" + "nvs_flash" + "esp_netif" + "esp_event" + "esp_system" + "freertos" + "NVM" +) \ No newline at end of file diff --git a/components/ESP-NOW/ESP-NOW.c b/components/ESP-NOW/ESP-NOW.c new file mode 100644 index 0000000..c116ca0 --- /dev/null +++ b/components/ESP-NOW/ESP-NOW.c @@ -0,0 +1,180 @@ +/* + * This program source code file is part of the KTag project. + * + * 🛡️ 🃞 + * + * Copyright © 2025 Joseph P. Kearney and the KTag developers. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * There should be a copy of the GNU Affero General Public License in the LICENSE + * file in the root of this repository. If not, see . + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_wifi.h" +#include "esp_mac.h" +#include "esp_event.h" +#include "esp_now.h" +// +#include "nvs_flash.h" + +static const char *TAG = "ESP-NOW"; + +// Broadcast address (all 0xFF sends to all devices) +static uint8_t Broadcast_MAC[ESP_NOW_ETH_ALEN] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +// Structure for the data to send +typedef struct +{ + uint32_t counter; + char message[32]; +} broadcast_data_t; + +// ESP-NOW receive callback +static void ESPNOW_Recv_CB(const esp_now_recv_info_t *recv_info, const uint8_t *data, int len) +{ + if (recv_info == NULL || data == NULL || len <= 0) + { + KLOG_ERROR(TAG, "Receive callback error"); + return; + } + + // Log sender MAC address + KLOG_INFO(TAG, "Received from: " MACSTR ", RSSI: %d", + MAC2STR(recv_info->src_addr), recv_info->rx_ctrl->rssi); + + // Parse received data + if (len == sizeof(broadcast_data_t)) + { + broadcast_data_t *recv_data = (broadcast_data_t *)data; + KLOG_INFO(TAG, "Counter: %lu, Message: %s", + recv_data->counter, recv_data->message); + } + else + { + KLOG_WARN(TAG, "Unexpected data length: %d", len); + } +} + +// ESP-NOW send callback +static void ESPNOW_Send_CB(const wifi_tx_info_t *tx_info, esp_now_send_status_t status) +{ + if (tx_info == NULL) + { + KLOG_ERROR(TAG, "Send callback error: tx_info is NULL"); + return; + } + + if (status == ESP_NOW_SEND_SUCCESS) + { + KLOG_INFO(TAG, "Send success to " MACSTR, MAC2STR(tx_info->des_addr)); + } + else + { + KLOG_WARN(TAG, "Send failed to " MACSTR, MAC2STR(tx_info->des_addr)); + } +} + +static esp_err_t Initialize_ESPNOW(void) +{ + esp_err_t ret = esp_now_init(); + if (ret != ESP_OK) + { + KLOG_ERROR(TAG, "ESP-NOW init failed: %s", esp_err_to_name(ret)); + return ret; + } + + // Register send and receive callbacks + esp_now_register_send_cb(ESPNOW_Send_CB); + esp_now_register_recv_cb(ESPNOW_Recv_CB); + + // Add broadcast peer + esp_now_peer_info_t peer_info = {}; + memcpy(peer_info.peer_addr, Broadcast_MAC, ESP_NOW_ETH_ALEN); + peer_info.channel = 0; // Use current channel + peer_info.ifidx = WIFI_IF_STA; + peer_info.encrypt = false; + + ret = esp_now_add_peer(&peer_info); + if (ret != ESP_OK) + { + KLOG_ERROR(TAG, "Failed to add broadcast peer: %s", esp_err_to_name(ret)); + return ret; + } + + KLOG_INFO(TAG, "ESP-NOW initialized successfully"); + return ESP_OK; +} + +// FreeRTOS task for periodic broadcast +static void ESPNOW_Task(void *pvParameters) +{ + broadcast_data_t data = {0}; + TickType_t last_wake = xTaskGetTickCount(); + const TickType_t period = pdMS_TO_TICKS(2000); + + KLOG_INFO(TAG, "ESP-NOW task started"); + + while (1) + { + // Update data + data.counter++; + snprintf(data.message, sizeof(data.message), "Broadcast #%lu", data.counter); + + // Send broadcast + esp_err_t ret = esp_now_send(Broadcast_MAC, (uint8_t *)&data, sizeof(data)); + if (ret != ESP_OK) + { + KLOG_ERROR(TAG, "Send error: %s", esp_err_to_name(ret)); + } + + // Wait for next period + vTaskDelayUntil(&last_wake, period); + } +} + +static void Initialize_WiFi(void) +{ + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_start()); + + KLOG_INFO(TAG, "WiFi initialized in station mode"); +} + +void Initialize_ESP_NOW(void) +{ + Initialize_WiFi(); + + // Initialize ESP-NOW + if (Initialize_ESPNOW() != ESP_OK) + { + KLOG_ERROR(TAG, "ESP-NOW initialization failed"); + return; + } + + // Enable Long Range mode + esp_wifi_set_protocol(WIFI_IF_STA, WIFI_PROTOCOL_LR); + esp_wifi_set_protocol(WIFI_IF_AP, WIFI_PROTOCOL_LR); + + // Create broadcast task + xTaskCreate(ESPNOW_Task, "ESP-NOW", 4096, NULL, 5, NULL); +} \ No newline at end of file diff --git a/components/ESP-NOW/ESP-NOW.h b/components/ESP-NOW/ESP-NOW.h new file mode 100644 index 0000000..24e9697 --- /dev/null +++ b/components/ESP-NOW/ESP-NOW.h @@ -0,0 +1,24 @@ +/* + * This program source code file is part of the KTag project. + * + * 🛡️ 🃞 + * + * Copyright © 2025 Joseph P. Kearney and the KTag developers. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * There should be a copy of the GNU Affero General Public License in the LICENSE + * file in the root of this repository. If not, see . + */ + +#pragma once + +void Initialize_ESP_NOW(void); \ No newline at end of file diff --git a/main/main.c b/main/main.c index 0a7856b..79fd198 100644 --- a/main/main.c +++ b/main/main.c @@ -49,6 +49,7 @@ #include #include #include +#include #include "nvs_flash.h" #include "HW_NeoPixels.h" #include "Version.h" @@ -122,6 +123,8 @@ void app_main(void) // Initialize the switches after SystemK, so xQueueEvents will have already been created. Initialize_Switches(); + Initialize_ESP_NOW(); + KLOG_INFO(TAG, "Initialization complete."); }