Compare commits

..

23 Commits

Author SHA1 Message Date
Alexey Rogachevskiy
cda7858fb9 clover: Update ros3djs, THREE.js 2020-03-19 21:44:16 +03:00
Alexey Rogachevskiy
a01d199890 selfcheck: Be more Python3-compatible 2020-03-19 21:21:21 +03:00
Alexey Rogachevskiy
63a792b29d aruco_pose: Use python2 shebang
This shebang will be ignored with newer dynamic_reconfigure packages, and its presence should allow builds to succeed when older dynamic_reconfigure is used.
2020-03-18 21:46:09 +03:00
Alexey Rogachevskiy
360eb02909 Revert "aruco_pose: Use bash trampoline for dynamic_reconfigure"
This reverts commit 6b9d90d3d7.

Newer dynamic_reconfigure uses PYTHON_EXECUTABLE CMake substitution, eliminating the need for a shebang (or a trampoline).
2020-03-18 21:44:39 +03:00
Alexey Rogachevskiy
688b4e3acb aruco_pose: Convert largemap test to ros_pytest 2020-03-18 21:05:49 +03:00
Alexey Rogachevskiy
8042669ade clever: Remove shebang from basic.py test
Tests executed by ros_pytest are not meant to have shebangs anyway.
2020-03-18 20:47:09 +03:00
Alexey Rogachevskiy
6b9d90d3d7 aruco_pose: Use bash trampoline for dynamic_reconfigure 2020-03-18 19:17:17 +03:00
Alexey Rogachevskiy
4f110d4eaa builder: Install rpi_ws281x for Python 2 and 3 2020-03-18 17:10:50 +03:00
Alexey Rogachevskiy
f7eda0be97 Merge remote-tracking branch 'origin/master' into buster-python3 2020-03-18 16:03:02 +03:00
Alexey Rogachevskiy
1da2f76758 builder: Use pip3 for butterfly installation 2020-03-18 16:00:04 +03:00
Alexey Rogachevskiy
60a77a35a5 builder: Make pip refer to pip2 by default
This may break rosdep down the line, but it seems to call `pip3` explicitly
2020-03-18 15:58:12 +03:00
Alexey Rogachevskiy
0ffde38b8b builder: Install ptvsd for python2 explicitly 2020-02-20 21:43:48 +03:00
Alexey Rogachevskiy
99632bf554 Merge remote-tracking branch 'origin/master' into buster-python3 2020-02-20 15:44:08 +03:00
Alexey Rogachevskiy
d44a80b357 builder: Don't try to deactivate nonexistent venv 2020-02-19 13:24:42 +03:00
Alexey Rogachevskiy
77189b5f5f builder: Install butterfly system-wide 2020-02-17 14:52:24 +03:00
Alexey Rogachevskiy
b37a32d4dc builder: Add pip for python2 back 2020-02-17 14:44:41 +03:00
Alexey Rogachevskiy
b359414377 builder: Drop python2 tests 2020-02-12 23:29:04 +03:00
Alexey Rogachevskiy
6d4dd6956f tests: Use python3 for most of it, python2 for cv2 2020-02-12 22:18:03 +03:00
Alexey Rogachevskiy
cb26f0933e builder: Fix python3.yaml identation 2020-02-11 19:44:51 +03:00
Alexey Rogachevskiy
d944f57ebb builder: Put python3.yaml into image 2020-02-11 19:20:54 +03:00
Alexey Rogachevskiy
ad430284de builder: Fix typo (meodic -> melodic) 2020-02-11 18:59:23 +03:00
Alexey Rogachevskiy
b5cf47fdb5 tests: Create ServiceProxy during validation 2020-02-11 18:53:17 +03:00
Alexey Rogachevskiy
99f24abf8d builder: Build against python3 2020-02-11 18:46:23 +03:00
339 changed files with 1273 additions and 78933 deletions

View File

@@ -1,5 +1,4 @@
os: linux sudo: required
dist: xenial
language: generic language: generic
services: services:
- docker - docker
@@ -44,7 +43,7 @@ jobs:
- cd images && zip ${IMAGE_NAME}.zip ${IMAGE_NAME} - cd images && zip ${IMAGE_NAME}.zip ${IMAGE_NAME}
deploy: deploy:
provider: releases provider: releases
token: ${GITHUB_OAUTH_TOKEN} api_key: ${GITHUB_OAUTH_TOKEN}
file: ${IMAGE_NAME}.zip file: ${IMAGE_NAME}.zip
skip_cleanup: true skip_cleanup: true
on: on:
@@ -73,11 +72,8 @@ jobs:
node_js: node_js:
- "10" - "10"
before_script: before_script:
- sudo sh -c "echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections"
- sudo apt update && sudo apt install -y calibre msttcorefonts
- npm install gitbook-cli -g - npm install gitbook-cli -g
- npm install markdownlint-cli -g - npm install markdownlint-cli -g
- npm install svgexport -g
- gitbook -V - gitbook -V
- markdownlint -V - markdownlint -V
script: script:
@@ -86,16 +82,15 @@ jobs:
- ./check_unused_assets.py - ./check_unused_assets.py
- gitbook install - gitbook install
- gitbook build - gitbook build
- gitbook pdf ./ _book/clover.pdf
deploy: deploy:
provider: pages provider: pages
local_dir: _book local-dir: _book
skip_cleanup: true skip-cleanup: true
token: ${GITHUB_OAUTH_TOKEN} github-token: ${GITHUB_OAUTH_TOKEN}
keep_history: true keep-history: true
target_branch: master target-branch: master
repo: CopterExpress/clover.coex.tech repo: CopterExpress/clever.coex.tech
fqdn: clover.coex.tech fqdn: clever.coex.tech
verbose: true verbose: true
on: on:
branch: master branch: master

105
README.md
View File

@@ -1,40 +1,103 @@
# COEX Clover Drone Kit # CLEVER
<img src="docs/assets/clever4-front-white.png" align="right" width="400px" alt="Clover Drone"> <img src="docs/assets/clever4-front-white.png" align="right" width="400px" alt="CLEVER drone">
Clover is an educational programmable drone kit consisting of an unassembled quadcopter, open source software and documentation. The kit includes Pixracer-compatible autopilot running PX4 firmware, Raspberry Pi 4 as companion computer, a camera for computer vision navigation as well as additional sensors and peripheral devices. CLEVER (Russian: *"Клевер"*, meaning *"Clover"*) is an educational programmable drone kit consisting of an unassembled quadcopter, open source software and documentation. The kit includes Pixhawk/Pixracer autopilot running PX4 firmware, Raspberry Pi 3 as companion computer, a camera for computer vision navigation as well as additional sensors and peripheral devices.
The main documentation is available [on Gitbook](https://clover.coex.tech/). Copter Express has implemented a large number of different autonomous drone projects using exactly the same platform: [automated pizza delivery](https://www.youtube.com/watch?v=hmkAoZOtF58) in Samara and Kazan, coffee delivery in Skolkovo Innovation Center, [autonomous quadcopter with charging station](https://www.youtube.com/watch?v=RjX6nUqw1mI) for site monitoring and security, winning drones on [Robocross-2016](https://www.youtube.com/watch?v=dGbDaz_VmYU) and [Robocross-2017](https://youtu.be/AQnd2CRczbQ) competitions and many others.
Official website: <a href="https://coex.tech/clover">coex.tech/clover</a>. **The main documentation is available [on Gitbook](https://clever.coex.tech/).**
## Video compilation Use it to learn how to assemble, configure, pilot and program autonomous CLEVER drone.
[![Clover Drone Kit autonomy compilation](http://img.youtube.com/vi/u3omgsYC4Fk/hqdefault.jpg)](https://youtu.be/u3omgsYC4Fk)
Clover drone is used on a wide range of educational events, including [Copter Hack](https://www.youtube.com/watch?v=xgXheg3TTs4), WorldSkills Drone Operation competition, [Autonomous Vehicles Track of NTI Olympics 20162020](https://www.youtube.com/watch?v=E1_ehvJRKxg), Quadro Hack 2019 (National University of Science and Technology MISiS), Russian Robot Olympiad (autonomous flights), and others.
## Raspberry Pi image ## Raspberry Pi image
Preconfigured image for Raspberry Pi with installed and configured software, ready to fly, is available [in the Releases section](https://github.com/CopterExpress/clover/releases). **Preconfigured image for Raspberry Pi 3 with installed and configured software, ready to fly, is available [in the Releases section](https://github.com/CopterExpress/clever/releases).**
[![Build Status](https://travis-ci.org/CopterExpress/clover.svg?branch=master)](https://travis-ci.org/CopterExpress/clover) [![Build Status](https://travis-ci.org/CopterExpress/clever.svg?branch=master)](https://travis-ci.org/CopterExpress/clever)
Image features: Image includes:
* Raspbian Buster * Raspbian Buster
* [ROS Melodic](http://wiki.ros.org/melodic) * ROS Melodic
* Configured networking * Configured networking
* OpenCV * OpenCV
* [`mavros`](http://wiki.ros.org/mavros) * mavros
* Periphery drivers for ROS ([GPIO](https://clover.coex.tech/en/gpio.html), [LED strip](https://clover.coex.tech/en/leds.html), etc) * Periphery drivers (`pigpiod`, `rpi_ws281x`, etc)
* `aruco_pose` package for marker-assisted navigation * CLEVER software bundle for autonomous drone control
* `clover` package for autonomous drone control
API description for autonomous flights is available [on GitBook](https://clover.coex.tech/en/simple_offboard.html). API description (in Russian) for autonomous flights is available [on GitBook](https://clever.coex.tech/simple_offboard.html).
For manual package installation and running see [`clover` package documentation](clover/README.md). ## Manual installation
Install ROS Melodic according to the [documentation](http://wiki.ros.org/melodic/Installation), then [create a Catkin workspace](http://wiki.ros.org/catkin/Tutorials/create_a_workspace).
Clone this repo to directory `~/catkin_ws/src/clever`:
```bash
cd ~/catkin_ws/src
git clone https://github.com/CopterExpress/clever.git clever
```
All the required ROS packages (including `mavros` and `opencv`) can be installed using `rosdep`:
```bash
cd ~/catkin_ws/
rosdep install -y --from-paths src --ignore-src
```
Build ROS packages (on memory constrained platforms you might be going to need to use `-j1` key):
```bash
cd ~/catkin_ws
catkin_make -j1
```
To complete `mavros` install you'll need to install `geographiclib` datasets:
```bash
curl https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | sudo bash
```
You may optionally install udev rules to provide `/dev/px4fmu` symlink to your PX4-based flight controller connected over USB. Copy `99-px4fmu.rules` to your `/lib/udev/rules.d` folder:
```bash
cd ~/catkin_ws/src/clever/clever/config
sudo cp 99-px4fmu.rules /lib/udev/rules.d
```
Alternatively you may change the `fcu_url` property in `mavros.launch` file to point to your flight controller device.
## Running
Enable systemd service `roscore` (if not running):
```bash
sudo systemctl enable /home/<username>/catkin_ws/src/clever/builder/assets/roscore.service
sudo systemctl start roscore
```
To start connection to SITL, use:
```bash
roslaunch clever sitl.launch
```
To start connection to the flight controller, use:
```bash
roslaunch clever clever.launch
```
> Note that the package is configured to connect to `/dev/px4fmu` by default (see [previous section](#manual-installation)). Install udev rules or specify path to your FCU device in `mavros.launch`.
Also, you can enable and start the systemd service:
```bash
sudo systemctl enable /home/<username>/catkin_ws/src/clever/deploy/clever.service
sudo systemctl start clever
```
## License ## License
While the Clover platform source code is available under the MIT License, note, that the [documentation](docs/) is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. While the Clever platform source code is available under the MIT License, note, that the [documentation](docs/) is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

View File

View File

@@ -1,6 +1,8 @@
cmake_minimum_required(VERSION 3.0) cmake_minimum_required(VERSION 3.0)
project(aruco_pose) project(aruco_pose)
add_definitions(-std=c++11 -Wall -g)
## Compile as C++11, supported in ROS Kinetic and newer ## Compile as C++11, supported in ROS Kinetic and newer
add_compile_options(-std=c++11) add_compile_options(-std=c++11)
@@ -23,7 +25,7 @@ find_package(catkin REQUIRED COMPONENTS
) )
find_package(OpenCV 3 REQUIRED COMPONENTS core imgproc calib3d) find_package(OpenCV 3 REQUIRED COMPONENTS core imgproc calib3d)
if ("${OpenCV_VERSION_MINOR}" LESS "9") if ("${OpenCV_VERSION_MINOR}" LESS "3")
message(STATUS "OpenCV version too low, using vendored ArUco package") message(STATUS "OpenCV version too low, using vendored ArUco package")
include(vendor/VendorOpenCV.cmake) include(vendor/VendorOpenCV.cmake)
else() else()
@@ -227,5 +229,4 @@ if (CATKIN_ENABLE_TESTING)
add_rostest(test/test_parser_empty_map.test) add_rostest(test/test_parser_empty_map.test)
add_rostest(test/test_node_failure.test) add_rostest(test/test_node_failure.test)
add_rostest(test/largemap.test) add_rostest(test/largemap.test)
add_rostest(test/crash_opencv.test)
endif() endif()

View File

@@ -58,9 +58,10 @@ using cv::Mat;
class ArucoDetect : public nodelet::Nodelet { class ArucoDetect : public nodelet::Nodelet {
private: private:
std::unique_ptr<tf2_ros::TransformBroadcaster> br_; ros::NodeHandle nh_, nh_priv_;
std::unique_ptr<tf2_ros::Buffer> tf_buffer_; tf2_ros::TransformBroadcaster br_;
std::unique_ptr<tf2_ros::TransformListener> tf_listener_; tf2_ros::Buffer tf_buffer_;
tf2_ros::TransformListener tf_listener_{tf_buffer_};
std::shared_ptr<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>> dyn_srv_; std::shared_ptr<dynamic_reconfigure::Server<aruco_pose::DetectorConfig>> dyn_srv_;
cv::Ptr<cv::aruco::Dictionary> dictionary_; cv::Ptr<cv::aruco::Dictionary> dictionary_;
cv::Ptr<cv::aruco::DetectorParameters> parameters_; cv::Ptr<cv::aruco::DetectorParameters> parameters_;
@@ -80,32 +81,30 @@ private:
public: public:
virtual void onInit() virtual void onInit()
{ {
ros::NodeHandle& nh_ = getNodeHandle(); nh_ = getNodeHandle();
ros::NodeHandle& nh_priv_ = getPrivateNodeHandle(); nh_priv_ = getPrivateNodeHandle();
br_.reset(new tf2_ros::TransformBroadcaster());
tf_buffer_.reset(new tf2_ros::Buffer());
tf_listener_.reset(new tf2_ros::TransformListener(*tf_buffer_, nh_));
int dictionary; int dictionary;
dictionary = nh_priv_.param("dictionary", 2); nh_priv_.param("dictionary", dictionary, 2);
estimate_poses_ = nh_priv_.param("estimate_poses", true); nh_priv_.param("estimate_poses", estimate_poses_, true);
send_tf_ = nh_priv_.param("send_tf", true); nh_priv_.param("send_tf", send_tf_, true);
if (estimate_poses_ && !nh_priv_.getParam("length", length_)) { if (estimate_poses_ && !nh_priv_.getParam("length", length_)) {
NODELET_FATAL("can't estimate marker's poses as ~length parameter is not defined"); NODELET_FATAL("can't estimate marker's poses as ~length parameter is not defined");
ros::shutdown(); ros::shutdown();
} }
readLengthOverride(nh_priv_); readLengthOverride();
known_tilt_ = nh_priv_.param<std::string>("known_tilt", ""); nh_priv_.param<std::string>("known_tilt", known_tilt_, "");
auto_flip_ = nh_priv_.param("auto_flip", false); nh_priv_.param("auto_flip", auto_flip_, false);
frame_id_prefix_ = nh_priv_.param<std::string>("frame_id_prefix", "aruco_"); nh_priv_.param<std::string>("frame_id_prefix", frame_id_prefix_, "aruco_");
camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F); camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
dist_coeffs_ = cv::Mat::zeros(8, 1, CV_64F);
dictionary_ = cv::aruco::getPredefinedDictionary(static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(dictionary)); dictionary_ = cv::aruco::getPredefinedDictionary(static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(dictionary));
parameters_ = cv::aruco::DetectorParameters::create(); parameters_ = cv::aruco::DetectorParameters::create();
parameters_->cornerRefinementMethod = cv::aruco::CORNER_REFINE_SUBPIX;
image_transport::ImageTransport it(nh_); image_transport::ImageTransport it(nh_);
image_transport::ImageTransport it_priv(nh_priv_); image_transport::ImageTransport it_priv(nh_priv_);
@@ -171,8 +170,8 @@ private:
if (!known_tilt_.empty()) { if (!known_tilt_.empty()) {
try { try {
snap_to = tf_buffer_->lookupTransform(msg->header.frame_id, known_tilt_, snap_to = tf_buffer_.lookupTransform(msg->header.frame_id, known_tilt_,
msg->header.stamp, ros::Duration(0.02)); msg->header.stamp, ros::Duration(0.02));
} catch (const tf2::TransformException& e) { } catch (const tf2::TransformException& e) {
NODELET_WARN_THROTTLE(5, "can't snap: %s", e.what()); NODELET_WARN_THROTTLE(5, "can't snap: %s", e.what());
} }
@@ -206,7 +205,7 @@ private:
if (map_markers_ids_.find(ids[i]) == map_markers_ids_.end()) { if (map_markers_ids_.find(ids[i]) == map_markers_ids_.end()) {
transform.transform.rotation = marker.pose.orientation; transform.transform.rotation = marker.pose.orientation;
fillTranslation(transform.transform.translation, tvecs[i]); fillTranslation(transform.transform.translation, tvecs[i]);
br_->sendTransform(transform); br_.sendTransform(transform);
} }
} }
} }
@@ -327,10 +326,10 @@ private:
return frame_id_prefix_ + std::to_string(id); return frame_id_prefix_ + std::to_string(id);
} }
void readLengthOverride(ros::NodeHandle& nh) void readLengthOverride()
{ {
std::map<std::string, double> length_override; std::map<std::string, double> length_override;
nh.getParam("length_override", length_override); nh_priv_.getParam("length_override", length_override);
for (auto const& item : length_override) { for (auto const& item : length_override) {
length_override_[std::stoi(item.first)] = item.second; length_override_[std::stoi(item.first)] = item.second;
} }

View File

@@ -58,6 +58,7 @@ typedef message_filters::sync_policies::ExactTime<Image, CameraInfo, MarkerArray
class ArucoMap : public nodelet::Nodelet { class ArucoMap : public nodelet::Nodelet {
private: private:
ros::NodeHandle nh_, nh_priv_;
ros::Publisher img_pub_, pose_pub_, markers_pub_, vis_markers_pub_; ros::Publisher img_pub_, pose_pub_, markers_pub_, vis_markers_pub_;
image_transport::Publisher debug_pub_; image_transport::Publisher debug_pub_;
message_filters::Subscriber<Image> image_sub_; message_filters::Subscriber<Image> image_sub_;
@@ -82,8 +83,8 @@ private:
public: public:
virtual void onInit() virtual void onInit()
{ {
ros::NodeHandle &nh_ = getNodeHandle(); nh_ = getNodeHandle();
ros::NodeHandle &nh_priv_ = getPrivateNodeHandle(); nh_priv_ = getPrivateNodeHandle();
image_transport::ImageTransport it_priv(nh_priv_); image_transport::ImageTransport it_priv(nh_priv_);
@@ -95,18 +96,19 @@ public:
board_->dictionary = cv::aruco::getPredefinedDictionary( board_->dictionary = cv::aruco::getPredefinedDictionary(
static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(nh_priv_.param("dictionary", 2))); static_cast<cv::aruco::PREDEFINED_DICTIONARY_NAME>(nh_priv_.param("dictionary", 2)));
camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F); camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
dist_coeffs_ = cv::Mat::zeros(8, 1, CV_64F);
std::string type, map; std::string type, map;
type = nh_priv_.param<std::string>("type", "map"); nh_priv_.param<std::string>("type", type, "map");
transform_.child_frame_id = nh_priv_.param<std::string>("frame_id", "aruco_map"); nh_priv_.param<std::string>("frame_id", transform_.child_frame_id, "aruco_map");
known_tilt_ = nh_priv_.param<std::string>("known_tilt", ""); nh_priv_.param<std::string>("known_tilt", known_tilt_, "");
auto_flip_ = nh_priv_.param("auto_flip", false); nh_priv_.param("auto_flip", auto_flip_, false);
image_width_ = nh_priv_.param("image_width" , 2000); nh_priv_.param("image_width", image_width_, 2000);
image_height_ = nh_priv_.param("image_height", 2000); nh_priv_.param("image_height", image_height_, 2000);
image_margin_ = nh_priv_.param("image_margin", 200); nh_priv_.param("image_margin", image_margin_, 200);
image_axis_ = nh_priv_.param("image_axis", true); nh_priv_.param("image_axis", image_axis_, true);
markers_parent_frame_ = nh_priv_.param<std::string>("markers/frame_id", transform_.child_frame_id); nh_priv_.param<std::string>("markers/frame_id", markers_parent_frame_, transform_.child_frame_id);
markers_frame_ = nh_priv_.param<std::string>("markers/child_frame_id_prefix", ""); nh_priv_.param<std::string>("markers/child_frame_id_prefix", markers_frame_, "");
// createStripLine(); // createStripLine();
@@ -114,7 +116,7 @@ public:
param(nh_priv_, "map", map); param(nh_priv_, "map", map);
loadMap(map); loadMap(map);
} else if (type == "gridboard") { } else if (type == "gridboard") {
createGridBoard(nh_priv_); createGridBoard();
} else { } else {
NODELET_FATAL("unknown type: %s", type.c_str()); NODELET_FATAL("unknown type: %s", type.c_str());
ros::shutdown(); ros::shutdown();
@@ -329,7 +331,7 @@ publish_debug:
NODELET_INFO("loading %s complete (%d markers)", filename.c_str(), static_cast<int>(board_->ids.size())); NODELET_INFO("loading %s complete (%d markers)", filename.c_str(), static_cast<int>(board_->ids.size()));
} }
void createGridBoard(ros::NodeHandle& nh) void createGridBoard()
{ {
NODELET_INFO("generate gridboard"); NODELET_INFO("generate gridboard");
NODELET_WARN("gridboard maps are deprecated"); NODELET_WARN("gridboard maps are deprecated");
@@ -337,15 +339,15 @@ publish_debug:
int markers_x, markers_y, first_marker; int markers_x, markers_y, first_marker;
double markers_side, markers_sep_x, markers_sep_y; double markers_side, markers_sep_x, markers_sep_y;
std::vector<int> marker_ids; std::vector<int> marker_ids;
markers_x = nh.param("markers_x", 10); nh_priv_.param<int>("markers_x", markers_x, 10);
markers_y = nh.param("markers_y", 10); nh_priv_.param<int>("markers_y", markers_y, 10);
first_marker = nh.param("first_marker", 0); nh_priv_.param<int>("first_marker", first_marker, 0);
param(nh, "markers_side", markers_side); param(nh_priv_, "markers_side", markers_side);
param(nh, "markers_sep_x", markers_sep_x); param(nh_priv_, "markers_sep_x", markers_sep_x);
param(nh, "markers_sep_y", markers_sep_y); param(nh_priv_, "markers_sep_y", markers_sep_y);
if (nh.getParam("marker_ids", marker_ids)) { if (nh_priv_.getParam("marker_ids", marker_ids)) {
if ((unsigned int)(markers_x * markers_y) != marker_ids.size()) { if ((unsigned int)(markers_x * markers_y) != marker_ids.size()) {
NODELET_FATAL("~marker_ids length should be equal to ~markers_x * ~markers_y"); NODELET_FATAL("~marker_ids length should be equal to ~markers_x * ~markers_y");
ros::shutdown(); ros::shutdown();
@@ -392,7 +394,7 @@ publish_debug:
int num_markers = board_->dictionary->bytesList.rows; int num_markers = board_->dictionary->bytesList.rows;
if (num_markers <= id) { if (num_markers <= id) {
NODELET_ERROR("Marker id %d is not in dictionary; current dictionary contains %d markers. " NODELET_ERROR("Marker id %d is not in dictionary; current dictionary contains %d markers. "
"Please see https://github.com/CopterExpress/clover/blob/master/aruco_pose/README.md#parameters for details", "Please see https://github.com/CopterExpress/clever/blob/master/aruco_pose/README.md#parameters for details",
id, num_markers); id, num_markers);
return; return;
} }

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# Copyright (C) 2018 Copter Express Technologies # Copyright (C) 2018 Copter Express Technologies
# #
@@ -13,7 +13,7 @@
Generate map file for aruco_map nodelet. Generate map file for aruco_map nodelet.
Usage: Usage:
genmap.py <length> <x> <y> <dist_x> <dist_y> [<first>] [<x0>] [<y0>] [--top-left | --bottom-left] genmap.py <length> <x> <y> <dist_x> <dist_y> [<first>] [--top-left | --bottom-left]
genmap.py (-h | --help) genmap.py (-h | --help)
Options: Options:
@@ -23,8 +23,6 @@ Options:
<dist_x> Distance between markers along X axis <dist_x> Distance between markers along X axis
<dist_y> Distance between markers along Y axis <dist_y> Distance between markers along Y axis
<first> First marker ID [default: 0] <first> First marker ID [default: 0]
<x0> X coordinate for the first marker [default: 0]
<y0> Y coordinate for the first marker [default: 0]
--top-left First marker is on top-left (default) --top-left First marker is on top-left (default)
--bottom-left First marker is on bottom-left --bottom-left First marker is on bottom-left
@@ -41,22 +39,20 @@ arguments = docopt(__doc__)
length = float(arguments['<length>']) length = float(arguments['<length>'])
first = int(arguments['<first>'] if arguments['<first>'] is not None else 0) first = int(arguments['<first>'] if arguments['<first>'] is not None else 0)
x0 = float(arguments['<x0>'] if arguments['<x0>'] is not None else 0)
y0 = float(arguments['<y0>'] if arguments['<y0>'] is not None else 0)
markers_x = int(arguments['<x>']) markers_x = int(arguments['<x>'])
markers_y = int(arguments['<y>']) markers_y = int(arguments['<y>'])
dist_x = float(arguments['<dist_x>']) dist_x = float(arguments['<dist_x>'])
dist_y = float(arguments['<dist_y>']) dist_y = float(arguments['<dist_y>'])
bottom_left = arguments['--bottom-left'] bottom_left = arguments['--bottom-left']
max_y = y0 + (markers_y - 1) * dist_y max_y = (markers_y - 1) * dist_y
print('# id\tlength\tx\ty\tz\trot_z\trot_y\trot_x') print('# id\tlength\tx\ty\tz\trot_z\trot_y\trot_x')
for y in range(markers_y): for y in range(markers_y):
for x in range(markers_x): for x in range(markers_x):
pos_x = x0 + x * dist_x pos_x = x * dist_x
pos_y = y0 + y * dist_y pos_y = y * dist_y
if not bottom_left: if not bottom_left:
pos_y = max_y - pos_y pos_y = max_y - pos_y
print('{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}'.format(first, length, pos_x, pos_y, 0, 0, 0, 0)) print('{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t'.format(first, length, pos_x, pos_y, 0, 0, 0, 0))
first += 1 first += 1

View File

@@ -35,7 +35,9 @@ static void parseCameraInfo(const sensor_msgs::CameraInfoConstPtr& cinfo, cv::Ma
for (unsigned int i = 0; i < 3; ++i) for (unsigned int i = 0; i < 3; ++i)
for (unsigned int j = 0; j < 3; ++j) for (unsigned int j = 0; j < 3; ++j)
matrix.at<double>(i, j) = cinfo->K[3 * i + j]; matrix.at<double>(i, j) = cinfo->K[3 * i + j];
dist = cv::Mat(cinfo->D, true);
for (unsigned int k = 0; k < cinfo->D.size(); k++)
dist.at<double>(k) = cinfo->D[k];
} }
inline void rotatePoint(cv::Point3f& p, cv::Point3f origin, float angle) inline void rotatePoint(cv::Point3f& p, cv::Point3f origin, float angle)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

View File

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

View File

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

View File

@@ -1,26 +1,19 @@
#!/usr/bin/env python
import sys
import unittest
import json
import rospy import rospy
import rostest import pytest
from sensor_msgs.msg import Image from sensor_msgs.msg import Image
from visualization_msgs.msg import MarkerArray as VisMarkerArray from visualization_msgs.msg import MarkerArray as VisMarkerArray
@pytest.fixture
def node():
return rospy.init_node('test_aruco_largemap', anonymous=True)
class TestArucoPose(unittest.TestCase): def test_large_map_image(node):
def setUp(self): img = rospy.wait_for_message('aruco_map/image', Image, timeout=5)
rospy.init_node('test_aruco_largemap', anonymous=True) assert img.width == 2000
assert img.height == 2000
assert img.encoding in ('mono8', 'rgb8')
def test_map_image(self): def test_large_map_visualization(node):
img = rospy.wait_for_message('aruco_map/image', Image, timeout=5) vis = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
self.assertEqual(img.width, 2000) assert len(vis.markers) == 11
self.assertEqual(img.height, 2000)
self.assertIn(img.encoding, ('mono8', 'rgb8'))
def test_map_visualization(self):
vis = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
rostest.rosrun('aruco_pose', 'test_aruco_largemap', TestArucoPose, sys.argv)

View File

@@ -9,5 +9,6 @@
<param name="map" value="$(find aruco_pose)/test/largemap.txt"/> <param name="map" value="$(find aruco_pose)/test/largemap.txt"/>
</node> </node>
<test test-name="test_aruco_pose_largemap" pkg="aruco_pose" type="largemap.py"/> <param name="test_module" value="$(find aruco_pose)/test/largemap.py"/>
<test test-name="test_node_pass" pkg="ros_pytest" type="ros_pytest_runner"/>
</launch> </launch>

View File

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

View File

@@ -1,5 +1,5 @@
{ {
"title": "Clover", "title": "Clever",
"description": "Конструктор квадрокоптера «Клевер»", "description": "Конструктор квадрокоптера «Клевер»",
"author": "Copter Express", "author": "Copter Express",
"language": "en", "language": "en",
@@ -28,7 +28,7 @@
"blank": true "blank": true
}, },
"sitemap": { "sitemap": {
"hostname": "https://clover.coex.tech" "hostname": "https://clever.coex.tech"
}, },
"toolbar": { "toolbar": {
"buttons": "buttons":
@@ -37,19 +37,19 @@
"label": "Edit page on github", "label": "Edit page on github",
"icon": "fa fa-pencil-square-o", "icon": "fa fa-pencil-square-o",
"position" : "left", "position" : "left",
"url": "https://github.com/CopterExpress/clover/edit/master/docs/{{filepath_lang}}" "url": "https://github.com/CopterExpress/clever/edit/master/docs/{{filepath_lang}}"
}, },
{ {
"label": "GitHub", "label": "GitHub",
"icon": "fa fa-github", "icon": "fa fa-github",
"position" : "left", "position" : "left",
"url": "https://github.com/CopterExpress/clover" "url": "https://github.com/CopterExpress/clever"
} }
] ]
}, },
"addcssjs": { "addcssjs": {
"css": ["../clover.css"], "css": ["../clever.css"],
"js": ["../clover.js"] "js": ["../clever.js"]
}, },
"language-picker": { "language-picker": {
"languages": [["ru", "Russian"], ["en", "English"]] "languages": [["ru", "Russian"], ["en", "English"]]

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
# Information: https://clover.coex.tech/programming # Information: https://clever.coex.tech/en/programming.html
import rospy import rospy
from clover import srv from clover import srv
@@ -15,7 +15,7 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates) set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger) land = rospy.ServiceProxy('land', Trigger)
# Take off and hover 1 m above the ground # Takeoff and hover 1 m above the ground
navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True) navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True)
# Wait for 3 seconds # Wait for 3 seconds

View File

@@ -1,37 +0,0 @@
# Information: https://clover.coex.tech/en/aruco.html
import rospy
from clover import srv
from std_srvs.srv import Trigger
rospy.init_node('flight')
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
navigate = rospy.ServiceProxy('navigate', srv.Navigate)
navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)
set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger)
# Take off and hover 1 m above the ground
navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True)
# Wait for 3 seconds
rospy.sleep(3)
# Fly 1 meter above ArUco marker 0
navigate(x=0, y=0, z=1, frame_id='aruco_0')
# Wait for 3 seconds
rospy.sleep(3)
# Fly to x=1 y=1 z=1 relative to ArUco markers map
navigate(x=1, y=1, z=1, frame_id='aruco_map')
# Wait for 3 seconds
rospy.sleep(3)
# Perform landing
land()

View File

@@ -1,4 +1,4 @@
# Information: https://clover.coex.tech/en/leds.html # Information: https://clever.coex.tech/en/leds.html
import rospy import rospy
from clover.srv import SetLEDEffect from clover.srv import SetLEDEffect

View File

@@ -1,41 +0,0 @@
# Information: https://clover.coex.tech/en/snippets.html#block-nav
import math
import rospy
from clover import srv
from std_srvs.srv import Trigger
rospy.init_node('flight')
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
navigate = rospy.ServiceProxy('navigate', srv.Navigate)
navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)
set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger)
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)
# Take off 1 meter
navigate_wait(z=1, frame_id='body', auto_arm=True)
# Fly forward 1 m
navigate_wait(x=1, frame_id='body')
# Land
land()

View File

@@ -62,10 +62,6 @@ hostnamectl set-hostname $NEW_HOSTNAME
sed -i 's/127\.0\.1\.1.*/127.0.1.1\t'${NEW_HOSTNAME}' '${NEW_HOSTNAME}'.local/g' /etc/hosts sed -i 's/127\.0\.1\.1.*/127.0.1.1\t'${NEW_HOSTNAME}' '${NEW_HOSTNAME}'.local/g' /etc/hosts
# .local (mdns) hostname added to make it accesable when wlan and ethernet interfaces are down # .local (mdns) hostname added to make it accesable when wlan and ethernet interfaces are down
echo_stamp "Enable ROS services"
systemctl enable roscore
systemctl enable clover
echo_stamp "Harware setup" echo_stamp "Harware setup"
/root/hardware_setup.sh /root/hardware_setup.sh

View File

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

View File

@@ -0,0 +1,9 @@
python3-wxgtk:
debian:
buster: [python3-wxgtk4.0]
python3-serial:
debian:
buster: [python3-serial]
python3-requests:
debian:
buster: [python3-requests]

View File

@@ -108,8 +108,6 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-software
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/examples' '/home/pi/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/examples' '/home/pi/'
# network setup # network setup
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-network.sh' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-network.sh'
# avahi setup
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/avahi-services/sftp-ssh.service' '/etc/avahi/services'
# If RPi then use a one thread to build a ROS package on RPi, else use all # If RPi then use a one thread to build a ROS package on RPi, else use all
[[ $(arch) == 'armv7l' ]] && NUMBER_THREADS=1 || NUMBER_THREADS=$(nproc --all) [[ $(arch) == 'armv7l' ]] && NUMBER_THREADS=1 || NUMBER_THREADS=$(nproc --all)
@@ -117,6 +115,7 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/avahi-s
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clover.service' '/lib/systemd/system/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clover.service' '/lib/systemd/system/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/roscore.service' '/lib/systemd/system/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/roscore.service' '/lib/systemd/system/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/melodic-rosdep-clover.yaml' '/etc/ros/rosdep/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/melodic-rosdep-clover.yaml' '/etc/ros/rosdep/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/python3.yaml' '/etc/ros/rosdep/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/ros_python_paths' '/etc/sudoers.d/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/ros_python_paths' '/etc/sudoers.d/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/pigpiod.service' '/lib/systemd/system/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/pigpiod.service' '/lib/systemd/system/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/launch.nanorc' '/usr/share/nano/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/launch.nanorc' '/usr/share/nano/'

View File

@@ -68,7 +68,8 @@ my_travis_retry() {
# TODO: 'kinetic-rosdep-clover.yaml' should add only if we use our repo? # TODO: 'kinetic-rosdep-clover.yaml' should add only if we use our repo?
echo_stamp "Init rosdep" echo_stamp "Init rosdep"
my_travis_retry rosdep init my_travis_retry rosdep init
echo "yaml file:///etc/ros/rosdep/melodic-rosdep-clover.yaml" >> /etc/ros/rosdep/sources.list.d/20-default.list echo "yaml file:///etc/ros/rosdep/melodic-rosdep-clover.yaml" > /etc/ros/rosdep/sources.list.d/30-clover.list
echo "yaml file:///etc/ros/rosdep/python3.yaml" > /etc/ros/rosdep/sources.list.d/40-python3.list
my_travis_retry rosdep update my_travis_retry rosdep update
echo_stamp "Populate rosdep for ROS user" echo_stamp "Populate rosdep for ROS user"
@@ -87,6 +88,7 @@ resolve_rosdep() {
} }
export ROS_IP='127.0.0.1' # needed for running tests export ROS_IP='127.0.0.1' # needed for running tests
export ROS_PYTHON_VERSION=3
echo_stamp "Reconfiguring Clover repository for simplier unshallowing" echo_stamp "Reconfiguring Clover repository for simplier unshallowing"
cd /home/pi/catkin_ws/src/clover cd /home/pi/catkin_ws/src/clover
@@ -95,11 +97,15 @@ git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
echo_stamp "Build and install Clover" echo_stamp "Build and install Clover"
cd /home/pi/catkin_ws cd /home/pi/catkin_ws
resolve_rosdep $(pwd) resolve_rosdep $(pwd)
my_travis_retry pip install wheel my_travis_retry pip3 install wheel
my_travis_retry pip install -r /home/pi/catkin_ws/src/clover/clover/requirements.txt my_travis_retry pip3 install -r /home/pi/catkin_ws/src/clover/clover/requirements.txt
source /opt/ros/melodic/setup.bash source /opt/ros/melodic/setup.bash
catkin_make -j2 -DCMAKE_BUILD_TYPE=Release catkin_make -j2 -DCMAKE_BUILD_TYPE=Release
echo_stamp "Enable ROS services"
systemctl enable roscore
systemctl enable clover
echo_stamp "Install clever package (for backwards compatibility)" echo_stamp "Install clever package (for backwards compatibility)"
cd /home/pi/catkin_ws/src/clover/builder/assets/clever cd /home/pi/catkin_ws/src/clover/builder/assets/clever
./setup.py install ./setup.py install
@@ -129,7 +135,7 @@ echo_stamp "Install GeographicLib datasets (needed for mavros)" \
# FIXME: Buster comes with tornado==5.1.1 but we need tornado==4.2.1 for rosbridge_suite # FIXME: Buster comes with tornado==5.1.1 but we need tornado==4.2.1 for rosbridge_suite
# (note that Python 3 will still have a more recent version) # (note that Python 3 will still have a more recent version)
pip install tornado==4.2.1 pip3 install tornado==4.2.1
echo_stamp "Running tests" echo_stamp "Running tests"
cd /home/pi/catkin_ws cd /home/pi/catkin_ws
@@ -143,6 +149,7 @@ echo_stamp "Setup ROS environment"
cat << EOF >> /home/pi/.bashrc cat << EOF >> /home/pi/.bashrc
LANG='C.UTF-8' LANG='C.UTF-8'
LC_ALL='C.UTF-8' LC_ALL='C.UTF-8'
ROS_DISTRO='melodic'
export ROS_HOSTNAME=\`hostname\`.local export ROS_HOSTNAME=\`hostname\`.local
source /opt/ros/melodic/setup.bash source /opt/ros/melodic/setup.bash
source /home/pi/catkin_ws/devel/setup.bash source /home/pi/catkin_ws/devel/setup.bash

View File

@@ -57,10 +57,6 @@ my_travis_retry() {
return $result return $result
} }
echo_stamp "Increase apt retries"
echo "APT::Acquire::Retries \"3\";" > /etc/apt/apt.conf.d/80-retries
echo_stamp "Install apt keys & repos" echo_stamp "Install apt keys & repos"
# TODO: This STDOUT consist 'OK' # TODO: This STDOUT consist 'OK'
@@ -71,7 +67,7 @@ apt-get update \
echo "deb http://packages.ros.org/ros/ubuntu buster main" > /etc/apt/sources.list.d/ros-latest.list echo "deb http://packages.ros.org/ros/ubuntu buster main" > /etc/apt/sources.list.d/ros-latest.list
echo "deb http://deb.coex.tech/opencv3 buster main" > /etc/apt/sources.list.d/opencv3.list echo "deb http://deb.coex.tech/opencv3 buster main" > /etc/apt/sources.list.d/opencv3.list
echo "deb http://deb.coex.tech/rpi-ros-melodic buster main" > /etc/apt/sources.list.d/rpi-ros-melodic.list echo "deb http://deb.coex.tech/melodic-py3 buster main" > /etc/apt/sources.list.d/rpi-ros-melodic.list
echo "deb http://deb.coex.tech/clover buster main" > /etc/apt/sources.list.d/clover.list echo "deb http://deb.coex.tech/clover buster main" > /etc/apt/sources.list.d/clover.list
echo_stamp "Update apt cache" echo_stamp "Update apt cache"
@@ -99,21 +95,21 @@ libjpeg8 \
tcpdump \ tcpdump \
ltrace \ ltrace \
libpoco-dev \ libpoco-dev \
libzbar0 \ python3-rosdep \
python-rosdep \ python3-rosinstall-generator \
python-rosinstall-generator \ python3-wstool \
python-wstool \ python3-rosinstall \
python-rosinstall \
build-essential \ build-essential \
libffi-dev \ libffi-dev \
monkey \ monkey \
pigpio python-pigpio python3-pigpio \ pigpio python-pigpio python3-pigpio \
i2c-tools \ i2c-tools \
espeak espeak-data python-espeak \ espeak espeak-data python3-espeak \
ntpdate \ ntpdate \
python-dev \ python-dev \
python3-dev \ python3-dev \
python-systemd \ python3-venv \
python3-systemd \
mjpg-streamer \ mjpg-streamer \
python3-opencv \ python3-opencv \
&& echo_stamp "Everything was installed!" "SUCCESS" \ && echo_stamp "Everything was installed!" "SUCCESS" \
@@ -137,13 +133,14 @@ pip3 --version
echo_stamp "Install and enable Butterfly (web terminal)" echo_stamp "Install and enable Butterfly (web terminal)"
echo_stamp "Workaround for tornado >= 6.0 breaking butterfly" echo_stamp "Workaround for tornado >= 6.0 breaking butterfly"
my_travis_retry pip3 install tornado==5.1.1 my_travis_retry pip3 install tornado==4.2.1
my_travis_retry pip3 install butterfly my_travis_retry pip3 install butterfly
my_travis_retry pip3 install butterfly[systemd] my_travis_retry pip3 install butterfly[systemd]
systemctl enable butterfly.socket systemctl enable butterfly.socket
echo_stamp "Install ws281x library" echo_stamp "Install ws281x library"
my_travis_retry pip install --prefer-binary rpi_ws281x my_travis_retry pip2 install --prefer-binary rpi_ws281x
my_travis_retry pip3 install --prefer-binary rpi_ws281x
echo_stamp "Setup Monkey" echo_stamp "Setup Monkey"
mv /etc/monkey/sites/default /etc/monkey/sites/default.orig mv /etc/monkey/sites/default /etc/monkey/sites/default.orig
@@ -159,13 +156,9 @@ rm -rf node-v10.15.0-linux-armv6l/
rm node-v10.15.0-linux-armv6l.tar.gz rm node-v10.15.0-linux-armv6l.tar.gz
echo_stamp "Installing ptvsd" echo_stamp "Installing ptvsd"
my_travis_retry pip install ptvsd my_travis_retry pip2 install ptvsd
my_travis_retry pip3 install ptvsd my_travis_retry pip3 install ptvsd
echo_stamp "Installing pyzbar"
my_travis_retry pip install pyzbar
my_travis_retry pip3 install pyzbar
echo_stamp "Add .vimrc" echo_stamp "Add .vimrc"
cat << EOF > /home/pi/.vimrc cat << EOF > /home/pi/.vimrc
set mouse-=a set mouse-=a

View File

@@ -18,13 +18,15 @@ echo "Run image tests"
export ROS_DISTRO='melodic' export ROS_DISTRO='melodic'
export ROS_IP='127.0.0.1' export ROS_IP='127.0.0.1'
export ROS_PYTHON_VERSION=3
source /opt/ros/melodic/setup.bash source /opt/ros/melodic/setup.bash
source /home/pi/catkin_ws/devel/setup.bash source /home/pi/catkin_ws/devel/setup.bash
cd /home/pi/catkin_ws/src/clover/builder/test/ cd /home/pi/catkin_ws/src/clover/builder/test/
./tests.sh ./tests.sh
./tests.py ./tests.py
./tests_py3.py # Disable Python 2 tests for image - we're dropping support, right?
# ./tests_py2.py
[[ $(./tests_clever.py) == "Warning: clever package is renamed to clover" ]] # test backwards compatibility [[ $(./tests_clever.py) == "Warning: clever package is renamed to clover" ]] # test backwards compatibility
echo "Move /etc/ld.so.preload back to its original position" echo "Move /etc/ld.so.preload back to its original position"

View File

@@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# Perform a "standalone install" in a Docker container # Perform a "standalone install" in a Docker container
set -e
# Step 1: Install pip # Step 1: Install pip
apt update apt update
apt install -y curl apt install -y curl

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# validate all required modules installed # validate all required modules installed
@@ -17,6 +17,8 @@ from std_srvs.srv import Trigger
from clover.srv import GetTelemetry, Navigate, NavigateGlobal, SetPosition, SetVelocity, \ from clover.srv import GetTelemetry, Navigate, NavigateGlobal, SetPosition, SetVelocity, \
SetAttitude, SetRates, SetLEDEffect SetAttitude, SetRates, SetLEDEffect
get_telemetry = rospy.ServiceProxy('get_telemetry', GetTelemetry)
import tf2_ros import tf2_ros
import tf2_geometry_msgs import tf2_geometry_msgs
@@ -26,6 +28,5 @@ from pymavlink import mavutil
import rpi_ws281x import rpi_ws281x
import pigpio import pigpio
from espeak import espeak from espeak import espeak
from pyzbar import pyzbar
print cv2.getBuildInformation() print(cv2.getBuildInformation())

View File

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

7
builder/test/tests_py2.py Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env python
# Make sure our Python 2 software is installed
import cv2
print(cv2.getBuildInformation())

View File

@@ -1,8 +0,0 @@
#!/usr/bin/env python3
# Make sure our Python 3 software is installed
import cv2
from pyzbar import pyzbar
print(cv2.getBuildInformation())

View File

@@ -30,12 +30,6 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
find_package(GeographicLib REQUIRED) find_package(GeographicLib REQUIRED)
find_package(OpenCV 3 REQUIRED
COMPONENTS
calib3d
imgproc
)
## System dependencies are found with CMake's conventions ## System dependencies are found with CMake's conventions
# find_package(Boost REQUIRED COMPONENTS system) # find_package(Boost REQUIRED COMPONENTS system)
@@ -210,7 +204,6 @@ add_dependencies(shell ${PROJECT_NAME}_generate_messages_cpp)
## Specify libraries to link a library or executable target against ## Specify libraries to link a library or executable target against
target_link_libraries(${PROJECT_NAME} target_link_libraries(${PROJECT_NAME}
${catkin_LIBRARIES} ${catkin_LIBRARIES}
${OpenCV_LIBRARIES}
) )
############# #############

View File

@@ -1,73 +0,0 @@
# `clover` ROS package
A bundle for autonomous navigation and drone control.
## Manual installation
Install ROS Melodic according to the [documentation](http://wiki.ros.org/melodic/Installation), then [create a Catkin workspace](http://wiki.ros.org/catkin/Tutorials/create_a_workspace).
Clone this repo to directory `~/catkin_ws/src/clover`:
```bash
cd ~/catkin_ws/src
git clone https://github.com/CopterExpress/clover.git clover
```
All the required ROS packages (including `mavros` and `opencv`) can be installed using `rosdep`:
```bash
cd ~/catkin_ws/
rosdep install -y --from-paths src --ignore-src
```
Build ROS packages (on memory constrained platforms you might be going to need to use `-j1` key):
```bash
cd ~/catkin_ws
catkin_make -j1
```
To complete `mavros` install you'll need to install `geographiclib` datasets:
```bash
curl https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | sudo bash
```
You may optionally install udev rules to provide `/dev/px4fmu` symlink to your PX4-based flight controller connected over USB. Copy `99-px4fmu.rules` to your `/lib/udev/rules.d` folder:
```bash
cd ~/catkin_ws/src/clover/clover/config
sudo cp 99-px4fmu.rules /lib/udev/rules.d
```
Alternatively you may change the `fcu_url` property in `mavros.launch` file to point to your flight controller device.
## Running
Enable systemd service `roscore` (if not running):
```bash
sudo systemctl enable /home/<username>/catkin_ws/src/clover/builder/assets/roscore.service
sudo systemctl start roscore
```
To start connection to SITL, use:
```bash
roslaunch clover sitl.launch
```
To start connection to the flight controller, use:
```bash
roslaunch clover clover.launch
```
> Note that the package is configured to connect to `/dev/px4fmu` by default (see [previous section](#manual-installation)). Install udev rules or specify path to your FCU device in `mavros.launch`.
Also, you can enable and start the systemd service:
```bash
sudo systemctl enable /home/<username>/catkin_ws/src/clover/deploy/clover.service
sudo systemctl start clover
```

View File

@@ -0,0 +1,45 @@
image_width: 320
image_height: 240
distortion_model: plumb_bob
camera_name: raspicam
camera_matrix:
rows: 3
cols: 3
data:
- 166.23942373073172
- 0.
- 162.19011246829268
- 0.
- 166.5880923974026
- 109.82227735714285
- 0.
- 0.
- 1.
distortion_coefficients:
rows: 1
cols: 8
data: [ 2.15356885e-01, -1.17472846e-01, -3.06197672e-04,
-1.09444025e-04, -4.53657258e-03, 5.73090623e-01,
-1.27574577e-01, -2.86125589e-02, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
0.00000000e+00, 0.00000000e+00]
rectification_matrix:
rows: 3
cols: 3
data: [1, 0, 0, 0, 1, 0, 0, 0, 1]
projection_matrix:
rows: 3
cols: 4
data:
- 166.23942373073172
- 0.
- 162.19011246829268
- 0.
- 0.
- 166.5880923974026
- 109.82227735714285
- 0.
- 0.
- 0.
- 1.
- 0.

View File

@@ -1,17 +1,17 @@
image_width: 640 image_width: 640
image_height: 480 image_height: 480
distortion_model: plumb_bob distortion_model: plumb_bob
camera_name: main_camera_optical camera_name: raspicam
camera_matrix: camera_matrix:
rows: 3 rows: 3
cols: 3 cols: 3
data: data:
- 332.47884746146343 - 332.47884746146343
- 0. - 0.
- 320.0 - 324.38022493658536
- 0. - 0.
- 333.1761847948052 - 333.1761847948052
- 240.0 - 219.6445547142857
- 0. - 0.
- 0. - 0.
- 1. - 1.

View File

@@ -1,26 +1,20 @@
<launch> <launch>
<arg name="aruco_detect" default="false"/> <!-- markers recognition enabled --> <arg name="aruco_detect" default="true"/>
<arg name="aruco_map" default="false"/> <!-- markers map recognition enabled --> <arg name="aruco_map" default="false"/>
<arg name="aruco_vpe" default="false"/> <!-- markers map positioning enabled --> <arg name="aruco_vpe" default="false"/>
<arg name="placement" default="floor"/> <!-- markers placement: floor, ceiling, unknown -->
<arg name="length" default="0.33"/> <!-- not-in-map markers length, m -->
<arg name="map" default="map.txt"/> <!-- markers map file name -->
<!-- For additional help go to https://clover.coex.tech/aruco --> <!-- For additional help go to https://clever.coex.tech/aruco -->
<!-- aruco_detect: detect aruco markers, estimate poses --> <!-- 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 nodelet_manager" output="screen" clear_params="true"> <node name="aruco_detect" pkg="nodelet" if="$(arg aruco_detect)" type="nodelet" args="load aruco_pose/aruco_detect nodelet_manager" output="screen" clear_params="true">
<remap from="image_raw" to="main_camera/image_raw"/> <remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/> <remap from="camera_info" to="main_camera/camera_info"/>
<remap from="map_markers" to="aruco_map/markers" if="$(arg aruco_map)"/> <remap from="map_markers" to="aruco_map/markers" if="$(arg aruco_map)"/>
<param name="cornerRefinementMethod" value="2"/>
<param name="estimate_poses" value="true"/> <param name="estimate_poses" value="true"/>
<param name="send_tf" value="true"/> <param name="send_tf" value="true"/>
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/> <param name="known_tilt" value="map"/>
<param name="known_tilt" value="map_flipped" if="$(eval position == 'ceiling')"/> <param name="length" value="0.33"/>
<param name="length" value="$(arg length)"/>
<!-- aruco detector parameters -->
<param name="cornerRefinementMethod" value="2"/> <!-- contour refinement -->
<param name="minMarkerPerimeterRate" value="0.075"/> <!-- 0.075 for 320x240, 0.0375 for 640x480 -->
</node> </node>
<!-- aruco_map: estimate aruco map pose --> <!-- aruco_map: estimate aruco map pose -->
@@ -28,9 +22,8 @@
<remap from="image_raw" to="main_camera/image_raw"/> <remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/> <remap from="camera_info" to="main_camera/camera_info"/>
<remap from="markers" to="aruco_detect/markers"/> <remap from="markers" to="aruco_detect/markers"/>
<param name="map" value="$(find aruco_pose)/map/$(arg map)"/> <param name="map" value="$(find aruco_pose)/map/map.txt"/>
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/> <param name="known_tilt" value="map"/>
<param name="known_tilt" value="map_flipped" if="$(eval placement == 'ceiling')"/>
<param name="image_axis" value="true"/> <param name="image_axis" value="true"/>
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/> <param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/>
<param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/> <param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/>

View File

@@ -1,15 +1,15 @@
<launch> <launch>
<arg name="fcu_conn" default="usb"/> <!-- FCU connection type: usb, uart, tcp, udp, sitl --> <arg name="fcu_conn" default="usb"/>
<arg name="fcu_ip" default="127.0.0.1"/> <!-- FCU IP adddress (if using TCP/UDP) --> <arg name="fcu_ip" default="127.0.0.1"/>
<arg name="fcu_sys_id" default="1"/> <!-- MAVLink system ID, noeditor --> <arg name="gcs_bridge" default="tcp"/>
<arg name="gcs_bridge" default="tcp"/> <!-- GCS bridge type: tcp, udp, udp-b, udp-pb --> <arg name="web_video_server" default="true"/>
<arg name="web_video_server" default="true"/> <!-- web video server enabled --> <arg name="rosbridge" default="true"/>
<arg name="rosbridge" default="true"/> <!-- rosbridge_suite enabled, noeditor --> <arg name="main_camera" default="true"/>
<arg name="main_camera" default="true"/> <!-- main camera enabled --> <arg name="optical_flow" default="true"/>
<arg name="optical_flow" default="true"/> <!-- optical flow enabled --> <arg name="aruco" default="false"/>
<arg name="rangefinder_vl53l1x" default="true"/> <!-- VL53l1X rangefinder enabled --> <arg name="rangefinder_vl53l1x" default="true"/>
<arg name="led" default="true"/> <!-- LED strip driver enabled --> <arg name="led" default="true"/>
<arg name="rc" default="true"/> <!-- support for mobile RC enabled --> <arg name="rc" default="true"/>
<arg name="shell" default="true"/> <arg name="shell" default="true"/>
<!-- log formatting --> <!-- log formatting -->
@@ -19,7 +19,6 @@
<include file="$(find clover)/launch/mavros.launch"> <include file="$(find clover)/launch/mavros.launch">
<arg name="fcu_conn" value="$(arg fcu_conn)"/> <arg name="fcu_conn" value="$(arg fcu_conn)"/>
<arg name="fcu_ip" value="$(arg fcu_ip)"/> <arg name="fcu_ip" value="$(arg fcu_ip)"/>
<arg name="fcu_sys_id" value="$(arg fcu_sys_id)"/>
<arg name="gcs_bridge" value="$(arg gcs_bridge)"/> <arg name="gcs_bridge" value="$(arg gcs_bridge)"/>
</include> </include>
@@ -30,7 +29,7 @@
</node> </node>
<!-- aruco markers --> <!-- aruco markers -->
<include file="$(find clover)/launch/aruco.launch"/> <include file="$(find clover)/launch/aruco.launch" if="$(arg aruco)"/>
<!-- optical flow --> <!-- optical flow -->
<node pkg="nodelet" type="nodelet" name="optical_flow" args="load clover/optical_flow nodelet_manager" if="$(arg optical_flow)" clear_params="true" output="screen"> <node pkg="nodelet" type="nodelet" name="optical_flow" args="load clover/optical_flow nodelet_manager" if="$(arg optical_flow)" clear_params="true" output="screen">
@@ -81,16 +80,4 @@
<!-- Shell access through ROS service --> <!-- Shell access through ROS service -->
<node name="shell" pkg="clover" type="shell" output="screen" if="$(arg shell)"/> <node name="shell" pkg="clover" type="shell" output="screen" if="$(arg shell)"/>
<!-- Update static directory -->
<node pkg="roswww_static" name="roswww_static" type="main.py" clear_params="true">
<param name="default_package" value="clover"/>
</node>
<!-- roslaunch editor parameters -->
<group ns="roslaunch_editor">
<param name="items" type="yaml" value="[clover/clover.launch, clover/main_camera.launch, clover/aruco.launch, clover/led.launch]"/>
<param name="apply_command" value="sudo systemctl restart clover"/>
<param name="hide_uncommented" value="true"/>
</group>
</launch> </launch>

View File

@@ -1,7 +1,7 @@
<launch> <launch>
<arg name="ws281x" default="true"/> <!-- LED strip driver enabled --> <arg name="ws281x" default="true"/>
<arg name="led_effect" default="true"/> <!-- LED effect API enabled --> <arg name="led_effect" default="true"/>
<arg name="led_notify" default="all"/> <!-- LED strip notifications: all, battery, none --> <arg name="led_notify" default="true"/>
<!-- For additional help go to https://clover.coex.tech/led --> <!-- For additional help go to https://clover.coex.tech/led -->
@@ -22,7 +22,7 @@
<param name="fade_period" value="0.5"/> <param name="fade_period" value="0.5"/>
<param name="rainbow_period" value="5"/> <param name="rainbow_period" value="5"/>
<!-- events effects table --> <!-- events effects table -->
<rosparam param="notify" if="$(eval led_notify == 'all')"> <rosparam param="notify" if="$(arg led_notify)">
startup: { r: 255, g: 255, b: 255 } startup: { r: 255, g: 255, b: 255 }
connected: { effect: rainbow } connected: { effect: rainbow }
disconnected: { effect: blink, r: 255, g: 50, b: 50 } disconnected: { effect: blink, r: 255, g: 50, b: 50 }
@@ -34,8 +34,5 @@
low_battery: { threshold: 3.7, effect: blink_fast, r: 255, g: 0, b: 0 } low_battery: { threshold: 3.7, effect: blink_fast, r: 255, g: 0, b: 0 }
error: { effect: flash, r: 255, g: 0, b: 0 } error: { effect: flash, r: 255, g: 0, b: 0 }
</rosparam> </rosparam>
<rosparam param="notify" if="$(eval led_notify == 'battery')">
low_battery: { threshold: 3.7, effect: blink_fast, r: 255, g: 0, b: 0 }
</rosparam>
</node> </node>
</launch> </launch>

View File

@@ -1,5 +1,5 @@
<launch> <launch>
<!-- article about camera setup: https://clover.coex.tech/camera_setup --> <!-- article about camera setup: https://clever.coex.tech/camera_frame -->
<arg name="direction_z" default="down"/> <!-- direction the camera points: down, up --> <arg name="direction_z" default="down"/> <!-- direction the camera points: down, up -->
<arg name="direction_y" default="backward"/> <!-- direction the camera cable points: backward, forward --> <arg name="direction_y" default="backward"/> <!-- direction the camera cable points: backward, forward -->
@@ -18,14 +18,14 @@
<node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet nodelet_manager" clear_params="true"> <node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet nodelet_manager" clear_params="true">
<param name="device_path" value="/dev/video0"/> <!-- v4l2 device --> <param name="device_path" value="/dev/video0"/> <!-- v4l2 device -->
<param name="frame_id" value="main_camera_optical"/> <param name="frame_id" value="main_camera_optical"/>
<param name="camera_info_url" value="file://$(find clover)/camera_info/fisheye_cam.yaml"/> <param name="camera_info_url" value="file://$(find clover)/camera_info/fisheye_cam_320.yaml"/>
<param name="rate" value="100"/> <!-- poll rate --> <param name="rate" value="100"/> <!-- poll rate -->
<param name="cv_cap_prop_fps" value="40"/> <!-- camera FPS --> <param name="cv_cap_prop_fps" value="40"/> <!-- camera FPS -->
<param name="capture_delay" value="0.02"/> <!-- approximate delay on frame retrieving --> <param name="capture_delay" value="0.02"/> <!-- approximate delay on frame retrieving -->
<param name="rescale_camera_info" value="true"/> <!-- automatically rescale camera calibration info --> <param name="rescale_camera_info" value="true"/> <!-- automatically rescale camera calibration info -->
<!-- camera resolution --> <!-- camera resolution, NOTE: camera_info file should match it -->
<param name="image_width" value="320"/> <param name="image_width" value="320"/>
<param name="image_height" value="240"/> <param name="image_height" value="240"/>
</node> </node>

View File

@@ -1,7 +1,6 @@
<launch> <launch>
<arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl --> <arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl -->
<arg name="fcu_ip" default="127.0.0.1"/> <arg name="fcu_ip" default="127.0.0.1"/>
<arg name="fcu_sys_id" default="1"/>
<arg name="gcs_bridge" default="tcp"/> <arg name="gcs_bridge" default="tcp"/>
<arg name="viz" default="true"/> <arg name="viz" default="true"/>
<arg name="respawn" default="true"/> <arg name="respawn" default="true"/>
@@ -20,9 +19,6 @@
<!-- sitl since PX4 1.9.0 --> <!-- sitl since PX4 1.9.0 -->
<param name="fcu_url" value="udp://@$(arg fcu_ip):14580" if="$(eval fcu_conn == 'sitl')"/> <param name="fcu_url" value="udp://@$(arg fcu_ip):14580" if="$(eval fcu_conn == 'sitl')"/>
<!-- set target_system_id -->
<param name="target_system_id" value="$(arg fcu_sys_id)" />
<!-- gcs bridge --> <!-- gcs bridge -->
<param name="gcs_url" value="tcp-l://0.0.0.0:5760" if="$(eval gcs_bridge == 'tcp')"/> <param name="gcs_url" value="tcp-l://0.0.0.0:5760" if="$(eval gcs_bridge == 'tcp')"/>
<param name="gcs_url" value="udp://0.0.0.0:14550@14550" if="$(eval gcs_bridge == 'udp')"/> <param name="gcs_url" value="udp://0.0.0.0:14550@14550" if="$(eval gcs_bridge == 'udp')"/>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<package format="2"> <package format="3">
<name>clover</name> <name>clover</name>
<version>0.0.1</version> <version>0.0.1</version>
<description>The Clover package</description> <description>The Clover package</description>
@@ -7,7 +7,7 @@
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer> <maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
<license>MIT</license> <license>MIT</license>
<url type="website">https://clover.coex.tech/</url> <url type="website">https://clever.coex.tech/</url>
<author email="okalachev@gmail.com">Oleg Kalachev</author> <author email="okalachev@gmail.com">Oleg Kalachev</author>
<author email="urpylka@gmail.com">Artem Smirnov</author> <author email="urpylka@gmail.com">Artem Smirnov</author>
@@ -37,8 +37,10 @@
<depend>rosbridge_server</depend> <depend>rosbridge_server</depend>
<depend>web_video_server</depend> <depend>web_video_server</depend>
<depend>tf2_web_republisher</depend> <depend>tf2_web_republisher</depend>
<depend>python-lxml</depend> <depend condition="$ROS_PYTHON_VERSION == 2">python-lxml</depend>
<exec_depend>python-pymavlink</exec_depend> <depend condition="$ROS_PYTHON_VERSION == 3">python3-lxml</depend>
<exec_depend condition="$ROS_PYTHON_VERSION == 2">python-pymavlink</exec_depend>
<exec_depend condition="$ROS_PYTHON_VERSION == 3">python-pymavlink</exec_depend>
<!-- Use test_depend for packages you need only for testing: --> <!-- Use test_depend for packages you need only for testing: -->
<!-- <test_depend>gtest</test_depend> --> <!-- <test_depend>gtest</test_depend> -->

View File

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

View File

@@ -34,7 +34,9 @@ class OpticalFlow : public nodelet::Nodelet
{ {
public: public:
OpticalFlow(): OpticalFlow():
camera_matrix_(3, 3, CV_64F) camera_matrix_(3, 3, CV_64F),
dist_coeffs_(8, 1, CV_64F),
tf_listener_(tf_buffer_)
{} {}
private: private:
@@ -50,8 +52,8 @@ private:
Mat hann_; Mat hann_;
Mat prev_, curr_; Mat prev_, curr_;
Mat camera_matrix_, dist_coeffs_; Mat camera_matrix_, dist_coeffs_;
std::unique_ptr<tf2_ros::Buffer> tf_buffer_; tf2_ros::Buffer tf_buffer_;
std::unique_ptr<tf2_ros::TransformListener> tf_listener_; tf2_ros::TransformListener tf_listener_;
bool calc_flow_gyro_; bool calc_flow_gyro_;
void onInit() void onInit()
@@ -61,14 +63,11 @@ private:
image_transport::ImageTransport it(nh); image_transport::ImageTransport it(nh);
image_transport::ImageTransport it_priv(nh_priv); image_transport::ImageTransport it_priv(nh_priv);
tf_buffer_.reset(new tf2_ros::Buffer()); nh.param<std::string>("mavros/local_position/tf/frame_id", local_frame_id_, "map");
tf_listener_.reset(new tf2_ros::TransformListener(*tf_buffer_, nh)); nh.param<std::string>("mavros/local_position/tf/child_frame_id", fcu_frame_id_, "base_link");
nh_priv.param("roi", roi_px_, 128);
local_frame_id_ = nh.param<std::string>("mavros/local_position/tf/frame_id", "map"); nh_priv.param("roi_rad", roi_rad_, 0.0);
fcu_frame_id_ = nh.param<std::string>("mavros/local_position/tf/child_frame_id", "base_link"); nh_priv.param("calc_flow_gyro", calc_flow_gyro_, false);
roi_px_ = nh_priv.param("roi", 128);
roi_rad_ = nh_priv.param("roi_rad", 0.0);
calc_flow_gyro_ = nh_priv.param("calc_flow_gyro", false);
img_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this); img_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this);
img_pub_ = it_priv.advertise("debug", 1); img_pub_ = it_priv.advertise("debug", 1);
@@ -92,7 +91,9 @@ private:
camera_matrix_.at<double>(i, j) = cinfo->K[3 * i + j]; camera_matrix_.at<double>(i, j) = cinfo->K[3 * i + j];
} }
} }
dist_coeffs_ = cv::Mat(cinfo->D, true); for (int k = 0; k < cinfo->D.size(); k++) {
dist_coeffs_.at<double>(k) = cinfo->D[k];
}
} }
void drawFlow(Mat& frame, double x, double y, double quality) const void drawFlow(Mat& frame, double x, double y, double quality) const
@@ -185,7 +186,7 @@ private:
flow_camera.vector.x = flow_y; // +y means counter-clockwise rotation around Y axis flow_camera.vector.x = flow_y; // +y means counter-clockwise rotation around Y axis
flow_camera.vector.y = -flow_x; // +x means clockwise rotation around X axis flow_camera.vector.y = -flow_x; // +x means clockwise rotation around X axis
try { try {
tf_buffer_->transform(flow_camera, flow_fcu, fcu_frame_id_); tf_buffer_.transform(flow_camera, flow_fcu, fcu_frame_id_);
} catch (const tf2::TransformException& e) { } catch (const tf2::TransformException& e) {
// transform is not available yet // transform is not available yet
return; return;
@@ -199,7 +200,7 @@ private:
try { try {
auto flow_gyro_camera = calcFlowGyro(msg->header.frame_id, prev_stamp_, msg->header.stamp); auto flow_gyro_camera = calcFlowGyro(msg->header.frame_id, prev_stamp_, msg->header.stamp);
static geometry_msgs::Vector3Stamped flow_gyro_fcu; static geometry_msgs::Vector3Stamped flow_gyro_fcu;
tf_buffer_->transform(flow_gyro_camera, flow_gyro_fcu, fcu_frame_id_); tf_buffer_.transform(flow_gyro_camera, flow_gyro_fcu, fcu_frame_id_);
flow_.integrated_xgyro = flow_gyro_fcu.vector.x; flow_.integrated_xgyro = flow_gyro_fcu.vector.x;
flow_.integrated_ygyro = flow_gyro_fcu.vector.y; flow_.integrated_ygyro = flow_gyro_fcu.vector.y;
flow_.integrated_zgyro = flow_gyro_fcu.vector.z; flow_.integrated_zgyro = flow_gyro_fcu.vector.z;
@@ -246,8 +247,8 @@ private:
geometry_msgs::Vector3Stamped calcFlowGyro(const std::string& frame_id, const ros::Time& prev, const ros::Time& curr) geometry_msgs::Vector3Stamped calcFlowGyro(const std::string& frame_id, const ros::Time& prev, const ros::Time& curr)
{ {
tf2::Quaternion prev_rot, curr_rot; tf2::Quaternion prev_rot, curr_rot;
tf2::fromMsg(tf_buffer_->lookupTransform(frame_id, local_frame_id_, prev).transform.rotation, prev_rot); tf2::fromMsg(tf_buffer_.lookupTransform(frame_id, local_frame_id_, prev).transform.rotation, prev_rot);
tf2::fromMsg(tf_buffer_->lookupTransform(frame_id, local_frame_id_, curr, ros::Duration(0.1)).transform.rotation, curr_rot); tf2::fromMsg(tf_buffer_.lookupTransform(frame_id, local_frame_id_, curr, ros::Duration(0.1)).transform.rotation, curr_rot);
geometry_msgs::Vector3Stamped flow; geometry_msgs::Vector3Stamped flow;
flow.header.frame_id = frame_id; flow.header.frame_id = frame_id;

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# coding=utf-8 # coding=utf-8
# Copyright (C) 2018 Copter Express Technologies # Copyright (C) 2018 Copter Express Technologies
@@ -138,7 +138,7 @@ def mavlink_exec(cmd, timeout=3.0):
timeout=3, timeout=3,
baudrate=0, baudrate=0,
count=len(cmd), count=len(cmd),
data=map(ord, cmd.ljust(70, '\0'))) data=[ord(c) for c in cmd.ljust(70, '\0')])
msg.pack(link) msg.pack(link)
ros_msg = mavlink.convert_to_rosmsg(msg) ros_msg = mavlink.convert_to_rosmsg(msg)
mavlink_pub.publish(ros_msg) mavlink_pub.publish(ros_msg)
@@ -210,7 +210,7 @@ def check_fcu():
is_clover_firmware = True is_clover_firmware = True
if not is_clover_firmware: if not is_clover_firmware:
failure('not running Clover PX4 firmware, https://clover.coex.tech/firmware') failure('not running Clover PX4 firmware, https://clever.coex.tech/firmware')
est = get_param('SYS_MC_EST_GROUP') est = get_param('SYS_MC_EST_GROUP')
if est == 1: if est == 1:
@@ -250,11 +250,11 @@ def check_fcu():
try: try:
battery = rospy.wait_for_message('mavros/battery', BatteryState, timeout=3) battery = rospy.wait_for_message('mavros/battery', BatteryState, timeout=3)
if not battery.cell_voltage: if not battery.cell_voltage:
failure('cell voltage is not available, https://clover.coex.tech/power') failure('cell voltage is not available, https://clever.coex.tech/power')
else: else:
cell = battery.cell_voltage[0] cell = battery.cell_voltage[0]
if cell > 4.3 or cell < 3.0: if cell > 4.3 or cell < 3.0:
failure('incorrect cell voltage: %.2f V, https://clover.coex.tech/power', cell) failure('incorrect cell voltage: %.2f V, https://clever.coex.tech/power', cell)
elif cell < 3.7: elif cell < 3.7:
failure('critically low cell voltage: %.2f V, recharge battery', cell) failure('critically low cell voltage: %.2f V, recharge battery', cell)
except rospy.ROSException: except rospy.ROSException:
@@ -609,7 +609,7 @@ def check_rangefinder():
@check('Boot duration') @check('Boot duration')
def check_boot_duration(): def check_boot_duration():
output = subprocess.check_output('systemd-analyze') output = subprocess.check_output('systemd-analyze').decode()
r = re.compile(r'([\d\.]+)s\s*$', flags=re.MULTILINE) r = re.compile(r'([\d\.]+)s\s*$', flags=re.MULTILINE)
duration = float(r.search(output).groups()[0]) duration = float(r.search(output).groups()[0])
if duration > 15: if duration > 15:
@@ -620,7 +620,7 @@ def check_boot_duration():
def check_cpu_usage(): def check_cpu_usage():
WHITELIST = 'nodelet', WHITELIST = 'nodelet',
CMD = "top -n 1 -b -i | tail -n +8 | awk '{ printf(\"%-8s\\t%-8s\\t%-8s\\n\", $1, $9, $12); }'" CMD = "top -n 1 -b -i | tail -n +8 | awk '{ printf(\"%-8s\\t%-8s\\t%-8s\\n\", $1, $9, $12); }'"
output = subprocess.check_output(CMD, shell=True) output = subprocess.check_output(CMD, shell=True).decode()
processes = output.split('\n') processes = output.split('\n')
for process in processes: for process in processes:
if not process: if not process:
@@ -636,7 +636,7 @@ def check_cpu_usage():
def check_clover_service(): def check_clover_service():
try: try:
output = subprocess.check_output('systemctl show -p ActiveState --value clover.service'.split(), output = subprocess.check_output('systemctl show -p ActiveState --value clover.service'.split(),
stderr=subprocess.STDOUT) stderr=subprocess.STDOUT).decode()
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
failure('systemctl returned %s: %s', e.returncode, e.output) failure('systemctl returned %s: %s', e.returncode, e.output)
return return
@@ -701,7 +701,7 @@ def check_preflight_status():
@check('Network') @check('Network')
def check_network(): def check_network():
ros_hostname = os.environ.get('ROS_HOSTNAME', '').strip() ros_hostname = os.environ.get('ROS_HOSTNAME').strip()
if not ros_hostname: if not ros_hostname:
failure('no ROS_HOSTNAME is set') failure('no ROS_HOSTNAME is set')
@@ -718,7 +718,7 @@ def check_network():
if ros_hostname in parts: if ros_hostname in parts:
break break
else: else:
failure('not found %s in /etc/hosts, ROS will malfunction if network interfaces are down, https://clover.coex.tech/hostname', ros_hostname) failure('not found %s in /etc/hosts, ROS will malfunction if network interfaces are down, https://clever.coex.tech/hostname', ros_hostname)
@check('RPi health') @check('RPi health')
@@ -751,7 +751,7 @@ def check_rpi_health():
# <parameter>=<value> # <parameter>=<value>
# In case of `get_throttled`, <value> is a hexadecimal number # In case of `get_throttled`, <value> is a hexadecimal number
# with some of the FLAGs OR'ed together # with some of the FLAGs OR'ed together
output = subprocess.check_output(['vcgencmd', 'get_throttled']) output = subprocess.check_output(['vcgencmd', 'get_throttled']).decode()
except OSError: except OSError:
failure('could not call vcgencmd binary; not a Raspberry Pi?') failure('could not call vcgencmd binary; not a Raspberry Pi?')
return return

View File

@@ -490,7 +490,7 @@ inline void checkState()
throw std::runtime_error("State timeout, check mavros settings"); throw std::runtime_error("State timeout, check mavros settings");
if (!state.connected) if (!state.connected)
throw std::runtime_error("No connection to FCU, https://clover.coex.tech/connection"); throw std::runtime_error("No connection to FCU, https://clever.coex.tech/connection");
} }
#define ENSURE_FINITE(var) { if (!std::isfinite(var)) throw std::runtime_error(#var " argument cannot be NaN or Inf"); } #define ENSURE_FINITE(var) { if (!std::isfinite(var)) throw std::runtime_error(#var " argument cannot be NaN or Inf"); }

View File

@@ -1,4 +1,3 @@
#!/usr/bin/env python
import rospy import rospy
import pytest import pytest
from mavros_msgs.msg import State from mavros_msgs.msg import State

View File

@@ -1,7 +1,7 @@
<h1>Clover Drone Kit Tools</h1> <h1>Clover Drone Kit Tools</h1>
<ul> <ul>
<li><a href="docs">View documentation</a> (snapshot of <a href="https://clover.coex.tech">clover.coex.tech</a>)</li> <li><a href="docs">View documentation</a> (snapshot of <a href="https://clever.coex.tech">clever.coex.tech</a>)</li>
<li><a href="" id="wvs">View image topics</a> (<code>web_video_server</code>)</li> <li><a href="" id="wvs">View image topics</a> (<code>web_video_server</code>)</li>
<li><a href="" id="butterfly">Open web terminal</a> (<code>Butterfly</code>)</li> <li><a href="" id="butterfly">Open web terminal</a> (<code>Butterfly</code>)</li>
<li><a href="viz.html">View 3D visualization</a> (<code>ros3djs</code>)</li> <li><a href="viz.html">View 3D visualization</a> (<code>ros3djs</code>)</li>
@@ -12,8 +12,8 @@
<script src="js/roslib.js"></script> <script src="js/roslib.js"></script>
<script type="text/javascript"> <script type="text/javascript">
document.querySelector("#wvs").href = location.protocol + '//' + location.hostname + ':8080'; document.querySelector("#wvs").href = location.origin + ':8080';
document.querySelector("#butterfly").href = location.protocol + '//' + location.hostname + ':57575'; document.querySelector("#butterfly").href = location.origin + ':57575';
// Determine image version // Determine image version
var ros = new ROSLIB.Ros({ url: 'ws://' + location.hostname + ':9090' }); var ros = new ROSLIB.Ros({ url: 'ws://' + location.hostname + ':9090' });

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 591 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 468 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 472 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 777 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 446 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 651 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 764 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 612 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 433 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 632 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 514 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

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