diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ff5c612..2a6c8ae 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -15,12 +15,23 @@ idf_component_register( "uart_proto.c" "cmd_handler.c" "cmd_version.c" + "esp_now_comm.c" "proto/uart_messages.pb.c" "proto/pb_encode.c" "proto/pb_common.c" INCLUDE_DIRS "." - "proto") + "proto" + REQUIRES + esp_wifi + esp_netif + esp_event + nvs_flash + PRIV_REQUIRES + esp_driver_gpio + esp_driver_uart + esp_driver_i2c + app_update) target_compile_definitions(${COMPONENT_LIB} PRIVATE "POWERPOD_GIT_HASH=\"${POWERPOD_GIT_HASH}\"") diff --git a/main/app_config.h b/main/app_config.h new file mode 100644 index 0000000..5d2ca23 --- /dev/null +++ b/main/app_config.h @@ -0,0 +1,15 @@ +#ifndef APP_CONFIG_H +#define APP_CONFIG_H + +#include +#include + +#define APP_RUNNING_PARTITION_LABEL_MAX 17 + +typedef struct { + bool master; + uint8_t network; + char running_partition[APP_RUNNING_PARTITION_LABEL_MAX]; +} app_config_t; + +#endif diff --git a/main/esp_now_comm.c b/main/esp_now_comm.c new file mode 100644 index 0000000..1ea8aba --- /dev/null +++ b/main/esp_now_comm.c @@ -0,0 +1,300 @@ +#include "esp_now_comm.h" +#include "esp_err.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_mac.h" +#include "esp_netif.h" +#include "esp_now.h" +#include "esp_wifi.h" +#include "freertos/FreeRTOS.h" +#include "freertos/idf_additions.h" +#include "nvs_flash.h" +#include +#include + +#ifndef POWERPOD_FW_VERSION +#define POWERPOD_FW_VERSION 1u +#endif + +#define ESPNOW_MAGIC 0xA1 +#define ESPNOW_MSG_DISCOVER 1 +#define ESPNOW_MSG_SLAVE_INFO 2 +#define ESPNOW_DISCOVER_INTERVAL_MS 500 +#define ESPNOW_MAX_SLAVES 16 + +static const uint8_t ESPNOW_BCAST[ESP_NOW_ETH_ALEN] = {0xff, 0xff, 0xff, + 0xff, 0xff, 0xff}; + +static const char *TAG = "[ESPNOW]"; + +typedef struct __attribute__((packed)) { + uint8_t magic; + uint8_t type; + uint8_t network; + uint8_t reserved; +} espnow_discover_packet_t; + +typedef struct __attribute__((packed)) { + uint8_t magic; + uint8_t type; + uint8_t network; + uint8_t mac[ESP_NOW_ETH_ALEN]; + uint32_t version; + uint32_t slave_id; + uint8_t available; + uint8_t used; +} espnow_slave_info_packet_t; + +typedef struct { + uint8_t mac[ESP_NOW_ETH_ALEN]; + uint32_t slave_id; + uint32_t version; + bool available; + bool used; + bool seen; +} slave_entry_t; + +static app_config_t s_config; +static uint8_t s_wifi_channel; +static uint8_t s_own_mac[ESP_NOW_ETH_ALEN]; +static slave_entry_t s_slaves[ESPNOW_MAX_SLAVES]; + +static uint8_t network_to_channel(uint8_t network) { + if (network < 1 || network > 13) { + return 1; + } + return network; +} + +static bool mac_equal(const uint8_t *a, const uint8_t *b) { + return memcmp(a, b, ESP_NOW_ETH_ALEN) == 0; +} + +static void mac_to_str(const uint8_t *mac, char *out, size_t out_len) { + snprintf(out, out_len, "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], + mac[2], mac[3], mac[4], mac[5]); +} + +static esp_err_t ensure_peer(const uint8_t *mac) { + if (esp_now_is_peer_exist(mac)) { + return ESP_OK; + } + + esp_now_peer_info_t peer = {0}; + memcpy(peer.peer_addr, mac, ESP_NOW_ETH_ALEN); + peer.channel = s_wifi_channel; + peer.ifidx = WIFI_IF_STA; + peer.encrypt = false; + + esp_err_t err = esp_now_add_peer(&peer); + if (err != ESP_OK) { + ESP_LOGW(TAG, "add peer failed: %s", esp_err_to_name(err)); + } + return err; +} + +static esp_err_t ensure_broadcast_peer(void) { return ensure_peer(ESPNOW_BCAST); } + +static slave_entry_t *find_slave(const uint8_t *mac) { + for (int i = 0; i < ESPNOW_MAX_SLAVES; i++) { + if (s_slaves[i].seen && mac_equal(s_slaves[i].mac, mac)) { + return &s_slaves[i]; + } + } + return NULL; +} + +static slave_entry_t *alloc_slave(const uint8_t *mac) { + slave_entry_t *existing = find_slave(mac); + if (existing != NULL) { + return existing; + } + for (int i = 0; i < ESPNOW_MAX_SLAVES; i++) { + if (!s_slaves[i].seen) { + memcpy(s_slaves[i].mac, mac, ESP_NOW_ETH_ALEN); + s_slaves[i].seen = true; + return &s_slaves[i]; + } + } + return NULL; +} + +static void send_slave_info(const uint8_t *dest_mac) { + espnow_slave_info_packet_t pkt = { + .magic = ESPNOW_MAGIC, + .type = ESPNOW_MSG_SLAVE_INFO, + .network = s_config.network, + .version = POWERPOD_FW_VERSION, + .slave_id = s_own_mac[5], + .available = 1, + .used = 0, + }; + memcpy(pkt.mac, s_own_mac, ESP_NOW_ETH_ALEN); + + if (ensure_peer(dest_mac) != ESP_OK) { + return; + } + + esp_err_t err = + esp_now_send(dest_mac, (const uint8_t *)&pkt, sizeof(pkt)); + if (err != ESP_OK) { + ESP_LOGW(TAG, "slave info send failed: %s", esp_err_to_name(err)); + } +} + +static void handle_discover(const uint8_t *sender_mac, + const espnow_discover_packet_t *pkt) { + if (pkt->network != s_config.network) { + return; + } + ESP_LOGI(TAG, "discover from master on network %u", (unsigned)pkt->network); + send_slave_info(sender_mac); +} + +static void handle_slave_info(const espnow_slave_info_packet_t *pkt) { + if (pkt->network != s_config.network) { + return; + } + + bool is_new = (find_slave(pkt->mac) == NULL); + slave_entry_t *entry = alloc_slave(pkt->mac); + if (entry == NULL) { + ESP_LOGW(TAG, "slave table full"); + return; + } + + entry->slave_id = pkt->slave_id; + entry->version = pkt->version; + entry->available = pkt->available != 0; + entry->used = pkt->used != 0; + + char mac_str[18]; + mac_to_str(pkt->mac, mac_str, sizeof(mac_str)); + if (is_new) { + ESP_LOGI(TAG, "slave joined id=%lu mac=%s ver=%lu", + (unsigned long)pkt->slave_id, mac_str, + (unsigned long)pkt->version); + } +} + +static void espnow_recv_cb(const esp_now_recv_info_t *info, const uint8_t *data, + int len) { + if (info == NULL || data == NULL || len < 3) { + return; + } + + if (data[0] != ESPNOW_MAGIC) { + return; + } + + switch (data[1]) { + case ESPNOW_MSG_DISCOVER: + if (!s_config.master && len >= (int)sizeof(espnow_discover_packet_t)) { + handle_discover(info->src_addr, + (const espnow_discover_packet_t *)data); + } + break; + case ESPNOW_MSG_SLAVE_INFO: + if (s_config.master && len >= (int)sizeof(espnow_slave_info_packet_t)) { + handle_slave_info((const espnow_slave_info_packet_t *)data); + } + break; + default: + break; + } +} + +static void master_discover_task(void *param) { + (void)param; + + espnow_discover_packet_t pkt = { + .magic = ESPNOW_MAGIC, + .type = ESPNOW_MSG_DISCOVER, + .network = s_config.network, + .reserved = 0, + }; + + ESP_LOGI(TAG, "master discover task on network %u ch %u", + (unsigned)s_config.network, (unsigned)s_wifi_channel); + + while (1) { + esp_err_t err = + esp_now_send(ESPNOW_BCAST, (const uint8_t *)&pkt, sizeof(pkt)); + if (err != ESP_OK) { + ESP_LOGW(TAG, "discover broadcast failed: %s", esp_err_to_name(err)); + } + vTaskDelay(pdMS_TO_TICKS(ESPNOW_DISCOVER_INTERVAL_MS)); + } +} + +static esp_err_t init_wifi_stack(uint8_t channel) { + esp_err_t err; + + err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + if (err != ESP_OK) { + return err; + } + + 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)); + + wifi_config_t wifi_config = {0}; + wifi_config.sta.channel = channel; + wifi_config.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; + wifi_config.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; + + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE)); + + return ESP_OK; +} + +esp_err_t esp_now_comm_init(const app_config_t *config) { + if (config == NULL) { + return ESP_ERR_INVALID_ARG; + } + + memset(&s_config, 0, sizeof(s_config)); + memcpy(&s_config, config, sizeof(s_config)); + memset(s_slaves, 0, sizeof(s_slaves)); + + s_wifi_channel = network_to_channel(config->network); + ESP_ERROR_CHECK(esp_read_mac(s_own_mac, ESP_MAC_WIFI_STA)); + + char mac_str[18]; + mac_to_str(s_own_mac, mac_str, sizeof(mac_str)); + ESP_LOGI(TAG, "role=%s network=%u channel=%u mac=%s", + config->master ? "master" : "slave", (unsigned)config->network, + (unsigned)s_wifi_channel, mac_str); + + esp_err_t err = init_wifi_stack(s_wifi_channel); + if (err != ESP_OK) { + ESP_LOGE(TAG, "wifi init failed: %s", esp_err_to_name(err)); + return err; + } + + ESP_ERROR_CHECK(esp_now_init()); + ESP_ERROR_CHECK(esp_now_register_recv_cb(espnow_recv_cb)); + + if (config->master) { + ESP_ERROR_CHECK(ensure_broadcast_peer()); + if (xTaskCreate(master_discover_task, "espnow_disc", 4096, NULL, 4, + NULL) != pdPASS) { + ESP_LOGE(TAG, "failed to create discover task"); + return ESP_FAIL; + } + } else { + ESP_LOGI(TAG, "slave listening for master discover"); + } + + return ESP_OK; +} diff --git a/main/esp_now_comm.h b/main/esp_now_comm.h new file mode 100644 index 0000000..adafe7a --- /dev/null +++ b/main/esp_now_comm.h @@ -0,0 +1,9 @@ +#ifndef ESP_NOW_COMM_H +#define ESP_NOW_COMM_H + +#include "app_config.h" +#include "esp_err.h" + +esp_err_t esp_now_comm_init(const app_config_t *config); + +#endif diff --git a/main/powerpod.c b/main/powerpod.c index 78944a1..8eee094 100644 --- a/main/powerpod.c +++ b/main/powerpod.c @@ -1,6 +1,8 @@ -#include "powerpod.h" +#include "app_config.h" #include "cmd_handler.h" #include "cmd_version.h" +#include "esp_now_comm.h" +#include "powerpod.h" #include "driver/gpio.h" #include "driver/i2c_master.h" #include "driver/i2c_types.h" @@ -11,8 +13,6 @@ #include "freertos/FreeRTOS.h" #include "freertos/idf_additions.h" #include "led_ring.h" -#include "nvs.h" -#include "nvs_flash.h" #include "uart.h" #include @@ -38,18 +38,12 @@ enum SLAVE_STATES { SSTATE_OTA_UPDATE, }; -struct app_config_t { - uint8_t Master; - uint8_t Network; - char RunningPartition[17]; -}; - static const char *TAG = "[Main]"; static i2c_master_bus_handle_t bus_handle; static i2c_master_dev_handle_t io_expander; -static struct app_config_t App_Config; +static app_config_t app_config; static QueueHandle_t cmd_queue; @@ -117,22 +111,29 @@ void app_main(void) { const esp_partition_t *running = esp_ota_get_running_partition(); - App_Config.Master = (master == true); - App_Config.Network = network; - memcpy(App_Config.RunningPartition, running->label, - sizeof(App_Config.RunningPartition)); + app_config.master = (master != 0); + app_config.network = network; + memcpy(app_config.running_partition, running->label, + sizeof(app_config.running_partition)); ESP_LOGI(TAG, "RUNNING CONFIG:"); - ESP_LOGI(TAG, "Master: %d", App_Config.Master); - ESP_LOGI(TAG, "Network: %d", App_Config.Network); - ESP_LOGI(TAG, "Running Partition: %s", App_Config.RunningPartition); + ESP_LOGI(TAG, "Master: %d", app_config.master); + ESP_LOGI(TAG, "Network: %d", app_config.network); + ESP_LOGI(TAG, "Running Partition: %s", app_config.running_partition); + + err = esp_now_comm_init(&app_config); + if (err != ESP_OK) { + ESP_LOGE(TAG, "ESP-NOW init failed: %s", esp_err_to_name(err)); + } led_ring_init(); - cmd_queue = xQueueCreate(10, sizeof(generic_msg_t)); - init_cmdHandler(cmd_queue); - init_uart(cmd_queue); - cmd_version_register(); + if (app_config.master) { + cmd_queue = xQueueCreate(10, sizeof(generic_msg_t)); + init_cmdHandler(cmd_queue); + init_uart(cmd_queue); + cmd_version_register(); + } uint8_t current_digit = 10; while (1) {