mirror of
https://github.com/okalachev/flix.git
synced 2026-08-16 00:38:56 +00:00
Compare commits
170
Commits
6064ee18f3
...
stm
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc94e1afbb | ||
|
|
35ed60a594 | ||
|
|
b855b6d28d | ||
|
|
d829de2a3f | ||
|
|
1cb3d8c25d | ||
|
|
63e39c31be | ||
|
|
cd9fb13dbf | ||
|
|
23e1e7bd22 | ||
|
|
8fe1a525bf | ||
|
|
2838c5cb5a | ||
|
|
3a62c89f56 | ||
|
|
88cdebd4ac | ||
|
|
6df52f1534 | ||
|
|
c8af340344 | ||
|
|
ad6a58d082 | ||
|
|
9b84538cf3 | ||
|
|
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 | ||
|
|
bd2b1bd5de | ||
|
|
4530c05b5c | ||
|
|
3816ae376f | ||
|
|
72a72fde80 | ||
|
|
e53051a349 | ||
|
|
f8a9f1f838 | ||
|
|
76af83fc88 | ||
|
|
dd176180a7 | ||
|
|
48c33c7050 | ||
|
|
35e94f6ea6 | ||
|
|
1f48e379e3 | ||
|
|
ee3c6999ab | ||
|
|
34c6993842 | ||
|
|
b62f2f9427 | ||
|
|
7dfef17165 | ||
|
|
8c8046676b | ||
|
|
702ec9792e | ||
|
|
06e2047097 | ||
|
|
87480476c2 | ||
|
|
68271c508c | ||
|
|
e81e84e7fc | ||
|
|
5f1a938d4f | ||
|
|
bd270db493 | ||
|
|
dbf24ea611 | ||
|
|
08683d696d | ||
|
|
9ca6841558 | ||
|
|
28da2d3c8e | ||
|
|
c6632ae6e4 | ||
|
|
35ca754583 | ||
|
|
2ccda03573 | ||
|
|
485a39e740 | ||
|
|
9bffe5b52f | ||
|
|
d6a79d6c66 | ||
|
|
350a82bfed | ||
|
|
6e439859bc | ||
|
|
835b2243e8 | ||
|
|
ed4e2d87d1 | ||
|
|
51cd5fc691 | ||
|
|
d8591ea2a9 | ||
|
|
c434107eaf | ||
|
|
814427dbfd | ||
|
|
0730ceeffa | ||
|
|
a687303062 | ||
|
|
b2daf2587f | ||
|
|
a8c25d8ac0 | ||
|
|
3e49d41986 | ||
|
|
67430c7aac | ||
|
|
3631743a29 | ||
|
|
3dde380bb7 | ||
|
|
377b21429b | ||
|
|
1ac443d6f8 | ||
|
|
964c0f7bc1 | ||
|
|
40bdaacedb | ||
|
|
7d74f3d5cd | ||
|
|
9fd35ba361 | ||
|
|
ca50f75576 | ||
|
|
e47a31f981 | ||
|
|
7ad3022798 | ||
|
|
5b654e4d8e | ||
|
|
cf10ec6161 | ||
|
|
6d01cd2e79 | ||
|
|
0abb18c616 | ||
|
|
30326a5662 | ||
|
|
dd3575174b | ||
|
|
c0f3301da4 | ||
|
|
a6bad3a69b | ||
|
|
9a9bd07251 | ||
|
|
28f5855a57 | ||
|
|
7e24ee30f7 | ||
|
|
2a8faf5759 | ||
|
|
f4e58a652a | ||
|
|
6c46328da1 | ||
|
|
c8e5e08b03 | ||
|
|
a5e3dfcf69 | ||
|
|
d6e8be0c05 | ||
|
|
68d16855df | ||
|
|
0547ea548b | ||
|
|
c02dba6812 | ||
|
|
f1dc4a0400 | ||
|
|
158827ac55 | ||
|
|
36ca30c3e4 | ||
|
|
48711b55e1 | ||
|
|
4d583185a9 | ||
|
|
d757ffa853 | ||
|
|
5352386486 | ||
|
|
9b5872740f | ||
|
|
31dbdaf241 | ||
|
|
f4b56262b1 | ||
|
|
49039f752d | ||
|
|
348721acc9 | ||
|
|
774144c430 | ||
|
|
0e6651ab82 |
+23
-10
@@ -13,7 +13,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
ARDUINO_SKETCH_ALWAYS_EXPORT_BINARIES: 1
|
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 for ESP32
|
- name: Build firmware for ESP32
|
||||||
@@ -28,22 +28,26 @@ jobs:
|
|||||||
run: make BOARD=esp32:esp32:esp32s3:CDCOnBoot=cdc,PSRAM=opi EXTRA=--output-dir=flix/build/esp32.esp32.esp32s3.opi
|
run: make BOARD=esp32:esp32:esp32s3:CDCOnBoot=cdc,PSRAM=opi EXTRA=--output-dir=flix/build/esp32.esp32.esp32s3.opi
|
||||||
- name: Build firmware for Flix2
|
- 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'
|
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: Install STM32 core
|
||||||
|
run: arduino-cli core install STMicroelectronics:stm32 --additional-urls https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
|
||||||
- name: Build firmware for STM32F4
|
- name: Build firmware for STM32F4
|
||||||
run: |
|
run: make BOARD=STMicroelectronics:stm32:GenF4:usb=CDCgen EXTRA='--build-property compiler.cpp.extra_flags=-DENABLE_HWSERIAL1'
|
||||||
arduino-cli core install STMicroelectronics:stm32 --additional-urls https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
|
- name: Build firmware for STM32H7
|
||||||
make BOARD=STMicroelectronics:stm32:GenF4
|
run: make BOARD=STMicroelectronics:stm32:GenH7:usb=CDCgen EXTRA='--build-property compiler.cpp.extra_flags=-DENABLE_HWSERIAL1'
|
||||||
- name: Upload binaries
|
- name: Upload binaries
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: firmware-binary
|
name: firmware-binary
|
||||||
path: flix/build
|
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
|
||||||
@@ -54,7 +58,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
|
||||||
@@ -75,7 +79,7 @@ jobs:
|
|||||||
DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget build-essential cmake g++ pkg-config gnupg2 lsb-release sudo
|
DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget build-essential cmake g++ pkg-config gnupg2 lsb-release sudo
|
||||||
- name: Install Arduino CLI
|
- name: Install Arduino CLI
|
||||||
run: curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=/usr/local/bin sh
|
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'
|
||||||
@@ -86,7 +90,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
|
||||||
@@ -98,7 +111,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*
|
||||||
|
|||||||
@@ -16,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
|
||||||
@@ -25,13 +25,16 @@ 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
|
- name: Wait for Build to complete
|
||||||
|
if: ${{ env.BINARIES }}
|
||||||
uses: lewagon/wait-on-check-action@v1.9.1
|
uses: lewagon/wait-on-check-action@v1.9.1
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.sha }}
|
ref: ${{ github.sha }}
|
||||||
@@ -39,6 +42,7 @@ jobs:
|
|||||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
wait-interval: 30
|
wait-interval: 30
|
||||||
- name: Find firmware binaries
|
- name: Find firmware binaries
|
||||||
|
if: ${{ env.BINARIES }}
|
||||||
id: build_run
|
id: build_run
|
||||||
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')
|
RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/build.yml/runs?head_sha=${{ github.sha }}&per_page=1" --jq '.workflow_runs[0].id')
|
||||||
@@ -46,7 +50,8 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Download firmware binaries
|
- name: Download firmware binaries
|
||||||
uses: actions/download-artifact@v4
|
if: ${{ env.BINARIES }}
|
||||||
|
uses: actions/download-artifact@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
repository: ${{ github.repository }}
|
repository: ${{ github.repository }}
|
||||||
@@ -54,6 +59,7 @@ jobs:
|
|||||||
name: firmware-binary
|
name: firmware-binary
|
||||||
path: docs/build
|
path: docs/build
|
||||||
- name: Create shortcuts for firmware binaries
|
- name: Create shortcuts for firmware binaries
|
||||||
|
if: ${{ env.BINARIES }}
|
||||||
working-directory: docs/build
|
working-directory: docs/build
|
||||||
run: |
|
run: |
|
||||||
for FQBN in esp32.esp32.*; do
|
for FQBN in esp32.esp32.*; do
|
||||||
@@ -64,12 +70,12 @@ jobs:
|
|||||||
ln -s "$FQBN/flix.ino.bootloader.bin" "flix.$BOARD.bootloader.bin"
|
ln -s "$FQBN/flix.ino.bootloader.bin" "flix.$BOARD.bootloader.bin"
|
||||||
done
|
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
|
||||||
@@ -81,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,10 +4,13 @@ PORT := $(strip $(wildcard /dev/serial/by-id/usb-Silicon_Labs_CP21* /dev/serial/
|
|||||||
export ARDUINO_NETWORK_CONNECTION_TIMEOUT := 1h
|
export ARDUINO_NETWORK_CONNECTION_TIMEOUT := 1h
|
||||||
|
|
||||||
build: .core .libs
|
build: .core .libs
|
||||||
arduino-cli compile --fqbn $(BOARD) --build-property "build.core_debug_level=1" flix $(EXTRA)
|
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
|
||||||
|
|
||||||
erase:
|
erase:
|
||||||
arduino-cli burn-bootloader --fqbn $(BOARD) -p "$(PORT)" -P esptool
|
arduino-cli burn-bootloader --fqbn $(BOARD) -p "$(PORT)" -P esptool
|
||||||
@@ -22,13 +25,13 @@ core .core:
|
|||||||
|
|
||||||
libs .libs:
|
libs .libs:
|
||||||
arduino-cli lib update-index
|
arduino-cli lib update-index
|
||||||
arduino-cli lib install "FlixPeriph"
|
ARDUINO_LIBRARY_ENABLE_UNSAFE_INSTALL=1 arduino-cli lib install --git-url 'https://github.com/okalachev/flixperiph.git#dev'
|
||||||
arduino-cli lib install "MAVLink"@2.0.25
|
arduino-cli lib install "MAVLink"@2.0.25
|
||||||
touch .libs
|
touch .libs
|
||||||
|
|
||||||
upload_proxy: .core .libs
|
upload_proxy: .core .libs
|
||||||
arduino-cli compile --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
|
||||||
|
|||||||
+3
-3
@@ -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;
|
||||||
@@ -193,7 +193,7 @@ void doCommand(String str, bool echo = false) {
|
|||||||
attitude = Quaternion();
|
attitude = Quaternion();
|
||||||
gyroBiasFilter.reset();
|
gyroBiasFilter.reset();
|
||||||
} else if (command == "reboot") {
|
} else if (command == "reboot") {
|
||||||
ESP.restart();
|
reboot();
|
||||||
} else {
|
} else {
|
||||||
print("Invalid command: %s\n", command.c_str());
|
print("Invalid command: %s\n", command.c_str());
|
||||||
}
|
}
|
||||||
@@ -212,7 +212,7 @@ void handleInput() {
|
|||||||
char c = Serial.read();
|
char c = Serial.read();
|
||||||
if (c == '\n' || c == '\r') {
|
if (c == '\n' || c == '\r') {
|
||||||
doCommand(input);
|
doCommand(input);
|
||||||
input.clear();
|
input = "";
|
||||||
} else {
|
} else {
|
||||||
input += c;
|
input += c;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
#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"
|
||||||
|
|
||||||
const int RAW = 0, ACRO = 1, STAB = 2, AUTO = 3; // flight modes
|
const int RAW = 0, ACRO = 1, STAB = 2, AUTO = 3; // flight modes
|
||||||
|
|||||||
+1
-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
|
||||||
|
|||||||
+13
-1
@@ -7,7 +7,7 @@
|
|||||||
#include <Wire.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"
|
||||||
|
|
||||||
IMU *imu;
|
IMU *imu;
|
||||||
@@ -33,11 +33,23 @@ void setupIMU() {
|
|||||||
|
|
||||||
if (imuBus == 0) {
|
if (imuBus == 0) {
|
||||||
// SPI connection
|
// SPI connection
|
||||||
|
#if defined(ESP32)
|
||||||
SPI.begin(imuSckPin, imuMisoPin, imuMosiPin);
|
SPI.begin(imuSckPin, imuMisoPin, imuMosiPin);
|
||||||
|
#elif defined(ARDUINO_ARCH_STM32)
|
||||||
|
SPI.setSCLK(imuSckPin);
|
||||||
|
SPI.setMOSI(imuMosiPin);
|
||||||
|
SPI.setMISO(imuMisoPin);
|
||||||
|
SPI.begin();
|
||||||
|
#endif
|
||||||
imu = IMU::create(imuModel, SPI, imuCsPin, imuIntPin);
|
imu = IMU::create(imuModel, SPI, imuCsPin, imuIntPin);
|
||||||
} else {
|
} else {
|
||||||
// I2C connection
|
// I2C connection
|
||||||
|
#if defined(ESP32)
|
||||||
Wire.setPins(imuSdaPin, imuSclPin);
|
Wire.setPins(imuSdaPin, imuSclPin);
|
||||||
|
#elif defined(ARDUINO_ARCH_STM32)
|
||||||
|
Wire.setSDA(imuSdaPin);
|
||||||
|
Wire.setSCL(imuSclPin);
|
||||||
|
#endif
|
||||||
imu = IMU::create(imuModel, Wire, imuIntPin);
|
imu = IMU::create(imuModel, Wire, imuIntPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -291,5 +291,5 @@ void sendMavlinkPrint() {
|
|||||||
0, 0, strlen(data), (uint8_t *)data, 0, 0);
|
0, 0, strlen(data), (uint8_t *)data, 0, 0);
|
||||||
sendMessage(&msg);
|
sendMessage(&msg);
|
||||||
}
|
}
|
||||||
mavlinkPrintBuffer.clear();
|
mavlinkPrintBuffer = "";
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
float motors[4]; // normalized motor thrusts in range [0..1]
|
float motors[4]; // normalized motor thrusts in range [0..1]
|
||||||
|
|
||||||
int motorPins[4] = {12, 13, 14, 15}; // default pin numbers
|
int motorPins[4] = {-1, -1, -1, -1}; // default pin numbers
|
||||||
int pwmFrequency = 78000;
|
int pwmFrequency = 78000;
|
||||||
int pwmResolution = 10;
|
int pwmResolution = 10;
|
||||||
int pwmStop = 0;
|
int pwmStop = 0;
|
||||||
|
|||||||
+5
-5
@@ -22,7 +22,7 @@ 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 inital; // default value
|
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) {};
|
||||||
@@ -150,7 +150,7 @@ void setupParameters() {
|
|||||||
storage.begin("flix");
|
storage.begin("flix");
|
||||||
// Read parameters from storage
|
// Read parameters from storage
|
||||||
for (auto ¶meter : parameters) {
|
for (auto ¶meter : parameters) {
|
||||||
parameter.inital = parameter.getValue();
|
parameter.initial = parameter.getValue();
|
||||||
if (storage.isKey(parameter.name)) {
|
if (storage.isKey(parameter.name)) {
|
||||||
parameter.setValue(storage.getFloat(parameter.name));
|
parameter.setValue(storage.getFloat(parameter.name));
|
||||||
}
|
}
|
||||||
@@ -211,15 +211,15 @@ void printParameters(const char *filter) {
|
|||||||
for (auto ¶meter : parameters) {
|
for (auto ¶meter : parameters) {
|
||||||
if (strncasecmp(parameter.name, filter, strlen(filter))) continue;
|
if (strncasecmp(parameter.name, filter, strlen(filter))) continue;
|
||||||
|
|
||||||
if (floatEquals(parameter.getValue(), parameter.inital)) { // parameter changed
|
if (floatEquals(parameter.getValue(), parameter.initial)) { // parameter changed
|
||||||
print("%-15s %-13g\n", parameter.name, parameter.getValue());
|
print("%-15s %-13g\n", parameter.name, parameter.getValue());
|
||||||
} else {
|
} else {
|
||||||
print("%-15s %-13g [%g]\n", parameter.name, parameter.getValue(), parameter.inital);
|
print("%-15s %-13g [%g]\n", parameter.name, parameter.getValue(), parameter.initial);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetParameters() {
|
void resetParameters() {
|
||||||
storage.clear();
|
storage.clear();
|
||||||
ESP.restart();
|
reboot();
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -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 = 0, float d = 0, 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) {
|
||||||
|
|||||||
+9
-3
@@ -7,7 +7,7 @@
|
|||||||
#include <soc/soc.h>
|
#include <soc/soc.h>
|
||||||
#include <soc/rtc_cntl_reg.h>
|
#include <soc/rtc_cntl_reg.h>
|
||||||
#endif
|
#endif
|
||||||
#include "lpf.h"
|
#include "filter.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
float voltage = NAN;
|
float voltage = NAN;
|
||||||
@@ -18,8 +18,8 @@ float voltageScale = 2;
|
|||||||
void setupPower() {
|
void setupPower() {
|
||||||
#ifdef ESP32
|
#ifdef ESP32
|
||||||
REG_CLR_BIT(RTC_CNTL_BROWN_OUT_REG, RTC_CNTL_BROWN_OUT_ENA); // disable reset on low voltage
|
REG_CLR_BIT(RTC_CNTL_BROWN_OUT_REG, RTC_CNTL_BROWN_OUT_ENA); // disable reset on low voltage
|
||||||
#endif
|
|
||||||
if (digitalPinToAnalogChannel(voltagePin) == -1) voltagePin = -1; // test ADC pin
|
if (digitalPinToAnalogChannel(voltagePin) == -1) voltagePin = -1; // test ADC pin
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void readVoltage() {
|
void readVoltage() {
|
||||||
@@ -28,6 +28,12 @@ void readVoltage() {
|
|||||||
static Rate rate(10);
|
static Rate rate(10);
|
||||||
if (!rate) return;
|
if (!rate) return;
|
||||||
|
|
||||||
float v = analogReadMilliVolts(voltagePin) * voltageScale / 1000.0f;
|
float v = 0;
|
||||||
|
#if defined(ESP32)
|
||||||
|
v = analogReadMilliVolts(voltagePin) * voltageScale / 1000.0f;
|
||||||
|
#elif defined(ARDUINO_ARCH_STM32)
|
||||||
|
v = analogRead(voltagePin) * voltageScale * 3.3f / 4095.0f;
|
||||||
|
#endif
|
||||||
|
|
||||||
voltage = voltageFilter.update(v);
|
voltage = voltageFilter.update(v);
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-6
@@ -1,4 +1,4 @@
|
|||||||
#ifdef ESP32
|
#ifndef ARDUINO_ARCH_STM32
|
||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
#else
|
#else
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ public:
|
|||||||
Preferences() = default;
|
Preferences() = default;
|
||||||
~Preferences() = default;
|
~Preferences() = default;
|
||||||
|
|
||||||
bool begin(const char *name, bool readOnly = false, const char *partition_label = NULL) {
|
bool begin(const char *name, bool readOnly = false, const char *partition_label = nullptr) {
|
||||||
(void)name;
|
(void)name;
|
||||||
(void)readOnly;
|
(void)readOnly;
|
||||||
(void)partition_label;
|
(void)partition_label;
|
||||||
@@ -128,9 +128,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
int availableStorageSize() {
|
int availableStorageSize() {
|
||||||
int n = STORAGE_SIZE;
|
return STORAGE_SIZE;
|
||||||
if (EEPROM.length() > 0 && EEPROM.length() < n) n = EEPROM.length();
|
|
||||||
return n;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool save() {
|
bool save() {
|
||||||
@@ -187,7 +185,6 @@ private:
|
|||||||
for (int i = 0; i < storageSize; i++) {
|
for (int i = 0; i < storageSize; i++) {
|
||||||
EEPROM.write(i, buffer[i]);
|
EEPROM.write(i, buffer[i]);
|
||||||
}
|
}
|
||||||
EEPROM.commit();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ public:
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
void reboot() {
|
||||||
|
#if defined(ESP32)
|
||||||
|
ESP.restart();
|
||||||
|
#elif defined(ARDUINO_ARCH_STM32)
|
||||||
|
NVIC_SystemReset();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// Rate limiter
|
// Rate limiter
|
||||||
class Rate {
|
class Rate {
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -170,6 +170,9 @@ bool ledcWrite(uint8_t pin, uint32_t duty) { return true; }
|
|||||||
uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) { return freq; }
|
uint32_t ledcChangeFrequency(uint8_t pin, uint32_t freq, uint8_t resolution) { return freq; }
|
||||||
int8_t digitalPinToAnalogChannel(uint8_t pin) { return -1; }
|
int8_t digitalPinToAnalogChannel(uint8_t pin) { return -1; }
|
||||||
uint32_t analogReadMilliVolts(uint8_t pin) { return 0; }
|
uint32_t analogReadMilliVolts(uint8_t pin) { return 0; }
|
||||||
|
void analogWrite(uint8_t pin, int value) {}
|
||||||
|
void analogWriteResolution(uint8_t res) {}
|
||||||
|
void analogWriteFrequency(uint32_t freq) {}
|
||||||
|
|
||||||
unsigned long __micros;
|
unsigned long __micros;
|
||||||
unsigned long __resetTime = 0;
|
unsigned long __resetTime = 0;
|
||||||
|
|||||||
+1
-1
@@ -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;
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// Mocks
|
// Mocks
|
||||||
int wifiMode = 1;
|
int wifiMode = 1;
|
||||||
int wifiLongRange = 0;
|
int wifiLongRange = 0;
|
||||||
|
int wifiBroadcast = 0;
|
||||||
int espnowChannel = 6;
|
int espnowChannel = 6;
|
||||||
const int W_DISABLED = 0, W_AP = 1, W_STA = 2, W_ESPNOW = 3;
|
const int W_DISABLED = 0, W_AP = 1, W_STA = 2, W_ESPNOW = 3;
|
||||||
|
|
||||||
|
|||||||
@@ -243,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
|
||||||
@@ -252,8 +252,8 @@ class Flix:
|
|||||||
raise ValueError(f'List must have 3 (vector) or 4 (quaternion) elements')
|
raise ValueError(f'List must have 3 (vector) or 4 (quaternion) elements')
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _flu_to_mavlink(v: 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:
|
||||||
@@ -320,13 +320,13 @@ class Flix:
|
|||||||
def set_armed(self, armed: bool):
|
def set_armed(self, armed: bool):
|
||||||
self._command_send(mavlink.MAV_CMD_COMPONENT_ARM_DISARM, (1 if armed else 0, 0, 0, 0, 0, 0, 0))
|
self._command_send(mavlink.MAV_CMD_COMPONENT_ARM_DISARM, (1 if armed else 0, 0, 0, 0, 0, 0, 0))
|
||||||
|
|
||||||
def set_position(self, position: 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:
|
||||||
@@ -339,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):
|
||||||
@@ -351,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):
|
||||||
|
|||||||
Reference in New Issue
Block a user