Add ESP-NOW master/slave discovery on DIP network channel.
Initialize WiFi and ESP-NOW from shared app config; master broadcasts discover packets and collects slave info, slaves respond on matching network while UART commands stay master-only. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
bde4c473ef
commit
54f2a7de5b
@ -15,12 +15,23 @@ idf_component_register(
|
|||||||
"uart_proto.c"
|
"uart_proto.c"
|
||||||
"cmd_handler.c"
|
"cmd_handler.c"
|
||||||
"cmd_version.c"
|
"cmd_version.c"
|
||||||
|
"esp_now_comm.c"
|
||||||
"proto/uart_messages.pb.c"
|
"proto/uart_messages.pb.c"
|
||||||
"proto/pb_encode.c"
|
"proto/pb_encode.c"
|
||||||
"proto/pb_common.c"
|
"proto/pb_common.c"
|
||||||
INCLUDE_DIRS
|
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}
|
target_compile_definitions(${COMPONENT_LIB}
|
||||||
PRIVATE "POWERPOD_GIT_HASH=\"${POWERPOD_GIT_HASH}\"")
|
PRIVATE "POWERPOD_GIT_HASH=\"${POWERPOD_GIT_HASH}\"")
|
||||||
|
|||||||
15
main/app_config.h
Normal file
15
main/app_config.h
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#ifndef APP_CONFIG_H
|
||||||
|
#define APP_CONFIG_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#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
|
||||||
300
main/esp_now_comm.c
Normal file
300
main/esp_now_comm.c
Normal file
@ -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 <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
9
main/esp_now_comm.h
Normal file
9
main/esp_now_comm.h
Normal file
@ -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
|
||||||
@ -1,6 +1,8 @@
|
|||||||
#include "powerpod.h"
|
#include "app_config.h"
|
||||||
#include "cmd_handler.h"
|
#include "cmd_handler.h"
|
||||||
#include "cmd_version.h"
|
#include "cmd_version.h"
|
||||||
|
#include "esp_now_comm.h"
|
||||||
|
#include "powerpod.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
#include "driver/i2c_master.h"
|
#include "driver/i2c_master.h"
|
||||||
#include "driver/i2c_types.h"
|
#include "driver/i2c_types.h"
|
||||||
@ -11,8 +13,6 @@
|
|||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/idf_additions.h"
|
#include "freertos/idf_additions.h"
|
||||||
#include "led_ring.h"
|
#include "led_ring.h"
|
||||||
#include "nvs.h"
|
|
||||||
#include "nvs_flash.h"
|
|
||||||
#include "uart.h"
|
#include "uart.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -38,18 +38,12 @@ enum SLAVE_STATES {
|
|||||||
SSTATE_OTA_UPDATE,
|
SSTATE_OTA_UPDATE,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct app_config_t {
|
|
||||||
uint8_t Master;
|
|
||||||
uint8_t Network;
|
|
||||||
char RunningPartition[17];
|
|
||||||
};
|
|
||||||
|
|
||||||
static const char *TAG = "[Main]";
|
static const char *TAG = "[Main]";
|
||||||
|
|
||||||
static i2c_master_bus_handle_t bus_handle;
|
static i2c_master_bus_handle_t bus_handle;
|
||||||
static i2c_master_dev_handle_t io_expander;
|
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;
|
static QueueHandle_t cmd_queue;
|
||||||
|
|
||||||
@ -117,22 +111,29 @@ void app_main(void) {
|
|||||||
|
|
||||||
const esp_partition_t *running = esp_ota_get_running_partition();
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
|
||||||
App_Config.Master = (master == true);
|
app_config.master = (master != 0);
|
||||||
App_Config.Network = network;
|
app_config.network = network;
|
||||||
memcpy(App_Config.RunningPartition, running->label,
|
memcpy(app_config.running_partition, running->label,
|
||||||
sizeof(App_Config.RunningPartition));
|
sizeof(app_config.running_partition));
|
||||||
|
|
||||||
ESP_LOGI(TAG, "RUNNING CONFIG:");
|
ESP_LOGI(TAG, "RUNNING CONFIG:");
|
||||||
ESP_LOGI(TAG, "Master: %d", App_Config.Master);
|
ESP_LOGI(TAG, "Master: %d", app_config.master);
|
||||||
ESP_LOGI(TAG, "Network: %d", App_Config.Network);
|
ESP_LOGI(TAG, "Network: %d", app_config.network);
|
||||||
ESP_LOGI(TAG, "Running Partition: %s", App_Config.RunningPartition);
|
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();
|
led_ring_init();
|
||||||
|
|
||||||
cmd_queue = xQueueCreate(10, sizeof(generic_msg_t));
|
if (app_config.master) {
|
||||||
init_cmdHandler(cmd_queue);
|
cmd_queue = xQueueCreate(10, sizeof(generic_msg_t));
|
||||||
init_uart(cmd_queue);
|
init_cmdHandler(cmd_queue);
|
||||||
cmd_version_register();
|
init_uart(cmd_queue);
|
||||||
|
cmd_version_register();
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t current_digit = 10;
|
uint8_t current_digit = 10;
|
||||||
while (1) {
|
while (1) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user