Compare commits
335 Commits
known_vert
...
v0.26-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
621b70711d | ||
|
|
2a2fbe3bed | ||
|
|
add15ac2fe | ||
|
|
abe6f5cf0f | ||
|
|
b04d8311bd | ||
|
|
b7e8acefb4 | ||
|
|
caa974b71c | ||
|
|
3f00001d9a | ||
|
|
1a9e689b8c | ||
|
|
1e2f92f165 | ||
|
|
d087d1c953 | ||
|
|
01c495597b | ||
|
|
8c8fdcda54 | ||
|
|
fbe353b767 | ||
|
|
4748d34fa2 | ||
|
|
afe46ab7e5 | ||
|
|
d7e8d22bf6 | ||
|
|
ee4165ea26 | ||
|
|
0a265db50e | ||
|
|
04d4dcaa1c | ||
|
|
6adfc038a2 | ||
|
|
5877235e9a | ||
|
|
b70c4387f7 | ||
|
|
00fb98c0c2 | ||
|
|
249800bc87 | ||
|
|
465bdd743b | ||
|
|
028d1ecd82 | ||
|
|
70439f172d | ||
|
|
dacaa8ebde | ||
|
|
a5309765f1 | ||
|
|
2e86ed199a | ||
|
|
ad14822684 | ||
|
|
4517cdf595 | ||
|
|
dc2293e960 | ||
|
|
1b191d9cf0 | ||
|
|
41b6311d9e | ||
|
|
b39fa10ba9 | ||
|
|
2c62fa3583 | ||
|
|
8dcdae1259 | ||
|
|
be27a6d3a3 | ||
|
|
d8c26c114e | ||
|
|
ef43b04a8b | ||
|
|
960a105cba | ||
|
|
4fcd0eaf2f | ||
|
|
f9026fa0e3 | ||
|
|
cd5a9d1176 | ||
|
|
cfe430676b | ||
|
|
3bccfd8c06 | ||
|
|
fef8a3299e | ||
|
|
63deabe01d | ||
|
|
b021afff7e | ||
|
|
b3d28723d7 | ||
|
|
22c9d238d4 | ||
|
|
eb1a6f4349 | ||
|
|
a41004d294 | ||
|
|
e788bf3d4d | ||
|
|
6e8a0d14b3 | ||
|
|
78465fc93f | ||
|
|
bf8482a6ce | ||
|
|
4659772d40 | ||
|
|
62cade7404 | ||
|
|
fa7a05bdb1 | ||
|
|
936efa985d | ||
|
|
5580bd754c | ||
|
|
50c295ed3e | ||
|
|
0f76999214 | ||
|
|
f7ae2c21a4 | ||
|
|
c55e0cb7e1 | ||
|
|
b8344dbb84 | ||
|
|
fae431c890 | ||
|
|
fdbd989d0e | ||
|
|
649e596479 | ||
|
|
d9d85f5979 | ||
|
|
bcf355acf9 | ||
|
|
89912c67b8 | ||
|
|
05c773b297 | ||
|
|
51d6d78b3d | ||
|
|
dd5dff7922 | ||
|
|
94c102b5e0 | ||
|
|
70026fef74 | ||
|
|
41f81f35e6 | ||
|
|
e7ae321b54 | ||
|
|
5b35df41b1 | ||
|
|
51af09bde8 | ||
|
|
3d17433ba8 | ||
|
|
2e174b2bc0 | ||
|
|
9bff8b47dc | ||
|
|
cceb62cfc4 | ||
|
|
4e111fa299 | ||
|
|
ba7f15b1d7 | ||
|
|
30e9d111ec | ||
|
|
8087bb26c9 | ||
|
|
3ef0065ab2 | ||
|
|
d0fa541d13 | ||
|
|
31dde130b4 | ||
|
|
bced762f77 | ||
|
|
2d8ac74f8d | ||
|
|
0e2f1b9ac1 | ||
|
|
f4733e7209 | ||
|
|
2cfdf36768 | ||
|
|
24961393a5 | ||
|
|
f16273c1ae | ||
|
|
9f20448831 | ||
|
|
343a09a242 | ||
|
|
9355790333 | ||
|
|
d07a55334f | ||
|
|
72197392fa | ||
|
|
c91da69552 | ||
|
|
8e09a1243a | ||
|
|
f6594dee62 | ||
|
|
a3fad60aae | ||
|
|
e548cade90 | ||
|
|
407ab40614 | ||
|
|
90d447734d | ||
|
|
f5a7dc2b94 | ||
|
|
2085b68cec | ||
|
|
c5bf88d858 | ||
|
|
5cf42ee650 | ||
|
|
a5a87f8590 | ||
|
|
8c119b8bc9 | ||
|
|
b1f36bf966 | ||
|
|
100eca7210 | ||
|
|
3a53b86707 | ||
|
|
ed623c2e97 | ||
|
|
956490b9bc | ||
|
|
5790d4de01 | ||
|
|
bea3149ac5 | ||
|
|
8c56da44fd | ||
|
|
26bff21253 | ||
|
|
eedb028232 | ||
|
|
c1a7fc7765 | ||
|
|
4ccd5003ad | ||
|
|
9dde4d0c51 | ||
|
|
23c795f21f | ||
|
|
05fc8ff626 | ||
|
|
380b395c72 | ||
|
|
b01f090453 | ||
|
|
3b7242f3d6 | ||
|
|
f1e9e50617 | ||
|
|
f0048f804a | ||
|
|
857d134919 | ||
|
|
8f47a26a1b | ||
|
|
77b195c203 | ||
|
|
cfeff0c74d | ||
|
|
bd99923f6b | ||
|
|
521c248c90 | ||
|
|
7a4f611e47 | ||
|
|
39cd8d5827 | ||
|
|
7d022a5af1 | ||
|
|
6fa9442963 | ||
|
|
ce4d84d8c1 | ||
|
|
ebd9c03251 | ||
|
|
0356133ab7 | ||
|
|
5755300d3a | ||
|
|
147d6238d5 | ||
|
|
9576141376 | ||
|
|
39f0be296e | ||
|
|
8c5551b00b | ||
|
|
42c26aa645 | ||
|
|
f91dc4df71 | ||
|
|
cf6f8f3488 | ||
|
|
4b059f3145 | ||
|
|
ce3b6c0cc7 | ||
|
|
9d0499159a | ||
|
|
3906bc0318 | ||
|
|
ee9b3d8fe7 | ||
|
|
aac5b25c60 | ||
|
|
5211630542 | ||
|
|
bb2afd5e9d | ||
|
|
88668897f9 | ||
|
|
36daaddfb2 | ||
|
|
b5e2073007 | ||
|
|
1154969af2 | ||
|
|
225d8e85ff | ||
|
|
bf29e87cea | ||
|
|
24c15f6e65 | ||
|
|
d6acb0a202 | ||
|
|
6defbea453 | ||
|
|
e31b69a790 | ||
|
|
7251a76315 | ||
|
|
921e09c392 | ||
|
|
9e69bdb01b | ||
|
|
50495a9de9 | ||
|
|
12ccd919a2 | ||
|
|
f0eacfc0f7 | ||
|
|
742d0535c3 | ||
|
|
af1b993e64 | ||
|
|
d3bda9df48 | ||
|
|
939086362a | ||
|
|
7cf14373b0 | ||
|
|
f428dfdb50 | ||
|
|
76982dc198 | ||
|
|
29f01c25e0 | ||
|
|
7ca0ede1d7 | ||
|
|
c3d87b1608 | ||
|
|
47901dcff2 | ||
|
|
9404d4be6d | ||
|
|
ad51d86464 | ||
|
|
9a713057b6 | ||
|
|
7b591d350c | ||
|
|
2f8915ce31 | ||
|
|
6fb84ae584 | ||
|
|
bf4f680164 | ||
|
|
c0baf30c96 | ||
|
|
8f2c3b2e55 | ||
|
|
6423eb91a2 | ||
|
|
22d7236a47 | ||
|
|
91d33a5961 | ||
|
|
2997951371 | ||
|
|
a2ffcf381c | ||
|
|
9aab324f60 | ||
|
|
984fb39b85 | ||
|
|
3a1a9d486c | ||
|
|
55297696d6 | ||
|
|
371f244228 | ||
|
|
ab3e7ac951 | ||
|
|
cdd6195f0b | ||
|
|
c9b015148f | ||
|
|
2054472c23 | ||
|
|
b1084f99b9 | ||
|
|
c5f405c4d9 | ||
|
|
099d39d42d | ||
|
|
c9035790f2 | ||
|
|
95da57fea1 | ||
|
|
ad0138cd26 | ||
|
|
d6101dc0a3 | ||
|
|
cbba62d165 | ||
|
|
28ddbbcdf9 | ||
|
|
cac6b59a56 | ||
|
|
c82490a0c1 | ||
|
|
808726b4b7 | ||
|
|
19fde7095f | ||
|
|
5e9f442996 | ||
|
|
68903373b0 | ||
|
|
ae05710a37 | ||
|
|
4c576ba5d4 | ||
|
|
ffd8b98e53 | ||
|
|
69deeae32f | ||
|
|
df66deb32c | ||
|
|
87a51221bc | ||
|
|
08bda736e9 | ||
|
|
56a2be8170 | ||
|
|
59518fddd1 | ||
|
|
25ae694d1f | ||
|
|
f78a03ec89 | ||
|
|
0cfdac43ec | ||
|
|
cb2850b1d4 | ||
|
|
460c3fdbe1 | ||
|
|
e3fb7cf28e | ||
|
|
3b930d48d2 | ||
|
|
f3aadd11ec | ||
|
|
976c7114e5 | ||
|
|
d8662007fe | ||
|
|
ac1ac33a1a | ||
|
|
95db8ba1b1 | ||
|
|
94a95b28b3 | ||
|
|
3870e62be7 | ||
|
|
45042cd6f5 | ||
|
|
2dda726d3e | ||
|
|
9e8470a3cb | ||
|
|
e9cd3d917c | ||
|
|
373cefb01c | ||
|
|
d0039ea23f | ||
|
|
bd25818aa7 | ||
|
|
a73457c5c5 | ||
|
|
c8a4d49577 | ||
|
|
123379b60e | ||
|
|
71d3144691 | ||
|
|
d306a9d96d | ||
|
|
1d514cf5ca | ||
|
|
96adadeae9 | ||
|
|
a1061ff32c | ||
|
|
e9d13b865c | ||
|
|
b655a4274e | ||
|
|
d58a59afab | ||
|
|
d0fabd8b3e | ||
|
|
b50207e5e9 | ||
|
|
97797a9178 | ||
|
|
1e8569b664 | ||
|
|
21cc47eecb | ||
|
|
4c56adafb2 | ||
|
|
8a524a8aa5 | ||
|
|
5493445747 | ||
|
|
0ef26334dc | ||
|
|
8d83e8faa3 | ||
|
|
9b90214d9d | ||
|
|
224f6cee27 | ||
|
|
ff04b2fb75 | ||
|
|
473444ae33 | ||
|
|
168734ad8c | ||
|
|
4e7b2e379a | ||
|
|
ad1d51fd9e | ||
|
|
57c415db22 | ||
|
|
568386a758 | ||
|
|
55f8f4fa1a | ||
|
|
dd0dd6b5c1 | ||
|
|
4c40bea226 | ||
|
|
6b3f5c3690 | ||
|
|
63067823ee | ||
|
|
880f67c3bc | ||
|
|
839aeb6a26 | ||
|
|
b123585756 | ||
|
|
fa4757a4c8 | ||
|
|
28d77aea33 | ||
|
|
0425e1da24 | ||
|
|
692d424a0b | ||
|
|
1c9fd7b126 | ||
|
|
a1752c1642 | ||
|
|
0dbd2d1048 | ||
|
|
79408861a2 | ||
|
|
063f2c3c13 | ||
|
|
68819bbd34 | ||
|
|
3a5c3d5bb4 | ||
|
|
032eb52a88 | ||
|
|
7dc24437cf | ||
|
|
1ccb756931 | ||
|
|
b5268a7b62 | ||
|
|
a0663d6b36 | ||
|
|
26f2c966ff | ||
|
|
4517186862 | ||
|
|
5236afe035 | ||
|
|
fe1707d0c3 | ||
|
|
03854d2589 | ||
|
|
28339e65af | ||
|
|
e11f0cf054 | ||
|
|
c1045cd11e | ||
|
|
aa3373da58 | ||
|
|
a4ad408228 | ||
|
|
0d436637cd | ||
|
|
18daff1044 | ||
|
|
b96d17a746 | ||
|
|
1506d3fd96 | ||
|
|
eec0833707 | ||
|
|
f9883baa87 | ||
|
|
3bebecf91e |
17
.github/workflows/build-image.yaml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
branches: [ master ]
|
||||
release:
|
||||
types: [ created ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -16,14 +17,24 @@ jobs:
|
||||
- name: Build image
|
||||
run: |
|
||||
docker run --privileged --rm -v /dev:/dev -v $(pwd):/builder/repo -e TRAVIS_TAG="${{ github.event.release.tag_name }}" sfalexrog/img-tool:qemu-update
|
||||
- name: Compress image
|
||||
# - name: Compress image
|
||||
# run: |
|
||||
# cd images && sudo chmod -R 777 . && zip -9 $(echo clover_*).zip clover_* && ls -lh . && unzip -l clover_*.zip
|
||||
- name: Compress image using 7-Zip
|
||||
run: |
|
||||
cd images && sudo chmod -R 777 . && zip -9 $(echo clover_*).zip clover_* && ls -l . && unzip -l clover_*.zip
|
||||
cd images && sudo chmod -R 777 . && 7z a -mx=9 $(echo *_*).7z *_* && ls -lh . && 7z l *_*.7z
|
||||
- name: Upload image
|
||||
uses: softprops/action-gh-release@v1
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
with:
|
||||
files: images/clover_*.zip
|
||||
files: images/*_*.zip
|
||||
prerelease: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload image to artifacts
|
||||
if: ${{ github.event_name == 'workflow_dispatch' || github.ref != 'refs/heads/master' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: image
|
||||
path: images/*_*.7z
|
||||
retention-days: 1
|
||||
|
||||
1
.github/workflows/build.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
# melodic:
|
||||
|
||||
4
.github/workflows/docs.yml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ '*' ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -81,5 +82,8 @@ jobs:
|
||||
needs: docs
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
env:
|
||||
FREEZE_DOCS: ${{ secrets.FREEZE_DOCS }}
|
||||
if: ${{ !env.FREEZE_DOCS }}
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
|
||||
1
.github/workflows/editorconfig.yaml
vendored
@@ -5,6 +5,7 @@ on:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
editorconfig:
|
||||
|
||||
@@ -113,7 +113,9 @@
|
||||
"VMware",
|
||||
"DuoCam"
|
||||
],
|
||||
"code_blocks": false
|
||||
"code_blocks": false,
|
||||
"html_elements": false
|
||||
},
|
||||
"MD045": false
|
||||
"MD045": false,
|
||||
"MD051": false
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ Clover drone is used on a wide range of educational events, including [Copter Ha
|
||||
|
||||
Preconfigured image for Raspberry Pi with installed and configured software, ready to fly, is available [in the Releases section](https://github.com/CopterExpress/clover/releases).
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
Image features:
|
||||
|
||||
@@ -4,7 +4,10 @@ PACKAGE = "aruco_pose"
|
||||
from dynamic_reconfigure.parameter_generator_catkin import *
|
||||
import cv2.aruco
|
||||
|
||||
p = cv2.aruco.DetectorParameters_create()
|
||||
try:
|
||||
p = cv2.aruco.DetectorParameters_create()
|
||||
except AttributeError:
|
||||
p = cv2.aruco.DetectorParameters()
|
||||
|
||||
gen = ParameterGenerator()
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="3">
|
||||
<name>aruco_pose</name>
|
||||
<version>0.23.0</version>
|
||||
<version>0.24.0</version>
|
||||
<description>Positioning with ArUco markers</description>
|
||||
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
@@ -28,6 +28,7 @@
|
||||
<depend>sensor_msgs</depend>
|
||||
<depend>rostest</depend>
|
||||
<depend>dynamic_reconfigure</depend>
|
||||
<depend>pluginlib</depend>
|
||||
<depend condition="$ROS_PYTHON_VERSION == 2">python-docopt</depend>
|
||||
<depend condition="$ROS_PYTHON_VERSION == 3">python3-docopt</depend>
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include <aruco_pose/DetectorConfig.h>
|
||||
#include <aruco_pose/SetMarkers.h>
|
||||
|
||||
#include "draw.h"
|
||||
#include "utils.h"
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
@@ -139,7 +140,7 @@ private:
|
||||
if (!enabled_) return;
|
||||
if (waiting_for_map_) return;
|
||||
|
||||
Mat image = cv_bridge::toCvShare(msg, "bgr8")->image;
|
||||
Mat image = cv_bridge::toCvShare(msg)->image;
|
||||
|
||||
vector<int> ids;
|
||||
vector<vector<cv::Point2f>> corners, rejected;
|
||||
@@ -264,8 +265,7 @@ private:
|
||||
cv::aruco::drawDetectedMarkers(debug, corners, ids); // draw markers
|
||||
if (estimate_poses_)
|
||||
for (unsigned int i = 0; i < ids.size(); i++)
|
||||
cv::aruco::drawAxis(debug, camera_matrix_, dist_coeffs_,
|
||||
rvecs[i], tvecs[i], getMarkerLength(ids[i]));
|
||||
_drawAxis(debug, camera_matrix_, dist_coeffs_, rvecs[i], tvecs[i], getMarkerLength(ids[i]));
|
||||
|
||||
cv_bridge::CvImage out_msg;
|
||||
out_msg.header.frame_id = msg->header.frame_id;
|
||||
|
||||
@@ -83,7 +83,7 @@ private:
|
||||
visualization_msgs::MarkerArray vis_array_;
|
||||
std::string known_vertical_, map_, markers_frame_, markers_parent_frame_;
|
||||
int image_width_, image_height_, image_margin_;
|
||||
bool flip_vertical_, auto_flip_, image_axis_;
|
||||
bool flip_vertical_, auto_flip_, image_axis_, put_markers_count_to_covariance_;
|
||||
|
||||
public:
|
||||
virtual void onInit()
|
||||
@@ -111,6 +111,7 @@ public:
|
||||
image_height_ = nh_priv_.param("image_height", 2000);
|
||||
image_margin_ = nh_priv_.param("image_margin", 200);
|
||||
image_axis_ = nh_priv_.param("image_axis", true);
|
||||
put_markers_count_to_covariance_ = nh_priv_.param("put_markers_count_to_covariance", false);
|
||||
markers_parent_frame_ = nh_priv_.param<std::string>("markers/frame_id", transform_.child_frame_id);
|
||||
markers_frame_ = nh_priv_.param<std::string>("markers/child_frame_id_prefix", "");
|
||||
|
||||
@@ -178,6 +179,20 @@ public:
|
||||
corners.push_back(marker_corners);
|
||||
}
|
||||
|
||||
if (put_markers_count_to_covariance_) {
|
||||
// HACK: pass markers count using covariance field
|
||||
int valid_markers = 0;
|
||||
for (auto const &marker : markers->markers) {
|
||||
for (auto const &board_marker : board_->ids) {
|
||||
if (board_marker == marker.id) {
|
||||
valid_markers++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
pose_.pose.covariance[0] = valid_markers;
|
||||
}
|
||||
|
||||
if (known_vertical_.empty()) {
|
||||
// simple estimation
|
||||
valid = cv::aruco::estimatePoseBoard(corners, ids, board_, camera_matrix_, dist_coeffs_,
|
||||
|
||||
@@ -5,7 +5,7 @@ Requires=roscore.service
|
||||
[Service]
|
||||
User=pi
|
||||
ExecStart=/bin/bash -c ". /home/pi/catkin_ws/devel/setup.sh; \
|
||||
ROS_HOSTNAME=`hostname`.local exec stdbuf -o L roslaunch clover clover.launch --wait --screen --skip-log-check \
|
||||
ROS_HOSTNAME=`hostname`.local ROS_OS_OVERRIDE=debian:bookworm exec stdbuf -o L roslaunch clover clover.launch --wait --screen --skip-log-check \
|
||||
2> >(tee /tmp/clover.err)"
|
||||
|
||||
ExecStartPre=+rm /var/log/clover.log
|
||||
|
||||
@@ -13,70 +13,38 @@
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
|
||||
set -e # Exit immidiately on non-zero result
|
||||
|
||||
echo_stamp() {
|
||||
# TEMPLATE: echo_stamp <TEXT> <TYPE>
|
||||
# TYPE: SUCCESS, ERROR, INFO
|
||||
|
||||
# More info there https://www.shellhacks.com/ru/bash-colors/
|
||||
|
||||
TEXT="$(date '+[%Y-%m-%d %H:%M:%S]') $1"
|
||||
TEXT="\e[1m$TEXT\e[0m" # BOLD
|
||||
|
||||
case "$2" in
|
||||
SUCCESS)
|
||||
TEXT="\e[32m${TEXT}\e[0m";; # GREEN
|
||||
ERROR)
|
||||
TEXT="\e[31m${TEXT}\e[0m";; # RED
|
||||
*)
|
||||
TEXT="\e[34m${TEXT}\e[0m";; # BLUE
|
||||
esac
|
||||
echo -e ${TEXT}
|
||||
}
|
||||
# https://www.raspberrypi.com/documentation/computers/configuration.html
|
||||
|
||||
##################################################
|
||||
# Configure hardware interfaces
|
||||
##################################################
|
||||
|
||||
# 1. Enable sshd
|
||||
echo_stamp "#1 Turn on sshd"
|
||||
touch /boot/ssh
|
||||
# /usr/bin/raspi-config nonint do_ssh 0
|
||||
echo "--- Enable sshd"
|
||||
/usr/bin/raspi-config nonint do_ssh 0
|
||||
|
||||
# 2. Enable GPIO
|
||||
echo_stamp "#2 GPIO enabled by default"
|
||||
echo "--- GPIO enabled by default"
|
||||
|
||||
# 3. Enable I2C
|
||||
echo_stamp "#3 Turn on I2C"
|
||||
echo "--- Enable I2C"
|
||||
/usr/bin/raspi-config nonint do_i2c 0
|
||||
|
||||
# 4. Enable SPI
|
||||
echo_stamp "#4 Turn on SPI"
|
||||
echo "--- Enable SPI"
|
||||
/usr/bin/raspi-config nonint do_spi 0
|
||||
|
||||
# 5. Enable raspicam
|
||||
echo_stamp "#5 Turn on raspicam"
|
||||
echo "--- Enable raspicam"
|
||||
/usr/bin/raspi-config nonint do_camera 0
|
||||
|
||||
# 6. Enable hardware UART
|
||||
echo_stamp "#6 Turn on UART"
|
||||
echo "--- Setup UART"
|
||||
# Temporary solution
|
||||
# https://github.com/RPi-Distro/raspi-config/pull/75
|
||||
/usr/bin/raspi-config nonint do_serial 1
|
||||
/usr/bin/raspi-config nonint set_config_var enable_uart 1 /boot/config.txt
|
||||
echo dtoverlay=pi3-disable-bt >> /boot/config.txt
|
||||
/usr/bin/raspi-config nonint do_serial_hw 0
|
||||
/usr/bin/raspi-config nonint do_serial_cons 1
|
||||
|
||||
echo dtoverlay=pi3-disable-bt >> /boot/firmware/config.txt
|
||||
systemctl disable hciuart.service
|
||||
|
||||
# After adding to Raspbian OS
|
||||
# https://github.com/RPi-Distro/raspi-config/commit/d6d9ecc0d9cbe4aaa9744ae733b9cb239e79c116
|
||||
#/usr/bin/raspi-config nonint do_serial 2
|
||||
|
||||
# 7. Enable V4L driver http://robocraft.ru/blog/electronics/3158.html
|
||||
echo "--- Enable v4l2 driver"
|
||||
# http://robocraft.ru/blog/electronics/3158.html
|
||||
#echo "bcm2835-v4l2" >> /etc/modules
|
||||
echo_stamp "#7 Turn on v4l2 driver"
|
||||
if ! grep -q "^bcm2835-v4l2" /etc/modules;
|
||||
then printf "bcm2835-v4l2\n" >> /etc/modules
|
||||
fi
|
||||
|
||||
echo_stamp "#8 End of configure hardware interfaces"
|
||||
|
||||
@@ -13,63 +13,31 @@
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
|
||||
set -e # Exit immidiately on non-zero result
|
||||
|
||||
echo_stamp() {
|
||||
# TEMPLATE: echo_stamp <TEXT> <TYPE>
|
||||
# TYPE: SUCCESS, ERROR, INFO
|
||||
|
||||
# More info there https://www.shellhacks.com/ru/bash-colors/
|
||||
|
||||
TEXT="$(date '+[%Y-%m-%d %H:%M:%S]') $1"
|
||||
TEXT="\e[1m$TEXT\e[0m" # BOLD
|
||||
|
||||
case "$2" in
|
||||
SUCCESS)
|
||||
TEXT="\e[32m${TEXT}\e[0m";; # GREEN
|
||||
ERROR)
|
||||
TEXT="\e[31m${TEXT}\e[0m";; # RED
|
||||
*)
|
||||
TEXT="\e[34m${TEXT}\e[0m";; # BLUE
|
||||
esac
|
||||
echo -e ${TEXT}
|
||||
}
|
||||
echo "--- Fix home directory permissions"
|
||||
chmod +rx /home/pi
|
||||
|
||||
NEW_SSID='clover-'$(head -c 100 /dev/urandom | xxd -ps -c 100 | sed -e "s/[^0-9]//g" | cut -c 1-4)
|
||||
echo_stamp "Setting SSID to ${NEW_SSID}"
|
||||
# TODO: Use wpa_cli insted direct file edit
|
||||
# FIXME: We rely on raspberrypi-net-mods to copy our file to /etc/wpa_supplicant.
|
||||
# This is not very reliable, but seems to fix our rfkill problem.
|
||||
cat << EOF >> /boot/wpa_supplicant.conf
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
country=GB
|
||||
network={
|
||||
ssid="${NEW_SSID}"
|
||||
psk="cloverwifi"
|
||||
mode=2
|
||||
proto=WPA RSN
|
||||
key_mgmt=WPA-PSK
|
||||
pairwise=CCMP
|
||||
group=CCMP
|
||||
auth_alg=OPEN
|
||||
}
|
||||
EOF
|
||||
echo "--- Creating Wi-Fi AP with SSID=${NEW_SSID}"
|
||||
nmcli con add type wifi ifname wlan0 mode ap con-name clover ssid $NEW_SSID autoconnect true \
|
||||
&& nmcli con modify clover 802-11-wireless.band bg \
|
||||
&& nmcli con modify clover ipv4.method shared ipv4.address 192.168.11.1/24 \
|
||||
&& nmcli con modify clover ipv6.method disabled \
|
||||
&& nmcli con modify clover wifi-sec.key-mgmt wpa-psk \
|
||||
&& nmcli con modify clover wifi-sec.psk "cloverwifi" \
|
||||
&& systemctl disable dnsmasq # disable dnsmasq to avoid conflicts with NetworkManager's dnsmasq
|
||||
|
||||
NEW_HOSTNAME=$(echo ${NEW_SSID} | tr '[:upper:]' '[:lower:]')
|
||||
echo_stamp "Setting hostname to $NEW_HOSTNAME"
|
||||
hostnamectl set-hostname $NEW_HOSTNAME
|
||||
sed -i 's/127\.0\.1\.1.*/127.0.1.1\t'${NEW_HOSTNAME}' '${NEW_HOSTNAME}'.local/g' /etc/hosts
|
||||
echo "--- Setting hostname to $NEW_HOSTNAME"
|
||||
hostnamectl set-hostname $NEW_HOSTNAME \
|
||||
&& sed -i 's/127\.0\.1\.1.*/127.0.1.1\t'${NEW_HOSTNAME}' '${NEW_HOSTNAME}'.local/g' /etc/hosts
|
||||
# .local (mdns) hostname added to make it accesable when wlan and ethernet interfaces are down
|
||||
|
||||
echo_stamp "Enable ROS services"
|
||||
echo "--- Enable ROS services"
|
||||
systemctl enable roscore
|
||||
systemctl enable clover
|
||||
|
||||
echo_stamp "Harware setup"
|
||||
echo "--- Harware setup"
|
||||
/root/hardware_setup.sh
|
||||
|
||||
echo_stamp "Remove init scripts"
|
||||
echo "--- Remove init scripts"
|
||||
rm /root/init_rpi.sh /root/hardware_setup.sh
|
||||
|
||||
echo_stamp "End of initialization of the image"
|
||||
|
||||
122
builder/image-build-ros.sh
Executable file
@@ -0,0 +1,122 @@
|
||||
#! /usr/bin/env bash
|
||||
|
||||
#
|
||||
# Script for building ROS packages from scratch
|
||||
#
|
||||
# Copyright (C) 2022 Copter Express Technologies
|
||||
#
|
||||
# Author: Oleg Kalachev <okalachev@gmail.com>
|
||||
#
|
||||
# Distributed under MIT License (available at https://opensource.org/licenses/MIT).
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
|
||||
set -ex # exit on error, echo commands
|
||||
|
||||
# http://wiki.ros.org/Installation/Source
|
||||
|
||||
export ROS_DISTRO=noetic
|
||||
. /etc/os-release # set $VERSION_CODENAME to Debian release code name
|
||||
export ROS_OS_OVERRIDE=debian:$VERSION_CODENAME
|
||||
export ROS_PYTHON_VERSION=3
|
||||
export CLOVER_DEPS="tf tf2 tf2_ros tf2_geometry_msgs geometry_msgs sensor_msgs visualization_msgs libgeographiclib-dev mavros mavros_extras cv_camera cv_bridge rosbridge_server web_video_server tf2_web_republisher libxml2 libxslt python3-lxml dynamic_reconfigure image_transport image_proc image_geometry python-pymavlink ros_pytest"
|
||||
export CLOVER_DEPS="$CLOVER_DEPS rostest python3-docopt image_publisher"
|
||||
|
||||
echo "=== Building ROS from scratch"
|
||||
|
||||
#echo "--- Adding sources"
|
||||
# echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list
|
||||
# curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add -
|
||||
|
||||
#cp /etc/apt/trusted.gpg /etc/apt/trusted.gpg.d # https://askubuntu.com/a/1408456
|
||||
apt-get update
|
||||
apt-get install -y python3-distutils build-essential cmake git python3-pip python3-rosinstall-generator python3-vcstools python3-empy libpoco-dev
|
||||
|
||||
# install vcstool using pip
|
||||
# curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python3 get-pip.py && rm get-pip.py
|
||||
pip3 install -U --break-system-packages vcstool rosdep rosinstall-generator catkin-pkg future
|
||||
|
||||
# sudo rosdep init
|
||||
# rm /etc/ros/rosdep/sources.list.d/20-default.list
|
||||
rosdep init
|
||||
rosdep update --os=debian:bullseye
|
||||
|
||||
# rm /etc/ros/rosdep/sources.list.d/20-default.list && rosdep init
|
||||
# rosdep --os=debian:$VERSION_CODENAME update
|
||||
|
||||
echo "--- Create Catkin workspace to build ROS package"
|
||||
mkdir ~/ros_catkin_ws
|
||||
cd ~/ros_catkin_ws
|
||||
|
||||
echo "--- Download ROS sources"
|
||||
rosinstall_generator ros_base $CLOVER_DEPS --rosdistro $ROS_DISTRO --deps --tar > noetic.rosinstall
|
||||
mkdir ./src
|
||||
vcs import --input noetic.rosinstall ./src
|
||||
|
||||
# https://answers.ros.org/question/343367/catkin-package-dependencies-issue-when-installing-ros-melodic-on-raspberry-pi-4/
|
||||
#sudo apt remove python-rospkg
|
||||
#sudo apt remove python-catkin-pkg
|
||||
##sudo apt --fix-broken install
|
||||
#sudo apt-get autoremove
|
||||
|
||||
#echo "--- Install catkin_pkg"
|
||||
#cd
|
||||
#git clone https://github.com/ros-infrastructure/catkin_pkg.git
|
||||
#cd catkin_pkg
|
||||
#python3 setup.py install
|
||||
#cd ~/ros_catkin_ws
|
||||
|
||||
echo "--- Resolve dependencies"
|
||||
rosdep install --from-paths ./src --ignore-packages-from-source --rosdistro $ROS_DISTRO -y --os=debian:bullseye --skip-keys="python3-catkin-pkg-modules libboost-thread python3-rosdep-modules" || true
|
||||
|
||||
echo "--- Install missing dependencies"
|
||||
apt-get install -y liborocos-kdl1.5 geographiclib-tools libgeographiclib-dev
|
||||
|
||||
echo "-- Install geographiclib datasets"
|
||||
wget https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh
|
||||
chmod +x install_geographiclib_datasets.sh
|
||||
./install_geographiclib_datasets.sh
|
||||
|
||||
echo "--- Apply patches"
|
||||
wget https://github.com/ros/rosconsole/pull/58.patch
|
||||
patch -p1 -d src/rosconsole < 58.patch
|
||||
|
||||
wget https://github.com/ros/ros_comm/pull/2353.patch
|
||||
patch -p2 -d src/ros_comm < 2353.patch
|
||||
|
||||
wget https://github.com/AJahueyM/web_video_server/commit/5b722eb0822bcc3fe45fefe7b393b87bfe004417.patch
|
||||
patch -p1 -d src/web_video_server < 5b722eb0822bcc3fe45fefe7b393b87bfe004417.patch
|
||||
|
||||
echo "--- Build ROS"
|
||||
# https://github.com/ros/catkin/issues/863#issuecomment-290392074
|
||||
./src/catkin/bin/catkin_make_isolated --install -DCMAKE_BUILD_TYPE=Release
|
||||
# -DSETUPTOOLS_DEB_LAYOUT=OFF \
|
||||
# --install-space=/opt/ros/$ROS_DISTRO
|
||||
|
||||
# source ~/ros_catkin_ws/install_isolated/setup.bash
|
||||
#source /opt/ros/$ROS_DISTRO/setup.bash
|
||||
#
|
||||
#echo "--- List built ROS packages"
|
||||
#set +x
|
||||
#rospack list-names | while read line; do echo $line `rosversion $line`; done
|
||||
#set -x
|
||||
#
|
||||
#echo "--- Build Debian packages"
|
||||
#apt-get install -y python3-bloom debhelper dpkg-dev libtinyxml-dev
|
||||
#
|
||||
## add rosdep file to help bloom-generate resolve missing bookworm dependencies
|
||||
#echo "yaml file:///etc/ros/rosdep/noetic-rosdep-clover.yaml" >> /etc/ros/rosdep/sources.list.d/20-default.list
|
||||
#rosdep update
|
||||
#
|
||||
#pip3 install setuptools==45.2.0 # https://github.com/ros/catkin/issues/863#issuecomment-1000446018
|
||||
#
|
||||
#for file in `find . -name "package.xml" -not -path "*/debian/*"`; do
|
||||
# cd $(dirname ${file})
|
||||
# rm -rf debian
|
||||
# bloom-generate rosdebian --os-name debian --os-version $VERSION_CODENAME --ros-distro $ROS_DISTRO --debug
|
||||
# debian/rules binary # fakeroot is not needed as we are root
|
||||
# cd -
|
||||
#done
|
||||
|
||||
ls
|
||||
@@ -13,42 +13,22 @@
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
|
||||
set -e # Exit immidiately on non-zero result
|
||||
set -ex # exit on error, echo commands
|
||||
|
||||
# https://www.raspberrypi.org/software/operating-systems/#raspberry-pi-os-32-bit
|
||||
SOURCE_IMAGE="https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-05-28/2021-05-07-raspios-buster-armhf-lite.zip"
|
||||
SOURCE_IMAGE="https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-03-15/2024-03-15-raspios-bookworm-armhf-lite.img.xz"
|
||||
|
||||
export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:='noninteractive'}
|
||||
export LANG=${LANG:='C.UTF-8'}
|
||||
export LC_ALL=${LC_ALL:='C.UTF-8'}
|
||||
|
||||
echo_stamp() {
|
||||
# TEMPLATE: echo_stamp <TEXT> <TYPE>
|
||||
# TYPE: SUCCESS, ERROR, INFO
|
||||
|
||||
# More info there https://www.shellhacks.com/ru/bash-colors/
|
||||
|
||||
TEXT="$(date '+[%Y-%m-%d %H:%M:%S]') $1"
|
||||
TEXT="\e[1m$TEXT\e[0m" # BOLD
|
||||
|
||||
case "$2" in
|
||||
SUCCESS)
|
||||
TEXT="\e[32m${TEXT}\e[0m";; # GREEN
|
||||
ERROR)
|
||||
TEXT="\e[31m${TEXT}\e[0m";; # RED
|
||||
*)
|
||||
TEXT="\e[34m${TEXT}\e[0m";; # BLUE
|
||||
esac
|
||||
echo -e ${TEXT}
|
||||
}
|
||||
|
||||
BUILDER_DIR="/builder"
|
||||
REPO_DIR="${BUILDER_DIR}/repo"
|
||||
SCRIPTS_DIR="${REPO_DIR}/builder"
|
||||
IMAGES_DIR="${REPO_DIR}/images"
|
||||
|
||||
[[ ! -d ${SCRIPTS_DIR} ]] && (echo_stamp "Directory ${SCRIPTS_DIR} doesn't exist" "ERROR"; exit 1)
|
||||
[[ ! -d ${IMAGES_DIR} ]] && mkdir ${IMAGES_DIR} && echo_stamp "Directory ${IMAGES_DIR} was created successful" "SUCCESS"
|
||||
[[ ! -d ${SCRIPTS_DIR} ]] && (echo "Error: directory ${SCRIPTS_DIR} doesn't exist"; exit 1)
|
||||
[[ ! -d ${IMAGES_DIR} ]] && mkdir ${IMAGES_DIR} && echo "Directory ${IMAGES_DIR} was created successful"
|
||||
|
||||
if [[ -z ${TRAVIS_TAG} ]]; then IMAGE_VERSION="$(cd ${REPO_DIR}; git log --format=%h -1)"; else IMAGE_VERSION="${TRAVIS_TAG}"; fi
|
||||
# IMAGE_VERSION="${TRAVIS_TAG:=$(cd ${REPO_DIR}; git log --format=%h -1)}"
|
||||
@@ -64,15 +44,17 @@ get_image() {
|
||||
local RPI_IMAGE_NAME=$(echo ${RPI_ZIP_NAME} | sed 's/zip/img/')
|
||||
|
||||
if [ ! -e "${BUILD_DIR}/${RPI_ZIP_NAME}" ]; then
|
||||
echo_stamp "Downloading original Linux distribution"
|
||||
echo "--- Downloading original Linux distribution"
|
||||
wget --progress=dot:giga -O ${BUILD_DIR}/${RPI_ZIP_NAME} $2
|
||||
echo_stamp "Downloading complete" "SUCCESS" \
|
||||
else echo_stamp "Linux distribution already donwloaded"; fi
|
||||
echo "--- Downloading complete" "SUCCESS"
|
||||
else
|
||||
echo "Linux distribution already downloaded"
|
||||
fi
|
||||
|
||||
echo_stamp "Unzipping Linux distribution image" \
|
||||
&& unzip -p ${BUILD_DIR}/${RPI_ZIP_NAME} ${RPI_IMAGE_NAME} > $1 \
|
||||
&& echo_stamp "Unzipping complete" "SUCCESS" \
|
||||
|| (echo_stamp "Unzipping was failed!" "ERROR"; exit 1)
|
||||
echo "--- Unzipping Linux distribution image"
|
||||
apt-get update --allow-releaseinfo-change
|
||||
apt-get install -y xz-utils
|
||||
unxz --stdout ${BUILD_DIR}/${RPI_ZIP_NAME} > $1
|
||||
}
|
||||
|
||||
get_image ${IMAGE_PATH} ${SOURCE_IMAGE}
|
||||
@@ -120,6 +102,7 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/launch.
|
||||
# ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-ros-clover.rosinstall' '/home/pi/ros_catkin_ws/'
|
||||
# Add rename script
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-ros.sh' ${REPO_URL} ${IMAGE_VERSION} false false ${NUMBER_THREADS}
|
||||
#${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-build-ros.sh'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh'
|
||||
|
||||
${BUILDER_DIR}/image-resize.sh ${IMAGE_PATH}
|
||||
|
||||
@@ -12,50 +12,33 @@
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
|
||||
set -e # Exit immidiately on non-zero result
|
||||
set -ex # exit on error, echo commands
|
||||
|
||||
echo_stamp() {
|
||||
# TEMPLATE: echo_stamp <TEXT> <TYPE>
|
||||
# TYPE: SUCCESS, ERROR, INFO
|
||||
echo "--- Move /etc/ld.so.preload out of the way"
|
||||
mv /etc/ld.so.preload /etc/ld.so.preload.disabled-for-build
|
||||
|
||||
# More info there https://www.shellhacks.com/ru/bash-colors/
|
||||
|
||||
TEXT="$(date '+[%Y-%m-%d %H:%M:%S]') $1"
|
||||
TEXT="\e[1m$TEXT\e[0m" # BOLD
|
||||
|
||||
case "$2" in
|
||||
SUCCESS)
|
||||
TEXT="\e[32m${TEXT}\e[0m";; # GREEN
|
||||
ERROR)
|
||||
TEXT="\e[31m${TEXT}\e[0m";; # RED
|
||||
*)
|
||||
TEXT="\e[34m${TEXT}\e[0m";; # BLUE
|
||||
esac
|
||||
echo -e ${TEXT}
|
||||
}
|
||||
|
||||
echo_stamp "Write Clover information"
|
||||
echo "--- Create pi user"
|
||||
echo 'pi:$6$c70VpvPsVNCG0YR5$l5vWWLsLko9Kj65gcQ8qvMkuOoRkEagI90qi3F/Y7rm8eNYZHW8CY6BOIKwMH7a3YYzZYL90zf304cAHLFaZE0' > /boot/userconf.txt
|
||||
|
||||
echo "--- Write Clover information"
|
||||
# Clover image version
|
||||
echo "$1" >> /etc/clover_version
|
||||
# Origin image file name
|
||||
echo "${2%.*}" >> /etc/clover_origin
|
||||
|
||||
echo_stamp "Write magic script to /etc/rc.local"
|
||||
echo "--- Write magic script to /etc/rc.local"
|
||||
MAGIC_SCRIPT="sudo /root/init_rpi.sh; sudo sed -i '/sudo \\\/root\\\/init_rpi.sh/d' /etc/rc.local && sudo reboot"
|
||||
sed -i "19a${MAGIC_SCRIPT}" /etc/rc.local
|
||||
# TODO: remake to oneshot systemd service
|
||||
|
||||
# It needs for autosizer.sh & maybe that is correct
|
||||
echo_stamp "Change boot partition"
|
||||
echo "--- Change boot partition"
|
||||
sed -i 's/root=[^ ]*/root=\/dev\/mmcblk0p2/' /boot/cmdline.txt
|
||||
sed -i 's/.* \/boot vfat defaults 0 2$/\/dev\/mmcblk0p1 \/boot vfat defaults 0 2/' /etc/fstab
|
||||
sed -i 's/.* \/boot\/firmware vfat defaults 0 2$/\/dev\/mmcblk0p1 \/boot\/firmware vfat defaults 0 2/' /etc/fstab
|
||||
sed -i 's/.* \/ ext4 defaults,noatime 0 1$/\/dev\/mmcblk0p2 \/ ext4 defaults,noatime 0 1/' /etc/fstab
|
||||
cat /boot/cmdline.txt
|
||||
cat /etc/fstab
|
||||
|
||||
echo_stamp "Set max space for syslogs"
|
||||
echo "--- Set max space for syslogs"
|
||||
# https://unix.stackexchange.com/questions/139513/how-to-clear-journalctl
|
||||
sed -i 's/#SystemMaxUse=/SystemMaxUse=200M/' /etc/systemd/journald.conf
|
||||
|
||||
echo_stamp "Move /etc/ld.so.preload out of the way"
|
||||
mv /etc/ld.so.preload /etc/ld.so.preload.disabled-for-build
|
||||
|
||||
echo_stamp "End of init image"
|
||||
|
||||
@@ -12,43 +12,20 @@
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
|
||||
set -e # Exit immidiately on non-zero result
|
||||
|
||||
echo_stamp() {
|
||||
# TEMPLATE: echo_stamp <TEXT> <TYPE>
|
||||
# TYPE: SUCCESS, ERROR, INFO
|
||||
|
||||
# More info there https://www.shellhacks.com/ru/bash-colors/
|
||||
|
||||
TEXT="$(date '+[%Y-%m-%d %H:%M:%S]') $1"
|
||||
TEXT="\e[1m$TEXT\e[0m" # BOLD
|
||||
|
||||
case "$2" in
|
||||
SUCCESS)
|
||||
TEXT="\e[32m${TEXT}\e[0m";; # GREEN
|
||||
ERROR)
|
||||
TEXT="\e[31m${TEXT}\e[0m";; # RED
|
||||
*)
|
||||
TEXT="\e[34m${TEXT}\e[0m";; # BLUE
|
||||
esac
|
||||
echo -e ${TEXT}
|
||||
}
|
||||
|
||||
echo_stamp "#1 Write STATIC to /etc/dhcpcd.conf"
|
||||
set -ex # exit on error, echo commands
|
||||
|
||||
echo "--- Write static to /etc/dhcpcd.conf"
|
||||
cat << EOF >> /etc/dhcpcd.conf
|
||||
interface wlan0
|
||||
static ip_address=192.168.11.1/24
|
||||
EOF
|
||||
|
||||
echo_stamp "#2 Set wpa_supplicant country"
|
||||
|
||||
echo "--- Set wpa_supplicant country"
|
||||
cat << EOF >> /etc/wpa_supplicant/wpa_supplicant.conf
|
||||
country=GB
|
||||
EOF
|
||||
|
||||
echo_stamp "#3 Write dhcp-config to /etc/dnsmasq.conf"
|
||||
|
||||
echo "--- Write dhcp-config to /etc/dnsmasq.conf"
|
||||
cat << EOF >> /etc/dnsmasq.conf
|
||||
interface=wlan0
|
||||
address=/clover/coex/192.168.11.1
|
||||
@@ -59,5 +36,3 @@ bogus-priv
|
||||
domain-needed
|
||||
quiet-dhcp6
|
||||
EOF
|
||||
|
||||
echo_stamp "#4 End of network installation"
|
||||
|
||||
@@ -15,44 +15,20 @@
|
||||
|
||||
set -ex # exit on error, echo commands
|
||||
|
||||
REPO=$1
|
||||
REF=$2
|
||||
INSTALL_ROS_PACK_SOURCES=$3
|
||||
DISCOVER_ROS_PACK=$4
|
||||
NUMBER_THREADS=$5
|
||||
|
||||
# Current ROS distribution
|
||||
ROS_DISTRO=noetic
|
||||
|
||||
echo_stamp() {
|
||||
# TEMPLATE: echo_stamp <TEXT> <TYPE>
|
||||
# TYPE: SUCCESS, ERROR, INFO
|
||||
|
||||
# More info there https://www.shellhacks.com/ru/bash-colors/
|
||||
|
||||
TEXT="$(date '+[%Y-%m-%d %H:%M:%S]') $1"
|
||||
TEXT="\e[1m$TEXT\e[0m" # BOLD
|
||||
|
||||
case "$2" in
|
||||
SUCCESS)
|
||||
TEXT="\e[32m${TEXT}\e[0m";; # GREEN
|
||||
ERROR)
|
||||
TEXT="\e[31m${TEXT}\e[0m";; # RED
|
||||
*)
|
||||
TEXT="\e[34m${TEXT}\e[0m";; # BLUE
|
||||
esac
|
||||
echo -e ${TEXT}
|
||||
}
|
||||
. /etc/os-release # set $VERSION_CODENAME to Debian release code name
|
||||
export ROS_OS_OVERRIDE=debian:$VERSION_CODENAME
|
||||
|
||||
# https://gist.github.com/letmaik/caa0f6cc4375cbfcc1ff26bd4530c2a3
|
||||
# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh
|
||||
my_travis_retry() {
|
||||
local result=0
|
||||
local count=1
|
||||
local max_count=50
|
||||
local max_count=5
|
||||
while [ $count -le $max_count ]; do
|
||||
[ $result -ne 0 ] && {
|
||||
echo -e "\nThe command \"$@\" failed. Retrying, $count of $max_count.\n" >&2
|
||||
echo -e "\nThe command \"$*\" failed. Retrying, $count of $max_count.\n" >&2
|
||||
}
|
||||
# ! { } ignores set -e, see https://stackoverflow.com/a/4073372
|
||||
! { "$@"; result=$?; }
|
||||
@@ -62,127 +38,136 @@ my_travis_retry() {
|
||||
done
|
||||
|
||||
[ $count -gt $max_count ] && {
|
||||
echo -e "\nThe command \"$@\" failed $max_count times.\n" >&2
|
||||
echo -e "\nThe command \"$*\" failed $max_count times.\n" >&2
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
echo "--- Install rosdep"
|
||||
my_travis_retry pip3 install -U rosdep
|
||||
|
||||
# TODO: 'kinetic-rosdep-clover.yaml' should add only if we use our repo?
|
||||
echo_stamp "Init rosdep"
|
||||
echo "--- Init rosdep"
|
||||
my_travis_retry rosdep init
|
||||
# FIXME: Re-add this after missing packages are built
|
||||
echo "yaml file:///etc/ros/rosdep/${ROS_DISTRO}-rosdep-clover.yaml" >> /etc/ros/rosdep/sources.list.d/20-default.list
|
||||
|
||||
echo "--- Update rosdep"
|
||||
echo "yaml file:///etc/ros/rosdep/${ROS_DISTRO}-rosdep-clover.yaml" >> /etc/ros/rosdep/sources.list.d/10-clover.list
|
||||
my_travis_retry rosdep update
|
||||
|
||||
echo_stamp "Populate rosdep for ROS user"
|
||||
my_travis_retry sudo -u pi rosdep update
|
||||
echo "--- Populate rosdep for ROS user"
|
||||
my_travis_retry sudo -u pi ROS_OS_OVERRIDE=debian:$VERSION_CODENAME rosdep update
|
||||
|
||||
export ROS_IP='127.0.0.1' # needed for running tests
|
||||
|
||||
# echo_stamp "Reconfiguring Clover repository for simplier unshallowing"
|
||||
# echo "Reconfiguring Clover repository for simplier unshallowing"
|
||||
cd /home/pi/catkin_ws/src/clover
|
||||
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
|
||||
|
||||
# This is sort of a hack to force "custom" packages to be installed - the ones built by COEX, linked against OpenCV 4.2
|
||||
# I **wish** OpenCV would not be such a mess, but, well, here we are.
|
||||
echo_stamp "Installing OpenCV 4.2-compatible ROS packages"
|
||||
apt install -y --no-install-recommends \
|
||||
ros-${ROS_DISTRO}-compressed-image-transport=1.14.0-0buster \
|
||||
ros-${ROS_DISTRO}-cv-bridge=1.15.0-0buster \
|
||||
ros-${ROS_DISTRO}-cv-camera=0.5.1-0buster \
|
||||
ros-${ROS_DISTRO}-image-publisher=1.15.3-0buster \
|
||||
ros-${ROS_DISTRO}-web-video-server=0.2.1-0buster
|
||||
apt-mark hold \
|
||||
ros-${ROS_DISTRO}-compressed-image-transport \
|
||||
ros-${ROS_DISTRO}-cv-bridge \
|
||||
ros-${ROS_DISTRO}-cv-camera \
|
||||
ros-${ROS_DISTRO}-image-publisher \
|
||||
ros-${ROS_DISTRO}-web-video-server
|
||||
# echo "--- Installing OpenCV 4.2-compatible ROS packages"
|
||||
# apt install -y --no-install-recommends \
|
||||
# ros-${ROS_DISTRO}-compressed-image-transport=1.14.0-0buster \
|
||||
# ros-${ROS_DISTRO}-cv-bridge=1.15.0-0buster \
|
||||
# ros-${ROS_DISTRO}-cv-camera=0.5.1-0buster \
|
||||
# ros-${ROS_DISTRO}-image-publisher=1.15.3-0buster \
|
||||
# ros-${ROS_DISTRO}-web-video-server=0.2.1-0buster
|
||||
# apt-mark hold \
|
||||
# ros-${ROS_DISTRO}-compressed-image-transport \
|
||||
# ros-${ROS_DISTRO}-cv-bridge \
|
||||
# ros-${ROS_DISTRO}-cv-camera \
|
||||
# ros-${ROS_DISTRO}-image-publisher \
|
||||
# ros-${ROS_DISTRO}-web-video-server
|
||||
|
||||
echo_stamp "Installing libboost-dev" # https://travis-ci.org/github/CopterExpress/clover/jobs/766318908#L6536
|
||||
my_travis_retry apt-get install -y --no-install-recommends libboost-dev libboost-all-dev
|
||||
#echo "--- Installing libboost-dev" # https://travis-ci.org/github/CopterExpress/clover/jobs/766318908#L6536
|
||||
#my_travis_retry apt-get install -y --no-install-recommends libboost-dev libboost-all-dev
|
||||
|
||||
echo_stamp "Build and install Clover"
|
||||
echo "--- Build and install Clover"
|
||||
cd /home/pi/catkin_ws
|
||||
# Don't try to install gazebo_ros
|
||||
my_travis_retry rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --os=debian:buster \
|
||||
--skip-keys=gazebo_ros --skip-keys=gazebo_plugins
|
||||
my_travis_retry rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --os=debian:$VERSION_CODENAME \
|
||||
--skip-keys="gazebo_ros gazebo_plugins"
|
||||
my_travis_retry pip3 install wheel
|
||||
my_travis_retry pip3 install -r /home/pi/catkin_ws/src/clover/clover/requirements.txt
|
||||
source /opt/ros/${ROS_DISTRO}/setup.bash
|
||||
# Don't build simulation plugins for actual drone
|
||||
catkin_make -j2 -DCMAKE_BUILD_TYPE=RelWithDebInfo
|
||||
source devel/setup.bash
|
||||
|
||||
echo_stamp "Install clever package (for backwards compatibility)"
|
||||
echo "--- Install clever package (for backwards compatibility)"
|
||||
cd /home/pi/catkin_ws/src/clover/builder/assets/clever
|
||||
./setup.py install
|
||||
rm -rf build # remove build artifacts
|
||||
|
||||
echo_stamp "Build Clover documentation"
|
||||
echo "--- Build Clover documentation"
|
||||
cd /home/pi/catkin_ws/src/clover
|
||||
builder/assets/install_gitbook.sh
|
||||
gitbook install
|
||||
gitbook build
|
||||
# replace assets copy to assets symlink to save space
|
||||
rm -rf _book/assets && ln -s ../docs/assets _book/assets
|
||||
touch node_modules/CATKIN_IGNORE docs/CATKIN_IGNORE _book/CATKIN_IGNORE clover/www/CATKIN_IGNORE apps/CATKIN_IGNORE # ignore documentation files by catkin
|
||||
npm cache clean --force
|
||||
|
||||
echo_stamp "Installing additional ROS packages"
|
||||
echo "--- Installing additional ROS packages"
|
||||
my_travis_retry apt-get install -y --no-install-recommends \
|
||||
ros-${ROS_DISTRO}-dynamic-reconfigure \
|
||||
ros-${ROS_DISTRO}-rosbridge-suite \
|
||||
ros-${ROS_DISTRO}-rosserial \
|
||||
ros-${ROS_DISTRO}-usb-cam \
|
||||
ros-${ROS_DISTRO}-vl53l1x \
|
||||
ros-${ROS_DISTRO}-ws281x \
|
||||
ros-${ROS_DISTRO}-libcamera-ros \
|
||||
ros-${ROS_DISTRO}-rosshow \
|
||||
ros-${ROS_DISTRO}-cmake-modules \
|
||||
ros-${ROS_DISTRO}-image-view
|
||||
ros-${ROS_DISTRO}-image-view \
|
||||
ros-${ROS_DISTRO}-nodelet-topic-tools \
|
||||
ros-${ROS_DISTRO}-stereo-msgs \
|
||||
ros-${ROS_DISTRO}-vision-msgs
|
||||
|
||||
# TODO move GeographicLib datasets to Mavros debian package
|
||||
echo_stamp "Install GeographicLib datasets (needed for mavros)" \
|
||||
echo "--- Install GeographicLib datasets (needed for mavros)" \
|
||||
&& wget -qO- https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | bash
|
||||
|
||||
echo_stamp "Running tests"
|
||||
echo "--- Running tests"
|
||||
export ROS_IP='127.0.0.1' # needed for running tests
|
||||
cd /home/pi/catkin_ws
|
||||
# FIXME: Investigate failing tests
|
||||
catkin_make run_tests #&& catkin_test_results
|
||||
|
||||
echo_stamp "Change permissions for catkin_ws"
|
||||
echo "--- Change permissions for catkin_ws"
|
||||
chown -Rf pi:pi /home/pi/catkin_ws
|
||||
|
||||
echo_stamp "Update www"
|
||||
echo "--- Update www"
|
||||
sudo -u pi sh -c ". devel/setup.sh && rosrun clover www"
|
||||
|
||||
echo_stamp "Make \$HOME/examples symlink"
|
||||
echo "--- Make \$HOME/examples symlink"
|
||||
ln -s "$(catkin_find clover examples --first-only)" /home/pi
|
||||
chown -Rf pi:pi /home/pi/examples
|
||||
|
||||
echo_stamp "Make systemd services symlinks"
|
||||
echo "--- Make systemd services symlinks"
|
||||
ln -s /home/pi/catkin_ws/src/clover/builder/assets/clover.service /lib/systemd/system/
|
||||
ln -s /home/pi/catkin_ws/src/clover/builder/assets/roscore.service /lib/systemd/system/
|
||||
# validate
|
||||
[ -f /lib/systemd/system/clover.service ]
|
||||
[ -f /lib/systemd/system/roscore.service ]
|
||||
|
||||
echo_stamp "Make udev rules symlink"
|
||||
echo "--- Make udev rules symlink"
|
||||
ln -s "$(catkin_find clover udev --first-only)"/* /lib/udev/rules.d/
|
||||
|
||||
echo_stamp "Setup ROS environment"
|
||||
echo "--- Setup ROS environment"
|
||||
cat << EOF >> /home/pi/.bashrc
|
||||
LANG='C.UTF-8'
|
||||
LC_ALL='C.UTF-8'
|
||||
export ROS_HOSTNAME=\`hostname\`.local
|
||||
export ROS_OS_OVERRIDE=debian:bookworm
|
||||
source /opt/ros/${ROS_DISTRO}/setup.bash
|
||||
source /home/pi/catkin_ws/devel/setup.bash
|
||||
EOF
|
||||
|
||||
#echo_stamp "Removing local apt mirror"
|
||||
# Restore original sources.list
|
||||
#mv /var/sources.list.bak /etc/apt/sources.list
|
||||
# Clean apt cache
|
||||
apt-get clean -qq > /dev/null
|
||||
# Remove local mirror repository key
|
||||
#apt-key del COEX-MIRROR
|
||||
echo "--- Cleanup apt"
|
||||
apt-get autoremove --purge -y
|
||||
apt-get clean
|
||||
|
||||
echo_stamp "END of ROS INSTALLATION"
|
||||
echo "--- Cleanup pip"
|
||||
pip3 cache purge
|
||||
|
||||
echo "--- Cleanup /tmp"
|
||||
rm -rf /tmp/*
|
||||
|
||||
@@ -12,27 +12,9 @@
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
|
||||
set -e # Exit immidiately on non-zero result
|
||||
set -ex # exit on error, echo commands
|
||||
|
||||
echo_stamp() {
|
||||
# TEMPLATE: echo_stamp <TEXT> <TYPE>
|
||||
# TYPE: SUCCESS, ERROR, INFO
|
||||
|
||||
# More info there https://www.shellhacks.com/ru/bash-colors/
|
||||
|
||||
TEXT="$(date '+[%Y-%m-%d %H:%M:%S]') $1"
|
||||
TEXT="\e[1m${TEXT}\e[0m" # BOLD
|
||||
|
||||
case "$2" in
|
||||
SUCCESS)
|
||||
TEXT="\e[32m${TEXT}\e[0m";; # GREEN
|
||||
ERROR)
|
||||
TEXT="\e[31m${TEXT}\e[0m";; # RED
|
||||
*)
|
||||
TEXT="\e[34m${TEXT}\e[0m";; # BLUE
|
||||
esac
|
||||
echo -e ${TEXT}
|
||||
}
|
||||
. /etc/os-release # set $VERSION_CODENAME to Debian release code name
|
||||
|
||||
# https://gist.github.com/letmaik/caa0f6cc4375cbfcc1ff26bd4530c2a3
|
||||
# https://github.com/travis-ci/travis-build/blob/master/lib/travis/build/templates/header.sh
|
||||
@@ -41,7 +23,7 @@ my_travis_retry() {
|
||||
local count=1
|
||||
while [ $count -le 3 ]; do
|
||||
[ $result -ne 0 ] && {
|
||||
echo -e "\n${ANSI_RED}The command \"$@\" failed. Retrying, $count of 3.${ANSI_RESET}\n" >&2
|
||||
echo -e "\n${ANSI_RED}The command \"$*\" failed. Retrying, $count of 3.${ANSI_RESET}\n" >&2
|
||||
}
|
||||
# ! { } ignores set -e, see https://stackoverflow.com/a/4073372
|
||||
! { "$@"; result=$?; }
|
||||
@@ -51,41 +33,39 @@ my_travis_retry() {
|
||||
done
|
||||
|
||||
[ $count -gt 3 ] && {
|
||||
echo -e "\n${ANSI_RED}The command \"$@\" failed 3 times.${ANSI_RESET}\n" >&2
|
||||
echo -e "\n${ANSI_RED}The command \"$*\" failed 3 times.${ANSI_RESET}\n" >&2
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
echo_stamp "Increase apt retries"
|
||||
|
||||
echo "--- Increase apt retries"
|
||||
echo "APT::Acquire::Retries \"3\";" > /etc/apt/apt.conf.d/80-retries
|
||||
|
||||
echo_stamp "Install apt keys & repos"
|
||||
echo "--- Install apt keys & repos"
|
||||
|
||||
# TODO: This STDOUT consist 'OK'
|
||||
apt-get update \
|
||||
&& apt-get install --no-install-recommends -y dirmngr > /dev/null \
|
||||
&& apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
|
||||
|
||||
echo "deb http://packages.ros.org/ros/ubuntu buster main" > /etc/apt/sources.list.d/ros-latest.list
|
||||
# echo "deb http://packages.ros.org/ros/ubuntu buster main" > /etc/apt/sources.list.d/ros-latest.list
|
||||
|
||||
wget -O - 'http://packages.coex.tech/key.asc' | apt-key add -
|
||||
echo 'deb http://packages.coex.tech buster main' >> /etc/apt/sources.list
|
||||
wget -O - 'http://packages.coex.tech/key.asc' | apt-key add -
|
||||
echo "deb http://packages.coex.tech $VERSION_CODENAME main" >> /etc/apt/sources.list
|
||||
|
||||
echo_stamp "Update apt cache"
|
||||
echo "--- Update apt cache"
|
||||
|
||||
# TODO: FIX ERROR: /usr/bin/apt-key: 596: /usr/bin/apt-key: cannot create /dev/null: Permission denied
|
||||
apt-get update
|
||||
# && apt upgrade -y
|
||||
|
||||
# Let's retry fetching those packages several times, just in case
|
||||
echo_stamp "Software installing"
|
||||
my_travis_retry apt-get install --no-install-recommends -y cmake-data=3.13.4-1 cmake=3.13.4-1 # FIXME: using older CMake due to https://travis-ci.org/github/CopterExpress/clover/jobs/764367665#L6984
|
||||
echo "--- Install software"
|
||||
my_travis_retry apt-get install --no-install-recommends -y cmake-data cmake
|
||||
my_travis_retry apt-get install --no-install-recommends -y \
|
||||
unzip \
|
||||
zip \
|
||||
ipython \
|
||||
ipython3 \
|
||||
screen \
|
||||
byobu \
|
||||
@@ -96,64 +76,57 @@ dnsmasq \
|
||||
tmux \
|
||||
tree \
|
||||
vim \
|
||||
libjpeg8 \
|
||||
tcpdump \
|
||||
libpoco-dev \
|
||||
libzbar0 \
|
||||
python3-rosdep \
|
||||
python3-rosinstall-generator \
|
||||
python3-wstool \
|
||||
python3-rosinstall \
|
||||
build-essential \
|
||||
libffi-dev \
|
||||
monkey \
|
||||
pigpio python-pigpio python3-pigpio \
|
||||
pigpio python3-pigpio \
|
||||
i2c-tools \
|
||||
espeak espeak-data python-espeak python3-espeak \
|
||||
espeak espeak-data python3-espeak \
|
||||
ntpdate \
|
||||
python-dev \
|
||||
python3-dev \
|
||||
python-systemd \
|
||||
mjpg-streamer \
|
||||
python3-opencv
|
||||
xxd \
|
||||
python3-dev \
|
||||
python3-systemd \
|
||||
python3-opencv \
|
||||
python3-pip
|
||||
#libjpeg8 \
|
||||
|
||||
# Deny byobu to check available updates
|
||||
sed -i "s/updates_available//" /usr/share/byobu/status/status
|
||||
# sed -i "s/updates_available//" /home/pi/.byobu/status
|
||||
|
||||
echo_stamp "Installing pip"
|
||||
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||
curl https://bootstrap.pypa.io/pip/2.7/get-pip.py -o get-pip2.py
|
||||
python3 get-pip.py
|
||||
python get-pip2.py
|
||||
rm get-pip.py get-pip2.py
|
||||
#my_travis_retry pip install --upgrade pip
|
||||
#my_travis_retry pip3 install --upgrade pip
|
||||
|
||||
echo_stamp "Make sure both pip and pip3 are installed"
|
||||
echo "--- Make sure pip is installed"
|
||||
pip --version
|
||||
pip3 --version
|
||||
|
||||
echo_stamp "Install and enable Butterfly (web terminal)"
|
||||
echo_stamp "Workaround for tornado >= 6.0 breaking butterfly"
|
||||
echo "--- Enable installing packages with pip"
|
||||
mv /usr/lib/python3.11/EXTERNALLY-MANAGED /usr/lib/python3.11/EXTERNALLY-MANAGED.old
|
||||
|
||||
echo "--- Install and enable Butterfly (web terminal)"
|
||||
export CRYPTOGRAPHY_DONT_BUILD_RUST=1
|
||||
my_travis_retry pip3 install cryptography==3.4.6 # https://stackoverflow.com/a/68472128/6850197
|
||||
my_travis_retry pip3 install pyOpenSSL==20.0.1
|
||||
my_travis_retry pip3 install tornado==5.1.1
|
||||
#my_travis_retry pip3 install pyOpenSSL==20.0.1
|
||||
#my_travis_retry pip3 install tornado==5.1.1
|
||||
my_travis_retry pip3 install butterfly
|
||||
my_travis_retry pip3 install butterfly[systemd]
|
||||
systemctl enable butterfly.socket
|
||||
|
||||
echo_stamp "Install ws281x library"
|
||||
echo "--- Install ws281x library"
|
||||
my_travis_retry pip3 install --prefer-binary rpi_ws281x
|
||||
|
||||
echo_stamp "Setup Monkey"
|
||||
echo "--- Setup Monkey"
|
||||
mv /etc/monkey/sites/default /etc/monkey/sites/default.orig
|
||||
mv /root/monkey /etc/monkey/sites/default
|
||||
sed -i 's/SymLink Off/SymLink On/' /etc/monkey/monkey.conf
|
||||
systemctl enable monkey.service
|
||||
|
||||
echo_stamp "Install Node.js"
|
||||
echo "--- Install Node.js"
|
||||
cd /home/pi
|
||||
wget --no-verbose https://nodejs.org/dist/v10.15.0/node-v10.15.0-linux-armv6l.tar.gz
|
||||
tar -xzf node-v10.15.0-linux-armv6l.tar.gz
|
||||
@@ -161,28 +134,24 @@ cp -R node-v10.15.0-linux-armv6l/* /usr/local/
|
||||
rm -rf node-v10.15.0-linux-armv6l/
|
||||
rm node-v10.15.0-linux-armv6l.tar.gz
|
||||
|
||||
echo_stamp "Installing ptvsd"
|
||||
my_travis_retry pip install ptvsd
|
||||
my_travis_retry pip3 install ptvsd
|
||||
echo "--- Installing debugpy"
|
||||
my_travis_retry pip3 install debugpy
|
||||
|
||||
echo_stamp "Installing pyzbar"
|
||||
my_travis_retry pip install pyzbar
|
||||
echo "--- Installing pyzbar"
|
||||
my_travis_retry pip3 install pyzbar
|
||||
|
||||
echo_stamp "Add .vimrc"
|
||||
echo "--- Add .vimrc"
|
||||
cat << EOF > /home/pi/.vimrc
|
||||
set mouse-=a
|
||||
syntax on
|
||||
autocmd BufNewFile,BufRead *.launch set syntax=xml
|
||||
EOF
|
||||
|
||||
echo_stamp "Change default keyboard layout to US"
|
||||
echo "--- Change default keyboard layout to US"
|
||||
sed -i 's/XKBLAYOUT="gb"/XKBLAYOUT="us"/g' /etc/default/keyboard
|
||||
|
||||
echo_stamp "Attempting to kill dirmngr"
|
||||
echo "--- Attempting to kill dirmngr"
|
||||
gpgconf --kill dirmngr
|
||||
# dirmngr is only used by apt-key, so we can safely kill it.
|
||||
# We ignore pkill's exit value as well.
|
||||
pkill -9 -f dirmngr || true
|
||||
|
||||
echo_stamp "End of software installation"
|
||||
|
||||
@@ -12,28 +12,33 @@
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
|
||||
set -ex
|
||||
set -ex # exit on error, echo commands
|
||||
|
||||
echo "Run image tests"
|
||||
|
||||
echo "--- Run image tests"
|
||||
|
||||
export ROS_DISTRO='noetic'
|
||||
export ROS_IP='127.0.0.1'
|
||||
source /opt/ros/${ROS_DISTRO}/setup.bash
|
||||
source /home/pi/catkin_ws/devel/setup.bash
|
||||
systemctl start roscore
|
||||
|
||||
cd /home/pi/catkin_ws/src/clover/builder/test/
|
||||
./tests.sh
|
||||
./tests.py
|
||||
./tests_py3.py
|
||||
[[ $(./test_qr.py) == "Found QRCODE with data Проверка Unicode with center at x=66.0, y=66.0" ]]
|
||||
[[ $(./tests_clever.py) == "Warning: clever package is renamed to clover" ]] # test backwards compatibility
|
||||
|
||||
systemctl stop roscore
|
||||
|
||||
# check documented packages available
|
||||
apt-cache show gst-rtsp-launch
|
||||
apt-cache show openvpn
|
||||
|
||||
echo "Largest packages installed"
|
||||
sudo -E sh -c 'apt-get install -y debian-goodies'
|
||||
dpigs -H -n 100
|
||||
|
||||
echo "Cleanup apt"
|
||||
apt-get autoremove --purge -y
|
||||
apt-get clean
|
||||
|
||||
echo "Move /etc/ld.so.preload back to its original position"
|
||||
mv /etc/ld.so.preload.disabled-for-build /etc/ld.so.preload
|
||||
|
||||
@@ -23,6 +23,10 @@ from clover.srv import GetTelemetry, Navigate, NavigateGlobal, SetPosition, SetV
|
||||
from led_msgs.srv import SetLEDs
|
||||
from led_msgs.msg import LEDStateArray, LEDState
|
||||
from aruco_pose.msg import Marker, MarkerArray, Point2D
|
||||
from vision_msgs.msg import BoundingBox2D, BoundingBox2DArray, BoundingBox3D, BoundingBox3DArray, \
|
||||
Classification2D, Classification3D, Detection2D, Detection2DArray, Detection3D, Detection3DArray, \
|
||||
ObjectHypothesis, ObjectHypothesisWithPose, VisionInfo
|
||||
|
||||
from clover import long_callback
|
||||
|
||||
import dynamic_reconfigure.client
|
||||
@@ -33,9 +37,12 @@ import tf2_geometry_msgs
|
||||
import VL53L1X
|
||||
import pymavlink
|
||||
from pymavlink import mavutil
|
||||
from image_geometry import PinholeCameraModel, StereoCameraModel
|
||||
# from espeak import espeak
|
||||
from pyzbar import pyzbar
|
||||
import docopt
|
||||
import geopy
|
||||
import flask
|
||||
|
||||
print(cv2.getBuildInformation())
|
||||
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -ex
|
||||
set -ex # exit on error, echo commands
|
||||
|
||||
# TODO: validate versions
|
||||
|
||||
# validate required software is installed
|
||||
|
||||
python2 --version
|
||||
python3 --version
|
||||
ipython3 --version
|
||||
|
||||
@@ -22,30 +21,28 @@ pip --version
|
||||
pip3 --version
|
||||
tcpdump --version
|
||||
monkey --version
|
||||
# espeak --version
|
||||
espeak --version
|
||||
xxd --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" ]]
|
||||
[[ $(python -c 'import sys;print(sys.version_info.major)') == "3" ]]
|
||||
|
||||
# ptvsd does not have a stand-alone binary
|
||||
python -m ptvsd --version
|
||||
python3 -m ptvsd --version
|
||||
python -m debugpy --version
|
||||
python3 -m debugpy --version
|
||||
|
||||
pigpiod -v
|
||||
i2cdetect -V
|
||||
butterfly -h
|
||||
/usr/local/bin/butterfly.server.py --help
|
||||
mjpg_streamer --version
|
||||
fi
|
||||
|
||||
# ros stuff
|
||||
|
||||
roscore -h
|
||||
catkin_find
|
||||
rosversion clover
|
||||
rosversion aruco_pose
|
||||
rosversion mavros
|
||||
@@ -61,14 +58,14 @@ rosversion web_video_server
|
||||
rosversion nodelet
|
||||
rosversion image_view
|
||||
|
||||
[[ $(rosversion ws281x) == "0.0.13" ]]
|
||||
[[ $(rosversion ws281x) == "0.0.15" ]]
|
||||
|
||||
if [ -z $VM ]; then
|
||||
rosversion compressed_image_transport
|
||||
# rosversion compressed_image_transport
|
||||
rosversion rosshow
|
||||
rosversion vl53l1x
|
||||
rosversion rosserial
|
||||
[[ $(rosversion cv_camera) == "0.5.1" ]] # patched version with init fix
|
||||
[[ $(rosversion cv_camera) == "0.6.1" ]] # patched version with init fix
|
||||
fi
|
||||
|
||||
# determine user home directory
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Make sure our Python 3 software is installed
|
||||
|
||||
import cv2
|
||||
from pyzbar import pyzbar
|
||||
|
||||
print(cv2.getBuildInformation())
|
||||
@@ -18,7 +18,7 @@ EXCLUDE = 'rviz.png', 'ssid.png', 'sitl_docker_demo.png', 'qgc-params.png', 'but
|
||||
'qgc-battery.png', 'qgc-radio.png', 'qgc-cal-acc.png', 'qgc-esc.png', 'qgc-cal-compass.png', \
|
||||
'qgc.png', 'qgc-parameters.png', 'clever4-front-white-large.png', 'qgc-modes.png', \
|
||||
'qgc-requires-setup.png', 'clever4-front-white.png', 'clever4-kit-white.png', '26_1.png', 'battery_holder.stl', \
|
||||
'camera_case.stl', 'camera_mount.stl'
|
||||
'camera_case.stl', 'camera_mount.stl', 'grip_right.stl', 'grip_left.stl'
|
||||
|
||||
code = 0
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ find_package(catkin REQUIRED COMPONENTS
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
|
||||
|
||||
# https://github.com/mavlink/mavros/blob/7f1a8/mavros/CMakeLists.txt#L42
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};/usr/share/cmake/geographiclib")
|
||||
find_package(GeographicLib REQUIRED)
|
||||
|
||||
# Workaround for OpenCV 3/4 support
|
||||
@@ -80,11 +82,10 @@ catkin_python_setup()
|
||||
## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
|
||||
|
||||
## Generate messages in the 'msg' folder
|
||||
# add_message_files(
|
||||
# FILES
|
||||
# Message1.msg
|
||||
# Message2.msg
|
||||
# )
|
||||
add_message_files(
|
||||
FILES
|
||||
State.msg
|
||||
)
|
||||
|
||||
## Generate services in the 'srv' folder
|
||||
add_service_files(
|
||||
@@ -92,6 +93,9 @@ add_service_files(
|
||||
GetTelemetry.srv
|
||||
Navigate.srv
|
||||
NavigateGlobal.srv
|
||||
SetAltitude.srv
|
||||
SetYaw.srv
|
||||
SetYawRate.srv
|
||||
SetPosition.srv
|
||||
SetVelocity.srv
|
||||
SetAttitude.srv
|
||||
@@ -306,4 +310,5 @@ endif()
|
||||
if (CATKIN_ENABLE_TESTING)
|
||||
find_package(rostest REQUIRED)
|
||||
add_rostest(test/basic.test)
|
||||
add_rostest(test/offboard.test)
|
||||
endif()
|
||||
|
||||
65
clover/camera_info/fisheye_cam_1280x720.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
# Generated from fisheye_cam.yaml by rescale_camera_info.py
|
||||
camera_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 415.5985593268293
|
||||
- 0.0
|
||||
- 400.0
|
||||
- 0.0
|
||||
- 312.35267324512984
|
||||
- 225.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
rows: 3
|
||||
camera_name: main_camera_optical
|
||||
distortion_coefficients:
|
||||
cols: 8
|
||||
data:
|
||||
- 0.215356885
|
||||
- -0.117472846
|
||||
- -0.000306197672
|
||||
- -0.000109444025
|
||||
- -0.00453657258
|
||||
- 0.573090623
|
||||
- -0.127574577
|
||||
- -0.0286125589
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
rows: 1
|
||||
distortion_model: plumb_bob
|
||||
image_height: 720
|
||||
image_width: 1280
|
||||
projection_matrix:
|
||||
cols: 4
|
||||
data:
|
||||
- 415.5985593268293
|
||||
- 0.0
|
||||
- 405.4752811707317
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 312.35267324512984
|
||||
- 205.91677004464282
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
rows: 3
|
||||
rectification_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
rows: 3
|
||||
65
clover/camera_info/fisheye_cam_1920x1080.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
# Generated from fisheye_cam.yaml by rescale_camera_info.py
|
||||
camera_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 1246.7956779804879
|
||||
- 0.0
|
||||
- 1200.0
|
||||
- 0.0
|
||||
- 702.7935148015422
|
||||
- 506.25
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
rows: 3
|
||||
camera_name: main_camera_optical
|
||||
distortion_coefficients:
|
||||
cols: 8
|
||||
data:
|
||||
- 0.215356885
|
||||
- -0.117472846
|
||||
- -0.000306197672
|
||||
- -0.000109444025
|
||||
- -0.00453657258
|
||||
- 0.573090623
|
||||
- -0.127574577
|
||||
- -0.0286125589
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
rows: 1
|
||||
distortion_model: plumb_bob
|
||||
image_height: 1080
|
||||
image_width: 1920
|
||||
projection_matrix:
|
||||
cols: 4
|
||||
data:
|
||||
- 1246.7956779804879
|
||||
- 0.0
|
||||
- 1216.4258435121951
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 702.7935148015422
|
||||
- 463.31273260044634
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
rows: 3
|
||||
rectification_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
rows: 3
|
||||
65
clover/camera_info/fisheye_cam_2592x1944.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
# Generated from fisheye_cam.yaml by rescale_camera_info.py
|
||||
camera_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 5049.522495820976
|
||||
- 0.0
|
||||
- 4860.0
|
||||
- 0.0
|
||||
- 2846.313734946246
|
||||
- 2050.3125
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
rows: 3
|
||||
camera_name: main_camera_optical
|
||||
distortion_coefficients:
|
||||
cols: 8
|
||||
data:
|
||||
- 0.215356885
|
||||
- -0.117472846
|
||||
- -0.000306197672
|
||||
- -0.000109444025
|
||||
- -0.00453657258
|
||||
- 0.573090623
|
||||
- -0.127574577
|
||||
- -0.0286125589
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
rows: 1
|
||||
distortion_model: plumb_bob
|
||||
image_height: 1944
|
||||
image_width: 2592
|
||||
projection_matrix:
|
||||
cols: 4
|
||||
data:
|
||||
- 5049.522495820976
|
||||
- 0.0
|
||||
- 4926.52466622439
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 2846.313734946246
|
||||
- 1876.4165670318075
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
rows: 3
|
||||
rectification_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
rows: 3
|
||||
65
clover/camera_info/fisheye_cam_320x240.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
# Generated from fisheye_cam.yaml by rescale_camera_info.py
|
||||
camera_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 166.23942373073172
|
||||
- 0.0
|
||||
- 160.0
|
||||
- 0.0
|
||||
- 166.5880923974026
|
||||
- 120.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
rows: 3
|
||||
camera_name: main_camera_optical
|
||||
distortion_coefficients:
|
||||
cols: 8
|
||||
data:
|
||||
- 0.215356885
|
||||
- -0.117472846
|
||||
- -0.000306197672
|
||||
- -0.000109444025
|
||||
- -0.00453657258
|
||||
- 0.573090623
|
||||
- -0.127574577
|
||||
- -0.0286125589
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
rows: 1
|
||||
distortion_model: plumb_bob
|
||||
image_height: 240
|
||||
image_width: 320
|
||||
projection_matrix:
|
||||
cols: 4
|
||||
data:
|
||||
- 166.23942373073172
|
||||
- 0.0
|
||||
- 162.19011246829268
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 166.5880923974026
|
||||
- 109.82227735714285
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
rows: 3
|
||||
rectification_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
rows: 3
|
||||
65
clover/camera_info/fisheye_cam_3840x2160.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
# Generated from fisheye_cam.yaml by rescale_camera_info.py
|
||||
camera_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 30297.13497492585
|
||||
- 0.0
|
||||
- 29160.0
|
||||
- 0.0
|
||||
- 12808.411807258106
|
||||
- 9226.40625
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
rows: 3
|
||||
camera_name: main_camera_optical
|
||||
distortion_coefficients:
|
||||
cols: 8
|
||||
data:
|
||||
- 0.215356885
|
||||
- -0.117472846
|
||||
- -0.000306197672
|
||||
- -0.000109444025
|
||||
- -0.00453657258
|
||||
- 0.573090623
|
||||
- -0.127574577
|
||||
- -0.0286125589
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
rows: 1
|
||||
distortion_model: plumb_bob
|
||||
image_height: 2160
|
||||
image_width: 3840
|
||||
projection_matrix:
|
||||
cols: 4
|
||||
data:
|
||||
- 30297.13497492585
|
||||
- 0.0
|
||||
- 29559.14799734634
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 12808.411807258106
|
||||
- 8443.874551643134
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
rows: 3
|
||||
rectification_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
rows: 3
|
||||
65
clover/camera_info/fisheye_cam_4056x3040.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
# Generated from fisheye_cam.yaml by rescale_camera_info.py
|
||||
camera_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 192008.0929035926
|
||||
- 0.0
|
||||
- 184801.5
|
||||
- 0.0
|
||||
- 81119.941445968
|
||||
- 58433.90625
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
rows: 3
|
||||
camera_name: main_camera_optical
|
||||
distortion_coefficients:
|
||||
cols: 8
|
||||
data:
|
||||
- 0.215356885
|
||||
- -0.117472846
|
||||
- -0.000306197672
|
||||
- -0.000109444025
|
||||
- -0.00453657258
|
||||
- 0.573090623
|
||||
- -0.127574577
|
||||
- -0.0286125589
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
rows: 1
|
||||
distortion_model: plumb_bob
|
||||
image_height: 3040
|
||||
image_width: 4056
|
||||
projection_matrix:
|
||||
cols: 4
|
||||
data:
|
||||
- 192008.0929035926
|
||||
- 0.0
|
||||
- 187331.10043318244
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 81119.941445968
|
||||
- 53477.87216040651
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
rows: 3
|
||||
rectification_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
rows: 3
|
||||
65
clover/camera_info/fisheye_cam_640x480.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
# Generated from fisheye_cam.yaml by rescale_camera_info.py
|
||||
camera_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 166.23942373073172
|
||||
- 0.0
|
||||
- 160.0
|
||||
- 0.0
|
||||
- 166.5880923974026
|
||||
- 120.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
rows: 3
|
||||
camera_name: main_camera_optical
|
||||
distortion_coefficients:
|
||||
cols: 8
|
||||
data:
|
||||
- 0.215356885
|
||||
- -0.117472846
|
||||
- -0.000306197672
|
||||
- -0.000109444025
|
||||
- -0.00453657258
|
||||
- 0.573090623
|
||||
- -0.127574577
|
||||
- -0.0286125589
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
rows: 1
|
||||
distortion_model: plumb_bob
|
||||
image_height: 480
|
||||
image_width: 640
|
||||
projection_matrix:
|
||||
cols: 4
|
||||
data:
|
||||
- 166.23942373073172
|
||||
- 0.0
|
||||
- 162.19011246829268
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 166.5880923974026
|
||||
- 109.82227735714285
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
rows: 3
|
||||
rectification_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
rows: 3
|
||||
65
clover/camera_info/fisheye_cam_800x600.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
# Generated from fisheye_cam.yaml by rescale_camera_info.py
|
||||
camera_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 207.79927966341464
|
||||
- 0.0
|
||||
- 200.0
|
||||
- 0.0
|
||||
- 208.23511549675322
|
||||
- 150.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
rows: 3
|
||||
camera_name: main_camera_optical
|
||||
distortion_coefficients:
|
||||
cols: 8
|
||||
data:
|
||||
- 0.215356885
|
||||
- -0.117472846
|
||||
- -0.000306197672
|
||||
- -0.000109444025
|
||||
- -0.00453657258
|
||||
- 0.573090623
|
||||
- -0.127574577
|
||||
- -0.0286125589
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
rows: 1
|
||||
distortion_model: plumb_bob
|
||||
image_height: 600
|
||||
image_width: 800
|
||||
projection_matrix:
|
||||
cols: 4
|
||||
data:
|
||||
- 207.79927966341464
|
||||
- 0.0
|
||||
- 202.73764058536585
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 208.23511549675322
|
||||
- 137.27784669642855
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 0.0
|
||||
- 1.0
|
||||
- 0.0
|
||||
rows: 3
|
||||
rectification_matrix:
|
||||
cols: 3
|
||||
data:
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
- 0
|
||||
- 0
|
||||
- 0
|
||||
- 1
|
||||
rows: 3
|
||||
@@ -1,18 +0,0 @@
|
||||
# taken from: https://github.com/mavlink/mavros/blob/master/libmavconn/cmake/Modules/FindGeographicLib.cmake
|
||||
|
||||
# Look for GeographicLib
|
||||
#
|
||||
# Set
|
||||
# GEOGRAPHICLIB_FOUND = TRUE
|
||||
# GeographicLib_INCLUDE_DIRS = /usr/local/include
|
||||
# GeographicLib_LIBRARIES = /usr/local/lib/libGeographic.so
|
||||
# GeographicLib_LIBRARY_DIRS = /usr/local/lib
|
||||
|
||||
find_path (GeographicLib_INCLUDE_DIRS NAMES GeographicLib/Config.h)
|
||||
|
||||
find_library (GeographicLib_LIBRARIES NAMES Geographic)
|
||||
|
||||
include (FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args (GeographicLib DEFAULT_MSG
|
||||
GeographicLib_LIBRARIES GeographicLib_INCLUDE_DIRS)
|
||||
mark_as_advanced (GeographicLib_LIBRARIES GeographicLib_INCLUDE_DIRS)
|
||||
@@ -5,7 +5,7 @@
|
||||
# - 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.
|
||||
# - prints its name to the console.
|
||||
|
||||
import rospy
|
||||
import cv2
|
||||
|
||||
@@ -16,11 +16,8 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
|
||||
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
|
||||
land = rospy.ServiceProxy('land', Trigger)
|
||||
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \
|
||||
frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
|
||||
frame_id=frame_id, auto_arm=auto_arm)
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=math.nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, speed=speed, frame_id=frame_id, auto_arm=auto_arm)
|
||||
|
||||
if not res.success:
|
||||
return res
|
||||
|
||||
91
clover/examples/red_circle.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# This example makes the drone find and follow the red circle.
|
||||
# To test in the simulator, place 'Red Circle' model on the floor.
|
||||
# More information: https://clover.coex.tech/red_circle
|
||||
|
||||
# Input topic: main_camera/image_raw (camera image)
|
||||
# Output topics:
|
||||
# cv/mask (red color mask)
|
||||
# cv/red_circle (position of the center of the red circle in 3D space)
|
||||
|
||||
import rospy
|
||||
import cv2
|
||||
import numpy as np
|
||||
from math import nan
|
||||
from sensor_msgs.msg import Image, CameraInfo
|
||||
from geometry_msgs.msg import PointStamped, Point
|
||||
from cv_bridge import CvBridge
|
||||
from clover import long_callback, srv
|
||||
import tf2_ros
|
||||
import tf2_geometry_msgs
|
||||
import image_geometry
|
||||
|
||||
rospy.init_node('cv', disable_signals=True) # disable signals to allow interrupting with ctrl+c
|
||||
|
||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
|
||||
|
||||
bridge = CvBridge()
|
||||
|
||||
tf_buffer = tf2_ros.Buffer()
|
||||
tf_listener = tf2_ros.TransformListener(tf_buffer)
|
||||
|
||||
mask_pub = rospy.Publisher('~mask', Image, queue_size=1)
|
||||
point_pub = rospy.Publisher('~red_circle', PointStamped, queue_size=1)
|
||||
|
||||
# read camera info
|
||||
camera_model = image_geometry.PinholeCameraModel()
|
||||
camera_model.fromCameraInfo(rospy.wait_for_message('main_camera/camera_info', CameraInfo))
|
||||
|
||||
|
||||
def img_xy_to_point(xy, dist):
|
||||
xy_rect = camera_model.rectifyPoint(xy)
|
||||
ray = camera_model.projectPixelTo3dRay(xy_rect)
|
||||
return Point(x=ray[0] * dist, y=ray[1] * dist, z=dist)
|
||||
|
||||
def get_center_of_mass(mask):
|
||||
M = cv2.moments(mask)
|
||||
if M['m00'] == 0:
|
||||
return None
|
||||
return M['m10'] // M['m00'], M['m01'] // M['m00']
|
||||
|
||||
follow_red_circle = False
|
||||
|
||||
@long_callback
|
||||
def image_callback(msg):
|
||||
img = bridge.imgmsg_to_cv2(msg, 'bgr8')
|
||||
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
|
||||
|
||||
# we need to use two ranges for red color
|
||||
mask1 = cv2.inRange(img_hsv, (0, 150, 150), (15, 255, 255))
|
||||
mask2 = cv2.inRange(img_hsv, (160, 150, 150), (180, 255, 255))
|
||||
|
||||
# combine two masks using bitwise OR
|
||||
mask = cv2.bitwise_or(mask1, mask2)
|
||||
|
||||
# publish the mask
|
||||
if mask_pub.get_num_connections() > 0:
|
||||
mask_pub.publish(bridge.cv2_to_imgmsg(mask, 'mono8'))
|
||||
|
||||
# calculate x and y of the circle
|
||||
xy = get_center_of_mass(mask)
|
||||
if xy is None:
|
||||
return
|
||||
|
||||
# calculate and publish the position of the circle in 3D space
|
||||
altitude = get_telemetry('terrain').z
|
||||
xy3d = img_xy_to_point(xy, altitude)
|
||||
target = PointStamped(header=msg.header, point=xy3d)
|
||||
point_pub.publish(target)
|
||||
|
||||
if follow_red_circle:
|
||||
# follow the target
|
||||
setpoint = tf_buffer.transform(target, 'map', timeout=rospy.Duration(0.2))
|
||||
set_position(x=setpoint.point.x, y=setpoint.point.y, z=nan, yaw=nan, frame_id=setpoint.header.frame_id)
|
||||
|
||||
# process each camera frame:
|
||||
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1)
|
||||
|
||||
rospy.loginfo('Hit enter to follow the red circle')
|
||||
input()
|
||||
follow_red_circle = True
|
||||
rospy.spin()
|
||||
@@ -16,9 +16,10 @@
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="map_markers" to="aruco_map/map"/>
|
||||
<param name="dictionary" value="2"/> <!-- DICT_4X4_250 -->
|
||||
<param name="estimate_poses" value="true"/>
|
||||
<param name="send_tf" value="true"/>
|
||||
<param name="use_map_markers" value="true"/>
|
||||
<param name="use_map_markers" value="$(arg aruco_map)"/>
|
||||
<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)"/>
|
||||
|
||||
@@ -45,12 +45,13 @@
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<param name="calc_flow_gyro" value="true"/>
|
||||
<param name="roi_rad" value="0.8"/>
|
||||
<param name="disable_on_vpe" value="false"/>
|
||||
<param name="disable_on_vpe" value="true"/>
|
||||
</node>
|
||||
|
||||
<!-- simplified offboard control -->
|
||||
<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="terrain_frame_mode" value="range"/>
|
||||
</node>
|
||||
|
||||
<!-- main camera -->
|
||||
@@ -71,6 +72,9 @@
|
||||
<param name="pass_statuses" type="yaml" value="[0, 6, 7, 11]"/>
|
||||
</node>
|
||||
|
||||
<!-- rangefinder's frame -->
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="rangefinder_frame" args="0 0 -0.05 0 1.5707963268 0 base_link rangefinder" if="$(arg rangefinder_vl53l1x)"/>
|
||||
|
||||
<!-- led strip -->
|
||||
<include file="$(find clover)/launch/led.launch" if="$(arg led)">
|
||||
<arg name="simulator" value="$(arg simulator)"/>
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
</node>
|
||||
|
||||
<!-- high level led effects control, events notification with leds -->
|
||||
<node pkg="clover" name="led_effect" type="led" ns="led" clear_params="true" output="screen" if="$(arg led_effect)">
|
||||
<node pkg="clover" name="led_effect" type="led" clear_params="true" output="screen" if="$(arg led_effect)">
|
||||
<param name="led" value="led"/>
|
||||
<param name="blink_rate" value="2"/>
|
||||
<param name="fade_period" value="0.5"/>
|
||||
<param name="rainbow_period" value="5"/>
|
||||
|
||||
@@ -3,9 +3,15 @@
|
||||
|
||||
<arg name="direction_z" default="down"/> <!-- direction the camera points: down, up -->
|
||||
<arg name="direction_y" default="backward"/> <!-- direction the camera cable points: backward, forward -->
|
||||
<arg name="device" default="/dev/video0"/> <!-- v4l2 device -->
|
||||
<arg name="type" default="libcamera"/> <!-- camera interface: libcamera, v4l2 -->
|
||||
<arg name="camera_id" default="0"/> <!-- libcamera camera id -->
|
||||
<arg name="device" default="/dev/video0"/> <!-- v4l2 device path -->
|
||||
<arg name="width" default="320"/>
|
||||
<arg name="height" default="240"/>
|
||||
<arg name="fps" default="40"/>
|
||||
<arg name="throttled_topic" default="true"/> <!-- enable throttled image topic -->
|
||||
<arg name="throttled_topic_rate" default="5.0"/> <!-- throttled image topic rate -->
|
||||
<arg name="rectify" default="false"/> <!-- enable rectification -->
|
||||
<arg name="simulator" default="false"/>
|
||||
|
||||
<node if="$(eval direction_z == 'down' and direction_y == 'backward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/>
|
||||
@@ -25,20 +31,34 @@
|
||||
<param name="num_worker_threads" value="2"/>
|
||||
</node>
|
||||
|
||||
<!-- camera node -->
|
||||
<node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet main_camera_nodelet_manager" launch-prefix="rosrun clover waitfile $(arg device)" clear_params="true" unless="$(arg simulator)" respawn="true">
|
||||
<!-- camera node using libcamera -->
|
||||
<node pkg="nodelet" type="nodelet" name="main_camera" args="load libcamera_ros/LibcameraRos main_camera_nodelet_manager" output="screen" clear_params="true" if="$(eval not simulator and type == 'libcamera')" respawn="true">
|
||||
<param name="camera_name" value=""/>
|
||||
<param name="camera_id" value="$(arg camera_id)"/>
|
||||
<param name="frame_id" value="main_camera_optical"/>
|
||||
<param name="calib_url" type="string" value="file://$(find clover)/camera_info/fisheye_cam_$(arg width)x$(arg height).yaml"/>
|
||||
<param name="stream_role" value="still"/>
|
||||
<param name="pixel_format" value="RGB888"/>
|
||||
<param name="use_ros_time" value="true"/>
|
||||
<param name="resolution/width" value="$(arg width)"/>
|
||||
<param name="resolution/height" value="$(arg height)"/>
|
||||
|
||||
<!-- see: https://github.com/ctu-mrs/libcamera_ros/blob/b3645/config/param.yaml#L19 -->
|
||||
<param name="control/fps" value="$(arg fps)"/>
|
||||
</node>
|
||||
|
||||
<!-- old camera node for v4l2 (cv_camera) -->
|
||||
<node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet main_camera_nodelet_manager" launch-prefix="rosrun clover waitfile $(arg device)" clear_params="true" if="$(eval not simulator and type == 'v4l2')" respawn="true">
|
||||
<param name="device_path" value="$(arg device)"/>
|
||||
<param name="frame_id" value="main_camera_optical"/>
|
||||
<param name="camera_info_url" value="file://$(find clover)/camera_info/fisheye_cam.yaml"/>
|
||||
|
||||
<param name="rate" value="100"/> <!-- poll rate -->
|
||||
<param name="cv_cap_prop_fps" value="40"/> <!-- camera FPS -->
|
||||
<param name="cv_cap_prop_fps" value="$(arg fps)"/> <!-- camera FPS -->
|
||||
<param name="capture_delay" value="0.02"/> <!-- approximate delay on frame retrieving -->
|
||||
<param name="rescale_camera_info" value="true"/> <!-- automatically rescale camera calibration info -->
|
||||
|
||||
<!-- camera resolution -->
|
||||
<param name="image_width" value="320"/>
|
||||
<param name="image_height" value="240"/>
|
||||
<param name="image_width" value="$(arg width)"/>
|
||||
<param name="image_height" value="$(arg height)"/>
|
||||
</node>
|
||||
|
||||
<!-- camera visualization markers -->
|
||||
@@ -49,4 +69,11 @@
|
||||
<!-- image topic throttled -->
|
||||
<node pkg="topic_tools" name="main_camera_throttle" type="throttle" ns="main_camera"
|
||||
args="messages image_raw $(arg throttled_topic_rate) image_raw_throttled" if="$(arg throttled_topic)"/>
|
||||
|
||||
<!-- rectified image topic -->
|
||||
<node pkg="nodelet" type="nodelet" name="rectify" args="load image_proc/rectify main_camera_nodelet_manager" if="$(arg rectify)">
|
||||
<remap from="image_mono" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="image_rect" to="main_camera/image_rect"/>
|
||||
</node>
|
||||
</launch>
|
||||
|
||||
@@ -77,9 +77,6 @@
|
||||
covariance: 1 # cm
|
||||
</rosparam>
|
||||
|
||||
<!-- Rangefinders frame -->
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="rangefinder_frame" args="0 0 -0.05 0 1.5707963268 0 base_link rangefinder"/>
|
||||
|
||||
<!-- Copter visualization -->
|
||||
<node name="visualization" pkg="mavros_extras" type="visualization" if="$(arg viz)">
|
||||
<remap to="mavros/local_position/pose" from="local_position"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<launch>
|
||||
<!-- shurtcut for running the simulation (`roslaunch clover simulator.launch`) -->
|
||||
<!-- shortcut for running the simulation (`roslaunch clover simulator.launch`) -->
|
||||
<include file="$(find clover_simulation)/launch/simulator.launch"/>
|
||||
</launch>
|
||||
|
||||
38
clover/msg/State.msg
Normal file
@@ -0,0 +1,38 @@
|
||||
uint8 MODE_NONE = 0
|
||||
uint8 MODE_NAVIGATE = 1
|
||||
uint8 MODE_NAVIGATE_GLOBAL = 2
|
||||
uint8 MODE_POSITION = 3
|
||||
uint8 MODE_VELOCITY = 4
|
||||
uint8 MODE_ATTITUDE = 5
|
||||
uint8 MODE_RATES = 6
|
||||
|
||||
uint8 YAW_MODE_YAW = 0
|
||||
uint8 YAW_MODE_YAW_RATE = 1
|
||||
uint8 YAW_MODE_YAW_TOWARDS = 2
|
||||
|
||||
# type of offboard control
|
||||
uint8 mode
|
||||
uint8 yaw_mode
|
||||
|
||||
# targets
|
||||
float32 x
|
||||
float32 y
|
||||
float32 z
|
||||
float32 speed
|
||||
float32 lat
|
||||
float32 lon
|
||||
float32 vx
|
||||
float32 vy
|
||||
float32 vz
|
||||
float32 roll
|
||||
float32 pitch
|
||||
float32 yaw
|
||||
float32 roll_rate
|
||||
float32 pitch_rate
|
||||
float32 yaw_rate
|
||||
float32 thrust
|
||||
|
||||
# frames of reference
|
||||
string xy_frame_id
|
||||
string z_frame_id
|
||||
string yaw_frame_id
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="3">
|
||||
<name>clover</name>
|
||||
<version>0.23.0</version>
|
||||
<version>0.24.0</version>
|
||||
<description>The Clover package</description>
|
||||
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
@@ -42,6 +42,8 @@
|
||||
<depend condition="$ROS_PYTHON_VERSION == 2">python-lxml</depend>
|
||||
<depend condition="$ROS_PYTHON_VERSION == 3">python3-lxml</depend>
|
||||
<depend>dynamic_reconfigure</depend>
|
||||
<depend>image_proc</depend>
|
||||
<depend>image_geometry</depend>
|
||||
<exec_depend>python-pymavlink</exec_depend>
|
||||
<test_depend>ros_pytest</test_depend>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
flask==1.1.1
|
||||
geopy==1.11.0
|
||||
smbus2==0.3.0
|
||||
VL53L1X==0.0.5
|
||||
flask
|
||||
geopy
|
||||
smbus2
|
||||
VL53L1X
|
||||
|
||||
@@ -35,11 +35,8 @@ def print_current_map_position():
|
||||
dist = rospy.wait_for_message('rangefinder/range', Range).range
|
||||
print('Map position:\tx={:.1f}\ty={:.1f}\tz={:.1f}\tyaw={:.1f}\tdist={:.2f}'.format(telem.x, telem.y, telem.z, telem.yaw, dist))
|
||||
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \
|
||||
frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
|
||||
frame_id=frame_id, auto_arm=auto_arm)
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=math.nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, speed=speed, frame_id=frame_id, auto_arm=auto_arm)
|
||||
|
||||
if not res.success:
|
||||
return res
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import rospy
|
||||
import math
|
||||
from math import nan
|
||||
from math import nan, inf
|
||||
import signal
|
||||
import sys
|
||||
from clover import srv
|
||||
@@ -15,6 +15,8 @@ rospy.init_node('autotest_flight', disable_signals=True) # disable signals to al
|
||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
|
||||
navigate_global = handle_response(rospy.ServiceProxy('navigate_global', srv.NavigateGlobal))
|
||||
set_yaw = handle_response(rospy.ServiceProxy('set_yaw', srv.SetYaw))
|
||||
set_yaw_rate = handle_response(rospy.ServiceProxy('set_yaw_rate', srv.SetYawRate))
|
||||
set_position = handle_response(rospy.ServiceProxy('set_position', srv.SetPosition))
|
||||
set_velocity = handle_response(rospy.ServiceProxy('set_velocity', srv.SetVelocity))
|
||||
set_attitude = handle_response(rospy.ServiceProxy('set_attitude', srv.SetAttitude))
|
||||
@@ -28,11 +30,8 @@ def interrupt(sig, frame):
|
||||
|
||||
signal.signal(signal.SIGINT, interrupt)
|
||||
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=nan, yaw_rate=0, speed=0.5, \
|
||||
frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
|
||||
frame_id=frame_id, auto_arm=auto_arm)
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, speed=speed, frame_id=frame_id, auto_arm=auto_arm)
|
||||
|
||||
if not res.success:
|
||||
return res
|
||||
@@ -69,17 +68,17 @@ set_velocity(vx=1, vy=0.0, vz=0, frame_id='body')
|
||||
rospy.sleep(2)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Rotate right 90° [enter] ')
|
||||
navigate(yaw=-math.pi / 2, frame_id='navigate_target')
|
||||
input('Rotate right 90° using set_yaw [enter] ')
|
||||
set_yaw(yaw=-math.pi / 2, frame_id='navigate_target')
|
||||
rospy.sleep(3)
|
||||
|
||||
input('Use set_attitude to fly backwards [enter]')
|
||||
set_attitude(pitch=-0.3, roll=0, yaw=0, thrust=0.5, frame_id='body')
|
||||
set_attitude(roll=0, pitch=-0.3, yaw=0, thrust=0.5, frame_id='body')
|
||||
rospy.sleep(0.3)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Use set_attitude to fly right [enter]')
|
||||
set_attitude(pitch=0, roll=0.3, yaw=0, thrust=0.5, frame_id='body')
|
||||
set_attitude(roll=0.3, pitch=0, yaw=0, thrust=0.5, frame_id='body')
|
||||
rospy.sleep(0.5)
|
||||
set_position(frame_id='body')
|
||||
|
||||
@@ -88,13 +87,13 @@ set_rates(roll_rate=1.2, thrust=0.5)
|
||||
rospy.sleep(0.4)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Rotate 360° to the right using yaw_rate [enter]')
|
||||
set_position(x=nan, y=nan, z=nan, frame_id='body', yaw=nan, yaw_rate=-1)
|
||||
input('Rotate 360° to the right using set_yaw_rate [enter]')
|
||||
set_yaw_rate(yaw_rate=-1)
|
||||
rospy.sleep(2 * math.pi)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Return to start point [enter]')
|
||||
navigate_wait(x=start.x, y=start.y, z=start.z, yaw=start.yaw, speed=1, frame_id='map')
|
||||
input('Return to start point heading forward [enter]')
|
||||
navigate_wait(x=start.x, y=start.y, z=start.z, yaw=inf, speed=1, frame_id='map')
|
||||
|
||||
input('Land [enter]')
|
||||
land()
|
||||
|
||||
@@ -309,15 +309,19 @@ int main(int argc, char **argv)
|
||||
nh_priv.param("notify/low_battery/threshold", low_battery_threshold, 3.7);
|
||||
nh_priv.param("notify/error/ignore", error_ignore, {});
|
||||
|
||||
ros::service::waitForService("set_leds"); // cannot work without set_leds service
|
||||
set_leds_srv = nh.serviceClient<led_msgs::SetLEDs>("set_leds", true);
|
||||
std::string led; // led namespace
|
||||
nh_priv.param("led", led, std::string("led"));
|
||||
if (!led.empty()) led += "/";
|
||||
|
||||
ros::service::waitForService(led + "set_leds"); // cannot work without set_leds service
|
||||
set_leds_srv = nh.serviceClient<led_msgs::SetLEDs>(led + "set_leds", true);
|
||||
|
||||
// wait for leds count info
|
||||
handleState(*ros::topic::waitForMessage<led_msgs::LEDStateArray>("state", nh));
|
||||
handleState(*ros::topic::waitForMessage<led_msgs::LEDStateArray>(led + "state", nh));
|
||||
|
||||
auto state_sub = nh.subscribe("state", 1, &handleState);
|
||||
auto state_sub = nh.subscribe(led + "state", 1, &handleState);
|
||||
|
||||
auto set_effect = nh.advertiseService("set_effect", &setEffect);
|
||||
auto set_effect = nh.advertiseService(led + "set_effect", &setEffect);
|
||||
|
||||
auto mavros_state_sub = nh.subscribe("mavros/state", 1, &handleMavrosState);
|
||||
auto battery_sub = nh.subscribe("mavros/battery", 1, &handleBattery);
|
||||
|
||||
@@ -107,7 +107,7 @@ def ff(value, precision=2):
|
||||
param_get = rospy.ServiceProxy('mavros/param/get', ParamGet)
|
||||
|
||||
|
||||
def get_param(name, default=None):
|
||||
def get_param(name, default=None, strict=True):
|
||||
try:
|
||||
res = param_get(param_id=name)
|
||||
except rospy.ServiceException as e:
|
||||
@@ -115,7 +115,8 @@ def get_param(name, default=None):
|
||||
return None
|
||||
|
||||
if not res.success:
|
||||
failure('unable to retrieve PX4 parameter %s', name)
|
||||
if strict:
|
||||
failure('unable to retrieve PX4 parameter %s', name)
|
||||
return default
|
||||
else:
|
||||
if res.value.integer != 0:
|
||||
@@ -263,7 +264,7 @@ def check_fcu():
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
if est == 1:
|
||||
info('selected estimator: LPE')
|
||||
fuse = get_param('LPE_FUSION')
|
||||
fuse = int(get_param('LPE_FUSION'))
|
||||
if fuse & (1 << 4):
|
||||
info('LPE_FUSION: land detector fusion is enabled')
|
||||
else:
|
||||
@@ -302,6 +303,14 @@ def check_fcu():
|
||||
failure('cell voltage is not available, https://clover.coex.tech/power')
|
||||
else:
|
||||
cell = battery.cell_voltage[0]
|
||||
# number of cells 1 means this is overall voltage
|
||||
if len(battery.cell_voltage) == 1:
|
||||
n_cells = get_param('BAT1_N_CELLS', strict=False)
|
||||
if n_cells is None:
|
||||
# older PX4
|
||||
n_cells = get_param('BAT_N_CELLS', strict=True)
|
||||
cell /= n_cells
|
||||
|
||||
if cell > 4.3 or cell < 3.0:
|
||||
failure('incorrect cell voltage: %.2f V, https://clover.coex.tech/power', cell)
|
||||
elif cell < 3.7:
|
||||
@@ -316,7 +325,13 @@ def check_fcu():
|
||||
failure('cannot read time sync offset')
|
||||
|
||||
except rospy.ROSException:
|
||||
failure('no MAVROS state (check wiring)')
|
||||
failure('no MAVROS state')
|
||||
fcu_url = rospy.get_param('mavros/fcu_url', '?')
|
||||
if fcu_url == '/dev/px4fmu':
|
||||
if not os.path.exists('/lib/udev/rules.d/99-px4fmu.rules'):
|
||||
info('udev rules are not installed, install udev rules or change usb_device to /dev/ttyACM0 in mavros.launch')
|
||||
else:
|
||||
info('udev did\'t recognize px4fmu device, check wiring or change usb_device to /dev/ttyACM0 in mavros.launch')
|
||||
info('fcu_url = %s', rospy.get_param('mavros/fcu_url', '?'))
|
||||
|
||||
|
||||
@@ -487,7 +502,7 @@ def check_vpe():
|
||||
failure('vision yaw weight is zero, change ATT_W_EXT_HDG parameter')
|
||||
else:
|
||||
info('vision yaw weight: %s', ff(vision_yaw_w))
|
||||
fuse = get_param('LPE_FUSION')
|
||||
fuse = int(get_param('LPE_FUSION'))
|
||||
if not fuse & (1 << 2):
|
||||
failure('vision position fusion is disabled, change LPE_FUSION parameter')
|
||||
delay = get_param('LPE_VIS_DELAY')
|
||||
@@ -495,11 +510,22 @@ def check_vpe():
|
||||
failure('LPE_VIS_DELAY = %s, but it should be zero', delay)
|
||||
info('LPE_VIS_XY = %s m, LPE_VIS_Z = %s m', get_paramf('LPE_VIS_XY'), get_paramf('LPE_VIS_Z'))
|
||||
elif est == 2:
|
||||
fuse = get_param('EKF2_AID_MASK')
|
||||
if not fuse & (1 << 3):
|
||||
failure('vision position fusion is disabled, change EKF2_AID_MASK parameter')
|
||||
if not fuse & (1 << 4):
|
||||
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
|
||||
ev_ctrl = get_param('EKF2_EV_CTRL', strict=False)
|
||||
if ev_ctrl is not None: # PX4 after v1.14
|
||||
ev_ctrl = int(ev_ctrl)
|
||||
if not ev_ctrl & (1 << 0):
|
||||
failure('vision horizontal position fusion is disabled, change EKF2_EV_CTRL parameter')
|
||||
if not ev_ctrl & (1 << 1):
|
||||
failure('vision vertical position fusion is disabled, change EKF2_EV_CTRL parameter')
|
||||
if not ev_ctrl & (1 << 3):
|
||||
failure('vision yaw fusion is disabled, change EKF2_EV_CTRL parameter')
|
||||
else: # PX4 before v1.14
|
||||
fuse = int(get_param('EKF2_AID_MASK'))
|
||||
if not fuse & (1 << 3):
|
||||
failure('vision position fusion is disabled, change EKF2_AID_MASK parameter')
|
||||
if not fuse & (1 << 4):
|
||||
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
|
||||
|
||||
delay = get_param('EKF2_EV_DELAY')
|
||||
if delay != 0:
|
||||
failure('EKF2_EV_DELAY = %.2f, but it should be zero', delay)
|
||||
@@ -606,8 +632,14 @@ def check_global_position():
|
||||
rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=0.8)
|
||||
except rospy.ROSException:
|
||||
info('no global position')
|
||||
if get_param('SYS_MC_EST_GROUP') == 2 and (get_param('EKF2_AID_MASK', 0) & (1 << 0)):
|
||||
failure('enabled GPS fusion may suppress vision position aiding')
|
||||
if get_param('SYS_MC_EST_GROUP') == 2:
|
||||
gps_ctrl = get_param('EKF2_GPS_CTRL', strict=False)
|
||||
if gps_ctrl is not None: # PX4 after v1.14
|
||||
if int(gps_ctrl) & (1 << 0):
|
||||
failure('GPS fusion enabled may suppress vision position aiding, change EKF2_GPS_CTRL')
|
||||
else: # PX4 before v1.14
|
||||
if int(get_param('EKF2_AID_MASK', 0)) & (1 << 0):
|
||||
failure('GPS fusion enabled may suppress vision position aiding, change EKF2_AID_MASK')
|
||||
|
||||
|
||||
@check('Optical flow')
|
||||
@@ -626,7 +658,7 @@ def check_optical_flow():
|
||||
failure('SENS_FLOW_ROT = %s, but it should be zero', rot)
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
if est == 1:
|
||||
fuse = get_param('LPE_FUSION')
|
||||
fuse = int(get_param('LPE_FUSION'))
|
||||
if not fuse & (1 << 1):
|
||||
failure('optical flow fusion is disabled, change LPE_FUSION parameter')
|
||||
if not fuse & (1 << 1):
|
||||
@@ -640,9 +672,14 @@ def check_optical_flow():
|
||||
get_paramf('LPE_FLW_R', 4),
|
||||
get_paramf('LPE_FLW_RR', 4))
|
||||
elif est == 2:
|
||||
fuse = get_param('EKF2_AID_MASK', 0)
|
||||
if not fuse & (1 << 1):
|
||||
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
|
||||
of_ctrl = get_param('EKF2_OF_CTRL', strict=False)
|
||||
if of_ctrl is not None: # PX4 after v1.14
|
||||
if of_ctrl == 0:
|
||||
failure('optical flow fusion is disabled, change EKF2_OF_CTRL')
|
||||
else: # PX4 before v1.14
|
||||
fuse = int(get_param('EKF2_AID_MASK', 0))
|
||||
if not fuse & (1 << 1):
|
||||
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
|
||||
delay = get_param('EKF2_OF_DELAY', 0)
|
||||
if delay != 0:
|
||||
failure('EKF2_OF_DELAY = %.2f, but it should be zero', delay)
|
||||
@@ -684,23 +721,26 @@ def check_rangefinder():
|
||||
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
if est == 1:
|
||||
fuse = get_param('LPE_FUSION', 0)
|
||||
fuse = int(get_param('LPE_FUSION', 0))
|
||||
if not fuse & (1 << 5):
|
||||
info('"pub agl as lpos down" in LPE_FUSION is disabled, NOT operating over flat surface')
|
||||
else:
|
||||
info('"pub agl as lpos down" in LPE_FUSION is enabled, operating over flat surface')
|
||||
|
||||
elif est == 2:
|
||||
hgt = get_param('EKF2_HGT_MODE')
|
||||
hgt = get_param('EKF2_HGT_REF', strict=False)
|
||||
if hgt is None: # PX4 before v1.14
|
||||
hgt = get_param('EKF2_HGT_MODE')
|
||||
if hgt != 2:
|
||||
info('EKF2_HGT_MODE != Range sensor, NOT operating over flat surface')
|
||||
else:
|
||||
info('EKF2_HGT_MODE = Range sensor, operating over flat surface')
|
||||
aid = get_param('EKF2_RNG_AID')
|
||||
if aid != 1:
|
||||
info('EKF2_RNG_AID != 1, range sensor aiding disabled')
|
||||
else:
|
||||
info('EKF2_RNG_AID = 1, range sensor aiding enabled')
|
||||
aid = get_param('EKF2_RNG_AID', strict=False)
|
||||
if aid is not None: # PX4 before v1.14
|
||||
if aid != 1:
|
||||
info('EKF2_RNG_AID != 1, range sensor aiding disabled')
|
||||
else:
|
||||
info('EKF2_RNG_AID = 1, range sensor aiding enabled')
|
||||
|
||||
|
||||
@check('Boot duration')
|
||||
|
||||
@@ -23,12 +23,14 @@
|
||||
#include <tf2_ros/static_transform_broadcaster.h>
|
||||
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
|
||||
#include <std_srvs/Trigger.h>
|
||||
#include <geometry_msgs/PointStamped.h>
|
||||
#include <geometry_msgs/PoseStamped.h>
|
||||
#include <geometry_msgs/TwistStamped.h>
|
||||
#include <geometry_msgs/Vector3Stamped.h>
|
||||
#include <geometry_msgs/QuaternionStamped.h>
|
||||
#include <sensor_msgs/NavSatFix.h>
|
||||
#include <sensor_msgs/BatteryState.h>
|
||||
#include <sensor_msgs/Range.h>
|
||||
#include <mavros_msgs/CommandBool.h>
|
||||
#include <mavros_msgs/SetMode.h>
|
||||
#include <mavros_msgs/PositionTarget.h>
|
||||
@@ -37,14 +39,19 @@
|
||||
#include <mavros_msgs/State.h>
|
||||
#include <mavros_msgs/StatusText.h>
|
||||
#include <mavros_msgs/ManualControl.h>
|
||||
#include <mavros_msgs/Altitude.h>
|
||||
|
||||
#include <clover/GetTelemetry.h>
|
||||
#include <clover/Navigate.h>
|
||||
#include <clover/NavigateGlobal.h>
|
||||
#include <clover/SetAltitude.h>
|
||||
#include <clover/SetYaw.h>
|
||||
#include <clover/SetYawRate.h>
|
||||
#include <clover/SetPosition.h>
|
||||
#include <clover/SetVelocity.h>
|
||||
#include <clover/SetAttitude.h>
|
||||
#include <clover/SetRates.h>
|
||||
#include <clover/State.h>
|
||||
|
||||
using std::string;
|
||||
using std::isnan;
|
||||
@@ -54,6 +61,7 @@ using namespace clover;
|
||||
using mavros_msgs::PositionTarget;
|
||||
using mavros_msgs::AttitudeTarget;
|
||||
using mavros_msgs::Thrust;
|
||||
using mavros_msgs::Altitude;
|
||||
|
||||
// tf2
|
||||
tf2_ros::Buffer tf_buffer;
|
||||
@@ -79,35 +87,43 @@ float default_speed;
|
||||
bool auto_release;
|
||||
bool land_only_in_offboard, nav_from_sp, check_kill_switch;
|
||||
std::map<string, string> reference_frames;
|
||||
string terrain_frame_mode;
|
||||
|
||||
// Publishers
|
||||
ros::Publisher attitude_pub, attitude_raw_pub, position_pub, position_raw_pub, rates_pub, thrust_pub;
|
||||
ros::Publisher attitude_pub, attitude_raw_pub, position_pub, position_raw_pub, rates_pub, thrust_pub, state_pub;
|
||||
|
||||
// Service clients
|
||||
ros::ServiceClient arming, set_mode;
|
||||
|
||||
// Containers
|
||||
ros::Timer setpoint_timer;
|
||||
tf::Quaternion tq;
|
||||
PoseStamped position_msg;
|
||||
PositionTarget position_raw_msg;
|
||||
AttitudeTarget att_raw_msg;
|
||||
Thrust thrust_msg;
|
||||
TwistStamped rates_msg;
|
||||
//TwistStamped rates_msg;
|
||||
TransformStamped target, setpoint;
|
||||
geometry_msgs::TransformStamped body;
|
||||
geometry_msgs::TransformStamped terrain;
|
||||
|
||||
// State
|
||||
PoseStamped nav_start;
|
||||
PoseStamped setpoint_position, setpoint_position_transformed;
|
||||
Vector3Stamped setpoint_velocity, setpoint_velocity_transformed;
|
||||
QuaternionStamped setpoint_attitude, setpoint_attitude_transformed;
|
||||
float setpoint_yaw_rate;
|
||||
PointStamped setpoint_position;
|
||||
PointStamped setpoint_altitude;
|
||||
Vector3Stamped setpoint_velocity;
|
||||
float setpoint_yaw, setpoint_roll, setpoint_pitch;
|
||||
Vector3 setpoint_rates;
|
||||
string yaw_frame_id;
|
||||
float setpoint_thrust;
|
||||
float nav_speed;
|
||||
float setpoint_lat = NAN, setpoint_lon = NAN;
|
||||
bool busy = false;
|
||||
bool wait_armed = false;
|
||||
bool nav_from_sp_flag = false;
|
||||
|
||||
// Last published
|
||||
PoseStamped setpoint_pose_local;
|
||||
Vector3Stamped setpoint_velocity_local;
|
||||
float yaw_local;
|
||||
|
||||
enum setpoint_type_t {
|
||||
NONE,
|
||||
NAVIGATE,
|
||||
@@ -115,7 +131,10 @@ enum setpoint_type_t {
|
||||
POSITION,
|
||||
VELOCITY,
|
||||
ATTITUDE,
|
||||
RATES
|
||||
RATES,
|
||||
_ALTITUDE,
|
||||
_YAW,
|
||||
_YAW_RATE,
|
||||
};
|
||||
|
||||
enum setpoint_type_t setpoint_type = NONE;
|
||||
@@ -170,7 +189,7 @@ void handleLocalPosition(const PoseStamped& pose)
|
||||
{
|
||||
local_position = pose;
|
||||
publishBodyFrame();
|
||||
// TODO: terrain?, home?
|
||||
// TODO: home?
|
||||
}
|
||||
|
||||
// wait for transform without interrupting publishing setpoints
|
||||
@@ -188,6 +207,29 @@ inline bool waitTransform(const string& target, const string& source,
|
||||
return false;
|
||||
}
|
||||
|
||||
void publishTerrain(const double distance, const ros::Time& stamp)
|
||||
{
|
||||
if (!waitTransform(local_frame, body.child_frame_id, stamp, ros::Duration(0.1))) return;
|
||||
|
||||
auto t = tf_buffer.lookupTransform(local_frame, body.child_frame_id, stamp);
|
||||
t.child_frame_id = terrain.child_frame_id;
|
||||
t.transform.translation.z -= distance;
|
||||
static_transform_broadcaster->sendTransform(t);
|
||||
}
|
||||
|
||||
void handleAltitude(const Altitude& alt)
|
||||
{
|
||||
if (!std::isfinite(alt.bottom_clearance)) return;
|
||||
publishTerrain(alt.bottom_clearance, alt.header.stamp);
|
||||
}
|
||||
|
||||
void handleRange(const Range& range)
|
||||
{
|
||||
if (!std::isfinite(range.range)) return;
|
||||
// TODO: check it's facing down
|
||||
publishTerrain(range.range, range.header.stamp);
|
||||
}
|
||||
|
||||
#define TIMEOUT(msg, timeout) (msg.header.stamp.isZero() || (ros::Time::now() - msg.header.stamp > timeout))
|
||||
|
||||
bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
|
||||
@@ -207,11 +249,11 @@ bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
|
||||
res.vx = NAN;
|
||||
res.vy = NAN;
|
||||
res.vz = NAN;
|
||||
res.pitch = NAN;
|
||||
res.roll = NAN;
|
||||
res.pitch = NAN;
|
||||
res.yaw = NAN;
|
||||
res.pitch_rate = NAN;
|
||||
res.roll_rate = NAN;
|
||||
res.pitch_rate = NAN;
|
||||
res.yaw_rate = NAN;
|
||||
res.voltage = NAN;
|
||||
res.cell_voltage = NAN;
|
||||
@@ -341,20 +383,20 @@ inline float getDistance(const Point& from, const Point& to)
|
||||
return hypot(from.x - to.x, from.y - to.y, from.z - to.z);
|
||||
}
|
||||
|
||||
void getNavigateSetpoint(const ros::Time& stamp, float speed, Point& nav_setpoint)
|
||||
void getNavigateSetpoint(const ros::Time& stamp, const float speed, Point& nav_setpoint)
|
||||
{
|
||||
if (wait_armed) {
|
||||
// don't start navigating if we're waiting arming
|
||||
nav_start.header.stamp = stamp;
|
||||
}
|
||||
|
||||
float distance = getDistance(nav_start.pose.position, setpoint_position_transformed.pose.position);
|
||||
float distance = getDistance(nav_start.pose.position, setpoint_pose_local.pose.position);
|
||||
float time = distance / speed;
|
||||
float passed = std::min((stamp - nav_start.header.stamp).toSec() / time, 1.0);
|
||||
|
||||
nav_setpoint.x = nav_start.pose.position.x + (setpoint_position_transformed.pose.position.x - nav_start.pose.position.x) * passed;
|
||||
nav_setpoint.y = nav_start.pose.position.y + (setpoint_position_transformed.pose.position.y - nav_start.pose.position.y) * passed;
|
||||
nav_setpoint.z = nav_start.pose.position.z + (setpoint_position_transformed.pose.position.z - nav_start.pose.position.z) * passed;
|
||||
nav_setpoint.x = nav_start.pose.position.x + (setpoint_pose_local.pose.position.x - nav_start.pose.position.x) * passed;
|
||||
nav_setpoint.y = nav_start.pose.position.y + (setpoint_pose_local.pose.position.y - nav_start.pose.position.y) * passed;
|
||||
nav_setpoint.z = nav_start.pose.position.z + (setpoint_pose_local.pose.position.z - nav_start.pose.position.z) * passed;
|
||||
}
|
||||
|
||||
PoseStamped globalToLocal(double lat, double lon)
|
||||
@@ -385,44 +427,101 @@ PoseStamped globalToLocal(double lat, double lon)
|
||||
return pose;
|
||||
}
|
||||
|
||||
// publish navigate_target frame
|
||||
void publishTarget(ros::Time stamp, bool _static = false)
|
||||
{
|
||||
bool single_frame = (setpoint_position.header.frame_id == setpoint_altitude.header.frame_id);
|
||||
|
||||
// handle yaw for target frame
|
||||
if (setpoint_yaw_type == YAW || setpoint_yaw_type == YAW_RATE) { // use last set yaw for yaw_rate
|
||||
if (setpoint_altitude.header.frame_id == yaw_frame_id) {
|
||||
target.transform.rotation = tf::createQuaternionMsgFromYaw(setpoint_yaw);
|
||||
} else {
|
||||
single_frame = false;
|
||||
target.transform.rotation = tf::createQuaternionMsgFromYaw(yaw_local);
|
||||
}
|
||||
} else if (setpoint_yaw_type == TOWARDS) {
|
||||
single_frame = false;
|
||||
target.transform.rotation = tf::createQuaternionMsgFromYaw(yaw_local);
|
||||
}
|
||||
|
||||
if (_static && single_frame) {
|
||||
// publish at user's command, if all frames are the same
|
||||
target.header.frame_id = setpoint_position.header.frame_id;
|
||||
target.header.stamp = stamp;
|
||||
target.transform.translation.x = setpoint_position.point.x;
|
||||
target.transform.translation.y = setpoint_position.point.y;
|
||||
target.transform.translation.z = setpoint_position.point.z;
|
||||
|
||||
} else if (!_static) {
|
||||
// publish at each iteration, if frames are different
|
||||
target.header = setpoint_pose_local.header;
|
||||
target.transform.translation.x = setpoint_pose_local.pose.position.x;
|
||||
target.transform.translation.y = setpoint_pose_local.pose.position.y;
|
||||
target.transform.translation.z = setpoint_pose_local.pose.position.z;
|
||||
}
|
||||
|
||||
static_transform_broadcaster->sendTransform(target);
|
||||
}
|
||||
|
||||
void publish(const ros::Time stamp)
|
||||
{
|
||||
if (setpoint_type == NONE) return;
|
||||
|
||||
position_raw_msg.header.stamp = stamp;
|
||||
thrust_msg.header.stamp = stamp;
|
||||
rates_msg.header.stamp = stamp;
|
||||
|
||||
try {
|
||||
// transform position and/or yaw
|
||||
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION || setpoint_type == VELOCITY || setpoint_type == ATTITUDE) {
|
||||
setpoint_position.header.stamp = stamp;
|
||||
tf_buffer.transform(setpoint_position, setpoint_position_transformed, local_frame, ros::Duration(0.05));
|
||||
// transform position
|
||||
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
|
||||
setpoint_position.header.stamp = stamp;
|
||||
setpoint_altitude.header.stamp = stamp;
|
||||
// transform xy
|
||||
try {
|
||||
auto xy = tf_buffer.transform(setpoint_position, local_frame, ros::Duration(0.05));
|
||||
setpoint_pose_local.header = xy.header;
|
||||
setpoint_pose_local.pose.position.x = xy.point.x;
|
||||
setpoint_pose_local.pose.position.y = xy.point.y;
|
||||
} catch (tf2::TransformException& ex) {
|
||||
// can't transform xy, use last known
|
||||
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
|
||||
}
|
||||
|
||||
// transform velocity
|
||||
if (setpoint_type == VELOCITY) {
|
||||
setpoint_velocity.header.stamp = stamp;
|
||||
tf_buffer.transform(setpoint_velocity, setpoint_velocity_transformed, local_frame, ros::Duration(0.05));
|
||||
// transform altitude
|
||||
try {
|
||||
setpoint_pose_local.pose.position.z = tf_buffer.transform(setpoint_altitude, local_frame, ros::Duration(0.05)).point.z;
|
||||
} catch (tf2::TransformException& ex) {
|
||||
// can't transform altitude, use last known
|
||||
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
|
||||
}
|
||||
|
||||
} catch (const tf2::TransformException& e) {
|
||||
ROS_WARN_THROTTLE(10, "can't transform");
|
||||
}
|
||||
|
||||
// transform yaw
|
||||
if (setpoint_yaw_type == YAW) {
|
||||
try {
|
||||
QuaternionStamped q;
|
||||
q.header.stamp = stamp;
|
||||
q.header.frame_id = yaw_frame_id;
|
||||
q.quaternion = tf::createQuaternionMsgFromYaw(setpoint_yaw);
|
||||
yaw_local = tf2::getYaw(tf_buffer.transform(q, local_frame, ros::Duration(0.05)).quaternion);
|
||||
} catch (tf2::TransformException& ex) {
|
||||
// can't transform yaw, use last known
|
||||
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
// compute navigate setpoint
|
||||
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) {
|
||||
position_msg.pose.orientation = setpoint_position_transformed.pose.orientation; // copy yaw
|
||||
getNavigateSetpoint(stamp, nav_speed, position_msg.pose.position);
|
||||
|
||||
if (setpoint_yaw_type == TOWARDS) {
|
||||
double yaw_towards = atan2(position_msg.pose.position.y - nav_start.pose.position.y,
|
||||
position_msg.pose.position.x - nav_start.pose.position.x);
|
||||
position_msg.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(0, 0, yaw_towards);
|
||||
yaw_local = atan2(position_msg.pose.position.y - nav_start.pose.position.y,
|
||||
position_msg.pose.position.x - nav_start.pose.position.x);
|
||||
}
|
||||
|
||||
position_msg.pose.orientation = tf::createQuaternionMsgFromYaw(yaw_local);
|
||||
}
|
||||
|
||||
if (setpoint_type == POSITION) {
|
||||
position_msg = setpoint_position_transformed;
|
||||
position_msg = setpoint_pose_local;
|
||||
position_msg.pose.orientation = tf::createQuaternionMsgFromYaw(yaw_local);
|
||||
}
|
||||
|
||||
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) {
|
||||
@@ -439,14 +538,14 @@ void publish(const ros::Time stamp)
|
||||
PositionTarget::IGNORE_AFY +
|
||||
PositionTarget::IGNORE_AFZ +
|
||||
PositionTarget::IGNORE_YAW;
|
||||
position_raw_msg.yaw_rate = setpoint_yaw_rate;
|
||||
position_raw_msg.yaw_rate = setpoint_rates.z;
|
||||
position_raw_msg.position = position_msg.pose.position;
|
||||
position_raw_pub.publish(position_raw_msg);
|
||||
}
|
||||
|
||||
// publish setpoint frame
|
||||
if (!setpoint.child_frame_id.empty()) {
|
||||
if (setpoint.header.stamp == position_msg.header.stamp) {
|
||||
if (setpoint.header.stamp >= position_msg.header.stamp) {
|
||||
return; // avoid TF_REPEATED_DATA warnings
|
||||
}
|
||||
|
||||
@@ -458,9 +557,22 @@ void publish(const ros::Time stamp)
|
||||
setpoint.header.stamp = position_msg.header.stamp;
|
||||
transform_broadcaster->sendTransform(setpoint);
|
||||
}
|
||||
|
||||
// publish dynamic target frame
|
||||
publishTarget(stamp);
|
||||
}
|
||||
|
||||
if (setpoint_type == VELOCITY) {
|
||||
// transform velocity to local frame
|
||||
setpoint_velocity.header.stamp = stamp;
|
||||
try {
|
||||
setpoint_velocity_local = tf_buffer.transform(setpoint_velocity, local_frame, ros::Duration(0.05));
|
||||
} catch (tf2::TransformException& ex) {
|
||||
// can't transform velocity, use last known
|
||||
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
|
||||
}
|
||||
|
||||
// publish velocity
|
||||
position_raw_msg.type_mask = PositionTarget::IGNORE_PX +
|
||||
PositionTarget::IGNORE_PY +
|
||||
PositionTarget::IGNORE_PZ +
|
||||
@@ -468,14 +580,22 @@ void publish(const ros::Time stamp)
|
||||
PositionTarget::IGNORE_AFY +
|
||||
PositionTarget::IGNORE_AFZ;
|
||||
position_raw_msg.type_mask += setpoint_yaw_type == YAW ? PositionTarget::IGNORE_YAW_RATE : PositionTarget::IGNORE_YAW;
|
||||
position_raw_msg.velocity = setpoint_velocity_transformed.vector;
|
||||
position_raw_msg.yaw = tf2::getYaw(setpoint_position_transformed.pose.orientation);
|
||||
position_raw_msg.yaw_rate = setpoint_yaw_rate;
|
||||
position_raw_msg.velocity = setpoint_velocity_local.vector;
|
||||
position_raw_msg.yaw = yaw_local;
|
||||
position_raw_msg.yaw_rate = setpoint_rates.z;
|
||||
position_raw_pub.publish(position_raw_msg);
|
||||
}
|
||||
|
||||
if (setpoint_type == ATTITUDE) {
|
||||
attitude_pub.publish(setpoint_position_transformed);
|
||||
PoseStamped msg;
|
||||
msg.header.stamp = stamp;
|
||||
msg.header.frame_id = local_frame;
|
||||
msg.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(setpoint_roll, setpoint_pitch, yaw_local);
|
||||
attitude_pub.publish(msg);
|
||||
|
||||
Thrust thrust_msg;
|
||||
thrust_msg.header.stamp = stamp;
|
||||
thrust_msg.thrust = setpoint_thrust;
|
||||
thrust_pub.publish(thrust_msg);
|
||||
}
|
||||
|
||||
@@ -484,11 +604,12 @@ void publish(const ros::Time stamp)
|
||||
// thrust_pub.publish(thrust_msg);
|
||||
// mavros rates topics waits for rates in local frame
|
||||
// use rates in body frame for simplicity
|
||||
AttitudeTarget att_raw_msg;
|
||||
att_raw_msg.header.stamp = stamp;
|
||||
att_raw_msg.header.frame_id = fcu_frame;
|
||||
att_raw_msg.type_mask = AttitudeTarget::IGNORE_ATTITUDE;
|
||||
att_raw_msg.body_rate = rates_msg.twist.angular;
|
||||
att_raw_msg.thrust = thrust_msg.thrust;
|
||||
att_raw_msg.body_rate = setpoint_rates;
|
||||
att_raw_msg.thrust = setpoint_thrust;
|
||||
attitude_raw_pub.publish(att_raw_msg);
|
||||
}
|
||||
}
|
||||
@@ -528,10 +649,59 @@ inline void checkState()
|
||||
throw std::runtime_error("No connection to FCU, https://clover.coex.tech/connection");
|
||||
}
|
||||
|
||||
void publishState()
|
||||
{
|
||||
clover::State msg;
|
||||
msg.mode = setpoint_type;
|
||||
msg.yaw_mode = setpoint_yaw_type;
|
||||
|
||||
if (setpoint_position.header.frame_id.empty()) {
|
||||
msg.x = NAN;
|
||||
msg.y = NAN;
|
||||
msg.z = NAN;
|
||||
} else {
|
||||
msg.x = setpoint_position.point.x;
|
||||
msg.y = setpoint_position.point.y;
|
||||
msg.z = setpoint_altitude.point.z;
|
||||
}
|
||||
|
||||
msg.speed = nav_speed;
|
||||
msg.lat = setpoint_lat;
|
||||
msg.lon = setpoint_lon;
|
||||
msg.vx = setpoint_velocity.vector.x;
|
||||
msg.vy = setpoint_velocity.vector.y;
|
||||
msg.vz = setpoint_velocity.vector.z;
|
||||
msg.roll = setpoint_roll;
|
||||
msg.pitch = setpoint_pitch;
|
||||
msg.yaw = !yaw_frame_id.empty() ? setpoint_yaw : NAN;
|
||||
|
||||
msg.roll_rate = setpoint_rates.x;
|
||||
msg.pitch_rate = setpoint_rates.y;
|
||||
msg.yaw_rate = setpoint_rates.z;
|
||||
msg.thrust = setpoint_thrust;
|
||||
|
||||
if (setpoint_type == VELOCITY) {
|
||||
msg.xy_frame_id = setpoint_velocity.header.frame_id;
|
||||
msg.z_frame_id = setpoint_velocity.header.frame_id;
|
||||
} else {
|
||||
msg.xy_frame_id = setpoint_position.header.frame_id;
|
||||
msg.z_frame_id = setpoint_altitude.header.frame_id;
|
||||
}
|
||||
msg.yaw_frame_id = yaw_frame_id;
|
||||
|
||||
state_pub.publish(msg);
|
||||
}
|
||||
|
||||
inline float safe(float value) {
|
||||
return std::isfinite(value) ? value : 0;
|
||||
}
|
||||
|
||||
#define ENSURE_FINITE(var) { if (!std::isfinite(var)) throw std::runtime_error(#var " argument cannot be NaN or Inf"); }
|
||||
|
||||
#define ENSURE_NON_INF(var) { if (std::isinf(var)) throw std::runtime_error(#var " argument cannot be Inf"); }
|
||||
|
||||
bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, float vy, float vz,
|
||||
float pitch, float roll, float yaw, float pitch_rate, float roll_rate, float yaw_rate, // editorconfig-checker-disable-line
|
||||
float roll, float pitch, float yaw, float roll_rate, float pitch_rate, float yaw_rate, // editorconfig-checker-disable-line
|
||||
float lat, float lon, float thrust, float speed, string frame_id, bool auto_arm, // editorconfig-checker-disable-line
|
||||
uint8_t& success, string& message) // editorconfig-checker-disable-line
|
||||
{
|
||||
@@ -558,69 +728,40 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
||||
auto search = reference_frames.find(frame_id);
|
||||
const string& reference_frame = search == reference_frames.end() ? frame_id : search->second;
|
||||
|
||||
// Serve "partial" commands
|
||||
ENSURE_NON_INF(x);
|
||||
ENSURE_NON_INF(y);
|
||||
ENSURE_NON_INF(z);
|
||||
ENSURE_NON_INF(speed); // TODO: allow inf
|
||||
ENSURE_NON_INF(vx);
|
||||
ENSURE_NON_INF(vy);
|
||||
ENSURE_NON_INF(vz);
|
||||
ENSURE_NON_INF(roll);
|
||||
ENSURE_NON_INF(pitch);
|
||||
ENSURE_NON_INF(roll_rate);
|
||||
ENSURE_NON_INF(pitch_rate);
|
||||
ENSURE_NON_INF(yaw_rate);
|
||||
ENSURE_NON_INF(thrust);
|
||||
|
||||
if (!auto_arm && std::isfinite(yaw) &&
|
||||
isnan(x) && isnan(y) && isnan(z) && isnan(vx) && isnan(vy) && isnan(vz) &&
|
||||
isnan(pitch) && isnan(roll) && isnan(thrust) &&
|
||||
isnan(lat) && isnan(lon)) {
|
||||
// change only the yaw
|
||||
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == VELOCITY) {
|
||||
if (!waitTransform(setpoint_position.header.frame_id, frame_id, stamp, transform_timeout))
|
||||
throw std::runtime_error("Can't transform from " + frame_id + " to " + setpoint_position.header.frame_id);
|
||||
|
||||
message = "Changing yaw only";
|
||||
|
||||
QuaternionStamped q;
|
||||
q.header.frame_id = frame_id;
|
||||
q.header.stamp = stamp;
|
||||
q.quaternion = tf::createQuaternionMsgFromYaw(yaw); // TODO: pitch=0, roll=0 is not totally correct
|
||||
setpoint_position.pose.orientation = tf_buffer.transform(q, setpoint_position.header.frame_id).quaternion;
|
||||
setpoint_yaw_type = YAW;
|
||||
goto publish_setpoint;
|
||||
} else {
|
||||
throw std::runtime_error("Setting yaw is possible only when position or velocity setpoints active");
|
||||
}
|
||||
}
|
||||
|
||||
if (!auto_arm && std::isfinite(yaw_rate) &&
|
||||
isnan(x) && isnan(y) && isnan(z) && isnan(vx) && isnan(vy) && isnan(vz) &&
|
||||
isnan(pitch) && isnan(roll) && isnan(yaw) && isnan(thrust) &&
|
||||
isnan(lat) && isnan(lon)) {
|
||||
// change only the yaw rate
|
||||
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == VELOCITY) {
|
||||
message = "Changing yaw rate only";
|
||||
|
||||
setpoint_yaw_type = YAW_RATE;
|
||||
setpoint_yaw_rate = yaw_rate;
|
||||
goto publish_setpoint;
|
||||
} else {
|
||||
throw std::runtime_error("Setting yaw rate is possible only when position or velocity setpoints active");
|
||||
}
|
||||
}
|
||||
|
||||
// Serve normal commands
|
||||
|
||||
if (sp_type == NAVIGATE || sp_type == POSITION) {
|
||||
ENSURE_FINITE(x);
|
||||
ENSURE_FINITE(y);
|
||||
ENSURE_FINITE(z);
|
||||
} else if (sp_type == NAVIGATE_GLOBAL) {
|
||||
if (sp_type == NAVIGATE_GLOBAL) {
|
||||
ENSURE_FINITE(lat);
|
||||
ENSURE_FINITE(lon);
|
||||
ENSURE_FINITE(z);
|
||||
} else if (sp_type == VELOCITY) {
|
||||
ENSURE_FINITE(vx);
|
||||
ENSURE_FINITE(vy);
|
||||
ENSURE_FINITE(vz);
|
||||
} else if (sp_type == ATTITUDE) {
|
||||
ENSURE_FINITE(pitch);
|
||||
ENSURE_FINITE(roll);
|
||||
ENSURE_FINITE(thrust);
|
||||
} else if (sp_type == RATES) {
|
||||
ENSURE_FINITE(pitch_rate);
|
||||
ENSURE_FINITE(roll_rate);
|
||||
ENSURE_FINITE(thrust);
|
||||
}
|
||||
|
||||
if (isfinite(x) != isfinite(y)) {
|
||||
throw std::runtime_error("x and y can be set only together");
|
||||
}
|
||||
|
||||
if (isfinite(yaw_rate)) {
|
||||
if (sp_type > RATES && setpoint_type == ATTITUDE) {
|
||||
throw std::runtime_error("Yaw rate cannot be set in attitude mode.");
|
||||
}
|
||||
}
|
||||
|
||||
// set_altitude
|
||||
if (sp_type == _ALTITUDE) {
|
||||
if (setpoint_type == VELOCITY || setpoint_type == ATTITUDE || setpoint_type == RATES) {
|
||||
throw std::runtime_error("Altitude cannot be set in velocity, attitude or rates mode.");
|
||||
}
|
||||
}
|
||||
|
||||
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
|
||||
@@ -634,20 +775,13 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
||||
speed = default_speed;
|
||||
}
|
||||
|
||||
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) {
|
||||
if (yaw_rate != 0 && !std::isnan(yaw))
|
||||
throw std::runtime_error("Yaw value should be NaN for setting yaw rate");
|
||||
|
||||
if (std::isnan(yaw_rate) && std::isnan(yaw))
|
||||
throw std::runtime_error("Both yaw and yaw_rate cannot be NaN");
|
||||
}
|
||||
|
||||
if (sp_type == NAVIGATE_GLOBAL) {
|
||||
if (TIMEOUT(global_position, global_position_timeout))
|
||||
throw std::runtime_error("No global position");
|
||||
}
|
||||
|
||||
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY || sp_type == ATTITUDE) {
|
||||
// if any value need to be transformed to reference frame
|
||||
if (isfinite(x) || isfinite(y) || isfinite(z) || isfinite(vx) || isfinite(vy) || isfinite(vz) || isfinite(yaw)) {
|
||||
// make sure transform from frame_id to reference frame available
|
||||
if (!waitTransform(reference_frame, frame_id, stamp, transform_timeout))
|
||||
throw std::runtime_error("Can't transform from " + frame_id + " to " + reference_frame);
|
||||
@@ -664,15 +798,27 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
||||
auto xy_in_req_frame = tf_buffer.transform(pose_local, frame_id);
|
||||
x = xy_in_req_frame.pose.position.x;
|
||||
y = xy_in_req_frame.pose.position.y;
|
||||
setpoint_lat = lat;
|
||||
setpoint_lon = lon;
|
||||
}
|
||||
|
||||
// Everything fine - switch setpoint type
|
||||
setpoint_type = sp_type;
|
||||
if (sp_type <= RATES) {
|
||||
setpoint_type = sp_type;
|
||||
}
|
||||
|
||||
if (sp_type != NAVIGATE && sp_type != NAVIGATE_GLOBAL) {
|
||||
if (setpoint_type != NAVIGATE && setpoint_type != NAVIGATE_GLOBAL) {
|
||||
nav_from_sp_flag = false;
|
||||
}
|
||||
|
||||
bool to_auto_arm = auto_arm && (state.mode != "OFFBOARD" || !state.armed);
|
||||
if (to_auto_arm || setpoint_type == VELOCITY || setpoint_type == ATTITUDE || setpoint_type == RATES) {
|
||||
// invalidate position setpoint
|
||||
setpoint_position.header.frame_id = "";
|
||||
setpoint_altitude.header.frame_id = "";
|
||||
yaw_frame_id = "";
|
||||
}
|
||||
|
||||
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
|
||||
// starting point
|
||||
if (nav_from_sp && nav_from_sp_flag) {
|
||||
@@ -681,89 +827,139 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
||||
} else {
|
||||
nav_start = local_position;
|
||||
}
|
||||
nav_speed = speed;
|
||||
|
||||
if (!isnan(speed)) {
|
||||
nav_speed = speed;
|
||||
}
|
||||
|
||||
nav_from_sp_flag = true;
|
||||
}
|
||||
|
||||
// if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) {
|
||||
// if (std::isnan(yaw) && yaw_rate == 0) {
|
||||
// // keep yaw unchanged
|
||||
// // TODO: this is incorrect, because we need yaw in desired frame
|
||||
// yaw = tf2::getYaw(local_position.pose.orientation);
|
||||
// }
|
||||
// }
|
||||
// handle position
|
||||
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
|
||||
|
||||
if (sp_type == POSITION || sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == VELOCITY || sp_type == ATTITUDE) {
|
||||
// destination point and/or attitude
|
||||
PoseStamped ps;
|
||||
ps.header.frame_id = frame_id;
|
||||
ps.header.stamp = stamp;
|
||||
ps.pose.position.x = x;
|
||||
ps.pose.position.y = y;
|
||||
ps.pose.position.z = z;
|
||||
ps.pose.orientation.w = 1.0; // Ensure quaternion is always valid
|
||||
PointStamped desired;
|
||||
desired.header.frame_id = frame_id;
|
||||
desired.header.stamp = stamp;
|
||||
desired.point.x = safe(x);
|
||||
desired.point.y = safe(y);
|
||||
desired.point.z = safe(z);
|
||||
|
||||
if (sp_type == ATTITUDE) {
|
||||
ps.pose.position.x = 0;
|
||||
ps.pose.position.y = 0;
|
||||
ps.pose.position.z = 0;
|
||||
ps.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(roll, pitch, yaw);
|
||||
} else if (std::isnan(yaw)) {
|
||||
setpoint_yaw_type = YAW_RATE;
|
||||
setpoint_yaw_rate = yaw_rate;
|
||||
} else if (std::isinf(yaw) && yaw > 0) {
|
||||
// yaw towards
|
||||
setpoint_yaw_type = TOWARDS;
|
||||
yaw = 0;
|
||||
setpoint_yaw_rate = 0;
|
||||
} else {
|
||||
setpoint_yaw_type = YAW;
|
||||
setpoint_yaw_rate = 0;
|
||||
ps.pose.orientation = tf::createQuaternionMsgFromYaw(yaw);
|
||||
// transform to reference frame
|
||||
desired = tf_buffer.transform(desired, reference_frame);
|
||||
|
||||
// set horizontal position
|
||||
if (isfinite(x) && isfinite(y)) {
|
||||
setpoint_position = desired;
|
||||
} else if (setpoint_position.header.frame_id.empty()) {
|
||||
// TODO: use transform for current stamp
|
||||
setpoint_position.header = local_position.header;
|
||||
setpoint_position.point = local_position.pose.position;
|
||||
}
|
||||
|
||||
tf_buffer.transform(ps, setpoint_position, reference_frame);
|
||||
// set altitude
|
||||
if (isfinite(z)) {
|
||||
setpoint_altitude = desired;
|
||||
} else if (setpoint_altitude.header.frame_id.empty()) {
|
||||
setpoint_altitude.header = local_position.header;
|
||||
setpoint_altitude.point = local_position.pose.position;
|
||||
}
|
||||
}
|
||||
|
||||
// handle velocity
|
||||
if (sp_type == VELOCITY) {
|
||||
Vector3Stamped vel;
|
||||
vel.header.frame_id = frame_id;
|
||||
vel.header.stamp = stamp;
|
||||
vel.vector.x = vx;
|
||||
vel.vector.y = vy;
|
||||
vel.vector.z = vz;
|
||||
tf_buffer.transform(vel, setpoint_velocity, reference_frame);
|
||||
// TODO: allow setting different modes by altitude and xy
|
||||
Vector3Stamped desired;
|
||||
desired.header.frame_id = frame_id;
|
||||
desired.header.stamp = stamp;
|
||||
desired.vector.x = safe(vx);
|
||||
desired.vector.y = safe(vy);
|
||||
desired.vector.z = safe(vz);
|
||||
|
||||
// transform to reference frame
|
||||
desired = tf_buffer.transform(desired, reference_frame);
|
||||
setpoint_velocity.header = desired.header;
|
||||
|
||||
// set horizontal velocity
|
||||
if (isfinite(vx) && isfinite(vy)) {
|
||||
setpoint_velocity.vector.x = desired.vector.x;
|
||||
setpoint_velocity.vector.y = desired.vector.y;
|
||||
}
|
||||
|
||||
// set vertical velocity
|
||||
if (isfinite(vz)) {
|
||||
setpoint_velocity.vector.z = desired.vector.z;
|
||||
}
|
||||
}
|
||||
|
||||
if (sp_type == ATTITUDE || sp_type == RATES) {
|
||||
thrust_msg.thrust = thrust;
|
||||
// handle yaw
|
||||
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY || sp_type == ATTITUDE || sp_type == _YAW) {
|
||||
if (isfinite(yaw)) {
|
||||
setpoint_yaw_type = YAW;
|
||||
QuaternionStamped desired;
|
||||
desired.header.frame_id = frame_id;
|
||||
desired.header.stamp = stamp;
|
||||
desired.quaternion = tf::createQuaternionMsgFromYaw(yaw);
|
||||
|
||||
// transform to reference frame
|
||||
desired = tf_buffer.transform(desired, reference_frame);
|
||||
setpoint_yaw = tf2::getYaw(desired.quaternion);
|
||||
yaw_frame_id = reference_frame;
|
||||
|
||||
} else if (isinf(yaw) && yaw > 0) {
|
||||
// yaw towards
|
||||
setpoint_yaw_type = TOWARDS;
|
||||
|
||||
} else if (yaw_frame_id.empty() || sp_type == _YAW) {
|
||||
// yaw is nan and not set previously OR set_yaw(yaw=nan) was called
|
||||
setpoint_yaw_type = YAW;
|
||||
setpoint_yaw = tf2::getYaw(local_position.pose.orientation); // set yaw to current yaw
|
||||
yaw_frame_id = local_position.header.frame_id;
|
||||
}
|
||||
}
|
||||
|
||||
if (sp_type == RATES) {
|
||||
rates_msg.twist.angular.x = roll_rate;
|
||||
rates_msg.twist.angular.y = pitch_rate;
|
||||
rates_msg.twist.angular.z = yaw_rate;
|
||||
// handle roll
|
||||
if (isfinite(roll)) {
|
||||
setpoint_roll = roll;
|
||||
}
|
||||
|
||||
// handle pitch
|
||||
if (isfinite(pitch)) {
|
||||
setpoint_pitch = pitch;
|
||||
}
|
||||
|
||||
// handle yaw rate
|
||||
if (isfinite(yaw_rate)) {
|
||||
setpoint_yaw_type = YAW_RATE;
|
||||
setpoint_rates.z = yaw_rate;
|
||||
}
|
||||
|
||||
// handle pitch rate
|
||||
if (isfinite(roll_rate)) {
|
||||
setpoint_rates.x = roll_rate;
|
||||
}
|
||||
|
||||
// handle roll rate
|
||||
if (isfinite(pitch_rate)) {
|
||||
setpoint_rates.y = pitch_rate;
|
||||
}
|
||||
|
||||
// handle thrust
|
||||
if (isfinite(thrust)) {
|
||||
setpoint_thrust = thrust;
|
||||
}
|
||||
|
||||
wait_armed = auto_arm;
|
||||
|
||||
publish_setpoint:
|
||||
publish(stamp); // calculate initial transformed messages first
|
||||
setpoint_timer.start();
|
||||
|
||||
// publish target frame
|
||||
if (!target.child_frame_id.empty()) {
|
||||
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
|
||||
target.header.frame_id = setpoint_position.header.frame_id;
|
||||
target.header.stamp = stamp;
|
||||
target.transform.translation.x = setpoint_position.pose.position.x;
|
||||
target.transform.translation.y = setpoint_position.pose.position.y;
|
||||
target.transform.translation.z = setpoint_position.pose.position.z;
|
||||
target.transform.rotation = setpoint_position.pose.orientation;
|
||||
static_transform_broadcaster->sendTransform(target);
|
||||
}
|
||||
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
|
||||
publishTarget(stamp, true);
|
||||
}
|
||||
|
||||
publishState();
|
||||
|
||||
if (auto_arm) {
|
||||
offboardAndArm();
|
||||
wait_armed = false;
|
||||
@@ -788,27 +984,39 @@ publish_setpoint:
|
||||
}
|
||||
|
||||
bool navigate(Navigate::Request& req, Navigate::Response& res) {
|
||||
return serve(NAVIGATE, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
return serve(NAVIGATE, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
}
|
||||
|
||||
bool navigateGlobal(NavigateGlobal::Request& req, NavigateGlobal::Response& res) {
|
||||
return serve(NAVIGATE_GLOBAL, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, req.lat, req.lon, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
return serve(NAVIGATE_GLOBAL, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, req.lat, req.lon, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
}
|
||||
|
||||
bool setAltitude(SetAltitude::Request& req, SetAltitude::Response& res) {
|
||||
return serve(_ALTITUDE, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, false, res.success, res.message);
|
||||
}
|
||||
|
||||
bool setYaw(SetYaw::Request& req, SetYaw::Response& res) {
|
||||
return serve(_YAW, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, false, res.success, res.message);
|
||||
}
|
||||
|
||||
bool setYawRate(SetYawRate::Request& req, SetYawRate::Response& res) {
|
||||
return serve(_YAW_RATE, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, "", false, res.success, res.message);
|
||||
}
|
||||
|
||||
bool setPosition(SetPosition::Request& req, SetPosition::Response& res) {
|
||||
return serve(POSITION, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
return serve(POSITION, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
}
|
||||
|
||||
bool setVelocity(SetVelocity::Request& req, SetVelocity::Response& res) {
|
||||
return serve(VELOCITY, NAN, NAN, NAN, req.vx, req.vy, req.vz, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
return serve(VELOCITY, NAN, NAN, NAN, req.vx, req.vy, req.vz, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
}
|
||||
|
||||
bool setAttitude(SetAttitude::Request& req, SetAttitude::Response& res) {
|
||||
return serve(ATTITUDE, NAN, NAN, NAN, NAN, NAN, NAN, req.pitch, req.roll, req.yaw, NAN, NAN, NAN, NAN, NAN, req.thrust, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
return serve(ATTITUDE, NAN, NAN, NAN, NAN, NAN, NAN, req.roll, req.pitch, req.yaw, NAN, NAN, NAN, NAN, NAN, req.thrust, NAN, req.frame_id, req.auto_arm, res.success, res.message);
|
||||
}
|
||||
|
||||
bool setRates(SetRates::Request& req, SetRates::Response& res) {
|
||||
return serve(RATES, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.pitch_rate, req.roll_rate, req.yaw_rate, NAN, NAN, req.thrust, NAN, "", req.auto_arm, res.success, res.message);
|
||||
return serve(RATES, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.roll_rate, req.pitch_rate, req.yaw_rate, NAN, NAN, req.thrust, NAN, "", req.auto_arm, res.success, res.message);
|
||||
}
|
||||
|
||||
bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||
@@ -840,9 +1048,7 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||
auto start = ros::Time::now();
|
||||
while (ros::ok()) {
|
||||
if (state.mode == "AUTO.LAND") {
|
||||
res.success = true;
|
||||
busy = false;
|
||||
return true;
|
||||
break;
|
||||
}
|
||||
if (ros::Time::now() - start > land_timeout)
|
||||
throw std::runtime_error("Land request timed out");
|
||||
@@ -851,6 +1057,18 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||
r.sleep();
|
||||
}
|
||||
|
||||
// stop setpoints and invalidate position setpoint
|
||||
setpoint_timer.stop();
|
||||
setpoint_type = NONE;
|
||||
setpoint_position.header.frame_id = "";
|
||||
setpoint_altitude.header.frame_id = "";
|
||||
yaw_frame_id = "";
|
||||
publishState();
|
||||
|
||||
res.success = true;
|
||||
busy = false;
|
||||
return true;
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
res.message = e.what();
|
||||
ROS_INFO("%s", e.what());
|
||||
@@ -863,6 +1081,11 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||
bool release(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||
{
|
||||
setpoint_timer.stop();
|
||||
setpoint_type = NONE;
|
||||
setpoint_position.header.frame_id = "";
|
||||
setpoint_altitude.header.frame_id = "";
|
||||
yaw_frame_id = "";
|
||||
publishState();
|
||||
res.success = true;
|
||||
return true;
|
||||
}
|
||||
@@ -888,6 +1111,8 @@ int main(int argc, char **argv)
|
||||
nh_priv.param("check_kill_switch", check_kill_switch, true);
|
||||
nh_priv.param("default_speed", default_speed, 0.5f);
|
||||
nh_priv.param<string>("body_frame", body.child_frame_id, "body");
|
||||
nh_priv.param<string>("terrain_frame", terrain.child_frame_id, "terrain");
|
||||
nh_priv.param<string>("terrain_frame_mode", terrain_frame_mode, "altitude");
|
||||
nh_priv.getParam("reference_frames", reference_frames);
|
||||
|
||||
// Default reference frames
|
||||
@@ -923,6 +1148,20 @@ int main(int argc, char **argv)
|
||||
auto manual_control_sub = nh.subscribe(mavros + "/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>);
|
||||
auto local_position_sub = nh.subscribe(mavros + "/local_position/pose", 1, &handleLocalPosition);
|
||||
|
||||
ros::Subscriber altitude_sub;
|
||||
if (!body.child_frame_id.empty() && !terrain.child_frame_id.empty()) {
|
||||
terrain.header.frame_id = local_frame;
|
||||
if (terrain_frame_mode == "altitude") {
|
||||
altitude_sub = nh.subscribe(mavros + "/altitude", 1, &handleAltitude);
|
||||
} else if (terrain_frame_mode == "range") {
|
||||
string range_topic = nh_priv.param("range_topic", string("rangefinder/range"));
|
||||
altitude_sub = nh.subscribe(range_topic, 1, &handleRange);
|
||||
} else {
|
||||
ROS_FATAL("Unknown terrain_frame_mode: %s, valid values: altitude, range", terrain_frame_mode.c_str());
|
||||
ros::shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// Setpoint publishers
|
||||
position_pub = nh.advertise<PoseStamped>(mavros + "/setpoint_position/local", 1);
|
||||
position_raw_pub = nh.advertise<PositionTarget>(mavros + "/setpoint_raw/local", 1);
|
||||
@@ -931,10 +1170,16 @@ int main(int argc, char **argv)
|
||||
rates_pub = nh.advertise<TwistStamped>(mavros + "/setpoint_attitude/cmd_vel", 1);
|
||||
thrust_pub = nh.advertise<Thrust>(mavros + "/setpoint_attitude/thrust", 1);
|
||||
|
||||
// State publisher
|
||||
state_pub = nh_priv.advertise<clover::State>("state", 1, true);
|
||||
|
||||
// Service servers
|
||||
auto gt_serv = nh.advertiseService("get_telemetry", &getTelemetry);
|
||||
auto na_serv = nh.advertiseService("navigate", &navigate);
|
||||
auto ng_serv = nh.advertiseService("navigate_global", &navigateGlobal);
|
||||
auto sl_serv = nh.advertiseService("set_altitude", &setAltitude);
|
||||
auto ya_serv = nh.advertiseService("set_yaw", &setYaw);
|
||||
auto yr_serv = nh.advertiseService("set_yaw_rate", &setYawRate);
|
||||
auto sp_serv = nh.advertiseService("set_position", &setPosition);
|
||||
auto sv_serv = nh.advertiseService("set_velocity", &setVelocity);
|
||||
auto sa_serv = nh.advertiseService("set_attitude", &setAttitude);
|
||||
@@ -948,7 +1193,7 @@ int main(int argc, char **argv)
|
||||
position_msg.header.frame_id = local_frame;
|
||||
position_raw_msg.header.frame_id = local_frame;
|
||||
position_raw_msg.coordinate_frame = PositionTarget::FRAME_LOCAL_NED;
|
||||
rates_msg.header.frame_id = fcu_frame;
|
||||
//rates_msg.header.frame_id = fcu_frame;
|
||||
|
||||
ROS_INFO("ready");
|
||||
ros::spin();
|
||||
|
||||
@@ -11,12 +11,14 @@
|
||||
|
||||
#include <string>
|
||||
#include <ros/ros.h>
|
||||
#include <tf/transform_datatypes.h>
|
||||
#include <tf2/transform_datatypes.h>
|
||||
#include <tf2_ros/buffer.h>
|
||||
#include <tf2_ros/transform_listener.h>
|
||||
#include <tf2_ros/static_transform_broadcaster.h>
|
||||
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
|
||||
#include <geometry_msgs/TransformStamped.h>
|
||||
#include <geometry_msgs/Quaternion.h>
|
||||
#include <geometry_msgs/PoseStamped.h>
|
||||
#include <geometry_msgs/PoseWithCovarianceStamped.h>
|
||||
#include <std_srvs/Trigger.h>
|
||||
@@ -66,6 +68,13 @@ inline Pose getPose(const PoseStampedConstPtr& pose) { return pose->pose; }
|
||||
|
||||
inline Pose getPose(const PoseWithCovarianceStampedConstPtr& pose) { return pose->pose.pose; }
|
||||
|
||||
inline void keepYaw(Quaternion& quaternion)
|
||||
{
|
||||
tf::Quaternion q;
|
||||
q.setRPY(0, 0, tf::getYaw(quaternion));
|
||||
tf::quaternionTFToMsg(q, quaternion);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void callback(const T& msg)
|
||||
{
|
||||
@@ -88,10 +97,29 @@ void callback(const T& msg)
|
||||
if (!offset_frame_id.empty()) {
|
||||
if (reset_flag || msg->header.stamp - vpe.header.stamp > offset_timeout) {
|
||||
// calculate the offset
|
||||
offset = tf_buffer.lookupTransform(local_frame_id, frame_id,
|
||||
msg->header.stamp, ros::Duration(0.02));
|
||||
// offset.header.frame_id = vpe.header.frame_id;
|
||||
offset.child_frame_id = offset_frame_id;
|
||||
if (!frame_id.empty()) {
|
||||
// calculate from TF
|
||||
offset = tf_buffer.lookupTransform(local_frame_id, frame_id,
|
||||
msg->header.stamp, ros::Duration(0.02));
|
||||
// offset.header.frame_id = vpe.header.frame_id;
|
||||
offset.child_frame_id = offset_frame_id;
|
||||
|
||||
} else {
|
||||
// calculate transform between pose in vpe frame and pose in local frame
|
||||
TransformStamped local_pose = tf_buffer.lookupTransform(local_frame_id, child_frame_id,
|
||||
msg->header.stamp, ros::Duration(0.02));
|
||||
keepYaw(local_pose.transform.rotation);
|
||||
|
||||
tf::Transform vpeTransform, poseTransform;
|
||||
tf::poseMsgToTF(vpe.pose, vpeTransform);
|
||||
tf::transformMsgToTF(local_pose.transform, poseTransform);
|
||||
tf::Transform offset_tf = vpeTransform.inverseTimes(poseTransform);
|
||||
tf::transformTFToMsg(offset_tf, offset.transform);
|
||||
offset.header.frame_id = local_frame_id;
|
||||
offset.header.stamp = msg->header.stamp;
|
||||
offset.child_frame_id = offset_frame_id;
|
||||
}
|
||||
|
||||
br.sendTransform(offset);
|
||||
reset_flag = false;
|
||||
ROS_INFO("offset reset");
|
||||
@@ -122,8 +150,9 @@ int main(int argc, char **argv) {
|
||||
|
||||
tf2_ros::TransformListener tf_listener(tf_buffer);
|
||||
|
||||
nh_priv.param<string>("frame_id", frame_id, "");
|
||||
nh_priv.param<string>("offset_frame_id", offset_frame_id, "");
|
||||
nh_priv.param<string>("frame_id", frame_id, ""); // name for used visual pose frame
|
||||
nh_priv.param<string>("offset_frame_id", offset_frame_id, ""); // name for published offset frame
|
||||
|
||||
nh.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
|
||||
nh.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link");
|
||||
offset_timeout = ros::Duration(nh_priv.param("offset_timeout", 3.0));
|
||||
|
||||
@@ -13,11 +13,11 @@ float32 alt
|
||||
float32 vx
|
||||
float32 vy
|
||||
float32 vz
|
||||
float32 pitch
|
||||
float32 roll
|
||||
float32 pitch
|
||||
float32 yaw
|
||||
float32 pitch_rate
|
||||
float32 roll_rate
|
||||
float32 pitch_rate
|
||||
float32 yaw_rate
|
||||
float32 voltage
|
||||
float32 cell_voltage
|
||||
|
||||
@@ -2,7 +2,6 @@ float32 x
|
||||
float32 y
|
||||
float32 z
|
||||
float32 yaw
|
||||
float32 yaw_rate
|
||||
float32 speed
|
||||
string frame_id
|
||||
bool auto_arm
|
||||
|
||||
@@ -2,7 +2,6 @@ float64 lat
|
||||
float64 lon
|
||||
float32 z
|
||||
float32 yaw
|
||||
float32 yaw_rate
|
||||
float32 speed
|
||||
string frame_id
|
||||
bool auto_arm
|
||||
|
||||
5
clover/srv/SetAltitude.srv
Normal file
@@ -0,0 +1,5 @@
|
||||
float32 z
|
||||
string frame_id
|
||||
---
|
||||
bool success
|
||||
string message
|
||||
@@ -1,5 +1,5 @@
|
||||
float32 pitch
|
||||
float32 roll
|
||||
float32 pitch
|
||||
float32 yaw
|
||||
float32 thrust
|
||||
string frame_id
|
||||
|
||||
@@ -2,7 +2,6 @@ float32 x
|
||||
float32 y
|
||||
float32 z
|
||||
float32 yaw
|
||||
float32 yaw_rate
|
||||
string frame_id
|
||||
bool auto_arm
|
||||
---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
float32 pitch_rate
|
||||
float32 roll_rate
|
||||
float32 pitch_rate
|
||||
float32 yaw_rate
|
||||
float32 thrust
|
||||
bool auto_arm
|
||||
|
||||
@@ -2,7 +2,6 @@ float32 vx
|
||||
float32 vy
|
||||
float32 vz
|
||||
float32 yaw
|
||||
float32 yaw_rate
|
||||
string frame_id
|
||||
bool auto_arm
|
||||
---
|
||||
|
||||
5
clover/srv/SetYaw.srv
Normal file
@@ -0,0 +1,5 @@
|
||||
float32 yaw
|
||||
string frame_id
|
||||
---
|
||||
bool success
|
||||
string message
|
||||
4
clover/srv/SetYawRate.srv
Normal file
@@ -0,0 +1,4 @@
|
||||
float32 yaw_rate
|
||||
---
|
||||
bool success
|
||||
string message
|
||||
@@ -40,6 +40,16 @@
|
||||
<node pkg="topic_tools" name="main_camera_throttle" type="throttle" ns="main_camera"
|
||||
args="messages image_raw 5.0 image_raw_throttled" required="true"/>
|
||||
|
||||
<node pkg="nodelet" type="nodelet" name="main_camera_nodelet_manager" args="manager" output="screen" required="true">
|
||||
<param name="num_worker_threads" value="2"/>
|
||||
</node>
|
||||
|
||||
<node pkg="nodelet" type="nodelet" name="rectify" args="load image_proc/rectify main_camera_nodelet_manager" required="true">
|
||||
<remap from="image_mono" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="image_rect" to="main_camera/image_rect"/>
|
||||
</node>
|
||||
|
||||
<param name="test_module" value="$(find clover)/test/basic.py"/>
|
||||
<test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner"/>
|
||||
</launch>
|
||||
|
||||
437
clover/test/offboard.py
Executable file
@@ -0,0 +1,437 @@
|
||||
import rospy
|
||||
import pytest
|
||||
from pytest import approx
|
||||
import threading
|
||||
import mavros_msgs.msg
|
||||
from mavros_msgs.srv import SetMode
|
||||
from geometry_msgs.msg import PoseStamped
|
||||
from clover import srv
|
||||
from clover.msg import State
|
||||
from std_srvs.srv import Trigger
|
||||
from math import nan, inf
|
||||
import tf2_ros
|
||||
import tf2_geometry_msgs
|
||||
|
||||
@pytest.fixture()
|
||||
def node():
|
||||
return rospy.init_node('offboard_test', anonymous=True)
|
||||
|
||||
@pytest.fixture
|
||||
def tf_buffer():
|
||||
buf = tf2_ros.Buffer()
|
||||
tf2_ros.TransformListener(buf)
|
||||
return buf
|
||||
|
||||
def get_state():
|
||||
return rospy.wait_for_message('/simple_offboard/state', State, timeout=1)
|
||||
|
||||
def get_navigate_target(tf_buffer):
|
||||
target = tf_buffer.lookup_transform('map', 'navigate_target', rospy.get_rostime(), rospy.Duration(1))
|
||||
assert target.child_frame_id == 'navigate_target'
|
||||
return target
|
||||
|
||||
def test_offboard(node, tf_buffer):
|
||||
navigate = rospy.ServiceProxy('navigate', srv.Navigate)
|
||||
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
|
||||
set_altitude = rospy.ServiceProxy('set_altitude', srv.SetAltitude)
|
||||
set_yaw = rospy.ServiceProxy('set_yaw', srv.SetYaw)
|
||||
set_yaw_rate = rospy.ServiceProxy('set_yaw_rate', srv.SetYawRate)
|
||||
set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)
|
||||
set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
|
||||
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
|
||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||
land = rospy.ServiceProxy('land', Trigger)
|
||||
|
||||
res = navigate()
|
||||
assert res.success == False
|
||||
assert res.message.startswith('State timeout')
|
||||
|
||||
telem = get_telemetry()
|
||||
assert telem.connected == False
|
||||
|
||||
# mocked state publisher
|
||||
state_pub = rospy.Publisher('/mavros/state', mavros_msgs.msg.State, latch=True, queue_size=1)
|
||||
state_msg = mavros_msgs.msg.State(mode='OFFBOARD', armed=True)
|
||||
|
||||
def publish_state():
|
||||
r = rospy.Rate(2)
|
||||
while not rospy.is_shutdown():
|
||||
state_msg.header.stamp = rospy.Time.now()
|
||||
state_pub.publish(state_msg)
|
||||
r.sleep()
|
||||
|
||||
# start publishing state
|
||||
threading.Thread(target=publish_state, daemon=True).start()
|
||||
rospy.sleep(0.5)
|
||||
|
||||
# set_mode service mock
|
||||
def set_mode(req):
|
||||
state_msg.mode = req.custom_mode # set mocked mode to requested
|
||||
return True,
|
||||
|
||||
rospy.Service('/mavros/set_mode', SetMode, set_mode)
|
||||
|
||||
telem = get_telemetry()
|
||||
assert telem.connected == False
|
||||
|
||||
res = navigate()
|
||||
assert res.success == False
|
||||
assert res.message.startswith('No connection to FCU')
|
||||
|
||||
state_msg.connected = True
|
||||
rospy.sleep(1)
|
||||
|
||||
telem = get_telemetry()
|
||||
assert telem.connected == True
|
||||
|
||||
res = navigate()
|
||||
assert res.success == False
|
||||
assert res.message.startswith('No local position')
|
||||
|
||||
local_position_pub = rospy.Publisher('/mavros/local_position/pose', PoseStamped, latch=True, queue_size=1)
|
||||
local_position_msg = PoseStamped()
|
||||
local_position_msg.header.frame_id = 'map'
|
||||
local_position_msg.pose.position.x = 1
|
||||
local_position_msg.pose.position.y = 2
|
||||
local_position_msg.pose.position.z = 3
|
||||
local_position_msg.pose.orientation.w = 1
|
||||
|
||||
def publish_local_position():
|
||||
r = rospy.Rate(30)
|
||||
while not rospy.is_shutdown():
|
||||
local_position_msg.header.stamp = rospy.Time.now()
|
||||
local_position_pub.publish(local_position_msg)
|
||||
r.sleep()
|
||||
|
||||
# start publishing local position
|
||||
threading.Thread(target=publish_local_position, daemon=True).start()
|
||||
rospy.sleep(0.5)
|
||||
|
||||
# check body frame
|
||||
body = tf_buffer.lookup_transform('map', 'body', rospy.get_rostime(), rospy.Duration(1))
|
||||
assert body.child_frame_id == 'body'
|
||||
assert body.transform.translation.x == approx(1)
|
||||
assert body.transform.translation.y == approx(2)
|
||||
assert body.transform.translation.z == approx(3)
|
||||
|
||||
res = navigate(x=3, y=2, z=1, frame_id='map')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 3
|
||||
assert state.y == 2
|
||||
assert state.z == 1
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'map'
|
||||
assert state.yaw_frame_id == 'map'
|
||||
target = get_navigate_target(tf_buffer)
|
||||
assert target.header.frame_id == 'map'
|
||||
assert target.transform.translation.x == approx(3)
|
||||
assert target.transform.translation.y == approx(2)
|
||||
assert target.transform.translation.z == approx(1)
|
||||
assert target.transform.rotation.x == 0
|
||||
assert target.transform.rotation.y == 0
|
||||
assert target.transform.rotation.z == 0
|
||||
assert target.transform.rotation.w == 1
|
||||
|
||||
# try to set only the y
|
||||
res = navigate(x=nan, y=1, z=nan)
|
||||
assert res.success == False
|
||||
assert res.message.startswith('x and y can be set only together')
|
||||
|
||||
# set z in body frame
|
||||
res = navigate(x=nan, y=nan, z=1, frame_id='body')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 3
|
||||
assert state.y == 2
|
||||
assert state.z == 4
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'map'
|
||||
assert state.yaw_frame_id == 'map'
|
||||
|
||||
# set xy in test frame
|
||||
res = navigate(x=1, y=2, z=nan, frame_id='test')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 1
|
||||
assert state.y == 2
|
||||
assert state.z == 4
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'test'
|
||||
assert state.z_frame_id == 'map'
|
||||
assert state.yaw_frame_id == 'test'
|
||||
|
||||
# auto_arm should not invalidate the setpoint if not effective
|
||||
res = navigate(x=nan, y=nan, z=1, frame_id='map', auto_arm=True)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 1
|
||||
assert state.y == 2
|
||||
assert state.z == 1
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'test'
|
||||
assert state.z_frame_id == 'map'
|
||||
assert state.yaw_frame_id == 'map'
|
||||
|
||||
# auto_arm should invalidate the setpoint if effective
|
||||
state_msg.mode = 'STABILIZED' # pretend we are not in OFFBOARD mode
|
||||
rospy.sleep(1)
|
||||
res = navigate(x=nan, y=nan, z=1, frame_id='map', auto_arm=True)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 1
|
||||
assert state.y == 2
|
||||
assert state.z == 1
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'map'
|
||||
assert state.yaw_frame_id == 'map'
|
||||
state_msg.mode = 'OFFBOARD'
|
||||
rospy.sleep(1)
|
||||
|
||||
# set_attitude should invalidate the setpoint
|
||||
res = set_attitude()
|
||||
assert res.success == True
|
||||
|
||||
res = navigate(x=5, y=6, z=nan, yaw=nan, frame_id='map')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 5
|
||||
assert state.y == 6
|
||||
assert state.z == 3
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'map'
|
||||
assert state.yaw_frame_id == 'map'
|
||||
|
||||
# test set_altitude
|
||||
res = set_altitude(z=7, frame_id='test')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 5
|
||||
assert state.y == 6
|
||||
assert state.z == 7
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'test'
|
||||
assert state.yaw_frame_id == 'map'
|
||||
|
||||
# test set_yaw
|
||||
res = set_yaw(yaw=0.5, frame_id='test2')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 5
|
||||
assert state.y == 6
|
||||
assert state.z == 7
|
||||
assert state.yaw == 0.5
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'test'
|
||||
assert state.yaw_frame_id == 'test2'
|
||||
|
||||
# test set_yaw_rate
|
||||
res = set_yaw_rate(yaw_rate=2)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||
assert state.x == 5
|
||||
assert state.y == 6
|
||||
assert state.z == 7
|
||||
assert state.yaw_rate == 2
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'test'
|
||||
|
||||
# navigate(yaw=nan) should keep yaw rate mode
|
||||
res = navigate(x=nan, y=nan, z=nan, yaw=nan)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||
assert state.x == 5
|
||||
assert state.y == 6
|
||||
assert state.z == 7
|
||||
assert state.yaw_rate == 2
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'test'
|
||||
|
||||
# set_yaw(nan) should change back to yaw mode
|
||||
res = set_yaw(yaw=nan)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_NAVIGATE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.yaw == 0
|
||||
assert state.yaw_frame_id == 'map'
|
||||
|
||||
# test set_position
|
||||
res = set_position(x=nan, y=nan, z=13, yaw=nan, frame_id='test2')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_POSITION
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 5
|
||||
assert state.y == 6
|
||||
assert state.z == 13
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'test2'
|
||||
assert state.yaw_frame_id == 'map'
|
||||
|
||||
# set_altitude should not change the mode
|
||||
res = set_altitude(z=3, frame_id='test')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_POSITION
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 5
|
||||
assert state.y == 6
|
||||
assert state.z == 3
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'test'
|
||||
assert state.yaw_frame_id == 'map'
|
||||
|
||||
# set_yaw should not change the main mode
|
||||
res = set_yaw(yaw=1, frame_id='test2')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_POSITION
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.x == 5
|
||||
assert state.y == 6
|
||||
assert state.z == 3
|
||||
assert state.yaw == 1
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'test'
|
||||
assert state.yaw_frame_id == 'test2'
|
||||
|
||||
# test set_velocity
|
||||
res = set_velocity(vx=1, frame_id='body')
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_VELOCITY
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.vx == 1
|
||||
assert state.vy == 0
|
||||
assert state.vz == 0
|
||||
assert state.yaw == 0
|
||||
assert state.xy_frame_id == 'map'
|
||||
assert state.z_frame_id == 'map'
|
||||
assert state.yaw_frame_id == 'map'
|
||||
|
||||
# set_altitude should not work in velocity mode
|
||||
res = set_altitude(z=3, frame_id='test')
|
||||
assert res.success == False
|
||||
assert res.message.startswith('Altitude cannot be set in')
|
||||
|
||||
# test set_attitude
|
||||
res = set_attitude(roll=0.1, pitch=0.2, yaw=0.3, thrust=0.5)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_ATTITUDE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.roll == approx(0.1)
|
||||
assert state.pitch == approx(0.2)
|
||||
assert state.yaw == approx(0.3)
|
||||
assert state.thrust == approx(0.5)
|
||||
assert state.yaw_frame_id == 'map'
|
||||
msg = rospy.wait_for_message('/mavros/setpoint_attitude/attitude', PoseStamped, timeout=3)
|
||||
# Tait-Bryan ZYX angle (rzyx) converted to quaternion
|
||||
assert msg.pose.orientation.x == approx(0.0342708)
|
||||
assert msg.pose.orientation.y == approx(0.10602051)
|
||||
assert msg.pose.orientation.z == approx(0.14357218)
|
||||
assert msg.pose.orientation.w == approx(0.98334744)
|
||||
msg = rospy.wait_for_message('/mavros/setpoint_attitude/thrust', mavros_msgs.msg.Thrust, timeout=3)
|
||||
assert msg.thrust == approx(0.5)
|
||||
|
||||
# set_yaw should work in attitude mode
|
||||
res = set_yaw(yaw=0.7, frame_id='test2')
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_ATTITUDE
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW
|
||||
assert state.roll == approx(0.1)
|
||||
assert state.pitch == approx(0.2)
|
||||
assert state.yaw == approx(0.7)
|
||||
assert state.thrust == approx(0.5)
|
||||
assert state.yaw_frame_id == 'test2'
|
||||
|
||||
# set_yaw_rate should not work in attitude mode
|
||||
res = set_yaw_rate(yaw_rate=0.3)
|
||||
assert res.success == False
|
||||
assert res.message.startswith('Yaw rate cannot be set in')
|
||||
|
||||
# test set_rates
|
||||
res = set_rates(roll_rate=nan, pitch_rate=nan, yaw_rate=0.3, thrust=0.6)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_RATES
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||
assert state.roll_rate == approx(0)
|
||||
assert state.pitch_rate == approx(0)
|
||||
assert state.yaw_rate == approx(0.3)
|
||||
assert state.thrust == approx(0.6)
|
||||
msg = rospy.wait_for_message('/mavros/setpoint_raw/attitude', mavros_msgs.msg.AttitudeTarget, timeout=3)
|
||||
assert msg.thrust == approx(0.6)
|
||||
|
||||
res = set_rates(roll_rate=0.3, pitch_rate=0.2, yaw_rate=0.1, thrust=0.4)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_RATES
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||
assert state.roll_rate == approx(0.3)
|
||||
assert state.pitch_rate == approx(0.2)
|
||||
assert state.yaw_rate == approx(0.1)
|
||||
assert state.thrust == approx(0.4)
|
||||
|
||||
res = set_rates(roll_rate=nan, pitch_rate=nan, yaw_rate=nan, thrust=0.3)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_RATES
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||
assert state.roll_rate == approx(0.3)
|
||||
assert state.pitch_rate == approx(0.2)
|
||||
assert state.yaw_rate == approx(0.1)
|
||||
assert state.thrust == approx(0.3)
|
||||
msg = rospy.wait_for_message('/mavros/setpoint_raw/attitude', mavros_msgs.msg.AttitudeTarget, timeout=3)
|
||||
assert msg.type_mask == mavros_msgs.msg.AttitudeTarget.IGNORE_ATTITUDE
|
||||
assert msg.body_rate.x == approx(0.3)
|
||||
assert msg.body_rate.y == approx(0.2)
|
||||
assert msg.body_rate.z == approx(0.1)
|
||||
|
||||
# set_yaw_rate should work in rates mode
|
||||
res = set_yaw_rate(yaw_rate=0.4)
|
||||
assert res.success == True
|
||||
state = get_state()
|
||||
assert state.mode == State.MODE_RATES
|
||||
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
|
||||
assert state.roll_rate == approx(0.3)
|
||||
assert state.pitch_rate == approx(0.2)
|
||||
assert state.yaw_rate == approx(0.4)
|
||||
assert state.thrust == approx(0.3)
|
||||
|
||||
res = set_rates(roll_rate=inf)
|
||||
assert res.success == False
|
||||
assert res.message == 'roll_rate argument cannot be Inf'
|
||||
|
||||
# test land service
|
||||
res = land()
|
||||
assert res.success == True
|
||||
assert state_msg.mode == 'AUTO.LAND' # check that the mode was set correctly
|
||||
10
clover/test/offboard.test
Normal file
@@ -0,0 +1,10 @@
|
||||
<launch>
|
||||
<node name="simple_offboard" pkg="clover" type="simple_offboard" required="true" output="screen"/>
|
||||
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="test_frame" args="10 20 30 0 0 0 map test"/>
|
||||
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="test2_frame" args="100 200 300 0 0 0 map test2"/>
|
||||
|
||||
<param name="test_module" value="$(find clover)/test/offboard.py"/>
|
||||
<test test-name="offboard_test" pkg="ros_pytest" type="ros_pytest_runner"/>
|
||||
</launch>
|
||||
68
clover/tools/rescale_camera_info.py
Executable file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (C) 2024 Copter Express Technologies
|
||||
#
|
||||
# Author: Oleg Kalachev <okalachev@gmail.com>
|
||||
#
|
||||
# Distributed under MIT License (available at https://opensource.org/licenses/MIT).
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
|
||||
"""Rescale camera info
|
||||
|
||||
Rescale camera info files for different resolutions.
|
||||
|
||||
Usage:
|
||||
rescale_camera_info.py <camera_info_file>
|
||||
rescale_camera_info.py (-h | --help)
|
||||
|
||||
Options:
|
||||
<camera_info_file> Path to the source camera info file
|
||||
|
||||
Example:
|
||||
rescale_camera_info.py camera_info.yaml
|
||||
"""
|
||||
|
||||
from docopt import docopt
|
||||
import yaml
|
||||
|
||||
arguments = docopt(__doc__)
|
||||
|
||||
camera_info = yaml.safe_load(open(arguments['<camera_info_file>']))
|
||||
RESOLUTIONS = (
|
||||
(320, 240), # QVGA
|
||||
(640, 480), # VGA
|
||||
(800, 600), # SVGA
|
||||
(1280, 720), # HD
|
||||
(1920, 1080), # FullHD
|
||||
(2592, 1944), # 5MP
|
||||
(3840, 2160), # 4K
|
||||
(4056, 3040),
|
||||
)
|
||||
# TODO: retrieve resolutions list (v4l2-ctl --list-formats-ext)
|
||||
|
||||
for resolution in RESOLUTIONS:
|
||||
width_k = resolution[0] / camera_info['image_width']
|
||||
height_k = resolution[1] / camera_info['image_height']
|
||||
|
||||
camera_info_rescaled = camera_info.copy()
|
||||
camera_info_rescaled['image_width'] = resolution[0]
|
||||
camera_info_rescaled['image_height'] = resolution[1]
|
||||
|
||||
# See http://docs.ros.org/api/sensor_msgs/html/msg/CameraInfo.html for clarification
|
||||
camera_info_rescaled['camera_matrix']['data'][0] *= width_k
|
||||
camera_info_rescaled['camera_matrix']['data'][2] *= width_k
|
||||
camera_info_rescaled['camera_matrix']['data'][4] *= height_k
|
||||
camera_info_rescaled['camera_matrix']['data'][5] *= height_k
|
||||
|
||||
camera_info_rescaled['projection_matrix']['data'][0] *= width_k
|
||||
camera_info_rescaled['projection_matrix']['data'][2] *= width_k
|
||||
camera_info_rescaled['projection_matrix']['data'][5] *= height_k
|
||||
camera_info_rescaled['projection_matrix']['data'][6] *= height_k
|
||||
|
||||
output_file = arguments['<camera_info_file>'].replace('.yaml', '_{}x{}.yaml'.format(resolution[0], resolution[1]))
|
||||
with open(output_file, 'w') as f:
|
||||
f.write('# Generated from {} by rescale_camera_info.py\n'.format(arguments['<camera_info_file>']))
|
||||
yaml.dump(camera_info_rescaled, f)
|
||||
|
||||
print('Saved {}'.format(output_file))
|
||||
@@ -1,17 +1,54 @@
|
||||
# PixHawk (px4fmu-v2), px4fmu-v3
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0011", ATTRS{product}=="PX4 FMU v2.x", SYMLINK+="px4fmu"
|
||||
# PixRacer (px4fmu-v4)
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0012", ATTRS{product}=="PX4 FMU v4.x", SYMLINK+="px4fmu"
|
||||
# px4fmu-v5
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0032", ATTRS{product}=="PX4 FMU v5.x", SYMLINK+="px4fmu"
|
||||
# auav
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0021", ATTRS{product}=="PX4 AUAV x2.1", SYMLINK+="px4fmu"
|
||||
# crazyflie
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{product}=="PX4 Crazyflie v2.0", SYMLINK+="px4fmu"
|
||||
# px4fmu-v4pro
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0013", ATTRS{product}=="PX4 FMU v4.x PRO", SYMLINK+="px4fmu"
|
||||
# Omnibus
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="PX4 OmnibusF4SD", SYMLINK+="px4fmu"
|
||||
# CUAV X7 Pro
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV X7Pro", SYMLINK+="px4fmu"
|
||||
# Source files: PX4-Autopilot/boards/**/nuttx-config/nsh/defconfig
|
||||
|
||||
# Additional info:
|
||||
# https://docs.px4.io/main/en/flight_controller/
|
||||
# https://github.com/mavlink/qgroundcontrol/blob/master/src/comm/USBBoardInfo.json
|
||||
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="PX4 GNF405", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="PX4 OmnibusF4SD", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{product}=="PX4 Crazyflie v2.0", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1FC9", ATTRS{idProduct}=="001c", ATTRS{product}=="PX4 FMUK66 v3.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1FC9", ATTRS{idProduct}=="001c", ATTRS{product}=="PX4 FMUK66 E", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1FC9", ATTRS{idProduct}=="001d", ATTRS{product}=="PX4 FMURT1062 v1.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="DiatoneMambaF405 MK2", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a32f", ATTRS{product}=="PX4 FMU ModalAI FCv1", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a330", ATTRS{product}=="PX4 FMU ModalAI FCv2", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0012", ATTRS{product}=="PX4 FMU UVify Core", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteH7", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteH7v2", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="004b", ATTRS{product}=="PX4 DurandalV1", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteF7", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteH7Mini-nand", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="004E", ATTRS{product}=="PX4 PIX32V5", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0061", ATTRS{product}=="PX4 ATL Mantis-EDU", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV Nora", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV X7Pro", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1B8C", ATTRS{idProduct}=="0036", ATTRS{product}=="MatekH743-mini", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1B8C", ATTRS{idProduct}=="0036", ATTRS{product}=="MatekH743", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="120A", ATTRS{idProduct}=="1004", ATTRS{product}=="Matekgnssm9nf4", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="1013", ATTRS{product}=="MatekH743", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="0037", ATTRS{product}=="PX4 FMU SmartAP AIRLink", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="2DAE", ATTRS{idProduct}=="1058", ATTRS{product}=="CubeOrange+", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="2DAE", ATTRS{idProduct}=="1012", ATTRS{product}=="CubeYellow", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="2DAE", ATTRS{idProduct}=="1016", ATTRS{product}=="CubeOrange", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0035", ATTRS{product}=="PX4 FMU v6X.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0038", ATTRS{product}=="PX4 FMU v6C.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0033", ATTRS{product}=="PX4 FMU v5X.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1B8C", ATTRS{idProduct}=="0036", ATTRS{product}=="PX4 FMU v6U.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0013", ATTRS{product}=="PX4 FMU v4.x PRO", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0011", ATTRS{product}=="PX4 FMU v2.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0012", ATTRS{product}=="PX4 FMU v4.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0032", ATTRS{product}=="PX4 FMU v5.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="004b", ATTRS{product}=="PX4 SP RACING H7 EXTREME", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0030", ATTRS{product}=="MindPX FMU v2.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0039", ATTRS{product}=="ARK FMU v6X.x", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{product}=="PX4 FreeFly RTK GPS", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1024", ATTRS{product}=="mRoControlZeroH7 OEM", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1017", ATTRS{product}=="mRoPixracerPro", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1023", ATTRS{product}=="mRoControlZeroH7", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="008D", ATTRS{product}=="mRoControlZeroF7", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0021", ATTRS{product}=="PX4 AUAV X2.1", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1022", ATTRS{product}=="mRoControlZero Classic", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0088", ATTRS{product}=="mRo x2.1-777", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="35a7", ATTRS{idProduct}=="0002", ATTRS{product}=="FCC-R1", SYMLINK+="px4fmu"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="35a7", ATTRS{idProduct}=="0001", ATTRS{product}=="FCC-K1", SYMLINK+="px4fmu"
|
||||
|
||||
@@ -47,6 +47,7 @@ http://<hostname>/clover_blocks/?navigate_tolerance=0.5&sleep_time=0.1
|
||||
|
||||
* `~running` ([*std_msgs/Bool*](http://docs.ros.org/noetic/api/std_msgs/html/msg/Bool.html)) – indicates if the program is currently running.
|
||||
* `~block` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – current executing block (maximum topic rate is limited).
|
||||
* `~print` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user program output messages (published in *print* blocks).
|
||||
* `~error` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user program errors and exceptions.
|
||||
* `~prompt` ([*clover_blocks/Prompt*](msg/Prompt.msg)) – user input request (includes random request ID string).
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="2">
|
||||
<name>clover_blocks</name>
|
||||
<version>0.23.0</version>
|
||||
<version>0.24.0</version>
|
||||
<description>Blockly programming support for Clover</description>
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
<license>MIT</license>
|
||||
|
||||
@@ -15,6 +15,7 @@ const COLOR_GPIO = 200;
|
||||
const DOCS_URL = 'https://clover.coex.tech/en/blocks.html';
|
||||
|
||||
var frameIds = [["body", "BODY"], ["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"], ["map", "MAP"]];
|
||||
var frameIdsWithTerrain = frameIds.concat([["terrain", "TERRAIN"]]);
|
||||
|
||||
function considerFrameId(e) {
|
||||
if (!(e instanceof Blockly.Events.Change || e instanceof Blockly.Events.Create)) return;
|
||||
@@ -22,7 +23,7 @@ function considerFrameId(e) {
|
||||
var frameId = this.getFieldValue('FRAME_ID');
|
||||
// set appropriate coordinates labels
|
||||
if (this.getInput('X')) { // block has x-y-z fields
|
||||
if (frameId == 'BODY' || frameId == 'NAVIGATE_TARGET' || frameId == 'BASE_LINK') {
|
||||
if (frameId == 'BODY' || frameId == 'NAVIGATE_TARGET' || frameId == 'BASE_LINK' || frameId == 'TERRAIN') {
|
||||
this.getInput('X').fieldRow[0].setValue('forward');
|
||||
this.getInput('Y').fieldRow[0].setValue('left');
|
||||
this.getInput('Z').fieldRow[0].setValue('up');
|
||||
@@ -59,8 +60,8 @@ function updateSetpointBlock(e) {
|
||||
this.getInput('VY').setVisible(velocity);
|
||||
this.getInput('VZ').setVisible(velocity);
|
||||
this.getInput('YAW').setVisible(attitude);
|
||||
this.getInput('PITCH').setVisible(attitude);
|
||||
this.getInput('ROLL').setVisible(attitude);
|
||||
this.getInput('PITCH').setVisible(attitude);
|
||||
this.getInput('THRUST').setVisible(attitude);
|
||||
this.getInput('RELATIVE_TO').setVisible(type != 'RATES');
|
||||
|
||||
@@ -73,7 +74,7 @@ function updateSetpointBlock(e) {
|
||||
|
||||
Blockly.Blocks['navigate'] = {
|
||||
init: function () {
|
||||
let navFrameId = frameIds.slice();
|
||||
let navFrameId = frameIdsWithTerrain.slice();
|
||||
navFrameId.push(['global', 'GLOBAL_LOCAL'])
|
||||
navFrameId.push(['global, WGS 84 alt.', 'GLOBAL'])
|
||||
this.appendDummyInput()
|
||||
@@ -163,14 +164,14 @@ Blockly.Blocks['setpoint'] = {
|
||||
this.appendValueInput("VZ")
|
||||
.setCheck("Number")
|
||||
.appendField("vz");
|
||||
this.appendValueInput("PITCH")
|
||||
.setCheck("Number")
|
||||
.appendField("pitch")
|
||||
.setVisible(false);
|
||||
this.appendValueInput("ROLL")
|
||||
.setCheck("Number")
|
||||
.appendField("roll")
|
||||
.setVisible(false);
|
||||
this.appendValueInput("PITCH")
|
||||
.setCheck("Number")
|
||||
.appendField("pitch")
|
||||
.setVisible(false);
|
||||
this.appendValueInput("YAW")
|
||||
.setCheck("Number")
|
||||
.appendField("yaw")
|
||||
@@ -213,7 +214,7 @@ Blockly.Blocks['get_position'] = {
|
||||
.appendField("current")
|
||||
.appendField(new Blockly.FieldDropdown([["x", "X"], ["y", "Y"], ["z", "Z"], ["vx", "VX"], ["vy", "VY"], ["vz", "VZ"]]), "FIELD")
|
||||
.appendField("relative to")
|
||||
.appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID");
|
||||
.appendField(new Blockly.FieldDropdown(frameIdsWithTerrain), "FRAME_ID");
|
||||
this.appendValueInput("ID")
|
||||
.setCheck("Number")
|
||||
.appendField("with ID")
|
||||
@@ -247,7 +248,7 @@ Blockly.Blocks['get_attitude'] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("current")
|
||||
.appendField(new Blockly.FieldDropdown([["pitch", "PITCH"], ["roll", "ROLL"], ["pitch rate", "PITCH_RATE"], ["roll rate", "ROLL_RATE"], ["yaw rate", "YAW_RATE"]]), "FIELD");
|
||||
.appendField(new Blockly.FieldDropdown([["roll", "ROLL"], ["pitch", "PITCH"], ["roll rate", "ROLL_RATE"], ["pitch rate", "PITCH_RATE"], ["yaw rate", "YAW_RATE"]]), "FIELD");
|
||||
this.setOutput(true, "Number");
|
||||
this.setColour(COLOR_STATE);
|
||||
this.setTooltip("Returns current orientation or angle rates in degree or degree per second (not radian).");
|
||||
@@ -268,6 +269,19 @@ Blockly.Blocks['voltage'] = {
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['get_rc'] = {
|
||||
init: function () {
|
||||
this.appendDummyInput()
|
||||
.appendField("RC channel")
|
||||
this.appendValueInput("CHANNEL")
|
||||
.setCheck("Number");
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, "Number");
|
||||
this.setColour(COLOR_STATE);
|
||||
this.setTooltip("Returns current RC channel value.");
|
||||
this.setHelpUrl(DOCS_URL + '#' + this.type);
|
||||
}
|
||||
}
|
||||
|
||||
Blockly.Blocks['armed'] = {
|
||||
init: function () {
|
||||
@@ -509,7 +523,7 @@ Blockly.Blocks['distance'] = {
|
||||
.appendField("z");
|
||||
this.appendDummyInput()
|
||||
.appendField("relative to")
|
||||
.appendField(new Blockly.FieldDropdown([["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"]]), "FRAME_ID");
|
||||
.appendField(new Blockly.FieldDropdown([["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"], ["terrain", "TERRAIN"]]), "FRAME_ID");
|
||||
this.appendValueInput("ID")
|
||||
.setCheck("Number")
|
||||
.appendField("with ID")
|
||||
|
||||
@@ -69,8 +69,8 @@
|
||||
<value name="VX"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="VY"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="VZ"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="PITCH"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="ROLL"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="PITCH"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="YAW"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
<value name="THRUST"><shadow type="math_number"><field name="NUM">0.5</field></shadow></value>
|
||||
<value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
@@ -100,6 +100,9 @@
|
||||
<block type="mode"></block>
|
||||
<block type="armed"></block>
|
||||
<block type="voltage"></block>
|
||||
<block type="get_rc">
|
||||
<value name="CHANNEL"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
|
||||
</block>
|
||||
</category>
|
||||
<category name="LED" colour="#02d754">
|
||||
<block type="set_effect">
|
||||
|
||||
@@ -81,7 +81,10 @@ function generateROSDefinitions() {
|
||||
code += `get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)\n`;
|
||||
code += `navigate = rospy.ServiceProxy('navigate', srv.Navigate)\n`;
|
||||
if (rosDefinitions.navigateGlobal) {
|
||||
code += `navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)\n`;
|
||||
code += `navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)\n`;
|
||||
}
|
||||
if (rosDefinitions.setYaw) {
|
||||
code += `set_yaw = rospy.ServiceProxy('set_yaw', srv.SetYaw)\n`;
|
||||
}
|
||||
if (rosDefinitions.setVelocity) {
|
||||
code += `set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)\n`;
|
||||
@@ -276,10 +279,11 @@ Blockly.Python.angle = function(block) {
|
||||
}
|
||||
|
||||
Blockly.Python.set_yaw = function(block) {
|
||||
rosDefinitions.setYaw = true;
|
||||
simpleOffboard();
|
||||
let yaw = Blockly.Python.valueToCode(block, 'YAW', Blockly.Python.ORDER_NONE);
|
||||
let frameId = buildFrameId(block);
|
||||
let code = `navigate(x=float('nan'), y=float('nan'), z=float('nan'), yaw=${yaw}, frame_id=${frameId})\n`;
|
||||
let code = `set_yaw(yaw=${yaw}, frame_id=${frameId})\n`;
|
||||
if (block.getFieldValue('WAIT') == 'TRUE') {
|
||||
rosDefinitions.waitYaw = true;
|
||||
simpleOffboard();
|
||||
@@ -328,11 +332,11 @@ Blockly.Python.setpoint = function(block) {
|
||||
} else if (type == 'ATTITUDE') {
|
||||
rosDefinitions.setAttitude = true;
|
||||
simpleOffboard();
|
||||
return `set_attitude(pitch=${pitch}, roll=${roll}, yaw=${yaw}, thrust=${thrust}, frame_id=${frameId})\n`;
|
||||
return `set_attitude(roll=${roll}, pitch=${pitch}, yaw=${yaw}, thrust=${thrust}, frame_id=${frameId})\n`;
|
||||
} else if (type == 'RATES') {
|
||||
rosDefinitions.setRates = true;
|
||||
simpleOffboard();
|
||||
return `set_rates(pitch_rate=${pitch}, roll_rate=${roll}, yaw_rate=${yaw}, thrust=${thrust})\n`;
|
||||
return `set_rates(roll_rate=${roll}, pitch_rate=${pitch}, yaw_rate=${yaw}, thrust=${thrust})\n`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,6 +402,12 @@ Blockly.Python.voltage = function(block) {
|
||||
return [code, Blockly.Python.ORDER_FUNCTION_CALL];
|
||||
}
|
||||
|
||||
Blockly.Python.get_rc = function(block) {
|
||||
Blockly.Python.definitions_['import_rcin'] = 'from mavros_msgs.msg import RCIn';
|
||||
var channel = Blockly.Python.valueToCode(block, 'CHANNEL', Blockly.Python.ORDER_NONE);
|
||||
return [`rospy.wait_for_message('mavros/rc/in', RCIn).channels[${channel}]`, Blockly.Python.ORDER_FUNCTION_CALL]
|
||||
}
|
||||
|
||||
function parseColor(color) {
|
||||
return {
|
||||
r: parseInt(color.substr(2, 2), 16),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<package format="2">
|
||||
<name>clover_description</name>
|
||||
<version>0.23.0</version>
|
||||
<version>0.24.0</version>
|
||||
<description>The clover_description package provides URDF models of the Clover series of quadcopters.</description>
|
||||
|
||||
<maintainer email="sfalexrog@gmail.com">Alexey Rogachevskiy</maintainer>
|
||||
|
||||
@@ -31,7 +31,7 @@ param set-default EKF2_OF_DELAY 0
|
||||
param set-default EKF2_OF_QMIN 10
|
||||
param set-default EKF2_OF_N_MIN 0.05
|
||||
param set-default EKF2_OF_N_MAX 0.2
|
||||
param set-default EKF2_HGT_MODE 2 # 0 = baro, 1 = gps, 2 = range, 3 = vision
|
||||
param set-default EKF2_HGT_MODE 3 # 0 = baro, 1 = gps, 2 = range, 3 = vision
|
||||
param set-default EKF2_EVA_NOISE 0.1
|
||||
param set-default EKF2_EVP_NOISE 0.1
|
||||
param set-default EKF2_EV_DELAY 0
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
material red_circle
|
||||
{
|
||||
technique
|
||||
{
|
||||
pass
|
||||
{
|
||||
scene_blend alpha_blend
|
||||
texture_unit
|
||||
{
|
||||
texture red_circle.png
|
||||
filtering none
|
||||
scale 1.0 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.9 KiB |
13
clover_simulation/models/red_circle/model.config
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0"?>
|
||||
<model>
|
||||
<name>Red Circle</name>
|
||||
<version>1.0</version>
|
||||
<sdf version="1.5">red_circle.sdf</sdf>
|
||||
<author>
|
||||
<name>Oleg Kalachev</name>
|
||||
<email>okalachev@gmail.com</email>
|
||||
</author>
|
||||
<description>
|
||||
Red circle of size 40 cm on the floor for recognizing by a drone
|
||||
</description>
|
||||
</model>
|
||||
24
clover_simulation/models/red_circle/red_circle.sdf
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0"?>
|
||||
<sdf version="1.5">
|
||||
<model name="red_circle">
|
||||
<static>true</static>
|
||||
<link name="red_circle_link">
|
||||
<pose>0 0 1e-3 0 0 0</pose>
|
||||
<visual name="red_circle_texture">
|
||||
<cast_shadows>false</cast_shadows>
|
||||
<geometry>
|
||||
<box>
|
||||
<size>0.4 0.4 1e-3</size>
|
||||
</box>
|
||||
</geometry>
|
||||
<material>
|
||||
<script>
|
||||
<uri>model://red_circle/materials/scripts</uri>
|
||||
<uri>model://red_circle/materials/textures</uri>
|
||||
<name>red_circle</name>
|
||||
</script>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
</model>
|
||||
</sdf>
|
||||
7
clover_simulation/models/red_circle/red_circle.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<title>
|
||||
red_circle
|
||||
</title><g fill="red">
|
||||
<circle cx="10.05" cy="10.05" r="9.9"/>
|
||||
</g></svg>
|
||||
|
After Width: | Height: | Size: 221 B |
@@ -1,6 +1,6 @@
|
||||
<package format="3">
|
||||
<name>clover_simulation</name>
|
||||
<version>0.23.0</version>
|
||||
<version>0.24.0</version>
|
||||
<description>The clover_simulation package provides worlds and launch files for Gazebo.</description>
|
||||
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
|
||||
BIN
docs/assets/ftl/acp_workflow1.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
docs/assets/ftl/acp_workflow2.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
docs/assets/ftl/acp_workflow3.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
docs/assets/ftl/acp_workflow4.png
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
docs/assets/mocap_clover/block_ROS.jpg
Normal file
|
After Width: | Height: | Size: 368 KiB |
BIN
docs/assets/mocap_clover/block_udp.jpg
Normal file
|
After Width: | Height: | Size: 383 KiB |
BIN
docs/assets/mocap_clover/drone_approach_small.jpg
Normal file
|
After Width: | Height: | Size: 322 KiB |
BIN
docs/assets/mocap_clover/px4_control_structure.jpg
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
docs/assets/mocap_clover/semi_logo_small.jpg
Normal file
|
After Width: | Height: | Size: 325 KiB |
BIN
docs/assets/raspberry-uart-telemetry2.png
Normal file
|
After Width: | Height: | Size: 84 KiB |
BIN
docs/assets/ssh-keys-known_hosts-fingerprint.png
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/assets/stl/grip_left.stl
Normal file
BIN
docs/assets/stl/grip_right.stl
Normal file
BIN
docs/assets/swarm_in_blocks_2/capa_swarm_23_banner.png
Normal file
|
After Width: | Height: | Size: 326 KiB |
@@ -36,7 +36,7 @@
|
||||
* [Optical Flow](optical_flow.md)
|
||||
* [Autonomous flight (OFFBOARD)](simple_offboard.md)
|
||||
* [Coordinate systems (frames)](frames.md)
|
||||
* [Code snippets](snippets.md)
|
||||
* [Code examples](snippets.md)
|
||||
* [Interfacing with a laser rangefinder](laser.md)
|
||||
* [LED strip](leds.md)
|
||||
* [Working with GPIO](gpio.md)
|
||||
@@ -57,6 +57,7 @@
|
||||
* [COEX Pix](coex_pix.md)
|
||||
* [COEX PDB](coex_pdb.md)
|
||||
* [COEX GPS](coex_gps.md)
|
||||
* [Using SSH keys](ssh_keys.md)
|
||||
* [Guide on autonomous flight](auto_setup.md)
|
||||
* [Hostname](hostname.md)
|
||||
* [PX4 Simulation](sitl.md)
|
||||
@@ -105,6 +106,12 @@
|
||||
* [Video contest](video_contest.md)
|
||||
* [Educational contests](educational_contests.md)
|
||||
* [Clover-based projects](projects.md)
|
||||
* [Clover Cloud Platform](clover-cloud-platform.md)
|
||||
* [Autonomous Racing Drone](djs_phoenix_chetak.md)
|
||||
* [Motion Capture System](mocap_clover.md)
|
||||
* [Swarm in Blocks 2](swarm_in_blocks_2.md)
|
||||
* [Advanced Clover 2](advanced_clover_simulator_platform.md)
|
||||
* [Network of charging stations](liceu128.md)
|
||||
* [Swarm-in-blocks](swarm_in_blocks.md)
|
||||
* [Obstacle avoidance using artificial potential fields method](obstacle-avoidance-potential-fields.md)
|
||||
* [The Clover Rescue Project](clover-rescue-team.md)
|
||||
|
||||
161
docs/en/advanced_clover_simulator_platform.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# Advanced Clover 3: The Platform
|
||||
|
||||
[CopterHack-2023](copterhack2023.md), team **FTL**.
|
||||
|
||||
## Team Information
|
||||
|
||||
```cpp
|
||||
#include "veryInterestingCommandDescription.h"
|
||||
```
|
||||
|
||||
Team members:
|
||||
|
||||
- Maxim Ramanouski, [@max8rr8](https://t.me/max8rr8).
|
||||
|
||||
Country: Belarus.
|
||||
|
||||
## Project Description
|
||||
|
||||
Last year at CopterHack 2022, we created a [project](../ru/advanced_clover_simulator.html) that simplified the simulation of Clover, and in 2021, we created a [project](../ru/advanced_clover.html) that simplified the development of products for Clover (IDE and library for writing). The time has come to combine them and achieve unlimited power.
|
||||
|
||||
### Project Idea
|
||||
|
||||
The idea of the project is to combine CloverIDE and CloverSim (a tool for running Clover simulations). Thus, a platform is planned that allows developing products based on Clover using a simulator and an advanced IDE. The platform will include the following features:
|
||||
|
||||
- Add a web interface that allows using CloverSim without touching the command line.
|
||||
- Work both in the browser (without installing anything) and from CLI.
|
||||
- Have a course that covers different aspects of clover.
|
||||
- Simplify installation, especially in WSL.
|
||||
- Running a simulation on a remote device (more powerful computer or cloud).
|
||||
|
||||
### Project videos
|
||||
|
||||
Video presentation of the project: [link](https://www.youtube.com/watch?v=T4RU9sfxsSI).
|
||||
|
||||
Live presentation at CopterHack: TBD.
|
||||
|
||||
CLI demonstration: [link](https://www.youtube.com/watch?v=Ao-ukR58sSQ).
|
||||
|
||||
## Installation
|
||||
|
||||
Installation process is described in the [project documentation](https://ftl-team.github.io/clover_sim/#/?id=installation).
|
||||
|
||||
## Usage
|
||||
|
||||
The CloverSim platform offers a seamless workflow for users:
|
||||
|
||||
1. Users can effortlessly select or create a workspace and task and
|
||||
launch them with ease.
|
||||
|
||||

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

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

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

|
||||
|
||||
5. Users can track their progress and scores in real-time and effortlessly
|
||||
restart the simulator if necessary. Additionally, different randomization
|
||||
seed can be set to check various inputs and outcomes.
|
||||
|
||||
We also have video demonstration/tutorial: [link](https://www.youtube.com/watch?v=aPOPHD3M3ZM).
|
||||
|
||||
## More features
|
||||
|
||||
- Easy installation process.
|
||||
- Efficient simulation launch, surpassing traditional virtual machines.
|
||||
- Generation of dynamic Gazebo worlds with randomization based on seed.
|
||||
- Real-time task completion verification and score presentation.
|
||||
- Execution with security in isolated containers.
|
||||
- Multiple project capability without the need for multiple virtual machine images.
|
||||
- WebUI for ease of use, removing the need to use the command line.
|
||||
- IDE similar to VSCode with support for C++ and Python, including autocompletion and autoformatting.
|
||||
- Custom-patched GZWeb with bug fixes and additional features, including the display of the Clover LED strip.
|
||||
- GZWeb provides a follow-objects feature superior to that of Gazebo.
|
||||
- IDE includes tools to interact with ROS, such as topic visualization, service calling, and image topic visualization.
|
||||
- IDE also includes Copter Status, displaying most of the drone's information, including position, camera, and LED strip, in one view.
|
||||
- IDE integrates with the simulator by providing control from it, viewing task descriptions, and opening GZWeb.
|
||||
|
||||
We also have developed a learning course based on CloverSim: [link](https://github.com/FTL-team/CloverSim_course). It currently has the following tasks:
|
||||
|
||||
- 1_thesquare - First task of CloverSim course with goal to fly square.
|
||||
- 2_iseeall - Task that teaches how to interact with camera.
|
||||
- 3_landmid - Find and land onto randomly positioned object.
|
||||
- 4_flybyline - Flying along the line.
|
||||
- 5_posknown - Find position of objects relative to ArUco map.
|
||||
|
||||
## More details
|
||||
|
||||
At this point, our platform consists of four major parts:
|
||||
|
||||
- [CloverSim](https://github.com/FTL-team/clover_sim) - tool that manages simulation.
|
||||
- [CloverSim Basefs](https://github.com/FTL-team/clover_sim_basefs) - container image that is used in simulator.
|
||||
- [Clover IDE](https://github.com/FTL-team/cloverIDE) - clover ide tools and theia.
|
||||
- [CloverSim course](https://github.com/FTL-team/CloverSim_course) - course with tasks based on our platform.
|
||||
|
||||
### CloverSim
|
||||
|
||||
The simulation architecture is a continuation of work from CopterHack 2022, but while 2022 version was closer to Proof-of-Concept, the updated version is more robust.
|
||||
|
||||
There are three major difference in simulator architecture
|
||||
|
||||
- Replacement of `systemd-nspawn` with `runc` provides us higher degree of container control and seemingless support of non-systemd systems, for example WSL.
|
||||
- Migration to squash fs images, which greatly reduced size of installed CloverSim from 13 gigabytes to just 3.5 gigabytes.
|
||||
- Tasks are now mounted instead of being copied and also build before starting.
|
||||
|
||||
Because of the way catkin_make works, it is incredibly slow when new packages are added (whole cmake configuration is rerun for all packages). catkin_make provides a way to build only some packages, but it caches this packages and to reset this cache you need to recompile whole catkin_make. But we have found a solution: `catkin_make -DCATKIN_WHITELIST_PACKAGES="task;CloverSim" --build build_CloverSim` This command, builds only CloverSim and task package in separate build directory, this drastically reduces time that catkin_make takes, and keeps expected behavior of catkin_make without arguments.
|
||||
|
||||
There are also differences in tool that launches simulation:
|
||||
|
||||
- Client-server architecture allows us to create web UI and run CloverSim on server.
|
||||
- More robust error handling improves user experience.
|
||||
- Rewritten in rust, better reliability and development experience.
|
||||
|
||||
### CloverSim basefs
|
||||
|
||||
Version 2 integrates CloverIDE into system. We also updated clover in simulator to v0.23 and added web terminal. Basefs is now squashed and doesn't require additional installation. It also uses patched(by us) version of gzweb that is more suitable for our use-case:
|
||||
|
||||
- Unlike original GZWeb assets can be dynamically loaded, which is required to support dynamically generated tasks.
|
||||
- It also implements multiple bugfixes for rendering, UI.
|
||||
- Fixed performance, original gzweb had two constantly running loops that used 200% of cpu. We fixed this by instead using synchronization primitives.
|
||||
- Clover LED strip is rendered, our gzweb connects to ROS and pulls LED data from there to render LED strip like Gazebo does.
|
||||
- Users can now follow-objects like in Gazebo better actually.
|
||||
- Reconnect on disconnect, when simulator is restarted gzweb looses connection and it now can automatically reconnect.
|
||||
|
||||
Patched gzweb available there: [FTL-team/gzweb](https://github.com/FTL-team/gzweb).
|
||||
|
||||
### CloverIDE
|
||||
|
||||
CloverIDE got some updates too:
|
||||
|
||||
- We have updated theia and extensions used.
|
||||
- Better C++ support via clangd.
|
||||
- Clover IDE tools can now reconnect after simulator restart.
|
||||
- Copter Status now displays LED strip status.
|
||||
- Tools ui has better support for different themes.
|
||||
|
||||
But the most important change is CloverSim integration, there are new tools (task description, simulator control and gzweb). While gzweb tool is just an iframe (though it's very cool to have it in IDE).
|
||||
|
||||
Task description and simulator control are more interesting as they have to interact with both IDE and CloverSim, to maintain different versions support we use quite interesting trick, extension webview after being initialized dynamically loads JavaScript from CloverSim. That provides better integration between two.
|
||||
|
||||
### CloverSim course
|
||||
|
||||
CloverSim course is a new part of our platform. It uses robust task API of CloverSim to create practical learning course. It currently teaches different aspects of clover development that i encountered during my participation in different contests involving clover. But we are happy to accpet suggestions about other aspects we should teach in out course.
|
||||
|
||||
## Conclusion
|
||||
|
||||
This project is a final (or maybe there is more?) project of our advanced clover saga. AdvancedClover is a project that is easy to use and greatly improves experience during learning about clover, participating in clover based competitions and development clover based projects. We thank COEX team for their support and look forward to further cooperation.
|
||||
@@ -136,7 +136,7 @@ navigate(x=2, y=2, z=2, speed=1, frame_id='aruco_map')
|
||||
|
||||
### Using a specific marker frame
|
||||
|
||||
Starting with the [image](image.md) version 0.18, the drone also can fly relative to a marker in the map, even if it is not currently visible. Like with [single-marker navigation](aruco_marker.md#working-with-detected-markers), this works by setting the frame_id parameter to aruco_ID, where ID is the desired marker number.
|
||||
Starting with the [image](image.md) version 0.18, the drone also can fly relative to a marker in the map, even if it is not currently visible. Like with [single-marker navigation](aruco_marker.md#working-with-detected-markers), this works by setting the frame_id parameter to `aruco_ID`, where ID is the desired marker number.
|
||||
|
||||
The following code will move the drone to the point 1 meter above the center of marker 5:
|
||||
|
||||
|
||||
@@ -72,12 +72,6 @@ Sample code to fly to a point 1 metre to the left and 2 metres above marker with
|
||||
navigate(frame_id='aruco_7', x=-1, y=0, z=2)
|
||||
```
|
||||
|
||||
Sample code to rotate counterclockwise while hovering 1.5 metres above marker id 10:
|
||||
|
||||
```python
|
||||
navigate(frame_id='aruco_10', x=0, y=0, z=1.5, yaw_rate=0.5)
|
||||
```
|
||||
|
||||
Note that if the required marker isn't detected for 0.5 seconds after the `navigate` command, the command will be ignored.
|
||||
|
||||
These frames may also be used in other services that accept TF frames (like `get_telemetry`). The following code will get the drone's position relative to the marker with id 3:
|
||||
|
||||
@@ -210,7 +210,7 @@ Most of the parameters for autonomous flight are located in the following direct
|
||||
<arg name="aruco_vpe" default="true"/>`
|
||||
```
|
||||
|
||||
- Generate the ArUco markers field. See the article [Map-based navigation with ArUco markers] (aruco_map.md # marker map settings) for details. To generate markers, you need to enter a command with specific values.
|
||||
- Generate the ArUco markers field. See the article [Map-based navigation with ArUco markers](aruco_map.md#marker-map-definition) for details. To generate markers, you need to enter a command with specific values.
|
||||
|
||||
Here is the example generating command where:
|
||||
|
||||
|
||||