diff --git a/Makefile b/Makefile index 5dfc9e6..de670c4 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,10 @@ dependencies .dependencies: arduino-cli lib install "MAVLink"@2.0.25 touch .dependencies +upload_proxy: .dependencies + arduino-cli compile --fqbn $(BOARD) tools/espnow-proxy + arduino-cli upload --fqbn $(BOARD) -p "$(PORT)" tools/espnow-proxy + gazebo/build cmake: gazebo/CMakeLists.txt mkdir -p gazebo/build cd gazebo/build && cmake .. diff --git a/flix/cli.ino b/flix/cli.ino index 4fa6454..7922f1a 100644 --- a/flix/cli.ino +++ b/flix/cli.ino @@ -46,7 +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" +"espnow [] - setup ESP-NOW peer\n" "mot - show motor output\n" "log [dump] - print log header [and data]\n" "cr - calibrate RC\n" diff --git a/flix/util.h b/flix/util.h index cda50f3..f721f4a 100644 --- a/flix/util.h +++ b/flix/util.h @@ -6,6 +6,7 @@ #pragma once #include +#include const float ONE_G = 9.80665; extern float t; @@ -46,6 +47,16 @@ void splitString(String& str, String& token0, String& token1, String& token2) { if (token2.c_str() == NULL) token2 = ""; } +// Simplified ESP-NOW Serial without tx buffering and resends +class ESPNOWSerial : public ESP_NOW_Serial_Class { +public: + using ESP_NOW_Serial_Class::ESP_NOW_Serial_Class; + void onSent(bool success) override {} // disable resends + size_t write(const uint8_t *data, size_t len) override { + return ESP_NOW_Peer::send(data, len); // pure send without buffering + } +}; + // Rate limiter class Rate { public: diff --git a/flix/wifi.ino b/flix/wifi.ino index 63c0488..80bbd09 100644 --- a/flix/wifi.ino +++ b/flix/wifi.ino @@ -9,19 +9,22 @@ #include #include #include "Preferences.h" +#include "util.h" extern Preferences storage; // use the main preferences storage 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); + +ESPNOWSerial espnow(NULL, 0, WIFI_IF_AP); +ESPNOWSerial espnowBroadcast(ESP_NOW.BROADCAST_ADDR, 0, WIFI_IF_AP); +int espnowChannel = 6; void setupWiFi() { print("Setup Wi-Fi\n"); @@ -37,8 +40,12 @@ void setupWiFi() { WiFi.mode(WIFI_AP); WiFi.setChannel(espnowChannel); espnow.setChannel(espnowChannel); - espnow.addr(MacAddress(storage.getString("ESPNOW_PEER_MAC", "").c_str())); + espnow.addr(MacAddress(storage.getString("ESPNOW_PEER_MAC", "FF:FF:FF:FF:FF:FF").c_str())); + String key = storage.getString("ESPNOW_PEER_KEY", ""); + espnow.setKey(key.isEmpty() ? nullptr : (const uint8_t *)key.c_str()); espnow.begin(); + espnowBroadcast.setChannel(espnowChannel); + espnowBroadcast.begin(); } WiFi.setSleep(false); // disable power save @@ -47,6 +54,8 @@ void setupWiFi() { void sendWiFi(const uint8_t *buf, int len) { if (espnow) { espnow.write(buf, len); + static Rate discovery(2); + if (discovery) espnowBroadcast.write((const uint8_t *)"flix", 4); // broadcast message to help finding this device return; } @@ -76,6 +85,7 @@ void printWiFiInfo() { 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("Encrypted: %d\n", espnow.isEncrypted()); print("Channel: %d\n", espnow.getChannel()); } else if (WiFi.getMode() == WIFI_MODE_AP) { print("Mode: Access Point (AP)\n"); @@ -103,14 +113,19 @@ void printWiFiInfo() { } void configWiFi(int mode, const char *first, const char *second) { - if (mode == W_AP) { + MacAddress mac; + if (mode == W_AP && strlen(first) > 0 && strlen(second) >= 8) { storage.putString("WIFI_AP_SSID", first); storage.putString("WIFI_AP_PASS", second); - } else if (mode == W_STA) { + } else if (mode == W_STA && strlen(first) > 0 && strlen(second) >= 8) { storage.putString("WIFI_STA_SSID", first); storage.putString("WIFI_STA_PASS", second); - } else if (mode == W_ESPNOW) { + } else if (mode == W_ESPNOW && mac.fromString(first)) { storage.putString("ESPNOW_PEER_MAC", first); + storage.putString("ESPNOW_PEER_KEY", strlen(second) == ESP_NOW_KEY_LEN ? second : ""); + } else { + print("Invalid configuration\n"); + return; } print("✓ Reboot to apply new settings\n"); } diff --git a/tools/espnow-proxy/espnow-proxy.ino b/tools/espnow-proxy/espnow-proxy.ino index 74bb445..b7d7658 100644 --- a/tools/espnow-proxy/espnow-proxy.ino +++ b/tools/espnow-proxy/espnow-proxy.ino @@ -7,16 +7,25 @@ #include #include #include +#include +#include "../../flix/util.h" +#include const int CHANNEL = 6; +char key[ESP_NOW_KEY_LEN + 1] = {0}; // with trailing null -ESP_NOW_Serial_Class espnow(NULL, CHANNEL, WIFI_IF_AP); -MacAddress peerMac; -volatile bool peerFound = false; +Preferences storage; + +std::vector peers; void onNewPeer(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) { - peerMac = info->src_addr; - peerFound = true; + if (len != 4 || memcmp(data, "flix", 4) != 0) return; // check if discovery message + + Serial.printf("New peer: " MACSTR "\n", MAC2STR(info->src_addr)); + ESPNOWSerial *link = new ESPNOWSerial(info->src_addr, CHANNEL, WIFI_IF_AP); + link->begin(); + link->setKey((const uint8_t *)key); + peers.push_back(link); } void setup() { @@ -25,22 +34,28 @@ void setup() { 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); + storage.begin("espnow-proxy"); + if (!storage.isKey("key")) { + generateRandomKey(); + storage.putString("key", key); } - Serial.printf("Peer found: %s\n", peerMac.toString().c_str()); + strcpy(key, storage.getString("key").c_str()); - espnow.addr(peerMac); - espnow.setChannel(CHANNEL); - espnow.begin(); + // Discover the first peer + while (peers.empty()) { + Serial.printf("espnow %s %s\n", WiFi.softAPmacAddress().c_str(), key); + delay(500); + } +} + +void generateRandomKey() { + const char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*-_+="; + for (int i = 0; i < ESP_NOW_KEY_LEN; i++) { + key[i] = chars[random(0, strlen(chars))]; + } } void loop() { @@ -57,16 +72,17 @@ void loop() { 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); + for (ESPNOWSerial *link : peers) { + link->write(buf, len); + } } } // Send from ESP-NOW to serial - int len = espnow.read(buf, sizeof(buf)); - if (len > 0) { - Serial.write(buf, len); + for (ESPNOWSerial *link : peers) { + int len = link->read(buf, sizeof(buf)); + if (len > 0) { + Serial.write(buf, len); + } } }