9 Commits

Author SHA1 Message Date
Oleg Kalachev ab8f79576d Another minor fixes 2026-05-21 10:45:27 +03:00
Oleg Kalachev 53c9756a5a Minor fix 2026-05-21 10:44:04 +03:00
Oleg Kalachev b3b4334b5a Change picture 2026-05-21 08:52:13 +03:00
Oleg Kalachev 68f96417f3 Add docs for espnow 2026-05-21 05:46:01 +03:00
Oleg Kalachev 2027514af7 Simplify
By default the WiFi channel is used
2026-05-21 02:51:27 +03:00
Oleg Kalachev 8e02b65b99 Fix simulator build 2026-05-21 02:03:10 +03:00
Oleg Kalachev b316cc423a Improve ESP-NOW connection
Implement auto-pairing.
Make key optional.
Remove re-sends.
Add command for uploading proxy to makefile.
2026-05-21 00:06:10 +03:00
Oleg Kalachev 9ac57b246b Implement ESP-NOW support 2026-05-18 14:17:13 +03:00
Oleg Kalachev bd2b1bd5de Improve voltage measurement
Apply PWM_VOLT_PIN without reboot.
Check if the voltage pin can be used with ADC when setting up.
Set voltage to NAN, when it's unknown (including pyflix).
pyflix@0.15.
Don't send BATTERY_STATUS when voltage is unknown.
Add dummy voltage to the simulator.
2026-05-18 00:30:42 +03:00
19 changed files with 240 additions and 35 deletions
+4
View File
@@ -18,6 +18,10 @@ dependencies .dependencies:
arduino-cli lib install "MAVLink"@2.0.25 arduino-cli lib install "MAVLink"@2.0.25
touch .dependencies 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 gazebo/build cmake: gazebo/CMakeLists.txt
mkdir -p gazebo/build mkdir -p gazebo/build
cd gazebo/build && cmake .. cd gazebo/build && cmake ..
+2 -2
View File
@@ -21,8 +21,8 @@
* Dedicated for education and research. * Dedicated for education and research.
* Made from general-purpose components. * Made from general-purpose components.
* Simple and clean source code in Arduino (<2k lines firmware). * Simple and clean source code in Arduino (<2k lines firmware).
* Connectivity using Wi-Fi and MAVLink protocol. * Communication using MAVLink protocol over Wi-Fi or ESP-NOW.
* Control using USB gamepad, remote control or smartphone. * Control with USB gamepad, remote control or smartphone.
* Wireless command line interface and analyzing. * Wireless command line interface and analyzing.
* Precise simulation with Gazebo. * Precise simulation with Gazebo.
* Python library for scripting and automatic flights. * Python library for scripting and automatic flights.
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

+33 -5
View File
@@ -290,11 +290,8 @@ The Wi-Fi mode is chosen using `WIFI_MODE` parameter in QGroundControl or in the
* `0` — Wi-Fi is disabled. * `0` — Wi-Fi is disabled.
* `1` — Access Point mode *(AP)* — the drone creates a Wi-Fi network. * `1` — Access Point mode *(AP)* — the drone creates a Wi-Fi network.
* `2` — Client mode *(STA)* — the drone connects to an existing Wi-Fi network. * `2` — Client mode *(STA)* — the drone connects to an existing Wi-Fi network (may cause additional delays, so generally not recommended).
* `3` — *ESP-NOW (not implemented yet)*. * `3` — ESP-NOW mode — the drone uses ESP-NOW protocol for communication.
> [!WARNING]
> Tests showed that Client mode may cause **additional delays** in remote control (due to retranslations), so it's generally not recommended.
The SSID and password are configured using the `ap` and `sta` console commands: The SSID and password are configured using the `ap` and `sta` console commands:
@@ -316,6 +313,37 @@ Disabling Wi-Fi:
p WIFI_MODE 0 p WIFI_MODE 0
``` ```
### Using ESP-NOW
[ESP-NOW](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/network/esp_now.html) is a low level wireless communication protocol. It can provide lower latency, better reliability, and longer range than Wi-Fi. However, it requires a second ESP32 board to be used as a proxy for the computer.
<img src="img/espnow-connection.jpg" width="600">
To setup ESP-NOW communication:
1. Flash the second ESP32 board with ESP-NOW proxy sketch: [`tools/espnow-proxy/espnow-proxy.ino`](../tools/espnow-proxy/espnow-proxy.ino). Use Arduino IDE or command line: `make upload_proxy`.
2. Open Serial Monitor or use `make monitor` command. The ESP32 will print its MAC address and generated encryption key, for example:
```
espnow 7a:c8:e3:eb:bf:e9 &PiuSysxP9+$L&5E
```
Run this line as a console command on each drone you want to bind to this proxy board.
3. Set the `WIFI_MODE` parameter to `3` on the drone:
```
p WIFI_MODE 3
```
4. Go to the QGroundControl menu ⇒ *Application Settings* ⇒ *Comm Links*, add new link with the following settings:
* Name: ESP32.
* Type: Serial.
* Serial Port: choose the port of the proxy ESP32 board, e. g. `/dev/cu.usbserial-0001`.
* Baud Rate: 115200.
5. Click *Save*. QGroundControl should connect to the drone using ESP-NOW and begin showing the telemetry.
## Flight log ## Flight log
After the flight, you can download the flight log for analysis wirelessly. Use the following command on your computer for that: After the flight, you can download the flight log for analysis wirelessly. Use the following command on your computer for that:
+6 -2
View File
@@ -10,6 +10,7 @@
extern const int MOTOR_REAR_LEFT, MOTOR_REAR_RIGHT, MOTOR_FRONT_RIGHT, MOTOR_FRONT_LEFT; 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 RAW, ACRO, STAB, AUTO;
extern const int W_AP, W_STA, W_ESPNOW;
extern float t, dt, loopRate; extern float t, dt, loopRate;
extern uint16_t channels[16]; extern uint16_t channels[16];
extern float controlTime; extern float controlTime;
@@ -45,6 +46,7 @@ const char* motd =
"wifi - show Wi-Fi info\n" "wifi - show Wi-Fi info\n"
"ap <ssid> <password> - setup Wi-Fi access point\n" "ap <ssid> <password> - setup Wi-Fi access point\n"
"sta <ssid> <password> - setup Wi-Fi client mode\n" "sta <ssid> <password> - setup Wi-Fi client mode\n"
"espnow <mac> [<key>] - setup ESP-NOW peer\n"
"mot - show motor output\n" "mot - show motor output\n"
"log [dump] - print log header [and data]\n" "log [dump] - print log header [and data]\n"
"cr - calibrate RC\n" "cr - calibrate RC\n"
@@ -143,9 +145,11 @@ void doCommand(String str, bool echo = false) {
} else if (command == "wifi") { } else if (command == "wifi") {
printWiFiInfo(); printWiFiInfo();
} else if (command == "ap") { } 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") { } 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") { } else if (command == "mot") {
print("front-right %g front-left %g rear-right %g rear-left %g\n", 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]); motors[MOTOR_FRONT_RIGHT], motors[MOTOR_FRONT_LEFT], motors[MOTOR_REAR_RIGHT], motors[MOTOR_REAR_LEFT]);
+1 -1
View File
@@ -46,7 +46,7 @@ void sendMavlink() {
float remaining = constrain(mapf(voltage, 3.4, 4.2, 0, 1), 0, 1); float remaining = constrain(mapf(voltage, 3.4, 4.2, 0, 1), 0, 1);
mavlink_msg_battery_status_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg, 0, MAV_BATTERY_FUNCTION_ALL, mavlink_msg_battery_status_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg, 0, MAV_BATTERY_FUNCTION_ALL,
MAV_BATTERY_TYPE_LIPO, INT16_MAX, voltages, -1, -1, -1, remaining * 100, 0, MAV_BATTERY_CHARGE_STATE_OK, voltagesExt, 0, 0); MAV_BATTERY_TYPE_LIPO, INT16_MAX, voltages, -1, -1, -1, remaining * 100, 0, MAV_BATTERY_CHARGE_STATE_OK, voltagesExt, 0, 0);
sendMessage(&msg); if (valid(voltage)) sendMessage(&msg);
} }
if (telemetryFast && mavlinkConnected) { if (telemetryFast && mavlinkConnected) {
+5 -2
View File
@@ -10,7 +10,7 @@ extern int channelZero[16];
extern int channelMax[16]; extern int channelMax[16];
extern int rollChannel, pitchChannel, throttleChannel, yawChannel, armedChannel, modeChannel; extern int rollChannel, pitchChannel, throttleChannel, yawChannel, armedChannel, modeChannel;
extern int rcRxPin; extern int rcRxPin;
extern int wifiMode, udpLocalPort, udpRemotePort; extern int wifiMode, wifiLongRange, udpLocalPort, udpRemotePort, espnowChannel;
extern float rcLossTimeout, descendTime; extern float rcLossTimeout, descendTime;
extern int voltagePin; extern int voltagePin;
extern float voltageScale; extern float voltageScale;
@@ -112,12 +112,15 @@ Parameter parameters[] = {
{"WIFI_MODE", &wifiMode}, {"WIFI_MODE", &wifiMode},
{"WIFI_PORT_LOC", &udpLocalPort}, {"WIFI_PORT_LOC", &udpLocalPort},
{"WIFI_PORT_REM", &udpRemotePort}, {"WIFI_PORT_REM", &udpRemotePort},
{"WIFI_LONG_RANGE", &wifiLongRange},
// espnow
{"ESPNOW_CHANNEL", &espnowChannel},
// mavlink // mavlink
{"MAV_SYS_ID", &mavlinkSysId}, {"MAV_SYS_ID", &mavlinkSysId},
{"MAV_RATE_SLOW", &telemetrySlow.rate}, {"MAV_RATE_SLOW", &telemetrySlow.rate},
{"MAV_RATE_FAST", &telemetryFast.rate}, {"MAV_RATE_FAST", &telemetryFast.rate},
// power // power
{"PWR_VOLT_PIN", &voltagePin}, {"PWR_VOLT_PIN", &voltagePin, setupPower},
{"PWR_VOLT_SCALE", &voltageScale}, {"PWR_VOLT_SCALE", &voltageScale},
{"PWR_VOLT_LPF_A", &voltageFilter.alpha}, {"PWR_VOLT_LPF_A", &voltageFilter.alpha},
// safety // safety
+3 -3
View File
@@ -8,14 +8,14 @@
#include "lpf.h" #include "lpf.h"
#include "util.h" #include "util.h"
float voltage; float voltage = NAN;
LowPassFilter<float> voltageFilter(0.2); LowPassFilter<float> voltageFilter(0.2);
int voltagePin = -1; int voltagePin = -1;
float voltageScale = 2; float voltageScale = 2;
void setupPower() { void setupPower() {
// Disable reset on low voltage REG_CLR_BIT(RTC_CNTL_BROWN_OUT_REG, RTC_CNTL_BROWN_OUT_ENA); // disable reset on low voltage
REG_CLR_BIT(RTC_CNTL_BROWN_OUT_REG, RTC_CNTL_BROWN_OUT_ENA); if (digitalPinToAnalogChannel(voltagePin) == -1) voltagePin = -1; // test ADC pin
} }
void readVoltage() { void readVoltage() {
+11
View File
@@ -6,6 +6,7 @@
#pragma once #pragma once
#include <math.h> #include <math.h>
#include <ESP32_NOW_Serial.h>
const float ONE_G = 9.80665; const float ONE_G = 9.80665;
extern float t; extern float t;
@@ -46,6 +47,16 @@ void splitString(String& str, String& token0, String& token1, String& token2) {
if (token2.c_str() == NULL) 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 // Rate limiter
class Rate { class Rate {
public: public:
+64 -17
View File
@@ -1,82 +1,129 @@
// Copyright (c) 2023 Oleg Kalachev <okalachev@gmail.com> // Copyright (c) 2023 Oleg Kalachev <okalachev@gmail.com>
// Repository: https://github.com/okalachev/flix // Repository: https://github.com/okalachev/flix
// Wi-Fi communication // Wi-Fi and ESP-NOW communication
#include <WiFi.h> #include <WiFi.h>
#include <WiFiAP.h> #include <WiFiAP.h>
#include <WiFiUdp.h> #include <WiFiUdp.h>
#include <MacAddress.h>
#include <ESP32_NOW_Serial.h>
#include "Preferences.h" #include "Preferences.h"
#include "util.h"
extern Preferences storage; // use the main preferences storage 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 wifiMode = W_AP;
int wifiLongRange = 0;
int udpLocalPort = 14550; int udpLocalPort = 14550;
int udpRemotePort = 14550; int udpRemotePort = 14550;
IPAddress udpRemoteIP = "255.255.255.255"; IPAddress udpRemoteIP = "255.255.255.255";
WiFiUDP udp; WiFiUDP udp;
ESPNOWSerial espnow(NULL, 0, WIFI_IF_AP);
ESPNOWSerial espnowBroadcast(ESP_NOW.BROADCAST_ADDR, 0, WIFI_IF_AP);
int espnowChannel = 6;
void setupWiFi() { void setupWiFi() {
print("Setup Wi-Fi\n"); print("Setup Wi-Fi\n");
WiFi.enableLongRange(wifiLongRange);
if (wifiMode == W_AP) { if (wifiMode == W_AP) {
WiFi.softAP(storage.getString("WIFI_AP_SSID", "flix").c_str(), storage.getString("WIFI_AP_PASS", "flixwifi").c_str()); 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) { } else if (wifiMode == W_STA) {
WiFi.begin(storage.getString("WIFI_STA_SSID", "").c_str(), storage.getString("WIFI_STA_PASS", "").c_str()); WiFi.begin(storage.getString("WIFI_STA_SSID", "").c_str(), storage.getString("WIFI_STA_PASS", "").c_str());
} else { udp.begin(udpLocalPort);
return; } else if (wifiMode == W_ESPNOW) {
WiFi.mode(WIFI_AP);
WiFi.setChannel(espnowChannel);
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.begin();
} }
WiFi.setSleep(false); // disable power save WiFi.setSleep(false); // disable power save
udp.begin(udpLocalPort);
} }
void sendWiFi(const uint8_t *buf, int len) { 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;
}
if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return; if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return;
udp.beginPacket(udpRemoteIP, udpRemotePort); udp.beginPacket(udpRemoteIP, udpRemotePort);
udp.write(buf, len); udp.write(buf, len);
udp.endPacket(); udp.endPacket();
} }
int receiveWiFi(uint8_t *buf, int len) { int receiveWiFi(uint8_t *buf, int len) {
if (espnow) {
return espnow.read(buf, len);
}
if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return 0; if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return 0;
udp.parsePacket(); udp.parsePacket();
if (udp.remoteIP()) udpRemoteIP = udp.remoteIP(); if (udp.remoteIP()) udpRemoteIP = udp.remoteIP();
return udp.read(buf, len); return udp.read(buf, len);
} }
void printWiFiInfo() { 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("Encrypted: %d\n", espnow.isEncrypted());
print("Channel: %d\n", espnow.getChannel());
} else if (WiFi.getMode() == WIFI_MODE_AP) {
print("Mode: Access Point (AP)\n"); print("Mode: Access Point (AP)\n");
print("MAC: %s\n", WiFi.softAPmacAddress().c_str()); print("MAC: %s\n", WiFi.softAPmacAddress().c_str());
print("SSID: %s\n", WiFi.softAPSSID().c_str()); print("SSID: %s\n", WiFi.softAPSSID().c_str());
print("Password: ***\n"); print("Password: ***\n");
print("Channel: %d\n", WiFi.channel());
print("Clients: %d\n", WiFi.softAPgetStationNum()); print("Clients: %d\n", WiFi.softAPgetStationNum());
print("IP: %s\n", WiFi.softAPIP().toString().c_str()); 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) { } else if (WiFi.getMode() == WIFI_MODE_STA) {
print("Mode: Client (STA)\n"); print("Mode: Client (STA)\n");
print("Connected: %d\n", WiFi.isConnected()); print("Connected: %d\n", WiFi.isConnected());
print("MAC: %s\n", WiFi.macAddress().c_str()); print("MAC: %s\n", WiFi.macAddress().c_str());
print("SSID: %s\n", WiFi.SSID().c_str()); print("SSID: %s\n", WiFi.SSID().c_str());
print("Password: ***\n"); 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("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 { } else {
print("Mode: Disabled\n"); 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); print("MAVLink connected: %d\n", mavlinkConnected);
} }
void configWiFi(bool ap, const char *ssid, const char *password) { void configWiFi(int mode, const char *first, const char *second) {
if (ap) { MacAddress mac;
storage.putString("WIFI_AP_SSID", ssid); if (mode == W_AP && strlen(first) > 0 && strlen(second) >= 8) {
storage.putString("WIFI_AP_PASS", password); storage.putString("WIFI_AP_SSID", first);
storage.putString("WIFI_AP_PASS", second);
} 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 && mac.fromString(first)) {
storage.putString("ESPNOW_PEER_MAC", first);
storage.putString("ESPNOW_PEER_KEY", strlen(second) == ESP_NOW_KEY_LEN ? second : "");
} else { } else {
storage.putString("WIFI_STA_SSID", ssid); print("Invalid configuration\n");
storage.putString("WIFI_STA_PASS", password); return;
} }
print("✓ Reboot to apply new settings\n"); print("✓ Reboot to apply new settings\n");
} }
+1
View File
@@ -168,6 +168,7 @@ void delay(uint32_t ms) {
bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution) { return true; } bool ledcAttach(uint8_t pin, uint32_t freq, uint8_t resolution) { return true; }
bool ledcWrite(uint8_t pin, uint32_t duty) { return true; } bool ledcWrite(uint8_t pin, uint32_t duty) { return true; }
uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) { return freq; } uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) { return freq; }
int8_t digitalPinToAnalogChannel(uint8_t pin) { return -1; }
uint32_t analogReadMilliVolts(uint8_t pin) { return 0; } uint32_t analogReadMilliVolts(uint8_t pin) { return 0; }
unsigned long __micros; unsigned long __micros;
+12
View File
@@ -0,0 +1,12 @@
// Dummy file for the simulator
class ESP_NOW_Peer {
protected:
size_t send(const uint8_t *data, int len) { return 0; }
};
class ESP_NOW_Serial_Class : public ESP_NOW_Peer {
public:
virtual void onSent(bool success) {};
virtual size_t write(const uint8_t *data, size_t len) { return 0; };
};
+1
View File
@@ -58,6 +58,7 @@ void handleMavlink(const void *_msg);
void mavlinkPrint(const char* str); void mavlinkPrint(const char* str);
void sendMavlinkPrint(); void sendMavlinkPrint();
inline Quaternion fluToFrd(const Quaternion &q); inline Quaternion fluToFrd(const Quaternion &q);
void setupPower();
void failsafe(); void failsafe();
void rcLossFailsafe(); void rcLossFailsafe();
void descend(); void descend();
+2
View File
@@ -73,6 +73,8 @@ public:
gyro = Vector(imu->AngularVelocity().X(), imu->AngularVelocity().Y(), imu->AngularVelocity().Z()); gyro = Vector(imu->AngularVelocity().X(), imu->AngularVelocity().Y(), imu->AngularVelocity().Z());
acc = this->accFilter.update(Vector(imu->LinearAcceleration().X(), imu->LinearAcceleration().Y(), imu->LinearAcceleration().Z())); acc = this->accFilter.update(Vector(imu->LinearAcceleration().X(), imu->LinearAcceleration().Y(), imu->LinearAcceleration().Z()));
voltage = 4.2f; // dummy voltage value
readRC(); readRC();
estimate(); estimate();
+3
View File
@@ -0,0 +1,3 @@
# ESPNOW-proxy
Proxy sketch for using ESP-NOW connection with Flix drone.
+88
View File
@@ -0,0 +1,88 @@
// Copyright (c) 2026 Oleg Kalachev <okalachev@gmail.com>
// Repository: https://github.com/okalachev/flix
// Proxy for ESP-NOW connection
#include <vector>
#include <WiFi.h>
#include <ESP32_NOW_Serial.h>
#include <MacAddress.h>
#include <MAVLink.h>
#include <Preferences.h>
#include "../../flix/util.h"
const int CHANNEL = 6;
char key[ESP_NOW_KEY_LEN + 1] = {0}; // with trailing null
Preferences storage;
std::vector<ESPNOWSerial *> peers;
void onNewPeer(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) {
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() {
Serial.begin(115200);
WiFi.mode(WIFI_AP);
WiFi.setSleep(false);
WiFi.setChannel(CHANNEL);
ESP_NOW.onNewPeer(onNewPeer, NULL);
ESP_NOW.begin();
storage.begin("espnow-proxy");
if (!storage.isKey("key")) {
generateRandomKey();
storage.putString("key", key);
}
strcpy(key, storage.getString("key").c_str());
// 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() {
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);
for (ESPNOWSerial *link : peers) {
link->write(buf, len);
}
}
}
// Send from ESP-NOW to Serial
for (ESPNOWSerial *link : peers) {
int len = link->read(buf, sizeof(buf));
if (len > 0) {
Serial.write(buf, len);
}
}
}
+1 -1
View File
@@ -37,7 +37,7 @@ print(flix.connected) # True if connected to the drone
print(flix.mode) # current flight mode (str) print(flix.mode) # current flight mode (str)
print(flix.armed) # True if the drone is armed print(flix.armed) # True if the drone is armed
print(flix.landed) # True if the drone is landed print(flix.landed) # True if the drone is landed
print(flix.voltage) # battery voltage print(flix.voltage) # battery voltage (NaN - unknown, ~0 - USB powered)
print(flix.attitude) # attitude quaternion [w, x, y, z] print(flix.attitude) # attitude quaternion [w, x, y, z]
print(flix.attitude_euler) # attitude as Euler angles [roll, pitch, yaw] print(flix.attitude_euler) # attitude as Euler angles [roll, pitch, yaw]
print(flix.rates) # angular rates [roll_rate, pitch_rate, yaw_rate] print(flix.rates) # angular rates [roll_rate, pitch_rate, yaw_rate]
+2 -1
View File
@@ -5,6 +5,7 @@
import os import os
import time import time
import math
from queue import Queue, Empty from queue import Queue, Empty
from typing import Optional, Callable, List, Dict, Any, Union, Sequence from typing import Optional, Callable, List, Dict, Any, Union, Sequence
import logging import logging
@@ -26,7 +27,7 @@ class Flix:
mode: str = '' mode: str = ''
armed: bool = False armed: bool = False
landed: bool = False landed: bool = False
voltage: float = 0 voltage: float = math.nan
attitude: List[float] attitude: List[float]
attitude_euler: List[float] # roll, pitch, yaw attitude_euler: List[float] # roll, pitch, yaw
rates: List[float] rates: List[float]
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "pyflix" name = "pyflix"
version = "0.14" version = "0.15"
description = "Python API for Flix drone" description = "Python API for Flix drone"
authors = [{ name="Oleg Kalachev", email="okalachev@gmail.com" }] authors = [{ name="Oleg Kalachev", email="okalachev@gmail.com" }]
license = "MIT" license = "MIT"