mirror of
https://github.com/CopterExpress/clover.git
synced 2026-06-01 15:39:32 +00:00
Compare commits
97 Commits
roswww-sta
...
v0.22-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5870521b4b | ||
|
|
c899d74f95 | ||
|
|
a722397a72 | ||
|
|
606c52f782 | ||
|
|
0d702b0c01 | ||
|
|
42f6da25b7 | ||
|
|
5c56b6a5ed | ||
|
|
ff2e8b0709 | ||
|
|
bd38ad56c7 | ||
|
|
9f2acc49c5 | ||
|
|
f5c11bc528 | ||
|
|
3e713c6c8a | ||
|
|
6ce2822b78 | ||
|
|
5a4c3a0da7 | ||
|
|
6b2f1445e8 | ||
|
|
55d6e47c60 | ||
|
|
558baae1fc | ||
|
|
f81247ccf3 | ||
|
|
7ac4f8bec0 | ||
|
|
2111b366db | ||
|
|
a28c774e8f | ||
|
|
a8b321ce22 | ||
|
|
109542dc91 | ||
|
|
72252eb06e | ||
|
|
876092e33e | ||
|
|
e119220e91 | ||
|
|
4a403b2399 | ||
|
|
ed64507bef | ||
|
|
98ff1d2b50 | ||
|
|
a8a42fe33d | ||
|
|
bd0037f3c9 | ||
|
|
67b7e903fd | ||
|
|
a184eb00af | ||
|
|
2bd49201be | ||
|
|
df28da0060 | ||
|
|
73d39e9ca6 | ||
|
|
23eac48fa4 | ||
|
|
9cdcbbc901 | ||
|
|
edc740c8c0 | ||
|
|
9cddc81d1d | ||
|
|
ced9b56452 | ||
|
|
8bd4d3a8f8 | ||
|
|
4730dabaaf | ||
|
|
0991427878 | ||
|
|
3d4c02bc03 | ||
|
|
8a6bdccc9c | ||
|
|
f36401274d | ||
|
|
2cfd21269c | ||
|
|
81bcfef85b | ||
|
|
00412125f9 | ||
|
|
55366a1fac | ||
|
|
cc2ebb368d | ||
|
|
33500a13d0 | ||
|
|
d7d95132ab | ||
|
|
4c049ac349 | ||
|
|
fca3f52424 | ||
|
|
27c0f23ffa | ||
|
|
d6590e9a8d | ||
|
|
c93beec30d | ||
|
|
47628ba4af | ||
|
|
98e43aba49 | ||
|
|
404b42c9f9 | ||
|
|
6b831359dc | ||
|
|
10076e35f4 | ||
|
|
76a6a58a42 | ||
|
|
62069ab80a | ||
|
|
e1643a681a | ||
|
|
6f30613ce0 | ||
|
|
d539753e72 | ||
|
|
cc80f2b4c1 | ||
|
|
1893f0528b | ||
|
|
ce1f1d9db5 | ||
|
|
8b42fcfda3 | ||
|
|
60b9d1d61d | ||
|
|
61ae5d0537 | ||
|
|
7ffcbde82e | ||
|
|
ccc53f1cfb | ||
|
|
be2b447b46 | ||
|
|
b333dd8708 | ||
|
|
4b5524e467 | ||
|
|
5b970d5197 | ||
|
|
617ae0dcdd | ||
|
|
a1d2ae9a23 | ||
|
|
9372386f6b | ||
|
|
4b97f9d4af | ||
|
|
6af1fd2837 | ||
|
|
b7545830ba | ||
|
|
4796c4acb7 | ||
|
|
de3fb77db2 | ||
|
|
036d3dccd6 | ||
|
|
24cfc54c06 | ||
|
|
173c8cbe3a | ||
|
|
2d8cd0e0ab | ||
|
|
ee667257ef | ||
|
|
3c4f57cbe7 | ||
|
|
3ee598004a | ||
|
|
673cabe7ab |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -3,7 +3,6 @@ roslib.js linguist-vendored
|
||||
eventemitter2.js linguist-vendored
|
||||
ros3d.js linguist-vendored
|
||||
three.min.js linguist-vendored
|
||||
json-to-pretty-yaml.js linguist-vendored
|
||||
aruco_pose/vendor/* linguist-vendored
|
||||
blockly/* linguist-vendored
|
||||
highlight/* linguist-vendored
|
||||
|
||||
29
.github/workflows/build-image.yaml
vendored
29
.github/workflows/build-image.yaml
vendored
@@ -1,29 +0,0 @@
|
||||
name: RPi image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
release:
|
||||
types: [ created ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build image
|
||||
run: |
|
||||
docker run --privileged --rm -v /dev:/dev -v $(pwd):/builder/repo -e TRAVIS_TAG="${{ github.event.release.tag_name }}" sfalexrog/img-tool:qemu-update
|
||||
- name: Compress image
|
||||
run: |
|
||||
cd images && sudo chmod -R 777 . && zip -9 $(echo clover_*).zip clover_* && ls -l . && unzip -l clover_*.zip
|
||||
- name: Upload image
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
with:
|
||||
files: images/clover_*.zip
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
51
.github/workflows/build.yml
vendored
51
.github/workflows/build.yml
vendored
@@ -1,51 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
# melodic:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - name: Native Melodic build
|
||||
# run: |
|
||||
# docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:melodic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh
|
||||
noetic:
|
||||
runs-on: ubuntu-latest
|
||||
container: ros:noetic-ros-base
|
||||
defaults:
|
||||
run:
|
||||
working-directory: catkin_ws
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: catkin_ws/src/clover
|
||||
- name: Install requirements
|
||||
run: apt-get update && apt-get -y install python3-pip fakeroot python3-bloom debhelper dpkg-dev
|
||||
- name: Install dependencies
|
||||
run: rosdep update && rosdep install --from-paths src --ignore-src -y
|
||||
- name: Install GeographicLib datasets
|
||||
run: wget -qO- https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | bash
|
||||
- name: catkin_make
|
||||
run: source /opt/ros/$ROS_DISTRO/setup.bash && catkin_make
|
||||
- name: Run tests
|
||||
run: source devel/setup.bash && catkin_make run_tests && catkin_test_results
|
||||
- name: Build Debian packages
|
||||
run: |
|
||||
source devel/setup.bash
|
||||
for file in `find . -name "package.xml"`; do
|
||||
cd $(dirname ${file})
|
||||
bloom-generate rosdebian --os-name ubuntu --os-version $(lsb_release -cs) --ros-distro $ROS_DISTRO
|
||||
fakeroot debian/rules binary
|
||||
cd -
|
||||
done
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-packages
|
||||
path: catkin_ws/src/clover/*.deb
|
||||
retention-days: 1
|
||||
86
.github/workflows/docs.yml
vendored
86
.github/workflows/docs.yml
vendored
@@ -1,86 +0,0 @@
|
||||
name: Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ '*' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with: { node-version: '10' }
|
||||
- name: Setup tools
|
||||
run: |
|
||||
sudo sh -c "echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections"
|
||||
sudo apt-get update && sudo apt-get install -y calibre msttcorefonts
|
||||
builder/assets/install_gitbook.sh
|
||||
npm install markdownlint-cli@0.28.1 -g # FIXME: https://github.com/DavidAnson/markdownlint/issues/435
|
||||
npm install svgexport -g
|
||||
gitbook -V
|
||||
markdownlint -V
|
||||
- name: Run markdownlint
|
||||
run: markdownlint docs
|
||||
- name: Check Assets
|
||||
run: |
|
||||
./check_assets_size.py
|
||||
./check_unused_assets.py
|
||||
- name: Build GitBook
|
||||
run: |
|
||||
gitbook install
|
||||
gitbook build
|
||||
- name: Generate PDF
|
||||
id: generate-pdf
|
||||
env:
|
||||
GITBOOK_SKIP_PDF: ${{ secrets.GITBOOK_SKIP_PDF }}
|
||||
continue-on-error: ${{ env.GITBOOK_SKIP_PDF != '' }}
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: |
|
||||
for i in 1 2 3 4; do gitbook pdf ./ _book/clover.pdf && break || sleep 1; done
|
||||
sudo apt-get -q install ghostscript
|
||||
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/default -dNOPAUSE -dQUIET -dBATCH -dDetectDuplicateImages -dCompressFonts=true -r150 -sOutputFile=_book/clover_ru_compressed.pdf _book/clover_ru.pdf
|
||||
gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/default -dNOPAUSE -dQUIET -dBATCH -dDetectDuplicateImages -dCompressFonts=true -r150 -sOutputFile=_book/clover_en_compressed.pdf _book/clover_en.pdf
|
||||
rm _book/clover_ru.pdf && mv _book/clover_ru_compressed.pdf _book/clover_ru.pdf
|
||||
rm _book/clover_en.pdf && mv _book/clover_en_compressed.pdf _book/clover_en.pdf
|
||||
ls -lah _book/clover*.pdf
|
||||
echo '::set-output name=GITBOOK_PDF_OK::1'
|
||||
- name: Download older PDFs
|
||||
if: ${{ !steps.generate-pdf.outputs.GITBOOK_PDF_OK }}
|
||||
run: |
|
||||
rm -f _book/clover*.pdf
|
||||
wget --no-verbose https://clover.coex.tech/clover_ru.pdf -P _book/
|
||||
wget --no-verbose https://clover.coex.tech/clover_en.pdf -P _book/
|
||||
- name: Upload artifact
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
path: _book
|
||||
|
||||
deploy-docs:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: docs
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
18
.github/workflows/editorconfig.yaml
vendored
18
.github/workflows/editorconfig.yaml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Editorconfig
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
editorconfig:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: .editorconfig Linter
|
||||
run: |
|
||||
wget --no-verbose https://github.com/okalachev/editorconfig-checker/releases/download/1.2.1-disable-spaces-amount/ec-linux-amd64
|
||||
chmod +x ec-linux-amd64
|
||||
./ec-linux-amd64 -spaces-after-tabs -e "roslib.js|ros3d.js|eventemitter2.js|json-to-pretty-yaml.js|draw.cpp|BinUtils.swift|\.idea|apps/android/app|blockly/|clover_blocks/programs/|highlight/|python.js|Assets.xcassets|test_parser_pass.txt|test_node_failure.txt|aruco_pose/vendor|\.stl|\.dxf|\.dae|\.material"
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,4 +6,3 @@ _book/
|
||||
package-lock.json
|
||||
clover_blocks/programs/*.*
|
||||
!clover_blocks/programs/examples/*
|
||||
/.vscode/
|
||||
|
||||
@@ -110,8 +110,7 @@
|
||||
"Li-ion",
|
||||
"Nvidia",
|
||||
"VirtualBox",
|
||||
"VMware",
|
||||
"DuoCam"
|
||||
"VMware"
|
||||
],
|
||||
"code_blocks": false
|
||||
},
|
||||
|
||||
132
.travis.yml
Normal file
132
.travis.yml
Normal file
@@ -0,0 +1,132 @@
|
||||
os: linux
|
||||
dist: xenial
|
||||
language: generic
|
||||
services:
|
||||
- docker
|
||||
env:
|
||||
global:
|
||||
- DOCKER="sfalexrog/img-tool:qemu-update"
|
||||
- TARGET_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git"
|
||||
- IMAGE_VERSION=${TRAVIS_TAG:-${TRAVIS_COMMIT:0:7}}
|
||||
- IMAGE_NAME="$(basename -s '.git' ${TARGET_REPO})_${IMAGE_VERSION}.img"
|
||||
git:
|
||||
depth: 1
|
||||
jobs:
|
||||
fast_finish: true
|
||||
include:
|
||||
- stage: Build
|
||||
name: "Raspberry Pi Image Build"
|
||||
cache:
|
||||
directories:
|
||||
- imgcache
|
||||
before_script:
|
||||
- docker pull ${DOCKER}
|
||||
# Check if there are any cached images, copy them to our "images" directory
|
||||
- if [ -n "$(ls -A imgcache/*.zip)" ]; then mkdir -p images && cp imgcache/*.zip images; fi
|
||||
script:
|
||||
- if [[ -z ${TRAVIS_TAG} && "${TRAVIS_PULL_REQUEST}" != "false" ]]; then
|
||||
echo "Commit range is ${TRAVIS_COMMIT_RANGE}" &&
|
||||
if [ $(git diff --name-only ${TRAVIS_COMMIT_RANGE} | grep -v ^"docs/" | wc -l) -eq 0 ]; then
|
||||
echo " === Docs-only change; skipping build ===" &&
|
||||
export SKIP_BUILD=true;
|
||||
fi;
|
||||
fi
|
||||
- if [ -z ${SKIP_BUILD} ]; then
|
||||
docker run --privileged --rm -v /dev:/dev -v $(pwd):/builder/repo -e TRAVIS_TAG="${TRAVIS_TAG}" ${DOCKER};
|
||||
fi
|
||||
before_cache:
|
||||
- cp images/*.zip imgcache
|
||||
after_success:
|
||||
- sudo chmod -R 777 *
|
||||
- cd images && zip -9 ${IMAGE_NAME}.zip ${IMAGE_NAME} && stat --printf="Compressed image size:%s\n" ${IMAGE_NAME}.zip
|
||||
before_deploy:
|
||||
# Set up git user name and tag this commit
|
||||
- git config --local user.name "goldarte"
|
||||
- git config --local user.email "goldartt@gmail.com"
|
||||
deploy:
|
||||
provider: releases
|
||||
token: ${GITHUB_OAUTH_TOKEN}
|
||||
file: ${IMAGE_NAME}.zip
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
draft: true
|
||||
name: ${TRAVIS_TAG}
|
||||
- stage: Build
|
||||
name: "Native Kinetic build"
|
||||
env:
|
||||
- NATIVE_DOCKER=ros:kinetic-ros-base
|
||||
before_script:
|
||||
- docker pull ${NATIVE_DOCKER}
|
||||
script:
|
||||
- docker run --rm -v $(pwd):/root/catkin_ws/src/clover ${NATIVE_DOCKER} /root/catkin_ws/src/clover/builder/standalone-install.sh
|
||||
- stage: Build
|
||||
name: "Native Melodic build"
|
||||
env:
|
||||
- NATIVE_DOCKER=ros:melodic-ros-base
|
||||
before_script:
|
||||
- docker pull ${NATIVE_DOCKER}
|
||||
script:
|
||||
- docker run --rm -v $(pwd):/root/catkin_ws/src/clover ${NATIVE_DOCKER} /root/catkin_ws/src/clover/builder/standalone-install.sh
|
||||
- stage: Build
|
||||
name: "Native Noetic build"
|
||||
env:
|
||||
- NATIVE_DOCKER=ros:noetic-ros-base
|
||||
before_script:
|
||||
- docker pull ${NATIVE_DOCKER}
|
||||
script:
|
||||
- docker run --rm -v $(pwd):/root/catkin_ws/src/clover ${NATIVE_DOCKER} /root/catkin_ws/src/clover/builder/standalone-install.sh
|
||||
- stage: Build
|
||||
name: "Documentation"
|
||||
language: node_js
|
||||
node_js:
|
||||
- "10"
|
||||
before_script:
|
||||
- sudo sh -c "echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections"
|
||||
- sudo apt update && sudo apt install -y calibre msttcorefonts
|
||||
- npm install gitbook-cli -g
|
||||
- gitbook fetch 3.2.3 && npm i npm@3.10.10 --prefix=~/.gitbook/versions/3.2.3/ # fixing https://travis-ci.org/github/CopterExpress/clover/jobs/766541125#L932
|
||||
- npm install markdownlint-cli -g
|
||||
- npm install svgexport -g
|
||||
- gitbook -V
|
||||
- markdownlint -V
|
||||
script:
|
||||
- markdownlint docs
|
||||
- ./check_assets_size.py
|
||||
- ./check_unused_assets.py
|
||||
- gitbook install
|
||||
- gitbook build
|
||||
- for i in 1 2 3 4; do gitbook pdf ./ _book/clover.pdf && break || sleep 1; done
|
||||
- sudo apt-get install ghostscript
|
||||
- gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/default -dNOPAUSE -dQUIET -dBATCH -dDetectDuplicateImages -dCompressFonts=true -r150 -sOutputFile=_book/clover_ru_compressed.pdf _book/clover_ru.pdf
|
||||
- gs -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 -dPDFSETTINGS=/default -dNOPAUSE -dQUIET -dBATCH -dDetectDuplicateImages -dCompressFonts=true -r150 -sOutputFile=_book/clover_en_compressed.pdf _book/clover_en.pdf
|
||||
- rm _book/clover_ru.pdf && mv _book/clover_ru_compressed.pdf _book/clover_ru.pdf
|
||||
- rm _book/clover_en.pdf && mv _book/clover_en_compressed.pdf _book/clover_en.pdf
|
||||
- ls -lah _book/clover*.pdf
|
||||
deploy:
|
||||
provider: pages
|
||||
local_dir: _book
|
||||
skip_cleanup: true
|
||||
token: ${GITHUB_OAUTH_TOKEN}
|
||||
keep_history: true
|
||||
target_branch: master
|
||||
repo: CopterExpress/clover.coex.tech
|
||||
fqdn: clover.coex.tech
|
||||
verbose: true
|
||||
on:
|
||||
branch: master
|
||||
- stage: Build
|
||||
name: Editorconfig-lint
|
||||
language: generic
|
||||
before_script:
|
||||
- wget https://github.com/okalachev/editorconfig-checker/releases/download/1.2.1-disable-spaces-amount/ec-linux-amd64
|
||||
- chmod +x ec-linux-amd64
|
||||
script:
|
||||
- ./ec-linux-amd64 -spaces-after-tabs -e "roslib.js|ros3d.js|eventemitter2.js|draw.cpp|BinUtils.swift|\.idea|apps/android/app|blockly/|clover_blocks/programs/|highlight/|python.js|Assets.xcassets|test_parser_pass.txt|test_node_failure.txt|aruco_pose/vendor|\.stl|\.dxf|\.dae"
|
||||
stages:
|
||||
- Build
|
||||
# More info there
|
||||
# https://github.com/travis-ci/travis-ci/issues/6893
|
||||
# https://docs.travis-ci.com/user/customizing-the-build/
|
||||
# https://docs.travis-ci.com/user/deployment/releases
|
||||
# https://docs.travis-ci.com/user/environment-variables/
|
||||
@@ -1,6 +1,6 @@
|
||||
# clover🍀: create autonomous drones easily
|
||||
|
||||
<img src="docs/assets/clover42-main-margin.png" align="right" width="400px" alt="COEX Clover Drone">
|
||||
<img src="docs/assets/clover42-main.png" align="right" width="400px" alt="COEX Clover Drone">
|
||||
|
||||
Clover is an open source [ROS](https://www.ros.org)-based framework, providing user-friendly tools to control [PX4](https://px4.io)-powered drones. Clover is available as a ROS package, but is shipped mainly as a preconfigured image for Raspberry Pi. Once you've installed Raspberry Pi on your drone and flashed the image to its microSD card, taking the drone up in the air is a matter of minutes.
|
||||
|
||||
@@ -20,7 +20,7 @@ Clover drone is used on a wide range of educational events, including [Copter Ha
|
||||
|
||||
Preconfigured image for Raspberry Pi with installed and configured software, ready to fly, is available [in the Releases section](https://github.com/CopterExpress/clover/releases).
|
||||
|
||||

|
||||
[](https://travis-ci.org/CopterExpress/clover)
|
||||

|
||||
|
||||
Image features:
|
||||
@@ -38,10 +38,6 @@ API description for autonomous flights is available [on GitBook](https://clover.
|
||||
|
||||
For manual package installation and running see [`clover` package documentation](clover/README.md).
|
||||
|
||||
## Support
|
||||
|
||||
[](https://t.me/COEXHelpdesk)
|
||||
|
||||
## License
|
||||
|
||||
While the Clover platform source code is available under the MIT License, note, that the [documentation](docs/) is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# iOS-приложение для управления Клевером
|
||||
iOS-приложение для управления Клевером
|
||||
--------------------------------------
|
||||
|
||||
Для установки зависимостей необходим [CocoaPods](https://cocoapods.org):
|
||||
|
||||
|
||||
@@ -83,10 +83,11 @@ add_message_files(
|
||||
)
|
||||
|
||||
## Generate services in the 'srv' folder
|
||||
add_service_files(
|
||||
FILES
|
||||
SetMarkers.srv
|
||||
)
|
||||
# add_service_files(
|
||||
# FILES
|
||||
# Service1.srv
|
||||
# Service2.srv
|
||||
# )
|
||||
|
||||
## Generate actions in the 'action' folder
|
||||
# add_action_files(
|
||||
@@ -118,8 +119,7 @@ generate_messages(
|
||||
|
||||
## Generate dynamic reconfigure parameters in the 'cfg' folder
|
||||
generate_dynamic_reconfigure_options(
|
||||
cfg/Detector.cfg
|
||||
cfg/Map.cfg
|
||||
cfg/DetectorParams.cfg
|
||||
)
|
||||
|
||||
###################################
|
||||
@@ -159,6 +159,9 @@ add_library(aruco_pose
|
||||
|
||||
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}_generate_messages_cpp ${PROJECT_NAME}_gencfg)
|
||||
|
||||
# FIXME: hack to fix https://travis-ci.org/github/CopterExpress/clover/jobs/766318908#L6532
|
||||
string(REPLACE "-lpthreads;" "" catkin_LIBRARIES "${catkin_LIBRARIES}")
|
||||
|
||||
## Declare a C++ executable
|
||||
## With catkin_make all packages are built within a single CMake context
|
||||
## The recommended prefix ensures that target names across packages don't collide
|
||||
@@ -202,11 +205,11 @@ set_property(TARGET aruco_pose
|
||||
# )
|
||||
|
||||
## Mark executables and/or libraries for installation
|
||||
install(TARGETS ${PROJECT_NAME}
|
||||
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
)
|
||||
# install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node
|
||||
# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
# )
|
||||
|
||||
## Mark cpp header files for installation
|
||||
# install(DIRECTORY include/${PROJECT_NAME}/
|
||||
@@ -226,10 +229,6 @@ catkin_install_python(PROGRAMS src/genmap.py
|
||||
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
)
|
||||
|
||||
install(DIRECTORY launch DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
|
||||
|
||||
install(DIRECTORY map DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
|
||||
|
||||
#############
|
||||
## Testing ##
|
||||
#############
|
||||
@@ -251,5 +250,4 @@ if (CATKIN_ENABLE_TESTING)
|
||||
add_rostest(test/test_node_failure.test)
|
||||
add_rostest(test/largemap.test)
|
||||
add_rostest(test/crash_opencv.test)
|
||||
add_rostest(test/duplicate.test)
|
||||
endif()
|
||||
|
||||
@@ -51,7 +51,6 @@ It's recommended to run it within the same nodelet manager with the camera nodel
|
||||
|
||||
* `image_raw` (*sensor_msgs/Image*) – camera image
|
||||
* `camera_info` (*sensor_msgs/CameraInfo*) – camera calibration info
|
||||
* `map_markers` (*aruco_pose/MarkerArray*) – list of markers to disable TF transform publishing
|
||||
|
||||
#### Published
|
||||
|
||||
@@ -71,11 +70,10 @@ It's recommended to run it within the same nodelet manager with the camera nodel
|
||||
|
||||
* `~map` – path to text file with markers list
|
||||
* `~frame_id` – published frame id (default: `aruco_map`)
|
||||
* `~known_tilt` – known tilt (pitch and roll) of markers map as a frame
|
||||
* `~known_tilt` – debug image width
|
||||
* `~image_width` – debug image width (default: 2000)
|
||||
* `~image_height` – debug image height (default: 2000)
|
||||
* `~image_margin` – debug image margin (default: 200)
|
||||
* `~image_axis` – whether debug image should contain axis (default: true)
|
||||
* `~dictionary` (*int*) – ArUco dictionary (default: 2) - should be the same as `dictionary` parameter of `aruco_detect` nodelet
|
||||
|
||||
Map file has one marker per line with the following line format:
|
||||
@@ -99,7 +97,6 @@ See examples in [`map`](map/) directory.
|
||||
#### Published
|
||||
|
||||
* `~pose` (*geometry_msgs/PoseWithCovarianceStamped*) – estimated map pose
|
||||
* `~map` (*aruco_pose/MarkerArray*) – list of markers in the loaded map
|
||||
* `~image` (*sensor_msgs/Image*) – planarized map image
|
||||
* `~visualization` (*visualization_msgs/MarkerArray*) – markers map visualization for rviz
|
||||
* `~debug` (*sensor_msgs/Image*) – debug image with detected markers and map axis
|
||||
|
||||
@@ -10,8 +10,6 @@ gen = ParameterGenerator()
|
||||
|
||||
gen.add("enabled", bool_t, 0, "if detection enabled", True)
|
||||
|
||||
gen.add("length", double_t, 0, "markers' side length", min=0, max=10)
|
||||
|
||||
gen.add("adaptiveThreshConstant", double_t, 0,
|
||||
"Constant for adaptive thresholding before finding contours",
|
||||
p.adaptiveThreshConstant, 0, 100)
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
PACKAGE = "aruco_pose"
|
||||
|
||||
from dynamic_reconfigure.parameter_generator_catkin import *
|
||||
|
||||
gen = ParameterGenerator()
|
||||
|
||||
gen.add("enabled", bool_t, 0, "if map detection enabled", True)
|
||||
|
||||
gen.add("map", str_t, 0, "full path for the map file")
|
||||
|
||||
gen.add("image_axis", bool_t, 0, "debug image axis", default=True)
|
||||
|
||||
exit(gen.generate(PACKAGE, "aruco_pose", "Map"))
|
||||
@@ -1,4 +1,3 @@
|
||||
# id length x y z rot_z rot_y rot_x
|
||||
0 0.33 0.0 9.0 0 0 0 0
|
||||
1 0.33 1.0 9.0 0 0 0 0
|
||||
2 0.33 2.0 9.0 0 0 0 0
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# id length x y z rot_z rot_y rot_x
|
||||
107 0.33 0 0 0 0 0 0
|
||||
106 0.33 0.77 0 0 0 0 0
|
||||
105 0.33 0 0.77 0 0 0 0
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# id length x y z rot_z rot_y rot_x
|
||||
14 0.365 0.000 0.0 0 0 0 0
|
||||
15 0.365 1.335 0.0 0 0 0 0
|
||||
30 0.365 2.865 0.0 0 0 0 0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="2">
|
||||
<name>aruco_pose</name>
|
||||
<version>0.23.0</version>
|
||||
<version>0.21.1</version>
|
||||
<description>Positioning with ArUco markers</description>
|
||||
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
|
||||
@@ -48,7 +48,6 @@
|
||||
#include <aruco_pose/Marker.h>
|
||||
#include <aruco_pose/MarkerArray.h>
|
||||
#include <aruco_pose/DetectorConfig.h>
|
||||
#include <aruco_pose/SetMarkers.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include <memory>
|
||||
@@ -70,11 +69,8 @@ private:
|
||||
image_transport::CameraSubscriber img_sub_;
|
||||
ros::Publisher markers_pub_, vis_markers_pub_;
|
||||
ros::Subscriber map_markers_sub_;
|
||||
ros::ServiceServer set_markers_srv_;
|
||||
bool estimate_poses_, send_tf_, auto_flip_, use_map_markers_;
|
||||
bool waiting_for_map_;
|
||||
bool estimate_poses_, send_tf_, auto_flip_;
|
||||
double length_;
|
||||
ros::Duration transform_timeout_;
|
||||
std::unordered_map<int, double> length_override_;
|
||||
std::string frame_id_prefix_, known_tilt_;
|
||||
Mat camera_matrix_, dist_coeffs_;
|
||||
@@ -96,14 +92,11 @@ public:
|
||||
dictionary = nh_priv_.param("dictionary", 2);
|
||||
estimate_poses_ = nh_priv_.param("estimate_poses", true);
|
||||
send_tf_ = nh_priv_.param("send_tf", true);
|
||||
use_map_markers_ = nh_priv_.param("use_map_markers", false);
|
||||
waiting_for_map_ = use_map_markers_;
|
||||
if (estimate_poses_ && !nh_priv_.getParam("length", length_)) {
|
||||
NODELET_FATAL("can't estimate marker's poses as ~length parameter is not defined");
|
||||
ros::shutdown();
|
||||
}
|
||||
readLengthOverride(nh_priv_);
|
||||
transform_timeout_ = ros::Duration(nh_priv_.param("transform_timeout", 0.02));
|
||||
|
||||
known_tilt_ = nh_priv_.param<std::string>("known_tilt", "");
|
||||
auto_flip_ = nh_priv_.param("auto_flip", false);
|
||||
@@ -121,8 +114,6 @@ public:
|
||||
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>>(nh_priv_);
|
||||
dyn_srv_->setCallback(std::bind(&ArucoDetect::paramCallback, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
set_markers_srv_ = nh_priv_.advertiseService("set_length_override", &ArucoDetect::setMarkers, this);
|
||||
|
||||
debug_pub_ = it_priv.advertise("debug", 1);
|
||||
markers_pub_ = nh_priv_.advertise<aruco_pose::MarkerArray>("markers", 1);
|
||||
vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1);
|
||||
@@ -136,7 +127,6 @@ private:
|
||||
void imageCallback(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr &cinfo)
|
||||
{
|
||||
if (!enabled_) return;
|
||||
if (waiting_for_map_) return;
|
||||
|
||||
Mat image = cv_bridge::toCvShare(msg, "bgr8")->image;
|
||||
|
||||
@@ -182,7 +172,7 @@ private:
|
||||
if (!known_tilt_.empty()) {
|
||||
try {
|
||||
snap_to = tf_buffer_->lookupTransform(msg->header.frame_id, known_tilt_,
|
||||
msg->header.stamp, transform_timeout_);
|
||||
msg->header.stamp, ros::Duration(0.02));
|
||||
} catch (const tf2::TransformException& e) {
|
||||
NODELET_WARN_THROTTLE(5, "can't snap: %s", e.what());
|
||||
}
|
||||
@@ -191,8 +181,6 @@ private:
|
||||
|
||||
array_.markers.reserve(ids.size());
|
||||
aruco_pose::Marker marker;
|
||||
vector<geometry_msgs::TransformStamped> transforms;
|
||||
transforms.reserve(ids.size());
|
||||
geometry_msgs::TransformStamped transform;
|
||||
transform.header.stamp = msg->header.stamp;
|
||||
transform.header.frame_id = msg->header.frame_id;
|
||||
@@ -210,33 +198,20 @@ private:
|
||||
snapOrientation(marker.pose.orientation, snap_to.transform.rotation, auto_flip_);
|
||||
}
|
||||
|
||||
// TODO: check IDs are unique
|
||||
if (send_tf_) {
|
||||
transform.child_frame_id = getChildFrameId(ids[i]);
|
||||
|
||||
// check if such static transform is in the map
|
||||
if (map_markers_ids_.find(ids[i]) == map_markers_ids_.end()) {
|
||||
// check if a markers with that id is already added
|
||||
bool send = true;
|
||||
for (auto &t : transforms) {
|
||||
if (t.child_frame_id == transform.child_frame_id) {
|
||||
send = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (send) {
|
||||
transform.transform.rotation = marker.pose.orientation;
|
||||
fillTranslation(transform.transform.translation, tvecs[i]);
|
||||
transforms.push_back(transform);
|
||||
}
|
||||
transform.transform.rotation = marker.pose.orientation;
|
||||
fillTranslation(transform.transform.translation, tvecs[i]);
|
||||
br_->sendTransform(transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
array_.markers.push_back(marker);
|
||||
}
|
||||
|
||||
if (send_tf_) {
|
||||
br_->sendTransform(transforms);
|
||||
}
|
||||
}
|
||||
|
||||
markers_pub_.publish(array_);
|
||||
@@ -371,47 +346,17 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
bool setMarkers(aruco_pose::SetMarkers::Request& req, aruco_pose::SetMarkers::Response& res)
|
||||
{
|
||||
for (auto const& marker : req.markers) {
|
||||
if (marker.id > 999) {
|
||||
res.message = "Invalid marker id: " + std::to_string(marker.id);
|
||||
ROS_ERROR("%s", res.message.c_str());
|
||||
return true;
|
||||
}
|
||||
if (!std::isfinite(marker.length) || marker.length <= 0) {
|
||||
res.message = "Invalid marker " + std::to_string(marker.id) + " length: " + std::to_string(marker.length);
|
||||
ROS_ERROR("%s", res.message.c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& marker : req.markers) {
|
||||
length_override_[marker.id] = marker.length;
|
||||
}
|
||||
|
||||
res.success = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void mapMarkersCallback(const aruco_pose::MarkerArray& msg)
|
||||
{
|
||||
map_markers_ids_.clear();
|
||||
for (auto const& marker : msg.markers) {
|
||||
map_markers_ids_.insert(marker.id);
|
||||
if (use_map_markers_) {
|
||||
if (length_override_.find(marker.id) == length_override_.end()) {
|
||||
length_override_[marker.id] = marker.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
waiting_for_map_ = false;
|
||||
}
|
||||
|
||||
void paramCallback(aruco_pose::DetectorConfig &config, uint32_t level)
|
||||
{
|
||||
enabled_ = config.enabled && config.length > 0;
|
||||
length_ = config.length;
|
||||
enabled_ = config.enabled;
|
||||
parameters_->adaptiveThreshConstant = config.adaptiveThreshConstant;
|
||||
parameters_->adaptiveThreshWinSizeMin = config.adaptiveThreshWinSizeMin;
|
||||
parameters_->adaptiveThreshWinSizeMax = config.adaptiveThreshWinSizeMax;
|
||||
|
||||
@@ -19,13 +19,11 @@
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <ros/ros.h>
|
||||
#include <nodelet/nodelet.h>
|
||||
#include <pluginlib/class_list_macros.h>
|
||||
#include <image_transport/image_transport.h>
|
||||
#include <cv_bridge/cv_bridge.h>
|
||||
#include <dynamic_reconfigure/server.h>
|
||||
#include <tf/transform_datatypes.h>
|
||||
#include <tf2_ros/buffer.h>
|
||||
#include <tf2_ros/transform_listener.h>
|
||||
@@ -43,7 +41,6 @@
|
||||
|
||||
#include <aruco_pose/MarkerArray.h>
|
||||
#include <aruco_pose/Marker.h>
|
||||
#include <aruco_pose/MapConfig.h>
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <opencv2/aruco.hpp>
|
||||
@@ -77,9 +74,6 @@ private:
|
||||
tf2_ros::StaticTransformBroadcaster static_br_;
|
||||
tf2_ros::Buffer tf_buffer_;
|
||||
tf2_ros::TransformListener tf_listener_{tf_buffer_};
|
||||
std::shared_ptr<dynamic_reconfigure::Server<aruco_pose::MapConfig>> dyn_srv_;
|
||||
bool enabled_ = true;
|
||||
std::string type_;
|
||||
visualization_msgs::MarkerArray vis_array_;
|
||||
std::string known_tilt_, map_, markers_frame_, markers_parent_frame_;
|
||||
int image_width_, image_height_, image_margin_;
|
||||
@@ -95,14 +89,15 @@ public:
|
||||
|
||||
// TODO: why image_transport doesn't work here?
|
||||
img_pub_ = nh_priv_.advertise<sensor_msgs::Image>("image", 1, true);
|
||||
markers_pub_ = nh_priv_.advertise<aruco_pose::MarkerArray>("map", 1, true);
|
||||
markers_pub_ = nh_priv_.advertise<aruco_pose::MarkerArray>("markers", 1, true);
|
||||
|
||||
board_ = cv::makePtr<cv::aruco::Board>();
|
||||
board_->dictionary = cv::aruco::getPredefinedDictionary(
|
||||
static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(nh_priv_.param("dictionary", 2)));
|
||||
camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
|
||||
|
||||
type_ = nh_priv_.param<std::string>("type", "map");
|
||||
std::string type, map;
|
||||
type = nh_priv_.param<std::string>("type", "map");
|
||||
transform_.child_frame_id = nh_priv_.param<std::string>("frame_id", "aruco_map");
|
||||
known_tilt_ = nh_priv_.param<std::string>("known_tilt", "");
|
||||
auto_flip_ = nh_priv_.param("auto_flip", false);
|
||||
@@ -115,13 +110,13 @@ public:
|
||||
|
||||
// createStripLine();
|
||||
|
||||
if (type_ == "map") {
|
||||
map_ = nh_priv_.param<std::string>("map" , "");
|
||||
loadMap(map_);
|
||||
} else if (type_ == "gridboard") {
|
||||
if (type == "map") {
|
||||
param(nh_priv_, "map", map);
|
||||
loadMap(map);
|
||||
} else if (type == "gridboard") {
|
||||
createGridBoard(nh_priv_);
|
||||
} else {
|
||||
NODELET_FATAL("unknown type: %s", type_.c_str());
|
||||
NODELET_FATAL("unknown type: %s", type.c_str());
|
||||
ros::shutdown();
|
||||
}
|
||||
|
||||
@@ -129,7 +124,10 @@ public:
|
||||
vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1, true);
|
||||
debug_pub_ = it_priv.advertise("debug", 1);
|
||||
|
||||
publishMap();
|
||||
publishMarkersFrames();
|
||||
publishMarkers();
|
||||
publishMapImage();
|
||||
vis_markers_pub_.publish(vis_array_);
|
||||
|
||||
image_sub_.subscribe(nh_, "image_raw", 1);
|
||||
info_sub_.subscribe(nh_, "camera_info", 1);
|
||||
@@ -138,12 +136,6 @@ public:
|
||||
sync_.reset(new message_filters::Synchronizer<SyncPolicy>(SyncPolicy(10), image_sub_, info_sub_, markers_sub_));
|
||||
sync_->registerCallback(boost::bind(&ArucoMap::callback, this, _1, _2, _3));
|
||||
|
||||
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<aruco_pose::MapConfig>>(nh_priv_);
|
||||
dynamic_reconfigure::Server<aruco_pose::MapConfig>::CallbackType cb;
|
||||
|
||||
cb = std::bind(&ArucoMap::paramCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
dyn_srv_->setCallback(cb);
|
||||
|
||||
NODELET_INFO("ready");
|
||||
}
|
||||
|
||||
@@ -151,9 +143,6 @@ public:
|
||||
const sensor_msgs::CameraInfoConstPtr& cinfo,
|
||||
const aruco_pose::MarkerArrayConstPtr& markers)
|
||||
{
|
||||
if (!enabled_) return;
|
||||
if (markers->markers.empty()) return; // map not loaded
|
||||
|
||||
int valid = 0;
|
||||
int count = markers->markers.size();
|
||||
std::vector<int> ids;
|
||||
@@ -279,17 +268,9 @@ publish_debug:
|
||||
std::ifstream f(filename);
|
||||
std::string line;
|
||||
|
||||
clearMarkers();
|
||||
|
||||
if (map_ == "") {
|
||||
NODELET_INFO("No map loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!f.good()) {
|
||||
NODELET_ERROR("%s - %s", strerror(errno), filename.c_str());
|
||||
map_ = "";
|
||||
return;
|
||||
NODELET_FATAL("%s - %s", strerror(errno), filename.c_str());
|
||||
ros::shutdown();
|
||||
}
|
||||
|
||||
while (std::getline(f, line)) {
|
||||
@@ -315,10 +296,9 @@ publish_debug:
|
||||
s.putback(first);
|
||||
} else {
|
||||
// Probably garbage data; inform user and throw an exception, possibly killing nodelet
|
||||
NODELET_ERROR("Malformed input: %s", line.c_str());
|
||||
map_ = "";
|
||||
clearMarkers();
|
||||
return;
|
||||
NODELET_FATAL("Malformed input: %s", line.c_str());
|
||||
ros::shutdown();
|
||||
throw std::runtime_error("Malformed input");
|
||||
}
|
||||
|
||||
if (!(s >> id >> length >> x >> y)) {
|
||||
@@ -349,14 +329,6 @@ publish_debug:
|
||||
NODELET_INFO("loading %s complete (%d markers)", filename.c_str(), static_cast<int>(board_->ids.size()));
|
||||
}
|
||||
|
||||
void publishMap()
|
||||
{
|
||||
publishMarkersFrames();
|
||||
publishMarkers();
|
||||
publishMapImage();
|
||||
vis_markers_pub_.publish(vis_array_);
|
||||
}
|
||||
|
||||
void createGridBoard(ros::NodeHandle& nh)
|
||||
{
|
||||
NODELET_INFO("generate gridboard");
|
||||
@@ -398,15 +370,6 @@ publish_debug:
|
||||
}
|
||||
}
|
||||
|
||||
void clearMarkers()
|
||||
{
|
||||
board_->ids.clear();
|
||||
board_->objPoints.clear();
|
||||
markers_.markers.clear();
|
||||
vis_array_.markers.clear();
|
||||
markers_transforms_.clear();
|
||||
}
|
||||
|
||||
// void createStripLine()
|
||||
// {
|
||||
// visualization_msgs::Marker marker;
|
||||
@@ -503,7 +466,7 @@ publish_debug:
|
||||
vis_marker.pose.position.x = x;
|
||||
vis_marker.pose.position.y = y;
|
||||
vis_marker.pose.position.z = z;
|
||||
tf::quaternionTFToMsg(q, vis_marker.pose.orientation);
|
||||
tf::quaternionTFToMsg(q, marker.pose.orientation);
|
||||
vis_marker.frame_locked = true;
|
||||
vis_array_.markers.push_back(vis_marker);
|
||||
|
||||
@@ -546,22 +509,6 @@ publish_debug:
|
||||
msg.image = image;
|
||||
img_pub_.publish(msg.toImageMsg());
|
||||
}
|
||||
|
||||
void paramCallback(aruco_pose::MapConfig &config, uint32_t level)
|
||||
{
|
||||
// https://github.com/CopterExpress/clover/commit/2cd334c474e3ed04ef65ca1ba7f08ab535a3dc6d#diff-942723f9452c398ae93f1a91427f9a7b614be5e5871f8a3e590f324d804f0d58R356
|
||||
enabled_ = config.enabled;
|
||||
if (type_ == "map" && config.map != map_) {
|
||||
map_ = config.map;
|
||||
loadMap(map_);
|
||||
publishMap();
|
||||
}
|
||||
|
||||
if (config.image_axis != image_axis_) {
|
||||
image_axis_ = config.image_axis;
|
||||
publishMapImage();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PLUGINLIB_EXPORT_CLASS(ArucoMap, nodelet::Nodelet)
|
||||
|
||||
@@ -30,7 +30,7 @@ Options:
|
||||
-o <filename> Output map file name in the 'map' subdirectory of aruco_pose package
|
||||
|
||||
Example:
|
||||
rosrun aruco_pose genmap.py 0.33 2 4 1 1 0 -o test_map.txt
|
||||
rosrun aruco_pose genmap.py 0.33 2 4 1 1 0 > $(catkin_find aruco_pose map)/test_map.txt
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# * Add or change markers in the map
|
||||
# * Change markers' properties, e. g. lengths
|
||||
|
||||
Marker[] markers # if length or pose is nan - remove from map
|
||||
---
|
||||
bool success
|
||||
string message
|
||||
@@ -6,7 +6,7 @@ import tf2_geometry_msgs
|
||||
from geometry_msgs.msg import PoseWithCovarianceStamped
|
||||
from sensor_msgs.msg import Image
|
||||
from aruco_pose.msg import MarkerArray
|
||||
from visualization_msgs.msg import MarkerArray as VisMarkerArray, Marker as VisMarker
|
||||
from visualization_msgs.msg import MarkerArray as VisMarkerArray
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -143,7 +143,7 @@ def test_map_image(node):
|
||||
assert img.encoding in ('mono8', 'rgb8')
|
||||
|
||||
def test_map_markers(node):
|
||||
markers = rospy.wait_for_message('aruco_map/map', MarkerArray, timeout=5)
|
||||
markers = rospy.wait_for_message('aruco_map/markers', MarkerArray, timeout=5)
|
||||
assert markers.markers[0].id == 1
|
||||
assert markers.markers[1].id == 2
|
||||
assert markers.markers[2].id == 3
|
||||
@@ -199,36 +199,6 @@ def test_map_markers(node):
|
||||
|
||||
def test_map_visualization(node):
|
||||
vis = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
|
||||
assert len(vis.markers) == 7
|
||||
assert vis.markers[0].header.frame_id == 'aruco_map'
|
||||
assert vis.markers[0].type == VisMarker.CUBE
|
||||
assert vis.markers[0].action == VisMarker.ADD
|
||||
assert vis.markers[0].pose.position.x == 0
|
||||
assert vis.markers[0].pose.position.y == 0
|
||||
assert vis.markers[0].pose.position.z == 0
|
||||
assert vis.markers[0].pose.orientation.x == 0
|
||||
assert vis.markers[0].pose.orientation.y == 0
|
||||
assert vis.markers[0].pose.orientation.z == 0
|
||||
assert vis.markers[0].pose.orientation.w == 1
|
||||
assert vis.markers[0].scale.x == approx(0.33)
|
||||
assert vis.markers[0].scale.y == approx(0.33)
|
||||
assert vis.markers[0].scale.z == approx(0.001)
|
||||
assert vis.markers[1].pose.position.x == 1
|
||||
assert vis.markers[1].pose.position.y == 0
|
||||
assert vis.markers[1].pose.position.z == 0
|
||||
assert vis.markers[1].pose.orientation.x == 0
|
||||
assert vis.markers[1].pose.orientation.y == 0
|
||||
assert vis.markers[1].pose.orientation.z == 0
|
||||
assert vis.markers[1].pose.orientation.w == 1
|
||||
# non-zero yaw marker:
|
||||
assert vis.markers[4].scale.x == approx(0.5)
|
||||
assert vis.markers[4].pose.position.x == approx(0.5)
|
||||
assert vis.markers[4].pose.position.y == 2
|
||||
assert vis.markers[4].pose.position.z == 0
|
||||
assert vis.markers[4].pose.orientation.x == 0
|
||||
assert vis.markers[4].pose.orientation.y == 0
|
||||
assert vis.markers[4].pose.orientation.z == approx(0.5646424733950354)
|
||||
assert vis.markers[4].pose.orientation.w == approx(0.8253356149096783)
|
||||
|
||||
def test_map_debug(node):
|
||||
img = rospy.wait_for_message('aruco_map/debug', Image, timeout=5)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 62 KiB |
@@ -1,8 +0,0 @@
|
||||
import pytest
|
||||
import subprocess
|
||||
|
||||
def test_no_tf_repeated_data():
|
||||
# `/rosout` acts weirdly inside rostest, so using a subprocess
|
||||
cmd = """python -c 'import rospy, tf; rospy.init_node("foo"); listener = tf.TransformListener(); rospy.sleep(2)'"""
|
||||
output = str(subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT))
|
||||
assert 'TF_REPEATED_DATA' not in output, 'TF_REPEATED_DATA was logged on duplicate markers'
|
||||
@@ -1,21 +0,0 @@
|
||||
<launch>
|
||||
<node pkg="image_publisher" type="image_publisher" name="main_camera" args="$(find aruco_pose)/test/duplicate.png">
|
||||
<param name="frame_id" value="main_camera_optical"/>
|
||||
<param name="publish_rate" value="10"/>
|
||||
<param name="camera_info_url" value="file://$(find aruco_pose)/test/camera_info.yaml" />
|
||||
</node>
|
||||
|
||||
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager" required="true"/>
|
||||
|
||||
<node pkg="nodelet" clear_params="true" type="nodelet" name="aruco_detect" args="load aruco_pose/aruco_detect nodelet_manager" required="true">
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<param name="length" value="0.33"/>
|
||||
<param name="estimate_poses" value="true"/>
|
||||
<param name="send_tf" value="true"/>
|
||||
<param name="cornerRefinementMethod" value="1"/>
|
||||
</node>
|
||||
|
||||
<param name="test_module" value="$(find aruco_pose)/test/duplicate.py"/>
|
||||
<test test-name="aruco_pose_test" pkg="ros_pytest" type="ros_pytest_runner"/>
|
||||
</launch>
|
||||
@@ -2,7 +2,6 @@ import rospy
|
||||
import pytest
|
||||
|
||||
from visualization_msgs.msg import MarkerArray as VisMarkerArray
|
||||
from aruco_pose.msg import MarkerArray
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -10,5 +9,5 @@ def node():
|
||||
return rospy.init_node('aruco_pose_test', anonymous=True)
|
||||
|
||||
def test_node_failure(node):
|
||||
assert rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5).markers == []
|
||||
assert rospy.wait_for_message('aruco_map/map', MarkerArray, timeout=5).markers == []
|
||||
with pytest.raises(rospy.exceptions.ROSException):
|
||||
rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"yametrika",
|
||||
"anchors",
|
||||
"collapsible-menu",
|
||||
"validate-links@https://github.com/okalachev/gitbook-plugin-validate-links.git",
|
||||
"validate-links",
|
||||
"bulk-redirect@https://github.com/okalachev/gitbook-plugin-bulk-redirect.git",
|
||||
"sitemap@https://github.com/okalachev/plugin-sitemap.git",
|
||||
"toolbar@https://github.com/hamishwillee/gitbook-plugin-toolbar.git",
|
||||
|
||||
@@ -8,9 +8,5 @@ ExecStart=/bin/bash -c ". /home/pi/catkin_ws/devel/setup.sh; \
|
||||
ROS_HOSTNAME=`hostname`.local exec stdbuf -o L roslaunch clover clover.launch --wait --screen --skip-log-check \
|
||||
2> >(tee /tmp/clover.err)"
|
||||
|
||||
ExecStartPre=+rm /var/log/clover.log
|
||||
StandardOutput=file:/var/log/clover.log
|
||||
StandardError=file:/var/log/clover.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
@@ -15,17 +15,17 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
|
||||
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
|
||||
land = rospy.ServiceProxy('land', Trigger)
|
||||
|
||||
print('Take off and hover 1 m above the ground')
|
||||
# Take off and hover 1 m above the ground
|
||||
navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True)
|
||||
|
||||
# Wait for 5 seconds
|
||||
rospy.sleep(5)
|
||||
# Wait for 3 seconds
|
||||
rospy.sleep(3)
|
||||
|
||||
print('Fly forward 1 m')
|
||||
# Fly forward 1 m
|
||||
navigate(x=1, y=0, z=0, frame_id='body')
|
||||
|
||||
# Wait for 5 seconds
|
||||
rospy.sleep(5)
|
||||
# Wait for 3 seconds
|
||||
rospy.sleep(3)
|
||||
|
||||
print('Perform landing')
|
||||
# Perform landing
|
||||
land()
|
||||
@@ -15,23 +15,23 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
|
||||
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
|
||||
land = rospy.ServiceProxy('land', Trigger)
|
||||
|
||||
print('Take off and hover 1 m above the ground')
|
||||
# Take off and hover 1 m above the ground
|
||||
navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True)
|
||||
|
||||
# Wait for 5 seconds
|
||||
rospy.sleep(5)
|
||||
# Wait for 3 seconds
|
||||
rospy.sleep(3)
|
||||
|
||||
print('Fly 1 meter above ArUco marker 0')
|
||||
# Fly 1 meter above ArUco marker 0
|
||||
navigate(x=0, y=0, z=1, frame_id='aruco_0')
|
||||
|
||||
# Wait for 5 seconds
|
||||
rospy.sleep(5)
|
||||
# Wait for 3 seconds
|
||||
rospy.sleep(3)
|
||||
|
||||
print('Fly to x=1 y=1 z=1 relative to ArUco markers map')
|
||||
# Fly to x=1 y=1 z=1 relative to ArUco markers map
|
||||
navigate(x=1, y=1, z=1, frame_id='aruco_map')
|
||||
|
||||
# Wait for 5 seconds
|
||||
rospy.sleep(5)
|
||||
# Wait for 3 seconds
|
||||
rospy.sleep(3)
|
||||
|
||||
print('Perform landing')
|
||||
# Perform landing
|
||||
land()
|
||||
@@ -31,11 +31,11 @@ def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \
|
||||
return res
|
||||
rospy.sleep(0.2)
|
||||
|
||||
print('Take off 1 meter')
|
||||
# Take off 1 meter
|
||||
navigate_wait(z=1, frame_id='body', auto_arm=True)
|
||||
|
||||
print('Fly forward 1 m')
|
||||
# Fly forward 1 m
|
||||
navigate_wait(x=1, frame_id='body')
|
||||
|
||||
print('Land')
|
||||
# Land
|
||||
land()
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# GitBook CLI is deprecated, its installation is broken.
|
||||
# This script fixes it until we stop using GitBook.
|
||||
|
||||
export NPM_CONFIG_UNSAFE_PERM=1
|
||||
|
||||
npm install gitbook-cli -g
|
||||
gitbook fetch 3.2.3 && npm i npm@3.10.10 --prefix=~/.gitbook/versions/3.2.3/ # fixing https://travis-ci.org/github/CopterExpress/clover/jobs/766541125#L932
|
||||
@@ -16,7 +16,7 @@
|
||||
set -e # Exit immidiately on non-zero result
|
||||
|
||||
# https://www.raspberrypi.org/software/operating-systems/#raspberry-pi-os-32-bit
|
||||
SOURCE_IMAGE="https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-05-28/2021-05-07-raspios-buster-armhf-lite.zip"
|
||||
SOURCE_IMAGE="https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-03-25/2021-03-04-raspios-buster-armhf-lite.zip"
|
||||
|
||||
export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:='noninteractive'}
|
||||
export LANG=${LANG:='C.UTF-8'}
|
||||
@@ -105,6 +105,8 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/butterf
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/monkey.service' '/lib/systemd/system/'
|
||||
# software install
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-software.sh'
|
||||
# examples
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/examples' '/home/pi/' # TODO: symlink?
|
||||
# network setup
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-network.sh'
|
||||
# avahi setup
|
||||
@@ -113,11 +115,15 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/avahi-s
|
||||
# If RPi then use a one thread to build a ROS package on RPi, else use all
|
||||
[[ $(arch) == 'armv7l' ]] && NUMBER_THREADS=1 || NUMBER_THREADS=$(nproc --all)
|
||||
# Clover
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clover.service' '/lib/systemd/system/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/roscore.service' '/lib/systemd/system/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/noetic-rosdep-clover.yaml' '/etc/ros/rosdep/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/ros_python_paths' '/etc/sudoers.d/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/pigpiod.service' '/lib/systemd/system/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/launch.nanorc' '/usr/share/nano/'
|
||||
# ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-ros-clover.rosinstall' '/home/pi/ros_catkin_ws/'
|
||||
# Add PX4 udev rules
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${REPO_DIR}'/clover/config/99-px4fmu.rules' '/lib/udev/rules.d/'
|
||||
# Add rename script
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-ros.sh' ${REPO_URL} ${IMAGE_VERSION} false false ${NUMBER_THREADS}
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh'
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
|
||||
set -ex # exit on error, echo commands
|
||||
set -e # Exit immidiately on non-zero result
|
||||
|
||||
REPO=$1
|
||||
REF=$2
|
||||
@@ -90,7 +90,7 @@ echo_stamp "Installing OpenCV 4.2-compatible ROS packages"
|
||||
apt install -y --no-install-recommends \
|
||||
ros-${ROS_DISTRO}-compressed-image-transport=1.14.0-0buster \
|
||||
ros-${ROS_DISTRO}-cv-bridge=1.15.0-0buster \
|
||||
ros-${ROS_DISTRO}-cv-camera=0.5.1-0buster \
|
||||
ros-${ROS_DISTRO}-cv-camera=0.5.0-0buster \
|
||||
ros-${ROS_DISTRO}-image-publisher=1.15.3-0buster \
|
||||
ros-${ROS_DISTRO}-web-video-server=0.2.1-0buster
|
||||
apt-mark hold \
|
||||
@@ -112,8 +112,7 @@ my_travis_retry pip3 install wheel
|
||||
my_travis_retry pip3 install -r /home/pi/catkin_ws/src/clover/clover/requirements.txt
|
||||
source /opt/ros/${ROS_DISTRO}/setup.bash
|
||||
# Don't build simulation plugins for actual drone
|
||||
sudo -E -u pi sh -c '. /opt/ros/${ROS_DISTRO}/setup.sh && catkin_make -j2 -DCMAKE_BUILD_TYPE=RelWithDebInfo'
|
||||
source devel/setup.bash
|
||||
catkin_make -j2 -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCATKIN_BLACKLIST_PACKAGES=clover_gazebo_plugins
|
||||
|
||||
echo_stamp "Install clever package (for backwards compatibility)"
|
||||
cd /home/pi/catkin_ws/src/clover/builder/assets/clever
|
||||
@@ -122,8 +121,9 @@ rm -rf build # remove build artifacts
|
||||
|
||||
echo_stamp "Build Clover documentation"
|
||||
cd /home/pi/catkin_ws/src/clover
|
||||
builder/assets/install_gitbook.sh
|
||||
gitbook install
|
||||
NPM_CONFIG_UNSAFE_PERM=true npm install gitbook-cli -g
|
||||
NPM_CONFIG_UNSAFE_PERM=true gitbook fetch 3.2.3 && npm i npm@3.10.10 --prefix=~/.gitbook/versions/3.2.3/ # fixing https://travis-ci.org/github/CopterExpress/clover/jobs/766541125#L932
|
||||
NPM_CONFIG_UNSAFE_PERM=true gitbook install
|
||||
gitbook build
|
||||
touch node_modules/CATKIN_IGNORE docs/CATKIN_IGNORE _book/CATKIN_IGNORE clover/www/CATKIN_IGNORE apps/CATKIN_IGNORE # ignore documentation files by catkin
|
||||
|
||||
@@ -135,9 +135,7 @@ my_travis_retry apt-get install -y --no-install-recommends \
|
||||
ros-${ROS_DISTRO}-usb-cam \
|
||||
ros-${ROS_DISTRO}-vl53l1x \
|
||||
ros-${ROS_DISTRO}-ws281x \
|
||||
ros-${ROS_DISTRO}-rosshow \
|
||||
ros-${ROS_DISTRO}-cmake-modules \
|
||||
ros-${ROS_DISTRO}-image-view
|
||||
ros-${ROS_DISTRO}-rosshow
|
||||
|
||||
# TODO move GeographicLib datasets to Mavros debian package
|
||||
echo_stamp "Install GeographicLib datasets (needed for mavros)" \
|
||||
@@ -151,20 +149,9 @@ catkin_make run_tests #&& catkin_test_results
|
||||
echo_stamp "Change permissions for catkin_ws"
|
||||
chown -Rf pi:pi /home/pi/catkin_ws
|
||||
|
||||
echo_stamp "Make \$HOME/examples symlink"
|
||||
ln -s "$(catkin_find clover examples --first-only)" /home/pi
|
||||
echo_stamp "Change permissions for examples"
|
||||
chown -Rf pi:pi /home/pi/examples
|
||||
|
||||
echo_stamp "Make systemd services symlinks"
|
||||
ln -s /home/pi/catkin_ws/src/clover/builder/assets/clover.service /lib/systemd/system/
|
||||
ln -s /home/pi/catkin_ws/src/clover/builder/assets/roscore.service /lib/systemd/system/
|
||||
# validate
|
||||
[ -f /lib/systemd/system/clover.service ]
|
||||
[ -f /lib/systemd/system/roscore.service ]
|
||||
|
||||
echo_stamp "Make udev rules symlink"
|
||||
ln -s "$(catkin_find clover udev --first-only)"/* /lib/udev/rules.d/
|
||||
|
||||
echo_stamp "Setup ROS environment"
|
||||
cat << EOF >> /home/pi/.bashrc
|
||||
LANG='C.UTF-8'
|
||||
|
||||
@@ -137,8 +137,6 @@ pip3 --version
|
||||
echo_stamp "Install and enable Butterfly (web terminal)"
|
||||
echo_stamp "Workaround for tornado >= 6.0 breaking butterfly"
|
||||
export CRYPTOGRAPHY_DONT_BUILD_RUST=1
|
||||
my_travis_retry pip3 install cryptography==3.4.6 # https://stackoverflow.com/a/68472128/6850197
|
||||
my_travis_retry pip3 install pyOpenSSL==20.0.1
|
||||
my_travis_retry pip3 install tornado==5.1.1
|
||||
my_travis_retry pip3 install butterfly
|
||||
my_travis_retry pip3 install butterfly[systemd]
|
||||
@@ -150,12 +148,11 @@ my_travis_retry pip3 install --prefer-binary rpi_ws281x
|
||||
echo_stamp "Setup Monkey"
|
||||
mv /etc/monkey/sites/default /etc/monkey/sites/default.orig
|
||||
mv /root/monkey /etc/monkey/sites/default
|
||||
sed -i 's/SymLink Off/SymLink On/' /etc/monkey/monkey.conf
|
||||
systemctl enable monkey.service
|
||||
|
||||
echo_stamp "Install Node.js"
|
||||
cd /home/pi
|
||||
wget --no-verbose https://nodejs.org/dist/v10.15.0/node-v10.15.0-linux-armv6l.tar.gz
|
||||
wget https://nodejs.org/dist/v10.15.0/node-v10.15.0-linux-armv6l.tar.gz
|
||||
tar -xzf node-v10.15.0-linux-armv6l.tar.gz
|
||||
cp -R node-v10.15.0-linux-armv6l/* /usr/local/
|
||||
rm -rf node-v10.15.0-linux-armv6l/
|
||||
|
||||
@@ -20,20 +20,12 @@ export ROS_DISTRO='noetic'
|
||||
export ROS_IP='127.0.0.1'
|
||||
source /opt/ros/${ROS_DISTRO}/setup.bash
|
||||
source /home/pi/catkin_ws/devel/setup.bash
|
||||
systemctl start roscore
|
||||
|
||||
cd /home/pi/catkin_ws/src/clover/builder/test/
|
||||
./tests.sh
|
||||
./tests.py
|
||||
./tests_py3.py
|
||||
[[ $(./test_qr.py) == "Found QRCODE with data Проверка Unicode with center at x=66.0, y=66.0" ]]
|
||||
[[ $(./tests_clever.py) == "Warning: clever package is renamed to clover" ]] # test backwards compatibility
|
||||
|
||||
systemctl stop roscore
|
||||
|
||||
# check documented packages available
|
||||
apt-cache show gst-rtsp-launch
|
||||
apt-cache show openvpn
|
||||
|
||||
echo "Move /etc/ld.so.preload back to its original position"
|
||||
mv /etc/ld.so.preload.disabled-for-build /etc/ld.so.preload
|
||||
|
||||
@@ -3,17 +3,10 @@
|
||||
# Perform a "standalone install" in a Docker container
|
||||
set -e
|
||||
# Step 1: Install pip
|
||||
apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 # https://github.com/osrf/docker_images/issues/535
|
||||
apt-get update
|
||||
apt-get install -y curl
|
||||
if [ "x${ROS_PYTHON_VERSION}" = "x3" ]; then
|
||||
PYTHON=python3
|
||||
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||
else
|
||||
PYTHON=python
|
||||
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip.py
|
||||
fi
|
||||
${PYTHON} ./get-pip.py
|
||||
apt update
|
||||
apt install -y curl
|
||||
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||
python3 ./get-pip.py
|
||||
|
||||
# Step 1.5: Add deb.coex.tech to apt
|
||||
curl http://deb.coex.tech/aptly_repo_signing.key 2> /dev/null | apt-key add -
|
||||
@@ -41,7 +34,7 @@ ws281x:
|
||||
ubuntu:
|
||||
${CODENAME}: [ros-${ROS_DISTRO}-ws281x]
|
||||
EOF
|
||||
apt-get update
|
||||
apt update
|
||||
rosdep update
|
||||
|
||||
# Step 2: Run rosdep to install all dependencies
|
||||
@@ -57,10 +50,7 @@ cd /root/catkin_ws
|
||||
catkin_make
|
||||
|
||||
# Step 4: Run tests
|
||||
${PYTHON} -m pip install --upgrade pytest
|
||||
python3 -m pip install --upgrade pytest # TODO: https://github.com/CopterExpress/clover/commit/5b970d51970cfa6f46e5c0b34acb7889d36b89c8
|
||||
cd /root/catkin_ws
|
||||
source devel/setup.bash
|
||||
catkin_make run_tests && catkin_test_results
|
||||
|
||||
# Step 5: Install packages
|
||||
catkin_make install
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Test QG recognition example
|
||||
# Should be synced with the documentation: /docs/en/camera.md, /docs/ru/camera.md
|
||||
# TODO: use real ROS topics
|
||||
|
||||
import rospy
|
||||
from pyzbar import pyzbar
|
||||
from cv_bridge import CvBridge
|
||||
from sensor_msgs.msg import Image
|
||||
|
||||
bridge = CvBridge()
|
||||
|
||||
# rospy.init_node('barcode_test')
|
||||
|
||||
# Image subscriber callback function
|
||||
def image_callback(data):
|
||||
cv_image = bridge.imgmsg_to_cv2(data, 'bgr8') # OpenCV image
|
||||
barcodes = pyzbar.decode(cv_image)
|
||||
for barcode in barcodes:
|
||||
b_data = barcode.data.decode("utf-8")
|
||||
b_type = barcode.type
|
||||
(x, y, w, h) = barcode.rect
|
||||
xc = x + w/2
|
||||
yc = y + h/2
|
||||
print("Found {} with data {} with center at x={}, y={}".format(b_type, b_data, xc, yc))
|
||||
# rospy.signal_shutdown('done')
|
||||
|
||||
# image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1)
|
||||
|
||||
# ==============================================================================
|
||||
# Publish test image
|
||||
# rospy.sleep(2)
|
||||
|
||||
import cv2
|
||||
img = cv2.imread('qr.png')
|
||||
image_callback(bridge.cv2_to_imgmsg(img, 'bgr8'))
|
||||
|
||||
# image_pub = rospy.Publisher('/main_camera/image_raw', Image, queue_size=1, latch=True)
|
||||
# image_pub.publish(bridge.cv2_to_imgmsg(img, 'bgr8'))
|
||||
|
||||
# rospy.spin()
|
||||
@@ -2,30 +2,20 @@
|
||||
|
||||
# validate all required modules installed
|
||||
|
||||
import os
|
||||
import rospy
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from sensor_msgs.msg import Range, BatteryState
|
||||
|
||||
import cv2
|
||||
import cv2.aruco
|
||||
from sensor_msgs.msg import Image
|
||||
from cv_bridge import CvBridge
|
||||
|
||||
import numpy
|
||||
import mavros
|
||||
from mavros_msgs.msg import State, StatusText, ExtendedState, RCIn, Mavlink
|
||||
from mavros_msgs.msg import State, StatusText, ExtendedState
|
||||
from mavros_msgs.srv import CommandBool, CommandLong, SetMode
|
||||
|
||||
from std_srvs.srv import Trigger
|
||||
from clover.srv import GetTelemetry, Navigate, NavigateGlobal, SetPosition, SetVelocity, \
|
||||
SetAttitude, SetRates, SetLEDEffect
|
||||
from led_msgs.srv import SetLEDs
|
||||
from led_msgs.msg import LEDStateArray, LEDState
|
||||
from aruco_pose.msg import Marker, MarkerArray, Point2D
|
||||
from clover import long_callback
|
||||
|
||||
import dynamic_reconfigure.client
|
||||
|
||||
import tf2_ros
|
||||
import tf2_geometry_msgs
|
||||
@@ -33,11 +23,9 @@ import tf2_geometry_msgs
|
||||
import VL53L1X
|
||||
import pymavlink
|
||||
from pymavlink import mavutil
|
||||
import rpi_ws281x
|
||||
import pigpio
|
||||
# from espeak import espeak
|
||||
from pyzbar import pyzbar
|
||||
|
||||
print(cv2.getBuildInformation())
|
||||
|
||||
if not os.environ.get('VM'):
|
||||
import rpi_ws281x
|
||||
import pigpio
|
||||
|
||||
@@ -6,10 +6,16 @@ set -ex
|
||||
|
||||
# validate required software is installed
|
||||
|
||||
python --version
|
||||
python2 --version
|
||||
python3 --version
|
||||
ipython --version
|
||||
ipython3 --version
|
||||
|
||||
# ptvsd does not have a stand-alone binary
|
||||
python -m ptvsd --version
|
||||
python3 -m ptvsd --version
|
||||
|
||||
node -v
|
||||
npm -v
|
||||
|
||||
@@ -19,64 +25,35 @@ lsof -v
|
||||
git --version
|
||||
vim --version
|
||||
pip --version
|
||||
pip2 --version
|
||||
pip3 --version
|
||||
tcpdump --version
|
||||
monkey --version
|
||||
pigpiod -v
|
||||
i2cdetect -V
|
||||
butterfly -h
|
||||
# espeak --version
|
||||
systemctl --version
|
||||
|
||||
if [ -z $VM ]; then
|
||||
# rpi only software
|
||||
python --version
|
||||
ipython --version
|
||||
pip2 --version
|
||||
# `python` is python2 for now
|
||||
[[ $(python -c 'import sys;print(sys.version_info.major)') == "2" ]]
|
||||
|
||||
# ptvsd does not have a stand-alone binary
|
||||
python -m ptvsd --version
|
||||
python3 -m ptvsd --version
|
||||
|
||||
pigpiod -v
|
||||
i2cdetect -V
|
||||
butterfly -h
|
||||
mjpg_streamer --version
|
||||
fi
|
||||
mjpg_streamer --version
|
||||
|
||||
# ros stuff
|
||||
|
||||
roscore -h
|
||||
rosversion clover
|
||||
rosversion aruco_pose
|
||||
rosversion vl53l1x
|
||||
rosversion mavros
|
||||
rosversion mavros_extras
|
||||
rosversion ws281x
|
||||
rosversion led_msgs
|
||||
rosversion dynamic_reconfigure
|
||||
rosversion tf2_web_republisher
|
||||
rosversion rosbridge_server
|
||||
rosversion compressed_image_transport
|
||||
rosversion rosbridge_suite
|
||||
rosversion rosserial
|
||||
rosversion usb_cam
|
||||
rosversion cv_camera
|
||||
rosversion web_video_server
|
||||
rosversion nodelet
|
||||
rosversion image_view
|
||||
|
||||
[[ $(rosversion ws281x) == "0.0.13" ]]
|
||||
|
||||
if [ -z $VM ]; then
|
||||
rosversion compressed_image_transport
|
||||
rosversion rosshow
|
||||
rosversion vl53l1x
|
||||
rosversion rosserial
|
||||
[[ $(rosversion cv_camera) == "0.5.1" ]] # patched version with init fix
|
||||
fi
|
||||
|
||||
[ $VM ] && H="/home/clover" || H="/home/pi"
|
||||
rosversion rosshow
|
||||
|
||||
# validate examples are present
|
||||
[[ $(ls $H/examples/*) ]]
|
||||
|
||||
# validate web tools present
|
||||
[ -d $H/.ros/www ]
|
||||
[ "$(readlink $H/.ros/www/clover)" = "$H/catkin_ws/src/clover/clover/www" ]
|
||||
[ "$(readlink $H/.ros/www/clover_blocks)" = "$H/catkin_ws/src/clover/clover_blocks/www" ]
|
||||
[[ $(ls /home/pi/examples/*) ]]
|
||||
|
||||
@@ -5,7 +5,7 @@ import sys
|
||||
import subprocess
|
||||
|
||||
EXCLUDE = ('clever4-front-white.png', 'clever4-front-white-large.png', '.DS_Store',
|
||||
'clever4-front-black-large.png','clover42-black.png', 'clover42-main-margin.png')
|
||||
'clever4-front-black-large.png','clover42-black.png')
|
||||
|
||||
code = 0
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ find_package(catkin REQUIRED COMPONENTS
|
||||
message_generation
|
||||
geometry_msgs
|
||||
sensor_msgs
|
||||
led_msgs
|
||||
geographic_msgs
|
||||
tf
|
||||
tf2
|
||||
@@ -25,7 +24,6 @@ find_package(catkin REQUIRED COMPONENTS
|
||||
tf2_ros
|
||||
image_transport
|
||||
cv_bridge
|
||||
dynamic_reconfigure
|
||||
)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
|
||||
@@ -53,7 +51,7 @@ find_package(OpenCV ${_opencv_version} REQUIRED
|
||||
## Uncomment this if the package has a setup.py. This macro ensures
|
||||
## modules and global scripts declared therein get installed
|
||||
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
|
||||
catkin_python_setup()
|
||||
# catkin_python_setup()
|
||||
|
||||
################################################
|
||||
## Declare ROS messages, services and actions ##
|
||||
@@ -128,9 +126,10 @@ generate_messages(
|
||||
## and list every .cfg file to be processed
|
||||
|
||||
## Generate dynamic reconfigure parameters in the 'cfg' folder
|
||||
generate_dynamic_reconfigure_options(
|
||||
cfg/Flow.cfg
|
||||
)
|
||||
# generate_dynamic_reconfigure_options(
|
||||
# cfg/DynReconf1.cfg
|
||||
# cfg/DynReconf2.cfg
|
||||
# )
|
||||
|
||||
###################################
|
||||
## catkin specific configuration ##
|
||||
@@ -186,6 +185,9 @@ add_executable(clover_led src/led.cpp)
|
||||
|
||||
add_executable(shell src/shell.cpp)
|
||||
|
||||
# FIXME: hack to fix https://travis-ci.org/github/CopterExpress/clover/jobs/766318908#L6532
|
||||
string(REPLACE "-lpthreads;" "" catkin_LIBRARIES "${catkin_LIBRARIES}")
|
||||
|
||||
target_link_libraries(simple_offboard
|
||||
${catkin_LIBRARIES}
|
||||
${GeographicLib_LIBRARIES}
|
||||
@@ -212,8 +214,6 @@ add_dependencies(clover_led ${PROJECT_NAME}_generate_messages_cpp)
|
||||
|
||||
add_dependencies(shell ${PROJECT_NAME}_generate_messages_cpp)
|
||||
|
||||
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}_gencfg)
|
||||
|
||||
## Rename C++ executable without prefix
|
||||
## The above recommended prefix causes long target names, the following renames the
|
||||
## target back to the shorter version for ease of user use
|
||||
@@ -230,9 +230,6 @@ target_link_libraries(${PROJECT_NAME}
|
||||
${OpenCV_LIBRARIES}
|
||||
)
|
||||
|
||||
# Set Clover to default www page
|
||||
set(ROSWWW_STATIC_DEFAULT ${PROJECT_NAME})
|
||||
|
||||
#############
|
||||
## Install ##
|
||||
#############
|
||||
@@ -247,12 +244,12 @@ set(ROSWWW_STATIC_DEFAULT ${PROJECT_NAME})
|
||||
# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
# )
|
||||
|
||||
# Mark executables and/or libraries for installation
|
||||
install(TARGETS simple_offboard clover_rc camera_markers vpe_publisher clover_led shell clover
|
||||
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
)
|
||||
## Mark executables and/or libraries for installation
|
||||
# install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node
|
||||
# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
|
||||
# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
# )
|
||||
|
||||
## Mark cpp header files for installation
|
||||
# install(DIRECTORY include/${PROJECT_NAME}/
|
||||
@@ -272,21 +269,13 @@ catkin_install_python(PROGRAMS src/selfcheck.py
|
||||
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
)
|
||||
|
||||
install(DIRECTORY launch DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
|
||||
|
||||
install(DIRECTORY examples DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
|
||||
|
||||
install(DIRECTORY www DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
|
||||
|
||||
# TODO: install www
|
||||
|
||||
# Only install udev rules when building a Debian package
|
||||
# FIXME: Other operating systems may have other prefixes
|
||||
string(FIND ${CMAKE_INSTALL_PREFIX} "/opt/ros" _PREFIX_INDEX)
|
||||
if (${_PREFIX_INDEX} EQUAL 0)
|
||||
message(STATUS "Building as a Debian package - adding udev rules as installable files")
|
||||
install(FILES
|
||||
udev/99-px4fmu.rules
|
||||
config/99-px4fmu.rules
|
||||
DESTINATION /lib/udev/rules.d
|
||||
)
|
||||
else()
|
||||
|
||||
@@ -36,7 +36,7 @@ curl https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/inst
|
||||
You may optionally install udev rules to provide `/dev/px4fmu` symlink to your PX4-based flight controller connected over USB. Copy `99-px4fmu.rules` to your `/lib/udev/rules.d` folder:
|
||||
|
||||
```bash
|
||||
cd ~/catkin_ws/src/clover/clover/udev
|
||||
cd ~/catkin_ws/src/clover/clover/config
|
||||
sudo cp 99-px4fmu.rules /lib/udev/rules.d
|
||||
```
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
PACKAGE = "clover"
|
||||
|
||||
from dynamic_reconfigure.parameter_generator_catkin import *
|
||||
|
||||
gen = ParameterGenerator()
|
||||
|
||||
gen.add("enabled", bool_t, 0, "if optical flow enabled", True)
|
||||
|
||||
exit(gen.generate(PACKAGE, "clover", "Flow"))
|
||||
@@ -12,6 +12,4 @@ SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{produ
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0013", ATTRS{product}=="PX4 FMU v4.x PRO", SYMLINK+="px4fmu"
|
||||
# Omnibus
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="PX4 OmnibusF4SD", SYMLINK+="px4fmu"
|
||||
# CUAV X7 Pro
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV X7Pro", SYMLINK+="px4fmu"
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
# Information: https://clover.coex.tech/camera
|
||||
|
||||
# Example on basic working with the camera and image processing:
|
||||
|
||||
# - cuts out a central square from the camera image;
|
||||
# - publishes this cropped image to the topic `/cv/center`;
|
||||
# - computes the average color of it;
|
||||
# - prints its name to the console.
|
||||
|
||||
import rospy
|
||||
import cv2
|
||||
from sensor_msgs.msg import Image
|
||||
from cv_bridge import CvBridge
|
||||
from clover import long_callback
|
||||
|
||||
rospy.init_node('cv')
|
||||
bridge = CvBridge()
|
||||
|
||||
printed_color = None
|
||||
center_pub = rospy.Publisher('~center', Image, queue_size=1)
|
||||
|
||||
def get_color_name(h):
|
||||
if h < 15: return 'red'
|
||||
if h < 30: return 'orange'
|
||||
elif h < 60: return 'yellow'
|
||||
elif h < 90: return 'green'
|
||||
elif h < 120: return 'cyan'
|
||||
elif h < 150: return 'blue'
|
||||
elif h < 170: return 'magenta'
|
||||
else: return 'red'
|
||||
|
||||
|
||||
@long_callback
|
||||
def image_callback(msg):
|
||||
img = bridge.imgmsg_to_cv2(msg, 'bgr8')
|
||||
|
||||
# convert to HSV to work with color hue
|
||||
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# cut out a central square
|
||||
w = img.shape[1]
|
||||
h = img.shape[0]
|
||||
r = 20
|
||||
center = img_hsv[h // 2 - r:h // 2 + r, w // 2 - r:w // 2 + r]
|
||||
|
||||
# compute and print the average hue
|
||||
mean_hue = center[:, :, 0].mean()
|
||||
color = get_color_name(mean_hue)
|
||||
global printed_color
|
||||
if color != printed_color:
|
||||
print(color)
|
||||
printed_color = color
|
||||
|
||||
# publish the cropped image
|
||||
center = cv2.cvtColor(center, cv2.COLOR_HSV2BGR)
|
||||
center_pub.publish(bridge.cv2_to_imgmsg(center, 'bgr8'))
|
||||
|
||||
# process every frame:
|
||||
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1)
|
||||
|
||||
# process 5 frames per second:
|
||||
# image_sub = rospy.Subscriber('main_camera/image_raw_throttled', Image, image_callback, queue_size=1)
|
||||
|
||||
rospy.spin()
|
||||
@@ -1,11 +0,0 @@
|
||||
# Information: https://clover.coex.tech/en/simple_offboard.html#gettelemetry
|
||||
|
||||
import rospy
|
||||
from clover import srv
|
||||
|
||||
rospy.init_node('flight')
|
||||
|
||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||
|
||||
# Print drone's state
|
||||
print(get_telemetry())
|
||||
@@ -1,47 +0,0 @@
|
||||
# Information: https://clover.coex.tech/en/simple_offboard.html#navigateglobal
|
||||
|
||||
import rospy
|
||||
from clover import srv
|
||||
from std_srvs.srv import Trigger
|
||||
import math
|
||||
|
||||
rospy.init_node('flight')
|
||||
|
||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||
navigate = rospy.ServiceProxy('navigate', srv.Navigate)
|
||||
navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)
|
||||
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
|
||||
set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)
|
||||
set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
|
||||
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
|
||||
land = rospy.ServiceProxy('land', Trigger)
|
||||
|
||||
# https://clover.coex.tech/en/snippets.html#wait_arrival
|
||||
def wait_arrival(tolerance=0.2):
|
||||
while not rospy.is_shutdown():
|
||||
telem = get_telemetry(frame_id='navigate_target')
|
||||
if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < tolerance:
|
||||
break
|
||||
rospy.sleep(0.2)
|
||||
|
||||
start = get_telemetry()
|
||||
|
||||
if math.isnan(start.lat):
|
||||
raise Exception('No global position, install and configure GPS sensor: https://clover.coex.tech/gps')
|
||||
|
||||
print('Start point global position: lat={}, lon={}'.format(start.lat, start.lon))
|
||||
|
||||
print('Take off 3 meters')
|
||||
navigate(x=0, y=0, z=3, frame_id='body', auto_arm=True)
|
||||
wait_arrival()
|
||||
|
||||
print('Fly 1 arcsecond to the North (approx. 30 meters)')
|
||||
navigate_global(lat=start.lat+1.0/60/60, lon=start.lon, z=start.z+3, yaw=math.inf, speed=5)
|
||||
wait_arrival()
|
||||
|
||||
print('Fly to home position')
|
||||
navigate_global(lat=start.lat, lon=start.lon, z=start.z+3, yaw=math.inf, speed=5)
|
||||
wait_arrival()
|
||||
|
||||
print('Land')
|
||||
land()
|
||||
@@ -1,15 +0,0 @@
|
||||
# Information: https://clover.coex.tech/en/laser.html
|
||||
|
||||
import rospy
|
||||
from sensor_msgs.msg import Range
|
||||
|
||||
rospy.init_node('process_rangefinder')
|
||||
|
||||
def range_callback(msg):
|
||||
# Process data from the rangefinder
|
||||
print('Rangefinder distance:', msg.range)
|
||||
|
||||
# Subscribe to laser rangefinder data
|
||||
rospy.Subscriber('rangefinder/range', Range, range_callback)
|
||||
|
||||
rospy.spin()
|
||||
@@ -3,26 +3,21 @@
|
||||
<arg name="aruco_map" default="false"/>
|
||||
<arg name="aruco_vpe" default="false"/>
|
||||
<arg name="placement" default="floor"/> <!-- markers placement: floor, ceiling, unknown -->
|
||||
<arg name="length" default="0.22"/> <!-- not-in-map markers length, m -->
|
||||
<arg name="length" default="0.33"/> <!-- not-in-map markers length, m -->
|
||||
<arg name="map" default="map.txt"/> <!-- markers map file name -->
|
||||
|
||||
<!-- For additional help go to https://clover.coex.tech/aruco -->
|
||||
|
||||
<arg name="force_init" default="false"/>
|
||||
<arg name="disable" default="false"/> <!-- only force init -->
|
||||
|
||||
<!-- aruco_detect: detect aruco markers, estimate poses -->
|
||||
<node name="aruco_detect" pkg="nodelet" if="$(eval aruco_detect and not disable)" type="nodelet" args="load aruco_pose/aruco_detect main_camera_nodelet_manager" output="screen" clear_params="true" respawn="true">
|
||||
<node name="aruco_detect" pkg="nodelet" if="$(arg aruco_detect)" type="nodelet" args="load aruco_pose/aruco_detect nodelet_manager" output="screen" clear_params="true" respawn="true">
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="map_markers" to="aruco_map/map"/>
|
||||
<remap from="map_markers" to="aruco_map/markers" if="$(arg aruco_map)"/>
|
||||
<param name="estimate_poses" value="true"/>
|
||||
<param name="send_tf" value="true"/>
|
||||
<param name="use_map_markers" value="true"/>
|
||||
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/>
|
||||
<param name="known_tilt" value="map_flipped" if="$(eval placement == 'ceiling')"/>
|
||||
<param name="length" value="$(arg length)"/>
|
||||
<param name="transform_timeout" value="0.1"/>
|
||||
<!-- aruco detector parameters -->
|
||||
<param name="cornerRefinementMethod" value="2"/> <!-- contour refinement -->
|
||||
<param name="minMarkerPerimeterRate" value="0.075"/> <!-- 0.075 for 320x240, 0.0375 for 640x480 -->
|
||||
@@ -31,7 +26,7 @@
|
||||
</node>
|
||||
|
||||
<!-- aruco_map: estimate aruco map pose -->
|
||||
<node name="aruco_map" pkg="nodelet" type="nodelet" if="$(eval aruco_map and not disable)" args="load aruco_pose/aruco_map main_camera_nodelet_manager" output="screen" clear_params="true" respawn="true">
|
||||
<node name="aruco_map" pkg="nodelet" type="nodelet" if="$(arg aruco_map)" args="load aruco_pose/aruco_map nodelet_manager" output="screen" clear_params="true" respawn="true">
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="markers" to="aruco_detect/markers"/>
|
||||
@@ -46,15 +41,11 @@
|
||||
</node>
|
||||
|
||||
<!-- vpe publisher from aruco markers -->
|
||||
<node name="vpe_publisher" pkg="clover" type="vpe_publisher" if="$(eval aruco_vpe or force_init)" output="screen" clear_params="true">
|
||||
<remap from="~pose_cov" to="aruco_map/pose" if="$(arg aruco_vpe)"/>
|
||||
<node name="vpe_publisher" pkg="clover" type="vpe_publisher" if="$(arg aruco_vpe)" output="screen" clear_params="true">
|
||||
<remap from="~pose_cov" to="aruco_map/pose"/>
|
||||
<remap from="~vpe" to="mavros/vision_pose/pose"/>
|
||||
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/>
|
||||
<param name="force_init" value="$(arg force_init)"/>
|
||||
<param name="frame_id" value="aruco_map_detected"/>
|
||||
<param name="publish_zero" value="true"/>
|
||||
<param name="offset_frame_id" value="aruco_map"/>
|
||||
</node>
|
||||
|
||||
<!-- run map_flipped frame if placement is ceiling -->
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="map_flipped_frame"
|
||||
args="0 0 0 3.1415926 3.1415926 0 map map_flipped" if="$(eval placement == 'ceiling')"/>
|
||||
</launch>
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
<arg name="rangefinder_vl53l1x" default="true"/>
|
||||
<arg name="led" default="true"/>
|
||||
<arg name="blocks" default="false"/>
|
||||
<arg name="rc" default="false"/>
|
||||
<arg name="force_init" default="true"/> <!-- force estimator to init by publishing zero pose -->
|
||||
<arg name="rc" default="true"/>
|
||||
<arg name="shell" default="true"/>
|
||||
|
||||
<arg name="simulator" default="false"/> <!-- flag that we are operating on a simulated drone -->
|
||||
|
||||
@@ -34,22 +34,28 @@
|
||||
</node>
|
||||
|
||||
<!-- aruco markers -->
|
||||
<include file="$(find clover)/launch/aruco.launch" if="$(eval aruco or force_init)">
|
||||
<arg name="force_init" value="$(arg force_init)"/>
|
||||
<arg name="disable" value="$(eval not aruco)"/>
|
||||
</include>
|
||||
<include file="$(find clover)/launch/aruco.launch" if="$(arg aruco)"/>
|
||||
|
||||
<!-- optical flow -->
|
||||
<node pkg="nodelet" type="nodelet" name="optical_flow" args="load clover/optical_flow main_camera_nodelet_manager" if="$(arg optical_flow)" clear_params="true" output="screen" respawn="true">
|
||||
<node pkg="nodelet" type="nodelet" name="optical_flow" args="load clover/optical_flow nodelet_manager" if="$(arg optical_flow)" clear_params="true" output="screen" respawn="true">
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<param name="calc_flow_gyro" value="true"/>
|
||||
<param name="roi_rad" value="0.8"/>
|
||||
<param name="disable_on_vpe" value="false"/>
|
||||
</node>
|
||||
|
||||
<!-- main nodelet manager -->
|
||||
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager" output="screen" clear_params="true" respawn="true">
|
||||
<param name="num_worker_threads" value="2"/>
|
||||
</node>
|
||||
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="map_flipped_frame" args="0 0 0 3.1415926 3.1415926 0 map map_flipped"/>
|
||||
|
||||
<!-- simplified offboard control -->
|
||||
<node name="simple_offboard" pkg="clover" type="simple_offboard" output="screen" clear_params="true">
|
||||
<param name="reference_frames/body" value="map"/>
|
||||
<param name="reference_frames/base_link" value="map"/>
|
||||
<param name="reference_frames/navigate_target" value="map"/>
|
||||
<param name="reference_frames/main_camera_optical" value="map"/>
|
||||
</node>
|
||||
|
||||
@@ -84,4 +90,12 @@
|
||||
<!-- Send fake GCS heartbeats. Set to "true" for upstream PX4 -->
|
||||
<param name="use_fake_gcs" value="false"/>
|
||||
</node>
|
||||
|
||||
<!-- Shell access through ROS service -->
|
||||
<node name="shell" pkg="clover" type="shell" output="screen" if="$(arg shell)"/>
|
||||
|
||||
<!-- Update static directory -->
|
||||
<node pkg="roswww_static" name="roswww_static" type="main.py" clear_params="true">
|
||||
<param name="default_package" value="clover"/>
|
||||
</node>
|
||||
</launch>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
posctl: { r: 50, g: 100, b: 220 }
|
||||
offboard: { r: 220, g: 20, b: 250 }
|
||||
low_battery: { threshold: 3.6, effect: blink_fast, r: 255, g: 0, b: 0 }
|
||||
error: { effect: flash, r: 255, g: 0, b: 0, ignore: [ "[lpe] vision position timeout" ]}
|
||||
error: { effect: flash, r: 255, g: 0, b: 0 }
|
||||
</rosparam>
|
||||
</node>
|
||||
</launch>
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
|
||||
<arg name="direction_z" default="down"/> <!-- direction the camera points: down, up -->
|
||||
<arg name="direction_y" default="backward"/> <!-- direction the camera cable points: backward, forward -->
|
||||
<arg name="device" default="/dev/video0"/> <!-- v4l2 device -->
|
||||
<arg name="throttled_topic" default="true"/> <!-- enable throttled image topic -->
|
||||
<arg name="throttled_topic_rate" default="5.0"/> <!-- throttled image topic rate -->
|
||||
<arg name="simulator" default="false"/>
|
||||
|
||||
<node if="$(eval direction_z == 'down' and direction_y == 'backward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/>
|
||||
@@ -20,14 +17,9 @@
|
||||
<!-- static_transform_publisher arguments: x y z yaw pitch roll frame_id child_frame_id -->
|
||||
<!-- <node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/> -->
|
||||
|
||||
<!-- camera nodelet manager -->
|
||||
<node pkg="nodelet" type="nodelet" name="main_camera_nodelet_manager" args="manager" output="screen" clear_params="true" respawn="true">
|
||||
<param name="num_worker_threads" value="2"/>
|
||||
</node>
|
||||
|
||||
<!-- camera node -->
|
||||
<node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet main_camera_nodelet_manager" launch-prefix="rosrun clover waitfile $(arg device)" clear_params="true" unless="$(arg simulator)" respawn="true">
|
||||
<param name="device_path" value="$(arg device)"/>
|
||||
<node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet nodelet_manager" clear_params="true" unless="$(arg simulator)" respawn="true">
|
||||
<param name="device_path" value="/dev/video0"/> <!-- v4l2 device -->
|
||||
<param name="frame_id" value="main_camera_optical"/>
|
||||
<param name="camera_info_url" value="file://$(find clover)/camera_info/fisheye_cam.yaml"/>
|
||||
|
||||
@@ -45,8 +37,4 @@
|
||||
<node pkg="clover" type="camera_markers" ns="main_camera" name="main_camera_markers">
|
||||
<param name="scale" value="3.0"/>
|
||||
</node>
|
||||
|
||||
<!-- image topic throttled -->
|
||||
<node pkg="topic_tools" name="main_camera_throttle" type="throttle" ns="main_camera"
|
||||
args="messages image_raw $(arg throttled_topic_rate) image_raw_throttled" if="$(arg throttled_topic)"/>
|
||||
</launch>
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
<launch>
|
||||
<arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl, hitl -->
|
||||
<arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl -->
|
||||
<arg name="fcu_ip" default="127.0.0.1"/>
|
||||
<arg name="fcu_sys_id" default="1"/>
|
||||
<arg name="gcs_bridge" default="tcp"/>
|
||||
<arg name="viz" default="true"/>
|
||||
<arg name="respawn" default="true"/>
|
||||
<arg name="distance_sensor_remap" default="rangefinder/range"/>
|
||||
<arg name="usb_device" default="/dev/px4fmu"/>
|
||||
<arg name="prefix" default="" unless="$(eval fcu_conn == 'usb')"/>
|
||||
<arg name="prefix" default="rosrun clover waitfile $(arg usb_device)" if="$(eval fcu_conn == 'usb')"/>
|
||||
|
||||
<node pkg="mavros" type="mavros_node" name="mavros" launch-prefix="$(arg prefix)" required="false" clear_params="true" respawn="$(arg respawn)" unless="$(eval fcu_conn == 'none')" respawn_delay="1" output="screen">
|
||||
<node pkg="mavros" type="mavros_node" name="mavros" required="false" clear_params="true" respawn="$(arg respawn)" unless="$(eval fcu_conn == 'none')" respawn_delay="1" output="screen">
|
||||
<!-- UART connection -->
|
||||
<param name="fcu_url" value="/dev/ttyAMA0:921600" if="$(eval fcu_conn is None or fcu_conn == 'uart')"/>
|
||||
|
||||
<!-- USB connection -->
|
||||
<param name="fcu_url" value="$(arg usb_device)" if="$(eval fcu_conn == 'usb')"/>
|
||||
<param name="fcu_url" value="/dev/px4fmu" if="$(eval fcu_conn == 'usb')"/>
|
||||
|
||||
<!-- sitl before PX4 1.9.0 -->
|
||||
<param name="fcu_url" value="udp://@$(arg fcu_ip):14557" if="$(eval fcu_conn == 'udp')"/>
|
||||
@@ -23,9 +20,6 @@
|
||||
<!-- sitl since PX4 1.9.0 -->
|
||||
<param name="fcu_url" value="udp://@$(arg fcu_ip):14580" if="$(eval fcu_conn == 'sitl')"/>
|
||||
|
||||
<!-- hitl connection (to gazebo_mavlink_interface plugin) -->
|
||||
<param name="fcu_url" value="udp://$(arg fcu_ip):14540@" if="$(eval fcu_conn == 'hitl')"/>
|
||||
|
||||
<!-- set target_system_id -->
|
||||
<param name="target_system_id" value="$(arg fcu_sys_id)" />
|
||||
|
||||
@@ -42,7 +36,7 @@
|
||||
<rosparam command="load" file="$(find clover)/launch/mavros_config.yaml"/>
|
||||
|
||||
<!-- remap rangefinder -->
|
||||
<remap from="mavros/distance_sensor/rangefinder_sub" to="$(arg distance_sensor_remap)" if="$(eval bool(distance_sensor_remap))"/>
|
||||
<remap from="mavros/distance_sensor/rangefinder_sub" to="rangefinder/range"/>
|
||||
|
||||
<rosparam param="plugin_whitelist">
|
||||
- altitude
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Config file for mavros
|
||||
# Based on https://raw.githubusercontent.com/mavlink/mavros/master/mavros/launch/px4_config.yaml
|
||||
|
||||
startup_px4_usb_quirk: false
|
||||
startup_px4_usb_quirk: true
|
||||
|
||||
conn:
|
||||
heartbeat_rate: 1.0 # send heartbeat rate in Hertz
|
||||
timeout: 10.0 # heartbeat timeout in seconds
|
||||
heartbeat_rate: 1.0 # send hertbeat rate in Hertz
|
||||
timeout: 10.0 # hertbeat timeout in seconds
|
||||
timesync_rate: 10.0 # TIMESYNC rate in Hertz (feature disabled if 0.0)
|
||||
system_time_rate: 1.0 # send system time to FCU rate in Hertz (disabled if 0.0)
|
||||
|
||||
@@ -13,7 +13,6 @@ time:
|
||||
time_ref_source: "fcu" # time_reference source
|
||||
timesync_mode: MAVLINK
|
||||
timesync_avg_alpha: 0.6 # timesync averaging factor
|
||||
publish_sim_time: false # don't publish /clock
|
||||
|
||||
global_position:
|
||||
frame_id: "map" # origin frame
|
||||
@@ -78,9 +77,6 @@ distance_sensor:
|
||||
field_of_view: 0.5
|
||||
rangefinder_sub:
|
||||
subscriber: true
|
||||
id: 1
|
||||
orientation: PITCH_270
|
||||
covariance: 1 # cm
|
||||
|
||||
# fake_gps
|
||||
fake_gps:
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<launch>
|
||||
<!-- shurtcut for running the simulation (`roslaunch clover simulator.launch`) -->
|
||||
<include file="$(find clover_simulation)/launch/simulator.launch"/>
|
||||
</launch>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="3">
|
||||
<name>clover</name>
|
||||
<version>0.23.0</version>
|
||||
<version>0.21.1</version>
|
||||
<description>The Clover package</description>
|
||||
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
@@ -37,11 +37,8 @@
|
||||
<depend>rosbridge_server</depend>
|
||||
<depend>web_video_server</depend>
|
||||
<depend>tf2_web_republisher</depend>
|
||||
<depend>libxml2</depend>
|
||||
<depend>libxslt</depend>
|
||||
<depend condition="$ROS_PYTHON_VERSION == 2">python-lxml</depend>
|
||||
<depend condition="$ROS_PYTHON_VERSION == 3">python3-lxml</depend>
|
||||
<depend>dynamic_reconfigure</depend>
|
||||
<exec_depend>python-pymavlink</exec_depend>
|
||||
<!-- Use test_depend for packages you need only for testing: -->
|
||||
<!-- <test_depend>gtest</test_depend> -->
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
## ! DO NOT MANUALLY INVOKE THIS setup.py, USE CATKIN INSTEAD
|
||||
|
||||
from distutils.core import setup
|
||||
from catkin_pkg.python_setup import generate_distutils_setup
|
||||
|
||||
# fetch values from package.xml
|
||||
setup_args = generate_distutils_setup(
|
||||
packages=['clover'],
|
||||
package_dir={'': 'src'})
|
||||
|
||||
setup(**setup_args)
|
||||
@@ -1,87 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import rospy
|
||||
import math
|
||||
import signal
|
||||
import sys
|
||||
import dynamic_reconfigure.client
|
||||
from clover import srv
|
||||
from std_srvs.srv import Trigger
|
||||
from sensor_msgs.msg import Range
|
||||
from aruco_pose.msg import MarkerArray
|
||||
from util import handle_response
|
||||
|
||||
rospy.init_node('autotest_aruco', disable_signals=True) # disable signals to allow interrupting with ctrl+c
|
||||
|
||||
flow_client = dynamic_reconfigure.client.Client('optical_flow')
|
||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
|
||||
land = handle_response(rospy.ServiceProxy('land', Trigger))
|
||||
|
||||
def interrupt(sig, frame):
|
||||
print('\nInterrupted, landing...')
|
||||
land()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, interrupt)
|
||||
|
||||
def print_current_map_position():
|
||||
telem = get_telemetry()
|
||||
dist = rospy.wait_for_message('rangefinder/range', Range).range
|
||||
print('Map position:\tx={:.1f}\ty={:.1f}\tz={:.1f}\tyaw={:.1f}\tdist={:.2f}'.format(telem.x, telem.y, telem.z, telem.yaw, dist))
|
||||
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \
|
||||
frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
|
||||
frame_id=frame_id, auto_arm=auto_arm)
|
||||
|
||||
if not res.success:
|
||||
return res
|
||||
|
||||
while not rospy.is_shutdown():
|
||||
telem = get_telemetry(frame_id='navigate_target')
|
||||
if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < tolerance:
|
||||
return res
|
||||
rospy.sleep(0.2)
|
||||
|
||||
markers = rospy.wait_for_message('aruco_map/map', MarkerArray, timeout=3)
|
||||
left = min(marker.pose.position.x for marker in markers.markers)
|
||||
bottom = min(marker.pose.position.y for marker in markers.markers)
|
||||
width = max(marker.pose.position.x for marker in markers.markers)
|
||||
height = max(marker.pose.position.y for marker in markers.markers)
|
||||
center_x = left + width / 2
|
||||
center_y = bottom + height / 2
|
||||
|
||||
print('Map rect: %g %g - %g %g' % (left, bottom, width, height))
|
||||
|
||||
input('Take off and hover 1 m [enter] ')
|
||||
navigate_wait(x=0, y=0, z=1, frame_id='body', auto_arm=True)
|
||||
print_current_map_position()
|
||||
|
||||
input('Go to corner %g %g 1.5 speed 1 [enter] ' % (width, height))
|
||||
navigate_wait(x=width, y=height, z=1.5, speed=1, frame_id='aruco_map')
|
||||
print_current_map_position()
|
||||
|
||||
input('Go to center %g %g 1.5 speed 5 [enter] ' % (center_x, center_y))
|
||||
navigate_wait(x=center_x, y=center_y, z=1.5, speed=5, frame_id='aruco_map')
|
||||
print_current_map_position()
|
||||
|
||||
input('Disable optical flow and keep hovering [enter] ')
|
||||
flow_client.update_configuration({'enabled': False})
|
||||
rospy.sleep(5)
|
||||
|
||||
input('Enable optical flow back [enter] ')
|
||||
flow_client.update_configuration({'enabled': True})
|
||||
|
||||
input('Go to side 1 %g 2 heading top [enter] ' % (center_y))
|
||||
navigate_wait(x=1, y=center_y, z=2, yaw=1.57, frame_id='aruco_map')
|
||||
print_current_map_position()
|
||||
|
||||
marker_id = markers.markers[0].id
|
||||
input('Go to marker %d z=1.5 [enter] ' % marker_id)
|
||||
navigate_wait(x=0, y=0, z=1.5, yaw=0, frame_id='aruco_%d' % marker_id)
|
||||
print_current_map_position()
|
||||
|
||||
input('Perform landing [enter] ')
|
||||
land()
|
||||
@@ -1,100 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import rospy
|
||||
import math
|
||||
from math import nan
|
||||
import signal
|
||||
import sys
|
||||
from clover import srv
|
||||
from std_srvs.srv import Trigger
|
||||
from sensor_msgs.msg import Range
|
||||
from util import handle_response
|
||||
|
||||
rospy.init_node('autotest_flight', disable_signals=True) # disable signals to allow interrupting with ctrl+c
|
||||
|
||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
|
||||
navigate_global = handle_response(rospy.ServiceProxy('navigate_global', srv.NavigateGlobal))
|
||||
set_position = handle_response(rospy.ServiceProxy('set_position', srv.SetPosition))
|
||||
set_velocity = handle_response(rospy.ServiceProxy('set_velocity', srv.SetVelocity))
|
||||
set_attitude = handle_response(rospy.ServiceProxy('set_attitude', srv.SetAttitude))
|
||||
set_rates = handle_response(rospy.ServiceProxy('set_rates', srv.SetRates))
|
||||
land = handle_response(rospy.ServiceProxy('land', Trigger))
|
||||
|
||||
def interrupt(sig, frame):
|
||||
print('\nInterrupted, landing...')
|
||||
land()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, interrupt)
|
||||
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=nan, yaw_rate=0, speed=0.5, \
|
||||
frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
|
||||
frame_id=frame_id, auto_arm=auto_arm)
|
||||
|
||||
if not res.success:
|
||||
return res
|
||||
|
||||
while not rospy.is_shutdown():
|
||||
telem = get_telemetry(frame_id='navigate_target')
|
||||
if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < tolerance:
|
||||
return res
|
||||
rospy.sleep(0.2)
|
||||
|
||||
def print_distance():
|
||||
dist = rospy.wait_for_message('rangefinder/range', Range).range
|
||||
print('Distance: {:.2f}'.format(dist))
|
||||
|
||||
input('Take off and hover 1 m [enter] ')
|
||||
navigate_wait(z=1, frame_id='body', auto_arm=True)
|
||||
print_distance()
|
||||
start = get_telemetry()
|
||||
|
||||
input('Fly forward 2 m [enter] ')
|
||||
navigate_wait(x=2, frame_id='navigate_target')
|
||||
print_distance()
|
||||
|
||||
input('Climb 0.5 m [enter] ')
|
||||
navigate_wait(z=0.5, frame_id='navigate_target')
|
||||
print_distance()
|
||||
|
||||
input('Rotate left 90° [enter] ')
|
||||
navigate(yaw=math.pi / 2, frame_id='navigate_target')
|
||||
rospy.sleep(3)
|
||||
|
||||
input('Use set_velocity to fly forward 2 m speed 1 [enter]')
|
||||
set_velocity(vx=1, vy=0.0, vz=0, frame_id='body')
|
||||
rospy.sleep(2)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Rotate right 90° [enter] ')
|
||||
navigate(yaw=-math.pi / 2, frame_id='navigate_target')
|
||||
rospy.sleep(3)
|
||||
|
||||
input('Use set_attitude to fly backwards [enter]')
|
||||
set_attitude(pitch=-0.3, roll=0, yaw=0, thrust=0.5, frame_id='body')
|
||||
rospy.sleep(0.3)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Use set_attitude to fly right [enter]')
|
||||
set_attitude(pitch=0, roll=0.3, yaw=0, thrust=0.5, frame_id='body')
|
||||
rospy.sleep(0.5)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Use set_rates to fly right [enter]')
|
||||
set_rates(roll_rate=1.2, thrust=0.5)
|
||||
rospy.sleep(0.4)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Rotate 360° to the right using yaw_rate [enter]')
|
||||
set_position(x=nan, y=nan, z=nan, frame_id='body', yaw=nan, yaw_rate=-1)
|
||||
rospy.sleep(2 * math.pi)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Return to start point [enter]')
|
||||
navigate_wait(x=start.x, y=start.y, z=start.z, yaw=start.yaw, speed=1, frame_id='map')
|
||||
|
||||
input('Land [enter]')
|
||||
land()
|
||||
@@ -1,72 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import rospy
|
||||
import functools
|
||||
from clover.srv import SetLEDEffect
|
||||
from led_msgs.srv import SetLEDs
|
||||
from led_msgs.msg import LEDStateArray, LEDState
|
||||
from util import handle_response
|
||||
|
||||
rospy.init_node('autotest_led', disable_signals=True)
|
||||
|
||||
set_leds = handle_response(rospy.ServiceProxy('led/set_leds', SetLEDs))
|
||||
set_effect = handle_response(rospy.ServiceProxy('led/set_effect', SetLEDEffect))
|
||||
|
||||
led_count = len(rospy.wait_for_message('led/state', LEDStateArray, timeout=10).leds)
|
||||
print('LED count =', led_count)
|
||||
|
||||
print('== Testing effects ==')
|
||||
|
||||
input('Fill red [enter] ')
|
||||
set_effect(r=255, g=0, b=0)
|
||||
|
||||
input('Fill green [enter] ')
|
||||
set_effect(r=0, g=100, b=0)
|
||||
|
||||
input('Blink white [enter] ')
|
||||
set_effect(effect='blink', r=255, g=255, b=255)
|
||||
rospy.sleep(3)
|
||||
|
||||
input('Blink fast violet [enter] ')
|
||||
set_effect(effect='blink_fast', r=220, g=20, b=250)
|
||||
rospy.sleep(3)
|
||||
|
||||
input('Fade to blue [enter] ')
|
||||
set_effect(effect='fade', r=0, g=0, b=255)
|
||||
|
||||
input('Wipe to yellow [enter] ')
|
||||
set_effect(effect='wipe', r=255, g=255, b=40)
|
||||
|
||||
input('Flash red [enter] ')
|
||||
set_effect(effect='flash', r=255, g=0, b=0)
|
||||
rospy.sleep(1)
|
||||
|
||||
input('Rainbow [enter] ')
|
||||
set_effect(effect='rainbow')
|
||||
rospy.sleep(4)
|
||||
|
||||
input('Rainbow fill [enter] ')
|
||||
set_effect(effect='rainbow_fill')
|
||||
rospy.sleep(4)
|
||||
|
||||
input('Turn off [enter] ')
|
||||
set_effect()
|
||||
|
||||
print('== Testing low-level control ==')
|
||||
|
||||
input('Fill orange [enter] ')
|
||||
set_leds(leds=[LEDState(index=i, r=245, g=155, b=0) for i in range(led_count)])
|
||||
|
||||
input('Fill blue gradient [enter] ')
|
||||
set_leds(leds=[LEDState(index=i, r=0, g=20, b=int(255 * i / led_count)) for i in range(led_count)])
|
||||
|
||||
input('Animate green dot [enter] ')
|
||||
set_effect()
|
||||
for i in range(led_count):
|
||||
if i > 0:
|
||||
set_leds(leds=[LEDState(index=i - 1, r=0, g=0, b=0)])
|
||||
set_leds(leds=[LEDState(index=i, r=0, g=255, b=0)])
|
||||
rospy.sleep(0.05)
|
||||
|
||||
input('Turn off [enter] ')
|
||||
set_effect()
|
||||
@@ -1,11 +0,0 @@
|
||||
import functools
|
||||
|
||||
# decorator to handle response and print error message
|
||||
def handle_response(fn):
|
||||
@functools.wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
res = fn(*args, **kwargs)
|
||||
if not res.success:
|
||||
print('\033[91mError:\033[0m {}'.format(res.message))
|
||||
return res
|
||||
return wrapper
|
||||
@@ -1,35 +0,0 @@
|
||||
import rospy
|
||||
from threading import Thread, Event
|
||||
|
||||
def long_callback(fn):
|
||||
"""
|
||||
Decorator fixing a rospy issue for long-running topic callbacks, primarily
|
||||
for image processing.
|
||||
|
||||
See: https://github.com/ros/ros_comm/issues/1901.
|
||||
|
||||
Usage example:
|
||||
|
||||
@long_callback
|
||||
def image_callback(msg):
|
||||
# perform image processing
|
||||
# ...
|
||||
|
||||
rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1)
|
||||
"""
|
||||
e = Event()
|
||||
|
||||
def thread():
|
||||
while not rospy.is_shutdown():
|
||||
e.wait()
|
||||
e.clear()
|
||||
fn(thread.current_msg)
|
||||
|
||||
thread.current_msg = None
|
||||
Thread(target=thread, daemon=True).start()
|
||||
|
||||
def wrapper(msg):
|
||||
thread.current_msg = msg
|
||||
e.set()
|
||||
|
||||
return wrapper
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
#include <ros/ros.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <clover/SetLEDEffect.h>
|
||||
@@ -30,7 +29,7 @@ ros::Timer timer;
|
||||
ros::Time start_time;
|
||||
double blink_rate, blink_fast_rate, flash_delay, fade_period, wipe_period, rainbow_period;
|
||||
double low_battery_threshold;
|
||||
std::vector<std::string> error_ignore;
|
||||
bool blink_state;
|
||||
led_msgs::SetLEDs set_leds;
|
||||
led_msgs::LEDStateArray state, start_state;
|
||||
ros::ServiceClient set_leds_srv;
|
||||
@@ -86,8 +85,9 @@ void proceed(const ros::TimerEvent& event)
|
||||
set_leds.request.leds.resize(led_count);
|
||||
|
||||
if (current_effect.effect == "blink" || current_effect.effect == "blink_fast") {
|
||||
// enable on odd counter
|
||||
if (counter % 2 != 0) {
|
||||
blink_state = !blink_state;
|
||||
// toggle all leds
|
||||
if (blink_state) {
|
||||
fill(current_effect.r, current_effect.g, current_effect.b);
|
||||
} else {
|
||||
fill(0, 0, 0);
|
||||
@@ -220,7 +220,6 @@ bool setEffect(clover::SetLEDEffect::Request& req, clover::SetLEDEffect::Respons
|
||||
counter = 0;
|
||||
start_state = state;
|
||||
start_time = ros::Time::now();
|
||||
proceed({ .current_real = start_time });
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -275,10 +274,6 @@ void handleMavrosState(const mavros_msgs::State& msg)
|
||||
void handleLog(const rosgraph_msgs::Log& log)
|
||||
{
|
||||
if (log.level >= rosgraph_msgs::Log::ERROR) {
|
||||
// check if ignored
|
||||
for (auto const& str : error_ignore) {
|
||||
if (log.msg.find(str) != std::string::npos) return;
|
||||
}
|
||||
notify("error");
|
||||
}
|
||||
}
|
||||
@@ -307,7 +302,6 @@ int main(int argc, char **argv)
|
||||
nh_priv.param("rainbow_period", rainbow_period, 5.0);
|
||||
|
||||
nh_priv.param("notify/low_battery/threshold", low_battery_threshold, 3.7);
|
||||
nh_priv.param("notify/error/ignore", error_ignore, {});
|
||||
|
||||
ros::service::waitForService("set_leds"); // cannot work without set_leds service
|
||||
set_leds_srv = nh.serviceClient<led_msgs::SetLEDs>("set_leds", true);
|
||||
@@ -319,8 +313,8 @@ int main(int argc, char **argv)
|
||||
|
||||
auto set_effect = nh.advertiseService("set_effect", &setEffect);
|
||||
|
||||
auto mavros_state_sub = nh.subscribe("mavros/state", 1, &handleMavrosState);
|
||||
auto battery_sub = nh.subscribe("mavros/battery", 1, &handleBattery);
|
||||
auto mavros_state_sub = nh.subscribe("/mavros/state", 1, &handleMavrosState);
|
||||
auto battery_sub = nh.subscribe("/mavros/battery", 1, &handleBattery);
|
||||
auto rosout_sub = nh.subscribe("/rosout_agg", 1, &handleLog);
|
||||
|
||||
timer = nh.createTimer(ros::Duration(0), &proceed, false, false);
|
||||
|
||||
@@ -22,14 +22,11 @@
|
||||
#include <tf2/utils.h>
|
||||
#include <tf2_ros/transform_listener.h>
|
||||
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
|
||||
#include <dynamic_reconfigure/server.h>
|
||||
#include <mavros_msgs/OpticalFlowRad.h>
|
||||
#include <sensor_msgs/Imu.h>
|
||||
#include <geometry_msgs/PoseStamped.h>
|
||||
#include <geometry_msgs/Vector3Stamped.h>
|
||||
#include <geometry_msgs/PointStamped.h>
|
||||
#include <geometry_msgs/TwistStamped.h>
|
||||
#include <clover/FlowConfig.h>
|
||||
|
||||
using cv::Mat;
|
||||
|
||||
@@ -41,7 +38,6 @@ public:
|
||||
{}
|
||||
|
||||
private:
|
||||
bool enabled_;
|
||||
ros::Publisher flow_pub_, velo_pub_, shift_pub_;
|
||||
ros::Time prev_stamp_;
|
||||
std::string fcu_frame_id_, local_frame_id_;
|
||||
@@ -57,11 +53,6 @@ private:
|
||||
std::unique_ptr<tf2_ros::Buffer> tf_buffer_;
|
||||
std::unique_ptr<tf2_ros::TransformListener> tf_listener_;
|
||||
bool calc_flow_gyro_;
|
||||
float flow_gyro_default_;
|
||||
bool disable_on_vpe_;
|
||||
ros::Subscriber vpe_sub_;
|
||||
ros::Time last_vpe_time_;
|
||||
std::shared_ptr<dynamic_reconfigure::Server<clover::FlowConfig>> dyn_srv_;
|
||||
|
||||
void onInit()
|
||||
{
|
||||
@@ -78,30 +69,21 @@ private:
|
||||
roi_px_ = nh_priv.param("roi", 128);
|
||||
roi_rad_ = nh_priv.param("roi_rad", 0.0);
|
||||
calc_flow_gyro_ = nh_priv.param("calc_flow_gyro", false);
|
||||
flow_gyro_default_ = nh_priv.param("flow_gyro_default", NAN);
|
||||
|
||||
img_pub_ = it_priv.advertise("debug", 1);
|
||||
flow_pub_ = nh.advertise<mavros_msgs::OpticalFlowRad>("mavros/px4flow/raw/send", 1);
|
||||
velo_pub_ = nh_priv.advertise<geometry_msgs::TwistStamped>("angular_velocity", 1);
|
||||
shift_pub_ = nh_priv.advertise<geometry_msgs::Vector3Stamped>("shift", 1);
|
||||
|
||||
flow_.integrated_xgyro = NAN; // no IMU available
|
||||
flow_.integrated_ygyro = NAN;
|
||||
flow_.integrated_zgyro = NAN;
|
||||
flow_.time_delta_distance_us = 0;
|
||||
flow_.distance = -1; // no distance sensor available
|
||||
flow_.temperature = 0;
|
||||
|
||||
img_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this);
|
||||
|
||||
disable_on_vpe_ = nh_priv.param("disable_on_vpe", false);
|
||||
if (disable_on_vpe_) {
|
||||
vpe_sub_ = nh.subscribe("mavros/vision_pose/pose", 1, &OpticalFlow::vpeCallback, this);
|
||||
}
|
||||
|
||||
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<clover::FlowConfig>>(nh_priv);
|
||||
dynamic_reconfigure::Server<clover::FlowConfig>::CallbackType cb;
|
||||
|
||||
cb = std::bind(&OpticalFlow::paramCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
dyn_srv_->setCallback(cb);
|
||||
|
||||
NODELET_INFO("Optical Flow initialized");
|
||||
}
|
||||
|
||||
@@ -128,14 +110,6 @@ private:
|
||||
|
||||
void flow(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr& cinfo)
|
||||
{
|
||||
if (!enabled_) return;
|
||||
|
||||
if (disable_on_vpe_ &&
|
||||
!last_vpe_time_.isZero() &&
|
||||
(msg->header.stamp - last_vpe_time_).toSec() < 0.1) {
|
||||
return;
|
||||
}
|
||||
|
||||
parseCameraInfo(cinfo);
|
||||
|
||||
auto img = cv_bridge::toCvShare(msg, "mono8")->image;
|
||||
@@ -169,7 +143,7 @@ private:
|
||||
|
||||
img.convertTo(curr_, CV_32F);
|
||||
|
||||
if (prev_.empty() || (msg->header.stamp - prev_stamp_).toSec() > 0.1) { // outdated previous frame
|
||||
if (prev_.empty()) {
|
||||
prev_ = curr_.clone();
|
||||
prev_stamp_ = msg->header.stamp;
|
||||
cv::createHanningWindow(hann_, curr_.size(), CV_32F);
|
||||
@@ -179,7 +153,7 @@ private:
|
||||
cv::Point2d shift = cv::phaseCorrelate(prev_, curr_, hann_, &response);
|
||||
|
||||
// Publish raw shift in pixels
|
||||
geometry_msgs::Vector3Stamped shift_vec;
|
||||
static geometry_msgs::Vector3Stamped shift_vec;
|
||||
shift_vec.header.stamp = msg->header.stamp;
|
||||
shift_vec.header.frame_id = msg->header.frame_id;
|
||||
shift_vec.vector.x = shift.x;
|
||||
@@ -205,8 +179,8 @@ private:
|
||||
double flow_x = atan2(points_undist[0].x, focal_length_x);
|
||||
double flow_y = atan2(points_undist[0].y, focal_length_y);
|
||||
|
||||
// Convert to FCU frame
|
||||
geometry_msgs::Vector3Stamped flow_camera, flow_fcu;
|
||||
// // Convert to FCU frame
|
||||
static geometry_msgs::Vector3Stamped flow_camera, flow_fcu;
|
||||
flow_camera.header.frame_id = msg->header.frame_id;
|
||||
flow_camera.header.stamp = msg->header.stamp;
|
||||
flow_camera.vector.x = flow_y; // +y means counter-clockwise rotation around Y axis
|
||||
@@ -222,21 +196,18 @@ private:
|
||||
ros::Duration integration_time = msg->header.stamp - prev_stamp_;
|
||||
uint32_t integration_time_us = integration_time.toSec() * 1.0e6;
|
||||
|
||||
// Calculate flow gyro
|
||||
flow_.integrated_xgyro = flow_gyro_default_;
|
||||
flow_.integrated_ygyro = flow_gyro_default_;
|
||||
flow_.integrated_zgyro = flow_gyro_default_;
|
||||
|
||||
if (calc_flow_gyro_) {
|
||||
try {
|
||||
auto flow_gyro_camera = calcFlowGyro(msg->header.frame_id, prev_stamp_, msg->header.stamp);
|
||||
geometry_msgs::Vector3Stamped flow_gyro_fcu;
|
||||
static geometry_msgs::Vector3Stamped flow_gyro_fcu;
|
||||
tf_buffer_->transform(flow_gyro_camera, flow_gyro_fcu, fcu_frame_id_);
|
||||
flow_.integrated_xgyro = flow_gyro_fcu.vector.x;
|
||||
flow_.integrated_ygyro = flow_gyro_fcu.vector.y;
|
||||
flow_.integrated_zgyro = flow_gyro_fcu.vector.z;
|
||||
} catch (const tf2::TransformException& e) {
|
||||
// Transform not available, keep NANs in flow gyro
|
||||
// Invalidate previous frame
|
||||
prev_.release();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,18 +219,6 @@ private:
|
||||
flow_.quality = (uint8_t)(response * 255);
|
||||
flow_pub_.publish(flow_);
|
||||
|
||||
prev_ = curr_.clone();
|
||||
prev_stamp_ = msg->header.stamp;
|
||||
|
||||
// Publish estimated angular velocity
|
||||
geometry_msgs::TwistStamped velo;
|
||||
velo.header.stamp = msg->header.stamp;
|
||||
velo.header.frame_id = fcu_frame_id_;
|
||||
velo.twist.angular.x = flow_fcu.vector.x / integration_time.toSec();
|
||||
velo.twist.angular.y = flow_fcu.vector.y / integration_time.toSec();
|
||||
velo_pub_.publish(velo);
|
||||
|
||||
publish_debug:
|
||||
// Publish debug image
|
||||
if (img_pub_.getNumSubscribers() > 0) {
|
||||
// publish debug image
|
||||
@@ -271,6 +230,17 @@ publish_debug:
|
||||
out_msg.image = img;
|
||||
img_pub_.publish(out_msg.toImageMsg());
|
||||
}
|
||||
|
||||
// Publish estimated angular velocity
|
||||
static geometry_msgs::TwistStamped velo;
|
||||
velo.header.stamp = msg->header.stamp;
|
||||
velo.header.frame_id = fcu_frame_id_;
|
||||
velo.twist.angular.x = flow_.integrated_x / integration_time.toSec();
|
||||
velo.twist.angular.y = flow_.integrated_y / integration_time.toSec();
|
||||
velo_pub_.publish(velo);
|
||||
|
||||
prev_ = curr_.clone();
|
||||
prev_stamp_ = msg->header.stamp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,18 +261,6 @@ publish_debug:
|
||||
|
||||
return flow;
|
||||
}
|
||||
|
||||
void paramCallback(clover::FlowConfig &config, uint32_t level)
|
||||
{
|
||||
enabled_ = config.enabled;
|
||||
if (!enabled_) {
|
||||
prev_ = Mat(); // clear previous frame
|
||||
}
|
||||
}
|
||||
|
||||
void vpeCallback(const geometry_msgs::PoseStamped& vpe) {
|
||||
last_vpe_time_ = vpe.header.stamp;
|
||||
}
|
||||
};
|
||||
|
||||
PLUGINLIB_EXPORT_CLASS(OpticalFlow, nodelet::Nodelet)
|
||||
|
||||
@@ -91,7 +91,7 @@ private:
|
||||
void fakeGCSThread()
|
||||
{
|
||||
// Awful workaround for fixing PX4 not sending STATUSTEXTs
|
||||
// if there is no GCS heartbeats.
|
||||
// if there is no GCS hearbeats.
|
||||
// TODO: use timer
|
||||
// TODO: remove, when PX4 get this fixed.
|
||||
ros::Publisher mavlink_pub = nh.advertise<mavros_msgs::Mavlink>("mavlink/to", 1);
|
||||
|
||||
@@ -15,8 +15,7 @@ import subprocess
|
||||
import re
|
||||
from collections import OrderedDict
|
||||
import traceback
|
||||
import threading
|
||||
from threading import Event, Thread, Lock
|
||||
from threading import Event
|
||||
import numpy
|
||||
import rospy
|
||||
import tf2_ros
|
||||
@@ -28,76 +27,67 @@ from mavros_msgs.msg import State, OpticalFlowRad, Mavlink
|
||||
from mavros_msgs.srv import ParamGet
|
||||
from geometry_msgs.msg import PoseStamped, TwistStamped, PoseWithCovarianceStamped, Vector3Stamped
|
||||
from visualization_msgs.msg import MarkerArray as VisualizationMarkerArray
|
||||
from diagnostic_msgs.msg import DiagnosticArray
|
||||
import tf.transformations as t
|
||||
from aruco_pose.msg import MarkerArray
|
||||
from mavros import mavlink
|
||||
import locale
|
||||
|
||||
|
||||
# TODO: check attitude is present
|
||||
# TODO: disk free space
|
||||
# TODO: map, base_link, body
|
||||
# TODO: rc service
|
||||
# TODO: perform commander check, ekf2 status on PX4
|
||||
# TODO: check if FCU params setter succeed
|
||||
# TODO: selfcheck ROS service (with blacklists for checks)
|
||||
|
||||
|
||||
rospy.init_node('selfcheck')
|
||||
|
||||
os.environ['ROSCONSOLE_FORMAT']='${message}'
|
||||
|
||||
# use user's locale to convert numbers, etc
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
tf_buffer = tf2_ros.Buffer()
|
||||
tf_listener = tf2_ros.TransformListener(tf_buffer)
|
||||
|
||||
|
||||
thread_local = threading.local()
|
||||
reports_lock = Lock()
|
||||
failures = []
|
||||
infos = []
|
||||
current_check = None
|
||||
|
||||
|
||||
def failure(text, *args):
|
||||
msg = text % args
|
||||
thread_local.reports += [{'failure': msg}]
|
||||
rospy.logwarn('%s: %s', current_check, msg)
|
||||
failures.append(msg)
|
||||
|
||||
|
||||
def info(text, *args):
|
||||
msg = text % args
|
||||
thread_local.reports += [{'info': msg}]
|
||||
rospy.loginfo('%s: %s', current_check, msg)
|
||||
infos.append(msg)
|
||||
|
||||
|
||||
def check(name):
|
||||
def inner(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
start = rospy.get_time()
|
||||
thread_local.reports = []
|
||||
failures[:] = []
|
||||
infos[:] = []
|
||||
global current_check
|
||||
current_check = name
|
||||
try:
|
||||
fn(*args, **kwargs)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
rospy.logerr('%s: exception occurred', name)
|
||||
with reports_lock:
|
||||
for report in thread_local.reports:
|
||||
if 'failure' in report:
|
||||
rospy.logerr('%s: %s', name, report['failure'])
|
||||
elif 'info' in report:
|
||||
rospy.loginfo('\033[90m%s\033[0m: %s', name, report['info'])
|
||||
if not thread_local.reports:
|
||||
rospy.loginfo('\033[90m%s\033[0m: \033[92mOK\033[0m', name)
|
||||
if rospy.get_param('~time', False):
|
||||
rospy.loginfo('%s: %.1f sec', name, rospy.get_time() - start)
|
||||
return
|
||||
if not failures and not infos:
|
||||
rospy.loginfo('%s: OK', name)
|
||||
return wrapper
|
||||
return inner
|
||||
|
||||
|
||||
def ff(value, precision=2):
|
||||
# safely format float or int
|
||||
if value is None:
|
||||
return '\033[31m???\033[0m'
|
||||
if isinstance(value, float):
|
||||
return ('{:.' + str(precision + 1) + '}').format(value)
|
||||
elif isinstance(value, int):
|
||||
return str(value)
|
||||
|
||||
|
||||
param_get = rospy.ServiceProxy('mavros/param/get', ParamGet)
|
||||
|
||||
|
||||
def get_param(name, default=None):
|
||||
def get_param(name):
|
||||
try:
|
||||
res = param_get(param_id=name)
|
||||
except rospy.ServiceException as e:
|
||||
@@ -106,17 +96,12 @@ def get_param(name, default=None):
|
||||
|
||||
if not res.success:
|
||||
failure('unable to retrieve PX4 parameter %s', name)
|
||||
return default
|
||||
else:
|
||||
if res.value.integer != 0:
|
||||
return res.value.integer
|
||||
return res.value.real
|
||||
|
||||
|
||||
def get_paramf(name, precision=2):
|
||||
return ff(get_param(name), precision)
|
||||
|
||||
|
||||
recv_event = Event()
|
||||
link = mavutil.mavlink.MAVLink('', 255, 1)
|
||||
mavlink_pub = rospy.Publisher('mavlink/to', Mavlink, queue_size=1)
|
||||
@@ -161,24 +146,6 @@ def mavlink_exec(cmd, timeout=3.0):
|
||||
return mavlink_recv
|
||||
|
||||
|
||||
def read_diagnostics(name, key):
|
||||
e = Event()
|
||||
def cb(msg):
|
||||
for status in msg.status:
|
||||
if status.name.lower() == name.lower():
|
||||
for value in status.values:
|
||||
if value.key.lower() == key.lower():
|
||||
cb.value = value.value
|
||||
e.set()
|
||||
return
|
||||
|
||||
cb.value = None
|
||||
sub = rospy.Subscriber('/diagnostics', DiagnosticArray, cb)
|
||||
e.wait(1.0) # wait to read all the diagnostics from nodes publishing them
|
||||
sub.unregister()
|
||||
return cb.value
|
||||
|
||||
|
||||
BOARD_ROTATIONS = {
|
||||
0: 'no rotation',
|
||||
1: 'yaw 45°',
|
||||
@@ -226,28 +193,24 @@ def check_fcu():
|
||||
failure('no connection to the FCU (check wiring)')
|
||||
return
|
||||
|
||||
if not is_process_running('px4', exact=True): # can't use px4 console in SITL
|
||||
clover_tag = re.compile(r'-cl[oe]ver\.\d+$')
|
||||
clover_fw = False
|
||||
# Make sure the console is available to us
|
||||
mavlink_exec('\n')
|
||||
version_str = mavlink_exec('ver all')
|
||||
if version_str == '':
|
||||
info('no version data available from SITL')
|
||||
|
||||
# Make sure the console is available to us
|
||||
mavlink_exec('\n')
|
||||
version_str = mavlink_exec('ver all')
|
||||
if version_str == '':
|
||||
info('no version data available from SITL')
|
||||
r = re.compile(r'^FW (git tag|version): (v?\d\.\d\.\d.*)$')
|
||||
is_clover_firmware = False
|
||||
for ver_line in version_str.split('\n'):
|
||||
match = r.search(ver_line)
|
||||
if match is not None:
|
||||
field, version = match.groups()
|
||||
info('firmware %s: %s' % (field, version))
|
||||
if 'clover' in version or 'clever' in version:
|
||||
is_clover_firmware = True
|
||||
|
||||
for line in version_str.split('\n'):
|
||||
if line.startswith('FW version: '):
|
||||
info(line[len('FW version: '):])
|
||||
elif line.startswith('FW git tag: '): # only Clover's firmware
|
||||
tag = line[len('FW git tag: '):]
|
||||
clover_fw = clover_tag.search(tag)
|
||||
info(tag)
|
||||
elif line.startswith('HW arch: '):
|
||||
info(line[len('HW arch: '):])
|
||||
|
||||
if not clover_fw:
|
||||
info('not Clover PX4 firmware, check https://clover.coex.tech/firmware')
|
||||
if not is_clover_firmware:
|
||||
failure('not running Clover PX4 firmware, https://clover.coex.tech/firmware')
|
||||
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
if est == 1:
|
||||
@@ -284,25 +247,18 @@ def check_fcu():
|
||||
if cbrk_usb_chk != 197848:
|
||||
failure('set parameter CBRK_USB_CHK to 197848 for flying with USB connected')
|
||||
|
||||
if not is_process_running('px4', exact=True): # skip battery check in SITL
|
||||
try:
|
||||
battery = rospy.wait_for_message('mavros/battery', BatteryState, timeout=3)
|
||||
if not battery.cell_voltage:
|
||||
failure('cell voltage is not available, https://clover.coex.tech/power')
|
||||
else:
|
||||
cell = battery.cell_voltage[0]
|
||||
if cell > 4.3 or cell < 3.0:
|
||||
failure('incorrect cell voltage: %.2f V, https://clover.coex.tech/power', cell)
|
||||
elif cell < 3.7:
|
||||
failure('critically low cell voltage: %.2f V, recharge battery', cell)
|
||||
except rospy.ROSException:
|
||||
failure('no battery state')
|
||||
|
||||
# time sync check
|
||||
try:
|
||||
info('time sync offset: %.2f s', float(read_diagnostics('mavros: Time Sync', 'Estimated time offset (s)')))
|
||||
except:
|
||||
failure('cannot read time sync offset')
|
||||
battery = rospy.wait_for_message('mavros/battery', BatteryState, timeout=3)
|
||||
if not battery.cell_voltage:
|
||||
failure('cell voltage is not available, https://clover.coex.tech/power')
|
||||
else:
|
||||
cell = battery.cell_voltage[0]
|
||||
if cell > 4.3 or cell < 3.0:
|
||||
failure('incorrect cell voltage: %.2f V, https://clover.coex.tech/power', cell)
|
||||
elif cell < 3.7:
|
||||
failure('critically low cell voltage: %.2f V, recharge battery', cell)
|
||||
except rospy.ROSException:
|
||||
failure('no battery state')
|
||||
|
||||
except rospy.ROSException:
|
||||
failure('no MAVROS state (check wiring)')
|
||||
@@ -372,7 +328,7 @@ def is_process_running(binary, exact=False, full=False):
|
||||
if exact:
|
||||
args.append('-x') # match exactly with the command name
|
||||
if full:
|
||||
args.append('-f') # use full command line (including arguments) to match
|
||||
args.append('-f') # use full process name to match
|
||||
args.append(binary)
|
||||
subprocess.check_output(args)
|
||||
return True
|
||||
@@ -382,8 +338,6 @@ def is_process_running(binary, exact=False, full=False):
|
||||
|
||||
@check('ArUco markers')
|
||||
def check_aruco():
|
||||
markers = None
|
||||
|
||||
if is_process_running('aruco_detect', full=True):
|
||||
try:
|
||||
info('aruco_detect/length = %g m', rospy.get_param('aruco_detect/length'))
|
||||
@@ -394,9 +348,9 @@ def check_aruco():
|
||||
known_tilt += ' (ALL markers are on the floor)'
|
||||
elif known_tilt == 'map_flipped':
|
||||
known_tilt += ' (ALL markers are on the ceiling)'
|
||||
info('aruco_detect/known_tilt = %s', known_tilt)
|
||||
info('aruco_detector/known_tilt = %s', known_tilt)
|
||||
try:
|
||||
markers = rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=0.8)
|
||||
rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=1)
|
||||
except rospy.ROSException:
|
||||
failure('no markers detection')
|
||||
return
|
||||
@@ -413,49 +367,34 @@ def check_aruco():
|
||||
info('aruco_map/known_tilt = %s', known_tilt)
|
||||
|
||||
try:
|
||||
visualization = rospy.wait_for_message('aruco_map/visualization', VisualizationMarkerArray, timeout=0.8)
|
||||
visualization = rospy.wait_for_message('aruco_map/visualization', VisualizationMarkerArray, timeout=1)
|
||||
info('map has %s markers', len(visualization.markers))
|
||||
except:
|
||||
failure('cannot read aruco_map/visualization topic')
|
||||
|
||||
try:
|
||||
rospy.wait_for_message('aruco_map/pose', PoseWithCovarianceStamped, timeout=0.8)
|
||||
rospy.wait_for_message('aruco_map/pose', PoseWithCovarianceStamped, timeout=1)
|
||||
except rospy.ROSException:
|
||||
if not markers:
|
||||
info('no map detection as no markers detection')
|
||||
elif not markers.markers:
|
||||
info('no map detection as no markers detected')
|
||||
else:
|
||||
failure('no map detection')
|
||||
failure('no map detection')
|
||||
else:
|
||||
info('aruco_map is not running')
|
||||
|
||||
|
||||
def is_on_the_floor():
|
||||
try:
|
||||
dist = rospy.wait_for_message('rangefinder/range', Range, timeout=1)
|
||||
return dist.range < 0.3
|
||||
except rospy.ROSException:
|
||||
return False
|
||||
|
||||
|
||||
@check('Vision position estimate')
|
||||
def check_vpe():
|
||||
vis = None
|
||||
try:
|
||||
vis = rospy.wait_for_message('mavros/vision_pose/pose', PoseStamped, timeout=0.8)
|
||||
vis = rospy.wait_for_message('mavros/vision_pose/pose', PoseStamped, timeout=1)
|
||||
except rospy.ROSException:
|
||||
try:
|
||||
vis = rospy.wait_for_message('mavros/mocap/pose', PoseStamped, timeout=0.8)
|
||||
vis = rospy.wait_for_message('mavros/mocap/pose', PoseStamped, timeout=1)
|
||||
except rospy.ROSException:
|
||||
if not is_process_running('vpe_publisher', full=True):
|
||||
info('no vision position estimate, vpe_publisher is not running')
|
||||
elif rospy.get_param('aruco_map/known_tilt') == 'map_flipped':
|
||||
failure('no vision position estimate, markers are on the ceiling')
|
||||
elif is_on_the_floor():
|
||||
info('no vision position estimate, the drone is on the floor')
|
||||
else:
|
||||
failure('no vision position estimate')
|
||||
failure('no VPE or MoCap messages')
|
||||
# check if vpe_publisher is running
|
||||
try:
|
||||
subprocess.check_output(['pgrep', '-x', 'vpe_publisher'])
|
||||
except subprocess.CalledProcessError:
|
||||
return # it's not running, skip following checks
|
||||
|
||||
# check PX4 settings
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
@@ -467,14 +406,14 @@ def check_vpe():
|
||||
if vision_yaw_w == 0:
|
||||
failure('vision yaw weight is zero, change ATT_W_EXT_HDG parameter')
|
||||
else:
|
||||
info('vision yaw weight: %s', ff(vision_yaw_w))
|
||||
info('Vision yaw weight: %.2f', vision_yaw_w)
|
||||
fuse = get_param('LPE_FUSION')
|
||||
if not fuse & (1 << 2):
|
||||
failure('vision position fusion is disabled, change LPE_FUSION parameter')
|
||||
delay = get_param('LPE_VIS_DELAY')
|
||||
if delay != 0:
|
||||
failure('LPE_VIS_DELAY = %s, but it should be zero', delay)
|
||||
info('LPE_VIS_XY = %s m, LPE_VIS_Z = %s m', get_paramf('LPE_VIS_XY'), get_paramf('LPE_VIS_Z'))
|
||||
failure('LPE_VIS_DELAY parameter is %s, but it should be zero', delay)
|
||||
info('LPE_VIS_XY is %.2f m, LPE_VIS_Z is %.2f m', get_param('LPE_VIS_XY'), get_param('LPE_VIS_Z'))
|
||||
elif est == 2:
|
||||
fuse = get_param('EKF2_AID_MASK')
|
||||
if not fuse & (1 << 3):
|
||||
@@ -483,10 +422,10 @@ def check_vpe():
|
||||
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
|
||||
delay = get_param('EKF2_EV_DELAY')
|
||||
if delay != 0:
|
||||
failure('EKF2_EV_DELAY = %.2f, but it should be zero', delay)
|
||||
info('EKF2_EVA_NOISE = %s, EKF2_EVP_NOISE = %s',
|
||||
get_paramf('EKF2_EVA_NOISE', 3),
|
||||
get_paramf('EKF2_EVP_NOISE', 3))
|
||||
failure('EKF2_EV_DELAY is %.2f, but it should be zero', delay)
|
||||
info('EKF2_EVA_NOISE is %.3f, EKF2_EVP_NOISE is %.3f',
|
||||
get_param('EKF2_EVA_NOISE'),
|
||||
get_param('EKF2_EVP_NOISE'))
|
||||
|
||||
if not vis:
|
||||
return
|
||||
@@ -544,12 +483,6 @@ def check_local_position():
|
||||
failure('roll is %.2f deg; place copter horizontally or redo level horizon calib',
|
||||
math.degrees(roll))
|
||||
|
||||
if not tf_buffer.can_transform('base_link', pose.header.frame_id, rospy.get_rostime(), rospy.Duration(0.5)):
|
||||
failure('can\'t transform from %s to base_link (timeout 0.5 s): is TF enabled?', pose.header.frame_id)
|
||||
|
||||
if not tf_buffer.can_transform('body', pose.header.frame_id, rospy.get_rostime(), rospy.Duration(0.5)):
|
||||
failure('can\'t transform from %s to body (timeout 0.5 s)', pose.header.frame_id)
|
||||
|
||||
except rospy.ROSException:
|
||||
failure('no local position')
|
||||
|
||||
@@ -584,11 +517,9 @@ def check_velocity():
|
||||
@check('Global position (GPS)')
|
||||
def check_global_position():
|
||||
try:
|
||||
rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=0.8)
|
||||
rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=1)
|
||||
except rospy.ROSException:
|
||||
info('no global position')
|
||||
if get_param('SYS_MC_EST_GROUP') == 2 and (get_param('EKF2_AID_MASK', 0) & (1 << 0)):
|
||||
failure('enabled GPS fusion may suppress vision position aiding')
|
||||
|
||||
|
||||
@check('Optical flow')
|
||||
@@ -600,7 +531,7 @@ def check_optical_flow():
|
||||
# check PX4 settings
|
||||
rot = get_param('SENS_FLOW_ROT')
|
||||
if rot != 0:
|
||||
failure('SENS_FLOW_ROT = %s, but it should be zero', rot)
|
||||
failure('SENS_FLOW_ROT parameter is %s, but it should be zero', rot)
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
if est == 1:
|
||||
fuse = get_param('LPE_FUSION')
|
||||
@@ -608,36 +539,32 @@ def check_optical_flow():
|
||||
failure('optical flow fusion is disabled, change LPE_FUSION parameter')
|
||||
if not fuse & (1 << 1):
|
||||
failure('flow gyro compensation is disabled, change LPE_FUSION parameter')
|
||||
scale = get_param('LPE_FLW_SCALE', 1)
|
||||
scale = get_param('LPE_FLW_SCALE')
|
||||
if not numpy.isclose(scale, 1.0):
|
||||
failure('LPE_FLW_SCALE = %.2f, but it should be 1.0', scale)
|
||||
failure('LPE_FLW_SCALE parameter is %.2f, but it should be 1.0', scale)
|
||||
|
||||
info('LPE_FLW_QMIN = %s, LPE_FLW_R = %s, LPE_FLW_RR = %s',
|
||||
get_paramf('LPE_FLW_QMIN'),
|
||||
get_paramf('LPE_FLW_R', 4),
|
||||
get_paramf('LPE_FLW_RR', 4))
|
||||
info('LPE_FLW_QMIN is %s, LPE_FLW_R is %.4f, LPE_FLW_RR is %.4f, SENS_FLOW_MINHGT is %.3f, SENS_FLOW_MAXHGT is %.3f',
|
||||
get_param('LPE_FLW_QMIN'),
|
||||
get_param('LPE_FLW_R'),
|
||||
get_param('LPE_FLW_RR'),
|
||||
get_param('SENS_FLOW_MINHGT'),
|
||||
get_param('SENS_FLOW_MAXHGT'))
|
||||
elif est == 2:
|
||||
fuse = get_param('EKF2_AID_MASK', 0)
|
||||
fuse = get_param('EKF2_AID_MASK')
|
||||
if not fuse & (1 << 1):
|
||||
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
|
||||
delay = get_param('EKF2_OF_DELAY', 0)
|
||||
delay = get_param('EKF2_OF_DELAY')
|
||||
if delay != 0:
|
||||
failure('EKF2_OF_DELAY = %.2f, but it should be zero', delay)
|
||||
info('EKF2_OF_QMIN = %s, EKF2_OF_N_MIN = %s, EKF2_OF_N_MAX = %s',
|
||||
get_paramf('EKF2_OF_QMIN'),
|
||||
get_paramf('EKF2_OF_N_MIN', 4),
|
||||
get_paramf('EKF2_OF_N_MAX', 4))
|
||||
info('SENS_FLOW_MINHGT = %s, SENS_FLOW_MAXHGT = %s', get_paramf('SENS_FLOW_MINHGT', 3), get_paramf('SENS_FLOW_MAXHGT', 3))
|
||||
failure('EKF2_OF_DELAY is %.2f, but it should be zero', delay)
|
||||
info('EKF2_OF_QMIN is %s, EKF2_OF_N_MIN is %.4f, EKF2_OF_N_MAX is %.4f, SENS_FLOW_MINHGT is %.3f, SENS_FLOW_MAXHGT is %.3f',
|
||||
get_param('EKF2_OF_QMIN'),
|
||||
get_param('EKF2_OF_N_MIN'),
|
||||
get_param('EKF2_OF_N_MAX'),
|
||||
get_param('SENS_FLOW_MINHGT'),
|
||||
get_param('SENS_FLOW_MAXHGT'))
|
||||
|
||||
except rospy.ROSException:
|
||||
if rospy.get_param('optical_flow/disable_on_vpe', False):
|
||||
try:
|
||||
rospy.wait_for_message('mavros/vision_pose/pose', PoseStamped, timeout=1)
|
||||
info('no optical flow as disable_on_vpe is true')
|
||||
except:
|
||||
failure('no optical flow on RPi, disable_on_vpe is true, but no vision pose also')
|
||||
else:
|
||||
failure('no optical flow on RPi')
|
||||
failure('no optical flow data (from Raspberry)')
|
||||
|
||||
|
||||
@check('Rangefinder')
|
||||
@@ -661,7 +588,7 @@ def check_rangefinder():
|
||||
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
if est == 1:
|
||||
fuse = get_param('LPE_FUSION', 0)
|
||||
fuse = get_param('LPE_FUSION')
|
||||
if not fuse & (1 << 5):
|
||||
info('"pub agl as lpos down" in LPE_FUSION is disabled, NOT operating over flat surface')
|
||||
else:
|
||||
@@ -682,20 +609,16 @@ def check_rangefinder():
|
||||
|
||||
@check('Boot duration')
|
||||
def check_boot_duration():
|
||||
if not os.path.exists('/etc/clover_version'):
|
||||
info('skip check')
|
||||
return # Don't check not on Clover's image
|
||||
|
||||
output = subprocess.check_output('systemd-analyze').decode()
|
||||
r = re.compile(r'([\d\.]+)s\s*$', flags=re.MULTILINE)
|
||||
duration = float(r.search(output).groups()[0])
|
||||
if duration > 20:
|
||||
if duration > 15:
|
||||
failure('long Raspbian boot duration: %ss (systemd-analyze for analyzing)', duration)
|
||||
|
||||
|
||||
@check('CPU usage')
|
||||
def check_cpu_usage():
|
||||
WHITELIST = 'nodelet', 'gzclient', 'gzserver', 'selfcheck.py'
|
||||
WHITELIST = 'nodelet',
|
||||
CMD = "top -n 1 -b -i | tail -n +8 | awk '{ printf(\"%-8s\\t%-8s\\t%-8s\\n\", $1, $9, $12); }'"
|
||||
output = subprocess.check_output(CMD, shell=True).decode()
|
||||
processes = output.split('\n')
|
||||
@@ -704,16 +627,13 @@ def check_cpu_usage():
|
||||
continue
|
||||
pid, cpu, cmd = process.split('\t')
|
||||
|
||||
if cmd.strip() not in WHITELIST and locale.atof(cpu) > 30:
|
||||
if cmd.strip() not in WHITELIST and float(cpu) > 30:
|
||||
failure('high CPU usage (%s%%) detected: %s (PID %s)',
|
||||
cpu.strip(), cmd.strip(), pid.strip())
|
||||
|
||||
|
||||
@check('clover.service')
|
||||
def check_clover_service():
|
||||
if not os.path.exists('/etc/clover_version'):
|
||||
return # Don't check not on Clover's image
|
||||
|
||||
try:
|
||||
output = subprocess.check_output('systemctl show -p ActiveState --value clover.service'.split(),
|
||||
stderr=subprocess.STDOUT).decode()
|
||||
@@ -726,22 +646,13 @@ def check_clover_service():
|
||||
elif 'failed' in output:
|
||||
failure('service failed to run, check your launch-files')
|
||||
|
||||
BLACKLIST = 'Unexpected command 520', 'Time jump detected', 'different index:'
|
||||
|
||||
r = re.compile(r'^(.*)\[(FATAL|ERROR| WARN)\] \[\d+.\d+\]: (.*?)(\x1b(.*))?$')
|
||||
r = re.compile(r'^(.*)\[(FATAL|ERROR)\] \[\d+.\d+\]: (.*?)(\x1b(.*))?$')
|
||||
error_count = OrderedDict()
|
||||
try:
|
||||
for line in open('/tmp/clover.err', 'r'):
|
||||
skip = False
|
||||
for substr in BLACKLIST:
|
||||
if substr in line:
|
||||
skip = True
|
||||
if skip:
|
||||
continue
|
||||
|
||||
node_error = r.search(line)
|
||||
if node_error:
|
||||
msg = node_error.groups()[1].strip() + ': ' + node_error.groups()[2]
|
||||
msg = node_error.groups()[1] + ': ' + node_error.groups()[2]
|
||||
if msg in error_count:
|
||||
error_count[msg] += 1
|
||||
else:
|
||||
@@ -764,18 +675,11 @@ def check_image():
|
||||
try:
|
||||
info('version: %s', open('/etc/clover_version').read().strip())
|
||||
except IOError:
|
||||
try:
|
||||
info('VM version: %s', open('/etc/clover_vm_version').read().strip())
|
||||
except IOError:
|
||||
info('no /etc/clover_version file, not the Clover image?')
|
||||
info('no /etc/clover_version file, not the Clover image?')
|
||||
|
||||
|
||||
@check('Preflight status')
|
||||
def check_preflight_status():
|
||||
if is_process_running('px4', exact=True):
|
||||
info('can\'t check in SITL')
|
||||
return
|
||||
|
||||
# Make sure the console is available to us
|
||||
mavlink_exec('\n')
|
||||
cmdr_output = mavlink_exec('commander check')
|
||||
@@ -797,10 +701,6 @@ def check_preflight_status():
|
||||
|
||||
@check('Network')
|
||||
def check_network():
|
||||
if not os.path.exists('/etc/clover_version'):
|
||||
# TODO:
|
||||
return # Don't check not on Clover's image
|
||||
|
||||
ros_hostname = os.environ.get('ROS_HOSTNAME', '').strip()
|
||||
|
||||
if not ros_hostname:
|
||||
@@ -823,14 +723,6 @@ def check_network():
|
||||
|
||||
@check('RPi health')
|
||||
def check_rpi_health():
|
||||
try:
|
||||
import shutil
|
||||
total, used, free = shutil.disk_usage('/')
|
||||
if free < 1024 * 1024 * 1024:
|
||||
failure('disk space is less than 1 GB; consider removing logs (~/.ros/log/)')
|
||||
except Exception as e:
|
||||
info('could not check the disk free space: %s', str(e))
|
||||
|
||||
# `vcgencmd get_throttled` output codes taken from
|
||||
# https://github.com/raspberrypi/documentation/blob/JamesH65-patch-vcgencmd-vcdbg-docs/raspbian/applications/vcgencmd.md#get_throttled
|
||||
# TODO: support more base platforms?
|
||||
@@ -861,7 +753,7 @@ def check_rpi_health():
|
||||
# with some of the FLAGs OR'ed together
|
||||
output = subprocess.check_output(['vcgencmd', 'get_throttled']).decode()
|
||||
except OSError:
|
||||
info('could not call vcgencmd binary; not a Raspberry Pi?')
|
||||
failure('could not call vcgencmd binary; not a Raspberry Pi?')
|
||||
return
|
||||
|
||||
throttle_mask = int(output.split('=')[1], base=16)
|
||||
@@ -878,47 +770,26 @@ def check_board():
|
||||
info('could not open /proc/device-tree/model, not a Raspberry Pi?')
|
||||
|
||||
|
||||
def parallel_for(fns):
|
||||
threads = []
|
||||
for fn in fns:
|
||||
thread = Thread(target=fn)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
|
||||
def consequentially_for(fns):
|
||||
for fn in fns:
|
||||
fn()
|
||||
|
||||
|
||||
def selfcheck():
|
||||
checks = [
|
||||
check_image,
|
||||
check_board,
|
||||
check_clover_service,
|
||||
check_network,
|
||||
check_fcu,
|
||||
check_imu,
|
||||
check_local_position,
|
||||
check_velocity,
|
||||
check_global_position,
|
||||
check_preflight_status,
|
||||
check_main_camera,
|
||||
check_aruco,
|
||||
check_simpleoffboard,
|
||||
check_optical_flow,
|
||||
check_vpe,
|
||||
check_rangefinder,
|
||||
check_rpi_health,
|
||||
check_cpu_usage,
|
||||
check_boot_duration,
|
||||
]
|
||||
if rospy.get_param('~parallel', False):
|
||||
parallel_for(checks)
|
||||
else:
|
||||
consequentially_for(checks)
|
||||
check_image()
|
||||
check_board()
|
||||
check_clover_service()
|
||||
check_network()
|
||||
check_fcu()
|
||||
check_imu()
|
||||
check_local_position()
|
||||
check_velocity()
|
||||
check_global_position()
|
||||
check_preflight_status()
|
||||
check_main_camera()
|
||||
check_aruco()
|
||||
check_simpleoffboard()
|
||||
check_optical_flow()
|
||||
check_vpe()
|
||||
check_rangefinder()
|
||||
check_rpi_health()
|
||||
check_cpu_usage()
|
||||
check_boot_duration()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -61,7 +61,6 @@ std::shared_ptr<tf2_ros::TransformBroadcaster> transform_broadcaster;
|
||||
std::shared_ptr<tf2_ros::StaticTransformBroadcaster> static_transform_broadcaster;
|
||||
|
||||
// Parameters
|
||||
string mavros;
|
||||
string local_frame;
|
||||
string fcu_frame;
|
||||
ros::Duration transform_timeout;
|
||||
@@ -150,9 +149,6 @@ void handleState(const mavros_msgs::State& s)
|
||||
inline void publishBodyFrame()
|
||||
{
|
||||
if (body.child_frame_id.empty()) return;
|
||||
if (!body.header.stamp.isZero() && body.header.stamp == local_position.header.stamp) {
|
||||
return; // avoid TF_REPEATED_DATA warnings
|
||||
}
|
||||
|
||||
tf::Quaternion q;
|
||||
q.setRPY(0, 0, tf::getYaw(local_position.pose.orientation));
|
||||
@@ -185,10 +181,9 @@ inline bool waitTransform(const string& target, const string& source,
|
||||
ros::spinOnce();
|
||||
r.sleep();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#define TIMEOUT(msg, timeout) (msg.header.stamp.isZero() || (ros::Time::now() - msg.header.stamp > timeout))
|
||||
#define TIMEOUT(msg, timeout) (ros::Time::now() - msg.header.stamp > timeout)
|
||||
|
||||
bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
|
||||
{
|
||||
@@ -446,10 +441,6 @@ void publish(const ros::Time stamp)
|
||||
|
||||
// publish setpoint frame
|
||||
if (!setpoint.child_frame_id.empty()) {
|
||||
if (setpoint.header.stamp == position_msg.header.stamp) {
|
||||
return; // avoid TF_REPEATED_DATA warnings
|
||||
}
|
||||
|
||||
setpoint.transform.translation.x = position_msg.pose.position.x;
|
||||
setpoint.transform.translation.y = position_msg.pose.position.y;
|
||||
setpoint.transform.translation.z = position_msg.pose.position.z;
|
||||
@@ -694,7 +685,7 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
||||
// }
|
||||
|
||||
if (sp_type == POSITION || sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == VELOCITY || sp_type == ATTITUDE) {
|
||||
// destination point and/or attitude
|
||||
// destination point and/or yaw
|
||||
PoseStamped ps;
|
||||
ps.header.frame_id = frame_id;
|
||||
ps.header.stamp = stamp;
|
||||
@@ -703,12 +694,7 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
||||
ps.pose.position.z = z;
|
||||
ps.pose.orientation.w = 1.0; // Ensure quaternion is always valid
|
||||
|
||||
if (sp_type == ATTITUDE) {
|
||||
ps.pose.position.x = 0;
|
||||
ps.pose.position.y = 0;
|
||||
ps.pose.position.z = 0;
|
||||
ps.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(roll, pitch, yaw);
|
||||
} else if (std::isnan(yaw)) {
|
||||
if (std::isnan(yaw)) {
|
||||
setpoint_yaw_type = YAW_RATE;
|
||||
setpoint_yaw_rate = yaw_rate;
|
||||
} else if (std::isinf(yaw) && yaw > 0) {
|
||||
@@ -726,7 +712,7 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
||||
}
|
||||
|
||||
if (sp_type == VELOCITY) {
|
||||
Vector3Stamped vel;
|
||||
static Vector3Stamped vel;
|
||||
vel.header.frame_id = frame_id;
|
||||
vel.header.stamp = stamp;
|
||||
vel.vector.x = vx;
|
||||
@@ -857,14 +843,6 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||
busy = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool release(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||
{
|
||||
setpoint_timer.stop();
|
||||
res.success = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
@@ -877,9 +855,8 @@ int main(int argc, char **argv)
|
||||
static_transform_broadcaster = std::make_shared<tf2_ros::StaticTransformBroadcaster>();
|
||||
|
||||
// Params
|
||||
nh_priv.param("mavros", mavros, string("mavros")); // for case of using multiple connections
|
||||
nh.param<string>(mavros + "/local_position/tf/frame_id", local_frame, "map");
|
||||
nh.param<string>(mavros + "/local_position/tf/child_frame_id", fcu_frame, "base_link");
|
||||
nh.param<string>("mavros/local_position/tf/frame_id", local_frame, "map");
|
||||
nh.param<string>("mavros/local_position/tf/child_frame_id", fcu_frame, "base_link");
|
||||
nh_priv.param("target_frame", target.child_frame_id, string("navigate_target"));
|
||||
nh_priv.param("setpoint", setpoint.child_frame_id, string("setpoint"));
|
||||
nh_priv.param("auto_release", auto_release, true);
|
||||
@@ -890,13 +867,6 @@ int main(int argc, char **argv)
|
||||
nh_priv.param<string>("body_frame", body.child_frame_id, "body");
|
||||
nh_priv.getParam("reference_frames", reference_frames);
|
||||
|
||||
// Default reference frames
|
||||
std::map<string, string> default_reference_frames;
|
||||
default_reference_frames[body.child_frame_id] = local_frame;
|
||||
default_reference_frames[fcu_frame] = local_frame;
|
||||
if (!target.child_frame_id.empty()) default_reference_frames[target.child_frame_id] = local_frame;
|
||||
reference_frames.insert(default_reference_frames.begin(), default_reference_frames.end()); // merge defaults
|
||||
|
||||
state_timeout = ros::Duration(nh_priv.param("state_timeout", 3.0));
|
||||
local_position_timeout = ros::Duration(nh_priv.param("local_position_timeout", 2.0));
|
||||
velocity_timeout = ros::Duration(nh_priv.param("velocity_timeout", 2.0));
|
||||
@@ -911,25 +881,25 @@ int main(int argc, char **argv)
|
||||
arming_timeout = ros::Duration(nh_priv.param("arming_timeout", 4.0));
|
||||
|
||||
// Service clients
|
||||
arming = nh.serviceClient<mavros_msgs::CommandBool>(mavros + "/cmd/arming");
|
||||
set_mode = nh.serviceClient<mavros_msgs::SetMode>(mavros + "/set_mode");
|
||||
arming = nh.serviceClient<mavros_msgs::CommandBool>("mavros/cmd/arming");
|
||||
set_mode = nh.serviceClient<mavros_msgs::SetMode>("mavros/set_mode");
|
||||
|
||||
// Telemetry subscribers
|
||||
auto state_sub = nh.subscribe(mavros + "/state", 1, &handleState);
|
||||
auto velocity_sub = nh.subscribe(mavros + "/local_position/velocity_body", 1, &handleMessage<TwistStamped, velocity>);
|
||||
auto global_position_sub = nh.subscribe(mavros + "/global_position/global", 1, &handleMessage<NavSatFix, global_position>);
|
||||
auto battery_sub = nh.subscribe(mavros + "/battery", 1, &handleMessage<BatteryState, battery>);
|
||||
auto statustext_sub = nh.subscribe(mavros + "/statustext/recv", 1, &handleMessage<mavros_msgs::StatusText, statustext>);
|
||||
auto manual_control_sub = nh.subscribe(mavros + "/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>);
|
||||
auto local_position_sub = nh.subscribe(mavros + "/local_position/pose", 1, &handleLocalPosition);
|
||||
auto state_sub = nh.subscribe("mavros/state", 1, &handleState);
|
||||
auto velocity_sub = nh.subscribe("mavros/local_position/velocity_body", 1, &handleMessage<TwistStamped, velocity>);
|
||||
auto global_position_sub = nh.subscribe("mavros/global_position/global", 1, &handleMessage<NavSatFix, global_position>);
|
||||
auto battery_sub = nh.subscribe("mavros/battery", 1, &handleMessage<BatteryState, battery>);
|
||||
auto statustext_sub = nh.subscribe("mavros/statustext/recv", 1, &handleMessage<mavros_msgs::StatusText, statustext>);
|
||||
auto manual_control_sub = nh.subscribe("mavros/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>);
|
||||
auto local_position_sub = nh.subscribe("mavros/local_position/pose", 1, &handleLocalPosition);
|
||||
|
||||
// Setpoint publishers
|
||||
position_pub = nh.advertise<PoseStamped>(mavros + "/setpoint_position/local", 1);
|
||||
position_raw_pub = nh.advertise<PositionTarget>(mavros + "/setpoint_raw/local", 1);
|
||||
attitude_pub = nh.advertise<PoseStamped>(mavros + "/setpoint_attitude/attitude", 1);
|
||||
attitude_raw_pub = nh.advertise<AttitudeTarget>(mavros + "/setpoint_raw/attitude", 1);
|
||||
rates_pub = nh.advertise<TwistStamped>(mavros + "/setpoint_attitude/cmd_vel", 1);
|
||||
thrust_pub = nh.advertise<Thrust>(mavros + "/setpoint_attitude/thrust", 1);
|
||||
position_pub = nh.advertise<PoseStamped>("mavros/setpoint_position/local", 1);
|
||||
position_raw_pub = nh.advertise<PositionTarget>("mavros/setpoint_raw/local", 1);
|
||||
attitude_pub = nh.advertise<PoseStamped>("mavros/setpoint_attitude/attitude", 1);
|
||||
attitude_raw_pub = nh.advertise<AttitudeTarget>("mavros/setpoint_raw/attitude", 1);
|
||||
rates_pub = nh.advertise<TwistStamped>("mavros/setpoint_attitude/cmd_vel", 1);
|
||||
thrust_pub = nh.advertise<Thrust>("mavros/setpoint_attitude/thrust", 1);
|
||||
|
||||
// Service servers
|
||||
auto gt_serv = nh.advertiseService("get_telemetry", &getTelemetry);
|
||||
@@ -940,7 +910,6 @@ int main(int argc, char **argv)
|
||||
auto sa_serv = nh.advertiseService("set_attitude", &setAttitude);
|
||||
auto sr_serv = nh.advertiseService("set_rates", &setRates);
|
||||
auto ld_serv = nh.advertiseService("land", &land);
|
||||
auto rl_serv = nh_priv.advertiseService("release", &release);
|
||||
|
||||
// Setpoint timer
|
||||
setpoint_timer = nh.createTimer(ros::Duration(1 / nh_priv.param("setpoint_rate", 30.0)), &publishSetpoint, false, false);
|
||||
|
||||
@@ -33,14 +33,14 @@ ros::Subscriber local_position_sub;
|
||||
ros::Timer zero_timer;
|
||||
PoseStamped vpe, pose;
|
||||
ros::Time got_local_pos(0);
|
||||
ros::Duration publish_zero_timeout, publish_zero_duration, offset_timeout;
|
||||
ros::Duration publish_zero_timout, publish_zero_duration, offset_timeout;
|
||||
TransformStamped offset;
|
||||
|
||||
void publishZero(const ros::TimerEvent& e)
|
||||
{
|
||||
if (!vpe.header.stamp.isZero() && e.current_real - vpe.header.stamp < publish_zero_timeout) return; // have vpe
|
||||
if (e.current_real - vpe.header.stamp < publish_zero_timout) return; // have vpe
|
||||
|
||||
if (!pose.header.stamp.isZero() && e.current_real - pose.header.stamp < publish_zero_timeout) { // have local position
|
||||
if (e.current_real - pose.header.stamp < publish_zero_timout) { // have local position
|
||||
if (got_local_pos.isZero()) {
|
||||
ROS_INFO("got local position");
|
||||
got_local_pos = e.current_real;
|
||||
@@ -53,7 +53,7 @@ void publishZero(const ros::TimerEvent& e)
|
||||
}
|
||||
|
||||
ROS_INFO_THROTTLE(10, "publish zero");
|
||||
geometry_msgs::PoseStamped zero;
|
||||
static geometry_msgs::PoseStamped zero;
|
||||
zero.header.frame_id = local_frame_id;
|
||||
zero.header.stamp = e.current_real;
|
||||
zero.pose.orientation.w = 1;
|
||||
@@ -124,8 +124,8 @@ int main(int argc, char **argv) {
|
||||
|
||||
nh_priv.param<string>("frame_id", frame_id, "");
|
||||
nh_priv.param<string>("offset_frame_id", offset_frame_id, "");
|
||||
nh.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
|
||||
nh.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link");
|
||||
nh_priv.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
|
||||
nh_priv.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link");
|
||||
offset_timeout = ros::Duration(nh_priv.param("offset_timeout", 3.0));
|
||||
|
||||
if (!frame_id.empty()) {
|
||||
@@ -141,11 +141,11 @@ int main(int argc, char **argv) {
|
||||
vpe_pub = nh_priv.advertise<PoseStamped>("vpe", 1);
|
||||
//vpe_cov_pub = nh_priv_.advertise<PoseStamped>("pose_cov_pub", 1);
|
||||
|
||||
if (nh_priv.param("force_init", false) || nh_priv.param("publish_zero", false)) { // publish_zero is old name
|
||||
if (nh_priv.param("publish_zero", false)) {
|
||||
// publish zero to initialize the local position
|
||||
zero_timer = nh.createTimer(ros::Duration(0.1), &publishZero);
|
||||
publish_zero_timeout = ros::Duration(nh_priv.param("force_init_timeout", 5.0));
|
||||
publish_zero_duration = ros::Duration(nh_priv.param("force_init_duration", 5.0));
|
||||
publish_zero_timout = ros::Duration(nh_priv.param("publish_zero_timout", 5.0));
|
||||
publish_zero_duration = ros::Duration(nh_priv.param("publish_zero_duration", 5.0));
|
||||
local_position_sub = nh.subscribe("mavros/local_position/pose", 1, &localPositionCallback);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# $ ./waitfile <file> <command> <args...>
|
||||
# wait until <file> appears and then invoke <command> with <args>
|
||||
|
||||
echo "wait for file $1"
|
||||
while [ ! -e "$1" ]; do sleep 1; done;
|
||||
echo "file $1 appeared"
|
||||
exec "${@:2}"
|
||||
@@ -3,7 +3,6 @@ import rospy
|
||||
import pytest
|
||||
from mavros_msgs.msg import State
|
||||
from clover import srv
|
||||
import time
|
||||
|
||||
@pytest.fixture()
|
||||
def node():
|
||||
@@ -25,7 +24,6 @@ def test_simple_offboard_services_available():
|
||||
rospy.wait_for_service('set_attitude', timeout=5)
|
||||
rospy.wait_for_service('set_rates', timeout=5)
|
||||
rospy.wait_for_service('land', timeout=5)
|
||||
rospy.wait_for_service('simple_offboard/release', timeout=5)
|
||||
|
||||
def test_web_video_server(node):
|
||||
try:
|
||||
@@ -36,43 +34,18 @@ def test_web_video_server(node):
|
||||
import urllib.request as urllib
|
||||
urllib.urlopen("http://localhost:8080").read()
|
||||
|
||||
def test_blocks(node):
|
||||
rospy.wait_for_service('clover_blocks/run', timeout=5)
|
||||
rospy.wait_for_service('clover_blocks/stop', timeout=5)
|
||||
rospy.wait_for_service('clover_blocks/load', timeout=5)
|
||||
rospy.wait_for_service('clover_blocks/store', timeout=5)
|
||||
def test_shell(node):
|
||||
execute = rospy.ServiceProxy('exec', srv.Execute)
|
||||
execute.wait_for_service(5)
|
||||
|
||||
from std_msgs.msg import String
|
||||
from clover_blocks.srv import Run
|
||||
res = execute(cmd='echo foo')
|
||||
assert res.code == 0
|
||||
assert res.output == 'foo\n'
|
||||
|
||||
def wait_print():
|
||||
try:
|
||||
wait_print.result = rospy.wait_for_message('clover_blocks/print', String, timeout=5).data
|
||||
except Exception as e:
|
||||
wait_print.result = str(e)
|
||||
res = execute(cmd='foo')
|
||||
assert res.code == 32512
|
||||
assert res.output == ''
|
||||
|
||||
import threading
|
||||
t = threading.Thread(target=wait_print)
|
||||
t.start()
|
||||
rospy.sleep(0.1)
|
||||
|
||||
run = rospy.ServiceProxy('clover_blocks/run', Run)
|
||||
assert run(code='print("test")').success == True
|
||||
|
||||
t.join()
|
||||
assert wait_print.result == 'test'
|
||||
|
||||
def test_long_callback():
|
||||
from clover import long_callback
|
||||
from time import sleep
|
||||
|
||||
# very basic test for long_callback
|
||||
@long_callback
|
||||
def cb(i):
|
||||
cb.counter += i
|
||||
cb.counter = 0
|
||||
cb(2)
|
||||
sleep(0.1)
|
||||
cb(3)
|
||||
sleep(1)
|
||||
assert cb.counter == 5
|
||||
res = execute(cmd='ls foo')
|
||||
assert res.code == 512
|
||||
assert res.output == ''
|
||||
|
||||
@@ -23,7 +23,10 @@
|
||||
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="map_flipped_frame" args="0 0 0 3.1415926 3.1415926 0 map map_flipped" required="true"/>
|
||||
|
||||
<node name="simple_offboard" pkg="clover" type="simple_offboard" required="true" output="screen"/>
|
||||
<node name="simple_offboard" pkg="clover" type="simple_offboard" required="true" output="screen">
|
||||
<param name="reference_frames/body" value="map"/>
|
||||
<param name="reference_frames/base_link" value="map"/>
|
||||
</node>
|
||||
|
||||
<node name="tf2_web_republisher" pkg="tf2_web_republisher" type="tf2_web_republisher" required="true"/>
|
||||
|
||||
@@ -35,11 +38,6 @@
|
||||
<rosparam param="notify">startup: { r: 255, g: 255, b: 255 }</rosparam>
|
||||
</node>
|
||||
|
||||
<node name="clover_blocks" pkg="clover_blocks" type="clover_blocks" output="screen" required="true"/>
|
||||
|
||||
<node pkg="topic_tools" name="main_camera_throttle" type="throttle" ns="main_camera"
|
||||
args="messages image_raw 5.0 image_raw_throttled" required="true"/>
|
||||
|
||||
<param name="test_module" value="$(find clover)/test/basic.py"/>
|
||||
<test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner"/>
|
||||
</launch>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/var/log/clover.log
|
||||
@@ -1 +0,0 @@
|
||||
/etc/clover_version
|
||||
@@ -1,23 +0,0 @@
|
||||
<h1>
|
||||
/var/log/clover.log
|
||||
<a style="font-size: 0.5em; vertical-align: super; font-weight: normal" href="clover.log" download>download</a>
|
||||
</h1>
|
||||
|
||||
<pre></pre>
|
||||
|
||||
<script type="module">
|
||||
var pre = document.querySelector('pre');
|
||||
|
||||
fetch('clover.log?' + Math.random()).then(function(response) { // random to forbid caching
|
||||
if (response.status == 404) {
|
||||
pre.innerHTML = '/var/log/clover.log does not exist';
|
||||
return;
|
||||
} else if (response.status !== 200) {
|
||||
pre.innerHTML('Error ' + response.status);
|
||||
return;
|
||||
}
|
||||
response.text().then(function(content) {
|
||||
pre.innerHTML = content;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -4,37 +4,24 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="docs">View documentation</a> (snapshot of <a href="https://clover.coex.tech">clover.coex.tech</a>)</li>
|
||||
<li><a href="topics.html">View topics</a></li>
|
||||
<li><a href="" id="wvs">View image topics</a> (<code>web_video_server</code>)</li>
|
||||
<li><a href="" id="butterfly">Open web terminal</a> (<code>Butterfly</code>)</li>
|
||||
<li>View <a href="viz.html">View 3D visualization</a>, <a href="aruco_map.html">3D visualization for markers map</a> (<code>ros3djs</code>)</li>
|
||||
<li><a href="viz.html">View 3D visualization</a> (<code>ros3djs</code>)</li>
|
||||
<li><a href="aruco_map.html">3D visualization for markers map</a> (<code>ros3djs</code>)</li>
|
||||
<li><a href="../clover_blocks/">Blocks programming</a> (<code>Blockly</code>)</li>
|
||||
<li><a href="console.html">Clover console</a> (<code>/var/log/clover.log</code>)</li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Update www using <code>rosrun roswww_static update</code>.
|
||||
</p>
|
||||
|
||||
<div class="version"></div>
|
||||
|
||||
<script src="js/roslib.js"></script>
|
||||
<script type="text/javascript">
|
||||
document.querySelector("#wvs").href = location.protocol + '//' + location.hostname + ':8080';
|
||||
document.querySelector("#butterfly").href = location.protocol + '//' + location.hostname + ':57575';
|
||||
|
||||
document.querySelector("#butterfly").addEventListener('click', function(e) {
|
||||
if (location.hostname == 'localhost' || location.hostname == '127.0.0.1') {
|
||||
if (!confirm('Please use regular Terminal app on a local machine.\nClick OK to proceed to Butterfly anyway.')) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Determine image version
|
||||
fetch('clover_version').then(function(response) {
|
||||
if (response.status !== 200) return;
|
||||
response.text().then(function(text) {
|
||||
document.querySelector('.version').innerHTML = 'Version: ' + text;
|
||||
});
|
||||
var ros = new ROSLIB.Ros({ url: 'ws://' + location.hostname + ':9090' });
|
||||
var exec = new ROSLIB.Service({ ros: ros, name : '/exec', serviceType : 'clover/Execute' });
|
||||
exec.callService(new ROSLIB.ServiceRequest({ cmd: 'cat /etc/clover_version' }), function(result) {
|
||||
document.querySelector('.version').innerHTML = 'Version: ' + result.output;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
// Browserified https://www.npmjs.com/package/json-to-pretty-yaml module
|
||||
|
||||
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
var typeOf = require('remedial').typeOf;
|
||||
var trimWhitespace = require('remove-trailing-spaces');
|
||||
|
||||
function stringify(data) {
|
||||
var handlers, indentLevel = '';
|
||||
|
||||
handlers = {
|
||||
"undefined": function() {
|
||||
// objects will not have `undefined` converted to `null`
|
||||
// as this may have unintended consequences
|
||||
// For arrays, however, this behavior seems appropriate
|
||||
return 'null';
|
||||
},
|
||||
"null": function() {
|
||||
return 'null';
|
||||
},
|
||||
"number": function(x) {
|
||||
return x;
|
||||
},
|
||||
"boolean": function(x) {
|
||||
return x ? 'true' : 'false';
|
||||
},
|
||||
"string": function(x) {
|
||||
// to avoid the string "true" being confused with the
|
||||
// the literal `true`, we always wrap strings in quotes
|
||||
return JSON.stringify(x);
|
||||
},
|
||||
"array": function(x) {
|
||||
var output = '';
|
||||
|
||||
if (0 === x.length) {
|
||||
output += '[]';
|
||||
return output;
|
||||
}
|
||||
|
||||
indentLevel = indentLevel.replace(/$/, ' ');
|
||||
x.forEach(function(y, i) {
|
||||
// TODO how should `undefined` be handled?
|
||||
var handler = handlers[typeOf(y)];
|
||||
|
||||
if (!handler) {
|
||||
throw new Error('what the crap: ' + typeOf(y));
|
||||
}
|
||||
|
||||
output += '\n' + indentLevel + '- ' + handler(y, true);
|
||||
|
||||
});
|
||||
indentLevel = indentLevel.replace(/ /, '');
|
||||
|
||||
return output;
|
||||
},
|
||||
"object": function(x, inArray, rootNode) {
|
||||
var output = '';
|
||||
|
||||
if (0 === Object.keys(x).length) {
|
||||
output += '{}';
|
||||
return output;
|
||||
}
|
||||
|
||||
if (!rootNode) {
|
||||
indentLevel = indentLevel.replace(/$/, ' ');
|
||||
}
|
||||
|
||||
Object.keys(x).forEach(function(k, i) {
|
||||
var val = x[k],
|
||||
handler = handlers[typeOf(val)];
|
||||
|
||||
if ('undefined' === typeof val) {
|
||||
// the user should do
|
||||
// delete obj.key
|
||||
// and not
|
||||
// obj.key = undefined
|
||||
// but we'll error on the side of caution
|
||||
return;
|
||||
}
|
||||
|
||||
if (!handler) {
|
||||
throw new Error('what the crap: ' + typeOf(val));
|
||||
}
|
||||
|
||||
if (!(inArray && i === 0)) {
|
||||
output += '\n' + indentLevel;
|
||||
}
|
||||
|
||||
output += k + ': ' + handler(val);
|
||||
});
|
||||
indentLevel = indentLevel.replace(/ /, '');
|
||||
|
||||
return output;
|
||||
},
|
||||
"function": function() {
|
||||
// TODO this should throw or otherwise be ignored
|
||||
return '[object Function]';
|
||||
}
|
||||
};
|
||||
return trimWhitespace(handlers[typeOf(data)](data, true, true) + '\n');
|
||||
|
||||
}
|
||||
|
||||
window.yamlStringify = stringify;
|
||||
module.exports.stringify = stringify;
|
||||
}());
|
||||
|
||||
},{"remedial":2,"remove-trailing-spaces":3}],2:[function(require,module,exports){
|
||||
/*jslint onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var global = Function('return this')()
|
||||
, classes = "Boolean Number String Function Array Date RegExp Object".split(" ")
|
||||
, i
|
||||
, name
|
||||
, class2type = {}
|
||||
;
|
||||
|
||||
for (i in classes) {
|
||||
if (classes.hasOwnProperty(i)) {
|
||||
name = classes[i];
|
||||
class2type["[object " + name + "]"] = name.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
function typeOf(obj) {
|
||||
return (null === obj || undefined === obj) ? String(obj) : class2type[Object.prototype.toString.call(obj)] || "object";
|
||||
}
|
||||
|
||||
function isEmpty(o) {
|
||||
var i, v;
|
||||
if (typeOf(o) === 'object') {
|
||||
for (i in o) { // fails jslint
|
||||
v = o[i];
|
||||
if (v !== undefined && typeOf(v) !== 'function') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!String.prototype.entityify) {
|
||||
String.prototype.entityify = function () {
|
||||
return this.replace(/&/g, "&").replace(/</g,
|
||||
"<").replace(/>/g, ">");
|
||||
};
|
||||
}
|
||||
|
||||
if (!String.prototype.quote) {
|
||||
String.prototype.quote = function () {
|
||||
var c, i, l = this.length, o = '"';
|
||||
for (i = 0; i < l; i += 1) {
|
||||
c = this.charAt(i);
|
||||
if (c >= ' ') {
|
||||
if (c === '\\' || c === '"') {
|
||||
o += '\\';
|
||||
}
|
||||
o += c;
|
||||
} else {
|
||||
switch (c) {
|
||||
case '\b':
|
||||
o += '\\b';
|
||||
break;
|
||||
case '\f':
|
||||
o += '\\f';
|
||||
break;
|
||||
case '\n':
|
||||
o += '\\n';
|
||||
break;
|
||||
case '\r':
|
||||
o += '\\r';
|
||||
break;
|
||||
case '\t':
|
||||
o += '\\t';
|
||||
break;
|
||||
default:
|
||||
c = c.charCodeAt();
|
||||
o += '\\u00' + Math.floor(c / 16).toString(16) +
|
||||
(c % 16).toString(16);
|
||||
}
|
||||
}
|
||||
}
|
||||
return o + '"';
|
||||
};
|
||||
}
|
||||
|
||||
if (!String.prototype.supplant) {
|
||||
String.prototype.supplant = function (o) {
|
||||
return this.replace(/{([^{}]*)}/g,
|
||||
function (a, b) {
|
||||
var r = o[b];
|
||||
return typeof r === 'string' || typeof r === 'number' ? r : a;
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
if (!String.prototype.trim) {
|
||||
String.prototype.trim = function () {
|
||||
return this.replace(/^\s*(\S*(?:\s+\S+)*)\s*$/, "$1");
|
||||
};
|
||||
}
|
||||
|
||||
// CommonJS / npm / Ender.JS
|
||||
module.exports = {
|
||||
typeOf: typeOf,
|
||||
isEmpty: isEmpty
|
||||
};
|
||||
global.typeOf = global.typeOf || typeOf;
|
||||
global.isEmpty = global.isEmpty || isEmpty;
|
||||
}());
|
||||
|
||||
},{}],3:[function(require,module,exports){
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* removeTrailingSpaces
|
||||
* Remove the trailing spaces from a string.
|
||||
*
|
||||
* @name removeTrailingSpaces
|
||||
* @function
|
||||
* @param {String} input The input string.
|
||||
* @returns {String} The output string.
|
||||
*/
|
||||
|
||||
module.exports = function removeTrailingSpaces(input) {
|
||||
// TODO If possible, use a regex
|
||||
return input.split("\n").map(function (x) {
|
||||
return x.trimRight();
|
||||
}).join("\n");
|
||||
};
|
||||
},{}]},{},[1]);
|
||||
@@ -1,86 +0,0 @@
|
||||
const url = 'ws://' + location.hostname + ':9090';
|
||||
const ros = new ROSLIB.Ros({ url: url });
|
||||
const params = Object.fromEntries(new URLSearchParams(window.location.search).entries());
|
||||
|
||||
ros.on('connection', function () {
|
||||
document.body.classList.add('connected');
|
||||
document.body.classList.remove('closed');
|
||||
init();
|
||||
});
|
||||
|
||||
ros.on('close', function () {
|
||||
document.body.classList.remove('connected');
|
||||
document.body.classList.add('closed');
|
||||
setTimeout(function() {
|
||||
// reconnect
|
||||
ros.connect(url);
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
const title = document.querySelector('h1');
|
||||
const topicsList = document.querySelector('#topics');
|
||||
const topicMessage = document.querySelector('#topic-message');
|
||||
|
||||
function viewTopicsList() {
|
||||
title.innerHTML = 'Topics:';
|
||||
|
||||
ros.getTopics(function(topics) {
|
||||
topicsList.innerHTML = topics.topics.map(function(topic, i) {
|
||||
const type = topics.types[i];
|
||||
if (type == 'sensor_msgs/Image') {
|
||||
let url = `${location.protocol}//${location.hostname}:8080/stream_viewer?topic=${topic}`;
|
||||
return `<li><a href="${url}" class=topic title=${type}>${topic}</a> 🖼</li>`;
|
||||
} else {
|
||||
return `<li><a href="?topic=${topic}" class=topic title=${type}>${topic}</a></li>`;
|
||||
}
|
||||
}).join('');
|
||||
});
|
||||
}
|
||||
|
||||
let rosdistro;
|
||||
|
||||
function viewTopic(topic) {
|
||||
let counter = 0;
|
||||
let index = '<a href=topics.html>Topics</a>';
|
||||
title.innerHTML = `${index}: ${topic}`;
|
||||
topicMessage.style.display = 'block';
|
||||
|
||||
ros.getTopicType(topic, function(typeStr) {
|
||||
const [pack, type] = typeStr.split('/');
|
||||
let href = `https://docs.ros.org/en/${rosdistro}/api/${pack}/html/msg/${type}.html`;
|
||||
title.innerHTML = `${index}: ${topic} <a id="topic-type" href=${href} target="_blank">${typeStr}</a>`;
|
||||
});
|
||||
|
||||
new ROSLIB.Topic({ ros: ros, name: topic }).subscribe(function(msg) {
|
||||
counter++;
|
||||
document.title = topic;
|
||||
if (mouseDown) return;
|
||||
|
||||
if (msg.header && msg.header.stamp) {
|
||||
if (params.date || params.offset) {
|
||||
let date = new Date(msg.header.stamp.secs * 1e3 + msg.header.stamp.nsecs * 1e-6);
|
||||
if (params.date) msg.header.date = date.toISOString();
|
||||
if (params.offset) msg.header.offset = (new Date() - date) * 1e-3;
|
||||
}
|
||||
}
|
||||
|
||||
let txt = `<div class=counter>${counter} received</div>${yamlStringify(msg)}`; // JSON.stringify(msg, null, 4);
|
||||
topicMessage.innerHTML = txt;
|
||||
});
|
||||
}
|
||||
|
||||
let mouseDown;
|
||||
|
||||
topicMessage.addEventListener('mousedown', function() { mouseDown = true; });
|
||||
topicMessage.addEventListener('mouseup', function() { mouseDown = false; });
|
||||
|
||||
function init() {
|
||||
if (!params.topic) {
|
||||
viewTopicsList();
|
||||
} else {
|
||||
new ROSLIB.Param({ ros: ros, name: '/rosdistro'}).get(function(value) {
|
||||
rosdistro = value.trim();
|
||||
viewTopic(params.topic);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>ROS topics</title>
|
||||
<script src="js/roslib.js"></script>
|
||||
<link rel="icon" href="data:,"> <!-- make chrome don't request icon -->
|
||||
<script type="module" src="js/topics.js"></script>
|
||||
<script src="js/json-to-pretty-yaml.js"></script>
|
||||
<style>
|
||||
#topics { line-height: 1.2em; }
|
||||
#topic-view {
|
||||
display: none;
|
||||
}
|
||||
#topic-message {
|
||||
display: none;
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
}
|
||||
.counter { color: #b9b9b9; margin-bottom: 1em; }
|
||||
#topic-type { font-family: monospace; font-size: 0.5em; vertical-align: super; font-weight: normal; }
|
||||
.topic { font-family: monospace; }
|
||||
body.closed { background-color: rgb(207, 207, 207); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1> </h1>
|
||||
<ul id="topics"></ul>
|
||||
<code id="topic-message">No messages received</code>
|
||||
</body>
|
||||
</html>
|
||||
@@ -73,13 +73,6 @@ catkin_install_python(PROGRAMS src/clover_blocks
|
||||
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||
)
|
||||
|
||||
# TODO: store programs in home directory?
|
||||
install(DIRECTORY programs
|
||||
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
|
||||
)
|
||||
|
||||
install(DIRECTORY www DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
|
||||
|
||||
#############
|
||||
## Testing ##
|
||||
#############
|
||||
|
||||
@@ -10,7 +10,7 @@ Internal package documentation is given below.
|
||||
|
||||
## Frontend
|
||||
|
||||
The frontend files are located in [`www`](./www/) subdirectory. The frontend application uses [`roslib.js`](http://wiki.ros.org/roslibjs) library for communicating with backend node and other ROS resources.
|
||||
The frontend files are located in [`www`](./www/) subdirectory. The frontend application uses [`roblib.js`](http://wiki.ros.org/roslibjs) library for communicating with backend node and other ROS resources.
|
||||
|
||||
## `clover_blocks` node
|
||||
|
||||
@@ -19,7 +19,7 @@ The frontend files are located in [`www`](./www/) subdirectory. The frontend app
|
||||
### Services
|
||||
|
||||
* `~run` ([*clover_blocks/Run*](srv/Run.srv)) – run Blockly-generated program (in Python).
|
||||
* `~stop` ([*std_srvs/Trigger*](http://docs.ros.org/noetic/api/std_srvs/html/srv/Trigger.html)) – terminate the running program.
|
||||
* `~stop` ([*std_srvs/Trigger*](http://docs.ros.org/melodic/api/std_srvs/html/srv/Trigger.html)) – terminate the running program.
|
||||
* `~store` ([*clover_blocks/load*](srv/Store.srv)) – store a user program (to `<package_path>/programs` by default).
|
||||
* `~load` ([*clover_blocks/load*](srv/Load.srv)) – load all the stored programs.
|
||||
|
||||
@@ -30,8 +30,7 @@ The frontend files are located in [`www`](./www/) subdirectory. The frontend app
|
||||
Parameters read by frontend:
|
||||
|
||||
* `~navigate_tolerance` (*float*) – distance tolerance in meters, used for navigate-like blocks (default: 0.2).
|
||||
* `~navigate_global_tolerance` (*float*) – distance tolerance for global coordinates navigation (default: 1).
|
||||
* `~yaw_tolerance` (*float*) – yaw angle tolerance in degrees, used in set_yaw block (default: 1).
|
||||
* `~yaw_tolerance` (*float*) – yaw angle tolerance in degrees, used in set_yaw block (default: 20).
|
||||
* `~sleep_time` (*float*) – duration of sleep in loop cycles, used for navigate-like blocks (default: 0.2).
|
||||
* `~confirm_run` (*bool*) – enable confirmation to run the program (default: true).
|
||||
|
||||
@@ -45,11 +44,11 @@ http://<hostname>/clover_blocks/?navigate_tolerance=0.5&sleep_time=0.1
|
||||
|
||||
#### Published
|
||||
|
||||
* `~running` ([*std_msgs/Bool*](http://docs.ros.org/noetic/api/std_msgs/html/msg/Bool.html)) – indicates if the program is currently running.
|
||||
* `~block` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – current executing block (maximum topic rate is limited).
|
||||
* `~error` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user program errors and exceptions.
|
||||
* `~running` ([*std_msgs/Bool*](http://docs.ros.org/melodic/api/std_msgs/html/msg/Bool.html)) – indicates if the program is currently running.
|
||||
* `~block` ([*std_msgs/String*](http://docs.ros.org/melodic/api/std_msgs/html/msg/String.html)) – current executing block (maximum topic rate is limited).
|
||||
* `~error` ([*std_msgs/String*](http://docs.ros.org/melodic/api/std_msgs/html/msg/String.html)) – user program errors and exceptions.
|
||||
* `~prompt` ([*clover_blocks/Prompt*](msg/Prompt.msg)) – user input request (includes random request ID string).
|
||||
|
||||
This topic is published from the frontend side:
|
||||
|
||||
* `~prompt/<request_id>` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user input response.
|
||||
* `~prompt/<request_id>` ([*std_msgs/String*](http://docs.ros.org/melodic/api/std_msgs/html/msg/String.html)) – user input response.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="2">
|
||||
<name>clover_blocks</name>
|
||||
<version>0.23.0</version>
|
||||
<version>0.21.1</version>
|
||||
<description>Blockly programming support for Clover</description>
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
<license>MIT</license>
|
||||
|
||||
@@ -11,8 +11,7 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import rospy
|
||||
import os, sys
|
||||
import traceback
|
||||
import os
|
||||
import threading
|
||||
import re
|
||||
import uuid
|
||||
@@ -117,12 +116,7 @@ def run(req):
|
||||
rospy.loginfo('Program forced to stop')
|
||||
except Exception as e:
|
||||
rospy.logerr(str(e))
|
||||
traceback.print_exc()
|
||||
etype, value, tb = sys.exc_info()
|
||||
fmt = traceback.format_exception(etype, value, tb)
|
||||
fmt.pop(1) # remove 'clover_blocks' file frame
|
||||
exc_info = ''.join(fmt)
|
||||
error_pub.publish(str(e) + '\n\n' + exc_info)
|
||||
error_pub.publish(str(e))
|
||||
|
||||
rospy.loginfo('Program terminated')
|
||||
running_lock.release()
|
||||
@@ -146,7 +140,6 @@ def stop(req):
|
||||
return {'success': True}
|
||||
|
||||
|
||||
# TODO: find dir in installed package
|
||||
programs_path = rospy.get_param('~programs_dir', os.path.dirname(os.path.abspath(__file__)) + '/../programs')
|
||||
|
||||
|
||||
|
||||
@@ -31,14 +31,6 @@ function considerFrameId(e) {
|
||||
this.getInput('Y').fieldRow[0].setValue('y');
|
||||
this.getInput('Z').fieldRow[0].setValue('z');
|
||||
}
|
||||
if (this.getInput('LAT')) { // block has global coordinates
|
||||
let global = frameId.startsWith('GLOBAL');
|
||||
this.getInput('LAT').setVisible(global);
|
||||
this.getInput('LON').setVisible(global);
|
||||
this.getInput('X').setVisible(!global);
|
||||
this.getInput('Y').setVisible(!global);
|
||||
this.getInput('Z').fieldRow[0].setValue(frameId == 'GLOBAL' ? 'altitude' : 'z');
|
||||
}
|
||||
}
|
||||
if (this.getInput('ID')) { // block has marker id field
|
||||
this.getInput('ID').setVisible(frameId == 'ARUCO'); // toggle id field
|
||||
@@ -73,9 +65,6 @@ function updateSetpointBlock(e) {
|
||||
|
||||
Blockly.Blocks['navigate'] = {
|
||||
init: function () {
|
||||
let navFrameId = frameIds.slice();
|
||||
navFrameId.push(['global', 'GLOBAL_LOCAL'])
|
||||
navFrameId.push(['global, WGS 84 alt.', 'GLOBAL'])
|
||||
this.appendDummyInput()
|
||||
.appendField("navigate to point");
|
||||
this.appendValueInput("X")
|
||||
@@ -84,20 +73,12 @@ Blockly.Blocks['navigate'] = {
|
||||
this.appendValueInput("Y")
|
||||
.setCheck("Number")
|
||||
.appendField("left");
|
||||
this.appendValueInput("LAT")
|
||||
.setCheck("Number")
|
||||
.appendField("latitude")
|
||||
.setVisible(false);
|
||||
this.appendValueInput("LON")
|
||||
.setCheck("Number")
|
||||
.appendField("longitude")
|
||||
.setVisible(false)
|
||||
this.appendValueInput("Z")
|
||||
.setCheck("Number")
|
||||
.appendField("up");
|
||||
this.appendDummyInput()
|
||||
.appendField("relative to")
|
||||
.appendField(new Blockly.FieldDropdown(navFrameId), "FRAME_ID");
|
||||
.appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID");
|
||||
this.appendValueInput("ID")
|
||||
.setCheck("Number")
|
||||
.appendField("with ID")
|
||||
@@ -287,7 +268,7 @@ Blockly.Blocks['mode'] = {
|
||||
.appendField("current flight mode");
|
||||
this.setOutput(true, "String");
|
||||
this.setColour(COLOR_STATE);
|
||||
this.setTooltip("Returns current flight mode (POSCTL, OFFBOARD, etc).");
|
||||
this.setTooltip("");
|
||||
this.setHelpUrl(DOCS_URL + '#' + this.type);
|
||||
}
|
||||
};
|
||||
@@ -394,7 +375,7 @@ Blockly.Blocks['take_off'] = {
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
this.setColour(COLOR_FLIGHT);
|
||||
this.setTooltip("Take off on desired altitude in meters.");
|
||||
this.setTooltip("Take off on desired altitude in meters");
|
||||
this.setHelpUrl(DOCS_URL + '#' + this.type);
|
||||
}
|
||||
};
|
||||
@@ -410,7 +391,7 @@ Blockly.Blocks['land'] = {
|
||||
this.setPreviousStatement(true, null);
|
||||
this.setNextStatement(true, null);
|
||||
this.setColour(COLOR_FLIGHT);
|
||||
this.setTooltip("Land the drone.");
|
||||
this.setTooltip("");
|
||||
this.setHelpUrl(DOCS_URL + '#' + this.type);
|
||||
}
|
||||
};
|
||||
@@ -419,10 +400,10 @@ Blockly.Blocks['global_position'] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("current")
|
||||
.appendField(new Blockly.FieldDropdown([["latitude", "LAT"], ["longitude", "LON"], ["altitude", "ALT"]]), "FIELD");
|
||||
.appendField(new Blockly.FieldDropdown([["latitude", "LATITUDE"], ["longitude", "LONGITUDE"], ["altitude", "ALTITUDE"]]), "NAME");
|
||||
this.setOutput(true, "Number");
|
||||
this.setColour(COLOR_STATE);
|
||||
this.setTooltip("Returns current global position (latitude, longitude, altitude above the WGS 84 ellipsoid).");
|
||||
this.setColour(230);
|
||||
this.setTooltip("");
|
||||
this.setHelpUrl(DOCS_URL + '#' + this.type);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,8 +52,6 @@
|
||||
<value name="X"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="Y"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="Z"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="LAT"><shadow type="math_number"><field name="NUM">47.397503</field></shadow></value>
|
||||
<value name="LON"><shadow type="math_number"><field name="NUM">8.544945</field></shadow></value>
|
||||
<value name="SPEED"><shadow type="math_number"><field name="NUM">0.5</field></shadow></value>
|
||||
<value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
</block>
|
||||
@@ -87,7 +85,6 @@
|
||||
<value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
</block>
|
||||
<block type="get_attitude"></block>
|
||||
<block type="global_position"></block>
|
||||
<block type="distance">
|
||||
<value name="X"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="Y"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
|
||||
@@ -39,8 +39,7 @@ var workspace = Blockly.inject('blockly', {
|
||||
function readParams() {
|
||||
return Promise.all([
|
||||
ros.readParam('navigate_tolerance', true, 0.2),
|
||||
ros.readParam('navigate_global_tolerance', true, 1),
|
||||
ros.readParam('yaw_tolerance', true, 1),
|
||||
ros.readParam('yaw_tolerance', true, 20),
|
||||
ros.readParam('sleep_time', true, 0.2),
|
||||
ros.readParam('confirm_run', true, true),
|
||||
]);
|
||||
@@ -211,7 +210,7 @@ function loadPrograms() {
|
||||
updateChanged();
|
||||
}, function(err) {
|
||||
document.querySelector('.backend-fail').style.display = 'inline';
|
||||
alert(`Error loading programs list.\n\nHave you enabled 'blocks' in clover.launch?`);
|
||||
alert(`Error loading programs list.\n\nHave you enabled clover_blocks in clover.launch?`);
|
||||
runButton.disabled = true;
|
||||
})
|
||||
}
|
||||
|
||||
@@ -33,18 +33,6 @@ const NAVIGATE_WAIT = () => `\ndef navigate_wait(x=0, y=0, z=0, speed=0.5, frame
|
||||
return
|
||||
rospy.sleep(${params.sleep_time})\n`;
|
||||
|
||||
const NAVIGATE_GLOBAL_WAIT = () => `\ndef navigate_global_wait(lat, lon, z, speed=0.5):
|
||||
res = navigate_global(lat=lat, lon=lon, z=z, yaw=float('inf'), speed=speed)
|
||||
|
||||
if not res.success:
|
||||
raise Exception(res.message)
|
||||
|
||||
while not rospy.is_shutdown():
|
||||
telem = get_telemetry(frame_id='navigate_target')
|
||||
if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < ${params.navigate_global_tolerance}:
|
||||
return
|
||||
rospy.sleep(${params.sleep_time})\n`;
|
||||
|
||||
const LAND_WAIT = () => `\ndef land_wait():
|
||||
land()
|
||||
while get_telemetry().armed:
|
||||
@@ -80,9 +68,6 @@ function generateROSDefinitions() {
|
||||
if (rosDefinitions.offboard) {
|
||||
code += `get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)\n`;
|
||||
code += `navigate = rospy.ServiceProxy('navigate', srv.Navigate)\n`;
|
||||
if (rosDefinitions.navigateGlobal) {
|
||||
code += `navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)\n`;
|
||||
}
|
||||
if (rosDefinitions.setVelocity) {
|
||||
code += `set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)\n`;
|
||||
}
|
||||
@@ -109,10 +94,6 @@ function generateROSDefinitions() {
|
||||
Blockly.Python.definitions_['import_math'] = 'import math';
|
||||
code += NAVIGATE_WAIT();
|
||||
}
|
||||
if (rosDefinitions.navigateGlobalWait) {
|
||||
Blockly.Python.definitions_['import_math'] = 'import math';
|
||||
code += NAVIGATE_GLOBAL_WAIT();
|
||||
}
|
||||
if (rosDefinitions.landWait) {
|
||||
code += LAND_WAIT();
|
||||
}
|
||||
@@ -180,48 +161,24 @@ Blockly.Python.navigate = function(block) {
|
||||
let x = Blockly.Python.valueToCode(block, 'X', Blockly.Python.ORDER_NONE);
|
||||
let y = Blockly.Python.valueToCode(block, 'Y', Blockly.Python.ORDER_NONE);
|
||||
let z = Blockly.Python.valueToCode(block, 'Z', Blockly.Python.ORDER_NONE);
|
||||
let lat = Blockly.Python.valueToCode(block, 'LAT', Blockly.Python.ORDER_NONE);
|
||||
let lon = Blockly.Python.valueToCode(block, 'LON', Blockly.Python.ORDER_NONE);
|
||||
let wait = block.getFieldValue('WAIT') == 'TRUE';
|
||||
let frameId = block.getFieldValue('FRAME_ID');
|
||||
let frameId = buildFrameId(block);
|
||||
let speed = Blockly.Python.valueToCode(block, 'SPEED', Blockly.Python.ORDER_NONE);
|
||||
|
||||
let params = [`x=${x}`, `y=${y}`, `z=${z}`, `frame_id=${frameId}`, `speed=${speed}`];
|
||||
|
||||
simpleOffboard();
|
||||
|
||||
// global coordinates
|
||||
if (frameId.startsWith('GLOBAL')) {
|
||||
rosDefinitions.navigateGlobal = true;
|
||||
if (block.getFieldValue('WAIT') == 'TRUE') {
|
||||
rosDefinitions.navigateWait = true;
|
||||
simpleOffboard();
|
||||
|
||||
if (frameId == 'GLOBAL') {
|
||||
z = `${z} + get_telemetry().alt - get_telemetry().z`;
|
||||
}
|
||||
|
||||
if (wait) {
|
||||
rosDefinitions.navigateGlobalWait = true;
|
||||
simpleOffboard();
|
||||
return `navigate_global_wait(lat=${lat}, lon=${lon}, z=${z}, speed=${speed})\n`;
|
||||
|
||||
} else {
|
||||
return `navigate_global(lat=${lat}, lon=${lon}, z=${z}, yaw=float('inf'), speed=${speed})\n`;
|
||||
}
|
||||
return `navigate_wait(${params.join(', ')})\n`;
|
||||
|
||||
} else {
|
||||
frameId = buildFrameId(block);
|
||||
let params = [`x=${x}`, `y=${y}`, `z=${z}`, `frame_id=${frameId}`, `speed=${speed}`];
|
||||
|
||||
if (wait) {
|
||||
rosDefinitions.navigateWait = true;
|
||||
simpleOffboard();
|
||||
|
||||
return `navigate_wait(${params.join(', ')})\n`;
|
||||
|
||||
} else {
|
||||
if (frameId != 'body') {
|
||||
params.push(`yaw=float('nan')`);
|
||||
}
|
||||
return `navigate(${params.join(', ')})\n`;
|
||||
if (frameId != 'body') {
|
||||
params.push(`yaw=float('nan')`);
|
||||
}
|
||||
return `navigate(${params.join(', ')})\n`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,12 +315,6 @@ Blockly.Python.get_attitude = function(block) {
|
||||
return [code, Blockly.Python.ORDER_FUNCTION_CALL];
|
||||
}
|
||||
|
||||
Blockly.Python.global_position = function(block) {
|
||||
simpleOffboard();
|
||||
var code = `get_telemetry().${block.getFieldValue('FIELD').toLowerCase()}`;
|
||||
return [code, Blockly.Python.ORDER_FUNCTION_CALL];
|
||||
}
|
||||
|
||||
Blockly.Python.distance = function(block) {
|
||||
rosDefinitions.distance = true;
|
||||
simpleOffboard();
|
||||
@@ -464,7 +415,7 @@ Blockly.Python.led_count = function(block) {
|
||||
|
||||
function pigpio() {
|
||||
Blockly.Python.definitions_['import_pigpio'] = 'import pigpio';
|
||||
Blockly.Python.definitions_['init_pigpio'] = 'pi = pigpio.pi()\nif not pi.connected: raise Exception(\'Cannot connect to pigpiod\')';
|
||||
Blockly.Python.definitions_['init_pigpio'] = 'pi = pigpio.pi()';
|
||||
}
|
||||
|
||||
const GPIO_READ = `\ndef gpio_read(pin):
|
||||
|
||||
@@ -14,7 +14,7 @@ You may provide additional parameters for `spawn_drone.launch` as well:
|
||||
* `rangefinder` (*boolean*, default: *true*) - controls whether the drone will have a downward-facing laser rangefinder attached;
|
||||
* `led` (*boolean*, default: *true*) - controls whether the drone will have a programmable LED strip (requires plugins from `clover_simulation`);
|
||||
* `gps` (*boolean*, default: *true*) - controls whether the drone will have a simulated GPS attached (requires plugins from `sitl_gazebo`);
|
||||
* `maintain_camera_rate` (*boolean*, default: *false*) - slow down the simulation to maintain camera publishing rate (internally changes the camera plugin from `libgazebo_ros_camera.so` to `libthrottling_camera.so` from [`clover_simulation`](../clover_simulation/README.md#throttling-camera-plugin-throttling_camera)).
|
||||
* `maintain_camera_rate` (*boolean*, default: *false*) - slow down the simultion to maintain camera publishing rate (internally changes the camera plugin from `libgazebo_ros_camera.so` to `libthrottling_camera.so` from [`clover_simulation`](../clover_simulation/README.md#throttling-camera-plugin-throttling_camera)).
|
||||
|
||||
For example, in order to spawn a drone without a GPS module, you may use the following command:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<launch>
|
||||
<arg name="model" default="$(find clover_description)/urdf/clover/clover4.xacro"/>
|
||||
<arg name="model" default="$(find clover_description)/urdf/drones/clover4.xacro"/>
|
||||
|
||||
<param name="robot_description" command="xacro $(arg model)"/>
|
||||
<param name="robot_description" command="$(find xacro)/xacro.py $(arg model)"/>
|
||||
|
||||
<node name="rviz" pkg="rviz" type="rviz" required="true"/>
|
||||
</launch>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<package format="2">
|
||||
<name>clover_description</name>
|
||||
<version>0.23.0</version>
|
||||
<version>0.21.1</version>
|
||||
<description>The clover_description package provides URDF models of the Clover series of quadcopters.</description>
|
||||
|
||||
<maintainer email="sfalexrog@gmail.com">Alexey Rogachevskiy</maintainer>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<robot name="clover" xmlns:xacro="http://ros.org/wiki/xacro">
|
||||
<robot name="clover_camera" xmlns:xacro="http://ros.org/wiki/xacro">
|
||||
|
||||
<xacro:arg name="main_camera" default="true"/>
|
||||
<xacro:arg name="rangefinder" default="true"/>
|
||||
@@ -8,10 +8,10 @@
|
||||
<xacro:arg name="maintain_camera_rate" default="false"/>
|
||||
<xacro:arg name="use_clover_physics" default="false"/>
|
||||
|
||||
<xacro:include filename="clover4_base.xacro" />
|
||||
<xacro:include filename="../sensors/rpi_cam.urdf.xacro"/>
|
||||
<xacro:include filename="../sensors/distance_sensor.urdf.xacro"/>
|
||||
<xacro:include filename="../leds/led_strip.xacro"/>
|
||||
<xacro:include filename="$(find clover_description)/urdf/clover/clover4_base.xacro" />
|
||||
<xacro:include filename="$(find clover_description)/urdf/sensors/rpi_cam.urdf.xacro"/>
|
||||
<xacro:include filename="$(find clover_description)/urdf/sensors/distance_sensor.urdf.xacro"/>
|
||||
<xacro:include filename="$(find clover_description)/urdf/leds/led_strip.xacro"/>
|
||||
|
||||
<!-- Create camera plugin -->
|
||||
<xacro:if value="$(arg main_camera)">
|
||||
@@ -36,17 +36,11 @@
|
||||
</xacro:if>
|
||||
|
||||
<xacro:if value="$(arg gps)">
|
||||
<gazebo>
|
||||
<include>
|
||||
<uri>model://gps</uri>
|
||||
<pose>0.1 0 0 0 0 0</pose>
|
||||
<name>gps0</name>
|
||||
</include>
|
||||
<joint name='gps0_joint' type='fixed'>
|
||||
<child>gps0::link</child>
|
||||
<parent>base_link</parent>
|
||||
</joint>
|
||||
</gazebo>
|
||||
<!-- Instantiate gps plugin. -->
|
||||
<xacro:gps_plugin_macro
|
||||
namespace="${namespace}"
|
||||
gps_noise="true"
|
||||
/>
|
||||
</xacro:if>
|
||||
|
||||
</robot>
|
||||
|
||||
@@ -1,15 +1,40 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<robot name="clover" xmlns:xacro="http://ros.org/wiki/xacro">
|
||||
<!-- Properties that can be assigned at build time as arguments.
|
||||
Is there a reason not to make all properties arguments?
|
||||
-->
|
||||
<xacro:arg name='name' default='iris' />
|
||||
<xacro:arg name='mavlink_addr' default='INADDR_ANY' />
|
||||
<xacro:arg name='mavlink_udp_port' default='14560' />
|
||||
<xacro:arg name='mavlink_tcp_port' default='4560' />
|
||||
<xacro:arg name='serial_enabled' default='false' />
|
||||
<xacro:arg name='serial_device' default='/dev/ttyACM0' />
|
||||
<xacro:arg name='baudrate' default='921600' />
|
||||
<xacro:arg name='qgc_addr' default='INADDR_ANY' />
|
||||
<xacro:arg name='qgc_udp_port' default='14550' />
|
||||
<xacro:arg name='sdk_addr' default='INADDR_ANY' />
|
||||
<xacro:arg name='sdk_udp_port' default='14540' />
|
||||
<xacro:arg name='hil_mode' default='false' />
|
||||
<xacro:arg name='hil_state_level' default='false' />
|
||||
<xacro:arg name='send_vision_estimation' default='false' />
|
||||
<xacro:arg name='send_odometry' default='false' />
|
||||
<xacro:arg name='enable_lockstep' default='true' />
|
||||
<xacro:arg name='use_tcp' default='true' />
|
||||
<xacro:arg name='vehicle_is_tailsitter' default='false' />
|
||||
<xacro:arg name='visual_material' default='DarkGrey' />
|
||||
<xacro:arg name='enable_mavlink_interface' default='true' />
|
||||
<xacro:arg name='enable_wind' default='false' />
|
||||
<!-- The following causes segfault with multiple vehicles if defaults to true!!! -->
|
||||
<xacro:arg name='enable_ground_truth' default='false' />
|
||||
<xacro:arg name='enable_logging' default='false' />
|
||||
<xacro:arg name='log_file' default='clover' />
|
||||
<xacro:arg name='log_file' default='iris' />
|
||||
|
||||
<!-- macros for gazebo plugins, sensors -->
|
||||
<xacro:include filename="../component_snippets.urdf.xacro" />
|
||||
<xacro:include filename="$(find clover_description)/urdf/component_snippets.urdf.xacro" />
|
||||
|
||||
<!-- Instantiate iris "mechanics" -->
|
||||
<xacro:include filename="clover4_physics.xacro" />
|
||||
<xacro:include filename="$(find clover_description)/urdf/clover/clover4_physics.xacro" />
|
||||
|
||||
<xacro:if value="$(arg enable_wind)">
|
||||
<xacro:wind_plugin_macro
|
||||
@@ -24,8 +49,126 @@
|
||||
/>
|
||||
</xacro:if>
|
||||
|
||||
<!-- Gazebo plugins -->
|
||||
<xacro:include filename="$(find clover_description)/urdf/clover/clover4_gazebo.xacro" />
|
||||
<!-- Instantiate magnetometer plugin. -->
|
||||
<xacro:magnetometer_plugin_macro
|
||||
namespace="${namespace}"
|
||||
pub_rate="20"
|
||||
noise_density="0.0004"
|
||||
random_walk="0.0000064"
|
||||
bias_correlation_time="600"
|
||||
mag_topic="/mag"
|
||||
>
|
||||
</xacro:magnetometer_plugin_macro>
|
||||
|
||||
<!-- Instantiate barometer plugin. -->
|
||||
<xacro:barometer_plugin_macro
|
||||
namespace="${namespace}"
|
||||
pub_rate="10"
|
||||
baro_topic="/baro"
|
||||
>
|
||||
</xacro:barometer_plugin_macro>
|
||||
|
||||
<xacro:if value="$(arg enable_mavlink_interface)">
|
||||
<!-- Instantiate mavlink telemetry interface. -->
|
||||
<xacro:mavlink_interface_macro
|
||||
namespace="${namespace}"
|
||||
imu_sub_topic="/imu"
|
||||
gps_sub_topic="/gps"
|
||||
mag_sub_topic="/mag"
|
||||
baro_sub_topic="/baro"
|
||||
mavlink_addr="$(arg mavlink_addr)"
|
||||
mavlink_udp_port="$(arg mavlink_udp_port)"
|
||||
mavlink_tcp_port="$(arg mavlink_tcp_port)"
|
||||
serial_enabled="$(arg serial_enabled)"
|
||||
serial_device="$(arg serial_device)"
|
||||
baudrate="$(arg baudrate)"
|
||||
qgc_addr="$(arg qgc_addr)"
|
||||
qgc_udp_port="$(arg qgc_udp_port)"
|
||||
sdk_addr="$(arg sdk_addr)"
|
||||
sdk_udp_port="$(arg sdk_udp_port)"
|
||||
hil_mode="$(arg hil_mode)"
|
||||
hil_state_level="$(arg hil_state_level)"
|
||||
vehicle_is_tailsitter="$(arg vehicle_is_tailsitter)"
|
||||
send_vision_estimation="$(arg send_vision_estimation)"
|
||||
send_odometry="$(arg send_odometry)"
|
||||
enable_lockstep="$(arg enable_lockstep)"
|
||||
use_tcp="$(arg use_tcp)"
|
||||
>
|
||||
</xacro:mavlink_interface_macro>
|
||||
</xacro:if>
|
||||
|
||||
<!-- Mount an ADIS16448 IMU. -->
|
||||
<xacro:imu_plugin_macro
|
||||
namespace="${namespace}"
|
||||
imu_suffix=""
|
||||
parent_link="base_link"
|
||||
imu_topic="/imu"
|
||||
mass_imu_sensor="0.015"
|
||||
gyroscope_noise_density="0.0003394"
|
||||
gyroscopoe_random_walk="0.000038785"
|
||||
gyroscope_bias_correlation_time="1000.0"
|
||||
gyroscope_turn_on_bias_sigma="0.0087"
|
||||
accelerometer_noise_density="0.004"
|
||||
accelerometer_random_walk="0.006"
|
||||
accelerometer_bias_correlation_time="300.0"
|
||||
accelerometer_turn_on_bias_sigma="0.1960"
|
||||
>
|
||||
<inertia ixx="0.00001" ixy="0.0" ixz="0.0" iyy="0.00001" iyz="0.0" izz="0.00001" />
|
||||
<origin xyz="0 0 0" rpy="0 0 0" />
|
||||
</xacro:imu_plugin_macro>
|
||||
|
||||
<xacro:if value="$(arg enable_ground_truth)">
|
||||
<!-- Mount an IMU providing ground truth. -->
|
||||
<xacro:imu_plugin_macro
|
||||
namespace="${namespace}"
|
||||
imu_suffix="gt"
|
||||
parent_link="base_link"
|
||||
imu_topic="ground_truth/imu"
|
||||
mass_imu_sensor="0.00001"
|
||||
gyroscope_noise_density="0.0"
|
||||
gyroscopoe_random_walk="0.0"
|
||||
gyroscope_bias_correlation_time="1000.0"
|
||||
gyroscope_turn_on_bias_sigma="0.0"
|
||||
accelerometer_noise_density="0.0"
|
||||
accelerometer_random_walk="0.0"
|
||||
accelerometer_bias_correlation_time="300.0"
|
||||
accelerometer_turn_on_bias_sigma="0.0"
|
||||
>
|
||||
<inertia ixx="0.00001" ixy="0.0" ixz="0.0" iyy="0.00001" iyz="0.0" izz="0.00001" />
|
||||
<origin xyz="0 0 0" rpy="0 0 0" />
|
||||
</xacro:imu_plugin_macro>
|
||||
|
||||
<!-- Mount a generic odometry sensor providing ground truth. -->
|
||||
<xacro:odometry_plugin_macro
|
||||
namespace="${namespace}/ground_truth"
|
||||
odometry_sensor_suffix="gt"
|
||||
parent_link="base_link"
|
||||
pose_topic="pose"
|
||||
pose_with_covariance_topic="pose_with_covariance"
|
||||
position_topic="position"
|
||||
transform_topic="transform"
|
||||
odometry_topic="odometry"
|
||||
parent_frame_id="world"
|
||||
mass_odometry_sensor="0.00001"
|
||||
measurement_divisor="1"
|
||||
measurement_delay="0"
|
||||
unknown_delay="0.0"
|
||||
noise_normal_position="0 0 0"
|
||||
noise_normal_quaternion="0 0 0"
|
||||
noise_normal_linear_velocity="0 0 0"
|
||||
noise_normal_angular_velocity="0 0 0"
|
||||
noise_uniform_position="0 0 0"
|
||||
noise_uniform_quaternion="0 0 0"
|
||||
noise_uniform_linear_velocity="0 0 0"
|
||||
noise_uniform_angular_velocity="0 0 0"
|
||||
enable_odometry_map="false"
|
||||
odometry_map=""
|
||||
image_scale=""
|
||||
>
|
||||
<inertia ixx="0.00001" ixy="0.0" ixz="0.0" iyy="0.00001" iyz="0.0" izz="0.00001" /> <!-- [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] -->
|
||||
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
|
||||
</xacro:odometry_plugin_macro>
|
||||
</xacro:if>
|
||||
|
||||
<xacro:if value="$(arg enable_logging)">
|
||||
<!-- Instantiate a logger -->
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<!-- Reference: https://github.com/PX4/PX4-SITL_gazebo/blob/7505aee97d2d3112fb2bd95198946345c6fa0b07/models/iris/iris.sdf.jinja#L430 -->
|
||||
|
||||
<robot xmlns:xacro="http://ros.org/wiki/xacro">
|
||||
|
||||
<!-- IMU link -->
|
||||
<link name="/imu_link">
|
||||
<inertial>
|
||||
<inertia ixx="0.00001" ixy="0.0" ixz="0.0" iyy="0.00001" iyz="0.0" izz="0.00001"/>
|
||||
<mass value="0.015"/>
|
||||
<!-- [kg] -->
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
</inertial>
|
||||
</link>
|
||||
|
||||
<!-- IMU joint -->
|
||||
<joint name="/imu_joint" type="fixed">
|
||||
<origin rpy="0 0 0" xyz="0 0 0"/>
|
||||
<parent link="base_link"/>
|
||||
<child link="/imu_link"/>
|
||||
<!-- <limit upper="0" lower="0" effort="0" velocity="0" /> -->
|
||||
</joint>
|
||||
|
||||
<gazebo reference="/imu_joint">
|
||||
<disableFixedJointLumping>true</disableFixedJointLumping>
|
||||
<preserveFixedJoint>true</preserveFixedJoint>
|
||||
</gazebo>
|
||||
|
||||
<gazebo>
|
||||
<plugin name='groundtruth_plugin' filename='libgazebo_groundtruth_plugin.so'>
|
||||
<robotNamespace/>
|
||||
</plugin>
|
||||
<plugin name='magnetometer_plugin' filename='libgazebo_magnetometer_plugin.so'>
|
||||
<robotNamespace/>
|
||||
<pubRate>100</pubRate>
|
||||
<noiseDensity>0.0004</noiseDensity>
|
||||
<randomWalk>6.4e-06</randomWalk>
|
||||
<biasCorrelationTime>600</biasCorrelationTime>
|
||||
<magTopic>/mag</magTopic>
|
||||
</plugin>
|
||||
<plugin name='barometer_plugin' filename='libgazebo_barometer_plugin.so'>
|
||||
<robotNamespace/>
|
||||
<pubRate>50</pubRate>
|
||||
<baroTopic>/baro</baroTopic>
|
||||
<baroDriftPaPerSec>0</baroDriftPaPerSec>
|
||||
</plugin>
|
||||
<plugin name='mavlink_interface' filename='libgazebo_mavlink_interface.so'>
|
||||
<robotNamespace/>
|
||||
<imuSubTopic>/imu</imuSubTopic>
|
||||
<magSubTopic>/mag</magSubTopic>
|
||||
<baroSubTopic>/baro</baroSubTopic>
|
||||
<mavlink_addr>INADDR_ANY</mavlink_addr>
|
||||
<mavlink_tcp_port>4560</mavlink_tcp_port>
|
||||
<mavlink_udp_port>14560</mavlink_udp_port>
|
||||
<serialEnabled>false</serialEnabled>
|
||||
<serialDevice>/dev/ttyACM0</serialDevice>
|
||||
<baudRate>921600</baudRate>
|
||||
<qgc_addr>INADDR_ANY</qgc_addr>
|
||||
<qgc_udp_port>14550</qgc_udp_port>
|
||||
<sdk_addr>INADDR_ANY</sdk_addr>
|
||||
<sdk_udp_port>14540</sdk_udp_port>
|
||||
<hil_mode>false</hil_mode>
|
||||
<hil_state_level>0</hil_state_level>
|
||||
<send_vision_estimation>0</send_vision_estimation>
|
||||
<send_odometry>1</send_odometry>
|
||||
<enable_lockstep>1</enable_lockstep>
|
||||
<use_tcp>1</use_tcp>
|
||||
<motorSpeedCommandPubTopic>/gazebo/command/motor_speed</motorSpeedCommandPubTopic>
|
||||
<control_channels>
|
||||
<channel name='rotor1'>
|
||||
<input_index>0</input_index>
|
||||
<input_offset>0</input_offset>
|
||||
<input_scaling>1000</input_scaling>
|
||||
<zero_position_disarmed>0</zero_position_disarmed>
|
||||
<zero_position_armed>100</zero_position_armed>
|
||||
<joint_control_type>velocity</joint_control_type>
|
||||
</channel>
|
||||
<channel name='rotor2'>
|
||||
<input_index>1</input_index>
|
||||
<input_offset>0</input_offset>
|
||||
<input_scaling>1000</input_scaling>
|
||||
<zero_position_disarmed>0</zero_position_disarmed>
|
||||
<zero_position_armed>100</zero_position_armed>
|
||||
<joint_control_type>velocity</joint_control_type>
|
||||
</channel>
|
||||
<channel name='rotor3'>
|
||||
<input_index>2</input_index>
|
||||
<input_offset>0</input_offset>
|
||||
<input_scaling>1000</input_scaling>
|
||||
<zero_position_disarmed>0</zero_position_disarmed>
|
||||
<zero_position_armed>100</zero_position_armed>
|
||||
<joint_control_type>velocity</joint_control_type>
|
||||
</channel>
|
||||
<channel name='rotor4'>
|
||||
<input_index>3</input_index>
|
||||
<input_offset>0</input_offset>
|
||||
<input_scaling>1000</input_scaling>
|
||||
<zero_position_disarmed>0</zero_position_disarmed>
|
||||
<zero_position_armed>100</zero_position_armed>
|
||||
<joint_control_type>velocity</joint_control_type>
|
||||
</channel>
|
||||
<channel name='rotor5'>
|
||||
<input_index>4</input_index>
|
||||
<input_offset>1</input_offset>
|
||||
<input_scaling>324.6</input_scaling>
|
||||
<zero_position_disarmed>0</zero_position_disarmed>
|
||||
<zero_position_armed>0</zero_position_armed>
|
||||
<joint_control_type>velocity</joint_control_type>
|
||||
<joint_control_pid>
|
||||
<p>0.1</p>
|
||||
<i>0</i>
|
||||
<d>0</d>
|
||||
<iMax>0.0</iMax>
|
||||
<iMin>0.0</iMin>
|
||||
<cmdMax>2</cmdMax>
|
||||
<cmdMin>-2</cmdMin>
|
||||
</joint_control_pid>
|
||||
<joint_name>zephyr_delta_wing::propeller_joint</joint_name>
|
||||
</channel>
|
||||
<channel name='rotor6'>
|
||||
<input_index>5</input_index>
|
||||
<input_offset>0</input_offset>
|
||||
<input_scaling>0.524</input_scaling>
|
||||
<zero_position_disarmed>0</zero_position_disarmed>
|
||||
<zero_position_armed>0</zero_position_armed>
|
||||
<joint_control_type>position</joint_control_type>
|
||||
<joint_name>zephyr_delta_wing::flap_left_joint</joint_name>
|
||||
<joint_control_pid>
|
||||
<p>10.0</p>
|
||||
<i>0</i>
|
||||
<d>0</d>
|
||||
<iMax>0</iMax>
|
||||
<iMin>0</iMin>
|
||||
<cmdMax>20</cmdMax>
|
||||
<cmdMin>-20</cmdMin>
|
||||
</joint_control_pid>
|
||||
</channel>
|
||||
<channel name='rotor7'>
|
||||
<input_index>6</input_index>
|
||||
<input_offset>0</input_offset>
|
||||
<input_scaling>0.524</input_scaling>
|
||||
<zero_position_disarmed>0</zero_position_disarmed>
|
||||
<zero_position_armed>0</zero_position_armed>
|
||||
<joint_control_type>position</joint_control_type>
|
||||
<joint_name>zephyr_delta_wing::flap_right_joint</joint_name>
|
||||
<joint_control_pid>
|
||||
<p>10.0</p>
|
||||
<i>0</i>
|
||||
<d>0</d>
|
||||
<iMax>0</iMax>
|
||||
<iMin>0</iMin>
|
||||
<cmdMax>20</cmdMax>
|
||||
<cmdMin>-20</cmdMin>
|
||||
</joint_control_pid>
|
||||
</channel>
|
||||
<channel name='rotor8'>
|
||||
<input_index>7</input_index>
|
||||
<input_offset>0</input_offset>
|
||||
<input_scaling>0.524</input_scaling>
|
||||
<zero_position_disarmed>0</zero_position_disarmed>
|
||||
<zero_position_armed>0</zero_position_armed>
|
||||
<joint_control_type>position</joint_control_type>
|
||||
</channel>
|
||||
</control_channels>
|
||||
</plugin>
|
||||
<static>0</static>
|
||||
<plugin name='rotors_gazebo_imu_plugin' filename='libgazebo_imu_plugin.so'>
|
||||
<robotNamespace/>
|
||||
<linkName>/imu_link</linkName>
|
||||
<imuTopic>/imu</imuTopic>
|
||||
<gyroscopeNoiseDensity>0.00018665</gyroscopeNoiseDensity>
|
||||
<gyroscopeRandomWalk>3.8785e-05</gyroscopeRandomWalk>
|
||||
<gyroscopeBiasCorrelationTime>1000.0</gyroscopeBiasCorrelationTime>
|
||||
<gyroscopeTurnOnBiasSigma>0.0087</gyroscopeTurnOnBiasSigma>
|
||||
<accelerometerNoiseDensity>0.00186</accelerometerNoiseDensity>
|
||||
<accelerometerRandomWalk>0.006</accelerometerRandomWalk>
|
||||
<accelerometerBiasCorrelationTime>300.0</accelerometerBiasCorrelationTime>
|
||||
<accelerometerTurnOnBiasSigma>0.196</accelerometerTurnOnBiasSigma>
|
||||
</plugin>
|
||||
</gazebo>
|
||||
|
||||
</robot>
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
-->
|
||||
|
||||
<robot name="clover" xmlns:xacro="http://ros.org/wiki/xacro">
|
||||
<robot name="iris" xmlns:xacro="http://ros.org/wiki/xacro">
|
||||
<!-- Properties -->
|
||||
<xacro:property name="namespace" value="" />
|
||||
<xacro:property name="rotor_velocity_slowdown_sim" value="10" />
|
||||
@@ -35,7 +35,7 @@
|
||||
<xacro:property name="sqrt2" value="1.4142135623730951" />
|
||||
<xacro:property name="rotor_drag_coefficient" value="1.75e-04" />
|
||||
<xacro:property name="rolling_moment_coefficient" value="0.000001" />
|
||||
<xacro:property name="color" value="DarkGrey" />
|
||||
<xacro:property name="color" value="$(arg visual_material)" />
|
||||
|
||||
<!-- Property Blocks -->
|
||||
<!-- Clover body inertia -->
|
||||
@@ -84,7 +84,7 @@
|
||||
</xacro:unless>
|
||||
|
||||
<!-- Included URDF Files -->
|
||||
<xacro:include filename="clover4_macros.xacro" />
|
||||
<xacro:include filename="$(find clover_description)/urdf/clover/clover4_macros.xacro" />
|
||||
|
||||
<!-- Instantiate multirotor_base_macro once -->
|
||||
<xacro:clover4_base_macro
|
||||
|
||||
@@ -64,12 +64,6 @@
|
||||
<!-- <gazebo>
|
||||
<static>true</static>
|
||||
</gazebo> -->
|
||||
<gazebo>
|
||||
<plugin name="${name}_ros_controller" filename="libsim_leds_controller.so">
|
||||
<robotNamespace></robotNamespace>
|
||||
<ledCount>${led_count}</ledCount>
|
||||
</plugin>
|
||||
</gazebo>
|
||||
</xacro:macro>
|
||||
|
||||
</robot>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/xacro">
|
||||
<xacro:include filename="../camera_sensor.urdf.xacro"/>
|
||||
<xacro:include filename="$(find clover_description)/urdf/camera_sensor.urdf.xacro"/>
|
||||
|
||||
<xacro:macro name="distance_sensor" params="name:=lidar_vl53l1x parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 range_min:=0.01 range_max:=4.0 resolution:=0.01 rate:=10 fov:=0.471239">
|
||||
<xacro:macro name="distance_sensor" params="name:=lidar_vl53l1x parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 range_min:=0.01 range_max:=4.0 resolution:=0.01 rate:=10">
|
||||
<joint name="${name}_joint" type="fixed">
|
||||
<origin xyz="${x} ${y} ${z}"
|
||||
rpy="${roll} ${pitch} ${yaw}"/>
|
||||
@@ -58,7 +58,7 @@
|
||||
<topicName>/rangefinder/range</topicName>
|
||||
<frameName>rangefinder</frameName>
|
||||
<radiation>infrared</radiation>
|
||||
<fov>${fov}</fov>
|
||||
<fov>0.01</fov>
|
||||
<gaussianNoise>0.001</gaussianNoise>
|
||||
<updateRate>${rate}</updateRate>
|
||||
<min_distance>${range_min}</min_distance>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<robot name="pinhole_camera" xmlns:xacro="http://ros.org/wiki/xacro">
|
||||
<xacro:include filename="../camera_sensor.urdf.xacro"/>
|
||||
<xacro:include filename="$(find clover_description)/urdf/camera_sensor.urdf.xacro"/>
|
||||
<xacro:camera_sensor name="main_camera" width="640" height="480" rate="30" horizontal_fov="${120.0*pi/180.0}"/>
|
||||
</robot>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user