mirror of
https://github.com/CopterExpress/clover.git
synced 2026-06-01 07:29:32 +00:00
Compare commits
130 Commits
strict-war
...
known_vert
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a72cb67d03 | ||
|
|
8ac757598d | ||
|
|
25c3f25642 | ||
|
|
4877d0101b | ||
|
|
d4a83bdf58 | ||
|
|
8fd69e4ea5 | ||
|
|
c3625490b2 | ||
|
|
cb1773b708 | ||
|
|
5afbcff949 | ||
|
|
cf62364ac7 | ||
|
|
c7bf964ea5 | ||
|
|
f719406c8b | ||
|
|
72f8d901d5 | ||
|
|
5ec04bbefa | ||
|
|
393801b023 | ||
|
|
275023b455 | ||
|
|
a0322c55f2 | ||
|
|
b3b530c1c7 | ||
|
|
3662f512a7 | ||
|
|
277eb7297f | ||
|
|
e719b0f1e2 | ||
|
|
e65d380b4b | ||
|
|
8fe34e90e6 | ||
|
|
54ab5ab4b5 | ||
|
|
2cda68ae4a | ||
|
|
d24b6617a4 | ||
|
|
640ec1ee1a | ||
|
|
96ea78f141 | ||
|
|
5e3b07ff5e | ||
|
|
92748a760b | ||
|
|
8512e8a045 | ||
|
|
8b1b365e67 | ||
|
|
2cd77662df | ||
|
|
64f939d7ed | ||
|
|
9a8aa00bc7 | ||
|
|
3f3d1cd220 | ||
|
|
9c34d7722c | ||
|
|
19e0d725b0 | ||
|
|
6fafaf3184 | ||
|
|
8f09df6b34 | ||
|
|
c5d01c678a | ||
|
|
2b13aa02eb | ||
|
|
ec9ddf5fd2 | ||
|
|
c5399868cb | ||
|
|
a6cee773ab | ||
|
|
d03cfe00ca | ||
|
|
0fb101cc59 | ||
|
|
0d44ff3993 | ||
|
|
dc5da00abd | ||
|
|
4f00960db3 | ||
|
|
ce0b4eb428 | ||
|
|
ccbd1cbad9 | ||
|
|
4b397670a1 | ||
|
|
89bfc150f3 | ||
|
|
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 | ||
|
|
24cd1f6fac | ||
|
|
5223bef5e7 | ||
|
|
105eac7e1d | ||
|
|
c1d6ed27aa | ||
|
|
614784e949 | ||
|
|
9376c017b4 | ||
|
|
b5d300e218 | ||
|
|
efb44484b0 | ||
|
|
0a2ad3d64f | ||
|
|
ffe2d3d5e4 | ||
|
|
81f4795aec | ||
|
|
596ed3dcf2 | ||
|
|
63c71fc331 | ||
|
|
0efb249d9b | ||
|
|
47c6e5aa9b | ||
|
|
687a4f50fd | ||
|
|
2372cdd7db | ||
|
|
596a7276ac | ||
|
|
a2d984272b | ||
|
|
e0f200f069 | ||
|
|
bb68b56c25 | ||
|
|
54e685a9d6 | ||
|
|
c64a80312c | ||
|
|
840f2c220c | ||
|
|
5325017a77 | ||
|
|
98d21d1760 | ||
|
|
a13806ef14 | ||
|
|
e8de04a1dd | ||
|
|
1dd098ba6b | ||
|
|
48de99a942 | ||
|
|
ac8caea2b1 | ||
|
|
fd22a3b19f | ||
|
|
e74df44a27 | ||
|
|
4cdf073c1d | ||
|
|
4179beca6d | ||
|
|
494a116cd3 | ||
|
|
6c7f8637f4 | ||
|
|
9955599a0a | ||
|
|
a360dc19c0 | ||
|
|
d9a547a3e5 | ||
|
|
762613f659 | ||
|
|
51112651d4 | ||
|
|
db0393a6f0 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -3,6 +3,7 @@ roslib.js linguist-vendored
|
|||||||
eventemitter2.js linguist-vendored
|
eventemitter2.js linguist-vendored
|
||||||
ros3d.js linguist-vendored
|
ros3d.js linguist-vendored
|
||||||
three.min.js linguist-vendored
|
three.min.js linguist-vendored
|
||||||
|
json-to-pretty-yaml.js linguist-vendored
|
||||||
aruco_pose/vendor/* linguist-vendored
|
aruco_pose/vendor/* linguist-vendored
|
||||||
blockly/* linguist-vendored
|
blockly/* linguist-vendored
|
||||||
highlight/* linguist-vendored
|
highlight/* linguist-vendored
|
||||||
|
|||||||
36
.github/workflows/build.yml
vendored
36
.github/workflows/build.yml
vendored
@@ -16,8 +16,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
|
||||||
|
|||||||
43
.github/workflows/docs.yml
vendored
43
.github/workflows/docs.yml
vendored
@@ -4,16 +4,21 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches: [ '*' ]
|
branches: [ '*' ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ master ]
|
branches: [ '*' ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docs:
|
docs:
|
||||||
runs-on: ubuntu-18.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Cancel previous runs
|
|
||||||
uses: styfle/cancel-workflow-action@0.9.1
|
|
||||||
with:
|
|
||||||
access_token: ${{ github.token }}
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Use Node.js
|
- name: Use Node.js
|
||||||
uses: actions/setup-node@v1
|
uses: actions/setup-node@v1
|
||||||
@@ -58,11 +63,23 @@ jobs:
|
|||||||
rm -f _book/clover*.pdf
|
rm -f _book/clover*.pdf
|
||||||
wget --no-verbose https://clover.coex.tech/clover_ru.pdf -P _book/
|
wget --no-verbose https://clover.coex.tech/clover_ru.pdf -P _book/
|
||||||
wget --no-verbose https://clover.coex.tech/clover_en.pdf -P _book/
|
wget --no-verbose https://clover.coex.tech/clover_en.pdf -P _book/
|
||||||
- name: Deploy
|
- name: Upload artifact
|
||||||
uses: JamesIves/github-pages-deploy-action@4.1.3
|
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
uses: actions/upload-pages-artifact@v1
|
||||||
with:
|
with:
|
||||||
branch: gh-pages
|
path: _book
|
||||||
folder: _book
|
|
||||||
clean: true
|
deploy-docs:
|
||||||
single-commit: true # to avoid multiple copies of large pdf files
|
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: true
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: docs
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v1
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ project(aruco_pose)
|
|||||||
## Compile as C++11, supported in ROS Kinetic and newer
|
## Compile as C++11, supported in ROS Kinetic and newer
|
||||||
add_compile_options(-std=c++11)
|
add_compile_options(-std=c++11)
|
||||||
|
|
||||||
add_compile_options(-Wall -Wextra -Werror)
|
|
||||||
|
|
||||||
## Find catkin macros and libraries
|
## Find catkin macros and libraries
|
||||||
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
|
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
|
||||||
## is used, also find other catkin packages
|
## is used, also find other catkin packages
|
||||||
@@ -121,6 +119,7 @@ generate_messages(
|
|||||||
## Generate dynamic reconfigure parameters in the 'cfg' folder
|
## Generate dynamic reconfigure parameters in the 'cfg' folder
|
||||||
generate_dynamic_reconfigure_options(
|
generate_dynamic_reconfigure_options(
|
||||||
cfg/Detector.cfg
|
cfg/Detector.cfg
|
||||||
|
cfg/Map.cfg
|
||||||
)
|
)
|
||||||
|
|
||||||
###################################
|
###################################
|
||||||
@@ -252,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,10 +72,12 @@ 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)
|
||||||
|
* `~image_axis` – whether debug image should contain axis (default: true)
|
||||||
* `~dictionary` (*int*) – ArUco dictionary (default: 2) - should be the same as `dictionary` parameter of `aruco_detect` nodelet
|
* `~dictionary` (*int*) – ArUco dictionary (default: 2) - should be the same as `dictionary` parameter of `aruco_detect` nodelet
|
||||||
|
|
||||||
Map file has one marker per line with the following line format:
|
Map file has one marker per line with the following line format:
|
||||||
|
|||||||
14
aruco_pose/cfg/Map.cfg
Normal file
14
aruco_pose/cfg/Map.cfg
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
PACKAGE = "aruco_pose"
|
||||||
|
|
||||||
|
from dynamic_reconfigure.parameter_generator_catkin import *
|
||||||
|
|
||||||
|
gen = ParameterGenerator()
|
||||||
|
|
||||||
|
gen.add("enabled", bool_t, 0, "if map detection enabled", True)
|
||||||
|
|
||||||
|
gen.add("map", str_t, 0, "full path for the map file")
|
||||||
|
|
||||||
|
gen.add("image_axis", bool_t, 0, "debug image axis", default=True)
|
||||||
|
|
||||||
|
exit(gen.generate(PACKAGE, "aruco_pose", "Map"))
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<?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.23.0</version>
|
||||||
<description>Positioning with ArUco markers</description>
|
<description>Positioning with ArUco markers</description>
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -71,11 +71,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 +96,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 +105,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,6 +137,7 @@ 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, "bgr8")->image;
|
||||||
|
|
||||||
@@ -140,7 +145,7 @@ private:
|
|||||||
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 +180,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 +206,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_);
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -19,11 +19,13 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <memory>
|
||||||
#include <ros/ros.h>
|
#include <ros/ros.h>
|
||||||
#include <nodelet/nodelet.h>
|
#include <nodelet/nodelet.h>
|
||||||
#include <pluginlib/class_list_macros.h>
|
#include <pluginlib/class_list_macros.h>
|
||||||
#include <image_transport/image_transport.h>
|
#include <image_transport/image_transport.h>
|
||||||
#include <cv_bridge/cv_bridge.h>
|
#include <cv_bridge/cv_bridge.h>
|
||||||
|
#include <dynamic_reconfigure/server.h>
|
||||||
#include <tf/transform_datatypes.h>
|
#include <tf/transform_datatypes.h>
|
||||||
#include <tf2_ros/buffer.h>
|
#include <tf2_ros/buffer.h>
|
||||||
#include <tf2_ros/transform_listener.h>
|
#include <tf2_ros/transform_listener.h>
|
||||||
@@ -41,6 +43,7 @@
|
|||||||
|
|
||||||
#include <aruco_pose/MarkerArray.h>
|
#include <aruco_pose/MarkerArray.h>
|
||||||
#include <aruco_pose/Marker.h>
|
#include <aruco_pose/Marker.h>
|
||||||
|
#include <aruco_pose/MapConfig.h>
|
||||||
|
|
||||||
#include <opencv2/opencv.hpp>
|
#include <opencv2/opencv.hpp>
|
||||||
#include <opencv2/aruco.hpp>
|
#include <opencv2/aruco.hpp>
|
||||||
@@ -74,10 +77,13 @@ private:
|
|||||||
tf2_ros::StaticTransformBroadcaster static_br_;
|
tf2_ros::StaticTransformBroadcaster static_br_;
|
||||||
tf2_ros::Buffer tf_buffer_;
|
tf2_ros::Buffer tf_buffer_;
|
||||||
tf2_ros::TransformListener tf_listener_{tf_buffer_};
|
tf2_ros::TransformListener tf_listener_{tf_buffer_};
|
||||||
|
std::shared_ptr<dynamic_reconfigure::Server<aruco_pose::MapConfig>> dyn_srv_;
|
||||||
|
bool enabled_ = true;
|
||||||
|
std::string type_;
|
||||||
visualization_msgs::MarkerArray vis_array_;
|
visualization_msgs::MarkerArray vis_array_;
|
||||||
std::string known_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_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void onInit()
|
virtual void onInit()
|
||||||
@@ -96,10 +102,10 @@ public:
|
|||||||
static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(nh_priv_.param("dictionary", 2)));
|
static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(nh_priv_.param("dictionary", 2)));
|
||||||
camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
|
camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
|
||||||
|
|
||||||
std::string type, map;
|
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);
|
||||||
@@ -110,13 +116,13 @@ public:
|
|||||||
|
|
||||||
// createStripLine();
|
// createStripLine();
|
||||||
|
|
||||||
if (type == "map") {
|
if (type_ == "map") {
|
||||||
param(nh_priv_, "map", map);
|
map_ = nh_priv_.param<std::string>("map" , "");
|
||||||
loadMap(map);
|
loadMap(map_);
|
||||||
} else if (type == "gridboard") {
|
} else if (type_ == "gridboard") {
|
||||||
createGridBoard(nh_priv_);
|
createGridBoard(nh_priv_);
|
||||||
} else {
|
} else {
|
||||||
NODELET_FATAL("unknown type: %s", type.c_str());
|
NODELET_FATAL("unknown type: %s", type_.c_str());
|
||||||
ros::shutdown();
|
ros::shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,10 +130,7 @@ public:
|
|||||||
vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1, true);
|
vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1, true);
|
||||||
debug_pub_ = it_priv.advertise("debug", 1);
|
debug_pub_ = it_priv.advertise("debug", 1);
|
||||||
|
|
||||||
publishMarkersFrames();
|
publishMap();
|
||||||
publishMarkers();
|
|
||||||
publishMapImage();
|
|
||||||
vis_markers_pub_.publish(vis_array_);
|
|
||||||
|
|
||||||
image_sub_.subscribe(nh_, "image_raw", 1);
|
image_sub_.subscribe(nh_, "image_raw", 1);
|
||||||
info_sub_.subscribe(nh_, "camera_info", 1);
|
info_sub_.subscribe(nh_, "camera_info", 1);
|
||||||
@@ -136,6 +139,12 @@ public:
|
|||||||
sync_.reset(new message_filters::Synchronizer<SyncPolicy>(SyncPolicy(10), image_sub_, info_sub_, markers_sub_));
|
sync_.reset(new message_filters::Synchronizer<SyncPolicy>(SyncPolicy(10), image_sub_, info_sub_, markers_sub_));
|
||||||
sync_->registerCallback(boost::bind(&ArucoMap::callback, this, _1, _2, _3));
|
sync_->registerCallback(boost::bind(&ArucoMap::callback, this, _1, _2, _3));
|
||||||
|
|
||||||
|
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<aruco_pose::MapConfig>>(nh_priv_);
|
||||||
|
dynamic_reconfigure::Server<aruco_pose::MapConfig>::CallbackType cb;
|
||||||
|
|
||||||
|
cb = std::bind(&ArucoMap::paramCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||||
|
dyn_srv_->setCallback(cb);
|
||||||
|
|
||||||
NODELET_INFO("ready");
|
NODELET_INFO("ready");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,6 +152,9 @@ public:
|
|||||||
const sensor_msgs::CameraInfoConstPtr& cinfo,
|
const sensor_msgs::CameraInfoConstPtr& cinfo,
|
||||||
const aruco_pose::MarkerArrayConstPtr& markers)
|
const aruco_pose::MarkerArrayConstPtr& markers)
|
||||||
{
|
{
|
||||||
|
if (!enabled_) return;
|
||||||
|
if (markers->markers.empty()) return; // map not loaded
|
||||||
|
|
||||||
int valid = 0;
|
int valid = 0;
|
||||||
int count = markers->markers.size();
|
int count = markers->markers.size();
|
||||||
std::vector<int> ids;
|
std::vector<int> ids;
|
||||||
@@ -166,7 +178,7 @@ public:
|
|||||||
corners.push_back(marker_corners);
|
corners.push_back(marker_corners);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (known_tilt_.empty()) {
|
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);
|
||||||
@@ -180,7 +192,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;
|
||||||
|
|
||||||
@@ -192,11 +204,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;
|
||||||
@@ -268,9 +280,17 @@ publish_debug:
|
|||||||
std::ifstream f(filename);
|
std::ifstream f(filename);
|
||||||
std::string line;
|
std::string line;
|
||||||
|
|
||||||
|
clearMarkers();
|
||||||
|
|
||||||
|
if (map_ == "") {
|
||||||
|
NODELET_INFO("No map loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!f.good()) {
|
if (!f.good()) {
|
||||||
NODELET_FATAL("%s - %s", strerror(errno), filename.c_str());
|
NODELET_ERROR("%s - %s", strerror(errno), filename.c_str());
|
||||||
ros::shutdown();
|
map_ = "";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (std::getline(f, line)) {
|
while (std::getline(f, line)) {
|
||||||
@@ -296,9 +316,10 @@ publish_debug:
|
|||||||
s.putback(first);
|
s.putback(first);
|
||||||
} else {
|
} else {
|
||||||
// Probably garbage data; inform user and throw an exception, possibly killing nodelet
|
// Probably garbage data; inform user and throw an exception, possibly killing nodelet
|
||||||
NODELET_FATAL("Malformed input: %s", line.c_str());
|
NODELET_ERROR("Malformed input: %s", line.c_str());
|
||||||
ros::shutdown();
|
map_ = "";
|
||||||
throw std::runtime_error("Malformed input");
|
clearMarkers();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(s >> id >> length >> x >> y)) {
|
if (!(s >> id >> length >> x >> y)) {
|
||||||
@@ -329,6 +350,14 @@ publish_debug:
|
|||||||
NODELET_INFO("loading %s complete (%d markers)", filename.c_str(), static_cast<int>(board_->ids.size()));
|
NODELET_INFO("loading %s complete (%d markers)", filename.c_str(), static_cast<int>(board_->ids.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void publishMap()
|
||||||
|
{
|
||||||
|
publishMarkersFrames();
|
||||||
|
publishMarkers();
|
||||||
|
publishMapImage();
|
||||||
|
vis_markers_pub_.publish(vis_array_);
|
||||||
|
}
|
||||||
|
|
||||||
void createGridBoard(ros::NodeHandle& nh)
|
void createGridBoard(ros::NodeHandle& nh)
|
||||||
{
|
{
|
||||||
NODELET_INFO("generate gridboard");
|
NODELET_INFO("generate gridboard");
|
||||||
@@ -370,6 +399,15 @@ publish_debug:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void clearMarkers()
|
||||||
|
{
|
||||||
|
board_->ids.clear();
|
||||||
|
board_->objPoints.clear();
|
||||||
|
markers_.markers.clear();
|
||||||
|
vis_array_.markers.clear();
|
||||||
|
markers_transforms_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// void createStripLine()
|
// void createStripLine()
|
||||||
// {
|
// {
|
||||||
// visualization_msgs::Marker marker;
|
// visualization_msgs::Marker marker;
|
||||||
@@ -466,7 +504,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);
|
||||||
|
|
||||||
@@ -509,6 +547,22 @@ publish_debug:
|
|||||||
msg.image = image;
|
msg.image = image;
|
||||||
img_pub_.publish(msg.toImageMsg());
|
img_pub_.publish(msg.toImageMsg());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void paramCallback(aruco_pose::MapConfig &config, uint32_t level)
|
||||||
|
{
|
||||||
|
// https://github.com/CopterExpress/clover/commit/2cd334c474e3ed04ef65ca1ba7f08ab535a3dc6d#diff-942723f9452c398ae93f1a91427f9a7b614be5e5871f8a3e590f324d804f0d58R356
|
||||||
|
enabled_ = config.enabled;
|
||||||
|
if (type_ == "map" && config.map != map_) {
|
||||||
|
map_ = config.map;
|
||||||
|
loadMap(map_);
|
||||||
|
publishMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.image_axis != image_axis_) {
|
||||||
|
image_axis_ = config.image_axis;
|
||||||
|
publishMapImage();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
PLUGINLIB_EXPORT_CLASS(ArucoMap, nodelet::Nodelet)
|
PLUGINLIB_EXPORT_CLASS(ArucoMap, nodelet::Nodelet)
|
||||||
|
|||||||
@@ -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
BIN
aruco_pose/test/duplicate.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 62 KiB |
8
aruco_pose/test/duplicate.py
Normal file
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
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>
|
||||||
@@ -2,6 +2,7 @@ import rospy
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from visualization_msgs.msg import MarkerArray as VisMarkerArray
|
from visualization_msgs.msg import MarkerArray as VisMarkerArray
|
||||||
|
from aruco_pose.msg import MarkerArray
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -9,5 +10,5 @@ def node():
|
|||||||
return rospy.init_node('aruco_pose_test', anonymous=True)
|
return rospy.init_node('aruco_pose_test', anonymous=True)
|
||||||
|
|
||||||
def test_node_failure(node):
|
def test_node_failure(node):
|
||||||
with pytest.raises(rospy.exceptions.ROSException):
|
assert rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5).markers == []
|
||||||
rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
|
assert rospy.wait_for_message('aruco_map/map', MarkerArray, timeout=5).markers == []
|
||||||
|
|||||||
@@ -151,6 +151,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
|
||||||
|
|||||||
@@ -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,12 @@ import tf2_geometry_msgs
|
|||||||
import VL53L1X
|
import VL53L1X
|
||||||
import pymavlink
|
import pymavlink
|
||||||
from pymavlink import mavutil
|
from pymavlink import mavutil
|
||||||
import rpi_ws281x
|
|
||||||
import pigpio
|
|
||||||
# from espeak import espeak
|
# from espeak import espeak
|
||||||
from pyzbar import pyzbar
|
from pyzbar import pyzbar
|
||||||
|
import docopt
|
||||||
|
|
||||||
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 ws281x) == "0.0.13" ]]
|
||||||
[[ $(rosversion cv_camera) == "0.5.1" ]] # patched version with init fix
|
|
||||||
[[ $(rosversion ws281x) == "0.0.12" ]]
|
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" ]
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ project(clover)
|
|||||||
## Compile as C++11, supported in ROS Kinetic and newer
|
## Compile as C++11, supported in ROS Kinetic and newer
|
||||||
add_compile_options(-std=c++11)
|
add_compile_options(-std=c++11)
|
||||||
|
|
||||||
add_compile_options(-Wall -Wextra -Werror)
|
|
||||||
|
|
||||||
## Find catkin macros and libraries
|
## Find catkin macros and libraries
|
||||||
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
|
## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz)
|
||||||
## is used, also find other catkin packages
|
## is used, also find other catkin packages
|
||||||
@@ -19,6 +17,7 @@ find_package(catkin REQUIRED COMPONENTS
|
|||||||
message_generation
|
message_generation
|
||||||
geometry_msgs
|
geometry_msgs
|
||||||
sensor_msgs
|
sensor_msgs
|
||||||
|
led_msgs
|
||||||
geographic_msgs
|
geographic_msgs
|
||||||
tf
|
tf
|
||||||
tf2
|
tf2
|
||||||
@@ -54,7 +53,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 ##
|
||||||
|
|||||||
64
clover/examples/camera.py
Normal file
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()
|
||||||
@@ -18,8 +18,9 @@
|
|||||||
<remap from="map_markers" to="aruco_map/map"/>
|
<remap from="map_markers" to="aruco_map/map"/>
|
||||||
<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 +36,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)"/>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<arg name="led" default="true"/>
|
<arg name="led" default="true"/>
|
||||||
<arg name="blocks" default="false"/>
|
<arg name="blocks" default="false"/>
|
||||||
<arg name="rc" default="false"/>
|
<arg name="rc" default="false"/>
|
||||||
<arg name="force_init" value="true"/> <!-- force estimator to init by publishing zero pose -->
|
<arg name="force_init" default="true"/> <!-- force estimator to init by publishing zero pose -->
|
||||||
|
|
||||||
<arg name="simulator" default="false"/> <!-- flag that we are operating on a simulated drone -->
|
<arg name="simulator" default="false"/> <!-- flag that we are operating on a simulated drone -->
|
||||||
|
|
||||||
@@ -45,10 +45,9 @@
|
|||||||
<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="false"/>
|
||||||
</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"/>
|
||||||
@@ -86,8 +85,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>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
<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="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 +45,8 @@
|
|||||||
<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)"/>
|
||||||
</launch>
|
</launch>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<launch>
|
<launch>
|
||||||
<arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl -->
|
<arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl, hitl -->
|
||||||
<arg name="fcu_ip" default="127.0.0.1"/>
|
<arg name="fcu_ip" default="127.0.0.1"/>
|
||||||
<arg name="fcu_sys_id" default="1"/>
|
<arg name="fcu_sys_id" default="1"/>
|
||||||
<arg name="gcs_bridge" default="tcp"/>
|
<arg name="gcs_bridge" default="tcp"/>
|
||||||
@@ -23,6 +23,9 @@
|
|||||||
<!-- sitl since PX4 1.9.0 -->
|
<!-- sitl since PX4 1.9.0 -->
|
||||||
<param name="fcu_url" value="udp://@$(arg fcu_ip):14580" if="$(eval fcu_conn == 'sitl')"/>
|
<param name="fcu_url" value="udp://@$(arg fcu_ip):14580" if="$(eval fcu_conn == 'sitl')"/>
|
||||||
|
|
||||||
|
<!-- hitl connection (to gazebo_mavlink_interface plugin) -->
|
||||||
|
<param name="fcu_url" value="udp://$(arg fcu_ip):14540@" if="$(eval fcu_conn == 'hitl')"/>
|
||||||
|
|
||||||
<!-- set target_system_id -->
|
<!-- set target_system_id -->
|
||||||
<param name="target_system_id" value="$(arg fcu_sys_id)" />
|
<param name="target_system_id" value="$(arg fcu_sys_id)" />
|
||||||
|
|
||||||
|
|||||||
4
clover/launch/simulator.launch
Normal file
4
clover/launch/simulator.launch
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<launch>
|
||||||
|
<!-- shurtcut for running the simulation (`roslaunch clover simulator.launch`) -->
|
||||||
|
<include file="$(find clover_simulation)/launch/simulator.launch"/>
|
||||||
|
</launch>
|
||||||
@@ -37,12 +37,13 @@
|
|||||||
<depend>rosbridge_server</depend>
|
<depend>rosbridge_server</depend>
|
||||||
<depend>web_video_server</depend>
|
<depend>web_video_server</depend>
|
||||||
<depend>tf2_web_republisher</depend>
|
<depend>tf2_web_republisher</depend>
|
||||||
|
<depend>libxml2</depend>
|
||||||
|
<depend>libxslt</depend>
|
||||||
<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>
|
||||||
<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==1.1.1
|
||||||
docopt==0.6.2
|
|
||||||
geopy==1.11.0
|
geopy==1.11.0
|
||||||
smbus2==0.3.0
|
smbus2==0.3.0
|
||||||
VL53L1X==0.0.5
|
VL53L1X==0.0.5
|
||||||
|
|||||||
11
clover/setup.py
Normal file
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))
|
||||||
@@ -67,12 +72,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')
|
||||||
|
|||||||
35
clover/src/clover/__init__.py
Normal file
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
|
||||||
@@ -31,7 +31,6 @@ ros::Time start_time;
|
|||||||
double blink_rate, blink_fast_rate, flash_delay, fade_period, wipe_period, rainbow_period;
|
double blink_rate, blink_fast_rate, flash_delay, fade_period, wipe_period, rainbow_period;
|
||||||
double low_battery_threshold;
|
double low_battery_threshold;
|
||||||
std::vector<std::string> error_ignore;
|
std::vector<std::string> error_ignore;
|
||||||
bool blink_state;
|
|
||||||
led_msgs::SetLEDs set_leds;
|
led_msgs::SetLEDs set_leds;
|
||||||
led_msgs::LEDStateArray state, start_state;
|
led_msgs::LEDStateArray state, start_state;
|
||||||
ros::ServiceClient set_leds_srv;
|
ros::ServiceClient set_leds_srv;
|
||||||
@@ -87,9 +86,8 @@ void proceed(const ros::TimerEvent& event)
|
|||||||
set_leds.request.leds.resize(led_count);
|
set_leds.request.leds.resize(led_count);
|
||||||
|
|
||||||
if (current_effect.effect == "blink" || current_effect.effect == "blink_fast") {
|
if (current_effect.effect == "blink" || current_effect.effect == "blink_fast") {
|
||||||
blink_state = !blink_state;
|
// enable on odd counter
|
||||||
// toggle all leds
|
if (counter % 2 != 0) {
|
||||||
if (blink_state) {
|
|
||||||
fill(current_effect.r, current_effect.g, current_effect.b);
|
fill(current_effect.r, current_effect.g, current_effect.b);
|
||||||
} else {
|
} else {
|
||||||
fill(0, 0, 0);
|
fill(0, 0, 0);
|
||||||
@@ -222,6 +220,7 @@ bool setEffect(clover::SetLEDEffect::Request& req, clover::SetLEDEffect::Respons
|
|||||||
counter = 0;
|
counter = 0;
|
||||||
start_state = state;
|
start_state = state;
|
||||||
start_time = ros::Time::now();
|
start_time = ros::Time::now();
|
||||||
|
proceed({ .current_real = start_time });
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -320,8 +319,8 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
auto set_effect = nh.advertiseService("set_effect", &setEffect);
|
auto set_effect = nh.advertiseService("set_effect", &setEffect);
|
||||||
|
|
||||||
auto mavros_state_sub = nh.subscribe("/mavros/state", 1, &handleMavrosState);
|
auto mavros_state_sub = nh.subscribe("mavros/state", 1, &handleMavrosState);
|
||||||
auto battery_sub = nh.subscribe("/mavros/battery", 1, &handleBattery);
|
auto battery_sub = nh.subscribe("mavros/battery", 1, &handleBattery);
|
||||||
auto rosout_sub = nh.subscribe("/rosout_agg", 1, &handleLog);
|
auto rosout_sub = nh.subscribe("/rosout_agg", 1, &handleLog);
|
||||||
|
|
||||||
timer = nh.createTimer(ros::Duration(0), &proceed, false, false);
|
timer = nh.createTimer(ros::Duration(0), &proceed, false, false);
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -154,7 +169,7 @@ private:
|
|||||||
|
|
||||||
img.convertTo(curr_, CV_32F);
|
img.convertTo(curr_, CV_32F);
|
||||||
|
|
||||||
if (prev_.empty()) {
|
if (prev_.empty() || (msg->header.stamp - prev_stamp_).toSec() > 0.1) { // outdated previous frame
|
||||||
prev_ = curr_.clone();
|
prev_ = curr_.clone();
|
||||||
prev_stamp_ = msg->header.stamp;
|
prev_stamp_ = msg->header.stamp;
|
||||||
cv::createHanningWindow(hann_, curr_.size(), CV_32F);
|
cv::createHanningWindow(hann_, curr_.size(), CV_32F);
|
||||||
@@ -236,6 +251,15 @@ 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 image
|
// Publish debug image
|
||||||
if (img_pub_.getNumSubscribers() > 0) {
|
if (img_pub_.getNumSubscribers() > 0) {
|
||||||
// publish debug image
|
// publish debug image
|
||||||
@@ -247,14 +271,6 @@ private:
|
|||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,13 +292,17 @@ private:
|
|||||||
return flow;
|
return flow;
|
||||||
}
|
}
|
||||||
|
|
||||||
void paramCallback(clover::FlowConfig &config, [[maybe_unused]] uint32_t level)
|
void paramCallback(clover::FlowConfig &config, uint32_t level)
|
||||||
{
|
{
|
||||||
enabled_ = config.enabled;
|
enabled_ = config.enabled;
|
||||||
if (!enabled_) {
|
if (!enabled_) {
|
||||||
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)
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ private:
|
|||||||
void fakeGCSThread()
|
void fakeGCSThread()
|
||||||
{
|
{
|
||||||
// Awful workaround for fixing PX4 not sending STATUSTEXTs
|
// Awful workaround for fixing PX4 not sending STATUSTEXTs
|
||||||
// if there is no GCS hearbeats.
|
// if there is no GCS heartbeats.
|
||||||
// TODO: use timer
|
// TODO: use timer
|
||||||
// TODO: remove, when PX4 get this fixed.
|
// TODO: remove, when PX4 get this fixed.
|
||||||
ros::Publisher mavlink_pub = nh.advertise<mavros_msgs::Mavlink>("mavlink/to", 1);
|
ros::Publisher mavlink_pub = nh.advertise<mavros_msgs::Mavlink>("mavlink/to", 1);
|
||||||
|
|||||||
@@ -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):
|
||||||
try:
|
try:
|
||||||
res = param_get(param_id=name)
|
res = param_get(param_id=name)
|
||||||
except rospy.ServiceException as e:
|
except rospy.ServiceException as e:
|
||||||
@@ -101,12 +116,17 @@ def get_param(name):
|
|||||||
|
|
||||||
if not res.success:
|
if not res.success:
|
||||||
failure('unable to retrieve PX4 parameter %s', name)
|
failure('unable to retrieve PX4 parameter %s', name)
|
||||||
|
return default
|
||||||
else:
|
else:
|
||||||
if res.value.integer != 0:
|
if res.value.integer != 0:
|
||||||
return res.value.integer
|
return res.value.integer
|
||||||
return res.value.real
|
return res.value.real
|
||||||
|
|
||||||
|
|
||||||
|
def get_paramf(name, precision=2):
|
||||||
|
return ff(get_param(name), precision)
|
||||||
|
|
||||||
|
|
||||||
recv_event = Event()
|
recv_event = Event()
|
||||||
link = mavutil.mavlink.MAVLink('', 255, 1)
|
link = mavutil.mavlink.MAVLink('', 255, 1)
|
||||||
mavlink_pub = rospy.Publisher('mavlink/to', Mavlink, queue_size=1)
|
mavlink_pub = rospy.Publisher('mavlink/to', Mavlink, queue_size=1)
|
||||||
@@ -151,6 +171,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,29 +234,31 @@ def check_fcu():
|
|||||||
state = rospy.wait_for_message('mavros/state', State, timeout=3)
|
state = rospy.wait_for_message('mavros/state', State, timeout=3)
|
||||||
if not state.connected:
|
if not state.connected:
|
||||||
failure('no connection to the FCU (check wiring)')
|
failure('no connection to the FCU (check wiring)')
|
||||||
|
info('fcu_url = %s', rospy.get_param('mavros/fcu_url', '?'))
|
||||||
return
|
return
|
||||||
|
|
||||||
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:
|
||||||
@@ -255,21 +295,29 @@ 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 (check wiring)')
|
||||||
|
info('fcu_url = %s', rospy.get_param('mavros/fcu_url', '?'))
|
||||||
|
|
||||||
|
|
||||||
def describe_direction(v):
|
def describe_direction(v):
|
||||||
@@ -336,7 +384,7 @@ def is_process_running(binary, exact=False, full=False):
|
|||||||
if exact:
|
if exact:
|
||||||
args.append('-x') # match exactly with the command name
|
args.append('-x') # match exactly with the command name
|
||||||
if full:
|
if full:
|
||||||
args.append('-f') # use full process name to match
|
args.append('-f') # use full command line (including arguments) to match
|
||||||
args.append(binary)
|
args.append(binary)
|
||||||
subprocess.check_output(args)
|
subprocess.check_output(args)
|
||||||
return True
|
return True
|
||||||
@@ -346,19 +394,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 +420,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,14 +486,14 @@ 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 = 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')
|
fuse = get_param('EKF2_AID_MASK')
|
||||||
if not fuse & (1 << 3):
|
if not fuse & (1 << 3):
|
||||||
@@ -430,10 +502,10 @@ def check_vpe():
|
|||||||
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
|
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,13 +603,19 @@ 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', 0) & (1 << 0)):
|
||||||
|
failure('enabled GPS fusion may suppress vision position aiding')
|
||||||
|
|
||||||
|
|
||||||
@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)
|
||||||
@@ -545,7 +623,7 @@ 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 = get_param('LPE_FUSION')
|
||||||
@@ -553,32 +631,36 @@ def check_optical_flow():
|
|||||||
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')
|
fuse = get_param('EKF2_AID_MASK', 0)
|
||||||
if not fuse & (1 << 1):
|
if not fuse & (1 << 1):
|
||||||
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
|
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
|
||||||
delay = get_param('EKF2_OF_DELAY')
|
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')
|
||||||
@@ -602,7 +684,7 @@ 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 = 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:
|
||||||
@@ -623,6 +705,10 @@ def check_rangefinder():
|
|||||||
|
|
||||||
@check('Boot duration')
|
@check('Boot duration')
|
||||||
def check_boot_duration():
|
def check_boot_duration():
|
||||||
|
if not os.path.exists('/etc/clover_version'):
|
||||||
|
info('skip check')
|
||||||
|
return # Don't check not on Clover's image
|
||||||
|
|
||||||
output = subprocess.check_output('systemd-analyze').decode()
|
output = subprocess.check_output('systemd-analyze').decode()
|
||||||
r = re.compile(r'([\d\.]+)s\s*$', flags=re.MULTILINE)
|
r = re.compile(r'([\d\.]+)s\s*$', flags=re.MULTILINE)
|
||||||
duration = float(r.search(output).groups()[0])
|
duration = float(r.search(output).groups()[0])
|
||||||
@@ -632,7 +718,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')
|
||||||
@@ -648,6 +734,9 @@ def check_cpu_usage():
|
|||||||
|
|
||||||
@check('clover.service')
|
@check('clover.service')
|
||||||
def check_clover_service():
|
def check_clover_service():
|
||||||
|
if not os.path.exists('/etc/clover_version'):
|
||||||
|
return # Don't check not on Clover's image
|
||||||
|
|
||||||
try:
|
try:
|
||||||
output = subprocess.check_output('systemctl show -p ActiveState --value clover.service'.split(),
|
output = subprocess.check_output('systemctl show -p ActiveState --value clover.service'.split(),
|
||||||
stderr=subprocess.STDOUT).decode()
|
stderr=subprocess.STDOUT).decode()
|
||||||
@@ -698,11 +787,18 @@ 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')
|
||||||
def check_preflight_status():
|
def check_preflight_status():
|
||||||
|
if is_process_running('px4', exact=True):
|
||||||
|
info('can\'t check in SITL')
|
||||||
|
return
|
||||||
|
|
||||||
# Make sure the console is available to us
|
# Make sure the console is available to us
|
||||||
mavlink_exec('\n')
|
mavlink_exec('\n')
|
||||||
cmdr_output = mavlink_exec('commander check')
|
cmdr_output = mavlink_exec('commander check')
|
||||||
@@ -724,6 +820,10 @@ def check_preflight_status():
|
|||||||
|
|
||||||
@check('Network')
|
@check('Network')
|
||||||
def check_network():
|
def check_network():
|
||||||
|
if not os.path.exists('/etc/clover_version'):
|
||||||
|
# TODO:
|
||||||
|
return # Don't check not on Clover's image
|
||||||
|
|
||||||
ros_hostname = os.environ.get('ROS_HOSTNAME', '').strip()
|
ros_hostname = os.environ.get('ROS_HOSTNAME', '').strip()
|
||||||
|
|
||||||
if not ros_hostname:
|
if not ros_hostname:
|
||||||
@@ -801,26 +901,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__':
|
||||||
|
|||||||
@@ -150,6 +150,9 @@ void handleState(const mavros_msgs::State& s)
|
|||||||
inline void publishBodyFrame()
|
inline void publishBodyFrame()
|
||||||
{
|
{
|
||||||
if (body.child_frame_id.empty()) return;
|
if (body.child_frame_id.empty()) return;
|
||||||
|
if (!body.header.stamp.isZero() && body.header.stamp == local_position.header.stamp) {
|
||||||
|
return; // avoid TF_REPEATED_DATA warnings
|
||||||
|
}
|
||||||
|
|
||||||
tf::Quaternion q;
|
tf::Quaternion q;
|
||||||
q.setRPY(0, 0, tf::getYaw(local_position.pose.orientation));
|
q.setRPY(0, 0, tf::getYaw(local_position.pose.orientation));
|
||||||
@@ -503,10 +506,10 @@ inline void checkManualControl()
|
|||||||
|
|
||||||
if (check_kill_switch) {
|
if (check_kill_switch) {
|
||||||
// switch values: https://github.com/PX4/PX4-Autopilot/blob/c302514a0809b1765fafd13c014d705446ae1113/msg/manual_control_setpoint.msg#L3
|
// switch values: https://github.com/PX4/PX4-Autopilot/blob/c302514a0809b1765fafd13c014d705446ae1113/msg/manual_control_setpoint.msg#L3
|
||||||
[[maybe_unused]] const uint8_t SWITCH_POS_NONE = 0; // switch is not mapped
|
const uint8_t SWITCH_POS_NONE = 0; // switch is not mapped
|
||||||
[[maybe_unused]] const uint8_t SWITCH_POS_ON = 1; // switch activated
|
const uint8_t SWITCH_POS_ON = 1; // switch activated
|
||||||
[[maybe_unused]] const uint8_t SWITCH_POS_MIDDLE = 2; // middle position
|
const uint8_t SWITCH_POS_MIDDLE = 2; // middle position
|
||||||
[[maybe_unused]] const uint8_t SWITCH_POS_OFF = 3; // switch not activated
|
const uint8_t SWITCH_POS_OFF = 3; // switch not activated
|
||||||
|
|
||||||
const int KILL_SWITCH_BIT = 12; // https://github.com/PX4/Firmware/blob/c302514a0809b1765fafd13c014d705446ae1113/src/modules/mavlink/mavlink_messages.cpp#L3975
|
const int KILL_SWITCH_BIT = 12; // https://github.com/PX4/Firmware/blob/c302514a0809b1765fafd13c014d705446ae1113/src/modules/mavlink/mavlink_messages.cpp#L3975
|
||||||
uint8_t kill_switch = (manual_control.buttons & (0b11 << KILL_SWITCH_BIT)) >> KILL_SWITCH_BIT;
|
uint8_t kill_switch = (manual_control.buttons & (0b11 << KILL_SWITCH_BIT)) >> KILL_SWITCH_BIT;
|
||||||
@@ -808,7 +811,7 @@ 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.pitch_rate, req.roll_rate, req.yaw_rate, NAN, NAN, req.thrust, NAN, "", req.auto_arm, res.success, res.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool land([[maybe_unused]] std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (busy)
|
if (busy)
|
||||||
@@ -857,6 +860,13 @@ bool land([[maybe_unused]] std_srvs::Trigger::Request& req, std_srvs::Trigger::R
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool release(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||||
|
{
|
||||||
|
setpoint_timer.stop();
|
||||||
|
res.success = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
ros::init(argc, argv, "simple_offboard");
|
ros::init(argc, argv, "simple_offboard");
|
||||||
@@ -930,6 +940,7 @@ int main(int argc, char **argv)
|
|||||||
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);
|
||||||
|
|||||||
@@ -25,7 +25,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;
|
||||||
@@ -33,14 +33,14 @@ ros::Subscriber local_position_sub;
|
|||||||
ros::Timer zero_timer;
|
ros::Timer zero_timer;
|
||||||
PoseStamped vpe, pose;
|
PoseStamped vpe, pose;
|
||||||
ros::Time got_local_pos(0);
|
ros::Time got_local_pos(0);
|
||||||
ros::Duration publish_zero_timout, publish_zero_duration, offset_timeout;
|
ros::Duration publish_zero_timeout, publish_zero_duration, offset_timeout;
|
||||||
TransformStamped offset;
|
TransformStamped offset;
|
||||||
|
|
||||||
void publishZero(const ros::TimerEvent& e)
|
void publishZero(const ros::TimerEvent& e)
|
||||||
{
|
{
|
||||||
if (!vpe.header.stamp.isZero() && e.current_real - vpe.header.stamp < publish_zero_timout) return; // have vpe
|
if (!vpe.header.stamp.isZero() && e.current_real - vpe.header.stamp < publish_zero_timeout) return; // have vpe
|
||||||
|
|
||||||
if (!pose.header.stamp.isZero() && e.current_real - pose.header.stamp < publish_zero_timout) { // have local position
|
if (!pose.header.stamp.isZero() && e.current_real - pose.header.stamp < publish_zero_timeout) { // have local position
|
||||||
if (got_local_pos.isZero()) {
|
if (got_local_pos.isZero()) {
|
||||||
ROS_INFO("got local position");
|
ROS_INFO("got local position");
|
||||||
got_local_pos = e.current_real;
|
got_local_pos = e.current_real;
|
||||||
@@ -109,7 +109,7 @@ void callback(const T& msg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool reset([[maybe_unused]] std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
bool reset(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||||
{
|
{
|
||||||
reset_flag = true;
|
reset_flag = true;
|
||||||
res.success = true;
|
res.success = true;
|
||||||
@@ -124,8 +124,8 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
nh_priv.param<string>("frame_id", frame_id, "");
|
nh_priv.param<string>("frame_id", frame_id, "");
|
||||||
nh_priv.param<string>("offset_frame_id", offset_frame_id, "");
|
nh_priv.param<string>("offset_frame_id", offset_frame_id, "");
|
||||||
nh_priv.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
|
nh.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
|
||||||
nh_priv.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link");
|
nh.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link");
|
||||||
offset_timeout = ros::Duration(nh_priv.param("offset_timeout", 3.0));
|
offset_timeout = ros::Duration(nh_priv.param("offset_timeout", 3.0));
|
||||||
|
|
||||||
if (!frame_id.empty()) {
|
if (!frame_id.empty()) {
|
||||||
@@ -144,7 +144,7 @@ int main(int argc, char **argv) {
|
|||||||
if (nh_priv.param("force_init", false) || nh_priv.param("publish_zero", false)) { // publish_zero is old name
|
if (nh_priv.param("force_init", false) || nh_priv.param("publish_zero", false)) { // publish_zero is old name
|
||||||
// publish zero to initialize the local position
|
// publish zero to initialize the local position
|
||||||
zero_timer = nh.createTimer(ros::Duration(0.1), &publishZero);
|
zero_timer = nh.createTimer(ros::Duration(0.1), &publishZero);
|
||||||
publish_zero_timout = ros::Duration(nh_priv.param("force_init_timeout", 5.0));
|
publish_zero_timeout = ros::Duration(nh_priv.param("force_init_timeout", 5.0));
|
||||||
publish_zero_duration = ros::Duration(nh_priv.param("force_init_duration", 5.0));
|
publish_zero_duration = ros::Duration(nh_priv.param("force_init_duration", 5.0));
|
||||||
local_position_sub = nh.subscribe("mavros/local_position/pose", 1, &localPositionCallback);
|
local_position_sub = nh.subscribe("mavros/local_position/pose", 1, &localPositionCallback);
|
||||||
}
|
}
|
||||||
|
|||||||
4
clover/src/www
Executable file
4
clover/src/www
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
export ROSWWW_DEFAULT=clover
|
||||||
|
rosrun roswww_static update
|
||||||
@@ -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,9 @@
|
|||||||
|
|
||||||
<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"/>
|
||||||
|
|
||||||
<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>
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ function viewTopicsList() {
|
|||||||
let rosdistro;
|
let rosdistro;
|
||||||
|
|
||||||
function viewTopic(topic) {
|
function viewTopic(topic) {
|
||||||
|
let counter = 0;
|
||||||
let index = '<a href=topics.html>Topics</a>';
|
let index = '<a href=topics.html>Topics</a>';
|
||||||
title.innerHTML = `${index}: ${topic}`;
|
title.innerHTML = `${index}: ${topic}`;
|
||||||
topicMessage.style.display = 'block';
|
topicMessage.style.display = 'block';
|
||||||
@@ -51,10 +52,11 @@ function viewTopic(topic) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
new ROSLIB.Topic({ ros: ros, name: topic }).subscribe(function(msg) {
|
new ROSLIB.Topic({ ros: ros, name: topic }).subscribe(function(msg) {
|
||||||
|
counter++;
|
||||||
document.title = topic;
|
document.title = topic;
|
||||||
if (mouseDown) return;
|
if (mouseDown) return;
|
||||||
|
|
||||||
if (msg.header.stamp) {
|
if (msg.header && msg.header.stamp) {
|
||||||
if (params.date || params.offset) {
|
if (params.date || params.offset) {
|
||||||
let date = new Date(msg.header.stamp.secs * 1e3 + msg.header.stamp.nsecs * 1e-6);
|
let date = new Date(msg.header.stamp.secs * 1e3 + msg.header.stamp.nsecs * 1e-6);
|
||||||
if (params.date) msg.header.date = date.toISOString();
|
if (params.date) msg.header.date = date.toISOString();
|
||||||
@@ -62,7 +64,8 @@ function viewTopic(topic) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
topicMessage.innerHTML = yamlStringify(msg); // JSON.stringify(msg, null, 4);
|
let txt = `<div class=counter>${counter} received</div>${yamlStringify(msg)}`; // JSON.stringify(msg, null, 4);
|
||||||
|
topicMessage.innerHTML = txt;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
white-space: pre;
|
white-space: pre;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
.counter { color: #b9b9b9; margin-bottom: 1em; }
|
||||||
#topic-type { font-family: monospace; font-size: 0.5em; vertical-align: super; font-weight: normal; }
|
#topic-type { font-family: monospace; font-size: 0.5em; vertical-align: super; font-weight: normal; }
|
||||||
.topic { font-family: monospace; }
|
.topic { font-family: monospace; }
|
||||||
body.closed { background-color: rgb(207, 207, 207); }
|
body.closed { background-color: rgb(207, 207, 207); }
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ The frontend files are located in [`www`](./www/) subdirectory. The frontend app
|
|||||||
### Services
|
### Services
|
||||||
|
|
||||||
* `~run` ([*clover_blocks/Run*](srv/Run.srv)) – run Blockly-generated program (in Python).
|
* `~run` ([*clover_blocks/Run*](srv/Run.srv)) – run Blockly-generated program (in Python).
|
||||||
* `~stop` ([*std_srvs/Trigger*](http://docs.ros.org/melodic/api/std_srvs/html/srv/Trigger.html)) – terminate the running program.
|
* `~stop` ([*std_srvs/Trigger*](http://docs.ros.org/noetic/api/std_srvs/html/srv/Trigger.html)) – terminate the running program.
|
||||||
* `~store` ([*clover_blocks/load*](srv/Store.srv)) – store a user program (to `<package_path>/programs` by default).
|
* `~store` ([*clover_blocks/load*](srv/Store.srv)) – store a user program (to `<package_path>/programs` by default).
|
||||||
* `~load` ([*clover_blocks/load*](srv/Load.srv)) – load all the stored programs.
|
* `~load` ([*clover_blocks/load*](srv/Load.srv)) – load all the stored programs.
|
||||||
|
|
||||||
@@ -45,11 +45,11 @@ http://<hostname>/clover_blocks/?navigate_tolerance=0.5&sleep_time=0.1
|
|||||||
|
|
||||||
#### Published
|
#### Published
|
||||||
|
|
||||||
* `~running` ([*std_msgs/Bool*](http://docs.ros.org/melodic/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/melodic/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).
|
||||||
* `~error` ([*std_msgs/String*](http://docs.ros.org/melodic/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).
|
||||||
|
|
||||||
This topic is published from the frontend side:
|
This topic is published from the frontend side:
|
||||||
|
|
||||||
* `~prompt/<request_id>` ([*std_msgs/String*](http://docs.ros.org/melodic/api/std_msgs/html/msg/String.html)) – user input response.
|
* `~prompt/<request_id>` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user input response.
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ The simulation may be configured by a set of arguments:
|
|||||||
|
|
||||||
* `mav_id` (*integer*, default: *0*) - MAVLink identifier of the vehicle. **Note**: Multi-vehicle simulation is possible, but requires extensive changes to launch files;
|
* `mav_id` (*integer*, default: *0*) - MAVLink identifier of the vehicle. **Note**: Multi-vehicle simulation is possible, but requires extensive changes to launch files;
|
||||||
* `est` (*string*, default: *lpe*, possible values: *lpe*, *ekf2*) - PX4 estimator selection. Note that this may be overriden in the startup scripts for your craft;
|
* `est` (*string*, default: *lpe*, possible values: *lpe*, *ekf2*) - PX4 estimator selection. Note that this may be overriden in the startup scripts for your craft;
|
||||||
* `vehicle` (*string*, default: *clover*) - PX4 vehicle name. Depending on this parameter, different PX4 presets will be loaded. **Note**: The default value, *clover*, requires you to use [Clover-specific PX4 branch](https://github.com/CopterExpress/Firmware/tree/v1.10.1-clever);
|
* `vehicle` (*string*, default: *clover*) - PX4 vehicle name. Depending on this parameter, different PX4 presets will be loaded.
|
||||||
* `main_camera` (*boolean*, default: *true*) - controls whether the drone will have a vision position estimation camera;
|
* `main_camera` (*boolean*, default: *true*) - controls whether the drone will have a vision position estimation camera;
|
||||||
* `rangefinder` (*boolean*, default: *true*) - controls whether the drone will have a laser rangefinder;
|
* `rangefinder` (*boolean*, default: *true*) - controls whether the drone will have a laser rangefinder;
|
||||||
* `led` (*boolean*, default: *true*) - controls whether the drone will have a programmable LED strip;
|
* `led` (*boolean*, default: *true*) - controls whether the drone will have a programmable LED strip;
|
||||||
|
|||||||
@@ -7,30 +7,31 @@
|
|||||||
|
|
||||||
. ${R}etc/init.d-posix/airframes/10016_iris # base on iris
|
. ${R}etc/init.d-posix/airframes/10016_iris # base on iris
|
||||||
|
|
||||||
param set ATT_W_EXT_HDG 0.5
|
param set-default ATT_W_EXT_HDG 0.5
|
||||||
param set ATT_EXT_HDG_M 1
|
param set-default ATT_EXT_HDG_M 1 # 0 = none, 1 = vision, 2 = mocap
|
||||||
|
|
||||||
param set COM_DISARM_LAND 1.0
|
param set-default COM_DISARM_LAND 1.0
|
||||||
|
param set-default COM_RCL_EXCEPT 4 # enable offboard flights without rc
|
||||||
|
|
||||||
param set LPE_FLW_SCALE 1.0
|
param set-default LPE_FLW_SCALE 1.0
|
||||||
param set LPE_FLW_R 0.2
|
param set-default LPE_FLW_R 0.2
|
||||||
param set LPE_FLW_RR 0.0
|
param set-default LPE_FLW_RR 0.0
|
||||||
param set LPE_FLW_QMIN 10
|
param set-default LPE_FLW_QMIN 10
|
||||||
param set LPE_VIS_DELAY 0.0
|
param set-default LPE_VIS_DELAY 0.0
|
||||||
param set LPE_VIS_Z 0.1
|
param set-default LPE_VIS_Z 0.1
|
||||||
param set LPE_FUSION 86
|
param set-default LPE_FUSION 86 # flow + vis + land detector + gyro comp
|
||||||
|
|
||||||
param set SENS_FLOW_ROT 0
|
param set-default SENS_FLOW_ROT 0
|
||||||
param set SENS_FLOW_MINHGT 0.0
|
param set-default SENS_FLOW_MINHGT 0.0
|
||||||
param set SENS_FLOW_MAXHGT 4.0
|
param set-default SENS_FLOW_MAXHGT 4.0
|
||||||
param set SENS_FLOW_MAXR 10.0
|
param set-default SENS_FLOW_MAXR 10.0
|
||||||
|
|
||||||
param set EKF2_AID_MASK 26 # flow + vis pos + vis yaw
|
param set-default EKF2_AID_MASK 26 # flow + vis pos + vis yaw
|
||||||
param set EKF2_OF_DELAY 0
|
param set-default EKF2_OF_DELAY 0
|
||||||
param set EKF2_OF_QMIN 10
|
param set-default EKF2_OF_QMIN 10
|
||||||
param set EKF2_OF_N_MIN 0.05
|
param set-default EKF2_OF_N_MIN 0.05
|
||||||
param set EKF2_OF_N_MAX 0.2
|
param set-default EKF2_OF_N_MAX 0.2
|
||||||
param set EKF2_HGT_MODE 2
|
param set-default EKF2_HGT_MODE 2 # 0 = baro, 1 = gps, 2 = range, 3 = vision
|
||||||
param set EKF2_EVA_NOISE 0.1
|
param set-default EKF2_EVA_NOISE 0.1
|
||||||
param set EKF2_EVP_NOISE 0.1
|
param set-default EKF2_EVP_NOISE 0.1
|
||||||
param set EKF2_EV_DELAY 0
|
param set-default EKF2_EV_DELAY 0
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</include>
|
</include>
|
||||||
|
|
||||||
<!-- PX4 instance -->
|
<!-- PX4 instance -->
|
||||||
<node name="sitl_$(arg mav_id)" pkg="px4" type="px4" output="screen" args="$(find px4)/ROMFS/px4fmu_common -s etc/init.d-posix/rcS -i $(arg mav_id)" unless="$(eval type == 'none')">
|
<node name="sitl_$(arg mav_id)" pkg="px4" type="px4" output="screen" required="true" args="$(find px4)/ROMFS/px4fmu_common -s etc/init.d-posix/rcS -i $(arg mav_id)" unless="$(eval type == 'none')">
|
||||||
<env name="PX4_SIM_MODEL" value="$(arg vehicle)"/>
|
<env name="PX4_SIM_MODEL" value="$(arg vehicle)"/>
|
||||||
<env name="PX4_ESTIMATOR" value="$(arg est)"/>
|
<env name="PX4_ESTIMATOR" value="$(arg est)"/>
|
||||||
</node>
|
</node>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<arg name="use_clover_physics" value="$(arg use_clover_physics)"/>
|
<arg name="use_clover_physics" value="$(arg use_clover_physics)"/>
|
||||||
</include>
|
</include>
|
||||||
|
|
||||||
<node name="jmavsim" pkg="px4" type="jmavsim_run.sh" output="screen" if="$(eval type == 'jmavsim')">
|
<node name="jmavsim" pkg="px4" required="true" type="jmavsim_run.sh" output="screen" if="$(eval type == 'jmavsim')">
|
||||||
<env name="HEADLESS" value="1" if="$(eval not gui)"/>
|
<env name="HEADLESS" value="1" if="$(eval not gui)"/>
|
||||||
</node>
|
</node>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<package format="2">
|
<package format="3">
|
||||||
<name>clover_simulation</name>
|
<name>clover_simulation</name>
|
||||||
<version>0.23.0</version>
|
<version>0.23.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>
|
||||||
@@ -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];
|
||||||
}
|
}
|
||||||
|
|||||||
61
docs/assets/copterhack2023.svg
Normal file
61
docs/assets/copterhack2023.svg
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 850.39 283.46" style="enable-background:new 0 0 850.39 283.46;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#333333;}
|
||||||
|
.st1{fill:#FFFFFF;}
|
||||||
|
.st2{fill:#CCCCCC;}
|
||||||
|
.st3{fill:#1E1183;}
|
||||||
|
.st4{fill:#F71523;}
|
||||||
|
.st5{fill:url(#SVGID_1_);}
|
||||||
|
.st6{fill:url(#SVGID_2_);}
|
||||||
|
.st7{fill:url(#SVGID_3_);}
|
||||||
|
.st8{opacity:0.05;fill:#FFFFFF;}
|
||||||
|
.st9{opacity:0.05;fill:#CCCCCC;}
|
||||||
|
.st10{opacity:0.05;fill:url(#SVGID_4_);}
|
||||||
|
.st11{opacity:0.05;fill:url(#SVGID_5_);}
|
||||||
|
.st12{opacity:0.05;fill:url(#SVGID_6_);}
|
||||||
|
.st13{opacity:0.05;fill:url(#SVGID_7_);}
|
||||||
|
.st14{opacity:0.05;fill:url(#SVGID_8_);}
|
||||||
|
.st15{opacity:0.05;fill:url(#SVGID_9_);}
|
||||||
|
.st16{opacity:0.05;fill:url(#SVGID_10_);}
|
||||||
|
.st17{opacity:0.05;fill:url(#SVGID_11_);}
|
||||||
|
.st18{opacity:0.05;fill:url(#SVGID_12_);}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<polygon class="st3" points="559.36,2.05 433.46,2.05 433.46,36.37 479.25,36.37 479.25,128.02 513.57,128.02 513.57,36.37
|
||||||
|
559.36,36.37 "/>
|
||||||
|
<polygon class="st3" points="702.71,36.37 702.71,2.05 576.81,2.05 576.81,2.12 576.75,2.12 576.75,128.02 576.81,128.02
|
||||||
|
611.07,128.02 702.71,128.02 702.71,93.7 611.07,93.7 611.07,82.23 668.45,82.23 668.45,47.91 611.07,47.91 611.07,36.37 "/>
|
||||||
|
<polygon class="st3" points="559.45,179.72 559.38,155.4 535.19,155.45 489.34,201.3 467.68,201.3 467.68,155.45 433.37,155.45
|
||||||
|
433.37,281.35 467.68,281.35 467.68,235.62 489.46,235.62 535.19,281.35 559.38,281.41 559.45,257.09 520.77,218.4 "/>
|
||||||
|
<path class="st3" d="M67.26,36.87h63.01V2.05H67.26c-34.8,0-63.01,28.21-63.01,63.01s28.21,63.01,63.01,63.01h63.01V93.25H67.26
|
||||||
|
c-15.57,0-28.19-12.62-28.19-28.19C39.07,49.49,51.69,36.87,67.26,36.87z"/>
|
||||||
|
<path class="st3" d="M238.36,218.4v63.01h34.82V218.4c0-34.8-28.21-63.01-63.01-63.01s-63.01,28.21-63.01,63.01v63.01h34.82V218.4
|
||||||
|
c0-15.57,12.62-28.19,28.19-28.19C225.74,190.21,238.36,202.83,238.36,218.4z"/>
|
||||||
|
<path class="st3" d="M353.08,190.21h63.01V155.4h-63.01c-34.8,0-63.01,28.21-63.01,63.01s28.21,63.01,63.01,63.01h63.01v-34.82
|
||||||
|
h-63.01c-15.57,0-28.19-12.62-28.19-28.19C324.88,202.83,337.51,190.21,353.08,190.21z"/>
|
||||||
|
<polygon class="st3" points="95.79,155.4 95.79,200.82 39.41,200.82 39.41,155.4 4.25,155.4 4.25,281.41 39.41,281.41
|
||||||
|
39.41,235.98 95.79,235.98 95.79,281.41 130.27,281.41 130.27,155.4 "/>
|
||||||
|
<path class="st4" d="M737.06,200.43c-0.42-25.12-21.44-45.04-46.57-45.04h-45.07v34.32l45.49,0c6.19,0,11.52,4.76,11.81,10.95
|
||||||
|
c0.31,6.61-4.95,12.06-11.49,12.06h-11.5c-18.95,0-34.32,15.36-34.32,34.32v0v34.26h91.64v-34.26h-45.82
|
||||||
|
C716.81,247.03,737.49,226.1,737.06,200.43z"/>
|
||||||
|
<path class="st3" d="M272.4,55.19c-5.46-34.37-37.74-57.8-72.11-52.35c-34.37,5.46-57.8,37.74-52.35,72.11
|
||||||
|
c5.46,34.37,37.74,57.8,72.11,52.35C254.42,121.84,277.85,89.56,272.4,55.19z M210.17,93.26c-15.57,0-28.19-12.62-28.19-28.19
|
||||||
|
c0-15.57,12.62-28.19,28.19-28.19c15.57,0,28.19,12.62,28.19,28.19C238.36,80.64,225.74,93.26,210.17,93.26z"/>
|
||||||
|
|
||||||
|
<rect x="593.74" y="155.4" transform="matrix(-1 -1.224647e-16 1.224647e-16 -1 1221.7888 356.6999)" class="st4" width="34.32" height="45.91"/>
|
||||||
|
<path class="st3" d="M370.51,2.12V2.05h-60.96v0.07h-19.48v125.9h34.32V93.26h46.13c25.17,0,45.57-20.4,45.57-45.57
|
||||||
|
C416.08,22.52,395.68,2.12,370.51,2.12z M364.98,64.71h-40.6V31.12h40.6v0.02c9.27,0,16.78,7.51,16.78,16.78
|
||||||
|
C381.77,57.19,374.25,64.71,364.98,64.71z"/>
|
||||||
|
<path class="st3" d="M846.12,47.69c0-25.17-20.4-45.57-45.57-45.57V2.05h-60.96v0.01h-19.48v126.02h34.32V93.26h32.67l34.76,34.76
|
||||||
|
l24.19,0.06l0.07-24.32l-19.03-19.03C838.61,76.45,846.12,62.95,846.12,47.69z M795.02,64.71
|
||||||
|
C795.02,64.71,795.02,64.71,795.02,64.71h-40.6V31.12h40.6v0.03c0,0,0,0,0,0c9.27,0,16.78,7.51,16.78,16.78
|
||||||
|
C811.8,57.19,804.29,64.71,795.02,64.71z"/>
|
||||||
|
<path class="st4" d="M846.1,195.54c0.31-22.05-18.25-40.09-40.3-40.09h-51.34v34.32h51.59v0.01c2.71,0,5.17,1.93,5.66,4.78
|
||||||
|
c0.33,1.96-0.48,3.98-1.98,5.29c-1.19,1.04-2.46,1.39-3.68,1.39v0.01h-27.06v34.32h27.06v0.01c2.71,0,5.17,1.93,5.66,4.78
|
||||||
|
c0.33,1.96-0.48,3.98-1.98,5.29c-1.19,1.04-2.46,1.39-3.68,1.39v0.01h-51.59v34.32h51.34c22.05,0,40.61-18.04,40.3-40.09
|
||||||
|
c-0.12-8.55-2.96-16.44-7.68-22.86C843.14,211.99,845.98,204.1,846.1,195.54z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -96,6 +96,7 @@
|
|||||||
* [Migration to v0.20](migrate20.md)
|
* [Migration to v0.20](migrate20.md)
|
||||||
* [Migration to v0.22](migrate22.md)
|
* [Migration to v0.22](migrate22.md)
|
||||||
* [Events](events.md)
|
* [Events](events.md)
|
||||||
|
* [CopterHack-2023](copterhack2023.md)
|
||||||
* [CopterHack-2022](copterhack2022.md)
|
* [CopterHack-2022](copterhack2022.md)
|
||||||
* [CopterHack-2021](copterhack2021.md)
|
* [CopterHack-2021](copterhack2021.md)
|
||||||
* [CopterHack-2019](copterhack2019.md)
|
* [CopterHack-2019](copterhack2019.md)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# Working with the camera
|
# Working with the camera
|
||||||
|
|
||||||
> **Note** In the image version **0.20** `clever` package was renamed to `clover`. See [previous version of the article](https://github.com/CopterExpress/clover/blob/v0.19/docs/en/camera.md) for older images.
|
|
||||||
|
|
||||||
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
|
||||||
@@ -147,6 +145,8 @@ 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`):
|
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`):
|
||||||
|
|
||||||
|
> **Note** Starting from [image](image.md) version **0.24** `image_raw_throttled` topic is available without addition configuration.
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<node pkg="topic_tools" name="cam_throttle" type="throttle"
|
<node pkg="topic_tools" name="cam_throttle" type="throttle"
|
||||||
args="messages main_camera/image_raw 5.0 main_camera/image_raw_throttled"/>
|
args="messages main_camera/image_raw 5.0 main_camera/image_raw_throttled"/>
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ There are several tools allowing to calibrate the camera and store calculated pa
|
|||||||
|
|
||||||
Main tutorial: http://wiki.ros.org/camera_calibration/Tutorials/MonocularCalibration.
|
Main tutorial: http://wiki.ros.org/camera_calibration/Tutorials/MonocularCalibration.
|
||||||
|
|
||||||
In order to calibrate the camera with the `camera_calibration` ROS-package you need a computer with OS GNU/Linux and [ROS Melodic](ros-install.md) installed.
|
In order to calibrate the camera with the `camera_calibration` ROS-package you need a computer with OS GNU/Linux and [ROS Noetic](http://wiki.ros.org/noetic/Installation/Ubuntu) installed.
|
||||||
|
|
||||||
<img src="../assets/camera_calibration.png" alt="ROS Camera Calibrator" class="zoom center" width=600>
|
<img src="../assets/camera_calibration.png" alt="ROS Camera Calibrator" class="zoom center" width=600>
|
||||||
|
|
||||||
1. Using the Terminal, install `camera_calibration` package to your computer:
|
1. Using the Terminal, install `camera_calibration` package to your computer:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install ros-melodic-camera-calibration
|
sudo apt-get install ros-noetic-camera-calibration
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Download the chessboard – [chessboard.pdf](../assets/chessboard.pdf). Print the chessboard on paper or open it on the computer screen.
|
2. Download the chessboard – [chessboard.pdf](../assets/chessboard.pdf). Print the chessboard on paper or open it on the computer screen.
|
||||||
|
|||||||
@@ -20,15 +20,14 @@ USB connection is the preferred way to connect to the flight controller.
|
|||||||
|
|
||||||
## UART connection
|
## UART connection
|
||||||
|
|
||||||
> **Note** In the image version **0.20** `clever` package and service was renamed to `clover`. See [previous version of the article](https://github.com/CopterExpress/clover/blob/v0.19/docs/en/connection.md) for older images.
|
|
||||||
|
|
||||||
<!-- TODO: Connection scheme -->
|
<!-- 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.
|
||||||
|
|
||||||
1. Connect Raspberry Pi to your FCU using a UART cable.
|
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. [Connect to the Raspberry Pi over SSH](ssh.md).
|
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.
|
||||||
3. Change the connection type in `~/catkin_ws/src/clover/clover/launch/clover.launch` to UART:
|
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:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<arg name="fcu_conn" default="uart"/>
|
<arg name="fcu_conn" default="uart"/>
|
||||||
@@ -40,15 +39,4 @@ UART connection is another way for the Raspberry Pi and FCU to communicate.
|
|||||||
sudo systemctl restart clover
|
sudo systemctl restart clover
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Hint** Set the `SYS_COMPANION` PX4 parameter to 921600 to enable UART on the FCU.
|
|
||||||
|
|
||||||
## SITL connection
|
|
||||||
|
|
||||||
In order to connect to a local or a remote [SITL](sitl.md) instance set the `fcu_conn` parameter to `udp` and `fcu_ip` to the IP address of the SITL instance (`127.0.0.1` if you are running the instance locally):
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<arg name="fcu_conn" default="udp"/>
|
|
||||||
<arg name="fcu_ip" default="127.0.0.1"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Next**: [Using QGroundControl over Wi-Fi](gcs_bridge.md)
|
**Next**: [Using QGroundControl over Wi-Fi](gcs_bridge.md)
|
||||||
|
|||||||
147
docs/en/copterhack2023.md
Normal file
147
docs/en/copterhack2023.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# CopterHack 2023
|
||||||
|
|
||||||
|
<img src="../assets/copterhack2023.svg" width=300 align=right>
|
||||||
|
|
||||||
|
CopterHack 2023 is an international open-source projects competition on aerial robotics. The main direction of the CopterHack is team competition with a free choice of the project topic. The competition’s official language is English.
|
||||||
|
|
||||||
|
To learn more about the articles of the CopterHack finalist teams follow the links [CopterHack 2021](copterhack2021.md), [CopterHack 2022](copterhack2022.md).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
1. Qualifying stage. Applications are accepted on the deadline date until October 31, 2022.
|
||||||
|
2. Projects development stage. This stage includes monthly updates and mentorship of participants. Starting date - November 1, 2022. Deadline date - February 28, 2023.
|
||||||
|
3. All teams-participants are required to make the final video to proceed to the final round. Final videos are required to be uploaded until March 31, 2023.
|
||||||
|
4. The final round. Projects presentation takes place April 23, 2023.
|
||||||
|
|
||||||
|
## Conditions and criteria for evaluation the final result
|
||||||
|
|
||||||
|
General project requirements:
|
||||||
|
|
||||||
|
1. Open-source.
|
||||||
|
2. Compatibility with the Clover platform.
|
||||||
|
|
||||||
|
Judging criteria for the jury at the final:
|
||||||
|
|
||||||
|
1. Readiness and the article (max. 10 points): the degree of readiness of the project; an accessible and understandable description of the project in the article; a link to the code with comments, diagrams, drawings. It should be possible to reproduce the project and get the result according to the article.
|
||||||
|
2. Amount of work done (max. 6 points): the amount of work done by the team in the framework within of CopterHack, its complexity, and the technical level.
|
||||||
|
3. Usefulness for Clover (max. 6 points): the relevance to the Clover and PX4 platform application in practice, the potential level of demand from other Clover users.
|
||||||
|
4. Presentation at the final (max. 3 points): quality and entertainment points of the final presentation; completeness of the project coverage; demonstration; answers to the jury's questions.
|
||||||
|
|
||||||
|
## Prize fund
|
||||||
|
|
||||||
|
Basing on the results of the evaluation of projects at the final round, the jury will select the winners with the following prizes.
|
||||||
|
|
||||||
|
* 1st place: $3000 (USD).
|
||||||
|
* 2nd place: $2000 (USD).
|
||||||
|
* 3rd place: $1000 (USD).
|
||||||
|
* 4th place: $500 (USD).
|
||||||
|
* 5th place: $500 (USD).
|
||||||
|
|
||||||
|
The competition partners can reward the teams according to additional criteria identified during the evaluation of projects during the final round.
|
||||||
|
|
||||||
|
## How to apply?
|
||||||
|
|
||||||
|
> **Note** In order to be able to apply, you must have an account on [GitHub](https://github.com).
|
||||||
|
|
||||||
|
Prepare your application and send it as a Draft Pull Request to [Clover repository](https://github.com/CopterExpress/clover)
|
||||||
|
|
||||||
|
1. Fork the Clover repository:
|
||||||
|
|
||||||
|
<img src="../assets/github_application/github-fork.png" alt="GitHub Fork">
|
||||||
|
|
||||||
|
2. On the web page of your fork, go to the `docs/en` section and create a new file in the [Markdown](http://en.wikipedia.org/wiki/Markdown) format:
|
||||||
|
|
||||||
|
<img src="../assets/github_application/create_new_file.png" alt="GitHub Create New File">
|
||||||
|
|
||||||
|
3. Enter the title of your article. For example, `new-article.md`
|
||||||
|
|
||||||
|
<img src="../assets/github_application/new_article.png" alt="GitHub New Article">
|
||||||
|
|
||||||
|
4. Fill in your application by the recommended template:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Project name
|
||||||
|
|
||||||
|
[CopterHack-2023](copterhack2023.md), team **Team name**.
|
||||||
|
|
||||||
|
## Team information
|
||||||
|
|
||||||
|
The list of team members:
|
||||||
|
|
||||||
|
(Describe the team: full name, contacts (Telegram username), role in the team).
|
||||||
|
|
||||||
|
* Alexander Sokolov, @aleksandrsokolov111, engineer.
|
||||||
|
* Elena Smirnova, @elenasmirnova111, programmer.
|
||||||
|
|
||||||
|
## Project description
|
||||||
|
|
||||||
|
### Project idea
|
||||||
|
|
||||||
|
Briefly describe the idea and stage of the project.
|
||||||
|
|
||||||
|
### The potential outcomes
|
||||||
|
|
||||||
|
Describe how you see the project result.
|
||||||
|
|
||||||
|
### Using Clover platform
|
||||||
|
|
||||||
|
Describe how the Clover platform will be used in your project.
|
||||||
|
|
||||||
|
### Additional information at the request of participants
|
||||||
|
|
||||||
|
For example, information about the team's experience while working on projects, attach a link to articles, videos.
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Go to the bottom of the page and create a new branch with the title of your article:
|
||||||
|
|
||||||
|
<img src="../assets/github_application/propose_new_file.png" alt="GitHub Propose New File">
|
||||||
|
|
||||||
|
> **Note** Don't commit changes directly to the `master` branch, create a new branch.
|
||||||
|
|
||||||
|
6. If necessary, place additional visual assets in the `docs/assets` folder and add them to your article.
|
||||||
|
|
||||||
|
7. Send a Draft Pull Request from your branch to Clover:
|
||||||
|
|
||||||
|
<img src="../assets/github_application/github-pull-request-create.png" alt="GitHub Create Pull">
|
||||||
|
|
||||||
|
8. In the Pull Request comments, you will be given feedback on the application.
|
||||||
|
|
||||||
|
9. Please note, in the *Checks* block the *Documentation* field should contain a tick, id cross appeared, click *Details* link to see the list of issues in you article found by markdownlint. If you need to change added files, edit them in you branch – changes will appear in the Pull Request automatically. **Do not open a new Pull Request for the same application**.
|
||||||
|
|
||||||
|
10. During the contest, you will work on this document, bringing it closer to the finished state. By the end of the contest you are expected to publish your article which is supposed to be the result of your work in CopterHack.
|
||||||
|
|
||||||
|
Teams-participants are supposed to be added to the special Telegram group, where one can send the project's updates and get feedback from the Jury. For all participating teams, COEX will provide a 40% discount on the Clover drone kit.
|
||||||
|
|
||||||
|
> **Info** There are no restrictions on the age, education, and number of people in a team.
|
||||||
|
|
||||||
|
## CopterHack 2023 projects’ papers contest
|
||||||
|
|
||||||
|
Our participants have been engaged in advanced projects in the field of aerial robotics for already two years. This year we are planning to launch a new type of contest stimulating participants to present the research results running within the whole contest, at high -level international conferences as well as to publish them in Russian and international magazines in thematic areas.
|
||||||
|
|
||||||
|
Original articles are accepted in following nominations:
|
||||||
|
|
||||||
|
* $2000 (USD) for an article in a magazine of first quartile (Q1), indexed in Scopus, Web of Science.
|
||||||
|
* $1000 (USD) for an article in a magazine, indexed in Scopus, Web of Science.
|
||||||
|
* $500 (USD) for an article, published in Compendium (Conference Proceedings), indexed in Scopus, Web of Science.
|
||||||
|
|
||||||
|
> **Note** [Easy way to find quartiles for journals in Web of Science and Scopus](https://www.texpedi.com/2021/07/how-to-find-journal-quartile.html).
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
1. The article is required to reflect the results of the project, developed within CopterHack 2023.
|
||||||
|
2. The article is required to be accepted for publication by the moment of application for the Contest.
|
||||||
|
3. It is required to indicate in the acknowledgement area that work is accomplished within the Contest.
|
||||||
|
|
||||||
|
**Applications deadline**: December 10, 2023. The application for the contest should be submitted through the [Google Form](https://docs.google.com/forms/d/e/1FAIpQLSf52x0CTur-wUCG2URwY-p85gEUBUvgC0mPVNot0RHVjqcLZA/viewform).
|
||||||
|
|
||||||
|
**Results announcement**: December 24, 2023.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
For all questions: [CopterHack in Telegram](https://t.me/CopterHack).
|
||||||
|
|
||||||
|
> **Info** Please contact [Oleg Ponfilenok in Telegram](https://t.me/ponfilenok) if you are interested in becoming the contest's partner or jury member.
|
||||||
@@ -20,7 +20,7 @@ The main goal of the contest is aerial robotics popularization and community de
|
|||||||
* Third parties can provide technical support for recording a lecture.
|
* Third parties can provide technical support for recording a lecture.
|
||||||
* The status of the participant is unlimited (student, representative of a general education institution, representative of the industry, amateur).
|
* The status of the participant is unlimited (student, representative of a general education institution, representative of the industry, amateur).
|
||||||
|
|
||||||
Applications deadline: September 1, 2022.
|
Applications deadline: November 30, 2022.
|
||||||
|
|
||||||
### How to apply?
|
### How to apply?
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@ The main goal of the contest is aerial robotics popularization and community de
|
|||||||
|
|
||||||
The application to the contest is performed via the [Google Form](https://docs.google.com/forms/d/e/1FAIpQLSdelVy6yQ1iN6u88KeiEIKGj7gGaM0xccSt2tiYKB46ICmjkQ/viewform).
|
The application to the contest is performed via the [Google Form](https://docs.google.com/forms/d/e/1FAIpQLSdelVy6yQ1iN6u88KeiEIKGj7gGaM0xccSt2tiYKB46ICmjkQ/viewform).
|
||||||
|
|
||||||
Applications deadline: September 1, 2022.
|
Applications deadline: November 30, 2022.
|
||||||
|
|
||||||
### Prizes
|
### Prizes
|
||||||
|
|
||||||
@@ -105,7 +105,7 @@ The course is evaluated according to a separate, publicly available lesson submi
|
|||||||
|
|
||||||
The application to the contest is performed via the [Google Form](https://docs.google.com/forms/d/e/1FAIpQLSdf2Q68X4hPnFE9f3EP95AxPNnzHKqIsFHtTRT6EBKiH93wzg/viewform) where the link to the video course should be attached.
|
The application to the contest is performed via the [Google Form](https://docs.google.com/forms/d/e/1FAIpQLSdf2Q68X4hPnFE9f3EP95AxPNnzHKqIsFHtTRT6EBKiH93wzg/viewform) where the link to the video course should be attached.
|
||||||
|
|
||||||
Applications deadline: September 1, 2022.
|
Applications deadline: November 30, 2022.
|
||||||
|
|
||||||
### Prizes
|
### Prizes
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ Messages published in the topics may be viewed with the `rostopic` utility, e.g.
|
|||||||
|
|
||||||
`/mavros/setpoint_position/local` — set target position and yaw of the drone \(in the ENU coordinate system\).
|
`/mavros/setpoint_position/local` — set target position and yaw of the drone \(in the ENU coordinate system\).
|
||||||
|
|
||||||
|
`/mavros/setpoint_position/global` – set target position in global coordinates (latitude, longitude, altitude) and yaw of the drone.
|
||||||
|
|
||||||
`/mavros/setpoint_position/cmd_vel` — set target linear velocity of the drone.
|
`/mavros/setpoint_position/cmd_vel` — set target linear velocity of the drone.
|
||||||
|
|
||||||
`/mavros/setpoint_attitude/attitude` and `/mavros/setpoint_attitude/att_throttle` — set target attitude and throttle level.
|
`/mavros/setpoint_attitude/attitude` and `/mavros/setpoint_attitude/att_throttle` — set target attitude and throttle level.
|
||||||
@@ -52,4 +54,4 @@ Messages published in the topics may be viewed with the `rostopic` utility, e.g.
|
|||||||
|
|
||||||
`/mavros/setpoint_raw/attitude` — sends [SET\_ATTITUDE\_TARGET](https://mavlink.io/en/messages/common.html#SET_ATTITUDE_TARGET) message. Allows setting the target attitude /angular velocity and throttle level. The values to be set are selected using the `type_mask` field
|
`/mavros/setpoint_raw/attitude` — sends [SET\_ATTITUDE\_TARGET](https://mavlink.io/en/messages/common.html#SET_ATTITUDE_TARGET) message. Allows setting the target attitude /angular velocity and throttle level. The values to be set are selected using the `type_mask` field
|
||||||
|
|
||||||
`/mavros/setpoint_raw/global` — sends [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://mavlink.io/en/messages/common.html#SET_POSITION_TARGET_GLOBAL_INT). Allows setting the target attitude in global coordinates \(latitude, longitude, altitude\) and flight speed. **Not supported in PX4** \([issue](https://github.com/PX4/Firmware/issues/7552)\).
|
`/mavros/setpoint_raw/global` — sends [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://mavlink.io/en/messages/common.html#SET_POSITION_TARGET_GLOBAL_INT). Allows setting the target attitude in global coordinates \(latitude, longitude, altitude\) and flight speed.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ In case of using LPE ([COEX patched firmware](firmware.md)):
|
|||||||
|
|
||||||
|Parameter|Value|Comment|
|
|Parameter|Value|Comment|
|
||||||
|-|-|-|
|
|-|-|-|
|
||||||
|`LPE_FUSION`|86|Checkboxes: *flow* + *vis* + *land Detector* + *gyro comp*. If flying over horizontal floor *pub agl as lpos down* checkbox is allowed.<br>Details: [Optical Flow](optical_flow.md), [ArUco markers](aruco_map.md), [GPS](gps.md).|
|
|`LPE_FUSION`|86|Checkboxes: *flow* + *vis* + *land detector* + *gyro comp*. If flying over horizontal floor *pub agl as lpos down* checkbox is allowed.<br>Details: [Optical Flow](optical_flow.md), [ArUco markers](aruco_map.md), [GPS](gps.md).|
|
||||||
|`LPE_VIS_DELAY`|0.0||
|
|`LPE_VIS_DELAY`|0.0||
|
||||||
|`LPE_VIS_Z`|0.1||
|
|`LPE_VIS_Z`|0.1||
|
||||||
|`LPE_FLW_SCALE`|1.0||
|
|`LPE_FLW_SCALE`|1.0||
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ To use rviz and rqt, a PC running Ubuntu Linux (or a virtual machine such as [Pa
|
|||||||
|
|
||||||
> **Hint** You can use the [preconfigured virtual machine image](simulation_vm.md) with ROS and Clover toolkit.
|
> **Hint** You can use the [preconfigured virtual machine image](simulation_vm.md) with ROS and Clover toolkit.
|
||||||
|
|
||||||
Install package `ros-melodic-desktop-full` or `ros-melodic-desktop` using the [installation documentation](http://wiki.ros.org/melodic/Installation/Ubuntu).
|
Install package `ros-noetic-desktop-full` or `ros-noetic-desktop` using the [installation documentation](http://wiki.ros.org/noetic/Installation/Ubuntu).
|
||||||
|
|
||||||
Start rviz
|
Start rviz
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ Parameters:
|
|||||||
* `yaw_rate` – angular yaw velocity (will be used if yaw is set to `NaN`) *(rad/s)*;
|
* `yaw_rate` – angular yaw velocity (will be used if yaw is set to `NaN`) *(rad/s)*;
|
||||||
* `speed` – flight speed (setpoint speed) *(m/s)*;
|
* `speed` – flight speed (setpoint speed) *(m/s)*;
|
||||||
* `auto_arm` – switch the drone to `OFFBOARD` mode and arm automatically (**the drone will take off**);
|
* `auto_arm` – switch the drone to `OFFBOARD` mode and arm automatically (**the drone will take off**);
|
||||||
* `frame_id` – [coordinate system](frames.md) for values `x`, `y`, `z`, `vx`, `vy`, `vz`. Example: `map`, `body`, `aruco_map`. Default value: `map`.
|
* `frame_id` – [coordinate system](frames.md) for values `x`, `y`, `z` and `yaw`. Example: `map`, `body`, `aruco_map`. Default value: `map`.
|
||||||
|
|
||||||
> **Note** If you don't want to change your current yaw set the `yaw` parameter to `NaN` (angular velocity by default is 0).
|
> **Note** If you don't want to change your current yaw set the `yaw` parameter to `NaN` (angular velocity by default is 0).
|
||||||
|
|
||||||
@@ -305,6 +305,16 @@ rosservice call /land "{}"
|
|||||||
|
|
||||||
> **Caution** In recent PX4 versions, the vehicle will be switched out of LAND mode to manual mode, if the remote control sticks are moved significantly.
|
> **Caution** In recent PX4 versions, the vehicle will be switched out of LAND mode to manual mode, if the remote control sticks are moved significantly.
|
||||||
|
|
||||||
|
### release
|
||||||
|
|
||||||
|
If it's necessary to pause sending setpoint messages, use the `simple_offboard/release` service:
|
||||||
|
|
||||||
|
```python
|
||||||
|
release = rospy.ServiceProxy('simple_offboard/release', Trigger)
|
||||||
|
|
||||||
|
release()
|
||||||
|
```
|
||||||
|
|
||||||
## Additional materials
|
## Additional materials
|
||||||
|
|
||||||
* [ArUco-based position estimation and navigation](aruco.md).
|
* [ArUco-based position estimation and navigation](aruco.md).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Setting up the simulation environment from scratch requires some effort, but results in the most performant setup, with less chance of driver issues.
|
Setting up the simulation environment from scratch requires some effort, but results in the most performant setup, with less chance of driver issues.
|
||||||
|
|
||||||
> **Hint** See up-to-date commands set for installation Clover simulation software in the script, that builds the virtual machine image with the simulator: [`install_software.sh`](https://github.com/CopterExpress/clover_vm/blob/master/scripts/install_software.sh).
|
<!-- > **Hint** See up-to-date commands set for installation Clover simulation software in the script, that builds the virtual machine image with the simulator: [`install_software.sh`](https://github.com/CopterExpress/clover_vm/blob/master/scripts/install_software.sh). -->
|
||||||
|
|
||||||
Prerequisites: **Ubuntu 20.04**.
|
Prerequisites: **Ubuntu 20.04**.
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ PX4 will be built along with the other packages in our workspace. You may clone
|
|||||||
Clone PX4 sources and make the required symlinks:
|
Clone PX4 sources and make the required symlinks:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone --recursive --depth 1 --branch v1.12.0 https://github.com/PX4/PX4-Autopilot.git ~/PX4-Autopilot
|
git clone --recursive --depth 1 --branch v1.12.3 https://github.com/PX4/PX4-Autopilot.git ~/PX4-Autopilot
|
||||||
ln -s ~/PX4-Autopilot ~/catkin_ws/src/
|
ln -s ~/PX4-Autopilot ~/catkin_ws/src/
|
||||||
ln -s ~/PX4-Autopilot/Tools/sitl_gazebo ~/catkin_ws/src/
|
ln -s ~/PX4-Autopilot/Tools/sitl_gazebo ~/catkin_ws/src/
|
||||||
ln -s ~/PX4-Autopilot/mavlink ~/catkin_ws/src/
|
ln -s ~/PX4-Autopilot/mavlink ~/catkin_ws/src/
|
||||||
@@ -132,6 +132,12 @@ You can test autonomous flight using example scripts in `~/catkin_ws/src/clover/
|
|||||||
|
|
||||||
## Additional steps
|
## Additional steps
|
||||||
|
|
||||||
|
To make it possible to run Gazebo simulation environment without Clover (`gazebo` command), add into your `.bashrc` sourcing Gazebo's initialization script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "source /usr/share/gazebo/setup.sh" >> ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
Optionally, install roscore systemd service to have roscore running in background:
|
Optionally, install roscore systemd service to have roscore running in background:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -141,6 +147,8 @@ sudo systemctl enable roscore
|
|||||||
sudo systemctl start roscore
|
sudo systemctl start roscore
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Web tools setup
|
||||||
|
|
||||||
Install any web server to serve Clover's web tools (`~/.ros/www` directory), e. g. Monkey:
|
Install any web server to serve Clover's web tools (`~/.ros/www` directory), e. g. Monkey:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -152,3 +160,11 @@ sudo cp ~/catkin_ws/src/clover/builder/assets/monkey.service /etc/systemd/system
|
|||||||
sudo systemctl enable monkey
|
sudo systemctl enable monkey
|
||||||
sudo systemctl start monkey
|
sudo systemctl start monkey
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Create `~/.ros/www` using the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rosrun clover www
|
||||||
|
```
|
||||||
|
|
||||||
|
If the set of packages containing a web part (through `www` directory) is changed, the above command also must be run.
|
||||||
|
|||||||
@@ -97,3 +97,13 @@ PX4_SIM_SPEED_FACTOR=0.42 roslaunch clover_simulation simulator.launch
|
|||||||
The virtual machine may benefit from several CPU cores, especially if the cores are not very performant. In our tests, a four-core machine with only a single core allocated to the VM was unable to run the simulation, with constant interface freezes and dropped ROS messages. The same machine with all four cores available to the VM was able to run the simulation at 0.25 real-time speed.
|
The virtual machine may benefit from several CPU cores, especially if the cores are not very performant. In our tests, a four-core machine with only a single core allocated to the VM was unable to run the simulation, with constant interface freezes and dropped ROS messages. The same machine with all four cores available to the VM was able to run the simulation at 0.25 real-time speed.
|
||||||
|
|
||||||
Do note that you should not allocate more resources than you have on your host hardware.
|
Do note that you should not allocate more resources than you have on your host hardware.
|
||||||
|
|
||||||
|
### Changing the map of ArUco-markers in the simulator
|
||||||
|
|
||||||
|
In order to change the map of ArUco-markers in the simulator, you can use the following command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rosrun clover_simulation aruco_gen --single-model --source-world=$(catkin_find clover_simulation resources/worlds/clover.world) $(catkin_find aruco_pose map/map.txt) > $(catkin_find clover_simulation resources/worlds/clover_aruco.world)
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example, `map.txt` is the name of markers name.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
In addition to [native installation instructions](simulation_native.md), we provide a [preconfigured developer virtual machine image](https://github.com/CopterExpress/clover_vm/releases/latest). The image contains:
|
In addition to [native installation instructions](simulation_native.md), we provide a [preconfigured developer virtual machine image](https://github.com/CopterExpress/clover_vm/releases/latest). The image contains:
|
||||||
|
|
||||||
* Ubuntu 18.04 with XFCE lightweight desktop environment;
|
* Ubuntu 20.04 with XFCE lightweight desktop environment;
|
||||||
* ROS packages required to develop for the Clover platform;
|
* ROS packages required to develop for the Clover platform;
|
||||||
* QGroundControl;
|
* QGroundControl;
|
||||||
* preconfigured Gazebo simulation environment;
|
* preconfigured Gazebo simulation environment;
|
||||||
@@ -16,8 +16,6 @@ The VM is an easy way to set up a simulation environment, but can be used as a d
|
|||||||
|
|
||||||
You can download the latest VM image [in the VM releases repository](https://github.com/CopterExpress/clover_vm/releases).
|
You can download the latest VM image [in the VM releases repository](https://github.com/CopterExpress/clover_vm/releases).
|
||||||
|
|
||||||
> **Note** The virtual machine should be used when native installation is not feasible or possible. You may experience reduced performance in programs that use 3D rendering, like rviz and Gazebo.
|
|
||||||
|
|
||||||
## Setting up the VM
|
## Setting up the VM
|
||||||
|
|
||||||
You need to use a VM manager that supports OVF format, like [VirtualBox](https://www.virtualbox.org/wiki/Downloads), [VMware Player](https://www.vmware.com/products/workstation-player.html) or [VMware Workstation](https://www.vmware.com/products/workstation-pro.html).
|
You need to use a VM manager that supports OVF format, like [VirtualBox](https://www.virtualbox.org/wiki/Downloads), [VMware Player](https://www.vmware.com/products/workstation-player.html) or [VMware Workstation](https://www.vmware.com/products/workstation-pro.html).
|
||||||
|
|||||||
@@ -207,9 +207,9 @@ def pose_update(pose):
|
|||||||
# Processing new data of copter's position
|
# Processing new data of copter's position
|
||||||
pass
|
pass
|
||||||
|
|
||||||
rospy.Subscriber('/mavros/local_position/pose', PoseStamped, pose_update)
|
rospy.Subscriber('mavros/local_position/pose', PoseStamped, pose_update)
|
||||||
rospy.Subscriber('/mavros/local_position/velocity', TwistStamped, velocity_update)
|
rospy.Subscriber('mavros/local_position/velocity', TwistStamped, velocity_update)
|
||||||
rospy.Subscriber('/mavros/battery', BatteryState, battery_update)
|
rospy.Subscriber('mavros/battery', BatteryState, battery_update)
|
||||||
rospy.Subscriber('mavros/rc/in', RCIn, rc_callback)
|
rospy.Subscriber('mavros/rc/in', RCIn, rc_callback)
|
||||||
|
|
||||||
rospy.spin()
|
rospy.spin()
|
||||||
@@ -240,6 +240,30 @@ ros_msg = mavlink.convert_to_rosmsg(msg)
|
|||||||
mavlink_pub.publish(ros_msg)
|
mavlink_pub.publish(ros_msg)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<!-- markdownlint-disable MD044 -->
|
||||||
|
|
||||||
|
### # {#mavlink-receive}
|
||||||
|
|
||||||
|
<!-- markdownlint-enable MD044 -->
|
||||||
|
|
||||||
|
Subscribe to all MAVLink messages from the flight controller and decode them:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mavros_msgs.msg import Mavlink
|
||||||
|
from mavros import mavlink
|
||||||
|
from pymavlink import mavutil
|
||||||
|
|
||||||
|
link = mavutil.mavlink.MAVLink('', 255, 1)
|
||||||
|
|
||||||
|
def mavlink_cb(msg):
|
||||||
|
mav_msg = link.decode(mavlink.convert_to_bytes(msg))
|
||||||
|
print('msgid =', msg.msgid, mav_msg) # print message id and parsed message
|
||||||
|
|
||||||
|
mavlink_sub = rospy.Subscriber('mavlink/from', Mavlink, mavlink_cb)
|
||||||
|
|
||||||
|
rospy.spin()
|
||||||
|
```
|
||||||
|
|
||||||
### # {#rc-sub}
|
### # {#rc-sub}
|
||||||
|
|
||||||
React to the drone's mode switching (may be used for starting an autonomous flight, see [example](https://gist.github.com/okalachev/b709f04522d2f9af97e835baedeb806b)):
|
React to the drone's mode switching (may be used for starting an autonomous flight, see [example](https://gist.github.com/okalachev/b709f04522d2f9af97e835baedeb806b)):
|
||||||
@@ -325,7 +349,7 @@ from pymavlink import mavutil
|
|||||||
from mavros_msgs.srv import CommandLong
|
from mavros_msgs.srv import CommandLong
|
||||||
from mavros_msgs.msg import State
|
from mavros_msgs.msg import State
|
||||||
|
|
||||||
send_command = rospy.ServiceProxy('/mavros/cmd/command', CommandLong)
|
send_command = rospy.ServiceProxy('mavros/cmd/command', CommandLong)
|
||||||
|
|
||||||
def calibrate_gyro():
|
def calibrate_gyro():
|
||||||
rospy.loginfo('Calibrate gyro')
|
rospy.loginfo('Calibrate gyro')
|
||||||
@@ -390,6 +414,26 @@ rospy.sleep(5)
|
|||||||
flow_client.update_configuration({'enabled': True})
|
flow_client.update_configuration({'enabled': True})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<!-- markdownlint-disable MD044 -->
|
||||||
|
|
||||||
|
### # {#aruco-map-dynamic}
|
||||||
|
|
||||||
|
> **Info** For [RPi image](image.md) version > 0.23.
|
||||||
|
|
||||||
|
Change the used [ArUco markers map file](aruco_map.md) dynamically:
|
||||||
|
|
||||||
|
<!-- markdownlint-enable MD044 -->
|
||||||
|
|
||||||
|
```python
|
||||||
|
import rospy
|
||||||
|
import dynamic_reconfigure.client
|
||||||
|
|
||||||
|
rospy.init_node('flight')
|
||||||
|
map_client = dynamic_reconfigure.client.Client('aruco_map')
|
||||||
|
|
||||||
|
map_client.update_configuration({'map': '/home/pi/catkin_ws/src/clover/aruco_pose/map/office.txt'})
|
||||||
|
```
|
||||||
|
|
||||||
### # {#wait-global-position}
|
### # {#wait-global-position}
|
||||||
|
|
||||||
Wait for global position to appear (finishing [GPS receiver](gps.md) initialization):
|
Wait for global position to appear (finishing [GPS receiver](gps.md) initialization):
|
||||||
@@ -436,3 +480,11 @@ param_set(param_id='COM_FLTMODE1', value=ParamValue(integer=8))
|
|||||||
# Set parameter of type FLOAT:
|
# Set parameter of type FLOAT:
|
||||||
param_set(param_id='MPC_Z_P', value=ParamValue(real=1.5))
|
param_set(param_id='MPC_Z_P', value=ParamValue(real=1.5))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### # {#is-simulation}
|
||||||
|
|
||||||
|
Check, if the code is running inside a [Gazebo simulation](simulation.md):
|
||||||
|
|
||||||
|
```python
|
||||||
|
is_simulation = rospy.get_param('/use_sim_time', False)
|
||||||
|
```
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ If you are using the marker map, where the markers have equal distances along th
|
|||||||
|
|
||||||
After you fill out the map, you need to apply it. To do it, edit the file `aruco.launch`, located in `~/catkin_ws/src/clover/clover/launch/`. Change the line `<param name="map" value="$(find aruco_pose)/map/map_name.txt"/>`, where `map_name.txt` is the name of your map file.
|
After you fill out the map, you need to apply it. To do it, edit the file `aruco.launch`, located in `~/catkin_ws/src/clover/clover/launch/`. Change the line `<param name="map" value="$(find aruco_pose)/map/map_name.txt"/>`, where `map_name.txt` is the name of your map file.
|
||||||
|
|
||||||
If you are using markers that are not linked to horizontal surfaces (floor, ceiling), you must disable the parameter `known_tilt` both in the module `aruco_detect` and `aruco_map` in the same file. To do it automatically, enter:
|
If you are using markers that are not linked to horizontal surfaces (floor, ceiling), you must blank the `placement` argument in the same file:
|
||||||
|
|
||||||
```bash
|
```xml
|
||||||
sed -i "/known_tilt/s/value=\".*\"/value=\"\"/" /home/pi/catkin_ws/src/clover/clover/launch/aruco.launch
|
<arg name="placement" default=""/>
|
||||||
```
|
```
|
||||||
|
|
||||||
After all the settings, call `sudo systemctl restart clover` to restart the `clover` service.
|
After all the settings, call `sudo systemctl restart clover` to restart the `clover` service.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Parameters `width`, `height`, etc. re also available. Read more about `web_video
|
|||||||
|
|
||||||
## Browse with rqt_image_view
|
## Browse with rqt_image_view
|
||||||
|
|
||||||
To browse images with the rqt tools the user needs a computer with Ubuntu 18.04 and [ROS Melodic](http://wiki.ros.org/melodic/Installation/Ubuntu).
|
To browse images with the rqt tools the user needs a computer with Ubuntu 20.04 and [ROS Noetic](http://wiki.ros.org/noetic/Installation/Ubuntu).
|
||||||
|
|
||||||
[Connect to the Clover Wi-Fi network](wifi.md) an run `rqt_image_view` with its IP-address:
|
[Connect to the Clover Wi-Fi network](wifi.md) an run `rqt_image_view` with its IP-address:
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,7 @@
|
|||||||
* [Виртуальная MAVLink-камера](duocam_mavlink.md)
|
* [Виртуальная MAVLink-камера](duocam_mavlink.md)
|
||||||
* [Настройка DuoCam](duocam_setup.md)
|
* [Настройка DuoCam](duocam_setup.md)
|
||||||
* [Мероприятия](events.md)
|
* [Мероприятия](events.md)
|
||||||
|
* [CopterHack-2023](copterhack2023.md)
|
||||||
* [CopterHack-2022](copterhack2022.md)
|
* [CopterHack-2022](copterhack2022.md)
|
||||||
* [CopterHack-2021](copterhack2021.md)
|
* [CopterHack-2021](copterhack2021.md)
|
||||||
* [CopterHack-2019](copterhack2019.md)
|
* [CopterHack-2019](copterhack2019.md)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
# Работа с камерой
|
# Работа с камерой
|
||||||
|
|
||||||
> **Note** В версии образа **0.20** пакет и сервис `clever` был переименован в `clover`. Для более ранних версий см. документацию для версии [**0.19**](https://github.com/CopterExpress/clover/blob/v0.19/docs/ru/camera.md).
|
|
||||||
|
|
||||||
<!-- TODO: физическое подключение -->
|
<!-- TODO: физическое подключение -->
|
||||||
|
|
||||||
Для работы с основной камерой необходимо убедиться что она включена в файле `~/catkin_ws/src/clover/clover/launch/clover.launch`:
|
Для работы с основной камерой необходимо убедиться что она включена в файле `~/catkin_ws/src/clover/clover/launch/clover.launch`:
|
||||||
@@ -149,6 +147,8 @@ rospy.spin()
|
|||||||
|
|
||||||
Скрипт будет занимать 100% процессора. Для искусственного замедления работы скрипта можно запустить [throttling](http://wiki.ros.org/topic_tools/throttle) кадров с камеры, например, в 5 Гц (`main_camera.launch`):
|
Скрипт будет занимать 100% процессора. Для искусственного замедления работы скрипта можно запустить [throttling](http://wiki.ros.org/topic_tools/throttle) кадров с камеры, например, в 5 Гц (`main_camera.launch`):
|
||||||
|
|
||||||
|
> **Note** Начиная с версии [образа](image.md) **0.24** топик `image_raw_throttled` доступен без дополнительной конфигурации.
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<node pkg="topic_tools" name="cam_throttle" type="throttle"
|
<node pkg="topic_tools" name="cam_throttle" type="throttle"
|
||||||
args="messages main_camera/image_raw 5.0 main_camera/image_raw_throttled"/>
|
args="messages main_camera/image_raw 5.0 main_camera/image_raw_throttled"/>
|
||||||
|
|||||||
@@ -10,14 +10,14 @@
|
|||||||
|
|
||||||
Основной туториал: http://wiki.ros.org/camera_calibration/Tutorials/MonocularCalibration.
|
Основной туториал: http://wiki.ros.org/camera_calibration/Tutorials/MonocularCalibration.
|
||||||
|
|
||||||
Для калибровки камеры с использованием ROS-пакета camera_calibration необходим компьютер с установленным ОС GNU/Linux и [ROS Melodic](ros-install.md).
|
Для калибровки камеры с использованием ROS-пакета camera_calibration необходим компьютер с установленным ОС GNU/Linux и [ROS Noetic](http://wiki.ros.org/noetic/Installation/Ubuntu).
|
||||||
|
|
||||||
<img src="../assets/camera_calibration.png" alt="ROS Camera Calibrator" class="zoom center" width=600>
|
<img src="../assets/camera_calibration.png" alt="ROS Camera Calibrator" class="zoom center" width=600>
|
||||||
|
|
||||||
1. Используя Терминал, установите на компьютер пакет `camera_calibration`:
|
1. Используя Терминал, установите на компьютер пакет `camera_calibration`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt-get install ros-melodic-camera-calibration
|
sudo apt-get install ros-noetic-camera-calibration
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Скачайте калибровочную доску – [`chessboard.pdf`](../assets/chessboard.pdf). Распечатайте доску на принтере либо выведите ее на экран компьютера.
|
2. Скачайте калибровочную доску – [`chessboard.pdf`](../assets/chessboard.pdf). Распечатайте доску на принтере либо выведите ее на экран компьютера.
|
||||||
|
|||||||
@@ -20,15 +20,14 @@
|
|||||||
|
|
||||||
## Подключение по UART
|
## Подключение по UART
|
||||||
|
|
||||||
> **Note** В версии образа **0.20** пакет и сервис `clever` был переименован в `clover`. Для более ранних версий см. документацию для версии [**0.19**](https://github.com/CopterExpress/clover/blob/v0.19/docs/ru/connection.md).
|
|
||||||
|
|
||||||
<!-- TODO схема подключения -->
|
<!-- TODO схема подключения -->
|
||||||
|
|
||||||
Дополнительным способом подключения является подключение подключение по интерфейсу UART.
|
Дополнительным способом подключения является подключение подключение по интерфейсу UART.
|
||||||
|
|
||||||
1. Подключите Raspberry Pi к полетному контроллеру по UART.
|
1. Подключите Raspberry Pi к полетному контроллеру по UART. Для этого соедините кабелем порт TELEM 2 на полетном контроллере к пинам на Raspberry Pi следующем образом: черный провод (GND) к Ground, зеленый (*UART_RX*) к *GPIO14*, желтый (*UART_TX*) к *GPIO15*. Красный провод (*5V*) подключать не нужно.
|
||||||
2. [Подключитесь в Raspberry Pi по SSH](ssh.md).
|
2. Измените значения параметров PX4: `MAV_1_CONFIG` на TELEM 2, `SER_TEL2_BAUND` на 921600 8N1. В PX4 до версии v1.10.0 необходима установка параметра `SYS_COMPANION` в значение 921600.
|
||||||
3. Поменяйте в launch-файле Клевера (`~/catkin_ws/src/clover/clover/launch/clover.launch`) тип подключения на UART:
|
3. [Подключитесь в Raspberry Pi по SSH](ssh.md).
|
||||||
|
4. Поменяйте в launch-файле Клевера (`~/catkin_ws/src/clover/clover/launch/clover.launch`) тип подключения на UART:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<arg name="fcu_conn" default="uart"/>
|
<arg name="fcu_conn" default="uart"/>
|
||||||
@@ -40,15 +39,4 @@
|
|||||||
sudo systemctl restart clover
|
sudo systemctl restart clover
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Hint** Для корректной работы подключения Raspberry Pi и полетного контроллера по UART необходимо установить значение параметра `SYS_COMPANION` на 921600.
|
|
||||||
|
|
||||||
## Подключение к SITL
|
|
||||||
|
|
||||||
Для того, чтобы подсоединиться к локально/удаленно запущенному [SITL](sitl.md), необходимо установить аргумент `fcu_conn` в `udp`, и `fcu_ip` в IP-адрес машины, где запущен SITL (`127.0.0.1` для локального):
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<arg name="fcu_conn" default="udp"/>
|
|
||||||
<arg name="fcu_ip" default="127.0.0.1"/>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Далее**: [Подключение QGroundControl по Wi-Fi](gcs_bridge.md).
|
**Далее**: [Подключение QGroundControl по Wi-Fi](gcs_bridge.md).
|
||||||
|
|||||||
147
docs/ru/copterhack2023.md
Normal file
147
docs/ru/copterhack2023.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# CopterHack 2023
|
||||||
|
|
||||||
|
<img src="../assets/copterhack2023.svg" width=300 align=right>
|
||||||
|
|
||||||
|
CopterHack 2023 — это международный конкурс по разработке проектов по летающей робототехнике с открытым исходным кодом. Основным языком конкурса является английский.
|
||||||
|
|
||||||
|
Ознакомиться со статьями команд-финалистов предыдущих лет можно в статьях о [CopterHack 2021](copterhack2021.md), [CopterHack 2022](copterhack2022.md).
|
||||||
|
|
||||||
|
На конкурс принимаются проекты с открытым исходным кодом и совместимые с платформой квадрокоптера "Клевер". На протяжении конкурса команды работают на собственными идеями и разработками, приближая их к состоянию готового продукта. В этом участникам помогают эксперты отрасли через лекции и регулярную обратную связь.
|
||||||
|
|
||||||
|
## Этапы CopterHack 2023
|
||||||
|
|
||||||
|
Отборочный и проектный этапы конкурса проходят в онлайн-формате, формат проведения финала – гибридный (оффлайн + онлайн). Конкурс подразумевает ежемесячные апдейты от команд с получением регулярной обратной связи от жюри. Для участия в заключительном этапе необходимо подготовить финальное видео и презентацию о результатах проекта.
|
||||||
|
|
||||||
|
1. Отборочный этап. Подача заявок (до 31 октября 2022).
|
||||||
|
2. Проектный этап. Менторство проектов (1 ноября 2022 — 28 февраля 2023).
|
||||||
|
3. Подготовка финального видео (1 — 31 марта 2023).
|
||||||
|
4. Заключительный этап. Финальная защита проектов на английском языке (23 апреля 2023).
|
||||||
|
|
||||||
|
## Условия и критерии оценки
|
||||||
|
|
||||||
|
Условия, предъявляемые к проектам:
|
||||||
|
|
||||||
|
1. Открытый исходный код/модели/схемы/чертежи.
|
||||||
|
2. Совместимость с платформой "Клевер".
|
||||||
|
|
||||||
|
Критерии оценивания жюри в финале:
|
||||||
|
|
||||||
|
1. Готовность и статья (макс. 10 баллов): степень готовности проекта; доступное и понятное описание проекта в статье; прикреплены код с комментариями, схемы, чертежи. По статье должно быть возможно повторить проект, получить результат.
|
||||||
|
2. Объем проделанной работы (макс. 6 баллов): объем проделанной командой работы в рамках CopterHack, ее сложность и технический уровень.
|
||||||
|
3. Полезность для Клевера (макс. 6 баллов): актуальность применения на практике в платформе Клевер и PX4, потенциальный уровень спроса на разработку со стороны других пользователей Клевера.
|
||||||
|
4. Презентация на финале (макс. 3 балла): качество и зрелищность финальной презентации; полнота освещения проекта; демонстрация; ответы на вопросы жюри.
|
||||||
|
|
||||||
|
## Призовой фонд
|
||||||
|
|
||||||
|
Призы от компании COEX по результатам оценивания жюри на финале:
|
||||||
|
|
||||||
|
* I место: $3000.
|
||||||
|
* II место: $2000.
|
||||||
|
* III место: $1000.
|
||||||
|
* IV место: $500.
|
||||||
|
* V место: $500.
|
||||||
|
|
||||||
|
Партнеры конкурса могут поощрить команды по дополнительным критериям, выявленным в результате оценки проектов в ходе финала.
|
||||||
|
|
||||||
|
## Как подать заявку?
|
||||||
|
|
||||||
|
> **Note** Для подачи заявки необходимо иметь аккаунт на [GitHub](https://github.com).
|
||||||
|
|
||||||
|
Подготовьте вашу заявку и пришлите ее в виде Draft Pull Request в [репозиторий Клевера](https://github.com/CopterExpress/clover).
|
||||||
|
|
||||||
|
1. Сделайте форк репозитория Клевера:
|
||||||
|
|
||||||
|
<img src="../assets/github_application/github-fork.png" alt="GitHub Fork">
|
||||||
|
|
||||||
|
2. На странице вашего форка зайдите в раздел `docs/ru` и создайте новый файл в формате [Markdown](https://ru.wikipedia.org/wiki/Markdown):
|
||||||
|
|
||||||
|
<img src="../assets/github_application/create_new_file.png" alt="GitHub Create New File">
|
||||||
|
|
||||||
|
3. Введите название вашей статьи. Например, `new-article.md`
|
||||||
|
|
||||||
|
<img src="../assets/github_application/new_article.png" alt="GitHub New Article">
|
||||||
|
|
||||||
|
4. Оформите вашу заявку в соответствии с рекомендуемым шаблоном:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Название проекта
|
||||||
|
|
||||||
|
[CopterHack-2023](copterhack2023.md), команда **Название команды**.
|
||||||
|
|
||||||
|
## Информация о команде
|
||||||
|
|
||||||
|
Состав команды:
|
||||||
|
|
||||||
|
(Опишите состав команды: имя и фамилия, контакты (имя пользователя в Telegram), роль в команде).
|
||||||
|
|
||||||
|
* Александр Соколов, @aleksandrsokolov111, инженер.
|
||||||
|
* Елена Смирнова, @elenasmirnova111, программист.
|
||||||
|
|
||||||
|
## Описание проекта
|
||||||
|
|
||||||
|
### Идея проекта
|
||||||
|
|
||||||
|
Опишите кратко идею и стадию проекта.
|
||||||
|
|
||||||
|
### Планируемые результаты
|
||||||
|
|
||||||
|
Опишите как вы видите результат проекта.
|
||||||
|
|
||||||
|
### Использование платформы "Клевер"
|
||||||
|
|
||||||
|
Опишите как в вашем проекте будет использоваться платформа "Клевер".
|
||||||
|
|
||||||
|
### Дополнительная информация по желанию участников
|
||||||
|
|
||||||
|
Например, информация об опыте работы команды над проектами, прикрепить ссылку на статьи, видео.
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Перейдите вниз страницы и создайте новую ветку с названием вашей статьи:
|
||||||
|
|
||||||
|
<img src="../assets/github_application/propose_new_file.png" alt="GitHub Propose New File">
|
||||||
|
|
||||||
|
> **Note** Не добавляйте ваши изменения непосредственно в ветку `master`, создайте новую ветку.
|
||||||
|
|
||||||
|
6. При необходимости поместите дополнительные визуальные материалы в папку `docs/assets` и оформите на них ссылки в вашей статье.
|
||||||
|
|
||||||
|
7. Сделайте Draft Pull Request вашей ветки в master Клевера:
|
||||||
|
|
||||||
|
<img src="../assets/github_application/github-pull-request-create.png" alt="GitHub Create Pull">
|
||||||
|
|
||||||
|
8. В комментариях Pull Request вам будет дана обратная связь по заявке.
|
||||||
|
|
||||||
|
9. Обратите внимание на блок *Checks*, в графе Documentation должна стоять галочка. Если там стоит крестик, перейдите по ссылке *Details*, чтобы увидеть список проблем с оформлением статьи. При необходимости изменения добавляемых файлов, меняйте их в вашей ветке – изменения будут появляться в Pull Request автоматически. **Не создавайте новый Pull Request для одной и той же заявки**.
|
||||||
|
|
||||||
|
10. На протяжении конкурса вы будете работать над этим документом, приближая его к состоянию статьи. В документе будет видна история разработки и ежемесячные апдейты. К финалу конкурса вы сможете опубликовать вашу статью, это и будет результат вашей работы в CopterHack.
|
||||||
|
|
||||||
|
Участники конкурса будут добавлены в Telegram-группу, куда можно отправлять первый апдейт и получить обратную связь от жюри. Для команд-участников предусмотрена скидка 40% на конструктор программируемого квадрокоптера "Клевер".
|
||||||
|
|
||||||
|
> **Info** Ограничения по возрасту, образованию и количеству человек в команде отсутствуют.
|
||||||
|
|
||||||
|
## Конкурс статей участников проектов CopterHack 2023
|
||||||
|
|
||||||
|
Наши участники уже 2 года работают над передовыми проектами в области летающей робототехники. В этом году мы хотим ввести новый конкурс, стимулирующий участников презентовать результаты исследований, выполняющихся в рамках конкурса на престижных международных конференциях, а также публиковать их в российских и международных журналах по тематике конкурса.
|
||||||
|
|
||||||
|
На конкурс принимаются оригинальные статьи в следующих номинациях:
|
||||||
|
|
||||||
|
* $2000 за статью в журнале первого квартиля (Q1), индексируемом в Scopus, Web of Science.
|
||||||
|
* $1000 за статью журнале, индексируемом в Scopus, Web of Science.
|
||||||
|
* $500 за статью, опубликованную в сборнике материалов конференции (Conference Proceedings), индексируемые в Scopus, Web of Science.
|
||||||
|
|
||||||
|
> **Note** [Как узнать квартиль журнала в Scopus и WOS](http://russian-science.info/kak-uznat-kvartil-i-protsentil-zhurnala-v-scopus-i-wos).
|
||||||
|
|
||||||
|
Правила:
|
||||||
|
|
||||||
|
1. Статья должна отражать результаты проекта, разработанного в рамках CopterHack 2023.
|
||||||
|
2. Статья должна быть принята к печати к моменту подачи заявки на участие в конкурсе статей.
|
||||||
|
3. В acknowledgment следует указать, что работа выполнена в рамках конкурса.
|
||||||
|
|
||||||
|
**Прием заявок**: до 10 декабря 2023 года. Прием заявок осуществляется через [Google Форму](https://docs.google.com/forms/d/e/1FAIpQLSf52x0CTur-wUCG2URwY-p85gEUBUvgC0mPVNot0RHVjqcLZA/viewform).
|
||||||
|
|
||||||
|
**Объявление результатов**: 24 декабря 2023 года.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
По всем вопросам: [группа CopterHack в Telegram](https://t.me/CopterHack).
|
||||||
|
|
||||||
|
> **Info** Если вы хотите стать партнером конкурса или членом жюри, обращайтесь к [Олегу Понфиленку в Telegram](https://t.me/ponfilenok).
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
|
|
||||||
Прием заявок осуществляется через [Google Форму](https://docs.google.com/forms/d/e/1FAIpQLScE2kN5dO2OYNSM8hOYzOa5Qvh2uDdd9Fjx8OnL1W93bfEBgw/viewform).
|
Прием заявок осуществляется через [Google Форму](https://docs.google.com/forms/d/e/1FAIpQLScE2kN5dO2OYNSM8hOYzOa5Qvh2uDdd9Fjx8OnL1W93bfEBgw/viewform).
|
||||||
|
|
||||||
Дедлайн подачи заявок: 1 сентября 2022 года.
|
Дедлайн подачи заявок: 30 ноября 2022 года.
|
||||||
|
|
||||||
### Призы
|
### Призы
|
||||||
|
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
Прием заявок осуществляется через [Google Форму](https://docs.google.com/forms/d/e/1FAIpQLSdelVy6yQ1iN6u88KeiEIKGj7gGaM0xccSt2tiYKB46ICmjkQ/viewform).
|
Прием заявок осуществляется через [Google Форму](https://docs.google.com/forms/d/e/1FAIpQLSdelVy6yQ1iN6u88KeiEIKGj7gGaM0xccSt2tiYKB46ICmjkQ/viewform).
|
||||||
|
|
||||||
Дедлайн подачи заявок: 1 сентября 2022 года.
|
Дедлайн подачи заявок: 30 ноября 2022 года.
|
||||||
|
|
||||||
### Призы
|
### Призы
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
|
|
||||||
Прием заявок осуществляется через [Google Форму](https://docs.google.com/forms/d/e/1FAIpQLSdf2Q68X4hPnFE9f3EP95AxPNnzHKqIsFHtTRT6EBKiH93wzg/viewform).
|
Прием заявок осуществляется через [Google Форму](https://docs.google.com/forms/d/e/1FAIpQLSdf2Q68X4hPnFE9f3EP95AxPNnzHKqIsFHtTRT6EBKiH93wzg/viewform).
|
||||||
|
|
||||||
Дедлайн подачи заявок: 1 сентября 2022 года
|
Дедлайн подачи заявок: 30 ноября 2022 года
|
||||||
|
|
||||||
### Призы
|
### Призы
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ MAVROS подписывается на определенные ROS-топики
|
|||||||
|
|
||||||
`/mavros/setpoint_position/local` — установить целевую позицию и рысканье \(yaw\) беспилотника \(в системе координат ENU\).
|
`/mavros/setpoint_position/local` — установить целевую позицию и рысканье \(yaw\) беспилотника \(в системе координат ENU\).
|
||||||
|
|
||||||
|
`/mavros/setpoint_position/global` – установить целевую позицию в глобальных координатах (ширина, долгота и высота) и рысканье беспилотника.
|
||||||
|
|
||||||
`/mavros/setpoint_velocity/cmd_vel` — установить целевую линейную скорость беспилотника.
|
`/mavros/setpoint_velocity/cmd_vel` — установить целевую линейную скорость беспилотника.
|
||||||
|
|
||||||
`/mavros/setpoint_attitude/attitude` и `/mavros/setpoint_attitude/att_throttle` — установить целевую ориентацию \(Attitude\) и уровень газа.
|
`/mavros/setpoint_attitude/attitude` и `/mavros/setpoint_attitude/att_throttle` — установить целевую ориентацию \(Attitude\) и уровень газа.
|
||||||
@@ -52,4 +54,4 @@ MAVROS подписывается на определенные ROS-топики
|
|||||||
|
|
||||||
`/mavros/setpoint_raw/attitude` — отправка пакета [SET\_ATTITUDE\_TARGET](https://mavlink.io/en/messages/common.html#SET_ATTITUDE_TARGET). Позволяет установить целевую ориентацию / угловые скорости и уровень газа. Выбор устанавливаемых величин осуществляется с помощью поля `type_mask`
|
`/mavros/setpoint_raw/attitude` — отправка пакета [SET\_ATTITUDE\_TARGET](https://mavlink.io/en/messages/common.html#SET_ATTITUDE_TARGET). Позволяет установить целевую ориентацию / угловые скорости и уровень газа. Выбор устанавливаемых величин осуществляется с помощью поля `type_mask`
|
||||||
|
|
||||||
`/mavros/setpoint_raw/global` — отправка пакета [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://mavlink.io/en/messages/common.html#SET_POSITION_TARGET_GLOBAL_INT). Позволяет установить целевую позицию в глобальных координатах \(ширина, долгота, высота\), а также скорости полета. **Не поддерживается в PX4** \([issue](https://github.com/PX4/Firmware/issues/7552)\).
|
`/mavros/setpoint_raw/global` — отправка пакета [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://mavlink.io/en/messages/common.html#SET_POSITION_TARGET_GLOBAL_INT). Позволяет установить целевую позицию в глобальных координатах \(ширина, долгота, высота\), а также скорости полета.
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
|Параметр|Значение|Примечание|
|
|Параметр|Значение|Примечание|
|
||||||
|-|-|-|
|
|-|-|-|
|
||||||
|`LPE_FUSION`|86|Чекбоксы: *flow* + *vis* + *land Detector* + *gyro comp*. При полете над ровным полом возможно включение *pub agl as lpos down*. <br>Подробнее: [Optical Flow](optical_flow.md), [ArUco-маркеры](aruco_map.md), [GPS](gps.md).|
|
|`LPE_FUSION`|86|Чекбоксы: *flow* + *vis* + *land detector* + *gyro comp*. При полете над ровным полом возможно включение *pub agl as lpos down*. <br>Подробнее: [Optical Flow](optical_flow.md), [ArUco-маркеры](aruco_map.md), [GPS](gps.md).|
|
||||||
|`LPE_VIS_DELAY`|0.0||
|
|`LPE_VIS_DELAY`|0.0||
|
||||||
|`LPE_VIS_Z`|0.1||
|
|`LPE_VIS_Z`|0.1||
|
||||||
|`LPE_FLW_SCALE`|1.0||
|
|`LPE_FLW_SCALE`|1.0||
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
> **Hint** Вы можете можете использовать готовый [образ виртуальной машины](simulation_vm.md) с инструментами для Клевера.
|
> **Hint** Вы можете можете использовать готовый [образ виртуальной машины](simulation_vm.md) с инструментами для Клевера.
|
||||||
|
|
||||||
На него необходимо установить пакет `ros-melodic-desktop-full` или `ros-melodic-desktop`, используя [документацию по установке](http://wiki.ros.org/melodic/Installation/Ubuntu).
|
На него необходимо установить пакет `ros-noetic-desktop-full` или `ros-noetic-desktop`, используя [документацию по установке](http://wiki.ros.org/noetic/Installation/Ubuntu).
|
||||||
|
|
||||||
Запуск rviz
|
Запуск rviz
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -305,6 +305,16 @@ rosservice call /land "{}"
|
|||||||
|
|
||||||
> **Caution** В более новых версиях PX4 коптер выйдет из режима LAND в ручной режим, если сильно перемещать стики.
|
> **Caution** В более новых версиях PX4 коптер выйдет из режима LAND в ручной режим, если сильно перемещать стики.
|
||||||
|
|
||||||
|
### release
|
||||||
|
|
||||||
|
В случае необходимости приостановки отправки setpoint-сообщений, используйте сервис `simple_offboard/release`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
release = rospy.ServiceProxy('simple_offboard/release', Trigger)
|
||||||
|
|
||||||
|
release()
|
||||||
|
```
|
||||||
|
|
||||||
## Дополнительные материалы
|
## Дополнительные материалы
|
||||||
|
|
||||||
* [Полеты в поле ArUco-маркеров](aruco.md).
|
* [Полеты в поле ArUco-маркеров](aruco.md).
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Настройка среды для симуляции с нуля требует некоторых усилий, однако это приведет к улучшению производительности и к уменьшению вероятности появления проблем с драйверами.
|
Настройка среды для симуляции с нуля требует некоторых усилий, однако это приведет к улучшению производительности и к уменьшению вероятности появления проблем с драйверами.
|
||||||
|
|
||||||
> **Hint** Смотрите актуальный набор команд установки необходимого ПО для запуска симулятора Клевера в скрипте сборки виртуальной машины с симулятором: [`install_software.sh`](https://github.com/CopterExpress/clover_vm/blob/master/scripts/install_software.sh).
|
<!-- > **Hint** Смотрите актуальный набор команд установки необходимого ПО для запуска симулятора Клевера в скрипте сборки виртуальной машины с симулятором: [`install_software.sh`](https://github.com/CopterExpress/clover_vm/blob/master/scripts/install_software.sh). -->
|
||||||
|
|
||||||
Требования для сборки: **Ubuntu 20.04**.
|
Требования для сборки: **Ubuntu 20.04**.
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@ sudo /usr/bin/python3 -m pip install -r ~/catkin_ws/src/clover/clover/requiremen
|
|||||||
Склонируйте исходный код PX4 и создайте необходимые симлинки:
|
Склонируйте исходный код PX4 и создайте необходимые симлинки:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone --recursive --depth 1 --branch v1.12.0 https://github.com/PX4/PX4-Autopilot.git ~/PX4-Autopilot
|
git clone --recursive --depth 1 --branch v1.12.3 https://github.com/PX4/PX4-Autopilot.git ~/PX4-Autopilot
|
||||||
ln -s ~/PX4-Autopilot ~/catkin_ws/src/
|
ln -s ~/PX4-Autopilot ~/catkin_ws/src/
|
||||||
ln -s ~/PX4-Autopilot/Tools/sitl_gazebo ~/catkin_ws/src/
|
ln -s ~/PX4-Autopilot/Tools/sitl_gazebo ~/catkin_ws/src/
|
||||||
ln -s ~/PX4-Autopilot/mavlink ~/catkin_ws/src/
|
ln -s ~/PX4-Autopilot/mavlink ~/catkin_ws/src/
|
||||||
@@ -132,6 +132,12 @@ roslaunch clover_simulation simulator.launch
|
|||||||
|
|
||||||
## Дополнительные шаги
|
## Дополнительные шаги
|
||||||
|
|
||||||
|
Для того, чтобы возможно было запускать среду симуляции Gazebo отдельно (команда `gazebo`), добавьте в `.bashrc` вызов соответствующего скрипта инициализации:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "source /usr/share/gazebo/setup.sh" >> ~/.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
Опционально вы можете установить systemd-сервис для roscore для того, чтобы roscore был постоянно запущен в фоне:
|
Опционально вы можете установить systemd-сервис для roscore для того, чтобы roscore был постоянно запущен в фоне:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -141,6 +147,8 @@ sudo systemctl enable roscore
|
|||||||
sudo systemctl start roscore
|
sudo systemctl start roscore
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Конфигурация веб-инструментов
|
||||||
|
|
||||||
Установите любой веб-сервер, чтобы раздавать веб-инструменты Клевера (директория `~/.ros/www`), например, Monkey:
|
Установите любой веб-сервер, чтобы раздавать веб-инструменты Клевера (директория `~/.ros/www`), например, Monkey:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -152,3 +160,11 @@ sudo cp ~/catkin_ws/src/clover/builder/assets/monkey.service /etc/systemd/system
|
|||||||
sudo systemctl enable monkey
|
sudo systemctl enable monkey
|
||||||
sudo systemctl start monkey
|
sudo systemctl start monkey
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Создайте директорию `~/.ros/www` следующей командой:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rosrun clover www
|
||||||
|
```
|
||||||
|
|
||||||
|
При обновлении набора пакетов, содержащих веб-часть (через каталог `www`), также необходимо выполнение данной команды.
|
||||||
|
|||||||
@@ -99,3 +99,13 @@ PX4_SIM_SPEED_FACTOR=0.42 roslaunch clover_simulation simulator.launch
|
|||||||
Выделение нескольких процессорных ядер для виртуальной машины может значительно повысить производительность симуляции. В наших испытаниях виртуальная машина, для которой было выделено одно ядро, не позволяла работать в симуляторе: окно Gazebo не реагировало на пользовательский ввод, сообщения ROS терялись. После выделения четырёх ядер для этой же виртуальной машины симуляция стала работать со скоростью 0.25 от реального времени.
|
Выделение нескольких процессорных ядер для виртуальной машины может значительно повысить производительность симуляции. В наших испытаниях виртуальная машина, для которой было выделено одно ядро, не позволяла работать в симуляторе: окно Gazebo не реагировало на пользовательский ввод, сообщения ROS терялись. После выделения четырёх ядер для этой же виртуальной машины симуляция стала работать со скоростью 0.25 от реального времени.
|
||||||
|
|
||||||
При этом не следует пытаться выделить для виртуальной машины больше ресурсов, чем доступно на основной системе.
|
При этом не следует пытаться выделить для виртуальной машины больше ресурсов, чем доступно на основной системе.
|
||||||
|
|
||||||
|
### Изменение карты ArUco-меток в симуляторе
|
||||||
|
|
||||||
|
Для того, чтобы изменить карту ArUco-меток в симуляторе, можно использовать следующую команду:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rosrun clover_simulation aruco_gen --single-model --source-world=$(catkin_find clover_simulation resources/worlds/clover.world) $(catkin_find aruco_pose map/map.txt) > $(catkin_find clover_simulation resources/worlds/clover_aruco.world)
|
||||||
|
```
|
||||||
|
|
||||||
|
В данном примере `map.txt` – имя карты меток.
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Установка виртуальной машины
|
# Установка виртуальной машины
|
||||||
|
|
||||||
Для работы с платформой Клевер рекомендуется иметь [установленное окружение ROS](ros.md) на своём компьютере. К сожалению, [установка ROS](ros-install.md) сопряжена с рядом трудностей: требуется использовать операционную систему Ubuntu 18.04, процесс установки длительный и требует выполнения большого количества команд в терминале.
|
Для работы с платформой Клевер рекомендуется иметь [установленное окружение ROS](ros.md) на своём компьютере. К сожалению, [установка ROS и симулятора](simulation_native.md) сопряжена с рядом трудностей: требуется использовать операционную систему Ubuntu 20.04, процесс установки длительный и требует выполнения большого количества команд в терминале.
|
||||||
|
|
||||||
Для облегчения процесса настройки окружения мы предлагаем использовать виртуальную машину со всем необходимым для работы с платформой Клевер. В состав виртуальной машины входят:
|
Для облегчения процесса настройки окружения мы предлагаем использовать виртуальную машину со всем необходимым для работы с платформой Клевер. В состав виртуальной машины входят:
|
||||||
|
|
||||||
* операционная система Ubuntu 18.04 с легковесной графической оболочкой XFCE;
|
* операционная система Ubuntu 20.04 с легковесной графической оболочкой XFCE;
|
||||||
* предустановленные пакеты ROS для работы с Клевером;
|
* предустановленные пакеты ROS для работы с Клевером;
|
||||||
* QGroundControl;
|
* QGroundControl;
|
||||||
* предварительно настроенный симулятор Gazebo;
|
* предварительно настроенный симулятор Gazebo;
|
||||||
@@ -18,8 +18,6 @@
|
|||||||
|
|
||||||
Скачать текущую версию виртуальной машины можно [в релизах репозитория виртуальной машины](https://github.com/CopterExpress/clover_vm/releases/latest).
|
Скачать текущую версию виртуальной машины можно [в релизах репозитория виртуальной машины](https://github.com/CopterExpress/clover_vm/releases/latest).
|
||||||
|
|
||||||
> **Warning** Виртуальную машину следует использовать только в тех случаях, когда по каким-то причинам использование Ubuntu 18.04 напрямую невозможно. Производительность всех программ, особенно тех, которые используют 3D-графику - jMAVSim, Gazebo, rviz - будет существенно ниже; кроме того, в ряде случаев будут возникать графические ошибки, приводящие к частичной или полной неработоспособности указанных программ.
|
|
||||||
|
|
||||||
## Установка виртуальной машины
|
## Установка виртуальной машины
|
||||||
|
|
||||||
Для запуска виртуальной машины разработчика требуется использовать одну из совместимых сред виртуализации: [VirtualBox](https://www.virtualbox.org/wiki/Downloads), [VMware Player](https://www.vmware.com/products/workstation-player.html), [VMware Workstation](https://www.vmware.com/products/workstation-pro.html).
|
Для запуска виртуальной машины разработчика требуется использовать одну из совместимых сред виртуализации: [VirtualBox](https://www.virtualbox.org/wiki/Downloads), [VMware Player](https://www.vmware.com/products/workstation-player.html), [VMware Workstation](https://www.vmware.com/products/workstation-pro.html).
|
||||||
|
|||||||
@@ -217,9 +217,9 @@ def pose_update(pose):
|
|||||||
# Обработка новых данных о позиции коптера
|
# Обработка новых данных о позиции коптера
|
||||||
pass
|
pass
|
||||||
|
|
||||||
rospy.Subscriber('/mavros/local_position/pose', PoseStamped, pose_update)
|
rospy.Subscriber('mavros/local_position/pose', PoseStamped, pose_update)
|
||||||
rospy.Subscriber('/mavros/local_position/velocity', TwistStamped, velocity_update)
|
rospy.Subscriber('mavros/local_position/velocity', TwistStamped, velocity_update)
|
||||||
rospy.Subscriber('/mavros/battery', BatteryState, battery_update)
|
rospy.Subscriber('mavros/battery', BatteryState, battery_update)
|
||||||
rospy.Subscriber('mavros/rc/in', RCIn, rc_callback)
|
rospy.Subscriber('mavros/rc/in', RCIn, rc_callback)
|
||||||
|
|
||||||
rospy.spin()
|
rospy.spin()
|
||||||
@@ -251,6 +251,30 @@ ros_msg = mavlink.convert_to_rosmsg(msg)
|
|||||||
mavlink_pub.publish(ros_msg)
|
mavlink_pub.publish(ros_msg)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<!-- markdownlint-disable MD044 -->
|
||||||
|
|
||||||
|
### # {#mavlink-receive}
|
||||||
|
|
||||||
|
<!-- markdownlint-enable MD044 -->
|
||||||
|
|
||||||
|
Подписка на все MAVLink-сообщения от полетного контроллера и их декодирование:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mavros_msgs.msg import Mavlink
|
||||||
|
from mavros import mavlink
|
||||||
|
from pymavlink import mavutil
|
||||||
|
|
||||||
|
link = mavutil.mavlink.MAVLink('', 255, 1)
|
||||||
|
|
||||||
|
def mavlink_cb(msg):
|
||||||
|
mav_msg = link.decode(mavlink.convert_to_bytes(msg))
|
||||||
|
print('msgid =', msg.msgid, mav_msg) # print message id and parsed message
|
||||||
|
|
||||||
|
mavlink_sub = rospy.Subscriber('mavlink/from', Mavlink, mavlink_cb)
|
||||||
|
|
||||||
|
rospy.spin()
|
||||||
|
```
|
||||||
|
|
||||||
### # {#rc-sub}
|
### # {#rc-sub}
|
||||||
|
|
||||||
Реакция на переключение режима на пульте радиоуправления (может быть использовано для запуска автономного полета, см. [пример](https://gist.github.com/okalachev/b709f04522d2f9af97e835baedeb806b)):
|
Реакция на переключение режима на пульте радиоуправления (может быть использовано для запуска автономного полета, см. [пример](https://gist.github.com/okalachev/b709f04522d2f9af97e835baedeb806b)):
|
||||||
@@ -336,7 +360,7 @@ from pymavlink import mavutil
|
|||||||
from mavros_msgs.srv import CommandLong
|
from mavros_msgs.srv import CommandLong
|
||||||
from mavros_msgs.msg import State
|
from mavros_msgs.msg import State
|
||||||
|
|
||||||
send_command = rospy.ServiceProxy('/mavros/cmd/command', CommandLong)
|
send_command = rospy.ServiceProxy('mavros/cmd/command', CommandLong)
|
||||||
|
|
||||||
def calibrate_gyro():
|
def calibrate_gyro():
|
||||||
rospy.loginfo('Calibrate gyro')
|
rospy.loginfo('Calibrate gyro')
|
||||||
@@ -401,6 +425,26 @@ rospy.sleep(5)
|
|||||||
flow_client.update_configuration({'enabled': True})
|
flow_client.update_configuration({'enabled': True})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<!-- markdownlint-disable MD044 -->
|
||||||
|
|
||||||
|
### # {#aruco-map-dynamic}
|
||||||
|
|
||||||
|
> **Info** Для [образа](image.md) версии > 0.23.
|
||||||
|
|
||||||
|
Динамически изменить используемый файл с [картой ArUco-маркеров](aruco_map.md):
|
||||||
|
|
||||||
|
<!-- markdownlint-enable MD044 -->
|
||||||
|
|
||||||
|
```python
|
||||||
|
import rospy
|
||||||
|
import dynamic_reconfigure.client
|
||||||
|
|
||||||
|
rospy.init_node('flight')
|
||||||
|
map_client = dynamic_reconfigure.client.Client('aruco_map')
|
||||||
|
|
||||||
|
map_client.update_configuration({'map': '/home/pi/catkin_ws/src/clover/aruco_pose/map/office.txt'})
|
||||||
|
```
|
||||||
|
|
||||||
### # {#wait-global-position}
|
### # {#wait-global-position}
|
||||||
|
|
||||||
Ожидать появления глобальной позиции (окончания инициализации [GPS-приемника](gps.md)):
|
Ожидать появления глобальной позиции (окончания инициализации [GPS-приемника](gps.md)):
|
||||||
@@ -447,3 +491,11 @@ param_set(param_id='COM_FLTMODE1', value=ParamValue(integer=8))
|
|||||||
# Изменить параметр типа FLOAT:
|
# Изменить параметр типа FLOAT:
|
||||||
param_set(param_id='MPC_Z_P', value=ParamValue(real=1.5))
|
param_set(param_id='MPC_Z_P', value=ParamValue(real=1.5))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### # {#is-simulation}
|
||||||
|
|
||||||
|
Проверить, что код запущен в [симуляции Gazebo](simulation.md):
|
||||||
|
|
||||||
|
```python
|
||||||
|
is_simulation = rospy.get_param('/use_sim_time', False)
|
||||||
|
```
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ sed -i "/direction_y/s/default=\".*\"/default=\"\"/" /home/pi/catkin_ws/src/clov
|
|||||||
|
|
||||||
После того, как вы заполните карту, необходимо применить ее — для этого отредактируйте файл `aruco.launch`, расположенный в `~/catkin_ws/src/clover/clover/launch/`. Измените в нем строку `<param name="map" value="$(find aruco_pose)/map/map_name.txt"/>`, где `map_name.txt` название вашего файла с картой.
|
После того, как вы заполните карту, необходимо применить ее — для этого отредактируйте файл `aruco.launch`, расположенный в `~/catkin_ws/src/clover/clover/launch/`. Измените в нем строку `<param name="map" value="$(find aruco_pose)/map/map_name.txt"/>`, где `map_name.txt` название вашего файла с картой.
|
||||||
|
|
||||||
При использовании маркеров, не привязанных к горизонтальным плоскостям(пол, потолок), необходимо отключить параметр `known_tilt` как в модуле `aruco_detect`, так и в модуле `aruco_map` в том же файле. Для того, чтобы сделать это автоматически, введите:
|
При использовании маркеров, не привязанных к горизонтальным плоскостям (пол, потолок), необходимо также сделать пустым значение аргумента `placement` в том же файле:
|
||||||
|
|
||||||
```bash
|
```xml
|
||||||
sed -i "/known_tilt/s/value=\".*\"/value=\"\"/" /home/pi/catkin_ws/src/clover/clover/launch/aruco.launch
|
<arg name="placement" default=""/>
|
||||||
```
|
```
|
||||||
|
|
||||||
После всех настроек вызовите `sudo systemctl restart clover` для перезагрузки сервиса `clover`.
|
После всех настроек вызовите `sudo systemctl restart clover` для перезагрузки сервиса `clover`.
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ http://192.168.11.1:8080/stream_viewer?topic=/main_camera/image_raw&type=mjpeg&q
|
|||||||
|
|
||||||
## Просмотр через rqt_image_view
|
## Просмотр через rqt_image_view
|
||||||
|
|
||||||
Для просмотра изображений через инструменты rqt необходим компьютер с установленной Ubuntu 18.04 и [ROS Melodic](http://wiki.ros.org/melodic/Installation/Ubuntu).
|
Для просмотра изображений через инструменты rqt необходим компьютер с установленной Ubuntu 20.04 и [ROS Noetic](http://wiki.ros.org/noetic/Installation/Ubuntu).
|
||||||
|
|
||||||
[Подключитесь к Wi-Fi сети Клевера](wifi.md) и запустите `rqt_image_view` с указанием его IP-адреса:
|
[Подключитесь к Wi-Fi сети Клевера](wifi.md) и запустите `rqt_image_view` с указанием его IP-адреса:
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
{ "from": "snippets.html", "to": "ru/snippets.html" },
|
{ "from": "snippets.html", "to": "ru/snippets.html" },
|
||||||
{ "from": "camera_frame.html", "to": "ru/camera_setup.html" },
|
{ "from": "camera_frame.html", "to": "ru/camera_setup.html" },
|
||||||
{ "from": "ru/camera_frame.html", "to": "camera_setup.html" },
|
{ "from": "ru/camera_frame.html", "to": "camera_setup.html" },
|
||||||
{ "from": "camera.html", "to": "ru/camera.html" },
|
{ "from": "camera.html", "to": "en/camera.html" },
|
||||||
{ "from": "led.html", "to": "en/leds.html" },
|
{ "from": "led.html", "to": "en/leds.html" },
|
||||||
{ "from": "leds.html", "to": "ru/leds.html" },
|
{ "from": "leds.html", "to": "ru/leds.html" },
|
||||||
{ "from": "rviz.html", "to": "ru/rviz.html" },
|
{ "from": "rviz.html", "to": "ru/rviz.html" },
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
{ "from": "firmware/", "to": "en/firmware.html" },
|
{ "from": "firmware/", "to": "en/firmware.html" },
|
||||||
{ "from": "simple_offboard/", "to": "en/simple_offboard.html" },
|
{ "from": "simple_offboard/", "to": "en/simple_offboard.html" },
|
||||||
{ "from": "offboard/", "to": "en/simple_offboard.html" },
|
{ "from": "offboard/", "to": "en/simple_offboard.html" },
|
||||||
{ "from": "camera/", "to": "ru/camera.html" },
|
{ "from": "camera/", "to": "en/camera.html" },
|
||||||
{ "from": "snippets/", "to": "ru/snippets.html" },
|
{ "from": "snippets/", "to": "ru/snippets.html" },
|
||||||
{ "from": "optical_flow/", "to": "ru/optical_flow.html" },
|
{ "from": "optical_flow/", "to": "ru/optical_flow.html" },
|
||||||
{ "from": "laser/", "to": "ru/laser.html" },
|
{ "from": "laser/", "to": "ru/laser.html" },
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
{ "from": "connection/", "to": "en/connection.html" },
|
{ "from": "connection/", "to": "en/connection.html" },
|
||||||
{ "from": "clover_vm/", "to": "en/simulation_vm.html" },
|
{ "from": "clover_vm/", "to": "en/simulation_vm.html" },
|
||||||
{ "from": "gpio/", "to": "en/gpio.html" },
|
{ "from": "gpio/", "to": "en/gpio.html" },
|
||||||
|
{ "from": "blocks/", "to": "en/blocks.html" },
|
||||||
|
|
||||||
{ "from": "ru/microsd_images.html", "to": "image.html" },
|
{ "from": "ru/microsd_images.html", "to": "image.html" },
|
||||||
{ "from": "en/microsd_images.html", "to": "image.html" },
|
{ "from": "en/microsd_images.html", "to": "image.html" },
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ find_package(catkin REQUIRED)
|
|||||||
|
|
||||||
catkin_package()
|
catkin_package()
|
||||||
|
|
||||||
install(DIRECTORY launch DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
|
catkin_install_python(PROGRAMS src/update
|
||||||
|
|
||||||
catkin_install_python(PROGRAMS main.py
|
|
||||||
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ Note: you should configure your web server to make it follow symlinks.
|
|||||||
|
|
||||||
## Instructions
|
## Instructions
|
||||||
|
|
||||||
* Run `main.py` node and it will generate the symlinks and index file.
|
* Run `update` script and it will generate the symlinks and index file: `rosrun roswww_static update`.
|
||||||
* Point your static web server path to `~/.ros/www`.
|
* Point your static web server path to `~/.ros/www`.
|
||||||
|
|
||||||
You can rerun `main.py` if the list of installed packages changes.
|
You can rerun `update` if the list of installed packages changes.
|
||||||
|
|
||||||
## Parameters
|
## Parameters
|
||||||
|
|
||||||
* `index` – path for index page, otherwise packages list would be generated.
|
Parameters are passed through environment variables:
|
||||||
* `default_package` – if set then the index page would redirect to this package's page.
|
|
||||||
|
* `ROSWWW_INDEX` – path for index page, otherwise packages list would be generated.
|
||||||
|
* `ROSWWW_DEFAULT` – if set then the index page would redirect to this package's page.
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
<launch>
|
|
||||||
<node pkg="roswww_static" name="roswww_static" type="main.py" clear_params="true" output="screen">
|
|
||||||
<!-- <param name="index" value="$(find my_package)/www/index.html"/> -->
|
|
||||||
<!-- <param name="default_package" value="my_package"/> -->
|
|
||||||
</node>
|
|
||||||
</launch>
|
|
||||||
@@ -13,17 +13,15 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import rospy
|
|
||||||
import rospkg
|
import rospkg
|
||||||
|
|
||||||
rospy.init_node('roswww_static')
|
|
||||||
|
|
||||||
rospack = rospkg.RosPack()
|
rospack = rospkg.RosPack()
|
||||||
|
|
||||||
www = rospkg.get_ros_home() + '/www'
|
www = rospkg.get_ros_home() + '/www'
|
||||||
index_file = rospy.get_param('~index_file', None)
|
index_file = os.environ.get('ROSWWW_INDEX')
|
||||||
default_package = rospy.get_param('~default_package', None)
|
default_package = os.environ.get('ROSWWW_DEFAULT')
|
||||||
|
|
||||||
|
print('using www dir: ' + www)
|
||||||
shutil.rmtree(www, ignore_errors=True) # reset www directory content
|
shutil.rmtree(www, ignore_errors=True) # reset www directory content
|
||||||
os.mkdir(www)
|
os.mkdir(www)
|
||||||
|
|
||||||
@@ -34,7 +32,7 @@ index = '<h1>Packages list</h1>\n<ul>\n'
|
|||||||
for name in packages:
|
for name in packages:
|
||||||
path = rospack.get_path(name)
|
path = rospack.get_path(name)
|
||||||
if os.path.exists(path + '/www'):
|
if os.path.exists(path + '/www'):
|
||||||
rospy.loginfo('found www path for %s package', name)
|
print('found www path for %s package' % name)
|
||||||
os.symlink(path + '/www', www + '/' + name)
|
os.symlink(path + '/www', www + '/' + name)
|
||||||
index += '<li><a href="{name}/">{name}</a></li>'.format(name=name)
|
index += '<li><a href="{name}/">{name}</a></li>'.format(name=name)
|
||||||
|
|
||||||
@@ -42,7 +40,7 @@ if default_package is not None:
|
|||||||
redirect_html = '<meta http-equiv=refresh content="0; url={name}/">'.format(name=default_package)
|
redirect_html = '<meta http-equiv=refresh content="0; url={name}/">'.format(name=default_package)
|
||||||
open(www + '/index.html', 'w').write(redirect_html)
|
open(www + '/index.html', 'w').write(redirect_html)
|
||||||
elif index_file is not None:
|
elif index_file is not None:
|
||||||
rospy.loginfo('symlinking index file')
|
print('symlinking index file')
|
||||||
os.symlink(index_file, www + '/index.html')
|
os.symlink(index_file, www + '/index.html')
|
||||||
else:
|
else:
|
||||||
open(www + '/index.html', 'w').write(index)
|
open(www + '/index.html', 'w').write(index)
|
||||||
Reference in New Issue
Block a user