Compare commits

..

1 Commits

Author SHA1 Message Date
Oleg Kalachev
a73efce116 Start working on web pages header 2020-05-07 07:08:20 +03:00
1236 changed files with 7705 additions and 165408 deletions

3
.gitattributes vendored
View File

@@ -3,7 +3,4 @@ roslib.js linguist-vendored
eventemitter2.js linguist-vendored eventemitter2.js linguist-vendored
ros3d.js linguist-vendored ros3d.js linguist-vendored
three.min.js linguist-vendored three.min.js linguist-vendored
json-to-pretty-yaml.js linguist-vendored
aruco_pose/vendor/* linguist-vendored aruco_pose/vendor/* linguist-vendored
blockly/* linguist-vendored
highlight/* linguist-vendored

View File

@@ -1,37 +0,0 @@
name: RPi image
on:
push:
branches: [ '*' ]
pull_request:
branches: [ master ]
release:
types: [ created ]
workflow_dispatch:
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 }}
- name: Upload image to artifacts
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/upload-artifact@v3
with:
name: image
path: images/clover_*.zip
retention-days: 1

View File

@@ -1,52 +0,0 @@
name: Build
on:
push:
branches: [ '*' ]
pull_request:
branches: [ master ]
workflow_dispatch:
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

View File

@@ -1,89 +0,0 @@
name: Documentation
on:
push:
branches: [ '*' ]
pull_request:
branches: [ '*' ]
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
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' }}
concurrency:
group: "pages"
cancel-in-progress: true
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: docs
steps:
- name: Deploy to GitHub Pages
env:
FREEZE_DOCS: ${{ secrets.FREEZE_DOCS }}
if: ${{ !env.FREEZE_DOCS }}
id: deployment
uses: actions/deploy-pages@v1

View File

@@ -1,19 +0,0 @@
name: Editorconfig
on:
push:
branches: [ '*' ]
pull_request:
branches: [ master ]
workflow_dispatch:
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"

3
.gitignore vendored
View File

@@ -4,6 +4,3 @@
node_modules/ node_modules/
_book/ _book/
package-lock.json package-lock.json
clover_blocks/programs/*.*
!clover_blocks/programs/examples/*
/.vscode/

View File

@@ -21,9 +21,7 @@
"ROS", "ROS",
"ROS Kinetic", "ROS Kinetic",
"ROS Melodic", "ROS Melodic",
"ROS Noetic",
"OpenCV", "OpenCV",
"OpenVPN",
"Gazebo", "Gazebo",
"GitHub", "GitHub",
"FPV", "FPV",
@@ -108,10 +106,7 @@
"UDP", "UDP",
"QR", "QR",
"Li-ion", "Li-ion",
"Nvidia", "Nvidia"
"VirtualBox",
"VMware",
"DuoCam"
], ],
"code_blocks": false "code_blocks": false
}, },

121
.travis.yml Normal file
View File

@@ -0,0 +1,121 @@
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"
- if [[ -z ${TRAVIS_TAG} ]]; then IMAGE_VERSION="${TRAVIS_COMMIT}}"; else IMAGE_VERSION="${TRAVIS_TAG}"; fi
- IMAGE_NAME="$(basename -s '.git' ${TARGET_REPO})_${IMAGE_VERSION}.img"
git:
depth: 50
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
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"
- sudo chmod -R 777 *
- cd images && zip ${IMAGE_NAME}.zip ${IMAGE_NAME}
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: "Documentation"
language: node_js
node_js:
- "10"
before_script:
- npm install gitbook-cli -g
- npm install markdownlint-cli -g
- gitbook -V
- markdownlint -V
script:
- markdownlint docs
- ./check_assets_size.py
- ./check_unused_assets.py
- gitbook install
- gitbook build
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: Annotate
name: Auto-generate changelog
language: python
python: 3.6
install:
- pip install GitPython PyGithub
script:
- PYTHONUNBUFFERED=1 python ./gen_changelog.py
- 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|Assets.xcassets|test_parser_pass.txt|test_node_failure.txt|aruco_pose/vendor|\.stl|\.dxf"
stages:
- Build
- Annotate
# 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/

View File

@@ -1,14 +1,12 @@
# clover🍀: create autonomous drones easily # COEX Clover Drone Kit
<img src="docs/assets/clover42-main-margin.png" align="right" width="400px" alt="COEX Clover Drone"> <img src="docs/assets/clever4-front-white.png" align="right" width="400px" alt="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. Clover is an educational programmable drone kit consisting of an unassembled quadcopter, open source software and documentation. The kit includes Pixracer-compatible autopilot running PX4 firmware, Raspberry Pi 4 as companion computer, a camera for computer vision navigation as well as additional sensors and peripheral devices.
COEX Clover Drone is an educational programmable drone kit, suited perfectly for running clover software. The kit is shipped unassembled and includes Pixracer-compatible autopilot running PX4 firmware, Raspberry Pi 4 as a companion computer, a camera for computer vision navigation as well as additional sensors and peripheral devices. Batteries included. The main documentation is available [on Gitbook](https://clover.coex.tech/).
The main documentation is available at [https://clover.coex.tech](https://clover.coex.tech/). Official website: [coex.tech/clover](https://coex.tech/clover). Official website: <a href="https://coex.tech/clover">coex.tech/clover</a>.
[__Support us on Kickstarter!__](https://www.kickstarter.com/projects/copterexpress/cloverdrone)
## Video compilation ## Video compilation
@@ -20,13 +18,12 @@ 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). 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).
![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/CopterExpress/clover/build-image.yaml?branch=master) [![Build Status](https://travis-ci.org/CopterExpress/clover.svg?branch=master)](https://travis-ci.org/CopterExpress/clover)
![GitHub all releases](https://img.shields.io/github/downloads/CopterExpress/clover/total)
Image features: Image features:
* Raspbian Buster * Raspbian Buster
* [ROS Noetic](http://wiki.ros.org/noetic) * [ROS Melodic](http://wiki.ros.org/melodic)
* Configured networking * Configured networking
* OpenCV * OpenCV
* [`mavros`](http://wiki.ros.org/mavros) * [`mavros`](http://wiki.ros.org/mavros)
@@ -38,10 +35,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). For manual package installation and running see [`clover` package documentation](clover/README.md).
## Support
[![Telegram Support Chat](https://img.shields.io/endpoint?label=Support%20Chat&url=https%3A%2F%2Ftelegram-badge-4mbpu8e0fit4.runkit.sh%2F%3Furl%3Dhttps%3A%2F%2Ft.me%2FCOEXHelpDesk)](https://t.me/COEXHelpdesk)
## License ## 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. 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.

View File

View File

@@ -1,4 +1,5 @@
# iOS-приложение для управления Клевером iOS-приложение для управления Клевером
--------------------------------------
Для установки зависимостей необходим [CocoaPods](https://cocoapods.org): Для установки зависимостей необходим [CocoaPods](https://cocoapods.org):

View File

@@ -1,8 +0,0 @@
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Changelog for package aruco_pose
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
0.21.1 (2020-11-17)
-------------------
* First release of aruco_pose package to ROS
* Contributors: Alamoris, Alexey Rogachevskiy, Arthur Golubtsov, Ilya Petrov, Oleg Kalachev

View File

@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.0)
project(aruco_pose) project(aruco_pose)
add_definitions(-std=c++11 -Wall -g)
## Compile as C++11, supported in ROS Kinetic and newer ## Compile as C++11, supported in ROS Kinetic and newer
add_compile_options(-std=c++11) add_compile_options(-std=c++11)
@@ -22,21 +24,13 @@ find_package(catkin REQUIRED COMPONENTS
dynamic_reconfigure dynamic_reconfigure
) )
# Workaround for OpenCV 3/4 support find_package(OpenCV 3 REQUIRED COMPONENTS core imgproc calib3d)
set(_opencv_version 4) if ("${OpenCV_VERSION_MINOR}" LESS "3")
find_package(OpenCV ${_opencv_version} QUIET COMPONENTS core imgproc calib3d)
if (NOT OpenCV_FOUND)
message(STATUS "Did not find OpenCV 4, searching for OpenCV 3")
set(_opencv_version 3)
endif()
find_package(OpenCV ${_opencv_version} REQUIRED COMPONENTS core imgproc calib3d)
if ("${OpenCV_VERSION_MINOR}" LESS "9")
message(STATUS "OpenCV version too low, using vendored ArUco package") message(STATUS "OpenCV version too low, using vendored ArUco package")
include(vendor/VendorOpenCV.cmake) include(vendor/VendorOpenCV.cmake)
else() else()
message(STATUS "Using system OpenCV ArUco package") message(STATUS "Using system OpenCV ArUco package")
find_package(OpenCV ${_opencv_version} REQUIRED COMPONENTS aruco) find_package(OpenCV 3 REQUIRED COMPONENTS aruco)
endif() endif()
message(STATUS "OpenCV include dirs: ${OpenCV_INCLUDE_DIRS}") message(STATUS "OpenCV include dirs: ${OpenCV_INCLUDE_DIRS}")
message(STATUS "OpenCV libraries: ${OpenCV_LIBRARIES}") message(STATUS "OpenCV libraries: ${OpenCV_LIBRARIES}")
@@ -83,10 +77,11 @@ add_message_files(
) )
## Generate services in the 'srv' folder ## Generate services in the 'srv' folder
add_service_files( # add_service_files(
FILES # FILES
SetMarkers.srv # Service1.srv
) # Service2.srv
# )
## Generate actions in the 'action' folder ## Generate actions in the 'action' folder
# add_action_files( # add_action_files(
@@ -118,8 +113,7 @@ generate_messages(
## Generate dynamic reconfigure parameters in the 'cfg' folder ## Generate dynamic reconfigure parameters in the 'cfg' folder
generate_dynamic_reconfigure_options( generate_dynamic_reconfigure_options(
cfg/Detector.cfg cfg/DetectorParams.cfg
cfg/Map.cfg
) )
################################### ###################################
@@ -180,13 +174,6 @@ target_link_libraries(aruco_pose
${OpenCV_LIBRARIES} ${OpenCV_LIBRARIES}
) )
# Prevent aruco_pose from having undefined symbols
set_property(TARGET aruco_pose
APPEND
PROPERTY LINK_FLAGS
-Wl,--no-undefined
)
############# #############
## Install ## ## Install ##
############# #############
@@ -202,11 +189,11 @@ set_property(TARGET aruco_pose
# ) # )
## Mark executables and/or libraries for installation ## Mark executables and/or libraries for installation
install(TARGETS ${PROJECT_NAME} # install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
) # )
## Mark cpp header files for installation ## Mark cpp header files for installation
# install(DIRECTORY include/${PROJECT_NAME}/ # install(DIRECTORY include/${PROJECT_NAME}/
@@ -222,14 +209,6 @@ install(TARGETS ${PROJECT_NAME}
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# ) # )
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 ## ## Testing ##
############# #############
@@ -250,6 +229,4 @@ if (CATKIN_ENABLE_TESTING)
add_rostest(test/test_parser_empty_map.test) add_rostest(test/test_parser_empty_map.test)
add_rostest(test/test_node_failure.test) add_rostest(test/test_node_failure.test)
add_rostest(test/largemap.test) add_rostest(test/largemap.test)
add_rostest(test/crash_opencv.test)
add_rostest(test/duplicate.test)
endif() endif()

View File

@@ -43,8 +43,7 @@ It's recommended to run it within the same nodelet manager with the camera nodel
* `~frame_id_prefix` (*string*) prefix for TF transforms names, marker's ID is appended (default: `aruco_`) * `~frame_id_prefix` (*string*) prefix for TF transforms names, marker's ID is appended (default: `aruco_`)
* `~length` (*double*) markers' sides length * `~length` (*double*) markers' sides length
* `~length_override` (*map*) lengths of markers with specified ids * `~length_override` (*map*) lengths of markers with specified ids
* `~known_vertical` (*string*) known vertical (Z axis) of all the markers as a frame * `~known_tilt` (*string*) known tilt (pitch and roll) of all the markers as a frame
* `~flip_vertical` flip vertical vector
### Topics ### Topics
@@ -52,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 * `image_raw` (*sensor_msgs/Image*) camera image
* `camera_info` (*sensor_msgs/CameraInfo*) camera calibration info * `camera_info` (*sensor_msgs/CameraInfo*) camera calibration info
* `map_markers` (*aruco_pose/MarkerArray*) list of markers to disable TF transform publishing
#### Published #### Published
@@ -72,12 +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 * `~map` path to text file with markers list
* `~frame_id` published frame id (default: `aruco_map`) * `~frame_id` published frame id (default: `aruco_map`)
* `~known_vertical` known vertical (Z axis) of markers map as a frame * `~known_tilt` debug image width
* `~flip_vertical` flip vertical vector
* `~image_width` debug image width (default: 2000) * `~image_width` debug image width (default: 2000)
* `~image_height` debug image height (default: 2000) * `~image_height` debug image height (default: 2000)
* `~image_margin`  debug image margin (default: 200) * `~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 * `~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: Map file has one marker per line with the following line format:
@@ -101,7 +97,6 @@ See examples in [`map`](map/) directory.
#### Published #### Published
* `~pose` (*geometry_msgs/PoseWithCovarianceStamped*) estimated map pose * `~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 * `~image` (*sensor_msgs/Image*) planarized map image
* `~visualization` (*visualization_msgs/MarkerArray*) markers map visualization for rviz * `~visualization` (*visualization_msgs/MarkerArray*) markers map visualization for rviz
* `~debug` (*sensor_msgs/Image*) debug image with detected markers and map axis * `~debug` (*sensor_msgs/Image*) debug image with detected markers and map axis

View File

@@ -4,17 +4,10 @@ PACKAGE = "aruco_pose"
from dynamic_reconfigure.parameter_generator_catkin import * from dynamic_reconfigure.parameter_generator_catkin import *
import cv2.aruco import cv2.aruco
try: p = cv2.aruco.DetectorParameters_create()
p = cv2.aruco.DetectorParameters_create()
except AttributeError:
p = cv2.aruco.DetectorParameters()
gen = ParameterGenerator() 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, gen.add("adaptiveThreshConstant", double_t, 0,
"Constant for adaptive thresholding before finding contours", "Constant for adaptive thresholding before finding contours",
p.adaptiveThreshConstant, 0, 100) p.adaptiveThreshConstant, 0, 100)

View File

@@ -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"))

View File

@@ -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 0 0.33 0.0 9.0 0 0 0 0
1 0.33 1.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 2 0.33 2.0 9.0 0 0 0 0

View File

@@ -1,4 +1,3 @@
# id length x y z rot_z rot_y rot_x
107 0.33 0 0 0 0 0 0 107 0.33 0 0 0 0 0 0
106 0.33 0.77 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 105 0.33 0 0.77 0 0 0 0

View File

@@ -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 14 0.365 0.000 0.0 0 0 0 0
15 0.365 1.335 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 30 0.365 2.865 0.0 0 0 0 0

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<package format="3"> <package format="2">
<name>aruco_pose</name> <name>aruco_pose</name>
<version>0.24.0</version> <version>0.0.1</version>
<description>Positioning with ArUco markers</description> <description>Positioning with ArUco markers</description>
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer> <maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
@@ -28,8 +28,6 @@
<depend>sensor_msgs</depend> <depend>sensor_msgs</depend>
<depend>rostest</depend> <depend>rostest</depend>
<depend>dynamic_reconfigure</depend> <depend>dynamic_reconfigure</depend>
<depend condition="$ROS_PYTHON_VERSION == 2">python-docopt</depend>
<depend condition="$ROS_PYTHON_VERSION == 3">python3-docopt</depend>
<test_depend>image_publisher</test_depend> <test_depend>image_publisher</test_depend>
<test_depend>ros_pytest</test_depend> <test_depend>ros_pytest</test_depend>

View File

@@ -48,9 +48,7 @@
#include <aruco_pose/Marker.h> #include <aruco_pose/Marker.h>
#include <aruco_pose/MarkerArray.h> #include <aruco_pose/MarkerArray.h>
#include <aruco_pose/DetectorConfig.h> #include <aruco_pose/DetectorConfig.h>
#include <aruco_pose/SetMarkers.h>
#include "draw.h"
#include "utils.h" #include "utils.h"
#include <memory> #include <memory>
#include <functional> #include <functional>
@@ -60,24 +58,21 @@ using cv::Mat;
class ArucoDetect : public nodelet::Nodelet { class ArucoDetect : public nodelet::Nodelet {
private: private:
std::unique_ptr<tf2_ros::TransformBroadcaster> br_; ros::NodeHandle nh_, nh_priv_;
std::unique_ptr<tf2_ros::Buffer> tf_buffer_; tf2_ros::TransformBroadcaster br_;
std::unique_ptr<tf2_ros::TransformListener> tf_listener_; tf2_ros::Buffer tf_buffer_;
tf2_ros::TransformListener tf_listener_{tf_buffer_};
std::shared_ptr<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>> dyn_srv_; std::shared_ptr<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>> dyn_srv_;
bool enabled_ = true;
cv::Ptr<cv::aruco::Dictionary> dictionary_; cv::Ptr<cv::aruco::Dictionary> dictionary_;
cv::Ptr<cv::aruco::DetectorParameters> parameters_; cv::Ptr<cv::aruco::DetectorParameters> parameters_;
image_transport::Publisher debug_pub_; image_transport::Publisher debug_pub_;
image_transport::CameraSubscriber img_sub_; image_transport::CameraSubscriber img_sub_;
ros::Publisher markers_pub_, vis_markers_pub_; ros::Publisher markers_pub_, vis_markers_pub_;
ros::Subscriber map_markers_sub_; ros::Subscriber map_markers_sub_;
ros::ServiceServer set_markers_srv_; bool estimate_poses_, send_tf_, auto_flip_;
bool estimate_poses_, send_tf_, flip_vertical_, auto_flip_, use_map_markers_;
bool waiting_for_map_;
double length_; double length_;
ros::Duration transform_timeout_;
std::unordered_map<int, double> length_override_; std::unordered_map<int, double> length_override_;
std::string frame_id_prefix_, known_vertical_; std::string frame_id_prefix_, known_tilt_;
Mat camera_matrix_, dist_coeffs_; Mat camera_matrix_, dist_coeffs_;
aruco_pose::MarkerArray array_; aruco_pose::MarkerArray array_;
std::unordered_set<int> map_markers_ids_; std::unordered_set<int> map_markers_ids_;
@@ -86,50 +81,45 @@ private:
public: public:
virtual void onInit() virtual void onInit()
{ {
ros::NodeHandle& nh_ = getNodeHandle(); nh_ = getNodeHandle();
ros::NodeHandle& nh_priv_ = getPrivateNodeHandle(); nh_priv_ = getPrivateNodeHandle();
br_.reset(new tf2_ros::TransformBroadcaster());
tf_buffer_.reset(new tf2_ros::Buffer());
tf_listener_.reset(new tf2_ros::TransformListener(*tf_buffer_, nh_));
int dictionary; int dictionary;
dictionary = nh_priv_.param("dictionary", 2); nh_priv_.param("dictionary", dictionary, 2);
estimate_poses_ = nh_priv_.param("estimate_poses", true); nh_priv_.param("estimate_poses", estimate_poses_, true);
send_tf_ = nh_priv_.param("send_tf", true); nh_priv_.param("send_tf", 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_)) { if (estimate_poses_ && !nh_priv_.getParam("length", length_)) {
NODELET_FATAL("can't estimate marker's poses as ~length parameter is not defined"); NODELET_FATAL("can't estimate marker's poses as ~length parameter is not defined");
ros::shutdown(); ros::shutdown();
} }
readLengthOverride(nh_priv_); readLengthOverride();
transform_timeout_ = ros::Duration(nh_priv_.param("transform_timeout", 0.02));
known_vertical_ = nh_priv_.param("known_vertical", nh_priv_.param("known_tilt", std::string(""))); // known_tilt is an old name nh_priv_.param<std::string>("known_tilt", known_tilt_, "");
flip_vertical_ = nh_priv_.param<bool>("flip_vertical", false); nh_priv_.param("auto_flip", auto_flip_, false);
auto_flip_ = nh_priv_.param("auto_flip", false);
frame_id_prefix_ = nh_priv_.param<std::string>("frame_id_prefix", "aruco_"); nh_priv_.param<std::string>("frame_id_prefix", frame_id_prefix_, "aruco_");
camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F); camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
dist_coeffs_ = cv::Mat::zeros(8, 1, CV_64F);
dictionary_ = cv::aruco::getPredefinedDictionary(static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(dictionary)); dictionary_ = cv::aruco::getPredefinedDictionary(static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(dictionary));
parameters_ = cv::aruco::DetectorParameters::create(); parameters_ = cv::aruco::DetectorParameters::create();
parameters_->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
image_transport::ImageTransport it(nh_); image_transport::ImageTransport it(nh_);
image_transport::ImageTransport it_priv(nh_priv_); image_transport::ImageTransport it_priv(nh_priv_);
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>>(nh_priv_); map_markers_sub_ = nh_.subscribe("map_markers", 1, &ArucoDetect::mapMarkersCallback, this);
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); debug_pub_ = it_priv.advertise("debug", 1);
markers_pub_ = nh_priv_.advertise<aruco_pose::MarkerArray>("markers", 1); markers_pub_ = nh_priv_.advertise<aruco_pose::MarkerArray>("markers", 1);
vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1); vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1);
img_sub_ = it.subscribeCamera("image_raw", 1, &ArucoDetect::imageCallback, this); img_sub_ = it.subscribeCamera("image_raw", 1, &ArucoDetect::imageCallback, this);
map_markers_sub_ = nh_.subscribe("map_markers", 1, &ArucoDetect::mapMarkersCallback, this);
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>>(nh_priv_);
dynamic_reconfigure::Server<aruco_pose::DetectorConfig>::CallbackType cb;
cb = std::bind(&ArucoDetect::paramCallback, this, std::placeholders::_1, std::placeholders::_2);
dyn_srv_->setCallback(cb);
NODELET_INFO("ready"); NODELET_INFO("ready");
} }
@@ -137,16 +127,13 @@ public:
private: private:
void imageCallback(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr &cinfo) void imageCallback(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr &cinfo)
{ {
if (!enabled_) return; Mat image = cv_bridge::toCvShare(msg, "bgr8")->image;
if (waiting_for_map_) return;
Mat image = cv_bridge::toCvShare(msg)->image;
vector<int> ids; vector<int> ids;
vector<vector<cv::Point2f>> corners, rejected; vector<vector<cv::Point2f>> corners, rejected;
vector<cv::Vec3d> rvecs, tvecs; vector<cv::Vec3d> rvecs, tvecs;
vector<cv::Point3f> obj_points; vector<cv::Point3f> obj_points;
geometry_msgs::TransformStamped vertical; geometry_msgs::TransformStamped snap_to;
// Detect markers // Detect markers
cv::aruco::detectMarkers(image, dictionary_, corners, ids, parameters_, rejected); cv::aruco::detectMarkers(image, dictionary_, corners, ids, parameters_, rejected);
@@ -181,20 +168,18 @@ private:
} }
} }
if (!known_vertical_.empty()) { if (!known_tilt_.empty()) {
try { try {
vertical = tf_buffer_->lookupTransform(msg->header.frame_id, known_vertical_, 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) { } catch (const tf2::TransformException& e) {
NODELET_WARN_THROTTLE(5, "can't retrieve known vertical: %s", e.what()); NODELET_WARN_THROTTLE(5, "can't snap: %s", e.what());
} }
} }
} }
array_.markers.reserve(ids.size()); array_.markers.reserve(ids.size());
aruco_pose::Marker marker; aruco_pose::Marker marker;
vector<geometry_msgs::TransformStamped> transforms;
transforms.reserve(ids.size());
geometry_msgs::TransformStamped transform; geometry_msgs::TransformStamped transform;
transform.header.stamp = msg->header.stamp; transform.header.stamp = msg->header.stamp;
transform.header.frame_id = msg->header.frame_id; transform.header.frame_id = msg->header.frame_id;
@@ -207,38 +192,25 @@ private:
if (estimate_poses_) { if (estimate_poses_) {
fillPose(marker.pose, rvecs[i], tvecs[i]); fillPose(marker.pose, rvecs[i], tvecs[i]);
// apply known vertical (if enabled and vertical frame available) // snap orientation (if enabled and snap frame available)
if (!known_vertical_.empty() && !vertical.header.frame_id.empty()) { if (!known_tilt_.empty() && !snap_to.header.frame_id.empty()) {
applyVertical(marker.pose.orientation, vertical.transform.rotation, false, auto_flip_); snapOrientation(marker.pose.orientation, snap_to.transform.rotation, auto_flip_);
} }
// TODO: check IDs are unique
if (send_tf_) { if (send_tf_) {
transform.child_frame_id = getChildFrameId(ids[i]); transform.child_frame_id = getChildFrameId(ids[i]);
// check if such static transform is in the map // check if such static transform is in the map
if (map_markers_ids_.find(ids[i]) == map_markers_ids_.end()) { if (map_markers_ids_.find(ids[i]) == map_markers_ids_.end()) {
// check if a markers with that id is already added transform.transform.rotation = marker.pose.orientation;
bool send = true; fillTranslation(transform.transform.translation, tvecs[i]);
for (auto &t : transforms) { br_.sendTransform(transform);
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);
}
} }
} }
} }
array_.markers.push_back(marker); array_.markers.push_back(marker);
} }
if (send_tf_) {
br_->sendTransform(transforms);
}
} }
markers_pub_.publish(array_); markers_pub_.publish(array_);
@@ -265,7 +237,8 @@ private:
cv::aruco::drawDetectedMarkers(debug, corners, ids); // draw markers cv::aruco::drawDetectedMarkers(debug, corners, ids); // draw markers
if (estimate_poses_) if (estimate_poses_)
for (unsigned int i = 0; i < ids.size(); i++) for (unsigned int i = 0; i < ids.size(); i++)
_drawAxis(debug, camera_matrix_, dist_coeffs_, rvecs[i], tvecs[i], getMarkerLength(ids[i])); cv::aruco::drawAxis(debug, camera_matrix_, dist_coeffs_,
rvecs[i], tvecs[i], getMarkerLength(ids[i]));
cv_bridge::CvImage out_msg; cv_bridge::CvImage out_msg;
out_msg.header.frame_id = msg->header.frame_id; out_msg.header.frame_id = msg->header.frame_id;
@@ -353,10 +326,10 @@ private:
return frame_id_prefix_ + std::to_string(id); return frame_id_prefix_ + std::to_string(id);
} }
void readLengthOverride(ros::NodeHandle& nh) void readLengthOverride()
{ {
std::map<std::string, double> length_override; std::map<std::string, double> length_override;
nh.getParam("length_override", length_override); nh_priv_.getParam("length_override", length_override);
for (auto const& item : length_override) { for (auto const& item : length_override) {
length_override_[std::stoi(item.first)] = item.second; length_override_[std::stoi(item.first)] = item.second;
} }
@@ -372,47 +345,16 @@ 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) void mapMarkersCallback(const aruco_pose::MarkerArray& msg)
{ {
map_markers_ids_.clear(); map_markers_ids_.clear();
for (auto const& marker : msg.markers) { for (auto const& marker : msg.markers) {
map_markers_ids_.insert(marker.id); 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) void paramCallback(aruco_pose::DetectorConfig &config, uint32_t level)
{ {
enabled_ = config.enabled && config.length > 0;
length_ = config.length;
parameters_->adaptiveThreshConstant = config.adaptiveThreshConstant; parameters_->adaptiveThreshConstant = config.adaptiveThreshConstant;
parameters_->adaptiveThreshWinSizeMin = config.adaptiveThreshWinSizeMin; parameters_->adaptiveThreshWinSizeMin = config.adaptiveThreshWinSizeMin;
parameters_->adaptiveThreshWinSizeMax = config.adaptiveThreshWinSizeMax; parameters_->adaptiveThreshWinSizeMax = config.adaptiveThreshWinSizeMax;

View File

@@ -19,13 +19,11 @@
#include <vector> #include <vector>
#include <fstream> #include <fstream>
#include <algorithm> #include <algorithm>
#include <memory>
#include <ros/ros.h> #include <ros/ros.h>
#include <nodelet/nodelet.h> #include <nodelet/nodelet.h>
#include <pluginlib/class_list_macros.h> #include <pluginlib/class_list_macros.h>
#include <image_transport/image_transport.h> #include <image_transport/image_transport.h>
#include <cv_bridge/cv_bridge.h> #include <cv_bridge/cv_bridge.h>
#include <dynamic_reconfigure/server.h>
#include <tf/transform_datatypes.h> #include <tf/transform_datatypes.h>
#include <tf2_ros/buffer.h> #include <tf2_ros/buffer.h>
#include <tf2_ros/transform_listener.h> #include <tf2_ros/transform_listener.h>
@@ -43,7 +41,6 @@
#include <aruco_pose/MarkerArray.h> #include <aruco_pose/MarkerArray.h>
#include <aruco_pose/Marker.h> #include <aruco_pose/Marker.h>
#include <aruco_pose/MapConfig.h>
#include <opencv2/opencv.hpp> #include <opencv2/opencv.hpp>
#include <opencv2/aruco.hpp> #include <opencv2/aruco.hpp>
@@ -61,6 +58,7 @@ typedef message_filters::sync_policies::ExactTime<Image, CameraInfo, MarkerArray
class ArucoMap : public nodelet::Nodelet { class ArucoMap : public nodelet::Nodelet {
private: private:
ros::NodeHandle nh_, nh_priv_;
ros::Publisher img_pub_, pose_pub_, markers_pub_, vis_markers_pub_; ros::Publisher img_pub_, pose_pub_, markers_pub_, vis_markers_pub_;
image_transport::Publisher debug_pub_; image_transport::Publisher debug_pub_;
message_filters::Subscriber<Image> image_sub_; message_filters::Subscriber<Image> image_sub_;
@@ -77,53 +75,50 @@ private:
tf2_ros::StaticTransformBroadcaster static_br_; tf2_ros::StaticTransformBroadcaster static_br_;
tf2_ros::Buffer tf_buffer_; tf2_ros::Buffer tf_buffer_;
tf2_ros::TransformListener tf_listener_{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_; visualization_msgs::MarkerArray vis_array_;
std::string known_vertical_, map_, markers_frame_, markers_parent_frame_; std::string known_tilt_, map_, markers_frame_, markers_parent_frame_;
int image_width_, image_height_, image_margin_; int image_width_, image_height_, image_margin_;
bool flip_vertical_, auto_flip_, image_axis_, put_markers_count_to_covariance_; bool auto_flip_, image_axis_;
public: public:
virtual void onInit() virtual void onInit()
{ {
ros::NodeHandle &nh_ = getNodeHandle(); nh_ = getNodeHandle();
ros::NodeHandle &nh_priv_ = getPrivateNodeHandle(); nh_priv_ = getPrivateNodeHandle();
image_transport::ImageTransport it_priv(nh_priv_); image_transport::ImageTransport it_priv(nh_priv_);
// TODO: why image_transport doesn't work here? // TODO: why image_transport doesn't work here?
img_pub_ = nh_priv_.advertise<sensor_msgs::Image>("image", 1, true); 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_ = cv::makePtr<cv::aruco::Board>();
board_->dictionary = cv::aruco::getPredefinedDictionary( board_->dictionary = cv::aruco::getPredefinedDictionary(
static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(nh_priv_.param("dictionary", 2))); static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(nh_priv_.param("dictionary", 2)));
camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F); camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
dist_coeffs_ = cv::Mat::zeros(8, 1, CV_64F);
type_ = nh_priv_.param<std::string>("type", "map"); std::string type, map;
transform_.child_frame_id = nh_priv_.param<std::string>("frame_id", "aruco_map"); nh_priv_.param<std::string>("type", type, "map");
known_vertical_ = nh_priv_.param("known_vertical", nh_priv_.param("known_tilt", std::string(""))); // known_tilt is an old name nh_priv_.param<std::string>("frame_id", transform_.child_frame_id, "aruco_map");
flip_vertical_ = nh_priv_.param<bool>("flip_vertical", false); nh_priv_.param<std::string>("known_tilt", known_tilt_, "");
auto_flip_ = nh_priv_.param("auto_flip", false); nh_priv_.param("auto_flip", auto_flip_, false);
image_width_ = nh_priv_.param("image_width" , 2000); nh_priv_.param("image_width", image_width_, 2000);
image_height_ = nh_priv_.param("image_height", 2000); nh_priv_.param("image_height", image_height_, 2000);
image_margin_ = nh_priv_.param("image_margin", 200); nh_priv_.param("image_margin", image_margin_, 200);
image_axis_ = nh_priv_.param("image_axis", true); nh_priv_.param("image_axis", image_axis_, true);
put_markers_count_to_covariance_ = nh_priv_.param("put_markers_count_to_covariance", false); nh_priv_.param<std::string>("markers/frame_id", markers_parent_frame_, transform_.child_frame_id);
markers_parent_frame_ = nh_priv_.param<std::string>("markers/frame_id", transform_.child_frame_id); nh_priv_.param<std::string>("markers/child_frame_id_prefix", markers_frame_, "");
markers_frame_ = nh_priv_.param<std::string>("markers/child_frame_id_prefix", "");
// createStripLine(); // createStripLine();
if (type_ == "map") { if (type == "map") {
map_ = nh_priv_.param<std::string>("map" , ""); param(nh_priv_, "map", map);
loadMap(map_); loadMap(map);
} else if (type_ == "gridboard") { } else if (type == "gridboard") {
createGridBoard(nh_priv_); createGridBoard();
} else { } else {
NODELET_FATAL("unknown type: %s", type_.c_str()); NODELET_FATAL("unknown type: %s", type.c_str());
ros::shutdown(); ros::shutdown();
} }
@@ -131,8 +126,6 @@ public:
vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1, true); vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1, true);
debug_pub_ = it_priv.advertise("debug", 1); debug_pub_ = it_priv.advertise("debug", 1);
publishMap();
image_sub_.subscribe(nh_, "image_raw", 1); image_sub_.subscribe(nh_, "image_raw", 1);
info_sub_.subscribe(nh_, "camera_info", 1); info_sub_.subscribe(nh_, "camera_info", 1);
markers_sub_.subscribe(nh_, "markers", 1); markers_sub_.subscribe(nh_, "markers", 1);
@@ -140,11 +133,10 @@ public:
sync_.reset(new message_filters::Synchronizer<SyncPolicy>(SyncPolicy(10), image_sub_, info_sub_, markers_sub_)); 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)); sync_->registerCallback(boost::bind(&ArucoMap::callback, this, _1, _2, _3));
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<aruco_pose::MapConfig>>(nh_priv_); publishMarkersFrames();
dynamic_reconfigure::Server<aruco_pose::MapConfig>::CallbackType cb; publishMarkers();
publishMapImage();
cb = std::bind(&ArucoMap::paramCallback, this, std::placeholders::_1, std::placeholders::_2); vis_markers_pub_.publish(vis_array_);
dyn_srv_->setCallback(cb);
NODELET_INFO("ready"); NODELET_INFO("ready");
} }
@@ -153,9 +145,6 @@ public:
const sensor_msgs::CameraInfoConstPtr& cinfo, const sensor_msgs::CameraInfoConstPtr& cinfo,
const aruco_pose::MarkerArrayConstPtr& markers) const aruco_pose::MarkerArrayConstPtr& markers)
{ {
if (!enabled_) return;
if (markers->markers.empty()) return; // map not loaded
int valid = 0; int valid = 0;
int count = markers->markers.size(); int count = markers->markers.size();
std::vector<int> ids; std::vector<int> ids;
@@ -179,21 +168,7 @@ public:
corners.push_back(marker_corners); corners.push_back(marker_corners);
} }
if (put_markers_count_to_covariance_) { if (known_tilt_.empty()) {
// HACK: pass markers count using covariance field
int valid_markers = 0;
for (auto const &marker : markers->markers) {
for (auto const &board_marker : board_->ids) {
if (board_marker == marker.id) {
valid_markers++;
break;
}
}
}
pose_.pose.covariance[0] = valid_markers;
}
if (known_vertical_.empty()) {
// simple estimation // simple estimation
valid = cv::aruco::estimatePoseBoard(corners, ids, board_, camera_matrix_, dist_coeffs_, valid = cv::aruco::estimatePoseBoard(corners, ids, board_, camera_matrix_, dist_coeffs_,
rvec, tvec, false); rvec, tvec, false);
@@ -207,7 +182,7 @@ public:
} else { } else {
Mat obj_points, img_points; Mat obj_points, img_points;
// estimation with known vertical // estimation with "snapping"
cv::aruco::getBoardObjectAndImagePoints(board_, corners, ids, obj_points, img_points); cv::aruco::getBoardObjectAndImagePoints(board_, corners, ids, obj_points, img_points);
if (obj_points.empty()) goto publish_debug; if (obj_points.empty()) goto publish_debug;
@@ -219,11 +194,11 @@ public:
fillTransform(transform_.transform, rvec, tvec); fillTransform(transform_.transform, rvec, tvec);
try { try {
geometry_msgs::TransformStamped vertical = tf_buffer_.lookupTransform(markers->header.frame_id, geometry_msgs::TransformStamped snap_to = tf_buffer_.lookupTransform(markers->header.frame_id,
known_vertical_, markers->header.stamp, ros::Duration(0.02)); known_tilt_, markers->header.stamp, ros::Duration(0.02));
applyVertical(transform_.transform.rotation, vertical.transform.rotation, flip_vertical_, auto_flip_); snapOrientation(transform_.transform.rotation, snap_to.transform.rotation, auto_flip_);
} catch (const tf2::TransformException& e) { } catch (const tf2::TransformException& e) {
NODELET_WARN_THROTTLE(1, "can't retrieve known vertical: %s", e.what()); NODELET_WARN_THROTTLE(1, "can't snap: %s", e.what());
} }
geometry_msgs::TransformStamped shift; geometry_msgs::TransformStamped shift;
@@ -295,17 +270,9 @@ publish_debug:
std::ifstream f(filename); std::ifstream f(filename);
std::string line; std::string line;
clearMarkers();
if (map_ == "") {
NODELET_INFO("No map loaded");
return;
}
if (!f.good()) { if (!f.good()) {
NODELET_ERROR("%s - %s", strerror(errno), filename.c_str()); NODELET_FATAL("%s - %s", strerror(errno), filename.c_str());
map_ = ""; ros::shutdown();
return;
} }
while (std::getline(f, line)) { while (std::getline(f, line)) {
@@ -331,10 +298,9 @@ publish_debug:
s.putback(first); s.putback(first);
} else { } else {
// Probably garbage data; inform user and throw an exception, possibly killing nodelet // Probably garbage data; inform user and throw an exception, possibly killing nodelet
NODELET_ERROR("Malformed input: %s", line.c_str()); NODELET_FATAL("Malformed input: %s", line.c_str());
map_ = ""; ros::shutdown();
clearMarkers(); throw std::runtime_error("Malformed input");
return;
} }
if (!(s >> id >> length >> x >> y)) { if (!(s >> id >> length >> x >> y)) {
@@ -365,15 +331,7 @@ publish_debug:
NODELET_INFO("loading %s complete (%d markers)", filename.c_str(), static_cast<int>(board_->ids.size())); NODELET_INFO("loading %s complete (%d markers)", filename.c_str(), static_cast<int>(board_->ids.size()));
} }
void publishMap() void createGridBoard()
{
publishMarkersFrames();
publishMarkers();
publishMapImage();
vis_markers_pub_.publish(vis_array_);
}
void createGridBoard(ros::NodeHandle& nh)
{ {
NODELET_INFO("generate gridboard"); NODELET_INFO("generate gridboard");
NODELET_WARN("gridboard maps are deprecated"); NODELET_WARN("gridboard maps are deprecated");
@@ -381,15 +339,15 @@ publish_debug:
int markers_x, markers_y, first_marker; int markers_x, markers_y, first_marker;
double markers_side, markers_sep_x, markers_sep_y; double markers_side, markers_sep_x, markers_sep_y;
std::vector<int> marker_ids; std::vector<int> marker_ids;
markers_x = nh.param("markers_x", 10); nh_priv_.param<int>("markers_x", markers_x, 10);
markers_y = nh.param("markers_y", 10); nh_priv_.param<int>("markers_y", markers_y, 10);
first_marker = nh.param("first_marker", 0); nh_priv_.param<int>("first_marker", first_marker, 0);
param(nh, "markers_side", markers_side); param(nh_priv_, "markers_side", markers_side);
param(nh, "markers_sep_x", markers_sep_x); param(nh_priv_, "markers_sep_x", markers_sep_x);
param(nh, "markers_sep_y", markers_sep_y); param(nh_priv_, "markers_sep_y", markers_sep_y);
if (nh.getParam("marker_ids", marker_ids)) { if (nh_priv_.getParam("marker_ids", marker_ids)) {
if ((unsigned int)(markers_x * markers_y) != marker_ids.size()) { if ((unsigned int)(markers_x * markers_y) != marker_ids.size()) {
NODELET_FATAL("~marker_ids length should be equal to ~markers_x * ~markers_y"); NODELET_FATAL("~marker_ids length should be equal to ~markers_x * ~markers_y");
ros::shutdown(); ros::shutdown();
@@ -414,15 +372,6 @@ publish_debug:
} }
} }
void clearMarkers()
{
board_->ids.clear();
board_->objPoints.clear();
markers_.markers.clear();
vis_array_.markers.clear();
markers_transforms_.clear();
}
// void createStripLine() // void createStripLine()
// { // {
// visualization_msgs::Marker marker; // visualization_msgs::Marker marker;
@@ -519,7 +468,7 @@ publish_debug:
vis_marker.pose.position.x = x; vis_marker.pose.position.x = x;
vis_marker.pose.position.y = y; vis_marker.pose.position.y = y;
vis_marker.pose.position.z = z; 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_marker.frame_locked = true;
vis_array_.markers.push_back(vis_marker); vis_array_.markers.push_back(vis_marker);
@@ -562,22 +511,6 @@ publish_debug:
msg.image = image; msg.image = image;
img_pub_.publish(msg.toImageMsg()); 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) PLUGINLIB_EXPORT_CLASS(ArucoMap, nodelet::Nodelet)

View File

@@ -3,11 +3,26 @@
#include "draw.h" #include "draw.h"
#include <math.h> #include <math.h>
#include <vector>
using namespace cv; using namespace cv;
using namespace cv::aruco; using namespace cv::aruco;
static void _cvProjectPoints2( const CvMat* object_points, const CvMat* rotation_vector,
const CvMat* translation_vector, const CvMat* camera_matrix,
const CvMat* distortion_coeffs, CvMat* image_points,
CvMat* dpdrot CV_DEFAULT(NULL), CvMat* dpdt CV_DEFAULT(NULL),
CvMat* dpdf CV_DEFAULT(NULL), CvMat* dpdc CV_DEFAULT(NULL),
CvMat* dpddist CV_DEFAULT(NULL),
double aspect_ratio CV_DEFAULT(0));
static void _projectPoints( InputArray objectPoints,
InputArray rvec, InputArray tvec,
InputArray cameraMatrix, InputArray distCoeffs,
OutputArray imagePoints,
OutputArray jacobian = noArray(),
double aspectRatio = 0 );
void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginSize, void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginSize,
int borderBits, bool drawAxis) { int borderBits, bool drawAxis) {
@@ -127,194 +142,35 @@ void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginS
} }
} }
/** /* Draw a (potentially partially visible) line. */
* @brief Convert point coordinates from world space to camera space. static void linePartial(InputOutputArray image, Point3f p1, Point3f p2, const Scalar& color,
* int thickness = 1, int lineType = LINE_8, int shift = 0)
* @param points A vector of points in world space.
* @param rvec Rotation matrix or Rodrigues rotation vector.
* @param tvec Translation vector from world to camera space.
*
* @return A vector of points in camera space.
*/
template<typename CvPointType>
static std::vector<CvPointType> worldToCamera(const std::vector<CvPointType>& points,
const cv::Mat& rvec, const cv::Mat& tvec)
{ {
// We operate with CV_64F matrices internally to avoid precision loss // If both points are behind the screen, don't draw anything
cv::Mat rvec_64f; if (p1.z <= 0 && p2.z <= 0) {
cv::Mat tvec_64f; return;
rvec.convertTo(rvec_64f, CV_64F);
tvec.convertTo(tvec_64f, CV_64F);
// Convert Rodrigues vector to rotation matrix
cv::Mat rmat;
if ((rvec_64f.cols == 3 && rvec_64f.rows == 1) ||
(rvec_64f.cols == 1 && rvec_64f.rows == 3))
{
Rodrigues(rvec_64f, rmat);
} }
else Point2f p1p{p1.x, p1.y};
{ Point2f p2p{p2.x, p2.y};
rmat = rvec_64f.clone(); // If points are on the different sides of the plane, compute intersection point
if (p1.z * p2.z < 0) {
// Compute intersection point with the screen
// We denote alpha as such:
// xi = (1 - alpha) * x1 + alpha * x2
// yi = (1 - alpha) * y1 + alpha * y2
// zi = (1 - alpha) * z1 + alpha * z2 = 0
// Thus, alpha can be expressed as
// alpha = z1 / (z1 - z2)
float alpha = p1.z / (p1.z - p2.z);
Point2f pi{(1 - alpha) * p1.x + alpha * p2.x, (1 - alpha) * p1.y + alpha * p2.y};
// Now, if z1 is negative, we draw the line from (xi, yi) to (x2, y2), else we draw from (x1, y1) to (xi, yi)
if (p1.z < 0) {
p1p = pi;
} else {
p2p = pi;
}
} }
// Make sure tvec has a size of (3, 1) line(image, p1p, p2p, color, thickness, lineType, shift);
if (tvec_64f.rows == 1)
{
tvec_64f = tvec_64f.t();
}
std::vector<CvPointType> result;
result.reserve(points.size());
for(const auto& point : points)
{
// Calculate point coordinates in camera frame
// static_casts are here to silence potential narrowing conversion warnings
CvPointType camPoint{
static_cast<decltype(CvPointType::x)>(point.x * rmat.at<double>(0,0) + point.y * rmat.at<double>(0,1) + point.z * rmat.at<double>(0,2) + tvec_64f.at<double>(0)),
static_cast<decltype(CvPointType::y)>(point.x * rmat.at<double>(1,0) + point.y * rmat.at<double>(1,1) + point.z * rmat.at<double>(1,2) + tvec_64f.at<double>(1)),
static_cast<decltype(CvPointType::z)>(point.x * rmat.at<double>(2,0) + point.y * rmat.at<double>(2,1) + point.z * rmat.at<double>(2,2) + tvec_64f.at<double>(2))
};
result.push_back(camPoint);
}
return result;
}
/**
* @brief Project points from camera space to screen space, applying distortion in the process.
*
* @param points A vector of points in camera space.
* @param cameraMatrix OpenCV intrinsic camera matrix.
* @param distCoeffs OpenCV distortion model coefficients.
*
* @return A vector of points in screen space.
*/
template<typename CvPointType>
static std::vector<CvPointType> cameraToScreen(const std::vector<CvPointType>& points,
const cv::Mat& cameraMatrix,
const cv::Mat& distCoeffs)
{
// We operate with CV_64F matrices internally to avoid precision loss
cv::Mat cm_64f; // camera matrix, CV_64F
cv::Mat dc_64f; // distortion coefficients, CV_64F
cameraMatrix.convertTo(cm_64f, CV_64F);
distCoeffs.convertTo(dc_64f, CV_64F);
// Make sure distortion vector has a size of (N, 1)
if (dc_64f.rows == 1)
{
dc_64f = dc_64f.t();
}
// We will always use 12 distortion coefficients,
// and we can safely pad missing ones with zeroes
dc_64f.resize(12, 0.0);
std::vector<CvPointType> result;
result.reserve(points.size());
for(const auto& point : points)
{
// Apply perspective projection, preserving initial Z coordinate
// Always use double-precision
cv::Point3d camPoint{
point.x / point.z,
point.y / point.z,
point.z
};
// Apply distortion
// Note that we do not consider tilted sensor distortion
// r^2 - distance from the image center squared
double r2 = camPoint.x * camPoint.x + camPoint.y * camPoint.y;
// r^4 - same, but to the 4th power
double r4 = r2 * r2;
// r^6 - same, but to the 6th power
double r6 = r4 * r2;
// tg1 - first tangential shift factor (2 * x * y)
double tg1 = 2 * camPoint.x * camPoint.y;
// tg2 - second tangential shift factor (r^2 + 2 * x^2)
double tg2 = r2 + 2 * camPoint.x * camPoint.x;
// tg3 - third tangential shift factor (r^2 + 2 * y^2)
double tg3 = r2 + 2 * camPoint.y * camPoint.y;
// polynomial distortion factor (numerator)
double pndist = 1 + dc_64f.at<double>(0) * r2 + dc_64f.at<double>(1) * r4 + dc_64f.at<double>(4) * r6;
// polynomial distortion factror (denominator)
double pddist = 1.0 / (1 + dc_64f.at<double>(5) * r2 + dc_64f.at<double>(6) * r4 + dc_64f.at<double>(7) * r6);
// Distorted point coordinates (always double-precision)
cv::Point3d distortedPoint{
camPoint.x * pndist * pddist + dc_64f.at<double>(2) * tg1 + dc_64f.at<double>(3) * tg2 + dc_64f.at<double>(8) * r2 + dc_64f.at<double>(9) * r4,
camPoint.y * pndist * pddist + dc_64f.at<double>(2) * tg3 + dc_64f.at<double>(3) * tg1 + dc_64f.at<double>(10) * r2 + dc_64f.at<double>(11) * r4,
camPoint.z
};
// Convert to screen space
// We use static_cast here to silence potential warnings about narrowing conversions
// (we expect that to be the case)
CvPointType screenPoint{
static_cast<decltype(CvPointType::x)>(distortedPoint.x * cm_64f.at<double>(0, 0) + cm_64f.at<double>(0, 2)),
static_cast<decltype(CvPointType::y)>(distortedPoint.y * cm_64f.at<double>(1, 1) + cm_64f.at<double>(1, 2)),
static_cast<decltype(CvPointType::z)>(distortedPoint.z)
};
result.push_back(screenPoint);
}
return result;
}
/**
* @brief Clip a line against a clip plane.
*
* This function "clips" a line (described by two points in *camera space*)
* against a clip plane that is `clipPlaneDistance` meters away from the
* camera focal point. If both points are further away from the focal point
* than `clipPlaneDistance`, they will be returned unmodified. If one of the
* points is behind the clipping plane, a point *on* the clipping plane will
* be computed and returned as one of the points.
*
* If none of the points are visible, an empty vector will be returned.
*
* @param p1 First point on the line, in camera space.
* @param p2 Second point on the line, in camera space.
* @param clipPlaneDistance Distance from the focal point to the clipping plane.
* @return A vector of zero or two points on the clipped line, in camera space.
*/
static std::vector<Point3f> lineClip(Point3f p1, Point3f p2, float clipPlaneDistance = 0.1f)
{
// We don't need to compute an intersection if both points are
// behind us
if (p1.z < clipPlaneDistance && p2.z < clipPlaneDistance)
{
return {};
}
// We don't need to compute an intersection if both points are
// in front of us
if (p1.z > clipPlaneDistance && p2.z > clipPlaneDistance)
{
return {p1, p2};
}
// We don't really want to compute an intersection if both Z coordinates
// are sufficiently close to each other
if (std::abs(p1.z - p2.z) < 0.0001) // The number here is chosen arbitrarily
{
return {p1, p2};
}
// We compute the intersection as such:
// zi = (1 - alpha) * p1.z + alpha * p2.z = clipPlaneDistance
// alpha = (p1.z - clipPlaneDistance) / (p1.z - p2.z)
double alpha = (p1.z - clipPlaneDistance) / (p1.z - p2.z);
Point3f clipPlanePoint{
static_cast<float>((1 - alpha) * p1.x + alpha * p2.x),
static_cast<float>((1 - alpha) * p1.y + alpha * p2.y),
clipPlaneDistance
};
if (p1.z < clipPlaneDistance)
{
return {clipPlanePoint, p2};
}
else
{
return {p1, clipPlanePoint};
}
// Unreachable?
} }
void _drawAxis(InputOutputArray _image, InputArray _cameraMatrix, InputArray _distCoeffs, void _drawAxis(InputOutputArray _image, InputArray _cameraMatrix, InputArray _distCoeffs,
@@ -330,23 +186,647 @@ void _drawAxis(InputOutputArray _image, InputArray _cameraMatrix, InputArray _di
axisPoints.push_back(Point3f(length, 0, 0)); axisPoints.push_back(Point3f(length, 0, 0));
axisPoints.push_back(Point3f(0, length, 0)); axisPoints.push_back(Point3f(0, length, 0));
axisPoints.push_back(Point3f(0, 0, length)); axisPoints.push_back(Point3f(0, 0, length));
auto camAxisPoints = worldToCamera(axisPoints, _rvec.getMat(), _tvec.getMat()); std::vector<Point3f> imagePointsZ;
auto axisX = cameraToScreen(lineClip(camAxisPoints[0], camAxisPoints[1]), _cameraMatrix.getMat(), _distCoeffs.getMat()); _projectPoints(axisPoints, _rvec, _tvec, _cameraMatrix, _distCoeffs, imagePointsZ);
auto axisY = cameraToScreen(lineClip(camAxisPoints[0], camAxisPoints[2]), _cameraMatrix.getMat(), _distCoeffs.getMat());
auto axisZ = cameraToScreen(lineClip(camAxisPoints[0], camAxisPoints[3]), _cameraMatrix.getMat(), _distCoeffs.getMat()); // draw axis lines
if (axisX.size() > 0) linePartial(_image, imagePointsZ[0], imagePointsZ[1], Scalar(0, 0, 255), 3);
{ linePartial(_image, imagePointsZ[0], imagePointsZ[2], Scalar(0, 255, 0), 3);
line(_image, Point2f{axisX[0].x, axisX[0].y}, Point2f{axisX[1].x, axisX[1].y}, linePartial(_image, imagePointsZ[0], imagePointsZ[3], Scalar(255, 0, 0), 3);
Scalar(0, 0, 255), 3); }
}
if (axisY.size() > 0) static CvMat _cvMat(const cv::Mat& m)
{ {
line(_image, Point2f{axisY[0].x, axisY[0].y}, Point2f{axisY[1].x, axisY[1].y}, CvMat self;
Scalar(0, 255, 0), 3); CV_DbgAssert(m.dims <= 2);
} self = cvMat(m.rows, m.dims == 1 ? 1 : m.cols, m.type(), m.data);
if (axisZ.size() > 0) self.step = (int)m.step[0];
{ self.type = (self.type & ~cv::Mat::CONTINUOUS_FLAG) | (m.flags & cv::Mat::CONTINUOUS_FLAG);
line(_image, Point2f{axisZ[0].x, axisZ[0].y}, Point2f{axisZ[1].x, axisZ[1].y}, return self;
Scalar(255, 0, 0), 3); }
}
static void _projectPoints( InputArray _opoints,
InputArray _rvec,
InputArray _tvec,
InputArray _cameraMatrix,
InputArray _distCoeffs,
OutputArray _ipoints,
OutputArray _jacobian,
double aspectRatio )
{
Mat opoints = _opoints.getMat();
int npoints = opoints.checkVector(3), depth = opoints.depth();
CV_Assert(npoints >= 0 && (depth == CV_32F || depth == CV_64F));
CvMat dpdrot, dpdt, dpdf, dpdc, dpddist;
CvMat *pdpdrot = 0, *pdpdt = 0, *pdpdf = 0, *pdpdc = 0, *pdpddist = 0;
CV_Assert(_ipoints.needed());
_ipoints.create(npoints, 1, CV_MAKETYPE(depth, 3), -1, true);
Mat imagePoints = _ipoints.getMat();
CvMat c_imagePoints = _cvMat(imagePoints);
CvMat c_objectPoints = _cvMat(opoints);
Mat cameraMatrix = _cameraMatrix.getMat();
Mat rvec = _rvec.getMat(), tvec = _tvec.getMat();
CvMat c_cameraMatrix = _cvMat(cameraMatrix);
CvMat c_rvec = _cvMat(rvec), c_tvec = _cvMat(tvec);
double dc0buf[5] = {0};
Mat dc0(5, 1, CV_64F, dc0buf);
Mat distCoeffs = _distCoeffs.getMat();
if (distCoeffs.empty())
distCoeffs = dc0;
CvMat c_distCoeffs = _cvMat(distCoeffs);
int ndistCoeffs = distCoeffs.rows + distCoeffs.cols - 1;
Mat jacobian;
if (_jacobian.needed())
{
_jacobian.create(npoints * 2, 3 + 3 + 2 + 2 + ndistCoeffs, CV_64F);
jacobian = _jacobian.getMat();
pdpdrot = &(dpdrot = _cvMat(jacobian.colRange(0, 3)));
pdpdt = &(dpdt = _cvMat(jacobian.colRange(3, 6)));
pdpdf = &(dpdf = _cvMat(jacobian.colRange(6, 8)));
pdpdc = &(dpdc = _cvMat(jacobian.colRange(8, 10)));
pdpddist = &(dpddist = _cvMat(jacobian.colRange(10, 10 + ndistCoeffs)));
}
_cvProjectPoints2(&c_objectPoints, &c_rvec, &c_tvec, &c_cameraMatrix, &c_distCoeffs,
&c_imagePoints, pdpdrot, pdpdt, pdpdf, pdpdc, pdpddist, aspectRatio);
}
namespace _detail
{
template <typename FLOAT>
void computeTiltProjectionMatrix(FLOAT tauX,
FLOAT tauY,
Matx<FLOAT, 3, 3>* matTilt = 0,
Matx<FLOAT, 3, 3>* dMatTiltdTauX = 0,
Matx<FLOAT, 3, 3>* dMatTiltdTauY = 0,
Matx<FLOAT, 3, 3>* invMatTilt = 0)
{
FLOAT cTauX = cos(tauX);
FLOAT sTauX = sin(tauX);
FLOAT cTauY = cos(tauY);
FLOAT sTauY = sin(tauY);
Matx<FLOAT, 3, 3> matRotX = Matx<FLOAT, 3, 3>(1,0,0,0,cTauX,sTauX,0,-sTauX,cTauX);
Matx<FLOAT, 3, 3> matRotY = Matx<FLOAT, 3, 3>(cTauY,0,-sTauY,0,1,0,sTauY,0,cTauY);
Matx<FLOAT, 3, 3> matRotXY = matRotY * matRotX;
Matx<FLOAT, 3, 3> matProjZ = Matx<FLOAT, 3, 3>(matRotXY(2,2),0,-matRotXY(0,2),0,matRotXY(2,2),-matRotXY(1,2),0,0,1);
if (matTilt)
{
// Matrix for trapezoidal distortion of tilted image sensor
*matTilt = matProjZ * matRotXY;
}
if (dMatTiltdTauX)
{
// Derivative with respect to tauX
Matx<FLOAT, 3, 3> dMatRotXYdTauX = matRotY * Matx<FLOAT, 3, 3>(0,0,0,0,-sTauX,cTauX,0,-cTauX,-sTauX);
Matx<FLOAT, 3, 3> dMatProjZdTauX = Matx<FLOAT, 3, 3>(dMatRotXYdTauX(2,2),0,-dMatRotXYdTauX(0,2),
0,dMatRotXYdTauX(2,2),-dMatRotXYdTauX(1,2),0,0,0);
*dMatTiltdTauX = (matProjZ * dMatRotXYdTauX) + (dMatProjZdTauX * matRotXY);
}
if (dMatTiltdTauY)
{
// Derivative with respect to tauY
Matx<FLOAT, 3, 3> dMatRotXYdTauY = Matx<FLOAT, 3, 3>(-sTauY,0,-cTauY,0,0,0,cTauY,0,-sTauY) * matRotX;
Matx<FLOAT, 3, 3> dMatProjZdTauY = Matx<FLOAT, 3, 3>(dMatRotXYdTauY(2,2),0,-dMatRotXYdTauY(0,2),
0,dMatRotXYdTauY(2,2),-dMatRotXYdTauY(1,2),0,0,0);
*dMatTiltdTauY = (matProjZ * dMatRotXYdTauY) + (dMatProjZdTauY * matRotXY);
}
if (invMatTilt)
{
FLOAT inv = 1./matRotXY(2,2);
Matx<FLOAT, 3, 3> invMatProjZ = Matx<FLOAT, 3, 3>(inv,0,inv*matRotXY(0,2),0,inv,inv*matRotXY(1,2),0,0,1);
*invMatTilt = matRotXY.t()*invMatProjZ;
}
}
}
static const char* cvDistCoeffErr = "Distortion coefficients must be 1x4, 4x1, 1x5, 5x1, 1x8, 8x1, 1x12, 12x1, 1x14 or 14x1 floating-point vector";
static void _cvProjectPoints2Internal( const CvMat* objectPoints,
const CvMat* r_vec,
const CvMat* t_vec,
const CvMat* A,
const CvMat* distCoeffs,
CvMat* imagePoints, CvMat* dpdr CV_DEFAULT(NULL),
CvMat* dpdt CV_DEFAULT(NULL), CvMat* dpdf CV_DEFAULT(NULL),
CvMat* dpdc CV_DEFAULT(NULL), CvMat* dpdk CV_DEFAULT(NULL),
CvMat* dpdo CV_DEFAULT(NULL),
double aspectRatio CV_DEFAULT(0) )
{
Ptr<CvMat> matM, _m;
Ptr<CvMat> _dpdr, _dpdt, _dpdc, _dpdf, _dpdk;
Ptr<CvMat> _dpdo;
int i, j, count;
int calc_derivatives;
const CvPoint3D64f* M;
CvPoint3D64f* m;
double r[3], R[9], dRdr[27], t[3], a[9], k[14] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, fx, fy, cx, cy;
Matx33d matTilt = Matx33d::eye();
Matx33d dMatTiltdTauX(0,0,0,0,0,0,0,-1,0);
Matx33d dMatTiltdTauY(0,0,0,0,0,0,1,0,0);
CvMat _r, _t, _a = cvMat( 3, 3, CV_64F, a ), _k;
CvMat matR = cvMat( 3, 3, CV_64F, R ), _dRdr = cvMat( 3, 9, CV_64F, dRdr );
double *dpdr_p = 0, *dpdt_p = 0, *dpdk_p = 0, *dpdf_p = 0, *dpdc_p = 0;
double* dpdo_p = 0;
int dpdr_step = 0, dpdt_step = 0, dpdk_step = 0, dpdf_step = 0, dpdc_step = 0;
int dpdo_step = 0;
bool fixedAspectRatio = aspectRatio > FLT_EPSILON;
if( !CV_IS_MAT(objectPoints) || !CV_IS_MAT(r_vec) ||
!CV_IS_MAT(t_vec) || !CV_IS_MAT(A) ||
/*!CV_IS_MAT(distCoeffs) ||*/ !CV_IS_MAT(imagePoints) )
CV_Error( CV_StsBadArg, "One of required arguments is not a valid matrix" );
int total = objectPoints->rows * objectPoints->cols * CV_MAT_CN(objectPoints->type);
if(total % 3 != 0)
{
//we have stopped support of homogeneous coordinates because it cause ambiguity in interpretation of the input data
CV_Error( CV_StsBadArg, "Homogeneous coordinates are not supported" );
}
count = total / 3;
if( CV_IS_CONT_MAT(objectPoints->type) &&
(CV_MAT_DEPTH(objectPoints->type) == CV_32F || CV_MAT_DEPTH(objectPoints->type) == CV_64F)&&
((objectPoints->rows == 1 && CV_MAT_CN(objectPoints->type) == 3) ||
(objectPoints->rows == count && CV_MAT_CN(objectPoints->type)*objectPoints->cols == 3) ||
(objectPoints->rows == 3 && CV_MAT_CN(objectPoints->type) == 1 && objectPoints->cols == count)))
{
matM.reset(cvCreateMat( objectPoints->rows, objectPoints->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(objectPoints->type)) ));
cvConvert(objectPoints, matM);
}
else
{
// matM = cvCreateMat( 1, count, CV_64FC3 );
// cvConvertPointsHomogeneous( objectPoints, matM );
CV_Error( CV_StsBadArg, "Homogeneous coordinates are not supported" );
}
if( CV_IS_CONT_MAT(imagePoints->type) &&
(CV_MAT_DEPTH(imagePoints->type) == CV_32F || CV_MAT_DEPTH(imagePoints->type) == CV_64F) &&
((imagePoints->rows == 1 && CV_MAT_CN(imagePoints->type) == 3) ||
(imagePoints->rows == count && CV_MAT_CN(imagePoints->type)*imagePoints->cols == 3) ||
(imagePoints->rows == 3 && CV_MAT_CN(imagePoints->type) == 1 && imagePoints->cols == count)))
{
_m.reset(cvCreateMat( imagePoints->rows, imagePoints->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(imagePoints->type)) ));
cvConvert(imagePoints, _m);
}
else
{
// _m = cvCreateMat( 1, count, CV_64FC2 );
CV_Error( CV_StsBadArg, "Homogeneous coordinates are not supported" );
}
M = (CvPoint3D64f*)matM->data.db;
m = (CvPoint3D64f*)_m->data.db;
if( (CV_MAT_DEPTH(r_vec->type) != CV_64F && CV_MAT_DEPTH(r_vec->type) != CV_32F) ||
(((r_vec->rows != 1 && r_vec->cols != 1) ||
r_vec->rows*r_vec->cols*CV_MAT_CN(r_vec->type) != 3) &&
((r_vec->rows != 3 && r_vec->cols != 3) || CV_MAT_CN(r_vec->type) != 1)))
CV_Error( CV_StsBadArg, "Rotation must be represented by 1x3 or 3x1 "
"floating-point rotation vector, or 3x3 rotation matrix" );
if( r_vec->rows == 3 && r_vec->cols == 3 )
{
_r = cvMat( 3, 1, CV_64FC1, r );
cvRodrigues2( r_vec, &_r );
cvRodrigues2( &_r, &matR, &_dRdr );
cvCopy( r_vec, &matR );
}
else
{
_r = cvMat( r_vec->rows, r_vec->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(r_vec->type)), r );
cvConvert( r_vec, &_r );
cvRodrigues2( &_r, &matR, &_dRdr );
}
if( (CV_MAT_DEPTH(t_vec->type) != CV_64F && CV_MAT_DEPTH(t_vec->type) != CV_32F) ||
(t_vec->rows != 1 && t_vec->cols != 1) ||
t_vec->rows*t_vec->cols*CV_MAT_CN(t_vec->type) != 3 )
CV_Error( CV_StsBadArg,
"Translation vector must be 1x3 or 3x1 floating-point vector" );
_t = cvMat( t_vec->rows, t_vec->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(t_vec->type)), t );
cvConvert( t_vec, &_t );
if( (CV_MAT_TYPE(A->type) != CV_64FC1 && CV_MAT_TYPE(A->type) != CV_32FC1) ||
A->rows != 3 || A->cols != 3 )
CV_Error( CV_StsBadArg, "Instrinsic parameters must be 3x3 floating-point matrix" );
cvConvert( A, &_a );
fx = a[0]; fy = a[4];
cx = a[2]; cy = a[5];
if( fixedAspectRatio )
fx = fy*aspectRatio;
if( distCoeffs )
{
if( !CV_IS_MAT(distCoeffs) ||
(CV_MAT_DEPTH(distCoeffs->type) != CV_64F &&
CV_MAT_DEPTH(distCoeffs->type) != CV_32F) ||
(distCoeffs->rows != 1 && distCoeffs->cols != 1) ||
(distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 4 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 5 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 8 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 12 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 14) )
CV_Error( CV_StsBadArg, cvDistCoeffErr );
_k = cvMat( distCoeffs->rows, distCoeffs->cols,
CV_MAKETYPE(CV_64F,CV_MAT_CN(distCoeffs->type)), k );
cvConvert( distCoeffs, &_k );
if(k[12] != 0 || k[13] != 0)
{
_detail::computeTiltProjectionMatrix(k[12], k[13],
&matTilt, &dMatTiltdTauX, &dMatTiltdTauY);
}
}
if( dpdr )
{
if( !CV_IS_MAT(dpdr) ||
(CV_MAT_TYPE(dpdr->type) != CV_32FC1 &&
CV_MAT_TYPE(dpdr->type) != CV_64FC1) ||
dpdr->rows != count*2 || dpdr->cols != 3 )
CV_Error( CV_StsBadArg, "dp/drot must be 2Nx3 floating-point matrix" );
if( CV_MAT_TYPE(dpdr->type) == CV_64FC1 )
{
_dpdr.reset(cvCloneMat(dpdr));
}
else
_dpdr.reset(cvCreateMat( 2*count, 3, CV_64FC1 ));
dpdr_p = _dpdr->data.db;
dpdr_step = _dpdr->step/sizeof(dpdr_p[0]);
}
if( dpdt )
{
if( !CV_IS_MAT(dpdt) ||
(CV_MAT_TYPE(dpdt->type) != CV_32FC1 &&
CV_MAT_TYPE(dpdt->type) != CV_64FC1) ||
dpdt->rows != count*2 || dpdt->cols != 3 )
CV_Error( CV_StsBadArg, "dp/dT must be 2Nx3 floating-point matrix" );
if( CV_MAT_TYPE(dpdt->type) == CV_64FC1 )
{
_dpdt.reset(cvCloneMat(dpdt));
}
else
_dpdt.reset(cvCreateMat( 2*count, 3, CV_64FC1 ));
dpdt_p = _dpdt->data.db;
dpdt_step = _dpdt->step/sizeof(dpdt_p[0]);
}
if( dpdf )
{
if( !CV_IS_MAT(dpdf) ||
(CV_MAT_TYPE(dpdf->type) != CV_32FC1 && CV_MAT_TYPE(dpdf->type) != CV_64FC1) ||
dpdf->rows != count*2 || dpdf->cols != 2 )
CV_Error( CV_StsBadArg, "dp/df must be 2Nx2 floating-point matrix" );
if( CV_MAT_TYPE(dpdf->type) == CV_64FC1 )
{
_dpdf.reset(cvCloneMat(dpdf));
}
else
_dpdf.reset(cvCreateMat( 2*count, 2, CV_64FC1 ));
dpdf_p = _dpdf->data.db;
dpdf_step = _dpdf->step/sizeof(dpdf_p[0]);
}
if( dpdc )
{
if( !CV_IS_MAT(dpdc) ||
(CV_MAT_TYPE(dpdc->type) != CV_32FC1 && CV_MAT_TYPE(dpdc->type) != CV_64FC1) ||
dpdc->rows != count*2 || dpdc->cols != 2 )
CV_Error( CV_StsBadArg, "dp/dc must be 2Nx2 floating-point matrix" );
if( CV_MAT_TYPE(dpdc->type) == CV_64FC1 )
{
_dpdc.reset(cvCloneMat(dpdc));
}
else
_dpdc.reset(cvCreateMat( 2*count, 2, CV_64FC1 ));
dpdc_p = _dpdc->data.db;
dpdc_step = _dpdc->step/sizeof(dpdc_p[0]);
}
if( dpdk )
{
if( !CV_IS_MAT(dpdk) ||
(CV_MAT_TYPE(dpdk->type) != CV_32FC1 && CV_MAT_TYPE(dpdk->type) != CV_64FC1) ||
dpdk->rows != count*2 || (dpdk->cols != 14 && dpdk->cols != 12 && dpdk->cols != 8 && dpdk->cols != 5 && dpdk->cols != 4 && dpdk->cols != 2) )
CV_Error( CV_StsBadArg, "dp/df must be 2Nx14, 2Nx12, 2Nx8, 2Nx5, 2Nx4 or 2Nx2 floating-point matrix" );
if( !distCoeffs )
CV_Error( CV_StsNullPtr, "distCoeffs is NULL while dpdk is not" );
if( CV_MAT_TYPE(dpdk->type) == CV_64FC1 )
{
_dpdk.reset(cvCloneMat(dpdk));
}
else
_dpdk.reset(cvCreateMat( dpdk->rows, dpdk->cols, CV_64FC1 ));
dpdk_p = _dpdk->data.db;
dpdk_step = _dpdk->step/sizeof(dpdk_p[0]);
}
if( dpdo )
{
if( !CV_IS_MAT( dpdo ) || ( CV_MAT_TYPE( dpdo->type ) != CV_32FC1
&& CV_MAT_TYPE( dpdo->type ) != CV_64FC1 )
|| dpdo->rows != count * 2 || dpdo->cols != count * 3 )
CV_Error( CV_StsBadArg, "dp/do must be 2Nx3N floating-point matrix" );
if( CV_MAT_TYPE( dpdo->type ) == CV_64FC1 )
{
_dpdo.reset( cvCloneMat( dpdo ) );
}
else
_dpdo.reset( cvCreateMat( 2 * count, 3 * count, CV_64FC1 ) );
cvZero(_dpdo);
dpdo_p = _dpdo->data.db;
dpdo_step = _dpdo->step / sizeof( dpdo_p[0] );
}
calc_derivatives = dpdr || dpdt || dpdf || dpdc || dpdk || dpdo;
for( i = 0; i < count; i++ )
{
double X = M[i].x, Y = M[i].y, Z = M[i].z;
double x = R[0]*X + R[1]*Y + R[2]*Z + t[0];
double y = R[3]*X + R[4]*Y + R[5]*Z + t[1];
double z = R[6]*X + R[7]*Y + R[8]*Z + t[2];
double r2, r4, r6, a1, a2, a3, cdist, icdist2;
double xd, yd, xd0, yd0, invProj;
Vec3d vecTilt;
Vec3d dVecTilt;
Matx22d dMatTilt;
Vec2d dXdYd;
double z0 = z;
z = z ? 1./z : 1;
x *= z; y *= z;
r2 = x*x + y*y;
r4 = r2*r2;
r6 = r4*r2;
a1 = 2*x*y;
a2 = r2 + 2*x*x;
a3 = r2 + 2*y*y;
cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6;
icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6);
xd0 = x*cdist*icdist2 + k[2]*a1 + k[3]*a2 + k[8]*r2+k[9]*r4;
yd0 = y*cdist*icdist2 + k[2]*a3 + k[3]*a1 + k[10]*r2+k[11]*r4;
// additional distortion by projecting onto a tilt plane
vecTilt = matTilt*Vec3d(xd0, yd0, 1);
invProj = vecTilt(2) ? 1./vecTilt(2) : 1;
xd = invProj * vecTilt(0);
yd = invProj * vecTilt(1);
m[i].x = xd*fx + cx;
m[i].y = yd*fy + cy;
m[i].z = z; // Just put the projected Z coordinate here, we mainly care about the sign
if( calc_derivatives )
{
if( dpdc_p )
{
dpdc_p[0] = 1; dpdc_p[1] = 0; // dp_xdc_x; dp_xdc_y
dpdc_p[dpdc_step] = 0;
dpdc_p[dpdc_step+1] = 1;
dpdc_p += dpdc_step*2;
}
if( dpdf_p )
{
if( fixedAspectRatio )
{
dpdf_p[0] = 0; dpdf_p[1] = xd*aspectRatio; // dp_xdf_x; dp_xdf_y
dpdf_p[dpdf_step] = 0;
dpdf_p[dpdf_step+1] = yd;
}
else
{
dpdf_p[0] = xd; dpdf_p[1] = 0;
dpdf_p[dpdf_step] = 0;
dpdf_p[dpdf_step+1] = yd;
}
dpdf_p += dpdf_step*2;
}
for (int row = 0; row < 2; ++row)
for (int col = 0; col < 2; ++col)
dMatTilt(row,col) = matTilt(row,col)*vecTilt(2)
- matTilt(2,col)*vecTilt(row);
double invProjSquare = (invProj*invProj);
dMatTilt *= invProjSquare;
if( dpdk_p )
{
dXdYd = dMatTilt*Vec2d(x*icdist2*r2, y*icdist2*r2);
dpdk_p[0] = fx*dXdYd(0);
dpdk_p[dpdk_step] = fy*dXdYd(1);
dXdYd = dMatTilt*Vec2d(x*icdist2*r4, y*icdist2*r4);
dpdk_p[1] = fx*dXdYd(0);
dpdk_p[dpdk_step+1] = fy*dXdYd(1);
if( _dpdk->cols > 2 )
{
dXdYd = dMatTilt*Vec2d(a1, a3);
dpdk_p[2] = fx*dXdYd(0);
dpdk_p[dpdk_step+2] = fy*dXdYd(1);
dXdYd = dMatTilt*Vec2d(a2, a1);
dpdk_p[3] = fx*dXdYd(0);
dpdk_p[dpdk_step+3] = fy*dXdYd(1);
if( _dpdk->cols > 4 )
{
dXdYd = dMatTilt*Vec2d(x*icdist2*r6, y*icdist2*r6);
dpdk_p[4] = fx*dXdYd(0);
dpdk_p[dpdk_step+4] = fy*dXdYd(1);
if( _dpdk->cols > 5 )
{
dXdYd = dMatTilt*Vec2d(
x*cdist*(-icdist2)*icdist2*r2, y*cdist*(-icdist2)*icdist2*r2);
dpdk_p[5] = fx*dXdYd(0);
dpdk_p[dpdk_step+5] = fy*dXdYd(1);
dXdYd = dMatTilt*Vec2d(
x*cdist*(-icdist2)*icdist2*r4, y*cdist*(-icdist2)*icdist2*r4);
dpdk_p[6] = fx*dXdYd(0);
dpdk_p[dpdk_step+6] = fy*dXdYd(1);
dXdYd = dMatTilt*Vec2d(
x*cdist*(-icdist2)*icdist2*r6, y*cdist*(-icdist2)*icdist2*r6);
dpdk_p[7] = fx*dXdYd(0);
dpdk_p[dpdk_step+7] = fy*dXdYd(1);
if( _dpdk->cols > 8 )
{
dXdYd = dMatTilt*Vec2d(r2, 0);
dpdk_p[8] = fx*dXdYd(0); //s1
dpdk_p[dpdk_step+8] = fy*dXdYd(1); //s1
dXdYd = dMatTilt*Vec2d(r4, 0);
dpdk_p[9] = fx*dXdYd(0); //s2
dpdk_p[dpdk_step+9] = fy*dXdYd(1); //s2
dXdYd = dMatTilt*Vec2d(0, r2);
dpdk_p[10] = fx*dXdYd(0);//s3
dpdk_p[dpdk_step+10] = fy*dXdYd(1); //s3
dXdYd = dMatTilt*Vec2d(0, r4);
dpdk_p[11] = fx*dXdYd(0);//s4
dpdk_p[dpdk_step+11] = fy*dXdYd(1); //s4
if( _dpdk->cols > 12 )
{
dVecTilt = dMatTiltdTauX * Vec3d(xd0, yd0, 1);
dpdk_p[12] = fx * invProjSquare * (
dVecTilt(0) * vecTilt(2) - dVecTilt(2) * vecTilt(0));
dpdk_p[dpdk_step+12] = fy*invProjSquare * (
dVecTilt(1) * vecTilt(2) - dVecTilt(2) * vecTilt(1));
dVecTilt = dMatTiltdTauY * Vec3d(xd0, yd0, 1);
dpdk_p[13] = fx * invProjSquare * (
dVecTilt(0) * vecTilt(2) - dVecTilt(2) * vecTilt(0));
dpdk_p[dpdk_step+13] = fy * invProjSquare * (
dVecTilt(1) * vecTilt(2) - dVecTilt(2) * vecTilt(1));
}
}
}
}
}
dpdk_p += dpdk_step*2;
}
if( dpdt_p )
{
double dxdt[] = { z, 0, -x*z }, dydt[] = { 0, z, -y*z };
for( j = 0; j < 3; j++ )
{
double dr2dt = 2*x*dxdt[j] + 2*y*dydt[j];
double dcdist_dt = k[0]*dr2dt + 2*k[1]*r2*dr2dt + 3*k[4]*r4*dr2dt;
double dicdist2_dt = -icdist2*icdist2*(k[5]*dr2dt + 2*k[6]*r2*dr2dt + 3*k[7]*r4*dr2dt);
double da1dt = 2*(x*dydt[j] + y*dxdt[j]);
double dmxdt = (dxdt[j]*cdist*icdist2 + x*dcdist_dt*icdist2 + x*cdist*dicdist2_dt +
k[2]*da1dt + k[3]*(dr2dt + 4*x*dxdt[j]) + k[8]*dr2dt + 2*r2*k[9]*dr2dt);
double dmydt = (dydt[j]*cdist*icdist2 + y*dcdist_dt*icdist2 + y*cdist*dicdist2_dt +
k[2]*(dr2dt + 4*y*dydt[j]) + k[3]*da1dt + k[10]*dr2dt + 2*r2*k[11]*dr2dt);
dXdYd = dMatTilt*Vec2d(dmxdt, dmydt);
dpdt_p[j] = fx*dXdYd(0);
dpdt_p[dpdt_step+j] = fy*dXdYd(1);
}
dpdt_p += dpdt_step*2;
}
if( dpdr_p )
{
double dx0dr[] =
{
X*dRdr[0] + Y*dRdr[1] + Z*dRdr[2],
X*dRdr[9] + Y*dRdr[10] + Z*dRdr[11],
X*dRdr[18] + Y*dRdr[19] + Z*dRdr[20]
};
double dy0dr[] =
{
X*dRdr[3] + Y*dRdr[4] + Z*dRdr[5],
X*dRdr[12] + Y*dRdr[13] + Z*dRdr[14],
X*dRdr[21] + Y*dRdr[22] + Z*dRdr[23]
};
double dz0dr[] =
{
X*dRdr[6] + Y*dRdr[7] + Z*dRdr[8],
X*dRdr[15] + Y*dRdr[16] + Z*dRdr[17],
X*dRdr[24] + Y*dRdr[25] + Z*dRdr[26]
};
for( j = 0; j < 3; j++ )
{
double dxdr = z*(dx0dr[j] - x*dz0dr[j]);
double dydr = z*(dy0dr[j] - y*dz0dr[j]);
double dr2dr = 2*x*dxdr + 2*y*dydr;
double dcdist_dr = (k[0] + 2*k[1]*r2 + 3*k[4]*r4)*dr2dr;
double dicdist2_dr = -icdist2*icdist2*(k[5] + 2*k[6]*r2 + 3*k[7]*r4)*dr2dr;
double da1dr = 2*(x*dydr + y*dxdr);
double dmxdr = (dxdr*cdist*icdist2 + x*dcdist_dr*icdist2 + x*cdist*dicdist2_dr +
k[2]*da1dr + k[3]*(dr2dr + 4*x*dxdr) + (k[8] + 2*r2*k[9])*dr2dr);
double dmydr = (dydr*cdist*icdist2 + y*dcdist_dr*icdist2 + y*cdist*dicdist2_dr +
k[2]*(dr2dr + 4*y*dydr) + k[3]*da1dr + (k[10] + 2*r2*k[11])*dr2dr);
dXdYd = dMatTilt*Vec2d(dmxdr, dmydr);
dpdr_p[j] = fx*dXdYd(0);
dpdr_p[dpdr_step+j] = fy*dXdYd(1);
}
dpdr_p += dpdr_step*2;
}
if( dpdo_p )
{
double dxdo[] = { z * ( R[0] - x * z * z0 * R[6] ),
z * ( R[1] - x * z * z0 * R[7] ),
z * ( R[2] - x * z * z0 * R[8] ) };
double dydo[] = { z * ( R[3] - y * z * z0 * R[6] ),
z * ( R[4] - y * z * z0 * R[7] ),
z * ( R[5] - y * z * z0 * R[8] ) };
for( j = 0; j < 3; j++ )
{
double dr2do = 2 * x * dxdo[j] + 2 * y * dydo[j];
double dr4do = 2 * r2 * dr2do;
double dr6do = 3 * r4 * dr2do;
double da1do = 2 * y * dxdo[j] + 2 * x * dydo[j];
double da2do = dr2do + 4 * x * dxdo[j];
double da3do = dr2do + 4 * y * dydo[j];
double dcdist_do
= k[0] * dr2do + k[1] * dr4do + k[4] * dr6do;
double dicdist2_do = -icdist2 * icdist2
* ( k[5] * dr2do + k[6] * dr4do + k[7] * dr6do );
double dxd0_do = cdist * icdist2 * dxdo[j]
+ x * icdist2 * dcdist_do + x * cdist * dicdist2_do
+ k[2] * da1do + k[3] * da2do + k[8] * dr2do
+ k[9] * dr4do;
double dyd0_do = cdist * icdist2 * dydo[j]
+ y * icdist2 * dcdist_do + y * cdist * dicdist2_do
+ k[2] * da3do + k[3] * da1do + k[10] * dr2do
+ k[11] * dr4do;
dXdYd = dMatTilt * Vec2d( dxd0_do, dyd0_do );
dpdo_p[i * 3 + j] = fx * dXdYd( 0 );
dpdo_p[dpdo_step + i * 3 + j] = fy * dXdYd( 1 );
}
dpdo_p += dpdo_step * 2;
}
}
}
if( _m != imagePoints )
cvConvert( _m, imagePoints );
if( _dpdr != dpdr )
cvConvert( _dpdr, dpdr );
if( _dpdt != dpdt )
cvConvert( _dpdt, dpdt );
if( _dpdf != dpdf )
cvConvert( _dpdf, dpdf );
if( _dpdc != dpdc )
cvConvert( _dpdc, dpdc );
if( _dpdk != dpdk )
cvConvert( _dpdk, dpdk );
if( _dpdo != dpdo )
cvConvert( _dpdo, dpdo );
}
static void _cvProjectPoints2( const CvMat* objectPoints,
const CvMat* r_vec,
const CvMat* t_vec,
const CvMat* A,
const CvMat* distCoeffs,
CvMat* imagePoints, CvMat* dpdr,
CvMat* dpdt, CvMat* dpdf,
CvMat* dpdc, CvMat* dpdk,
double aspectRatio )
{
_cvProjectPoints2Internal( objectPoints, r_vec, t_vec, A, distCoeffs, imagePoints, dpdr, dpdt,
dpdf, dpdc, dpdk, NULL, aspectRatio );
} }

View File

@@ -13,7 +13,7 @@
Generate map file for aruco_map nodelet. Generate map file for aruco_map nodelet.
Usage: Usage:
genmap.py <length> <x> <y> <dist_x> <dist_y> [<first>] [<x0>] [<y0>] [--top-left | --bottom-left] [-o <filename>] genmap.py <length> <x> <y> <dist_x> <dist_y> [<first>] [--top-left | --bottom-left]
genmap.py (-h | --help) genmap.py (-h | --help)
Options: Options:
@@ -23,20 +23,15 @@ Options:
<dist_x> Distance between markers along X axis <dist_x> Distance between markers along X axis
<dist_y> Distance between markers along Y axis <dist_y> Distance between markers along Y axis
<first> First marker ID [default: 0] <first> First marker ID [default: 0]
<x0> X coordinate for the first marker [default: 0]
<y0> Y coordinate for the first marker [default: 0]
--top-left First marker is on top-left (default) --top-left First marker is on top-left (default)
--bottom-left First marker is on bottom-left --bottom-left First marker is on bottom-left
-o <filename> Output map file name in the 'map' subdirectory of aruco_pose package
Example: 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 from __future__ import print_function
import sys
from os import path
from docopt import docopt from docopt import docopt
@@ -44,27 +39,20 @@ arguments = docopt(__doc__)
length = float(arguments['<length>']) length = float(arguments['<length>'])
first = int(arguments['<first>'] if arguments['<first>'] is not None else 0) first = int(arguments['<first>'] if arguments['<first>'] is not None else 0)
x0 = float(arguments['<x0>'] if arguments['<x0>'] is not None else 0)
y0 = float(arguments['<y0>'] if arguments['<y0>'] is not None else 0)
markers_x = int(arguments['<x>']) markers_x = int(arguments['<x>'])
markers_y = int(arguments['<y>']) markers_y = int(arguments['<y>'])
dist_x = float(arguments['<dist_x>']) dist_x = float(arguments['<dist_x>'])
dist_y = float(arguments['<dist_y>']) dist_y = float(arguments['<dist_y>'])
bottom_left = arguments['--bottom-left'] bottom_left = arguments['--bottom-left']
if arguments['-o'] is None: max_y = (markers_y - 1) * dist_y
output = sys.stdout
else:
output = open(path.join(path.dirname(__file__), '..', 'map', arguments['-o']), 'w')
max_y = y0 + (markers_y - 1) * dist_y print('# id\tlength\tx\ty\tz\trot_z\trot_y\trot_x')
output.write('# id\tlength\tx\ty\tz\trot_z\trot_y\trot_x\n')
for y in range(markers_y): for y in range(markers_y):
for x in range(markers_x): for x in range(markers_x):
pos_x = x0 + x * dist_x pos_x = x * dist_x
pos_y = y0 + y * dist_y pos_y = y * dist_y
if not bottom_left: if not bottom_left:
pos_y = max_y - pos_y pos_y = max_y - pos_y
output.write('{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\n'.format(first, length, pos_x, pos_y, 0, 0, 0, 0)) print('{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t'.format(first, length, pos_x, pos_y, 0, 0, 0, 0))
first += 1 first += 1

View File

@@ -35,7 +35,9 @@ static void parseCameraInfo(const sensor_msgs::CameraInfoConstPtr& cinfo, cv::Ma
for (unsigned int i = 0; i < 3; ++i) for (unsigned int i = 0; i < 3; ++i)
for (unsigned int j = 0; j < 3; ++j) for (unsigned int j = 0; j < 3; ++j)
matrix.at<double>(i, j) = cinfo->K[3 * i + j]; matrix.at<double>(i, j) = cinfo->K[3 * i + j];
dist = cv::Mat(cinfo->D, true);
for (unsigned int k = 0; k < cinfo->D.size(); k++)
dist.at<double>(k) = cinfo->D[k];
} }
inline void rotatePoint(cv::Point3f& p, cv::Point3f origin, float angle) inline void rotatePoint(cv::Point3f& p, cv::Point3f origin, float angle)
@@ -106,25 +108,26 @@ inline bool isFlipped(tf::Quaternion& q)
return (abs(pitch) > M_PI / 2) || (abs(roll) > M_PI / 2); return (abs(pitch) > M_PI / 2) || (abs(roll) > M_PI / 2);
} }
/* Apply a vertical to an orientation */ /* Set roll and pitch from "from" to "to", keeping yaw */
inline void applyVertical(geometry_msgs::Quaternion& orientation, const geometry_msgs::Quaternion& vertical, inline void snapOrientation(geometry_msgs::Quaternion& to, const geometry_msgs::Quaternion& from, bool auto_flip = false)
bool flip_vertical = false, bool auto_flip = false) // editorconfig-checker-disable-line
{ {
tf::Quaternion _vertical, _orientation; tf::Quaternion _from, _to;
tf::quaternionMsgToTF(vertical, _vertical); tf::quaternionMsgToTF(from, _from);
tf::quaternionMsgToTF(orientation, _orientation); tf::quaternionMsgToTF(to, _to);
if (flip_vertical || (auto_flip && !isFlipped(_orientation))) { if (auto_flip) {
static const tf::Quaternion flip = tf::createQuaternionFromRPY(M_PI, 0, 0); if (!isFlipped(_from)) {
_vertical *= flip; // flip vertical static const tf::Quaternion flip = tf::createQuaternionFromRPY(M_PI, 0, 0);
_from *= flip; // flip "from"
}
} }
auto diff = tf::Matrix3x3(_orientation).transposeTimes(tf::Matrix3x3(_vertical)); auto diff = tf::Matrix3x3(_to).transposeTimes(tf::Matrix3x3(_from));
double _, yaw; double _, yaw;
diff.getRPY(_, _, yaw); diff.getRPY(_, _, yaw);
auto q = tf::createQuaternionFromRPY(0, 0, -yaw); auto q = tf::createQuaternionFromRPY(0, 0, -yaw);
_vertical = _vertical * q; // set yaw from orientation to vertical _from = _from * q; // set yaw from "to" to "from"
tf::quaternionTFToMsg(_vertical, orientation); // set vertical to orientation tf::quaternionTFToMsg(_from, to); // set "from" to "to"
} }
inline void transformToPose(const geometry_msgs::Transform& transform, geometry_msgs::Pose& pose) inline void transformToPose(const geometry_msgs::Transform& transform, geometry_msgs::Pose& pose)

View File

@@ -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

View File

@@ -6,7 +6,7 @@ import tf2_geometry_msgs
from geometry_msgs.msg import PoseWithCovarianceStamped from geometry_msgs.msg import PoseWithCovarianceStamped
from sensor_msgs.msg import Image from sensor_msgs.msg import Image
from aruco_pose.msg import MarkerArray 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 @pytest.fixture
@@ -143,7 +143,7 @@ def test_map_image(node):
assert img.encoding in ('mono8', 'rgb8') assert img.encoding in ('mono8', 'rgb8')
def test_map_markers(node): 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[0].id == 1
assert markers.markers[1].id == 2 assert markers.markers[1].id == 2
assert markers.markers[2].id == 3 assert markers.markers[2].id == 3
@@ -199,36 +199,6 @@ def test_map_markers(node):
def test_map_visualization(node): def test_map_visualization(node):
vis = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5) 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): def test_map_debug(node):
img = rospy.wait_for_message('aruco_map/debug', Image, timeout=5) img = rospy.wait_for_message('aruco_map/debug', Image, timeout=5)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

View File

@@ -1,18 +0,0 @@
import rospy
import pytest
from visualization_msgs.msg import MarkerArray as VisMarkerArray
@pytest.fixture
def node():
return rospy.init_node('aruco_pose_opencv_crash', anonymous=True)
def test_opencv_crashes_img01(node):
rospy.wait_for_message('aruco_detect_01/visualization', VisMarkerArray, timeout=5)
def test_opencv_crashes_img02(node):
rospy.wait_for_message('aruco_detect_02/visualization', VisMarkerArray, timeout=5)
def test_opencv_crashes_img03(node):
rospy.wait_for_message('aruco_detect_03/visualization', VisMarkerArray, timeout=5)

View File

@@ -1,51 +0,0 @@
<launch>
<arg name="corner_method" default="2"/>
<node pkg="image_publisher" type="image_publisher" name="imgpub_01" args="$(find aruco_pose)/test/crash_image_01.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="image_publisher" type="image_publisher" name="imgpub_02" args="$(find aruco_pose)/test/crash_image_02.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="image_publisher" type="image_publisher" name="imgpub_03" args="$(find aruco_pose)/test/crash_image_03.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_01" args="manager"/>
<node pkg="nodelet" clear_params="true" type="nodelet" name="aruco_detect_01" args="load aruco_pose/aruco_detect nodelet_manager_01">
<remap from="image_raw" to="imgpub_01/image_raw"/>
<remap from="camera_info" to="imgpub_01/camera_info"/>
<param name="length" value="0.33"/>
<param name="cornerRefinementMethod" value="$(arg corner_method)"/>
</node>
<node pkg="nodelet" type="nodelet" name="nodelet_manager_02" args="manager"/>
<node pkg="nodelet" clear_params="true" type="nodelet" name="aruco_detect_02" args="load aruco_pose/aruco_detect nodelet_manager_02">
<remap from="image_raw" to="imgpub_02/image_raw"/>
<remap from="camera_info" to="imgpub_02/camera_info"/>
<param name="length" value="0.33"/>
<param name="cornerRefinementMethod" value="$(arg corner_method)"/>
</node>
<node pkg="nodelet" type="nodelet" name="nodelet_manager_03" args="manager"/>
<node pkg="nodelet" clear_params="true" type="nodelet" name="aruco_detect_03" args="load aruco_pose/aruco_detect nodelet_manager_03">
<remap from="image_raw" to="imgpub_03/image_raw"/>
<remap from="camera_info" to="imgpub_03/camera_info"/>
<param name="length" value="0.33"/>
<param name="cornerRefinementMethod" value="$(arg corner_method)"/>
</node>
<param name="test_module" value="$(find aruco_pose)/test/crash_opencv.py"/>
<test test-name="crash_opencv" pkg="ros_pytest" type="ros_pytest_runner"/>
</launch>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -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'

View File

@@ -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>

View File

@@ -2,7 +2,6 @@ import rospy
import pytest import pytest
from visualization_msgs.msg import MarkerArray as VisMarkerArray from visualization_msgs.msg import MarkerArray as VisMarkerArray
from aruco_pose.msg import MarkerArray
@pytest.fixture @pytest.fixture
@@ -10,5 +9,5 @@ def node():
return rospy.init_node('aruco_pose_test', anonymous=True) return rospy.init_node('aruco_pose_test', anonymous=True)
def test_node_failure(node): def test_node_failure(node):
assert rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5).markers == [] with pytest.raises(rospy.exceptions.ROSException):
assert rospy.wait_for_message('aruco_map/map', MarkerArray, timeout=5).markers == [] rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)

View File

@@ -7,7 +7,6 @@ endif()
message(STATUS "Adding vendored aruco_pose OpenCV module") message(STATUS "Adding vendored aruco_pose OpenCV module")
add_library(_opencv_aruco STATIC add_library(_opencv_aruco STATIC
vendor/aruco/src/apriltag_quad_thresh.cpp
vendor/aruco/src/aruco.cpp vendor/aruco/src/aruco.cpp
vendor/aruco/src/charuco.cpp vendor/aruco/src/charuco.cpp
vendor/aruco/src/dictionary.cpp vendor/aruco/src/dictionary.cpp
@@ -24,7 +23,7 @@ target_compile_definitions(_opencv_aruco PRIVATE
CV_OVERRIDE=override CV_OVERRIDE=override
) )
target_compile_options(_opencv_aruco PRIVATE target_compile_options(_opencv_aruco PRIVATE
-fpic -fPIC -fvisibility=hidden -fpic -fPIC
) )
target_include_directories(_opencv_aruco PUBLIC target_include_directories(_opencv_aruco PUBLIC

View File

@@ -94,7 +94,7 @@ void ptsort_(struct pt *pts, int sz)
// Use stack storage if it's not too big. // Use stack storage if it's not too big.
cv::AutoBuffer<struct pt, 1024> _tmp_stack(sz); cv::AutoBuffer<struct pt, 1024> _tmp_stack(sz);
memcpy(_tmp_stack, pts, sizeof(struct pt) * sz); memcpy(_tmp_stack.data(), pts, sizeof(struct pt) * sz);
int asz = sz/2; int asz = sz/2;
int bsz = sz - asz; int bsz = sz - asz;
@@ -470,11 +470,11 @@ int quad_segment_agg(int sz, struct line_fit_pt *lfps, int indices[4]){
int rvalloc_pos = 0; int rvalloc_pos = 0;
int rvalloc_size = 3*sz; int rvalloc_size = 3*sz;
cv::AutoBuffer<struct remove_vertex, 0> rvalloc_(std::max(1, rvalloc_size)); cv::AutoBuffer<struct remove_vertex, 0> rvalloc_(std::max(1, rvalloc_size));
memset(rvalloc_, 0, sizeof(rvalloc_[0]) * rvalloc_.size()); // TODO Add AutoBuffer zero fill memset(rvalloc_.data(), 0, sizeof(rvalloc_[0]) * rvalloc_.size()); // TODO Add AutoBuffer zero fill
struct remove_vertex *rvalloc = rvalloc_; struct remove_vertex *rvalloc = rvalloc_.data();
cv::AutoBuffer<struct segment, 0> segs_(std::max(1, sz)); // TODO Add AutoBuffer zero fill cv::AutoBuffer<struct segment, 0> segs_(std::max(1, sz)); // TODO Add AutoBuffer zero fill
memset(segs_, 0, sizeof(segs_[0]) * segs_.size()); memset(segs_.data(), 0, sizeof(segs_[0]) * segs_.size());
struct segment *segs = segs_; struct segment *segs = segs_.data();
// populate with initial entries // populate with initial entries
for (int i = 0; i < sz; i++) { for (int i = 0; i < sz; i++) {
@@ -753,8 +753,8 @@ int fit_quad(const Ptr<DetectorParameters> &_params, const Mat im, zarray_t *clu
// efficiently computed for any contiguous range of indices. // efficiently computed for any contiguous range of indices.
cv::AutoBuffer<struct line_fit_pt, 64> lfps_(sz); cv::AutoBuffer<struct line_fit_pt, 64> lfps_(sz);
memset(lfps_, 0, sizeof(lfps_[0]) * lfps_.size()); // TODO Add AutoBuffer zero fill memset(lfps_.data(), 0, sizeof(lfps_[0]) * lfps_.size()); // TODO Add AutoBuffer zero fill
struct line_fit_pt *lfps = lfps_; struct line_fit_pt *lfps = lfps_.data();
for (int i = 0; i < sz; i++) { for (int i = 0; i < sz; i++) {
struct pt *p; struct pt *p;

View File

@@ -924,8 +924,6 @@ static void _refineCandidateLines(std::vector<Point>& nContours, std::vector<Poi
// calculate the line :: who passes through the grouped points // calculate the line :: who passes through the grouped points
Point3f lines[4]; Point3f lines[4];
for(int i=0; i<4; i++){ for(int i=0; i<4; i++){
// Don't try to "interpolate" single points
if (cntPts[i].size() < 2) return;
lines[i]=_interpolate2Dline(cntPts[i]); lines[i]=_interpolate2Dline(cntPts[i]);
} }

View File

@@ -9,8 +9,7 @@
"richquotes@https://github.com/okalachev/gitbook-plugin-richquotes.git", "richquotes@https://github.com/okalachev/gitbook-plugin-richquotes.git",
"yametrika", "yametrika",
"anchors", "anchors",
"collapsible-menu", "validate-links",
"validate-links@https://github.com/okalachev/gitbook-plugin-validate-links.git",
"bulk-redirect@https://github.com/okalachev/gitbook-plugin-bulk-redirect.git", "bulk-redirect@https://github.com/okalachev/gitbook-plugin-bulk-redirect.git",
"sitemap@https://github.com/okalachev/plugin-sitemap.git", "sitemap@https://github.com/okalachev/plugin-sitemap.git",
"toolbar@https://github.com/hamishwillee/gitbook-plugin-toolbar.git", "toolbar@https://github.com/hamishwillee/gitbook-plugin-toolbar.git",

View File

@@ -1,34 +0,0 @@
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<!--
This file is part of avahi.
avahi is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2 of the
License, or (at your option) any later version.
avahi is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with avahi; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
-->
<!-- See avahi.service(5) for more information about this configuration file -->
<service-group>
<name replace-wildcards="yes">%h</name>
<service>
<type>_sftp-ssh._tcp</type>
<port>22</port>
</service>
</service-group>

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
from distutils.core import setup from distutils.core import setup

View File

@@ -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 \ ROS_HOSTNAME=`hostname`.local exec stdbuf -o L roslaunch clover clover.launch --wait --screen --skip-log-check \
2> >(tee /tmp/clover.err)" 2> >(tee /tmp/clover.err)"
ExecStartPre=+rm /var/log/clover.log
StandardOutput=file:/var/log/clover.log
StandardError=file:/var/log/clover.log
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -15,17 +15,17 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates) set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger) 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) navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True)
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) rospy.sleep(3)
print('Fly forward 1 m') # Fly forward 1 m
navigate(x=1, y=0, z=0, frame_id='body') navigate(x=1, y=0, z=0, frame_id='body')
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) rospy.sleep(3)
print('Perform landing') # Perform landing
land() land()

View File

@@ -1,4 +1,4 @@
# Information: https://clover.coex.tech/aruco # Information: https://clover.coex.tech/en/aruco.html
import rospy import rospy
from clover import srv from clover import srv
@@ -15,23 +15,23 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates) set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger) 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) navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True)
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) 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') navigate(x=0, y=0, z=1, frame_id='aruco_0')
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) 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') navigate(x=1, y=1, z=1, frame_id='aruco_map')
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) rospy.sleep(3)
print('Perform landing') # Perform landing
land() land()

View File

@@ -1,4 +1,4 @@
# Information: https://clover.coex.tech/led # Information: https://clover.coex.tech/en/leds.html
import rospy import rospy
from clover.srv import SetLEDEffect from clover.srv import SetLEDEffect
@@ -7,25 +7,19 @@ rospy.init_node('leds')
set_effect = rospy.ServiceProxy('led/set_effect', SetLEDEffect) # define proxy to ROS-service set_effect = rospy.ServiceProxy('led/set_effect', SetLEDEffect) # define proxy to ROS-service
print('Fill red')
set_effect(r=255, g=0, b=0) # fill strip with red color set_effect(r=255, g=0, b=0) # fill strip with red color
rospy.sleep(2) rospy.sleep(2)
print('Fill green')
set_effect(r=0, g=100, b=0) # fill strip with green color set_effect(r=0, g=100, b=0) # fill strip with green color
rospy.sleep(2) rospy.sleep(2)
print('Fade to blue')
set_effect(effect='fade', r=0, g=0, b=255) # fade to blue color set_effect(effect='fade', r=0, g=0, b=255) # fade to blue color
rospy.sleep(2) rospy.sleep(2)
print('Flash red')
set_effect(effect='flash', r=255, g=0, b=0) # flash twice with red color set_effect(effect='flash', r=255, g=0, b=0) # flash twice with red color
rospy.sleep(2) rospy.sleep(5)
print('Blink white')
set_effect(effect='blink', r=255, g=255, b=255) # blink with white color set_effect(effect='blink', r=255, g=255, b=255) # blink with white color
rospy.sleep(5) rospy.sleep(5)
print('Rainbow')
set_effect(effect='rainbow') # show rainbow set_effect(effect='rainbow') # show rainbow

View File

@@ -1,4 +1,4 @@
# Information: https://clover.coex.tech/en/snippets.html#navigate_wait # Information: https://clover.coex.tech/en/snippets.html#block-nav
import math import math
import rospy import rospy
@@ -16,8 +16,11 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates) set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger) land = rospy.ServiceProxy('land', Trigger)
def navigate_wait(x=0, y=0, z=0, yaw=math.nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False): def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \
res = navigate(x=x, y=y, z=z, yaw=yaw, speed=speed, frame_id=frame_id, auto_arm=auto_arm) 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: if not res.success:
return res return res
@@ -28,11 +31,11 @@ def navigate_wait(x=0, y=0, z=0, yaw=math.nan, speed=0.5, frame_id='body', toler
return res return res
rospy.sleep(0.2) rospy.sleep(0.2)
print('Take off 1 meter') # Take off 1 meter
navigate_wait(z=1, frame_id='body', auto_arm=True) 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') navigate_wait(x=1, frame_id='body')
print('Land') # Land
land() land()

View File

@@ -65,7 +65,7 @@ echo_stamp "#6 Turn on UART"
# https://github.com/RPi-Distro/raspi-config/pull/75 # https://github.com/RPi-Distro/raspi-config/pull/75
/usr/bin/raspi-config nonint do_serial 1 /usr/bin/raspi-config nonint do_serial 1
/usr/bin/raspi-config nonint set_config_var enable_uart 1 /boot/config.txt /usr/bin/raspi-config nonint set_config_var enable_uart 1 /boot/config.txt
echo dtoverlay=pi3-disable-bt >> /boot/config.txt /usr/bin/raspi-config nonint set_config_var dtoverlay pi3-disable-bt /boot/config.txt
systemctl disable hciuart.service systemctl disable hciuart.service
# After adding to Raspbian OS # After adding to Raspbian OS

View File

@@ -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

View File

@@ -20,7 +20,7 @@
# Example: # Example:
# DocumentRoot /home/krypton/htdocs # DocumentRoot /home/krypton/htdocs
DocumentRoot /home/pi/.ros/www DocumentRoot /home/pi/catkin_ws/src/clover/clover/www
# Redirect: # Redirect:
# --------- # ---------

View File

@@ -1,741 +0,0 @@
async_web_server_cpp:
debian:
buster: [ros-noetic-async-web-server-cpp]
led_msgs:
debian:
buster: [ros-noetic-led-msgs]
ros_pytest:
debian:
buster: [ros-noetic-ros-pytest]
tf2_web_republisher:
debian:
buster: [ros-noetic-tf2-web-republisher]
web_video_server:
debian:
buster: [ros-noetic-web-video-server]
ws281x:
debian:
buster: [ros-noetic-ws281x]
catkin:
debian:
buster: [ros-noetic-catkin]
genmsg:
debian:
buster: [ros-noetic-genmsg]
gencpp:
debian:
buster: [ros-noetic-gencpp]
geneus:
debian:
buster: [ros-noetic-geneus]
genlisp:
debian:
buster: [ros-noetic-genlisp]
gennodejs:
debian:
buster: [ros-noetic-gennodejs]
genpy:
debian:
buster: [ros-noetic-genpy]
bond_core:
debian:
buster: [ros-noetic-bond-core]
cmake_modules:
debian:
buster: [ros-noetic-cmake-modules]
class_loader:
debian:
buster: [ros-noetic-class-loader]
common_msgs:
debian:
buster: [ros-noetic-common-msgs]
common_tutorials:
debian:
buster: [ros-noetic-common-tutorials]
cpp_common:
debian:
buster: [ros-noetic-cpp-common]
desktop:
debian:
buster: [ros-noetic-desktop]
diagnostics:
debian:
buster: [ros-noetic-diagnostics]
executive_smach:
debian:
buster: [ros-noetic-executive-smach]
geometry:
debian:
buster: [ros-noetic-geometry]
geometry_tutorials:
debian:
buster: [ros-noetic-geometry-tutorials]
gl_dependency:
debian:
buster: [ros-noetic-gl-dependency]
image_common:
debian:
buster: [ros-noetic-image-common]
image_pipeline:
debian:
buster: [ros-noetic-image-pipeline]
image_transport_plugins:
debian:
buster: [ros-noetic-image-transport-plugins]
laser_pipeline:
debian:
buster: [ros-noetic-laser-pipeline]
mavlink:
debian:
buster: [ros-noetic-mavlink]
media_export:
debian:
buster: [ros-noetic-media-export]
message_generation:
debian:
buster: [ros-noetic-message-generation]
message_runtime:
debian:
buster: [ros-noetic-message-runtime]
mk:
debian:
buster: [ros-noetic-mk]
nodelet_core:
debian:
buster: [ros-noetic-nodelet-core]
orocos_kdl:
debian:
buster: [ros-noetic-orocos-kdl]
perception:
debian:
buster: [ros-noetic-perception]
perception_pcl:
debian:
buster: [ros-noetic-perception-pcl]
python_orocos_kdl:
debian:
buster: [ros-noetic-python-orocos-kdl]
qt_dotgraph:
debian:
buster: [ros-noetic-qt-dotgraph]
qt_gui:
debian:
buster: [ros-noetic-qt-gui]
qt_gui_py_common:
debian:
buster: [ros-noetic-qt-gui-py-common]
qwt_dependency:
debian:
buster: [ros-noetic-qwt-dependency]
robot:
debian:
buster: [ros-noetic-robot]
ros:
debian:
buster: [ros-noetic-ros]
ros_base:
debian:
buster: [ros-noetic-ros-base]
ros_comm:
debian:
buster: [ros-noetic-ros-comm]
ros_core:
debian:
buster: [ros-noetic-ros-core]
ros_environment:
debian:
buster: [ros-noetic-ros-environment]
ros_tutorials:
debian:
buster: [ros-noetic-ros-tutorials]
rosapi:
debian:
buster: [ros-noetic-rosapi]
rosbag_migration_rule:
debian:
buster: [ros-noetic-rosbag-migration-rule]
rosbash:
debian:
buster: [ros-noetic-rosbash]
rosboost_cfg:
debian:
buster: [ros-noetic-rosboost-cfg]
rosbridge_server:
debian:
buster: [ros-noetic-rosbridge-server]
rosbridge_suite:
debian:
buster: [ros-noetic-rosbridge-suite]
rosbuild:
debian:
buster: [ros-noetic-rosbuild]
rosclean:
debian:
buster: [ros-noetic-rosclean]
roscpp_core:
debian:
buster: [ros-noetic-roscpp-core]
roscpp_traits:
debian:
buster: [ros-noetic-roscpp-traits]
roscreate:
debian:
buster: [ros-noetic-roscreate]
rosgraph:
debian:
buster: [ros-noetic-rosgraph]
roslang:
debian:
buster: [ros-noetic-roslang]
roslint:
debian:
buster: [ros-noetic-roslint]
roslisp:
debian:
buster: [ros-noetic-roslisp]
rosmake:
debian:
buster: [ros-noetic-rosmake]
rosmaster:
debian:
buster: [ros-noetic-rosmaster]
rospack:
debian:
buster: [ros-noetic-rospack]
roslib:
debian:
buster: [ros-noetic-roslib]
rosparam:
debian:
buster: [ros-noetic-rosparam]
rospy:
debian:
buster: [ros-noetic-rospy]
rosserial:
debian:
buster: [ros-noetic-rosserial]
rosserial_msgs:
debian:
buster: [ros-noetic-rosserial-msgs]
rosserial_python:
debian:
buster: [ros-noetic-rosserial-python]
rosservice:
debian:
buster: [ros-noetic-rosservice]
rostime:
debian:
buster: [ros-noetic-rostime]
roscpp_serialization:
debian:
buster: [ros-noetic-roscpp-serialization]
python_qt_binding:
debian:
buster: [ros-noetic-python-qt-binding]
roslaunch:
debian:
buster: [ros-noetic-roslaunch]
rosunit:
debian:
buster: [ros-noetic-rosunit]
angles:
debian:
buster: [ros-noetic-angles]
libmavconn:
debian:
buster: [ros-noetic-libmavconn]
rosconsole:
debian:
buster: [ros-noetic-rosconsole]
pluginlib:
debian:
buster: [ros-noetic-pluginlib]
qt_gui_cpp:
debian:
buster: [ros-noetic-qt-gui-cpp]
resource_retriever:
debian:
buster: [ros-noetic-resource-retriever]
rosconsole_bridge:
debian:
buster: [ros-noetic-rosconsole-bridge]
roslz4:
debian:
buster: [ros-noetic-roslz4]
rosserial_client:
debian:
buster: [ros-noetic-rosserial-client]
rostest:
debian:
buster: [ros-noetic-rostest]
rqt_action:
debian:
buster: [ros-noetic-rqt-action]
rqt_bag:
debian:
buster: [ros-noetic-rqt-bag]
rqt_bag_plugins:
debian:
buster: [ros-noetic-rqt-bag-plugins]
rqt_common_plugins:
debian:
buster: [ros-noetic-rqt-common-plugins]
rqt_console:
debian:
buster: [ros-noetic-rqt-console]
rqt_dep:
debian:
buster: [ros-noetic-rqt-dep]
rqt_graph:
debian:
buster: [ros-noetic-rqt-graph]
rqt_gui:
debian:
buster: [ros-noetic-rqt-gui]
rqt_logger_level:
debian:
buster: [ros-noetic-rqt-logger-level]
rqt_moveit:
debian:
buster: [ros-noetic-rqt-moveit]
rqt_msg:
debian:
buster: [ros-noetic-rqt-msg]
rqt_nav_view:
debian:
buster: [ros-noetic-rqt-nav-view]
rqt_plot:
debian:
buster: [ros-noetic-rqt-plot]
rqt_pose_view:
debian:
buster: [ros-noetic-rqt-pose-view]
rqt_publisher:
debian:
buster: [ros-noetic-rqt-publisher]
rqt_py_console:
debian:
buster: [ros-noetic-rqt-py-console]
rqt_reconfigure:
debian:
buster: [ros-noetic-rqt-reconfigure]
rqt_robot_dashboard:
debian:
buster: [ros-noetic-rqt-robot-dashboard]
rqt_robot_monitor:
debian:
buster: [ros-noetic-rqt-robot-monitor]
rqt_robot_plugins:
debian:
buster: [ros-noetic-rqt-robot-plugins]
rqt_robot_steering:
debian:
buster: [ros-noetic-rqt-robot-steering]
rqt_runtime_monitor:
debian:
buster: [ros-noetic-rqt-runtime-monitor]
rqt_service_caller:
debian:
buster: [ros-noetic-rqt-service-caller]
rqt_shell:
debian:
buster: [ros-noetic-rqt-shell]
rqt_srv:
debian:
buster: [ros-noetic-rqt-srv]
rqt_tf_tree:
debian:
buster: [ros-noetic-rqt-tf-tree]
rqt_top:
debian:
buster: [ros-noetic-rqt-top]
rqt_topic:
debian:
buster: [ros-noetic-rqt-topic]
rqt_web:
debian:
buster: [ros-noetic-rqt-web]
smach:
debian:
buster: [ros-noetic-smach]
smclib:
debian:
buster: [ros-noetic-smclib]
std_msgs:
debian:
buster: [ros-noetic-std-msgs]
actionlib_msgs:
debian:
buster: [ros-noetic-actionlib-msgs]
bond:
debian:
buster: [ros-noetic-bond]
diagnostic_msgs:
debian:
buster: [ros-noetic-diagnostic-msgs]
geometry_msgs:
debian:
buster: [ros-noetic-geometry-msgs]
eigen_conversions:
debian:
buster: [ros-noetic-eigen-conversions]
kdl_conversions:
debian:
buster: [ros-noetic-kdl-conversions]
nav_msgs:
debian:
buster: [ros-noetic-nav-msgs]
rosbridge_msgs:
debian:
buster: [ros-noetic-rosbridge-msgs]
rosgraph_msgs:
debian:
buster: [ros-noetic-rosgraph-msgs]
rosmsg:
debian:
buster: [ros-noetic-rosmsg]
rqt_py_common:
debian:
buster: [ros-noetic-rqt-py-common]
shape_msgs:
debian:
buster: [ros-noetic-shape-msgs]
smach_msgs:
debian:
buster: [ros-noetic-smach-msgs]
std_srvs:
debian:
buster: [ros-noetic-std-srvs]
tf2_msgs:
debian:
buster: [ros-noetic-tf2-msgs]
tf2:
debian:
buster: [ros-noetic-tf2]
tf2_eigen:
debian:
buster: [ros-noetic-tf2-eigen]
trajectory_msgs:
debian:
buster: [ros-noetic-trajectory-msgs]
control_msgs:
debian:
buster: [ros-noetic-control-msgs]
urdf_parser_plugin:
debian:
buster: [ros-noetic-urdf-parser-plugin]
urdfdom_py:
debian:
buster: [ros-noetic-urdfdom-py]
uuid_msgs:
debian:
buster: [ros-noetic-uuid-msgs]
geographic_msgs:
debian:
buster: [ros-noetic-geographic-msgs]
vision_opencv:
debian:
buster: [ros-noetic-vision-opencv]
visualization_msgs:
debian:
buster: [ros-noetic-visualization-msgs]
visualization_tutorials:
debian:
buster: [ros-noetic-visualization-tutorials]
viz:
debian:
buster: [ros-noetic-viz]
webkit_dependency:
debian:
buster: [ros-noetic-webkit-dependency]
xmlrpcpp:
debian:
buster: [ros-noetic-xmlrpcpp]
roscpp:
debian:
buster: [ros-noetic-roscpp]
bondcpp:
debian:
buster: [ros-noetic-bondcpp]
bondpy:
debian:
buster: [ros-noetic-bondpy]
nodelet:
debian:
buster: [ros-noetic-nodelet]
nodelet_tutorial_math:
debian:
buster: [ros-noetic-nodelet-tutorial-math]
pluginlib_tutorials:
debian:
buster: [ros-noetic-pluginlib-tutorials]
roscpp_tutorials:
debian:
buster: [ros-noetic-roscpp-tutorials]
rosout:
debian:
buster: [ros-noetic-rosout]
camera_calibration:
debian:
buster: [ros-noetic-camera-calibration]
diagnostic_aggregator:
debian:
buster: [ros-noetic-diagnostic-aggregator]
diagnostic_updater:
debian:
buster: [ros-noetic-diagnostic-updater]
diagnostic_common_diagnostics:
debian:
buster: [ros-noetic-diagnostic-common-diagnostics]
dynamic_reconfigure:
debian:
buster: [ros-noetic-dynamic-reconfigure]
filters:
debian:
buster: [ros-noetic-filters]
joint_state_publisher:
debian:
buster: [ros-noetic-joint-state-publisher]
message_filters:
debian:
buster: [ros-noetic-message-filters]
rosauth:
debian:
buster: [ros-noetic-rosauth]
rosbag_storage:
debian:
buster: [ros-noetic-rosbag-storage]
rosnode:
debian:
buster: [ros-noetic-rosnode]
rospy_tutorials:
debian:
buster: [ros-noetic-rospy-tutorials]
rosshow:
debian:
buster: [ros-noetic-rosshow]
rostopic:
debian:
buster: [ros-noetic-rostopic]
rqt_gui_cpp:
debian:
buster: [ros-noetic-rqt-gui-cpp]
rqt_gui_py:
debian:
buster: [ros-noetic-rqt-gui-py]
self_test:
debian:
buster: [ros-noetic-self-test]
smach_ros:
debian:
buster: [ros-noetic-smach-ros]
tf2_py:
debian:
buster: [ros-noetic-tf2-py]
topic_tools:
debian:
buster: [ros-noetic-topic-tools]
rosbag:
debian:
buster: [ros-noetic-rosbag]
actionlib:
debian:
buster: [ros-noetic-actionlib]
actionlib_tutorials:
debian:
buster: [ros-noetic-actionlib-tutorials]
diagnostic_analysis:
debian:
buster: [ros-noetic-diagnostic-analysis]
nodelet_topic_tools:
debian:
buster: [ros-noetic-nodelet-topic-tools]
roswtf:
debian:
buster: [ros-noetic-roswtf]
rqt_launch:
debian:
buster: [ros-noetic-rqt-launch]
sensor_msgs:
debian:
buster: [ros-noetic-sensor-msgs]
camera_calibration_parsers:
debian:
buster: [ros-noetic-camera-calibration-parsers]
cv_bridge:
debian:
buster: [ros-noetic-cv-bridge]
image_geometry:
debian:
buster: [ros-noetic-image-geometry]
image_transport:
debian:
buster: [ros-noetic-image-transport]
camera_info_manager:
debian:
buster: [ros-noetic-camera-info-manager]
compressed_depth_image_transport:
debian:
buster: [ros-noetic-compressed-depth-image-transport]
compressed_image_transport:
debian:
buster: [ros-noetic-compressed-image-transport]
cv_camera:
debian:
buster: [ros-noetic-cv-camera]
image_proc:
debian:
buster: [ros-noetic-image-proc]
image_publisher:
debian:
buster: [ros-noetic-image-publisher]
map_msgs:
debian:
buster: [ros-noetic-map-msgs]
mavros_msgs:
debian:
buster: [ros-noetic-mavros-msgs]
pcl_msgs:
debian:
buster: [ros-noetic-pcl-msgs]
pcl_conversions:
debian:
buster: [ros-noetic-pcl-conversions]
polled_camera:
debian:
buster: [ros-noetic-polled-camera]
rqt_image_view:
debian:
buster: [ros-noetic-rqt-image-view]
stereo_msgs:
debian:
buster: [ros-noetic-stereo-msgs]
image_view:
debian:
buster: [ros-noetic-image-view]
rosbridge_library:
debian:
buster: [ros-noetic-rosbridge-library]
stereo_image_proc:
debian:
buster: [ros-noetic-stereo-image-proc]
tf2_ros:
debian:
buster: [ros-noetic-tf2-ros]
depth_image_proc:
debian:
buster: [ros-noetic-depth-image-proc]
mavros:
debian:
buster: [ros-noetic-mavros]
tf:
debian:
buster: [ros-noetic-tf]
interactive_markers:
debian:
buster: [ros-noetic-interactive-markers]
interactive_marker_tutorials:
debian:
buster: [ros-noetic-interactive-marker-tutorials]
laser_geometry:
debian:
buster: [ros-noetic-laser-geometry]
laser_assembler:
debian:
buster: [ros-noetic-laser-assembler]
laser_filters:
debian:
buster: [ros-noetic-laser-filters]
pcl_ros:
debian:
buster: [ros-noetic-pcl-ros]
tf2_geometry_msgs:
debian:
buster: [ros-noetic-tf2-geometry-msgs]
image_rotate:
debian:
buster: [ros-noetic-image-rotate]
tf2_kdl:
debian:
buster: [ros-noetic-tf2-kdl]
tf2_web_republisher:
debian:
buster: [ros-noetic-tf2-web-republisher]
tf_conversions:
debian:
buster: [ros-noetic-tf-conversions]
theora_image_transport:
debian:
buster: [ros-noetic-theora-image-transport]
turtlesim:
debian:
buster: [ros-noetic-turtlesim]
turtle_actionlib:
debian:
buster: [ros-noetic-turtle-actionlib]
turtle_tf:
debian:
buster: [ros-noetic-turtle-tf]
turtle_tf2:
debian:
buster: [ros-noetic-turtle-tf2]
urdf:
debian:
buster: [ros-noetic-urdf]
kdl_parser:
debian:
buster: [ros-noetic-kdl-parser]
kdl_parser_py:
debian:
buster: [ros-noetic-kdl-parser-py]
mavros_extras:
debian:
buster: [ros-noetic-mavros-extras]
robot_state_publisher:
debian:
buster: [ros-noetic-robot-state-publisher]
rviz:
debian:
buster: [ros-noetic-rviz]
librviz_tutorial:
debian:
buster: [ros-noetic-librviz-tutorial]
rqt_rviz:
debian:
buster: [ros-noetic-rqt-rviz]
rviz_plugin_tutorials:
debian:
buster: [ros-noetic-rviz-plugin-tutorials]
rviz_python_tutorial:
debian:
buster: [ros-noetic-rviz-python-tutorial]
urdf_tutorial:
debian:
buster: [ros-noetic-urdf-tutorial]
usb_cam:
debian:
buster: [ros-noetic-usb-cam]
visualization_marker_tutorials:
debian:
buster: [ros-noetic-visualization-marker-tutorials]
vl53l1x:
debian:
buster: [ros-noetic-vl53l1x]
xacro:
debian:
buster: [ros-noetic-xacro]
ddynamic_reconfigure:
debian:
buster: [ros-noetic-ddynamic-reconfigure]
librealsense2:
debian:
buster: [ros-noetic-librealsense2]
realsense2_camera:
debian:
buster: [ros-noetic-realsense2-camera]
realsense2_description:
debian:
buster: [ros-noetic-realsense2-description]
geographiclib:
debian:
buster: [libgeographic-dev]

View File

@@ -3,7 +3,7 @@ Description=Launcher for the ROS master, parameter server and rosout logging nod
[Service] [Service]
User=pi User=pi
ExecStart=/bin/sh -c ". /opt/ros/noetic/setup.sh; ROS_HOSTNAME=`hostname`.local exec roscore" ExecStart=/bin/sh -c ". /opt/ros/melodic/setup.sh; ROS_HOSTNAME=`hostname`.local exec roscore"
Restart=on-failure Restart=on-failure
RestartSec=3 RestartSec=3

View File

@@ -15,8 +15,7 @@
set -e # Exit immidiately on non-zero result 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/raspbian_lite/images/raspbian_lite-2020-02-14/2020-02-13-raspbian-buster-lite.zip"
SOURCE_IMAGE="https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-05-28/2021-05-07-raspios-buster-armhf-lite.zip"
export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:='noninteractive'} export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:='noninteractive'}
export LANG=${LANG:='C.UTF-8'} export LANG=${LANG:='C.UTF-8'}
@@ -105,19 +104,23 @@ ${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/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/monkey.service' '/lib/systemd/system/'
# software install # software install
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-software.sh' ${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/'
# network setup # network setup
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-network.sh' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-network.sh'
# avahi setup
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/avahi-services/sftp-ssh.service' '/etc/avahi/services'
# If RPi then use a one thread to build a ROS package on RPi, else use all # 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) [[ $(arch) == 'armv7l' ]] && NUMBER_THREADS=1 || NUMBER_THREADS=$(nproc --all)
# Clover # Clover
${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/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/melodic-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/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/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/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/' # ${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 # 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-ros.sh' ${REPO_URL} ${IMAGE_VERSION} false false ${NUMBER_THREADS}
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh'

View File

@@ -58,7 +58,4 @@ sed -i 's/#SystemMaxUse=/SystemMaxUse=200M/' /etc/systemd/journald.conf
echo_stamp "Move /etc/ld.so.preload out of the way" echo_stamp "Move /etc/ld.so.preload out of the way"
mv /etc/ld.so.preload /etc/ld.so.preload.disabled-for-build mv /etc/ld.so.preload /etc/ld.so.preload.disabled-for-build
echo_stamp "Setup apt so it store all the downloaded packages"
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/02keep-debs
echo_stamp "End of init image" echo_stamp "End of init image"

View File

@@ -13,7 +13,7 @@
# copies or substantial portions of the Software. # copies or substantial portions of the Software.
# #
set -ex # exit on error, echo commands set -e # Exit immidiately on non-zero result
REPO=$1 REPO=$1
REF=$2 REF=$2
@@ -21,9 +21,6 @@ INSTALL_ROS_PACK_SOURCES=$3
DISCOVER_ROS_PACK=$4 DISCOVER_ROS_PACK=$4
NUMBER_THREADS=$5 NUMBER_THREADS=$5
# Current ROS distribution
ROS_DISTRO=noetic
echo_stamp() { echo_stamp() {
# TEMPLATE: echo_stamp <TEXT> <TYPE> # TEMPLATE: echo_stamp <TEXT> <TYPE>
# TYPE: SUCCESS, ERROR, INFO # TYPE: SUCCESS, ERROR, INFO
@@ -49,7 +46,7 @@ echo_stamp() {
my_travis_retry() { my_travis_retry() {
local result=0 local result=0
local count=1 local count=1
local max_count=5 local max_count=50
while [ $count -le $max_count ]; do while [ $count -le $max_count ]; do
[ $result -ne 0 ] && { [ $result -ne 0 ] && {
echo -e "\nThe command \"$@\" failed. Retrying, $count of $max_count.\n" >&2 echo -e "\nThe command \"$@\" failed. Retrying, $count of $max_count.\n" >&2
@@ -71,49 +68,37 @@ my_travis_retry() {
# TODO: 'kinetic-rosdep-clover.yaml' should add only if we use our repo? # TODO: 'kinetic-rosdep-clover.yaml' should add only if we use our repo?
echo_stamp "Init rosdep" echo_stamp "Init rosdep"
my_travis_retry rosdep init my_travis_retry rosdep init
# FIXME: Re-add this after missing packages are built echo "yaml file:///etc/ros/rosdep/melodic-rosdep-clover.yaml" >> /etc/ros/rosdep/sources.list.d/20-default.list
echo "yaml file:///etc/ros/rosdep/${ROS_DISTRO}-rosdep-clover.yaml" >> /etc/ros/rosdep/sources.list.d/10-clover.list
my_travis_retry rosdep update my_travis_retry rosdep update
echo_stamp "Populate rosdep for ROS user" echo_stamp "Populate rosdep for ROS user"
my_travis_retry sudo -u pi rosdep update my_travis_retry sudo -u pi rosdep update
resolve_rosdep() {
# TEMPLATE: resolve_rosdep <CATKIN_PATH> <ROS_DISTRO> <OS_DISTRO> <OS_VERSION>
CATKIN_PATH=$1
ROS_DISTRO='melodic'
OS_DISTRO='debian'
OS_VERSION='buster'
echo_stamp "Installing dependencies apps with rosdep in ${CATKIN_PATH}"
cd ${CATKIN_PATH}
my_travis_retry rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --os=${OS_DISTRO}:${OS_VERSION}
}
export ROS_IP='127.0.0.1' # needed for running tests export ROS_IP='127.0.0.1' # needed for running tests
# echo_stamp "Reconfiguring Clover repository for simplier unshallowing" echo_stamp "Reconfiguring Clover repository for simplier unshallowing"
cd /home/pi/catkin_ws/src/clover cd /home/pi/catkin_ws/src/clover
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
# This is sort of a hack to force "custom" packages to be installed - the ones built by COEX, linked against OpenCV 4.2
# I **wish** OpenCV would not be such a mess, but, well, here we are.
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}-image-publisher=1.15.3-0buster \
ros-${ROS_DISTRO}-web-video-server=0.2.1-0buster
apt-mark hold \
ros-${ROS_DISTRO}-compressed-image-transport \
ros-${ROS_DISTRO}-cv-bridge \
ros-${ROS_DISTRO}-cv-camera \
ros-${ROS_DISTRO}-image-publisher \
ros-${ROS_DISTRO}-web-video-server
echo_stamp "Installing libboost-dev" # https://travis-ci.org/github/CopterExpress/clover/jobs/766318908#L6536
my_travis_retry apt-get install -y --no-install-recommends libboost-dev libboost-all-dev
echo_stamp "Build and install Clover" echo_stamp "Build and install Clover"
cd /home/pi/catkin_ws cd /home/pi/catkin_ws
# Don't try to install gazebo_ros resolve_rosdep $(pwd)
my_travis_retry rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --os=debian:buster \ my_travis_retry pip install wheel
--skip-keys=gazebo_ros --skip-keys=gazebo_plugins my_travis_retry pip install -r /home/pi/catkin_ws/src/clover/clover/requirements.txt
my_travis_retry pip3 install wheel source /opt/ros/melodic/setup.bash
my_travis_retry pip3 install -r /home/pi/catkin_ws/src/clover/clover/requirements.txt catkin_make -j2 -DCMAKE_BUILD_TYPE=Release
source /opt/ros/${ROS_DISTRO}/setup.bash
# Don't build simulation plugins for actual drone
catkin_make -j2 -DCMAKE_BUILD_TYPE=RelWithDebInfo
source devel/setup.bash
echo_stamp "Install clever package (for backwards compatibility)" echo_stamp "Install clever package (for backwards compatibility)"
cd /home/pi/catkin_ws/src/clover/builder/assets/clever cd /home/pi/catkin_ws/src/clover/builder/assets/clever
@@ -122,30 +107,30 @@ rm -rf build # remove build artifacts
echo_stamp "Build Clover documentation" echo_stamp "Build Clover documentation"
cd /home/pi/catkin_ws/src/clover cd /home/pi/catkin_ws/src/clover
builder/assets/install_gitbook.sh NPM_CONFIG_UNSAFE_PERM=true npm install gitbook-cli -g
gitbook install NPM_CONFIG_UNSAFE_PERM=true gitbook install
gitbook build gitbook build
# replace assets copy to assets symlink to save space
rm -rf _book/assets && ln -s ../docs/assets _book/assets
touch node_modules/CATKIN_IGNORE docs/CATKIN_IGNORE _book/CATKIN_IGNORE clover/www/CATKIN_IGNORE apps/CATKIN_IGNORE # ignore documentation files by catkin touch node_modules/CATKIN_IGNORE docs/CATKIN_IGNORE _book/CATKIN_IGNORE clover/www/CATKIN_IGNORE apps/CATKIN_IGNORE # ignore documentation files by catkin
echo_stamp "Installing additional ROS packages" echo_stamp "Installing additional ROS packages"
my_travis_retry apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-rosbridge-suite \ ros-melodic-dynamic-reconfigure \
ros-${ROS_DISTRO}-rosserial \ ros-melodic-compressed-image-transport \
ros-${ROS_DISTRO}-usb-cam \ ros-melodic-rosbridge-suite \
ros-${ROS_DISTRO}-vl53l1x \ ros-melodic-rosserial \
ros-${ROS_DISTRO}-ws281x \ ros-melodic-usb-cam \
ros-${ROS_DISTRO}-rosshow \ ros-melodic-vl53l1x \
ros-${ROS_DISTRO}-cmake-modules \ ros-melodic-ws281x \
ros-${ROS_DISTRO}-image-view \ ros-melodic-rosshow
ros-${ROS_DISTRO}-nodelet-topic-tools \
ros-${ROS_DISTRO}-stereo-msgs
# TODO move GeographicLib datasets to Mavros debian package # TODO move GeographicLib datasets to Mavros debian package
echo_stamp "Install GeographicLib datasets (needed for mavros)" \ echo_stamp "Install GeographicLib datasets (needed for mavros)" \
&& wget -qO- https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | bash && wget -qO- https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | bash
# FIXME: Buster comes with tornado==5.1.1 but we need tornado==4.2.1 for rosbridge_suite
# (note that Python 3 will still have a more recent version)
pip install tornado==4.2.1
echo_stamp "Running tests" echo_stamp "Running tests"
cd /home/pi/catkin_ws cd /home/pi/catkin_ws
# FIXME: Investigate failing tests # FIXME: Investigate failing tests
@@ -154,29 +139,13 @@ catkin_make run_tests #&& catkin_test_results
echo_stamp "Change permissions for catkin_ws" echo_stamp "Change permissions for catkin_ws"
chown -Rf pi:pi /home/pi/catkin_ws chown -Rf pi:pi /home/pi/catkin_ws
echo_stamp "Update www"
sudo -u pi sh -c ". devel/setup.sh && rosrun clover www"
echo_stamp "Make \$HOME/examples symlink"
ln -s "$(catkin_find clover examples --first-only)" /home/pi
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" echo_stamp "Setup ROS environment"
cat << EOF >> /home/pi/.bashrc cat << EOF >> /home/pi/.bashrc
LANG='C.UTF-8' LANG='C.UTF-8'
LC_ALL='C.UTF-8' LC_ALL='C.UTF-8'
ROS_DISTRO='melodic'
export ROS_HOSTNAME=\`hostname\`.local export ROS_HOSTNAME=\`hostname\`.local
source /opt/ros/${ROS_DISTRO}/setup.bash source /opt/ros/melodic/setup.bash
source /home/pi/catkin_ws/devel/setup.bash source /home/pi/catkin_ws/devel/setup.bash
EOF EOF
@@ -184,7 +153,7 @@ EOF
# Restore original sources.list # Restore original sources.list
#mv /var/sources.list.bak /etc/apt/sources.list #mv /var/sources.list.bak /etc/apt/sources.list
# Clean apt cache # Clean apt cache
# apt-get clean -qq > /dev/null apt-get clean -qq > /dev/null
# Remove local mirror repository key # Remove local mirror repository key
#apt-key del COEX-MIRROR #apt-key del COEX-MIRROR

View File

@@ -57,21 +57,18 @@ my_travis_retry() {
return $result return $result
} }
echo_stamp "Increase apt retries"
echo "APT::Acquire::Retries \"3\";" > /etc/apt/apt.conf.d/80-retries
echo_stamp "Install apt keys & repos" echo_stamp "Install apt keys & repos"
# TODO: This STDOUT consist 'OK' # TODO: This STDOUT consist 'OK'
curl http://deb.coex.tech/aptly_repo_signing.key 2> /dev/null | apt-key add -
apt-get update \ apt-get update \
&& apt-get install --no-install-recommends -y dirmngr > /dev/null \ && apt-get install --no-install-recommends -y dirmngr > /dev/null \
&& apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654 && apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
echo "deb http://packages.ros.org/ros/ubuntu buster main" > /etc/apt/sources.list.d/ros-latest.list echo "deb http://packages.ros.org/ros/ubuntu buster main" > /etc/apt/sources.list.d/ros-latest.list
echo "deb http://deb.coex.tech/opencv3 buster main" > /etc/apt/sources.list.d/opencv3.list
wget -O - 'http://packages.coex.tech/key.asc' | apt-key add - echo "deb http://deb.coex.tech/rpi-ros-melodic buster main" > /etc/apt/sources.list.d/rpi-ros-melodic.list
echo 'deb http://packages.coex.tech buster main' >> /etc/apt/sources.list echo "deb http://deb.coex.tech/clover buster main" > /etc/apt/sources.list.d/clover.list
echo_stamp "Update apt cache" echo_stamp "Update apt cache"
@@ -79,10 +76,8 @@ echo_stamp "Update apt cache"
apt-get update apt-get update
# && apt upgrade -y # && apt upgrade -y
# Let's retry fetching those packages several times, just in case
echo_stamp "Software installing" echo_stamp "Software installing"
my_travis_retry apt-get install --no-install-recommends -y cmake-data=3.13.4-1 cmake=3.13.4-1 # FIXME: using older CMake due to https://travis-ci.org/github/CopterExpress/clover/jobs/764367665#L6984 apt-get install --no-install-recommends -y \
my_travis_retry apt-get install --no-install-recommends -y \
unzip \ unzip \
zip \ zip \
ipython \ ipython \
@@ -94,28 +89,31 @@ lsof \
git \ git \
dnsmasq \ dnsmasq \
tmux \ tmux \
tree \
vim \ vim \
cmake \
libjpeg8 \ libjpeg8 \
tcpdump \ tcpdump \
ltrace \
libpoco-dev \ libpoco-dev \
libzbar0 \ libzbar0 \
python3-rosdep \ python-rosdep \
python3-rosinstall-generator \ python-rosinstall-generator \
python3-wstool \ python-wstool \
python3-rosinstall \ python-rosinstall \
build-essential \ build-essential \
libffi-dev \ libffi-dev \
monkey \ monkey \
pigpio python-pigpio python3-pigpio \ pigpio python-pigpio python3-pigpio \
i2c-tools \ i2c-tools \
espeak espeak-data python-espeak python3-espeak \ espeak espeak-data python-espeak \
ntpdate \ ntpdate \
python-dev \ python-dev \
python3-dev \ python3-dev \
python-systemd \ python-systemd \
mjpg-streamer \ mjpg-streamer \
python3-opencv python3-opencv \
&& echo_stamp "Everything was installed!" "SUCCESS" \
|| (echo_stamp "Some packages wasn't installed!" "ERROR"; exit 1)
# Deny byobu to check available updates # Deny byobu to check available updates
sed -i "s/updates_available//" /usr/share/byobu/status/status sed -i "s/updates_available//" /usr/share/byobu/status/status
@@ -123,10 +121,9 @@ sed -i "s/updates_available//" /usr/share/byobu/status/status
echo_stamp "Installing pip" echo_stamp "Installing pip"
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip2.py
python3 get-pip.py python3 get-pip.py
python get-pip2.py python get-pip.py
rm get-pip.py get-pip2.py rm get-pip.py
#my_travis_retry pip install --upgrade pip #my_travis_retry pip install --upgrade pip
#my_travis_retry pip3 install --upgrade pip #my_travis_retry pip3 install --upgrade pip
@@ -136,26 +133,22 @@ pip3 --version
echo_stamp "Install and enable Butterfly (web terminal)" echo_stamp "Install and enable Butterfly (web terminal)"
echo_stamp "Workaround for tornado >= 6.0 breaking butterfly" 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 tornado==5.1.1
my_travis_retry pip3 install butterfly my_travis_retry pip3 install butterfly
my_travis_retry pip3 install butterfly[systemd] my_travis_retry pip3 install butterfly[systemd]
systemctl enable butterfly.socket systemctl enable butterfly.socket
echo_stamp "Install ws281x library" echo_stamp "Install ws281x library"
my_travis_retry pip3 install --prefer-binary rpi_ws281x my_travis_retry pip install --prefer-binary rpi_ws281x
echo_stamp "Setup Monkey" echo_stamp "Setup Monkey"
mv /etc/monkey/sites/default /etc/monkey/sites/default.orig mv /etc/monkey/sites/default /etc/monkey/sites/default.orig
mv /root/monkey /etc/monkey/sites/default mv /root/monkey /etc/monkey/sites/default
sed -i 's/SymLink Off/SymLink On/' /etc/monkey/monkey.conf
systemctl enable monkey.service systemctl enable monkey.service
echo_stamp "Install Node.js" echo_stamp "Install Node.js"
cd /home/pi 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 tar -xzf node-v10.15.0-linux-armv6l.tar.gz
cp -R node-v10.15.0-linux-armv6l/* /usr/local/ cp -R node-v10.15.0-linux-armv6l/* /usr/local/
rm -rf node-v10.15.0-linux-armv6l/ rm -rf node-v10.15.0-linux-armv6l/

View File

@@ -16,28 +16,16 @@ set -ex
echo "Run image tests" echo "Run image tests"
export ROS_DISTRO='noetic' export ROS_DISTRO='melodic'
export ROS_IP='127.0.0.1' export ROS_IP='127.0.0.1'
source /opt/ros/${ROS_DISTRO}/setup.bash source /opt/ros/melodic/setup.bash
source /home/pi/catkin_ws/devel/setup.bash source /home/pi/catkin_ws/devel/setup.bash
systemctl start roscore
cd /home/pi/catkin_ws/src/clover/builder/test/ cd /home/pi/catkin_ws/src/clover/builder/test/
./tests.sh ./tests.sh
./tests.py ./tests.py
./tests_py3.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 [[ $(./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" echo "Move /etc/ld.so.preload back to its original position"
mv /etc/ld.so.preload.disabled-for-build /etc/ld.so.preload mv /etc/ld.so.preload.disabled-for-build /etc/ld.so.preload
echo "Largest packages installed"
sudo -E sh -c 'apt-get install -y debian-goodies'
dpigs -H -n 100

View File

@@ -1,47 +1,27 @@
#!/bin/bash #!/bin/bash
# Perform a "standalone install" in a Docker container # Perform a "standalone install" in a Docker container
set -e
# Step 1: Install pip # 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 update
apt-get update apt install -y curl
apt-get install -y curl curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
if [ "x${ROS_PYTHON_VERSION}" = "x3" ]; then python ./get-pip.py
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
# Step 1.5: Add deb.coex.tech to apt # Step 1.5: Add deb.coex.tech to apt
curl http://deb.coex.tech/aptly_repo_signing.key 2> /dev/null | apt-key add - curl http://deb.coex.tech/aptly_repo_signing.key 2> /dev/null | apt-key add -
echo "deb http://deb.coex.tech/ros xenial main" > /etc/apt/sources.list.d/coex.tech.list echo "deb http://deb.coex.tech/ros xenial main" > /etc/apt/sources.list.d/coex.tech.list
echo "yaml file:///etc/ros/rosdep/coex.yaml" > /etc/ros/rosdep/sources.list.d/99-coex.list echo "yaml file:///etc/ros/rosdep/coex.yaml" > /etc/ros/rosdep/sources.list.d/99-coex.list
CODENAME=$(lsb_release -sc)
cat <<EOF > /etc/ros/rosdep/coex.yaml cat <<EOF > /etc/ros/rosdep/coex.yaml
led_msgs: led_msgs:
ubuntu: ubuntu:
${CODENAME}: [ros-${ROS_DISTRO}-led-msgs] xenial: ros-kinetic-led-msgs
async_web_server_cpp: bionic: ros-melodic-led-msgs
ubuntu: debian:
${CODENAME}: [ros-${ROS_DISTRO}-async-web-server-cpp] stretch: ros-kinetic-led-msgs
ros_pytest: buster: ros-melodic-led-msgs
ubuntu:
${CODENAME}: [ros-${ROS_DISTRO}-ros-pytest]
tf2_web_republisher:
ubuntu:
${CODENAME}: [ros-${ROS_DISTRO}-tf2-web-republisher]
web_video_server:
ubuntu:
${CODENAME}: [ros-${ROS_DISTRO}-web-video-server]
ws281x:
ubuntu:
${CODENAME}: [ros-${ROS_DISTRO}-ws281x]
EOF EOF
apt-get update apt update
rosdep update rosdep update
# Step 2: Run rosdep to install all dependencies # Step 2: Run rosdep to install all dependencies
@@ -57,10 +37,7 @@ cd /root/catkin_ws
catkin_make catkin_make
# Step 4: Run tests # Step 4: Run tests
${PYTHON} -m pip install --upgrade pytest pip install --upgrade pytest
cd /root/catkin_ws cd /root/catkin_ws
source devel/setup.bash source devel/setup.bash
catkin_make run_tests && catkin_test_results 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

View File

@@ -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()

View File

@@ -1,31 +1,21 @@
#!/usr/bin/env python3 #!/usr/bin/env python
# validate all required modules installed # validate all required modules installed
import os
import rospy import rospy
from geometry_msgs.msg import PoseStamped from geometry_msgs.msg import PoseStamped
from sensor_msgs.msg import Range, BatteryState
import cv2 import cv2
import cv2.aruco import cv2.aruco
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
import numpy import numpy
import mavros 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 mavros_msgs.srv import CommandBool, CommandLong, SetMode
from std_srvs.srv import Trigger from std_srvs.srv import Trigger
from clover.srv import GetTelemetry, Navigate, NavigateGlobal, SetPosition, SetVelocity, \ from clover.srv import GetTelemetry, Navigate, NavigateGlobal, SetPosition, SetVelocity, \
SetAttitude, SetRates, SetLEDEffect 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_ros
import tf2_geometry_msgs import tf2_geometry_msgs
@@ -33,15 +23,9 @@ import tf2_geometry_msgs
import VL53L1X import VL53L1X
import pymavlink import pymavlink
from pymavlink import mavutil from pymavlink import mavutil
from image_geometry import PinholeCameraModel, StereoCameraModel import rpi_ws281x
# from espeak import espeak import pigpio
from espeak import espeak
from pyzbar import pyzbar from pyzbar import pyzbar
import docopt
import geopy
import flask
print(cv2.getBuildInformation()) print cv2.getBuildInformation()
if not os.environ.get('VM'):
import rpi_ws281x
import pigpio

View File

@@ -6,10 +6,16 @@ set -ex
# validate required software is installed # validate required software is installed
python --version
python2 --version python2 --version
python3 --version python3 --version
ipython --version
ipython3 --version ipython3 --version
# ptvsd does not have a stand-alone binary
python -m ptvsd --version
python3 -m ptvsd --version
node -v node -v
npm -v npm -v
@@ -19,77 +25,33 @@ lsof -v
git --version git --version
vim --version vim --version
pip --version pip --version
pip2 --version
pip3 --version pip3 --version
tcpdump --version tcpdump --version
monkey --version monkey --version
# espeak --version pigpiod -v
systemctl --version i2cdetect -V
butterfly -h
if [ -z $VM ]; then espeak --version
# rpi only software mjpg_streamer --version
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
# ros stuff # ros stuff
roscore -h roscore -h
rosversion clover rosversion clover
rosversion aruco_pose rosversion aruco_pose
rosversion vl53l1x
rosversion mavros rosversion mavros
rosversion mavros_extras rosversion mavros_extras
rosversion ws281x
rosversion led_msgs
rosversion dynamic_reconfigure rosversion dynamic_reconfigure
rosversion tf2_web_republisher rosversion tf2_web_republisher
rosversion rosbridge_server rosversion compressed_image_transport
rosversion rosbridge_suite
rosversion rosserial
rosversion usb_cam rosversion usb_cam
rosversion cv_camera rosversion cv_camera
rosversion web_video_server rosversion web_video_server
rosversion nodelet rosversion rosshow
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
# determine user home directory
[ $VM ] && H="/home/clover" || H="/home/pi"
# test basic ros tool work
source $H/catkin_ws/devel/setup.bash
roscd
rosrun
rosmsg
rossrv
rosnode || [ $? -eq 64 ] # usage output code is 64
rostopic || [ $? -eq 64 ]
rosservice || [ $? -eq 64 ]
rosparam
roslaunch -h
# validate examples are present # validate examples are present
[[ $(ls $H/examples/*) ]] [[ $(ls /home/pi/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" ]

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python
# test backwards compatibility # test backwards compatibility

View File

@@ -18,7 +18,7 @@ EXCLUDE = 'rviz.png', 'ssid.png', 'sitl_docker_demo.png', 'qgc-params.png', 'but
'qgc-battery.png', 'qgc-radio.png', 'qgc-cal-acc.png', 'qgc-esc.png', 'qgc-cal-compass.png', \ 'qgc-battery.png', 'qgc-radio.png', 'qgc-cal-acc.png', 'qgc-esc.png', 'qgc-cal-compass.png', \
'qgc.png', 'qgc-parameters.png', 'clever4-front-white-large.png', 'qgc-modes.png', \ 'qgc.png', 'qgc-parameters.png', 'clever4-front-white-large.png', 'qgc-modes.png', \
'qgc-requires-setup.png', 'clever4-front-white.png', 'clever4-kit-white.png', '26_1.png', 'battery_holder.stl', \ 'qgc-requires-setup.png', 'clever4-front-white.png', 'clever4-kit-white.png', '26_1.png', 'battery_holder.stl', \
'camera_case.stl', 'camera_mount.stl', 'grip_right.stl', 'grip_left.stl' 'camera_case.stl', 'camera_mount.stl'
code = 0 code = 0

View File

@@ -4,9 +4,7 @@ import os
import sys import sys
import subprocess import subprocess
EXCLUDE = ('clever4-front-white.png', 'clever4-front-white-large.png', '.DS_Store', EXCLUDE = ('clever4-front-white.png', '.DS_Store', 'clever4-front-black-large.png')
'clever4-front-black-large.png','clover42-black.png', 'clover42-main-margin.png')
code = 0 code = 0
os.chdir('./docs') os.chdir('./docs')

View File

@@ -1,8 +0,0 @@
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Changelog for package clover
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
0.21.1 (2020-11-17)
-------------------
* First release of clover package to ROS
* Contributors: Alexey Rogachevskiy, Arthur Golubtsov, Oleg Kalachev

View File

@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 2.8.3)
project(clover) project(clover)
## Compile as C++11, supported in ROS Kinetic and newer ## Compile as C++11, supported in ROS Kinetic and newer
@@ -17,7 +17,6 @@ find_package(catkin REQUIRED COMPONENTS
message_generation message_generation
geometry_msgs geometry_msgs
sensor_msgs sensor_msgs
led_msgs
geographic_msgs geographic_msgs
tf tf
tf2 tf2
@@ -25,24 +24,13 @@ find_package(catkin REQUIRED COMPONENTS
tf2_ros tf2_ros
image_transport image_transport
cv_bridge cv_bridge
dynamic_reconfigure
) )
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
# https://github.com/mavlink/mavros/blob/7f1a8/mavros/CMakeLists.txt#L42
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};/usr/share/cmake/geographiclib")
find_package(GeographicLib REQUIRED) find_package(GeographicLib REQUIRED)
# Workaround for OpenCV 3/4 support find_package(OpenCV 3 REQUIRED
set(_opencv_version 4)
find_package(OpenCV ${_opencv_version} QUIET COMPONENTS calib3d imgproc)
if (NOT OpenCV_FOUND)
message(STATUS "Did not find OpenCV 4, searching for OpenCV 3")
set(_opencv_version 3)
endif()
find_package(OpenCV ${_opencv_version} REQUIRED
COMPONENTS COMPONENTS
calib3d calib3d
imgproc imgproc
@@ -55,7 +43,7 @@ find_package(OpenCV ${_opencv_version} REQUIRED
## Uncomment this if the package has a setup.py. This macro ensures ## Uncomment this if the package has a setup.py. This macro ensures
## modules and global scripts declared therein get installed ## modules and global scripts declared therein get installed
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html ## 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 ## ## Declare ROS messages, services and actions ##
@@ -82,10 +70,11 @@ catkin_python_setup()
## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
## Generate messages in the 'msg' folder ## Generate messages in the 'msg' folder
add_message_files( # add_message_files(
FILES # FILES
State.msg # Message1.msg
) # Message2.msg
# )
## Generate services in the 'srv' folder ## Generate services in the 'srv' folder
add_service_files( add_service_files(
@@ -93,9 +82,6 @@ add_service_files(
GetTelemetry.srv GetTelemetry.srv
Navigate.srv Navigate.srv
NavigateGlobal.srv NavigateGlobal.srv
SetAltitude.srv
SetYaw.srv
SetYawRate.srv
SetPosition.srv SetPosition.srv
SetVelocity.srv SetVelocity.srv
SetAttitude.srv SetAttitude.srv
@@ -132,9 +118,10 @@ generate_messages(
## and list every .cfg file to be processed ## and list every .cfg file to be processed
## Generate dynamic reconfigure parameters in the 'cfg' folder ## Generate dynamic reconfigure parameters in the 'cfg' folder
generate_dynamic_reconfigure_options( # generate_dynamic_reconfigure_options(
cfg/Flow.cfg # cfg/DynReconf1.cfg
) # cfg/DynReconf2.cfg
# )
################################### ###################################
## catkin specific configuration ## ## catkin specific configuration ##
@@ -179,14 +166,13 @@ add_library(${PROJECT_NAME}
## The recommended prefix ensures that target names across packages don't collide ## The recommended prefix ensures that target names across packages don't collide
add_executable(simple_offboard src/simple_offboard.cpp) add_executable(simple_offboard src/simple_offboard.cpp)
# PX4 already has rc and led targets, so we prefix ours with clover_ add_executable(rc src/rc.cpp)
add_executable(clover_rc src/rc.cpp)
add_executable(camera_markers src/camera_markers.cpp) add_executable(camera_markers src/camera_markers.cpp)
add_executable(vpe_publisher src/vpe_publisher.cpp) add_executable(vpe_publisher src/vpe_publisher.cpp)
add_executable(clover_led src/led.cpp) add_executable(led src/led.cpp)
add_executable(shell src/shell.cpp) add_executable(shell src/shell.cpp)
@@ -195,29 +181,22 @@ target_link_libraries(simple_offboard
${GeographicLib_LIBRARIES} ${GeographicLib_LIBRARIES}
) )
# Don't change actual binary names target_link_libraries(rc ${catkin_LIBRARIES})
set_target_properties(clover_rc PROPERTIES OUTPUT_NAME rc)
set_target_properties(clover_led PROPERTIES OUTPUT_NAME led)
target_link_libraries(clover_rc ${catkin_LIBRARIES})
target_link_libraries(camera_markers ${catkin_LIBRARIES}) target_link_libraries(camera_markers ${catkin_LIBRARIES})
target_link_libraries(vpe_publisher ${catkin_LIBRARIES}) target_link_libraries(vpe_publisher ${catkin_LIBRARIES})
target_link_libraries(clover_led ${catkin_LIBRARIES}) target_link_libraries(led ${catkin_LIBRARIES})
target_link_libraries(shell ${catkin_LIBRARIES}) target_link_libraries(shell ${catkin_LIBRARIES})
add_dependencies(simple_offboard ${PROJECT_NAME}_generate_messages_cpp) add_dependencies(simple_offboard ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(clover_led ${PROJECT_NAME}_generate_messages_cpp) add_dependencies(led ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(shell ${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 ## Rename C++ executable without prefix
## The above recommended prefix causes long target names, the following renames the ## The above recommended prefix causes long target names, the following renames the
## target back to the shorter version for ease of user use ## target back to the shorter version for ease of user use
@@ -248,12 +227,12 @@ target_link_libraries(${PROJECT_NAME}
# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
# ) # )
# Mark executables and/or libraries for installation ## Mark executables and/or libraries for installation
install(TARGETS simple_offboard clover_rc camera_markers vpe_publisher clover_led shell clover # install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
) # )
## Mark cpp header files for installation ## Mark cpp header files for installation
# install(DIRECTORY include/${PROJECT_NAME}/ # install(DIRECTORY include/${PROJECT_NAME}/
@@ -269,25 +248,13 @@ install(TARGETS simple_offboard clover_rc camera_markers vpe_publisher clover_le
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# ) # )
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 # Only install udev rules when building a Debian package
# FIXME: Other operating systems may have other prefixes # FIXME: Other operating systems may have other prefixes
string(FIND ${CMAKE_INSTALL_PREFIX} "/opt/ros" _PREFIX_INDEX) string(FIND ${CMAKE_INSTALL_PREFIX} "/opt/ros" _PREFIX_INDEX)
if (${_PREFIX_INDEX} EQUAL 0) if (${_PREFIX_INDEX} EQUAL 0)
message(STATUS "Building as a Debian package - adding udev rules as installable files") message(STATUS "Building as a Debian package - adding udev rules as installable files")
install(FILES install(FILES
udev/99-px4fmu.rules config/99-px4fmu.rules
DESTINATION /lib/udev/rules.d DESTINATION /lib/udev/rules.d
) )
else() else()
@@ -310,5 +277,4 @@ endif()
if (CATKIN_ENABLE_TESTING) if (CATKIN_ENABLE_TESTING)
find_package(rostest REQUIRED) find_package(rostest REQUIRED)
add_rostest(test/basic.test) add_rostest(test/basic.test)
add_rostest(test/offboard.test)
endif() endif()

View File

@@ -4,7 +4,7 @@ A bundle for autonomous navigation and drone control.
## Manual installation ## Manual installation
Install ROS Noetic according to the [documentation](http://wiki.ros.org/noetic/Installation), then [create a Catkin workspace](http://wiki.ros.org/catkin/Tutorials/create_a_workspace). Install ROS Melodic according to the [documentation](http://wiki.ros.org/melodic/Installation), then [create a Catkin workspace](http://wiki.ros.org/catkin/Tutorials/create_a_workspace).
Clone this repo to directory `~/catkin_ws/src/clover`: Clone this repo to directory `~/catkin_ws/src/clover`:
@@ -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: 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 ```bash
cd ~/catkin_ws/src/clover/clover/udev cd ~/catkin_ws/src/clover/clover/config
sudo cp 99-px4fmu.rules /lib/udev/rules.d sudo cp 99-px4fmu.rules /lib/udev/rules.d
``` ```
@@ -44,12 +44,30 @@ Alternatively you may change the `fcu_url` property in `mavros.launch` file to p
## Running ## Running
Enable systemd service `roscore` (if not running):
```bash
sudo systemctl enable /home/<username>/catkin_ws/src/clover/builder/assets/roscore.service
sudo systemctl start roscore
```
To start connection to SITL, use:
```bash
roslaunch clover sitl.launch
```
To start connection to the flight controller, use: To start connection to the flight controller, use:
```bash ```bash
roslaunch clover clover.launch roslaunch clover clover.launch
``` ```
For the simulation information see the [corresponding article](https://clover.coex.tech/en/simulation.html).
> Note that the package is configured to connect to `/dev/px4fmu` by default (see [previous section](#manual-installation)). Install udev rules or specify path to your FCU device in `mavros.launch`. > Note that the package is configured to connect to `/dev/px4fmu` by default (see [previous section](#manual-installation)). Install udev rules or specify path to your FCU device in `mavros.launch`.
Also, you can enable and start the systemd service:
```bash
sudo systemctl enable /home/<username>/catkin_ws/src/clover/deploy/clover.service
sudo systemctl start clover
```

View File

@@ -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"))

View File

@@ -0,0 +1,18 @@
# taken from: https://github.com/mavlink/mavros/blob/master/libmavconn/cmake/Modules/FindGeographicLib.cmake
# Look for GeographicLib
#
# Set
# GEOGRAPHICLIB_FOUND = TRUE
# GeographicLib_INCLUDE_DIRS = /usr/local/include
# GeographicLib_LIBRARIES = /usr/local/lib/libGeographic.so
# GeographicLib_LIBRARY_DIRS = /usr/local/lib
find_path (GeographicLib_INCLUDE_DIRS NAMES GeographicLib/Config.h)
find_library (GeographicLib_LIBRARIES NAMES Geographic)
include (FindPackageHandleStandardArgs)
find_package_handle_standard_args (GeographicLib DEFAULT_MSG
GeographicLib_LIBRARIES GeographicLib_INCLUDE_DIRS)
mark_as_advanced (GeographicLib_LIBRARIES GeographicLib_INCLUDE_DIRS)

View File

@@ -0,0 +1,15 @@
# PixHawk (px4fmu-v2), px4fmu-v3
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0011", ATTRS{product}=="PX4 FMU v2.x", SYMLINK+="px4fmu"
# PixRacer (px4fmu-v4)
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0012", ATTRS{product}=="PX4 FMU v4.x", SYMLINK+="px4fmu"
# px4fmu-v5
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0032", ATTRS{product}=="PX4 FMU v5.x", SYMLINK+="px4fmu"
# auav
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0021", ATTRS{product}=="PX4 AUAV x2.1", SYMLINK+="px4fmu"
# crazyflie
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{product}=="PX4 Crazyflie v2.0", SYMLINK+="px4fmu"
# px4fmu-v4pro
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"

View File

@@ -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'
elif 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()

View File

@@ -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())

View File

@@ -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()

View File

@@ -1,91 +0,0 @@
# This example makes the drone find and follow the red circle.
# To test in the simulator, place 'Red Circle' model on the floor.
# More information: https://clover.coex.tech/red_circle
# Input topic: main_camera/image_raw (camera image)
# Output topics:
# cv/mask (red color mask)
# cv/red_circle (position of the center of the red circle in 3D space)
import rospy
import cv2
import numpy as np
from math import nan
from sensor_msgs.msg import Image, CameraInfo
from geometry_msgs.msg import PointStamped, Point
from cv_bridge import CvBridge
from clover import long_callback, srv
import tf2_ros
import tf2_geometry_msgs
import image_geometry
rospy.init_node('cv', disable_signals=True) # disable signals to allow interrupting with ctrl+c
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
bridge = CvBridge()
tf_buffer = tf2_ros.Buffer()
tf_listener = tf2_ros.TransformListener(tf_buffer)
mask_pub = rospy.Publisher('~mask', Image, queue_size=1)
point_pub = rospy.Publisher('~red_circle', PointStamped, queue_size=1)
# read camera info
camera_model = image_geometry.PinholeCameraModel()
camera_model.fromCameraInfo(rospy.wait_for_message('main_camera/camera_info', CameraInfo))
def img_xy_to_point(xy, dist):
xy_rect = camera_model.rectifyPoint(xy)
ray = camera_model.projectPixelTo3dRay(xy_rect)
return Point(x=ray[0] * dist, y=ray[1] * dist, z=dist)
def get_center_of_mass(mask):
M = cv2.moments(mask)
if M['m00'] == 0:
return None
return M['m10'] // M['m00'], M['m01'] // M['m00']
follow_red_circle = False
@long_callback
def image_callback(msg):
img = bridge.imgmsg_to_cv2(msg, 'bgr8')
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# we need to use two ranges for red color
mask1 = cv2.inRange(img_hsv, (0, 150, 150), (15, 255, 255))
mask2 = cv2.inRange(img_hsv, (160, 150, 150), (180, 255, 255))
# combine two masks using bitwise OR
mask = cv2.bitwise_or(mask1, mask2)
# publish the mask
if mask_pub.get_num_connections() > 0:
mask_pub.publish(bridge.cv2_to_imgmsg(mask, 'mono8'))
# calculate x and y of the circle
xy = get_center_of_mass(mask)
if xy is None:
return
# calculate and publish the position of the circle in 3D space
altitude = get_telemetry('terrain').z
xy3d = img_xy_to_point(xy, altitude)
target = PointStamped(header=msg.header, point=xy3d)
point_pub.publish(target)
if follow_red_circle:
# follow the target
setpoint = tf_buffer.transform(target, 'map', timeout=rospy.Duration(0.2))
set_position(x=setpoint.point.x, y=setpoint.point.y, z=nan, yaw=nan, frame_id=setpoint.header.frame_id)
# process each camera frame:
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1)
rospy.loginfo('Hit enter to follow the red circle')
input()
follow_red_circle = True
rospy.spin()

View File

@@ -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()

View File

@@ -2,43 +2,28 @@
<arg name="aruco_detect" default="true"/> <arg name="aruco_detect" default="true"/>
<arg name="aruco_map" default="false"/> <arg name="aruco_map" default="false"/>
<arg name="aruco_vpe" 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="map" default="map.txt"/> <!-- markers map file name -->
<!-- For additional help go to https://clover.coex.tech/aruco --> <!-- 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 --> <!-- 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">
<remap from="image_raw" to="main_camera/image_raw"/> <remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/> <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="dictionary" value="2"/> <!-- DICT_4X4_250 --> <param name="cornerRefinementMethod" value="2"/>
<param name="estimate_poses" value="true"/> <param name="estimate_poses" value="true"/>
<param name="send_tf" value="true"/> <param name="send_tf" value="true"/>
<param name="use_map_markers" value="true"/> <param name="known_tilt" value="map"/>
<param name="known_vertical" value="map" if="$(eval placement == 'floor' or placement == 'ceiling')"/> <param name="length" value="0.33"/>
<param name="flip_vertical" value="true" 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 -->
<!-- length override example: -->
<!-- <param name="length_override/3" value="0.1"/> -->
</node> </node>
<!-- aruco_map: estimate aruco map pose --> <!-- 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">
<remap from="image_raw" to="main_camera/image_raw"/> <remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/> <remap from="camera_info" to="main_camera/camera_info"/>
<remap from="markers" to="aruco_detect/markers"/> <remap from="markers" to="aruco_detect/markers"/>
<param name="map" value="$(find aruco_pose)/map/$(arg map)"/> <param name="map" value="$(find aruco_pose)/map/map.txt"/>
<param name="known_vertical" value="map" if="$(eval placement == 'floor' or placement == 'ceiling')"/> <param name="known_tilt" value="map"/>
<param name="flip_vertical" value="true" if="$(eval placement == 'ceiling')"/>
<param name="image_axis" value="true"/> <param name="image_axis" value="true"/>
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/> <param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/>
<param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/> <param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/>
@@ -47,11 +32,11 @@
</node> </node>
<!-- vpe publisher from aruco markers --> <!-- 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"> <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" if="$(arg aruco_vpe)"/> <remap from="~pose_cov" to="aruco_map/pose"/>
<remap from="~vpe" to="mavros/vision_pose/pose"/> <remap from="~vpe" to="mavros/vision_pose/pose"/>
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/> <param name="frame_id" value="aruco_map_detected"/>
<param name="force_init" value="$(arg force_init)"/> <param name="publish_zero" value="true"/>
<param name="offset_frame_id" value="aruco_map"/> <param name="offset_frame_id" value="aruco_map"/>
</node> </node>
</launch> </launch>

View File

@@ -10,11 +10,8 @@
<arg name="aruco" default="false"/> <arg name="aruco" default="false"/>
<arg name="rangefinder_vl53l1x" default="true"/> <arg name="rangefinder_vl53l1x" default="true"/>
<arg name="led" default="true"/> <arg name="led" default="true"/>
<arg name="blocks" default="false"/> <arg name="rc" default="true"/>
<arg name="rc" default="false"/> <arg name="shell" default="true"/>
<arg name="force_init" default="true"/> <!-- force estimator to init by publishing zero pose -->
<arg name="simulator" default="false"/> <!-- flag that we are operating on a simulated drone -->
<!-- log formatting --> <!-- log formatting -->
<env name="ROSCONSOLE_FORMAT" value="[${severity}] [${time}]: ${logger}: ${message}"/> <env name="ROSCONSOLE_FORMAT" value="[${severity}] [${time}]: ${logger}: ${message}"/>
@@ -34,30 +31,32 @@
</node> </node>
<!-- aruco markers --> <!-- aruco markers -->
<include file="$(find clover)/launch/aruco.launch" if="$(eval aruco or force_init)"> <include file="$(find clover)/launch/aruco.launch" if="$(arg aruco)"/>
<arg name="force_init" value="$(arg force_init)"/>
<arg name="disable" value="$(eval not aruco)"/>
</include>
<!-- optical flow --> <!-- 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">
<remap from="image_raw" to="main_camera/image_raw"/> <remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/> <remap from="camera_info" to="main_camera/camera_info"/>
<param name="calc_flow_gyro" value="true"/> <param name="calc_flow_gyro" value="true"/>
<param name="roi_rad" value="0.8"/> <param name="roi_rad" value="0.8"/>
<param name="disable_on_vpe" value="true"/>
</node> </node>
<!-- main nodelet manager -->
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager" output="screen" clear_params="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 --> <!-- simplified offboard control -->
<node name="simple_offboard" pkg="clover" type="simple_offboard" output="screen" clear_params="true"> <node name="simple_offboard" pkg="clover" type="simple_offboard" output="screen" clear_params="true">
<param name="reference_frames/main_camera_optical" value="map"/> <param name="reference_frames/body" value="map"/>
<param name="terrain_frame_mode" value="range"/> <param name="reference_frames/base_link" value="map"/>
<param name="reference_frames/navigate_target" value="map"/>
</node> </node>
<!-- main camera --> <!-- main camera -->
<include file="$(find clover)/launch/main_camera.launch" if="$(arg main_camera)"> <include file="$(find clover)/launch/main_camera.launch" if="$(arg main_camera)"/>
<arg name="simulator" value="$(arg simulator)"/>
</include>
<!-- rosbridge --> <!-- rosbridge -->
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" if="$(eval rosbridge or rc)"/> <include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" if="$(eval rosbridge or rc)"/>
@@ -66,22 +65,14 @@
<node name="tf2_web_republisher" pkg="tf2_web_republisher" type="tf2_web_republisher" output="screen" if="$(arg rosbridge)"/> <node name="tf2_web_republisher" pkg="tf2_web_republisher" type="tf2_web_republisher" output="screen" if="$(arg rosbridge)"/>
<!-- vl53l1x ToF rangefinder --> <!-- vl53l1x ToF rangefinder -->
<node name="rangefinder" pkg="vl53l1x" type="vl53l1x_node" output="screen" if="$(eval rangefinder_vl53l1x and not simulator)"> <node name="rangefinder" pkg="vl53l1x" type="vl53l1x_node" output="screen" if="$(arg rangefinder_vl53l1x)">
<param name="frame_id" value="rangefinder"/> <param name="frame_id" value="rangefinder"/>
<param name="min_signal" value="0.4"/> <param name="min_signal" value="0.4"/>
<param name="pass_statuses" type="yaml" value="[0, 6, 7, 11]"/> <param name="pass_statuses" type="yaml" value="[0, 6, 7, 11]"/>
</node> </node>
<!-- rangefinder's frame -->
<node pkg="tf2_ros" type="static_transform_publisher" name="rangefinder_frame" args="0 0 -0.05 0 1.5707963268 0 base_link rangefinder" if="$(arg rangefinder_vl53l1x)"/>
<!-- led strip --> <!-- led strip -->
<include file="$(find clover)/launch/led.launch" if="$(arg led)"> <include file="$(find clover)/launch/led.launch" if="$(arg led)"/>
<arg name="simulator" value="$(arg simulator)"/>
</include>
<!-- Clover Blocks -->
<node name="clover_blocks" pkg="clover_blocks" type="clover_blocks" output="screen" if="$(arg blocks)"/>
<!-- rc backend --> <!-- rc backend -->
<node name="rc" pkg="clover" type="rc" output="screen" if="$(arg rc)" clear_params="true"> <node name="rc" pkg="clover" type="rc" output="screen" if="$(arg rc)" clear_params="true">
@@ -89,4 +80,6 @@
<param name="use_fake_gcs" value="false"/> <param name="use_fake_gcs" value="false"/>
</node> </node>
<!-- Shell access through ROS service -->
<node name="shell" pkg="clover" type="shell" output="screen" if="$(arg shell)"/>
</launch> </launch>

View File

@@ -2,17 +2,13 @@
<arg name="ws281x" default="true"/> <arg name="ws281x" default="true"/>
<arg name="led_effect" default="true"/> <arg name="led_effect" default="true"/>
<arg name="led_notify" default="true"/> <arg name="led_notify" default="true"/>
<arg name="led_count" default="72"/>
<arg name="gpio_pin" default="21"/>
<arg name="simulator" default="false"/>
<!-- For additional help go to https://clover.coex.tech/led --> <!-- For additional help go to https://clover.coex.tech/led -->
<!-- ws281x led strip driver --> <!-- ws281x led strip driver -->
<node pkg="ws281x" name="led" type="ws281x_node" clear_params="true" output="screen" if="$(eval ws281x and not simulator)"> <node pkg="ws281x" name="led" type="ws281x_node" clear_params="true" output="screen" if="$(arg ws281x)">
<param name="led_count" value="$(arg led_count)"/> <param name="led_count" value="58"/>
<param name="gpio_pin" value="$(arg gpio_pin)"/> <param name="gpio_pin" value="21"/>
<param name="brightness" value="64"/> <param name="brightness" value="64"/>
<param name="strip_type" value="WS2811_STRIP_GRB"/> <param name="strip_type" value="WS2811_STRIP_GRB"/>
<param name="target_frequency" value="800000"/> <param name="target_frequency" value="800000"/>
@@ -21,8 +17,7 @@
</node> </node>
<!-- high level led effects control, events notification with leds --> <!-- high level led effects control, events notification with leds -->
<node pkg="clover" name="led_effect" type="led" clear_params="true" output="screen" if="$(arg led_effect)"> <node pkg="clover" name="led_effect" type="led" ns="led" clear_params="true" output="screen" if="$(arg led_effect)">
<param name="led" value="led"/>
<param name="blink_rate" value="2"/> <param name="blink_rate" value="2"/>
<param name="fade_period" value="0.5"/> <param name="fade_period" value="0.5"/>
<param name="rainbow_period" value="5"/> <param name="rainbow_period" value="5"/>
@@ -36,8 +31,8 @@
altctl: { r: 255, g: 255, b: 40 } altctl: { r: 255, g: 255, b: 40 }
posctl: { r: 50, g: 100, b: 220 } posctl: { r: 50, g: 100, b: 220 }
offboard: { r: 220, g: 20, b: 250 } offboard: { r: 220, g: 20, b: 250 }
low_battery: { threshold: 3.6, effect: blink_fast, r: 255, g: 0, b: 0 } low_battery: { threshold: 3.7, 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> </rosparam>
</node> </node>
</launch> </launch>

View File

@@ -3,32 +3,20 @@
<arg name="direction_z" default="down"/> <!-- direction the camera points: down, up --> <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="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="rectify" default="false"/> <!-- enable rectification -->
<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"/> <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"/>
<node if="$(eval direction_z == 'down' and direction_y == 'forward')" 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"/> <node if="$(eval direction_z == 'down' and direction_y == 'forward')" 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"/>
<node if="$(eval direction_z == 'up' and direction_y == 'backward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 0.07 1.5707963 0 0 base_link main_camera_optical"/> <node if="$(eval direction_z == 'up' and direction_y == 'backward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 0.07 1.5707963 0 0 base_link main_camera_optical"/>
<node if="$(eval direction_z == 'up' and direction_y == 'forward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 0.07 -1.5707963 0 0 base_link main_camera_optical"/> <node if="$(eval direction_z == 'up' and direction_y == 'forward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 0.07 -1.5707963 0 0 base_link main_camera_optical"/>
<node if="$(eval direction_z == 'forward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.03 0 0.05 -1.5707963 0 -1.5707963 base_link main_camera_optical"/>
<node if="$(eval direction_z == 'backward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="-0.03 0 0.05 1.5707963 0 -1.5707963 base_link main_camera_optical"/>
<!-- Template for custom camera orientation --> <!-- Template for custom camera orientation -->
<!-- Camera position and orientation are represented by base_link -> main_camera_optical transform --> <!-- Camera position and orientation are represented by base_link -> main_camera_optical transform -->
<!-- static_transform_publisher arguments: x y z yaw pitch roll frame_id child_frame_id --> <!-- 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"/> --> <!-- <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 --> <!-- 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"> <node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet nodelet_manager" clear_params="true">
<param name="device_path" value="$(arg device)"/> <param name="device_path" value="/dev/video0"/> <!-- v4l2 device -->
<param name="frame_id" value="main_camera_optical"/> <param name="frame_id" value="main_camera_optical"/>
<param name="camera_info_url" value="file://$(find clover)/camera_info/fisheye_cam.yaml"/> <param name="camera_info_url" value="file://$(find clover)/camera_info/fisheye_cam.yaml"/>
@@ -46,15 +34,4 @@
<node pkg="clover" type="camera_markers" ns="main_camera" name="main_camera_markers"> <node pkg="clover" type="camera_markers" ns="main_camera" name="main_camera_markers">
<param name="scale" value="3.0"/> <param name="scale" value="3.0"/>
</node> </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)"/>
<!-- rectified image topic -->
<node pkg="nodelet" type="nodelet" name="rectify" args="load image_proc/rectify main_camera_nodelet_manager" if="$(arg rectify)">
<remap from="image_mono" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/>
<remap from="image_rect" to="main_camera/image_rect"/>
</node>
</launch> </launch>

View File

@@ -1,21 +1,18 @@
<launch> <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_ip" default="127.0.0.1"/>
<arg name="fcu_sys_id" default="1"/> <arg name="fcu_sys_id" default="1"/>
<arg name="gcs_bridge" default="tcp"/> <arg name="gcs_bridge" default="tcp"/>
<arg name="viz" default="true"/> <arg name="viz" default="true"/>
<arg name="respawn" default="true"/> <arg name="respawn" default="true"/>
<arg name="distance_sensor_remap" default="rangefinder/range"/> <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 --> <!-- UART connection -->
<param name="fcu_url" value="/dev/ttyAMA0:921600" if="$(eval fcu_conn is None or fcu_conn == 'uart')"/> <param name="fcu_url" value="/dev/ttyAMA0:921600" if="$(eval fcu_conn is None or fcu_conn == 'uart')"/>
<!-- USB connection --> <!-- 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 --> <!-- sitl before PX4 1.9.0 -->
<param name="fcu_url" value="udp://@$(arg fcu_ip):14557" if="$(eval fcu_conn == 'udp')"/> <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 --> <!-- sitl since PX4 1.9.0 -->
<param name="fcu_url" value="udp://@$(arg fcu_ip):14580" if="$(eval fcu_conn == 'sitl')"/> <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 --> <!-- set target_system_id -->
<param name="target_system_id" value="$(arg fcu_sys_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"/> <rosparam command="load" file="$(find clover)/launch/mavros_config.yaml"/>
<!-- remap rangefinder --> <!-- 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"> <rosparam param="plugin_whitelist">
- altitude - altitude
@@ -77,6 +71,9 @@
covariance: 1 # cm covariance: 1 # cm
</rosparam> </rosparam>
<!-- Rangefinders frame -->
<node pkg="tf2_ros" type="static_transform_publisher" name="rangefinder_frame" args="0 0 -0.05 0 1.5707963268 0 base_link rangefinder"/>
<!-- Copter visualization --> <!-- Copter visualization -->
<node name="visualization" pkg="mavros_extras" type="visualization" if="$(arg viz)"> <node name="visualization" pkg="mavros_extras" type="visualization" if="$(arg viz)">
<remap to="mavros/local_position/pose" from="local_position"/> <remap to="mavros/local_position/pose" from="local_position"/>

View File

@@ -1,11 +1,11 @@
# Config file for mavros # Config file for mavros
# Based on https://raw.githubusercontent.com/mavlink/mavros/master/mavros/launch/px4_config.yaml # 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: conn:
heartbeat_rate: 1.0 # send heartbeat rate in Hertz heartbeat_rate: 1.0 # send hertbeat rate in Hertz
timeout: 10.0 # heartbeat timeout in seconds timeout: 10.0 # hertbeat timeout in seconds
timesync_rate: 10.0 # TIMESYNC rate in Hertz (feature disabled if 0.0) 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) 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 time_ref_source: "fcu" # time_reference source
timesync_mode: MAVLINK timesync_mode: MAVLINK
timesync_avg_alpha: 0.6 # timesync averaging factor timesync_avg_alpha: 0.6 # timesync averaging factor
publish_sim_time: false # don't publish /clock
global_position: global_position:
frame_id: "map" # origin frame frame_id: "map" # origin frame
@@ -78,9 +77,6 @@ distance_sensor:
field_of_view: 0.5 field_of_view: 0.5
rangefinder_sub: rangefinder_sub:
subscriber: true subscriber: true
id: 1
orientation: PITCH_270
covariance: 1 # cm
# fake_gps # fake_gps
fake_gps: fake_gps:

View File

@@ -1,4 +0,0 @@
<launch>
<!-- shortcut for running the simulation (`roslaunch clover simulator.launch`) -->
<include file="$(find clover_simulation)/launch/simulator.launch"/>
</launch>

19
clover/launch/sitl.launch Normal file
View File

@@ -0,0 +1,19 @@
<launch>
<!-- clover configuration for testing in sitl -->
<arg name="ip" default="127.0.0.1"/>
<arg name="rosbridge" default="false"/>
<include file="$(find clover)/launch/clover.launch">
<arg name="fcu_conn" value="udp"/>
<arg name="fcu_ip" value="$(arg ip)"/>
<arg name="gcs_bridge" value="false"/>
<arg name="optical_flow" value="false"/>
<arg name="web_video_server" default="false"/>
<arg name="main_camera" default="false"/>
<arg name="rosbridge" value="$(arg rosbridge)"/>
<arg name="aruco" default="false"/>
<arg name="rangefinder_vl53l1x" default="false"/>
<arg name="led" default="false"/>
<arg name="rc" default="false"/>
</include>
</launch>

View File

@@ -1,38 +0,0 @@
uint8 MODE_NONE = 0
uint8 MODE_NAVIGATE = 1
uint8 MODE_NAVIGATE_GLOBAL = 2
uint8 MODE_POSITION = 3
uint8 MODE_VELOCITY = 4
uint8 MODE_ATTITUDE = 5
uint8 MODE_RATES = 6
uint8 YAW_MODE_YAW = 0
uint8 YAW_MODE_YAW_RATE = 1
uint8 YAW_MODE_YAW_TOWARDS = 2
# type of offboard control
uint8 mode
uint8 yaw_mode
# targets
float32 x
float32 y
float32 z
float32 speed
float32 lat
float32 lon
float32 vx
float32 vy
float32 vz
float32 roll
float32 pitch
float32 yaw
float32 roll_rate
float32 pitch_rate
float32 yaw_rate
float32 thrust
# frames of reference
string xy_frame_id
string z_frame_id
string yaw_frame_id

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<package format="3"> <package format="2">
<name>clover</name> <name>clover</name>
<version>0.24.0</version> <version>0.0.1</version>
<description>The Clover package</description> <description>The Clover package</description>
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer> <maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
@@ -37,15 +37,10 @@
<depend>rosbridge_server</depend> <depend>rosbridge_server</depend>
<depend>web_video_server</depend> <depend>web_video_server</depend>
<depend>tf2_web_republisher</depend> <depend>tf2_web_republisher</depend>
<depend>libxml2</depend> <depend>python-lxml</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>
<depend>image_proc</depend>
<depend>image_geometry</depend>
<exec_depend>python-pymavlink</exec_depend> <exec_depend>python-pymavlink</exec_depend>
<test_depend>ros_pytest</test_depend> <!-- Use test_depend for packages you need only for testing: -->
<!-- <test_depend>gtest</test_depend> -->
<!-- The export tag contains other, unspecified, tags --> <!-- The export tag contains other, unspecified, tags -->
<export> <export>

View File

@@ -1,4 +1,5 @@
flask flask==1.1.1
geopy docopt==0.6.2
smbus2 geopy==1.11.0
VL53L1X smbus2==0.2.1
VL53L1X==0.0.2

View File

@@ -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)

View File

@@ -1,90 +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
try:
flow_client = dynamic_reconfigure.client.Client('optical_flow', timeout=2)
except rospy.ROSException:
flow_client = None
print('Cannot configure optical flow, skip')
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=math.nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
res = navigate(x=x, y=y, z=z, yaw=yaw, 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()
if flow_client:
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()

View File

@@ -1,99 +0,0 @@
#!/usr/bin/env python3
import rospy
import math
from math import nan, inf
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_yaw = handle_response(rospy.ServiceProxy('set_yaw', srv.SetYaw))
set_yaw_rate = handle_response(rospy.ServiceProxy('set_yaw_rate', srv.SetYawRate))
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, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
res = navigate(x=x, y=y, z=z, yaw=yaw, 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° using set_yaw [enter] ')
set_yaw(yaw=-math.pi / 2, frame_id='navigate_target')
rospy.sleep(3)
input('Use set_attitude to fly backwards [enter]')
set_attitude(roll=0, pitch=-0.3, 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(roll=0.3, pitch=0, 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 set_yaw_rate [enter]')
set_yaw_rate(yaw_rate=-1)
rospy.sleep(2 * math.pi)
set_position(frame_id='body')
input('Return to start point heading forward [enter]')
navigate_wait(x=start.x, y=start.y, z=start.z, yaw=inf, speed=1, frame_id='map')
input('Land [enter]')
land()

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -12,7 +12,6 @@
#include <ros/ros.h> #include <ros/ros.h>
#include <string> #include <string>
#include <vector>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <clover/SetLEDEffect.h> #include <clover/SetLEDEffect.h>
@@ -30,7 +29,7 @@ ros::Timer timer;
ros::Time start_time; ros::Time start_time;
double blink_rate, blink_fast_rate, flash_delay, fade_period, wipe_period, rainbow_period; double blink_rate, blink_fast_rate, flash_delay, fade_period, wipe_period, rainbow_period;
double low_battery_threshold; double low_battery_threshold;
std::vector<std::string> error_ignore; bool blink_state;
led_msgs::SetLEDs set_leds; led_msgs::SetLEDs set_leds;
led_msgs::LEDStateArray state, start_state; led_msgs::LEDStateArray state, start_state;
ros::ServiceClient set_leds_srv; ros::ServiceClient set_leds_srv;
@@ -86,8 +85,9 @@ void proceed(const ros::TimerEvent& event)
set_leds.request.leds.resize(led_count); set_leds.request.leds.resize(led_count);
if (current_effect.effect == "blink" || current_effect.effect == "blink_fast") { if (current_effect.effect == "blink" || current_effect.effect == "blink_fast") {
// enable on odd counter blink_state = !blink_state;
if (counter % 2 != 0) { // toggle all leds
if (blink_state) {
fill(current_effect.r, current_effect.g, current_effect.b); fill(current_effect.r, current_effect.g, current_effect.b);
} else { } else {
fill(0, 0, 0); fill(0, 0, 0);
@@ -220,7 +220,6 @@ bool setEffect(clover::SetLEDEffect::Request& req, clover::SetLEDEffect::Respons
counter = 0; counter = 0;
start_state = state; start_state = state;
start_time = ros::Time::now(); start_time = ros::Time::now();
proceed({ .current_real = start_time });
return true; return true;
} }
@@ -275,10 +274,6 @@ void handleMavrosState(const mavros_msgs::State& msg)
void handleLog(const rosgraph_msgs::Log& log) void handleLog(const rosgraph_msgs::Log& log)
{ {
if (log.level >= rosgraph_msgs::Log::ERROR) { 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"); notify("error");
} }
} }
@@ -307,24 +302,19 @@ int main(int argc, char **argv)
nh_priv.param("rainbow_period", rainbow_period, 5.0); 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/low_battery/threshold", low_battery_threshold, 3.7);
nh_priv.param("notify/error/ignore", error_ignore, {});
std::string led; // led namespace ros::service::waitForService("set_leds"); // cannot work without set_leds service
nh_priv.param("led", led, std::string("led")); set_leds_srv = nh.serviceClient<led_msgs::SetLEDs>("set_leds", true);
if (!led.empty()) led += "/";
ros::service::waitForService(led + "set_leds"); // cannot work without set_leds service
set_leds_srv = nh.serviceClient<led_msgs::SetLEDs>(led + "set_leds", true);
// wait for leds count info // wait for leds count info
handleState(*ros::topic::waitForMessage<led_msgs::LEDStateArray>(led + "state", nh)); handleState(*ros::topic::waitForMessage<led_msgs::LEDStateArray>("state", nh));
auto state_sub = nh.subscribe(led + "state", 1, &handleState); auto state_sub = nh.subscribe("state", 1, &handleState);
auto set_effect = nh.advertiseService(led + "set_effect", &setEffect); auto set_effect = nh.advertiseService("set_effect", &setEffect);
auto mavros_state_sub = nh.subscribe("mavros/state", 1, &handleMavrosState); auto mavros_state_sub = nh.subscribe("/mavros/state", 1, &handleMavrosState);
auto battery_sub = nh.subscribe("mavros/battery", 1, &handleBattery); auto battery_sub = nh.subscribe("/mavros/battery", 1, &handleBattery);
auto rosout_sub = nh.subscribe("/rosout_agg", 1, &handleLog); auto rosout_sub = nh.subscribe("/rosout_agg", 1, &handleLog);
timer = nh.createTimer(ros::Duration(0), &proceed, false, false); timer = nh.createTimer(ros::Duration(0), &proceed, false, false);

View File

@@ -22,14 +22,11 @@
#include <tf2/utils.h> #include <tf2/utils.h>
#include <tf2_ros/transform_listener.h> #include <tf2_ros/transform_listener.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h> #include <tf2_geometry_msgs/tf2_geometry_msgs.h>
#include <dynamic_reconfigure/server.h>
#include <mavros_msgs/OpticalFlowRad.h> #include <mavros_msgs/OpticalFlowRad.h>
#include <sensor_msgs/Imu.h> #include <sensor_msgs/Imu.h>
#include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/Vector3Stamped.h> #include <geometry_msgs/Vector3Stamped.h>
#include <geometry_msgs/PointStamped.h> #include <geometry_msgs/PointStamped.h>
#include <geometry_msgs/TwistStamped.h> #include <geometry_msgs/TwistStamped.h>
#include <clover/FlowConfig.h>
using cv::Mat; using cv::Mat;
@@ -37,11 +34,12 @@ class OpticalFlow : public nodelet::Nodelet
{ {
public: public:
OpticalFlow(): OpticalFlow():
camera_matrix_(3, 3, CV_64F) camera_matrix_(3, 3, CV_64F),
dist_coeffs_(8, 1, CV_64F),
tf_listener_(tf_buffer_)
{} {}
private: private:
bool enabled_;
ros::Publisher flow_pub_, velo_pub_, shift_pub_; ros::Publisher flow_pub_, velo_pub_, shift_pub_;
ros::Time prev_stamp_; ros::Time prev_stamp_;
std::string fcu_frame_id_, local_frame_id_; std::string fcu_frame_id_, local_frame_id_;
@@ -54,14 +52,9 @@ private:
Mat hann_; Mat hann_;
Mat prev_, curr_; Mat prev_, curr_;
Mat camera_matrix_, dist_coeffs_; Mat camera_matrix_, dist_coeffs_;
std::unique_ptr<tf2_ros::Buffer> tf_buffer_; tf2_ros::Buffer tf_buffer_;
std::unique_ptr<tf2_ros::TransformListener> tf_listener_; tf2_ros::TransformListener tf_listener_;
bool calc_flow_gyro_; 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() void onInit()
{ {
@@ -70,38 +63,25 @@ private:
image_transport::ImageTransport it(nh); image_transport::ImageTransport it(nh);
image_transport::ImageTransport it_priv(nh_priv); image_transport::ImageTransport it_priv(nh_priv);
tf_buffer_.reset(new tf2_ros::Buffer()); nh.param<std::string>("mavros/local_position/tf/frame_id", local_frame_id_, "map");
tf_listener_.reset(new tf2_ros::TransformListener(*tf_buffer_, nh)); nh.param<std::string>("mavros/local_position/tf/child_frame_id", fcu_frame_id_, "base_link");
nh_priv.param("roi", roi_px_, 128);
local_frame_id_ = nh.param<std::string>("mavros/local_position/tf/frame_id", "map"); nh_priv.param("roi_rad", roi_rad_, 0.0);
fcu_frame_id_ = nh.param<std::string>("mavros/local_position/tf/child_frame_id", "base_link"); nh_priv.param("calc_flow_gyro", calc_flow_gyro_, false);
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_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this);
img_pub_ = it_priv.advertise("debug", 1); img_pub_ = it_priv.advertise("debug", 1);
flow_pub_ = nh.advertise<mavros_msgs::OpticalFlowRad>("mavros/px4flow/raw/send", 1); flow_pub_ = nh.advertise<mavros_msgs::OpticalFlowRad>("mavros/px4flow/raw/send", 1);
velo_pub_ = nh_priv.advertise<geometry_msgs::TwistStamped>("angular_velocity", 1); velo_pub_ = nh_priv.advertise<geometry_msgs::TwistStamped>("angular_velocity", 1);
shift_pub_ = nh_priv.advertise<geometry_msgs::Vector3Stamped>("shift", 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_.time_delta_distance_us = 0;
flow_.distance = -1; // no distance sensor available flow_.distance = -1; // no distance sensor available
flow_.temperature = 0; 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"); NODELET_INFO("Optical Flow initialized");
} }
@@ -111,7 +91,9 @@ private:
camera_matrix_.at<double>(i, j) = cinfo->K[3 * i + j]; camera_matrix_.at<double>(i, j) = cinfo->K[3 * i + j];
} }
} }
dist_coeffs_ = cv::Mat(cinfo->D, true); for (int k = 0; k < cinfo->D.size(); k++) {
dist_coeffs_.at<double>(k) = cinfo->D[k];
}
} }
void drawFlow(Mat& frame, double x, double y, double quality) const void drawFlow(Mat& frame, double x, double y, double quality) const
@@ -128,14 +110,6 @@ private:
void flow(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr& cinfo) 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); parseCameraInfo(cinfo);
auto img = cv_bridge::toCvShare(msg, "mono8")->image; auto img = cv_bridge::toCvShare(msg, "mono8")->image;
@@ -169,7 +143,7 @@ private:
img.convertTo(curr_, CV_32F); 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_ = curr_.clone();
prev_stamp_ = msg->header.stamp; prev_stamp_ = msg->header.stamp;
cv::createHanningWindow(hann_, curr_.size(), CV_32F); cv::createHanningWindow(hann_, curr_.size(), CV_32F);
@@ -179,7 +153,7 @@ private:
cv::Point2d shift = cv::phaseCorrelate(prev_, curr_, hann_, &response); cv::Point2d shift = cv::phaseCorrelate(prev_, curr_, hann_, &response);
// Publish raw shift in pixels // 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.stamp = msg->header.stamp;
shift_vec.header.frame_id = msg->header.frame_id; shift_vec.header.frame_id = msg->header.frame_id;
shift_vec.vector.x = shift.x; shift_vec.vector.x = shift.x;
@@ -205,14 +179,14 @@ private:
double flow_x = atan2(points_undist[0].x, focal_length_x); double flow_x = atan2(points_undist[0].x, focal_length_x);
double flow_y = atan2(points_undist[0].y, focal_length_y); double flow_y = atan2(points_undist[0].y, focal_length_y);
// Convert to FCU frame // // Convert to FCU frame
geometry_msgs::Vector3Stamped flow_camera, flow_fcu; static geometry_msgs::Vector3Stamped flow_camera, flow_fcu;
flow_camera.header.frame_id = msg->header.frame_id; flow_camera.header.frame_id = msg->header.frame_id;
flow_camera.header.stamp = msg->header.stamp; flow_camera.header.stamp = msg->header.stamp;
flow_camera.vector.x = flow_y; // +y means counter-clockwise rotation around Y axis flow_camera.vector.x = flow_y; // +y means counter-clockwise rotation around Y axis
flow_camera.vector.y = -flow_x; // +x means clockwise rotation around X axis flow_camera.vector.y = -flow_x; // +x means clockwise rotation around X axis
try { try {
tf_buffer_->transform(flow_camera, flow_fcu, fcu_frame_id_); tf_buffer_.transform(flow_camera, flow_fcu, fcu_frame_id_);
} catch (const tf2::TransformException& e) { } catch (const tf2::TransformException& e) {
// transform is not available yet // transform is not available yet
return; return;
@@ -222,21 +196,18 @@ private:
ros::Duration integration_time = msg->header.stamp - prev_stamp_; ros::Duration integration_time = msg->header.stamp - prev_stamp_;
uint32_t integration_time_us = integration_time.toSec() * 1.0e6; 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_) { if (calc_flow_gyro_) {
try { try {
auto flow_gyro_camera = calcFlowGyro(msg->header.frame_id, prev_stamp_, msg->header.stamp); 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_); tf_buffer_.transform(flow_gyro_camera, flow_gyro_fcu, fcu_frame_id_);
flow_.integrated_xgyro = flow_gyro_fcu.vector.x; flow_.integrated_xgyro = flow_gyro_fcu.vector.x;
flow_.integrated_ygyro = flow_gyro_fcu.vector.y; flow_.integrated_ygyro = flow_gyro_fcu.vector.y;
flow_.integrated_zgyro = flow_gyro_fcu.vector.z; flow_.integrated_zgyro = flow_gyro_fcu.vector.z;
} catch (const tf2::TransformException& e) { } 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_.quality = (uint8_t)(response * 255);
flow_pub_.publish(flow_); 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 // Publish debug image
if (img_pub_.getNumSubscribers() > 0) { if (img_pub_.getNumSubscribers() > 0) {
// publish debug image // publish debug image
@@ -271,14 +230,25 @@ publish_debug:
out_msg.image = img; out_msg.image = img;
img_pub_.publish(out_msg.toImageMsg()); 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;
} }
} }
geometry_msgs::Vector3Stamped calcFlowGyro(const std::string& frame_id, const ros::Time& prev, const ros::Time& curr) geometry_msgs::Vector3Stamped calcFlowGyro(const std::string& frame_id, const ros::Time& prev, const ros::Time& curr)
{ {
tf2::Quaternion prev_rot, curr_rot; tf2::Quaternion prev_rot, curr_rot;
tf2::fromMsg(tf_buffer_->lookupTransform(frame_id, local_frame_id_, prev).transform.rotation, prev_rot); tf2::fromMsg(tf_buffer_.lookupTransform(frame_id, local_frame_id_, prev).transform.rotation, prev_rot);
tf2::fromMsg(tf_buffer_->lookupTransform(frame_id, local_frame_id_, curr, ros::Duration(0.1)).transform.rotation, curr_rot); tf2::fromMsg(tf_buffer_.lookupTransform(frame_id, local_frame_id_, curr, ros::Duration(0.1)).transform.rotation, curr_rot);
geometry_msgs::Vector3Stamped flow; geometry_msgs::Vector3Stamped flow;
flow.header.frame_id = frame_id; flow.header.frame_id = frame_id;
@@ -291,18 +261,6 @@ publish_debug:
return flow; 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) PLUGINLIB_EXPORT_CLASS(OpticalFlow, nodelet::Nodelet)

View File

@@ -91,7 +91,7 @@ private:
void fakeGCSThread() void fakeGCSThread()
{ {
// Awful workaround for fixing PX4 not sending STATUSTEXTs // Awful workaround for fixing PX4 not sending STATUSTEXTs
// if there is no GCS heartbeats. // if there is no GCS hearbeats.
// TODO: use timer // TODO: use timer
// TODO: remove, when PX4 get this fixed. // TODO: remove, when PX4 get this fixed.
ros::Publisher mavlink_pub = nh.advertise<mavros_msgs::Mavlink>("mavlink/to", 1); ros::Publisher mavlink_pub = nh.advertise<mavros_msgs::Mavlink>("mavlink/to", 1);

View File

@@ -9,14 +9,13 @@
# The above copyright notice and this permission notice shall be included in all # The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software. # copies or substantial portions of the Software.
import os, sys import os
import math import math
import subprocess import subprocess
import re import re
from collections import OrderedDict from collections import OrderedDict
import traceback import traceback
import threading from threading import Event
from threading import Event, Thread, Lock
import numpy import numpy
import rospy import rospy
import tf2_ros import tf2_ros
@@ -28,86 +27,67 @@ from mavros_msgs.msg import State, OpticalFlowRad, Mavlink
from mavros_msgs.srv import ParamGet from mavros_msgs.srv import ParamGet
from geometry_msgs.msg import PoseStamped, TwistStamped, PoseWithCovarianceStamped, Vector3Stamped from geometry_msgs.msg import PoseStamped, TwistStamped, PoseWithCovarianceStamped, Vector3Stamped
from visualization_msgs.msg import MarkerArray as VisualizationMarkerArray from visualization_msgs.msg import MarkerArray as VisualizationMarkerArray
from diagnostic_msgs.msg import DiagnosticArray
import tf.transformations as t import tf.transformations as t
from aruco_pose.msg import MarkerArray from aruco_pose.msg import MarkerArray
from mavros import mavlink 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') 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_buffer = tf2_ros.Buffer()
tf_listener = tf2_ros.TransformListener(tf_buffer) tf_listener = tf2_ros.TransformListener(tf_buffer)
thread_local = threading.local() failures = []
reports_lock = Lock() infos = []
current_check = None
# formatting colors
if sys.stdout.isatty():
GREY = '\033[90m'
GREEN = '\033[92m'
RED = '\033[31m'
END = '\033[0m'
else:
GREY = GREEN = RED = END = ''
def failure(text, *args): def failure(text, *args):
msg = text % args msg = text % args
thread_local.reports += [{'failure': msg}] rospy.logwarn('%s: %s', current_check, msg)
failures.append(msg)
def info(text, *args): def info(text, *args):
msg = text % args msg = text % args
thread_local.reports += [{'info': msg}] rospy.loginfo('%s: %s', current_check, msg)
infos.append(msg)
def check(name): def check(name):
def inner(fn): def inner(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
start = rospy.get_time() failures[:] = []
thread_local.reports = [] infos[:] = []
global current_check
current_check = name
try: try:
fn(*args, **kwargs) fn(*args, **kwargs)
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
rospy.logerr('%s: exception occurred', name) rospy.logerr('%s: exception occurred', name)
with reports_lock: return
for report in thread_local.reports: if not failures and not infos:
if 'failure' in report: rospy.loginfo('%s: OK', name)
rospy.logerr('%s: %s', name, report['failure'])
elif 'info' in report:
rospy.loginfo(GREY + name + END + ': ' + report['info'])
if not thread_local.reports:
rospy.loginfo(GREY + name + END + ': ' + GREEN + 'OK' + END)
if rospy.get_param('~time', False):
rospy.loginfo('%s: %.1f sec', name, rospy.get_time() - start)
return wrapper return wrapper
return inner return inner
def ff(value, precision=2):
# safely format float or int
if value is None:
return RED + '???' + END
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) param_get = rospy.ServiceProxy('mavros/param/get', ParamGet)
def get_param(name, default=None, strict=True): def get_param(name):
try: try:
res = param_get(param_id=name) res = param_get(param_id=name)
except rospy.ServiceException as e: except rospy.ServiceException as e:
@@ -115,19 +95,13 @@ def get_param(name, default=None, strict=True):
return None return None
if not res.success: if not res.success:
if strict: failure('unable to retrieve PX4 parameter %s', name)
failure('unable to retrieve PX4 parameter %s', name)
return default
else: else:
if res.value.integer != 0: if res.value.integer != 0:
return res.value.integer return res.value.integer
return res.value.real return res.value.real
def get_paramf(name, precision=2):
return ff(get_param(name), precision)
recv_event = Event() recv_event = Event()
link = mavutil.mavlink.MAVLink('', 255, 1) link = mavutil.mavlink.MAVLink('', 255, 1)
mavlink_pub = rospy.Publisher('mavlink/to', Mavlink, queue_size=1) mavlink_pub = rospy.Publisher('mavlink/to', Mavlink, queue_size=1)
@@ -164,7 +138,7 @@ def mavlink_exec(cmd, timeout=3.0):
timeout=3, timeout=3,
baudrate=0, baudrate=0,
count=len(cmd), count=len(cmd),
data=[ord(c) for c in cmd.ljust(70, '\0')]) data=map(ord, cmd.ljust(70, '\0')))
msg.pack(link) msg.pack(link)
ros_msg = mavlink.convert_to_rosmsg(msg) ros_msg = mavlink.convert_to_rosmsg(msg)
mavlink_pub.publish(ros_msg) mavlink_pub.publish(ros_msg)
@@ -172,24 +146,6 @@ def mavlink_exec(cmd, timeout=3.0):
return mavlink_recv 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 = { BOARD_ROTATIONS = {
0: 'no rotation', 0: 'no rotation',
1: 'yaw 45°', 1: 'yaw 45°',
@@ -235,36 +191,31 @@ def check_fcu():
state = rospy.wait_for_message('mavros/state', State, timeout=3) state = rospy.wait_for_message('mavros/state', State, timeout=3)
if not state.connected: if not state.connected:
failure('no connection to the FCU (check wiring)') failure('no connection to the FCU (check wiring)')
info('fcu_url = %s', rospy.get_param('mavros/fcu_url', '?'))
return return
if not is_process_running('px4', exact=True): # can't use px4 console in SITL # Make sure the console is available to us
clover_tag = re.compile(r'-cl[oe]ver\.\d+$') mavlink_exec('\n')
clover_fw = False version_str = mavlink_exec('ver all')
if version_str == '':
info('no version data available from SITL')
# Make sure the console is available to us r = re.compile(r'^FW (git tag|version): (v?\d\.\d\.\d.*)$')
mavlink_exec('\n') is_clover_firmware = False
version_str = mavlink_exec('ver all') for ver_line in version_str.split('\n'):
if version_str == '': match = r.search(ver_line)
info('no version data available from SITL') 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 not is_clover_firmware:
if line.startswith('FW version: '): failure('not running Clover PX4 firmware, https://clover.coex.tech/firmware')
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')
est = get_param('SYS_MC_EST_GROUP') est = get_param('SYS_MC_EST_GROUP')
if est == 1: if est == 1:
info('selected estimator: LPE') info('selected estimator: LPE')
fuse = int(get_param('LPE_FUSION')) fuse = get_param('LPE_FUSION')
if fuse & (1 << 4): if fuse & (1 << 4):
info('LPE_FUSION: land detector fusion is enabled') info('LPE_FUSION: land detector fusion is enabled')
else: else:
@@ -296,35 +247,21 @@ def check_fcu():
if cbrk_usb_chk != 197848: if cbrk_usb_chk != 197848:
failure('set parameter CBRK_USB_CHK to 197848 for flying with USB connected') 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: try:
info('time sync offset: %.2f s', float(read_diagnostics('mavros: Time Sync', 'Estimated time offset (s)'))) battery = rospy.wait_for_message('mavros/battery', BatteryState, timeout=3)
except: if not battery.cell_voltage:
failure('cannot read time sync offset') 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: except rospy.ROSException:
failure('no MAVROS state') failure('no MAVROS state (check wiring)')
fcu_url = rospy.get_param('mavros/fcu_url', '?')
if fcu_url == '/dev/px4fmu':
if not os.path.exists('/lib/udev/rules.d/99-px4fmu.rules'):
info('udev rules are not installed, install udev rules or change usb_device to /dev/ttyACM0 in mavros.launch')
else:
info('udev did\'t recognize px4fmu device, check wiring or change usb_device to /dev/ttyACM0 in mavros.launch')
info('fcu_url = %s', rospy.get_param('mavros/fcu_url', '?'))
def describe_direction(v): def describe_direction(v):
@@ -391,7 +328,7 @@ def is_process_running(binary, exact=False, full=False):
if exact: if exact:
args.append('-x') # match exactly with the command name args.append('-x') # match exactly with the command name
if full: 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) args.append(binary)
subprocess.check_output(args) subprocess.check_output(args)
return True return True
@@ -401,24 +338,19 @@ def is_process_running(binary, exact=False, full=False):
@check('ArUco markers') @check('ArUco markers')
def check_aruco(): def check_aruco():
markers = None
if is_process_running('aruco_detect', full=True): if is_process_running('aruco_detect', full=True):
try: try:
info('aruco_detect/length = %g m', rospy.get_param('aruco_detect/length', '?')) info('aruco_detect/length = %g m', rospy.get_param('aruco_detect/length'))
except KeyError: except KeyError:
failure('aruco_detect/length parameter is not set') failure('aruco_detect/length parameter is not set')
known_vertical = rospy.get_param('aruco_detect/known_vertical', '') known_tilt = rospy.get_param('aruco_detect/known_tilt', '')
flip_vertical = rospy.get_param('aruco_detect/flip_vertical', False) if known_tilt == 'map':
description = '' known_tilt += ' (ALL markers are on the floor)'
if known_vertical == 'map' and not flip_vertical: elif known_tilt == 'map_flipped':
description = ' (all markers are on the floor)' known_tilt += ' (ALL markers are on the ceiling)'
elif known_vertical == 'map' and flip_vertical: info('aruco_detector/known_tilt = %s', known_tilt)
description = ' (all markers are on the ceiling)'
info('aruco_detect/known_vertical = %s', known_vertical)
info('aruco_detect/flip_vertical = %s%s', flip_vertical, description)
try: 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: except rospy.ROSException:
failure('no markers detection') failure('no markers detection')
return return
@@ -427,61 +359,42 @@ def check_aruco():
return return
if is_process_running('aruco_map', full=True): if is_process_running('aruco_map', full=True):
known_vertical = rospy.get_param('aruco_map/known_vertical', '') known_tilt = rospy.get_param('aruco_map/known_tilt', '')
flip_vertical = rospy.get_param('aruco_map/flip_vertical', False) if known_tilt == 'map':
description = '' known_tilt += ' (marker\'s map is on the floor)'
if known_vertical == 'map' and not flip_vertical: elif known_tilt == 'map_flipped':
description += ' (markers map is on the floor)' known_tilt += ' (marker\'s map is on the ceiling)'
elif known_vertical == 'map' and flip_vertical: info('aruco_map/known_tilt = %s', known_tilt)
description += ' (markers map is on the ceiling)'
info('aruco_map/known_vertical = %s', known_vertical)
info('aruco_map/flip_vertical = %s%s', flip_vertical, description)
try: 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)) info('map has %s markers', len(visualization.markers))
except: except:
failure('cannot read aruco_map/visualization topic') failure('cannot read aruco_map/visualization topic')
try: 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: except rospy.ROSException:
if not markers: failure('no map detection')
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')
else: else:
info('aruco_map is not running') 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') @check('Vision position estimate')
def check_vpe(): def check_vpe():
vis = None vis = None
try: 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: except rospy.ROSException:
try: 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: except rospy.ROSException:
if not is_process_running('vpe_publisher', full=True): failure('no VPE or MoCap messages')
info('no vision position estimate, vpe_publisher is not running') # check if vpe_publisher is running
elif rospy.get_param('aruco_map/known_vertical', '') == 'map' \ try:
and rospy.get_param('aruco_map/flip_vertical', False): subprocess.check_output(['pgrep', '-x', 'vpe_publisher'])
failure('no vision position estimate, markers are on the ceiling') except subprocess.CalledProcessError:
elif is_on_the_floor(): return # it's not running, skip following checks
info('no vision position estimate, the drone is on the floor')
else:
failure('no vision position estimate')
# check PX4 settings # check PX4 settings
est = get_param('SYS_MC_EST_GROUP') est = get_param('SYS_MC_EST_GROUP')
@@ -493,37 +406,26 @@ def check_vpe():
if vision_yaw_w == 0: if vision_yaw_w == 0:
failure('vision yaw weight is zero, change ATT_W_EXT_HDG parameter') failure('vision yaw weight is zero, change ATT_W_EXT_HDG parameter')
else: else:
info('vision yaw weight: %s', ff(vision_yaw_w)) info('Vision yaw weight: %.2f', vision_yaw_w)
fuse = int(get_param('LPE_FUSION')) fuse = get_param('LPE_FUSION')
if not fuse & (1 << 2): if not fuse & (1 << 2):
failure('vision position fusion is disabled, change LPE_FUSION parameter') failure('vision position fusion is disabled, change LPE_FUSION parameter')
delay = get_param('LPE_VIS_DELAY') delay = get_param('LPE_VIS_DELAY')
if delay != 0: if delay != 0:
failure('LPE_VIS_DELAY = %s, but it should be zero', delay) failure('LPE_VIS_DELAY parameter is %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')) 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: elif est == 2:
ev_ctrl = get_param('EKF2_EV_CTRL', strict=False) fuse = get_param('EKF2_AID_MASK')
if ev_ctrl is not None: # PX4 after v1.14 if not fuse & (1 << 3):
ev_ctrl = int(ev_ctrl) failure('vision position fusion is disabled, change EKF2_AID_MASK parameter')
if not ev_ctrl & (1 << 0): if not fuse & (1 << 4):
failure('vision horizontal position fusion is disabled, change EKF2_EV_CTRL parameter') failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
if not ev_ctrl & (1 << 1):
failure('vision vertical position fusion is disabled, change EKF2_EV_CTRL parameter')
if not ev_ctrl & (1 << 3):
failure('vision yaw fusion is disabled, change EKF2_EV_CTRL parameter')
else: # PX4 before v1.14
fuse = int(get_param('EKF2_AID_MASK'))
if not fuse & (1 << 3):
failure('vision position fusion is disabled, change EKF2_AID_MASK parameter')
if not fuse & (1 << 4):
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
delay = get_param('EKF2_EV_DELAY') delay = get_param('EKF2_EV_DELAY')
if delay != 0: if delay != 0:
failure('EKF2_EV_DELAY = %.2f, but it should be zero', delay) failure('EKF2_EV_DELAY is %.2f, but it should be zero', delay)
info('EKF2_EVA_NOISE = %s, EKF2_EVP_NOISE = %s', info('EKF2_EVA_NOISE is %.3f, EKF2_EVP_NOISE is %.3f',
get_paramf('EKF2_EVA_NOISE', 3), get_param('EKF2_EVA_NOISE'),
get_paramf('EKF2_EVP_NOISE', 3)) get_param('EKF2_EVP_NOISE'))
if not vis: if not vis:
return return
@@ -581,12 +483,6 @@ def check_local_position():
failure('roll is %.2f deg; place copter horizontally or redo level horizon calib', failure('roll is %.2f deg; place copter horizontally or redo level horizon calib',
math.degrees(roll)) 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: except rospy.ROSException:
failure('no local position') failure('no local position')
@@ -621,25 +517,13 @@ def check_velocity():
@check('Global position (GPS)') @check('Global position (GPS)')
def check_global_position(): def check_global_position():
try: 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: except rospy.ROSException:
info('no global position') info('no global position')
if get_param('SYS_MC_EST_GROUP') == 2:
gps_ctrl = get_param('EKF2_GPS_CTRL', strict=False)
if gps_ctrl is not None: # PX4 after v1.14
if int(gps_ctrl) & (1 << 0):
failure('GPS fusion enabled may suppress vision position aiding, change EKF2_GPS_CTRL')
else: # PX4 before v1.14
if int(get_param('EKF2_AID_MASK', 0)) & (1 << 0):
failure('GPS fusion enabled may suppress vision position aiding, change EKF2_AID_MASK')
@check('Optical flow') @check('Optical flow')
def check_optical_flow(): def check_optical_flow():
if not is_process_running('optical_flow', full=True):
info('optical_flow is not running')
return
# TODO:check FPS! # TODO:check FPS!
try: try:
rospy.wait_for_message('mavros/px4flow/raw/send', OpticalFlowRad, timeout=0.5) rospy.wait_for_message('mavros/px4flow/raw/send', OpticalFlowRad, timeout=0.5)
@@ -647,49 +531,40 @@ def check_optical_flow():
# check PX4 settings # check PX4 settings
rot = get_param('SENS_FLOW_ROT') rot = get_param('SENS_FLOW_ROT')
if rot != 0: 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') est = get_param('SYS_MC_EST_GROUP')
if est == 1: if est == 1:
fuse = int(get_param('LPE_FUSION')) fuse = get_param('LPE_FUSION')
if not fuse & (1 << 1): if not fuse & (1 << 1):
failure('optical flow fusion is disabled, change LPE_FUSION parameter') failure('optical flow fusion is disabled, change LPE_FUSION parameter')
if not fuse & (1 << 1): if not fuse & (1 << 1):
failure('flow gyro compensation is disabled, change LPE_FUSION parameter') 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): 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', 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_paramf('LPE_FLW_QMIN'), get_param('LPE_FLW_QMIN'),
get_paramf('LPE_FLW_R', 4), get_param('LPE_FLW_R'),
get_paramf('LPE_FLW_RR', 4)) get_param('LPE_FLW_RR'),
get_param('SENS_FLOW_MINHGT'),
get_param('SENS_FLOW_MAXHGT'))
elif est == 2: elif est == 2:
of_ctrl = get_param('EKF2_OF_CTRL', strict=False) fuse = get_param('EKF2_AID_MASK')
if of_ctrl is not None: # PX4 after v1.14 if not fuse & (1 << 1):
if of_ctrl == 0: failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
failure('optical flow fusion is disabled, change EKF2_OF_CTRL') delay = get_param('EKF2_OF_DELAY')
else: # PX4 before v1.14
fuse = int(get_param('EKF2_AID_MASK', 0))
if not fuse & (1 << 1):
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
delay = get_param('EKF2_OF_DELAY', 0)
if delay != 0: if delay != 0:
failure('EKF2_OF_DELAY = %.2f, but it should be zero', delay) failure('EKF2_OF_DELAY is %.2f, but it should be zero', delay)
info('EKF2_OF_QMIN = %s, EKF2_OF_N_MIN = %s, EKF2_OF_N_MAX = %s', 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_paramf('EKF2_OF_QMIN'), get_param('EKF2_OF_QMIN'),
get_paramf('EKF2_OF_N_MIN', 4), get_param('EKF2_OF_N_MIN'),
get_paramf('EKF2_OF_N_MAX', 4)) get_param('EKF2_OF_N_MAX'),
info('SENS_FLOW_MINHGT = %s, SENS_FLOW_MAXHGT = %s', get_paramf('SENS_FLOW_MINHGT', 3), get_paramf('SENS_FLOW_MAXHGT', 3)) get_param('SENS_FLOW_MINHGT'),
get_param('SENS_FLOW_MAXHGT'))
except rospy.ROSException: except rospy.ROSException:
if rospy.get_param('optical_flow/disable_on_vpe', False): failure('no optical flow data (from Raspberry)')
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')
@check('Rangefinder') @check('Rangefinder')
@@ -713,65 +588,55 @@ def check_rangefinder():
est = get_param('SYS_MC_EST_GROUP') est = get_param('SYS_MC_EST_GROUP')
if est == 1: if est == 1:
fuse = int(get_param('LPE_FUSION', 0)) fuse = get_param('LPE_FUSION')
if not fuse & (1 << 5): if not fuse & (1 << 5):
info('"pub agl as lpos down" in LPE_FUSION is disabled, NOT operating over flat surface') info('"pub agl as lpos down" in LPE_FUSION is disabled, NOT operating over flat surface')
else: else:
info('"pub agl as lpos down" in LPE_FUSION is enabled, operating over flat surface') info('"pub agl as lpos down" in LPE_FUSION is enabled, operating over flat surface')
elif est == 2: elif est == 2:
hgt = get_param('EKF2_HGT_REF', strict=False) hgt = get_param('EKF2_HGT_MODE')
if hgt is None: # PX4 before v1.14
hgt = get_param('EKF2_HGT_MODE')
if hgt != 2: if hgt != 2:
info('EKF2_HGT_MODE != Range sensor, NOT operating over flat surface') info('EKF2_HGT_MODE != Range sensor, NOT operating over flat surface')
else: else:
info('EKF2_HGT_MODE = Range sensor, operating over flat surface') info('EKF2_HGT_MODE = Range sensor, operating over flat surface')
aid = get_param('EKF2_RNG_AID', strict=False) aid = get_param('EKF2_RNG_AID')
if aid is not None: # PX4 before v1.14 if aid != 1:
if aid != 1: info('EKF2_RNG_AID != 1, range sensor aiding disabled')
info('EKF2_RNG_AID != 1, range sensor aiding disabled') else:
else: info('EKF2_RNG_AID = 1, range sensor aiding enabled')
info('EKF2_RNG_AID = 1, range sensor aiding enabled')
@check('Boot duration') @check('Boot duration')
def check_boot_duration(): def check_boot_duration():
if not os.path.exists('/etc/clover_version'): output = subprocess.check_output('systemd-analyze')
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) r = re.compile(r'([\d\.]+)s\s*$', flags=re.MULTILINE)
duration = float(r.search(output).groups()[0]) duration = float(r.search(output).groups()[0])
if duration > 20: if duration > 15:
failure('long Raspbian boot duration: %ss (systemd-analyze for analyzing)', duration) failure('long Raspbian boot duration: %ss (systemd-analyze for analyzing)', duration)
@check('CPU usage') @check('CPU usage')
def 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); }'" 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() output = subprocess.check_output(CMD, shell=True)
processes = output.split('\n') processes = output.split('\n')
for process in processes: for process in processes:
if not process: if not process:
continue continue
pid, cpu, cmd = process.split('\t') 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)', failure('high CPU usage (%s%%) detected: %s (PID %s)',
cpu.strip(), cmd.strip(), pid.strip()) cpu.strip(), cmd.strip(), pid.strip())
@check('clover.service') @check('clover.service')
def 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: try:
output = subprocess.check_output('systemctl show -p ActiveState --value clover.service'.split(), output = subprocess.check_output('systemctl show -p ActiveState --value clover.service'.split(),
stderr=subprocess.STDOUT).decode() stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
failure('systemctl returned %s: %s', e.returncode, e.output) failure('systemctl returned %s: %s', e.returncode, e.output)
return return
@@ -781,22 +646,13 @@ def check_clover_service():
elif 'failed' in output: elif 'failed' in output:
failure('service failed to run, check your launch-files') failure('service failed to run, check your launch-files')
BLACKLIST = 'Unexpected command 520', 'Time jump detected', 'different index:' r = re.compile(r'^(.*)\[(FATAL|ERROR)\] \[\d+.\d+\]: (.*?)(\x1b(.*))?$')
r = re.compile(r'^(.*)\[(FATAL|ERROR| WARN)\] \[\d+.\d+\]: (.*?)(\x1b(.*))?$')
error_count = OrderedDict() error_count = OrderedDict()
try: try:
for line in open('/tmp/clover.err', 'r'): 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) node_error = r.search(line)
if node_error: 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: if msg in error_count:
error_count[msg] += 1 error_count[msg] += 1
else: else:
@@ -819,18 +675,11 @@ def check_image():
try: try:
info('version: %s', open('/etc/clover_version').read().strip()) info('version: %s', open('/etc/clover_version').read().strip())
except IOError: except IOError:
try: info('no /etc/clover_version file, not the Clover image?')
info('VM version: %s', open('/etc/clover_vm_version').read().strip())
except IOError:
info('no /etc/clover_version file, not the Clover image?')
@check('Preflight status') @check('Preflight status')
def 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 # Make sure the console is available to us
mavlink_exec('\n') mavlink_exec('\n')
cmdr_output = mavlink_exec('commander check') cmdr_output = mavlink_exec('commander check')
@@ -852,11 +701,7 @@ def check_preflight_status():
@check('Network') @check('Network')
def check_network(): def check_network():
if not os.path.exists('/etc/clover_version'): ros_hostname = os.environ.get('ROS_HOSTNAME').strip()
# TODO:
return # Don't check not on Clover's image
ros_hostname = os.environ.get('ROS_HOSTNAME', '').strip()
if not ros_hostname: if not ros_hostname:
failure('no ROS_HOSTNAME is set') failure('no ROS_HOSTNAME is set')
@@ -878,14 +723,6 @@ def check_network():
@check('RPi health') @check('RPi health')
def 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 # `vcgencmd get_throttled` output codes taken from
# https://github.com/raspberrypi/documentation/blob/JamesH65-patch-vcgencmd-vcdbg-docs/raspbian/applications/vcgencmd.md#get_throttled # https://github.com/raspberrypi/documentation/blob/JamesH65-patch-vcgencmd-vcdbg-docs/raspbian/applications/vcgencmd.md#get_throttled
# TODO: support more base platforms? # TODO: support more base platforms?
@@ -914,9 +751,9 @@ def check_rpi_health():
# <parameter>=<value> # <parameter>=<value>
# In case of `get_throttled`, <value> is a hexadecimal number # In case of `get_throttled`, <value> is a hexadecimal number
# with some of the FLAGs OR'ed together # with some of the FLAGs OR'ed together
output = subprocess.check_output(['vcgencmd', 'get_throttled']).decode() output = subprocess.check_output(['vcgencmd', 'get_throttled'])
except OSError: except OSError:
info('could not call vcgencmd binary; not a Raspberry Pi?') failure('could not call vcgencmd binary; not a Raspberry Pi?')
return return
throttle_mask = int(output.split('=')[1], base=16) throttle_mask = int(output.split('=')[1], base=16)
@@ -933,47 +770,26 @@ def check_board():
info('could not open /proc/device-tree/model, not a Raspberry Pi?') 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(): def selfcheck():
checks = [ check_image()
check_image, check_board()
check_board, check_clover_service()
check_clover_service, check_network()
check_network, check_fcu()
check_fcu, check_imu()
check_imu, check_local_position()
check_local_position, check_velocity()
check_velocity, check_global_position()
check_global_position, check_preflight_status()
check_preflight_status, check_main_camera()
check_main_camera, check_aruco()
check_aruco, check_simpleoffboard()
check_simpleoffboard, check_optical_flow()
check_optical_flow, check_vpe()
check_vpe, check_rangefinder()
check_rangefinder, check_rpi_health()
check_rpi_health, check_cpu_usage()
check_cpu_usage, check_boot_duration()
check_boot_duration,
]
if rospy.get_param('~parallel', False):
parallel_for(checks)
else:
consequentially_for(checks)
if __name__ == '__main__': if __name__ == '__main__':

File diff suppressed because it is too large Load Diff

View File

@@ -11,14 +11,12 @@
#include <string> #include <string>
#include <ros/ros.h> #include <ros/ros.h>
#include <tf/transform_datatypes.h>
#include <tf2/transform_datatypes.h> #include <tf2/transform_datatypes.h>
#include <tf2_ros/buffer.h> #include <tf2_ros/buffer.h>
#include <tf2_ros/transform_listener.h> #include <tf2_ros/transform_listener.h>
#include <tf2_ros/static_transform_broadcaster.h> #include <tf2_ros/static_transform_broadcaster.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h> #include <tf2_geometry_msgs/tf2_geometry_msgs.h>
#include <geometry_msgs/TransformStamped.h> #include <geometry_msgs/TransformStamped.h>
#include <geometry_msgs/Quaternion.h>
#include <geometry_msgs/PoseStamped.h> #include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/PoseWithCovarianceStamped.h> #include <geometry_msgs/PoseWithCovarianceStamped.h>
#include <std_srvs/Trigger.h> #include <std_srvs/Trigger.h>
@@ -27,7 +25,7 @@
using std::string; using std::string;
using namespace geometry_msgs; using namespace geometry_msgs;
bool reset_flag = true; // offset should be reset on the start bool reset_flag = false;
string local_frame_id, frame_id, child_frame_id, offset_frame_id; string local_frame_id, frame_id, child_frame_id, offset_frame_id;
tf2_ros::Buffer tf_buffer; tf2_ros::Buffer tf_buffer;
ros::Publisher vpe_pub; ros::Publisher vpe_pub;
@@ -35,14 +33,14 @@ ros::Subscriber local_position_sub;
ros::Timer zero_timer; ros::Timer zero_timer;
PoseStamped vpe, pose; PoseStamped vpe, pose;
ros::Time got_local_pos(0); 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; TransformStamped offset;
void publishZero(const ros::TimerEvent& e) 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()) { if (got_local_pos.isZero()) {
ROS_INFO("got local position"); ROS_INFO("got local position");
got_local_pos = e.current_real; got_local_pos = e.current_real;
@@ -55,7 +53,7 @@ void publishZero(const ros::TimerEvent& e)
} }
ROS_INFO_THROTTLE(10, "publish zero"); ROS_INFO_THROTTLE(10, "publish zero");
geometry_msgs::PoseStamped zero; static geometry_msgs::PoseStamped zero;
zero.header.frame_id = local_frame_id; zero.header.frame_id = local_frame_id;
zero.header.stamp = e.current_real; zero.header.stamp = e.current_real;
zero.pose.orientation.w = 1; zero.pose.orientation.w = 1;
@@ -68,13 +66,6 @@ inline Pose getPose(const PoseStampedConstPtr& pose) { return pose->pose; }
inline Pose getPose(const PoseWithCovarianceStampedConstPtr& pose) { return pose->pose.pose; } inline Pose getPose(const PoseWithCovarianceStampedConstPtr& pose) { return pose->pose.pose; }
inline void keepYaw(Quaternion& quaternion)
{
tf::Quaternion q;
q.setRPY(0, 0, tf::getYaw(quaternion));
tf::quaternionTFToMsg(q, quaternion);
}
template <typename T> template <typename T>
void callback(const T& msg) void callback(const T& msg)
{ {
@@ -97,29 +88,10 @@ void callback(const T& msg)
if (!offset_frame_id.empty()) { if (!offset_frame_id.empty()) {
if (reset_flag || msg->header.stamp - vpe.header.stamp > offset_timeout) { if (reset_flag || msg->header.stamp - vpe.header.stamp > offset_timeout) {
// calculate the offset // calculate the offset
if (!frame_id.empty()) { offset = tf_buffer.lookupTransform(local_frame_id, frame_id,
// calculate from TF msg->header.stamp, ros::Duration(0.02));
offset = tf_buffer.lookupTransform(local_frame_id, frame_id, // offset.header.frame_id = vpe.header.frame_id;
msg->header.stamp, ros::Duration(0.02)); offset.child_frame_id = offset_frame_id;
// offset.header.frame_id = vpe.header.frame_id;
offset.child_frame_id = offset_frame_id;
} else {
// calculate transform between pose in vpe frame and pose in local frame
TransformStamped local_pose = tf_buffer.lookupTransform(local_frame_id, child_frame_id,
msg->header.stamp, ros::Duration(0.02));
keepYaw(local_pose.transform.rotation);
tf::Transform vpeTransform, poseTransform;
tf::poseMsgToTF(vpe.pose, vpeTransform);
tf::transformMsgToTF(local_pose.transform, poseTransform);
tf::Transform offset_tf = vpeTransform.inverseTimes(poseTransform);
tf::transformTFToMsg(offset_tf, offset.transform);
offset.header.frame_id = local_frame_id;
offset.header.stamp = msg->header.stamp;
offset.child_frame_id = offset_frame_id;
}
br.sendTransform(offset); br.sendTransform(offset);
reset_flag = false; reset_flag = false;
ROS_INFO("offset reset"); ROS_INFO("offset reset");
@@ -150,11 +122,10 @@ int main(int argc, char **argv) {
tf2_ros::TransformListener tf_listener(tf_buffer); tf2_ros::TransformListener tf_listener(tf_buffer);
nh_priv.param<string>("frame_id", frame_id, ""); // name for used visual pose frame nh_priv.param<string>("frame_id", frame_id, "");
nh_priv.param<string>("offset_frame_id", offset_frame_id, ""); // name for published offset frame nh_priv.param<string>("offset_frame_id", offset_frame_id, "");
nh_priv.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
nh.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");
nh.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)); offset_timeout = ros::Duration(nh_priv.param("offset_timeout", 3.0));
if (!frame_id.empty()) { if (!frame_id.empty()) {
@@ -170,11 +141,11 @@ int main(int argc, char **argv) {
vpe_pub = nh_priv.advertise<PoseStamped>("vpe", 1); vpe_pub = nh_priv.advertise<PoseStamped>("vpe", 1);
//vpe_cov_pub = nh_priv_.advertise<PoseStamped>("pose_cov_pub", 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 // publish zero to initialize the local position
zero_timer = nh.createTimer(ros::Duration(0.1), &publishZero); zero_timer = nh.createTimer(ros::Duration(0.1), &publishZero);
publish_zero_timeout = ros::Duration(nh_priv.param("force_init_timeout", 5.0)); publish_zero_timout = ros::Duration(nh_priv.param("publish_zero_timout", 5.0));
publish_zero_duration = ros::Duration(nh_priv.param("force_init_duration", 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); local_position_sub = nh.subscribe("mavros/local_position/pose", 1, &localPositionCallback);
} }

View File

@@ -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}"

Some files were not shown because too many files have changed in this diff Show More