Compare commits
208 Commits
v0.23-rc.5
...
bullseye
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6defbea453 | ||
|
|
9e8470a3cb | ||
|
|
e9cd3d917c | ||
|
|
373cefb01c | ||
|
|
d0039ea23f | ||
|
|
bd25818aa7 | ||
|
|
a73457c5c5 | ||
|
|
c8a4d49577 | ||
|
|
123379b60e | ||
|
|
71d3144691 | ||
|
|
d306a9d96d | ||
|
|
1d514cf5ca | ||
|
|
c0707e066a | ||
|
|
91c6998633 | ||
|
|
96adadeae9 | ||
|
|
a1061ff32c | ||
|
|
e9d13b865c | ||
|
|
b655a4274e | ||
|
|
d58a59afab | ||
|
|
7b431fa021 | ||
|
|
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 | ||
|
|
1e12498cb2 | ||
|
|
43037f515d | ||
|
|
2ea848721c | ||
|
|
d06b0a0cd2 | ||
|
|
1efe10c9dd | ||
|
|
24cd1f6fac | ||
|
|
5223bef5e7 | ||
|
|
105eac7e1d | ||
|
|
c1d6ed27aa | ||
|
|
614784e949 | ||
|
|
9376c017b4 | ||
|
|
b5d300e218 | ||
|
|
efb44484b0 | ||
|
|
0a2ad3d64f | ||
|
|
ffe2d3d5e4 | ||
|
|
81f4795aec | ||
|
|
596ed3dcf2 | ||
|
|
63c71fc331 | ||
|
|
0efb249d9b | ||
|
|
47c6e5aa9b | ||
|
|
687a4f50fd | ||
|
|
2372cdd7db | ||
|
|
596a7276ac | ||
|
|
a2d984272b | ||
|
|
e0f200f069 | ||
|
|
bb68b56c25 | ||
|
|
54e685a9d6 | ||
|
|
c64a80312c | ||
|
|
840f2c220c | ||
|
|
5325017a77 | ||
|
|
98d21d1760 | ||
|
|
a13806ef14 | ||
|
|
e8de04a1dd | ||
|
|
1dd098ba6b | ||
|
|
18daff1044 | ||
|
|
b96d17a746 | ||
|
|
1506d3fd96 | ||
|
|
eec0833707 | ||
|
|
48de99a942 | ||
|
|
ac8caea2b1 | ||
|
|
fd22a3b19f | ||
|
|
e74df44a27 | ||
|
|
4cdf073c1d | ||
|
|
4179beca6d | ||
|
|
494a116cd3 | ||
|
|
6c7f8637f4 | ||
|
|
9955599a0a | ||
|
|
a360dc19c0 | ||
|
|
d9a547a3e5 | ||
|
|
762613f659 | ||
|
|
51112651d4 | ||
|
|
db0393a6f0 | ||
|
|
8d9dc1d122 | ||
|
|
f567ba689c | ||
|
|
cbdc93d1c3 | ||
|
|
c4cd157f7c | ||
|
|
9692c030f1 | ||
|
|
dd01353533 | ||
|
|
afa81e8ee2 | ||
|
|
8cef6be840 | ||
|
|
07cac29937 | ||
|
|
7df4cb2589 | ||
|
|
f1d2f45a9e | ||
|
|
addc600f48 | ||
|
|
608c09f3a5 | ||
|
|
1e68369053 | ||
|
|
80730fd7b3 | ||
|
|
031c8b5305 | ||
|
|
d0ab69df7f | ||
|
|
4562bf3b57 | ||
|
|
00aef350ea | ||
|
|
2796917bd0 | ||
|
|
da3f570225 | ||
|
|
1cb257b6a1 | ||
|
|
16d29fed80 | ||
|
|
2418c259a8 | ||
|
|
38b9b7215d | ||
|
|
f1215347f6 | ||
|
|
b3f46e47ec | ||
|
|
a053d0a3fc | ||
|
|
8838c0b8bf | ||
|
|
2a0f4155ef | ||
|
|
620f10118d | ||
|
|
6762b251c9 | ||
|
|
59d9274c9b | ||
|
|
c145789be1 | ||
|
|
180c892eaa | ||
|
|
da065a79f5 | ||
|
|
d1f0fe5aa9 | ||
|
|
d3eed2cba9 | ||
|
|
6356292c6f | ||
|
|
4cf91dd73d | ||
|
|
88c1b85608 | ||
|
|
169680129b | ||
|
|
6541d60d08 | ||
|
|
e3addb9eb0 | ||
|
|
b7d74ef6c9 | ||
|
|
da92aea727 | ||
|
|
0b78c84ac0 | ||
|
|
de2467acb1 | ||
|
|
3d6b8b6a10 | ||
|
|
b6f1ca5d20 | ||
|
|
850b49b2b6 | ||
|
|
f21ba3feb4 | ||
|
|
9c3a97f945 | ||
|
|
293448028a | ||
|
|
b5cd9512ef | ||
|
|
dd74ceb383 | ||
|
|
e217678f7d | ||
|
|
dc06ba1bd2 | ||
|
|
21bbc8a86c | ||
|
|
76ef764143 | ||
|
|
d282098134 | ||
|
|
0f37f19b40 | ||
|
|
e9c3c6ff72 | ||
|
|
7909756046 | ||
|
|
1e8a4841af | ||
|
|
6ec574e193 | ||
|
|
8381aecd50 | ||
|
|
f5eb475660 | ||
|
|
928f4f135e | ||
|
|
8d15de0849 | ||
|
|
826f631b97 | ||
|
|
52b5d7b04e | ||
|
|
455d52007e | ||
|
|
e9e6cabbb9 | ||
|
|
8fcd6e9b9e | ||
|
|
f9883baa87 | ||
|
|
24d3a1df8d | ||
|
|
9784e7bfa1 | ||
|
|
fbad85d87f | ||
|
|
3bebecf91e | ||
|
|
c1ca40187e | ||
|
|
c1179869cd |
1
.gitattributes
vendored
@@ -3,6 +3,7 @@ roslib.js linguist-vendored
|
||||
eventemitter2.js linguist-vendored
|
||||
ros3d.js linguist-vendored
|
||||
three.min.js linguist-vendored
|
||||
json-to-pretty-yaml.js linguist-vendored
|
||||
aruco_pose/vendor/* linguist-vendored
|
||||
blockly/* linguist-vendored
|
||||
highlight/* linguist-vendored
|
||||
|
||||
50
.github/workflows/build.yml
vendored
@@ -7,17 +7,45 @@ on:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
melodic:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Native Melodic build
|
||||
run: |
|
||||
docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:melodic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh
|
||||
# melodic:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - name: Native Melodic build
|
||||
# run: |
|
||||
# docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:melodic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh
|
||||
noetic:
|
||||
runs-on: ubuntu-latest
|
||||
container: ros:noetic-ros-base
|
||||
defaults:
|
||||
run:
|
||||
working-directory: catkin_ws
|
||||
shell: bash
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Native Noetic build
|
||||
run: |
|
||||
docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:noetic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: catkin_ws/src/clover
|
||||
- name: Install requirements
|
||||
run: apt-get update && apt-get -y install python3-pip fakeroot python3-bloom debhelper dpkg-dev
|
||||
- name: Install dependencies
|
||||
run: rosdep update && rosdep install --from-paths src --ignore-src -y
|
||||
- name: Install GeographicLib datasets
|
||||
run: wget -qO- https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | bash
|
||||
- name: catkin_make
|
||||
run: source /opt/ros/$ROS_DISTRO/setup.bash && catkin_make
|
||||
- name: Run tests
|
||||
run: source devel/setup.bash && catkin_make run_tests && catkin_test_results
|
||||
- name: Build Debian packages
|
||||
run: |
|
||||
source devel/setup.bash
|
||||
for file in `find . -name "package.xml"`; do
|
||||
cd $(dirname ${file})
|
||||
bloom-generate rosdebian --os-name ubuntu --os-version $(lsb_release -cs) --ros-distro $ROS_DISTRO
|
||||
fakeroot debian/rules binary
|
||||
cd -
|
||||
done
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: debian-packages
|
||||
path: catkin_ws/src/clover/*.deb
|
||||
retention-days: 1
|
||||
|
||||
57
.github/workflows/docs.yml
vendored
@@ -4,16 +4,25 @@ on:
|
||||
push:
|
||||
branches: [ '*' ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
branches: [ '*' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
docs:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Cancel previous runs
|
||||
uses: styfle/cancel-workflow-action@0.9.1
|
||||
with:
|
||||
access_token: ${{ github.token }}
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
@@ -38,7 +47,11 @@ jobs:
|
||||
gitbook install
|
||||
gitbook build
|
||||
- name: Generate PDF
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
id: generate-pdf
|
||||
env:
|
||||
GITBOOK_SKIP_PDF: ${{ secrets.GITBOOK_SKIP_PDF }}
|
||||
continue-on-error: ${{ env.GITBOOK_SKIP_PDF != '' }}
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: |
|
||||
for i in 1 2 3 4; do gitbook pdf ./ _book/clover.pdf && break || sleep 1; done
|
||||
sudo apt-get -q install ghostscript
|
||||
@@ -47,11 +60,27 @@ jobs:
|
||||
rm _book/clover_ru.pdf && mv _book/clover_ru_compressed.pdf _book/clover_ru.pdf
|
||||
rm _book/clover_en.pdf && mv _book/clover_en_compressed.pdf _book/clover_en.pdf
|
||||
ls -lah _book/clover*.pdf
|
||||
- name: Deploy
|
||||
uses: JamesIves/github-pages-deploy-action@4.1.3
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
echo '::set-output name=GITBOOK_PDF_OK::1'
|
||||
- name: Download older PDFs
|
||||
if: ${{ !steps.generate-pdf.outputs.GITBOOK_PDF_OK }}
|
||||
run: |
|
||||
rm -f _book/clover*.pdf
|
||||
wget --no-verbose https://clover.coex.tech/clover_ru.pdf -P _book/
|
||||
wget --no-verbose https://clover.coex.tech/clover_en.pdf -P _book/
|
||||
- name: Upload artifact
|
||||
# if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: actions/upload-pages-artifact@v1
|
||||
with:
|
||||
branch: gh-pages
|
||||
folder: _book
|
||||
clean: true
|
||||
single-commit: true # to avoid multiple copies of large pdf files
|
||||
path: _book
|
||||
|
||||
deploy-docs:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: docs
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v1
|
||||
|
||||
@@ -83,11 +83,10 @@ add_message_files(
|
||||
)
|
||||
|
||||
## Generate services in the 'srv' folder
|
||||
# add_service_files(
|
||||
# FILES
|
||||
# Service1.srv
|
||||
# Service2.srv
|
||||
# )
|
||||
add_service_files(
|
||||
FILES
|
||||
SetMarkers.srv
|
||||
)
|
||||
|
||||
## Generate actions in the 'action' folder
|
||||
# add_action_files(
|
||||
@@ -120,6 +119,7 @@ generate_messages(
|
||||
## Generate dynamic reconfigure parameters in the 'cfg' folder
|
||||
generate_dynamic_reconfigure_options(
|
||||
cfg/Detector.cfg
|
||||
cfg/Map.cfg
|
||||
)
|
||||
|
||||
###################################
|
||||
@@ -251,4 +251,5 @@ if (CATKIN_ENABLE_TESTING)
|
||||
add_rostest(test/test_node_failure.test)
|
||||
add_rostest(test/largemap.test)
|
||||
add_rostest(test/crash_opencv.test)
|
||||
add_rostest(test/duplicate.test)
|
||||
endif()
|
||||
|
||||
@@ -51,6 +51,7 @@ It's recommended to run it within the same nodelet manager with the camera nodel
|
||||
|
||||
* `image_raw` (*sensor_msgs/Image*) – camera image
|
||||
* `camera_info` (*sensor_msgs/CameraInfo*) – camera calibration info
|
||||
* `map_markers` (*aruco_pose/MarkerArray*) – list of markers to disable TF transform publishing
|
||||
|
||||
#### Published
|
||||
|
||||
@@ -74,6 +75,7 @@ It's recommended to run it within the same nodelet manager with the camera nodel
|
||||
* `~image_width` – debug image width (default: 2000)
|
||||
* `~image_height` – debug image height (default: 2000)
|
||||
* `~image_margin` – debug image margin (default: 200)
|
||||
* `~image_axis` – whether debug image should contain axis (default: true)
|
||||
* `~dictionary` (*int*) – ArUco dictionary (default: 2) - should be the same as `dictionary` parameter of `aruco_detect` nodelet
|
||||
|
||||
Map file has one marker per line with the following line format:
|
||||
@@ -97,6 +99,7 @@ See examples in [`map`](map/) directory.
|
||||
#### Published
|
||||
|
||||
* `~pose` (*geometry_msgs/PoseWithCovarianceStamped*) – estimated map pose
|
||||
* `~map` (*aruco_pose/MarkerArray*) – list of markers in the loaded map
|
||||
* `~image` (*sensor_msgs/Image*) – planarized map image
|
||||
* `~visualization` (*visualization_msgs/MarkerArray*) – markers map visualization for rviz
|
||||
* `~debug` (*sensor_msgs/Image*) – debug image with detected markers and map axis
|
||||
|
||||
@@ -10,6 +10,8 @@ gen = ParameterGenerator()
|
||||
|
||||
gen.add("enabled", bool_t, 0, "if detection enabled", True)
|
||||
|
||||
gen.add("length", double_t, 0, "markers' side length", min=0, max=10)
|
||||
|
||||
gen.add("adaptiveThreshConstant", double_t, 0,
|
||||
"Constant for adaptive thresholding before finding contours",
|
||||
p.adaptiveThreshConstant, 0, 100)
|
||||
|
||||
14
aruco_pose/cfg/Map.cfg
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env python
|
||||
PACKAGE = "aruco_pose"
|
||||
|
||||
from dynamic_reconfigure.parameter_generator_catkin import *
|
||||
|
||||
gen = ParameterGenerator()
|
||||
|
||||
gen.add("enabled", bool_t, 0, "if map detection enabled", True)
|
||||
|
||||
gen.add("map", str_t, 0, "full path for the map file")
|
||||
|
||||
gen.add("image_axis", bool_t, 0, "debug image axis", default=True)
|
||||
|
||||
exit(gen.generate(PACKAGE, "aruco_pose", "Map"))
|
||||
@@ -1,3 +1,4 @@
|
||||
# id length x y z rot_z rot_y rot_x
|
||||
0 0.33 0.0 9.0 0 0 0 0
|
||||
1 0.33 1.0 9.0 0 0 0 0
|
||||
2 0.33 2.0 9.0 0 0 0 0
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# id length x y z rot_z rot_y rot_x
|
||||
107 0.33 0 0 0 0 0 0
|
||||
106 0.33 0.77 0 0 0 0 0
|
||||
105 0.33 0 0.77 0 0 0 0
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# id length x y z rot_z rot_y rot_x
|
||||
14 0.365 0.000 0.0 0 0 0 0
|
||||
15 0.365 1.335 0.0 0 0 0 0
|
||||
30 0.365 2.865 0.0 0 0 0 0
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="2">
|
||||
<name>aruco_pose</name>
|
||||
<version>0.21.1</version>
|
||||
<version>0.23.0</version>
|
||||
<description>Positioning with ArUco markers</description>
|
||||
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include <aruco_pose/Marker.h>
|
||||
#include <aruco_pose/MarkerArray.h>
|
||||
#include <aruco_pose/DetectorConfig.h>
|
||||
#include <aruco_pose/SetMarkers.h>
|
||||
|
||||
#include "utils.h"
|
||||
#include <memory>
|
||||
@@ -69,8 +70,10 @@ private:
|
||||
image_transport::CameraSubscriber img_sub_;
|
||||
ros::Publisher markers_pub_, vis_markers_pub_;
|
||||
ros::Subscriber map_markers_sub_;
|
||||
ros::ServiceServer set_markers_srv_;
|
||||
bool estimate_poses_, send_tf_, auto_flip_;
|
||||
double length_;
|
||||
ros::Duration transform_timeout_;
|
||||
std::unordered_map<int, double> length_override_;
|
||||
std::string frame_id_prefix_, known_tilt_;
|
||||
Mat camera_matrix_, dist_coeffs_;
|
||||
@@ -97,6 +100,7 @@ public:
|
||||
ros::shutdown();
|
||||
}
|
||||
readLengthOverride(nh_priv_);
|
||||
transform_timeout_ = ros::Duration(nh_priv_.param("transform_timeout", 0.02));
|
||||
|
||||
known_tilt_ = nh_priv_.param<std::string>("known_tilt", "");
|
||||
auto_flip_ = nh_priv_.param("auto_flip", false);
|
||||
@@ -114,6 +118,8 @@ public:
|
||||
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>>(nh_priv_);
|
||||
dyn_srv_->setCallback(std::bind(&ArucoDetect::paramCallback, this, std::placeholders::_1, std::placeholders::_2));
|
||||
|
||||
set_markers_srv_ = nh_priv_.advertiseService("set_length_override", &ArucoDetect::setMarkers, this);
|
||||
|
||||
debug_pub_ = it_priv.advertise("debug", 1);
|
||||
markers_pub_ = nh_priv_.advertise<aruco_pose::MarkerArray>("markers", 1);
|
||||
vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1);
|
||||
@@ -172,7 +178,7 @@ private:
|
||||
if (!known_tilt_.empty()) {
|
||||
try {
|
||||
snap_to = tf_buffer_->lookupTransform(msg->header.frame_id, known_tilt_,
|
||||
msg->header.stamp, ros::Duration(0.02));
|
||||
msg->header.stamp, transform_timeout_);
|
||||
} catch (const tf2::TransformException& e) {
|
||||
NODELET_WARN_THROTTLE(5, "can't snap: %s", e.what());
|
||||
}
|
||||
@@ -181,6 +187,8 @@ private:
|
||||
|
||||
array_.markers.reserve(ids.size());
|
||||
aruco_pose::Marker marker;
|
||||
vector<geometry_msgs::TransformStamped> transforms;
|
||||
transforms.reserve(ids.size());
|
||||
geometry_msgs::TransformStamped transform;
|
||||
transform.header.stamp = msg->header.stamp;
|
||||
transform.header.frame_id = msg->header.frame_id;
|
||||
@@ -198,20 +206,33 @@ private:
|
||||
snapOrientation(marker.pose.orientation, snap_to.transform.rotation, auto_flip_);
|
||||
}
|
||||
|
||||
// TODO: check IDs are unique
|
||||
if (send_tf_) {
|
||||
transform.child_frame_id = getChildFrameId(ids[i]);
|
||||
|
||||
// check if such static transform is in the map
|
||||
if (map_markers_ids_.find(ids[i]) == map_markers_ids_.end()) {
|
||||
transform.transform.rotation = marker.pose.orientation;
|
||||
fillTranslation(transform.transform.translation, tvecs[i]);
|
||||
br_->sendTransform(transform);
|
||||
// check if a markers with that id is already added
|
||||
bool send = true;
|
||||
for (auto &t : transforms) {
|
||||
if (t.child_frame_id == transform.child_frame_id) {
|
||||
send = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (send) {
|
||||
transform.transform.rotation = marker.pose.orientation;
|
||||
fillTranslation(transform.transform.translation, tvecs[i]);
|
||||
transforms.push_back(transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
array_.markers.push_back(marker);
|
||||
}
|
||||
|
||||
if (send_tf_) {
|
||||
br_->sendTransform(transforms);
|
||||
}
|
||||
}
|
||||
|
||||
markers_pub_.publish(array_);
|
||||
@@ -346,6 +367,29 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
bool setMarkers(aruco_pose::SetMarkers::Request& req, aruco_pose::SetMarkers::Response& res)
|
||||
{
|
||||
for (auto const& marker : req.markers) {
|
||||
if (marker.id > 999) {
|
||||
res.message = "Invalid marker id: " + std::to_string(marker.id);
|
||||
ROS_ERROR("%s", res.message.c_str());
|
||||
return true;
|
||||
}
|
||||
if (!std::isfinite(marker.length) || marker.length <= 0) {
|
||||
res.message = "Invalid marker " + std::to_string(marker.id) + " length: " + std::to_string(marker.length);
|
||||
ROS_ERROR("%s", res.message.c_str());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const& marker : req.markers) {
|
||||
length_override_[marker.id] = marker.length;
|
||||
}
|
||||
|
||||
res.success = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void mapMarkersCallback(const aruco_pose::MarkerArray& msg)
|
||||
{
|
||||
map_markers_ids_.clear();
|
||||
@@ -356,7 +400,8 @@ private:
|
||||
|
||||
void paramCallback(aruco_pose::DetectorConfig &config, uint32_t level)
|
||||
{
|
||||
enabled_ = config.enabled;
|
||||
enabled_ = config.enabled && config.length > 0;
|
||||
length_ = config.length;
|
||||
parameters_->adaptiveThreshConstant = config.adaptiveThreshConstant;
|
||||
parameters_->adaptiveThreshWinSizeMin = config.adaptiveThreshWinSizeMin;
|
||||
parameters_->adaptiveThreshWinSizeMax = config.adaptiveThreshWinSizeMax;
|
||||
|
||||
@@ -19,11 +19,13 @@
|
||||
#include <vector>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <ros/ros.h>
|
||||
#include <nodelet/nodelet.h>
|
||||
#include <pluginlib/class_list_macros.h>
|
||||
#include <image_transport/image_transport.h>
|
||||
#include <cv_bridge/cv_bridge.h>
|
||||
#include <dynamic_reconfigure/server.h>
|
||||
#include <tf/transform_datatypes.h>
|
||||
#include <tf2_ros/buffer.h>
|
||||
#include <tf2_ros/transform_listener.h>
|
||||
@@ -41,6 +43,7 @@
|
||||
|
||||
#include <aruco_pose/MarkerArray.h>
|
||||
#include <aruco_pose/Marker.h>
|
||||
#include <aruco_pose/MapConfig.h>
|
||||
|
||||
#include <opencv2/opencv.hpp>
|
||||
#include <opencv2/aruco.hpp>
|
||||
@@ -74,6 +77,9 @@ private:
|
||||
tf2_ros::StaticTransformBroadcaster static_br_;
|
||||
tf2_ros::Buffer tf_buffer_;
|
||||
tf2_ros::TransformListener tf_listener_{tf_buffer_};
|
||||
std::shared_ptr<dynamic_reconfigure::Server<aruco_pose::MapConfig>> dyn_srv_;
|
||||
bool enabled_ = true;
|
||||
std::string type_;
|
||||
visualization_msgs::MarkerArray vis_array_;
|
||||
std::string known_tilt_, map_, markers_frame_, markers_parent_frame_;
|
||||
int image_width_, image_height_, image_margin_;
|
||||
@@ -89,15 +95,14 @@ public:
|
||||
|
||||
// TODO: why image_transport doesn't work here?
|
||||
img_pub_ = nh_priv_.advertise<sensor_msgs::Image>("image", 1, true);
|
||||
markers_pub_ = nh_priv_.advertise<aruco_pose::MarkerArray>("markers", 1, true);
|
||||
markers_pub_ = nh_priv_.advertise<aruco_pose::MarkerArray>("map", 1, true);
|
||||
|
||||
board_ = cv::makePtr<cv::aruco::Board>();
|
||||
board_->dictionary = cv::aruco::getPredefinedDictionary(
|
||||
static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(nh_priv_.param("dictionary", 2)));
|
||||
camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
|
||||
|
||||
std::string type, map;
|
||||
type = nh_priv_.param<std::string>("type", "map");
|
||||
type_ = nh_priv_.param<std::string>("type", "map");
|
||||
transform_.child_frame_id = nh_priv_.param<std::string>("frame_id", "aruco_map");
|
||||
known_tilt_ = nh_priv_.param<std::string>("known_tilt", "");
|
||||
auto_flip_ = nh_priv_.param("auto_flip", false);
|
||||
@@ -110,13 +115,13 @@ public:
|
||||
|
||||
// createStripLine();
|
||||
|
||||
if (type == "map") {
|
||||
param(nh_priv_, "map", map);
|
||||
loadMap(map);
|
||||
} else if (type == "gridboard") {
|
||||
if (type_ == "map") {
|
||||
map_ = nh_priv_.param<std::string>("map" , "");
|
||||
loadMap(map_);
|
||||
} else if (type_ == "gridboard") {
|
||||
createGridBoard(nh_priv_);
|
||||
} else {
|
||||
NODELET_FATAL("unknown type: %s", type.c_str());
|
||||
NODELET_FATAL("unknown type: %s", type_.c_str());
|
||||
ros::shutdown();
|
||||
}
|
||||
|
||||
@@ -124,10 +129,7 @@ public:
|
||||
vis_markers_pub_ = nh_priv_.advertise<visualization_msgs::MarkerArray>("visualization", 1, true);
|
||||
debug_pub_ = it_priv.advertise("debug", 1);
|
||||
|
||||
publishMarkersFrames();
|
||||
publishMarkers();
|
||||
publishMapImage();
|
||||
vis_markers_pub_.publish(vis_array_);
|
||||
publishMap();
|
||||
|
||||
image_sub_.subscribe(nh_, "image_raw", 1);
|
||||
info_sub_.subscribe(nh_, "camera_info", 1);
|
||||
@@ -136,6 +138,12 @@ public:
|
||||
sync_.reset(new message_filters::Synchronizer<SyncPolicy>(SyncPolicy(10), image_sub_, info_sub_, markers_sub_));
|
||||
sync_->registerCallback(boost::bind(&ArucoMap::callback, this, _1, _2, _3));
|
||||
|
||||
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<aruco_pose::MapConfig>>(nh_priv_);
|
||||
dynamic_reconfigure::Server<aruco_pose::MapConfig>::CallbackType cb;
|
||||
|
||||
cb = std::bind(&ArucoMap::paramCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
dyn_srv_->setCallback(cb);
|
||||
|
||||
NODELET_INFO("ready");
|
||||
}
|
||||
|
||||
@@ -143,6 +151,9 @@ public:
|
||||
const sensor_msgs::CameraInfoConstPtr& cinfo,
|
||||
const aruco_pose::MarkerArrayConstPtr& markers)
|
||||
{
|
||||
if (!enabled_) return;
|
||||
if (markers->markers.empty()) return; // map not loaded
|
||||
|
||||
int valid = 0;
|
||||
int count = markers->markers.size();
|
||||
std::vector<int> ids;
|
||||
@@ -268,9 +279,17 @@ publish_debug:
|
||||
std::ifstream f(filename);
|
||||
std::string line;
|
||||
|
||||
clearMarkers();
|
||||
|
||||
if (map_ == "") {
|
||||
NODELET_INFO("No map loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!f.good()) {
|
||||
NODELET_FATAL("%s - %s", strerror(errno), filename.c_str());
|
||||
ros::shutdown();
|
||||
NODELET_ERROR("%s - %s", strerror(errno), filename.c_str());
|
||||
map_ = "";
|
||||
return;
|
||||
}
|
||||
|
||||
while (std::getline(f, line)) {
|
||||
@@ -296,9 +315,10 @@ publish_debug:
|
||||
s.putback(first);
|
||||
} else {
|
||||
// Probably garbage data; inform user and throw an exception, possibly killing nodelet
|
||||
NODELET_FATAL("Malformed input: %s", line.c_str());
|
||||
ros::shutdown();
|
||||
throw std::runtime_error("Malformed input");
|
||||
NODELET_ERROR("Malformed input: %s", line.c_str());
|
||||
map_ = "";
|
||||
clearMarkers();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(s >> id >> length >> x >> y)) {
|
||||
@@ -329,6 +349,14 @@ publish_debug:
|
||||
NODELET_INFO("loading %s complete (%d markers)", filename.c_str(), static_cast<int>(board_->ids.size()));
|
||||
}
|
||||
|
||||
void publishMap()
|
||||
{
|
||||
publishMarkersFrames();
|
||||
publishMarkers();
|
||||
publishMapImage();
|
||||
vis_markers_pub_.publish(vis_array_);
|
||||
}
|
||||
|
||||
void createGridBoard(ros::NodeHandle& nh)
|
||||
{
|
||||
NODELET_INFO("generate gridboard");
|
||||
@@ -370,6 +398,15 @@ publish_debug:
|
||||
}
|
||||
}
|
||||
|
||||
void clearMarkers()
|
||||
{
|
||||
board_->ids.clear();
|
||||
board_->objPoints.clear();
|
||||
markers_.markers.clear();
|
||||
vis_array_.markers.clear();
|
||||
markers_transforms_.clear();
|
||||
}
|
||||
|
||||
// void createStripLine()
|
||||
// {
|
||||
// visualization_msgs::Marker marker;
|
||||
@@ -509,6 +546,22 @@ publish_debug:
|
||||
msg.image = image;
|
||||
img_pub_.publish(msg.toImageMsg());
|
||||
}
|
||||
|
||||
void paramCallback(aruco_pose::MapConfig &config, uint32_t level)
|
||||
{
|
||||
// https://github.com/CopterExpress/clover/commit/2cd334c474e3ed04ef65ca1ba7f08ab535a3dc6d#diff-942723f9452c398ae93f1a91427f9a7b614be5e5871f8a3e590f324d804f0d58R356
|
||||
enabled_ = config.enabled;
|
||||
if (type_ == "map" && config.map != map_) {
|
||||
map_ = config.map;
|
||||
loadMap(map_);
|
||||
publishMap();
|
||||
}
|
||||
|
||||
if (config.image_axis != image_axis_) {
|
||||
image_axis_ = config.image_axis;
|
||||
publishMapImage();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PLUGINLIB_EXPORT_CLASS(ArucoMap, nodelet::Nodelet)
|
||||
|
||||
7
aruco_pose/srv/SetMarkers.srv
Normal file
@@ -0,0 +1,7 @@
|
||||
# * Add or change markers in the map
|
||||
# * Change markers' properties, e. g. lengths
|
||||
|
||||
Marker[] markers # if length or pose is nan - remove from map
|
||||
---
|
||||
bool success
|
||||
string message
|
||||
@@ -143,7 +143,7 @@ def test_map_image(node):
|
||||
assert img.encoding in ('mono8', 'rgb8')
|
||||
|
||||
def test_map_markers(node):
|
||||
markers = rospy.wait_for_message('aruco_map/markers', MarkerArray, timeout=5)
|
||||
markers = rospy.wait_for_message('aruco_map/map', MarkerArray, timeout=5)
|
||||
assert markers.markers[0].id == 1
|
||||
assert markers.markers[1].id == 2
|
||||
assert markers.markers[2].id == 3
|
||||
|
||||
BIN
aruco_pose/test/duplicate.png
Normal file
|
After Width: | Height: | Size: 62 KiB |
8
aruco_pose/test/duplicate.py
Normal file
@@ -0,0 +1,8 @@
|
||||
import pytest
|
||||
import subprocess
|
||||
|
||||
def test_no_tf_repeated_data():
|
||||
# `/rosout` acts weirdly inside rostest, so using a subprocess
|
||||
cmd = """python -c 'import rospy, tf; rospy.init_node("foo"); listener = tf.TransformListener(); rospy.sleep(2)'"""
|
||||
output = str(subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT))
|
||||
assert 'TF_REPEATED_DATA' not in output, 'TF_REPEATED_DATA was logged on duplicate markers'
|
||||
21
aruco_pose/test/duplicate.test
Normal file
@@ -0,0 +1,21 @@
|
||||
<launch>
|
||||
<node pkg="image_publisher" type="image_publisher" name="main_camera" args="$(find aruco_pose)/test/duplicate.png">
|
||||
<param name="frame_id" value="main_camera_optical"/>
|
||||
<param name="publish_rate" value="10"/>
|
||||
<param name="camera_info_url" value="file://$(find aruco_pose)/test/camera_info.yaml" />
|
||||
</node>
|
||||
|
||||
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager" required="true"/>
|
||||
|
||||
<node pkg="nodelet" clear_params="true" type="nodelet" name="aruco_detect" args="load aruco_pose/aruco_detect nodelet_manager" required="true">
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<param name="length" value="0.33"/>
|
||||
<param name="estimate_poses" value="true"/>
|
||||
<param name="send_tf" value="true"/>
|
||||
<param name="cornerRefinementMethod" value="1"/>
|
||||
</node>
|
||||
|
||||
<param name="test_module" value="$(find aruco_pose)/test/duplicate.py"/>
|
||||
<test test-name="aruco_pose_test" pkg="ros_pytest" type="ros_pytest_runner"/>
|
||||
</launch>
|
||||
@@ -2,6 +2,7 @@ import rospy
|
||||
import pytest
|
||||
|
||||
from visualization_msgs.msg import MarkerArray as VisMarkerArray
|
||||
from aruco_pose.msg import MarkerArray
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@@ -9,5 +10,5 @@ def node():
|
||||
return rospy.init_node('aruco_pose_test', anonymous=True)
|
||||
|
||||
def test_node_failure(node):
|
||||
with pytest.raises(rospy.exceptions.ROSException):
|
||||
rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
|
||||
assert rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5).markers == []
|
||||
assert rospy.wait_for_message('aruco_map/map', MarkerArray, timeout=5).markers == []
|
||||
|
||||
735
builder/assets/noetic-bullseye.yaml
Normal file
@@ -0,0 +1,735 @@
|
||||
catkin:
|
||||
debian:
|
||||
bullseye: [ros-noetic-catkin]
|
||||
genmsg:
|
||||
debian:
|
||||
bullseye: [ros-noetic-genmsg]
|
||||
gencpp:
|
||||
debian:
|
||||
bullseye: [ros-noetic-gencpp]
|
||||
geneus:
|
||||
debian:
|
||||
bullseye: [ros-noetic-geneus]
|
||||
genlisp:
|
||||
debian:
|
||||
bullseye: [ros-noetic-genlisp]
|
||||
gennodejs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-gennodejs]
|
||||
genpy:
|
||||
debian:
|
||||
bullseye: [ros-noetic-genpy]
|
||||
bond_core:
|
||||
debian:
|
||||
bullseye: [ros-noetic-bond-core]
|
||||
cmake_modules:
|
||||
debian:
|
||||
bullseye: [ros-noetic-cmake-modules]
|
||||
class_loader:
|
||||
debian:
|
||||
bullseye: [ros-noetic-class-loader]
|
||||
common_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-common-msgs]
|
||||
common_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-common-tutorials]
|
||||
cpp_common:
|
||||
debian:
|
||||
bullseye: [ros-noetic-cpp-common]
|
||||
desktop:
|
||||
debian:
|
||||
bullseye: [ros-noetic-desktop]
|
||||
diagnostics:
|
||||
debian:
|
||||
bullseye: [ros-noetic-diagnostics]
|
||||
executive_smach:
|
||||
debian:
|
||||
bullseye: [ros-noetic-executive-smach]
|
||||
geometry:
|
||||
debian:
|
||||
bullseye: [ros-noetic-geometry]
|
||||
geometry_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-geometry-tutorials]
|
||||
gl_dependency:
|
||||
debian:
|
||||
bullseye: [ros-noetic-gl-dependency]
|
||||
image_common:
|
||||
debian:
|
||||
bullseye: [ros-noetic-image-common]
|
||||
image_pipeline:
|
||||
debian:
|
||||
bullseye: [ros-noetic-image-pipeline]
|
||||
image_transport_plugins:
|
||||
debian:
|
||||
bullseye: [ros-noetic-image-transport-plugins]
|
||||
laser_pipeline:
|
||||
debian:
|
||||
bullseye: [ros-noetic-laser-pipeline]
|
||||
mavlink:
|
||||
debian:
|
||||
bullseye: [ros-noetic-mavlink]
|
||||
media_export:
|
||||
debian:
|
||||
bullseye: [ros-noetic-media-export]
|
||||
message_generation:
|
||||
debian:
|
||||
bullseye: [ros-noetic-message-generation]
|
||||
message_runtime:
|
||||
debian:
|
||||
bullseye: [ros-noetic-message-runtime]
|
||||
mk:
|
||||
debian:
|
||||
bullseye: [ros-noetic-mk]
|
||||
nodelet_core:
|
||||
debian:
|
||||
bullseye: [ros-noetic-nodelet-core]
|
||||
orocos_kdl:
|
||||
debian:
|
||||
bullseye: [ros-noetic-orocos-kdl]
|
||||
perception:
|
||||
debian:
|
||||
bullseye: [ros-noetic-perception]
|
||||
perception_pcl:
|
||||
debian:
|
||||
bullseye: [ros-noetic-perception-pcl]
|
||||
python_orocos_kdl:
|
||||
debian:
|
||||
bullseye: [ros-noetic-python-orocos-kdl]
|
||||
qt_dotgraph:
|
||||
debian:
|
||||
bullseye: [ros-noetic-qt-dotgraph]
|
||||
qt_gui:
|
||||
debian:
|
||||
bullseye: [ros-noetic-qt-gui]
|
||||
qt_gui_py_common:
|
||||
debian:
|
||||
bullseye: [ros-noetic-qt-gui-py-common]
|
||||
qwt_dependency:
|
||||
debian:
|
||||
bullseye: [ros-noetic-qwt-dependency]
|
||||
robot:
|
||||
debian:
|
||||
bullseye: [ros-noetic-robot]
|
||||
ros:
|
||||
debian:
|
||||
bullseye: [ros-noetic-ros]
|
||||
ros_base:
|
||||
debian:
|
||||
bullseye: [ros-noetic-ros-base]
|
||||
ros_comm:
|
||||
debian:
|
||||
bullseye: [ros-noetic-ros-comm]
|
||||
ros_core:
|
||||
debian:
|
||||
bullseye: [ros-noetic-ros-core]
|
||||
ros_environment:
|
||||
debian:
|
||||
bullseye: [ros-noetic-ros-environment]
|
||||
ros_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-ros-tutorials]
|
||||
rosapi:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosapi]
|
||||
rosbag_migration_rule:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosbag-migration-rule]
|
||||
rosbash:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosbash]
|
||||
rosboost_cfg:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosboost-cfg]
|
||||
rosbridge_server:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosbridge-server]
|
||||
rosbridge_suite:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosbridge-suite]
|
||||
rosbuild:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosbuild]
|
||||
rosclean:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosclean]
|
||||
roscpp_core:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roscpp-core]
|
||||
roscpp_traits:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roscpp-traits]
|
||||
roscreate:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roscreate]
|
||||
rosgraph:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosgraph]
|
||||
roslang:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roslang]
|
||||
roslint:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roslint]
|
||||
roslisp:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roslisp]
|
||||
rosmake:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosmake]
|
||||
rosmaster:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosmaster]
|
||||
rospack:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rospack]
|
||||
roslib:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roslib]
|
||||
rosparam:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosparam]
|
||||
rospy:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rospy]
|
||||
rosserial:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosserial]
|
||||
rosserial_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosserial-msgs]
|
||||
rosserial_python:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosserial-python]
|
||||
rosservice:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosservice]
|
||||
rostime:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rostime]
|
||||
roscpp_serialization:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roscpp-serialization]
|
||||
python_qt_binding:
|
||||
debian:
|
||||
bullseye: [ros-noetic-python-qt-binding]
|
||||
roslaunch:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roslaunch]
|
||||
rosunit:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosunit]
|
||||
angles:
|
||||
debian:
|
||||
bullseye: [ros-noetic-angles]
|
||||
libmavconn:
|
||||
debian:
|
||||
bullseye: [ros-noetic-libmavconn]
|
||||
rosconsole:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosconsole]
|
||||
pluginlib:
|
||||
debian:
|
||||
bullseye: [ros-noetic-pluginlib]
|
||||
qt_gui_cpp:
|
||||
debian:
|
||||
bullseye: [ros-noetic-qt-gui-cpp]
|
||||
resource_retriever:
|
||||
debian:
|
||||
bullseye: [ros-noetic-resource-retriever]
|
||||
rosconsole_bridge:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosconsole-bridge]
|
||||
roslz4:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roslz4]
|
||||
rosserial_client:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosserial-client]
|
||||
rostest:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rostest]
|
||||
rqt_action:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-action]
|
||||
rqt_bag:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-bag]
|
||||
rqt_bag_plugins:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-bag-plugins]
|
||||
rqt_common_plugins:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-common-plugins]
|
||||
rqt_console:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-console]
|
||||
rqt_dep:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-dep]
|
||||
rqt_graph:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-graph]
|
||||
rqt_gui:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-gui]
|
||||
rqt_logger_level:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-logger-level]
|
||||
rqt_moveit:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-moveit]
|
||||
rqt_msg:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-msg]
|
||||
rqt_nav_view:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-nav-view]
|
||||
rqt_plot:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-plot]
|
||||
rqt_pose_view:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-pose-view]
|
||||
rqt_publisher:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-publisher]
|
||||
rqt_py_console:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-py-console]
|
||||
rqt_reconfigure:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-reconfigure]
|
||||
rqt_robot_dashboard:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-robot-dashboard]
|
||||
rqt_robot_monitor:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-robot-monitor]
|
||||
rqt_robot_plugins:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-robot-plugins]
|
||||
rqt_robot_steering:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-robot-steering]
|
||||
rqt_runtime_monitor:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-runtime-monitor]
|
||||
rqt_service_caller:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-service-caller]
|
||||
rqt_shell:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-shell]
|
||||
rqt_srv:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-srv]
|
||||
rqt_tf_tree:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-tf-tree]
|
||||
rqt_top:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-top]
|
||||
rqt_topic:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-topic]
|
||||
rqt_web:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-web]
|
||||
smach:
|
||||
debian:
|
||||
bullseye: [ros-noetic-smach]
|
||||
smclib:
|
||||
debian:
|
||||
bullseye: [ros-noetic-smclib]
|
||||
std_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-std-msgs]
|
||||
actionlib_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-actionlib-msgs]
|
||||
bond:
|
||||
debian:
|
||||
bullseye: [ros-noetic-bond]
|
||||
diagnostic_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-diagnostic-msgs]
|
||||
geometry_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-geometry-msgs]
|
||||
eigen_conversions:
|
||||
debian:
|
||||
bullseye: [ros-noetic-eigen-conversions]
|
||||
kdl_conversions:
|
||||
debian:
|
||||
bullseye: [ros-noetic-kdl-conversions]
|
||||
nav_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-nav-msgs]
|
||||
rosbridge_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosbridge-msgs]
|
||||
rosgraph_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosgraph-msgs]
|
||||
rosmsg:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosmsg]
|
||||
rqt_py_common:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-py-common]
|
||||
shape_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-shape-msgs]
|
||||
smach_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-smach-msgs]
|
||||
std_srvs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-std-srvs]
|
||||
tf2_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf2-msgs]
|
||||
tf2:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf2]
|
||||
tf2_eigen:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf2-eigen]
|
||||
trajectory_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-trajectory-msgs]
|
||||
control_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-control-msgs]
|
||||
urdf_parser_plugin:
|
||||
debian:
|
||||
bullseye: [ros-noetic-urdf-parser-plugin]
|
||||
urdfdom_py:
|
||||
debian:
|
||||
bullseye: [ros-noetic-urdfdom-py]
|
||||
uuid_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-uuid-msgs]
|
||||
geographic_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-geographic-msgs]
|
||||
vision_opencv:
|
||||
debian:
|
||||
bullseye: [ros-noetic-vision-opencv]
|
||||
visualization_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-visualization-msgs]
|
||||
visualization_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-visualization-tutorials]
|
||||
viz:
|
||||
debian:
|
||||
bullseye: [ros-noetic-viz]
|
||||
webkit_dependency:
|
||||
debian:
|
||||
bullseye: [ros-noetic-webkit-dependency]
|
||||
xmlrpcpp:
|
||||
debian:
|
||||
bullseye: [ros-noetic-xmlrpcpp]
|
||||
roscpp:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roscpp]
|
||||
bondcpp:
|
||||
debian:
|
||||
bullseye: [ros-noetic-bondcpp]
|
||||
bondpy:
|
||||
debian:
|
||||
bullseye: [ros-noetic-bondpy]
|
||||
nodelet:
|
||||
debian:
|
||||
bullseye: [ros-noetic-nodelet]
|
||||
nodelet_tutorial_math:
|
||||
debian:
|
||||
bullseye: [ros-noetic-nodelet-tutorial-math]
|
||||
pluginlib_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-pluginlib-tutorials]
|
||||
roscpp_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roscpp-tutorials]
|
||||
rosout:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosout]
|
||||
async_web_server_cpp:
|
||||
debian:
|
||||
bullseye: [ros-noetic-async-web-server-cpp]
|
||||
camera_calibration:
|
||||
debian:
|
||||
bullseye: [ros-noetic-camera-calibration]
|
||||
diagnostic_aggregator:
|
||||
debian:
|
||||
bullseye: [ros-noetic-diagnostic-aggregator]
|
||||
diagnostic_updater:
|
||||
debian:
|
||||
bullseye: [ros-noetic-diagnostic-updater]
|
||||
diagnostic_common_diagnostics:
|
||||
debian:
|
||||
bullseye: [ros-noetic-diagnostic-common-diagnostics]
|
||||
dynamic_reconfigure:
|
||||
debian:
|
||||
bullseye: [ros-noetic-dynamic-reconfigure]
|
||||
filters:
|
||||
debian:
|
||||
bullseye: [ros-noetic-filters]
|
||||
joint_state_publisher:
|
||||
debian:
|
||||
bullseye: [ros-noetic-joint-state-publisher]
|
||||
message_filters:
|
||||
debian:
|
||||
bullseye: [ros-noetic-message-filters]
|
||||
ros_pytest:
|
||||
debian:
|
||||
bullseye: [ros-noetic-ros-pytest]
|
||||
rosauth:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosauth]
|
||||
rosbag_storage:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosbag-storage]
|
||||
rosnode:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosnode]
|
||||
rospy_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rospy-tutorials]
|
||||
rosshow:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosshow]
|
||||
rostopic:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rostopic]
|
||||
rqt_gui_cpp:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-gui-cpp]
|
||||
rqt_gui_py:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-gui-py]
|
||||
self_test:
|
||||
debian:
|
||||
bullseye: [ros-noetic-self-test]
|
||||
smach_ros:
|
||||
debian:
|
||||
bullseye: [ros-noetic-smach-ros]
|
||||
tf2_py:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf2-py]
|
||||
topic_tools:
|
||||
debian:
|
||||
bullseye: [ros-noetic-topic-tools]
|
||||
rosbag:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosbag]
|
||||
actionlib:
|
||||
debian:
|
||||
bullseye: [ros-noetic-actionlib]
|
||||
actionlib_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-actionlib-tutorials]
|
||||
diagnostic_analysis:
|
||||
debian:
|
||||
bullseye: [ros-noetic-diagnostic-analysis]
|
||||
nodelet_topic_tools:
|
||||
debian:
|
||||
bullseye: [ros-noetic-nodelet-topic-tools]
|
||||
roswtf:
|
||||
debian:
|
||||
bullseye: [ros-noetic-roswtf]
|
||||
rqt_launch:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-launch]
|
||||
sensor_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-sensor-msgs]
|
||||
camera_calibration_parsers:
|
||||
debian:
|
||||
bullseye: [ros-noetic-camera-calibration-parsers]
|
||||
cv_bridge:
|
||||
debian:
|
||||
bullseye: [ros-noetic-cv-bridge]
|
||||
image_geometry:
|
||||
debian:
|
||||
bullseye: [ros-noetic-image-geometry]
|
||||
image_transport:
|
||||
debian:
|
||||
bullseye: [ros-noetic-image-transport]
|
||||
camera_info_manager:
|
||||
debian:
|
||||
bullseye: [ros-noetic-camera-info-manager]
|
||||
compressed_depth_image_transport:
|
||||
debian:
|
||||
bullseye: [ros-noetic-compressed-depth-image-transport]
|
||||
compressed_image_transport:
|
||||
debian:
|
||||
bullseye: [ros-noetic-compressed-image-transport]
|
||||
cv_camera:
|
||||
debian:
|
||||
bullseye: [ros-noetic-cv-camera]
|
||||
image_proc:
|
||||
debian:
|
||||
bullseye: [ros-noetic-image-proc]
|
||||
image_publisher:
|
||||
debian:
|
||||
bullseye: [ros-noetic-image-publisher]
|
||||
map_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-map-msgs]
|
||||
mavros_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-mavros-msgs]
|
||||
pcl_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-pcl-msgs]
|
||||
pcl_conversions:
|
||||
debian:
|
||||
bullseye: [ros-noetic-pcl-conversions]
|
||||
polled_camera:
|
||||
debian:
|
||||
bullseye: [ros-noetic-polled-camera]
|
||||
rqt_image_view:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-image-view]
|
||||
stereo_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-stereo-msgs]
|
||||
image_view:
|
||||
debian:
|
||||
bullseye: [ros-noetic-image-view]
|
||||
rosbridge_library:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rosbridge-library]
|
||||
stereo_image_proc:
|
||||
debian:
|
||||
bullseye: [ros-noetic-stereo-image-proc]
|
||||
tf2_ros:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf2-ros]
|
||||
depth_image_proc:
|
||||
debian:
|
||||
bullseye: [ros-noetic-depth-image-proc]
|
||||
mavros:
|
||||
debian:
|
||||
bullseye: [ros-noetic-mavros]
|
||||
tf:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf]
|
||||
interactive_markers:
|
||||
debian:
|
||||
bullseye: [ros-noetic-interactive-markers]
|
||||
interactive_marker_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-interactive-marker-tutorials]
|
||||
laser_geometry:
|
||||
debian:
|
||||
bullseye: [ros-noetic-laser-geometry]
|
||||
laser_assembler:
|
||||
debian:
|
||||
bullseye: [ros-noetic-laser-assembler]
|
||||
laser_filters:
|
||||
debian:
|
||||
bullseye: [ros-noetic-laser-filters]
|
||||
pcl_ros:
|
||||
debian:
|
||||
bullseye: [ros-noetic-pcl-ros]
|
||||
tf2_geometry_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf2-geometry-msgs]
|
||||
image_rotate:
|
||||
debian:
|
||||
bullseye: [ros-noetic-image-rotate]
|
||||
tf2_kdl:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf2-kdl]
|
||||
tf2_web_republisher:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf2-web-republisher]
|
||||
tf_conversions:
|
||||
debian:
|
||||
bullseye: [ros-noetic-tf-conversions]
|
||||
theora_image_transport:
|
||||
debian:
|
||||
bullseye: [ros-noetic-theora-image-transport]
|
||||
turtlesim:
|
||||
debian:
|
||||
bullseye: [ros-noetic-turtlesim]
|
||||
turtle_actionlib:
|
||||
debian:
|
||||
bullseye: [ros-noetic-turtle-actionlib]
|
||||
turtle_tf:
|
||||
debian:
|
||||
bullseye: [ros-noetic-turtle-tf]
|
||||
turtle_tf2:
|
||||
debian:
|
||||
bullseye: [ros-noetic-turtle-tf2]
|
||||
urdf:
|
||||
debian:
|
||||
bullseye: [ros-noetic-urdf]
|
||||
kdl_parser:
|
||||
debian:
|
||||
bullseye: [ros-noetic-kdl-parser]
|
||||
kdl_parser_py:
|
||||
debian:
|
||||
bullseye: [ros-noetic-kdl-parser-py]
|
||||
mavros_extras:
|
||||
debian:
|
||||
bullseye: [ros-noetic-mavros-extras]
|
||||
robot_state_publisher:
|
||||
debian:
|
||||
bullseye: [ros-noetic-robot-state-publisher]
|
||||
rviz:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rviz]
|
||||
librviz_tutorial:
|
||||
debian:
|
||||
bullseye: [ros-noetic-librviz-tutorial]
|
||||
rqt_rviz:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rqt-rviz]
|
||||
rviz_plugin_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rviz-plugin-tutorials]
|
||||
rviz_python_tutorial:
|
||||
debian:
|
||||
bullseye: [ros-noetic-rviz-python-tutorial]
|
||||
urdf_tutorial:
|
||||
debian:
|
||||
bullseye: [ros-noetic-urdf-tutorial]
|
||||
usb_cam:
|
||||
debian:
|
||||
bullseye: [ros-noetic-usb-cam]
|
||||
visualization_marker_tutorials:
|
||||
debian:
|
||||
bullseye: [ros-noetic-visualization-marker-tutorials]
|
||||
vl53l1x:
|
||||
debian:
|
||||
bullseye: [ros-noetic-vl53l1x]
|
||||
web_video_server:
|
||||
debian:
|
||||
bullseye: [ros-noetic-web-video-server]
|
||||
xacro:
|
||||
debian:
|
||||
bullseye: [ros-noetic-xacro]
|
||||
led_msgs:
|
||||
debian:
|
||||
bullseye: [ros-noetic-led-msgs]
|
||||
ws281x:
|
||||
debian:
|
||||
bullseye: [ros-noetic-ws281x]
|
||||
ddynamic_reconfigure:
|
||||
debian:
|
||||
bullseye: [ros-noetic-ddynamic-reconfigure]
|
||||
librealsense2:
|
||||
debian:
|
||||
bullseye: [ros-noetic-librealsense2]
|
||||
realsense2_camera:
|
||||
debian:
|
||||
bullseye: [ros-noetic-realsense2-camera]
|
||||
realsense2_description:
|
||||
debian:
|
||||
bullseye: [ros-noetic-realsense2-description]
|
||||
100
builder/image-build-ros.sh
Executable file
@@ -0,0 +1,100 @@
|
||||
#! /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:11:$VERSION_CODENAME
|
||||
|
||||
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 -
|
||||
|
||||
apt-get update
|
||||
apt-get install -y python3-distutils python3-rosdep python3-rosinstall-generator build-essential git # python3-vcstool
|
||||
|
||||
# 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 vcstool
|
||||
|
||||
# sudo rosdep init
|
||||
rm /etc/ros/rosdep/sources.list.d/20-default.list
|
||||
rosdep init
|
||||
rosdep update
|
||||
|
||||
# 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 --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:$VERSION_CODENAME --skip-keys="python3-catkin-pkg python3-catkin-pkg-modules python3-rosdep-modules"
|
||||
|
||||
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 bullseye dependencies
|
||||
echo "yaml file:///etc/ros/rosdep/noetic-bullseye.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
|
||||
@@ -16,7 +16,7 @@
|
||||
set -e # Exit immidiately on non-zero result
|
||||
|
||||
# https://www.raspberrypi.org/software/operating-systems/#raspberry-pi-os-32-bit
|
||||
SOURCE_IMAGE="https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-05-28/2021-05-07-raspios-buster-armhf-lite.zip"
|
||||
SOURCE_IMAGE="https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2023-12-11/2023-12-11-raspios-bookworm-armhf-lite.img.xz"
|
||||
|
||||
export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:='noninteractive'}
|
||||
export LANG=${LANG:='C.UTF-8'}
|
||||
@@ -104,7 +104,7 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/butterf
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/butterfly.socket' '/lib/systemd/system/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/monkey.service' '/lib/systemd/system/'
|
||||
# software install
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-software.sh'
|
||||
# ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-software.sh'
|
||||
# network setup
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-network.sh'
|
||||
# avahi setup
|
||||
@@ -113,13 +113,14 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/avahi-s
|
||||
# If RPi then use a one thread to build a ROS package on RPi, else use all
|
||||
[[ $(arch) == 'armv7l' ]] && NUMBER_THREADS=1 || NUMBER_THREADS=$(nproc --all)
|
||||
# Clover
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/noetic-rosdep-clover.yaml' '/etc/ros/rosdep/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/noetic-bullseye.yaml' '/etc/ros/rosdep/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/ros_python_paths' '/etc/sudoers.d/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/pigpiod.service' '/lib/systemd/system/'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/launch.nanorc' '/usr/share/nano/'
|
||||
# ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-ros-clover.rosinstall' '/home/pi/ros_catkin_ws/'
|
||||
# Add rename script
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-ros.sh' ${REPO_URL} ${IMAGE_VERSION} false false ${NUMBER_THREADS}
|
||||
#${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-ros.sh' ${REPO_URL} ${IMAGE_VERSION} false false ${NUMBER_THREADS}
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-build-ros.sh'
|
||||
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh'
|
||||
|
||||
${BUILDER_DIR}/image-resize.sh ${IMAGE_PATH}
|
||||
|
||||
@@ -90,7 +90,7 @@ echo_stamp "Installing OpenCV 4.2-compatible ROS packages"
|
||||
apt install -y --no-install-recommends \
|
||||
ros-${ROS_DISTRO}-compressed-image-transport=1.14.0-0buster \
|
||||
ros-${ROS_DISTRO}-cv-bridge=1.15.0-0buster \
|
||||
ros-${ROS_DISTRO}-cv-camera=0.5.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 \
|
||||
|
||||
@@ -58,5 +58,9 @@ rosversion rosshow
|
||||
rosversion nodelet
|
||||
rosversion image_view
|
||||
|
||||
# validate some versions
|
||||
[[ $(rosversion cv_camera) == "0.5.1" ]] # patched version with init fix
|
||||
[[ $(rosversion ws281x) == "0.0.13" ]]
|
||||
|
||||
# validate examples are present
|
||||
[[ $(ls /home/pi/examples/*) ]]
|
||||
|
||||
@@ -17,6 +17,7 @@ find_package(catkin REQUIRED COMPONENTS
|
||||
message_generation
|
||||
geometry_msgs
|
||||
sensor_msgs
|
||||
led_msgs
|
||||
geographic_msgs
|
||||
tf
|
||||
tf2
|
||||
@@ -24,6 +25,7 @@ find_package(catkin REQUIRED COMPONENTS
|
||||
tf2_ros
|
||||
image_transport
|
||||
cv_bridge
|
||||
dynamic_reconfigure
|
||||
)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
|
||||
@@ -126,10 +128,9 @@ generate_messages(
|
||||
## and list every .cfg file to be processed
|
||||
|
||||
## Generate dynamic reconfigure parameters in the 'cfg' folder
|
||||
# generate_dynamic_reconfigure_options(
|
||||
# cfg/DynReconf1.cfg
|
||||
# cfg/DynReconf2.cfg
|
||||
# )
|
||||
generate_dynamic_reconfigure_options(
|
||||
cfg/Flow.cfg
|
||||
)
|
||||
|
||||
###################################
|
||||
## catkin specific configuration ##
|
||||
@@ -211,6 +212,8 @@ add_dependencies(clover_led ${PROJECT_NAME}_generate_messages_cpp)
|
||||
|
||||
add_dependencies(shell ${PROJECT_NAME}_generate_messages_cpp)
|
||||
|
||||
add_dependencies(${PROJECT_NAME} ${PROJECT_NAME}_gencfg)
|
||||
|
||||
## Rename C++ executable without prefix
|
||||
## The above recommended prefix causes long target names, the following renames the
|
||||
## target back to the shorter version for ease of user use
|
||||
|
||||
10
clover/cfg/Flow.cfg
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python
|
||||
PACKAGE = "clover"
|
||||
|
||||
from dynamic_reconfigure.parameter_generator_catkin import *
|
||||
|
||||
gen = ParameterGenerator()
|
||||
|
||||
gen.add("enabled", bool_t, 0, "if optical flow enabled", True)
|
||||
|
||||
exit(gen.generate(PACKAGE, "clover", "Flow"))
|
||||
@@ -8,16 +8,20 @@
|
||||
|
||||
<!-- For additional help go to https://clover.coex.tech/aruco -->
|
||||
|
||||
<arg name="force_init" default="false"/>
|
||||
<arg name="disable" default="false"/> <!-- only force init -->
|
||||
|
||||
<!-- aruco_detect: detect aruco markers, estimate poses -->
|
||||
<node name="aruco_detect" pkg="nodelet" if="$(arg aruco_detect)" type="nodelet" args="load aruco_pose/aruco_detect main_camera_nodelet_manager" output="screen" clear_params="true" respawn="true">
|
||||
<node name="aruco_detect" pkg="nodelet" if="$(eval aruco_detect and not disable)" type="nodelet" args="load aruco_pose/aruco_detect main_camera_nodelet_manager" output="screen" clear_params="true" respawn="true">
|
||||
<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/markers"/>
|
||||
<remap from="map_markers" to="aruco_map/map"/>
|
||||
<param name="estimate_poses" value="true"/>
|
||||
<param name="send_tf" value="true"/>
|
||||
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/>
|
||||
<param name="known_tilt" value="map_flipped" if="$(eval placement == 'ceiling')"/>
|
||||
<param name="length" value="$(arg length)"/>
|
||||
<param name="transform_timeout" value="0.1"/>
|
||||
<!-- aruco detector parameters -->
|
||||
<param name="cornerRefinementMethod" value="2"/> <!-- contour refinement -->
|
||||
<param name="minMarkerPerimeterRate" value="0.075"/> <!-- 0.075 for 320x240, 0.0375 for 640x480 -->
|
||||
@@ -26,7 +30,7 @@
|
||||
</node>
|
||||
|
||||
<!-- aruco_map: estimate aruco map pose -->
|
||||
<node name="aruco_map" pkg="nodelet" type="nodelet" if="$(arg aruco_map)" args="load aruco_pose/aruco_map main_camera_nodelet_manager" output="screen" clear_params="true" respawn="true">
|
||||
<node name="aruco_map" pkg="nodelet" type="nodelet" if="$(eval aruco_map and not disable)" args="load aruco_pose/aruco_map main_camera_nodelet_manager" output="screen" clear_params="true" respawn="true">
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="markers" to="aruco_detect/markers"/>
|
||||
@@ -41,11 +45,11 @@
|
||||
</node>
|
||||
|
||||
<!-- vpe publisher from aruco markers -->
|
||||
<node name="vpe_publisher" pkg="clover" type="vpe_publisher" if="$(arg aruco_vpe)" output="screen" clear_params="true">
|
||||
<remap from="~pose_cov" to="aruco_map/pose"/>
|
||||
<node name="vpe_publisher" pkg="clover" type="vpe_publisher" if="$(eval aruco_vpe or force_init)" output="screen" clear_params="true">
|
||||
<remap from="~pose_cov" to="aruco_map/pose" if="$(arg aruco_vpe)"/>
|
||||
<remap from="~vpe" to="mavros/vision_pose/pose"/>
|
||||
<param name="frame_id" value="aruco_map_detected"/>
|
||||
<param name="publish_zero" value="true"/>
|
||||
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/>
|
||||
<param name="force_init" value="$(arg force_init)"/>
|
||||
<param name="offset_frame_id" value="aruco_map"/>
|
||||
</node>
|
||||
</launch>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<arg name="led" default="true"/>
|
||||
<arg name="blocks" default="false"/>
|
||||
<arg name="rc" default="false"/>
|
||||
<arg name="force_init" default="true"/> <!-- force estimator to init by publishing zero pose -->
|
||||
|
||||
<arg name="simulator" default="false"/> <!-- flag that we are operating on a simulated drone -->
|
||||
|
||||
@@ -33,7 +34,10 @@
|
||||
</node>
|
||||
|
||||
<!-- aruco markers -->
|
||||
<include file="$(find clover)/launch/aruco.launch" if="$(arg aruco)"/>
|
||||
<include file="$(find clover)/launch/aruco.launch" if="$(eval aruco or force_init)">
|
||||
<arg name="force_init" value="$(arg force_init)"/>
|
||||
<arg name="disable" value="$(eval not aruco)"/>
|
||||
</include>
|
||||
|
||||
<!-- optical flow -->
|
||||
<node pkg="nodelet" type="nodelet" name="optical_flow" args="load clover/optical_flow main_camera_nodelet_manager" if="$(arg optical_flow)" clear_params="true" output="screen" respawn="true">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<launch>
|
||||
<arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl -->
|
||||
<arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl, hitl -->
|
||||
<arg name="fcu_ip" default="127.0.0.1"/>
|
||||
<arg name="fcu_sys_id" default="1"/>
|
||||
<arg name="gcs_bridge" default="tcp"/>
|
||||
@@ -23,6 +23,9 @@
|
||||
<!-- sitl since PX4 1.9.0 -->
|
||||
<param name="fcu_url" value="udp://@$(arg fcu_ip):14580" if="$(eval fcu_conn == 'sitl')"/>
|
||||
|
||||
<!-- hitl connection (to gazebo_mavlink_interface plugin) -->
|
||||
<param name="fcu_url" value="udp://$(arg fcu_ip):14540@" if="$(eval fcu_conn == 'hitl')"/>
|
||||
|
||||
<!-- set target_system_id -->
|
||||
<param name="target_system_id" value="$(arg fcu_sys_id)" />
|
||||
|
||||
@@ -39,7 +42,7 @@
|
||||
<rosparam command="load" file="$(find clover)/launch/mavros_config.yaml"/>
|
||||
|
||||
<!-- remap rangefinder -->
|
||||
<remap from="mavros/distance_sensor/rangefinder_sub" to="rangefinder/range"/>
|
||||
<remap from="mavros/distance_sensor/rangefinder_sub" to="$(arg distance_sensor_remap)" if="$(eval bool(distance_sensor_remap))"/>
|
||||
|
||||
<rosparam param="plugin_whitelist">
|
||||
- altitude
|
||||
|
||||
@@ -78,6 +78,9 @@ distance_sensor:
|
||||
field_of_view: 0.5
|
||||
rangefinder_sub:
|
||||
subscriber: true
|
||||
id: 1
|
||||
orientation: PITCH_270
|
||||
covariance: 1 # cm
|
||||
|
||||
# fake_gps
|
||||
fake_gps:
|
||||
|
||||
4
clover/launch/simulator.launch
Normal file
@@ -0,0 +1,4 @@
|
||||
<launch>
|
||||
<!-- shurtcut for running the simulation (`roslaunch clover simulator.launch`) -->
|
||||
<include file="$(find clover_simulation)/launch/simulator.launch"/>
|
||||
</launch>
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="3">
|
||||
<name>clover</name>
|
||||
<version>0.21.1</version>
|
||||
<version>0.23.0</version>
|
||||
<description>The Clover package</description>
|
||||
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
@@ -37,8 +37,11 @@
|
||||
<depend>rosbridge_server</depend>
|
||||
<depend>web_video_server</depend>
|
||||
<depend>tf2_web_republisher</depend>
|
||||
<depend>libxml2</depend>
|
||||
<depend>libxslt</depend>
|
||||
<depend condition="$ROS_PYTHON_VERSION == 2">python-lxml</depend>
|
||||
<depend condition="$ROS_PYTHON_VERSION == 3">python3-lxml</depend>
|
||||
<depend>dynamic_reconfigure</depend>
|
||||
<exec_depend>python-pymavlink</exec_depend>
|
||||
<!-- Use test_depend for packages you need only for testing: -->
|
||||
<!-- <test_depend>gtest</test_depend> -->
|
||||
|
||||
87
clover/src/autotest/autotest_aruco.py
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import rospy
|
||||
import math
|
||||
import signal
|
||||
import sys
|
||||
import dynamic_reconfigure.client
|
||||
from clover import srv
|
||||
from std_srvs.srv import Trigger
|
||||
from sensor_msgs.msg import Range
|
||||
from aruco_pose.msg import MarkerArray
|
||||
from util import handle_response
|
||||
|
||||
rospy.init_node('autotest_aruco', disable_signals=True) # disable signals to allow interrupting with ctrl+c
|
||||
|
||||
flow_client = dynamic_reconfigure.client.Client('optical_flow')
|
||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
|
||||
land = handle_response(rospy.ServiceProxy('land', Trigger))
|
||||
|
||||
def interrupt(sig, frame):
|
||||
print('\nInterrupted, landing...')
|
||||
land()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, interrupt)
|
||||
|
||||
def print_current_map_position():
|
||||
telem = get_telemetry()
|
||||
dist = rospy.wait_for_message('rangefinder/range', Range).range
|
||||
print('Map position:\tx={:.1f}\ty={:.1f}\tz={:.1f}\tyaw={:.1f}\tdist={:.2f}'.format(telem.x, telem.y, telem.z, telem.yaw, dist))
|
||||
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \
|
||||
frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
|
||||
frame_id=frame_id, auto_arm=auto_arm)
|
||||
|
||||
if not res.success:
|
||||
return res
|
||||
|
||||
while not rospy.is_shutdown():
|
||||
telem = get_telemetry(frame_id='navigate_target')
|
||||
if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < tolerance:
|
||||
return res
|
||||
rospy.sleep(0.2)
|
||||
|
||||
markers = rospy.wait_for_message('aruco_map/map', MarkerArray, timeout=3)
|
||||
left = min(marker.pose.position.x for marker in markers.markers)
|
||||
bottom = min(marker.pose.position.y for marker in markers.markers)
|
||||
width = max(marker.pose.position.x for marker in markers.markers)
|
||||
height = max(marker.pose.position.y for marker in markers.markers)
|
||||
center_x = left + width / 2
|
||||
center_y = bottom + height / 2
|
||||
|
||||
print('Map rect: %g %g - %g %g' % (left, bottom, width, height))
|
||||
|
||||
input('Take off and hover 1 m [enter] ')
|
||||
navigate_wait(x=0, y=0, z=1, frame_id='body', auto_arm=True)
|
||||
print_current_map_position()
|
||||
|
||||
input('Go to corner %g %g 1.5 speed 1 [enter] ' % (width, height))
|
||||
navigate_wait(x=width, y=height, z=1.5, speed=1, frame_id='aruco_map')
|
||||
print_current_map_position()
|
||||
|
||||
input('Go to center %g %g 1.5 speed 5 [enter] ' % (center_x, center_y))
|
||||
navigate_wait(x=center_x, y=center_y, z=1.5, speed=5, frame_id='aruco_map')
|
||||
print_current_map_position()
|
||||
|
||||
input('Disable optical flow and keep hovering [enter] ')
|
||||
flow_client.update_configuration({'enabled': False})
|
||||
rospy.sleep(5)
|
||||
|
||||
input('Enable optical flow back [enter] ')
|
||||
flow_client.update_configuration({'enabled': True})
|
||||
|
||||
input('Go to side 1 %g 2 heading top [enter] ' % (center_y))
|
||||
navigate_wait(x=1, y=center_y, z=2, yaw=1.57, frame_id='aruco_map')
|
||||
print_current_map_position()
|
||||
|
||||
marker_id = markers.markers[0].id
|
||||
input('Go to marker %d z=1.5 [enter] ' % marker_id)
|
||||
navigate_wait(x=0, y=0, z=1.5, yaw=0, frame_id='aruco_%d' % marker_id)
|
||||
print_current_map_position()
|
||||
|
||||
input('Perform landing [enter] ')
|
||||
land()
|
||||
100
clover/src/autotest/autotest_flight.py
Executable file
@@ -0,0 +1,100 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import rospy
|
||||
import math
|
||||
from math import nan
|
||||
import signal
|
||||
import sys
|
||||
from clover import srv
|
||||
from std_srvs.srv import Trigger
|
||||
from sensor_msgs.msg import Range
|
||||
from util import handle_response
|
||||
|
||||
rospy.init_node('autotest_flight', disable_signals=True) # disable signals to allow interrupting with ctrl+c
|
||||
|
||||
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
|
||||
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
|
||||
navigate_global = handle_response(rospy.ServiceProxy('navigate_global', srv.NavigateGlobal))
|
||||
set_position = handle_response(rospy.ServiceProxy('set_position', srv.SetPosition))
|
||||
set_velocity = handle_response(rospy.ServiceProxy('set_velocity', srv.SetVelocity))
|
||||
set_attitude = handle_response(rospy.ServiceProxy('set_attitude', srv.SetAttitude))
|
||||
set_rates = handle_response(rospy.ServiceProxy('set_rates', srv.SetRates))
|
||||
land = handle_response(rospy.ServiceProxy('land', Trigger))
|
||||
|
||||
def interrupt(sig, frame):
|
||||
print('\nInterrupted, landing...')
|
||||
land()
|
||||
sys.exit(0)
|
||||
|
||||
signal.signal(signal.SIGINT, interrupt)
|
||||
|
||||
def navigate_wait(x=0, y=0, z=0, yaw=nan, yaw_rate=0, speed=0.5, \
|
||||
frame_id='body', tolerance=0.2, auto_arm=False):
|
||||
|
||||
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
|
||||
frame_id=frame_id, auto_arm=auto_arm)
|
||||
|
||||
if not res.success:
|
||||
return res
|
||||
|
||||
while not rospy.is_shutdown():
|
||||
telem = get_telemetry(frame_id='navigate_target')
|
||||
if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < tolerance:
|
||||
return res
|
||||
rospy.sleep(0.2)
|
||||
|
||||
def print_distance():
|
||||
dist = rospy.wait_for_message('rangefinder/range', Range).range
|
||||
print('Distance: {:.2f}'.format(dist))
|
||||
|
||||
input('Take off and hover 1 m [enter] ')
|
||||
navigate_wait(z=1, frame_id='body', auto_arm=True)
|
||||
print_distance()
|
||||
start = get_telemetry()
|
||||
|
||||
input('Fly forward 2 m [enter] ')
|
||||
navigate_wait(x=2, frame_id='navigate_target')
|
||||
print_distance()
|
||||
|
||||
input('Climb 0.5 m [enter] ')
|
||||
navigate_wait(z=0.5, frame_id='navigate_target')
|
||||
print_distance()
|
||||
|
||||
input('Rotate left 90° [enter] ')
|
||||
navigate(yaw=math.pi / 2, frame_id='navigate_target')
|
||||
rospy.sleep(3)
|
||||
|
||||
input('Use set_velocity to fly forward 2 m speed 1 [enter]')
|
||||
set_velocity(vx=1, vy=0.0, vz=0, frame_id='body')
|
||||
rospy.sleep(2)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Rotate right 90° [enter] ')
|
||||
navigate(yaw=-math.pi / 2, frame_id='navigate_target')
|
||||
rospy.sleep(3)
|
||||
|
||||
input('Use set_attitude to fly backwards [enter]')
|
||||
set_attitude(pitch=-0.3, roll=0, yaw=0, thrust=0.5, frame_id='body')
|
||||
rospy.sleep(0.3)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Use set_attitude to fly right [enter]')
|
||||
set_attitude(pitch=0, roll=0.3, yaw=0, thrust=0.5, frame_id='body')
|
||||
rospy.sleep(0.5)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Use set_rates to fly right [enter]')
|
||||
set_rates(roll_rate=1.2, thrust=0.5)
|
||||
rospy.sleep(0.4)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Rotate 360° to the right using yaw_rate [enter]')
|
||||
set_position(x=nan, y=nan, z=nan, frame_id='body', yaw=nan, yaw_rate=-1)
|
||||
rospy.sleep(2 * math.pi)
|
||||
set_position(frame_id='body')
|
||||
|
||||
input('Return to start point [enter]')
|
||||
navigate_wait(x=start.x, y=start.y, z=start.z, yaw=start.yaw, speed=1, frame_id='map')
|
||||
|
||||
input('Land [enter]')
|
||||
land()
|
||||
72
clover/src/autotest/autotest_led.py
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import rospy
|
||||
import functools
|
||||
from clover.srv import SetLEDEffect
|
||||
from led_msgs.srv import SetLEDs
|
||||
from led_msgs.msg import LEDStateArray, LEDState
|
||||
from util import handle_response
|
||||
|
||||
rospy.init_node('autotest_led', disable_signals=True)
|
||||
|
||||
set_leds = handle_response(rospy.ServiceProxy('led/set_leds', SetLEDs))
|
||||
set_effect = handle_response(rospy.ServiceProxy('led/set_effect', SetLEDEffect))
|
||||
|
||||
led_count = len(rospy.wait_for_message('led/state', LEDStateArray, timeout=10).leds)
|
||||
print('LED count =', led_count)
|
||||
|
||||
print('== Testing effects ==')
|
||||
|
||||
input('Fill red [enter] ')
|
||||
set_effect(r=255, g=0, b=0)
|
||||
|
||||
input('Fill green [enter] ')
|
||||
set_effect(r=0, g=100, b=0)
|
||||
|
||||
input('Blink white [enter] ')
|
||||
set_effect(effect='blink', r=255, g=255, b=255)
|
||||
rospy.sleep(3)
|
||||
|
||||
input('Blink fast violet [enter] ')
|
||||
set_effect(effect='blink_fast', r=220, g=20, b=250)
|
||||
rospy.sleep(3)
|
||||
|
||||
input('Fade to blue [enter] ')
|
||||
set_effect(effect='fade', r=0, g=0, b=255)
|
||||
|
||||
input('Wipe to yellow [enter] ')
|
||||
set_effect(effect='wipe', r=255, g=255, b=40)
|
||||
|
||||
input('Flash red [enter] ')
|
||||
set_effect(effect='flash', r=255, g=0, b=0)
|
||||
rospy.sleep(1)
|
||||
|
||||
input('Rainbow [enter] ')
|
||||
set_effect(effect='rainbow')
|
||||
rospy.sleep(4)
|
||||
|
||||
input('Rainbow fill [enter] ')
|
||||
set_effect(effect='rainbow_fill')
|
||||
rospy.sleep(4)
|
||||
|
||||
input('Turn off [enter] ')
|
||||
set_effect()
|
||||
|
||||
print('== Testing low-level control ==')
|
||||
|
||||
input('Fill orange [enter] ')
|
||||
set_leds(leds=[LEDState(index=i, r=245, g=155, b=0) for i in range(led_count)])
|
||||
|
||||
input('Fill blue gradient [enter] ')
|
||||
set_leds(leds=[LEDState(index=i, r=0, g=20, b=int(255 * i / led_count)) for i in range(led_count)])
|
||||
|
||||
input('Animate green dot [enter] ')
|
||||
set_effect()
|
||||
for i in range(led_count):
|
||||
if i > 0:
|
||||
set_leds(leds=[LEDState(index=i - 1, r=0, g=0, b=0)])
|
||||
set_leds(leds=[LEDState(index=i, r=0, g=255, b=0)])
|
||||
rospy.sleep(0.05)
|
||||
|
||||
input('Turn off [enter] ')
|
||||
set_effect()
|
||||
11
clover/src/autotest/util.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import functools
|
||||
|
||||
# decorator to handle response and print error message
|
||||
def handle_response(fn):
|
||||
@functools.wraps(fn)
|
||||
def wrapper(*args, **kwargs):
|
||||
res = fn(*args, **kwargs)
|
||||
if not res.success:
|
||||
print('\033[91mError:\033[0m {}'.format(res.message))
|
||||
return res
|
||||
return wrapper
|
||||
@@ -31,7 +31,6 @@ ros::Time start_time;
|
||||
double blink_rate, blink_fast_rate, flash_delay, fade_period, wipe_period, rainbow_period;
|
||||
double low_battery_threshold;
|
||||
std::vector<std::string> error_ignore;
|
||||
bool blink_state;
|
||||
led_msgs::SetLEDs set_leds;
|
||||
led_msgs::LEDStateArray state, start_state;
|
||||
ros::ServiceClient set_leds_srv;
|
||||
@@ -87,9 +86,8 @@ void proceed(const ros::TimerEvent& event)
|
||||
set_leds.request.leds.resize(led_count);
|
||||
|
||||
if (current_effect.effect == "blink" || current_effect.effect == "blink_fast") {
|
||||
blink_state = !blink_state;
|
||||
// toggle all leds
|
||||
if (blink_state) {
|
||||
// enable on odd counter
|
||||
if (counter % 2 != 0) {
|
||||
fill(current_effect.r, current_effect.g, current_effect.b);
|
||||
} else {
|
||||
fill(0, 0, 0);
|
||||
@@ -222,6 +220,7 @@ bool setEffect(clover::SetLEDEffect::Request& req, clover::SetLEDEffect::Respons
|
||||
counter = 0;
|
||||
start_state = state;
|
||||
start_time = ros::Time::now();
|
||||
proceed({ .current_real = start_time });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -22,11 +22,13 @@
|
||||
#include <tf2/utils.h>
|
||||
#include <tf2_ros/transform_listener.h>
|
||||
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
|
||||
#include <dynamic_reconfigure/server.h>
|
||||
#include <mavros_msgs/OpticalFlowRad.h>
|
||||
#include <sensor_msgs/Imu.h>
|
||||
#include <geometry_msgs/Vector3Stamped.h>
|
||||
#include <geometry_msgs/PointStamped.h>
|
||||
#include <geometry_msgs/TwistStamped.h>
|
||||
#include <clover/FlowConfig.h>
|
||||
|
||||
using cv::Mat;
|
||||
|
||||
@@ -38,6 +40,7 @@ public:
|
||||
{}
|
||||
|
||||
private:
|
||||
bool enabled_;
|
||||
ros::Publisher flow_pub_, velo_pub_, shift_pub_;
|
||||
ros::Time prev_stamp_;
|
||||
std::string fcu_frame_id_, local_frame_id_;
|
||||
@@ -54,6 +57,7 @@ private:
|
||||
std::unique_ptr<tf2_ros::TransformListener> tf_listener_;
|
||||
bool calc_flow_gyro_;
|
||||
float flow_gyro_default_;
|
||||
std::shared_ptr<dynamic_reconfigure::Server<clover::FlowConfig>> dyn_srv_;
|
||||
|
||||
void onInit()
|
||||
{
|
||||
@@ -83,6 +87,12 @@ private:
|
||||
|
||||
img_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this);
|
||||
|
||||
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<clover::FlowConfig>>(nh_priv);
|
||||
dynamic_reconfigure::Server<clover::FlowConfig>::CallbackType cb;
|
||||
|
||||
cb = std::bind(&OpticalFlow::paramCallback, this, std::placeholders::_1, std::placeholders::_2);
|
||||
dyn_srv_->setCallback(cb);
|
||||
|
||||
NODELET_INFO("Optical Flow initialized");
|
||||
}
|
||||
|
||||
@@ -109,6 +119,8 @@ private:
|
||||
|
||||
void flow(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr& cinfo)
|
||||
{
|
||||
if (!enabled_) return;
|
||||
|
||||
parseCameraInfo(cinfo);
|
||||
|
||||
auto img = cv_bridge::toCvShare(msg, "mono8")->image;
|
||||
@@ -142,7 +154,7 @@ private:
|
||||
|
||||
img.convertTo(curr_, CV_32F);
|
||||
|
||||
if (prev_.empty()) {
|
||||
if (prev_.empty() || (msg->header.stamp - prev_stamp_).toSec() > 0.1) { // outdated previous frame
|
||||
prev_ = curr_.clone();
|
||||
prev_stamp_ = msg->header.stamp;
|
||||
cv::createHanningWindow(hann_, curr_.size(), CV_32F);
|
||||
@@ -264,6 +276,14 @@ publish_debug:
|
||||
|
||||
return flow;
|
||||
}
|
||||
|
||||
void paramCallback(clover::FlowConfig &config, uint32_t level)
|
||||
{
|
||||
enabled_ = config.enabled;
|
||||
if (!enabled_) {
|
||||
prev_ = Mat(); // clear previous frame
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PLUGINLIB_EXPORT_CLASS(OpticalFlow, nodelet::Nodelet)
|
||||
|
||||
@@ -91,7 +91,7 @@ private:
|
||||
void fakeGCSThread()
|
||||
{
|
||||
// Awful workaround for fixing PX4 not sending STATUSTEXTs
|
||||
// if there is no GCS hearbeats.
|
||||
// if there is no GCS heartbeats.
|
||||
// TODO: use timer
|
||||
// TODO: remove, when PX4 get this fixed.
|
||||
ros::Publisher mavlink_pub = nh.advertise<mavros_msgs::Mavlink>("mavlink/to", 1);
|
||||
|
||||
@@ -30,6 +30,7 @@ from visualization_msgs.msg import MarkerArray as VisualizationMarkerArray
|
||||
import tf.transformations as t
|
||||
from aruco_pose.msg import MarkerArray
|
||||
from mavros import mavlink
|
||||
import locale
|
||||
|
||||
|
||||
# TODO: check attitude is present
|
||||
@@ -45,6 +46,8 @@ rospy.init_node('selfcheck')
|
||||
|
||||
os.environ['ROSCONSOLE_FORMAT']='[${severity}]: ${message}'
|
||||
|
||||
# use user's locale to convert numbers, etc
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
tf_buffer = tf2_ros.Buffer()
|
||||
tf_listener = tf2_ros.TransformListener(tf_buffer)
|
||||
@@ -195,24 +198,27 @@ def check_fcu():
|
||||
failure('no connection to the FCU (check wiring)')
|
||||
return
|
||||
|
||||
clover_tag = re.compile(r'-cl[oe]ver\.\d+$')
|
||||
clover_fw = False
|
||||
|
||||
# Make sure the console is available to us
|
||||
mavlink_exec('\n')
|
||||
version_str = mavlink_exec('ver all')
|
||||
if version_str == '':
|
||||
info('no version data available from SITL')
|
||||
|
||||
r = re.compile(r'^FW (git tag|version): (v?\d\.\d\.\d.*)$')
|
||||
is_clover_firmware = False
|
||||
for ver_line in version_str.split('\n'):
|
||||
match = r.search(ver_line)
|
||||
if match is not None:
|
||||
field, version = match.groups()
|
||||
info('firmware %s: %s' % (field, version))
|
||||
if 'clover' in version or 'clever' in version:
|
||||
is_clover_firmware = True
|
||||
for line in version_str.split('\n'):
|
||||
if line.startswith('FW version: '):
|
||||
info(line[len('FW version: '):])
|
||||
elif line.startswith('FW git tag: '): # only Clover's firmware
|
||||
tag = line[len('FW git tag: '):]
|
||||
clover_fw = clover_tag.search(tag)
|
||||
info(tag)
|
||||
elif line.startswith('HW arch: '):
|
||||
info(line[len('HW arch: '):])
|
||||
|
||||
if not is_clover_firmware:
|
||||
failure('not running Clover PX4 firmware, https://clover.coex.tech/firmware')
|
||||
if not clover_fw:
|
||||
info('not Clover PX4 firmware, check https://clover.coex.tech/firmware')
|
||||
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
if est == 1:
|
||||
@@ -330,7 +336,7 @@ def is_process_running(binary, exact=False, full=False):
|
||||
if exact:
|
||||
args.append('-x') # match exactly with the command name
|
||||
if full:
|
||||
args.append('-f') # use full process name to match
|
||||
args.append('-f') # use full command line (including arguments) to match
|
||||
args.append(binary)
|
||||
subprocess.check_output(args)
|
||||
return True
|
||||
@@ -528,6 +534,8 @@ def check_global_position():
|
||||
rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=1)
|
||||
except rospy.ROSException:
|
||||
info('no global position')
|
||||
if get_param('SYS_MC_EST_GROUP') == 2 and (get_param('EKF2_AID_MASK') & (1 << 0)):
|
||||
failure('enabled GPS fusion may suppress vision position aiding')
|
||||
|
||||
|
||||
@check('Optical flow')
|
||||
@@ -617,6 +625,10 @@ def check_rangefinder():
|
||||
|
||||
@check('Boot duration')
|
||||
def check_boot_duration():
|
||||
if not os.path.exists('/etc/clover_version'):
|
||||
info('skip check')
|
||||
return # Don't check not on Clover's image
|
||||
|
||||
output = subprocess.check_output('systemd-analyze').decode()
|
||||
r = re.compile(r'([\d\.]+)s\s*$', flags=re.MULTILINE)
|
||||
duration = float(r.search(output).groups()[0])
|
||||
@@ -635,13 +647,16 @@ def check_cpu_usage():
|
||||
continue
|
||||
pid, cpu, cmd = process.split('\t')
|
||||
|
||||
if cmd.strip() not in WHITELIST and float(cpu) > 30:
|
||||
if cmd.strip() not in WHITELIST and locale.atof(cpu) > 30:
|
||||
failure('high CPU usage (%s%%) detected: %s (PID %s)',
|
||||
cpu.strip(), cmd.strip(), pid.strip())
|
||||
|
||||
|
||||
@check('clover.service')
|
||||
def check_clover_service():
|
||||
if not os.path.exists('/etc/clover_version'):
|
||||
return # Don't check not on Clover's image
|
||||
|
||||
try:
|
||||
output = subprocess.check_output('systemctl show -p ActiveState --value clover.service'.split(),
|
||||
stderr=subprocess.STDOUT).decode()
|
||||
@@ -697,6 +712,10 @@ def check_image():
|
||||
|
||||
@check('Preflight status')
|
||||
def check_preflight_status():
|
||||
if is_process_running('px4', exact=True):
|
||||
info('can\'t check in SITL')
|
||||
return
|
||||
|
||||
# Make sure the console is available to us
|
||||
mavlink_exec('\n')
|
||||
cmdr_output = mavlink_exec('commander check')
|
||||
@@ -718,6 +737,10 @@ def check_preflight_status():
|
||||
|
||||
@check('Network')
|
||||
def check_network():
|
||||
if not os.path.exists('/etc/clover_version'):
|
||||
# TODO:
|
||||
return # Don't check not on Clover's image
|
||||
|
||||
ros_hostname = os.environ.get('ROS_HOSTNAME', '').strip()
|
||||
|
||||
if not ros_hostname:
|
||||
@@ -740,6 +763,14 @@ def check_network():
|
||||
|
||||
@check('RPi health')
|
||||
def check_rpi_health():
|
||||
try:
|
||||
import shutil
|
||||
total, used, free = shutil.disk_usage('/')
|
||||
if free < 1024 * 1024 * 1024:
|
||||
failure('disk space is less than 1 GB; consider removing logs (~/.ros/log/)')
|
||||
except Exception as e:
|
||||
info('could not check the disk free space: %s', str(e))
|
||||
|
||||
# `vcgencmd get_throttled` output codes taken from
|
||||
# https://github.com/raspberrypi/documentation/blob/JamesH65-patch-vcgencmd-vcdbg-docs/raspbian/applications/vcgencmd.md#get_throttled
|
||||
# TODO: support more base platforms?
|
||||
|
||||
@@ -61,6 +61,7 @@ std::shared_ptr<tf2_ros::TransformBroadcaster> transform_broadcaster;
|
||||
std::shared_ptr<tf2_ros::StaticTransformBroadcaster> static_transform_broadcaster;
|
||||
|
||||
// Parameters
|
||||
string mavros;
|
||||
string local_frame;
|
||||
string fcu_frame;
|
||||
ros::Duration transform_timeout;
|
||||
@@ -149,6 +150,9 @@ void handleState(const mavros_msgs::State& s)
|
||||
inline void publishBodyFrame()
|
||||
{
|
||||
if (body.child_frame_id.empty()) return;
|
||||
if (!body.header.stamp.isZero() && body.header.stamp == local_position.header.stamp) {
|
||||
return; // avoid TF_REPEATED_DATA warnings
|
||||
}
|
||||
|
||||
tf::Quaternion q;
|
||||
q.setRPY(0, 0, tf::getYaw(local_position.pose.orientation));
|
||||
@@ -690,7 +694,7 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
||||
// }
|
||||
|
||||
if (sp_type == POSITION || sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == VELOCITY || sp_type == ATTITUDE) {
|
||||
// destination point and/or yaw
|
||||
// destination point and/or attitude
|
||||
PoseStamped ps;
|
||||
ps.header.frame_id = frame_id;
|
||||
ps.header.stamp = stamp;
|
||||
@@ -699,7 +703,12 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
|
||||
ps.pose.position.z = z;
|
||||
ps.pose.orientation.w = 1.0; // Ensure quaternion is always valid
|
||||
|
||||
if (std::isnan(yaw)) {
|
||||
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) {
|
||||
@@ -861,8 +870,9 @@ int main(int argc, char **argv)
|
||||
static_transform_broadcaster = std::make_shared<tf2_ros::StaticTransformBroadcaster>();
|
||||
|
||||
// Params
|
||||
nh.param<string>("mavros/local_position/tf/frame_id", local_frame, "map");
|
||||
nh.param<string>("mavros/local_position/tf/child_frame_id", fcu_frame, "base_link");
|
||||
nh_priv.param("mavros", mavros, string("mavros")); // for case of using multiple connections
|
||||
nh.param<string>(mavros + "/local_position/tf/frame_id", local_frame, "map");
|
||||
nh.param<string>(mavros + "/local_position/tf/child_frame_id", fcu_frame, "base_link");
|
||||
nh_priv.param("target_frame", target.child_frame_id, string("navigate_target"));
|
||||
nh_priv.param("setpoint", setpoint.child_frame_id, string("setpoint"));
|
||||
nh_priv.param("auto_release", auto_release, true);
|
||||
@@ -894,25 +904,25 @@ int main(int argc, char **argv)
|
||||
arming_timeout = ros::Duration(nh_priv.param("arming_timeout", 4.0));
|
||||
|
||||
// Service clients
|
||||
arming = nh.serviceClient<mavros_msgs::CommandBool>("mavros/cmd/arming");
|
||||
set_mode = nh.serviceClient<mavros_msgs::SetMode>("mavros/set_mode");
|
||||
arming = nh.serviceClient<mavros_msgs::CommandBool>(mavros + "/cmd/arming");
|
||||
set_mode = nh.serviceClient<mavros_msgs::SetMode>(mavros + "/set_mode");
|
||||
|
||||
// Telemetry subscribers
|
||||
auto state_sub = nh.subscribe("mavros/state", 1, &handleState);
|
||||
auto velocity_sub = nh.subscribe("mavros/local_position/velocity_body", 1, &handleMessage<TwistStamped, velocity>);
|
||||
auto global_position_sub = nh.subscribe("mavros/global_position/global", 1, &handleMessage<NavSatFix, global_position>);
|
||||
auto battery_sub = nh.subscribe("mavros/battery", 1, &handleMessage<BatteryState, battery>);
|
||||
auto statustext_sub = nh.subscribe("mavros/statustext/recv", 1, &handleMessage<mavros_msgs::StatusText, statustext>);
|
||||
auto manual_control_sub = nh.subscribe("mavros/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>);
|
||||
auto local_position_sub = nh.subscribe("mavros/local_position/pose", 1, &handleLocalPosition);
|
||||
auto state_sub = nh.subscribe(mavros + "/state", 1, &handleState);
|
||||
auto velocity_sub = nh.subscribe(mavros + "/local_position/velocity_body", 1, &handleMessage<TwistStamped, velocity>);
|
||||
auto global_position_sub = nh.subscribe(mavros + "/global_position/global", 1, &handleMessage<NavSatFix, global_position>);
|
||||
auto battery_sub = nh.subscribe(mavros + "/battery", 1, &handleMessage<BatteryState, battery>);
|
||||
auto statustext_sub = nh.subscribe(mavros + "/statustext/recv", 1, &handleMessage<mavros_msgs::StatusText, statustext>);
|
||||
auto manual_control_sub = nh.subscribe(mavros + "/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>);
|
||||
auto local_position_sub = nh.subscribe(mavros + "/local_position/pose", 1, &handleLocalPosition);
|
||||
|
||||
// Setpoint publishers
|
||||
position_pub = nh.advertise<PoseStamped>("mavros/setpoint_position/local", 1);
|
||||
position_raw_pub = nh.advertise<PositionTarget>("mavros/setpoint_raw/local", 1);
|
||||
attitude_pub = nh.advertise<PoseStamped>("mavros/setpoint_attitude/attitude", 1);
|
||||
attitude_raw_pub = nh.advertise<AttitudeTarget>("mavros/setpoint_raw/attitude", 1);
|
||||
rates_pub = nh.advertise<TwistStamped>("mavros/setpoint_attitude/cmd_vel", 1);
|
||||
thrust_pub = nh.advertise<Thrust>("mavros/setpoint_attitude/thrust", 1);
|
||||
position_pub = nh.advertise<PoseStamped>(mavros + "/setpoint_position/local", 1);
|
||||
position_raw_pub = nh.advertise<PositionTarget>(mavros + "/setpoint_raw/local", 1);
|
||||
attitude_pub = nh.advertise<PoseStamped>(mavros + "/setpoint_attitude/attitude", 1);
|
||||
attitude_raw_pub = nh.advertise<AttitudeTarget>(mavros + "/setpoint_raw/attitude", 1);
|
||||
rates_pub = nh.advertise<TwistStamped>(mavros + "/setpoint_attitude/cmd_vel", 1);
|
||||
thrust_pub = nh.advertise<Thrust>(mavros + "/setpoint_attitude/thrust", 1);
|
||||
|
||||
// Service servers
|
||||
auto gt_serv = nh.advertiseService("get_telemetry", &getTelemetry);
|
||||
|
||||
@@ -33,14 +33,14 @@ ros::Subscriber local_position_sub;
|
||||
ros::Timer zero_timer;
|
||||
PoseStamped vpe, pose;
|
||||
ros::Time got_local_pos(0);
|
||||
ros::Duration publish_zero_timout, publish_zero_duration, offset_timeout;
|
||||
ros::Duration publish_zero_timeout, publish_zero_duration, offset_timeout;
|
||||
TransformStamped offset;
|
||||
|
||||
void publishZero(const ros::TimerEvent& e)
|
||||
{
|
||||
if (e.current_real - vpe.header.stamp < publish_zero_timout) return; // have vpe
|
||||
if (!vpe.header.stamp.isZero() && e.current_real - vpe.header.stamp < publish_zero_timeout) return; // have vpe
|
||||
|
||||
if (e.current_real - pose.header.stamp < publish_zero_timout) { // have local position
|
||||
if (!pose.header.stamp.isZero() && e.current_real - pose.header.stamp < publish_zero_timeout) { // have local position
|
||||
if (got_local_pos.isZero()) {
|
||||
ROS_INFO("got local position");
|
||||
got_local_pos = e.current_real;
|
||||
@@ -124,8 +124,8 @@ int main(int argc, char **argv) {
|
||||
|
||||
nh_priv.param<string>("frame_id", frame_id, "");
|
||||
nh_priv.param<string>("offset_frame_id", offset_frame_id, "");
|
||||
nh_priv.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
|
||||
nh_priv.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link");
|
||||
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));
|
||||
|
||||
if (!frame_id.empty()) {
|
||||
@@ -141,11 +141,11 @@ int main(int argc, char **argv) {
|
||||
vpe_pub = nh_priv.advertise<PoseStamped>("vpe", 1);
|
||||
//vpe_cov_pub = nh_priv_.advertise<PoseStamped>("pose_cov_pub", 1);
|
||||
|
||||
if (nh_priv.param("publish_zero", false)) {
|
||||
if (nh_priv.param("force_init", false) || nh_priv.param("publish_zero", false)) { // publish_zero is old name
|
||||
// publish zero to initialize the local position
|
||||
zero_timer = nh.createTimer(ros::Duration(0.1), &publishZero);
|
||||
publish_zero_timout = ros::Duration(nh_priv.param("publish_zero_timout", 5.0));
|
||||
publish_zero_duration = ros::Duration(nh_priv.param("publish_zero_duration", 5.0));
|
||||
publish_zero_timeout = ros::Duration(nh_priv.param("force_init_timeout", 5.0));
|
||||
publish_zero_duration = ros::Duration(nh_priv.param("force_init_duration", 5.0));
|
||||
local_position_sub = nh.subscribe("mavros/local_position/pose", 1, &localPositionCallback);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,3 +33,29 @@ def test_web_video_server(node):
|
||||
# Python 3
|
||||
import urllib.request as urllib
|
||||
urllib.urlopen("http://localhost:8080").read()
|
||||
|
||||
def test_blocks(node):
|
||||
rospy.wait_for_service('clover_blocks/run', timeout=5)
|
||||
rospy.wait_for_service('clover_blocks/stop', timeout=5)
|
||||
rospy.wait_for_service('clover_blocks/load', timeout=5)
|
||||
rospy.wait_for_service('clover_blocks/store', timeout=5)
|
||||
|
||||
from std_msgs.msg import String
|
||||
from clover_blocks.srv import Run
|
||||
|
||||
def wait_print():
|
||||
try:
|
||||
wait_print.result = rospy.wait_for_message('clover_blocks/print', String, timeout=5).data
|
||||
except Exception as e:
|
||||
wait_print.result = str(e)
|
||||
|
||||
import threading
|
||||
t = threading.Thread(target=wait_print)
|
||||
t.start()
|
||||
rospy.sleep(0.1)
|
||||
|
||||
run = rospy.ServiceProxy('clover_blocks/run', Run)
|
||||
assert run(code='print("test")').success == True
|
||||
|
||||
t.join()
|
||||
assert wait_print.result == 'test'
|
||||
|
||||
@@ -23,10 +23,7 @@
|
||||
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="map_flipped_frame" args="0 0 0 3.1415926 3.1415926 0 map map_flipped" required="true"/>
|
||||
|
||||
<node name="simple_offboard" pkg="clover" type="simple_offboard" required="true" output="screen">
|
||||
<param name="reference_frames/body" value="map"/>
|
||||
<param name="reference_frames/base_link" value="map"/>
|
||||
</node>
|
||||
<node name="simple_offboard" pkg="clover" type="simple_offboard" required="true" output="screen"/>
|
||||
|
||||
<node name="tf2_web_republisher" pkg="tf2_web_republisher" type="tf2_web_republisher" required="true"/>
|
||||
|
||||
@@ -38,6 +35,8 @@
|
||||
<rosparam param="notify">startup: { r: 255, g: 255, b: 255 }</rosparam>
|
||||
</node>
|
||||
|
||||
<node name="clover_blocks" pkg="clover_blocks" type="clover_blocks" output="screen" required="true"/>
|
||||
|
||||
<param name="test_module" value="$(find clover)/test/basic.py"/>
|
||||
<test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner"/>
|
||||
</launch>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const url = 'ws://' + location.hostname + ':9090';
|
||||
const ros = new ROSLIB.Ros({ url: url });
|
||||
const params = Object.fromEntries(new URLSearchParams(window.location.search).entries());
|
||||
|
||||
ros.on('connection', function () {
|
||||
document.body.classList.add('connected');
|
||||
@@ -39,6 +40,7 @@ function viewTopicsList() {
|
||||
let rosdistro;
|
||||
|
||||
function viewTopic(topic) {
|
||||
let counter = 0;
|
||||
let index = '<a href=topics.html>Topics</a>';
|
||||
title.innerHTML = `${index}: ${topic}`;
|
||||
topicMessage.style.display = 'block';
|
||||
@@ -50,9 +52,20 @@ function viewTopic(topic) {
|
||||
});
|
||||
|
||||
new ROSLIB.Topic({ ros: ros, name: topic }).subscribe(function(msg) {
|
||||
counter++;
|
||||
document.title = topic;
|
||||
if (mouseDown) return;
|
||||
topicMessage.innerHTML = yamlStringify(msg); // JSON.stringify(msg, null, 4);
|
||||
|
||||
if (msg.header && msg.header.stamp) {
|
||||
if (params.date || params.offset) {
|
||||
let date = new Date(msg.header.stamp.secs * 1e3 + msg.header.stamp.nsecs * 1e-6);
|
||||
if (params.date) msg.header.date = date.toISOString();
|
||||
if (params.offset) msg.header.offset = (new Date() - date) * 1e-3;
|
||||
}
|
||||
}
|
||||
|
||||
let txt = `<div class=counter>${counter} received</div>${yamlStringify(msg)}`; // JSON.stringify(msg, null, 4);
|
||||
topicMessage.innerHTML = txt;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,8 +75,6 @@ topicMessage.addEventListener('mousedown', function() { mouseDown = true; });
|
||||
topicMessage.addEventListener('mouseup', function() { mouseDown = false; });
|
||||
|
||||
function init() {
|
||||
const params = Object.fromEntries(new URLSearchParams(window.location.search).entries());
|
||||
|
||||
if (!params.topic) {
|
||||
viewTopicsList();
|
||||
} else {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
}
|
||||
.counter { color: #b9b9b9; margin-bottom: 1em; }
|
||||
#topic-type { font-family: monospace; font-size: 0.5em; vertical-align: super; font-weight: normal; }
|
||||
.topic { font-family: monospace; }
|
||||
body.closed { background-color: rgb(207, 207, 207); }
|
||||
@@ -23,6 +24,6 @@
|
||||
<body>
|
||||
<h1> </h1>
|
||||
<ul id="topics"></ul>
|
||||
<code id="topic-message" title="Hold mouse button to pause">No messages received</code>
|
||||
<code id="topic-message">No messages received</code>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -19,7 +19,7 @@ The frontend files are located in [`www`](./www/) subdirectory. The frontend app
|
||||
### Services
|
||||
|
||||
* `~run` ([*clover_blocks/Run*](srv/Run.srv)) – run Blockly-generated program (in Python).
|
||||
* `~stop` ([*std_srvs/Trigger*](http://docs.ros.org/melodic/api/std_srvs/html/srv/Trigger.html)) – terminate the running program.
|
||||
* `~stop` ([*std_srvs/Trigger*](http://docs.ros.org/noetic/api/std_srvs/html/srv/Trigger.html)) – terminate the running program.
|
||||
* `~store` ([*clover_blocks/load*](srv/Store.srv)) – store a user program (to `<package_path>/programs` by default).
|
||||
* `~load` ([*clover_blocks/load*](srv/Load.srv)) – load all the stored programs.
|
||||
|
||||
@@ -45,11 +45,11 @@ http://<hostname>/clover_blocks/?navigate_tolerance=0.5&sleep_time=0.1
|
||||
|
||||
#### Published
|
||||
|
||||
* `~running` ([*std_msgs/Bool*](http://docs.ros.org/melodic/api/std_msgs/html/msg/Bool.html)) – indicates if the program is currently running.
|
||||
* `~block` ([*std_msgs/String*](http://docs.ros.org/melodic/api/std_msgs/html/msg/String.html)) – current executing block (maximum topic rate is limited).
|
||||
* `~error` ([*std_msgs/String*](http://docs.ros.org/melodic/api/std_msgs/html/msg/String.html)) – user program errors and exceptions.
|
||||
* `~running` ([*std_msgs/Bool*](http://docs.ros.org/noetic/api/std_msgs/html/msg/Bool.html)) – indicates if the program is currently running.
|
||||
* `~block` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – current executing block (maximum topic rate is limited).
|
||||
* `~error` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user program errors and exceptions.
|
||||
* `~prompt` ([*clover_blocks/Prompt*](msg/Prompt.msg)) – user input request (includes random request ID string).
|
||||
|
||||
This topic is published from the frontend side:
|
||||
|
||||
* `~prompt/<request_id>` ([*std_msgs/String*](http://docs.ros.org/melodic/api/std_msgs/html/msg/String.html)) – user input response.
|
||||
* `~prompt/<request_id>` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) – user input response.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0"?>
|
||||
<package format="2">
|
||||
<name>clover_blocks</name>
|
||||
<version>0.21.1</version>
|
||||
<version>0.23.0</version>
|
||||
<description>Blockly programming support for Clover</description>
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
<license>MIT</license>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<package format="2">
|
||||
<name>clover_description</name>
|
||||
<version>0.21.1</version>
|
||||
<version>0.23.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>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<xacro:property name="sqrt2" value="1.4142135623730951" />
|
||||
<xacro:property name="rotor_drag_coefficient" value="1.75e-04" />
|
||||
<xacro:property name="rolling_moment_coefficient" value="0.000001" />
|
||||
<xacro:property name="color" value="$(arg visual_material)" />
|
||||
<xacro:property name="color" value="DarkGrey" />
|
||||
|
||||
<!-- Property Blocks -->
|
||||
<!-- Clover body inertia -->
|
||||
|
||||
@@ -64,6 +64,12 @@
|
||||
<!-- <gazebo>
|
||||
<static>true</static>
|
||||
</gazebo> -->
|
||||
<gazebo>
|
||||
<plugin name="${name}_ros_controller" filename="libsim_leds_controller.so">
|
||||
<robotNamespace></robotNamespace>
|
||||
<ledCount>${led_count}</ledCount>
|
||||
</plugin>
|
||||
</gazebo>
|
||||
</xacro:macro>
|
||||
|
||||
</robot>
|
||||
|
||||
@@ -37,6 +37,14 @@ target_compile_options(sim_leds PRIVATE -std=c++11)
|
||||
|
||||
add_dependencies(sim_leds ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
|
||||
|
||||
add_library(sim_leds_controller src/sim_leds_controller.cpp)
|
||||
|
||||
target_include_directories(sim_leds_controller PRIVATE ${catkin_INCLUDE_DIRS} ${GAZEBO_INCLUDE_DIRS})
|
||||
target_link_libraries(sim_leds_controller PRIVATE ${catkin_LIBRARIES} ${GAZEBO_LIBRARIES})
|
||||
target_compile_options(sim_leds_controller PRIVATE -std=c++11)
|
||||
|
||||
add_dependencies(sim_leds_controller ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
|
||||
|
||||
# Gazebo throttling camera plugin
|
||||
# for some reason, CMake does not support per-target link directories, and Gazebo does not put
|
||||
# CameraPlugin into ${GAZEBO_LIBRARIES}
|
||||
|
||||
@@ -10,7 +10,7 @@ The simulation may be configured by a set of arguments:
|
||||
|
||||
* `mav_id` (*integer*, default: *0*) - MAVLink identifier of the vehicle. **Note**: Multi-vehicle simulation is possible, but requires extensive changes to launch files;
|
||||
* `est` (*string*, default: *lpe*, possible values: *lpe*, *ekf2*) - PX4 estimator selection. Note that this may be overriden in the startup scripts for your craft;
|
||||
* `vehicle` (*string*, default: *clover*) - PX4 vehicle name. Depending on this parameter, different PX4 presets will be loaded. **Note**: The default value, *clover*, requires you to use [Clover-specific PX4 branch](https://github.com/CopterExpress/Firmware/tree/v1.10.1-clever);
|
||||
* `vehicle` (*string*, default: *clover*) - PX4 vehicle name. Depending on this parameter, different PX4 presets will be loaded.
|
||||
* `main_camera` (*boolean*, default: *true*) - controls whether the drone will have a vision position estimation camera;
|
||||
* `rangefinder` (*boolean*, default: *true*) - controls whether the drone will have a laser rangefinder;
|
||||
* `led` (*boolean*, default: *true*) - controls whether the drone will have a programmable LED strip;
|
||||
|
||||
@@ -7,30 +7,31 @@
|
||||
|
||||
. ${R}etc/init.d-posix/airframes/10016_iris # base on iris
|
||||
|
||||
param set ATT_W_EXT_HDG 0.5
|
||||
param set ATT_EXT_HDG_M 1
|
||||
param set-default ATT_W_EXT_HDG 0.5
|
||||
param set-default ATT_EXT_HDG_M 1 # 0 = none, 1 = vision, 2 = mocap
|
||||
|
||||
param set COM_DISARM_LAND 1.0
|
||||
param set-default COM_DISARM_LAND 1.0
|
||||
param set-default COM_RCL_EXCEPT 4 # enable offboard flights without rc
|
||||
|
||||
param set LPE_FLW_SCALE 1.0
|
||||
param set LPE_FLW_R 0.2
|
||||
param set LPE_FLW_RR 0.0
|
||||
param set LPE_FLW_QMIN 10
|
||||
param set LPE_VIS_DELAY 0.0
|
||||
param set LPE_VIS_Z 0.1
|
||||
param set LPE_FUSION 86
|
||||
param set-default LPE_FLW_SCALE 1.0
|
||||
param set-default LPE_FLW_R 0.2
|
||||
param set-default LPE_FLW_RR 0.0
|
||||
param set-default LPE_FLW_QMIN 10
|
||||
param set-default LPE_VIS_DELAY 0.0
|
||||
param set-default LPE_VIS_Z 0.1
|
||||
param set-default LPE_FUSION 86 # flow + vis + land detector + gyro comp
|
||||
|
||||
param set SENS_FLOW_ROT 0
|
||||
param set SENS_FLOW_MINHGT 0.01
|
||||
param set SENS_FLOW_MAXHGT 4.0
|
||||
param set SENS_FLOW_MAXR 10.0
|
||||
param set-default SENS_FLOW_ROT 0
|
||||
param set-default SENS_FLOW_MINHGT 0.0
|
||||
param set-default SENS_FLOW_MAXHGT 4.0
|
||||
param set-default SENS_FLOW_MAXR 10.0
|
||||
|
||||
param set EKF2_AID_MASK 27 # gps + flow + vis pos + vis yaw
|
||||
param set EKF2_OF_DELAY 0
|
||||
param set EKF2_OF_QMIN 10
|
||||
param set EKF2_OF_N_MIN 0.05
|
||||
param set EKF2_OF_N_MAX 0.2
|
||||
param set EKF2_HGT_MODE 2
|
||||
param set EKF2_EVA_NOISE 0.1
|
||||
param set EKF2_EVP_NOISE 0.1
|
||||
param set EKF2_EV_DELAY 0
|
||||
param set-default EKF2_AID_MASK 26 # flow + vis pos + vis yaw
|
||||
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_EVA_NOISE 0.1
|
||||
param set-default EKF2_EVP_NOISE 0.1
|
||||
param set-default EKF2_EV_DELAY 0
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</include>
|
||||
|
||||
<!-- PX4 instance -->
|
||||
<node name="sitl_$(arg mav_id)" pkg="px4" type="px4" output="screen" args="$(find px4)/ROMFS/px4fmu_common -s etc/init.d-posix/rcS -i $(arg mav_id)" unless="$(eval type == 'none')">
|
||||
<node name="sitl_$(arg mav_id)" pkg="px4" type="px4" output="screen" required="true" args="$(find px4)/ROMFS/px4fmu_common -s etc/init.d-posix/rcS -i $(arg mav_id)" unless="$(eval type == 'none')">
|
||||
<env name="PX4_SIM_MODEL" value="$(arg vehicle)"/>
|
||||
<env name="PX4_ESTIMATOR" value="$(arg est)"/>
|
||||
</node>
|
||||
@@ -36,7 +36,7 @@
|
||||
<arg name="use_clover_physics" value="$(arg use_clover_physics)"/>
|
||||
</include>
|
||||
|
||||
<node name="jmavsim" pkg="px4" type="jmavsim_run.sh" output="screen" if="$(eval type == 'jmavsim')">
|
||||
<node name="jmavsim" pkg="px4" required="true" type="jmavsim_run.sh" output="screen" if="$(eval type == 'jmavsim')">
|
||||
<env name="HEADLESS" value="1" if="$(eval not gui)"/>
|
||||
</node>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<package format="2">
|
||||
<name>clover_simulation</name>
|
||||
<version>0.21.1</version>
|
||||
<version>0.23.0</version>
|
||||
<description>The clover_simulation package provides worlds and launch files for Gazebo.</description>
|
||||
|
||||
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
|
||||
|
||||
@@ -49,14 +49,9 @@ private:
|
||||
|
||||
std::unique_ptr<ros::NodeHandle> nh;
|
||||
|
||||
ros::ServiceServer setLedsSrv;
|
||||
// Note: LED state should only be published by the /gazebo node
|
||||
led_msgs::LEDStateArray ledState;
|
||||
ros::Publisher statePublisher;
|
||||
// LED state will be read from the topic to avoid creating more services
|
||||
ros::Subscriber stateSubscriber;
|
||||
|
||||
bool setLeds(led_msgs::SetLEDs::Request& req, led_msgs::SetLEDs::Response& resp);
|
||||
void handleLedsMsg(const led_msgs::LEDStateArrayConstPtr& leds);
|
||||
|
||||
public:
|
||||
@@ -73,16 +68,8 @@ public:
|
||||
ROS_INFO_NAMED(("LedController_" + robotNamespace).c_str(), "LedController has started (as %s)", role == Role::Client ? "client" : "server");
|
||||
|
||||
nh.reset(new ros::NodeHandle(robotNamespace));
|
||||
if (role == Role::Server)
|
||||
{
|
||||
setLedsSrv = nh->advertiseService("led/set_leds", &LedController::setLeds, this);
|
||||
statePublisher = nh->advertise<led_msgs::LEDStateArray>("led/state", 1, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// LED state should be published to the "led/state" topic, so we grab our data there
|
||||
stateSubscriber = nh->subscribe<led_msgs::LEDStateArray>("led/state", 1, &LedController::handleLedsMsg, this);
|
||||
}
|
||||
|
||||
stateSubscriber = nh->subscribe<led_msgs::LEDStateArray>("led/state", 1, &LedController::handleLedsMsg, this);
|
||||
};
|
||||
|
||||
~LedController()
|
||||
@@ -96,13 +83,9 @@ public:
|
||||
std::lock_guard<std::mutex> lock(registryMutex);
|
||||
if (totalLeds > 0) {
|
||||
registeredLeds.resize(totalLeds);
|
||||
ledState.leds.resize(totalLeds);
|
||||
}
|
||||
ROS_DEBUG_NAMED(("LedController_" + robotNamespace).c_str(), "Registering LED visual plugin to %s (LED id=%d)", (role == Role::Client) ? "client" : "server", ledIdx);
|
||||
registeredLeds[ledIdx] = plugin;
|
||||
ledState.leds[ledIdx].index = ledIdx;
|
||||
if (role == Role::Server)
|
||||
statePublisher.publish(ledState);
|
||||
}
|
||||
|
||||
void unregisterPlugin(sim_led::LedVisualPlugin* plugin)
|
||||
@@ -157,7 +140,8 @@ public:
|
||||
{
|
||||
auto indexStr = parentName.substr(lastDashPos + 1);
|
||||
try {
|
||||
myIndex = std::stoi(indexStr);
|
||||
if (indexStr == "visual") myIndex = 0; // the first visual doesn't have index
|
||||
else myIndex = std::stoi(indexStr);
|
||||
} catch(const std::exception &e) {
|
||||
gzwarn << "Failed to convert " << indexStr << " to integer: " << e.what() << ", assuming 0\n";
|
||||
myIndex = 0;
|
||||
@@ -195,26 +179,6 @@ public:
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME: These two functions basically do the same thing, maybe they can be merged?
|
||||
bool led_controller::LedController::setLeds(led_msgs::SetLEDs::Request &req, led_msgs::SetLEDs::Response &resp)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(registryMutex);
|
||||
for(const auto& led : req.leds)
|
||||
{
|
||||
if (led.index < registeredLeds.size()) {
|
||||
auto color = GazeboColor(led.r / 255.0f, led.g / 255.0f, led.b / 255.0f);
|
||||
auto ledPlugin = registeredLeds[led.index];
|
||||
if (ledPlugin) ledPlugin->SetColor(color);
|
||||
ledState.leds[led.index].r = led.r;
|
||||
ledState.leds[led.index].g = led.g;
|
||||
ledState.leds[led.index].b = led.b;
|
||||
}
|
||||
}
|
||||
statePublisher.publish(ledState);
|
||||
resp.success = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void led_controller::LedController::handleLedsMsg(const led_msgs::LEDStateArrayConstPtr& leds)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(registryMutex);
|
||||
|
||||
71
clover_simulation/src/sim_leds_controller.cpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#include <led_msgs/SetLEDs.h>
|
||||
#include <led_msgs/LEDStateArray.h>
|
||||
|
||||
#include <ros/ros.h>
|
||||
|
||||
#include <gazebo/gazebo.hh>
|
||||
#include <gazebo/physics/physics.hh>
|
||||
#include <gazebo/common/common.hh>
|
||||
|
||||
class LedControllerPlugin : public gazebo::ModelPlugin {
|
||||
private:
|
||||
std::unique_ptr<ros::NodeHandle> nh;
|
||||
std::string ns;
|
||||
ros::ServiceServer setLedsSrv;
|
||||
led_msgs::LEDStateArray ledState;
|
||||
ros::Publisher statePublisher;
|
||||
std::mutex handleMutex;
|
||||
|
||||
public:
|
||||
bool setLeds(led_msgs::SetLEDs::Request &req, led_msgs::SetLEDs::Response &resp)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(handleMutex);
|
||||
for(const auto& led : req.leds)
|
||||
{
|
||||
if (led.index < ledState.leds.size()) {
|
||||
ledState.leds[led.index].r = led.r;
|
||||
ledState.leds[led.index].g = led.g;
|
||||
ledState.leds[led.index].b = led.b;
|
||||
}
|
||||
}
|
||||
statePublisher.publish(ledState);
|
||||
resp.success = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void Load(gazebo::physics::ModelPtr model, sdf::ElementPtr sdf) override
|
||||
{
|
||||
ROS_INFO("Initialize LED Controller");
|
||||
|
||||
// We need "libgazebo_ros_api.so" to be loaded
|
||||
if (!ros::isInitialized())
|
||||
{
|
||||
ROS_FATAL_NAMED("LedController", "Tried to load ROS plugin when ROS Gazebo API is not loaded. Please use gazebo_ros node to"
|
||||
"launch Gazebo.");
|
||||
}
|
||||
|
||||
ns = "";
|
||||
|
||||
if (sdf->HasElement("robotNamespace")) {
|
||||
ns = sdf->Get<std::string>("robotNamespace");
|
||||
}
|
||||
if (!sdf->HasElement("ledCount")) {
|
||||
gzerr << "ledCount is not set, but is required for the plugin to function correctly\n";
|
||||
return;
|
||||
}
|
||||
int totalLeds = sdf->Get<int>("ledCount");
|
||||
ledState.leds.resize(totalLeds);
|
||||
for (int i = 0; i < totalLeds; i++) {
|
||||
ledState.leds[i].index = i;
|
||||
}
|
||||
|
||||
nh.reset(new ros::NodeHandle(ns));
|
||||
|
||||
setLedsSrv = nh->advertiseService("led/set_leds", &LedControllerPlugin::setLeds, this);
|
||||
statePublisher = nh->advertise<led_msgs::LEDStateArray>("led/state", 1, true);
|
||||
|
||||
statePublisher.publish(ledState);
|
||||
}
|
||||
};
|
||||
|
||||
GZ_REGISTER_MODEL_PLUGIN(LedControllerPlugin);
|
||||
BIN
docs/assets/c305.jpg
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
docs/assets/c4s/antennac4s.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
docs/assets/c4s/arecibo_broken_c4s.jpg
Normal file
|
After Width: | Height: | Size: 299 KiB |
BIN
docs/assets/c4s/arecibo_c4s.jpg
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
docs/assets/c4s/code_c4s.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
docs/assets/c4s/drone_c4s.jpg
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/assets/c4s/logo_c4s.jpg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
docs/assets/c4s/module2_c4s.jpg
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
docs/assets/c4s/module_c4s.jpg
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
docs/assets/c4s/pic1_c4s.jpg
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
docs/assets/c4s/pic2_c4s.jpg
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
docs/assets/c4s/pic3_c4s.jpg
Normal file
|
After Width: | Height: | Size: 267 KiB |
BIN
docs/assets/c4s/pic4_c4s.jpg
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
docs/assets/c4s/shot_c4s.jpg
Normal file
|
After Width: | Height: | Size: 265 KiB |
BIN
docs/assets/c4s/string_c4s.jpg
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
docs/assets/c4s/white_noise_c4s.jpg
Normal file
|
After Width: | Height: | Size: 424 KiB |
BIN
docs/assets/clover-rescue-team/allsettings.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
docs/assets/clover-rescue-team/bot1.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
docs/assets/clover-rescue-team/bot2.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/assets/clover-rescue-team/bot3.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
docs/assets/clover-rescue-team/bot4.png
Normal file
|
After Width: | Height: | Size: 70 KiB |
BIN
docs/assets/clover-rescue-team/bot5.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
docs/assets/clover-rescue-team/bot6.jpg
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
docs/assets/clover-rescue-team/bot7.jpg
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
docs/assets/clover-rescue-team/m1.jpg
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
docs/assets/clover-rescue-team/m2.jpg
Normal file
|
After Width: | Height: | Size: 35 KiB |
BIN
docs/assets/clover-rescue-team/m3.jpg
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
docs/assets/clover-rescue-team/m4.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
BIN
docs/assets/clover-rescue-team/m5.jpg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
docs/assets/clover-rescue-team/main.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
docs/assets/clover-rescue-team/mockup.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/assets/clover-rescue-team/s1.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
docs/assets/clover-rescue-team/s2.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
docs/assets/clover-rescue-team/s3.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
docs/assets/clover-rescue-team/s4.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
docs/assets/clover-rescue-team/signup.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/assets/clover-rescue-team/status.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
docs/assets/copter_cat/board_bottom_nums.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
docs/assets/copter_cat/board_top_nums.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
25
docs/assets/copter_cat/logo.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<!-- Creator: CorelDRAW 2019 (64-Bit) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="255.858mm" height="78.1171mm" version="1.1" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd"
|
||||
viewBox="0 0 5794.02 1769"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:xodm="http://www.corel.com/coreldraw/odm/2003">
|
||||
<defs>
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
.fil1 {fill:#1F1B20}
|
||||
.fil0 {fill:#1F1B20;fill-rule:nonzero}
|
||||
]]>
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Слой_x0020_1">
|
||||
<metadata id="CorelCorpID_0Corel-Layer"/>
|
||||
<polygon class="fil0" points="3293.37,1768.99 2509.06,1768.97 2507.86,1661.12 3292.17,1660.65 "/>
|
||||
<polygon class="fil0" points="3133.99,231.57 2664.24,231.57 2556.99,123.72 3237.05,123.23 "/>
|
||||
<path class="fil0" d="M3647.51 123.72l1925.38 0 0 381.38 -107.85 -8.69 0 -264.84 -1809.74 0 -7.79 -107.85zm1925.38 676.77l0 968.51 -1653.42 0 -1.5 -108.34 1547.07 0.49 0 -850.17 107.85 -10.48z"/>
|
||||
<path class="fil0" d="M2142.36 123.72l-1925.38 0 0 381.38 107.85 -8.69 0 -264.84 1809.74 0 7.79 -107.85zm-1925.38 676.77l0 968.51 1654.08 0 1.5 -108.34 -1547.73 0.49 0 -850.17 -107.85 -10.48z"/>
|
||||
<path class="fil1" d="M2887.45 1229.18c-100.66,0 -189.34,-87.18 -244.46,-111.14 -55.12,-23.96 -215.7,-63.51 -215.7,-63.51 0,0 -88.67,-131.81 -110.24,-196.52 -21.57,-64.71 -38.35,-225.29 -38.35,-225.29 0,0 -28.76,-59.92 -38.35,-86.28 -9.59,-26.36 -19.17,-79.09 -19.17,-79.09l67.11 -59.92 -71.9 -21.57c0,0 -9.59,-131.82 -9.59,-184.54 0,-52.73 28.76,-201.32 28.76,-201.32 0,0 143.8,76.69 242.06,148.59 98.26,71.9 201.32,220.49 201.32,220.49 0,0 97.96,-32.96 220.19,-32.96 122.23,0 212.4,32.96 212.4,32.96 0,0 103.06,-148.59 201.32,-220.49 98.26,-71.9 242.06,-148.59 242.06,-148.59 29.86,154.29 35.16,239.28 25.77,366.69 -7.33,99.38 -20.22,164.6 -68.91,266.02 0,0 -16.78,146.2 -38.35,225.29 -21.57,79.09 -110.24,196.52 -110.24,196.52 0,0 -165.37,25.17 -221.69,53.92 -56.32,28.76 -153.38,120.73 -254.04,120.73zm-260.11 -471.35c0,0 -32.95,31.4 -32.95,71.54 0,40.15 29.36,76.45 29.36,76.45 0,0 30.97,-38.24 30.97,-73.59 0,-35.35 -27.38,-74.4 -27.38,-74.4zm-164.17 -37.15c0,0 84.84,-18.88 192.09,33.85 107.25,52.73 163.22,135.12 163.22,135.12 0,0 -122.28,71.58 -245.11,36.23 -122.83,-35.35 -110.2,-205.2 -110.2,-205.2zm687.07 37.15c0,0 32.95,31.4 32.95,71.54 0,40.15 -29.36,76.45 -29.36,76.45 0,0 -30.97,-38.24 -30.97,-73.59 0,-35.35 27.38,-74.4 27.38,-74.4zm164.17 -37.15c0,0 -84.84,-18.88 -192.09,33.85 -107.25,52.73 -163.22,135.12 -163.22,135.12 0,0 122.28,71.58 245.11,36.23 122.83,-35.35 110.2,-205.2 110.2,-205.2z"/>
|
||||
<path class="fil1" d="M2895.05 1398.42c0,0 -216.07,-1.19 -337.7,-47.93 -121.63,-46.74 -147.39,-68.91 -147.39,-68.91l-149.64 380.47 134.66 0 1.2 105.45 -411.02 0 0 -105.45 154.43 0 105.6 -464.95c0,0 -226.48,-22.77 -424.21,-81.49 -197.72,-58.72 -354.7,-127.02 -492.51,-133.01 -137.81,-5.99 -114.44,-2.99 -198.32,-7.79 -83.88,-4.79 -142.6,-47.92 -142.6,-82.68 0,-34.75 1.2,-59.32 1.2,-97.66 0,-38.35 32.36,-46.74 32.36,-46.74l-1.2 -29.96 -22.77 0c0,-8.09 12.58,-46.74 -53.93,-46.74 -66.51,0 -175.67,58.19 -233.67,77.89 -58,19.71 -552.43,-15.58 -644.7,-41.94 -92.27,-26.36 -64.71,-77.89 -45.54,-94.67 19.17,-16.78 539.25,-63.51 645.9,-56.32 106.65,7.19 329.54,44.34 329.54,44.34l0 -26.36 25.76 0 0 -25.76 12.58 0c0,0 2.39,-94.67 2.39,-134.21 0,-39.54 37.23,-60.33 65.31,-59.9 28.08,0.43 63.2,25.75 63.2,61.1 0,35.35 2.7,128.82 2.7,128.82l7.49 -0.15 0 25.02 32.95 0 0 27.56c0,0 253.15,-39.85 361,-45.84 107.85,-5.99 594.37,37.15 617.14,58.72 22.77,21.57 35.95,77.89 -39.55,100.66 -75.5,22.77 -581.19,57.52 -639.91,46.74 -58.72,-10.79 -173.73,-77.7 -243.26,-76.69 -69.53,1.01 -56.62,35.95 -56.62,45.54l-22.78 -0.9 -0.16 18.87c0,0 83.07,-1.13 121.42,16.84 38.35,17.97 115.72,50.26 168.44,62.24 52.72,11.98 568.01,-14.38 636.31,-28.76 68.3,-14.38 115.48,-40.16 115.48,-40.16 0,0 16.34,115.66 47.5,189.95 31.16,74.3 86.28,147.39 109.05,164.17 22.77,16.78 156.98,45.54 196.52,59.92 39.55,14.38 110.24,58.72 149.79,86.28 39.55,27.56 112.97,44.34 172.89,44.34 59.92,0 126.57,-16.78 166.12,-44.34 39.55,-27.56 110.24,-71.9 149.79,-86.28 39.55,-14.38 173.76,-43.14 196.52,-59.92 22.77,-16.78 77.89,-89.88 109.05,-164.17 31.16,-74.3 47.5,-189.95 47.5,-189.95 0,0 47.17,25.78 115.48,40.16 68.3,14.38 583.59,40.74 636.31,28.76 52.72,-11.98 130.1,-44.27 168.44,-62.24 38.35,-17.97 121.42,-16.84 121.42,-16.84l-0.16 -18.87 -22.78 0.9c0,-9.59 12.91,-44.53 -56.62,-45.54 -69.53,-1.01 -184.54,65.91 -243.26,76.69 -58.72,10.79 -564.41,-23.97 -639.91,-46.74 -75.5,-22.77 -62.31,-79.09 -39.55,-100.66 22.77,-21.57 509.29,-64.71 617.14,-58.72 107.85,5.99 361,45.84 361,45.84l0 -27.56 32.95 0 0 -25.02 7.49 0.15c0,0 2.7,-93.47 2.7,-128.82 0,-35.35 35.12,-60.67 63.2,-61.1 28.08,-0.43 65.31,20.36 65.31,59.9 0,39.54 2.39,134.21 2.39,134.21l12.58 0 0 25.76 25.76 0 0 26.36c0,0 222.89,-37.15 329.54,-44.34 106.65,-7.19 626.73,39.54 645.9,56.32 19.17,16.78 46.74,68.3 -45.54,94.67 -92.27,26.36 -586.7,61.65 -644.7,41.94 -58,-19.71 -167.17,-77.89 -233.67,-77.89 -66.51,0 -53.93,38.65 -53.93,46.74l-22.77 0 -1.2 29.96c0,0 32.36,8.4 32.36,46.74 0,38.35 1.2,62.91 1.2,97.66 0,34.75 -58.72,77.88 -142.6,82.68 -83.88,4.79 -60.52,1.79 -198.32,7.79 -137.81,5.99 -294.79,74.3 -492.51,133.01 -197.72,58.72 -424.21,81.49 -424.21,81.49l105.6 464.95 154.43 0 0 105.45 -411.02 0 1.2 -105.45 134.66 0 -149.64 -380.47c0,0 -25.76,22.17 -147.39,68.91 -121.63,46.74 -341.62,47.93 -341.62,47.93z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.5 KiB |
61
docs/assets/copterhack2023.svg
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Слой_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 850.39 283.46" style="enable-background:new 0 0 850.39 283.46;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#333333;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#CCCCCC;}
|
||||
.st3{fill:#1E1183;}
|
||||
.st4{fill:#F71523;}
|
||||
.st5{fill:url(#SVGID_1_);}
|
||||
.st6{fill:url(#SVGID_2_);}
|
||||
.st7{fill:url(#SVGID_3_);}
|
||||
.st8{opacity:0.05;fill:#FFFFFF;}
|
||||
.st9{opacity:0.05;fill:#CCCCCC;}
|
||||
.st10{opacity:0.05;fill:url(#SVGID_4_);}
|
||||
.st11{opacity:0.05;fill:url(#SVGID_5_);}
|
||||
.st12{opacity:0.05;fill:url(#SVGID_6_);}
|
||||
.st13{opacity:0.05;fill:url(#SVGID_7_);}
|
||||
.st14{opacity:0.05;fill:url(#SVGID_8_);}
|
||||
.st15{opacity:0.05;fill:url(#SVGID_9_);}
|
||||
.st16{opacity:0.05;fill:url(#SVGID_10_);}
|
||||
.st17{opacity:0.05;fill:url(#SVGID_11_);}
|
||||
.st18{opacity:0.05;fill:url(#SVGID_12_);}
|
||||
</style>
|
||||
<g>
|
||||
<polygon class="st3" points="559.36,2.05 433.46,2.05 433.46,36.37 479.25,36.37 479.25,128.02 513.57,128.02 513.57,36.37
|
||||
559.36,36.37 "/>
|
||||
<polygon class="st3" points="702.71,36.37 702.71,2.05 576.81,2.05 576.81,2.12 576.75,2.12 576.75,128.02 576.81,128.02
|
||||
611.07,128.02 702.71,128.02 702.71,93.7 611.07,93.7 611.07,82.23 668.45,82.23 668.45,47.91 611.07,47.91 611.07,36.37 "/>
|
||||
<polygon class="st3" points="559.45,179.72 559.38,155.4 535.19,155.45 489.34,201.3 467.68,201.3 467.68,155.45 433.37,155.45
|
||||
433.37,281.35 467.68,281.35 467.68,235.62 489.46,235.62 535.19,281.35 559.38,281.41 559.45,257.09 520.77,218.4 "/>
|
||||
<path class="st3" d="M67.26,36.87h63.01V2.05H67.26c-34.8,0-63.01,28.21-63.01,63.01s28.21,63.01,63.01,63.01h63.01V93.25H67.26
|
||||
c-15.57,0-28.19-12.62-28.19-28.19C39.07,49.49,51.69,36.87,67.26,36.87z"/>
|
||||
<path class="st3" d="M238.36,218.4v63.01h34.82V218.4c0-34.8-28.21-63.01-63.01-63.01s-63.01,28.21-63.01,63.01v63.01h34.82V218.4
|
||||
c0-15.57,12.62-28.19,28.19-28.19C225.74,190.21,238.36,202.83,238.36,218.4z"/>
|
||||
<path class="st3" d="M353.08,190.21h63.01V155.4h-63.01c-34.8,0-63.01,28.21-63.01,63.01s28.21,63.01,63.01,63.01h63.01v-34.82
|
||||
h-63.01c-15.57,0-28.19-12.62-28.19-28.19C324.88,202.83,337.51,190.21,353.08,190.21z"/>
|
||||
<polygon class="st3" points="95.79,155.4 95.79,200.82 39.41,200.82 39.41,155.4 4.25,155.4 4.25,281.41 39.41,281.41
|
||||
39.41,235.98 95.79,235.98 95.79,281.41 130.27,281.41 130.27,155.4 "/>
|
||||
<path class="st4" d="M737.06,200.43c-0.42-25.12-21.44-45.04-46.57-45.04h-45.07v34.32l45.49,0c6.19,0,11.52,4.76,11.81,10.95
|
||||
c0.31,6.61-4.95,12.06-11.49,12.06h-11.5c-18.95,0-34.32,15.36-34.32,34.32v0v34.26h91.64v-34.26h-45.82
|
||||
C716.81,247.03,737.49,226.1,737.06,200.43z"/>
|
||||
<path class="st3" d="M272.4,55.19c-5.46-34.37-37.74-57.8-72.11-52.35c-34.37,5.46-57.8,37.74-52.35,72.11
|
||||
c5.46,34.37,37.74,57.8,72.11,52.35C254.42,121.84,277.85,89.56,272.4,55.19z M210.17,93.26c-15.57,0-28.19-12.62-28.19-28.19
|
||||
c0-15.57,12.62-28.19,28.19-28.19c15.57,0,28.19,12.62,28.19,28.19C238.36,80.64,225.74,93.26,210.17,93.26z"/>
|
||||
|
||||
<rect x="593.74" y="155.4" transform="matrix(-1 -1.224647e-16 1.224647e-16 -1 1221.7888 356.6999)" class="st4" width="34.32" height="45.91"/>
|
||||
<path class="st3" d="M370.51,2.12V2.05h-60.96v0.07h-19.48v125.9h34.32V93.26h46.13c25.17,0,45.57-20.4,45.57-45.57
|
||||
C416.08,22.52,395.68,2.12,370.51,2.12z M364.98,64.71h-40.6V31.12h40.6v0.02c9.27,0,16.78,7.51,16.78,16.78
|
||||
C381.77,57.19,374.25,64.71,364.98,64.71z"/>
|
||||
<path class="st3" d="M846.12,47.69c0-25.17-20.4-45.57-45.57-45.57V2.05h-60.96v0.01h-19.48v126.02h34.32V93.26h32.67l34.76,34.76
|
||||
l24.19,0.06l0.07-24.32l-19.03-19.03C838.61,76.45,846.12,62.95,846.12,47.69z M795.02,64.71
|
||||
C795.02,64.71,795.02,64.71,795.02,64.71h-40.6V31.12h40.6v0.03c0,0,0,0,0,0c9.27,0,16.78,7.51,16.78,16.78
|
||||
C811.8,57.19,804.29,64.71,795.02,64.71z"/>
|
||||
<path class="st4" d="M846.1,195.54c0.31-22.05-18.25-40.09-40.3-40.09h-51.34v34.32h51.59v0.01c2.71,0,5.17,1.93,5.66,4.78
|
||||
c0.33,1.96-0.48,3.98-1.98,5.29c-1.19,1.04-2.46,1.39-3.68,1.39v0.01h-27.06v34.32h27.06v0.01c2.71,0,5.17,1.93,5.66,4.78
|
||||
c0.33,1.96-0.48,3.98-1.98,5.29c-1.19,1.04-2.46,1.39-3.68,1.39v0.01h-51.59v34.32h51.34c22.05,0,40.61-18.04,40.3-40.09
|
||||
c-0.12-8.55-2.96-16.44-7.68-22.86C843.14,211.99,845.98,204.1,846.1,195.54z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
BIN
docs/assets/djs-phoenix/1.png
Normal file
|
After Width: | Height: | Size: 26 KiB |