23 Commits
Author SHA1 Message Date
Oleg Kalachev 142cb9af01 Position control draft 2026-08-01 20:13:48 +03:00
Oleg Kalachev 74b082957e Add threshold for rc sticks move for quitting auto mode 2026-07-21 10:58:22 +03:00
Oleg Kalachev 4582942919 Minor docs update 2026-07-21 10:51:50 +03:00
Oleg Kalachev df14a5678f Don't send discovery message if encrypting is disabled in espnow 2026-07-20 19:01:38 +03:00
Oleg Kalachev 81721abf91 Disable log as it may crash the program 2026-07-20 10:16:30 +03:00
Oleg Kalachev 1276d220a6 Add tilt disarm failsafe 2026-07-18 21:35:13 +03:00
Oleg Kalachev 79c7e282cb Fix esp-now proxy 2026-07-18 14:25:19 +03:00
Oleg Kalachev f031cf3046 Fix printing espnow channel number in wifi command 2026-07-18 14:16:33 +03:00
Oleg Kalachev 60f389a289 Fix espnow proxy 2026-07-18 11:35:20 +03:00
Oleg Kalachev 5c24ce113d Minor docs update 2026-07-18 00:45:19 +03:00
Oleg Kalachev b58c2e7c7a Add auto channel search to espnow-proxy 2026-07-18 00:03:18 +03:00
Oleg Kalachev 2137730a8d Make espnow-proxy output more obvious 2026-07-18 00:03:01 +03:00
Oleg Kalachev 309d2d2e29 Add espnow-proxy build to ci 2026-07-17 23:42:28 +03:00
Oleg Kalachev 0c91ccfab5 Use STA interface in ESP-NOW proxy 2026-07-17 23:39:21 +03:00
Oleg Kalachev 26d53d4ce1 Docs fix 2026-07-17 17:18:07 +03:00
Oleg Kalachev 0366b24ff3 Minor docs fix 2026-07-17 17:06:22 +03:00
Oleg Kalachev 3de4e9ab9a Add info on uploading the firmware to ESP32-S3 Super Mini 2026-07-17 17:04:45 +03:00
Oleg Kalachev f1e024b19a Add instructions on how to set pin numbers for imu 2026-07-17 14:13:02 +03:00
Oleg Kalachev d9be26c5be Disable gyro notch filter by default 2026-07-16 23:21:17 +03:00
Oleg Kalachev f8a4cbfee1 Fix in docs 2026-07-16 17:48:16 +03:00
Oleg Kalachev 658040e652 Merge branch 'master' into robolager2026 2026-07-16 16:54:47 +03:00
Oleg Kalachev 70459df8dd Disable swarming in espnow proxy by default 2026-07-16 11:30:10 +03:00
Oleg Kalachev 61213bb8a1 Unset default motor pins (for safety) 2026-07-15 20:19:02 +03:00
27 changed files with 345 additions and 344 deletions
+15 -30
View File
@@ -10,29 +10,23 @@ on:
jobs: jobs:
build_linux: build_linux:
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
ARDUINO_SKETCH_ALWAYS_EXPORT_BINARIES: 1
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Install Arduino CLI - name: Install Arduino CLI
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
- name: Build firmware for ESP32 - name: Build firmware
env:
ARDUINO_SKETCH_ALWAYS_EXPORT_BINARIES: 1
run: make run: make
- name: Build firmware for ESP32-C3
run: make BOARD=esp32:esp32:esp32c3
- name: Build firmware for ESP32-S3
run: make BOARD=esp32:esp32:esp32s3:CDCOnBoot=cdc
- name: Build firmware for ESP32-S3 with QSPI PSRAM
run: make BOARD=esp32:esp32:esp32s3:CDCOnBoot=cdc,PSRAM=enabled EXTRA=--output-dir=flix/build/esp32.esp32.esp32s3.qspi
- name: Build firmware for ESP32-S3 with OPI PSRAM
run: make BOARD=esp32:esp32:esp32s3:CDCOnBoot=cdc,PSRAM=opi EXTRA=--output-dir=flix/build/esp32.esp32.esp32s3.opi
- name: Build firmware for Flix2
run: make BOARD=esp32:esp32:esp32s3:FlashSize=4M,CDCOnBoot=cdc,PSRAM=opi FLAGS=-DFLIX2 EXTRA=--output-dir=flix/build/esp32.esp32.flix2
- name: Upload binaries - name: Upload binaries
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: firmware-binary name: firmware-binary
path: flix/build path: flix/build
- name: Build firmware for ESP32-C3
run: make BOARD=esp32:esp32:esp32c3
- name: Build firmware for ESP32-S3
run: make BOARD=esp32:esp32:esp32s3
- name: Build espnow-proxy - name: Build espnow-proxy
run: arduino-cli compile --fqbn esp32:esp32:esp32 tools/espnow-proxy run: arduino-cli compile --fqbn esp32:esp32:esp32 tools/espnow-proxy
- name: Check c_cpp_properties.json - name: Check c_cpp_properties.json
@@ -41,7 +35,7 @@ jobs:
build_macos: build_macos:
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Install Arduino CLI - name: Install Arduino CLI
run: brew install arduino-cli run: brew install arduino-cli
- name: Build firmware - name: Build firmware
@@ -52,7 +46,7 @@ jobs:
build_windows: build_windows:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Install Arduino CLI - name: Install Arduino CLI
run: choco install arduino-cli run: choco install arduino-cli
- name: Install Make - name: Install Make
@@ -72,8 +66,8 @@ jobs:
apt-get update apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget build-essential cmake g++ pkg-config gnupg2 lsb-release sudo DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget build-essential cmake g++ pkg-config gnupg2 lsb-release sudo
- name: Install Arduino CLI - name: Install Arduino CLI
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh uses: arduino/setup-arduino-cli@v1.1.1
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Install Gazebo - name: Install Gazebo
run: | run: |
sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list' sudo sh -c 'echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list'
@@ -84,16 +78,7 @@ jobs:
run: sudo apt-get install -y libsdl2-dev run: sudo apt-get install -y libsdl2-dev
- name: Build simulator - name: Build simulator
run: make build_simulator run: make build_simulator
- name: Run simulator - uses: actions/upload-artifact@v4
env:
GAZEBO_MODEL_PATH: ${{ github.workspace }}/gazebo/models
GAZEBO_PLUGIN_PATH: ${{ github.workspace }}/gazebo/build
run: |
OUT=$(timeout -k 10s 120s gzserver --verbose gazebo/flix.world 2>&1 | tee /dev/stderr)
if echo "$OUT" | grep -Pq "\[Err\](?! \[RenderEngine)"; then
exit 1
fi
- uses: actions/upload-artifact@v7
with: with:
name: gazebo-plugin-binary name: gazebo-plugin-binary
path: gazebo/build/*.so path: gazebo/build/*.so
@@ -105,7 +90,7 @@ jobs:
steps: steps:
- name: Install Arduino CLI - name: Install Arduino CLI
run: brew install arduino-cli run: brew install arduino-cli
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Clean up python binaries # Workaround for https://github.com/actions/setup-python/issues/577 - name: Clean up python binaries # Workaround for https://github.com/actions/setup-python/issues/577
run: | run: |
rm -f /usr/local/bin/2to3* rm -f /usr/local/bin/2to3*
+5 -44
View File
@@ -8,7 +8,6 @@ on:
permissions: permissions:
contents: read contents: read
actions: read
pages: write pages: write
id-token: write id-token: write
@@ -16,7 +15,7 @@ jobs:
markdownlint: markdownlint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Install markdownlint - name: Install markdownlint
run: npm install -g markdownlint-cli2 run: npm install -g markdownlint-cli2
- name: Run markdownlint - name: Run markdownlint
@@ -25,57 +24,19 @@ jobs:
build_book: build_book:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: markdownlint needs: markdownlint
env:
BINARIES: ${{ github.event_name == 'push' && (github.ref_name == 'master' || github.ref_name == 'dev') && github.repository == 'okalachev/flix' }}
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Install mdBook - name: Install mdBook
run: cargo install mdbook --vers 0.4.43 --locked run: cargo install mdbook --vers 0.4.43 --locked
- name: Build book - name: Build book
run: cd docs && mdbook build run: cd docs && mdbook build
- name: Wait for Build to complete
if: ${{ env.BINARIES }}
uses: lewagon/wait-on-check-action@v1.9.1
with:
ref: ${{ github.sha }}
check-name: build_linux
repo-token: ${{ secrets.GITHUB_TOKEN }}
wait-interval: 30
- name: Find firmware binaries
if: ${{ env.BINARIES }}
id: build_run
run: |
RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/build.yml/runs?head_sha=${{ github.sha }}&per_page=1" --jq '.workflow_runs[0].id')
echo "id=$RUN_ID" >> $GITHUB_OUTPUT
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Download firmware binaries
if: ${{ env.BINARIES }}
uses: actions/download-artifact@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
run-id: ${{ steps.build_run.outputs.id }}
name: firmware-binary
path: docs/build
- name: Create shortcuts for firmware binaries
if: ${{ env.BINARIES }}
working-directory: docs/build
run: |
for FQBN in esp32.esp32.*; do
zip -r $FQBN.zip $FQBN
BOARD="${FQBN#esp32.esp32.}"
ln -s "$FQBN/flix.ino.merged.bin" "flix.$BOARD.merged.bin"
ln -s "$FQBN/flix.ino.bin" "flix.$BOARD.bin"
ln -s "$FQBN/flix.ino.bootloader.bin" "flix.$BOARD.bootloader.bin"
done
- name: Upload artifact - name: Upload artifact
uses: actions/upload-pages-artifact@v5 uses: actions/upload-pages-artifact@v3
with: with:
path: docs/build path: docs/build
deploy: deploy:
if: ${{ github.event_name == 'push' && github.ref_name == 'master' }} if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
concurrency: concurrency:
group: "pages" group: "pages"
cancel-in-progress: true cancel-in-progress: true
@@ -87,4 +48,4 @@ jobs:
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
id: deployment id: deployment
uses: actions/deploy-pages@v5 uses: actions/deploy-pages@v4
+4 -24
View File
@@ -10,7 +10,7 @@ jobs:
csv_to_ulog: csv_to_ulog:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Build csv_to_ulog - name: Build csv_to_ulog
run: cd tools/csv_to_ulog && mkdir build && cd build && cmake .. && make run: cd tools/csv_to_ulog && mkdir build && cd build && cmake .. && make
- name: Test csv_to_ulog - name: Test csv_to_ulog
@@ -22,13 +22,13 @@ jobs:
pyflix: pyflix:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Install Python build tools - name: Install Python build tools
run: pip install build run: pip install build
- name: Build pyflix - name: Build pyflix
run: python3 -m build tools run: python3 -m build tools
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v7 uses: actions/upload-artifact@v4
with: with:
name: pyflix name: pyflix
path: | path: |
@@ -37,7 +37,7 @@ jobs:
python_tools: python_tools:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v4
- name: Install Python dependencies - name: Install Python dependencies
run: pip install -r tools/requirements.txt run: pip install -r tools/requirements.txt
- name: Test csv_to_mcap tool - name: Test csv_to_mcap tool
@@ -46,23 +46,3 @@ jobs:
echo -e "t,x,y,z\n0,1,2,3\n1,4,5,6" > log.csv echo -e "t,x,y,z\n0,1,2,3\n1,4,5,6" > log.csv
./csv_to_mcap.py log.csv ./csv_to_mcap.py log.csv
test $(stat -c %s log.mcap) -eq 883 test $(stat -c %s log.mcap) -eq 883
sloc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- run: sudo apt-get install -y cloc jq
- name: Print source lines of code
run: cloc --by-file-by-lang flix
- name: Checkout previous revision
uses: actions/checkout@v7
with:
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }}
path: prev
- name: Annotate total source lines
run: |
SLOC_CURR=$(cloc flix --json | jq -r '.SUM.code')
SLOC_PREV=$(cloc prev/flix --json | jq -r '.SUM.code')
DIFF=$(printf '%+d' "$((SLOC_CURR - SLOC_PREV))")
echo "* Current SLOC: $SLOC_CURR" >> $GITHUB_STEP_SUMMARY
echo "* Previous SLOC: $SLOC_PREV" >> $GITHUB_STEP_SUMMARY
echo "* Diff: $DIFF" >> $GITHUB_STEP_SUMMARY
+6 -10
View File
@@ -1,17 +1,13 @@
BOARD = esp32:esp32:esp32 BOARD = esp32:esp32:d1_mini32
PORT := $(strip $(wildcard /dev/serial/by-id/usb-Silicon_Labs_CP21* /dev/serial/by-id/usb-1a86_USB_Single_Serial_* /dev/cu.usbserial-* /dev/cu.usbmodem*)) PORT := $(strip $(wildcard /dev/serial/by-id/usb-Silicon_Labs_CP21* /dev/serial/by-id/usb-1a86_USB_Single_Serial_* /dev/cu.usbserial-* /dev/cu.usbmodem*))
VERSION = $(shell git describe --always --dirty)
export ARDUINO_NETWORK_CONNECTION_TIMEOUT := 1h export ARDUINO_NETWORK_CONNECTION_TIMEOUT := 1h
build: .core .libs build: .core .libs
arduino-cli compile flix \ arduino-cli compile --fqbn $(BOARD) --build-property "build.core_debug_level=1" flix
--fqbn $(BOARD) \
--build-property "build.core_debug_level=1" \
--build-property "compiler.cpp.extra_flags=-DVERSION=$(VERSION) $(FLAGS)" $(EXTRA)
upload: build upload: build
arduino-cli upload flix --fqbn $(BOARD) -p "$(PORT)" arduino-cli upload --fqbn $(BOARD) -p "$(PORT)" flix
erase: erase:
arduino-cli burn-bootloader --fqbn $(BOARD) -p "$(PORT)" -P esptool arduino-cli burn-bootloader --fqbn $(BOARD) -p "$(PORT)" -P esptool
@@ -26,13 +22,13 @@ core .core:
libs .libs: libs .libs:
arduino-cli lib update-index arduino-cli lib update-index
ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=1 arduino-cli lib install --git-url 'https://github.com/okalachev/flixperiph.git#dev' arduino-cli lib install "FlixPeriph"
arduino-cli lib install "MAVLink"@2.0.25 arduino-cli lib install "MAVLink"@2.0.25
touch .libs touch .libs
upload_proxy: .core .libs upload_proxy: .core .libs
arduino-cli compile tools/espnow-proxy --fqbn $(BOARD) arduino-cli compile --fqbn $(BOARD) tools/espnow-proxy
arduino-cli upload tools/espnow-proxy --fqbn $(BOARD) -p "$(PORT)" 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
-6
View File
@@ -55,12 +55,6 @@ Outdoor flights demo video of the current prototype:
<a href="https://youtu.be/KXlNmvUTi4g"><img width=300 src="https://i3.ytimg.com/vi/KXlNmvUTi4g/maxresdefault.jpg"></a> <a href="https://youtu.be/KXlNmvUTi4g"><img width=300 src="https://i3.ytimg.com/vi/KXlNmvUTi4g/maxresdefault.jpg"></a>
### Position control
The position control feature is in development. RoboCamp 2026 demo (using an overhead camera, [sources](https://github.com/xTimop/flix-poscontrol/compare/robolager2026...xTimop:flix-poscontrol:poscontrol)):
<a href="https://youtu.be/369Xowm4HcU"><img width=300 src="https://i3.ytimg.com/vi/369Xowm4HcU/maxresdefault.jpg"></a>
## Simulation ## Simulation
The simulator is implemented using Gazebo and runs the original Arduino code: The simulator is implemented using Gazebo and runs the original Arduino code:
+1 -1
View File
@@ -73,7 +73,7 @@ Parameters subsystem (`parameters.ino`) uses standard [Preferences.h](https://do
To add a new parameter: To add a new parameter:
1. Define a global variable for the parameter, three types are supported: `float`, `int`, and `bool`. 1. Define a global variable for the parameter, two types are supported: `float` and `int`.
2. Add an entry to the `parameters` array, with the parameter name, a pointer to the variable, and optionally a callback function to call when the parameter is changed. 2. Add an entry to the `parameters` array, with the parameter name, a pointer to the variable, and optionally a callback function to call when the parameter is changed.
3. Everything else will be handled automatically. 3. Everything else will be handled automatically.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

+39 -52
View File
@@ -1,48 +1,20 @@
# Usage: build, setup and flight # Usage: build, setup and flight
To fly Flix quadcopter, you need to upload the firmware to the ESP32 board, and set up the drone for flight. To fly Flix quadcopter, you need to build the firmware, upload it to the ESP32 board, and set up the drone for flight.
## Uploading the firmware To get the firmware sources, clone the repository using git:
You can either use the **prebuilt binaries** or **build the firmware** from sources — this will let you modify the firmware and add new features.
### Prebuilt binaries (the easiest way)
1. Download the latest firmware file using the following links:
<!-- markdownlint-disable MD044 -->
|Type|Boards|Link|
|-|-|-|
|ESP32|DevKit, D1 Mini|[`quadcopter.dev/flix.esp32.merged.bin`](https://quadcopter.dev/flix.esp32.merged.bin)|
|ESP32-S3|Most S3 based|[`quadcopter.dev/flix.esp32s3.merged.bin`](https://quadcopter.dev/flix.esp32s3.merged.bin)|
|ESP32-S3 (2MB PSRAM)|S3 Super Mini, S3 Zero (2MB PSRAM)|[`quadcopter.dev/flix.esp32s3.qspi.merged.bin`](https://quadcopter.dev/flix.esp32s3.qspi.merged.bin)|
|ESP32-S3 (8/16MB PSRAM)|S3 Zero (8MB PSRAM)|[`quadcopter.dev/flix.esp32s3.opi.merged.bin`](https://quadcopter.dev/flix.esp32s3.opi.merged.bin)|
|ESP32-C3|C3 Super Mini|[`quadcopter.dev/flix.esp32c3.merged.bin`](https://quadcopter.dev/flix.esp32c3.merged.bin)|
|Flix2|Flix2 board|[`quadcopter.dev/flix.flix2.merged.bin`](https://quadcopter.dev/flix.flix2.merged.bin)|
<!-- markdownlint-enable MD044 -->
2. Flash your ESP32 board using [ESP32 Web Flasher](https://www.espboards.dev/tools/program/):
<img src="img/web-flasher.png" width="400">
* Connect the board to your computer, press *Connect to ESP*, choose the serial port.
* Go to the *Flash* tab.
* Choose the downloaded firmware file, set *Flash address* to *0* (important).
* Click *Program* button and wait until the process is finished.
### Building from sources (flexible)
You can build and upload the firmware using either **Arduino IDE** (easier for beginners) or **command line**.
Get the sources using git:
```bash ```bash
git clone https://github.com/okalachev/flix.git && cd flix git clone https://github.com/okalachev/flix.git && cd flix
``` ```
Beginners can [download the sources as a ZIP archive](https://github.com/okalachev/flix/archive/refs/heads/master.zip). Beginners can [download the source code as a ZIP archive](https://github.com/okalachev/flix/archive/refs/heads/master.zip).
#### Arduino IDE (Windows, Linux, macOS) ## Building the firmware
You can build and upload the firmware using either **Arduino IDE** (easier for beginners) or **command line**.
### Arduino IDE (Windows, Linux, macOS)
<img src="img/arduino-ide.png" width="400" alt="Flix firmware open in Arduino IDE"> <img src="img/arduino-ide.png" width="400" alt="Flix firmware open in Arduino IDE">
@@ -57,7 +29,7 @@ Beginners can [download the sources as a ZIP archive](https://github.com/okalach
7. Set *Tools**Core Debug Level* to *Error* to see the errors in the serial console. Set *Tools**USB CDC on Boot* to *Enabled* for ESP32-S3/ESP32-C3 boards. 7. Set *Tools**Core Debug Level* to *Error* to see the errors in the serial console. Set *Tools**USB CDC on Boot* to *Enabled* for ESP32-S3/ESP32-C3 boards.
8. [Build and upload](https://docs.arduino.cc/software/ide-v2/tutorials/getting-started/ide-v2-uploading-a-sketch) the firmware using Arduino IDE. 8. [Build and upload](https://docs.arduino.cc/software/ide-v2/tutorials/getting-started/ide-v2-uploading-a-sketch) the firmware using Arduino IDE.
#### Command line (Windows, Linux, macOS) ### Command line (Windows, Linux, macOS)
1. [Install Arduino CLI](https://arduino.github.io/arduino-cli/installation/). 1. [Install Arduino CLI](https://arduino.github.io/arduino-cli/installation/).
@@ -99,6 +71,32 @@ See other available Make commands in [Makefile](../Makefile).
## Before first flight ## Before first flight
### Choose the IMU model
In case if using different IMU model than MPU9250, change `imu` variable declaration in the `imu.ino`:
```cpp
ICM20948 imu(SPI); // For ICM-20948 via SPI
// or
MPU6050 imu(Wire); // For MPU-6050 via I2C
```
If using non-default SPI pins, pass SCK, MISO, and MOSI pin numbers to `SPI.begin` call and SS pin to `imu` constructor like that:
```cpp
ICM20948(SPI, <SS>);
// ...
SPI.begin(<SCK>, <MISO>, <MOSI>);
imu.begin();
```
If using non-default I2C pins, pass SDA and SCL pin numbers to `Wire.setPins` call like that:
```cpp
Wire.begin(<SDA>, <SCL>);
imu.begin();
```
### Connect using QGroundControl ### Connect using QGroundControl
QGroundControl is a ground control station software that can be used to monitor and control the drone. QGroundControl is a ground control station software that can be used to monitor and control the drone.
@@ -139,17 +137,6 @@ The drone is configured using parameters. To access and modify them, go to the Q
You can also work with parameters using `p` command in the console. Parameter names are case-insensitive. You can also work with parameters using `p` command in the console. Parameter names are case-insensitive.
### Configure the IMU
1. Configure the following parameters for the IMU:
* `IMU_MODEL` — IMU model (1 for MPU-9250/MPU-6500, 2 for ICM-20948, 3 for MPU-6050, 4 for ICM-40609-D).
* `IMU_BUS` — communication bus (0 for SPI, 1 for I²C).
* `IMU_PIN_SCK`, `IMU_PIN_MISO`, `IMU_PIN_MOSI`, `IMU_PIN_CS` — SPI pin numbers.
* `IMU_PIN_SCL`, `IMU_PIN_SDA` — I²C pin numbers.
* `IMU_PIN_INT` — IMU data ready pin number (-1 if not used).
2. Reboot the drone.
3. Check the IMU is working using `imu` command in the console (should print `status: OK`).
### Define IMU orientation ### Define IMU orientation
The IMU orientation (relative to the drone's axes) is defined using the parameters: `IMU_ROT_ROLL`, `IMU_ROT_PITCH`, and `IMU_ROT_YAW`. The IMU orientation (relative to the drone's axes) is defined using the parameters: `IMU_ROT_ROLL`, `IMU_ROT_PITCH`, and `IMU_ROT_YAW`.
@@ -178,9 +165,9 @@ Before flight you need to calibrate the accelerometer:
If using non-default motor pins, set the pin numbers using the parameters: `MOTOR_PIN_FL`, `MOTOR_PIN_FR`, `MOTOR_PIN_RL`, `MOTOR_PIN_RR` (front-left, front-right, rear-left, rear-right respectively). If using non-default motor pins, set the pin numbers using the parameters: `MOTOR_PIN_FL`, `MOTOR_PIN_FR`, `MOTOR_PIN_RL`, `MOTOR_PIN_RR` (front-left, front-right, rear-left, rear-right respectively).
#### Brushless motors Certain ESP32 models (such as ESP32-S3 and ESP32-C3) support a lower maximum PWM frequency; on these boards the parameter `MOT_PWM_FREQ` should be set to 38000 Hz.
If using brushless motors with ESCs: If using brushless motors and ESCs:
1. Set the appropriate PWM using the parameters: `MOT_PWM_STOP`, `MOT_PWM_MIN`, and `MOT_PWM_MAX` (1000, 1000, and 2000 is typical). 1. Set the appropriate PWM using the parameters: `MOT_PWM_STOP`, `MOT_PWM_MIN`, and `MOT_PWM_MAX` (1000, 1000, and 2000 is typical).
2. Decrease the PWM frequency using the `MOT_PWM_FREQ` parameter (400 is typical). 2. Decrease the PWM frequency using the `MOT_PWM_FREQ` parameter (400 is typical).
@@ -188,7 +175,7 @@ If using brushless motors with ESCs:
> [!CAUTION] > [!CAUTION]
> **Remove the props when configuring the motors!** If improperly configured, you may not be able to stop them. > **Remove the props when configuring the motors!** If improperly configured, you may not be able to stop them.
### Battery voltage monitoring (optional) ### Battery voltage monitoring
ESP32 ADC can measure only up to 3.3 V, so you need to use a voltage divider to monitor the battery voltage. To enable voltage measurement, set the following parameters: ESP32 ADC can measure only up to 3.3 V, so you need to use a voltage divider to monitor the battery voltage. To enable voltage measurement, set the following parameters:
@@ -385,7 +372,7 @@ To setup ESP-NOW communication:
5. Click *Save*, click *Connect*. QGroundControl should connect to the drone using ESP-NOW and begin showing the telemetry. 5. Click *Save*, click *Connect*. QGroundControl should connect to the drone using ESP-NOW and begin showing the telemetry.
> [!TIP] > [!TIP]
> Make sure Arduino IDE is not running when using ESP-NOW proxy board, as it may block the serial port. > Make sure Arduino IDE is not running when using ESP-NOW proxy board, as it may block it.
## Flight log ## Flight log
-9
View File
@@ -4,15 +4,6 @@ This page contains user-built drones based on the Flix project. Publish your pro
--- ---
Author: [Oleg1405](https://t.me/Oleg1405).<br>
Description: ESP32 Mini, MPU-6500 IMU, boost converter, BT2.0 power connector, 65 mm props, BetaFPV ELRS Lite Receiver, Radiomaster Pocket + Mavlink Joystick (Android) control.
<img src="img/user/oleg1405/1.jpg" height=300>
[Flight video](https://www.youtube.com/shorts/rbXV4sHbpso).
---
Author: Alican Erüst.<br> Author: Alican Erüst.<br>
Description: QX95 mm frame, 55 mm propellers, 3.7 V 25C 1050 mAh LiPo battery, MPU6050 IMU, Logitech F310 gamepad controller, with a total quadcopter weight of 66 g. Description: QX95 mm frame, 55 mm propellers, 3.7 V 25C 1050 mAh LiPo battery, MPU6050 IMU, Logitech F310 gamepad controller, with a total quadcopter weight of 66 g.
+5 -6
View File
@@ -9,7 +9,7 @@
#include "filter.h" #include "filter.h"
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, POS;
extern const int W_AP, W_STA, W_ESPNOW; 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];
@@ -105,7 +105,7 @@ void doCommand(String str, bool echo = false) {
if (success) { if (success) {
print("%s = %g\n", arg0.c_str(), getParameter(arg0.c_str())); print("%s = %g\n", arg0.c_str(), getParameter(arg0.c_str()));
} else { } else {
print("Cannot set parameter: %s\n", arg0.c_str()); print("Parameter not found: %s\n", arg0.c_str());
} }
} else if (command == "preset") { } else if (command == "preset") {
resetParameters(); resetParameters();
@@ -134,6 +134,8 @@ void doCommand(String str, bool echo = false) {
mode = ACRO; mode = ACRO;
} else if (command == "auto") { } else if (command == "auto") {
mode = AUTO; mode = AUTO;
} else if (command == "pos") {
mode = POS;
} else if (command == "rc") { } else if (command == "rc") {
print("channels: "); print("channels: ");
for (int i = 0; i < 16; i++) { for (int i = 0; i < 16; i++) {
@@ -191,10 +193,7 @@ void doCommand(String str, bool echo = false) {
print("Free heap: %d KB\n", ESP.getFreeHeap() / 1024); print("Free heap: %d KB\n", ESP.getFreeHeap() / 1024);
print("PSRAM: %d KB\n", ESP.getPsramSize() / 1024); print("PSRAM: %d KB\n", ESP.getPsramSize() / 1024);
print("Free PSRAM: %d KB\n", ESP.getFreePsram() / 1024); print("Free PSRAM: %d KB\n", ESP.getFreePsram() / 1024);
#ifdef VERSION print("Firmware: " __DATE__ " " __TIME__ "\n");
print("Version: %s\n", STRINGIFY(VERSION));
#endif
print("Build date: " __DATE__ " " __TIME__ "\n");
// Print tasks table // Print tasks table
print("Num Task MinSt Prio Core CPU%%\n"); print("Num Task MinSt Prio Core CPU%%\n");
int taskCount = uxTaskGetNumberOfTasks(); int taskCount = uxTaskGetNumberOfTasks();
-27
View File
@@ -1,27 +0,0 @@
// Copyright (c) 2026 Oleg Kalachev <okalachev@gmail.com>
// Repository: https://github.com/okalachev/flix
// Parameter defaults
#pragma once
void setDefaults() {
// Set defaults here
#if defined(CONFIG_IDF_TARGET_ESP32S3) || defined(CONFIG_IDF_TARGET_ESP32C3)
pwmFrequency = 38000;
#endif
#ifdef FLIX2
imuModel = 4; // ICM-40609-D
imuIntPin = 10;
imuCsPin = 14;
motorPins[MOTOR_REAR_LEFT] = 41;
motorPins[MOTOR_REAR_RIGHT] = 7;
motorPins[MOTOR_FRONT_RIGHT] = 18;
motorPins[MOTOR_FRONT_LEFT] = 38;
voltagePin = 3;
#endif
}
+48 -11
View File
@@ -9,28 +9,54 @@
#include "filter.h" #include "filter.h"
#include "util.h" #include "util.h"
const int RAW = 0, ACRO = 1, STAB = 2, AUTO = 3; // flight modes #define PITCHRATE_P 0.05
#define PITCHRATE_I 0.2
#define PITCHRATE_D 0.001
#define PITCHRATE_I_LIM 0.3
#define ROLLRATE_P PITCHRATE_P
#define ROLLRATE_I PITCHRATE_I
#define ROLLRATE_D PITCHRATE_D
#define ROLLRATE_I_LIM PITCHRATE_I_LIM
#define YAWRATE_P 0.3
#define YAWRATE_I 0.0
#define YAWRATE_D 0.0
#define YAWRATE_I_LIM 0.3
#define ROLL_P 6
#define ROLL_I 0
#define ROLL_D 0
#define PITCH_P ROLL_P
#define PITCH_I ROLL_I
#define PITCH_D ROLL_D
#define YAW_P 3
#define PITCHRATE_MAX radians(360)
#define ROLLRATE_MAX radians(360)
#define YAWRATE_MAX radians(300)
#define TILT_MAX radians(30)
#define RATES_D_LPF_ALPHA 0.2 // cutoff frequency ~ 40 Hz
const int RAW = 0, ACRO = 1, STAB = 2, AUTO = 3, POS = 4;
int mode = STAB; int mode = STAB;
bool armed = false; bool armed = false;
Quaternion attitudeTarget; Quaternion attitudeTarget;
Vector ratesTarget; Vector ratesTarget;
Vector ratesExtra; // feedforward rates Vector ratesExtra; // feedforward rates
Vector torqueTarget; // 0 - no torque, 1 - maximum torque Vector torqueTarget;
float thrustTarget; float thrustTarget;
PID rollRatePID(0.05, 0.2, 0.001, 0.3, 0.2); PID rollRatePID(ROLLRATE_P, ROLLRATE_I, ROLLRATE_D, ROLLRATE_I_LIM, RATES_D_LPF_ALPHA);
PID pitchRatePID(0.05, 0.2, 0.001, 0.3, 0.2); PID pitchRatePID(PITCHRATE_P, PITCHRATE_I, PITCHRATE_D, PITCHRATE_I_LIM, RATES_D_LPF_ALPHA);
PID yawRatePID(0.3, 0, 0, 0.3); PID yawRatePID(YAWRATE_P, YAWRATE_I, YAWRATE_D);
PID rollPID(6); PID rollPID(ROLL_P, ROLL_I, ROLL_D);
PID pitchPID(6); PID pitchPID(PITCH_P, PITCH_I, PITCH_D);
PID yawPID(3); PID yawPID(YAW_P, 0, 0);
Vector maxRate(radians(360), radians(360), radians(360)); Vector maxRate(ROLLRATE_MAX, PITCHRATE_MAX, YAWRATE_MAX);
float tiltMax = radians(30); float tiltMax = TILT_MAX;
int flightModes[] = {STAB, STAB, STAB}; // map for rc mode switch int flightModes[] = {STAB, STAB, STAB}; // map for rc mode switch
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 float controlRoll, controlPitch, controlThrottle, controlYaw, controlMode; extern float controlRoll, controlPitch, controlThrottle, controlYaw, controlMode;
extern Vector position, positionTarget;
void control() { void control() {
interpretControls(); interpretControls();
@@ -54,7 +80,18 @@ void interpretControls() {
thrustTarget = controlThrottle; thrustTarget = controlThrottle;
if (mode == STAB) { if (mode == POS) {
if (controlRoll != 0 || controlPitch != 0) {
positionTarget.x = NAN;
positionTarget.y = NAN;
} else if (invalid(positionTarget.x) || invalid(positionTarget.y)) {
// reset position target
positionTarget.x = position.x;
positionTarget.y = position.y;
}
}
if (mode == STAB || (mode == POS && invalid(positionTarget.x))) {
float yawTarget = attitudeTarget.getYaw(); float yawTarget = attitudeTarget.getYaw();
if (!armed || invalid(yawTarget) || controlYaw != 0) yawTarget = attitude.getYaw(); // reset yaw target if (!armed || invalid(yawTarget) || controlYaw != 0) yawTarget = attitude.getYaw(); // reset yaw target
attitudeTarget = Quaternion::fromEuler(Vector(controlRoll * tiltMax, controlPitch * tiltMax, yawTarget)); attitudeTarget = Quaternion::fromEuler(Vector(controlRoll * tiltMax, controlPitch * tiltMax, yawTarget));
+2 -1
View File
@@ -15,7 +15,7 @@ bool landed;
float accWeight = 0.003; float accWeight = 0.003;
float levelWeight = 0.0002; float levelWeight = 0.0002;
LowPassFilter<Vector> ratesFilter(0.2); // cutoff frequency ~ 40 Hz LowPassFilter<Vector> ratesFilter(0.2); // cutoff frequency ~ 40 Hz
NotchFilter<Vector> ratesNotch(382, 40); NotchFilter<Vector> ratesNotch(382, 0);
void setupEstimate() { void setupEstimate() {
print("Setup estimation\n"); print("Setup estimation\n");
@@ -26,6 +26,7 @@ void estimate() {
applyGyro(); applyGyro();
applyAcc(); applyAcc();
applyLevel(); applyLevel();
estimatePosition();
} }
void applyGyro() { void applyGyro() {
+20 -39
View File
@@ -4,17 +4,12 @@
// Work with the IMU sensor // Work with the IMU sensor
#include <SPI.h> #include <SPI.h>
#include <Wire.h>
#include <FlixPeriph.h> #include <FlixPeriph.h>
#include "vector.h" #include "vector.h"
#include "filter.h" #include "filter.h"
#include "util.h" #include "util.h"
IMU *imu; MPU9250 imu(SPI);
int imuModel = -1; // 1 - MPU9250, 2 - ICM20948, 3 - MPU6050, 4 - ICM40609D
int imuBus = 0; // 0 - SPI, 1 - I2C
int imuSckPin = SCK, imuMisoPin = MISO, imuMosiPin = MOSI, imuCsPin = SS, imuIntPin = -1;
int imuSdaPin = SDA, imuSclPin = SCL;
Vector imuRotation(0, 0, PI / 2); // imu orientation as Euler angles Vector imuRotation(0, 0, PI / 2); // imu orientation as Euler angles
Vector gyro; // gyroscope output, rad/s Vector gyro; // gyroscope output, rad/s
@@ -28,35 +23,22 @@ LowPassFilter<Vector> gyroBiasFilter(0.001);
void setupIMU() { void setupIMU() {
print("Setup IMU\n"); print("Setup IMU\n");
free(imu); imu.begin();
if (imuModel == 3) imuBus = 1; // MPU6050 is I2C only
if (imuBus == 0) {
// SPI connection
SPI.begin(imuSckPin, imuMisoPin, imuMosiPin);
imu = IMU::create(imuModel, SPI, imuCsPin, imuIntPin);
} else {
// I2C connection
Wire.setPins(imuSdaPin, imuSclPin);
imu = IMU::create(imuModel, Wire, imuIntPin);
}
imu->begin();
configureIMU(); configureIMU();
} }
void configureIMU() { void configureIMU() {
imu->setAccelRange(IMU::ACCEL_RANGE_4G); imu.setAccelRange(imu.ACCEL_RANGE_4G);
imu->setGyroRange(IMU::GYRO_RANGE_2000DPS); imu.setGyroRange(imu.GYRO_RANGE_2000DPS);
imu->setDLPF(IMU::DLPF_MAX); imu.setDLPF(imu.DLPF_MAX);
imu->setRate(IMU::RATE_1KHZ_APPROX); imu.setRate(imu.RATE_1KHZ_APPROX);
imu->setupInterrupt(); imu.setupInterrupt();
} }
void readIMU() { void readIMU() {
imu->waitForData(); imu.waitForData();
imu->getGyro(gyro.x, gyro.y, gyro.z); imu.getGyro(gyro.x, gyro.y, gyro.z);
imu->getAccel(acc.x, acc.y, acc.z); imu.getAccel(acc.x, acc.y, acc.z);
calibrateGyroOnce(); calibrateGyroOnce();
// Apply scale and bias // Apply scale and bias
@@ -78,7 +60,7 @@ void calibrateGyroOnce() {
void calibrateAccel() { void calibrateAccel() {
print("Calibrating accelerometer\n"); print("Calibrating accelerometer\n");
imu->setAccelRange(IMU::ACCEL_RANGE_2G); // the most sensitive mode imu.setAccelRange(imu.ACCEL_RANGE_2G); // the most sensitive mode
print("1/6 Place level [8 sec]\n"); print("1/6 Place level [8 sec]\n");
pause(8); pause(8);
@@ -112,9 +94,9 @@ void calibrateAccelOnce() {
// Compute the average of the accelerometer readings // Compute the average of the accelerometer readings
acc = Vector(0, 0, 0); acc = Vector(0, 0, 0);
for (int i = 0; i < samples; i++) { for (int i = 0; i < samples; i++) {
imu->waitForData(); imu.waitForData();
Vector sample; Vector sample;
imu->getAccel(sample.x, sample.y, sample.z); imu.getAccel(sample.x, sample.y, sample.z);
acc = acc + sample; acc = acc + sample;
} }
acc = acc / samples; acc = acc / samples;
@@ -139,18 +121,17 @@ void printIMUCalibration() {
} }
void printIMUInfo() { void printIMUInfo() {
imu->status() ? print("status: ERROR %d\n", imu->status()) : print("status: OK\n"); imu.status() ? print("status: ERROR %d\n", imu.status()) : print("status: OK\n");
print("model: %s\n", imu->getModel()); print("model: %s\n", imu.getModel());
print("who am I: 0x%02X\n", imu->whoAmI()); print("who am I: 0x%02X\n", imu.whoAmI());
print("rate: %.0f\n", loopRate); print("rate: %.0f\n", loopRate);
print("interrupt mode: %s\n", imuIntPin != -1 ? "pin" : "timer"); print("temperature: %.1f °C\n", imu.getTemp());
print("temperature: %.1f °C\n", imu->getTemp());
print("gyro: %f %f %f\n", gyro.x, gyro.y, gyro.z); print("gyro: %f %f %f\n", gyro.x, gyro.y, gyro.z);
print("acc: %f %f %f\n", acc.x, acc.y, acc.z); print("acc: %f %f %f\n", acc.x, acc.y, acc.z);
imu->waitForData(); imu.waitForData();
Vector rawGyro, rawAcc; Vector rawGyro, rawAcc;
imu->getGyro(rawGyro.x, rawGyro.y, rawGyro.z); imu.getGyro(rawGyro.x, rawGyro.y, rawGyro.z);
imu->getAccel(rawAcc.x, rawAcc.y, rawAcc.z); imu.getAccel(rawAcc.x, rawAcc.y, rawAcc.z);
print("raw gyro: %f %f %f\n", rawGyro.x, rawGyro.y, rawGyro.z); print("raw gyro: %f %f %f\n", rawGyro.x, rawGyro.y, rawGyro.z);
print("raw acc: %f %f %f\n", rawAcc.x, rawAcc.y, rawAcc.z); print("raw acc: %f %f %f\n", rawAcc.x, rawAcc.y, rawAcc.z);
} }
+7 -2
View File
@@ -6,7 +6,7 @@
#include "vector.h" #include "vector.h"
#include "util.h" #include "util.h"
int logMemory = 0; // 0 - RAM, 1 - PSRAM, -1 - disabled int logMemory = -1; // 0 - RAM, 1 - PSRAM, -1 - disabled
float logUsage = 0.5; // fraction of free memory to use for log float logUsage = 0.5; // fraction of free memory to use for log
struct LogValue { struct LogValue {
@@ -68,6 +68,11 @@ LogTopic logTopics[] = {
{"attitude.pitch", []() { return attitude.getPitch(); }}, {"attitude.pitch", []() { return attitude.getPitch(); }},
{"attitude.yaw", []() { return attitude.getYaw(); }}), {"attitude.yaw", []() { return attitude.getYaw(); }}),
LogTopic(50,
{"position.x", &position.x},
{"position.y", &position.y},
{"position.z", &position.z}),
// rc // rc
LogTopic(10, LogTopic(10,
{"controlTime", &controlTime, false}, // trigger value {"controlTime", &controlTime, false}, // trigger value
@@ -100,7 +105,7 @@ LogTopic logTopics[] = {
LogTopic(5, LogTopic(5,
{"voltage", &voltage}, {"voltage", &voltage},
{"temp", &temperatureRead}, {"temp", &temperatureRead},
{"imuTemp", []() { return imu->getTemp(); }}), {"imuTemp", []() { return imu.getTemp(); }}),
}; };
void *logBuffer; // buffer for log data void *logBuffer; // buffer for log data
+17 -10
View File
@@ -18,7 +18,7 @@ Rate telemetryMotors(10);
Rate telemetryIMU(15); Rate telemetryIMU(15);
Rate telemetryTopic(10); Rate telemetryTopic(10);
float mavlinkTime = NAN; // time of last received message bool mavlinkConnected = false;
String mavlinkPrintBuffer; String mavlinkPrintBuffer;
void processMavlink() { void processMavlink() {
@@ -41,7 +41,7 @@ void sendMavlink() {
sendMessage(&msg); sendMessage(&msg);
} }
if (!valid(mavlinkTime)) return; // send only heartbeat until connected if (!mavlinkConnected) return; // send only heartbeat until connected
if (telemetrySlow) { if (telemetrySlow) {
mavlink_msg_extended_sys_state_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg, mavlink_msg_extended_sys_state_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg,
@@ -128,7 +128,7 @@ void receiveMavlink() {
mavlink_status_t status; mavlink_status_t status;
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
if (mavlink_parse_char(MAVLINK_COMM_0, buf[i], &msg, &status)) { if (mavlink_parse_char(MAVLINK_COMM_0, buf[i], &msg, &status)) {
mavlinkTime = t; mavlinkConnected = true;
handleMavlink(&msg); handleMavlink(&msg);
} }
} }
@@ -196,6 +196,13 @@ void handleMavlink(const void *_msg) {
sendMessage(&msg); sendMessage(&msg);
} }
if (msg.msgid == MAVLINK_MSG_ID_VISION_POSITION_ESTIMATE) {
mavlink_vision_position_estimate_t m;
mavlink_msg_vision_position_estimate_decode(&msg, &m);
Vector pos(m.x, m.y, m.z);
correctPosition(pos);
}
if (msg.msgid == MAVLINK_MSG_ID_MISSION_REQUEST_LIST) { // handle to make qgc happy if (msg.msgid == MAVLINK_MSG_ID_MISSION_REQUEST_LIST) { // handle to make qgc happy
mavlink_mission_request_list_t m; mavlink_mission_request_list_t m;
mavlink_msg_mission_request_list_decode(&msg, &m); mavlink_msg_mission_request_list_decode(&msg, &m);
@@ -227,17 +234,17 @@ void handleMavlink(const void *_msg) {
// Attitude control // Attitude control
attitudeTarget.w = m.q[0]; attitudeTarget.w = m.q[0];
attitudeTarget.x = m.q[1]; attitudeTarget.x = m.q[1];
attitudeTarget.y = -m.q[2]; // convert to flu attitudeTarget.y = -m.q[2];
attitudeTarget.z = -m.q[3]; attitudeTarget.z = -m.q[3];
ratesExtra.x = m.body_roll_rate; ratesExtra.x = m.type_mask & ATTITUDE_TARGET_TYPEMASK_BODY_ROLL_RATE_IGNORE ? 0 : m.body_roll_rate;
ratesExtra.y = -m.body_pitch_rate; ratesExtra.y = m.type_mask & ATTITUDE_TARGET_TYPEMASK_BODY_PITCH_RATE_IGNORE ? 0 : -m.body_pitch_rate; // convert to flu
ratesExtra.z = -m.body_yaw_rate; ratesExtra.z = m.type_mask & ATTITUDE_TARGET_TYPEMASK_BODY_YAW_RATE_IGNORE ? 0 : -m.body_yaw_rate;
} else { } else {
// Rates control // Rates control
attitudeTarget.invalidate(); attitudeTarget.invalidate();
ratesTarget.x = m.body_roll_rate; ratesTarget.x = m.type_mask & ATTITUDE_TARGET_TYPEMASK_BODY_ROLL_RATE_IGNORE ? ratesTarget.x : m.body_roll_rate;
ratesTarget.y = -m.body_pitch_rate; ratesTarget.y = m.type_mask & ATTITUDE_TARGET_TYPEMASK_BODY_PITCH_RATE_IGNORE ? ratesTarget.y : -m.body_pitch_rate;
ratesTarget.z = -m.body_yaw_rate; ratesTarget.z = m.type_mask & ATTITUDE_TARGET_TYPEMASK_BODY_YAW_RATE_IGNORE ? ratesTarget.z : -m.body_yaw_rate;
} }
thrustTarget = valid(m.thrust) ? m.thrust : thrustTarget; thrustTarget = valid(m.thrust) ? m.thrust : thrustTarget;
+1 -1
View File
@@ -7,7 +7,7 @@
float motors[4]; // normalized motor thrusts in range [0..1] float motors[4]; // normalized motor thrusts in range [0..1]
int motorPins[4] = {12, 13, 14, 15}; // default pin numbers int motorPins[4] = {-1, -1, -1, -1}; // default pin numbers
int pwmFrequency = 78000; int pwmFrequency = 78000;
int pwmResolution = 10; int pwmResolution = 10;
int pwmStop = 0; int pwmStop = 0;
+31 -32
View File
@@ -5,26 +5,30 @@
#include <Preferences.h> #include <Preferences.h>
#include "util.h" #include "util.h"
#include "pid.h"
extern int channelZero[16], channelMax[16]; extern int channelZero[16], channelMax[16];
extern int rollChannel, pitchChannel, throttleChannel, yawChannel, armedChannel, modeChannel; extern int rollChannel, pitchChannel, throttleChannel, yawChannel, armedChannel, modeChannel;
extern int rcRxPin, voltagePin; extern int rcRxPin, voltagePin;
extern int wifiMode, wifiLongRange, wifiBroadcast, udpLocalPort, udpRemotePort, espnowChannel; extern int wifiMode, wifiLongRange, udpLocalPort, udpRemotePort, espnowChannel;
extern float rcLossTimeout, descendTime, disarmTilt; extern float rcLossTimeout, descendTime, disarmTilt;
extern float voltageScale; extern float voltageScale;
extern LowPassFilter<float> voltageFilter; extern LowPassFilter<float> voltageFilter;
extern PID posX_PID, posY_PID;
#include "config.h"
Preferences storage; Preferences storage;
struct Parameter { struct Parameter {
const char *name; // max length is 15 const char *name; // max length is 15
Value value; // pointer to the variable bool integer;
float initial; // default value union { float *f; int *i; }; // pointer to the variable
float inital; // default value
float cache; // what's stored in flash float cache; // what's stored in flash
void (*callback)(); // called after parameter change void (*callback)(); // called after parameter change
Parameter(const char *name, Value value, void (*callback)() = nullptr) : name(name), value(value), callback(callback) {}; Parameter(const char *name, float *variable, void (*callback)() = nullptr) : name(name), integer(false), f(variable), callback(callback) {};
Parameter(const char *name, int *variable, void (*callback)() = nullptr) : name(name), integer(true), i(variable), callback(callback) {};
float getValue() const { return integer ? *i : *f; };
void setValue(const float value) { if (integer) *i = value; else *f = value; };
}; };
Parameter parameters[] = { Parameter parameters[] = {
@@ -42,7 +46,6 @@ Parameter parameters[] = {
{"CTL_Y_RATE_P", &yawRatePID.p}, {"CTL_Y_RATE_P", &yawRatePID.p},
{"CTL_Y_RATE_I", &yawRatePID.i}, {"CTL_Y_RATE_I", &yawRatePID.i},
{"CTL_Y_RATE_D", &yawRatePID.d}, {"CTL_Y_RATE_D", &yawRatePID.d},
{"CTL_Y_RATE_WU", &yawRatePID.windup},
{"CTL_Y_RATE_D_A", &yawRatePID.lpf.alpha}, {"CTL_Y_RATE_D_A", &yawRatePID.lpf.alpha},
{"CTL_R_P", &rollPID.p}, {"CTL_R_P", &rollPID.p},
{"CTL_R_I", &rollPID.i}, {"CTL_R_I", &rollPID.i},
@@ -58,16 +61,13 @@ Parameter parameters[] = {
{"CTL_FLT_MODE_0", &flightModes[0]}, {"CTL_FLT_MODE_0", &flightModes[0]},
{"CTL_FLT_MODE_1", &flightModes[1]}, {"CTL_FLT_MODE_1", &flightModes[1]},
{"CTL_FLT_MODE_2", &flightModes[2]}, {"CTL_FLT_MODE_2", &flightModes[2]},
// position
{"POS_XY_P", &posX_PID.p, setupPosition},
{"POS_XY_I", &posX_PID.i, setupPosition},
{"POS_XY_D", &posX_PID.d, setupPosition},
{"POS_XY_WU", &posX_PID.windup, setupPosition},
{"POS_XY_D_A", &posX_PID.lpf.alpha, setupPosition},
// imu // imu
{"IMU_MODEL", &imuModel},
{"IMU_BUS", &imuBus},
{"IMU_PIN_SCK", &imuSckPin},
{"IMU_PIN_MISO", &imuMisoPin},
{"IMU_PIN_MOSI", &imuMosiPin},
{"IMU_PIN_CS", &imuCsPin},
{"IMU_PIN_SDA", &imuSdaPin},
{"IMU_PIN_SCL", &imuSclPin},
{"IMU_PIN_INT", &imuIntPin},
{"IMU_ROT_ROLL", &imuRotation.x}, {"IMU_ROT_ROLL", &imuRotation.x},
{"IMU_ROT_PITCH", &imuRotation.y}, {"IMU_ROT_PITCH", &imuRotation.y},
{"IMU_ROT_YAW", &imuRotation.z}, {"IMU_ROT_YAW", &imuRotation.z},
@@ -122,7 +122,6 @@ Parameter parameters[] = {
{"WIFI_PORT_LOC", &udpLocalPort}, {"WIFI_PORT_LOC", &udpLocalPort},
{"WIFI_PORT_REM", &udpRemotePort}, {"WIFI_PORT_REM", &udpRemotePort},
{"WIFI_LONG_RANGE", &wifiLongRange}, {"WIFI_LONG_RANGE", &wifiLongRange},
{"WIFI_BROADCAST", &wifiBroadcast},
// espnow // espnow
{"ESPNOW_CHANNEL", &espnowChannel}, {"ESPNOW_CHANNEL", &espnowChannel},
// mavlink // mavlink
@@ -155,20 +154,19 @@ Parameter parameters[] = {
// safety // safety
{"SF_RC_LOSS_TIME", &rcLossTimeout}, {"SF_RC_LOSS_TIME", &rcLossTimeout},
{"SF_DESCEND_TIME", &descendTime}, {"SF_DESCEND_TIME", &descendTime},
{"SF_DISARM_TILT", &disarmTilt}, {"SF_DISARM_TILT", &disarmTilt}
}; };
void setupParameters() { void setupParameters() {
print("Setup parameters\n"); print("Setup parameters\n");
setDefaults();
storage.begin("flix"); storage.begin("flix");
// Read parameters from storage // Read parameters from storage
for (auto &parameter : parameters) { for (auto &parameter : parameters) {
parameter.initial = parameter.value.get(); parameter.inital = parameter.getValue();
if (storage.isKey(parameter.name)) { if (storage.isKey(parameter.name)) {
parameter.value.set(storage.getFloat(parameter.name)); parameter.setValue(storage.getFloat(parameter.name));
} }
parameter.cache = parameter.value.get(); parameter.cache = parameter.getValue();
} }
} }
@@ -183,13 +181,13 @@ const char *getParameterName(int index) {
float getParameter(int index) { float getParameter(int index) {
if (index < 0 || index >= parametersCount()) return NAN; if (index < 0 || index >= parametersCount()) return NAN;
return parameters[index].value.get(); return parameters[index].getValue();
} }
float getParameter(const char *name) { float getParameter(const char *name) {
for (auto &parameter : parameters) { for (auto &parameter : parameters) {
if (strcasecmp(parameter.name, name) == 0) { if (strcasecmp(parameter.name, name) == 0) {
return parameter.value.get(); return parameter.getValue();
} }
} }
return NAN; return NAN;
@@ -198,9 +196,10 @@ float getParameter(const char *name) {
bool setParameter(const char *name, const float value) { bool setParameter(const char *name, const float value) {
for (auto &parameter : parameters) { for (auto &parameter : parameters) {
if (strcasecmp(parameter.name, name) == 0) { if (strcasecmp(parameter.name, name) == 0) {
bool success = parameter.value.set(value); if (parameter.integer && !isfinite(value)) return false; // can't set integer to NaN or Inf
parameter.setValue(value);
if (parameter.callback) parameter.callback(); if (parameter.callback) parameter.callback();
return success; return true;
} }
} }
return false; return false;
@@ -212,10 +211,10 @@ void syncParameters() {
if (motorsActive()) return; // don't use flash while flying, it may cause a delay if (motorsActive()) return; // don't use flash while flying, it may cause a delay
for (auto &parameter : parameters) { for (auto &parameter : parameters) {
if (floatEquals(parameter.value.get(), parameter.cache)) continue; // no change if (floatEquals(parameter.getValue(), parameter.cache)) continue; // no change
storage.putFloat(parameter.name, parameter.value.get()); storage.putFloat(parameter.name, parameter.getValue());
parameter.cache = parameter.value.get(); // update cache parameter.cache = parameter.getValue(); // update cache
} }
} }
@@ -224,10 +223,10 @@ void printParameters(const char *filter) {
for (auto &parameter : parameters) { for (auto &parameter : parameters) {
if (strncasecmp(parameter.name, filter, strlen(filter))) continue; if (strncasecmp(parameter.name, filter, strlen(filter))) continue;
if (floatEquals(parameter.value.get(), parameter.initial)) { // parameter changed if (floatEquals(parameter.getValue(), parameter.inital)) { // parameter changed
print("%-15s %-13g\n", parameter.name, parameter.value.get()); print("%-15s %-13g\n", parameter.name, parameter.getValue());
} else { } else {
print("%-15s %-13g [%g]\n", parameter.name, parameter.value.get(), parameter.initial); print("%-15s %-13g [%g]\n", parameter.name, parameter.getValue(), parameter.inital);
} }
} }
} }
+1 -1
View File
@@ -18,7 +18,7 @@ public:
LowPassFilter<float> lpf; // low pass filter for derivative term LowPassFilter<float> lpf; // low pass filter for derivative term
PID(float p, float i = 0, float d = 0, float windup = INFINITY, float dAlpha = 1, float dtMax = 0.1) : PID(float p, float i, float d, float windup = 0, float dAlpha = 1, float dtMax = 0.1) :
p(p), i(i), d(d), windup(windup), lpf(dAlpha), dtMax(dtMax) {} p(p), i(i), d(d), windup(windup), lpf(dAlpha), dtMax(dtMax) {}
float update(float error) { float update(float error) {
+95
View File
@@ -0,0 +1,95 @@
#include "vector.h"
#include "quaternion.h"
#include "pid.h"
#include "util.h"
float mass = 0.065;
float motorThrust = 0.031 * ONE_G;
float positionWeight = 0.9;
float velocityWeight = 0.9;
PID velX_PID(1.0, 0.0, 0.02);
PID velY_PID(1.0, 0.0, 0.02);
PID velZ_PID(1.0, 0.0, 0.02);
// PID posX_PID(1.0, 0.0, 0.0);
// PID posY_PID(1.0, 0.0, 0.0);
// PID posZ_PID(1.0, 0.0, 0.0);
PID posX_PID(0.1, 0.0, 0.0);
PID posY_PID(0.1, 0.0, 0.0);
PID posZ_PID(0.1, 0.0, 0.0);
Vector acceleration;
Vector velocity;
Vector position;
Vector velocityTarget(NAN, NAN, NAN);
Vector positionTarget(NAN, NAN, NAN);
extern Quaternion attitudeTarget;
extern float thrustTarget;
void setupPosition() {
// TODO: posY_PID = posX_PID;
// posX_PID.copy(posY_PID);
posY_PID.p = posX_PID.p;
posY_PID.i = posX_PID.i;
posY_PID.d = posX_PID.d;
}
void estimatePosition() {
acceleration = Quaternion::rotateVector(acceleration, attitude) + Vector(0, 0, -ONE_G);
velocity += acceleration * dt;
position += velocity * dt;
}
void correctPosition(Vector& pos) {
// position += (pos - position) * positionWeight;
position = pos;
// positionUpdated = true;
if (invalid(positionTarget.x) || invalid(positionTarget.y)) return;
attitudeTarget.setPitch(posX_PID.update(positionTarget.x - position.x));
attitudeTarget.setRoll(posY_PID.update(positionTarget.y - position.y));
}
void correctVelocity(Vector& vel) {
velocity += (vel - velocity) * velocityWeight;
}
void controlVelocity() {
Vector accelerationTarget(
velX_PID.update(velocityTarget.x - velocity.x),
velY_PID.update(velocityTarget.y - velocity.y),
velZ_PID.update(velocityTarget.z - velocity.z)
);
Vector thrustVector = (accelerationTarget + Vector(0, 0, ONE_G)) * mass;
const Vector up(0, 0, 1);
attitudeTarget = Quaternion::fromBetweenVectors(up, thrustVector);
float maxThrust = motorThrust * 4; // 4 motors
thrustTarget = thrustVector.norm() / maxThrust;
}
void controlPosition() {
if (positionTarget.invalid()) return;
velocityTarget = Vector(
posX_PID.update(positionTarget.x - position.x),
posY_PID.update(positionTarget.y - position.y),
posZ_PID.update(positionTarget.z - position.z)
);
controlVelocity();
}
void controlPositionSimple() {
if (positionTarget.invalid()) return;
// straight attitude target control
attitudeTarget.setPitch(posX_PID.update(positionTarget.x - position.x));
attitudeTarget.setRoll(posY_PID.update(positionTarget.y - position.y));
}
+5 -8
View File
@@ -11,8 +11,6 @@
const float ONE_G = 9.80665; const float ONE_G = 9.80665;
extern float t; extern float t;
#define STRINGIFY(x) __STRINGIFY(x)
float mapf(float x, float in_min, float in_max, float out_min, float out_max) { float mapf(float x, float in_min, float in_max, float out_min, float out_max) {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
} }
@@ -99,22 +97,21 @@ struct Value {
} }
}; };
bool set(float value) const { void set(float value) const {
switch (type) { switch (type) {
case FLOAT: *_float = value; break; case FLOAT: *_float = value; break;
case INT: if (!isfinite(value)) return false; *_int = value; break; case INT: *_int = value; break;
case BOOL: *_bool = (value != 0); break; case BOOL: *_bool = (value != 0); break;
default: return false; default: break;
} }
return true; };
}
}; };
// Rate limiter // Rate limiter
class Rate { class Rate {
public: public:
float rate; float rate;
float last = -INFINITY; float last = 0;
Rate(float rate) : rate(rate) {} Rate(float rate) : rate(rate) {}
operator bool() { operator bool() {
+3 -5
View File
@@ -17,7 +17,6 @@ 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 wifiLongRange = 0;
int wifiBroadcast = 0; // 0 - broadcast until connected, 1 - always broadcast
int udpLocalPort = 14550; int udpLocalPort = 14550;
int udpRemotePort = 14550; int udpRemotePort = 14550;
IPAddress udpRemoteIP = "255.255.255.255"; IPAddress udpRemoteIP = "255.255.255.255";
@@ -65,8 +64,7 @@ void sendWiFi(const uint8_t *buf, int len) {
if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return; if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return;
bool broadcast = wifiBroadcast || !(t - mavlinkTime < 5); // broadcast if lost connection udp.beginPacket(udpRemoteIP, udpRemotePort);
udp.beginPacket(broadcast ? IPAddress(255, 255, 255, 255) : udpRemoteIP, udpRemotePort);
udp.write(buf, len); udp.write(buf, len);
udp.endPacket(); udp.endPacket();
} }
@@ -91,7 +89,7 @@ void printWiFiInfo() {
print("MAC: %s\n", WiFi.softAPmacAddress().c_str()); print("MAC: %s\n", WiFi.softAPmacAddress().c_str());
print("Peer MAC: %s\n", MacAddress(espnow.addr()).toString().c_str()); print("Peer MAC: %s\n", MacAddress(espnow.addr()).toString().c_str());
print("Encrypted: %d\n", espnow.isEncrypted()); print("Encrypted: %d\n", espnow.isEncrypted());
print("Channel: %d\n", espnow.getChannel()); print("Channel: %d\n", WiFi.channel());
print("Lost packets: %d\n", espnow.lost); print("Lost packets: %d\n", espnow.lost);
} else if (WiFi.getMode() == WIFI_MODE_AP) { } else if (WiFi.getMode() == WIFI_MODE_AP) {
print("Mode: Access Point (AP)\n"); print("Mode: Access Point (AP)\n");
@@ -115,7 +113,7 @@ void printWiFiInfo() {
} else { } else {
print("Mode: Disabled\n"); print("Mode: Disabled\n");
} }
print("MAVLink connected: %d\n", valid(mavlinkTime)); print("MAVLink connected: %d\n", mavlinkConnected);
} }
void configWiFi(int mode, const char *first, const char *second) { void configWiFi(int mode, const char *first, const char *second) {
+1 -4
View File
@@ -21,9 +21,6 @@ extern float motors[4];
Vector gyro, acc, imuRotation; Vector gyro, acc, imuRotation;
Vector accBias, gyroBias, accScale(1, 1, 1); Vector accBias, gyroBias, accScale(1, 1, 1);
LowPassFilter<Vector> gyroBiasFilter(0); LowPassFilter<Vector> gyroBiasFilter(0);
int imuModel = 1, imuBus = 0;
int imuSckPin = 0, imuMisoPin = 0, imuMosiPin = 0, imuCsPin = -1, imuIntPin = -1;
int imuSdaPin = 0, imuSclPin = 0;
// declarations // declarations
void step(); void step();
@@ -96,4 +93,4 @@ void setWiFiMode(const String& mode) { print("Skip WiFi mode set\n"); };
class IMU { class IMU {
public: public:
float getTemp() { return 0; } float getTemp() { return 0; }
} *imu; } imu;
-1
View File
@@ -14,7 +14,6 @@
// Mocks // Mocks
int wifiMode = 1; int wifiMode = 1;
int wifiLongRange = 0; int wifiLongRange = 0;
int wifiBroadcast = 0;
int espnowChannel = 6; int espnowChannel = 6;
const int W_DISABLED = 0, W_AP = 1, W_STA = 2, W_ESPNOW = 3; const int W_DISABLED = 0, W_AP = 1, W_STA = 2, W_ESPNOW = 3;
+30 -10
View File
@@ -11,18 +11,22 @@
#include <Preferences.h> #include <Preferences.h>
#include "../../flix/util.h" #include "../../flix/util.h"
const int CHANNEL = 6; const bool DISABLE_SWARM = true;
const int CHANNEL = -1; // -1 means auto search
char key[ESP_NOW_KEY_LEN + 1] = {0}; // with trailing null char key[ESP_NOW_KEY_LEN + 1] = {0}; // with trailing null
Preferences storage; Preferences storage;
std::vector<ESPNOWSerial *> peers; std::vector<ESPNOWSerial *> peers;
bool stop = false;
void onNewPeer(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) { 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 if (len != 4 || memcmp(data, "flix", 4) != 0) return; // check if discovery message
if (stop) return;
Serial.printf("New peer: " MACSTR "\n", MAC2STR(info->src_addr)); Serial.printf("New peer: " MACSTR "\n", MAC2STR(info->src_addr));
ESPNOWSerial *link = new ESPNOWSerial(info->src_addr, CHANNEL, WIFI_IF_AP); ESPNOWSerial *link = new ESPNOWSerial(info->src_addr, WiFi.channel(), WIFI_IF_STA);
link->begin(); link->begin();
link->setKey((const uint8_t *)key); link->setKey((const uint8_t *)key);
peers.push_back(link); peers.push_back(link);
@@ -30,9 +34,8 @@ void onNewPeer(const esp_now_recv_info_t *info, const uint8_t *data, int len, vo
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
WiFi.mode(WIFI_AP); WiFi.mode(WIFI_STA);
WiFi.setSleep(false); WiFi.setSleep(false);
WiFi.setChannel(CHANNEL);
ESP_NOW.onNewPeer(onNewPeer, NULL); ESP_NOW.onNewPeer(onNewPeer, NULL);
ESP_NOW.begin(); ESP_NOW.begin();
@@ -43,12 +46,6 @@ void setup() {
storage.putString("key", key); storage.putString("key", key);
} }
strcpy(key, storage.getString("key").c_str()); 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() { void generateRandomKey() {
@@ -61,6 +58,20 @@ void generateRandomKey() {
void loop() { void loop() {
uint8_t buf[5000]; uint8_t buf[5000];
static int channelIndex = 0;
static const int channels[] = {6, 11, 1, 2, 6, 11, 3, 4, 5, 6, 1, 11, 8, 6, 9, 10, 11, 6, 1, 12, 13}; // 6, 1 and 11 are most common
static unsigned long last = 0;
if (!stop && millis() - last > 500) {
// Change search channel
last = millis();
channelIndex = (channelIndex + 1) % (sizeof(channels) / sizeof(channels[0]));
int channel = CHANNEL < 0 ? channels[channelIndex] : CHANNEL;
Serial.printf("Run on Flix: espnow %s %s\n", WiFi.STA.macAddress().c_str(), key);
Serial.printf("Searching channel %d\n", channel);
WiFi.setChannel(channel);
}
// Send from Serial to ESP-NOW // Send from Serial to ESP-NOW
while (Serial.available() > 0) { while (Serial.available() > 0) {
int b = Serial.read(); int b = Serial.read();
@@ -81,6 +92,15 @@ void loop() {
// Send from ESP-NOW to Serial // Send from ESP-NOW to Serial
for (ESPNOWSerial *link : peers) { for (ESPNOWSerial *link : peers) {
int len = link->read(buf, sizeof(buf)); int len = link->read(buf, sizeof(buf));
if (!stop) {
for (int i = 0; i < len; i++) {
if (buf[i] == MAVLINK_STX) {
// Got MAVLink message, stop discovery
Serial.printf("Received MAVLink from " MACSTR "\n", MAC2STR(link->addr()));
if (DISABLE_SWARM) stop = true;
}
}
}
if (len > 0) { if (len > 0) {
Serial.write(buf, len); Serial.write(buf, len);
} }
+9 -10
View File
@@ -243,7 +243,7 @@ class Flix:
time.sleep(1) time.sleep(1)
@staticmethod @staticmethod
def _mavlink_to_flu(v: Sequence[float]) -> List[float]: def _mavlink_to_flu(v: List[float]) -> List[float]:
if len(v) == 3: # vector if len(v) == 3: # vector
return [v[0], -v[1], -v[2]] return [v[0], -v[1], -v[2]]
elif len(v) == 4: # quaternion elif len(v) == 4: # quaternion
@@ -252,8 +252,8 @@ class Flix:
raise ValueError(f'List must have 3 (vector) or 4 (quaternion) elements') raise ValueError(f'List must have 3 (vector) or 4 (quaternion) elements')
@staticmethod @staticmethod
def _flu_to_mavlink(v: Sequence[float]) -> List[float]: def _flu_to_mavlink(v: List[float]) -> List[float]:
return Flix._mavlink_to_flu(v) # flu to mavlink is the same as mavlink to flu return Flix._mavlink_to_flu(v)
def _command_send(self, command: int, params: Sequence[float]): def _command_send(self, command: int, params: Sequence[float]):
if len(params) != 7: if len(params) != 7:
@@ -320,13 +320,13 @@ class Flix:
def set_armed(self, armed: bool): def set_armed(self, armed: bool):
self._command_send(mavlink.MAV_CMD_COMPONENT_ARM_DISARM, (1 if armed else 0, 0, 0, 0, 0, 0, 0)) self._command_send(mavlink.MAV_CMD_COMPONENT_ARM_DISARM, (1 if armed else 0, 0, 0, 0, 0, 0, 0))
def set_position(self, position: Sequence[float], yaw: Optional[float] = None, wait: bool = False, tolerance: float = 0.1): def set_position(self, position: List[float], yaw: Optional[float] = None, wait: bool = False, tolerance: float = 0.1):
raise NotImplementedError('Position control is not implemented yet') raise NotImplementedError('Position control is not implemented yet')
def set_velocity(self, velocity: Sequence[float], yaw: Optional[float] = None): def set_velocity(self, velocity: List[float], yaw: Optional[float] = None):
raise NotImplementedError('Velocity control is not implemented yet') raise NotImplementedError('Velocity control is not implemented yet')
def set_attitude(self, attitude: Sequence[float], thrust: float, rates_extra: Sequence[float] = (0, 0, 0)): def set_attitude(self, attitude: List[float], thrust: float):
if len(attitude) == 3: if len(attitude) == 3:
attitude = Quaternion([attitude[0], attitude[1], attitude[2]]).q # type: ignore attitude = Quaternion([attitude[0], attitude[1], attitude[2]]).q # type: ignore
elif len(attitude) != 4: elif len(attitude) != 4:
@@ -334,13 +334,12 @@ class Flix:
if not (0 <= thrust <= 1): if not (0 <= thrust <= 1):
raise ValueError('Thrust must be in range [0, 1]') raise ValueError('Thrust must be in range [0, 1]')
attitude = self._flu_to_mavlink(attitude) attitude = self._flu_to_mavlink(attitude)
rates_extra = self._flu_to_mavlink(rates_extra)
for _ in range(2): # duplicate to ensure delivery for _ in range(2): # duplicate to ensure delivery
self.mavlink.set_attitude_target_send(0, self.system_id, 0, 0, self.mavlink.set_attitude_target_send(0, self.system_id, 0, 0,
[attitude[0], attitude[1], attitude[2], attitude[3]], [attitude[0], attitude[1], attitude[2], attitude[3]],
rates_extra[0], rates_extra[1], rates_extra[2], thrust) 0, 0, 0, thrust)
def set_rates(self, rates: Sequence[float], thrust: float): def set_rates(self, rates: List[float], thrust: float):
if len(rates) != 3: if len(rates) != 3:
raise ValueError('Rates must be [roll_rate, pitch_rate, yaw_rate]') raise ValueError('Rates must be [roll_rate, pitch_rate, yaw_rate]')
if not (0 <= thrust <= 1): if not (0 <= thrust <= 1):
@@ -352,7 +351,7 @@ class Flix:
[1, 0, 0, 0], [1, 0, 0, 0],
rates[0], rates[1], rates[2], thrust) rates[0], rates[1], rates[2], thrust)
def set_motors(self, motors: Sequence[float]): def set_motors(self, motors: List[float]):
if len(motors) != 4: if len(motors) != 4:
raise ValueError('motors must have 4 values') raise ValueError('motors must have 4 values')
if not all(0 <= m <= 1 for m in motors): if not all(0 <= m <= 1 for m in motors):