Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37f267ecc9 | ||
|
|
3450932dce | ||
|
|
149c891cfc | ||
|
|
0512f5ce68 | ||
|
|
f12b239af9 | ||
|
|
0a377cfe6f | ||
|
|
fd8bf03cc7 | ||
|
|
3ea680605a | ||
|
|
5edac09473 | ||
|
|
7312aeff32 | ||
|
|
d65c5c0eb1 | ||
|
|
76456b778b | ||
|
|
f79c444f07 | ||
|
|
2a96f4795d | ||
|
|
6c55ab3625 | ||
|
|
97edb8e65b | ||
|
|
bfeee7a00a | ||
|
|
3f4577bb2c | ||
|
|
070eb8a0de | ||
|
|
aee43c6548 | ||
|
|
8e276bde38 | ||
|
|
a5991930d1 | ||
|
|
406f7a4af5 | ||
|
|
5ee6d91440 | ||
|
|
38925d7885 | ||
|
|
5c81181221 | ||
|
|
fd249fcaa6 | ||
|
|
8917849711 | ||
|
|
1cdc7a8641 | ||
|
|
d64bf24c6d | ||
|
|
fabd5e072d | ||
|
|
1ae85ff118 | ||
|
|
26a0dd65be | ||
|
|
9d439afd80 | ||
|
|
64b21a3a6b | ||
|
|
545eed8944 | ||
|
|
518abf1555 | ||
|
|
17df1c5396 | ||
|
|
8e2ffd7c69 | ||
|
|
52b74afba6 | ||
|
|
0ca2473655 | ||
|
|
b4c2fe3988 | ||
|
|
e51b47b798 | ||
|
|
71abe1bcdb | ||
|
|
0f2e384ce6 | ||
|
|
5e153a210d | ||
|
|
9d47bcb82e | ||
|
|
1fafc27b39 | ||
|
|
faca48ced3 | ||
|
|
a5dbd2c829 | ||
|
|
59f9528d34 | ||
|
|
607b2ff0b7 | ||
|
|
22c06f76c4 | ||
|
|
488ceb3004 | ||
|
|
b83c9b3845 | ||
|
|
2f4b1423e6 | ||
|
|
4e32414dae | ||
|
|
a294883dea | ||
|
|
cdfba72a0b | ||
|
|
18e81720e0 | ||
|
|
91173d06c9 | ||
|
|
fdcc9533b3 |
@@ -10,30 +10,38 @@ 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@v4
|
- uses: actions/checkout@v7
|
||||||
- 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
|
- name: Build firmware for ESP32
|
||||||
env:
|
|
||||||
ARDUINO_SKETCH_ALWAYS_EXPORT_BINARIES: 1
|
|
||||||
run: make
|
run: make
|
||||||
- name: Upload binaries
|
|
||||||
uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: firmware-binary
|
|
||||||
path: flix/build
|
|
||||||
- name: Build firmware for ESP32-C3
|
- name: Build firmware for ESP32-C3
|
||||||
run: make BOARD=esp32:esp32:esp32c3
|
run: make BOARD=esp32:esp32:esp32c3
|
||||||
- name: Build firmware for ESP32-S3
|
- name: Build firmware for ESP32-S3
|
||||||
run: make BOARD=esp32:esp32:esp32s3
|
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 EXTRA='--build-property "compiler.cpp.extra_flags=-DFLIX2" --output-dir=flix/build/esp32.esp32.flix2'
|
||||||
|
- name: Upload binaries
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: firmware-binary
|
||||||
|
path: flix/build
|
||||||
|
- name: Build 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
|
||||||
run: tools/check_c_cpp_properties.py
|
run: tools/check_c_cpp_properties.py
|
||||||
|
|
||||||
build_macos:
|
build_macos:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v7
|
||||||
- name: Install Arduino CLI
|
- name: Install Arduino CLI
|
||||||
run: brew install arduino-cli
|
run: brew install arduino-cli
|
||||||
- name: Build firmware
|
- name: Build firmware
|
||||||
@@ -44,7 +52,7 @@ jobs:
|
|||||||
build_windows:
|
build_windows:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v7
|
||||||
- name: Install Arduino CLI
|
- name: Install Arduino CLI
|
||||||
run: choco install arduino-cli
|
run: choco install arduino-cli
|
||||||
- name: Install Make
|
- name: Install Make
|
||||||
@@ -64,8 +72,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
|
||||||
uses: arduino/setup-arduino-cli@v1.1.1
|
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v7
|
||||||
- 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'
|
||||||
@@ -76,7 +84,16 @@ 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
|
||||||
- uses: actions/upload-artifact@v4
|
- name: Run simulator
|
||||||
|
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
|
||||||
@@ -88,7 +105,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@v4
|
- uses: actions/checkout@v7
|
||||||
- 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*
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
actions: read
|
||||||
pages: write
|
pages: write
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ jobs:
|
|||||||
markdownlint:
|
markdownlint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v7
|
||||||
- name: Install markdownlint
|
- name: Install markdownlint
|
||||||
run: npm install -g markdownlint-cli2
|
run: npm install -g markdownlint-cli2
|
||||||
- name: Run markdownlint
|
- name: Run markdownlint
|
||||||
@@ -24,19 +25,57 @@ 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@v4
|
- uses: actions/checkout@v7
|
||||||
- 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@v3
|
uses: actions/upload-pages-artifact@v5
|
||||||
with:
|
with:
|
||||||
path: docs/build
|
path: docs/build
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
if: ${{ github.event_name == 'push' && github.ref_name == 'master' }}
|
||||||
concurrency:
|
concurrency:
|
||||||
group: "pages"
|
group: "pages"
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
@@ -48,4 +87,4 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v4
|
uses: actions/deploy-pages@v5
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ jobs:
|
|||||||
csv_to_ulog:
|
csv_to_ulog:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v7
|
||||||
- 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@v4
|
- uses: actions/checkout@v7
|
||||||
- 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@v4
|
uses: actions/upload-artifact@v7
|
||||||
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@v4
|
- uses: actions/checkout@v7
|
||||||
- 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,3 +46,23 @@ 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
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ build/
|
|||||||
tools/log/
|
tools/log/
|
||||||
tools/dist/
|
tools/dist/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.dependencies
|
.core
|
||||||
|
.libs
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.default.json
|
||||||
!.vscode/c_cpp_properties.json
|
!.vscode/c_cpp_properties.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
|
|||||||
@@ -6,18 +6,18 @@
|
|||||||
"${workspaceFolder}/flix",
|
"${workspaceFolder}/flix",
|
||||||
"${workspaceFolder}/gazebo",
|
"${workspaceFolder}/gazebo",
|
||||||
"${workspaceFolder}/tools/**",
|
"${workspaceFolder}/tools/**",
|
||||||
"~/.arduino15/packages/esp32/hardware/esp32/3.3.6/cores/esp32",
|
"~/.arduino15/packages/esp32/hardware/esp32/3.3.10/cores/esp32",
|
||||||
"~/.arduino15/packages/esp32/hardware/esp32/3.3.6/libraries/**",
|
"~/.arduino15/packages/esp32/hardware/esp32/3.3.10/libraries/**",
|
||||||
"~/.arduino15/packages/esp32/hardware/esp32/3.3.6/variants/d1_mini32",
|
"~/.arduino15/packages/esp32/hardware/esp32/3.3.10/variants/d1_mini32",
|
||||||
"~/.arduino15/packages/esp32/tools/esp32-libs/3.3.6/include/**",
|
"~/.arduino15/packages/esp32/tools/esp32-libs/3.3.10/include/**",
|
||||||
"~/Arduino/libraries/**",
|
"~/Arduino/libraries/**",
|
||||||
"/usr/include/gazebo-11/",
|
"/usr/include/gazebo-11/",
|
||||||
"/usr/include/ignition/math6/"
|
"/usr/include/ignition/math6/"
|
||||||
],
|
],
|
||||||
"forcedInclude": [
|
"forcedInclude": [
|
||||||
"${workspaceFolder}/.vscode/intellisense.h",
|
"${workspaceFolder}/.vscode/intellisense.h",
|
||||||
"~/.arduino15/packages/esp32/hardware/esp32/3.3.6/cores/esp32/Arduino.h",
|
"~/.arduino15/packages/esp32/hardware/esp32/3.3.10/cores/esp32/Arduino.h",
|
||||||
"~/.arduino15/packages/esp32/hardware/esp32/3.3.6/variants/d1_mini32/pins_arduino.h",
|
"~/.arduino15/packages/esp32/hardware/esp32/3.3.10/variants/d1_mini32/pins_arduino.h",
|
||||||
"${workspaceFolder}/flix/cli.ino",
|
"${workspaceFolder}/flix/cli.ino",
|
||||||
"${workspaceFolder}/flix/control.ino",
|
"${workspaceFolder}/flix/control.ino",
|
||||||
"${workspaceFolder}/flix/estimate.ino",
|
"${workspaceFolder}/flix/estimate.ino",
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"${workspaceFolder}/flix/parameters.ino",
|
"${workspaceFolder}/flix/parameters.ino",
|
||||||
"${workspaceFolder}/flix/safety.ino"
|
"${workspaceFolder}/flix/safety.ino"
|
||||||
],
|
],
|
||||||
"compilerPath": "~/.arduino15/packages/esp32/tools/esp-x32/2511/bin/xtensa-esp32-elf-g++",
|
"compilerPath": "~/.arduino15/packages/esp32/tools/esp-x32/2601/bin/xtensa-esp32-elf-g++",
|
||||||
"cStandard": "c11",
|
"cStandard": "c11",
|
||||||
"cppStandard": "c++17",
|
"cppStandard": "c++17",
|
||||||
"defines": [
|
"defines": [
|
||||||
@@ -53,18 +53,18 @@
|
|||||||
"name": "Mac",
|
"name": "Mac",
|
||||||
"includePath": [
|
"includePath": [
|
||||||
"${workspaceFolder}/flix",
|
"${workspaceFolder}/flix",
|
||||||
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.6/cores/esp32",
|
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.10/cores/esp32",
|
||||||
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.6/libraries/**",
|
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.10/libraries/**",
|
||||||
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.6/variants/d1_mini32",
|
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.10/variants/d1_mini32",
|
||||||
"~/Library/Arduino15/packages/esp32/tools/esp32-libs/3.3.6/include/**",
|
"~/Library/Arduino15/packages/esp32/tools/esp32-libs/3.3.10/include/**",
|
||||||
"~/Documents/Arduino/libraries/**",
|
"~/Documents/Arduino/libraries/**",
|
||||||
"/opt/homebrew/include/gazebo-11/",
|
"/opt/homebrew/include/gazebo-11/",
|
||||||
"/opt/homebrew/include/ignition/math6/"
|
"/opt/homebrew/include/ignition/math6/"
|
||||||
],
|
],
|
||||||
"forcedInclude": [
|
"forcedInclude": [
|
||||||
"${workspaceFolder}/.vscode/intellisense.h",
|
"${workspaceFolder}/.vscode/intellisense.h",
|
||||||
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.6/cores/esp32/Arduino.h",
|
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.10/cores/esp32/Arduino.h",
|
||||||
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.6/variants/d1_mini32/pins_arduino.h",
|
"~/Library/Arduino15/packages/esp32/hardware/esp32/3.3.10/variants/d1_mini32/pins_arduino.h",
|
||||||
"${workspaceFolder}/flix/flix.ino",
|
"${workspaceFolder}/flix/flix.ino",
|
||||||
"${workspaceFolder}/flix/cli.ino",
|
"${workspaceFolder}/flix/cli.ino",
|
||||||
"${workspaceFolder}/flix/control.ino",
|
"${workspaceFolder}/flix/control.ino",
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
"${workspaceFolder}/flix/parameters.ino",
|
"${workspaceFolder}/flix/parameters.ino",
|
||||||
"${workspaceFolder}/flix/safety.ino"
|
"${workspaceFolder}/flix/safety.ino"
|
||||||
],
|
],
|
||||||
"compilerPath": "~/Library/Arduino15/packages/esp32/tools/esp-x32/2511/bin/xtensa-esp32-elf-g++",
|
"compilerPath": "~/Library/Arduino15/packages/esp32/tools/esp-x32/2601/bin/xtensa-esp32-elf-g++",
|
||||||
"cStandard": "c11",
|
"cStandard": "c11",
|
||||||
"cppStandard": "c++17",
|
"cppStandard": "c++17",
|
||||||
"defines": [
|
"defines": [
|
||||||
@@ -103,16 +103,16 @@
|
|||||||
"${workspaceFolder}/flix",
|
"${workspaceFolder}/flix",
|
||||||
"${workspaceFolder}/gazebo",
|
"${workspaceFolder}/gazebo",
|
||||||
"${workspaceFolder}/tools/**",
|
"${workspaceFolder}/tools/**",
|
||||||
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.6/cores/esp32",
|
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.10/cores/esp32",
|
||||||
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.6/libraries/**",
|
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.10/libraries/**",
|
||||||
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.6/variants/d1_mini32",
|
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.10/variants/d1_mini32",
|
||||||
"~/AppData/Local/Arduino15/packages/esp32/tools/esp32-libs/3.3.6/include/**",
|
"~/AppData/Local/Arduino15/packages/esp32/tools/esp32-libs/3.3.10/include/**",
|
||||||
"~/Documents/Arduino/libraries/**"
|
"~/Documents/Arduino/libraries/**"
|
||||||
],
|
],
|
||||||
"forcedInclude": [
|
"forcedInclude": [
|
||||||
"${workspaceFolder}/.vscode/intellisense.h",
|
"${workspaceFolder}/.vscode/intellisense.h",
|
||||||
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.6/cores/esp32/Arduino.h",
|
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.10/cores/esp32/Arduino.h",
|
||||||
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.6/variants/d1_mini32/pins_arduino.h",
|
"~/AppData/Local/Arduino15/packages/esp32/hardware/esp32/3.3.10/variants/d1_mini32/pins_arduino.h",
|
||||||
"${workspaceFolder}/flix/cli.ino",
|
"${workspaceFolder}/flix/cli.ino",
|
||||||
"${workspaceFolder}/flix/control.ino",
|
"${workspaceFolder}/flix/control.ino",
|
||||||
"${workspaceFolder}/flix/estimate.ino",
|
"${workspaceFolder}/flix/estimate.ino",
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
"${workspaceFolder}/flix/parameters.ino",
|
"${workspaceFolder}/flix/parameters.ino",
|
||||||
"${workspaceFolder}/flix/safety.ino"
|
"${workspaceFolder}/flix/safety.ino"
|
||||||
],
|
],
|
||||||
"compilerPath": "~/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2511/bin/xtensa-esp32-elf-g++.exe",
|
"compilerPath": "~/AppData/Local/Arduino15/packages/esp32/tools/esp-x32/2601/bin/xtensa-esp32-elf-g++.exe",
|
||||||
"cStandard": "c11",
|
"cStandard": "c11",
|
||||||
"cppStandard": "c++17",
|
"cppStandard": "c++17",
|
||||||
"defines": [
|
"defines": [
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
|
"dangmai.workspace-default-settings",
|
||||||
"ms-vscode.cpptools",
|
"ms-vscode.cpptools",
|
||||||
"ms-vscode.cmake-tools",
|
"ms-vscode.cmake-tools",
|
||||||
"ms-python.python"
|
"ms-python.python"
|
||||||
|
|||||||
@@ -1,32 +1,40 @@
|
|||||||
BOARD = esp32:esp32:d1_mini32
|
BOARD = esp32:esp32:esp32
|
||||||
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*))
|
||||||
|
|
||||||
build: .dependencies
|
export ARDUINO_NETWORK_CONNECTION_TIMEOUT := 1h
|
||||||
arduino-cli compile --fqbn $(BOARD) flix
|
|
||||||
|
build: .core .libs
|
||||||
|
arduino-cli compile flix --fqbn $(BOARD) --build-property "build.core_debug_level=1" $(EXTRA)
|
||||||
|
|
||||||
upload: build
|
upload: build
|
||||||
arduino-cli upload --fqbn $(BOARD) -p "$(PORT)" flix
|
arduino-cli upload flix --fqbn $(BOARD) -p "$(PORT)"
|
||||||
|
|
||||||
|
erase:
|
||||||
|
arduino-cli burn-bootloader --fqbn $(BOARD) -p "$(PORT)" -P esptool
|
||||||
|
|
||||||
monitor:
|
monitor:
|
||||||
arduino-cli monitor -p "$(PORT)" -c baudrate=115200
|
arduino-cli monitor -p "$(PORT)" -c baudrate=115200
|
||||||
|
|
||||||
dependencies .dependencies:
|
core .core:
|
||||||
arduino-cli core update-index --config-file arduino-cli.yaml
|
arduino-cli core update-index --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json
|
||||||
arduino-cli core install esp32:esp32@3.3.6 --config-file arduino-cli.yaml
|
arduino-cli core install esp32:esp32@3.3.10 --additional-urls https://espressif.github.io/arduino-esp32/package_esp32_index.json
|
||||||
|
touch .core
|
||||||
|
|
||||||
|
libs .libs:
|
||||||
arduino-cli lib update-index
|
arduino-cli lib update-index
|
||||||
arduino-cli lib install "FlixPeriph"
|
arduino-cli lib install "FlixPeriph"
|
||||||
arduino-cli lib install "MAVLink"@2.0.25
|
arduino-cli lib install "MAVLink"@2.0.25
|
||||||
touch .dependencies
|
touch .libs
|
||||||
|
|
||||||
upload_proxy: .dependencies
|
upload_proxy: .core .libs
|
||||||
arduino-cli compile --fqbn $(BOARD) tools/espnow-proxy
|
arduino-cli compile tools/espnow-proxy --fqbn $(BOARD)
|
||||||
arduino-cli upload --fqbn $(BOARD) -p "$(PORT)" tools/espnow-proxy
|
arduino-cli upload tools/espnow-proxy --fqbn $(BOARD) -p "$(PORT)"
|
||||||
|
|
||||||
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 ..
|
||||||
|
|
||||||
build_simulator: .dependencies gazebo/build
|
build_simulator: .libs gazebo/build
|
||||||
make -C gazebo/build
|
make -C gazebo/build
|
||||||
|
|
||||||
simulator: build_simulator
|
simulator: build_simulator
|
||||||
@@ -41,6 +49,6 @@ plot:
|
|||||||
plotjuggler -d $(shell ls -t tools/log/*.csv | head -n1)
|
plotjuggler -d $(shell ls -t tools/log/*.csv | head -n1)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf gazebo/build flix/build flix/cache .dependencies
|
rm -rf gazebo/build flix/build flix/cache .core .libs
|
||||||
|
|
||||||
.PHONY: build upload monitor dependencies cmake build_simulator simulator log clean
|
.PHONY: build upload monitor core libs cmake build_simulator simulator log clean
|
||||||
|
|||||||
@@ -47,6 +47,20 @@ See the [user builds gallery](docs/user.md):
|
|||||||
|
|
||||||
<a href="docs/user.md"><img src="docs/img/user/user.jpg" width=500></a>
|
<a href="docs/user.md"><img src="docs/img/user/user.jpg" width=500></a>
|
||||||
|
|
||||||
|
### PCB
|
||||||
|
|
||||||
|
The official PCB *(Flix2)* is in development now. Follow the [project's channel](https://t.me/opensourcequadcopter) to track the progress.
|
||||||
|
|
||||||
|
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>
|
||||||
|
|
||||||
|
### 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:
|
||||||
@@ -73,10 +87,10 @@ Additional articles:
|
|||||||
|-|-|:-:|:-:|
|
|-|-|:-:|:-:|
|
||||||
|Microcontroller board|ESP32 Mini.<br>ESP32-S3/ESP32-C3 boards are also supported.|<img src="docs/img/esp32.jpg" width=100>|1|
|
|Microcontroller board|ESP32 Mini.<br>ESP32-S3/ESP32-C3 boards are also supported.|<img src="docs/img/esp32.jpg" width=100>|1|
|
||||||
|IMU (and barometer¹) board|GY‑91, MPU-9265 (or other MPU‑9250/MPU‑6500 board)<br>ICM20948V2 (ICM‑20948)<br>GY-521 (MPU-6050)|<img src="docs/img/gy-91.jpg" width=90 align=center><br><img src="docs/img/icm-20948.jpg" width=100><br><img src="docs/img/gy-521.jpg" width=100>|1|
|
|IMU (and barometer¹) board|GY‑91, MPU-9265 (or other MPU‑9250/MPU‑6500 board)<br>ICM20948V2 (ICM‑20948)<br>GY-521 (MPU-6050)|<img src="docs/img/gy-91.jpg" width=90 align=center><br><img src="docs/img/icm-20948.jpg" width=100><br><img src="docs/img/gy-521.jpg" width=100>|1|
|
||||||
|Boost converter (optional, for more stable power supply)|5V output|<img src="docs/img/buck-boost.jpg" width=100>|1|
|
|*Boost converter (optional, for more stable power supply)*|*5V output*|<img src="docs/img/buck-boost.jpg" width=100>|1|
|
||||||
|Motor|8520 3.7V brushed motor.<br>Motor with exact 3.7V voltage is needed, not ranged working voltage (3.7V — 6V).<br>Make sure the motor shaft diameter and propeller hole diameter match!|<img src="docs/img/motor.jpeg" width=100>|4|
|
|Motor|8520 3.7V brushed motor.<br>Motor with exact 3.7V voltage is needed, not ranged working voltage (3.7V — 6V).<br>Make sure the motor shaft diameter and propeller hole diameter match!|<img src="docs/img/motor.jpeg" width=100>|4|
|
||||||
|Propeller|55 mm or 65 mm|<img src="docs/img/prop.jpg" width=100>|4|
|
|Propeller|55 mm or 65 mm|<img src="docs/img/prop.jpg" width=100>|4|
|
||||||
|MOSFET (transistor)|100N03A or [analog](https://t.me/opensourcequadcopter/33)|<img src="docs/img/100n03a.jpg" width=100>|4|
|
|MOSFET (transistor)|UMW 100N03A or [analog](https://t.me/opensourcequadcopter/33).<br>Warning: don't use KIA 100N03A or other manufacturers, they might not work!|<img src="docs/img/100n03a.jpg" width=100>|4|
|
||||||
|Pull-down resistor<br>Voltage measurement resistor|10 kΩ|<img src="docs/img/resistor10k.jpg" width=100>|6|
|
|Pull-down resistor<br>Voltage measurement resistor|10 kΩ|<img src="docs/img/resistor10k.jpg" width=100>|6|
|
||||||
|3.7V Li-Po battery|LW 952540 (or any compatible by the size).<br>Make sure the battery has enough discharge rate — 25C or more!|<img src="docs/img/battery.jpg" width=100>|1|
|
|3.7V Li-Po battery|LW 952540 (or any compatible by the size).<br>Make sure the battery has enough discharge rate — 25C or more!|<img src="docs/img/battery.jpg" width=100>|1|
|
||||||
|Battery connector cable|MX2.0 2P female|<img src="docs/img/mx.png" width=100>|1|
|
|Battery connector cable|MX2.0 2P female|<img src="docs/img/mx.png" width=100>|1|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
board_manager:
|
|
||||||
additional_urls:
|
|
||||||
- https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
|
|
||||||
network:
|
|
||||||
connection_timeout: 1h
|
|
||||||
@@ -79,6 +79,9 @@ To add a new parameter:
|
|||||||
|
|
||||||
See examples of adding new parameters in commits: [c434107](https://github.com/okalachev/flix/commit/c434107), [a687303](https://github.com/okalachev/flix/commit/a687303).
|
See examples of adding new parameters in commits: [c434107](https://github.com/okalachev/flix/commit/c434107), [a687303](https://github.com/okalachev/flix/commit/a687303).
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> Since all the parameters are internally stored and passed as floats, the safe range for `int` parameters is -16777216 to 16777215.
|
||||||
|
|
||||||
## Adding a subsystem
|
## Adding a subsystem
|
||||||
|
|
||||||
To add a new subsystem:
|
To add a new subsystem:
|
||||||
|
|||||||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 69 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 65 KiB |
@@ -5,7 +5,7 @@
|
|||||||
Do the following:
|
Do the following:
|
||||||
|
|
||||||
* **Check ESP32 core is installed**. Check if the version matches the one used in the [tutorial](usage.md#building-the-firmware).
|
* **Check ESP32 core is installed**. Check if the version matches the one used in the [tutorial](usage.md#building-the-firmware).
|
||||||
* **Check libraries**. Install all the required libraries from the tutorial. Make sure there are no MPU9250 or other peripherals libraries that may conflict with the ones used in the tutorial.
|
* **Check libraries**. Install all the required libraries from the tutorial. Make sure there are no MPU-9250 or other peripherals libraries that may conflict with the ones used in the tutorial.
|
||||||
* **Check the chosen board**. The correct board to choose in Arduino IDE for ESP32 Mini is *WEMOS D1 MINI ESP32*.
|
* **Check the chosen board**. The correct board to choose in Arduino IDE for ESP32 Mini is *WEMOS D1 MINI ESP32*.
|
||||||
|
|
||||||
## The drone doesn't fly
|
## The drone doesn't fly
|
||||||
|
|||||||
@@ -1,34 +1,63 @@
|
|||||||
# Usage: build, setup and flight
|
# Usage: build, setup and flight
|
||||||
|
|
||||||
To fly Flix quadcopter, you need to build the firmware, upload it to the ESP32 board, and set up the drone for flight.
|
To fly Flix quadcopter, you need to upload the firmware to the ESP32 board, and set up the drone for flight.
|
||||||
|
|
||||||
To get the firmware sources, clone the repository using git:
|
## Uploading the firmware
|
||||||
|
|
||||||
|
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 source code as a ZIP archive](https://github.com/okalachev/flix/archive/refs/heads/master.zip).
|
Beginners can [download the sources as a ZIP archive](https://github.com/okalachev/flix/archive/refs/heads/master.zip).
|
||||||
|
|
||||||
## Building the firmware
|
#### Arduino IDE (Windows, Linux, macOS)
|
||||||
|
|
||||||
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">
|
||||||
|
|
||||||
1. Install [Arduino IDE](https://www.arduino.cc/en/software) (version 2 is recommended).
|
1. Install [Arduino IDE](https://www.arduino.cc/en/software) (version 2 is recommended).
|
||||||
2. *Windows users might need to install [USB to UART bridge driver from Silicon Labs](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers).*
|
2. *Windows users might need to install [USB to UART bridge driver from Silicon Labs](https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers).*
|
||||||
3. Install ESP32 core, version 3.3.6. See the [official Espressif's instructions](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#installing-using-arduino-ide) on installing ESP32 Core in Arduino IDE.
|
3. Install ESP32 core, version 3.3.10. See the [official Espressif's instructions](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html#installing-using-arduino-ide) on installing ESP32 Core in Arduino IDE.
|
||||||
4. Install the following libraries using [Library Manager](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library):
|
4. Install the following libraries using [Library Manager](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library):
|
||||||
* `FlixPeriph`, the latest version.
|
* `FlixPeriph`, the latest version.
|
||||||
* `MAVLink`, version 2.0.25.
|
* `MAVLink`, version 2.0.25.
|
||||||
5. Open the `flix/flix.ino` sketch from downloaded firmware sources in Arduino IDE.
|
5. Open the `flix/flix.ino` sketch from downloaded firmware sources in Arduino IDE.
|
||||||
6. Connect your ESP32 board to the computer and choose correct board type in Arduino IDE (*WEMOS D1 MINI ESP32* for ESP32 Mini) and the port.
|
6. Connect your ESP32 board to the computer and choose correct board type in Arduino IDE (*WEMOS D1 MINI ESP32* for ESP32 Mini, *ESP32S3 Dev Module* for ESP32-S3 Super Mini) and the port.
|
||||||
7. [Build and upload](https://docs.arduino.cc/software/ide-v2/tutorials/getting-started/ide-v2-uploading-a-sketch) the firmware using Arduino IDE.
|
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.
|
||||||
|
|
||||||
### 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/).
|
||||||
|
|
||||||
@@ -57,6 +86,12 @@ You can build and upload the firmware using either **Arduino IDE** (easier for b
|
|||||||
make upload monitor
|
make upload monitor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For ESP32-S3/ESP32-C3 boards, set the appropriate [FQBN](https://docs.arduino.cc/arduino-cli/FAQ/#whats-the-fqbn-string) using `BOARD` parameter:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make BOARD=esp32:esp32:esp32s3:FlashSize=4M,CDCOnBoot=cdc upload
|
||||||
|
```
|
||||||
|
|
||||||
See other available Make commands in [Makefile](../Makefile).
|
See other available Make commands in [Makefile](../Makefile).
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
@@ -64,15 +99,6 @@ 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
|
|
||||||
MPU6050 imu(Wire); // For MPU-6050
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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.
|
||||||
@@ -82,6 +108,9 @@ QGroundControl is a ground control station software that can be used to monitor
|
|||||||
3. Connect your computer or smartphone to the appeared `flix` Wi-Fi network (password: `flixwifi`).
|
3. Connect your computer or smartphone to the appeared `flix` Wi-Fi network (password: `flixwifi`).
|
||||||
4. Launch QGroundControl app. It should connect and begin showing the drone's telemetry automatically.
|
4. Launch QGroundControl app. It should connect and begin showing the drone's telemetry automatically.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> If QGroundControl doesn't connect, try to disable the firewall and/or VPN on your computer, as they may block the connection.
|
||||||
|
|
||||||
### Access console
|
### Access console
|
||||||
|
|
||||||
The console is a command line interface (CLI) that allows to interact with the drone, change parameters, and perform various actions. There are two ways of accessing the console: using **serial port** or using **QGroundControl (wirelessly)**.
|
The console is a command line interface (CLI) that allows to interact with the drone, change parameters, and perform various actions. There are two ways of accessing the console: using **serial port** or using **QGroundControl (wirelessly)**.
|
||||||
@@ -95,7 +124,7 @@ To access the console using serial port:
|
|||||||
To access the console using QGroundControl:
|
To access the console using QGroundControl:
|
||||||
|
|
||||||
1. Connect to the drone using QGroundControl app.
|
1. Connect to the drone using QGroundControl app.
|
||||||
2. Go to the QGroundControl menu ⇒ *Vehicle Setup* ⇒ *Analyze Tools* ⇒ *MAVLink Console*.
|
2. Go to the QGroundControl menu ⇒ *Analyze Tools* ⇒ *MAVLink Console*.
|
||||||
|
|
||||||
<img src="img/cli.png" width="400">
|
<img src="img/cli.png" width="400">
|
||||||
|
|
||||||
@@ -110,6 +139,17 @@ 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`.
|
||||||
@@ -138,9 +178,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).
|
||||||
|
|
||||||
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.
|
#### Brushless motors
|
||||||
|
|
||||||
If using brushless motors and ESCs:
|
If using brushless motors with 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).
|
||||||
@@ -148,7 +188,7 @@ If using brushless motors and 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
|
### Battery voltage monitoring (optional)
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
@@ -188,7 +228,7 @@ After this setup, you should see the battery voltage in QGroundControl top panel
|
|||||||
|
|
||||||
## Setup remote control
|
## Setup remote control
|
||||||
|
|
||||||
There are several ways to control the drone's flight: using **smartphone** (Wi-Fi), using **SBUS remote control**, or using **USB remote control** (Wi-Fi).
|
There are several ways to control the drone's flight: using **smartphone** (Wi-Fi), using **SBUS remote control**, or using **USB remote control** (Wi-Fi/ESP-NOW).
|
||||||
|
|
||||||
### Control with a smartphone
|
### Control with a smartphone
|
||||||
|
|
||||||
@@ -233,7 +273,7 @@ If your drone doesn't have RC receiver installed, you can use USB remote control
|
|||||||
3. Power up the drone.
|
3. Power up the drone.
|
||||||
4. Connect your computer to the appeared `flix` Wi-Fi network (password: `flixwifi`).
|
4. Connect your computer to the appeared `flix` Wi-Fi network (password: `flixwifi`).
|
||||||
5. Launch QGroundControl app. It should connect and begin showing the drone's telemetry automatically.
|
5. Launch QGroundControl app. It should connect and begin showing the drone's telemetry automatically.
|
||||||
6. Go the the QGroundControl menu ⇒ *Vehicle Setup* ⇒ *Joystick*. Calibrate you USB remote control there.
|
6. Go to the QGroundControl menu ⇒ *Vehicle Setup* ⇒ *Joystick*. Calibrate your USB remote control there.
|
||||||
7. Use the USB remote control to fly the drone!
|
7. Use the USB remote control to fly the drone!
|
||||||
|
|
||||||
## Flight
|
## Flight
|
||||||
@@ -323,13 +363,13 @@ 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`.
|
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:
|
2. Open Serial Monitor in Arduino IDE 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
|
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.
|
Run this line as a console command on each drone you want to bind to this proxy board. [The maximum number](https://github.com/espressif/esp-idf/blob/e95cab4be8fd293e3f3323181e7a2280874da6f7/components/esp_wifi/include/esp_now.h#L32-L33) of simultaneously connected drones is 20 (unencrypted) or 6 (encrypted).
|
||||||
|
|
||||||
3. Set the `WIFI_MODE` parameter to `3` on the drone:
|
3. Set the `WIFI_MODE` parameter to `3` on the drone:
|
||||||
|
|
||||||
@@ -342,11 +382,14 @@ To setup ESP-NOW communication:
|
|||||||
* Type: Serial.
|
* Type: Serial.
|
||||||
* Serial Port: choose the port of the proxy ESP32 board, e. g. `/dev/cu.usbserial-0001`.
|
* Serial Port: choose the port of the proxy ESP32 board, e. g. `/dev/cu.usbserial-0001`.
|
||||||
* Baud Rate: 115200.
|
* Baud Rate: 115200.
|
||||||
5. Click *Save*. 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]
|
||||||
|
> Make sure Arduino IDE is not running when using ESP-NOW proxy board, as it may block the serial port.
|
||||||
|
|
||||||
## 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 wirelessly for analysis. Use the following command on your computer for that:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make log
|
make log
|
||||||
|
|||||||
@@ -4,6 +4,55 @@ 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>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<img src="img/user/alicanerus/1.jpg" height=200> <img src="img/user/alicanerus/2.jpg" height=200> <img src="img/user/alicanerus/3.jpg" height=200>
|
||||||
|
|
||||||
|
[Flight video](https://drive.google.com/file/d/1k0WeWTKnCAfaugkX7LcmNxsUuq79RL8Z/view?usp=sharing).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Author: [Неруш Михаил](https://t.me/NerushMV).<br>
|
||||||
|
Description: custom frame made of 4 mm plywood, 8520 brushed motors, 75 mm propellers, MPU-6500. FlySky FS-i6X with ESP32-based adapter for ESP-NOW communication (using PPM output).<br>
|
||||||
|
Sources and materials: [link](https://drive.google.com/drive/folders/1uWiDcuorLrtVs_IIR7Y13omij-7Q1nx8).
|
||||||
|
|
||||||
|
<img src="img/user/nerush/1.jpg" height=200> <img src="img/user/nerush/2.jpg" height=200>
|
||||||
|
|
||||||
|
[Flight video](https://drive.google.com/file/d/1jRXeGx34lJpUfw0GKLQeIzkWZvooQJSE/view?usp=sharing).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Author: [Konstantinos Paraskevas](https://github.com/Frapais).<br>
|
||||||
|
Description: drone with a custom single-boarded airframe, extending the [Sprig-C3 module](https://github.com/Frapais/Sprig-C3).
|
||||||
|
ESP32-C3 microcontroller, ICM-20948 IMU, on-board fuel-gauge, status LED indicator.<br>
|
||||||
|
Repository with all the code and PCB sources: https://github.com/Frapais/Sprig-Drone.
|
||||||
|
|
||||||
|
<img src="img/user/kostas/1.jpg" height=150> <img src="img/user/kostas/2.jpg" height=150>
|
||||||
|
|
||||||
|
Detailed video about making the drone:
|
||||||
|
|
||||||
|
<a href="https://youtu.be/82Q-uBq6s48"><img width=400 src="https://i3.ytimg.com/vi/82Q-uBq6s48/maxresdefault.jpg"></a>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Author: [Awab Anas](http://t.me/AW_VENOM).<br>
|
||||||
|
Description: ESP32 D1 Mini, MPU-6050, 8520 3.7V brushed motors, 55 mm propellers, battery li-po 1200 mAh, controlling via [Mavlink Joystick app](https://github.com/goldarte/mavlink-joystick/releases/latest).<br>
|
||||||
|
[Flight validation](https://drive.google.com/file/d/12z0jfctZDBA6b5UKCG0Uje5rAxj6DhF-/view?usp=sharing).
|
||||||
|
|
||||||
|
<img src="img/user/aw_venom/1.jpg" height=200>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Author: [Ina Tix](https://t.me/ina_tix).<br>
|
Author: [Ina Tix](https://t.me/ina_tix).<br>
|
||||||
Description: XR2981 based DC-DC converter, ELRS MINI 2.4GHz RX SX1280 receiver (SBUS interface), Radiomaster TX12 remote control.<br>
|
Description: XR2981 based DC-DC converter, ELRS MINI 2.4GHz RX SX1280 receiver (SBUS interface), Radiomaster TX12 remote control.<br>
|
||||||
[Flight validation](https://drive.google.com/file/d/1yqkKNuz4R_yxGqUNQxVpixJbXqEEcUSj/view?usp=share_link).
|
[Flight validation](https://drive.google.com/file/d/1yqkKNuz4R_yxGqUNQxVpixJbXqEEcUSj/view?usp=share_link).
|
||||||
@@ -57,6 +106,17 @@ Author: [goldarte](https://t.me/goldarte).<br>
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
Author: [malagis](https://oshwhub.com/malagis).<br>
|
||||||
|
|
||||||
|
A Chinese custom PCB version of Flix with a big community of users, lots of materials and modifications.
|
||||||
|
|
||||||
|
Main project's page: https://oshwhub.com/malagis/esp32-mini-plane.<br>
|
||||||
|
Video about the project: https://www.bilibili.com/video/BV14vyqBFEJn/.
|
||||||
|
|
||||||
|
<img src="img/user/malagis/1.jpg" height=200> <img src="img/user/malagis/2.jpg" height=200> <img src="img/user/malagis/3.jpg" height=200>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## School 548 course
|
## School 548 course
|
||||||
|
|
||||||
Special course on quadcopter design and engineering took place in october-november 2025 in School 548, Moscow. The course included UAV control theory, electronics, drone assembly and setup practice, using the Flix project.
|
Special course on quadcopter design and engineering took place in october-november 2025 in School 548, Moscow. The course included UAV control theory, electronics, drone assembly and setup practice, using the Flix project.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include "pid.h"
|
#include "pid.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "lpf.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;
|
||||||
@@ -31,33 +31,33 @@ const char* motd =
|
|||||||
"Commands:\n\n"
|
"Commands:\n\n"
|
||||||
"help - show help\n"
|
"help - show help\n"
|
||||||
"p - show all parameters\n"
|
"p - show all parameters\n"
|
||||||
"p <name> - show parameter\n"
|
"p <str> - show parameters starting with str\n"
|
||||||
"p <name> <value> - set parameter\n"
|
"p <name> <value> - set parameter\n"
|
||||||
"preset - reset parameters\n"
|
"preset - reset parameters\n"
|
||||||
"time - show time info\n"
|
"time - show time info\n"
|
||||||
"ps - show pitch/roll/yaw\n"
|
|
||||||
"psq - show attitude quaternion\n"
|
|
||||||
"imu - show IMU data\n"
|
"imu - show IMU data\n"
|
||||||
|
"ca - calibrate accel\n"
|
||||||
|
"st - show state estimation\n"
|
||||||
"arm - arm the drone\n"
|
"arm - arm the drone\n"
|
||||||
"disarm - disarm the drone\n"
|
"disarm - disarm the drone\n"
|
||||||
"raw/stab/acro/auto - set mode\n"
|
"raw/stab/acro/auto - set mode\n"
|
||||||
"rc - show RC data\n"
|
"rc - show RC data\n"
|
||||||
|
"cr - calibrate RC\n"
|
||||||
"pw - show power info\n"
|
"pw - show power info\n"
|
||||||
"wifi - show Wi-Fi info\n"
|
"wifi - show Wi-Fi info\n"
|
||||||
"ap <ssid> <password> - setup Wi-Fi access point\n"
|
"wifi ap/sta/espnow/off - set Wi-Fi mode\n"
|
||||||
"sta <ssid> <password> - setup Wi-Fi client mode\n"
|
"ap <ssid> <password> - configure Wi-Fi access point\n"
|
||||||
"espnow <mac> [<key>] - setup ESP-NOW peer\n"
|
"sta <ssid> <password> - configure Wi-Fi client mode\n"
|
||||||
|
"espnow <mac> [<key>] - configure 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"
|
"mfr/mfl/mrr/mrl [<thrust>] - test motor (remove props)\n"
|
||||||
"ca - calibrate accel\n"
|
|
||||||
"mfr, mfl, mrr, mrl - test motor (remove props)\n"
|
|
||||||
"sys - show system info\n"
|
"sys - show system info\n"
|
||||||
"reset - reset drone's state\n"
|
"reset - reset drone's state\n"
|
||||||
"reboot - reboot the drone\n";
|
"reboot - reboot the drone\n";
|
||||||
|
|
||||||
void print(const char* format, ...) {
|
void print(const char* format, ...) {
|
||||||
char buf[1000];
|
char buf[3000];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, format);
|
va_start(args, format);
|
||||||
vsnprintf(buf, sizeof(buf), format, args);
|
vsnprintf(buf, sizeof(buf), format, args);
|
||||||
@@ -92,10 +92,8 @@ void doCommand(String str, bool echo = false) {
|
|||||||
// execute command
|
// execute command
|
||||||
if (command == "help" || command == "motd") {
|
if (command == "help" || command == "motd") {
|
||||||
print("%s\n", motd);
|
print("%s\n", motd);
|
||||||
} else if (command == "p" && arg0 == "") {
|
} else if (command == "p" && arg1 == "") {
|
||||||
printParameters();
|
printParameters(arg0.c_str());
|
||||||
} else if (command == "p" && arg0 != "" && arg1 == "") {
|
|
||||||
print("%s = %g\n", arg0.c_str(), getParameter(arg0.c_str()));
|
|
||||||
} else if (command == "p") {
|
} else if (command == "p") {
|
||||||
bool success = setParameter(arg0.c_str(), arg1.toFloat());
|
bool success = setParameter(arg0.c_str(), arg1.toFloat());
|
||||||
if (success) {
|
if (success) {
|
||||||
@@ -109,15 +107,15 @@ void doCommand(String str, bool echo = false) {
|
|||||||
print("Time: %f\n", t);
|
print("Time: %f\n", t);
|
||||||
print("Loop rate: %.0f\n", loopRate);
|
print("Loop rate: %.0f\n", loopRate);
|
||||||
print("dt: %f\n", dt);
|
print("dt: %f\n", dt);
|
||||||
} else if (command == "ps") {
|
|
||||||
Vector a = attitude.toEuler();
|
|
||||||
print("roll: %f pitch: %f yaw: %f\n", degrees(a.x), degrees(a.y), degrees(a.z));
|
|
||||||
} else if (command == "psq") {
|
|
||||||
print("qw: %f qx: %f qy: %f qz: %f\n", attitude.w, attitude.x, attitude.y, attitude.z);
|
|
||||||
} else if (command == "imu") {
|
} else if (command == "imu") {
|
||||||
printIMUInfo();
|
printIMUInfo();
|
||||||
printIMUCalibration();
|
printIMUCalibration();
|
||||||
print("landed: %d\n", landed);
|
print("landed: %d\n", landed);
|
||||||
|
} else if (command == "st") {
|
||||||
|
print("rates: %g %g %g\n", rates.x, rates.y, rates.z);
|
||||||
|
print("attitude: %g %g %g %g\n", attitude.w, attitude.x, attitude.y, attitude.z);
|
||||||
|
print("roll: %g° pitch: %g° yaw: %g°\n", degrees(attitude.getRoll()), degrees(attitude.getPitch()), degrees(attitude.getYaw()));
|
||||||
|
print("landed: %d\n", landed);
|
||||||
} else if (command == "arm") {
|
} else if (command == "arm") {
|
||||||
armed = true;
|
armed = true;
|
||||||
} else if (command == "disarm") {
|
} else if (command == "disarm") {
|
||||||
@@ -142,8 +140,10 @@ void doCommand(String str, bool echo = false) {
|
|||||||
print("armed: %d\n", armed);
|
print("armed: %d\n", armed);
|
||||||
} else if (command == "pw") {
|
} else if (command == "pw") {
|
||||||
print("Voltage: %.1f V\n", voltage);
|
print("Voltage: %.1f V\n", voltage);
|
||||||
} else if (command == "wifi") {
|
} else if (command == "wifi" && arg0 == "") {
|
||||||
printWiFiInfo();
|
printWiFiInfo();
|
||||||
|
} else if (command == "wifi") {
|
||||||
|
setWiFiMode(arg0);
|
||||||
} else if (command == "ap") {
|
} else if (command == "ap") {
|
||||||
configWiFi(W_AP, arg0.c_str(), arg1.c_str());
|
configWiFi(W_AP, arg0.c_str(), arg1.c_str());
|
||||||
} else if (command == "sta") {
|
} else if (command == "sta") {
|
||||||
@@ -161,21 +161,22 @@ void doCommand(String str, bool echo = false) {
|
|||||||
} else if (command == "ca") {
|
} else if (command == "ca") {
|
||||||
calibrateAccel();
|
calibrateAccel();
|
||||||
} else if (command == "mfr") {
|
} else if (command == "mfr") {
|
||||||
testMotor(MOTOR_FRONT_RIGHT);
|
testMotor(MOTOR_FRONT_RIGHT, arg0.isEmpty() ? 0.2 : arg0.toFloat());
|
||||||
} else if (command == "mfl") {
|
} else if (command == "mfl") {
|
||||||
testMotor(MOTOR_FRONT_LEFT);
|
testMotor(MOTOR_FRONT_LEFT, arg0.isEmpty() ? 0.2 : arg0.toFloat());
|
||||||
} else if (command == "mrr") {
|
} else if (command == "mrr") {
|
||||||
testMotor(MOTOR_REAR_RIGHT);
|
testMotor(MOTOR_REAR_RIGHT, arg0.isEmpty() ? 0.2 : arg0.toFloat());
|
||||||
} else if (command == "mrl") {
|
} else if (command == "mrl") {
|
||||||
testMotor(MOTOR_REAR_LEFT);
|
testMotor(MOTOR_REAR_LEFT, arg0.isEmpty() ? 0.2 : arg0.toFloat());
|
||||||
} else if (command == "sys") {
|
} else if (command == "sys") {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
print("Chip: %s\n", ESP.getChipModel());
|
print("Chip: %s\n", ESP.getChipModel());
|
||||||
print("Temperature: %.1f °C\n", temperatureRead());
|
print("Temperature: %.1f °C\n", temperatureRead());
|
||||||
print("Free heap: %d\n", ESP.getFreeHeap());
|
print("Total RAM: %d KB\n", ESP.getHeapSize() / 1024);
|
||||||
|
print("Free heap: %d KB\n", ESP.getFreeHeap() / 1024);
|
||||||
print("Firmware: " __DATE__ " " __TIME__ "\n");
|
print("Firmware: " __DATE__ " " __TIME__ "\n");
|
||||||
// Print tasks table
|
// Print tasks table
|
||||||
print("Num Task Stack Prio Core CPU%%\n");
|
print("Num Task MinSt Prio Core CPU%%\n");
|
||||||
int taskCount = uxTaskGetNumberOfTasks();
|
int taskCount = uxTaskGetNumberOfTasks();
|
||||||
TaskStatus_t *systemState = new TaskStatus_t[taskCount];
|
TaskStatus_t *systemState = new TaskStatus_t[taskCount];
|
||||||
uint32_t totalRunTime;
|
uint32_t totalRunTime;
|
||||||
@@ -209,7 +210,7 @@ void handleInput() {
|
|||||||
|
|
||||||
while (Serial.available()) {
|
while (Serial.available()) {
|
||||||
char c = Serial.read();
|
char c = Serial.read();
|
||||||
if (c == '\n') {
|
if (c == '\n' || c == '\r') {
|
||||||
doCommand(input);
|
doCommand(input);
|
||||||
input.clear();
|
input.clear();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -6,34 +6,9 @@
|
|||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
#include "quaternion.h"
|
#include "quaternion.h"
|
||||||
#include "pid.h"
|
#include "pid.h"
|
||||||
#include "lpf.h"
|
#include "filter.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
#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; // flight modes
|
const int RAW = 0, ACRO = 1, STAB = 2, AUTO = 3; // flight modes
|
||||||
int mode = STAB;
|
int mode = STAB;
|
||||||
bool armed = false;
|
bool armed = false;
|
||||||
@@ -44,14 +19,14 @@ Vector ratesExtra; // feedforward rates
|
|||||||
Vector torqueTarget;
|
Vector torqueTarget;
|
||||||
float thrustTarget;
|
float thrustTarget;
|
||||||
|
|
||||||
PID rollRatePID(ROLLRATE_P, ROLLRATE_I, ROLLRATE_D, ROLLRATE_I_LIM, RATES_D_LPF_ALPHA);
|
PID rollRatePID(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 pitchRatePID(0.05, 0.2, 0.001, 0.3, 0.2);
|
||||||
PID yawRatePID(YAWRATE_P, YAWRATE_I, YAWRATE_D);
|
PID yawRatePID(0.3, 0, 0, 0.3);
|
||||||
PID rollPID(ROLL_P, ROLL_I, ROLL_D);
|
PID rollPID(6);
|
||||||
PID pitchPID(PITCH_P, PITCH_I, PITCH_D);
|
PID pitchPID(6);
|
||||||
PID yawPID(YAW_P, 0, 0);
|
PID yawPID(3);
|
||||||
Vector maxRate(ROLLRATE_MAX, PITCHRATE_MAX, YAWRATE_MAX);
|
Vector maxRate(radians(360), radians(360), radians(360));
|
||||||
float tiltMax = TILT_MAX;
|
float tiltMax = radians(30);
|
||||||
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;
|
||||||
@@ -149,6 +124,7 @@ void controlTorque() {
|
|||||||
motors[MOTOR_REAR_LEFT] = thrustTarget + torqueTarget.x + torqueTarget.y - torqueTarget.z;
|
motors[MOTOR_REAR_LEFT] = thrustTarget + torqueTarget.x + torqueTarget.y - torqueTarget.z;
|
||||||
motors[MOTOR_REAR_RIGHT] = thrustTarget - torqueTarget.x + torqueTarget.y + torqueTarget.z;
|
motors[MOTOR_REAR_RIGHT] = thrustTarget - torqueTarget.x + torqueTarget.y + torqueTarget.z;
|
||||||
|
|
||||||
|
// Prioritize angle control over thrust control
|
||||||
desaturate(motors[MOTOR_FRONT_LEFT], motors[MOTOR_FRONT_RIGHT], motors[MOTOR_REAR_LEFT], motors[MOTOR_REAR_RIGHT]);
|
desaturate(motors[MOTOR_FRONT_LEFT], motors[MOTOR_FRONT_RIGHT], motors[MOTOR_REAR_LEFT], motors[MOTOR_REAR_RIGHT]);
|
||||||
|
|
||||||
motors[0] = constrain(motors[0], 0, 1);
|
motors[0] = constrain(motors[0], 0, 1);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#include "quaternion.h"
|
#include "quaternion.h"
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
#include "lpf.h"
|
#include "filter.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
Vector rates; // estimated angular rates, rad/s
|
Vector rates; // estimated angular rates, rad/s
|
||||||
@@ -32,8 +32,7 @@ void applyGyro() {
|
|||||||
|
|
||||||
void applyAcc() {
|
void applyAcc() {
|
||||||
// test should we apply accelerometer gravity correction
|
// test should we apply accelerometer gravity correction
|
||||||
float accNorm = acc.norm();
|
landed = !motorsActive() && abs(acc.norm() - ONE_G) < ONE_G * 0.1f;
|
||||||
landed = !motorsActive() && abs(accNorm - ONE_G) < ONE_G * 0.1f;
|
|
||||||
|
|
||||||
if (!landed) return;
|
if (!landed) return;
|
||||||
|
|
||||||
@@ -47,6 +46,7 @@ void applyAcc() {
|
|||||||
|
|
||||||
void applyLevel() {
|
void applyLevel() {
|
||||||
if (landed) return;
|
if (landed) return;
|
||||||
|
if (thrustTarget < 0.1) return; // skip at idle thrust
|
||||||
|
|
||||||
// assume the pilot keeps the drone more or less level in flight
|
// assume the pilot keeps the drone more or less level in flight
|
||||||
Vector up = Quaternion::rotateVector(Vector(0, 0, 1), attitude);
|
Vector up = Quaternion::rotateVector(Vector(0, 0, 1), attitude);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ extern float motors[4];
|
|||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
print("Initializing flix\n");
|
print("Initializing Flix\n");
|
||||||
setupParameters();
|
setupParameters();
|
||||||
setupPower();
|
setupPower();
|
||||||
setupLED();
|
setupLED();
|
||||||
|
|||||||
@@ -4,12 +4,17 @@
|
|||||||
// 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 "lpf.h"
|
#include "filter.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
MPU9250 imu(SPI);
|
IMU *imu;
|
||||||
|
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
|
||||||
@@ -23,27 +28,42 @@ LowPassFilter<Vector> gyroBiasFilter(0.001);
|
|||||||
|
|
||||||
void setupIMU() {
|
void setupIMU() {
|
||||||
print("Setup IMU\n");
|
print("Setup IMU\n");
|
||||||
imu.begin();
|
free(imu);
|
||||||
|
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
|
||||||
acc = (acc - accBias) / accScale;
|
acc = (acc - accBias) / accScale;
|
||||||
gyro = gyro - gyroBias;
|
gyro = gyro - gyroBias;
|
||||||
// rotate to body frame
|
|
||||||
|
// Rotate to body frame
|
||||||
Quaternion rotation = Quaternion::fromEuler(imuRotation);
|
Quaternion rotation = Quaternion::fromEuler(imuRotation);
|
||||||
acc = Quaternion::rotateVector(acc, rotation.inversed());
|
acc = Quaternion::rotateVector(acc, rotation.inversed());
|
||||||
gyro = Quaternion::rotateVector(gyro, rotation.inversed());
|
gyro = Quaternion::rotateVector(gyro, rotation.inversed());
|
||||||
@@ -52,12 +72,13 @@ void readIMU() {
|
|||||||
void calibrateGyroOnce() {
|
void calibrateGyroOnce() {
|
||||||
static Delay landedDelay(2);
|
static Delay landedDelay(2);
|
||||||
if (!landedDelay.update(landed)) return; // calibrate only if definitely stationary
|
if (!landedDelay.update(landed)) return; // calibrate only if definitely stationary
|
||||||
|
|
||||||
gyroBias = gyroBiasFilter.update(gyro);
|
gyroBias = gyroBiasFilter.update(gyro);
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
@@ -91,9 +112,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;
|
||||||
@@ -105,6 +126,7 @@ void calibrateAccelOnce() {
|
|||||||
if (acc.x < accMin.x) accMin.x = acc.x;
|
if (acc.x < accMin.x) accMin.x = acc.x;
|
||||||
if (acc.y < accMin.y) accMin.y = acc.y;
|
if (acc.y < accMin.y) accMin.y = acc.y;
|
||||||
if (acc.z < accMin.z) accMin.z = acc.z;
|
if (acc.z < accMin.z) accMin.z = acc.z;
|
||||||
|
|
||||||
// Compute scale and bias
|
// Compute scale and bias
|
||||||
accScale = (accMax - accMin) / 2 / ONE_G;
|
accScale = (accMax - accMin) / 2 / ONE_G;
|
||||||
accBias = (accMax + accMin) / 2;
|
accBias = (accMax + accMin) / 2;
|
||||||
@@ -117,16 +139,18 @@ 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("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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,14 @@ extern float controlTime;
|
|||||||
extern float voltage;
|
extern float voltage;
|
||||||
|
|
||||||
int mavlinkSysId = 1;
|
int mavlinkSysId = 1;
|
||||||
Rate telemetryFast(10);
|
|
||||||
Rate telemetrySlow(2);
|
|
||||||
|
|
||||||
bool mavlinkConnected = false;
|
Rate telemetrySlow(2);
|
||||||
|
Rate telemetryAttitude(20);
|
||||||
|
Rate telemetryRC(10);
|
||||||
|
Rate telemetryMotors(10);
|
||||||
|
Rate telemetryIMU(15);
|
||||||
|
|
||||||
|
float mavlinkTime = NAN; // time of last received message
|
||||||
String mavlinkPrintBuffer;
|
String mavlinkPrintBuffer;
|
||||||
|
|
||||||
void processMavlink() {
|
void processMavlink() {
|
||||||
@@ -34,36 +38,46 @@ void sendMavlink() {
|
|||||||
((mode == AUTO) ? MAV_MODE_FLAG_AUTO_ENABLED : MAV_MODE_FLAG_MANUAL_INPUT_ENABLED),
|
((mode == AUTO) ? MAV_MODE_FLAG_AUTO_ENABLED : MAV_MODE_FLAG_MANUAL_INPUT_ENABLED),
|
||||||
mode, MAV_STATE_STANDBY);
|
mode, MAV_STATE_STANDBY);
|
||||||
sendMessage(&msg);
|
sendMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
if (!mavlinkConnected) return; // send only heartbeat until connected
|
if (!valid(mavlinkTime)) return; // send only heartbeat until connected
|
||||||
|
|
||||||
|
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,
|
||||||
MAV_VTOL_STATE_UNDEFINED, landed ? MAV_LANDED_STATE_ON_GROUND : MAV_LANDED_STATE_IN_AIR);
|
MAV_VTOL_STATE_UNDEFINED, landed ? MAV_LANDED_STATE_ON_GROUND : MAV_LANDED_STATE_IN_AIR);
|
||||||
sendMessage(&msg);
|
sendMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t voltages[] = {voltage * 1000, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
if (telemetrySlow && valid(voltage)) {
|
||||||
|
uint16_t voltages[] = {(uint16_t)(voltage * 1000), UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX, UINT16_MAX};
|
||||||
uint16_t voltagesExt[] = {0, 0, 0, 0};
|
uint16_t voltagesExt[] = {0, 0, 0, 0};
|
||||||
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);
|
||||||
if (valid(voltage)) sendMessage(&msg);
|
sendMessage(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (telemetryFast && mavlinkConnected) {
|
if (telemetryAttitude) {
|
||||||
const float offset[] = {0, 0, 0, 0};
|
const float offset[] = {0, 0, 0, 0};
|
||||||
mavlink_msg_attitude_quaternion_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg,
|
mavlink_msg_attitude_quaternion_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg,
|
||||||
time, attitude.w, attitude.x, -attitude.y, -attitude.z, rates.x, -rates.y, -rates.z, offset); // convert to frd
|
time, attitude.w, attitude.x, -attitude.y, -attitude.z, rates.x, -rates.y, -rates.z, offset); // convert to frd
|
||||||
sendMessage(&msg);
|
sendMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telemetryRC && channels[0]) { // 0 means no RC input
|
||||||
mavlink_msg_rc_channels_raw_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg, controlTime * 1000, 0,
|
mavlink_msg_rc_channels_raw_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg, controlTime * 1000, 0,
|
||||||
channels[0], channels[1], channels[2], channels[3], channels[4], channels[5], channels[6], channels[7], UINT8_MAX);
|
channels[0], channels[1], channels[2], channels[3], channels[4], channels[5], channels[6], channels[7], UINT8_MAX);
|
||||||
if (channels[0] != 0) sendMessage(&msg); // 0 means no RC input
|
sendMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telemetryMotors) {
|
||||||
float controls[8];
|
float controls[8];
|
||||||
memcpy(controls, motors, sizeof(motors));
|
memcpy(controls, motors, sizeof(motors));
|
||||||
mavlink_msg_actuator_control_target_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg, time, 0, controls);
|
mavlink_msg_actuator_control_target_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg, time, 0, controls);
|
||||||
sendMessage(&msg);
|
sendMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (telemetryIMU) {
|
||||||
mavlink_msg_scaled_imu_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg, time,
|
mavlink_msg_scaled_imu_pack(mavlinkSysId, MAV_COMP_ID_AUTOPILOT1, &msg, time,
|
||||||
acc.x / ONE_G * 1000, -acc.y / ONE_G * 1000, -acc.z / ONE_G * 1000, // convert to frd
|
acc.x / ONE_G * 1000, -acc.y / ONE_G * 1000, -acc.z / ONE_G * 1000, // convert to frd
|
||||||
gyro.x * 1000, -gyro.y * 1000, -gyro.z * 1000,
|
gyro.x * 1000, -gyro.y * 1000, -gyro.z * 1000,
|
||||||
@@ -81,13 +95,13 @@ void sendMessage(const void *msg) {
|
|||||||
void receiveMavlink() {
|
void receiveMavlink() {
|
||||||
uint8_t buf[MAVLINK_MAX_PACKET_LEN];
|
uint8_t buf[MAVLINK_MAX_PACKET_LEN];
|
||||||
int len = receiveWiFi(buf, MAVLINK_MAX_PACKET_LEN);
|
int len = receiveWiFi(buf, MAVLINK_MAX_PACKET_LEN);
|
||||||
if (len) mavlinkConnected = true;
|
|
||||||
|
|
||||||
// New packet, parse it
|
// New packet, parse it
|
||||||
mavlink_message_t msg;
|
mavlink_message_t msg;
|
||||||
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;
|
||||||
handleMavlink(&msg);
|
handleMavlink(&msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +255,7 @@ void handleMavlink(const void *_msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m.command == MAV_CMD_COMPONENT_ARM_DISARM) {
|
if (m.command == MAV_CMD_COMPONENT_ARM_DISARM) {
|
||||||
if (m.param1 && controlThrottle > 0.05) return; // don't arm if throttle is not low
|
if (m.param1 == 1 && controlThrottle > 0.05) return; // don't arm if throttle is not low
|
||||||
accepted = true;
|
accepted = true;
|
||||||
armed = m.param1 == 1;
|
armed = m.param1 == 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,30 +14,29 @@ int pwmStop = 0;
|
|||||||
int pwmMin = 0;
|
int pwmMin = 0;
|
||||||
int pwmMax = -1; // -1 means duty cycle mode
|
int pwmMax = -1; // -1 means duty cycle mode
|
||||||
|
|
||||||
const int MOTOR_REAR_LEFT = 0;
|
const int MOTOR_REAR_LEFT = 0, MOTOR_REAR_RIGHT = 1, MOTOR_FRONT_RIGHT = 2, MOTOR_FRONT_LEFT = 3;
|
||||||
const int MOTOR_REAR_RIGHT = 1;
|
|
||||||
const int MOTOR_FRONT_RIGHT = 2;
|
|
||||||
const int MOTOR_FRONT_LEFT = 3;
|
|
||||||
|
|
||||||
void setupMotors() {
|
void setupMotors() {
|
||||||
print("Setup Motors\n");
|
print("Setup motors\n");
|
||||||
// configure pins
|
// Configure pins
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (motorPins[i] < 0) continue; // skip unassigned motors
|
||||||
ledcAttach(motorPins[i], pwmFrequency, pwmResolution);
|
ledcAttach(motorPins[i], pwmFrequency, pwmResolution);
|
||||||
pwmFrequency = ledcChangeFrequency(motorPins[i], pwmFrequency, pwmResolution); // when reconfiguring
|
pwmFrequency = ledcChangeFrequency(motorPins[i], pwmFrequency, pwmResolution); // when reconfiguring
|
||||||
}
|
}
|
||||||
sendMotors();
|
sendMotors();
|
||||||
print("Motors initialized\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sendMotors() {
|
void sendMotors() {
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (motorPins[i] < 0) continue; // skip unassigned motors
|
||||||
ledcWrite(motorPins[i], getDutyCycle(motors[i]));
|
ledcWrite(motorPins[i], getDutyCycle(motors[i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int getDutyCycle(float value) {
|
int getDutyCycle(float value) {
|
||||||
value = constrain(value, 0, 1);
|
value = constrain(value, 0, 1);
|
||||||
|
|
||||||
if (pwmMax >= 0) { // pwm mode
|
if (pwmMax >= 0) { // pwm mode
|
||||||
float pwm = mapf(value, 0, 1, pwmMin, pwmMax);
|
float pwm = mapf(value, 0, 1, pwmMin, pwmMax);
|
||||||
if (value == 0) pwm = pwmStop;
|
if (value == 0) pwm = pwmStop;
|
||||||
@@ -52,9 +51,9 @@ bool motorsActive() {
|
|||||||
return motors[0] != 0 || motors[1] != 0 || motors[2] != 0 || motors[3] != 0;
|
return motors[0] != 0 || motors[1] != 0 || motors[2] != 0 || motors[3] != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void testMotor(int n) {
|
void testMotor(int n, float thrust) {
|
||||||
print("Testing motor %d\n", n);
|
print("Testing motor %d\n", n);
|
||||||
motors[n] = 0.2;
|
motors[n] = thrust;
|
||||||
delay(50); // ESP32 may need to wait until the end of the current cycle to change duty https://github.com/espressif/arduino-esp32/issues/5306
|
delay(50); // ESP32 may need to wait until the end of the current cycle to change duty https://github.com/espressif/arduino-esp32/issues/5306
|
||||||
sendMotors();
|
sendMotors();
|
||||||
pause(3);
|
pause(3);
|
||||||
|
|||||||
@@ -6,22 +6,23 @@
|
|||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
extern int channelZero[16];
|
extern int channelZero[16], 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, voltagePin;
|
||||||
extern int wifiMode, wifiLongRange, udpLocalPort, udpRemotePort, espnowChannel;
|
extern int wifiMode, wifiLongRange, wifiBroadcast, udpLocalPort, udpRemotePort, espnowChannel;
|
||||||
extern float rcLossTimeout, descendTime;
|
extern float rcLossTimeout, descendTime, disarmTilt;
|
||||||
extern int voltagePin;
|
|
||||||
extern float voltageScale;
|
extern float voltageScale;
|
||||||
extern LowPassFilter<float> voltageFilter;
|
extern LowPassFilter<float> voltageFilter;
|
||||||
|
|
||||||
|
#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
|
||||||
bool integer;
|
bool integer;
|
||||||
union { float *f; int *i; }; // pointer to the variable
|
union { float *f; int *i; }; // pointer to the variable
|
||||||
|
float initial; // 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, float *variable, void (*callback)() = nullptr) : name(name), integer(false), f(variable), callback(callback) {};
|
Parameter(const char *name, float *variable, void (*callback)() = nullptr) : name(name), integer(false), f(variable), callback(callback) {};
|
||||||
@@ -45,6 +46,7 @@ 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},
|
||||||
@@ -61,6 +63,15 @@ Parameter parameters[] = {
|
|||||||
{"CTL_FLT_MODE_1", &flightModes[1]},
|
{"CTL_FLT_MODE_1", &flightModes[1]},
|
||||||
{"CTL_FLT_MODE_2", &flightModes[2]},
|
{"CTL_FLT_MODE_2", &flightModes[2]},
|
||||||
// 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},
|
||||||
@@ -113,12 +124,16 @@ 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
|
||||||
{"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_ATT", &telemetryAttitude.rate},
|
||||||
|
{"MAV_RATE_RC", &telemetryRC.rate},
|
||||||
|
{"MAV_RATE_MOT", &telemetryMotors.rate},
|
||||||
|
{"MAV_RATE_IMU", &telemetryIMU.rate},
|
||||||
// power
|
// power
|
||||||
{"PWR_VOLT_PIN", &voltagePin, setupPower},
|
{"PWR_VOLT_PIN", &voltagePin, setupPower},
|
||||||
{"PWR_VOLT_SCALE", &voltageScale},
|
{"PWR_VOLT_SCALE", &voltageScale},
|
||||||
@@ -126,17 +141,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},
|
||||||
};
|
};
|
||||||
|
|
||||||
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 ¶meter : parameters) {
|
for (auto ¶meter : parameters) {
|
||||||
if (!storage.isKey(parameter.name)) {
|
parameter.initial = parameter.getValue();
|
||||||
storage.putFloat(parameter.name, parameter.getValue()); // store default value
|
if (storage.isKey(parameter.name)) {
|
||||||
|
parameter.setValue(storage.getFloat(parameter.name));
|
||||||
}
|
}
|
||||||
parameter.setValue(storage.getFloat(parameter.name, 0));
|
|
||||||
parameter.cache = parameter.getValue();
|
parameter.cache = parameter.getValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -182,17 +199,23 @@ 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 ¶meter : parameters) {
|
for (auto ¶meter : parameters) {
|
||||||
if (parameter.getValue() == parameter.cache) continue; // no change
|
if (floatEquals(parameter.getValue(), parameter.cache)) continue; // no change
|
||||||
if (isnan(parameter.getValue()) && isnan(parameter.cache)) continue; // both are NAN
|
|
||||||
|
|
||||||
storage.putFloat(parameter.name, parameter.getValue());
|
storage.putFloat(parameter.name, parameter.getValue());
|
||||||
parameter.cache = parameter.getValue(); // update cache
|
parameter.cache = parameter.getValue(); // update cache
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void printParameters() {
|
void printParameters(const char *filter) {
|
||||||
|
print("Name Value [Default]\n");
|
||||||
for (auto ¶meter : parameters) {
|
for (auto ¶meter : parameters) {
|
||||||
print("%s = %g\n", parameter.name, parameter.getValue());
|
if (strncasecmp(parameter.name, filter, strlen(filter))) continue;
|
||||||
|
|
||||||
|
if (floatEquals(parameter.getValue(), parameter.initial)) { // parameter changed
|
||||||
|
print("%-15s %-13g\n", parameter.name, parameter.getValue());
|
||||||
|
} else {
|
||||||
|
print("%-15s %-13g [%g]\n", parameter.name, parameter.getValue(), parameter.initial);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "lpf.h"
|
#include "filter.h"
|
||||||
|
|
||||||
class PID {
|
class PID {
|
||||||
public:
|
public:
|
||||||
@@ -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, float d, float windup = 0, float dAlpha = 1, float dtMax = 0.1) :
|
PID(float p, float i = 0, float d = 0, float windup = INFINITY, 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) {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
|
|
||||||
#include <soc/soc.h>
|
#include <soc/soc.h>
|
||||||
#include <soc/rtc_cntl_reg.h>
|
#include <soc/rtc_cntl_reg.h>
|
||||||
#include "lpf.h"
|
#include "filter.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
float voltage = NAN;
|
float voltage = NAN;
|
||||||
LowPassFilter<float> voltageFilter(0.2);
|
LowPassFilter<float> voltageFilter(1);
|
||||||
int voltagePin = -1;
|
int voltagePin = -1;
|
||||||
float voltageScale = 2;
|
float voltageScale = 2;
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ void setupPower() {
|
|||||||
|
|
||||||
void readVoltage() {
|
void readVoltage() {
|
||||||
if (voltagePin < 0) return;
|
if (voltagePin < 0) return;
|
||||||
|
|
||||||
static Rate rate(10);
|
static Rate rate(10);
|
||||||
if (!rate) return;
|
if (!rate) return;
|
||||||
|
|
||||||
|
|||||||
@@ -27,14 +27,12 @@ void setupRC() {
|
|||||||
|
|
||||||
bool readRC() {
|
bool readRC() {
|
||||||
if (rcRxPin < 0) return false;
|
if (rcRxPin < 0) return false;
|
||||||
if (rc.read()) {
|
if (!rc.read()) return false;
|
||||||
SBUSData data = rc.data();
|
|
||||||
for (int i = 0; i < 16; i++) channels[i] = data.ch[i]; // copy channels data
|
rc.getChannels(channels);
|
||||||
normalizeRC();
|
normalizeRC();
|
||||||
controlTime = t;
|
controlTime = t;
|
||||||
return true;
|
return true;
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void normalizeRC() {
|
void normalizeRC() {
|
||||||
@@ -55,6 +53,7 @@ void calibrateRC() {
|
|||||||
print("RC_RX_PIN = %d, set the RC pin!\n", rcRxPin);
|
print("RC_RX_PIN = %d, set the RC pin!\n", rcRxPin);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t zero[16]; // for zero positions
|
uint16_t zero[16]; // for zero positions
|
||||||
uint16_t center[16]; // for center positions
|
uint16_t center[16]; // for center positions
|
||||||
uint16_t _[16]; // for unused data
|
uint16_t _[16]; // for unused data
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ extern float controlRoll, controlPitch, controlThrottle, controlYaw;
|
|||||||
|
|
||||||
float rcLossTimeout = 1;
|
float rcLossTimeout = 1;
|
||||||
float descendTime = 10;
|
float descendTime = 10;
|
||||||
|
float disarmTilt = radians(120);
|
||||||
|
|
||||||
void failsafe() {
|
void failsafe() {
|
||||||
rcLossFailsafe();
|
rcLossFailsafe();
|
||||||
autoFailsafe();
|
autoFailsafe();
|
||||||
|
tiltFailsafe();
|
||||||
}
|
}
|
||||||
|
|
||||||
// RC loss failsafe
|
// RC loss failsafe
|
||||||
@@ -36,7 +38,7 @@ void descend() {
|
|||||||
// Allow pilot to interrupt automatic flight
|
// Allow pilot to interrupt automatic flight
|
||||||
void autoFailsafe() {
|
void autoFailsafe() {
|
||||||
static float roll, pitch, yaw, throttle;
|
static float roll, pitch, yaw, throttle;
|
||||||
if (roll != controlRoll || pitch != controlPitch || yaw != controlYaw || abs(throttle - controlThrottle) > 0.05) {
|
if (abs(roll - controlRoll) > 0.05 || abs(pitch - controlPitch) > 0.05 || abs(yaw - controlYaw) > 0.05 || abs(throttle - controlThrottle) > 0.05) {
|
||||||
// controls changed and mode switch is not configured
|
// controls changed and mode switch is not configured
|
||||||
if (mode == AUTO && invalid(controlMode)) mode = STAB; // regain control by the pilot
|
if (mode == AUTO && invalid(controlMode)) mode = STAB; // regain control by the pilot
|
||||||
}
|
}
|
||||||
@@ -45,3 +47,15 @@ void autoFailsafe() {
|
|||||||
yaw = controlYaw;
|
yaw = controlYaw;
|
||||||
throttle = controlThrottle;
|
throttle = controlThrottle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Disarm if tilted too much
|
||||||
|
void tiltFailsafe() {
|
||||||
|
if (!armed) return;
|
||||||
|
if (mode != STAB) return;
|
||||||
|
|
||||||
|
Vector up = Quaternion::rotateVector(Vector(0, 0, 1), attitude);
|
||||||
|
float tilt = acos(up.z);
|
||||||
|
if (disarmTilt && tilt > disarmTilt) {
|
||||||
|
armed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,12 @@ bool valid(float x) {
|
|||||||
return isfinite(x);
|
return isfinite(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool floatEquals(float a, float b, float epsilon = 0) {
|
||||||
|
if (isnan(a) && isnan(b)) return true;
|
||||||
|
if (a == b) return true;
|
||||||
|
return fabsf(a - b) <= epsilon;
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap angle to [-PI, PI)
|
// Wrap angle to [-PI, PI)
|
||||||
float wrapAngle(float angle) {
|
float wrapAngle(float angle) {
|
||||||
angle = fmodf(angle, 2 * PI);
|
angle = fmodf(angle, 2 * PI);
|
||||||
@@ -47,13 +53,14 @@ 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
|
// Simplified ESP-NOW Serial without resends
|
||||||
class ESPNOWSerial : public ESP_NOW_Serial_Class {
|
class ESPNOWSerial : public ESP_NOW_Serial_Class {
|
||||||
public:
|
public:
|
||||||
|
int lost = 0;
|
||||||
using ESP_NOW_Serial_Class::ESP_NOW_Serial_Class;
|
using ESP_NOW_Serial_Class::ESP_NOW_Serial_Class;
|
||||||
void onSent(bool success) override {} // disable resends
|
void onSent(bool success) override {
|
||||||
size_t write(const uint8_t *data, size_t len) override {
|
if (!success) lost++;
|
||||||
return ESP_NOW_Peer::send(data, len); // pure send without buffering
|
ESP_NOW_Serial_Class::onSent(true); // always report success to avoid resends
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -61,10 +68,13 @@ public:
|
|||||||
class Rate {
|
class Rate {
|
||||||
public:
|
public:
|
||||||
float rate;
|
float rate;
|
||||||
float last = 0;
|
float last = -INFINITY;
|
||||||
Rate(float rate) : rate(rate) {}
|
Rate(float rate) : rate(rate) {}
|
||||||
|
|
||||||
operator bool() {
|
operator bool() {
|
||||||
|
if (t == last) {
|
||||||
|
return true; // the same step
|
||||||
|
}
|
||||||
if (t - last >= 1 / rate) {
|
if (t - last >= 1 / rate) {
|
||||||
last = t;
|
last = t;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
#include <MacAddress.h>
|
#include <MacAddress.h>
|
||||||
#include <ESP32_NOW_Serial.h>
|
#include <ESP32_NOW_Serial.h>
|
||||||
#include "Preferences.h"
|
#include <Preferences.h>
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
extern Preferences storage; // use the main preferences storage
|
extern Preferences storage; // use the main preferences storage
|
||||||
@@ -17,6 +17,7 @@ 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";
|
||||||
@@ -33,10 +34,14 @@ void setupWiFi() {
|
|||||||
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);
|
udp.begin(udpLocalPort);
|
||||||
} else if (wifiMode == W_STA) {
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
udp.begin(udpLocalPort);
|
udp.begin(udpLocalPort);
|
||||||
} else if (wifiMode == W_ESPNOW) {
|
}
|
||||||
|
|
||||||
|
if (wifiMode == W_ESPNOW) {
|
||||||
WiFi.mode(WIFI_AP);
|
WiFi.mode(WIFI_AP);
|
||||||
WiFi.setChannel(espnowChannel);
|
WiFi.setChannel(espnowChannel);
|
||||||
espnow.addr(MacAddress(storage.getString("ESPNOW_PEER_MAC", "FF:FF:FF:FF:FF:FF").c_str()));
|
espnow.addr(MacAddress(storage.getString("ESPNOW_PEER_MAC", "FF:FF:FF:FF:FF:FF").c_str()));
|
||||||
@@ -52,14 +57,16 @@ void setupWiFi() {
|
|||||||
void sendWiFi(const uint8_t *buf, int len) {
|
void sendWiFi(const uint8_t *buf, int len) {
|
||||||
if (espnow) {
|
if (espnow) {
|
||||||
espnow.write(buf, len);
|
espnow.write(buf, len);
|
||||||
|
|
||||||
static Rate discovery(2);
|
static Rate discovery(2);
|
||||||
if (discovery) espnowBroadcast.write((const uint8_t *)"flix", 4); // broadcast message to help finding this device
|
if (espnow.isEncrypted() && discovery) espnowBroadcast.write((const uint8_t *)"flix", 4); // broadcast message to help finding this device
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return;
|
if (WiFi.softAPgetStationNum() == 0 && !WiFi.isConnected()) return;
|
||||||
|
|
||||||
udp.beginPacket(udpRemoteIP, udpRemotePort);
|
bool broadcast = wifiBroadcast || !(t - mavlinkTime < 5); // broadcast if lost connection
|
||||||
|
udp.beginPacket(broadcast ? IPAddress(255, 255, 255, 255) : udpRemoteIP, udpRemotePort);
|
||||||
udp.write(buf, len);
|
udp.write(buf, len);
|
||||||
udp.endPacket();
|
udp.endPacket();
|
||||||
}
|
}
|
||||||
@@ -85,6 +92,7 @@ void printWiFiInfo() {
|
|||||||
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", espnow.getChannel());
|
||||||
|
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");
|
||||||
print("MAC: %s\n", WiFi.softAPmacAddress().c_str());
|
print("MAC: %s\n", WiFi.softAPmacAddress().c_str());
|
||||||
@@ -107,7 +115,7 @@ void printWiFiInfo() {
|
|||||||
} else {
|
} else {
|
||||||
print("Mode: Disabled\n");
|
print("Mode: Disabled\n");
|
||||||
}
|
}
|
||||||
print("MAVLink connected: %d\n", mavlinkConnected);
|
print("MAVLink connected: %d\n", valid(mavlinkTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
void configWiFi(int mode, const char *first, const char *second) {
|
void configWiFi(int mode, const char *first, const char *second) {
|
||||||
@@ -127,3 +135,20 @@ void configWiFi(int mode, const char *first, const char *second) {
|
|||||||
}
|
}
|
||||||
print("✓ Reboot to apply new settings\n");
|
print("✓ Reboot to apply new settings\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setWiFiMode(const String& mode) {
|
||||||
|
if (mode == "ap") {
|
||||||
|
wifiMode = W_AP;
|
||||||
|
} else if (mode == "sta") {
|
||||||
|
wifiMode = W_STA;
|
||||||
|
} else if (mode == "espnow") {
|
||||||
|
wifiMode = W_ESPNOW;
|
||||||
|
} else if (mode == "off") {
|
||||||
|
wifiMode = W_DISABLED;
|
||||||
|
} else {
|
||||||
|
print("Invalid Wi-Fi mode\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
static const char *modes[] = {"Disabled", "Access Point (AP)", "Client (STA)", "ESP-NOW"};
|
||||||
|
print("✓ Wi-Fi mode set to %s, reboot to apply\n", modes[wifiMode]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,12 +15,11 @@ public:
|
|||||||
SBUS(HardwareSerial& bus, const int8_t rxpin, const int8_t txpin, const bool inv = true) {};
|
SBUS(HardwareSerial& bus, const int8_t rxpin, const int8_t txpin, const bool inv = true) {};
|
||||||
void begin(int rxpin = -1, int txpin = -1, bool inv = true, bool fast = false) {};
|
void begin(int rxpin = -1, int txpin = -1, bool inv = true, bool fast = false) {};
|
||||||
bool read() { return joystickInit(); };
|
bool read() { return joystickInit(); };
|
||||||
SBUSData data() {
|
void getChannels(uint16_t (&channels)[16]) const {
|
||||||
SBUSData data;
|
int16_t ch[16];
|
||||||
joystickGet(data.ch);
|
joystickGet(ch);
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
data.ch[i] = map(data.ch[i], -32768, 32767, 1000, 2000); // convert to pulse width style
|
channels[i] = map(ch[i], -32768, 32767, 1000, 2000); // convert to pulse width style
|
||||||
}
|
}
|
||||||
return data;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
#include "quaternion.h"
|
#include "quaternion.h"
|
||||||
#include "Arduino.h"
|
#include "Arduino.h"
|
||||||
#include "wifi.h"
|
#include "wifi.h"
|
||||||
#include "lpf.h"
|
#include "filter.h"
|
||||||
|
|
||||||
extern float t, dt;
|
extern float t, dt;
|
||||||
extern float controlRoll, controlPitch, controlYaw, controlThrottle, controlMode;
|
extern float controlRoll, controlPitch, controlYaw, controlThrottle, controlMode;
|
||||||
@@ -21,6 +21,9 @@ 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();
|
||||||
@@ -38,7 +41,7 @@ const char* getModeName();
|
|||||||
void sendMotors();
|
void sendMotors();
|
||||||
int getDutyCycle(float value);
|
int getDutyCycle(float value);
|
||||||
bool motorsActive();
|
bool motorsActive();
|
||||||
void testMotor(int n);
|
void testMotor(int, float);
|
||||||
void print(const char* format, ...);
|
void print(const char* format, ...);
|
||||||
void pause(float duration);
|
void pause(float duration);
|
||||||
void doCommand(String str, bool echo);
|
void doCommand(String str, bool echo);
|
||||||
@@ -63,19 +66,20 @@ void failsafe();
|
|||||||
void rcLossFailsafe();
|
void rcLossFailsafe();
|
||||||
void descend();
|
void descend();
|
||||||
void autoFailsafe();
|
void autoFailsafe();
|
||||||
|
void tiltFailsafe();
|
||||||
int parametersCount();
|
int parametersCount();
|
||||||
const char *getParameterName(int index);
|
const char *getParameterName(int index);
|
||||||
float getParameter(int index);
|
float getParameter(int index);
|
||||||
float getParameter(const char *name);
|
float getParameter(const char *name);
|
||||||
bool setParameter(const char *name, const float value);
|
bool setParameter(const char *name, const float value);
|
||||||
void printParameters();
|
void printParameters(const char *filter);
|
||||||
void resetParameters();
|
void resetParameters();
|
||||||
|
|
||||||
// mocks
|
// mocks
|
||||||
void setLED(bool on) {};
|
void setLED(bool on) {};
|
||||||
void calibrateGyro() { print("Skip gyro calibrating\n"); };
|
|
||||||
void calibrateAccel() { print("Skip accel calibrating\n"); };
|
void calibrateAccel() { print("Skip accel calibrating\n"); };
|
||||||
void printIMUCalibration() { print("cal: N/A\n"); };
|
void printIMUCalibration() { print("cal: N/A\n"); };
|
||||||
void printIMUInfo() {};
|
void printIMUInfo() {};
|
||||||
void printWiFiInfo() {};
|
void printWiFiInfo() {};
|
||||||
void configWiFi(bool, const char*, const char*) { print("Skip WiFi config\n"); };
|
void configWiFi(bool, const char*, const char*) { print("Skip WiFi config\n"); };
|
||||||
|
void setWiFiMode(const String& mode) { print("Skip WiFi mode set\n"); };
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
#include "estimate.ino"
|
#include "estimate.ino"
|
||||||
#include "safety.ino"
|
#include "safety.ino"
|
||||||
#include "log.ino"
|
#include "log.ino"
|
||||||
#include "lpf.h"
|
#include "filter.h"
|
||||||
#include "mavlink.ino"
|
#include "mavlink.ino"
|
||||||
#include "motors.ino"
|
#include "motors.ino"
|
||||||
#include "parameters.ino"
|
#include "parameters.ino"
|
||||||
@@ -55,6 +55,7 @@ public:
|
|||||||
initNode();
|
initNode();
|
||||||
Serial.begin(0);
|
Serial.begin(0);
|
||||||
setupParameters();
|
setupParameters();
|
||||||
|
rcRxPin = 1; // set rc pin to enable rc reading
|
||||||
gzmsg << "Flix plugin loaded" << endl;
|
gzmsg << "Flix plugin loaded" << endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,13 @@
|
|||||||
#include <sys/poll.h>
|
#include <sys/poll.h>
|
||||||
#include <gazebo/gazebo.hh>
|
#include <gazebo/gazebo.hh>
|
||||||
|
|
||||||
int wifiMode = 1; // mock
|
// Mocks
|
||||||
|
int wifiMode = 1;
|
||||||
|
int wifiLongRange = 0;
|
||||||
|
int wifiBroadcast = 0;
|
||||||
|
int espnowChannel = 6;
|
||||||
|
const int W_DISABLED = 0, W_AP = 1, W_STA = 2, W_ESPNOW = 3;
|
||||||
|
|
||||||
int udpLocalPort = 14580;
|
int udpLocalPort = 14580;
|
||||||
int udpRemotePort = 14550;
|
int udpRemotePort = 14550;
|
||||||
const char *udpRemoteIP = "255.255.255.255";
|
const char *udpRemoteIP = "255.255.255.255";
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ from pyflix import Flix
|
|||||||
flix = Flix() # create a Flix object and wait for connection
|
flix = Flix() # create a Flix object and wait for connection
|
||||||
```
|
```
|
||||||
|
|
||||||
|
If using ESP-NOW connection, specify the proxy device name in `FLIX_DEVICE` environment variable or pass it to the constructor: `Flix(device='/dev/cu.usbserial-0001')`.
|
||||||
|
|
||||||
### Telemetry
|
### Telemetry
|
||||||
|
|
||||||
Basic telemetry is available through object properties. The property names generally match the corresponding variables in the firmware code:
|
Basic telemetry is available through object properties. The property names generally match the corresponding variables in the firmware code:
|
||||||
@@ -220,6 +222,13 @@ The following scripts demonstrate how to use the library:
|
|||||||
* [`log.py`](../log.py) — download flight logs from the drone.
|
* [`log.py`](../log.py) — download flight logs from the drone.
|
||||||
* [`example.py`](../example.py) — a simple example, prints telemetry data and waits for events.
|
* [`example.py`](../example.py) — a simple example, prints telemetry data and waits for events.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Set `FLIX_DEVICE` environment variable to use these tools with ESP-NOW connection, for example:
|
||||||
|
>
|
||||||
|
> ```bash
|
||||||
|
> FLIX_DEVICE=/dev/cu.usbserial-0001 tools/cli.py
|
||||||
|
> ```
|
||||||
|
|
||||||
## Advanced usage
|
## Advanced usage
|
||||||
|
|
||||||
### MAVLink
|
### MAVLink
|
||||||
|
|||||||
@@ -44,22 +44,27 @@ class Flix:
|
|||||||
_print_buffer: str = ''
|
_print_buffer: str = ''
|
||||||
_modes = ['RAW', 'ACRO', 'STAB', 'AUTO']
|
_modes = ['RAW', 'ACRO', 'STAB', 'AUTO']
|
||||||
|
|
||||||
def __init__(self, system_id: int=1, wait_connection: bool=True):
|
def __init__(self, system_id: int=1, wait_connection: bool=True, device=os.getenv('FLIX_DEVICE')):
|
||||||
if not (0 <= system_id < 256):
|
if not (0 <= system_id < 256):
|
||||||
raise ValueError('system_id must be in range [0, 255]')
|
raise ValueError('system_id must be in range [0, 255]')
|
||||||
self._setup_mavlink()
|
self._setup_mavlink()
|
||||||
self.system_id = system_id
|
self.system_id = system_id
|
||||||
self._init_state()
|
self._init_state()
|
||||||
try:
|
if device is not None:
|
||||||
# Direct connection
|
# User defined connection
|
||||||
logger.debug('Listening on port 14550')
|
logger.debug(f'Connecting to {device}')
|
||||||
self.connection: mavutil.mavfile = mavutil.mavlink_connection('udpin:0.0.0.0:14550', source_system=255) # type: ignore
|
self.connection: mavutil.mavfile = mavutil.mavlink_connection(device, source_system=255) # type: ignore
|
||||||
except OSError as e:
|
else:
|
||||||
if e.errno != errno.EADDRINUSE:
|
try:
|
||||||
raise
|
# Direct connection
|
||||||
# Port busy - using proxy
|
logger.debug('Listening on port 14550')
|
||||||
logger.debug('Listening on port 14555 (proxy)')
|
self.connection: mavutil.mavfile = mavutil.mavlink_connection('udpin:0.0.0.0:14550', source_system=255) # type: ignore
|
||||||
self.connection: mavutil.mavfile = mavutil.mavlink_connection('udpin:0.0.0.0:14555', source_system=254) # type: ignore
|
except OSError as e:
|
||||||
|
if e.errno != errno.EADDRINUSE:
|
||||||
|
raise
|
||||||
|
# Port busy - using proxy
|
||||||
|
logger.debug('Listening on port 14555 (proxy)')
|
||||||
|
self.connection: mavutil.mavfile = mavutil.mavlink_connection('udpin:0.0.0.0:14555', source_system=254) # type: ignore
|
||||||
self.connection.target_system = system_id
|
self.connection.target_system = system_id
|
||||||
self.mavlink: mavlink.MAVLink = self.connection.mav
|
self.mavlink: mavlink.MAVLink = self.connection.mav
|
||||||
self._event_listeners: Dict[str, List[Callable[..., Any]]] = {}
|
self._event_listeners: Dict[str, List[Callable[..., Any]]] = {}
|
||||||
@@ -238,7 +243,7 @@ class Flix:
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _mavlink_to_flu(v: List[float]) -> List[float]:
|
def _mavlink_to_flu(v: Sequence[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
|
||||||
@@ -247,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: List[float]) -> List[float]:
|
def _flu_to_mavlink(v: Sequence[float]) -> List[float]:
|
||||||
return Flix._mavlink_to_flu(v)
|
return Flix._mavlink_to_flu(v) # flu to mavlink is the same as mavlink to flu
|
||||||
|
|
||||||
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:
|
||||||
@@ -315,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: List[float], yaw: Optional[float] = None, wait: bool = False, tolerance: float = 0.1):
|
def set_position(self, position: Sequence[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: List[float], yaw: Optional[float] = None):
|
def set_velocity(self, velocity: Sequence[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: List[float], thrust: float):
|
def set_attitude(self, attitude: Sequence[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,7 +339,7 @@ class Flix:
|
|||||||
[attitude[0], attitude[1], attitude[2], attitude[3]],
|
[attitude[0], attitude[1], attitude[2], attitude[3]],
|
||||||
0, 0, 0, thrust)
|
0, 0, 0, thrust)
|
||||||
|
|
||||||
def set_rates(self, rates: List[float], thrust: float):
|
def set_rates(self, rates: Sequence[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):
|
||||||
@@ -346,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: List[float]):
|
def set_motors(self, motors: Sequence[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):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "pyflix"
|
name = "pyflix"
|
||||||
version = "0.15"
|
version = "0.16"
|
||||||
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"
|
||||||
|
|||||||