Compare commits
168 Commits
test-raw-b
...
fix-deps
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4512204206 | ||
|
|
c55e0cb7e1 | ||
|
|
b8344dbb84 | ||
|
|
3b7242f3d6 | ||
|
|
cfeff0c74d | ||
|
|
7d022a5af1 | ||
|
|
ebd9c03251 | ||
|
|
5755300d3a | ||
|
|
8c5551b00b | ||
|
|
42c26aa645 | ||
|
|
f91dc4df71 | ||
|
|
e31b69a790 | ||
|
|
7251a76315 | ||
|
|
921e09c392 | ||
|
|
9e69bdb01b | ||
|
|
50495a9de9 | ||
|
|
12ccd919a2 | ||
|
|
f0eacfc0f7 | ||
|
|
742d0535c3 | ||
|
|
af1b993e64 | ||
|
|
d3bda9df48 | ||
|
|
939086362a | ||
|
|
7cf14373b0 | ||
|
|
f428dfdb50 | ||
|
|
76982dc198 | ||
|
|
29f01c25e0 | ||
|
|
7ca0ede1d7 | ||
|
|
c3d87b1608 | ||
|
|
47901dcff2 | ||
|
|
9404d4be6d | ||
|
|
ad51d86464 | ||
|
|
9a713057b6 | ||
|
|
7b591d350c | ||
|
|
2f8915ce31 | ||
|
|
6fb84ae584 | ||
|
|
bf4f680164 | ||
|
|
c0baf30c96 | ||
|
|
8f2c3b2e55 | ||
|
|
6423eb91a2 | ||
|
|
22d7236a47 | ||
|
|
91d33a5961 | ||
|
|
2997951371 | ||
|
|
a2ffcf381c | ||
|
|
9aab324f60 | ||
|
|
984fb39b85 | ||
|
|
3a1a9d486c | ||
|
|
55297696d6 | ||
|
|
371f244228 | ||
|
|
ab3e7ac951 | ||
|
|
cdd6195f0b | ||
|
|
c9b015148f | ||
|
|
2054472c23 | ||
|
|
b1084f99b9 | ||
|
|
c5f405c4d9 | ||
|
|
099d39d42d | ||
|
|
c9035790f2 | ||
|
|
95da57fea1 | ||
|
|
ad0138cd26 | ||
|
|
d6101dc0a3 | ||
|
|
cbba62d165 | ||
|
|
28ddbbcdf9 | ||
|
|
cac6b59a56 | ||
|
|
c82490a0c1 | ||
|
|
808726b4b7 | ||
|
|
19fde7095f | ||
|
|
5e9f442996 | ||
|
|
68903373b0 | ||
|
|
ae05710a37 | ||
|
|
4c576ba5d4 | ||
|
|
ffd8b98e53 | ||
|
|
69deeae32f | ||
|
|
df66deb32c | ||
|
|
87a51221bc | ||
|
|
08bda736e9 | ||
|
|
56a2be8170 | ||
|
|
59518fddd1 | ||
|
|
25ae694d1f | ||
|
|
f78a03ec89 | ||
|
|
0cfdac43ec | ||
|
|
cb2850b1d4 | ||
|
|
460c3fdbe1 | ||
|
|
e3fb7cf28e | ||
|
|
3b930d48d2 | ||
|
|
f3aadd11ec | ||
|
|
976c7114e5 | ||
|
|
d8662007fe | ||
|
|
ac1ac33a1a | ||
|
|
95db8ba1b1 | ||
|
|
94a95b28b3 | ||
|
|
d4a83bdf58 | ||
|
|
cb1773b708 | ||
|
|
5afbcff949 | ||
|
|
3870e62be7 | ||
|
|
f719406c8b | ||
|
|
72f8d901d5 | ||
|
|
393801b023 | ||
|
|
a0322c55f2 | ||
|
|
3662f512a7 | ||
|
|
277eb7297f | ||
|
|
e719b0f1e2 | ||
|
|
e65d380b4b | ||
|
|
8fe34e90e6 | ||
|
|
54ab5ab4b5 | ||
|
|
2cda68ae4a | ||
|
|
d24b6617a4 | ||
|
|
640ec1ee1a | ||
|
|
96ea78f141 | ||
|
|
5e3b07ff5e | ||
|
|
92748a760b | ||
|
|
8512e8a045 | ||
|
|
8b1b365e67 | ||
|
|
2cd77662df | ||
|
|
64f939d7ed | ||
|
|
9a8aa00bc7 | ||
|
|
3f3d1cd220 | ||
|
|
9c34d7722c | ||
|
|
19e0d725b0 | ||
|
|
6fafaf3184 | ||
|
|
8f09df6b34 | ||
|
|
c5d01c678a | ||
|
|
2b13aa02eb | ||
|
|
45042cd6f5 | ||
|
|
ec9ddf5fd2 | ||
|
|
c5399868cb | ||
|
|
a6cee773ab | ||
|
|
d03cfe00ca | ||
|
|
0fb101cc59 | ||
|
|
0d44ff3993 | ||
|
|
dc5da00abd | ||
|
|
4f00960db3 | ||
|
|
ce0b4eb428 | ||
|
|
ccbd1cbad9 | ||
|
|
4b397670a1 | ||
|
|
89bfc150f3 | ||
|
|
2dda726d3e | ||
|
|
6b05cb34e5 | ||
|
|
22293c2220 | ||
|
|
38a3f656ab | ||
|
|
2e79979411 | ||
|
|
b165e154f5 | ||
|
|
99fad312c5 | ||
|
|
ee17a3bada | ||
|
|
1461dd22f4 | ||
|
|
2c07bbffe3 | ||
|
|
0b62f677af | ||
|
|
070c23be53 | ||
|
|
c907e6041a | ||
|
|
69d5d1e521 | ||
|
|
1700ad24df | ||
|
|
6361984794 | ||
|
|
7f31fdd320 | ||
|
|
f9450fe03d | ||
|
|
b41cb6b581 | ||
|
|
b855c4586a | ||
|
|
26245dfb42 | ||
|
|
d6f9327ede | ||
|
|
0f5f111f46 | ||
|
|
4e9d8a64d0 | ||
|
|
94171d51ac | ||
|
|
eb448ae0e7 | ||
|
|
c0707e066a | ||
|
|
91c6998633 | ||
|
|
7b431fa021 | ||
|
|
1e12498cb2 | ||
|
|
43037f515d | ||
|
|
2ea848721c | ||
|
|
d06b0a0cd2 | ||
|
|
1efe10c9dd |
1
.github/workflows/build-image.yaml
vendored
@@ -7,6 +7,7 @@ on:
|
|||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
release:
|
release:
|
||||||
types: [ created ]
|
types: [ created ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
37
.github/workflows/build.yml
vendored
@@ -5,6 +5,7 @@ on:
|
|||||||
branches: [ '*' ]
|
branches: [ '*' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# melodic:
|
# melodic:
|
||||||
@@ -16,8 +17,36 @@ jobs:
|
|||||||
# docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:melodic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh
|
# docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:melodic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh
|
||||||
noetic:
|
noetic:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
container: ros:noetic-ros-base
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: catkin_ws
|
||||||
|
shell: bash
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Native Noetic build
|
with:
|
||||||
run: |
|
path: catkin_ws/src/clover
|
||||||
docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:noetic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh
|
- 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
|
||||||
|
|||||||
11
.github/workflows/docs.yml
vendored
@@ -5,16 +5,13 @@ on:
|
|||||||
branches: [ '*' ]
|
branches: [ '*' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ '*' ]
|
branches: [ '*' ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pages: write
|
pages: write
|
||||||
id-token: write
|
id-token: write
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: "pages"
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -75,6 +72,9 @@ jobs:
|
|||||||
|
|
||||||
deploy-docs:
|
deploy-docs:
|
||||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: true
|
||||||
environment:
|
environment:
|
||||||
name: github-pages
|
name: github-pages
|
||||||
url: ${{ steps.deployment.outputs.page_url }}
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
@@ -82,5 +82,8 @@ jobs:
|
|||||||
needs: docs
|
needs: docs
|
||||||
steps:
|
steps:
|
||||||
- name: Deploy to GitHub Pages
|
- name: Deploy to GitHub Pages
|
||||||
|
env:
|
||||||
|
FREEZE_DOCS: ${{ secrets.FREEZE_DOCS }}
|
||||||
|
if: ${{ !env.FREEZE_DOCS }}
|
||||||
id: deployment
|
id: deployment
|
||||||
uses: actions/deploy-pages@v1
|
uses: actions/deploy-pages@v1
|
||||||
|
|||||||
1
.github/workflows/editorconfig.yaml
vendored
@@ -5,6 +5,7 @@ on:
|
|||||||
branches: [ '*' ]
|
branches: [ '*' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ master ]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
editorconfig:
|
editorconfig:
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Clover drone is used on a wide range of educational events, including [Copter Ha
|
|||||||
|
|
||||||
Preconfigured image for Raspberry Pi with installed and configured software, ready to fly, is available [in the Releases section](https://github.com/CopterExpress/clover/releases).
|
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).
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
Image features:
|
Image features:
|
||||||
|
|||||||
@@ -251,4 +251,5 @@ if (CATKIN_ENABLE_TESTING)
|
|||||||
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/crash_opencv.test)
|
||||||
|
add_rostest(test/duplicate.test)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ 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_tilt` (*string*) – known tilt (pitch and roll) of all the markers as a frame
|
* `~known_vertical` (*string*) – known vertical (Z axis) of all the markers as a frame
|
||||||
|
* `~flip_vertical` – flip vertical vector
|
||||||
|
|
||||||
### Topics
|
### Topics
|
||||||
|
|
||||||
@@ -71,7 +72,8 @@ 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_tilt` – known tilt (pitch and roll) of markers map as a frame
|
* `~known_vertical` – known vertical (Z axis) of markers map as a frame
|
||||||
|
* `~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)
|
||||||
|
|||||||
@@ -4,7 +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
|
||||||
|
|
||||||
p = cv2.aruco.DetectorParameters_create()
|
try:
|
||||||
|
p = cv2.aruco.DetectorParameters_create()
|
||||||
|
except AttributeError:
|
||||||
|
p = cv2.aruco.DetectorParameters()
|
||||||
|
|
||||||
gen = ParameterGenerator()
|
gen = ParameterGenerator()
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<package format="2">
|
<package format="3">
|
||||||
<name>aruco_pose</name>
|
<name>aruco_pose</name>
|
||||||
<version>0.23.0</version>
|
<version>0.24.0</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,6 +28,8 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -50,6 +50,7 @@
|
|||||||
#include <aruco_pose/DetectorConfig.h>
|
#include <aruco_pose/DetectorConfig.h>
|
||||||
#include <aruco_pose/SetMarkers.h>
|
#include <aruco_pose/SetMarkers.h>
|
||||||
|
|
||||||
|
#include "draw.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -71,11 +72,12 @@ private:
|
|||||||
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_;
|
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_;
|
ros::Duration transform_timeout_;
|
||||||
std::unordered_map<int, double> length_override_;
|
std::unordered_map<int, double> length_override_;
|
||||||
std::string frame_id_prefix_, known_tilt_;
|
std::string frame_id_prefix_, known_vertical_;
|
||||||
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_;
|
||||||
@@ -95,6 +97,8 @@ public:
|
|||||||
dictionary = nh_priv_.param("dictionary", 2);
|
dictionary = nh_priv_.param("dictionary", 2);
|
||||||
estimate_poses_ = nh_priv_.param("estimate_poses", true);
|
estimate_poses_ = nh_priv_.param("estimate_poses", true);
|
||||||
send_tf_ = nh_priv_.param("send_tf", true);
|
send_tf_ = nh_priv_.param("send_tf", true);
|
||||||
|
use_map_markers_ = nh_priv_.param("use_map_markers", false);
|
||||||
|
waiting_for_map_ = use_map_markers_;
|
||||||
if (estimate_poses_ && !nh_priv_.getParam("length", length_)) {
|
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();
|
||||||
@@ -102,7 +106,8 @@ public:
|
|||||||
readLengthOverride(nh_priv_);
|
readLengthOverride(nh_priv_);
|
||||||
transform_timeout_ = ros::Duration(nh_priv_.param("transform_timeout", 0.02));
|
transform_timeout_ = ros::Duration(nh_priv_.param("transform_timeout", 0.02));
|
||||||
|
|
||||||
known_tilt_ = nh_priv_.param<std::string>("known_tilt", "");
|
known_vertical_ = nh_priv_.param("known_vertical", nh_priv_.param("known_tilt", std::string(""))); // known_tilt is an old name
|
||||||
|
flip_vertical_ = nh_priv_.param<bool>("flip_vertical", false);
|
||||||
auto_flip_ = nh_priv_.param("auto_flip", false);
|
auto_flip_ = nh_priv_.param("auto_flip", false);
|
||||||
|
|
||||||
frame_id_prefix_ = nh_priv_.param<std::string>("frame_id_prefix", "aruco_");
|
frame_id_prefix_ = nh_priv_.param<std::string>("frame_id_prefix", "aruco_");
|
||||||
@@ -133,14 +138,15 @@ 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;
|
if (!enabled_) return;
|
||||||
|
if (waiting_for_map_) return;
|
||||||
|
|
||||||
Mat image = cv_bridge::toCvShare(msg, "bgr8")->image;
|
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 snap_to;
|
geometry_msgs::TransformStamped vertical;
|
||||||
|
|
||||||
// Detect markers
|
// Detect markers
|
||||||
cv::aruco::detectMarkers(image, dictionary_, corners, ids, parameters_, rejected);
|
cv::aruco::detectMarkers(image, dictionary_, corners, ids, parameters_, rejected);
|
||||||
@@ -175,18 +181,20 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!known_tilt_.empty()) {
|
if (!known_vertical_.empty()) {
|
||||||
try {
|
try {
|
||||||
snap_to = tf_buffer_->lookupTransform(msg->header.frame_id, known_tilt_,
|
vertical = tf_buffer_->lookupTransform(msg->header.frame_id, known_vertical_,
|
||||||
msg->header.stamp, transform_timeout_);
|
msg->header.stamp, transform_timeout_);
|
||||||
} catch (const tf2::TransformException& e) {
|
} catch (const tf2::TransformException& e) {
|
||||||
NODELET_WARN_THROTTLE(5, "can't snap: %s", e.what());
|
NODELET_WARN_THROTTLE(5, "can't retrieve known vertical: %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;
|
||||||
@@ -199,25 +207,38 @@ private:
|
|||||||
if (estimate_poses_) {
|
if (estimate_poses_) {
|
||||||
fillPose(marker.pose, rvecs[i], tvecs[i]);
|
fillPose(marker.pose, rvecs[i], tvecs[i]);
|
||||||
|
|
||||||
// snap orientation (if enabled and snap frame available)
|
// apply known vertical (if enabled and vertical frame available)
|
||||||
if (!known_tilt_.empty() && !snap_to.header.frame_id.empty()) {
|
if (!known_vertical_.empty() && !vertical.header.frame_id.empty()) {
|
||||||
snapOrientation(marker.pose.orientation, snap_to.transform.rotation, auto_flip_);
|
applyVertical(marker.pose.orientation, vertical.transform.rotation, false, 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()) {
|
||||||
transform.transform.rotation = marker.pose.orientation;
|
// check if a markers with that id is already added
|
||||||
fillTranslation(transform.transform.translation, tvecs[i]);
|
bool send = true;
|
||||||
br_->sendTransform(transform);
|
for (auto &t : transforms) {
|
||||||
|
if (t.child_frame_id == transform.child_frame_id) {
|
||||||
|
send = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (send) {
|
||||||
|
transform.transform.rotation = marker.pose.orientation;
|
||||||
|
fillTranslation(transform.transform.translation, tvecs[i]);
|
||||||
|
transforms.push_back(transform);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
array_.markers.push_back(marker);
|
array_.markers.push_back(marker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (send_tf_) {
|
||||||
|
br_->sendTransform(transforms);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
markers_pub_.publish(array_);
|
markers_pub_.publish(array_);
|
||||||
@@ -244,8 +265,7 @@ 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++)
|
||||||
cv::aruco::drawAxis(debug, camera_matrix_, dist_coeffs_,
|
_drawAxis(debug, camera_matrix_, dist_coeffs_, rvecs[i], tvecs[i], getMarkerLength(ids[i]));
|
||||||
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;
|
||||||
@@ -380,7 +400,13 @@ private:
|
|||||||
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)
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ private:
|
|||||||
bool enabled_ = true;
|
bool enabled_ = true;
|
||||||
std::string type_;
|
std::string type_;
|
||||||
visualization_msgs::MarkerArray vis_array_;
|
visualization_msgs::MarkerArray vis_array_;
|
||||||
std::string known_tilt_, map_, markers_frame_, markers_parent_frame_;
|
std::string known_vertical_, map_, markers_frame_, markers_parent_frame_;
|
||||||
int image_width_, image_height_, image_margin_;
|
int image_width_, image_height_, image_margin_;
|
||||||
bool auto_flip_, image_axis_;
|
bool flip_vertical_, auto_flip_, image_axis_, put_markers_count_to_covariance_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void onInit()
|
virtual void onInit()
|
||||||
@@ -104,12 +104,14 @@ public:
|
|||||||
|
|
||||||
type_ = nh_priv_.param<std::string>("type", "map");
|
type_ = nh_priv_.param<std::string>("type", "map");
|
||||||
transform_.child_frame_id = nh_priv_.param<std::string>("frame_id", "aruco_map");
|
transform_.child_frame_id = nh_priv_.param<std::string>("frame_id", "aruco_map");
|
||||||
known_tilt_ = nh_priv_.param<std::string>("known_tilt", "");
|
known_vertical_ = nh_priv_.param("known_vertical", nh_priv_.param("known_tilt", std::string(""))); // known_tilt is an old name
|
||||||
|
flip_vertical_ = nh_priv_.param<bool>("flip_vertical", false);
|
||||||
auto_flip_ = nh_priv_.param("auto_flip", false);
|
auto_flip_ = nh_priv_.param("auto_flip", false);
|
||||||
image_width_ = nh_priv_.param("image_width" , 2000);
|
image_width_ = nh_priv_.param("image_width" , 2000);
|
||||||
image_height_ = nh_priv_.param("image_height", 2000);
|
image_height_ = nh_priv_.param("image_height", 2000);
|
||||||
image_margin_ = nh_priv_.param("image_margin", 200);
|
image_margin_ = nh_priv_.param("image_margin", 200);
|
||||||
image_axis_ = nh_priv_.param("image_axis", true);
|
image_axis_ = nh_priv_.param("image_axis", true);
|
||||||
|
put_markers_count_to_covariance_ = nh_priv_.param("put_markers_count_to_covariance", false);
|
||||||
markers_parent_frame_ = nh_priv_.param<std::string>("markers/frame_id", transform_.child_frame_id);
|
markers_parent_frame_ = nh_priv_.param<std::string>("markers/frame_id", transform_.child_frame_id);
|
||||||
markers_frame_ = nh_priv_.param<std::string>("markers/child_frame_id_prefix", "");
|
markers_frame_ = nh_priv_.param<std::string>("markers/child_frame_id_prefix", "");
|
||||||
|
|
||||||
@@ -177,7 +179,21 @@ public:
|
|||||||
corners.push_back(marker_corners);
|
corners.push_back(marker_corners);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (known_tilt_.empty()) {
|
if (put_markers_count_to_covariance_) {
|
||||||
|
// 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);
|
||||||
@@ -191,7 +207,7 @@ public:
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
Mat obj_points, img_points;
|
Mat obj_points, img_points;
|
||||||
// estimation with "snapping"
|
// estimation with known vertical
|
||||||
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;
|
||||||
|
|
||||||
@@ -203,11 +219,11 @@ public:
|
|||||||
|
|
||||||
fillTransform(transform_.transform, rvec, tvec);
|
fillTransform(transform_.transform, rvec, tvec);
|
||||||
try {
|
try {
|
||||||
geometry_msgs::TransformStamped snap_to = tf_buffer_.lookupTransform(markers->header.frame_id,
|
geometry_msgs::TransformStamped vertical = tf_buffer_.lookupTransform(markers->header.frame_id,
|
||||||
known_tilt_, markers->header.stamp, ros::Duration(0.02));
|
known_vertical_, markers->header.stamp, ros::Duration(0.02));
|
||||||
snapOrientation(transform_.transform.rotation, snap_to.transform.rotation, auto_flip_);
|
applyVertical(transform_.transform.rotation, vertical.transform.rotation, flip_vertical_, auto_flip_);
|
||||||
} catch (const tf2::TransformException& e) {
|
} catch (const tf2::TransformException& e) {
|
||||||
NODELET_WARN_THROTTLE(1, "can't snap: %s", e.what());
|
NODELET_WARN_THROTTLE(1, "can't retrieve known vertical: %s", e.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
geometry_msgs::TransformStamped shift;
|
geometry_msgs::TransformStamped shift;
|
||||||
@@ -503,7 +519,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, marker.pose.orientation);
|
tf::quaternionTFToMsg(q, vis_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);
|
||||||
|
|
||||||
|
|||||||
@@ -106,26 +106,25 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set roll and pitch from "from" to "to", keeping yaw */
|
/* Apply a vertical to an orientation */
|
||||||
inline void snapOrientation(geometry_msgs::Quaternion& to, const geometry_msgs::Quaternion& from, bool auto_flip = false)
|
inline void applyVertical(geometry_msgs::Quaternion& orientation, const geometry_msgs::Quaternion& vertical,
|
||||||
|
bool flip_vertical = false, bool auto_flip = false) // editorconfig-checker-disable-line
|
||||||
{
|
{
|
||||||
tf::Quaternion _from, _to;
|
tf::Quaternion _vertical, _orientation;
|
||||||
tf::quaternionMsgToTF(from, _from);
|
tf::quaternionMsgToTF(vertical, _vertical);
|
||||||
tf::quaternionMsgToTF(to, _to);
|
tf::quaternionMsgToTF(orientation, _orientation);
|
||||||
|
|
||||||
if (auto_flip) {
|
if (flip_vertical || (auto_flip && !isFlipped(_orientation))) {
|
||||||
if (!isFlipped(_from)) {
|
static const tf::Quaternion flip = tf::createQuaternionFromRPY(M_PI, 0, 0);
|
||||||
static const tf::Quaternion flip = tf::createQuaternionFromRPY(M_PI, 0, 0);
|
_vertical *= flip; // flip vertical
|
||||||
_from *= flip; // flip "from"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto diff = tf::Matrix3x3(_to).transposeTimes(tf::Matrix3x3(_from));
|
auto diff = tf::Matrix3x3(_orientation).transposeTimes(tf::Matrix3x3(_vertical));
|
||||||
double _, yaw;
|
double _, yaw;
|
||||||
diff.getRPY(_, _, yaw);
|
diff.getRPY(_, _, yaw);
|
||||||
auto q = tf::createQuaternionFromRPY(0, 0, -yaw);
|
auto q = tf::createQuaternionFromRPY(0, 0, -yaw);
|
||||||
_from = _from * q; // set yaw from "to" to "from"
|
_vertical = _vertical * q; // set yaw from orientation to vertical
|
||||||
tf::quaternionTFToMsg(_from, to); // set "from" to "to"
|
tf::quaternionTFToMsg(_vertical, orientation); // set vertical to orientation
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void transformToPose(const geometry_msgs::Transform& transform, geometry_msgs::Pose& pose)
|
inline void transformToPose(const geometry_msgs::Transform& transform, geometry_msgs::Pose& pose)
|
||||||
|
|||||||
@@ -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
|
from visualization_msgs.msg import MarkerArray as VisMarkerArray, Marker as VisMarker
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -199,6 +199,36 @@ 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)
|
||||||
|
|||||||
BIN
aruco_pose/test/duplicate.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
8
aruco_pose/test/duplicate.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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'
|
||||||
21
aruco_pose/test/duplicate.test
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<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>
|
||||||
@@ -16,3 +16,726 @@ web_video_server:
|
|||||||
ws281x:
|
ws281x:
|
||||||
debian:
|
debian:
|
||||||
buster: [ros-noetic-ws281x]
|
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]
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ echo_stamp() {
|
|||||||
my_travis_retry() {
|
my_travis_retry() {
|
||||||
local result=0
|
local result=0
|
||||||
local count=1
|
local count=1
|
||||||
local max_count=50
|
local max_count=5
|
||||||
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
|
||||||
@@ -72,7 +72,7 @@ my_travis_retry() {
|
|||||||
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
|
# FIXME: Re-add this after missing packages are built
|
||||||
echo "yaml file:///etc/ros/rosdep/${ROS_DISTRO}-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"
|
||||||
@@ -125,11 +125,12 @@ cd /home/pi/catkin_ws/src/clover
|
|||||||
builder/assets/install_gitbook.sh
|
builder/assets/install_gitbook.sh
|
||||||
gitbook install
|
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 \
|
my_travis_retry apt-get install -y --no-install-recommends \
|
||||||
ros-${ROS_DISTRO}-dynamic-reconfigure \
|
|
||||||
ros-${ROS_DISTRO}-rosbridge-suite \
|
ros-${ROS_DISTRO}-rosbridge-suite \
|
||||||
ros-${ROS_DISTRO}-rosserial \
|
ros-${ROS_DISTRO}-rosserial \
|
||||||
ros-${ROS_DISTRO}-usb-cam \
|
ros-${ROS_DISTRO}-usb-cam \
|
||||||
@@ -137,7 +138,9 @@ my_travis_retry apt-get install -y --no-install-recommends \
|
|||||||
ros-${ROS_DISTRO}-ws281x \
|
ros-${ROS_DISTRO}-ws281x \
|
||||||
ros-${ROS_DISTRO}-rosshow \
|
ros-${ROS_DISTRO}-rosshow \
|
||||||
ros-${ROS_DISTRO}-cmake-modules \
|
ros-${ROS_DISTRO}-cmake-modules \
|
||||||
ros-${ROS_DISTRO}-image-view
|
ros-${ROS_DISTRO}-image-view \
|
||||||
|
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)" \
|
||||||
@@ -151,6 +154,9 @@ 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"
|
echo_stamp "Make \$HOME/examples symlink"
|
||||||
ln -s "$(catkin_find clover examples --first-only)" /home/pi
|
ln -s "$(catkin_find clover examples --first-only)" /home/pi
|
||||||
chown -Rf pi:pi /home/pi/examples
|
chown -Rf pi:pi /home/pi/examples
|
||||||
|
|||||||
@@ -37,3 +37,7 @@ 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
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
# 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
|
from sensor_msgs.msg import Range, BatteryState
|
||||||
@@ -22,6 +23,7 @@ from clover.srv import GetTelemetry, Navigate, NavigateGlobal, SetPosition, SetV
|
|||||||
from led_msgs.srv import SetLEDs
|
from led_msgs.srv import SetLEDs
|
||||||
from led_msgs.msg import LEDStateArray, LEDState
|
from led_msgs.msg import LEDStateArray, LEDState
|
||||||
from aruco_pose.msg import Marker, MarkerArray, Point2D
|
from aruco_pose.msg import Marker, MarkerArray, Point2D
|
||||||
|
from clover import long_callback
|
||||||
|
|
||||||
import dynamic_reconfigure.client
|
import dynamic_reconfigure.client
|
||||||
|
|
||||||
@@ -31,9 +33,15 @@ import tf2_geometry_msgs
|
|||||||
import VL53L1X
|
import VL53L1X
|
||||||
import pymavlink
|
import pymavlink
|
||||||
from pymavlink import mavutil
|
from pymavlink import mavutil
|
||||||
import rpi_ws281x
|
from image_geometry import PinholeCameraModel, StereoCameraModel
|
||||||
import pigpio
|
|
||||||
# from espeak import espeak
|
# 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
|
||||||
|
|||||||
@@ -6,16 +6,10 @@ 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
|
||||||
|
|
||||||
@@ -25,42 +19,77 @@ 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
|
||||||
pigpiod -v
|
|
||||||
i2cdetect -V
|
|
||||||
butterfly -h
|
|
||||||
# espeak --version
|
# espeak --version
|
||||||
mjpg_streamer --version
|
|
||||||
systemctl --version
|
systemctl --version
|
||||||
|
|
||||||
|
if [ -z $VM ]; then
|
||||||
|
# rpi only software
|
||||||
|
python --version
|
||||||
|
ipython --version
|
||||||
|
pip2 --version
|
||||||
|
# `python` is python2 for now
|
||||||
|
[[ $(python -c 'import sys;print(sys.version_info.major)') == "2" ]]
|
||||||
|
|
||||||
|
# ptvsd does not have a stand-alone binary
|
||||||
|
python -m ptvsd --version
|
||||||
|
python3 -m ptvsd --version
|
||||||
|
|
||||||
|
pigpiod -v
|
||||||
|
i2cdetect -V
|
||||||
|
butterfly -h
|
||||||
|
mjpg_streamer --version
|
||||||
|
fi
|
||||||
|
|
||||||
# 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 ws281x
|
||||||
rosversion led_msgs
|
rosversion led_msgs
|
||||||
rosversion dynamic_reconfigure
|
rosversion dynamic_reconfigure
|
||||||
rosversion tf2_web_republisher
|
rosversion tf2_web_republisher
|
||||||
rosversion compressed_image_transport
|
rosversion rosbridge_server
|
||||||
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 rosshow
|
|
||||||
rosversion nodelet
|
rosversion nodelet
|
||||||
rosversion image_view
|
rosversion image_view
|
||||||
|
|
||||||
# validate some versions
|
|
||||||
[[ $(rosversion cv_camera) == "0.5.1" ]] # patched version with init fix
|
|
||||||
[[ $(rosversion ws281x) == "0.0.13" ]]
|
[[ $(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 /home/pi/examples/*) ]]
|
[[ $(ls $H/examples/*) ]]
|
||||||
|
|
||||||
|
# validate web tools present
|
||||||
|
[ -d $H/.ros/www ]
|
||||||
|
[ "$(readlink $H/.ros/www/clover)" = "$H/catkin_ws/src/clover/clover/www" ]
|
||||||
|
[ "$(readlink $H/.ros/www/clover_blocks)" = "$H/catkin_ws/src/clover/clover_blocks/www" ]
|
||||||
|
|||||||
@@ -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'
|
'camera_case.stl', 'camera_mount.stl', 'grip_right.stl', 'grip_left.stl'
|
||||||
|
|
||||||
code = 0
|
code = 0
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ find_package(catkin REQUIRED COMPONENTS
|
|||||||
|
|
||||||
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
|
# Workaround for OpenCV 3/4 support
|
||||||
@@ -53,7 +55,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 ##
|
||||||
@@ -80,11 +82,10 @@ find_package(OpenCV ${_opencv_version} REQUIRED
|
|||||||
## * 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
|
||||||
# Message1.msg
|
State.msg
|
||||||
# Message2.msg
|
)
|
||||||
# )
|
|
||||||
|
|
||||||
## Generate services in the 'srv' folder
|
## Generate services in the 'srv' folder
|
||||||
add_service_files(
|
add_service_files(
|
||||||
@@ -92,6 +93,9 @@ 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
|
||||||
@@ -306,4 +310,5 @@ 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()
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
# 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)
|
|
||||||
64
clover/examples/camera.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# 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()
|
||||||
@@ -16,11 +16,8 @@ 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=float('nan'), yaw_rate=0, speed=0.5, \
|
def navigate_wait(x=0, y=0, z=0, yaw=math.nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
|
||||||
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)
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
91
clover/examples/red_circle.py
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# 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()
|
||||||
@@ -16,10 +16,12 @@
|
|||||||
<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/map"/>
|
||||||
|
<param name="dictionary" value="2"/> <!-- DICT_4X4_250 -->
|
||||||
<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="known_tilt" value="map" if="$(eval placement == 'floor')"/>
|
<param name="use_map_markers" value="true"/>
|
||||||
<param name="known_tilt" value="map_flipped" if="$(eval placement == 'ceiling')"/>
|
<param name="known_vertical" value="map" if="$(eval placement == 'floor' or placement == 'ceiling')"/>
|
||||||
|
<param name="flip_vertical" value="true" if="$(eval placement == 'ceiling')"/>
|
||||||
<param name="length" value="$(arg length)"/>
|
<param name="length" value="$(arg length)"/>
|
||||||
<param name="transform_timeout" value="0.1"/>
|
<param name="transform_timeout" value="0.1"/>
|
||||||
<!-- aruco detector parameters -->
|
<!-- aruco detector parameters -->
|
||||||
@@ -35,8 +37,8 @@
|
|||||||
<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/$(arg map)"/>
|
||||||
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/>
|
<param name="known_vertical" value="map" if="$(eval placement == 'floor' or placement == 'ceiling')"/>
|
||||||
<param name="known_tilt" value="map_flipped" if="$(eval placement == 'ceiling')"/>
|
<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)"/>
|
||||||
|
|||||||
@@ -45,13 +45,13 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<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/main_camera_optical" value="map"/>
|
||||||
|
<param name="terrain_frame_mode" value="range"/>
|
||||||
</node>
|
</node>
|
||||||
|
|
||||||
<!-- main camera -->
|
<!-- main camera -->
|
||||||
@@ -72,6 +72,9 @@
|
|||||||
<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)"/>
|
<arg name="simulator" value="$(arg simulator)"/>
|
||||||
@@ -86,8 +89,4 @@
|
|||||||
<param name="use_fake_gcs" value="false"/>
|
<param name="use_fake_gcs" value="false"/>
|
||||||
</node>
|
</node>
|
||||||
|
|
||||||
<!-- Update static directory -->
|
|
||||||
<node pkg="roswww_static" name="roswww_static" type="main.py" clear_params="true">
|
|
||||||
<param name="default_package" value="clover"/>
|
|
||||||
</node>
|
|
||||||
</launch>
|
</launch>
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
</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" ns="led" clear_params="true" output="screen" if="$(arg led_effect)">
|
<node pkg="clover" name="led_effect" type="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"/>
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
<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="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"/>
|
<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"/>
|
||||||
@@ -43,4 +46,15 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -77,9 +77,6 @@
|
|||||||
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"/>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<launch>
|
<launch>
|
||||||
<!-- shurtcut for running the simulation (`roslaunch clover simulator.launch`) -->
|
<!-- shortcut for running the simulation (`roslaunch clover simulator.launch`) -->
|
||||||
<include file="$(find clover_simulation)/launch/simulator.launch"/>
|
<include file="$(find clover_simulation)/launch/simulator.launch"/>
|
||||||
</launch>
|
</launch>
|
||||||
|
|||||||
38
clover/msg/State.msg
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
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
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<package format="3">
|
<package format="3">
|
||||||
<name>clover</name>
|
<name>clover</name>
|
||||||
<version>0.23.0</version>
|
<version>0.24.0</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>
|
||||||
@@ -42,9 +42,10 @@
|
|||||||
<depend condition="$ROS_PYTHON_VERSION == 2">python-lxml</depend>
|
<depend condition="$ROS_PYTHON_VERSION == 2">python-lxml</depend>
|
||||||
<depend condition="$ROS_PYTHON_VERSION == 3">python3-lxml</depend>
|
<depend condition="$ROS_PYTHON_VERSION == 3">python3-lxml</depend>
|
||||||
<depend>dynamic_reconfigure</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>
|
||||||
<!-- Use test_depend for packages you need only for testing: -->
|
<test_depend>ros_pytest</test_depend>
|
||||||
<!-- <test_depend>gtest</test_depend> -->
|
|
||||||
|
|
||||||
<!-- The export tag contains other, unspecified, tags -->
|
<!-- The export tag contains other, unspecified, tags -->
|
||||||
<export>
|
<export>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
flask==1.1.1
|
flask
|
||||||
docopt==0.6.2
|
geopy
|
||||||
geopy==1.11.0
|
smbus2
|
||||||
smbus2==0.3.0
|
VL53L1X
|
||||||
VL53L1X==0.0.5
|
|
||||||
|
|||||||
11
clover/setup.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
## ! 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)
|
||||||
@@ -13,7 +13,12 @@ from util import handle_response
|
|||||||
|
|
||||||
rospy.init_node('autotest_aruco', disable_signals=True) # disable signals to allow interrupting with ctrl+c
|
rospy.init_node('autotest_aruco', disable_signals=True) # disable signals to allow interrupting with ctrl+c
|
||||||
|
|
||||||
flow_client = dynamic_reconfigure.client.Client('optical_flow')
|
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)
|
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||||
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
|
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
|
||||||
land = handle_response(rospy.ServiceProxy('land', Trigger))
|
land = handle_response(rospy.ServiceProxy('land', Trigger))
|
||||||
@@ -30,11 +35,8 @@ def print_current_map_position():
|
|||||||
dist = rospy.wait_for_message('rangefinder/range', Range).range
|
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))
|
print('Map position:\tx={:.1f}\ty={:.1f}\tz={:.1f}\tyaw={:.1f}\tdist={:.2f}'.format(telem.x, telem.y, telem.z, telem.yaw, dist))
|
||||||
|
|
||||||
def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \
|
def navigate_wait(x=0, y=0, z=0, yaw=math.nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
|
||||||
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)
|
||||||
|
|
||||||
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
|
||||||
@@ -67,12 +69,13 @@ 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')
|
navigate_wait(x=center_x, y=center_y, z=1.5, speed=5, frame_id='aruco_map')
|
||||||
print_current_map_position()
|
print_current_map_position()
|
||||||
|
|
||||||
input('Disable optical flow and keep hovering [enter] ')
|
if flow_client:
|
||||||
flow_client.update_configuration({'enabled': False})
|
input('Disable optical flow and keep hovering [enter] ')
|
||||||
rospy.sleep(5)
|
flow_client.update_configuration({'enabled': False})
|
||||||
|
rospy.sleep(5)
|
||||||
|
|
||||||
input('Enable optical flow back [enter] ')
|
input('Enable optical flow back [enter] ')
|
||||||
flow_client.update_configuration({'enabled': True})
|
flow_client.update_configuration({'enabled': True})
|
||||||
|
|
||||||
input('Go to side 1 %g 2 heading top [enter] ' % (center_y))
|
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')
|
navigate_wait(x=1, y=center_y, z=2, yaw=1.57, frame_id='aruco_map')
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import rospy
|
import rospy
|
||||||
import math
|
import math
|
||||||
from math import nan
|
from math import nan, inf
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
from clover import srv
|
from clover import srv
|
||||||
@@ -15,6 +15,8 @@ rospy.init_node('autotest_flight', disable_signals=True) # disable signals to al
|
|||||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||||
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
|
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
|
||||||
navigate_global = handle_response(rospy.ServiceProxy('navigate_global', srv.NavigateGlobal))
|
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_position = handle_response(rospy.ServiceProxy('set_position', srv.SetPosition))
|
||||||
set_velocity = handle_response(rospy.ServiceProxy('set_velocity', srv.SetVelocity))
|
set_velocity = handle_response(rospy.ServiceProxy('set_velocity', srv.SetVelocity))
|
||||||
set_attitude = handle_response(rospy.ServiceProxy('set_attitude', srv.SetAttitude))
|
set_attitude = handle_response(rospy.ServiceProxy('set_attitude', srv.SetAttitude))
|
||||||
@@ -28,11 +30,8 @@ def interrupt(sig, frame):
|
|||||||
|
|
||||||
signal.signal(signal.SIGINT, interrupt)
|
signal.signal(signal.SIGINT, interrupt)
|
||||||
|
|
||||||
def navigate_wait(x=0, y=0, z=0, yaw=nan, yaw_rate=0, speed=0.5, \
|
def navigate_wait(x=0, y=0, z=0, yaw=nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
|
||||||
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)
|
||||||
|
|
||||||
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
|
||||||
@@ -69,17 +68,17 @@ set_velocity(vx=1, vy=0.0, vz=0, frame_id='body')
|
|||||||
rospy.sleep(2)
|
rospy.sleep(2)
|
||||||
set_position(frame_id='body')
|
set_position(frame_id='body')
|
||||||
|
|
||||||
input('Rotate right 90° [enter] ')
|
input('Rotate right 90° using set_yaw [enter] ')
|
||||||
navigate(yaw=-math.pi / 2, frame_id='navigate_target')
|
set_yaw(yaw=-math.pi / 2, frame_id='navigate_target')
|
||||||
rospy.sleep(3)
|
rospy.sleep(3)
|
||||||
|
|
||||||
input('Use set_attitude to fly backwards [enter]')
|
input('Use set_attitude to fly backwards [enter]')
|
||||||
set_attitude(pitch=-0.3, roll=0, yaw=0, thrust=0.5, frame_id='body')
|
set_attitude(roll=0, pitch=-0.3, yaw=0, thrust=0.5, frame_id='body')
|
||||||
rospy.sleep(0.3)
|
rospy.sleep(0.3)
|
||||||
set_position(frame_id='body')
|
set_position(frame_id='body')
|
||||||
|
|
||||||
input('Use set_attitude to fly right [enter]')
|
input('Use set_attitude to fly right [enter]')
|
||||||
set_attitude(pitch=0, roll=0.3, yaw=0, thrust=0.5, frame_id='body')
|
set_attitude(roll=0.3, pitch=0, yaw=0, thrust=0.5, frame_id='body')
|
||||||
rospy.sleep(0.5)
|
rospy.sleep(0.5)
|
||||||
set_position(frame_id='body')
|
set_position(frame_id='body')
|
||||||
|
|
||||||
@@ -88,13 +87,13 @@ set_rates(roll_rate=1.2, thrust=0.5)
|
|||||||
rospy.sleep(0.4)
|
rospy.sleep(0.4)
|
||||||
set_position(frame_id='body')
|
set_position(frame_id='body')
|
||||||
|
|
||||||
input('Rotate 360° to the right using yaw_rate [enter]')
|
input('Rotate 360° to the right using set_yaw_rate [enter]')
|
||||||
set_position(x=nan, y=nan, z=nan, frame_id='body', yaw=nan, yaw_rate=-1)
|
set_yaw_rate(yaw_rate=-1)
|
||||||
rospy.sleep(2 * math.pi)
|
rospy.sleep(2 * math.pi)
|
||||||
set_position(frame_id='body')
|
set_position(frame_id='body')
|
||||||
|
|
||||||
input('Return to start point [enter]')
|
input('Return to start point heading forward [enter]')
|
||||||
navigate_wait(x=start.x, y=start.y, z=start.z, yaw=start.yaw, speed=1, frame_id='map')
|
navigate_wait(x=start.x, y=start.y, z=start.z, yaw=inf, speed=1, frame_id='map')
|
||||||
|
|
||||||
input('Land [enter]')
|
input('Land [enter]')
|
||||||
land()
|
land()
|
||||||
|
|||||||
35
clover/src/clover/__init__.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
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
|
||||||
@@ -309,18 +309,22 @@ int main(int argc, char **argv)
|
|||||||
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, {});
|
nh_priv.param("notify/error/ignore", error_ignore, {});
|
||||||
|
|
||||||
ros::service::waitForService("set_leds"); // cannot work without set_leds service
|
std::string led; // led namespace
|
||||||
set_leds_srv = nh.serviceClient<led_msgs::SetLEDs>("set_leds", true);
|
nh_priv.param("led", led, std::string("led"));
|
||||||
|
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>("state", nh));
|
handleState(*ros::topic::waitForMessage<led_msgs::LEDStateArray>(led + "state", nh));
|
||||||
|
|
||||||
auto state_sub = nh.subscribe("state", 1, &handleState);
|
auto state_sub = nh.subscribe(led + "state", 1, &handleState);
|
||||||
|
|
||||||
auto set_effect = nh.advertiseService("set_effect", &setEffect);
|
auto set_effect = nh.advertiseService(led + "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);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include <dynamic_reconfigure/server.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>
|
||||||
@@ -57,6 +58,9 @@ private:
|
|||||||
std::unique_ptr<tf2_ros::TransformListener> tf_listener_;
|
std::unique_ptr<tf2_ros::TransformListener> tf_listener_;
|
||||||
bool calc_flow_gyro_;
|
bool calc_flow_gyro_;
|
||||||
float flow_gyro_default_;
|
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_;
|
std::shared_ptr<dynamic_reconfigure::Server<clover::FlowConfig>> dyn_srv_;
|
||||||
|
|
||||||
void onInit()
|
void onInit()
|
||||||
@@ -87,6 +91,11 @@ private:
|
|||||||
|
|
||||||
img_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this);
|
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);
|
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<clover::FlowConfig>>(nh_priv);
|
||||||
dynamic_reconfigure::Server<clover::FlowConfig>::CallbackType cb;
|
dynamic_reconfigure::Server<clover::FlowConfig>::CallbackType cb;
|
||||||
|
|
||||||
@@ -121,6 +130,12 @@ private:
|
|||||||
{
|
{
|
||||||
if (!enabled_) return;
|
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;
|
||||||
@@ -236,6 +251,14 @@ private:
|
|||||||
prev_ = curr_.clone();
|
prev_ = curr_.clone();
|
||||||
prev_stamp_ = msg->header.stamp;
|
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:
|
||||||
// Publish debug image
|
// Publish debug image
|
||||||
if (img_pub_.getNumSubscribers() > 0) {
|
if (img_pub_.getNumSubscribers() > 0) {
|
||||||
@@ -248,14 +271,6 @@ 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
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,6 +299,10 @@ publish_debug:
|
|||||||
prev_ = Mat(); // clear previous frame
|
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)
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
# 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
|
import os, sys
|
||||||
import math
|
import math
|
||||||
import subprocess
|
import subprocess
|
||||||
import re
|
import re
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import traceback
|
import traceback
|
||||||
from threading import Event
|
import threading
|
||||||
|
from threading import Event, Thread, Lock
|
||||||
import numpy
|
import numpy
|
||||||
import rospy
|
import rospy
|
||||||
import tf2_ros
|
import tf2_ros
|
||||||
@@ -27,24 +28,16 @@ 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
|
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']='[${severity}]: ${message}'
|
os.environ['ROSCONSOLE_FORMAT']='${message}'
|
||||||
|
|
||||||
# use user's locale to convert numbers, etc
|
# use user's locale to convert numbers, etc
|
||||||
locale.setlocale(locale.LC_ALL, '')
|
locale.setlocale(locale.LC_ALL, '')
|
||||||
@@ -53,46 +46,68 @@ tf_buffer = tf2_ros.Buffer()
|
|||||||
tf_listener = tf2_ros.TransformListener(tf_buffer)
|
tf_listener = tf2_ros.TransformListener(tf_buffer)
|
||||||
|
|
||||||
|
|
||||||
failures = []
|
thread_local = threading.local()
|
||||||
infos = []
|
reports_lock = Lock()
|
||||||
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
|
||||||
rospy.logwarn('%s: %s', current_check, msg)
|
thread_local.reports += [{'failure': msg}]
|
||||||
failures.append(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def info(text, *args):
|
def info(text, *args):
|
||||||
msg = text % args
|
msg = text % args
|
||||||
rospy.loginfo('%s: %s', current_check, msg)
|
thread_local.reports += [{'info': msg}]
|
||||||
infos.append(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def check(name):
|
def check(name):
|
||||||
def inner(fn):
|
def inner(fn):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
failures[:] = []
|
start = rospy.get_time()
|
||||||
infos[:] = []
|
thread_local.reports = []
|
||||||
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)
|
||||||
return
|
with reports_lock:
|
||||||
if not failures and not infos:
|
for report in thread_local.reports:
|
||||||
rospy.loginfo('%s: OK', name)
|
if 'failure' in report:
|
||||||
|
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):
|
def get_param(name, default=None, strict=True):
|
||||||
try:
|
try:
|
||||||
res = param_get(param_id=name)
|
res = param_get(param_id=name)
|
||||||
except rospy.ServiceException as e:
|
except rospy.ServiceException as e:
|
||||||
@@ -100,13 +115,19 @@ def get_param(name):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if not res.success:
|
if not res.success:
|
||||||
failure('unable to retrieve PX4 parameter %s', name)
|
if strict:
|
||||||
|
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)
|
||||||
@@ -151,6 +172,24 @@ 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°',
|
||||||
@@ -196,34 +235,36 @@ 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
|
||||||
|
|
||||||
clover_tag = re.compile(r'-cl[oe]ver\.\d+$')
|
if not is_process_running('px4', exact=True): # can't use px4 console in SITL
|
||||||
clover_fw = False
|
clover_tag = re.compile(r'-cl[oe]ver\.\d+$')
|
||||||
|
clover_fw = False
|
||||||
|
|
||||||
# Make sure the console is available to us
|
# Make sure the console is available to us
|
||||||
mavlink_exec('\n')
|
mavlink_exec('\n')
|
||||||
version_str = mavlink_exec('ver all')
|
version_str = mavlink_exec('ver all')
|
||||||
if version_str == '':
|
if version_str == '':
|
||||||
info('no version data available from SITL')
|
info('no version data available from SITL')
|
||||||
|
|
||||||
for line in version_str.split('\n'):
|
for line in version_str.split('\n'):
|
||||||
if line.startswith('FW version: '):
|
if line.startswith('FW version: '):
|
||||||
info(line[len('FW version: '):])
|
info(line[len('FW version: '):])
|
||||||
elif line.startswith('FW git tag: '): # only Clover's firmware
|
elif line.startswith('FW git tag: '): # only Clover's firmware
|
||||||
tag = line[len('FW git tag: '):]
|
tag = line[len('FW git tag: '):]
|
||||||
clover_fw = clover_tag.search(tag)
|
clover_fw = clover_tag.search(tag)
|
||||||
info(tag)
|
info(tag)
|
||||||
elif line.startswith('HW arch: '):
|
elif line.startswith('HW arch: '):
|
||||||
info(line[len('HW arch: '):])
|
info(line[len('HW arch: '):])
|
||||||
|
|
||||||
if not clover_fw:
|
if not clover_fw:
|
||||||
info('not Clover PX4 firmware, check https://clover.coex.tech/firmware')
|
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 = get_param('LPE_FUSION')
|
fuse = int(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:
|
||||||
@@ -255,21 +296,35 @@ 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:
|
||||||
battery = rospy.wait_for_message('mavros/battery', BatteryState, timeout=3)
|
info('time sync offset: %.2f s', float(read_diagnostics('mavros: Time Sync', 'Estimated time offset (s)')))
|
||||||
if not battery.cell_voltage:
|
except:
|
||||||
failure('cell voltage is not available, https://clover.coex.tech/power')
|
failure('cannot read time sync offset')
|
||||||
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 (check wiring)')
|
failure('no MAVROS state')
|
||||||
|
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):
|
||||||
@@ -346,19 +401,24 @@ 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_tilt = rospy.get_param('aruco_detect/known_tilt', '')
|
known_vertical = rospy.get_param('aruco_detect/known_vertical', '')
|
||||||
if known_tilt == 'map':
|
flip_vertical = rospy.get_param('aruco_detect/flip_vertical', False)
|
||||||
known_tilt += ' (ALL markers are on the floor)'
|
description = ''
|
||||||
elif known_tilt == 'map_flipped':
|
if known_vertical == 'map' and not flip_vertical:
|
||||||
known_tilt += ' (ALL markers are on the ceiling)'
|
description = ' (all markers are on the floor)'
|
||||||
info('aruco_detector/known_tilt = %s', known_tilt)
|
elif known_vertical == 'map' and flip_vertical:
|
||||||
|
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:
|
||||||
rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=1)
|
markers = rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=0.8)
|
||||||
except rospy.ROSException:
|
except rospy.ROSException:
|
||||||
failure('no markers detection')
|
failure('no markers detection')
|
||||||
return
|
return
|
||||||
@@ -367,42 +427,61 @@ def check_aruco():
|
|||||||
return
|
return
|
||||||
|
|
||||||
if is_process_running('aruco_map', full=True):
|
if is_process_running('aruco_map', full=True):
|
||||||
known_tilt = rospy.get_param('aruco_map/known_tilt', '')
|
known_vertical = rospy.get_param('aruco_map/known_vertical', '')
|
||||||
if known_tilt == 'map':
|
flip_vertical = rospy.get_param('aruco_map/flip_vertical', False)
|
||||||
known_tilt += ' (marker\'s map is on the floor)'
|
description = ''
|
||||||
elif known_tilt == 'map_flipped':
|
if known_vertical == 'map' and not flip_vertical:
|
||||||
known_tilt += ' (marker\'s map is on the ceiling)'
|
description += ' (markers map is on the floor)'
|
||||||
info('aruco_map/known_tilt = %s', known_tilt)
|
elif known_vertical == 'map' and flip_vertical:
|
||||||
|
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=1)
|
visualization = rospy.wait_for_message('aruco_map/visualization', VisualizationMarkerArray, timeout=0.8)
|
||||||
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=1)
|
rospy.wait_for_message('aruco_map/pose', PoseWithCovarianceStamped, timeout=0.8)
|
||||||
except rospy.ROSException:
|
except rospy.ROSException:
|
||||||
failure('no map detection')
|
if not markers:
|
||||||
|
info('no map detection as no markers detection')
|
||||||
|
elif not markers.markers:
|
||||||
|
info('no map detection as no markers detected')
|
||||||
|
else:
|
||||||
|
failure('no map detection')
|
||||||
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=1)
|
vis = rospy.wait_for_message('mavros/vision_pose/pose', PoseStamped, timeout=0.8)
|
||||||
except rospy.ROSException:
|
except rospy.ROSException:
|
||||||
try:
|
try:
|
||||||
vis = rospy.wait_for_message('mavros/mocap/pose', PoseStamped, timeout=1)
|
vis = rospy.wait_for_message('mavros/mocap/pose', PoseStamped, timeout=0.8)
|
||||||
except rospy.ROSException:
|
except rospy.ROSException:
|
||||||
failure('no VPE or MoCap messages')
|
if not is_process_running('vpe_publisher', full=True):
|
||||||
# check if vpe_publisher is running
|
info('no vision position estimate, vpe_publisher is not running')
|
||||||
try:
|
elif rospy.get_param('aruco_map/known_vertical', '') == 'map' \
|
||||||
subprocess.check_output(['pgrep', '-x', 'vpe_publisher'])
|
and rospy.get_param('aruco_map/flip_vertical', False):
|
||||||
except subprocess.CalledProcessError:
|
failure('no vision position estimate, markers are on the ceiling')
|
||||||
return # it's not running, skip following checks
|
elif is_on_the_floor():
|
||||||
|
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')
|
||||||
@@ -414,26 +493,37 @@ 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: %.2f', vision_yaw_w)
|
info('vision yaw weight: %s', ff(vision_yaw_w))
|
||||||
fuse = get_param('LPE_FUSION')
|
fuse = int(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 parameter is %s, but it should be zero', delay)
|
failure('LPE_VIS_DELAY = %s, but it should be zero', delay)
|
||||||
info('LPE_VIS_XY is %.2f m, LPE_VIS_Z is %.2f m', get_param('LPE_VIS_XY'), get_param('LPE_VIS_Z'))
|
info('LPE_VIS_XY = %s m, LPE_VIS_Z = %s m', get_paramf('LPE_VIS_XY'), get_paramf('LPE_VIS_Z'))
|
||||||
elif est == 2:
|
elif est == 2:
|
||||||
fuse = get_param('EKF2_AID_MASK')
|
ev_ctrl = get_param('EKF2_EV_CTRL', strict=False)
|
||||||
if not fuse & (1 << 3):
|
if ev_ctrl is not None: # PX4 after v1.14
|
||||||
failure('vision position fusion is disabled, change EKF2_AID_MASK parameter')
|
ev_ctrl = int(ev_ctrl)
|
||||||
if not fuse & (1 << 4):
|
if not ev_ctrl & (1 << 0):
|
||||||
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
|
failure('vision horizontal position fusion is disabled, change EKF2_EV_CTRL 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 is %.2f, but it should be zero', delay)
|
failure('EKF2_EV_DELAY = %.2f, but it should be zero', delay)
|
||||||
info('EKF2_EVA_NOISE is %.3f, EKF2_EVP_NOISE is %.3f',
|
info('EKF2_EVA_NOISE = %s, EKF2_EVP_NOISE = %s',
|
||||||
get_param('EKF2_EVA_NOISE'),
|
get_paramf('EKF2_EVA_NOISE', 3),
|
||||||
get_param('EKF2_EVP_NOISE'))
|
get_paramf('EKF2_EVP_NOISE', 3))
|
||||||
|
|
||||||
if not vis:
|
if not vis:
|
||||||
return
|
return
|
||||||
@@ -531,15 +621,25 @@ 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=1)
|
rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=0.8)
|
||||||
except rospy.ROSException:
|
except rospy.ROSException:
|
||||||
info('no global position')
|
info('no global position')
|
||||||
if get_param('SYS_MC_EST_GROUP') == 2 and (get_param('EKF2_AID_MASK') & (1 << 0)):
|
if get_param('SYS_MC_EST_GROUP') == 2:
|
||||||
failure('enabled GPS fusion may suppress vision position aiding')
|
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)
|
||||||
@@ -547,40 +647,49 @@ 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 parameter is %s, but it should be zero', rot)
|
failure('SENS_FLOW_ROT = %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 = get_param('LPE_FUSION')
|
fuse = int(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')
|
scale = get_param('LPE_FLW_SCALE', 1)
|
||||||
if not numpy.isclose(scale, 1.0):
|
if not numpy.isclose(scale, 1.0):
|
||||||
failure('LPE_FLW_SCALE parameter is %.2f, but it should be 1.0', scale)
|
failure('LPE_FLW_SCALE = %.2f, but it should be 1.0', scale)
|
||||||
|
|
||||||
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',
|
info('LPE_FLW_QMIN = %s, LPE_FLW_R = %s, LPE_FLW_RR = %s',
|
||||||
get_param('LPE_FLW_QMIN'),
|
get_paramf('LPE_FLW_QMIN'),
|
||||||
get_param('LPE_FLW_R'),
|
get_paramf('LPE_FLW_R', 4),
|
||||||
get_param('LPE_FLW_RR'),
|
get_paramf('LPE_FLW_RR', 4))
|
||||||
get_param('SENS_FLOW_MINHGT'),
|
|
||||||
get_param('SENS_FLOW_MAXHGT'))
|
|
||||||
elif est == 2:
|
elif est == 2:
|
||||||
fuse = get_param('EKF2_AID_MASK')
|
of_ctrl = get_param('EKF2_OF_CTRL', strict=False)
|
||||||
if not fuse & (1 << 1):
|
if of_ctrl is not None: # PX4 after v1.14
|
||||||
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
|
if of_ctrl == 0:
|
||||||
delay = get_param('EKF2_OF_DELAY')
|
failure('optical flow fusion is disabled, change EKF2_OF_CTRL')
|
||||||
|
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 is %.2f, but it should be zero', delay)
|
failure('EKF2_OF_DELAY = %.2f, but it should be zero', delay)
|
||||||
info('EKF2_OF_QMIN is %s, EKF2_OF_N_MIN is %.4f, EKF2_OF_N_MAX is %.4f, SENS_FLOW_MINHGT is %.3f, SENS_FLOW_MAXHGT is %.3f',
|
info('EKF2_OF_QMIN = %s, EKF2_OF_N_MIN = %s, EKF2_OF_N_MAX = %s',
|
||||||
get_param('EKF2_OF_QMIN'),
|
get_paramf('EKF2_OF_QMIN'),
|
||||||
get_param('EKF2_OF_N_MIN'),
|
get_paramf('EKF2_OF_N_MIN', 4),
|
||||||
get_param('EKF2_OF_N_MAX'),
|
get_paramf('EKF2_OF_N_MAX', 4))
|
||||||
get_param('SENS_FLOW_MINHGT'),
|
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_MAXHGT'))
|
|
||||||
|
|
||||||
except rospy.ROSException:
|
except rospy.ROSException:
|
||||||
failure('no optical flow data (from Raspberry)')
|
if rospy.get_param('optical_flow/disable_on_vpe', False):
|
||||||
|
try:
|
||||||
|
rospy.wait_for_message('mavros/vision_pose/pose', PoseStamped, timeout=1)
|
||||||
|
info('no optical flow as disable_on_vpe is true')
|
||||||
|
except:
|
||||||
|
failure('no optical flow on RPi, disable_on_vpe is true, but no vision pose also')
|
||||||
|
else:
|
||||||
|
failure('no optical flow on RPi')
|
||||||
|
|
||||||
|
|
||||||
@check('Rangefinder')
|
@check('Rangefinder')
|
||||||
@@ -604,23 +713,26 @@ def check_rangefinder():
|
|||||||
|
|
||||||
est = get_param('SYS_MC_EST_GROUP')
|
est = get_param('SYS_MC_EST_GROUP')
|
||||||
if est == 1:
|
if est == 1:
|
||||||
fuse = get_param('LPE_FUSION')
|
fuse = int(get_param('LPE_FUSION', 0))
|
||||||
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_MODE')
|
hgt = get_param('EKF2_HGT_REF', strict=False)
|
||||||
|
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')
|
aid = get_param('EKF2_RNG_AID', strict=False)
|
||||||
if aid != 1:
|
if aid is not None: # PX4 before v1.14
|
||||||
info('EKF2_RNG_AID != 1, range sensor aiding disabled')
|
if aid != 1:
|
||||||
else:
|
info('EKF2_RNG_AID != 1, range sensor aiding disabled')
|
||||||
info('EKF2_RNG_AID = 1, range sensor aiding enabled')
|
else:
|
||||||
|
info('EKF2_RNG_AID = 1, range sensor aiding enabled')
|
||||||
|
|
||||||
|
|
||||||
@check('Boot duration')
|
@check('Boot duration')
|
||||||
@@ -638,7 +750,7 @@ def check_boot_duration():
|
|||||||
|
|
||||||
@check('CPU usage')
|
@check('CPU usage')
|
||||||
def check_cpu_usage():
|
def check_cpu_usage():
|
||||||
WHITELIST = 'nodelet', 'gzclient', 'gzserver'
|
WHITELIST = 'nodelet', 'gzclient', 'gzserver', 'selfcheck.py'
|
||||||
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).decode()
|
||||||
processes = output.split('\n')
|
processes = output.split('\n')
|
||||||
@@ -707,7 +819,10 @@ 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:
|
||||||
info('no /etc/clover_version file, not the Clover image?')
|
try:
|
||||||
|
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')
|
||||||
@@ -818,26 +933,47 @@ 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():
|
||||||
check_image()
|
checks = [
|
||||||
check_board()
|
check_image,
|
||||||
check_clover_service()
|
check_board,
|
||||||
check_network()
|
check_clover_service,
|
||||||
check_fcu()
|
check_network,
|
||||||
check_imu()
|
check_fcu,
|
||||||
check_local_position()
|
check_imu,
|
||||||
check_velocity()
|
check_local_position,
|
||||||
check_global_position()
|
check_velocity,
|
||||||
check_preflight_status()
|
check_global_position,
|
||||||
check_main_camera()
|
check_preflight_status,
|
||||||
check_aruco()
|
check_main_camera,
|
||||||
check_simpleoffboard()
|
check_aruco,
|
||||||
check_optical_flow()
|
check_simpleoffboard,
|
||||||
check_vpe()
|
check_optical_flow,
|
||||||
check_rangefinder()
|
check_vpe,
|
||||||
check_rpi_health()
|
check_rangefinder,
|
||||||
check_cpu_usage()
|
check_rpi_health,
|
||||||
check_boot_duration()
|
check_cpu_usage,
|
||||||
|
check_boot_duration,
|
||||||
|
]
|
||||||
|
if rospy.get_param('~parallel', False):
|
||||||
|
parallel_for(checks)
|
||||||
|
else:
|
||||||
|
consequentially_for(checks)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@@ -23,12 +23,14 @@
|
|||||||
#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 <std_srvs/Trigger.h>
|
#include <std_srvs/Trigger.h>
|
||||||
|
#include <geometry_msgs/PointStamped.h>
|
||||||
#include <geometry_msgs/PoseStamped.h>
|
#include <geometry_msgs/PoseStamped.h>
|
||||||
#include <geometry_msgs/TwistStamped.h>
|
#include <geometry_msgs/TwistStamped.h>
|
||||||
#include <geometry_msgs/Vector3Stamped.h>
|
#include <geometry_msgs/Vector3Stamped.h>
|
||||||
#include <geometry_msgs/QuaternionStamped.h>
|
#include <geometry_msgs/QuaternionStamped.h>
|
||||||
#include <sensor_msgs/NavSatFix.h>
|
#include <sensor_msgs/NavSatFix.h>
|
||||||
#include <sensor_msgs/BatteryState.h>
|
#include <sensor_msgs/BatteryState.h>
|
||||||
|
#include <sensor_msgs/Range.h>
|
||||||
#include <mavros_msgs/CommandBool.h>
|
#include <mavros_msgs/CommandBool.h>
|
||||||
#include <mavros_msgs/SetMode.h>
|
#include <mavros_msgs/SetMode.h>
|
||||||
#include <mavros_msgs/PositionTarget.h>
|
#include <mavros_msgs/PositionTarget.h>
|
||||||
@@ -37,14 +39,19 @@
|
|||||||
#include <mavros_msgs/State.h>
|
#include <mavros_msgs/State.h>
|
||||||
#include <mavros_msgs/StatusText.h>
|
#include <mavros_msgs/StatusText.h>
|
||||||
#include <mavros_msgs/ManualControl.h>
|
#include <mavros_msgs/ManualControl.h>
|
||||||
|
#include <mavros_msgs/Altitude.h>
|
||||||
|
|
||||||
#include <clover/GetTelemetry.h>
|
#include <clover/GetTelemetry.h>
|
||||||
#include <clover/Navigate.h>
|
#include <clover/Navigate.h>
|
||||||
#include <clover/NavigateGlobal.h>
|
#include <clover/NavigateGlobal.h>
|
||||||
|
#include <clover/SetAltitude.h>
|
||||||
|
#include <clover/SetYaw.h>
|
||||||
|
#include <clover/SetYawRate.h>
|
||||||
#include <clover/SetPosition.h>
|
#include <clover/SetPosition.h>
|
||||||
#include <clover/SetVelocity.h>
|
#include <clover/SetVelocity.h>
|
||||||
#include <clover/SetAttitude.h>
|
#include <clover/SetAttitude.h>
|
||||||
#include <clover/SetRates.h>
|
#include <clover/SetRates.h>
|
||||||
|
#include <clover/State.h>
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::isnan;
|
using std::isnan;
|
||||||
@@ -54,6 +61,7 @@ using namespace clover;
|
|||||||
using mavros_msgs::PositionTarget;
|
using mavros_msgs::PositionTarget;
|
||||||
using mavros_msgs::AttitudeTarget;
|
using mavros_msgs::AttitudeTarget;
|
||||||
using mavros_msgs::Thrust;
|
using mavros_msgs::Thrust;
|
||||||
|
using mavros_msgs::Altitude;
|
||||||
|
|
||||||
// tf2
|
// tf2
|
||||||
tf2_ros::Buffer tf_buffer;
|
tf2_ros::Buffer tf_buffer;
|
||||||
@@ -79,35 +87,43 @@ float default_speed;
|
|||||||
bool auto_release;
|
bool auto_release;
|
||||||
bool land_only_in_offboard, nav_from_sp, check_kill_switch;
|
bool land_only_in_offboard, nav_from_sp, check_kill_switch;
|
||||||
std::map<string, string> reference_frames;
|
std::map<string, string> reference_frames;
|
||||||
|
string terrain_frame_mode;
|
||||||
|
|
||||||
// Publishers
|
// Publishers
|
||||||
ros::Publisher attitude_pub, attitude_raw_pub, position_pub, position_raw_pub, rates_pub, thrust_pub;
|
ros::Publisher attitude_pub, attitude_raw_pub, position_pub, position_raw_pub, rates_pub, thrust_pub, state_pub;
|
||||||
|
|
||||||
// Service clients
|
// Service clients
|
||||||
ros::ServiceClient arming, set_mode;
|
ros::ServiceClient arming, set_mode;
|
||||||
|
|
||||||
// Containers
|
// Containers
|
||||||
ros::Timer setpoint_timer;
|
ros::Timer setpoint_timer;
|
||||||
tf::Quaternion tq;
|
|
||||||
PoseStamped position_msg;
|
PoseStamped position_msg;
|
||||||
PositionTarget position_raw_msg;
|
PositionTarget position_raw_msg;
|
||||||
AttitudeTarget att_raw_msg;
|
//TwistStamped rates_msg;
|
||||||
Thrust thrust_msg;
|
|
||||||
TwistStamped rates_msg;
|
|
||||||
TransformStamped target, setpoint;
|
TransformStamped target, setpoint;
|
||||||
geometry_msgs::TransformStamped body;
|
geometry_msgs::TransformStamped body;
|
||||||
|
geometry_msgs::TransformStamped terrain;
|
||||||
|
|
||||||
// State
|
// State
|
||||||
PoseStamped nav_start;
|
PoseStamped nav_start;
|
||||||
PoseStamped setpoint_position, setpoint_position_transformed;
|
PointStamped setpoint_position;
|
||||||
Vector3Stamped setpoint_velocity, setpoint_velocity_transformed;
|
PointStamped setpoint_altitude;
|
||||||
QuaternionStamped setpoint_attitude, setpoint_attitude_transformed;
|
Vector3Stamped setpoint_velocity;
|
||||||
float setpoint_yaw_rate;
|
float setpoint_yaw, setpoint_roll, setpoint_pitch;
|
||||||
|
Vector3 setpoint_rates;
|
||||||
|
string yaw_frame_id;
|
||||||
|
float setpoint_thrust;
|
||||||
float nav_speed;
|
float nav_speed;
|
||||||
|
float setpoint_lat = NAN, setpoint_lon = NAN;
|
||||||
bool busy = false;
|
bool busy = false;
|
||||||
bool wait_armed = false;
|
bool wait_armed = false;
|
||||||
bool nav_from_sp_flag = false;
|
bool nav_from_sp_flag = false;
|
||||||
|
|
||||||
|
// Last published
|
||||||
|
PoseStamped setpoint_pose_local;
|
||||||
|
Vector3Stamped setpoint_velocity_local;
|
||||||
|
float yaw_local;
|
||||||
|
|
||||||
enum setpoint_type_t {
|
enum setpoint_type_t {
|
||||||
NONE,
|
NONE,
|
||||||
NAVIGATE,
|
NAVIGATE,
|
||||||
@@ -115,7 +131,10 @@ enum setpoint_type_t {
|
|||||||
POSITION,
|
POSITION,
|
||||||
VELOCITY,
|
VELOCITY,
|
||||||
ATTITUDE,
|
ATTITUDE,
|
||||||
RATES
|
RATES,
|
||||||
|
_ALTITUDE,
|
||||||
|
_YAW,
|
||||||
|
_YAW_RATE,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum setpoint_type_t setpoint_type = NONE;
|
enum setpoint_type_t setpoint_type = NONE;
|
||||||
@@ -170,7 +189,7 @@ void handleLocalPosition(const PoseStamped& pose)
|
|||||||
{
|
{
|
||||||
local_position = pose;
|
local_position = pose;
|
||||||
publishBodyFrame();
|
publishBodyFrame();
|
||||||
// TODO: terrain?, home?
|
// TODO: home?
|
||||||
}
|
}
|
||||||
|
|
||||||
// wait for transform without interrupting publishing setpoints
|
// wait for transform without interrupting publishing setpoints
|
||||||
@@ -188,6 +207,29 @@ inline bool waitTransform(const string& target, const string& source,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void publishTerrain(const double distance, const ros::Time& stamp)
|
||||||
|
{
|
||||||
|
if (!waitTransform(local_frame, body.child_frame_id, stamp, ros::Duration(0.1))) return;
|
||||||
|
|
||||||
|
auto t = tf_buffer.lookupTransform(local_frame, body.child_frame_id, stamp);
|
||||||
|
t.child_frame_id = terrain.child_frame_id;
|
||||||
|
t.transform.translation.z -= distance;
|
||||||
|
static_transform_broadcaster->sendTransform(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleAltitude(const Altitude& alt)
|
||||||
|
{
|
||||||
|
if (!std::isfinite(alt.bottom_clearance)) return;
|
||||||
|
publishTerrain(alt.bottom_clearance, alt.header.stamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleRange(const Range& range)
|
||||||
|
{
|
||||||
|
if (!std::isfinite(range.range)) return;
|
||||||
|
// TODO: check it's facing down
|
||||||
|
publishTerrain(range.range, range.header.stamp);
|
||||||
|
}
|
||||||
|
|
||||||
#define TIMEOUT(msg, timeout) (msg.header.stamp.isZero() || (ros::Time::now() - msg.header.stamp > timeout))
|
#define TIMEOUT(msg, timeout) (msg.header.stamp.isZero() || (ros::Time::now() - msg.header.stamp > timeout))
|
||||||
|
|
||||||
bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
|
bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
|
||||||
@@ -207,11 +249,11 @@ bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
|
|||||||
res.vx = NAN;
|
res.vx = NAN;
|
||||||
res.vy = NAN;
|
res.vy = NAN;
|
||||||
res.vz = NAN;
|
res.vz = NAN;
|
||||||
res.pitch = NAN;
|
|
||||||
res.roll = NAN;
|
res.roll = NAN;
|
||||||
|
res.pitch = NAN;
|
||||||
res.yaw = NAN;
|
res.yaw = NAN;
|
||||||
res.pitch_rate = NAN;
|
|
||||||
res.roll_rate = NAN;
|
res.roll_rate = NAN;
|
||||||
|
res.pitch_rate = NAN;
|
||||||
res.yaw_rate = NAN;
|
res.yaw_rate = NAN;
|
||||||
res.voltage = NAN;
|
res.voltage = NAN;
|
||||||
res.cell_voltage = NAN;
|
res.cell_voltage = NAN;
|
||||||
@@ -341,20 +383,20 @@ inline float getDistance(const Point& from, const Point& to)
|
|||||||
return hypot(from.x - to.x, from.y - to.y, from.z - to.z);
|
return hypot(from.x - to.x, from.y - to.y, from.z - to.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
void getNavigateSetpoint(const ros::Time& stamp, float speed, Point& nav_setpoint)
|
void getNavigateSetpoint(const ros::Time& stamp, const float speed, Point& nav_setpoint)
|
||||||
{
|
{
|
||||||
if (wait_armed) {
|
if (wait_armed) {
|
||||||
// don't start navigating if we're waiting arming
|
// don't start navigating if we're waiting arming
|
||||||
nav_start.header.stamp = stamp;
|
nav_start.header.stamp = stamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
float distance = getDistance(nav_start.pose.position, setpoint_position_transformed.pose.position);
|
float distance = getDistance(nav_start.pose.position, setpoint_pose_local.pose.position);
|
||||||
float time = distance / speed;
|
float time = distance / speed;
|
||||||
float passed = std::min((stamp - nav_start.header.stamp).toSec() / time, 1.0);
|
float passed = std::min((stamp - nav_start.header.stamp).toSec() / time, 1.0);
|
||||||
|
|
||||||
nav_setpoint.x = nav_start.pose.position.x + (setpoint_position_transformed.pose.position.x - nav_start.pose.position.x) * passed;
|
nav_setpoint.x = nav_start.pose.position.x + (setpoint_pose_local.pose.position.x - nav_start.pose.position.x) * passed;
|
||||||
nav_setpoint.y = nav_start.pose.position.y + (setpoint_position_transformed.pose.position.y - nav_start.pose.position.y) * passed;
|
nav_setpoint.y = nav_start.pose.position.y + (setpoint_pose_local.pose.position.y - nav_start.pose.position.y) * passed;
|
||||||
nav_setpoint.z = nav_start.pose.position.z + (setpoint_position_transformed.pose.position.z - nav_start.pose.position.z) * passed;
|
nav_setpoint.z = nav_start.pose.position.z + (setpoint_pose_local.pose.position.z - nav_start.pose.position.z) * passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
PoseStamped globalToLocal(double lat, double lon)
|
PoseStamped globalToLocal(double lat, double lon)
|
||||||
@@ -385,44 +427,101 @@ PoseStamped globalToLocal(double lat, double lon)
|
|||||||
return pose;
|
return pose;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// publish navigate_target frame
|
||||||
|
void publishTarget(ros::Time stamp, bool _static = false)
|
||||||
|
{
|
||||||
|
bool single_frame = (setpoint_position.header.frame_id == setpoint_altitude.header.frame_id);
|
||||||
|
|
||||||
|
// handle yaw for target frame
|
||||||
|
if (setpoint_yaw_type == YAW || setpoint_yaw_type == YAW_RATE) { // use last set yaw for yaw_rate
|
||||||
|
if (setpoint_altitude.header.frame_id == yaw_frame_id) {
|
||||||
|
target.transform.rotation = tf::createQuaternionMsgFromYaw(setpoint_yaw);
|
||||||
|
} else {
|
||||||
|
single_frame = false;
|
||||||
|
target.transform.rotation = tf::createQuaternionMsgFromYaw(yaw_local);
|
||||||
|
}
|
||||||
|
} else if (setpoint_yaw_type == TOWARDS) {
|
||||||
|
single_frame = false;
|
||||||
|
target.transform.rotation = tf::createQuaternionMsgFromYaw(yaw_local);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_static && single_frame) {
|
||||||
|
// publish at user's command, if all frames are the same
|
||||||
|
target.header.frame_id = setpoint_position.header.frame_id;
|
||||||
|
target.header.stamp = stamp;
|
||||||
|
target.transform.translation.x = setpoint_position.point.x;
|
||||||
|
target.transform.translation.y = setpoint_position.point.y;
|
||||||
|
target.transform.translation.z = setpoint_position.point.z;
|
||||||
|
|
||||||
|
} else if (!_static) {
|
||||||
|
// publish at each iteration, if frames are different
|
||||||
|
target.header = setpoint_pose_local.header;
|
||||||
|
target.transform.translation.x = setpoint_pose_local.pose.position.x;
|
||||||
|
target.transform.translation.y = setpoint_pose_local.pose.position.y;
|
||||||
|
target.transform.translation.z = setpoint_pose_local.pose.position.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_transform_broadcaster->sendTransform(target);
|
||||||
|
}
|
||||||
|
|
||||||
void publish(const ros::Time stamp)
|
void publish(const ros::Time stamp)
|
||||||
{
|
{
|
||||||
if (setpoint_type == NONE) return;
|
if (setpoint_type == NONE) return;
|
||||||
|
|
||||||
position_raw_msg.header.stamp = stamp;
|
position_raw_msg.header.stamp = stamp;
|
||||||
thrust_msg.header.stamp = stamp;
|
|
||||||
rates_msg.header.stamp = stamp;
|
|
||||||
|
|
||||||
try {
|
// transform position
|
||||||
// transform position and/or yaw
|
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
|
||||||
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION || setpoint_type == VELOCITY || setpoint_type == ATTITUDE) {
|
setpoint_position.header.stamp = stamp;
|
||||||
setpoint_position.header.stamp = stamp;
|
setpoint_altitude.header.stamp = stamp;
|
||||||
tf_buffer.transform(setpoint_position, setpoint_position_transformed, local_frame, ros::Duration(0.05));
|
// transform xy
|
||||||
|
try {
|
||||||
|
auto xy = tf_buffer.transform(setpoint_position, local_frame, ros::Duration(0.05));
|
||||||
|
setpoint_pose_local.header = xy.header;
|
||||||
|
setpoint_pose_local.pose.position.x = xy.point.x;
|
||||||
|
setpoint_pose_local.pose.position.y = xy.point.y;
|
||||||
|
} catch (tf2::TransformException& ex) {
|
||||||
|
// can't transform xy, use last known
|
||||||
|
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
|
||||||
}
|
}
|
||||||
|
// transform altitude
|
||||||
// transform velocity
|
try {
|
||||||
if (setpoint_type == VELOCITY) {
|
setpoint_pose_local.pose.position.z = tf_buffer.transform(setpoint_altitude, local_frame, ros::Duration(0.05)).point.z;
|
||||||
setpoint_velocity.header.stamp = stamp;
|
} catch (tf2::TransformException& ex) {
|
||||||
tf_buffer.transform(setpoint_velocity, setpoint_velocity_transformed, local_frame, ros::Duration(0.05));
|
// can't transform altitude, use last known
|
||||||
|
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (const tf2::TransformException& e) {
|
|
||||||
ROS_WARN_THROTTLE(10, "can't transform");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// transform yaw
|
||||||
|
if (setpoint_yaw_type == YAW) {
|
||||||
|
try {
|
||||||
|
QuaternionStamped q;
|
||||||
|
q.header.stamp = stamp;
|
||||||
|
q.header.frame_id = yaw_frame_id;
|
||||||
|
q.quaternion = tf::createQuaternionMsgFromYaw(setpoint_yaw);
|
||||||
|
yaw_local = tf2::getYaw(tf_buffer.transform(q, local_frame, ros::Duration(0.05)).quaternion);
|
||||||
|
} catch (tf2::TransformException& ex) {
|
||||||
|
// can't transform yaw, use last known
|
||||||
|
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute navigate setpoint
|
||||||
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) {
|
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) {
|
||||||
position_msg.pose.orientation = setpoint_position_transformed.pose.orientation; // copy yaw
|
|
||||||
getNavigateSetpoint(stamp, nav_speed, position_msg.pose.position);
|
getNavigateSetpoint(stamp, nav_speed, position_msg.pose.position);
|
||||||
|
|
||||||
if (setpoint_yaw_type == TOWARDS) {
|
if (setpoint_yaw_type == TOWARDS) {
|
||||||
double yaw_towards = atan2(position_msg.pose.position.y - nav_start.pose.position.y,
|
yaw_local = atan2(position_msg.pose.position.y - nav_start.pose.position.y,
|
||||||
position_msg.pose.position.x - nav_start.pose.position.x);
|
position_msg.pose.position.x - nav_start.pose.position.x);
|
||||||
position_msg.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(0, 0, yaw_towards);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
position_msg.pose.orientation = tf::createQuaternionMsgFromYaw(yaw_local);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setpoint_type == POSITION) {
|
if (setpoint_type == POSITION) {
|
||||||
position_msg = setpoint_position_transformed;
|
position_msg = setpoint_pose_local;
|
||||||
|
position_msg.pose.orientation = tf::createQuaternionMsgFromYaw(yaw_local);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) {
|
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) {
|
||||||
@@ -439,14 +538,14 @@ void publish(const ros::Time stamp)
|
|||||||
PositionTarget::IGNORE_AFY +
|
PositionTarget::IGNORE_AFY +
|
||||||
PositionTarget::IGNORE_AFZ +
|
PositionTarget::IGNORE_AFZ +
|
||||||
PositionTarget::IGNORE_YAW;
|
PositionTarget::IGNORE_YAW;
|
||||||
position_raw_msg.yaw_rate = setpoint_yaw_rate;
|
position_raw_msg.yaw_rate = setpoint_rates.z;
|
||||||
position_raw_msg.position = position_msg.pose.position;
|
position_raw_msg.position = position_msg.pose.position;
|
||||||
position_raw_pub.publish(position_raw_msg);
|
position_raw_pub.publish(position_raw_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// publish setpoint frame
|
// publish setpoint frame
|
||||||
if (!setpoint.child_frame_id.empty()) {
|
if (!setpoint.child_frame_id.empty()) {
|
||||||
if (setpoint.header.stamp == position_msg.header.stamp) {
|
if (setpoint.header.stamp >= position_msg.header.stamp) {
|
||||||
return; // avoid TF_REPEATED_DATA warnings
|
return; // avoid TF_REPEATED_DATA warnings
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,9 +557,22 @@ void publish(const ros::Time stamp)
|
|||||||
setpoint.header.stamp = position_msg.header.stamp;
|
setpoint.header.stamp = position_msg.header.stamp;
|
||||||
transform_broadcaster->sendTransform(setpoint);
|
transform_broadcaster->sendTransform(setpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// publish dynamic target frame
|
||||||
|
publishTarget(stamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setpoint_type == VELOCITY) {
|
if (setpoint_type == VELOCITY) {
|
||||||
|
// transform velocity to local frame
|
||||||
|
setpoint_velocity.header.stamp = stamp;
|
||||||
|
try {
|
||||||
|
setpoint_velocity_local = tf_buffer.transform(setpoint_velocity, local_frame, ros::Duration(0.05));
|
||||||
|
} catch (tf2::TransformException& ex) {
|
||||||
|
// can't transform velocity, use last known
|
||||||
|
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
// publish velocity
|
||||||
position_raw_msg.type_mask = PositionTarget::IGNORE_PX +
|
position_raw_msg.type_mask = PositionTarget::IGNORE_PX +
|
||||||
PositionTarget::IGNORE_PY +
|
PositionTarget::IGNORE_PY +
|
||||||
PositionTarget::IGNORE_PZ +
|
PositionTarget::IGNORE_PZ +
|
||||||
@@ -468,14 +580,22 @@ void publish(const ros::Time stamp)
|
|||||||
PositionTarget::IGNORE_AFY +
|
PositionTarget::IGNORE_AFY +
|
||||||
PositionTarget::IGNORE_AFZ;
|
PositionTarget::IGNORE_AFZ;
|
||||||
position_raw_msg.type_mask += setpoint_yaw_type == YAW ? PositionTarget::IGNORE_YAW_RATE : PositionTarget::IGNORE_YAW;
|
position_raw_msg.type_mask += setpoint_yaw_type == YAW ? PositionTarget::IGNORE_YAW_RATE : PositionTarget::IGNORE_YAW;
|
||||||
position_raw_msg.velocity = setpoint_velocity_transformed.vector;
|
position_raw_msg.velocity = setpoint_velocity_local.vector;
|
||||||
position_raw_msg.yaw = tf2::getYaw(setpoint_position_transformed.pose.orientation);
|
position_raw_msg.yaw = yaw_local;
|
||||||
position_raw_msg.yaw_rate = setpoint_yaw_rate;
|
position_raw_msg.yaw_rate = setpoint_rates.z;
|
||||||
position_raw_pub.publish(position_raw_msg);
|
position_raw_pub.publish(position_raw_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setpoint_type == ATTITUDE) {
|
if (setpoint_type == ATTITUDE) {
|
||||||
attitude_pub.publish(setpoint_position_transformed);
|
PoseStamped msg;
|
||||||
|
msg.header.stamp = stamp;
|
||||||
|
msg.header.frame_id = local_frame;
|
||||||
|
msg.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(setpoint_roll, setpoint_pitch, yaw_local);
|
||||||
|
attitude_pub.publish(msg);
|
||||||
|
|
||||||
|
Thrust thrust_msg;
|
||||||
|
thrust_msg.header.stamp = stamp;
|
||||||
|
thrust_msg.thrust = setpoint_thrust;
|
||||||
thrust_pub.publish(thrust_msg);
|
thrust_pub.publish(thrust_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,11 +604,12 @@ void publish(const ros::Time stamp)
|
|||||||
// thrust_pub.publish(thrust_msg);
|
// thrust_pub.publish(thrust_msg);
|
||||||
// mavros rates topics waits for rates in local frame
|
// mavros rates topics waits for rates in local frame
|
||||||
// use rates in body frame for simplicity
|
// use rates in body frame for simplicity
|
||||||
|
AttitudeTarget att_raw_msg;
|
||||||
att_raw_msg.header.stamp = stamp;
|
att_raw_msg.header.stamp = stamp;
|
||||||
att_raw_msg.header.frame_id = fcu_frame;
|
att_raw_msg.header.frame_id = fcu_frame;
|
||||||
att_raw_msg.type_mask = AttitudeTarget::IGNORE_ATTITUDE;
|
att_raw_msg.type_mask = AttitudeTarget::IGNORE_ATTITUDE;
|
||||||
att_raw_msg.body_rate = rates_msg.twist.angular;
|
att_raw_msg.body_rate = setpoint_rates;
|
||||||
att_raw_msg.thrust = thrust_msg.thrust;
|
att_raw_msg.thrust = setpoint_thrust;
|
||||||
attitude_raw_pub.publish(att_raw_msg);
|
attitude_raw_pub.publish(att_raw_msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -528,10 +649,59 @@ inline void checkState()
|
|||||||
throw std::runtime_error("No connection to FCU, https://clover.coex.tech/connection");
|
throw std::runtime_error("No connection to FCU, https://clover.coex.tech/connection");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void publishState()
|
||||||
|
{
|
||||||
|
clover::State msg;
|
||||||
|
msg.mode = setpoint_type;
|
||||||
|
msg.yaw_mode = setpoint_yaw_type;
|
||||||
|
|
||||||
|
if (setpoint_position.header.frame_id.empty()) {
|
||||||
|
msg.x = NAN;
|
||||||
|
msg.y = NAN;
|
||||||
|
msg.z = NAN;
|
||||||
|
} else {
|
||||||
|
msg.x = setpoint_position.point.x;
|
||||||
|
msg.y = setpoint_position.point.y;
|
||||||
|
msg.z = setpoint_altitude.point.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.speed = nav_speed;
|
||||||
|
msg.lat = setpoint_lat;
|
||||||
|
msg.lon = setpoint_lon;
|
||||||
|
msg.vx = setpoint_velocity.vector.x;
|
||||||
|
msg.vy = setpoint_velocity.vector.y;
|
||||||
|
msg.vz = setpoint_velocity.vector.z;
|
||||||
|
msg.roll = setpoint_roll;
|
||||||
|
msg.pitch = setpoint_pitch;
|
||||||
|
msg.yaw = !yaw_frame_id.empty() ? setpoint_yaw : NAN;
|
||||||
|
|
||||||
|
msg.roll_rate = setpoint_rates.x;
|
||||||
|
msg.pitch_rate = setpoint_rates.y;
|
||||||
|
msg.yaw_rate = setpoint_rates.z;
|
||||||
|
msg.thrust = setpoint_thrust;
|
||||||
|
|
||||||
|
if (setpoint_type == VELOCITY) {
|
||||||
|
msg.xy_frame_id = setpoint_velocity.header.frame_id;
|
||||||
|
msg.z_frame_id = setpoint_velocity.header.frame_id;
|
||||||
|
} else {
|
||||||
|
msg.xy_frame_id = setpoint_position.header.frame_id;
|
||||||
|
msg.z_frame_id = setpoint_altitude.header.frame_id;
|
||||||
|
}
|
||||||
|
msg.yaw_frame_id = yaw_frame_id;
|
||||||
|
|
||||||
|
state_pub.publish(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float safe(float value) {
|
||||||
|
return std::isfinite(value) ? value : 0;
|
||||||
|
}
|
||||||
|
|
||||||
#define ENSURE_FINITE(var) { if (!std::isfinite(var)) throw std::runtime_error(#var " argument cannot be NaN or Inf"); }
|
#define ENSURE_FINITE(var) { if (!std::isfinite(var)) throw std::runtime_error(#var " argument cannot be NaN or Inf"); }
|
||||||
|
|
||||||
|
#define ENSURE_NON_INF(var) { if (std::isinf(var)) throw std::runtime_error(#var " argument cannot be Inf"); }
|
||||||
|
|
||||||
bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, float vy, float vz,
|
bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, float vy, float vz,
|
||||||
float pitch, float roll, float yaw, float pitch_rate, float roll_rate, float yaw_rate, // editorconfig-checker-disable-line
|
float roll, float pitch, float yaw, float roll_rate, float pitch_rate, float yaw_rate, // editorconfig-checker-disable-line
|
||||||
float lat, float lon, float thrust, float speed, string frame_id, bool auto_arm, // editorconfig-checker-disable-line
|
float lat, float lon, float thrust, float speed, string frame_id, bool auto_arm, // editorconfig-checker-disable-line
|
||||||
uint8_t& success, string& message) // editorconfig-checker-disable-line
|
uint8_t& success, string& message) // editorconfig-checker-disable-line
|
||||||
{
|
{
|
||||||
@@ -558,69 +728,40 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
|||||||
auto search = reference_frames.find(frame_id);
|
auto search = reference_frames.find(frame_id);
|
||||||
const string& reference_frame = search == reference_frames.end() ? frame_id : search->second;
|
const string& reference_frame = search == reference_frames.end() ? frame_id : search->second;
|
||||||
|
|
||||||
// Serve "partial" commands
|
ENSURE_NON_INF(x);
|
||||||
|
ENSURE_NON_INF(y);
|
||||||
|
ENSURE_NON_INF(z);
|
||||||
|
ENSURE_NON_INF(speed); // TODO: allow inf
|
||||||
|
ENSURE_NON_INF(vx);
|
||||||
|
ENSURE_NON_INF(vy);
|
||||||
|
ENSURE_NON_INF(vz);
|
||||||
|
ENSURE_NON_INF(roll);
|
||||||
|
ENSURE_NON_INF(pitch);
|
||||||
|
ENSURE_NON_INF(roll_rate);
|
||||||
|
ENSURE_NON_INF(pitch_rate);
|
||||||
|
ENSURE_NON_INF(yaw_rate);
|
||||||
|
ENSURE_NON_INF(thrust);
|
||||||
|
|
||||||
if (!auto_arm && std::isfinite(yaw) &&
|
if (sp_type == NAVIGATE_GLOBAL) {
|
||||||
isnan(x) && isnan(y) && isnan(z) && isnan(vx) && isnan(vy) && isnan(vz) &&
|
|
||||||
isnan(pitch) && isnan(roll) && isnan(thrust) &&
|
|
||||||
isnan(lat) && isnan(lon)) {
|
|
||||||
// change only the yaw
|
|
||||||
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == VELOCITY) {
|
|
||||||
if (!waitTransform(setpoint_position.header.frame_id, frame_id, stamp, transform_timeout))
|
|
||||||
throw std::runtime_error("Can't transform from " + frame_id + " to " + setpoint_position.header.frame_id);
|
|
||||||
|
|
||||||
message = "Changing yaw only";
|
|
||||||
|
|
||||||
QuaternionStamped q;
|
|
||||||
q.header.frame_id = frame_id;
|
|
||||||
q.header.stamp = stamp;
|
|
||||||
q.quaternion = tf::createQuaternionMsgFromYaw(yaw); // TODO: pitch=0, roll=0 is not totally correct
|
|
||||||
setpoint_position.pose.orientation = tf_buffer.transform(q, setpoint_position.header.frame_id).quaternion;
|
|
||||||
setpoint_yaw_type = YAW;
|
|
||||||
goto publish_setpoint;
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("Setting yaw is possible only when position or velocity setpoints active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!auto_arm && std::isfinite(yaw_rate) &&
|
|
||||||
isnan(x) && isnan(y) && isnan(z) && isnan(vx) && isnan(vy) && isnan(vz) &&
|
|
||||||
isnan(pitch) && isnan(roll) && isnan(yaw) && isnan(thrust) &&
|
|
||||||
isnan(lat) && isnan(lon)) {
|
|
||||||
// change only the yaw rate
|
|
||||||
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == VELOCITY) {
|
|
||||||
message = "Changing yaw rate only";
|
|
||||||
|
|
||||||
setpoint_yaw_type = YAW_RATE;
|
|
||||||
setpoint_yaw_rate = yaw_rate;
|
|
||||||
goto publish_setpoint;
|
|
||||||
} else {
|
|
||||||
throw std::runtime_error("Setting yaw rate is possible only when position or velocity setpoints active");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serve normal commands
|
|
||||||
|
|
||||||
if (sp_type == NAVIGATE || sp_type == POSITION) {
|
|
||||||
ENSURE_FINITE(x);
|
|
||||||
ENSURE_FINITE(y);
|
|
||||||
ENSURE_FINITE(z);
|
|
||||||
} else if (sp_type == NAVIGATE_GLOBAL) {
|
|
||||||
ENSURE_FINITE(lat);
|
ENSURE_FINITE(lat);
|
||||||
ENSURE_FINITE(lon);
|
ENSURE_FINITE(lon);
|
||||||
ENSURE_FINITE(z);
|
}
|
||||||
} else if (sp_type == VELOCITY) {
|
|
||||||
ENSURE_FINITE(vx);
|
if (isfinite(x) != isfinite(y)) {
|
||||||
ENSURE_FINITE(vy);
|
throw std::runtime_error("x and y can be set only together");
|
||||||
ENSURE_FINITE(vz);
|
}
|
||||||
} else if (sp_type == ATTITUDE) {
|
|
||||||
ENSURE_FINITE(pitch);
|
if (isfinite(yaw_rate)) {
|
||||||
ENSURE_FINITE(roll);
|
if (sp_type > RATES && setpoint_type == ATTITUDE) {
|
||||||
ENSURE_FINITE(thrust);
|
throw std::runtime_error("Yaw rate cannot be set in attitude mode.");
|
||||||
} else if (sp_type == RATES) {
|
}
|
||||||
ENSURE_FINITE(pitch_rate);
|
}
|
||||||
ENSURE_FINITE(roll_rate);
|
|
||||||
ENSURE_FINITE(thrust);
|
// set_altitude
|
||||||
|
if (sp_type == _ALTITUDE) {
|
||||||
|
if (setpoint_type == VELOCITY || setpoint_type == ATTITUDE || setpoint_type == RATES) {
|
||||||
|
throw std::runtime_error("Altitude cannot be set in velocity, attitude or rates mode.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
|
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
|
||||||
@@ -634,20 +775,13 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
|||||||
speed = default_speed;
|
speed = default_speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) {
|
|
||||||
if (yaw_rate != 0 && !std::isnan(yaw))
|
|
||||||
throw std::runtime_error("Yaw value should be NaN for setting yaw rate");
|
|
||||||
|
|
||||||
if (std::isnan(yaw_rate) && std::isnan(yaw))
|
|
||||||
throw std::runtime_error("Both yaw and yaw_rate cannot be NaN");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sp_type == NAVIGATE_GLOBAL) {
|
if (sp_type == NAVIGATE_GLOBAL) {
|
||||||
if (TIMEOUT(global_position, global_position_timeout))
|
if (TIMEOUT(global_position, global_position_timeout))
|
||||||
throw std::runtime_error("No global position");
|
throw std::runtime_error("No global position");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY || sp_type == ATTITUDE) {
|
// if any value need to be transformed to reference frame
|
||||||
|
if (isfinite(x) || isfinite(y) || isfinite(z) || isfinite(vx) || isfinite(vy) || isfinite(vz) || isfinite(yaw)) {
|
||||||
// make sure transform from frame_id to reference frame available
|
// make sure transform from frame_id to reference frame available
|
||||||
if (!waitTransform(reference_frame, frame_id, stamp, transform_timeout))
|
if (!waitTransform(reference_frame, frame_id, stamp, transform_timeout))
|
||||||
throw std::runtime_error("Can't transform from " + frame_id + " to " + reference_frame);
|
throw std::runtime_error("Can't transform from " + frame_id + " to " + reference_frame);
|
||||||
@@ -664,15 +798,27 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
|||||||
auto xy_in_req_frame = tf_buffer.transform(pose_local, frame_id);
|
auto xy_in_req_frame = tf_buffer.transform(pose_local, frame_id);
|
||||||
x = xy_in_req_frame.pose.position.x;
|
x = xy_in_req_frame.pose.position.x;
|
||||||
y = xy_in_req_frame.pose.position.y;
|
y = xy_in_req_frame.pose.position.y;
|
||||||
|
setpoint_lat = lat;
|
||||||
|
setpoint_lon = lon;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Everything fine - switch setpoint type
|
// Everything fine - switch setpoint type
|
||||||
setpoint_type = sp_type;
|
if (sp_type <= RATES) {
|
||||||
|
setpoint_type = sp_type;
|
||||||
|
}
|
||||||
|
|
||||||
if (sp_type != NAVIGATE && sp_type != NAVIGATE_GLOBAL) {
|
if (setpoint_type != NAVIGATE && setpoint_type != NAVIGATE_GLOBAL) {
|
||||||
nav_from_sp_flag = false;
|
nav_from_sp_flag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool to_auto_arm = auto_arm && (state.mode != "OFFBOARD" || !state.armed);
|
||||||
|
if (to_auto_arm || setpoint_type == VELOCITY || setpoint_type == ATTITUDE || setpoint_type == RATES) {
|
||||||
|
// invalidate position setpoint
|
||||||
|
setpoint_position.header.frame_id = "";
|
||||||
|
setpoint_altitude.header.frame_id = "";
|
||||||
|
yaw_frame_id = "";
|
||||||
|
}
|
||||||
|
|
||||||
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
|
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
|
||||||
// starting point
|
// starting point
|
||||||
if (nav_from_sp && nav_from_sp_flag) {
|
if (nav_from_sp && nav_from_sp_flag) {
|
||||||
@@ -681,89 +827,139 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
|||||||
} else {
|
} else {
|
||||||
nav_start = local_position;
|
nav_start = local_position;
|
||||||
}
|
}
|
||||||
nav_speed = speed;
|
|
||||||
|
if (!isnan(speed)) {
|
||||||
|
nav_speed = speed;
|
||||||
|
}
|
||||||
|
|
||||||
nav_from_sp_flag = true;
|
nav_from_sp_flag = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) {
|
// handle position
|
||||||
// if (std::isnan(yaw) && yaw_rate == 0) {
|
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
|
||||||
// // keep yaw unchanged
|
|
||||||
// // TODO: this is incorrect, because we need yaw in desired frame
|
|
||||||
// yaw = tf2::getYaw(local_position.pose.orientation);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (sp_type == POSITION || sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == VELOCITY || sp_type == ATTITUDE) {
|
PointStamped desired;
|
||||||
// destination point and/or attitude
|
desired.header.frame_id = frame_id;
|
||||||
PoseStamped ps;
|
desired.header.stamp = stamp;
|
||||||
ps.header.frame_id = frame_id;
|
desired.point.x = safe(x);
|
||||||
ps.header.stamp = stamp;
|
desired.point.y = safe(y);
|
||||||
ps.pose.position.x = x;
|
desired.point.z = safe(z);
|
||||||
ps.pose.position.y = y;
|
|
||||||
ps.pose.position.z = z;
|
|
||||||
ps.pose.orientation.w = 1.0; // Ensure quaternion is always valid
|
|
||||||
|
|
||||||
if (sp_type == ATTITUDE) {
|
// transform to reference frame
|
||||||
ps.pose.position.x = 0;
|
desired = tf_buffer.transform(desired, reference_frame);
|
||||||
ps.pose.position.y = 0;
|
|
||||||
ps.pose.position.z = 0;
|
// set horizontal position
|
||||||
ps.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(roll, pitch, yaw);
|
if (isfinite(x) && isfinite(y)) {
|
||||||
} else if (std::isnan(yaw)) {
|
setpoint_position = desired;
|
||||||
setpoint_yaw_type = YAW_RATE;
|
} else if (setpoint_position.header.frame_id.empty()) {
|
||||||
setpoint_yaw_rate = yaw_rate;
|
// TODO: use transform for current stamp
|
||||||
} else if (std::isinf(yaw) && yaw > 0) {
|
setpoint_position.header = local_position.header;
|
||||||
// yaw towards
|
setpoint_position.point = local_position.pose.position;
|
||||||
setpoint_yaw_type = TOWARDS;
|
|
||||||
yaw = 0;
|
|
||||||
setpoint_yaw_rate = 0;
|
|
||||||
} else {
|
|
||||||
setpoint_yaw_type = YAW;
|
|
||||||
setpoint_yaw_rate = 0;
|
|
||||||
ps.pose.orientation = tf::createQuaternionMsgFromYaw(yaw);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tf_buffer.transform(ps, setpoint_position, reference_frame);
|
// set altitude
|
||||||
|
if (isfinite(z)) {
|
||||||
|
setpoint_altitude = desired;
|
||||||
|
} else if (setpoint_altitude.header.frame_id.empty()) {
|
||||||
|
setpoint_altitude.header = local_position.header;
|
||||||
|
setpoint_altitude.point = local_position.pose.position;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle velocity
|
||||||
if (sp_type == VELOCITY) {
|
if (sp_type == VELOCITY) {
|
||||||
Vector3Stamped vel;
|
// TODO: allow setting different modes by altitude and xy
|
||||||
vel.header.frame_id = frame_id;
|
Vector3Stamped desired;
|
||||||
vel.header.stamp = stamp;
|
desired.header.frame_id = frame_id;
|
||||||
vel.vector.x = vx;
|
desired.header.stamp = stamp;
|
||||||
vel.vector.y = vy;
|
desired.vector.x = safe(vx);
|
||||||
vel.vector.z = vz;
|
desired.vector.y = safe(vy);
|
||||||
tf_buffer.transform(vel, setpoint_velocity, reference_frame);
|
desired.vector.z = safe(vz);
|
||||||
|
|
||||||
|
// transform to reference frame
|
||||||
|
desired = tf_buffer.transform(desired, reference_frame);
|
||||||
|
setpoint_velocity.header = desired.header;
|
||||||
|
|
||||||
|
// set horizontal velocity
|
||||||
|
if (isfinite(vx) && isfinite(vy)) {
|
||||||
|
setpoint_velocity.vector.x = desired.vector.x;
|
||||||
|
setpoint_velocity.vector.y = desired.vector.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set vertical velocity
|
||||||
|
if (isfinite(vz)) {
|
||||||
|
setpoint_velocity.vector.z = desired.vector.z;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sp_type == ATTITUDE || sp_type == RATES) {
|
// handle yaw
|
||||||
thrust_msg.thrust = thrust;
|
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY || sp_type == ATTITUDE || sp_type == _YAW) {
|
||||||
|
if (isfinite(yaw)) {
|
||||||
|
setpoint_yaw_type = YAW;
|
||||||
|
QuaternionStamped desired;
|
||||||
|
desired.header.frame_id = frame_id;
|
||||||
|
desired.header.stamp = stamp;
|
||||||
|
desired.quaternion = tf::createQuaternionMsgFromYaw(yaw);
|
||||||
|
|
||||||
|
// transform to reference frame
|
||||||
|
desired = tf_buffer.transform(desired, reference_frame);
|
||||||
|
setpoint_yaw = tf2::getYaw(desired.quaternion);
|
||||||
|
yaw_frame_id = reference_frame;
|
||||||
|
|
||||||
|
} else if (isinf(yaw) && yaw > 0) {
|
||||||
|
// yaw towards
|
||||||
|
setpoint_yaw_type = TOWARDS;
|
||||||
|
|
||||||
|
} else if (yaw_frame_id.empty() || sp_type == _YAW) {
|
||||||
|
// yaw is nan and not set previously OR set_yaw(yaw=nan) was called
|
||||||
|
setpoint_yaw_type = YAW;
|
||||||
|
setpoint_yaw = tf2::getYaw(local_position.pose.orientation); // set yaw to current yaw
|
||||||
|
yaw_frame_id = local_position.header.frame_id;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sp_type == RATES) {
|
// handle roll
|
||||||
rates_msg.twist.angular.x = roll_rate;
|
if (isfinite(roll)) {
|
||||||
rates_msg.twist.angular.y = pitch_rate;
|
setpoint_roll = roll;
|
||||||
rates_msg.twist.angular.z = yaw_rate;
|
}
|
||||||
|
|
||||||
|
// handle pitch
|
||||||
|
if (isfinite(pitch)) {
|
||||||
|
setpoint_pitch = pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle yaw rate
|
||||||
|
if (isfinite(yaw_rate)) {
|
||||||
|
setpoint_yaw_type = YAW_RATE;
|
||||||
|
setpoint_rates.z = yaw_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle pitch rate
|
||||||
|
if (isfinite(roll_rate)) {
|
||||||
|
setpoint_rates.x = roll_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle roll rate
|
||||||
|
if (isfinite(pitch_rate)) {
|
||||||
|
setpoint_rates.y = pitch_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle thrust
|
||||||
|
if (isfinite(thrust)) {
|
||||||
|
setpoint_thrust = thrust;
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_armed = auto_arm;
|
wait_armed = auto_arm;
|
||||||
|
|
||||||
publish_setpoint:
|
|
||||||
publish(stamp); // calculate initial transformed messages first
|
publish(stamp); // calculate initial transformed messages first
|
||||||
setpoint_timer.start();
|
setpoint_timer.start();
|
||||||
|
|
||||||
// publish target frame
|
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
|
||||||
if (!target.child_frame_id.empty()) {
|
publishTarget(stamp, true);
|
||||||
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
|
|
||||||
target.header.frame_id = setpoint_position.header.frame_id;
|
|
||||||
target.header.stamp = stamp;
|
|
||||||
target.transform.translation.x = setpoint_position.pose.position.x;
|
|
||||||
target.transform.translation.y = setpoint_position.pose.position.y;
|
|
||||||
target.transform.translation.z = setpoint_position.pose.position.z;
|
|
||||||
target.transform.rotation = setpoint_position.pose.orientation;
|
|
||||||
static_transform_broadcaster->sendTransform(target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
publishState();
|
||||||
|
|
||||||
if (auto_arm) {
|
if (auto_arm) {
|
||||||
offboardAndArm();
|
offboardAndArm();
|
||||||
wait_armed = false;
|
wait_armed = false;
|
||||||
@@ -788,27 +984,39 @@ publish_setpoint:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool navigate(Navigate::Request& req, Navigate::Response& res) {
|
bool navigate(Navigate::Request& req, Navigate::Response& res) {
|
||||||
return serve(NAVIGATE, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
|
return serve(NAVIGATE, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool navigateGlobal(NavigateGlobal::Request& req, NavigateGlobal::Response& res) {
|
bool navigateGlobal(NavigateGlobal::Request& req, NavigateGlobal::Response& res) {
|
||||||
return serve(NAVIGATE_GLOBAL, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, req.lat, req.lon, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
|
return serve(NAVIGATE_GLOBAL, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, req.lat, req.lon, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setAltitude(SetAltitude::Request& req, SetAltitude::Response& res) {
|
||||||
|
return serve(_ALTITUDE, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, false, res.success, res.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setYaw(SetYaw::Request& req, SetYaw::Response& res) {
|
||||||
|
return serve(_YAW, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, false, res.success, res.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setYawRate(SetYawRate::Request& req, SetYawRate::Response& res) {
|
||||||
|
return serve(_YAW_RATE, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, "", false, res.success, res.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setPosition(SetPosition::Request& req, SetPosition::Response& res) {
|
bool setPosition(SetPosition::Request& req, SetPosition::Response& res) {
|
||||||
return serve(POSITION, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
return serve(POSITION, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setVelocity(SetVelocity::Request& req, SetVelocity::Response& res) {
|
bool setVelocity(SetVelocity::Request& req, SetVelocity::Response& res) {
|
||||||
return serve(VELOCITY, NAN, NAN, NAN, req.vx, req.vy, req.vz, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
return serve(VELOCITY, NAN, NAN, NAN, req.vx, req.vy, req.vz, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setAttitude(SetAttitude::Request& req, SetAttitude::Response& res) {
|
bool setAttitude(SetAttitude::Request& req, SetAttitude::Response& res) {
|
||||||
return serve(ATTITUDE, NAN, NAN, NAN, NAN, NAN, NAN, req.pitch, req.roll, req.yaw, NAN, NAN, NAN, NAN, NAN, req.thrust, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
return serve(ATTITUDE, NAN, NAN, NAN, NAN, NAN, NAN, req.roll, req.pitch, req.yaw, NAN, NAN, NAN, NAN, NAN, req.thrust, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool setRates(SetRates::Request& req, SetRates::Response& res) {
|
bool setRates(SetRates::Request& req, SetRates::Response& res) {
|
||||||
return serve(RATES, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.pitch_rate, req.roll_rate, req.yaw_rate, NAN, NAN, req.thrust, NAN, "", req.auto_arm, res.success, res.message);
|
return serve(RATES, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.roll_rate, req.pitch_rate, req.yaw_rate, NAN, NAN, req.thrust, NAN, "", req.auto_arm, res.success, res.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||||
@@ -840,9 +1048,7 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
|||||||
auto start = ros::Time::now();
|
auto start = ros::Time::now();
|
||||||
while (ros::ok()) {
|
while (ros::ok()) {
|
||||||
if (state.mode == "AUTO.LAND") {
|
if (state.mode == "AUTO.LAND") {
|
||||||
res.success = true;
|
break;
|
||||||
busy = false;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
if (ros::Time::now() - start > land_timeout)
|
if (ros::Time::now() - start > land_timeout)
|
||||||
throw std::runtime_error("Land request timed out");
|
throw std::runtime_error("Land request timed out");
|
||||||
@@ -851,6 +1057,18 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
|||||||
r.sleep();
|
r.sleep();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stop setpoints and invalidate position setpoint
|
||||||
|
setpoint_timer.stop();
|
||||||
|
setpoint_type = NONE;
|
||||||
|
setpoint_position.header.frame_id = "";
|
||||||
|
setpoint_altitude.header.frame_id = "";
|
||||||
|
yaw_frame_id = "";
|
||||||
|
publishState();
|
||||||
|
|
||||||
|
res.success = true;
|
||||||
|
busy = false;
|
||||||
|
return true;
|
||||||
|
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
res.message = e.what();
|
res.message = e.what();
|
||||||
ROS_INFO("%s", e.what());
|
ROS_INFO("%s", e.what());
|
||||||
@@ -860,6 +1078,18 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool release(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||||
|
{
|
||||||
|
setpoint_timer.stop();
|
||||||
|
setpoint_type = NONE;
|
||||||
|
setpoint_position.header.frame_id = "";
|
||||||
|
setpoint_altitude.header.frame_id = "";
|
||||||
|
yaw_frame_id = "";
|
||||||
|
publishState();
|
||||||
|
res.success = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
ros::init(argc, argv, "simple_offboard");
|
ros::init(argc, argv, "simple_offboard");
|
||||||
@@ -881,6 +1111,8 @@ int main(int argc, char **argv)
|
|||||||
nh_priv.param("check_kill_switch", check_kill_switch, true);
|
nh_priv.param("check_kill_switch", check_kill_switch, true);
|
||||||
nh_priv.param("default_speed", default_speed, 0.5f);
|
nh_priv.param("default_speed", default_speed, 0.5f);
|
||||||
nh_priv.param<string>("body_frame", body.child_frame_id, "body");
|
nh_priv.param<string>("body_frame", body.child_frame_id, "body");
|
||||||
|
nh_priv.param<string>("terrain_frame", terrain.child_frame_id, "terrain");
|
||||||
|
nh_priv.param<string>("terrain_frame_mode", terrain_frame_mode, "altitude");
|
||||||
nh_priv.getParam("reference_frames", reference_frames);
|
nh_priv.getParam("reference_frames", reference_frames);
|
||||||
|
|
||||||
// Default reference frames
|
// Default reference frames
|
||||||
@@ -916,6 +1148,20 @@ int main(int argc, char **argv)
|
|||||||
auto manual_control_sub = nh.subscribe(mavros + "/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>);
|
auto manual_control_sub = nh.subscribe(mavros + "/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>);
|
||||||
auto local_position_sub = nh.subscribe(mavros + "/local_position/pose", 1, &handleLocalPosition);
|
auto local_position_sub = nh.subscribe(mavros + "/local_position/pose", 1, &handleLocalPosition);
|
||||||
|
|
||||||
|
ros::Subscriber altitude_sub;
|
||||||
|
if (!body.child_frame_id.empty() && !terrain.child_frame_id.empty()) {
|
||||||
|
terrain.header.frame_id = local_frame;
|
||||||
|
if (terrain_frame_mode == "altitude") {
|
||||||
|
altitude_sub = nh.subscribe(mavros + "/altitude", 1, &handleAltitude);
|
||||||
|
} else if (terrain_frame_mode == "range") {
|
||||||
|
string range_topic = nh_priv.param("range_topic", string("rangefinder/range"));
|
||||||
|
altitude_sub = nh.subscribe(range_topic, 1, &handleRange);
|
||||||
|
} else {
|
||||||
|
ROS_FATAL("Unknown terrain_frame_mode: %s, valid values: altitude, range", terrain_frame_mode.c_str());
|
||||||
|
ros::shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Setpoint publishers
|
// Setpoint publishers
|
||||||
position_pub = nh.advertise<PoseStamped>(mavros + "/setpoint_position/local", 1);
|
position_pub = nh.advertise<PoseStamped>(mavros + "/setpoint_position/local", 1);
|
||||||
position_raw_pub = nh.advertise<PositionTarget>(mavros + "/setpoint_raw/local", 1);
|
position_raw_pub = nh.advertise<PositionTarget>(mavros + "/setpoint_raw/local", 1);
|
||||||
@@ -924,15 +1170,22 @@ int main(int argc, char **argv)
|
|||||||
rates_pub = nh.advertise<TwistStamped>(mavros + "/setpoint_attitude/cmd_vel", 1);
|
rates_pub = nh.advertise<TwistStamped>(mavros + "/setpoint_attitude/cmd_vel", 1);
|
||||||
thrust_pub = nh.advertise<Thrust>(mavros + "/setpoint_attitude/thrust", 1);
|
thrust_pub = nh.advertise<Thrust>(mavros + "/setpoint_attitude/thrust", 1);
|
||||||
|
|
||||||
|
// State publisher
|
||||||
|
state_pub = nh_priv.advertise<clover::State>("state", 1, true);
|
||||||
|
|
||||||
// Service servers
|
// Service servers
|
||||||
auto gt_serv = nh.advertiseService("get_telemetry", &getTelemetry);
|
auto gt_serv = nh.advertiseService("get_telemetry", &getTelemetry);
|
||||||
auto na_serv = nh.advertiseService("navigate", &navigate);
|
auto na_serv = nh.advertiseService("navigate", &navigate);
|
||||||
auto ng_serv = nh.advertiseService("navigate_global", &navigateGlobal);
|
auto ng_serv = nh.advertiseService("navigate_global", &navigateGlobal);
|
||||||
|
auto sl_serv = nh.advertiseService("set_altitude", &setAltitude);
|
||||||
|
auto ya_serv = nh.advertiseService("set_yaw", &setYaw);
|
||||||
|
auto yr_serv = nh.advertiseService("set_yaw_rate", &setYawRate);
|
||||||
auto sp_serv = nh.advertiseService("set_position", &setPosition);
|
auto sp_serv = nh.advertiseService("set_position", &setPosition);
|
||||||
auto sv_serv = nh.advertiseService("set_velocity", &setVelocity);
|
auto sv_serv = nh.advertiseService("set_velocity", &setVelocity);
|
||||||
auto sa_serv = nh.advertiseService("set_attitude", &setAttitude);
|
auto sa_serv = nh.advertiseService("set_attitude", &setAttitude);
|
||||||
auto sr_serv = nh.advertiseService("set_rates", &setRates);
|
auto sr_serv = nh.advertiseService("set_rates", &setRates);
|
||||||
auto ld_serv = nh.advertiseService("land", &land);
|
auto ld_serv = nh.advertiseService("land", &land);
|
||||||
|
auto rl_serv = nh_priv.advertiseService("release", &release);
|
||||||
|
|
||||||
// Setpoint timer
|
// Setpoint timer
|
||||||
setpoint_timer = nh.createTimer(ros::Duration(1 / nh_priv.param("setpoint_rate", 30.0)), &publishSetpoint, false, false);
|
setpoint_timer = nh.createTimer(ros::Duration(1 / nh_priv.param("setpoint_rate", 30.0)), &publishSetpoint, false, false);
|
||||||
@@ -940,7 +1193,7 @@ int main(int argc, char **argv)
|
|||||||
position_msg.header.frame_id = local_frame;
|
position_msg.header.frame_id = local_frame;
|
||||||
position_raw_msg.header.frame_id = local_frame;
|
position_raw_msg.header.frame_id = local_frame;
|
||||||
position_raw_msg.coordinate_frame = PositionTarget::FRAME_LOCAL_NED;
|
position_raw_msg.coordinate_frame = PositionTarget::FRAME_LOCAL_NED;
|
||||||
rates_msg.header.frame_id = fcu_frame;
|
//rates_msg.header.frame_id = fcu_frame;
|
||||||
|
|
||||||
ROS_INFO("ready");
|
ROS_INFO("ready");
|
||||||
ros::spin();
|
ros::spin();
|
||||||
|
|||||||
@@ -11,12 +11,14 @@
|
|||||||
|
|
||||||
#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>
|
||||||
@@ -25,7 +27,7 @@
|
|||||||
using std::string;
|
using std::string;
|
||||||
using namespace geometry_msgs;
|
using namespace geometry_msgs;
|
||||||
|
|
||||||
bool reset_flag = false;
|
bool reset_flag = true; // offset should be reset on the start
|
||||||
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;
|
||||||
@@ -66,6 +68,13 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -88,10 +97,29 @@ 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
|
||||||
offset = tf_buffer.lookupTransform(local_frame_id, frame_id,
|
if (!frame_id.empty()) {
|
||||||
msg->header.stamp, ros::Duration(0.02));
|
// calculate from TF
|
||||||
// offset.header.frame_id = vpe.header.frame_id;
|
offset = tf_buffer.lookupTransform(local_frame_id, frame_id,
|
||||||
offset.child_frame_id = offset_frame_id;
|
msg->header.stamp, ros::Duration(0.02));
|
||||||
|
// 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");
|
||||||
@@ -122,8 +150,9 @@ 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, "");
|
nh_priv.param<string>("frame_id", frame_id, ""); // name for used visual pose frame
|
||||||
nh_priv.param<string>("offset_frame_id", offset_frame_id, "");
|
nh_priv.param<string>("offset_frame_id", offset_frame_id, ""); // name for published offset frame
|
||||||
|
|
||||||
nh.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
|
nh.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
|
||||||
nh.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link");
|
nh.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));
|
||||||
|
|||||||
4
clover/src/www
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
export ROSWWW_DEFAULT=clover
|
||||||
|
rosrun roswww_static update
|
||||||
@@ -13,11 +13,11 @@ float32 alt
|
|||||||
float32 vx
|
float32 vx
|
||||||
float32 vy
|
float32 vy
|
||||||
float32 vz
|
float32 vz
|
||||||
float32 pitch
|
|
||||||
float32 roll
|
float32 roll
|
||||||
|
float32 pitch
|
||||||
float32 yaw
|
float32 yaw
|
||||||
float32 pitch_rate
|
|
||||||
float32 roll_rate
|
float32 roll_rate
|
||||||
|
float32 pitch_rate
|
||||||
float32 yaw_rate
|
float32 yaw_rate
|
||||||
float32 voltage
|
float32 voltage
|
||||||
float32 cell_voltage
|
float32 cell_voltage
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ float32 x
|
|||||||
float32 y
|
float32 y
|
||||||
float32 z
|
float32 z
|
||||||
float32 yaw
|
float32 yaw
|
||||||
float32 yaw_rate
|
|
||||||
float32 speed
|
float32 speed
|
||||||
string frame_id
|
string frame_id
|
||||||
bool auto_arm
|
bool auto_arm
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ float64 lat
|
|||||||
float64 lon
|
float64 lon
|
||||||
float32 z
|
float32 z
|
||||||
float32 yaw
|
float32 yaw
|
||||||
float32 yaw_rate
|
|
||||||
float32 speed
|
float32 speed
|
||||||
string frame_id
|
string frame_id
|
||||||
bool auto_arm
|
bool auto_arm
|
||||||
|
|||||||
5
clover/srv/SetAltitude.srv
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
float32 z
|
||||||
|
string frame_id
|
||||||
|
---
|
||||||
|
bool success
|
||||||
|
string message
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
float32 pitch
|
|
||||||
float32 roll
|
float32 roll
|
||||||
|
float32 pitch
|
||||||
float32 yaw
|
float32 yaw
|
||||||
float32 thrust
|
float32 thrust
|
||||||
string frame_id
|
string frame_id
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ float32 x
|
|||||||
float32 y
|
float32 y
|
||||||
float32 z
|
float32 z
|
||||||
float32 yaw
|
float32 yaw
|
||||||
float32 yaw_rate
|
|
||||||
string frame_id
|
string frame_id
|
||||||
bool auto_arm
|
bool auto_arm
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
float32 pitch_rate
|
|
||||||
float32 roll_rate
|
float32 roll_rate
|
||||||
|
float32 pitch_rate
|
||||||
float32 yaw_rate
|
float32 yaw_rate
|
||||||
float32 thrust
|
float32 thrust
|
||||||
bool auto_arm
|
bool auto_arm
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ float32 vx
|
|||||||
float32 vy
|
float32 vy
|
||||||
float32 vz
|
float32 vz
|
||||||
float32 yaw
|
float32 yaw
|
||||||
float32 yaw_rate
|
|
||||||
string frame_id
|
string frame_id
|
||||||
bool auto_arm
|
bool auto_arm
|
||||||
---
|
---
|
||||||
|
|||||||
5
clover/srv/SetYaw.srv
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
float32 yaw
|
||||||
|
string frame_id
|
||||||
|
---
|
||||||
|
bool success
|
||||||
|
string message
|
||||||
4
clover/srv/SetYawRate.srv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
float32 yaw_rate
|
||||||
|
---
|
||||||
|
bool success
|
||||||
|
string message
|
||||||
@@ -3,6 +3,7 @@ import rospy
|
|||||||
import pytest
|
import pytest
|
||||||
from mavros_msgs.msg import State
|
from mavros_msgs.msg import State
|
||||||
from clover import srv
|
from clover import srv
|
||||||
|
import time
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def node():
|
def node():
|
||||||
@@ -24,6 +25,7 @@ def test_simple_offboard_services_available():
|
|||||||
rospy.wait_for_service('set_attitude', timeout=5)
|
rospy.wait_for_service('set_attitude', timeout=5)
|
||||||
rospy.wait_for_service('set_rates', timeout=5)
|
rospy.wait_for_service('set_rates', timeout=5)
|
||||||
rospy.wait_for_service('land', timeout=5)
|
rospy.wait_for_service('land', timeout=5)
|
||||||
|
rospy.wait_for_service('simple_offboard/release', timeout=5)
|
||||||
|
|
||||||
def test_web_video_server(node):
|
def test_web_video_server(node):
|
||||||
try:
|
try:
|
||||||
@@ -59,3 +61,18 @@ def test_blocks(node):
|
|||||||
|
|
||||||
t.join()
|
t.join()
|
||||||
assert wait_print.result == 'test'
|
assert wait_print.result == 'test'
|
||||||
|
|
||||||
|
def test_long_callback():
|
||||||
|
from clover import long_callback
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
# very basic test for long_callback
|
||||||
|
@long_callback
|
||||||
|
def cb(i):
|
||||||
|
cb.counter += i
|
||||||
|
cb.counter = 0
|
||||||
|
cb(2)
|
||||||
|
sleep(0.1)
|
||||||
|
cb(3)
|
||||||
|
sleep(1)
|
||||||
|
assert cb.counter == 5
|
||||||
|
|||||||
@@ -37,6 +37,19 @@
|
|||||||
|
|
||||||
<node name="clover_blocks" pkg="clover_blocks" type="clover_blocks" output="screen" required="true"/>
|
<node name="clover_blocks" pkg="clover_blocks" type="clover_blocks" output="screen" required="true"/>
|
||||||
|
|
||||||
|
<node pkg="topic_tools" name="main_camera_throttle" type="throttle" ns="main_camera"
|
||||||
|
args="messages image_raw 5.0 image_raw_throttled" required="true"/>
|
||||||
|
|
||||||
|
<node pkg="nodelet" type="nodelet" name="main_camera_nodelet_manager" args="manager" output="screen" required="true">
|
||||||
|
<param name="num_worker_threads" value="2"/>
|
||||||
|
</node>
|
||||||
|
|
||||||
|
<node pkg="nodelet" type="nodelet" name="rectify" args="load image_proc/rectify main_camera_nodelet_manager" required="true">
|
||||||
|
<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>
|
||||||
|
|
||||||
<param name="test_module" value="$(find clover)/test/basic.py"/>
|
<param name="test_module" value="$(find clover)/test/basic.py"/>
|
||||||
<test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner"/>
|
<test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner"/>
|
||||||
</launch>
|
</launch>
|
||||||
|
|||||||
437
clover/test/offboard.py
Executable file
@@ -0,0 +1,437 @@
|
|||||||
|
import rospy
|
||||||
|
import pytest
|
||||||
|
from pytest import approx
|
||||||
|
import threading
|
||||||
|
import mavros_msgs.msg
|
||||||
|
from mavros_msgs.srv import SetMode
|
||||||
|
from geometry_msgs.msg import PoseStamped
|
||||||
|
from clover import srv
|
||||||
|
from clover.msg import State
|
||||||
|
from std_srvs.srv import Trigger
|
||||||
|
from math import nan, inf
|
||||||
|
import tf2_ros
|
||||||
|
import tf2_geometry_msgs
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def node():
|
||||||
|
return rospy.init_node('offboard_test', anonymous=True)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def tf_buffer():
|
||||||
|
buf = tf2_ros.Buffer()
|
||||||
|
tf2_ros.TransformListener(buf)
|
||||||
|
return buf
|
||||||
|
|
||||||
|
def get_state():
|
||||||
|
return rospy.wait_for_message('/simple_offboard/state', State, timeout=1)
|
||||||
|
|
||||||
|
def get_navigate_target(tf_buffer):
|
||||||
|
target = tf_buffer.lookup_transform('map', 'navigate_target', rospy.get_rostime(), rospy.Duration(1))
|
||||||
|
assert target.child_frame_id == 'navigate_target'
|
||||||
|
return target
|
||||||
|
|
||||||
|
def test_offboard(node, tf_buffer):
|
||||||
|
navigate = rospy.ServiceProxy('navigate', srv.Navigate)
|
||||||
|
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
|
||||||
|
set_altitude = rospy.ServiceProxy('set_altitude', srv.SetAltitude)
|
||||||
|
set_yaw = rospy.ServiceProxy('set_yaw', srv.SetYaw)
|
||||||
|
set_yaw_rate = rospy.ServiceProxy('set_yaw_rate', srv.SetYawRate)
|
||||||
|
set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)
|
||||||
|
set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
|
||||||
|
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
|
||||||
|
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||||
|
land = rospy.ServiceProxy('land', Trigger)
|
||||||
|
|
||||||
|
res = navigate()
|
||||||
|
assert res.success == False
|
||||||
|
assert res.message.startswith('State timeout')
|
||||||
|
|
||||||
|
telem = get_telemetry()
|
||||||
|
assert telem.connected == False
|
||||||
|
|
||||||
|
# mocked state publisher
|
||||||
|
state_pub = rospy.Publisher('/mavros/state', mavros_msgs.msg.State, latch=True, queue_size=1)
|
||||||
|
state_msg = mavros_msgs.msg.State(mode='OFFBOARD', armed=True)
|
||||||
|
|
||||||
|
def publish_state():
|
||||||
|
r = rospy.Rate(2)
|
||||||
|
while not rospy.is_shutdown():
|
||||||
|
state_msg.header.stamp = rospy.Time.now()
|
||||||
|
state_pub.publish(state_msg)
|
||||||
|
r.sleep()
|
||||||
|
|
||||||
|
# start publishing state
|
||||||
|
threading.Thread(target=publish_state, daemon=True).start()
|
||||||
|
rospy.sleep(0.5)
|
||||||
|
|
||||||
|
# set_mode service mock
|
||||||
|
def set_mode(req):
|
||||||
|
state_msg.mode = req.custom_mode # set mocked mode to requested
|
||||||
|
return True,
|
||||||
|
|
||||||
|
rospy.Service('/mavros/set_mode', SetMode, set_mode)
|
||||||
|
|
||||||
|
telem = get_telemetry()
|
||||||
|
assert telem.connected == False
|
||||||
|
|
||||||
|
res = navigate()
|
||||||
|
assert res.success == False
|
||||||
|
assert res.message.startswith('No connection to FCU')
|
||||||
|
|
||||||
|
state_msg.connected = True
|
||||||
|
rospy.sleep(1)
|
||||||
|
|
||||||
|
telem = get_telemetry()
|
||||||
|
assert telem.connected == True
|
||||||
|
|
||||||
|
res = navigate()
|
||||||
|
assert res.success == False
|
||||||
|
assert res.message.startswith('No local position')
|
||||||
|
|
||||||
|
local_position_pub = rospy.Publisher('/mavros/local_position/pose', PoseStamped, latch=True, queue_size=1)
|
||||||
|
local_position_msg = PoseStamped()
|
||||||
|
local_position_msg.header.frame_id = 'map'
|
||||||
|
local_position_msg.pose.position.x = 1
|
||||||
|
local_position_msg.pose.position.y = 2
|
||||||
|
local_position_msg.pose.position.z = 3
|
||||||
|
local_position_msg.pose.orientation.w = 1
|
||||||
|
|
||||||
|
def publish_local_position():
|
||||||
|
r = rospy.Rate(30)
|
||||||
|
while not rospy.is_shutdown():
|
||||||
|
local_position_msg.header.stamp = rospy.Time.now()
|
||||||
|
local_position_pub.publish(local_position_msg)
|
||||||
|
r.sleep()
|
||||||
|
|
||||||
|
# start publishing local position
|
||||||
|
threading.Thread(target=publish_local_position, daemon=True).start()
|
||||||
|
rospy.sleep(0.5)
|
||||||
|
|
||||||
|
# check body frame
|
||||||
|
body = tf_buffer.lookup_transform('map', 'body', rospy.get_rostime(), rospy.Duration(1))
|
||||||
|
assert body.child_frame_id == 'body'
|
||||||
|
assert body.transform.translation.x == approx(1)
|
||||||
|
assert body.transform.translation.y == approx(2)
|
||||||
|
assert body.transform.translation.z == approx(3)
|
||||||
|
|
||||||
|
res = navigate(x=3, y=2, z=1, frame_id='map')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 3
|
||||||
|
assert state.y == 2
|
||||||
|
assert state.z == 1
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'map'
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
target = get_navigate_target(tf_buffer)
|
||||||
|
assert target.header.frame_id == 'map'
|
||||||
|
assert target.transform.translation.x == approx(3)
|
||||||
|
assert target.transform.translation.y == approx(2)
|
||||||
|
assert target.transform.translation.z == approx(1)
|
||||||
|
assert target.transform.rotation.x == 0
|
||||||
|
assert target.transform.rotation.y == 0
|
||||||
|
assert target.transform.rotation.z == 0
|
||||||
|
assert target.transform.rotation.w == 1
|
||||||
|
|
||||||
|
# try to set only the y
|
||||||
|
res = navigate(x=nan, y=1, z=nan)
|
||||||
|
assert res.success == False
|
||||||
|
assert res.message.startswith('x and y can be set only together')
|
||||||
|
|
||||||
|
# set z in body frame
|
||||||
|
res = navigate(x=nan, y=nan, z=1, frame_id='body')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 3
|
||||||
|
assert state.y == 2
|
||||||
|
assert state.z == 4
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'map'
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
|
||||||
|
# set xy in test frame
|
||||||
|
res = navigate(x=1, y=2, z=nan, frame_id='test')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 1
|
||||||
|
assert state.y == 2
|
||||||
|
assert state.z == 4
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'test'
|
||||||
|
assert state.z_frame_id == 'map'
|
||||||
|
assert state.yaw_frame_id == 'test'
|
||||||
|
|
||||||
|
# auto_arm should not invalidate the setpoint if not effective
|
||||||
|
res = navigate(x=nan, y=nan, z=1, frame_id='map', auto_arm=True)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 1
|
||||||
|
assert state.y == 2
|
||||||
|
assert state.z == 1
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'test'
|
||||||
|
assert state.z_frame_id == 'map'
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
|
||||||
|
# auto_arm should invalidate the setpoint if effective
|
||||||
|
state_msg.mode = 'STABILIZED' # pretend we are not in OFFBOARD mode
|
||||||
|
rospy.sleep(1)
|
||||||
|
res = navigate(x=nan, y=nan, z=1, frame_id='map', auto_arm=True)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 1
|
||||||
|
assert state.y == 2
|
||||||
|
assert state.z == 1
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'map'
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
state_msg.mode = 'OFFBOARD'
|
||||||
|
rospy.sleep(1)
|
||||||
|
|
||||||
|
# set_attitude should invalidate the setpoint
|
||||||
|
res = set_attitude()
|
||||||
|
assert res.success == True
|
||||||
|
|
||||||
|
res = navigate(x=5, y=6, z=nan, yaw=nan, frame_id='map')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 5
|
||||||
|
assert state.y == 6
|
||||||
|
assert state.z == 3
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'map'
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
|
||||||
|
# test set_altitude
|
||||||
|
res = set_altitude(z=7, frame_id='test')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 5
|
||||||
|
assert state.y == 6
|
||||||
|
assert state.z == 7
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'test'
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
|
||||||
|
# test set_yaw
|
||||||
|
res = set_yaw(yaw=0.5, frame_id='test2')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 5
|
||||||
|
assert state.y == 6
|
||||||
|
assert state.z == 7
|
||||||
|
assert state.yaw == 0.5
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'test'
|
||||||
|
assert state.yaw_frame_id == 'test2'
|
||||||
|
|
||||||
|
# test set_yaw_rate
|
||||||
|
res = set_yaw_rate(yaw_rate=2)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||||
|
assert state.x == 5
|
||||||
|
assert state.y == 6
|
||||||
|
assert state.z == 7
|
||||||
|
assert state.yaw_rate == 2
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'test'
|
||||||
|
|
||||||
|
# navigate(yaw=nan) should keep yaw rate mode
|
||||||
|
res = navigate(x=nan, y=nan, z=nan, yaw=nan)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||||
|
assert state.x == 5
|
||||||
|
assert state.y == 6
|
||||||
|
assert state.z == 7
|
||||||
|
assert state.yaw_rate == 2
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'test'
|
||||||
|
|
||||||
|
# set_yaw(nan) should change back to yaw mode
|
||||||
|
res = set_yaw(yaw=nan)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_NAVIGATE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
|
||||||
|
# test set_position
|
||||||
|
res = set_position(x=nan, y=nan, z=13, yaw=nan, frame_id='test2')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_POSITION
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 5
|
||||||
|
assert state.y == 6
|
||||||
|
assert state.z == 13
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'test2'
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
|
||||||
|
# set_altitude should not change the mode
|
||||||
|
res = set_altitude(z=3, frame_id='test')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_POSITION
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 5
|
||||||
|
assert state.y == 6
|
||||||
|
assert state.z == 3
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'test'
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
|
||||||
|
# set_yaw should not change the main mode
|
||||||
|
res = set_yaw(yaw=1, frame_id='test2')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_POSITION
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.x == 5
|
||||||
|
assert state.y == 6
|
||||||
|
assert state.z == 3
|
||||||
|
assert state.yaw == 1
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'test'
|
||||||
|
assert state.yaw_frame_id == 'test2'
|
||||||
|
|
||||||
|
# test set_velocity
|
||||||
|
res = set_velocity(vx=1, frame_id='body')
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_VELOCITY
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.vx == 1
|
||||||
|
assert state.vy == 0
|
||||||
|
assert state.vz == 0
|
||||||
|
assert state.yaw == 0
|
||||||
|
assert state.xy_frame_id == 'map'
|
||||||
|
assert state.z_frame_id == 'map'
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
|
||||||
|
# set_altitude should not work in velocity mode
|
||||||
|
res = set_altitude(z=3, frame_id='test')
|
||||||
|
assert res.success == False
|
||||||
|
assert res.message.startswith('Altitude cannot be set in')
|
||||||
|
|
||||||
|
# test set_attitude
|
||||||
|
res = set_attitude(roll=0.1, pitch=0.2, yaw=0.3, thrust=0.5)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_ATTITUDE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.roll == approx(0.1)
|
||||||
|
assert state.pitch == approx(0.2)
|
||||||
|
assert state.yaw == approx(0.3)
|
||||||
|
assert state.thrust == approx(0.5)
|
||||||
|
assert state.yaw_frame_id == 'map'
|
||||||
|
msg = rospy.wait_for_message('/mavros/setpoint_attitude/attitude', PoseStamped, timeout=3)
|
||||||
|
# Tait-Bryan ZYX angle (rzyx) converted to quaternion
|
||||||
|
assert msg.pose.orientation.x == approx(0.0342708)
|
||||||
|
assert msg.pose.orientation.y == approx(0.10602051)
|
||||||
|
assert msg.pose.orientation.z == approx(0.14357218)
|
||||||
|
assert msg.pose.orientation.w == approx(0.98334744)
|
||||||
|
msg = rospy.wait_for_message('/mavros/setpoint_attitude/thrust', mavros_msgs.msg.Thrust, timeout=3)
|
||||||
|
assert msg.thrust == approx(0.5)
|
||||||
|
|
||||||
|
# set_yaw should work in attitude mode
|
||||||
|
res = set_yaw(yaw=0.7, frame_id='test2')
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_ATTITUDE
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||||
|
assert state.roll == approx(0.1)
|
||||||
|
assert state.pitch == approx(0.2)
|
||||||
|
assert state.yaw == approx(0.7)
|
||||||
|
assert state.thrust == approx(0.5)
|
||||||
|
assert state.yaw_frame_id == 'test2'
|
||||||
|
|
||||||
|
# set_yaw_rate should not work in attitude mode
|
||||||
|
res = set_yaw_rate(yaw_rate=0.3)
|
||||||
|
assert res.success == False
|
||||||
|
assert res.message.startswith('Yaw rate cannot be set in')
|
||||||
|
|
||||||
|
# test set_rates
|
||||||
|
res = set_rates(roll_rate=nan, pitch_rate=nan, yaw_rate=0.3, thrust=0.6)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_RATES
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||||
|
assert state.roll_rate == approx(0)
|
||||||
|
assert state.pitch_rate == approx(0)
|
||||||
|
assert state.yaw_rate == approx(0.3)
|
||||||
|
assert state.thrust == approx(0.6)
|
||||||
|
msg = rospy.wait_for_message('/mavros/setpoint_raw/attitude', mavros_msgs.msg.AttitudeTarget, timeout=3)
|
||||||
|
assert msg.thrust == approx(0.6)
|
||||||
|
|
||||||
|
res = set_rates(roll_rate=0.3, pitch_rate=0.2, yaw_rate=0.1, thrust=0.4)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_RATES
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||||
|
assert state.roll_rate == approx(0.3)
|
||||||
|
assert state.pitch_rate == approx(0.2)
|
||||||
|
assert state.yaw_rate == approx(0.1)
|
||||||
|
assert state.thrust == approx(0.4)
|
||||||
|
|
||||||
|
res = set_rates(roll_rate=nan, pitch_rate=nan, yaw_rate=nan, thrust=0.3)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_RATES
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||||
|
assert state.roll_rate == approx(0.3)
|
||||||
|
assert state.pitch_rate == approx(0.2)
|
||||||
|
assert state.yaw_rate == approx(0.1)
|
||||||
|
assert state.thrust == approx(0.3)
|
||||||
|
msg = rospy.wait_for_message('/mavros/setpoint_raw/attitude', mavros_msgs.msg.AttitudeTarget, timeout=3)
|
||||||
|
assert msg.type_mask == mavros_msgs.msg.AttitudeTarget.IGNORE_ATTITUDE
|
||||||
|
assert msg.body_rate.x == approx(0.3)
|
||||||
|
assert msg.body_rate.y == approx(0.2)
|
||||||
|
assert msg.body_rate.z == approx(0.1)
|
||||||
|
|
||||||
|
# set_yaw_rate should work in rates mode
|
||||||
|
res = set_yaw_rate(yaw_rate=0.4)
|
||||||
|
assert res.success == True
|
||||||
|
state = get_state()
|
||||||
|
assert state.mode == State.MODE_RATES
|
||||||
|
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||||
|
assert state.roll_rate == approx(0.3)
|
||||||
|
assert state.pitch_rate == approx(0.2)
|
||||||
|
assert state.yaw_rate == approx(0.4)
|
||||||
|
assert state.thrust == approx(0.3)
|
||||||
|
|
||||||
|
res = set_rates(roll_rate=inf)
|
||||||
|
assert res.success == False
|
||||||
|
assert res.message == 'roll_rate argument cannot be Inf'
|
||||||
|
|
||||||
|
# test land service
|
||||||
|
res = land()
|
||||||
|
assert res.success == True
|
||||||
|
assert state_msg.mode == 'AUTO.LAND' # check that the mode was set correctly
|
||||||
10
clover/test/offboard.test
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<launch>
|
||||||
|
<node name="simple_offboard" pkg="clover" type="simple_offboard" required="true" output="screen"/>
|
||||||
|
|
||||||
|
<node pkg="tf2_ros" type="static_transform_publisher" name="test_frame" args="10 20 30 0 0 0 map test"/>
|
||||||
|
|
||||||
|
<node pkg="tf2_ros" type="static_transform_publisher" name="test2_frame" args="100 200 300 0 0 0 map test2"/>
|
||||||
|
|
||||||
|
<param name="test_module" value="$(find clover)/test/offboard.py"/>
|
||||||
|
<test test-name="offboard_test" pkg="ros_pytest" type="ros_pytest_runner"/>
|
||||||
|
</launch>
|
||||||
@@ -1,17 +1,54 @@
|
|||||||
# PixHawk (px4fmu-v2), px4fmu-v3
|
# Source files: PX4-Autopilot/boards/**/nuttx-config/nsh/defconfig
|
||||||
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"
|
|
||||||
# CUAV X7 Pro
|
|
||||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV X7Pro", SYMLINK+="px4fmu"
|
|
||||||
|
|
||||||
|
# Additional info:
|
||||||
|
# https://docs.px4.io/main/en/flight_controller/
|
||||||
|
# https://github.com/mavlink/qgroundcontrol/blob/master/src/comm/USBBoardInfo.json
|
||||||
|
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="PX4 GNF405", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="PX4 OmnibusF4SD", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{product}=="PX4 Crazyflie v2.0", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1FC9", ATTRS{idProduct}=="001c", ATTRS{product}=="PX4 FMUK66 v3.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1FC9", ATTRS{idProduct}=="001c", ATTRS{product}=="PX4 FMUK66 E", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1FC9", ATTRS{idProduct}=="001d", ATTRS{product}=="PX4 FMURT1062 v1.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="DiatoneMambaF405 MK2", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a32f", ATTRS{product}=="PX4 FMU ModalAI FCv1", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a330", ATTRS{product}=="PX4 FMU ModalAI FCv2", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0012", ATTRS{product}=="PX4 FMU UVify Core", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteH7", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteH7v2", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="004b", ATTRS{product}=="PX4 DurandalV1", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteF7", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteH7Mini-nand", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="004E", ATTRS{product}=="PX4 PIX32V5", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0061", ATTRS{product}=="PX4 ATL Mantis-EDU", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV Nora", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV X7Pro", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1B8C", ATTRS{idProduct}=="0036", ATTRS{product}=="MatekH743-mini", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1B8C", ATTRS{idProduct}=="0036", ATTRS{product}=="MatekH743", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="120A", ATTRS{idProduct}=="1004", ATTRS{product}=="Matekgnssm9nf4", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="1013", ATTRS{product}=="MatekH743", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="0037", ATTRS{product}=="PX4 FMU SmartAP AIRLink", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="2DAE", ATTRS{idProduct}=="1058", ATTRS{product}=="CubeOrange+", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="2DAE", ATTRS{idProduct}=="1012", ATTRS{product}=="CubeYellow", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="2DAE", ATTRS{idProduct}=="1016", ATTRS{product}=="CubeOrange", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0035", ATTRS{product}=="PX4 FMU v6X.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0038", ATTRS{product}=="PX4 FMU v6C.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0033", ATTRS{product}=="PX4 FMU v5X.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="1B8C", ATTRS{idProduct}=="0036", ATTRS{product}=="PX4 FMU v6U.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0013", ATTRS{product}=="PX4 FMU v4.x PRO", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0011", ATTRS{product}=="PX4 FMU v2.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0012", ATTRS{product}=="PX4 FMU v4.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0032", ATTRS{product}=="PX4 FMU v5.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="004b", ATTRS{product}=="PX4 SP RACING H7 EXTREME", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0030", ATTRS{product}=="MindPX FMU v2.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0039", ATTRS{product}=="ARK FMU v6X.x", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{product}=="PX4 FreeFly RTK GPS", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1024", ATTRS{product}=="mRoControlZeroH7 OEM", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1017", ATTRS{product}=="mRoPixracerPro", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1023", ATTRS{product}=="mRoControlZeroH7", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="008D", ATTRS{product}=="mRoControlZeroF7", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0021", ATTRS{product}=="PX4 AUAV X2.1", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1022", ATTRS{product}=="mRoControlZero Classic", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0088", ATTRS{product}=="mRo x2.1-777", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="35a7", ATTRS{idProduct}=="0002", ATTRS{product}=="FCC-R1", SYMLINK+="px4fmu"
|
||||||
|
SUBSYSTEM=="tty", ATTRS{idVendor}=="35a7", ATTRS{idProduct}=="0001", ATTRS{product}=="FCC-K1", SYMLINK+="px4fmu"
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ http://<hostname>/clover_blocks/?navigate_tolerance=0.5&sleep_time=0.1
|
|||||||
|
|
||||||
* `~running` ([*std_msgs/Bool*](http://docs.ros.org/noetic/api/std_msgs/html/msg/Bool.html)) – indicates if the program is currently running.
|
* `~running` ([*std_msgs/Bool*](http://docs.ros.org/noetic/api/std_msgs/html/msg/Bool.html)) – indicates if the program is currently running.
|
||||||
* `~block` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – current executing block (maximum topic rate is limited).
|
* `~block` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – current executing block (maximum topic rate is limited).
|
||||||
|
* `~print` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user program output messages (published in *print* blocks).
|
||||||
* `~error` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user program errors and exceptions.
|
* `~error` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user program errors and exceptions.
|
||||||
* `~prompt` ([*clover_blocks/Prompt*](msg/Prompt.msg)) – user input request (includes random request ID string).
|
* `~prompt` ([*clover_blocks/Prompt*](msg/Prompt.msg)) – user input request (includes random request ID string).
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<package format="2">
|
<package format="2">
|
||||||
<name>clover_blocks</name>
|
<name>clover_blocks</name>
|
||||||
<version>0.23.0</version>
|
<version>0.24.0</version>
|
||||||
<description>Blockly programming support for Clover</description>
|
<description>Blockly programming support for Clover</description>
|
||||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||||
<license>MIT</license>
|
<license>MIT</license>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const COLOR_GPIO = 200;
|
|||||||
const DOCS_URL = 'https://clover.coex.tech/en/blocks.html';
|
const DOCS_URL = 'https://clover.coex.tech/en/blocks.html';
|
||||||
|
|
||||||
var frameIds = [["body", "BODY"], ["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"], ["map", "MAP"]];
|
var frameIds = [["body", "BODY"], ["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"], ["map", "MAP"]];
|
||||||
|
var frameIdsWithTerrain = frameIds.concat([["terrain", "TERRAIN"]]);
|
||||||
|
|
||||||
function considerFrameId(e) {
|
function considerFrameId(e) {
|
||||||
if (!(e instanceof Blockly.Events.Change || e instanceof Blockly.Events.Create)) return;
|
if (!(e instanceof Blockly.Events.Change || e instanceof Blockly.Events.Create)) return;
|
||||||
@@ -22,7 +23,7 @@ function considerFrameId(e) {
|
|||||||
var frameId = this.getFieldValue('FRAME_ID');
|
var frameId = this.getFieldValue('FRAME_ID');
|
||||||
// set appropriate coordinates labels
|
// set appropriate coordinates labels
|
||||||
if (this.getInput('X')) { // block has x-y-z fields
|
if (this.getInput('X')) { // block has x-y-z fields
|
||||||
if (frameId == 'BODY' || frameId == 'NAVIGATE_TARGET' || frameId == 'BASE_LINK') {
|
if (frameId == 'BODY' || frameId == 'NAVIGATE_TARGET' || frameId == 'BASE_LINK' || frameId == 'TERRAIN') {
|
||||||
this.getInput('X').fieldRow[0].setValue('forward');
|
this.getInput('X').fieldRow[0].setValue('forward');
|
||||||
this.getInput('Y').fieldRow[0].setValue('left');
|
this.getInput('Y').fieldRow[0].setValue('left');
|
||||||
this.getInput('Z').fieldRow[0].setValue('up');
|
this.getInput('Z').fieldRow[0].setValue('up');
|
||||||
@@ -59,8 +60,8 @@ function updateSetpointBlock(e) {
|
|||||||
this.getInput('VY').setVisible(velocity);
|
this.getInput('VY').setVisible(velocity);
|
||||||
this.getInput('VZ').setVisible(velocity);
|
this.getInput('VZ').setVisible(velocity);
|
||||||
this.getInput('YAW').setVisible(attitude);
|
this.getInput('YAW').setVisible(attitude);
|
||||||
this.getInput('PITCH').setVisible(attitude);
|
|
||||||
this.getInput('ROLL').setVisible(attitude);
|
this.getInput('ROLL').setVisible(attitude);
|
||||||
|
this.getInput('PITCH').setVisible(attitude);
|
||||||
this.getInput('THRUST').setVisible(attitude);
|
this.getInput('THRUST').setVisible(attitude);
|
||||||
this.getInput('RELATIVE_TO').setVisible(type != 'RATES');
|
this.getInput('RELATIVE_TO').setVisible(type != 'RATES');
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ function updateSetpointBlock(e) {
|
|||||||
|
|
||||||
Blockly.Blocks['navigate'] = {
|
Blockly.Blocks['navigate'] = {
|
||||||
init: function () {
|
init: function () {
|
||||||
let navFrameId = frameIds.slice();
|
let navFrameId = frameIdsWithTerrain.slice();
|
||||||
navFrameId.push(['global', 'GLOBAL_LOCAL'])
|
navFrameId.push(['global', 'GLOBAL_LOCAL'])
|
||||||
navFrameId.push(['global, WGS 84 alt.', 'GLOBAL'])
|
navFrameId.push(['global, WGS 84 alt.', 'GLOBAL'])
|
||||||
this.appendDummyInput()
|
this.appendDummyInput()
|
||||||
@@ -163,14 +164,14 @@ Blockly.Blocks['setpoint'] = {
|
|||||||
this.appendValueInput("VZ")
|
this.appendValueInput("VZ")
|
||||||
.setCheck("Number")
|
.setCheck("Number")
|
||||||
.appendField("vz");
|
.appendField("vz");
|
||||||
this.appendValueInput("PITCH")
|
|
||||||
.setCheck("Number")
|
|
||||||
.appendField("pitch")
|
|
||||||
.setVisible(false);
|
|
||||||
this.appendValueInput("ROLL")
|
this.appendValueInput("ROLL")
|
||||||
.setCheck("Number")
|
.setCheck("Number")
|
||||||
.appendField("roll")
|
.appendField("roll")
|
||||||
.setVisible(false);
|
.setVisible(false);
|
||||||
|
this.appendValueInput("PITCH")
|
||||||
|
.setCheck("Number")
|
||||||
|
.appendField("pitch")
|
||||||
|
.setVisible(false);
|
||||||
this.appendValueInput("YAW")
|
this.appendValueInput("YAW")
|
||||||
.setCheck("Number")
|
.setCheck("Number")
|
||||||
.appendField("yaw")
|
.appendField("yaw")
|
||||||
@@ -213,7 +214,7 @@ Blockly.Blocks['get_position'] = {
|
|||||||
.appendField("current")
|
.appendField("current")
|
||||||
.appendField(new Blockly.FieldDropdown([["x", "X"], ["y", "Y"], ["z", "Z"], ["vx", "VX"], ["vy", "VY"], ["vz", "VZ"]]), "FIELD")
|
.appendField(new Blockly.FieldDropdown([["x", "X"], ["y", "Y"], ["z", "Z"], ["vx", "VX"], ["vy", "VY"], ["vz", "VZ"]]), "FIELD")
|
||||||
.appendField("relative to")
|
.appendField("relative to")
|
||||||
.appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID");
|
.appendField(new Blockly.FieldDropdown(frameIdsWithTerrain), "FRAME_ID");
|
||||||
this.appendValueInput("ID")
|
this.appendValueInput("ID")
|
||||||
.setCheck("Number")
|
.setCheck("Number")
|
||||||
.appendField("with ID")
|
.appendField("with ID")
|
||||||
@@ -247,7 +248,7 @@ Blockly.Blocks['get_attitude'] = {
|
|||||||
init: function () {
|
init: function () {
|
||||||
this.appendDummyInput()
|
this.appendDummyInput()
|
||||||
.appendField("current")
|
.appendField("current")
|
||||||
.appendField(new Blockly.FieldDropdown([["pitch", "PITCH"], ["roll", "ROLL"], ["pitch rate", "PITCH_RATE"], ["roll rate", "ROLL_RATE"], ["yaw rate", "YAW_RATE"]]), "FIELD");
|
.appendField(new Blockly.FieldDropdown([["roll", "ROLL"], ["pitch", "PITCH"], ["roll rate", "ROLL_RATE"], ["pitch rate", "PITCH_RATE"], ["yaw rate", "YAW_RATE"]]), "FIELD");
|
||||||
this.setOutput(true, "Number");
|
this.setOutput(true, "Number");
|
||||||
this.setColour(COLOR_STATE);
|
this.setColour(COLOR_STATE);
|
||||||
this.setTooltip("Returns current orientation or angle rates in degree or degree per second (not radian).");
|
this.setTooltip("Returns current orientation or angle rates in degree or degree per second (not radian).");
|
||||||
@@ -268,6 +269,19 @@ Blockly.Blocks['voltage'] = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Blockly.Blocks['get_rc'] = {
|
||||||
|
init: function () {
|
||||||
|
this.appendDummyInput()
|
||||||
|
.appendField("RC channel")
|
||||||
|
this.appendValueInput("CHANNEL")
|
||||||
|
.setCheck("Number");
|
||||||
|
this.setInputsInline(true);
|
||||||
|
this.setOutput(true, "Number");
|
||||||
|
this.setColour(COLOR_STATE);
|
||||||
|
this.setTooltip("Returns current RC channel value.");
|
||||||
|
this.setHelpUrl(DOCS_URL + '#' + this.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Blockly.Blocks['armed'] = {
|
Blockly.Blocks['armed'] = {
|
||||||
init: function () {
|
init: function () {
|
||||||
@@ -509,7 +523,7 @@ Blockly.Blocks['distance'] = {
|
|||||||
.appendField("z");
|
.appendField("z");
|
||||||
this.appendDummyInput()
|
this.appendDummyInput()
|
||||||
.appendField("relative to")
|
.appendField("relative to")
|
||||||
.appendField(new Blockly.FieldDropdown([["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"]]), "FRAME_ID");
|
.appendField(new Blockly.FieldDropdown([["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"], ["terrain", "TERRAIN"]]), "FRAME_ID");
|
||||||
this.appendValueInput("ID")
|
this.appendValueInput("ID")
|
||||||
.setCheck("Number")
|
.setCheck("Number")
|
||||||
.appendField("with ID")
|
.appendField("with ID")
|
||||||
|
|||||||
@@ -69,8 +69,8 @@
|
|||||||
<value name="VX"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
<value name="VX"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||||
<value name="VY"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
<value name="VY"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||||
<value name="VZ"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
<value name="VZ"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||||
<value name="PITCH"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
|
||||||
<value name="ROLL"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
<value name="ROLL"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||||
|
<value name="PITCH"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||||
<value name="YAW"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
<value name="YAW"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||||
<value name="THRUST"><shadow type="math_number"><field name="NUM">0.5</field></shadow></value>
|
<value name="THRUST"><shadow type="math_number"><field name="NUM">0.5</field></shadow></value>
|
||||||
<value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
<value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||||
@@ -100,6 +100,9 @@
|
|||||||
<block type="mode"></block>
|
<block type="mode"></block>
|
||||||
<block type="armed"></block>
|
<block type="armed"></block>
|
||||||
<block type="voltage"></block>
|
<block type="voltage"></block>
|
||||||
|
<block type="get_rc">
|
||||||
|
<value name="CHANNEL"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||||
|
</block>
|
||||||
</category>
|
</category>
|
||||||
<category name="LED" colour="#02d754">
|
<category name="LED" colour="#02d754">
|
||||||
<block type="set_effect">
|
<block type="set_effect">
|
||||||
|
|||||||
@@ -81,7 +81,10 @@ function generateROSDefinitions() {
|
|||||||
code += `get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)\n`;
|
code += `get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)\n`;
|
||||||
code += `navigate = rospy.ServiceProxy('navigate', srv.Navigate)\n`;
|
code += `navigate = rospy.ServiceProxy('navigate', srv.Navigate)\n`;
|
||||||
if (rosDefinitions.navigateGlobal) {
|
if (rosDefinitions.navigateGlobal) {
|
||||||
code += `navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)\n`;
|
code += `navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)\n`;
|
||||||
|
}
|
||||||
|
if (rosDefinitions.setYaw) {
|
||||||
|
code += `set_yaw = rospy.ServiceProxy('set_yaw', srv.SetYaw)\n`;
|
||||||
}
|
}
|
||||||
if (rosDefinitions.setVelocity) {
|
if (rosDefinitions.setVelocity) {
|
||||||
code += `set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)\n`;
|
code += `set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)\n`;
|
||||||
@@ -276,10 +279,11 @@ Blockly.Python.angle = function(block) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Blockly.Python.set_yaw = function(block) {
|
Blockly.Python.set_yaw = function(block) {
|
||||||
|
rosDefinitions.setYaw = true;
|
||||||
simpleOffboard();
|
simpleOffboard();
|
||||||
let yaw = Blockly.Python.valueToCode(block, 'YAW', Blockly.Python.ORDER_NONE);
|
let yaw = Blockly.Python.valueToCode(block, 'YAW', Blockly.Python.ORDER_NONE);
|
||||||
let frameId = buildFrameId(block);
|
let frameId = buildFrameId(block);
|
||||||
let code = `navigate(x=float('nan'), y=float('nan'), z=float('nan'), yaw=${yaw}, frame_id=${frameId})\n`;
|
let code = `set_yaw(yaw=${yaw}, frame_id=${frameId})\n`;
|
||||||
if (block.getFieldValue('WAIT') == 'TRUE') {
|
if (block.getFieldValue('WAIT') == 'TRUE') {
|
||||||
rosDefinitions.waitYaw = true;
|
rosDefinitions.waitYaw = true;
|
||||||
simpleOffboard();
|
simpleOffboard();
|
||||||
@@ -328,11 +332,11 @@ Blockly.Python.setpoint = function(block) {
|
|||||||
} else if (type == 'ATTITUDE') {
|
} else if (type == 'ATTITUDE') {
|
||||||
rosDefinitions.setAttitude = true;
|
rosDefinitions.setAttitude = true;
|
||||||
simpleOffboard();
|
simpleOffboard();
|
||||||
return `set_attitude(pitch=${pitch}, roll=${roll}, yaw=${yaw}, thrust=${thrust}, frame_id=${frameId})\n`;
|
return `set_attitude(roll=${roll}, pitch=${pitch}, yaw=${yaw}, thrust=${thrust}, frame_id=${frameId})\n`;
|
||||||
} else if (type == 'RATES') {
|
} else if (type == 'RATES') {
|
||||||
rosDefinitions.setRates = true;
|
rosDefinitions.setRates = true;
|
||||||
simpleOffboard();
|
simpleOffboard();
|
||||||
return `set_rates(pitch_rate=${pitch}, roll_rate=${roll}, yaw_rate=${yaw}, thrust=${thrust})\n`;
|
return `set_rates(roll_rate=${roll}, pitch_rate=${pitch}, yaw_rate=${yaw}, thrust=${thrust})\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,6 +402,12 @@ Blockly.Python.voltage = function(block) {
|
|||||||
return [code, Blockly.Python.ORDER_FUNCTION_CALL];
|
return [code, Blockly.Python.ORDER_FUNCTION_CALL];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Blockly.Python.get_rc = function(block) {
|
||||||
|
Blockly.Python.definitions_['import_rcin'] = 'from mavros_msgs.msg import RCIn';
|
||||||
|
var channel = Blockly.Python.valueToCode(block, 'CHANNEL', Blockly.Python.ORDER_NONE);
|
||||||
|
return [`rospy.wait_for_message('mavros/rc/in', RCIn).channels[${channel}]`, Blockly.Python.ORDER_FUNCTION_CALL]
|
||||||
|
}
|
||||||
|
|
||||||
function parseColor(color) {
|
function parseColor(color) {
|
||||||
return {
|
return {
|
||||||
r: parseInt(color.substr(2, 2), 16),
|
r: parseInt(color.substr(2, 2), 16),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<package format="2">
|
<package format="2">
|
||||||
<name>clover_description</name>
|
<name>clover_description</name>
|
||||||
<version>0.23.0</version>
|
<version>0.24.0</version>
|
||||||
<description>The clover_description package provides URDF models of the Clover series of quadcopters.</description>
|
<description>The clover_description package provides URDF models of the Clover series of quadcopters.</description>
|
||||||
|
|
||||||
<maintainer email="sfalexrog@gmail.com">Alexey Rogachevskiy</maintainer>
|
<maintainer email="sfalexrog@gmail.com">Alexey Rogachevskiy</maintainer>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/xacro">
|
<robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/xacro">
|
||||||
<xacro:include filename="../camera_sensor.urdf.xacro"/>
|
<xacro:include filename="../camera_sensor.urdf.xacro"/>
|
||||||
|
|
||||||
<xacro:macro name="distance_sensor" params="name:=lidar_vl53l1x parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 range_min:=0.01 range_max:=4.0 resolution:=0.01 rate:=10">
|
<xacro:macro name="distance_sensor" params="name:=lidar_vl53l1x parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 range_min:=0.01 range_max:=4.0 resolution:=0.01 rate:=10 fov:=0.471239">
|
||||||
<joint name="${name}_joint" type="fixed">
|
<joint name="${name}_joint" type="fixed">
|
||||||
<origin xyz="${x} ${y} ${z}"
|
<origin xyz="${x} ${y} ${z}"
|
||||||
rpy="${roll} ${pitch} ${yaw}"/>
|
rpy="${roll} ${pitch} ${yaw}"/>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<topicName>/rangefinder/range</topicName>
|
<topicName>/rangefinder/range</topicName>
|
||||||
<frameName>rangefinder</frameName>
|
<frameName>rangefinder</frameName>
|
||||||
<radiation>infrared</radiation>
|
<radiation>infrared</radiation>
|
||||||
<fov>0.01</fov>
|
<fov>${fov}</fov>
|
||||||
<gaussianNoise>0.001</gaussianNoise>
|
<gaussianNoise>0.001</gaussianNoise>
|
||||||
<updateRate>${rate}</updateRate>
|
<updateRate>${rate}</updateRate>
|
||||||
<min_distance>${range_min}</min_distance>
|
<min_distance>${range_min}</min_distance>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ param set-default EKF2_OF_DELAY 0
|
|||||||
param set-default EKF2_OF_QMIN 10
|
param set-default EKF2_OF_QMIN 10
|
||||||
param set-default EKF2_OF_N_MIN 0.05
|
param set-default EKF2_OF_N_MIN 0.05
|
||||||
param set-default EKF2_OF_N_MAX 0.2
|
param set-default EKF2_OF_N_MAX 0.2
|
||||||
param set-default EKF2_HGT_MODE 2 # 0 = baro, 1 = gps, 2 = range, 3 = vision
|
param set-default EKF2_HGT_MODE 3 # 0 = baro, 1 = gps, 2 = range, 3 = vision
|
||||||
param set-default EKF2_EVA_NOISE 0.1
|
param set-default EKF2_EVA_NOISE 0.1
|
||||||
param set-default EKF2_EVP_NOISE 0.1
|
param set-default EKF2_EVP_NOISE 0.1
|
||||||
param set-default EKF2_EV_DELAY 0
|
param set-default EKF2_EV_DELAY 0
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
material red_circle
|
||||||
|
{
|
||||||
|
technique
|
||||||
|
{
|
||||||
|
pass
|
||||||
|
{
|
||||||
|
scene_blend alpha_blend
|
||||||
|
texture_unit
|
||||||
|
{
|
||||||
|
texture red_circle.png
|
||||||
|
filtering none
|
||||||
|
scale 1.0 1.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 7.9 KiB |
13
clover_simulation/models/red_circle/model.config
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<model>
|
||||||
|
<name>Red Circle</name>
|
||||||
|
<version>1.0</version>
|
||||||
|
<sdf version="1.5">red_circle.sdf</sdf>
|
||||||
|
<author>
|
||||||
|
<name>Oleg Kalachev</name>
|
||||||
|
<email>okalachev@gmail.com</email>
|
||||||
|
</author>
|
||||||
|
<description>
|
||||||
|
Red circle of size 40 cm on the floor for recognizing by a drone
|
||||||
|
</description>
|
||||||
|
</model>
|
||||||
24
clover_simulation/models/red_circle/red_circle.sdf
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<sdf version="1.5">
|
||||||
|
<model name="red_circle">
|
||||||
|
<static>true</static>
|
||||||
|
<link name="red_circle_link">
|
||||||
|
<pose>0 0 1e-3 0 0 0</pose>
|
||||||
|
<visual name="red_circle_texture">
|
||||||
|
<cast_shadows>false</cast_shadows>
|
||||||
|
<geometry>
|
||||||
|
<box>
|
||||||
|
<size>0.4 0.4 1e-3</size>
|
||||||
|
</box>
|
||||||
|
</geometry>
|
||||||
|
<material>
|
||||||
|
<script>
|
||||||
|
<uri>model://red_circle/materials/scripts</uri>
|
||||||
|
<uri>model://red_circle/materials/textures</uri>
|
||||||
|
<name>red_circle</name>
|
||||||
|
</script>
|
||||||
|
</material>
|
||||||
|
</visual>
|
||||||
|
</link>
|
||||||
|
</model>
|
||||||
|
</sdf>
|
||||||
7
clover_simulation/models/red_circle/red_circle.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||||
|
<title>
|
||||||
|
red_circle
|
||||||
|
</title><g fill="red">
|
||||||
|
<circle cx="10.05" cy="10.05" r="9.9"/>
|
||||||
|
</g></svg>
|
||||||
|
After Width: | Height: | Size: 221 B |
@@ -1,6 +1,6 @@
|
|||||||
<package format="2">
|
<package format="3">
|
||||||
<name>clover_simulation</name>
|
<name>clover_simulation</name>
|
||||||
<version>0.23.0</version>
|
<version>0.24.0</version>
|
||||||
<description>The clover_simulation package provides worlds and launch files for Gazebo.</description>
|
<description>The clover_simulation package provides worlds and launch files for Gazebo.</description>
|
||||||
|
|
||||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
<depend>gazebo_ros</depend>
|
<depend>gazebo_ros</depend>
|
||||||
<depend>gazebo_plugins</depend>
|
<depend>gazebo_plugins</depend>
|
||||||
<depend>rospy</depend>
|
<depend>rospy</depend>
|
||||||
|
<depend condition="$ROS_PYTHON_VERSION == 2">python-docopt</depend>
|
||||||
|
<depend condition="$ROS_PYTHON_VERSION == 3">python3-docopt</depend>
|
||||||
|
|
||||||
<export>
|
<export>
|
||||||
<gazebo_ros gazebo_media_path="${prefix}"/>
|
<gazebo_ros gazebo_media_path="${prefix}"/>
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
role = (ros::this_node::getName() == "/gazebo") ? Role::Server : Role::Client;
|
role = (ros::this_node::getName() == "/gazebo") ? Role::Server : Role::Client;
|
||||||
ROS_INFO_NAMED(("LedController_" + robotNamespace).c_str(), "LedController has started (as %s)", role == Role::Client ? "client" : "server");
|
ROS_INFO_NAMED(("LedController_" + robotNamespace).c_str(), "LedController has started (as %s) in namespace '%s'",
|
||||||
|
role == Role::Client ? "client" : "server", robotNamespace.c_str());
|
||||||
|
|
||||||
nh.reset(new ros::NodeHandle(robotNamespace));
|
nh.reset(new ros::NodeHandle(robotNamespace));
|
||||||
|
|
||||||
@@ -109,7 +110,6 @@ LedController& get(std::string robotNamespace)
|
|||||||
std::lock_guard<std::mutex> lock(controllerMutex);
|
std::lock_guard<std::mutex> lock(controllerMutex);
|
||||||
auto it = controllers.find(robotNamespace);
|
auto it = controllers.find(robotNamespace);
|
||||||
if (it == controllers.end()) {
|
if (it == controllers.end()) {
|
||||||
gzwarn << "Creating new LED controller for namespace " << robotNamespace << "\n";
|
|
||||||
controllers[robotNamespace].reset(new LedController(robotNamespace));
|
controllers[robotNamespace].reset(new LedController(robotNamespace));
|
||||||
return *controllers[robotNamespace];
|
return *controllers[robotNamespace];
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
docs/assets/ftl/acp_workflow1.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
docs/assets/ftl/acp_workflow2.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
docs/assets/ftl/acp_workflow3.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
docs/assets/ftl/acp_workflow4.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
docs/assets/mocap_clover/block_ROS.jpg
Normal file
|
After Width: | Height: | Size: 368 KiB |
BIN
docs/assets/mocap_clover/block_udp.jpg
Normal file
|
After Width: | Height: | Size: 383 KiB |
BIN
docs/assets/mocap_clover/drone_approach_small.jpg
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
docs/assets/mocap_clover/px4_control_structure.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
docs/assets/mocap_clover/semi_logo_small.jpg
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
docs/assets/raspberry-uart-telemetry2.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
docs/assets/ssh-keys-known_hosts-fingerprint.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/assets/stl/grip_left.stl
Normal file
BIN
docs/assets/stl/grip_right.stl
Normal file
BIN
docs/assets/swarm_in_blocks_2/capa_swarm_23_banner.png
Normal file
|
After Width: | Height: | Size: 326 KiB |
@@ -36,7 +36,7 @@
|
|||||||
* [Optical Flow](optical_flow.md)
|
* [Optical Flow](optical_flow.md)
|
||||||
* [Autonomous flight (OFFBOARD)](simple_offboard.md)
|
* [Autonomous flight (OFFBOARD)](simple_offboard.md)
|
||||||
* [Coordinate systems (frames)](frames.md)
|
* [Coordinate systems (frames)](frames.md)
|
||||||
* [Code snippets](snippets.md)
|
* [Code examples](snippets.md)
|
||||||
* [Interfacing with a laser rangefinder](laser.md)
|
* [Interfacing with a laser rangefinder](laser.md)
|
||||||
* [LED strip](leds.md)
|
* [LED strip](leds.md)
|
||||||
* [Working with GPIO](gpio.md)
|
* [Working with GPIO](gpio.md)
|
||||||
@@ -57,6 +57,7 @@
|
|||||||
* [COEX Pix](coex_pix.md)
|
* [COEX Pix](coex_pix.md)
|
||||||
* [COEX PDB](coex_pdb.md)
|
* [COEX PDB](coex_pdb.md)
|
||||||
* [COEX GPS](coex_gps.md)
|
* [COEX GPS](coex_gps.md)
|
||||||
|
* [Using SSH keys](ssh_keys.md)
|
||||||
* [Guide on autonomous flight](auto_setup.md)
|
* [Guide on autonomous flight](auto_setup.md)
|
||||||
* [Hostname](hostname.md)
|
* [Hostname](hostname.md)
|
||||||
* [PX4 Simulation](sitl.md)
|
* [PX4 Simulation](sitl.md)
|
||||||
@@ -105,6 +106,12 @@
|
|||||||
* [Video contest](video_contest.md)
|
* [Video contest](video_contest.md)
|
||||||
* [Educational contests](educational_contests.md)
|
* [Educational contests](educational_contests.md)
|
||||||
* [Clover-based projects](projects.md)
|
* [Clover-based projects](projects.md)
|
||||||
|
* [Clover Cloud Platform](clover-cloud-platform.md)
|
||||||
|
* [Autonomous Racing Drone](djs_phoenix_chetak.md)
|
||||||
|
* [Motion Capture System](mocap_clover.md)
|
||||||
|
* [Swarm in Blocks 2](swarm_in_blocks_2.md)
|
||||||
|
* [Advanced Clover 2](advanced_clover_simulator_platform.md)
|
||||||
|
* [Network of charging stations](liceu128.md)
|
||||||
* [Swarm-in-blocks](swarm_in_blocks.md)
|
* [Swarm-in-blocks](swarm_in_blocks.md)
|
||||||
* [Obstacle avoidance using artificial potential fields method](obstacle-avoidance-potential-fields.md)
|
* [Obstacle avoidance using artificial potential fields method](obstacle-avoidance-potential-fields.md)
|
||||||
* [The Clover Rescue Project](clover-rescue-team.md)
|
* [The Clover Rescue Project](clover-rescue-team.md)
|
||||||
|
|||||||
161
docs/en/advanced_clover_simulator_platform.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# Advanced Clover 3: The Platform
|
||||||
|
|
||||||
|
[CopterHack-2023](copterhack2023.md), team **FTL**.
|
||||||
|
|
||||||
|
## Team Information
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
#include "veryInterestingCommandDescription.h"
|
||||||
|
```
|
||||||
|
|
||||||
|
Team members:
|
||||||
|
|
||||||
|
- Maxim Ramanouski, [@max8rr8](https://t.me/max8rr8).
|
||||||
|
|
||||||
|
Country: Belarus.
|
||||||
|
|
||||||
|
## Project Description
|
||||||
|
|
||||||
|
Last year at CopterHack 2022, we created a [project](../ru/advanced_clover_simulator.html) that simplified the simulation of Clover, and in 2021, we created a [project](../ru/advanced_clover.html) that simplified the development of products for Clover (IDE and library for writing). The time has come to combine them and achieve unlimited power.
|
||||||
|
|
||||||
|
### Project Idea
|
||||||
|
|
||||||
|
The idea of the project is to combine CloverIDE and CloverSim (a tool for running Clover simulations). Thus, a platform is planned that allows developing products based on Clover using a simulator and an advanced IDE. The platform will include the following features:
|
||||||
|
|
||||||
|
- Add a web interface that allows using CloverSim without touching the command line.
|
||||||
|
- Work both in the browser (without installing anything) and from CLI.
|
||||||
|
- Have a course that covers different aspects of clover.
|
||||||
|
- Simplify installation, especially in WSL.
|
||||||
|
- Running a simulation on a remote device (more powerful computer or cloud).
|
||||||
|
|
||||||
|
### Project videos
|
||||||
|
|
||||||
|
Video presentation of the project: [link](https://www.youtube.com/watch?v=T4RU9sfxsSI).
|
||||||
|
|
||||||
|
Live presentation at CopterHack: TBD.
|
||||||
|
|
||||||
|
CLI demonstration: [link](https://www.youtube.com/watch?v=Ao-ukR58sSQ).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Installation process is described in the [project documentation](https://ftl-team.github.io/clover_sim/#/?id=installation).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The CloverSim platform offers a seamless workflow for users:
|
||||||
|
|
||||||
|
1. Users can effortlessly select or create a workspace and task and
|
||||||
|
launch them with ease.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. After launching the simulation, users are presented with CloverSim WebUI that
|
||||||
|
provides them with an intuitive way to view their scores and progress,
|
||||||
|
control the simulator, and access task descriptions and scoring information.
|
||||||
|
From it users can open terminal, gzweb and more importantly they can easily
|
||||||
|
access the CloverSim IDE to solve task.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
3. The IDE provides a full suite of tools and features for writing and
|
||||||
|
debugging code. One example is autocompletion to help streamline the
|
||||||
|
development process, making it more efficient and effective.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
4. Users can launch their programs with ease and monitor its progress via
|
||||||
|
the GZWeb, CopterStatus, and SimulatorStatus views of the IDE.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
5. Users can track their progress and scores in real-time and effortlessly
|
||||||
|
restart the simulator if necessary. Additionally, different randomization
|
||||||
|
seed can be set to check various inputs and outcomes.
|
||||||
|
|
||||||
|
We also have video demonstration/tutorial: [link](https://www.youtube.com/watch?v=aPOPHD3M3ZM).
|
||||||
|
|
||||||
|
## More features
|
||||||
|
|
||||||
|
- Easy installation process.
|
||||||
|
- Efficient simulation launch, surpassing traditional virtual machines.
|
||||||
|
- Generation of dynamic Gazebo worlds with randomization based on seed.
|
||||||
|
- Real-time task completion verification and score presentation.
|
||||||
|
- Execution with security in isolated containers.
|
||||||
|
- Multiple project capability without the need for multiple virtual machine images.
|
||||||
|
- WebUI for ease of use, removing the need to use the command line.
|
||||||
|
- IDE similar to VSCode with support for C++ and Python, including autocompletion and autoformatting.
|
||||||
|
- Custom-patched GZWeb with bug fixes and additional features, including the display of the Clover LED strip.
|
||||||
|
- GZWeb provides a follow-objects feature superior to that of Gazebo.
|
||||||
|
- IDE includes tools to interact with ROS, such as topic visualization, service calling, and image topic visualization.
|
||||||
|
- IDE also includes Copter Status, displaying most of the drone's information, including position, camera, and LED strip, in one view.
|
||||||
|
- IDE integrates with the simulator by providing control from it, viewing task descriptions, and opening GZWeb.
|
||||||
|
|
||||||
|
We also have developed a learning course based on CloverSim: [link](https://github.com/FTL-team/CloverSim_course). It currently has the following tasks:
|
||||||
|
|
||||||
|
- 1_thesquare - First task of CloverSim course with goal to fly square.
|
||||||
|
- 2_iseeall - Task that teaches how to interact with camera.
|
||||||
|
- 3_landmid - Find and land onto randomly positioned object.
|
||||||
|
- 4_flybyline - Flying along the line.
|
||||||
|
- 5_posknown - Find position of objects relative to ArUco map.
|
||||||
|
|
||||||
|
## More details
|
||||||
|
|
||||||
|
At this point, our platform consists of four major parts:
|
||||||
|
|
||||||
|
- [CloverSim](https://github.com/FTL-team/clover_sim) - tool that manages simulation.
|
||||||
|
- [CloverSim Basefs](https://github.com/FTL-team/clover_sim_basefs) - container image that is used in simulator.
|
||||||
|
- [Clover IDE](https://github.com/FTL-team/cloverIDE) - clover ide tools and theia.
|
||||||
|
- [CloverSim course](https://github.com/FTL-team/CloverSim_course) - course with tasks based on our platform.
|
||||||
|
|
||||||
|
### CloverSim
|
||||||
|
|
||||||
|
The simulation architecture is a continuation of work from CopterHack 2022, but while 2022 version was closer to Proof-of-Concept, the updated version is more robust.
|
||||||
|
|
||||||
|
There are three major difference in simulator architecture
|
||||||
|
|
||||||
|
- Replacement of `systemd-nspawn` with `runc` provides us higher degree of container control and seemingless support of non-systemd systems, for example WSL.
|
||||||
|
- Migration to squash fs images, which greatly reduced size of installed CloverSim from 13 gigabytes to just 3.5 gigabytes.
|
||||||
|
- Tasks are now mounted instead of being copied and also build before starting.
|
||||||
|
|
||||||
|
Because of the way catkin_make works, it is incredibly slow when new packages are added (whole cmake configuration is rerun for all packages). catkin_make provides a way to build only some packages, but it caches this packages and to reset this cache you need to recompile whole catkin_make. But we have found a solution: `catkin_make -DCATKIN_WHITELIST_PACKAGES="task;CloverSim" --build build_CloverSim` This command, builds only CloverSim and task package in separate build directory, this drastically reduces time that catkin_make takes, and keeps expected behavior of catkin_make without arguments.
|
||||||
|
|
||||||
|
There are also differences in tool that launches simulation:
|
||||||
|
|
||||||
|
- Client-server architecture allows us to create web UI and run CloverSim on server.
|
||||||
|
- More robust error handling improves user experience.
|
||||||
|
- Rewritten in rust, better reliability and development experience.
|
||||||
|
|
||||||
|
### CloverSim basefs
|
||||||
|
|
||||||
|
Version 2 integrates CloverIDE into system. We also updated clover in simulator to v0.23 and added web terminal. Basefs is now squashed and doesn't require additional installation. It also uses patched(by us) version of gzweb that is more suitable for our use-case:
|
||||||
|
|
||||||
|
- Unlike original GZWeb assets can be dynamically loaded, which is required to support dynamically generated tasks.
|
||||||
|
- It also implements multiple bugfixes for rendering, UI.
|
||||||
|
- Fixed performance, original gzweb had two constantly running loops that used 200% of cpu. We fixed this by instead using synchronization primitives.
|
||||||
|
- Clover LED strip is rendered, our gzweb connects to ROS and pulls LED data from there to render LED strip like Gazebo does.
|
||||||
|
- Users can now follow-objects like in Gazebo better actually.
|
||||||
|
- Reconnect on disconnect, when simulator is restarted gzweb looses connection and it now can automatically reconnect.
|
||||||
|
|
||||||
|
Patched gzweb available there: [FTL-team/gzweb](https://github.com/FTL-team/gzweb).
|
||||||
|
|
||||||
|
### CloverIDE
|
||||||
|
|
||||||
|
CloverIDE got some updates too:
|
||||||
|
|
||||||
|
- We have updated theia and extensions used.
|
||||||
|
- Better C++ support via clangd.
|
||||||
|
- Clover IDE tools can now reconnect after simulator restart.
|
||||||
|
- Copter Status now displays LED strip status.
|
||||||
|
- Tools ui has better support for different themes.
|
||||||
|
|
||||||
|
But the most important change is CloverSim integration, there are new tools (task description, simulator control and gzweb). While gzweb tool is just an iframe (though it's very cool to have it in IDE).
|
||||||
|
|
||||||
|
Task description and simulator control are more interesting as they have to interact with both IDE and CloverSim, to maintain different versions support we use quite interesting trick, extension webview after being initialized dynamically loads JavaScript from CloverSim. That provides better integration between two.
|
||||||
|
|
||||||
|
### CloverSim course
|
||||||
|
|
||||||
|
CloverSim course is a new part of our platform. It uses robust task API of CloverSim to create practical learning course. It currently teaches different aspects of clover development that i encountered during my participation in different contests involving clover. But we are happy to accpet suggestions about other aspects we should teach in out course.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
This project is a final (or maybe there is more?) project of our advanced clover saga. AdvancedClover is a project that is easy to use and greatly improves experience during learning about clover, participating in clover based competitions and development clover based projects. We thank COEX team for their support and look forward to further cooperation.
|
||||||
@@ -72,12 +72,6 @@ Sample code to fly to a point 1 metre to the left and 2 metres above marker with
|
|||||||
navigate(frame_id='aruco_7', x=-1, y=0, z=2)
|
navigate(frame_id='aruco_7', x=-1, y=0, z=2)
|
||||||
```
|
```
|
||||||
|
|
||||||
Sample code to rotate counterclockwise while hovering 1.5 metres above marker id 10:
|
|
||||||
|
|
||||||
```python
|
|
||||||
navigate(frame_id='aruco_10', x=0, y=0, z=1.5, yaw_rate=0.5)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that if the required marker isn't detected for 0.5 seconds after the `navigate` command, the command will be ignored.
|
Note that if the required marker isn't detected for 0.5 seconds after the `navigate` command, the command will be ignored.
|
||||||
|
|
||||||
These frames may also be used in other services that accept TF frames (like `get_telemetry`). The following code will get the drone's position relative to the marker with id 3:
|
These frames may also be used in other services that accept TF frames (like `get_telemetry`). The following code will get the drone's position relative to the marker with id 3:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<img src="../assets/blocks/blockly.svg" width=200 align="right">
|
<img src="../assets/blocks/blockly.svg" width=200 align="right">
|
||||||
|
|
||||||
Visual blocks programming feature has been added to the [RPi image](image.md), starting with the version **0.21**. Blocks programming is implemented using [Google Blockly](https://developers.google.com/blockly) platform. Blocks programming integration can lower the entry barrier to a minimum.
|
Visual blocks programming feature has been added to the [RPi image](image.md), starting with the version **0.21**. Blocks programming is implemented using [Google Blockly](https://developers.google.com/blockly) library. Blocks programming integration can lower the entry barrier to a minimum.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Working with the camera
|
# Working with the camera
|
||||||
|
|
||||||
|
> **Note** The following applies to the [image version **0.24**](https://github.com/CopterExpress/clover/releases/tag/v0.24), which is not yet released. Older documentation is still available for [for version **0.23**](https://github.com/CopterExpress/clover/blob/f78a03ec8943b596d5a99b893188a159d5319888/docs/en/camera.md).
|
||||||
|
|
||||||
Make sure the camera is enabled in the `~/catkin_ws/src/clover/clover/launch/clover.launch` file:
|
Make sure the camera is enabled in the `~/catkin_ws/src/clover/clover/launch/clover.launch` file:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
@@ -14,7 +16,7 @@ The `clover` service must be restarted after the launch-file has been edited:
|
|||||||
sudo systemctl restart clover
|
sudo systemctl restart clover
|
||||||
```
|
```
|
||||||
|
|
||||||
You may use rqt or [web_video_server](web_video_server.md) to view the camera stream.
|
You may use [rqt](rviz.md) or [web_video_server](web_video_server.md) to view the camera stream.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
@@ -52,8 +54,6 @@ The [SD card image](image.md) comes with a preinstalled [OpenCV](https://opencv.
|
|||||||
|
|
||||||
### Python
|
### Python
|
||||||
|
|
||||||
Main article: http://wiki.ros.org/cv_bridge/Tutorials/ConvertingBetweenROSImagesAndOpenCVImagesPython.
|
|
||||||
|
|
||||||
An example of creating a subscriber for a topic with an image from the main camera for processing with OpenCV:
|
An example of creating a subscriber for a topic with an image from the main camera for processing with OpenCV:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -61,12 +61,14 @@ import rospy
|
|||||||
import cv2
|
import cv2
|
||||||
from sensor_msgs.msg import Image
|
from sensor_msgs.msg import Image
|
||||||
from cv_bridge import CvBridge
|
from cv_bridge import CvBridge
|
||||||
|
from clover import long_callback
|
||||||
|
|
||||||
rospy.init_node('computer_vision_sample')
|
rospy.init_node('cv')
|
||||||
bridge = CvBridge()
|
bridge = CvBridge()
|
||||||
|
|
||||||
|
@long_callback
|
||||||
def image_callback(data):
|
def image_callback(data):
|
||||||
cv_image = bridge.imgmsg_to_cv2(data, 'bgr8') # OpenCV image
|
img = bridge.imgmsg_to_cv2(data, 'bgr8') # OpenCV image
|
||||||
# Do any image processing with cv2...
|
# Do any image processing with cv2...
|
||||||
|
|
||||||
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback)
|
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback)
|
||||||
@@ -74,19 +76,31 @@ image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback)
|
|||||||
rospy.spin()
|
rospy.spin()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **Note** Image processing may take significant time to finish. This can cause an [issue](https://github.com/ros/ros_comm/issues/1901) in rospy library, which would lead to processing stale camera frames. To solve this problem you need to use `long_callback` decorator from `clover` library, as in the example above.
|
||||||
|
|
||||||
|
#### Limiting CPU usage
|
||||||
|
|
||||||
|
When using the `main_camera/image_raw` topic, the script will process the maximum number of frames from the camera, actively utilizing the CPU (up to 100%). In tasks, where processing each camera frame is not critical, you can use the topic, where the frames are published at rate 5 Hz: `main_camera/image_raw_throttled`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
image_sub = rospy.Subscriber('main_camera/image_raw_throttled', Image, image_callback, queue_size=1)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Publishing images
|
||||||
|
|
||||||
To debug image processing, you can publish a separate topic with the processed image:
|
To debug image processing, you can publish a separate topic with the processed image:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
image_pub = rospy.Publisher('~debug', Image)
|
image_pub = rospy.Publisher('~debug', Image)
|
||||||
```
|
```
|
||||||
|
|
||||||
Publishing the processed image (at the end of the image_callback function):
|
Publishing the processed image:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
image_pub.publish(bridge.cv2_to_imgmsg(cv_image, 'bgr8'))
|
image_pub.publish(bridge.cv2_to_imgmsg(img, 'bgr8'))
|
||||||
```
|
```
|
||||||
|
|
||||||
The obtained images can be viewed using [web_video_server](web_video_server.md).
|
The published images can be viewed using [web_video_server](web_video_server.md) or [rqt](rviz.md).
|
||||||
|
|
||||||
#### Retrieving one frame
|
#### Retrieving one frame
|
||||||
|
|
||||||
@@ -97,7 +111,7 @@ import rospy
|
|||||||
from sensor_msgs.msg import Image
|
from sensor_msgs.msg import Image
|
||||||
from cv_bridge import CvBridge
|
from cv_bridge import CvBridge
|
||||||
|
|
||||||
rospy.init_node('computer_vision_sample')
|
rospy.init_node('cv')
|
||||||
bridge = CvBridge()
|
bridge = CvBridge()
|
||||||
|
|
||||||
# ...
|
# ...
|
||||||
@@ -119,38 +133,32 @@ QR codes recognition in Python:
|
|||||||
```python
|
```python
|
||||||
import rospy
|
import rospy
|
||||||
from pyzbar import pyzbar
|
from pyzbar import pyzbar
|
||||||
|
import cv2
|
||||||
from cv_bridge import CvBridge
|
from cv_bridge import CvBridge
|
||||||
from sensor_msgs.msg import Image
|
from sensor_msgs.msg import Image
|
||||||
|
from clover import long_callback
|
||||||
|
|
||||||
|
rospy.init_node('cv')
|
||||||
bridge = CvBridge()
|
bridge = CvBridge()
|
||||||
|
|
||||||
rospy.init_node('barcode_test')
|
@long_callback
|
||||||
|
def image_callback(msg):
|
||||||
# Image subscriber callback function
|
img = bridge.imgmsg_to_cv2(msg, 'bgr8')
|
||||||
def image_callback(data):
|
barcodes = pyzbar.decode(img)
|
||||||
cv_image = bridge.imgmsg_to_cv2(data, 'bgr8') # OpenCV image
|
|
||||||
barcodes = pyzbar.decode(cv_image)
|
|
||||||
for barcode in barcodes:
|
for barcode in barcodes:
|
||||||
b_data = barcode.data.decode("utf-8")
|
b_data = barcode.data.decode('utf-8')
|
||||||
b_type = barcode.type
|
b_type = barcode.type
|
||||||
(x, y, w, h) = barcode.rect
|
(x, y, w, h) = barcode.rect
|
||||||
xc = x + w/2
|
xc = x + w/2
|
||||||
yc = y + h/2
|
yc = y + h/2
|
||||||
print("Found {} with data {} with center at x={}, y={}".format(b_type, b_data, xc, yc))
|
print('Found {} with data {} with center at x={}, y={}'.format(b_type, b_data, xc, yc))
|
||||||
|
|
||||||
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1)
|
image_sub = rospy.Subscriber('main_camera/image_raw_throttled', Image, image_callback, queue_size=1)
|
||||||
|
|
||||||
rospy.spin()
|
rospy.spin()
|
||||||
```
|
```
|
||||||
|
|
||||||
The script will take up to 100% CPU capacity. To slow down the script artificially, you can use [throttling](http://wiki.ros.org/topic_tools/throttle) of frames from the camera, for example, at 5 Hz (`main_camera.launch`):
|
> **Hint** See other computer vision examples in the `~/examples` directory of the [RPi image](image.md).
|
||||||
|
|
||||||
```xml
|
|
||||||
<node pkg="topic_tools" name="cam_throttle" type="throttle"
|
|
||||||
args="messages main_camera/image_raw 5.0 main_camera/image_raw_throttled"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
The topic for the subscriber in this case should be changed for `main_camera/image_raw_throttled`.
|
|
||||||
|
|
||||||
## Video recording
|
## Video recording
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,16 @@ Print path to the current directory:
|
|||||||
pwd
|
pwd
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Go to the user's home directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# all three commands are equivalent, where the tilde character (~) is an abbreviated
|
||||||
|
# path entry to the home directory, and the $HOME variable stores this path
|
||||||
|
cd
|
||||||
|
cd ~
|
||||||
|
cd $HOME
|
||||||
|
```
|
||||||
|
|
||||||
Print contents of the `file.py` file:
|
Print contents of the `file.py` file:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
93
docs/en/clover-cloud-platform.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# Clover Cloud Platform
|
||||||
|
|
||||||
|
[CopterHack-2023](copterhack2023.md), team **Clover Cloud Team**.
|
||||||
|
|
||||||
|
The list of our team members:
|
||||||
|
|
||||||
|
* Кирилл Лещинский / Kirill Leshchinskiy, [@k_leshchinskiy](https://t.me/k_leshchinskiy) - Team Lead.
|
||||||
|
* Кузнецов Михаил / Mikhail Kuznetsov, [@bruhfloppa](https://t.me/bruhfloppa) - Frontend Developer.
|
||||||
|
* Даниил Валишин / Daniil Valishin, [@Astel_1](https://t.me/Astel_1) - Backend Developer.
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
* [Introduction](#introduction)
|
||||||
|
* [Usability](#usability)
|
||||||
|
* [How to work with our platform?](#how-to-work-with-our-platform)
|
||||||
|
* [About the development of the platform](#about-the-development-of-the-platform)
|
||||||
|
* [Conclusion](#conclusion)
|
||||||
|
|
||||||
|
## Video demonstration
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.youtube.com/watch?v=FZPl2LOMgi4"><img img width="560" height="315" src="https://img.youtube.com/vi/FZPl2LOMgi4/maxresdefault.jpg" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Clover Cloud Platform is an innovative platform that enables users to access COEX Clover drone simulation online, without the need to download any programs or virtual machines.
|
||||||
|
|
||||||
|
> **Note** Visit our [documentation](https://docs.clovercloud.software) to learn all about the platform, its development and how to use it.
|
||||||
|
|
||||||
|
## Unleash Your Coding Power: Develop Autonomous Flight Code at Lightning Speed on Clover Cloud Platform
|
||||||
|
|
||||||
|
If you're a developer working on autonomous flight projects, you know how time-consuming and distracting all of the routine activities can be. Between managing your hardware, debugging, and configuring your environment, it can feel like the real work of coding gets lost in the shuffle.
|
||||||
|
|
||||||
|
That's where our platform comes in. Our streamlined interface and powerful tools make it easy to tackle all of those essential tasks so you can focus on what really matters: developing flawless, high-performance code for your autonomous flight project.
|
||||||
|
|
||||||
|
So why wait to unleash your coding power? Sign up for our platform today and discover the difference it can make in the speed, quality, and focus of your autonomous flight coding work.
|
||||||
|
|
||||||
|
## Usability
|
||||||
|
|
||||||
|
Our platform is incredibly user-friendly and provides seamless access to the simulation in just a few clicks. Together with a simulator that displays simulation data accurately and without delay, there is a map editor allows users to edit the ArUco marker map and add or modify other objects on the scene directly within the simulation window. Additionally, users can create pre-configured workspaces complete with autonomous flight code and simulation scene configuration. Each user can also create their templates or apply a pre-made one to their workspace in just a few clicks. In addition to its other features, Clover Cloud Platform provides users with a convenient code editor for autonomous flight coding. Users can write code in the built-in editor and run it directly from the editor, viewing program output in real-time in the terminal. The platform also includes a file manager that simplifies file manipulation tasks, further enhancing the user's overall experience. With these tools at your fingertips, Clover Cloud Platform delivers an unparalleled level of accessibility and convenience for autonomous flight simulation.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/Clover-Cloud-Platform/clover-cloud-platform-frontend/master/docs/workspace.png" alt="Workspace screenshot">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
## The CodeSandbox for COEX Clover
|
||||||
|
|
||||||
|
You can describe the usability and relevance of our platform in another way. Have you heard of CodeSandbox? Our platform offers the same convenience, flexibility, and accessibility as CodeSandbox, but is specifically designed to work with the COEX Clover drone simulation.
|
||||||
|
|
||||||
|
## How to work with our platform?
|
||||||
|
|
||||||
|
Let's dive into the sea of functionality that our platform offers. Detailed description of each feature is available in our [documentation](https://docs.clovercloud.software), here we will provide a general overview of the platform.
|
||||||
|
|
||||||
|
### Creating an account
|
||||||
|
|
||||||
|
First, you should create an account on our site. You can do this by clicking on this [link](https://clovercloud.software/signup).
|
||||||
|
|
||||||
|
### Instance management
|
||||||
|
|
||||||
|
After creating an account, you will be taken to the [dashboard](https://clovercloud.software/instances). Here you can create, start, stop and delete workspaces.
|
||||||
|
|
||||||
|
>Workspaces are containers with Gazebo simulator and our software that provide data flow for simulation visualization, as well as handle requests from file manager, code editor and terminal.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/Clover-Cloud-Platform/clover-cloud-platform-frontend/master/docs/instances.gif" alt="Instance management">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### Workspace overview
|
||||||
|
|
||||||
|
In the workspace, in addition to the simulator, you have a file manager, code editor and terminal. There is also an editing mode in the simulator - one of the key features of our project. It allows you to quickly and conveniently edit the simulation scene, namely: move ArUco markers, change their size, change id of the marker, load instead of marker picture, add new markers or delete them. You can also add 3d objects to the scene and change their position, size and color. Below is an example of working with our workspace.
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="https://github.com/Clover-Cloud-Platform/clover-cloud-platform-frontend/raw/master/docs/workspace.gif" alt="Workspace overview">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### Templates
|
||||||
|
|
||||||
|
Templates are another key feature of our platform.Is there something you can't do and you want to see how to properly perform a task? Look for the right template with ready-made code in the Template Browser and apply it to your workspace! Each user can create a template with an autonomous flight code and simulator configuration and share it.
|
||||||
|
|
||||||
|
## About the development of the platform
|
||||||
|
|
||||||
|
Our team has worked tirelessly to develop a simple yet multifunctional platform. We utilized the most modern standards and tools and implemented numerous optimization methods to ensure seamless performance and error-free operation. The frontend programming language chosen was JavaScript with the React framework, as a design system we utilizing Material Design style for an elegant and intuitive user interface. With the help of GitHub Actions the website is being built and deployed to Firebase hosting. The platform's backend is written in Python and contains multiple simultaneously running scripts. User data is secured and stored in a MongoDB database. Communication between the server and site is enabled through web sockets and the socket.io library, guaranteeing lightning-fast data transfer with minimal lag.
|
||||||
|
|
||||||
|
You can view the source code of our platform by clicking on the links below:
|
||||||
|
|
||||||
|
[Repository with the frontend-side code](https://github.com/Clover-Cloud-Platform/clover-cloud-platform-frontend)
|
||||||
|
|
||||||
|
[Repository with the backend-side code](https://github.com/Clover-Cloud-Platform/clover-cloud-platform-backend)
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
In conclusion, we have successfully created a truly convenient and useful platform, suitable for both novice and advanced COEX Clover drone users. Beginners can test their first autonomous flight code without the need for demanding simulator installation or virtual machines. They can also explore all of the drone's functions and capabilities without editing any configuration files. Advanced users benefit from access to their workspace from anywhere in the world and on any device, along with a convenient code-sharing system. In the future, we plan to add more new features to our platform, scale our network to serve a greater number of users, and collaborate with COEX to integrate their Clover quadcopter documentation into our platform, offering users a very simple and user-friendly way to learn to program autonomous drone flight. We also want to express gratitude to the COEX customer support team for their assistance in resolving complex issues that arose during development.
|
||||||
@@ -6,37 +6,59 @@ In order to program [autonomous flights](simple_offboard.md), [work with Pixhawk
|
|||||||
|
|
||||||
USB connection is the preferred way to connect to the flight controller.
|
USB connection is the preferred way to connect to the flight controller.
|
||||||
|
|
||||||
|
<img src="../assets/assembling_clever4/usb_connection_1.png" alt="USB connection" height=400 class="zoom border center">
|
||||||
|
|
||||||
1. Connect your FCU to the Raspberry Pi using a microUSB to USB cable.
|
1. Connect your FCU to the Raspberry Pi using a microUSB to USB cable.
|
||||||
2. [Connect to the Raspberry Pi over SSH](ssh.md).
|
2. [Connect to the Raspberry Pi over SSH](ssh.md).
|
||||||
3. Make sure the connection is working by [running the following command on the Raspberry Pi](ssh.md):
|
3. Make sure that the connection is working properly by [running the following command on the Raspberry Pi](cli.md):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
rostopic echo /mavros/state
|
rostopic echo /mavros/state
|
||||||
```
|
```
|
||||||
|
|
||||||
The `connected` field should have the `True` value.s
|
The `connected` field should have the `True` value.
|
||||||
|
|
||||||
> **Hint** You need to set the `CBRK_USB_CHK` [parameter](parameters.md) to 197848 for the USB connection to work.
|
> **Hint** You need to set the `CBRK_USB_CHK` [parameter](parameters.md) to 197848 for the USB connection to work.
|
||||||
|
|
||||||
## UART connection
|
## UART connection
|
||||||
|
|
||||||
<!-- TODO: Connection scheme -->
|
|
||||||
|
|
||||||
UART connection is another way for the Raspberry Pi and FCU to communicate.
|
UART connection is another way for the Raspberry Pi and FCU to communicate.
|
||||||
|
|
||||||
|
<img src="../assets/raspberry-uart-telemetry2.png" alt="UART connection via TELEM2" height=400 class="zoom border center">
|
||||||
|
|
||||||
|
If the pin marked GND is occupied, you can use any other ground pin (look at the [pinout](https://pinout.xyz) for reference).
|
||||||
|
|
||||||
1. Connect the TELEM 2 port on the flight controller using a UART cable to the Raspberry Pi pins following this instruction: the black cable (*GND*) to Ground, the green cable (*UART_RX*) to *GPIO14*, the yellow cable (*UART_TX*) to *GPIO15*. Do not connect the red cable (*5V*).
|
1. Connect the TELEM 2 port on the flight controller using a UART cable to the Raspberry Pi pins following this instruction: the black cable (*GND*) to Ground, the green cable (*UART_RX*) to *GPIO14*, the yellow cable (*UART_TX*) to *GPIO15*. Do not connect the red cable (*5V*).
|
||||||
2. Set the PX4 parameters: `MAV_1_CONFIG` to TELEM 2, `SER_TEL2_BAUND` to 921600 8N1. In PX4 of version prior to v1.10.0 the parameter `SYS_COMPANION` should be set to 921600.
|
2. In PX4 of version v1.9.0 or higher, set parameter values: `MAV_1_CONFIG` to TELEM 2, `SER_TEL2_BAUND` to 921600 8N1. In PX4 of version [prior to v1.9.0](https://github.com/mavlink/qgroundcontrol/issues/6905#issuecomment-464549610) the parameter `SYS_COMPANION` should be set to `Companion Link (921600 baud, 8N1)`, to set it correctly use the old version of QGC [v3.3.1](https://github.com/mavlink/qgroundcontrol/releases/tag/v3.3.1).
|
||||||
3. [Connect to the Raspberry Pi over SSH](ssh.md).
|
3. [Connect to the Raspberry Pi over SSH](ssh.md).
|
||||||
4. Change the connection type in `~/catkin_ws/src/clover/clover/launch/clover.launch` to UART:
|
4. Check the presence of the parameters `enable_uart=1` and `dtoverlay=pi 3-disable-bt` in the file `/boot/config.txt` by [running the following command on the Raspberry Pi](cli.md):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cat /boot/config.txt | grep -E "^enable_uart=.|^dtoverlay=pi3-disable-bt"
|
||||||
|
```
|
||||||
|
|
||||||
|
If the parameters in the file are different or missing, then edit the file and restart the Raspberry Pi.
|
||||||
|
|
||||||
|
5. Change the connection type from `usb` to `uart` in the Clover' launch file `~/catkin_ws/src/clover/clover/launch/clover.launch`:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<arg name="fcu_conn" default="uart"/>
|
<arg name="fcu_conn" default="uart"/>
|
||||||
```
|
```
|
||||||
|
|
||||||
Be sure to restart the `clover` service after editing the .launch file:
|
If you change the launch file, you need to restart the `clover' service:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo systemctl restart clover
|
sudo systemctl restart clover
|
||||||
```
|
```
|
||||||
|
|
||||||
|
6. Make sure that the connection is working properly by running the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rostopic echo -n1 /mavros/state
|
||||||
|
```
|
||||||
|
|
||||||
|
The `connected` field should have the `True` value.
|
||||||
|
|
||||||
|
Read more in the PX4 docs: https://docs.px4.io/main/en/peripherals/serial_configuration.html.
|
||||||
|
|
||||||
**Next**: [Using QGroundControl over Wi-Fi](gcs_bridge.md)
|
**Next**: [Using QGroundControl over Wi-Fi](gcs_bridge.md)
|
||||||
|
|||||||
@@ -8,6 +8,36 @@ To learn more about the articles of the CopterHack finalist teams follow the lin
|
|||||||
|
|
||||||
The proposed projects are supposed to be open-source and be compatible with the Clover quadcopter platform. Teams-participants are supposed to work on their projects throughout the competition, bringing them closer to the state of the finished product while being assisted by industry experts through lectures and regular feedback.
|
The proposed projects are supposed to be open-source and be compatible with the Clover quadcopter platform. Teams-participants are supposed to work on their projects throughout the competition, bringing them closer to the state of the finished product while being assisted by industry experts through lectures and regular feedback.
|
||||||
|
|
||||||
|
Final of the CopterHack 2022 was held on May 27, 2023. The winner team was the team 🇷🇺 **[Clover Cloud Platform](clover-cloud-platform.md)**.
|
||||||
|
|
||||||
|
## Full stream of the final
|
||||||
|
|
||||||
|
<iframe width="560" height="315" src="https://www.youtube.com/embed/Hdl6Sah7nkE" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
|
||||||
|
|
||||||
|
## Projects of the contest's participants {#participants}
|
||||||
|
|
||||||
|
|Place|Team|Project|Points|
|
||||||
|
|:-:|-|-|-|
|
||||||
|
|1|🇷🇺 Clover Cloud Team|[Clover Cloud Platform](clover-cloud-platform.md)|21.7|
|
||||||
|
|2|🇧🇾 FTL|[Advanced Clover 2](advanced_clover_simulator_platform.md)|21|
|
||||||
|
|3|🇨🇦 Clover with Motion Capture System|[Clover with Motion Capture System](mocap_clover.md)|20.5|
|
||||||
|
|4|🇧🇷 Atena|[Swarm in Blocks 2](swarm_in_blocks_2.md)|20.3|
|
||||||
|
|5|🇷🇺 C305|[Система радио-навигации](../ru/nav-beacon.html)|17.5|
|
||||||
|
|6|🇮🇳 DJS PHOENIX|[Autonomous Racing Drone](djs_phoenix_chetak.md)|14.6|
|
||||||
|
|7|🇷🇺 Lyceum №128|[Network of Clover charging stations](liceu128.md)|13.7|
|
||||||
|
|✕|🇰🇬 Zavarka|[Система обмена грузами с помощью конвейера](https://github.com/aiurobotics/clover/blob/conveyance/docs/ru/conveyance.md)||
|
||||||
|
|✕|🇷🇺 FSOTM|[Drone Interceptor](https://github.com/deadln/clover/blob/interceptor/docs/ru/interceptor.md)||
|
||||||
|
|✕|🇰🇬 Homelesses|[Trash Collector](https://github.com/Isa-jesus/clover/blob/trash-collector/docs/ru/show_maker.md)||
|
||||||
|
|✕|🇷🇺 Digital otters|[Digital otters](https://github.com/Mentalsupernova/clover_cool/blob/new-article.md/docs/ru/new-article.md)||
|
||||||
|
|✕|🇷🇺 Light Flight|[Сопровождение БПЛА при посадке](https://github.com/SirSerow/clover_inertial_ns/blob/inertial-1/Description.md)||
|
||||||
|
|✕|🇰🇬 LiveSavers|[LiveSavers](https://github.com/Sarvar00/clover/blob/livesavers/docs/ru/livesaver.md)||
|
||||||
|
|✕|🇷🇺 XenCOM|[Bound by fate](https://github.com/xenkek/clover/blob/xenkek-patch-1/docs/ru/bound_by_fate.md)||
|
||||||
|
|✕|🇷🇺 Ava_Clover|[DoubleClover](https://github.com/bessiaka/clover/blob/Ava_Clover/docs/ru/soosocta.md)||
|
||||||
|
|✕|🇷🇺 TPU_1|[Совместная транспортировка груза](https://github.com/shamoleg/clover/blob/tpu_1/docs/ru/tpu_1.md)||
|
||||||
|
|✕|🇷🇺 TPU_2|[Алгоритм полета сквозь лесную местность](https://github.com/shamoleg/clover/blob/tpu_2/docs/ru/tpu_2.md)| |
|
||||||
|
|
||||||
|
See all points by criteria in the [full table](https://docs.google.com/spreadsheets/d/1qTpW8zFVdSEGnbtOvMgQD6DcYwu8URFt1RKOCeUaOe8).
|
||||||
|
|
||||||
## CopterHack 2023 stages
|
## CopterHack 2023 stages
|
||||||
|
|
||||||
The qualifying and project development stages will be held in an online format, however, the final round will be in a hybrid mode (offline + online). The competition involves monthly updates from the teams with regular feedback from the jury. All teams are required to prepare a final video and presentation on the project's results to participate in the final stage.
|
The qualifying and project development stages will be held in an online format, however, the final round will be in a hybrid mode (offline + online). The competition involves monthly updates from the teams with regular feedback from the jury. All teams are required to prepare a final video and presentation on the project's results to participate in the final stage.
|
||||||
|
|||||||
55
docs/en/djs_phoenix_chetak.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Autonomous Racing Drone: CHETAK
|
||||||
|
|
||||||
|
[CopterHack-2023](copterhack2023.md), team **DJS PHOENIX**.
|
||||||
|
|
||||||
|
## Team Information
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
We are the DJS Phoenix, the official drone team of Dwarkadas. J. Sanghvi College of Engineering
|
||||||
|
|
||||||
|
The list of team members:
|
||||||
|
|
||||||
|
* Shubham Mehta, @Just_me_05, Mentor.
|
||||||
|
* Harshal Warde, @kryptonisinert, Mechanical.
|
||||||
|
* Parth Sawjiyani, @Non_Active, Mechanical.
|
||||||
|
* Soham Dalvi, @devilsfootprint_1973, Mechanical.
|
||||||
|
* Vedant Patel, @VedantMP, Mechanical.
|
||||||
|
* Harsh Shah, @harssshhhhh, Mechanical.
|
||||||
|
* Lisha Mehta, @lishamehta, Mechanical.
|
||||||
|
* Shubh Pokarne, @Shubhpokarne, Electronics.
|
||||||
|
* Tushar Nagda, @tushar_n11, Electronics.
|
||||||
|
* Deep Tank, @Kraven, Electronics.
|
||||||
|
* Khushi Sanghvi, @Cryptoknigghtt, Programmer.
|
||||||
|
* Harshil Shah, @divine_fossil, Programmer.
|
||||||
|
* Omkar Parab, @Omkar_parab21, Programmer.
|
||||||
|
* Madhura Korgaonkar, @Madhura221, Programmer.
|
||||||
|
* Shruti Shah, @Shrutishah22, Programmer.
|
||||||
|
* Aditi Dubey, @aditi_0503, Marketing.
|
||||||
|
* Krisha Lakhani, @krishalakhani, Marketing.
|
||||||
|
|
||||||
|
## Project Description
|
||||||
|
|
||||||
|
This year, our team DJS Phoenix, presents to you a fully Autonomous Racing Drone. The drone scans for ArUco tags on the gates and passes through them.
|
||||||
|
|
||||||
|
### Project Idea
|
||||||
|
|
||||||
|
This project proposes to develop an autonomous racing drone that can navigate through complex courses at high speeds while avoiding obstacles and detecting changes in the environment. In racing competitions, autonomous drones can compete in high-speed, precision races that challenge their agility, speed, and accuracy. These competitions could be held in indoor arenas or outdoor tracks, and they could attract enthusiasts and spectators from all over the world. With their advanced capabilities, autonomous racing drones could usher in a new era of racing events that are more exciting and challenging than ever before. From racing competitions to search and rescue operations, the autonomous racing drone can be used in a wide range of applications that benefit individuals, businesses, and society as a whole.
|
||||||
|
|
||||||
|
## Potential Outcome
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
In many industries and applications, there is a need for fast, efficient, and safe movement of goods and information. Drones have become an increasingly popular tool for a wide range of applications, from aerial photography to surveying and monitoring. However, operating a drone requires a certain level of skill and experience, which can be a barrier for individuals or businesses who want to take advantage of this technology. Additionally, traditional drones can be expensive and time-consuming to operate, limiting their accessibility and effectiveness. Therefore, there is a need for a more user-friendly and affordable solution that can expand the use of drones to new audiences and applications.
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
|
||||||
|
The solution to the above problem statement is an autonomous racing drone. An autonomous racing drone is equipped with a camera that scans the ArUco tags for gate detection which is supported by software used in autonomous flights that enable it to navigate through a predetermined course while avoiding obstacles and achieving high speeds. Unlike traditional drones, an autonomous racing drone does not require manual control, making it an ideal solution for those who do not have the skills or experience to operate a drone.Its autonomous capabilities make it a more accessible and user-friendly solution than traditional drones, enabling individuals or businesses to take advantage of this technology without requiring extensive training or expertise.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Additional Information
|
||||||
|
|
||||||
|
In 2017, a student committee for DJS Phoenix was formed. In India, our team has participated in a number of contests, including IDRL-IIT GandhiNagar (sixth rank), IDRL-SVPCET Nagpur(second rank) and TECHNOXIAN (second place out of 50 national teams). In CopterHack-2021, our team participated, and we placed eighth internationally. We are back with improved concepts after learning from the previous season.
|
||||||
|
|
||||||
|
For more information checkout gitbook: https://djs-phoenix.gitbook.io/chetak-faster-than-you-can-imagine/.
|
||||||