From 9ac57b246b3eb3b8bf81005056c553688eb4a190 Mon Sep 17 00:00:00 2001 From: Oleg Kalachev Date: Mon, 18 May 2026 14:17:13 +0300 Subject: [PATCH] Implement ESP-NOW support --- flix/cli.ino | 8 +++- flix/parameters.ino | 5 +- flix/wifi.ino | 68 ++++++++++++++++++++------- tools/espnow-proxy/README.md | 3 ++ tools/espnow-proxy/espnow-proxy.ino | 72 +++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 tools/espnow-proxy/README.md create mode 100644 tools/espnow-proxy/espnow-proxy.ino diff --git a/flix/cli.ino b/flix/cli.ino index 6be66c0..4fa6454 100644 --- a/flix/cli.ino +++ b/flix/cli.ino @@ -10,6 +10,7 @@ extern const int MOTOR_REAR_LEFT, MOTOR_REAR_RIGHT, MOTOR_FRONT_RIGHT, MOTOR_FRONT_LEFT; extern const int RAW, ACRO, STAB, AUTO; +extern const int W_AP, W_STA, W_ESPNOW; extern float t, dt, loopRate; extern uint16_t channels[16]; extern float controlTime; @@ -45,6 +46,7 @@ const char* motd = "wifi - show Wi-Fi info\n" "ap - setup Wi-Fi access point\n" "sta - setup Wi-Fi client mode\n" +"espnow - setup ESP-NOW peer\n" "mot - show motor output\n" "log [dump] - print log header [and data]\n" "cr - calibrate RC\n" @@ -143,9 +145,11 @@ void doCommand(String str, bool echo = false) { } else if (command == "wifi") { printWiFiInfo(); } else if (command == "ap") { - configWiFi(true, arg0.c_str(), arg1.c_str()); + configWiFi(W_AP, arg0.c_str(), arg1.c_str()); } else if (command == "sta") { - configWiFi(false, arg0.c_str(), arg1.c_str()); + configWiFi(W_STA, arg0.c_str(), arg1.c_str()); + } else if (command == "espnow") { + configWiFi(W_ESPNOW, arg0.c_str(), arg1.c_str()); } else if (command == "mot") { print("front-right %g front-left %g rear-right %g rear-left %g\n", motors[MOTOR_FRONT_RIGHT], motors[MOTOR_FRONT_LEFT], motors[MOTOR_REAR_RIGHT], motors[MOTOR_REAR_LEFT]); diff --git a/flix/parameters.ino b/flix/parameters.ino index d0d88bd..6533110 100644 --- a/flix/parameters.ino +++ b/flix/parameters.ino @@ -10,7 +10,7 @@ extern int channelZero[16]; extern int channelMax[16]; extern int rollChannel, pitchChannel, throttleChannel, yawChannel, armedChannel, modeChannel; extern int rcRxPin; -extern int wifiMode, udpLocalPort, udpRemotePort; +extern int wifiMode, wifiLongRange, udpLocalPort, udpRemotePort, espnowChannel; extern float rcLossTimeout, descendTime; extern int voltagePin; extern float voltageScale; @@ -112,6 +112,9 @@ Parameter parameters[] = { {"WIFI_MODE", &wifiMode}, {"WIFI_PORT_LOC", &udpLocalPort}, {"WIFI_PORT_REM", &udpRemotePort}, + {"WIFI_LONG_RANGE", &wifiLongRange}, + // espnow + {"ESPNOW_CHANNEL", &espnowChannel}, // mavlink {"MAV_SYS_ID", &mavlinkSysId}, {"MAV_RATE_SLOW", &telemetrySlow.rate}, diff --git a/flix/wifi.ino b/flix/wifi.ino index c0aa119..63c0488 100644 --- a/flix/wifi.ino +++ b/flix/wifi.ino @@ -1,82 +1,116 @@ // Copyright (c) 2023 Oleg Kalachev // Repository: https://github.com/okalachev/flix -// Wi-Fi communication +// Wi-Fi and ESP-NOW communication #include #include #include +#include +#include #include "Preferences.h" extern Preferences storage; // use the main preferences storage -const int W_DISABLED = 0, W_AP = 1, W_STA = 2; +const int W_DISABLED = 0, W_AP = 1, W_STA = 2, W_ESPNOW = 3; int wifiMode = W_AP; +int wifiLongRange = 0; int udpLocalPort = 14550; int udpRemotePort = 14550; +int espnowChannel = 6; IPAddress udpRemoteIP = "255.255.255.255"; WiFiUDP udp; +ESP_NOW_Serial_Class espnow(NULL, 0, WIFI_IF_AP); void setupWiFi() { print("Setup Wi-Fi\n"); + WiFi.enableLongRange(wifiLongRange); + if (wifiMode == W_AP) { WiFi.softAP(storage.getString("WIFI_AP_SSID", "flix").c_str(), storage.getString("WIFI_AP_PASS", "flixwifi").c_str()); + udp.begin(udpLocalPort); } else if (wifiMode == W_STA) { WiFi.begin(storage.getString("WIFI_STA_SSID", "").c_str(), storage.getString("WIFI_STA_PASS", "").c_str()); - } else { - return; + udp.begin(udpLocalPort); + } else if (wifiMode == W_ESPNOW) { + WiFi.mode(WIFI_AP); + WiFi.setChannel(espnowChannel); + espnow.setChannel(espnowChannel); + espnow.addr(MacAddress(storage.getString("ESPNOW_PEER_MAC", "").c_str())); + espnow.begin(); } + WiFi.setSleep(false); // disable power save - udp.begin(udpLocalPort); } void sendWiFi(const uint8_t *buf, int len) { + if (espnow) { + espnow.write(buf, len); + return; + } + if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return; + udp.beginPacket(udpRemoteIP, udpRemotePort); udp.write(buf, len); udp.endPacket(); } int receiveWiFi(uint8_t *buf, int len) { + if (espnow) { + return espnow.read(buf, len); + } + if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return 0; + udp.parsePacket(); if (udp.remoteIP()) udpRemoteIP = udp.remoteIP(); return udp.read(buf, len); } void printWiFiInfo() { - if (WiFi.getMode() == WIFI_MODE_AP) { + if (espnow) { + print("Mode: ESP-NOW\n"); + print("ESP-NOW version: %d\n", ESP_NOW.getVersion()); + print("Max packet size: %d\n", ESP_NOW.getMaxDataLen()); + print("MAC: %s\n", WiFi.softAPmacAddress().c_str()); + print("Peer MAC: %s\n", MacAddress(espnow.addr()).toString().c_str()); + print("Channel: %d\n", espnow.getChannel()); + } else if (WiFi.getMode() == WIFI_MODE_AP) { print("Mode: Access Point (AP)\n"); print("MAC: %s\n", WiFi.softAPmacAddress().c_str()); print("SSID: %s\n", WiFi.softAPSSID().c_str()); print("Password: ***\n"); + print("Channel: %d\n", WiFi.channel()); print("Clients: %d\n", WiFi.softAPgetStationNum()); print("IP: %s\n", WiFi.softAPIP().toString().c_str()); + print("Remote IP: %s\n", udpRemoteIP.toString().c_str()); } else if (WiFi.getMode() == WIFI_MODE_STA) { print("Mode: Client (STA)\n"); print("Connected: %d\n", WiFi.isConnected()); print("MAC: %s\n", WiFi.macAddress().c_str()); print("SSID: %s\n", WiFi.SSID().c_str()); print("Password: ***\n"); - print("IP: %s\n", WiFi.localIP().toString().c_str()); + print("Channel: %d\n", WiFi.channel()); print("RSSI: %d dBm\n", WiFi.RSSI()); + print("IP: %s\n", WiFi.localIP().toString().c_str()); + print("Remote IP: %s\n", udpRemoteIP.toString().c_str()); } else { print("Mode: Disabled\n"); - return; } - print("Channel: %d\n", WiFi.channel()); - print("Remote IP: %s\n", udpRemoteIP.toString().c_str()); print("MAVLink connected: %d\n", mavlinkConnected); } -void configWiFi(bool ap, const char *ssid, const char *password) { - if (ap) { - storage.putString("WIFI_AP_SSID", ssid); - storage.putString("WIFI_AP_PASS", password); - } else { - storage.putString("WIFI_STA_SSID", ssid); - storage.putString("WIFI_STA_PASS", password); +void configWiFi(int mode, const char *first, const char *second) { + if (mode == W_AP) { + storage.putString("WIFI_AP_SSID", first); + storage.putString("WIFI_AP_PASS", second); + } else if (mode == W_STA) { + storage.putString("WIFI_STA_SSID", first); + storage.putString("WIFI_STA_PASS", second); + } else if (mode == W_ESPNOW) { + storage.putString("ESPNOW_PEER_MAC", first); } print("✓ Reboot to apply new settings\n"); } diff --git a/tools/espnow-proxy/README.md b/tools/espnow-proxy/README.md new file mode 100644 index 0000000..b6de5f7 --- /dev/null +++ b/tools/espnow-proxy/README.md @@ -0,0 +1,3 @@ +# ESPNOW-proxy + +Proxy sketch for using ESP-NOW MAVLink connection with Flix drone. diff --git a/tools/espnow-proxy/espnow-proxy.ino b/tools/espnow-proxy/espnow-proxy.ino new file mode 100644 index 0000000..74bb445 --- /dev/null +++ b/tools/espnow-proxy/espnow-proxy.ino @@ -0,0 +1,72 @@ +// Copyright (c) 2026 Oleg Kalachev +// Repository: https://github.com/okalachev/flix + +// Proxy for ESP-NOW connection + +#include +#include +#include +#include + +const int CHANNEL = 6; + +ESP_NOW_Serial_Class espnow(NULL, CHANNEL, WIFI_IF_AP); +MacAddress peerMac; +volatile bool peerFound = false; + +void onNewPeer(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) { + peerMac = info->src_addr; + peerFound = true; +} + +void setup() { + Serial.begin(115200); + WiFi.mode(WIFI_AP); + WiFi.setSleep(false); + WiFi.setChannel(CHANNEL); + + // while (!WiFi.AP.started()) { + // delay(100); + // } + + ESP_NOW.onNewPeer(onNewPeer, NULL); + ESP_NOW.begin(); + + while (!peerFound) { + Serial.printf("MAC: %s, waiting for peer...\n", WiFi.softAPmacAddress().c_str()); + delay(1000); + } + Serial.printf("Peer found: %s\n", peerMac.toString().c_str()); + + espnow.addr(peerMac); + espnow.setChannel(CHANNEL); + espnow.begin(); +} + +void loop() { + uint8_t buf[5000]; + + // Send from serial to ESP-NOW + while (Serial.available() > 0) { + int b = Serial.read(); + if (b < 0) { + break; + } + + mavlink_message_t msg; + mavlink_status_t status; + if (mavlink_parse_char(MAVLINK_COMM_0, (uint8_t)b, &msg, &status)) { + int len = mavlink_msg_to_send_buffer(buf, &msg); + // ESP_NOW.write(buf, len); + espnow.write(buf, len); + // espnow.send(buf, len); + // esp_now_send(peerMac, buf, len); + } + } + + // Send from ESP-NOW to serial + int len = espnow.read(buf, sizeof(buf)); + if (len > 0) { + Serial.write(buf, len); + } +}