Compare commits

...

69 Commits

Author SHA1 Message Date
Oleg Kalachev
355196c127 Add some simple_offboard tests for testing with SITL 2019-08-04 19:48:06 +03:00
Oleg Kalachev
0acbf61c8f docs: fix ROS_HOSTNAME value 2019-08-04 18:36:19 +03:00
Oleg Kalachev
c5bcb165cc builder: fix ROS_HOSTNAME value in .bashrc 2019-08-04 18:34:05 +03:00
Oleg Kalachev
1538ec33f7 Rework assigning hostname and service files (#150)
* Rework assigning hostname and service files

* docs: small fix in hostname article

* Correct path to setup.bash in clever.service

* docs: correct ip in hostname article

* init_rpi: put normal and .local hostname on one line in hosts

* docs: add English version of hostname article

* clever.service: use sh instead of bash

* docs: Spellcheck english version, add note about hostname vs SSID

* clever.service: return roscore requirement back
2019-08-04 00:40:27 +03:00
Oleg Kalachev
05af14a792 Add editorconfig-checker (#149)
* Add editorconfig-checker

* Editorconfig-check fix

* Remove temporal cat
2019-08-01 23:27:04 +03:00
Arthur Golubtsov
9873c24cae docs: add information about writing new article (#148)
* docs: Add information about writing new article

* docs: Add illustrations for pull request

* docs: Trying to make markdown linter happy

* docs: Trying to make markdown linter happy 2

* docs: Trying to make markdown linter happy 3

* docs: Finall corrections of article about writing new article

* docs: Finall corrections of article about writing new article 2
2019-08-01 19:00:05 +03:00
Tenessinum
8956b3459e docs: add article about "Big Challenges 2019" (#147)
* Added article about "Big Challenges 2019"

* Fixed md errors

* Changed article name

* Update bigchallenges.md

* Update bigchallenges.md

* Added markdownlint-disable

* Added videos to Aerotaxi article
2019-08-01 17:39:02 +03:00
sfalexrog
a236574642 docs: Add links to eLinux 2019-08-01 15:43:02 +03:00
sfalexrog
45f637e9a8 docs: GPIO proofreading 2019-08-01 15:43:02 +03:00
Oleg Kalachev
f05e0cc33e docs: add English version of GPIO article 2019-08-01 15:43:02 +03:00
Oleg Kalachev
9b1d58143e aruco.launch: define image_axis parameter 2019-08-01 04:07:31 +03:00
Oleg Kalachev
60fd4f9c0f aruco_map: add image_axis parameter for drawing axis on ~image topic 2019-08-01 04:06:18 +03:00
Oleg Kalachev
47bc3b90da simple_offboard: use velocity_body topic (for supporting mavros > 0.29) 2019-07-31 20:20:12 +03:00
Oleg Kalachev
991d7c9798 docs: add note about setting aruco to true in clever.launch 2019-07-31 19:03:31 +03:00
sfalexrog
d9aa62e2dd clever: Move udev rule file, add note to readme 2019-07-31 17:19:41 +03:00
timkondratiev
c590302130 docs: edit "setup" article (#145)
* Added a new parameter setup

Empty Voltage (per cell) - 3.50V.

* Added a new parameter setup

Empty Voltage (per cell) - 3.50V.

* Deleted outdated screenshot

* Added updated screenshot

* deleted wrong screenshot

* Added updated screenshot
2019-07-31 17:04:41 +03:00
sfalexrog
32cdce47c4 simple_offboard: Use string literal as format string 2019-07-31 13:30:00 +03:00
Oleg Kalachev
99e6518c90 docs: check images size is not larger than 600K 2019-07-31 03:11:43 +03:00
Alamoris
f953b1bbd9 Changed the condition of the roll flip 2019-07-30 19:38:13 +03:00
Oleg Kalachev
f5da6bc11c docs: add more proper names to markdownlint 2019-07-30 18:03:42 +03:00
Hany Hamed
b25a58c047 docs: fix EOL sequence 2019-07-30 16:15:45 +03:00
Oleg Kalachev
977c69908e docs: small fix 2019-07-30 04:11:00 +03:00
Oleg Kalachev
22940e266f docs: little fix 2019-07-30 04:05:46 +03:00
Oleg Kalachev
df134841c3 docs: fix article name 2019-07-30 03:57:06 +03:00
Oleg Kalachev
d94d3cc88d Merge pull request #142 from CopterExpress/robocross-2019-article
[WIP] docs: add article about robocross-2019
2019-07-30 03:52:51 +03:00
Oleg Kalachev
3db1f653bc docs: add article about robocross-2019 2019-07-30 03:50:44 +03:00
Oleg Kalachev
4cf825e004 simple_offboard: obtain vehicle pose using lookupTransform 2019-07-30 00:26:43 +03:00
Oleg Kalachev
6ea693eb85 docs: while True -> while not rospy.is_shutdown() in snippets 2019-07-29 22:48:49 +03:00
Hany Hamed
76a358e3bb docs: adding the documentation of human pose estimation drone control project in the English docs of Git book (#141)
* Add Human pose estimation drone control project to the documentation in Gitbook

* Update human_pose_estimation_drone_control.md

* Update human_pose_estimation_drone_control.md

* Change the videos links

* Update human_pose_estimation_drone_control.md

Fix markdownlint warnings

* Reduce the size of poses image
2019-07-26 18:50:04 +03:00
timkondratiev
6a6f26aff7 docs: fix the 3d-scanning drone article (#140)
* Fixed broken video link

* Fixed broken embedded video and image width

* Fixed image width on mobile
2019-07-26 16:32:37 +03:00
timkondratiev
e83c284313 docs: add article about 3D-scanning drone (#139)
* Added a photo of 3d-scanning drone

* Added a new article about 3d-scanning drone.

* Updated the article

* Included the new article about 3d-scanning drone.

* Added a new article.

* Included a new article

3D-scanning drone

* Fixed bugs

* Fixed md syntax
2019-07-25 19:07:20 +03:00
Oleg Kalachev
2e4b1e2637 simple_offboard: ensure all the arguments are finite 2019-07-23 22:07:28 +03:00
Oleg Kalachev
b3e2158250 simple_offboard: quick fix for /navigate_global sometimes not working 2019-07-22 15:09:46 +03:00
Oleg Kalachev
06a79f8d66 simple_offboard: descrease timeout for local position in /navigate_global 2019-07-22 15:09:46 +03:00
sfalexrog
18fff51181 docs: Fix MAVLink links, revert global setpoint note 2019-07-21 23:26:36 +03:00
Oleg Kalachev
eae36ab22d simple_offboard: fix transform timeout in /navigate_global 2019-07-21 21:46:52 +03:00
Oleg Kalachev
82f2a2df50 simple_offboard: increase the rate of checking in waitTransform 2019-07-21 21:25:51 +03:00
Oleg Kalachev
ea072ad01a selfcheck.py: check results must not be capitalized 2019-07-20 20:52:51 +03:00
Oleg Kalachev
0801ea2b67 selfcheck.py: catch some more exceptions 2019-07-20 20:02:14 +03:00
Oleg Kalachev
f7d3122f58 www: use location.hostname for hostname 2019-07-20 19:50:43 +03:00
Oleg Kalachev
bc0e45740f Use aruco_map_ prefix for markers in the map 2019-07-20 17:30:06 +03:00
Oleg Kalachev
57c22fccf7 docs: add takeoff_wait function to snippets 2019-07-20 15:33:06 +03:00
Oleg Kalachev
09d06a517f Update Flask version 2019-07-19 23:51:42 +03:00
sfalexrog
865b999431 builder: Update kernel version 2019-07-15 19:40:56 +03:00
Oleg Kalachev
0522622cea docs: add land_wait function to snippets 2019-07-14 12:35:17 +03:00
Oleg Kalachev
7b6103b941 docs: unneeded note about Cyrillic in English version 2019-07-14 12:28:51 +03:00
sfalexrog
95a509cd60 docs: Fix repository link 2019-07-12 17:19:54 +03:00
Oleg Kalachev
4cca8b2d84 selfcheck.py: print board rotation 2019-07-11 15:57:06 +03:00
sfalexrog
33ff7ea694 docs: Sync network docs across languages 2019-07-09 16:03:38 +03:00
Hany Hamed
d958d565a7 Fix network documentation in the English gitbook (#136)
Fix network documentation in the English gitbook
2019-07-09 15:06:14 +03:00
Oleg Kalachev
96cc0c7ad9 Forgotten lines 2019-07-03 05:38:10 +03:00
Oleg Kalachev
997484cd1f aruco_map: fix includes order 2019-07-03 05:23:44 +03:00
Oleg Kalachev
48b24a5fef aruco_map: possibility to publish static transforms for map's markers 2019-07-03 05:18:44 +03:00
Oleg Kalachev
2ae5ffe09f aruco_pose: add testing markers' tf frames 2019-07-02 05:18:33 +03:00
Oleg Kalachev
da29beda47 builder: run tests after GeographicLib datasets is installed 2019-07-02 02:54:20 +03:00
Oleg Kalachev
0303e645b7 Fix typo 2019-07-02 01:26:07 +03:00
Oleg Kalachev
979c863033 Add some test for clever package 2019-07-02 01:21:49 +03:00
Oleg Kalachev
46b8390c03 Little fix 2019-07-02 01:01:07 +03:00
Oleg Kalachev
e5df1cd1a0 aruco_pose: require all the nodelets not to crash in tests 2019-07-02 00:39:47 +03:00
Oleg Kalachev
32c04ef58d Bring back lxml to package.xml 2019-07-01 23:54:26 +03:00
Oleg Kalachev
596fa9aecf Add tf2_web_republisher to package dependencies 2019-07-01 22:47:47 +03:00
Oleg Kalachev
f883825def clever.launch: support copter_visualization renamed to visualization 2019-07-01 22:24:04 +03:00
Oleg Kalachev
d65df5d1ba Improve manual installation instruction and make some related fixes 2019-07-01 22:20:15 +03:00
tinderad
a183be2708 docs: add new version of camera calibration article (#134) 2019-06-29 15:34:57 +03:00
Oleg Kalachev
9c9ac3150d Use pytest for tests (#133)
* aruco_pose: use pytest

* Use ros_pytest

* Add ros_pytest to rosdep

* aruco_pose: compare floats more roughly in pytest

* aruco_pose: rewrite all the rest tests in pytest
2019-06-28 17:40:40 +03:00
Oleg Kalachev
82649cbe20 aruco_pose: remove unused lines from tests 2019-06-26 23:02:32 +03:00
Oleg Kalachev
b542851b24 aruco_pose: fix crashing the nodelet if markers on the map are to small 2019-06-26 23:00:49 +03:00
Oleg Kalachev
65d359b5c2 aruco_pose: fix command for running tests in readme 2019-06-26 22:42:08 +03:00
Oleg Kalachev
1b6f38f8cf docs: small fix 2019-06-26 21:15:58 +03:00
95 changed files with 1897 additions and 1342 deletions

View File

@@ -13,17 +13,32 @@
"MD040": false, "MD040": false,
"MD044": { "MD044": {
"names": [ "names": [
"COEX",
"Copter Express",
"Коптер Экспресс",
"Клевер",
"MAVLink", "MAVLink",
"ROS", "ROS",
"ROS Kinetic", "ROS Kinetic",
"OpenCV", "OpenCV",
"Gazebo",
"GitHub", "GitHub",
"FPV", "FPV",
"PPM", "PPM",
"PWM", "PWM",
"Python", "Python",
"C++", "C++",
"JavaScript",
"Node.js",
"Django",
"Flask",
"HTTP",
"HTTPS",
"WebSocket",
"RPC",
"PX4", "PX4",
"ArduPilot",
"jMAVSim",
"px4.io", "px4.io",
"logs.px4.io", "logs.px4.io",
"QGroundControl", "QGroundControl",
@@ -31,16 +46,23 @@
"WireShark", "WireShark",
"FlightPlot", "FlightPlot",
"OFFBOARD", "OFFBOARD",
"ACRO",
"RPY",
"LPE", "LPE",
"EKF2", "EKF2",
"IMU",
"VPE",
"SITL", "SITL",
"PID", "PID",
"Wi-Fi", "Wi-Fi",
"Raspberry Pi", "Raspberry Pi",
"RPi", "RPi",
"Linux", "Linux",
"GNU",
"GNU/Linux",
"Windows", "Windows",
"Docker", "Docker",
"RFC",
"Travis", "Travis",
"travis-ci.org", "travis-ci.org",
"travis-ci.com", "travis-ci.com",
@@ -51,6 +73,7 @@
"Raspbian", "Raspbian",
"Raspbian Jesse", "Raspbian Jesse",
"Raspbian Stretch", "Raspbian Stretch",
"Raspbian Buster",
"Pixhawk", "Pixhawk",
"Pixracer", "Pixracer",
"Arduino", "Arduino",
@@ -59,12 +82,19 @@
"LIRC", "LIRC",
"GPIO", "GPIO",
"HC-SR04", "HC-SR04",
"RCW-0001",
"RealSense",
"NUC",
"NVIDIA",
"Jetson",
"Jetson Nano",
"STM", "STM",
"LED", "LED",
"USB", "USB",
"FAT32", "FAT32",
"uORB", "uORB",
"SSH", "SSH",
"PuTTY",
"API", "API",
"UART", "UART",
"GND", "GND",

View File

@@ -62,6 +62,7 @@ jobs:
- markdownlint -V - markdownlint -V
script: script:
- markdownlint docs - markdownlint docs
- ./check_files_size.py
- gitbook install - gitbook install
- gitbook build - gitbook build
deploy: deploy:
@@ -84,6 +85,14 @@ jobs:
- pip install GitPython PyGithub - pip install GitPython PyGithub
script: script:
- PYTHONUNBUFFERED=1 python ./gen_changelog.py - PYTHONUNBUFFERED=1 python ./gen_changelog.py
- stage: Build
name: Editorconfig-lint
language: generic
before_script:
- wget https://github.com/okalachev/editorconfig-checker/releases/download/1.2.1-disable-spaces-amount/ec-linux-amd64
- chmod +x ec-linux-amd64
script:
- ./ec-linux-amd64 -spaces-after-tabs -e "roslib.js|ros3d.js|eventemitter2.js|draw.cpp|BinUtils.swift|\.idea|apps/android/app|Assets.xcassets|test_parser_pass.txt|test_node_failure.txt"
stages: stages:
- Build - Build
- Annotate - Annotate

View File

@@ -10,7 +10,7 @@ Copter Express has implemented a large number of different autonomous drone proj
Use it to learn how to assemble, configure, pilot and program autonomous CLEVER drone. Use it to learn how to assemble, configure, pilot and program autonomous CLEVER drone.
## Preconfigured RPi 3 image ## Raspberry Pi image
**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).** **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).**
@@ -30,49 +30,70 @@ API description (in Russian) for autonomous flights is available [on GitBook](ht
## Manual installation ## Manual installation
Install ROS Kinetic according to the [documentation](http://wiki.ros.org/kinetic/Installation). Install ROS Kinetic according to the [documentation](http://wiki.ros.org/kinetic/Installation), then [create a Catkin workspace](http://wiki.ros.org/catkin/Tutorials/create_a_workspace).
Clone repo to directory `/home/pi/catkin_ws/src/clever`: Clone this repo to directory `~/catkin_ws/src/clever`:
```bash ```bash
cd ~/catkin_ws/src cd ~/catkin_ws/src
git clone https://github.com/CopterExpress/clever.git clever git clone https://github.com/CopterExpress/clever.git clever
``` ```
Build ROS packages: 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 ```bash
cd ~/catkin_ws cd ~/catkin_ws
catkin_make -j1 catkin_make -j1
``` ```
Enable systemd service `roscore` (if not enabled): To complete `mavros` install you'll need to install `geographiclib` datasets:
```bash ```bash
sudo systemctl enable /home/pi/catkin_ws/src/clever/deploy/roscore.service 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/deploy/roscore.service
sudo systemctl start roscore sudo systemctl start roscore
``` ```
Enable systemd service `clever`: To start connection to SITL, use:
```bash ```bash
sudo systemctl enable /home/pi/catkin_ws/src/clever/deploy/clever.service roslaunch clever sitl.launch
sudo systemctl start clever
``` ```
### Dependencies To start connection to the flight controller, use:
[ROS Kinetic](http://wiki.ros.org/kinetic). ```bash
roslaunch clever clever.launch
```
Necessary ROS packages: > 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`.
* `opencv3` Also, you can enable and start the systemd service:
* `mavros`
* `rosbridge_suite` ```bash
* `web_video_server` sudo systemctl enable /home/<username>/catkin_ws/src/clever/deploy/clever.service
* `cv_camera` sudo systemctl start clever
* `nodelet` ```
* `dynamic_reconfigure`
* `bondcpp`, branch `master`
* `roslint`
* `rosserial`

View File

@@ -23,19 +23,18 @@ function throttle(func, ms) {
} }
function postAppMessage(msg) { function postAppMessage(msg) {
if (window.webkit != undefined) { if (window.webkit != undefined) {
if (window.webkit.messageHandlers.appInterface != undefined) { if (window.webkit.messageHandlers.appInterface != undefined) {
window.webkit.messageHandlers.appInterface.postMessage(JSON.stringify(msg)); window.webkit.messageHandlers.appInterface.postMessage(JSON.stringify(msg));
} }
} } else if (window.appInterface != undefined) {
else if (window.appInterface != undefined) { window.appInterface.postMessage(JSON.stringify(msg));
window.appInterface.postMessage(JSON.stringify(msg)); }
}
} }
function callNativeApp(name, msg) { function callNativeApp(name, msg) {
try { try {
postAppMessage(msg); postAppMessage(msg);
return true; return true;
} catch(err) { } catch(err) {
console.warn('The native context does not exist yet'); console.warn('The native context does not exist yet');
@@ -109,12 +108,12 @@ function stickTouchStart(e) {
function stickTouchMove(e) { function stickTouchMove(e) {
for (touch of e.changedTouches) { for (touch of e.changedTouches) {
onStickTouchMove(touch); onStickTouchMove(touch);
} }
//onStickTouchMove(e.changedTouches[0]); //onStickTouchMove(e.changedTouches[0]);
rcPublishThrottled(); rcPublishThrottled();
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} }
function stickTouchEnd(e) { function stickTouchEnd(e) {
@@ -136,4 +135,4 @@ stickRight.addEventListener('touchmove', stickTouchMove);
stickLeft.addEventListener('touchstart', stickTouchStart); stickLeft.addEventListener('touchstart', stickTouchStart);
stickRight.addEventListener('touchstart', stickTouchStart); stickRight.addEventListener('touchstart', stickTouchStart);
stickLeft.addEventListener('touchend', stickTouchEnd); stickLeft.addEventListener('touchend', stickTouchEnd);
stickRight.addEventListener('touchend', stickTouchEnd); stickRight.addEventListener('touchend', stickTouchEnd);

View File

@@ -173,7 +173,7 @@ target_link_libraries(aruco_pose
# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html
## Mark executable scripts (Python etc.) for installation ## Mark executable scripts (Python etc.) for installation
## in contrast to setup.py, you can choose the destination ## in contrast to setup.py, you can choose the destination
# install(PROGRAMS # install(PROGRAMS
# scripts/my_python_script # scripts/my_python_script
# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
@@ -219,4 +219,5 @@ if (CATKIN_ENABLE_TESTING)
add_rostest(test/test_parser_pass.test) add_rostest(test/test_parser_pass.test)
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)
endif() endif()

View File

@@ -110,7 +110,7 @@ See examples in [`map`](map/) directory.
Command for running tests: Command for running tests:
```bash ```bash
rostest aruco_pose basic.test catkin_make run_tests && catkin_test_results
``` ```
## Copyright ## Copyright

View File

@@ -30,6 +30,7 @@
<depend>rostest</depend> <depend>rostest</depend>
<test_depend>image_publisher</test_depend> <test_depend>image_publisher</test_depend>
<test_depend>ros_pytest</test_depend>
<!-- The export tag contains other, unspecified, tags --> <!-- The export tag contains other, unspecified, tags -->
<export> <export>

View File

@@ -185,9 +185,13 @@ private:
// TODO: check IDs are unique // TODO: check IDs are unique
if (send_tf_) { if (send_tf_) {
transform.child_frame_id = getChildFrameId(ids[i]); transform.child_frame_id = getChildFrameId(ids[i]);
transform.transform.rotation = marker.pose.orientation;
fillTranslation(transform.transform.translation, tvecs[i]); // check if such static transform exists
br_.sendTransform(transform); if (!tf_buffer_.canTransform(transform.header.frame_id, transform.child_frame_id, transform.header.stamp)) {
transform.transform.rotation = marker.pose.orientation;
fillTranslation(transform.transform.translation, tvecs[i]);
br_.sendTransform(transform);
}
} }
} }
array_.markers.push_back(marker); array_.markers.push_back(marker);

View File

@@ -18,6 +18,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <fstream> #include <fstream>
#include <algorithm>
#include <ros/ros.h> #include <ros/ros.h>
#include <nodelet/nodelet.h> #include <nodelet/nodelet.h>
#include <pluginlib/class_list_macros.h> #include <pluginlib/class_list_macros.h>
@@ -27,6 +28,7 @@
#include <tf2_ros/buffer.h> #include <tf2_ros/buffer.h>
#include <tf2_ros/transform_listener.h> #include <tf2_ros/transform_listener.h>
#include <tf2_ros/transform_broadcaster.h> #include <tf2_ros/transform_broadcaster.h>
#include <tf2_ros/static_transform_broadcaster.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h> #include <tf2_geometry_msgs/tf2_geometry_msgs.h>
#include <message_filters/subscriber.h> #include <message_filters/subscriber.h>
#include <message_filters/synchronizer.h> #include <message_filters/synchronizer.h>
@@ -36,7 +38,6 @@
#include <sensor_msgs/Image.h> #include <sensor_msgs/Image.h>
#include <visualization_msgs/Marker.h> #include <visualization_msgs/Marker.h>
#include <visualization_msgs/MarkerArray.h> #include <visualization_msgs/MarkerArray.h>
#include <algorithm>
#include <aruco_pose/MarkerArray.h> #include <aruco_pose/MarkerArray.h>
#include <aruco_pose/Marker.h> #include <aruco_pose/Marker.h>
@@ -68,13 +69,15 @@ private:
Mat camera_matrix_, dist_coeffs_; Mat camera_matrix_, dist_coeffs_;
geometry_msgs::TransformStamped transform_; geometry_msgs::TransformStamped transform_;
geometry_msgs::PoseWithCovarianceStamped pose_; geometry_msgs::PoseWithCovarianceStamped pose_;
vector<geometry_msgs::TransformStamped> markers_transforms_;
tf2_ros::TransformBroadcaster br_; tf2_ros::TransformBroadcaster br_;
tf2_ros::StaticTransformBroadcaster static_br_;
tf2_ros::Buffer tf_buffer_; tf2_ros::Buffer tf_buffer_;
tf2_ros::TransformListener tf_listener_{tf_buffer_}; tf2_ros::TransformListener tf_listener_{tf_buffer_};
visualization_msgs::MarkerArray vis_array_; visualization_msgs::MarkerArray vis_array_;
std::string known_tilt_; std::string known_tilt_, map_, markers_frame_, markers_parent_frame_;
int image_width_, image_height_, image_margin_; int image_width_, image_height_, image_margin_;
bool auto_flip_; bool auto_flip_, image_axis_;
public: public:
virtual void onInit() virtual void onInit()
@@ -101,6 +104,9 @@ public:
nh_priv_.param("image_width", image_width_, 2000); nh_priv_.param("image_width", image_width_, 2000);
nh_priv_.param("image_height", image_height_, 2000); nh_priv_.param("image_height", image_height_, 2000);
nh_priv_.param("image_margin", image_margin_, 200); nh_priv_.param("image_margin", image_margin_, 200);
nh_priv_.param("image_axis", image_axis_, true);
nh_priv_.param<std::string>("markers/frame_id", markers_parent_frame_, transform_.child_frame_id);
nh_priv_.param<std::string>("markers/child_frame_id_prefix", markers_frame_, "");
// createStripLine(); // createStripLine();
@@ -125,6 +131,7 @@ public:
sync_.reset(new message_filters::Synchronizer<SyncPolicy>(SyncPolicy(10), image_sub_, info_sub_, markers_sub_)); sync_.reset(new message_filters::Synchronizer<SyncPolicy>(SyncPolicy(10), image_sub_, info_sub_, markers_sub_));
sync_->registerCallback(boost::bind(&ArucoMap::callback, this, _1, _2, _3)); sync_->registerCallback(boost::bind(&ArucoMap::callback, this, _1, _2, _3));
publishMarkersFrames();
publishMapImage(); publishMapImage();
vis_markers_pub_.publish(vis_array_); vis_markers_pub_.publish(vis_array_);
@@ -422,6 +429,15 @@ publish_debug:
board_->ids.push_back(id); board_->ids.push_back(id);
board_->objPoints.push_back(obj_points); board_->objPoints.push_back(obj_points);
// Add marker's static transform
if (!markers_frame_.empty()) {
geometry_msgs::TransformStamped marker_transform;
marker_transform.header.frame_id = markers_parent_frame_;
marker_transform.child_frame_id = markers_frame_ + std::to_string(id);
tf::transformTFToMsg(transform, marker_transform.transform);
markers_transforms_.push_back(marker_transform);
}
// Add visualization marker // Add visualization marker
visualization_msgs::Marker marker; visualization_msgs::Marker marker;
marker.header.frame_id = transform_.child_frame_id; marker.header.frame_id = transform_.child_frame_id;
@@ -452,6 +468,13 @@ publish_debug:
// vis_array_.markers.at(0).points.push_back(p); // vis_array_.markers.at(0).points.push_back(p);
} }
void publishMarkersFrames()
{
if (!markers_transforms_.empty()) {
static_br_.sendTransform(markers_transforms_);
}
}
void publishMapImage() void publishMapImage()
{ {
cv::Size size(image_width_, image_height_); cv::Size size(image_width_, image_height_);
@@ -459,14 +482,15 @@ publish_debug:
cv_bridge::CvImage msg; cv_bridge::CvImage msg;
if (!board_->ids.empty()) { if (!board_->ids.empty()) {
_drawPlanarBoard(board_, size, image, image_margin_, 1); _drawPlanarBoard(board_, size, image, image_margin_, 1, image_axis_);
msg.encoding = image_axis_ ? sensor_msgs::image_encodings::RGB8 : sensor_msgs::image_encodings::MONO8;
} else { } else {
// empty map // empty map
image.create(size, CV_8UC1); image.create(size, CV_8UC1);
image.setTo(cv::Scalar::all(255)); image.setTo(cv::Scalar::all(255));
msg.encoding = sensor_msgs::image_encodings::MONO8;
} }
msg.encoding = sensor_msgs::image_encodings::MONO8;
msg.image = image; msg.image = image;
img_pub_.publish(msg.toImageMsg()); img_pub_.publish(msg.toImageMsg());
} }

View File

@@ -23,12 +23,12 @@ static void _projectPoints( InputArray objectPoints,
void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginSize, void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginSize,
int borderBits) { int borderBits, bool drawAxis) {
CV_Assert(outSize.area() > 0); CV_Assert(outSize.area() > 0);
CV_Assert(marginSize >= 0); CV_Assert(marginSize >= 0);
_img.create(outSize, CV_8UC1); _img.create(outSize, drawAxis ? CV_8UC3 : CV_8UC1);
Mat out = _img.getMat(); Mat out = _img.getMat();
out.setTo(Scalar::all(255)); out.setTo(Scalar::all(255));
out.adjustROI(-marginSize, -marginSize, -marginSize, -marginSize); out.adjustROI(-marginSize, -marginSize, -marginSize, -marginSize);
@@ -87,8 +87,12 @@ void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginS
// dst_sz.width = dst_sz.height = std::min(dst_sz.width, dst_sz.height); //marker should be square // dst_sz.width = dst_sz.height = std::min(dst_sz.width, dst_sz.height); //marker should be square
double diag = std::round(std::hypot(dst_sz.width, dst_sz.height)); double diag = std::round(std::hypot(dst_sz.width, dst_sz.height));
int side = std::round(diag / std::sqrt(2)); int side = std::round(diag / std::sqrt(2));
side = std::max(side, 10);
dictionary.drawMarker(_board->ids[m], side, marker, borderBits); dictionary.drawMarker(_board->ids[m], side, marker, borderBits);
if (drawAxis) {
cvtColor(marker, marker, COLOR_GRAY2RGB);
}
// interpolate tiny marker to marker position in markerZone // interpolate tiny marker to marker position in markerZone
inCorners[0] = Point2f(-0.5f, -0.5f); inCorners[0] = Point2f(-0.5f, -0.5f);
@@ -100,74 +104,81 @@ void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginS
warpAffine(marker, out, transformation, out.size(), INTER_LINEAR, warpAffine(marker, out, transformation, out.size(), INTER_LINEAR,
BORDER_TRANSPARENT); BORDER_TRANSPARENT);
} }
// draw axis
if (drawAxis) {
Size wholeSize; Point ofs;
out.locateROI(wholeSize, ofs);
auto out_copy = _img.getMat();
cv::Point center(ofs.x - minX / sizeX * float(out.cols), ofs.y + out.rows + minY / sizeY * float(out.rows));
line(out_copy, center, center + Point(300, 0), Scalar(255, 0, 0), 10); // x axis
line(out_copy, center, center + Point(0, -300), Scalar(0, 255, 0), 10); // y axis
line(out_copy, center, center + Point(-200, 200), Scalar(0, 0, 255), 10); // z axis
}
} }
/* Draw a (potentially partially visible) line. */ /* Draw a (potentially partially visible) line. */
static void linePartial(InputOutputArray image, Point3f p1, Point3f p2, const Scalar& color, static void linePartial(InputOutputArray image, Point3f p1, Point3f p2, const Scalar& color,
int thickness = 1, int lineType = LINE_8, int shift = 0) int thickness = 1, int lineType = LINE_8, int shift = 0)
{ {
// If both points are behind the screen, don't draw anything // If both points are behind the screen, don't draw anything
if (p1.z <= 0 && p2.z <= 0) if (p1.z <= 0 && p2.z <= 0) {
{ return;
return; }
} Point2f p1p{p1.x, p1.y};
Point2f p1p{p1.x, p1.y}; Point2f p2p{p2.x, p2.y};
Point2f p2p{p2.x, p2.y}; // If points are on the different sides of the plane, compute intersection point
// If points are on the different sides of the plane, compute intersection point if (p1.z * p2.z < 0) {
if (p1.z * p2.z < 0) // Compute intersection point with the screen
{ // We denote alpha as such:
// Compute intersection point with the screen // xi = (1 - alpha) * x1 + alpha * x2
// We denote alpha as such: // yi = (1 - alpha) * y1 + alpha * y2
// xi = (1 - alpha) * x1 + alpha * x2 // zi = (1 - alpha) * z1 + alpha * z2 = 0
// yi = (1 - alpha) * y1 + alpha * y2 // Thus, alpha can be expressed as
// zi = (1 - alpha) * z1 + alpha * z2 = 0 // alpha = z1 / (z1 - z2)
// Thus, alpha can be expressed as float alpha = p1.z / (p1.z - p2.z);
// alpha = z1 / (z1 - z2) Point2f pi{(1 - alpha) * p1.x + alpha * p2.x, (1 - alpha) * p1.y + alpha * p2.y};
float alpha = p1.z / (p1.z - p2.z); // Now, if z1 is negative, we draw the line from (xi, yi) to (x2, y2), else we draw from (x1, y1) to (xi, yi)
Point2f pi{(1 - alpha) * p1.x + alpha * p2.x, (1 - alpha) * p1.y + alpha * p2.y}; if (p1.z < 0) {
// Now, if z1 is negative, we draw the line from (xi, yi) to (x2, y2), else we draw from (x1, y1) to (xi, yi) p1p = pi;
if (p1.z < 0) } else {
{ p2p = pi;
p1p = pi; }
} }
else line(image, p1p, p2p, color, thickness, lineType, shift);
{
p2p = pi;
}
}
line(image, p1p, p2p, color, thickness, lineType, shift);
} }
void _drawAxis(InputOutputArray _image, InputArray _cameraMatrix, InputArray _distCoeffs, void _drawAxis(InputOutputArray _image, InputArray _cameraMatrix, InputArray _distCoeffs,
InputArray _rvec, InputArray _tvec, float length) { InputArray _rvec, InputArray _tvec, float length) {
CV_Assert(_image.getMat().total() != 0 && CV_Assert(_image.getMat().total() != 0 &&
(_image.getMat().channels() == 1 || _image.getMat().channels() == 3)); (_image.getMat().channels() == 1 || _image.getMat().channels() == 3));
CV_Assert(length > 0); CV_Assert(length > 0);
// project axis points // project axis points
std::vector< Point3f > axisPoints; std::vector<Point3f> axisPoints;
axisPoints.push_back(Point3f(0, 0, 0)); axisPoints.push_back(Point3f(0, 0, 0));
axisPoints.push_back(Point3f(length, 0, 0)); axisPoints.push_back(Point3f(length, 0, 0));
axisPoints.push_back(Point3f(0, length, 0)); axisPoints.push_back(Point3f(0, length, 0));
axisPoints.push_back(Point3f(0, 0, length)); axisPoints.push_back(Point3f(0, 0, length));
std::vector< Point3f > imagePointsZ; std::vector<Point3f> imagePointsZ;
_projectPoints(axisPoints, _rvec, _tvec, _cameraMatrix, _distCoeffs, imagePointsZ); _projectPoints(axisPoints, _rvec, _tvec, _cameraMatrix, _distCoeffs, imagePointsZ);
// draw axis lines // draw axis lines
linePartial(_image, imagePointsZ[0], imagePointsZ[1], Scalar(0, 0, 255), 3); linePartial(_image, imagePointsZ[0], imagePointsZ[1], Scalar(0, 0, 255), 3);
linePartial(_image, imagePointsZ[0], imagePointsZ[2], Scalar(0, 255, 0), 3); linePartial(_image, imagePointsZ[0], imagePointsZ[2], Scalar(0, 255, 0), 3);
linePartial(_image, imagePointsZ[0], imagePointsZ[3], Scalar(255, 0, 0), 3); linePartial(_image, imagePointsZ[0], imagePointsZ[3], Scalar(255, 0, 0), 3);
} }
static CvMat _cvMat(const cv::Mat& m) static CvMat _cvMat(const cv::Mat& m)
{ {
CvMat self; CvMat self;
CV_DbgAssert(m.dims <= 2); CV_DbgAssert(m.dims <= 2);
self = cvMat(m.rows, m.dims == 1 ? 1 : m.cols, m.type(), m.data); self = cvMat(m.rows, m.dims == 1 ? 1 : m.cols, m.type(), m.data);
self.step = (int)m.step[0]; self.step = (int)m.step[0];
self.type = (self.type & ~cv::Mat::CONTINUOUS_FLAG) | (m.flags & cv::Mat::CONTINUOUS_FLAG); self.type = (self.type & ~cv::Mat::CONTINUOUS_FLAG) | (m.flags & cv::Mat::CONTINUOUS_FLAG);
return self; return self;
} }
static void _projectPoints( InputArray _opoints, static void _projectPoints( InputArray _opoints,
@@ -179,47 +190,47 @@ static void _projectPoints( InputArray _opoints,
OutputArray _jacobian, OutputArray _jacobian,
double aspectRatio ) double aspectRatio )
{ {
Mat opoints = _opoints.getMat(); Mat opoints = _opoints.getMat();
int npoints = opoints.checkVector(3), depth = opoints.depth(); int npoints = opoints.checkVector(3), depth = opoints.depth();
CV_Assert(npoints >= 0 && (depth == CV_32F || depth == CV_64F)); CV_Assert(npoints >= 0 && (depth == CV_32F || depth == CV_64F));
CvMat dpdrot, dpdt, dpdf, dpdc, dpddist; CvMat dpdrot, dpdt, dpdf, dpdc, dpddist;
CvMat *pdpdrot=0, *pdpdt=0, *pdpdf=0, *pdpdc=0, *pdpddist=0; CvMat *pdpdrot = 0, *pdpdt = 0, *pdpdf = 0, *pdpdc = 0, *pdpddist = 0;
CV_Assert( _ipoints.needed() ); CV_Assert(_ipoints.needed());
_ipoints.create(npoints, 1, CV_MAKETYPE(depth, 3), -1, true); _ipoints.create(npoints, 1, CV_MAKETYPE(depth, 3), -1, true);
Mat imagePoints = _ipoints.getMat(); Mat imagePoints = _ipoints.getMat();
CvMat c_imagePoints = _cvMat(imagePoints); CvMat c_imagePoints = _cvMat(imagePoints);
CvMat c_objectPoints = _cvMat(opoints); CvMat c_objectPoints = _cvMat(opoints);
Mat cameraMatrix = _cameraMatrix.getMat(); Mat cameraMatrix = _cameraMatrix.getMat();
Mat rvec = _rvec.getMat(), tvec = _tvec.getMat(); Mat rvec = _rvec.getMat(), tvec = _tvec.getMat();
CvMat c_cameraMatrix = _cvMat(cameraMatrix); CvMat c_cameraMatrix = _cvMat(cameraMatrix);
CvMat c_rvec = _cvMat(rvec), c_tvec = _cvMat(tvec); CvMat c_rvec = _cvMat(rvec), c_tvec = _cvMat(tvec);
double dc0buf[5]={0}; double dc0buf[5] = {0};
Mat dc0(5,1,CV_64F,dc0buf); Mat dc0(5, 1, CV_64F, dc0buf);
Mat distCoeffs = _distCoeffs.getMat(); Mat distCoeffs = _distCoeffs.getMat();
if( distCoeffs.empty() ) if (distCoeffs.empty())
distCoeffs = dc0; distCoeffs = dc0;
CvMat c_distCoeffs = _cvMat(distCoeffs); CvMat c_distCoeffs = _cvMat(distCoeffs);
int ndistCoeffs = distCoeffs.rows + distCoeffs.cols - 1; int ndistCoeffs = distCoeffs.rows + distCoeffs.cols - 1;
Mat jacobian; Mat jacobian;
if( _jacobian.needed() ) if (_jacobian.needed())
{ {
_jacobian.create(npoints*2, 3+3+2+2+ndistCoeffs, CV_64F); _jacobian.create(npoints * 2, 3 + 3 + 2 + 2 + ndistCoeffs, CV_64F);
jacobian = _jacobian.getMat(); jacobian = _jacobian.getMat();
pdpdrot = &(dpdrot = _cvMat(jacobian.colRange(0, 3))); pdpdrot = &(dpdrot = _cvMat(jacobian.colRange(0, 3)));
pdpdt = &(dpdt = _cvMat(jacobian.colRange(3, 6))); pdpdt = &(dpdt = _cvMat(jacobian.colRange(3, 6)));
pdpdf = &(dpdf = _cvMat(jacobian.colRange(6, 8))); pdpdf = &(dpdf = _cvMat(jacobian.colRange(6, 8)));
pdpdc = &(dpdc = _cvMat(jacobian.colRange(8, 10))); pdpdc = &(dpdc = _cvMat(jacobian.colRange(8, 10)));
pdpddist = &(dpddist = _cvMat(jacobian.colRange(10, 10+ndistCoeffs))); pdpddist = &(dpddist = _cvMat(jacobian.colRange(10, 10 + ndistCoeffs)));
} }
_cvProjectPoints2( &c_objectPoints, &c_rvec, &c_tvec, &c_cameraMatrix, &c_distCoeffs, _cvProjectPoints2(&c_objectPoints, &c_rvec, &c_tvec, &c_cameraMatrix, &c_distCoeffs,
&c_imagePoints, pdpdrot, pdpdt, pdpdf, pdpdc, pdpddist, aspectRatio ); &c_imagePoints, pdpdrot, pdpdt, pdpdf, pdpdc, pdpddist, aspectRatio);
} }
namespace _detail namespace _detail

View File

@@ -3,6 +3,7 @@
#include <opencv2/opencv.hpp> #include <opencv2/opencv.hpp>
#include <opencv2/aruco.hpp> #include <opencv2/aruco.hpp>
void _drawPlanarBoard(cv::aruco::Board *_board, cv::Size outSize, cv::OutputArray _img, int marginSize, int borderBits); void _drawPlanarBoard(cv::aruco::Board *_board, cv::Size outSize, cv::OutputArray _img,
int marginSize, int borderBits, bool drawAxis); // editorconfig-checker-disable-line
void _drawAxis(cv::InputOutputArray image, cv::InputArray cameraMatrix, cv::InputArray distCoeffs, void _drawAxis(cv::InputOutputArray image, cv::InputArray cameraMatrix, cv::InputArray distCoeffs,
cv::InputArray rvec, cv::InputArray tvec, float length); cv::InputArray rvec, cv::InputArray tvec, float length); // editorconfig-checker-disable-line

View File

@@ -30,8 +30,7 @@ static void param(ros::NodeHandle nh, const std::string& param_name, T& param_va
} }
} }
static void parseCameraInfo(const sensor_msgs::CameraInfoConstPtr& cinfo, static void parseCameraInfo(const sensor_msgs::CameraInfoConstPtr& cinfo, cv::Mat& matrix, cv::Mat& dist)
cv::Mat& matrix, cv::Mat& dist)
{ {
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)

View File

@@ -1,101 +1,149 @@
#!/usr/bin/env python
import sys
import unittest
import json
import rospy import rospy
import rostest import pytest
import tf2_ros
import tf2_geometry_msgs
from geometry_msgs.msg import PoseWithCovarianceStamped from geometry_msgs.msg import PoseWithCovarianceStamped
from sensor_msgs.msg import Image from sensor_msgs.msg import Image
from aruco_pose.msg import MarkerArray from aruco_pose.msg import MarkerArray
from visualization_msgs.msg import MarkerArray as VisMarkerArray from visualization_msgs.msg import MarkerArray as VisMarkerArray
class TestArucoPose(unittest.TestCase): @pytest.fixture
def setUp(self): def node():
rospy.init_node('test_aruco_detect', anonymous=True) return rospy.init_node('aruco_pose_test', anonymous=True)
def test_markers(self): @pytest.fixture
markers = rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=5) def tf_buffer():
self.assertEqual(len(markers.markers), 4) buf = tf2_ros.Buffer()
self.assertEqual(markers.header.frame_id, 'main_camera_optical') tf2_ros.TransformListener(buf)
return buf
self.assertEqual(markers.markers[0].id, 2) def approx(expected):
self.assertAlmostEqual(markers.markers[0].length, 0.33, places=4) return pytest.approx(expected, abs=1e-4) # compare floats more roughly
self.assertAlmostEqual(markers.markers[0].pose.position.x, 0.36706567854, places=4)
self.assertAlmostEqual(markers.markers[0].pose.position.y, 0.290484516644, places=4)
self.assertAlmostEqual(markers.markers[0].pose.position.z, 2.18787602301, places=4)
self.assertAlmostEqual(markers.markers[0].pose.orientation.x, 0.993997406299, places=4)
self.assertAlmostEqual(markers.markers[0].pose.orientation.y, -0.00532003481626, places=4)
self.assertAlmostEqual(markers.markers[0].pose.orientation.z, -0.107390951553, places=4)
self.assertAlmostEqual(markers.markers[0].pose.orientation.w, 0.0201999263402, places=4)
self.assertAlmostEqual(markers.markers[0].c1.x, 415.557739258, places=4)
self.assertAlmostEqual(markers.markers[0].c1.y, 335.557739258, places=4)
self.assertAlmostEqual(markers.markers[0].c2.x, 509.442260742, places=4)
self.assertAlmostEqual(markers.markers[0].c2.y, 335.557739258, places=4)
self.assertAlmostEqual(markers.markers[0].c3.x, 509.442260742, places=4)
self.assertAlmostEqual(markers.markers[0].c3.y, 429.442260742, places=4)
self.assertAlmostEqual(markers.markers[0].c4.x, 415.557739258, places=4)
self.assertAlmostEqual(markers.markers[0].c4.y, 429.442260742, places=4)
self.assertEqual(markers.markers[3].id, 3) def test_markers(node):
self.assertAlmostEqual(markers.markers[3].length, 0.1, places=4) markers = rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=5)
self.assertAlmostEqual(markers.markers[3].pose.position.x, -0.1805169666, places=4) assert len(markers.markers) == 5
self.assertAlmostEqual(markers.markers[3].pose.position.y, -0.200697302327, places=4) assert markers.header.frame_id == 'main_camera_optical'
self.assertAlmostEqual(markers.markers[3].pose.position.z, 0.585767514823, places=4)
self.assertAlmostEqual(markers.markers[3].pose.orientation.x, -0.961738074009, places=4)
self.assertAlmostEqual(markers.markers[3].pose.orientation.y, -0.0375180244707, places=4)
self.assertAlmostEqual(markers.markers[3].pose.orientation.z, -0.0115387773672, places=4)
self.assertAlmostEqual(markers.markers[3].pose.orientation.w, 0.271144115664, places=4)
self.assertAlmostEqual(markers.markers[3].c1.x, 129.557723999, places=4)
self.assertAlmostEqual(markers.markers[3].c1.y, 49.557723999, places=4)
self.assertAlmostEqual(markers.markers[3].c2.x, 223.442276001, places=4)
self.assertAlmostEqual(markers.markers[3].c2.y, 49.557723999, places=4)
self.assertAlmostEqual(markers.markers[3].c3.x, 223.442276001, places=4)
self.assertAlmostEqual(markers.markers[3].c3.y, 143.442276001, places=4)
self.assertAlmostEqual(markers.markers[3].c4.x, 129.557723999, places=4)
self.assertAlmostEqual(markers.markers[3].c4.y, 143.442276001, places=4)
self.assertEqual(markers.markers[1].id, 1) assert markers.markers[0].id == 2
self.assertAlmostEqual(markers.markers[1].length, 0.33, places=4) assert markers.markers[0].length == approx(0.33)
self.assertEqual(markers.markers[2].id, 4) assert markers.markers[0].pose.position.x == approx(0.36706567854)
self.assertAlmostEqual(markers.markers[2].length, 0.33, places=4) assert markers.markers[0].pose.position.y == approx(0.290484516644)
assert markers.markers[0].pose.position.z == approx(2.18787602301)
assert markers.markers[0].pose.orientation.x == approx(0.993997406299)
assert markers.markers[0].pose.orientation.y == approx(-0.00532003481626)
assert markers.markers[0].pose.orientation.z == approx(-0.107390951553)
assert markers.markers[0].pose.orientation.w == approx(0.0201999263402)
assert markers.markers[0].c1.x == approx(415.557739258)
assert markers.markers[0].c1.y == approx(335.557739258)
assert markers.markers[0].c2.x == approx(509.442260742)
assert markers.markers[0].c2.y == approx(335.557739258)
assert markers.markers[0].c3.x == approx(509.442260742)
assert markers.markers[0].c3.y == approx(429.442260742)
assert markers.markers[0].c4.x == approx(415.557739258)
assert markers.markers[0].c4.y == approx(429.442260742)
def test_visualization(self): assert markers.markers[4].id == 3
vis = rospy.wait_for_message('aruco_detect/visualization', VisMarkerArray, timeout=5) assert markers.markers[4].length == approx(0.1)
self.assertEqual(len(vis.markers), 9) assert markers.markers[4].pose.position.x == approx(-0.1805169666)
assert markers.markers[4].pose.position.y == approx(-0.200697302327)
assert markers.markers[4].pose.position.z == approx(0.585767514823)
assert markers.markers[4].pose.orientation.x == approx(-0.961738074009)
assert markers.markers[4].pose.orientation.y == approx(-0.0375180244707)
assert markers.markers[4].pose.orientation.z == approx(-0.0115387773672)
assert markers.markers[4].pose.orientation.w == approx(0.271144115664)
assert markers.markers[4].c1.x == approx(129.557723999)
assert markers.markers[4].c1.y == approx(49.557723999)
assert markers.markers[4].c2.x == approx(223.442276001)
assert markers.markers[4].c2.y == approx(49.557723999)
assert markers.markers[4].c3.x == approx(223.442276001)
assert markers.markers[4].c3.y == approx(143.442276001)
assert markers.markers[4].c4.x == approx(129.557723999)
assert markers.markers[4].c4.y == approx(143.442276001)
def test_debug(self): assert markers.markers[1].id == 1
img = rospy.wait_for_message('aruco_detect/debug', Image, timeout=5) assert markers.markers[1].length == approx(0.33)
self.assertEqual(img.width, 640) assert markers.markers[3].id == 4
self.assertEqual(img.height, 480) assert markers.markers[3].length == approx(0.33)
self.assertEqual(img.header.frame_id, 'main_camera_optical')
def test_map(self): assert markers.markers[2].id == 100
pose = rospy.wait_for_message('aruco_map/pose', PoseWithCovarianceStamped, timeout=5) assert markers.markers[2].length == approx(0.33)
self.assertEqual(pose.header.frame_id, 'main_camera_optical') assert markers.markers[2].pose.position.x == approx(-1.37600105389)
self.assertAlmostEqual(pose.pose.pose.position.x, -0.629167753342, places=4) assert markers.markers[2].pose.position.y == approx(-0.323028530991)
self.assertAlmostEqual(pose.pose.pose.position.y, 0.293822650809, places=4) assert markers.markers[2].pose.position.z == approx(2.94611272668)
self.assertAlmostEqual(pose.pose.pose.position.z, 2.12641343155, places=4) assert markers.markers[2].pose.orientation.x == approx(-0.955543925678)
self.assertAlmostEqual(pose.pose.pose.orientation.x, -0.998383794799, places=4) assert markers.markers[2].pose.orientation.y == approx(0.0458801909197)
self.assertAlmostEqual(pose.pose.pose.orientation.y, -5.20919098575e-06, places=4) assert markers.markers[2].pose.orientation.z == approx(-0.249604946264)
self.assertAlmostEqual(pose.pose.pose.orientation.z, -0.0300861070302, places=4) assert markers.markers[2].pose.orientation.w == approx(-0.150093920537)
self.assertAlmostEqual(pose.pose.pose.orientation.w, 0.0482143590507, places=4) assert markers.markers[2].c1.x == approx(52.557723999)
assert markers.markers[2].c1.y == approx(205.557723999)
assert markers.markers[2].c2.x == approx(113.442276001)
assert markers.markers[2].c2.y == approx(205.557723999)
assert markers.markers[2].c3.x == approx(113.442276001)
assert markers.markers[2].c3.y == approx(265.442260742)
assert markers.markers[2].c4.x == approx(52.557723999)
assert markers.markers[2].c4.y == approx(265.442260742)
def test_map_image(self): def test_markers_frames(node, tf_buffer):
img = rospy.wait_for_message('aruco_map/image', Image, timeout=5) marker_2 = tf_buffer.lookup_transform('main_camera_optical', 'aruco_2', rospy.Time(), rospy.Duration(5))
self.assertEqual(img.width, 2000) assert marker_2.transform.translation.x == approx(0.36706567854)
self.assertEqual(img.height, 2000) assert marker_2.transform.translation.y == approx(0.290484516644)
self.assertEqual(img.encoding, 'mono8') assert marker_2.transform.translation.z == approx(2.18787602301)
assert marker_2.transform.rotation.x == approx(0.993997406299)
assert marker_2.transform.rotation.y == approx(-0.00532003481626)
assert marker_2.transform.rotation.z == approx(-0.107390951553)
assert marker_2.transform.rotation.w == approx(0.0201999263402)
def test_map_visualization(self): def test_map_markers_frames(node, tf_buffer):
vis = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5) stamp = rospy.get_rostime()
timeout = rospy.Duration(5)
def test_map_debug(self): marker_1 = tf_buffer.lookup_transform('aruco_map', 'aruco_in_map_1', stamp, timeout)
img = rospy.wait_for_message('aruco_map/debug', Image, timeout=5) assert marker_1.transform.translation.x == approx(0)
assert marker_1.transform.translation.y == approx(0)
assert marker_1.transform.translation.z == approx(0)
# def test_transforms(self): marker_4 = tf_buffer.lookup_transform('aruco_map', 'aruco_in_map_4', stamp, timeout)
# pass assert marker_4.transform.translation.x == approx(1)
assert marker_4.transform.translation.y == approx(1)
assert marker_4.transform.translation.z == approx(0)
marker_12 = tf_buffer.lookup_transform('aruco_map', 'aruco_in_map_12', stamp, timeout)
assert marker_12.transform.translation.x == approx(0.2)
assert marker_12.transform.translation.y == approx(0.5)
assert marker_12.transform.translation.z == approx(0)
rostest.rosrun('aruco_pose', 'test_aruco_detect', TestArucoPose, sys.argv) def test_visualization(node):
vis = rospy.wait_for_message('aruco_detect/visualization', VisMarkerArray, timeout=5)
assert len(vis.markers) == 11
def test_debug(node):
img = rospy.wait_for_message('aruco_detect/debug', Image, timeout=5)
assert img.width == 640
assert img.height == 480
assert img.header.frame_id == 'main_camera_optical'
def test_map(node):
pose = rospy.wait_for_message('aruco_map/pose', PoseWithCovarianceStamped, timeout=5)
assert pose.header.frame_id == 'main_camera_optical'
assert pose.pose.pose.position.x == approx(-0.629167753342)
assert pose.pose.pose.position.y == approx(0.293822650809)
assert pose.pose.pose.position.z == approx(2.12641343155)
assert pose.pose.pose.orientation.x == approx(-0.998383794799)
assert pose.pose.pose.orientation.y == approx(-5.20919098575e-06)
assert pose.pose.pose.orientation.z == approx(-0.0300861070302)
assert pose.pose.pose.orientation.w == approx(0.0482143590507)
def test_map_image(node):
img = rospy.wait_for_message('aruco_map/image', Image, timeout=5)
assert img.width == 2000
assert img.height == 2000
assert img.encoding == 'mono8'
def test_map_visualization(node):
vis = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
def test_map_debug(node):
img = rospy.wait_for_message('aruco_map/debug', Image, timeout=5)

View File

@@ -5,23 +5,27 @@
<param name="camera_info_url" value="file://$(find aruco_pose)/test/camera_info.yaml" /> <param name="camera_info_url" value="file://$(find aruco_pose)/test/camera_info.yaml" />
</node> </node>
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager"/> <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"> <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="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"/>
<param name="length" value="0.33"/> <param name="length" value="0.33"/>
<param name="length_override/3" value="0.1"/> <param name="length_override/3" value="0.1"/>
<param name="estimate_poses" value="true"/> <param name="estimate_poses" value="true"/>
<param name="send_tf" value="true"/>
</node> </node>
<node name="aruco_map" pkg="nodelet" type="nodelet" args="load aruco_pose/aruco_map nodelet_manager" clear_params="true"> <node name="aruco_map" pkg="nodelet" type="nodelet" args="load aruco_pose/aruco_map nodelet_manager" clear_params="true" required="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="markers" to="aruco_detect/markers"/> <remap from="markers" to="aruco_detect/markers"/>
<param name="type" value="map"/> <param name="type" value="map"/>
<param name="map" value="$(find aruco_pose)/test/basic.txt"/> <param name="map" value="$(find aruco_pose)/test/basic.txt"/>
<param name="markers/frame_id" value="aruco_map"/>
<param name="markers/child_frame_id_prefix" value="aruco_in_map_"/>
</node> </node>
<test test-name="test_aruco_pose" pkg="aruco_pose" type="basic.py"/> <param name="test_module" value="$(find aruco_pose)/test/basic.py"/>
<test test-name="aruco_pose_test" pkg="ros_pytest" type="ros_pytest_runner"/>
</launch> </launch>

26
aruco_pose/test/largemap.py Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env python
import sys
import unittest
import json
import rospy
import rostest
from sensor_msgs.msg import Image
from visualization_msgs.msg import MarkerArray as VisMarkerArray
class TestArucoPose(unittest.TestCase):
def setUp(self):
rospy.init_node('test_aruco_largemap', anonymous=True)
def test_map_image(self):
img = rospy.wait_for_message('aruco_map/image', Image, timeout=5)
self.assertEqual(img.width, 2000)
self.assertEqual(img.height, 2000)
self.assertEqual(img.encoding, 'mono8')
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

@@ -0,0 +1,13 @@
<launch>
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager" required="true"/>
<node name="aruco_map" pkg="nodelet" type="nodelet" args="load aruco_pose/aruco_map nodelet_manager" clear_params="true" required="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"/>
<param name="type" value="map"/>
<param name="map" value="$(find aruco_pose)/test/largemap.txt"/>
</node>
<test test-name="test_aruco_pose_largemap" pkg="aruco_pose" type="largemap.py"/>
</launch>

View File

@@ -0,0 +1,11 @@
0 0.2 0 0 0 0 0 0
1 0.2 10 0 0 0 0 0
2 0.2 20 0 0 0 0 0
3 0.2 30 0 0 0 0 0
4 0.2 40 0 0 0 0 0
5 0.2 50 0 0 0 0 0
6 0.2 60 0 0 0 0 0
7 0.2 70 0 0 0 0 0
8 0.2 80 0 0 0 0 0
9 0.2 90 0 0 0 0 0
10 0.2 100 0 0 0 0 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -1,27 +1,13 @@
#!/usr/bin/env python
import sys
import unittest
import json
import rospy import rospy
import rostest import pytest
from geometry_msgs.msg import PoseWithCovarianceStamped
from sensor_msgs.msg import Image
from aruco_pose.msg import MarkerArray
from visualization_msgs.msg import MarkerArray as VisMarkerArray from visualization_msgs.msg import MarkerArray as VisMarkerArray
class TestArucoMapPass(unittest.TestCase): @pytest.fixture
def setUp(self): def node():
rospy.init_node('test_parser_fail', anonymous=True) return rospy.init_node('aruco_pose_test', anonymous=True)
def test_node_failure(self): def test_node_failure(node):
try: with pytest.raises(rospy.exceptions.ROSException):
markers = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5) rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
did_post_message = True
except rospy.exceptions.ROSException:
did_post_message = False
self.assertFalse(did_post_message)
rostest.rosrun('aruco_pose', 'test_aruco_map', TestArucoMapPass, sys.argv)

View File

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

View File

@@ -1,24 +1,13 @@
#!/usr/bin/env python
import sys
import unittest
import json
import rospy import rospy
import rostest import pytest
from geometry_msgs.msg import PoseWithCovarianceStamped
from sensor_msgs.msg import Image
from aruco_pose.msg import MarkerArray
from visualization_msgs.msg import MarkerArray as VisMarkerArray from visualization_msgs.msg import MarkerArray as VisMarkerArray
class TestArucoMapPass(unittest.TestCase): @pytest.fixture
def setUp(self): def node():
rospy.init_node('test_parser_fail', anonymous=True) return rospy.init_node('aruco_pose_test_empty_map', anonymous=True)
def test_empty_map(node):
def test_node_failure(self): markers = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
markers = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5) assert len(markers.markers) == 0
self.assertEquals(len(markers.markers), 0)
rostest.rosrun('aruco_pose', 'test_aruco_map', TestArucoMapPass, sys.argv)

View File

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

View File

@@ -1,75 +1,61 @@
#!/usr/bin/env python
import sys
import unittest
import json
import rospy import rospy
import rostest import pytest
from geometry_msgs.msg import PoseWithCovarianceStamped
from sensor_msgs.msg import Image from sensor_msgs.msg import Image
from aruco_pose.msg import MarkerArray from aruco_pose.msg import MarkerArray
from visualization_msgs.msg import MarkerArray as VisMarkerArray from visualization_msgs.msg import MarkerArray as VisMarkerArray
class TestArucoMapPass(unittest.TestCase): @pytest.fixture
def setUp(self): def node():
rospy.init_node('test_parser_pass', anonymous=True) return rospy.init_node('aruco_pose_test', anonymous=True)
def test_markers(self): def approx(expected):
markers = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5) return pytest.approx(expected, abs=1e-4) # compare floats more roughly
self.assertEqual(len(markers.markers), 6)
# FIXME: visual marker id is not ArUco marker id
# self.assertEqual(markers.markers[0].id, 1)
# self.assertEqual(markers.markers[1].id, 2)
# self.assertEqual(markers.markers[2].id, 3)
# self.assertEqual(markers.markers[3].id, 4)
self.assertAlmostEqual(markers.markers[0].pose.position.x, 0, places=7) def test_markers(node):
self.assertAlmostEqual(markers.markers[0].pose.position.y, 0, places=7) markers = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
self.assertAlmostEqual(markers.markers[0].pose.position.z, 0, places=7) assert len(markers.markers) == 6
self.assertAlmostEqual(markers.markers[1].pose.position.x, 1, places=7)
self.assertAlmostEqual(markers.markers[1].pose.position.y, 1, places=7)
self.assertAlmostEqual(markers.markers[1].pose.position.z, 1, places=7)
self.assertAlmostEqual(markers.markers[2].pose.position.x, 1, places=7)
self.assertAlmostEqual(markers.markers[2].pose.position.y, 0, places=7)
self.assertAlmostEqual(markers.markers[2].pose.position.z, 0.5, places=7)
self.assertAlmostEqual(markers.markers[3].pose.position.x, 0, places=7)
self.assertAlmostEqual(markers.markers[3].pose.position.y, 1, places=7)
self.assertAlmostEqual(markers.markers[3].pose.position.z, 0, places=7)
self.assertAlmostEqual(markers.markers[4].pose.position.x, 1, places=7)
self.assertAlmostEqual(markers.markers[4].pose.position.y, 0.5, places=7)
self.assertAlmostEqual(markers.markers[4].pose.position.z, 0, places=7)
self.assertAlmostEqual(markers.markers[5].pose.position.x, 2.2, places=7)
self.assertAlmostEqual(markers.markers[5].pose.position.y, 0.2, places=7)
self.assertAlmostEqual(markers.markers[5].pose.position.z, 0, places=7)
self.assertAlmostEqual(markers.markers[0].scale.x, 0.33, places=7) assert markers.markers[0].pose.position.x == approx(0)
self.assertAlmostEqual(markers.markers[0].scale.y, 0.33, places=7) assert markers.markers[0].pose.position.y == approx(0)
self.assertAlmostEqual(markers.markers[1].scale.x, 0.225, places=7) assert markers.markers[0].pose.position.z == approx(0)
self.assertAlmostEqual(markers.markers[1].scale.y, 0.225, places=7)
self.assertAlmostEqual(markers.markers[2].scale.x, 0.45, places=7)
self.assertAlmostEqual(markers.markers[2].scale.y, 0.45, places=7)
self.assertAlmostEqual(markers.markers[3].scale.x, 0.15, places=7)
self.assertAlmostEqual(markers.markers[3].scale.y, 0.15, places=7)
self.assertAlmostEqual(markers.markers[4].scale.x, 0.25, places=7)
self.assertAlmostEqual(markers.markers[4].scale.y, 0.25, places=7)
self.assertAlmostEqual(markers.markers[5].scale.x, 0.35, places=7)
self.assertAlmostEqual(markers.markers[5].scale.y, 0.35, places=7)
def test_map_image(self): assert markers.markers[1].pose.position.x == approx(1)
img = rospy.wait_for_message('aruco_map/image', Image, timeout=5) assert markers.markers[1].pose.position.y == approx(1)
self.assertEqual(img.width, 2000) assert markers.markers[1].pose.position.z == approx(1)
self.assertEqual(img.height, 2000)
self.assertEqual(img.encoding, 'mono8')
# def test_transforms(self): assert markers.markers[2].pose.position.x == approx(1)
# pass assert markers.markers[2].pose.position.y == approx(0)
assert markers.markers[2].pose.position.z == approx(0.5)
assert markers.markers[3].pose.position.x == approx(0)
assert markers.markers[3].pose.position.y == approx(1)
assert markers.markers[3].pose.position.z == approx(0)
rostest.rosrun('aruco_pose', 'test_aruco_map', TestArucoMapPass, sys.argv) assert markers.markers[4].pose.position.x == approx(1)
assert markers.markers[4].pose.position.y == approx(0.5)
assert markers.markers[4].pose.position.z == approx(0)
assert markers.markers[5].pose.position.x == approx(2.2)
assert markers.markers[5].pose.position.y == approx(0.2)
assert markers.markers[5].pose.position.z == approx(0)
assert markers.markers[0].scale.x == approx(0.33)
assert markers.markers[0].scale.y == approx(0.33)
assert markers.markers[1].scale.x == approx(0.225)
assert markers.markers[1].scale.y == approx(0.225)
assert markers.markers[2].scale.x == approx(0.45)
assert markers.markers[2].scale.y == approx(0.45)
assert markers.markers[3].scale.x == approx(0.15)
assert markers.markers[3].scale.y == approx(0.15)
assert markers.markers[4].scale.x == approx(0.25)
assert markers.markers[4].scale.y == approx(0.25)
assert markers.markers[5].scale.x == approx(0.35)
assert markers.markers[5].scale.y == approx(0.35)
def test_map_image(node):
img = rospy.wait_for_message('aruco_map/image', Image, timeout=5)
assert img.width == 2000
assert img.height == 2000
assert img.encoding == 'mono8'

View File

@@ -1,7 +1,7 @@
<launch> <launch>
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager"/> <node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager" required="true"/>
<node name="aruco_map" pkg="nodelet" type="nodelet" args="load aruco_pose/aruco_map nodelet_manager" clear_params="true"> <node name="aruco_map" pkg="nodelet" type="nodelet" args="load aruco_pose/aruco_map nodelet_manager" clear_params="true" required="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="markers" to="aruco_detect/markers"/> <remap from="markers" to="aruco_detect/markers"/>
@@ -9,5 +9,6 @@
<param name="map" value="$(find aruco_pose)/test/test_parser_pass.txt"/> <param name="map" value="$(find aruco_pose)/test/test_parser_pass.txt"/>
</node> </node>
<test test-name="test_aruco_map" pkg="aruco_pose" type="test_parser_pass.py"/> <param name="test_module" value="$(find aruco_pose)/test/test_parser_pass.py"/>
<test test-name="test_node_pass" pkg="ros_pytest" type="ros_pytest_runner"/>
</launch> </launch>

View File

@@ -1,12 +1,12 @@
[Unit] [Unit]
Description=Clever ROS package Description=Clever ROS package
Requires=roscore.service Requires=roscore.service
After=roscore.service After=network.target
[Service] [Service]
User=pi User=pi
EnvironmentFile=/lib/systemd/system/roscore.env ExecStart=/bin/sh -c ". /home/pi/catkin_ws/devel/setup.sh; \
ExecStart=/opt/ros/kinetic/bin/roslaunch clever clever.launch --wait --screen ROS_HOSTNAME=`hostname`.local exec roslaunch clever clever.launch --wait --screen --skip-log-check"
Restart=on-failure Restart=on-failure
RestartSec=3 RestartSec=3

View File

@@ -1,35 +0,0 @@
#!/usr/bin/env bash
# Set Clever hostname to the specified value
set -e
NEW_NAME_OPT=$1
if [[ -z ${NEW_NAME_OPT} ]]; then
echo "Please specify new name for this Clever"
exit 1
fi
NEW_NAME=$(echo ${NEW_NAME_OPT} | tr '[:upper:]' '[:lower:]')
echo "Setting name to ${NEW_NAME}"
echo "Backing up /etc/hostname"
cp /etc/hostname /etc/hostname.bak
echo "Writing new /etc/hostname"
echo ${NEW_NAME} > /etc/hostname
echo "Backing up /etc/hosts"
cp /etc/hosts /etc/hosts.bak
echo "Rewriting /etc/hosts with new values"
sed -i 's/127\.0\.1\.1.*/127.0.1.1\t'${NEW_NAME}'/g' /etc/hosts
echo "Changing hostname in /lib/systemd/system/roscore.env"
sed -i 's/ROS_HOSTNAME=.*/ROS_HOSTNAME='${NEW_NAME}'.local/g' /lib/systemd/system/roscore.env
echo "Changing hostname in .bashrc"
sed -i 's/export ROS_HOSTNAME=.*/export ROS_HOSTNAME='${NEW_NAME}'.local/g' /home/pi/.bashrc
echo "Done, reboot your Clever to see the results"

View File

@@ -38,7 +38,11 @@ echo_stamp() {
echo_stamp "Rename SSID" echo_stamp "Rename SSID"
NEW_SSID='CLEVER-'$(head -c 100 /dev/urandom | xxd -ps -c 100 | sed -e "s/[^0-9]//g" | cut -c 1-4) NEW_SSID='CLEVER-'$(head -c 100 /dev/urandom | xxd -ps -c 100 | sed -e "s/[^0-9]//g" | cut -c 1-4)
sudo sed -i.OLD "s/CLEVER/${NEW_SSID}/" /etc/wpa_supplicant/wpa_supplicant.conf sudo sed -i.OLD "s/CLEVER/${NEW_SSID}/" /etc/wpa_supplicant/wpa_supplicant.conf
clever_rename ${NEW_SSID}
echo_stamp "Rename hostname to $NEW_SSID"
hostnamectl set-hostname $NEW_SSID
sed -i 's/127\.0\.1\.1.*/127.0.1.1\t'${NEW_SSID}' '${NEW_SSID}'.local/g' /etc/hosts
# .local (mdns) hostname added to make it accesable when wlan and ethernet interfaces down
echo_stamp "Harware setup" echo_stamp "Harware setup"
/root/hardware_setup.sh /root/hardware_setup.sh

View File

@@ -550,3 +550,6 @@ ddynamic_reconfigure:
realsense2_camera: realsense2_camera:
debian: debian:
stretch: [ros-kinetic-realsense2-camera] stretch: [ros-kinetic-realsense2-camera]
ros_pytest:
debian:
stretch: [ros-kinetic-ros-pytest]

View File

@@ -1,10 +0,0 @@
ROS_ROOT=/opt/ros/kinetic/share/ros
ROS_DISTRO=kinetic
ROS_PACKAGE_PATH=/home/pi/catkin_ws/src:/opt/ros/kinetic/share
ROS_PORT=11311
ROS_MASTER_URI=http://localhost:11311
CMAKE_PREFIX_PATH=/home/pi/catkin_ws/devel:/opt/ros/kinetic
PATH=/opt/ros/kinetic/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
LD_LIBRARY_PATH=/opt/ros/kinetic/lib
PYTHONPATH=/home/pi/catkin_ws/devel/lib/python2.7/dist-packages:/opt/ros/kinetic/lib/python2.7/dist-packages
ROS_HOSTNAME=raspberrypi.local

View File

@@ -4,8 +4,7 @@ After=network.target
[Service] [Service]
User=pi User=pi
EnvironmentFile=/lib/systemd/system/roscore.env ExecStart=/bin/sh -c ". /opt/ros/kinetic/setup.sh; ROS_HOSTNAME=`hostname`.local exec roscore"
ExecStart=/opt/ros/kinetic/bin/roscore
Restart=on-failure Restart=on-failure
RestartSec=3 RestartSec=3

View File

@@ -109,16 +109,14 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-network.
[[ $(arch) == 'armv7l' ]] && NUMBER_THREADS=1 || NUMBER_THREADS=$(nproc --all) [[ $(arch) == 'armv7l' ]] && NUMBER_THREADS=1 || NUMBER_THREADS=$(nproc --all)
# Clever # Clever
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clever.service' '/lib/systemd/system/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clever.service' '/lib/systemd/system/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/roscore.env' '/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/kinetic-rosdep-clever.yaml' '/etc/ros/rosdep/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-rosdep-clever.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/kinetic-ros-clever.rosinstall' '/home/pi/ros_catkin_ws/' # ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-ros-clever.rosinstall' '/home/pi/ros_catkin_ws/'
# Add PX4 udev rules # Add PX4 udev rules
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/99-px4fmu.rules' '/lib/udev/rules.d/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${REPO_DIR}'/clever/config/99-px4fmu.rules' '/lib/udev/rules.d/'
# Add rename script # Add rename script
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clever_rename' '/usr/local/bin/clever_rename'
${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-validate.sh' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh'

View File

@@ -155,8 +155,6 @@ echo_stamp "Installing CLEVER" \
&& my_travis_retry pip install -r /home/pi/catkin_ws/src/clever/clever/requirements.txt \ && my_travis_retry pip install -r /home/pi/catkin_ws/src/clever/clever/requirements.txt \
&& source /opt/ros/kinetic/setup.bash \ && source /opt/ros/kinetic/setup.bash \
&& catkin_make -j2 -DCMAKE_BUILD_TYPE=Release \ && catkin_make -j2 -DCMAKE_BUILD_TYPE=Release \
&& catkin_make run_tests \
&& catkin_test_results \
&& systemctl enable roscore \ && systemctl enable roscore \
&& systemctl enable clever \ && systemctl enable clever \
&& echo_stamp "All CLEVER was installed!" "SUCCESS" \ && echo_stamp "All CLEVER was installed!" "SUCCESS" \
@@ -171,7 +169,6 @@ gitbook build
echo_stamp "Installing additional ROS packages" echo_stamp "Installing additional ROS packages"
apt-get install -y --no-install-recommends \ apt-get install -y --no-install-recommends \
ros-kinetic-dynamic-reconfigure \ ros-kinetic-dynamic-reconfigure \
ros-kinetic-tf2-web-republisher \
ros-kinetic-compressed-image-transport \ ros-kinetic-compressed-image-transport \
ros-kinetic-rosbridge-suite \ ros-kinetic-rosbridge-suite \
ros-kinetic-rosserial \ ros-kinetic-rosserial \
@@ -181,9 +178,12 @@ apt-get install -y --no-install-recommends \
ros-kinetic-rosshow ros-kinetic-rosshow
# TODO move GeographicLib datasets to Mavros debian package # TODO move GeographicLib datasets to Mavros debian package
echo_stamp "Install GeographicLib datasets (needs for mavros)" \ echo_stamp "Install GeographicLib datasets (needed for mavros)" \
&& wget -qO- https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | bash && wget -qO- https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | bash
echo_stamp "Running tests"
catkin_make run_tests && catkin_test_results
echo_stamp "Change permissions for catkin_ws" echo_stamp "Change permissions for catkin_ws"
chown -Rf pi:pi /home/pi/catkin_ws chown -Rf pi:pi /home/pi/catkin_ws
@@ -192,7 +192,7 @@ 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='kinetic' ROS_DISTRO='kinetic'
export ROS_HOSTNAME='raspberrypi.local' export ROS_HOSTNAME=\`hostname\`.local
source /opt/ros/kinetic/setup.bash source /opt/ros/kinetic/setup.bash
source /home/pi/catkin_ws/devel/setup.bash source /home/pi/catkin_ws/devel/setup.bash
EOF EOF

View File

@@ -108,16 +108,17 @@ ntpdate \
python-dev \ python-dev \
python3-dev \ python3-dev \
python-systemd \ python-systemd \
mjpg-streamer=2.0 \
&& echo_stamp "Everything was installed!" "SUCCESS" \ && echo_stamp "Everything was installed!" "SUCCESS" \
|| (echo_stamp "Some packages wasn't installed!" "ERROR"; exit 1) || (echo_stamp "Some packages wasn't installed!" "ERROR"; exit 1)
echo_stamp "Updating kernel to fix camera bug" echo_stamp "Updating kernel to fix camera bug"
apt-get install --no-install-recommends -y \ apt-get install --no-install-recommends -y \
raspberrypi-kernel=1.20190517-1 \ raspberrypi-kernel=1.20190709~stretch-1 \
raspberrypi-bootloader=1.20190517-1 \ raspberrypi-bootloader=1.20190709~stretch-1 \
libraspberrypi-bin=1.20190517-1 \ libraspberrypi-bin=1.20190709~stretch-1 \
libraspberrypi-dev=1.20190517-1 \ libraspberrypi-dev=1.20190709~stretch-1 \
libraspberrypi0=1.20190517-1 \ libraspberrypi0=1.20190709~stretch-1 \
wireless-regdb=2018.05.09-0~rpt1 \ wireless-regdb=2018.05.09-0~rpt1 \
wpasupplicant=2:2.6-21~bpo9~rpt1 wpasupplicant=2:2.6-21~bpo9~rpt1

View File

@@ -29,6 +29,7 @@ pigpiod -v
i2cdetect -V i2cdetect -V
butterfly -h butterfly -h
espeak --version espeak --version
mjpg_streamer --version
# ros stuff # ros stuff

31
check_files_size.py Executable file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python3
import os
import sys
def human_size(num, suffix='B'):
for unit in ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi']:
if abs(num) < 1024.0:
return "%3.1f %s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f %s%s" % (num, 'Yi', suffix)
SIZE_LIMIT = 600 * 1024
EXCLUDE = 'rviz.png', 'ssid.png', 'sitl_docker_demo.png', 'qgc-params.png', 'butterfly.png', \
'Clever main.png', 'fpv_3.png', '1_4.png', 'fpv_4.png', 'detal1.png', 'lockradio.png', \
'qground.png', 'allElements.png', 'download-log.png', 'explosion.png', 'rqt.png', \
'cl3_mountBEC.JPG', 'cl3_mountRpiCamera.JPG'
code = 0
for root, dirs, files in os.walk('docs/'):
for f in files:
if f not in EXCLUDE:
path = os.path.join(root, f)
size = os.path.getsize(path)
if size > SIZE_LIMIT:
print('\x1b[1;31mFile too large ({}): {}\x1b[0m'.format(human_size(size), path), \
file=sys.stderr)
code = 1
sys.exit(code)

View File

@@ -227,6 +227,19 @@ target_link_libraries(clever
# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
# ) # )
# Only install udev rules when building a Debian package
# FIXME: Other operating systems may have other prefixes
string(FIND ${CMAKE_INSTALL_PREFIX} "/opt/ros" _PREFIX_INDEX)
if (${_PREFIX_INDEX} EQUAL 0)
message(STATUS "Building as a Debian package - adding udev rules as installable files")
install(FILES
config/99-px4fmu.rules
DESTINATION /lib/udev/rules.d
)
else()
message(STATUS "Building in a user workspace - not adding udev rules")
endif()
############# #############
## Testing ## ## Testing ##
############# #############
@@ -239,3 +252,8 @@ target_link_libraries(clever
## Add folders to be run by python nosetests ## Add folders to be run by python nosetests
# catkin_add_nosetests(test) # catkin_add_nosetests(test)
if (CATKIN_ENABLE_TESTING)
find_package(rostest REQUIRED)
add_rostest(test/basic.test)
endif()

View File

@@ -22,8 +22,11 @@
<remap from="markers" to="aruco_detect/markers"/> <remap from="markers" to="aruco_detect/markers"/>
<param name="map" value="$(find aruco_pose)/map/map.txt"/> <param name="map" value="$(find aruco_pose)/map/map.txt"/>
<param name="known_tilt" value="map"/> <param name="known_tilt" value="map"/>
<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)"/>
<param name="markers/frame_id" value="aruco_map"/>
<param name="markers/child_frame_id_prefix" value="aruco_"/>
</node> </node>
<!-- vpe publisher from aruco markers --> <!-- vpe publisher from aruco markers -->

View File

@@ -56,7 +56,7 @@
<node pkg="tf2_ros" type="static_transform_publisher" name="rangefinder_frame" args="0 0 -0.05 0 1.5707963268 0 base_link rangefinder"/> <node pkg="tf2_ros" type="static_transform_publisher" name="rangefinder_frame" args="0 0 -0.05 0 1.5707963268 0 base_link rangefinder"/>
<!-- Copter visualization --> <!-- Copter visualization -->
<node name="copter_visualization" pkg="mavros_extras" type="copter_visualization" if="$(arg viz)"> <node name="visualization" pkg="mavros_extras" type="visualization" if="$(arg viz)">
<remap to="mavros/local_position/pose" from="local_position"/> <remap to="mavros/local_position/pose" from="local_position"/>
<remap to="mavros/setpoint_position/local" from="local_setpoint"/> <remap to="mavros/setpoint_position/local" from="local_setpoint"/>
<param name="fixed_frame_id" value="map"/> <param name="fixed_frame_id" value="map"/>

View File

@@ -30,13 +30,13 @@
<depend>nodelet</depend> <depend>nodelet</depend>
<depend>mavros</depend> <depend>mavros</depend>
<depend>mavros_extras</depend> <depend>mavros_extras</depend>
<depend>lxml</depend>
<depend>cv_camera</depend> <depend>cv_camera</depend>
<depend>cv_bridge</depend> <depend>cv_bridge</depend>
<depend>opencv3</depend> <depend>opencv3</depend>
<depend>mjpg-streamer</depend>
<depend>rosbridge_server</depend> <depend>rosbridge_server</depend>
<depend>web_video_server</depend> <depend>web_video_server</depend>
<depend>tf2_web_republisher</depend>
<depend>python-lxml</depend>
<exec_depend>python-pymavlink</exec_depend> <exec_depend>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,4 +1,4 @@
flask==0.12.3 flask==1.1.1
docopt==0.6.2 docopt==0.6.2
geopy==1.11.0 geopy==1.11.0
smbus2==0.2.1 smbus2==0.2.1

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python #!/usr/bin/env python
# coding=utf-8
# Copyright (C) 2018 Copter Express Technologies # Copyright (C) 2018 Copter Express Technologies
# #
@@ -144,6 +145,45 @@ def mavlink_exec(cmd, timeout=3.0):
return mavlink_recv return mavlink_recv
BOARD_ROTATIONS = {
0: 'no rotation',
1: 'yaw 45°',
2: 'yaw 90°',
3: 'yaw 135°',
4: 'yaw 180°',
5: 'yaw 225°',
6: 'yaw 270°',
7: 'yaw 315°',
8: 'roll 180°',
9: 'roll 180°, yaw 45°',
10: 'roll 180°, yaw 90°',
11: 'roll 180°, yaw 135°',
12: 'pitch 180°',
13: 'roll 180°, yaw 225°',
14: 'roll 180°, yaw 270°',
15: 'roll 180°, yaw 315°',
16: 'roll 90°',
17: 'roll 90°, yaw 45°',
18: 'roll 90°, yaw 90°',
19: 'roll 90°, yaw 135°',
20: 'roll 270°',
21: 'roll 270°, yaw 45°',
22: 'roll 270°, yaw 90°',
23: 'roll 270°, yaw 135°',
24: 'pitch 90°',
25: 'pitch 270°',
26: 'roll 270°, yaw 270°',
27: 'roll 180°, pitch 270°',
28: 'pitch 90°, yaw 180',
29: 'pitch 90°, roll 90°',
30: 'yaw 293°, pitch 68°, roll 90°',
31: 'pitch 90°, roll 270°',
32: 'pitch 9°, yaw 180°',
33: 'pitch 45°',
34: 'pitch 315°',
}
@check('FCU') @check('FCU')
def check_fcu(): def check_fcu():
try: try:
@@ -189,6 +229,13 @@ def check_fcu():
else: else:
failure('unknown selected estimator: %s', est) failure('unknown selected estimator: %s', est)
rot = get_param('SENS_BOARD_ROT')
if rot is not None:
try:
info('board rotation: %s', BOARD_ROTATIONS[rot])
except KeyError:
failure('unknown board rotation %s', rot)
except rospy.ROSException: except rospy.ROSException:
failure('no MAVROS state (check wiring)') failure('no MAVROS state (check wiring)')
@@ -240,7 +287,7 @@ def check_camera(name):
if not optical or not cable: if not optical or not cable:
info('%s: custom camera orientation detected', name) info('%s: custom camera orientation detected', name)
else: else:
info('camera is oriented %s, camera cable goes %s', optical, cable) info('camera is oriented %s, camera cable goes %s', optical, cable)
except tf2_ros.TransformException: except tf2_ros.TransformException:
failure('cannot transform from base_link to camera frame') failure('cannot transform from base_link to camera frame')
@@ -350,8 +397,8 @@ def check_vpe():
if delay != 0: if delay != 0:
failure('EKF2_EV_DELAY is %.2f, but it should be zero', delay) failure('EKF2_EV_DELAY is %.2f, but it should be zero', delay)
info('EKF2_EVA_NOISE is %.3f, EKF2_EVP_NOISE is %.3f', info('EKF2_EVA_NOISE is %.3f, EKF2_EVP_NOISE is %.3f',
get_param('EKF2_EVA_NOISE'), get_param('EKF2_EVA_NOISE'),
get_param('EKF2_EVP_NOISE')) get_param('EKF2_EVP_NOISE'))
if not vis: if not vis:
return return
@@ -559,10 +606,16 @@ def check_cpu_usage():
@check('clever.service') @check('clever.service')
def check_clever_service(): def check_clever_service():
output = subprocess.check_output('systemctl show -p ActiveState --value clever.service'.split()) try:
output = subprocess.check_output('systemctl show -p ActiveState --value clever.service'.split(),
stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
failure('systemctl returned %s: %s', e.returncode, e.output)
return
if 'inactive' in output: if 'inactive' in output:
failure('clever.service is not running, try sudo systemctl restart clever') failure('clever.service is not running, try sudo systemctl restart clever')
return return
j = journal.Reader() j = journal.Reader()
j.this_boot() j.this_boot()
j.add_match(_SYSTEMD_UNIT='clever.service') j.add_match(_SYSTEMD_UNIT='clever.service')
@@ -585,7 +638,10 @@ def check_clever_service():
@check('Image') @check('Image')
def check_image(): def check_image():
info('version: %s', open('/etc/clever_version').read().strip()) try:
info('version: %s', open('/etc/clever_version').read().strip())
except IOError:
info('no /etc/clever_version file, not the Clever image?')
@check('Preflight status') @check('Preflight status')
@@ -594,7 +650,7 @@ def check_preflight_status():
mavlink_exec('\n') mavlink_exec('\n')
cmdr_output = mavlink_exec('commander check') cmdr_output = mavlink_exec('commander check')
if cmdr_output == '': if cmdr_output == '':
failure('No data from FCU') failure('no data from FCU')
return return
cmdr_lines = cmdr_output.split('\n') cmdr_lines = cmdr_output.split('\n')
r = re.compile(r'^(.*)(Preflight|Prearm) check: (.*)') r = re.compile(r'^(.*)(Preflight|Prearm) check: (.*)')

View File

@@ -123,7 +123,7 @@ TwistStamped velocity;
NavSatFix global_position; NavSatFix global_position;
BatteryState battery; BatteryState battery;
// Common subcriber callback template that stores message to the variable // Common subscriber callback template that stores message to the variable
template<typename T, T& STORAGE> template<typename T, T& STORAGE>
void handleMessage(const T& msg) void handleMessage(const T& msg)
{ {
@@ -155,9 +155,9 @@ void handleLocalPosition(const PoseStamped& pose)
// wait for transform without interrupting publishing setpoints // wait for transform without interrupting publishing setpoints
inline bool waitTransform(const string& target, const string& source, inline bool waitTransform(const string& target, const string& source,
const ros::Time& stamp, const ros::Duration& timeout) const ros::Time& stamp, const ros::Duration& timeout) // editorconfig-checker-disable-line
{ {
ros::Rate r(10); ros::Rate r(100);
auto start = ros::Time::now(); auto start = ros::Time::now();
while (ros::ok()) { while (ros::ok()) {
if (ros::Time::now() - start > timeout) return false; if (ros::Time::now() - start > timeout) return false;
@@ -201,31 +201,29 @@ bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
res.mode = state.mode; res.mode = state.mode;
} }
waitTransform(local_frame, req.frame_id, stamp, telemetry_transform_timeout); try {
waitTransform(req.frame_id, fcu_frame, stamp, telemetry_transform_timeout);
auto transform = tf_buffer.lookupTransform(req.frame_id, fcu_frame, stamp);
res.x = transform.transform.translation.x;
res.y = transform.transform.translation.y;
res.z = transform.transform.translation.z;
if (!TIMEOUT(local_position, local_position_timeout)) { double yaw, pitch, roll;
try { tf2::getEulerYPR(transform.transform.rotation, yaw, pitch, roll);
// transform pose res.yaw = yaw;
PoseStamped pose; res.pitch = pitch;
tf_buffer.transform(local_position, pose, req.frame_id); res.roll = roll;
res.x = pose.pose.position.x; } catch (const tf2::TransformException& e) {
res.y = pose.pose.position.y; ROS_DEBUG("simple_offboard: %s", e.what());
res.z = pose.pose.position.z;
// Tait-Bryan angles, order z-y-x
double yaw, pitch, roll;
tf2::getEulerYPR(pose.pose.orientation, yaw, pitch, roll);
res.yaw = yaw;
res.pitch = pitch;
res.roll = roll;
} catch (const tf2::TransformException& e) {}
} }
if (!TIMEOUT(velocity, velocity_timeout)) { if (!TIMEOUT(velocity, velocity_timeout)) {
try { try {
// transform velocity // transform velocity
waitTransform(req.frame_id, fcu_frame, velocity.header.stamp, telemetry_transform_timeout);
Vector3Stamped vec, vec_out; Vector3Stamped vec, vec_out;
vec.header = velocity.header; vec.header.stamp = velocity.header.stamp;
vec.header.frame_id = velocity.header.frame_id;
vec.vector = velocity.twist.linear; vec.vector = velocity.twist.linear;
tf_buffer.transform(vec, vec_out, req.frame_id); tf_buffer.transform(vec, vec_out, req.frame_id);
@@ -351,6 +349,10 @@ PoseStamped globalToLocal(double lat, double lon)
x_offset = distance * sin(azimuth_radians); x_offset = distance * sin(azimuth_radians);
y_offset = distance * cos(azimuth_radians); y_offset = distance * cos(azimuth_radians);
if (!waitTransform(local_frame, fcu_frame, global_position.header.stamp, ros::Duration(0.2))) {
throw std::runtime_error("No local position");
}
auto local = tf_buffer.lookupTransform(local_frame, fcu_frame, global_position.header.stamp); auto local = tf_buffer.lookupTransform(local_frame, fcu_frame, global_position.header.stamp);
PoseStamped pose; PoseStamped pose;
@@ -479,10 +481,12 @@ inline void checkState()
throw std::runtime_error("No connection to FCU, https://clever.copterexpress.com/connection.html"); throw std::runtime_error("No connection to FCU, https://clever.copterexpress.com/connection.html");
} }
#define ENSURE_FINITE(var) { if (!std::isfinite(var)) throw std::runtime_error(#var " argument cannot be NaN or Inf"); }
bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, float vy, float vz, bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, float vy, float vz,
float pitch, float roll, float yaw, float pitch_rate, float roll_rate, float yaw_rate, float pitch, float roll, float yaw, float pitch_rate, float roll_rate, float yaw_rate, // editorconfig-checker-disable-line
float lat, float lon, float thrust, float speed, string frame_id, bool auto_arm, float lat, float lon, float thrust, float speed, string frame_id, bool auto_arm, // editorconfig-checker-disable-line
uint8_t& success, string& message) uint8_t& success, string& message) // editorconfig-checker-disable-line
{ {
auto stamp = ros::Time::now(); auto stamp = ros::Time::now();
@@ -490,6 +494,20 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
if (busy) if (busy)
throw std::runtime_error("Busy"); throw std::runtime_error("Busy");
ENSURE_FINITE(x);
ENSURE_FINITE(y);
ENSURE_FINITE(z);
ENSURE_FINITE(vx);
ENSURE_FINITE(vy);
ENSURE_FINITE(vz);
ENSURE_FINITE(pitch);
ENSURE_FINITE(roll);
ENSURE_FINITE(pitch_rate);
ENSURE_FINITE(roll_rate);
ENSURE_FINITE(lat);
ENSURE_FINITE(lon);
ENSURE_FINITE(thrust);
busy = true; busy = true;
// Checks // Checks
@@ -539,7 +557,9 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
if (sp_type == NAVIGATE_GLOBAL) { if (sp_type == NAVIGATE_GLOBAL) {
// Calculate x and from lat and lot in request's frame // Calculate x and from lat and lot in request's frame
auto xy_in_req_frame = tf_buffer.transform(globalToLocal(lat, lon), frame_id); auto pose_local = globalToLocal(lat, lon);
pose_local.header.stamp = stamp; // TODO: fix
auto xy_in_req_frame = tf_buffer.transform(pose_local, frame_id);
x = xy_in_req_frame.pose.position.x; x = xy_in_req_frame.pose.position.x;
y = xy_in_req_frame.pose.position.y; y = xy_in_req_frame.pose.position.y;
} }
@@ -743,7 +763,7 @@ int main(int argc, char **argv)
// Telemetry subscribers // Telemetry subscribers
auto state_sub = nh.subscribe("mavros/state", 1, &handleMessage<mavros_msgs::State, state>); auto state_sub = nh.subscribe("mavros/state", 1, &handleMessage<mavros_msgs::State, state>);
auto velocity_sub = nh.subscribe("mavros/local_position/velocity", 1, &handleMessage<TwistStamped, velocity>); 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 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 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 statustext_sub = nh.subscribe("mavros/statustext/recv", 1, &handleMessage<mavros_msgs::StatusText, statustext>);

29
clever/test/basic.py Executable file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python
import rospy
import pytest
from mavros_msgs.msg import State
@pytest.fixture()
def node():
return rospy.init_node('clever_test', anonymous=True)
def test_state(node):
state = rospy.wait_for_message('mavros/state', State, timeout=10)
assert state.connected == False
assert state.armed == False
assert state.guided == False
assert state.mode == ''
def test_simple_offboard_services_available():
rospy.wait_for_service('get_telemetry', timeout=5)
rospy.wait_for_service('navigate', timeout=5)
rospy.wait_for_service('navigate_global', timeout=5)
rospy.wait_for_service('set_position', timeout=5)
rospy.wait_for_service('set_velocity', timeout=5)
rospy.wait_for_service('set_attitude', timeout=5)
rospy.wait_for_service('set_rates', timeout=5)
rospy.wait_for_service('land', timeout=5)
def test_web_video_server(node):
import urllib2
urllib2.urlopen("http://localhost:8080").read()

37
clever/test/basic.test Executable file
View File

@@ -0,0 +1,37 @@
<launch>
<!-- Verify all the required nodes basically work -->
<node pkg="mavros" type="mavros_node" name="mavros" required="true" output="screen">
<param name="fcu_url" value="udp://@127.0.1:14557"/>
<rosparam command="load" file="$(find clever)/launch/mavros_config.yaml"/>
</node>
<node name="visualization" pkg="mavros_extras" type="visualization" required="true">
<remap to="mavros/local_position/pose" from="local_position"/>
<remap to="mavros/setpoint_position/local" from="local_setpoint"/>
<param name="fixed_frame_id" value="map"/>
<param name="child_frame_id" value="base_link"/>
<param name="marker_scale" value="1"/>
<param name="max_track_size" value="20"/>
<param name="num_rotors" value="4"/>
</node>
<node name="web_video_server" pkg="web_video_server" type="web_video_server" required="true" output="screen">
<param name="default_stream_type" value="ros_compressed"/>
<param name="publish_rate" value="1.0"/>
</node>
<node pkg="tf2_ros" type="static_transform_publisher" name="map_flipped_frame" args="0 0 0 3.1415926 3.1415926 0 map map_flipped" required="true"/>
<node name="simple_offboard" pkg="clever" 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="tf2_web_republisher" pkg="tf2_web_republisher" type="tf2_web_republisher" required="true"/>
<node name="rc" pkg="clever" type="rc" required="true" output="screen"/>
<param name="test_module" value="$(find clever)/test/basic.py"/>
<test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner"/>
</launch>

197
clever/test/sitl.py Normal file
View File

@@ -0,0 +1,197 @@
#!/usr/bin/env python
import math
import rospy
import pytest
from pytest import approx
from numpy import isfinite
from geometry_msgs.msg import PoseStamped
from mavros_msgs.msg import State
from sensor_msgs.msg import NavSatFix
from clever import srv
from std_srvs.srv import Trigger
import tf2_ros
import tf2_geometry_msgs
def roughly(expected):
return approx(expected, abs=1e-1)
def very_roughly(expected):
return approx(expected, abs=1)
@pytest.fixture()
def node():
return rospy.init_node('clever_test', anonymous=True)
def wait_service(name, service_class):
res = rospy.ServiceProxy(name, service_class)
res.wait_for_service(5)
return res
@pytest.fixture
def get_telemetry():
return wait_service('get_telemetry', srv.GetTelemetry)
@pytest.fixture
def navigate():
return wait_service('navigate', srv.Navigate)
@pytest.fixture
def navigate():
res = rospy.ServiceProxy('navigate', srv.Navigate)
res.wait_for_service(5)
return res
@pytest.fixture
def land():
return wait_service('land', Trigger)
@pytest.fixture
def tf_buffer():
buf = tf2_ros.Buffer()
tf2_ros.TransformListener(buf)
return buf
def wait_connection():
start = rospy.get_rostime()
while rospy.get_rostime() - start <= rospy.Duration(15):
state = rospy.wait_for_message('mavros/state', State, timeout=10)
if state.connected:
return True
assert False, "no connection to SITL"
def minimal_rate(func, rate):
start = rospy.get_rostime()
i = 0
while rospy.get_rostime() - start <= rospy.Duration(2):
func()
i += 1
result_rate = i / 2
assert result_rate >= rate, 'Rate too low: %s Hz' % result_rate
def test_state_initial(node):
wait_connection()
state = rospy.wait_for_message('mavros/state', State, timeout=10)
assert state.connected == True
assert state.armed == False
def test_telem_initial(node, get_telemetry):
# wait for local position
rospy.wait_for_message('mavros/local_position/pose', PoseStamped, timeout=15)
rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=30)
telem = get_telemetry()
assert telem.frame_id == 'map'
assert telem.connected == True
assert telem.armed == False
assert telem.mode != ''
assert telem.x == roughly(0.0)
assert telem.y == roughly(0.0)
assert telem.z == roughly(0.0)
assert telem.lat == approx(47.397742)
assert telem.lon == approx(8.545594)
assert telem.vx == roughly(0.0)
assert telem.vy == roughly(0.0)
assert telem.vz == roughly(0.0)
assert telem.pitch == roughly(0.0)
assert telem.roll == roughly(0.0)
assert telem.yaw == roughly(1.56)
assert isfinite(telem.voltage)
assert isfinite(telem.cell_voltage)
def test_telem_rate(node, get_telemetry):
minimal_rate(lambda: get_telemetry(), 20)
minimal_rate(lambda: get_telemetry(frame_id='body'), 20)
minimal_rate(lambda: get_telemetry(frame_id='base_link'), 200)
def test_takeoff_without_auto_arm(node, navigate):
res = navigate(z=2, frame_id='body')
assert res.success == False
assert res.message == 'Copter is not in OFFBOARD mode, use auto_arm?'
def test_takeoff(node, navigate, get_telemetry, tf_buffer):
res = navigate(z=2, speed=1, frame_id='body', auto_arm=True)
assert res.success == True, res.message
rospy.sleep(5)
telem = get_telemetry()
assert telem.z == very_roughly(2.0)
assert telem.x == very_roughly(0.0)
assert telem.y == very_roughly(0.0)
assert telem.pitch == roughly(0.0)
assert telem.roll == roughly(0.0)
assert telem.yaw == roughly(1.56)
def test_navigate_nans(node, navigate):
res = navigate(x=float('nan'))
assert res.success == False
assert res.message == 'x argument cannot be NaN or Inf'
res = navigate(y=float('nan'))
assert res.success == False
assert res.message == 'y argument cannot be NaN or Inf'
res = navigate(z=float('nan'))
assert res.success == False
assert res.message == 'z argument cannot be NaN or Inf'
res = navigate(x=float('inf'))
assert res.success == False
assert res.message == 'x argument cannot be NaN or Inf'
res = navigate(y=float('inf'))
assert res.success == False
assert res.message == 'y argument cannot be NaN or Inf'
res = navigate(z=float('inf'))
assert res.success == False
assert res.message == 'z argument cannot be NaN or Inf'
res = navigate(x=float('-inf'))
assert res.success == False
assert res.message == 'x argument cannot be NaN or Inf'
res = navigate(y=float('-inf'))
assert res.success == False
assert res.message == 'y argument cannot be NaN or Inf'
res = navigate(z=float('-inf'))
assert res.success == False
assert res.message == 'z argument cannot be NaN or Inf'
def test_navigate(node, navigate, get_telemetry, tf_buffer):
res = navigate(x=1, y=2, z=3, speed=1)
assert res.success == True, res.message
nav_target = tf_buffer.lookup_transform('map', 'navigate_target', rospy.get_rostime(), rospy.Duration(0.2))
assert nav_target.transform.translation.x == approx(1)
assert nav_target.transform.translation.y == approx(2)
assert nav_target.transform.translation.z == approx(3)
rospy.sleep(10)
telem = get_telemetry()
assert telem.x == very_roughly(1.0)
assert telem.y == very_roughly(2.0)
assert telem.z == very_roughly(3.0)
assert telem.pitch == roughly(0.0)
assert telem.roll == roughly(0.0)
assert telem.yaw == roughly(0.0)
res = navigate(x=-1, y=-2, z=-1, frame_id='body', speed=1)
assert res.success == True, res.message
nav_target = tf_buffer.lookup_transform('map', 'navigate_target', rospy.get_rostime(), rospy.Duration(0.2))
assert nav_target.transform.translation.x == very_roughly(0)
assert nav_target.transform.translation.y == very_roughly(0)
assert nav_target.transform.translation.z == very_roughly(2)
rospy.sleep(10)
telem = get_telemetry()
assert telem.x == very_roughly(0)
assert telem.y == very_roughly(0)
assert telem.z == very_roughly(2)
assert telem.pitch == roughly(0)
assert telem.roll == roughly(0)
assert telem.yaw == roughly(0)
# TODO
# test navigate_global, set_velocity, set_attitude, set_rates
def test_land(node, get_telemetry, land):
res = land()
assert res.success, res.message
telem = get_telemetry()
assert telem.mode == 'AUTO.LAND'
assert telem.armed == True, 'Drone unexpectedly disarmed while landing'
rospy.sleep(12)
telem = get_telemetry()
assert telem.mode == 'AUTO.LAND'
assert telem.armed == False, 'Drone is not disarmed after landing'

20
clever/test/sitl.test Normal file
View File

@@ -0,0 +1,20 @@
<launch>
<arg name="ip" default="127.0.0.1"/>
<!-- mavros -->
<include file="$(find clever)/launch/mavros.launch">
<arg name="fcu_conn" value="udp"/>
<arg name="fcu_ip" value="$(arg ip)"/>
<arg name="gcs_bridge" value="false"/>
<arg name="viz" value="false"/>
<arg name="respawn" default="false"/>
</include>
<node name="simple_offboard" pkg="clever" type="simple_offboard" required="true" output="screen">
<param name="reference_frames/body" value="map"/>
<param name="reference_frames/base_link" value="map"/>
</node>
<param name="test_module" value="$(find clever)/test/sitl.py"/>
<test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner" time-limit="120.0"/>
</launch>

View File

@@ -3,7 +3,7 @@ var titleEl = document.querySelector('title');
var modeEl = document.querySelector('.mode'); var modeEl = document.querySelector('.mode');
var batteryEl = document.querySelector('.battery'); var batteryEl = document.querySelector('.battery');
var url = 'ws://' + location.host + ':9090'; var url = 'ws://' + location.hostname + ':9090';
var ros = new ROSLIB.Ros({ url: url }); var ros = new ROSLIB.Ros({ url: url });
function speak(txt) { function speak(txt) {

View File

@@ -1,5 +1,5 @@
var ros = new ROSLIB.Ros({ var ros = new ROSLIB.Ros({
url : 'ws://' + location.host + ':9090' url : 'ws://' + location.hostname + ':9090'
}); });
var titleEl = document.querySelector('title'); var titleEl = document.querySelector('title');

BIN
docs/assets/3d_drone_1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 MiB

View File

@@ -1,735 +0,0 @@
# Onboard parameters for Vehicle 1
#
# Stack: PX4 Pro
# Vehicle: Multi-Rotor
# Version: 1.7.0
# Git Revision: cbdb08bb6109c1ea
#
# Vehicle-Id Component-Id Name Value Type
1 1 ATT_ACC_COMP 1 6
1 1 ATT_BIAS_MAX 0.050000000745058060 9
1 1 ATT_EXT_HDG_M 0 6
1 1 ATT_MAG_DECL 0.000000000000000000 9
1 1 ATT_MAG_DECL_A 1 6
1 1 ATT_W_ACC 0.200000002980232239 9
1 1 ATT_W_EXT_HDG 0.100000001490116119 9
1 1 ATT_W_GYRO_BIAS 0.100000001490116119 9
1 1 ATT_W_MAG 0.100000001490116119 9
1 1 BAT_A_PER_V 15.391030311584472656 9
1 1 BAT_CAPACITY -1.000000000000000000 9
1 1 BAT_CNT_V_CURR 0.000805664050858468 9
1 1 BAT_CNT_V_VOLT 0.000805664050858468 9
1 1 BAT_CRIT_THR 0.070000000298023224 9
1 1 BAT_EMERGEN_THR 0.050000000745058060 9
1 1 BAT_LOW_THR 0.150000005960464478 9
1 1 BAT_N_CELLS 4 6
1 1 BAT_R_INTERNAL -1.000000000000000000 9
1 1 BAT_SOURCE 0 6
1 1 BAT_V_CHARGED 4.199999809265136719 9
1 1 BAT_V_DIV 10.887768745422363281 9
1 1 BAT_V_EMPTY 3.400000095367431641 9
1 1 BAT_V_LOAD_DROP 0.300000011920928955 9
1 1 BAT_V_OFFS_CURR 0.000000000000000000 9
1 1 CAL_ACC0_EN 1 6
1 1 CAL_ACC0_ID 1246218 6
1 1 CAL_ACC0_XOFF 0.081070423126220703 9
1 1 CAL_ACC0_XSCALE 0.996176421642303467 9
1 1 CAL_ACC0_YOFF -0.182177066802978516 9
1 1 CAL_ACC0_YSCALE 1.000774979591369629 9
1 1 CAL_ACC0_ZOFF 0.506614208221435547 9
1 1 CAL_ACC0_ZSCALE 0.991645038127899170 9
1 1 CAL_ACC1_EN 1 6
1 1 CAL_ACC1_ID 1114634 6
1 1 CAL_ACC1_XOFF 0.864228248596191406 9
1 1 CAL_ACC1_XSCALE 1.004844427108764648 9
1 1 CAL_ACC1_YOFF 0.980560302734375000 9
1 1 CAL_ACC1_YSCALE 0.980703771114349365 9
1 1 CAL_ACC1_ZOFF 0.873121738433837891 9
1 1 CAL_ACC1_ZSCALE 0.989114820957183838 9
1 1 CAL_ACC2_XOFF 0.000000000000000000 9
1 1 CAL_ACC2_XSCALE 1.000000000000000000 9
1 1 CAL_ACC2_YOFF 0.000000000000000000 9
1 1 CAL_ACC2_YSCALE 1.000000000000000000 9
1 1 CAL_ACC2_ZOFF 0.000000000000000000 9
1 1 CAL_ACC2_ZSCALE 1.000000000000000000 9
1 1 CAL_AIR_CMODEL 0 6
1 1 CAL_AIR_TUBED_MM 1.500000000000000000 9
1 1 CAL_AIR_TUBELEN 0.200000002980232239 9
1 1 CAL_GYRO0_EN 1 6
1 1 CAL_GYRO0_ID 2163722 6
1 1 CAL_GYRO0_XOFF 0.013700588606297970 9
1 1 CAL_GYRO0_XSCALE 1.000000000000000000 9
1 1 CAL_GYRO0_YOFF 0.049494847655296326 9
1 1 CAL_GYRO0_YSCALE 1.000000000000000000 9
1 1 CAL_GYRO0_ZOFF -0.032555881887674332 9
1 1 CAL_GYRO0_ZSCALE 1.000000000000000000 9
1 1 CAL_GYRO1_EN 1 6
1 1 CAL_GYRO1_ID 2228490 6
1 1 CAL_GYRO1_XOFF -0.007464688271284103 9
1 1 CAL_GYRO1_XSCALE 1.000000000000000000 9
1 1 CAL_GYRO1_YOFF 0.008172192610800266 9
1 1 CAL_GYRO1_YSCALE 1.000000000000000000 9
1 1 CAL_GYRO1_ZOFF -0.010663406923413277 9
1 1 CAL_GYRO1_ZSCALE 1.000000000000000000 9
1 1 CAL_GYRO2_XOFF 0.000000000000000000 9
1 1 CAL_GYRO2_XSCALE 1.000000000000000000 9
1 1 CAL_GYRO2_YOFF 0.000000000000000000 9
1 1 CAL_GYRO2_YSCALE 1.000000000000000000 9
1 1 CAL_GYRO2_ZOFF 0.000000000000000000 9
1 1 CAL_GYRO2_ZSCALE 1.000000000000000000 9
1 1 CAL_MAG0_EN 1 6
1 1 CAL_MAG0_ID 131594 6
1 1 CAL_MAG0_ROT -1 6
1 1 CAL_MAG0_XOFF -0.020210459828376770 9
1 1 CAL_MAG0_XSCALE 1.033145427703857422 9
1 1 CAL_MAG0_YOFF 0.100117556750774384 9
1 1 CAL_MAG0_YSCALE 1.017574071884155273 9
1 1 CAL_MAG0_ZOFF -0.113783776760101318 9
1 1 CAL_MAG0_ZSCALE 0.933741152286529541 9
1 1 CAL_MAG1_ID 0 6
1 1 CAL_MAG1_ROT -1 6
1 1 CAL_MAG1_XOFF 0.000000000000000000 9
1 1 CAL_MAG1_XSCALE 1.000000000000000000 9
1 1 CAL_MAG1_YOFF 0.000000000000000000 9
1 1 CAL_MAG1_YSCALE 1.000000000000000000 9
1 1 CAL_MAG1_ZOFF 0.000000000000000000 9
1 1 CAL_MAG1_ZSCALE 1.000000000000000000 9
1 1 CAL_MAG2_ID 0 6
1 1 CAL_MAG2_ROT -1 6
1 1 CAL_MAG2_XOFF 0.000000000000000000 9
1 1 CAL_MAG2_XSCALE 1.000000000000000000 9
1 1 CAL_MAG2_YOFF 0.000000000000000000 9
1 1 CAL_MAG2_YSCALE 1.000000000000000000 9
1 1 CAL_MAG2_ZOFF 0.000000000000000000 9
1 1 CAL_MAG2_ZSCALE 1.000000000000000000 9
1 1 CAL_MAG3_ID 0 6
1 1 CAL_MAG3_ROT -1 6
1 1 CAL_MAG3_XOFF 0.000000000000000000 9
1 1 CAL_MAG3_XSCALE 1.000000000000000000 9
1 1 CAL_MAG3_YOFF 0.000000000000000000 9
1 1 CAL_MAG3_YSCALE 1.000000000000000000 9
1 1 CAL_MAG3_ZOFF 0.000000000000000000 9
1 1 CAL_MAG3_ZSCALE 1.000000000000000000 9
1 1 CAL_MAG_SIDES 63 6
1 1 CBRK_AIRSPD_CHK 0 6
1 1 CBRK_ENGINEFAIL 284953 6
1 1 CBRK_FLIGHTTERM 121212 6
1 1 CBRK_GPSFAIL 240024 6
1 1 CBRK_IO_SAFETY 22027 6
1 1 CBRK_RATE_CTRL 0 6
1 1 CBRK_SUPPLY_CHK 894281 6
1 1 CBRK_USB_CHK 197848 6
1 1 CBRK_VELPOSERR 0 6
1 1 COM_ARM_AUTH 256010 6
1 1 COM_ARM_EKF_AB 0.004999999888241291 9
1 1 COM_ARM_EKF_GB 0.000869999988935888 9
1 1 COM_ARM_EKF_HGT 1.000000000000000000 9
1 1 COM_ARM_EKF_POS 0.500000000000000000 9
1 1 COM_ARM_EKF_VEL 0.500000000000000000 9
1 1 COM_ARM_EKF_YAW 0.500000000000000000 9
1 1 COM_ARM_IMU_ACC 0.699999988079071045 9
1 1 COM_ARM_IMU_GYR 0.200000002980232239 9
1 1 COM_ARM_MIS_REQ 0 6
1 1 COM_ARM_SWISBTN 0 6
1 1 COM_ARM_WO_GPS 1 6
1 1 COM_DISARM_LAND 1 6
1 1 COM_DL_LOSS_T 10 6
1 1 COM_DL_REG_T 0 6
1 1 COM_EF_C2T 5.000000000000000000 9
1 1 COM_EF_THROT 0.500000000000000000 9
1 1 COM_EF_TIME 10.000000000000000000 9
1 1 COM_FLIGHT_UUID 34 6
1 1 COM_FLTMODE1 8 6
1 1 COM_FLTMODE2 -1 6
1 1 COM_FLTMODE3 -1 6
1 1 COM_FLTMODE4 1 6
1 1 COM_FLTMODE5 -1 6
1 1 COM_FLTMODE6 2 6
1 1 COM_HOME_H_T 5.000000000000000000 9
1 1 COM_HOME_V_T 10.000000000000000000 9
1 1 COM_LOW_BAT_ACT 2 6
1 1 COM_OBL_ACT 0 6
1 1 COM_OBL_RC_ACT 0 6
1 1 COM_OF_LOSS_T 0.000000000000000000 9
1 1 COM_POSCTL_NAVL 0 6
1 1 COM_POS_FS_DELAY 1 6
1 1 COM_POS_FS_GAIN 10 6
1 1 COM_POS_FS_PROB 30 6
1 1 COM_RC_ARM_HYST 1000 6
1 1 COM_RC_IN_MODE 0 6
1 1 COM_RC_LOSS_T 0.500000000000000000 9
1 1 COM_RC_OVERRIDE 0 6
1 1 COM_RC_STICK_OV 12.000000000000000000 9
1 1 FW_CLMBOUT_DIFF 10.000000000000000000 9
1 1 FW_MAN_P_SC 1.000000000000000000 9
1 1 FW_MAN_R_SC 1.000000000000000000 9
1 1 FW_MAN_Y_SC 1.000000000000000000 9
1 1 GF_ACTION 1 6
1 1 GF_ALTMODE 0 6
1 1 GF_COUNT -1 6
1 1 GF_MAX_HOR_DIST 0.000000000000000000 9
1 1 GF_MAX_VER_DIST 0.000000000000000000 9
1 1 GF_SOURCE 0 6
1 1 GPS_DUMP_COMM 0 6
1 1 GPS_UBX_DYNMODEL 7 6
1 1 IMU_ACCEL_CUTOFF 30.000000000000000000 9
1 1 IMU_GYRO_CUTOFF 30.000000000000000000 9
1 1 LED_RGB_MAXBRT 15 6
1 1 LNDMC_ALT_MAX 10000.000000000000000000 9
1 1 LNDMC_FFALL_THR 2.000000000000000000 9
1 1 LNDMC_FFALL_TTRI 0.300000011920928955 9
1 1 LNDMC_ROT_MAX 20.000000000000000000 9
1 1 LNDMC_THR_RANGE 0.100000001490116119 9
1 1 LNDMC_XY_VEL_MAX 1.500000000000000000 9
1 1 LNDMC_Z_VEL_MAX 0.500000000000000000 9
1 1 LND_FLIGHT_T_HI 0 6
1 1 LND_FLIGHT_T_LO 959960540 6
1 1 LPE_ACC_XY 0.012000000104308128 9
1 1 LPE_ACC_Z 0.019999999552965164 9
1 1 LPE_BAR_Z 3.000000000000000000 9
1 1 LPE_EPH_MAX 3.000000000000000000 9
1 1 LPE_EPV_MAX 5.000000000000000000 9
1 1 LPE_FAKE_ORIGIN 1 6
1 1 LPE_FGYRO_HP 0.001000000047497451 9
1 1 LPE_FLW_OFF_Z 0.000000000000000000 9
1 1 LPE_FLW_QMIN 150 6
1 1 LPE_FLW_R 7.000000000000000000 9
1 1 LPE_FLW_RR 7.000000000000000000 9
1 1 LPE_FLW_SCALE 1.299999952316284180 9
1 1 LPE_FUSION 28 6
1 1 LPE_GPS_DELAY 0.289999991655349731 9
1 1 LPE_GPS_VXY 0.250000000000000000 9
1 1 LPE_GPS_VZ 0.250000000000000000 9
1 1 LPE_GPS_XY 1.000000000000000000 9
1 1 LPE_GPS_Z 3.000000000000000000 9
1 1 LPE_LAND_VXY 0.050000000745058060 9
1 1 LPE_LAND_Z 0.029999999329447746 9
1 1 LPE_LAT 47.397743225097656250 9
1 1 LPE_LDR_OFF_Z 0.000000000000000000 9
1 1 LPE_LDR_Z 0.029999999329447746 9
1 1 LPE_LON 8.545594215393066406 9
1 1 LPE_PN_B 0.001000000047497451 9
1 1 LPE_PN_P 0.100000001490116119 9
1 1 LPE_PN_T 0.001000000047497451 9
1 1 LPE_PN_V 0.100000001490116119 9
1 1 LPE_SNR_OFF_Z 0.000000000000000000 9
1 1 LPE_SNR_Z 0.050000000745058060 9
1 1 LPE_T_MAX_GRADE 1.000000000000000000 9
1 1 LPE_VIC_P 0.001000000047497451 9
1 1 LPE_VIS_DELAY 0.000000000000000000 9
1 1 LPE_VIS_XY 0.100000001490116119 9
1 1 LPE_VIS_Z 0.100000001490116119 9
1 1 LPE_VXY_PUB 0.300000011920928955 9
1 1 LPE_X_LP 5.000000000000000000 9
1 1 LPE_Z_PUB 1.000000000000000000 9
1 1 MAV_BROADCAST 0 6
1 1 MAV_COMP_ID 1 6
1 1 MAV_FWDEXTSP 1 6
1 1 MAV_PROTO_VER 0 6
1 1 MAV_RADIO_ID 0 6
1 1 MAV_SYS_ID 1 6
1 1 MAV_TEST_PAR 1 6
1 1 MAV_TYPE 2 6
1 1 MAV_USEHILGPS 0 6
1 1 MC_ACRO_EXPO 0.689999997615814209 9
1 1 MC_ACRO_P_MAX 360.000000000000000000 9
1 1 MC_ACRO_R_MAX 360.000000000000000000 9
1 1 MC_ACRO_SUPEXPO 0.699999988079071045 9
1 1 MC_ACRO_Y_MAX 360.000000000000000000 9
1 1 MC_BAT_SCALE_EN 0 6
1 1 MC_PITCHRATE_D 0.003000000026077032 9
1 1 MC_PITCHRATE_FF 0.000000000000000000 9
1 1 MC_PITCHRATE_I 0.045000001788139343 9
1 1 MC_PITCHRATE_MAX 220.000000000000000000 9
1 1 MC_PITCHRATE_P 0.144999995827674866 9
1 1 MC_PITCH_P 6.500000000000000000 9
1 1 MC_PITCH_TC 0.200000002980232239 9
1 1 MC_PR_INT_LIM 0.300000011920928955 9
1 1 MC_RATT_TH 0.800000011920928955 9
1 1 MC_ROLLRATE_D 0.003000000026077032 9
1 1 MC_ROLLRATE_FF 0.000000000000000000 9
1 1 MC_ROLLRATE_I 0.045000001788139343 9
1 1 MC_ROLLRATE_MAX 220.000000000000000000 9
1 1 MC_ROLLRATE_P 0.144999995827674866 9
1 1 MC_ROLL_P 6.500000000000000000 9
1 1 MC_ROLL_TC 0.200000002980232239 9
1 1 MC_RR_INT_LIM 0.300000011920928955 9
1 1 MC_TPA_BREAK_D 1.000000000000000000 9
1 1 MC_TPA_BREAK_I 1.000000000000000000 9
1 1 MC_TPA_BREAK_P 1.000000000000000000 9
1 1 MC_TPA_RATE_D 0.000000000000000000 9
1 1 MC_TPA_RATE_I 0.000000000000000000 9
1 1 MC_TPA_RATE_P 0.000000000000000000 9
1 1 MC_YAWRATE_D 0.000000000000000000 9
1 1 MC_YAWRATE_FF 0.000000000000000000 9
1 1 MC_YAWRATE_I 0.100000001490116119 9
1 1 MC_YAWRATE_MAX 200.000000000000000000 9
1 1 MC_YAWRATE_P 0.200000002980232239 9
1 1 MC_YAWRAUTO_MAX 45.000000000000000000 9
1 1 MC_YAW_FF 0.500000000000000000 9
1 1 MC_YAW_P 2.799999952316284180 9
1 1 MC_YR_INT_LIM 0.300000011920928955 9
1 1 MIS_ALTMODE 1 6
1 1 MIS_DIST_1WP 900.000000000000000000 9
1 1 MIS_DIST_WPS 900.000000000000000000 9
1 1 MIS_LTRMIN_ALT -1.000000000000000000 9
1 1 MIS_ONBOARD_EN 1 6
1 1 MIS_TAKEOFF_ALT 2.500000000000000000 9
1 1 MIS_YAWMODE 1 6
1 1 MIS_YAW_ERR 12.000000000000000000 9
1 1 MIS_YAW_TMT -1.000000000000000000 9
1 1 MNT_MODE_IN -1 6
1 1 MOT_SLEW_MAX 0.000000000000000000 9
1 1 MPC_ACC_DOWN_MAX 5.000000000000000000 9
1 1 MPC_ACC_HOR 5.000000000000000000 9
1 1 MPC_ACC_HOR_MAX 5.000000000000000000 9
1 1 MPC_ACC_UP_MAX 5.000000000000000000 9
1 1 MPC_ALT_MODE 0 6
1 1 MPC_CRUISE_90 3.000000000000000000 9
1 1 MPC_DEC_HOR_SLOW 5.000000000000000000 9
1 1 MPC_HOLD_DZ 0.100000001490116119 9
1 1 MPC_HOLD_MAX_XY 0.800000011920928955 9
1 1 MPC_HOLD_MAX_Z 0.600000023841857910 9
1 1 MPC_JERK_MAX 0.000000000000000000 9
1 1 MPC_JERK_MIN 1.000000000000000000 9
1 1 MPC_LAND_ALT1 10.000000000000000000 9
1 1 MPC_LAND_ALT2 5.000000000000000000 9
1 1 MPC_LAND_SPEED 0.500000000000000000 9
1 1 MPC_MANTHR_MAX 0.899999976158142090 9
1 1 MPC_MANTHR_MIN 0.079999998211860657 9
1 1 MPC_MAN_TILT_MAX 35.000000000000000000 9
1 1 MPC_MAN_Y_MAX 200.000000000000000000 9
1 1 MPC_THR_HOVER 0.500000000000000000 9
1 1 MPC_THR_MAX 0.899999976158142090 9
1 1 MPC_THR_MIN 0.119999997317790985 9
1 1 MPC_TILTMAX_AIR 45.000000000000000000 9
1 1 MPC_TILTMAX_LND 12.000000000000000000 9
1 1 MPC_TKO_RAMP_T 0.400000005960464478 9
1 1 MPC_TKO_SPEED 1.500000000000000000 9
1 1 MPC_VELD_LP 5.000000000000000000 9
1 1 MPC_VEL_MANUAL 10.000000000000000000 9
1 1 MPC_XY_CRUISE 5.000000000000000000 9
1 1 MPC_XY_MAN_EXPO 0.000000000000000000 9
1 1 MPC_XY_P 0.949999988079071045 9
1 1 MPC_XY_VEL_D 0.009999999776482582 9
1 1 MPC_XY_VEL_I 0.019999999552965164 9
1 1 MPC_XY_VEL_MAX 8.000000000000000000 9
1 1 MPC_XY_VEL_P 0.090000003576278687 9
1 1 MPC_Z_MAN_EXPO 0.000000000000000000 9
1 1 MPC_Z_P 1.000000000000000000 9
1 1 MPC_Z_VEL_D 0.000000000000000000 9
1 1 MPC_Z_VEL_I 0.019999999552965164 9
1 1 MPC_Z_VEL_MAX_DN 1.000000000000000000 9
1 1 MPC_Z_VEL_MAX_UP 3.000000000000000000 9
1 1 MPC_Z_VEL_P 0.200000002980232239 9
1 1 NAV_ACC_RAD 2.000000000000000000 9
1 1 NAV_AH_ALT 600.000000000000000000 9
1 1 NAV_AH_LAT -265847810 6
1 1 NAV_AH_LON 1518423250 6
1 1 NAV_DLL_ACT 0 6
1 1 NAV_DLL_AH_T 120.000000000000000000 9
1 1 NAV_DLL_CHSK 0 6
1 1 NAV_DLL_CH_ALT 600.000000000000000000 9
1 1 NAV_DLL_CH_LAT -266072120 6
1 1 NAV_DLL_CH_LON 1518453890 6
1 1 NAV_DLL_CH_T 120.000000000000000000 9
1 1 NAV_DLL_N 2 6
1 1 NAV_FORCE_VT 1 6
1 1 NAV_FT_DST 8.000000000000000000 9
1 1 NAV_FT_FS 1 6
1 1 NAV_FT_RS 0.500000000000000000 9
1 1 NAV_FW_ALT_RAD 10.000000000000000000 9
1 1 NAV_GPSF_LT 30.000000000000000000 9
1 1 NAV_GPSF_P 0.000000000000000000 9
1 1 NAV_GPSF_R 15.000000000000000000 9
1 1 NAV_GPSF_TR 0.699999988079071045 9
1 1 NAV_LOITER_RAD 50.000000000000000000 9
1 1 NAV_MC_ALT_RAD 0.800000011920928955 9
1 1 NAV_MIN_FT_HT 8.000000000000000000 9
1 1 NAV_RCL_ACT 3 6
1 1 NAV_RCL_LT 120.000000000000000000 9
1 1 NAV_TRAFF_AVOID 1 6
1 1 PWM_AUX_DIS1 -1 6
1 1 PWM_AUX_DIS2 -1 6
1 1 PWM_AUX_DIS3 -1 6
1 1 PWM_AUX_DIS4 -1 6
1 1 PWM_AUX_DIS5 -1 6
1 1 PWM_AUX_DIS6 -1 6
1 1 PWM_AUX_DISARMED 1500 6
1 1 PWM_AUX_MAX 2000 6
1 1 PWM_AUX_MIN 1000 6
1 1 PWM_AUX_REV1 0 6
1 1 PWM_AUX_REV2 0 6
1 1 PWM_AUX_REV3 0 6
1 1 PWM_AUX_REV4 0 6
1 1 PWM_AUX_REV5 0 6
1 1 PWM_AUX_REV6 0 6
1 1 PWM_AUX_TRIM1 0.000000000000000000 9
1 1 PWM_AUX_TRIM2 0.000000000000000000 9
1 1 PWM_AUX_TRIM3 0.000000000000000000 9
1 1 PWM_AUX_TRIM4 0.000000000000000000 9
1 1 PWM_AUX_TRIM5 0.000000000000000000 9
1 1 PWM_AUX_TRIM6 0.000000000000000000 9
1 1 PWM_DISARMED 900 6
1 1 PWM_MAIN_DIS1 -1 6
1 1 PWM_MAIN_DIS2 -1 6
1 1 PWM_MAIN_DIS3 -1 6
1 1 PWM_MAIN_DIS4 -1 6
1 1 PWM_MAIN_DIS5 -1 6
1 1 PWM_MAIN_DIS6 -1 6
1 1 PWM_MAIN_DIS7 -1 6
1 1 PWM_MAIN_DIS8 -1 6
1 1 PWM_MAIN_REV1 0 6
1 1 PWM_MAIN_REV2 0 6
1 1 PWM_MAIN_REV3 0 6
1 1 PWM_MAIN_REV4 0 6
1 1 PWM_MAIN_REV5 0 6
1 1 PWM_MAIN_REV6 0 6
1 1 PWM_MAIN_REV7 0 6
1 1 PWM_MAIN_REV8 0 6
1 1 PWM_MAIN_TRIM1 0.000000000000000000 9
1 1 PWM_MAIN_TRIM2 0.000000000000000000 9
1 1 PWM_MAIN_TRIM3 0.000000000000000000 9
1 1 PWM_MAIN_TRIM4 0.000000000000000000 9
1 1 PWM_MAIN_TRIM5 0.000000000000000000 9
1 1 PWM_MAIN_TRIM6 0.000000000000000000 9
1 1 PWM_MAIN_TRIM7 0.000000000000000000 9
1 1 PWM_MAIN_TRIM8 0.000000000000000000 9
1 1 PWM_MAX 1950 6
1 1 PWM_MIN 1075 6
1 1 PWM_RATE 400 6
1 1 PWM_SBUS_MODE 0 6
1 1 RC10_DZ 0.000000000000000000 9
1 1 RC10_MAX 2000.000000000000000000 9
1 1 RC10_MIN 1000.000000000000000000 9
1 1 RC10_REV 1.000000000000000000 9
1 1 RC10_TRIM 1500.000000000000000000 9
1 1 RC11_DZ 0.000000000000000000 9
1 1 RC11_MAX 2000.000000000000000000 9
1 1 RC11_MIN 1000.000000000000000000 9
1 1 RC11_REV 1.000000000000000000 9
1 1 RC11_TRIM 1500.000000000000000000 9
1 1 RC12_DZ 0.000000000000000000 9
1 1 RC12_MAX 2000.000000000000000000 9
1 1 RC12_MIN 1000.000000000000000000 9
1 1 RC12_REV 1.000000000000000000 9
1 1 RC12_TRIM 1500.000000000000000000 9
1 1 RC13_DZ 0.000000000000000000 9
1 1 RC13_MAX 2000.000000000000000000 9
1 1 RC13_MIN 1000.000000000000000000 9
1 1 RC13_REV 1.000000000000000000 9
1 1 RC13_TRIM 1500.000000000000000000 9
1 1 RC14_DZ 0.000000000000000000 9
1 1 RC14_MAX 2000.000000000000000000 9
1 1 RC14_MIN 1000.000000000000000000 9
1 1 RC14_REV 1.000000000000000000 9
1 1 RC14_TRIM 1500.000000000000000000 9
1 1 RC15_DZ 0.000000000000000000 9
1 1 RC15_MAX 2000.000000000000000000 9
1 1 RC15_MIN 1000.000000000000000000 9
1 1 RC15_REV 1.000000000000000000 9
1 1 RC15_TRIM 1500.000000000000000000 9
1 1 RC16_DZ 0.000000000000000000 9
1 1 RC16_MAX 2000.000000000000000000 9
1 1 RC16_MIN 1000.000000000000000000 9
1 1 RC16_REV 1.000000000000000000 9
1 1 RC16_TRIM 1500.000000000000000000 9
1 1 RC17_DZ 0.000000000000000000 9
1 1 RC17_MAX 2000.000000000000000000 9
1 1 RC17_MIN 1000.000000000000000000 9
1 1 RC17_REV 1.000000000000000000 9
1 1 RC17_TRIM 1500.000000000000000000 9
1 1 RC18_DZ 0.000000000000000000 9
1 1 RC18_MAX 2000.000000000000000000 9
1 1 RC18_MIN 1000.000000000000000000 9
1 1 RC18_REV 1.000000000000000000 9
1 1 RC18_TRIM 1500.000000000000000000 9
1 1 RC1_DZ 10.000000000000000000 9
1 1 RC1_MAX 1999.000000000000000000 9
1 1 RC1_MIN 1000.000000000000000000 9
1 1 RC1_REV 1.000000000000000000 9
1 1 RC1_TRIM 1499.000000000000000000 9
1 1 RC2_DZ 10.000000000000000000 9
1 1 RC2_MAX 2000.000000000000000000 9
1 1 RC2_MIN 1001.000000000000000000 9
1 1 RC2_REV 1.000000000000000000 9
1 1 RC2_TRIM 1499.000000000000000000 9
1 1 RC3_DZ 10.000000000000000000 9
1 1 RC3_MAX 1989.000000000000000000 9
1 1 RC3_MIN 1000.000000000000000000 9
1 1 RC3_REV 1.000000000000000000 9
1 1 RC3_TRIM 1000.000000000000000000 9
1 1 RC4_DZ 10.000000000000000000 9
1 1 RC4_MAX 1999.000000000000000000 9
1 1 RC4_MIN 1018.000000000000000000 9
1 1 RC4_REV 1.000000000000000000 9
1 1 RC4_TRIM 1501.000000000000000000 9
1 1 RC5_DZ 10.000000000000000000 9
1 1 RC5_MAX 2000.000000000000000000 9
1 1 RC5_MIN 1000.000000000000000000 9
1 1 RC5_REV 1.000000000000000000 9
1 1 RC5_TRIM 1500.000000000000000000 9
1 1 RC6_DZ 10.000000000000000000 9
1 1 RC6_MAX 2000.000000000000000000 9
1 1 RC6_MIN 1000.000000000000000000 9
1 1 RC6_REV 1.000000000000000000 9
1 1 RC6_TRIM 1500.000000000000000000 9
1 1 RC7_DZ 10.000000000000000000 9
1 1 RC7_MAX 2000.000000000000000000 9
1 1 RC7_MIN 1000.000000000000000000 9
1 1 RC7_REV 1.000000000000000000 9
1 1 RC7_TRIM 1500.000000000000000000 9
1 1 RC8_DZ 10.000000000000000000 9
1 1 RC8_MAX 2000.000000000000000000 9
1 1 RC8_MIN 1000.000000000000000000 9
1 1 RC8_REV 1.000000000000000000 9
1 1 RC8_TRIM 1500.000000000000000000 9
1 1 RC9_DZ 0.000000000000000000 9
1 1 RC9_MAX 2000.000000000000000000 9
1 1 RC9_MIN 1000.000000000000000000 9
1 1 RC9_REV 1.000000000000000000 9
1 1 RC9_TRIM 1500.000000000000000000 9
1 1 RC_ACRO_TH 0.500000000000000000 9
1 1 RC_ARMSWITCH_TH 0.250000000000000000 9
1 1 RC_ASSIST_TH 0.250000000000000000 9
1 1 RC_AUTO_TH 0.750000000000000000 9
1 1 RC_CHAN_CNT 8 6
1 1 RC_FAILS_THR 0 6
1 1 RC_FLT_CUTOFF 10.000000000000000000 9
1 1 RC_FLT_SMP_RATE 50.000000000000000000 9
1 1 RC_GEAR_TH 0.250000000000000000 9
1 1 RC_KILLSWITCH_TH 0.250000000000000000 9
1 1 RC_LOITER_TH 0.500000000000000000 9
1 1 RC_MAN_TH 0.500000000000000000 9
1 1 RC_MAP_ACRO_SW 0 6
1 1 RC_MAP_ARM_SW 0 6
1 1 RC_MAP_AUX1 0 6
1 1 RC_MAP_AUX2 0 6
1 1 RC_MAP_AUX3 0 6
1 1 RC_MAP_AUX4 0 6
1 1 RC_MAP_AUX5 0 6
1 1 RC_MAP_FAILSAFE 0 6
1 1 RC_MAP_FLAPS 0 6
1 1 RC_MAP_FLTMODE 5 6
1 1 RC_MAP_GEAR_SW 0 6
1 1 RC_MAP_KILL_SW 6 6
1 1 RC_MAP_LOITER_SW 0 6
1 1 RC_MAP_MAN_SW 0 6
1 1 RC_MAP_MODE_SW 0 6
1 1 RC_MAP_OFFB_SW 0 6
1 1 RC_MAP_PARAM1 0 6
1 1 RC_MAP_PARAM2 0 6
1 1 RC_MAP_PARAM3 0 6
1 1 RC_MAP_PITCH 2 6
1 1 RC_MAP_POSCTL_SW 0 6
1 1 RC_MAP_RATT_SW 0 6
1 1 RC_MAP_RETURN_SW 0 6
1 1 RC_MAP_ROLL 1 6
1 1 RC_MAP_STAB_SW 0 6
1 1 RC_MAP_THROTTLE 3 6
1 1 RC_MAP_TRANS_SW 0 6
1 1 RC_MAP_YAW 4 6
1 1 RC_OFFB_TH 0.500000000000000000 9
1 1 RC_POSCTL_TH 0.500000000000000000 9
1 1 RC_RATT_TH 0.500000000000000000 9
1 1 RC_RETURN_TH 0.500000000000000000 9
1 1 RC_RSSI_PWM_CHAN 0 6
1 1 RC_RSSI_PWM_MAX 1000 6
1 1 RC_RSSI_PWM_MIN 2000 6
1 1 RC_STAB_TH 0.500000000000000000 9
1 1 RC_TRANS_TH 0.250000000000000000 9
1 1 RTL_DESCEND_ALT 10.000000000000000000 9
1 1 RTL_LAND_DELAY 0.000000000000000000 9
1 1 RTL_MIN_DIST 5.000000000000000000 9
1 1 RTL_RETURN_ALT 30.000000000000000000 9
1 1 SDLOG_DIRS_MAX 0 6
1 1 SDLOG_MODE 0 6
1 1 SDLOG_PROFILE 3 6
1 1 SDLOG_UTC_OFFSET 0 6
1 1 SENS_BARO_QNH 1013.250000000000000000 9
1 1 SENS_BOARD_ROT 0 6
1 1 SENS_BOARD_X_OFF 0.576516091823577881 9
1 1 SENS_BOARD_Y_OFF 2.084044218063354492 9
1 1 SENS_BOARD_Z_OFF 0.000000000000000000 9
1 1 SENS_DPRES_ANSC 0.000000000000000000 9
1 1 SENS_DPRES_OFF 0.000000000000000000 9
1 1 SENS_EN_LL40LS 0 6
1 1 SENS_EN_MB12XX 0 6
1 1 SENS_EN_SF0X 0 6
1 1 SENS_EN_SF1XX 0 6
1 1 SENS_EN_THERMAL -1 6
1 1 SENS_EN_TRANGER 0 6
1 1 SYS_AUTOCONFIG 0 6
1 1 SYS_AUTOSTART 4001 6
1 1 SYS_CAL_ACCEL 0 6
1 1 SYS_CAL_BARO 0 6
1 1 SYS_CAL_GYRO 0 6
1 1 SYS_CAL_TDEL 24 6
1 1 SYS_CAL_TMAX 10 6
1 1 SYS_CAL_TMIN 5 6
1 1 SYS_COMPANION 921600 6
1 1 SYS_FMU_TASK 0 6
1 1 SYS_HITL 0 6
1 1 SYS_LOGGER 1 6
1 1 SYS_MC_EST_GROUP 1 6
1 1 SYS_PARAM_VER 1 6
1 1 SYS_RESTART_TYPE 0 6
1 1 SYS_STCK_EN 1 6
1 1 SYS_USE_IO 1 6
1 1 TC_A0_ID 0 6
1 1 TC_A0_SCL_0 1.000000000000000000 9
1 1 TC_A0_SCL_1 1.000000000000000000 9
1 1 TC_A0_SCL_2 1.000000000000000000 9
1 1 TC_A0_TMAX 100.000000000000000000 9
1 1 TC_A0_TMIN 0.000000000000000000 9
1 1 TC_A0_TREF 25.000000000000000000 9
1 1 TC_A0_X0_0 0.000000000000000000 9
1 1 TC_A0_X0_1 0.000000000000000000 9
1 1 TC_A0_X0_2 0.000000000000000000 9
1 1 TC_A0_X1_0 0.000000000000000000 9
1 1 TC_A0_X1_1 0.000000000000000000 9
1 1 TC_A0_X1_2 0.000000000000000000 9
1 1 TC_A0_X2_0 0.000000000000000000 9
1 1 TC_A0_X2_1 0.000000000000000000 9
1 1 TC_A0_X2_2 0.000000000000000000 9
1 1 TC_A0_X3_0 0.000000000000000000 9
1 1 TC_A0_X3_1 0.000000000000000000 9
1 1 TC_A0_X3_2 0.000000000000000000 9
1 1 TC_A1_ID 0 6
1 1 TC_A1_SCL_0 1.000000000000000000 9
1 1 TC_A1_SCL_1 1.000000000000000000 9
1 1 TC_A1_SCL_2 1.000000000000000000 9
1 1 TC_A1_TMAX 100.000000000000000000 9
1 1 TC_A1_TMIN 0.000000000000000000 9
1 1 TC_A1_TREF 25.000000000000000000 9
1 1 TC_A1_X0_0 0.000000000000000000 9
1 1 TC_A1_X0_1 0.000000000000000000 9
1 1 TC_A1_X0_2 0.000000000000000000 9
1 1 TC_A1_X1_0 0.000000000000000000 9
1 1 TC_A1_X1_1 0.000000000000000000 9
1 1 TC_A1_X1_2 0.000000000000000000 9
1 1 TC_A1_X2_0 0.000000000000000000 9
1 1 TC_A1_X2_1 0.000000000000000000 9
1 1 TC_A1_X2_2 0.000000000000000000 9
1 1 TC_A1_X3_0 0.000000000000000000 9
1 1 TC_A1_X3_1 0.000000000000000000 9
1 1 TC_A1_X3_2 0.000000000000000000 9
1 1 TC_A2_ID 0 6
1 1 TC_A2_SCL_0 1.000000000000000000 9
1 1 TC_A2_SCL_1 1.000000000000000000 9
1 1 TC_A2_SCL_2 1.000000000000000000 9
1 1 TC_A2_TMAX 100.000000000000000000 9
1 1 TC_A2_TMIN 0.000000000000000000 9
1 1 TC_A2_TREF 25.000000000000000000 9
1 1 TC_A2_X0_0 0.000000000000000000 9
1 1 TC_A2_X0_1 0.000000000000000000 9
1 1 TC_A2_X0_2 0.000000000000000000 9
1 1 TC_A2_X1_0 0.000000000000000000 9
1 1 TC_A2_X1_1 0.000000000000000000 9
1 1 TC_A2_X1_2 0.000000000000000000 9
1 1 TC_A2_X2_0 0.000000000000000000 9
1 1 TC_A2_X2_1 0.000000000000000000 9
1 1 TC_A2_X2_2 0.000000000000000000 9
1 1 TC_A2_X3_0 0.000000000000000000 9
1 1 TC_A2_X3_1 0.000000000000000000 9
1 1 TC_A2_X3_2 0.000000000000000000 9
1 1 TC_A_ENABLE 0 6
1 1 TC_B0_ID 0 6
1 1 TC_B0_SCL 1.000000000000000000 9
1 1 TC_B0_TMAX 75.000000000000000000 9
1 1 TC_B0_TMIN 5.000000000000000000 9
1 1 TC_B0_TREF 40.000000000000000000 9
1 1 TC_B0_X0 0.000000000000000000 9
1 1 TC_B0_X1 0.000000000000000000 9
1 1 TC_B0_X2 0.000000000000000000 9
1 1 TC_B0_X3 0.000000000000000000 9
1 1 TC_B0_X4 0.000000000000000000 9
1 1 TC_B0_X5 0.000000000000000000 9
1 1 TC_B1_ID 0 6
1 1 TC_B1_SCL 1.000000000000000000 9
1 1 TC_B1_TMAX 75.000000000000000000 9
1 1 TC_B1_TMIN 5.000000000000000000 9
1 1 TC_B1_TREF 40.000000000000000000 9
1 1 TC_B1_X0 0.000000000000000000 9
1 1 TC_B1_X1 0.000000000000000000 9
1 1 TC_B1_X2 0.000000000000000000 9
1 1 TC_B1_X3 0.000000000000000000 9
1 1 TC_B1_X4 0.000000000000000000 9
1 1 TC_B1_X5 0.000000000000000000 9
1 1 TC_B2_ID 0 6
1 1 TC_B2_SCL 1.000000000000000000 9
1 1 TC_B2_TMAX 75.000000000000000000 9
1 1 TC_B2_TMIN 5.000000000000000000 9
1 1 TC_B2_TREF 40.000000000000000000 9
1 1 TC_B2_X0 0.000000000000000000 9
1 1 TC_B2_X1 0.000000000000000000 9
1 1 TC_B2_X2 0.000000000000000000 9
1 1 TC_B2_X3 0.000000000000000000 9
1 1 TC_B2_X4 0.000000000000000000 9
1 1 TC_B2_X5 0.000000000000000000 9
1 1 TC_B_ENABLE 0 6
1 1 TC_G0_ID 0 6
1 1 TC_G0_SCL_0 1.000000000000000000 9
1 1 TC_G0_SCL_1 1.000000000000000000 9
1 1 TC_G0_SCL_2 1.000000000000000000 9
1 1 TC_G0_TMAX 100.000000000000000000 9
1 1 TC_G0_TMIN 0.000000000000000000 9
1 1 TC_G0_TREF 25.000000000000000000 9
1 1 TC_G0_X0_0 0.000000000000000000 9
1 1 TC_G0_X0_1 0.000000000000000000 9
1 1 TC_G0_X0_2 0.000000000000000000 9
1 1 TC_G0_X1_0 0.000000000000000000 9
1 1 TC_G0_X1_1 0.000000000000000000 9
1 1 TC_G0_X1_2 0.000000000000000000 9
1 1 TC_G0_X2_0 0.000000000000000000 9
1 1 TC_G0_X2_1 0.000000000000000000 9
1 1 TC_G0_X2_2 0.000000000000000000 9
1 1 TC_G0_X3_0 0.000000000000000000 9
1 1 TC_G0_X3_1 0.000000000000000000 9
1 1 TC_G0_X3_2 0.000000000000000000 9
1 1 TC_G1_ID 0 6
1 1 TC_G1_SCL_0 1.000000000000000000 9
1 1 TC_G1_SCL_1 1.000000000000000000 9
1 1 TC_G1_SCL_2 1.000000000000000000 9
1 1 TC_G1_TMAX 100.000000000000000000 9
1 1 TC_G1_TMIN 0.000000000000000000 9
1 1 TC_G1_TREF 25.000000000000000000 9
1 1 TC_G1_X0_0 0.000000000000000000 9
1 1 TC_G1_X0_1 0.000000000000000000 9
1 1 TC_G1_X0_2 0.000000000000000000 9
1 1 TC_G1_X1_0 0.000000000000000000 9
1 1 TC_G1_X1_1 0.000000000000000000 9
1 1 TC_G1_X1_2 0.000000000000000000 9
1 1 TC_G1_X2_0 0.000000000000000000 9
1 1 TC_G1_X2_1 0.000000000000000000 9
1 1 TC_G1_X2_2 0.000000000000000000 9
1 1 TC_G1_X3_0 0.000000000000000000 9
1 1 TC_G1_X3_1 0.000000000000000000 9
1 1 TC_G1_X3_2 0.000000000000000000 9
1 1 TC_G2_ID 0 6
1 1 TC_G2_SCL_0 1.000000000000000000 9
1 1 TC_G2_SCL_1 1.000000000000000000 9
1 1 TC_G2_SCL_2 1.000000000000000000 9
1 1 TC_G2_TMAX 100.000000000000000000 9
1 1 TC_G2_TMIN 0.000000000000000000 9
1 1 TC_G2_TREF 25.000000000000000000 9
1 1 TC_G2_X0_0 0.000000000000000000 9
1 1 TC_G2_X0_1 0.000000000000000000 9
1 1 TC_G2_X0_2 0.000000000000000000 9
1 1 TC_G2_X1_0 0.000000000000000000 9
1 1 TC_G2_X1_1 0.000000000000000000 9
1 1 TC_G2_X1_2 0.000000000000000000 9
1 1 TC_G2_X2_0 0.000000000000000000 9
1 1 TC_G2_X2_1 0.000000000000000000 9
1 1 TC_G2_X2_2 0.000000000000000000 9
1 1 TC_G2_X3_0 0.000000000000000000 9
1 1 TC_G2_X3_1 0.000000000000000000 9
1 1 TC_G2_X3_2 0.000000000000000000 9
1 1 TC_G_ENABLE 0 6
1 1 THR_MDL_FAC 0.000000000000000000 9
1 1 TRIG_MODE 0 6
1 1 TRIM_PITCH 0.000000000000000000 9
1 1 TRIM_ROLL 0.000000000000000000 9
1 1 TRIM_YAW 0.000000000000000000 9
1 1 VT_B_DEC_MSS 2.000000000000000000 9
1 1 VT_B_REV_DEL 0.000000000000000000 9

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 177 KiB

BIN
docs/assets/cam_calib1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
docs/assets/cam_calib2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

BIN
docs/assets/cam_calib3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

BIN
docs/assets/cam_calib4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/assets/cup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
docs/assets/github-fork.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

BIN
docs/assets/viz.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

23
docs/en/3dscan.md Normal file
View File

@@ -0,0 +1,23 @@
# 3D-scanning drone
<img src="../assets/3d_drone_1.jpg" title="3D-scanning drone">
The project was created in collaboration with Texel inc. that develops 3D-scanners for scanning people.
Our fellows from Texel provided a module consisting of a Raspberry Pi and a PrimeSense 3D-sensor.
We provided a Clever 3 drone that's capable of autonomous flight and wrote a flight program.
To make it all work we conducted many tests, made changes in the drone's design and tuned the drone properly.
## Video
<iframe width="966" height="543" src="https://www.youtube.com/embed/aqBION3TVhg" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## Team
The project was made by:
* Timofei Kondratiev [Copter Express] - drone assembly, writing and debugging the program, conducting tests;
* Anton Maltsev [Copter Express] - modeling of the protection of the propellers;
* Andrei Poskonin [Texel] - modifying the Texel's software to work on Raspberry Pi.

View File

@@ -45,16 +45,20 @@
* [Computer vision basics](camera.md) * [Computer vision basics](camera.md)
* [LED strip](leds.md) * [LED strip](leds.md)
* [Using rviz and rqt](rviz.md) * [Using rviz and rqt](rviz.md)
* [Working with GPIO](gpio.md)
* [Interfacing with a sonar](sonar.md) * [Interfacing with a sonar](sonar.md)
* [Interfacing with a laser rangefinder](laser.md) * [Interfacing with a laser rangefinder](laser.md)
* [PX4 Simulation](sitl.md) * [PX4 Simulation](sitl.md)
* [Software autorun](autolaunch.md) * [Software autorun](autolaunch.md)
* [Hostname](hostname.md)
* [Controlling the copter from Arduino](arduino.md) * [Controlling the copter from Arduino](arduino.md)
* [Using an external 3G modem](3g.md) * [Using an external 3G modem](3g.md)
* Clever-based projects * Clever-based projects
* [Copter spheric guard](shield.md) * [Copter spheric guard](shield.md)
* [Face recognition system](face_recognition.md) * [Face recognition system](face_recognition.md)
* [Android RC app](android.md) * [Android RC app](android.md)
* [3D-scanning drone](3dscan.md)
* [Human pose estimation drone control](human_pose_estimation_drone_control.md)
* [Copter Hack 2018](copterhack2018.md) * [Copter Hack 2018](copterhack2018.md)
* [Copter Hack 2017](copterhack2017.md) * [Copter Hack 2017](copterhack2017.md)
* Supplementary materials * Supplementary materials

View File

@@ -10,6 +10,12 @@
## Configuration ## Configuration
Set the `aruco` argument in `~/catkin_ws/src/clever/clever/launch/aruco.launch` to `true`:
```xml
<arg name="aruco" default="true"/>
```
In order to enable map detection set `aruco_map` and `aruco_detect` arguments to `true` in `~/catkin_ws/src/clever/clever/launch/aruco.launch`: In order to enable map detection set `aruco_map` and `aruco_detect` arguments to `true` in `~/catkin_ws/src/clever/clever/launch/aruco.launch`:
```xml ```xml

View File

@@ -10,7 +10,13 @@ Using this module along with [map-based navigation](aruco_map.md) is also possib
## Setup ## Setup
Set the `aruco_detect` argument in `~/catkin_ws/src/clever/clever/launch/aruco.launch` to `true` to automatically launch the module: Set the `aruco` argument in `~/catkin_ws/src/clever/clever/launch/aruco.launch` to `true`:
```xml
<arg name="aruco" default="true"/>
```
For enabling detection set the `aruco_detect` argument in `~/catkin_ws/src/clever/clever/launch/aruco.launch` to `true`:
```xml ```xml
<arg name="aruco_detect" default="true"/> <arg name="aruco_detect" default="true"/>

View File

@@ -11,7 +11,7 @@ An unmanned aerial vehicle with an electronic stabilization system and the numbe
## Flight controller / autopilot ## Flight controller / autopilot
**1\.** A specialized circuit-board designed for controlling a multicopter, an aircraft or another apparatus. Examples: **1\.** A specialized circuit-board designed for controlling a multicopter, an aircraft or another apparatus. Examples:
Pixhawk, Ardupilot, Naze32, CC3D. Pixhawk, ArduPilot, Naze32, CC3D.
**2\.** Software for the multicopter control circuit-board. Examples: PX4, APM, CleanFlight. **2\.** Software for the multicopter control circuit-board. Examples: PX4, APM, CleanFlight.
@@ -41,7 +41,7 @@ Clever may also be [controlled from a smartphone](rc.md).
## Arming ## Arming
Armed is the state of copter readiness for the fligh. When the gas stick is lifted, or when an external command with the target point is sent, the copter will fly. Usually, a copter starts rotating its propellers when it is switched to the "armed" state, even if the gas stick is down. Armed is the state of copter readiness for the flight. When the gas stick is lifted, or when an external command with the target point is sent, the copter will fly. Usually, a copter starts rotating its propellers when it is switched to the "armed" state, even if the gas stick is down.
The opposite state is Disarmed. The opposite state is Disarmed.

98
docs/en/gpio.md Normal file
View File

@@ -0,0 +1,98 @@
# Working with GPIO
A GPIO (General-Purpose Input/Output) pin is a programmable digital signal pin on a circuit board or a microcontroller that may act as an input or an output. Raspberry Pi has a set of easily accessible GPIO pins, some of which have hardware <abbr title="Pulse-width modulation">PWM</abbr>.
> **Info** Use the [pinout](https://pinout.xyz) for figuring out, which Raspberry Pi's pins support GPIO and PWM.
The [`pigpio`](http://abyz.me.uk/rpi/pigpio) library for interfacing with the GPIO pins is already preinstalled on [the RPi image](microsd_images.md). To interact with this library, run the appropriate daemon:
```bash
sudo systemctl start pigpiod.service
```
To enable automatic launch of the daemon, run:
```bash
sudo systemctl enable pigpiod.service
```
Example of working with the library:
```python
import time
import pigpio
# initializing connection to pigpiod
pi = pigpio.pi()
# set pin 11 mode for output
pi.set_mode(11, pigpio.OUTPUT)
# set signal of pin 11 to high
pi.write(11, 1)
time.sleep(2)
# set signal on pin 11 to low
pi.write(11, 0)
# ...
# setting pin 12 mode for input
pi.set_mode(12, pigpio.INPUT)
# read the state of pin 12
level = pi.read(12)
```
To find out the pins' numbers, consult the [Raspberry Pi pinout](https://pinout.xyz).
## Connecting servos
Servo motors are typically controlled with PWM signal. Extreme positions of servos are reached with signal widths approximately equal to 1000 and 2000 µs. Values for a specific servo can be determined experimentally.
Connect the signal wire of the servo to one of GPIO-pins of the Raspberry. To control a servo connected to the pin 13 use a code like this:
```python
import time
import pigpio
pi = pigpio.pi()
# set mode of pin 13 to output
pi.set_mode(13, pigpio.OUTPUT)
# set pin 13 to output PWM signal 1000 us
pi.set_servo_pulsewidth(13, 1000)
time.sleep(2)
# set pin 13 to output PWM signal 2000 us
pi.set_servo_pulsewidth(13, 2000)
```
## Connecting an electromagnet
![GPIO Mosfet Magnet Connection](../assets/gpio_mosfet_magnet.png)
To connect an electromagnet use a field-effect transistor (MOSFET). Connect the MOSFET to one of GPIO-pins of the Raspberry Pi. To control the magnet connected to the pin 15 use a code like this:
```python
import time
import pigpio
pi = pigpio.pi()
# set mode of pin 15 for output
pi.set_mode(15, pigpio.OUTPUT)
# enable the magnet
pi.write(15, 1)
time.sleep(2)
# disable the magnet
pi.write(15, 0)
```
> **Note** A more [comprehensive description](https://elinux.org/RPi_Low-level_peripherals) of the Raspberry Pi GPIO pins and [additional examples](https://elinux.org/RPi_GPIO_Interface_Circuits) of circuits are available at the [Embedded Linux wiki](https://elinux.org/RPi_Hub).

31
docs/en/hostname.md Normal file
View File

@@ -0,0 +1,31 @@
# Hostname
[By default](microsd_images.md) the hostname of the Clever drone is set to `clever-xxxx`, where `xxxx` are random numbers. These numbers are the same as in the [Wi-Fi SSID](wifi.md).
Thus, Clever is accessible on machines that support mDNS as `clever-xxxx.local`. You can use this name to access Clever over SSH:
```bash
ssh pi@clever-xxxx.local
```
Also, this name can be used in place of IP-address to open Clever web pages in browser, accessing ROS master, etc.
## Changing hostname
In some situations it is necessary to change Clever's hostname. You can use the `hostnamectl` utility for that:
```bash
sudo hostnamectl set-hostname newname
```
Where `newname` is the new name of the machine. `hostnamectl` utility will change the name in `/etc/hostname` file.
You should also put the new name to `/etc/hosts` file:
```txt
127.0.1.1 newname newname.local
```
Setting `newname.local` is necessary to allow ROS to resolve this name in situations where all the network interfaces are down (when Wi-Fi is turned off or disconnected).
> **Note** Changing the hostname does not affect the Wi-Fi SSID (and vice versa, changing the Wi-Fi SSID won't affect the hostname).

View File

@@ -0,0 +1,152 @@
# Human Pose Estimation drone control
## Introduction
Human pose estimation is one of the computer vision applications in order to estimate all the joints and the different poses of the human body through a special camera and a special hardware or process the images from a regular camera by machine learning and deep learning techniques. This project is about controlling the drone through the poses of a person in front of regular camera without needing to an external hardware or hard build dependencies on your computer.
## Demo
<iframe width="770" height="421" src="https://www.youtube.com/embed/ucPONeHg2lk" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="853" height="480" src="https://www.youtube.com/embed/EDcTtPLxzoU" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
In the demo video, we were using Ubuntu 18.04 and clever4 drone.
## Development
We used posenet from tensorflow.js as our human pose estimation module because it is easier to use, build, fast and compatible with different environments(Hardware and OS). You can find the work of posenet for this project [here](https://github.com/hany606/tfjs-posenet). Websockets were used as communication protocol between the browser and a running server on the drone.
## Architecture
The image below is a visualization of our architecture for the project.
![Architecture](../assets/human_pose_estimation_project_architecture.png)
This figure is made from [here](https://www.draw.io/)
## Dependencies
Before you test it you need to install on your laptop:
- Install Nodejs from [here](https://nodejs.org/en/download/). For [Ubuntu installation](https://tecadmin.net/install-latest-nodejs-npm-on-ubuntu/)
- Install Yarn package manager from [here](https://yarnpkg.com/lang/en/docs/install/). [Usual problem](https://github.com/yarnpkg/yarn/issues/3189) while installing and using yarn with Ubuntu.
- Have an experience in manual control on the drone in case of any weird behavior happen.
- Worked before with COEX drones, if this is your first time to work with COEX drones check [this](https://clever.copterexpress.com/en/).
and you are ready to build and use the required codes.
## Setup & installation
### In your main laptop
#### (It has been tested until now only on Ubuntu 18.04)
- Clone the repo of posenet in your computer or download it if you are using Windows without GitHub
```sh
git clone https://github.com/hany606/tfjs-posenet.git
```
- Do the steps of running and setup as it is described in the README [here](https://github.com/hany606/tfjs-posenet/tree/master/posenet)
### In the Raspberry Pi of the drone (Main controller)
- Access the Raspberry Pi
- [Switch to Client mode](https://clever.copterexpress.com/en/network.html) and ensure that the network has internet connection.
Notice: I have already made a bash script based on that tutorial, it is in COEX-Internship19/helpers/ called .to_client.bash
To run it:
```sh
chmod +x .to_client.bash
./.to_client <NAME_OF_NETWORK> <PASSWORD>
```
- Install the tornado library to make a WebSocket server
```sh
sudo pip install tornado
```
- Clone the main repo on the Raspberry Pi of the drone
```sh
git clone https://github.com/hany606/COEX-Internship19.git
```
- Go to the project directory
```sh
cd COEX-Internship19/projects/Human_pose_estimation_drone_control/
```
- Run the server to test that everything is correct and run the posenet, you should see a lot of data is printed in the terminal (if you are running the human pose estimation code on your main computer, just refresh the page in the browser after running the below command in Raspberry Pi)
```sh
python websocket_server_test.py
```
- Close the server using Ctrl+C
- To run the main file
```sh
python main_drone.py
```
## How to use it
- Run the server first from the Raspberry Pi from the correct directory
```sh
python main_drone.py
```
- Run Human pose estimation module on your laptop with WebSocket by
```sh
yarn websocket
```
Or refresh the page if you already run it.
- You should see the instructions on the screen of the terminal of the Raspberry Pi right now.
- Firstly, you should be visible for the camera and it is better to have a clear background without many details.
- Secondly, you should do initial pose as it is described in the images below.
- You can perform any pose and try to keep it until your drone finish doing this move that is corresponding to the pose.
- After you do the pose return to the initial pose in order to give the drone the command to listen to another pose.
- If you want to stop the program, land the drone and don't return to the initial pose and press Ctrl+C to stop the drone.
## Poses
![Poses](../assets/human_pose_estimation_project_poses.jpg)
Animation is created by [this](https://justsketchme.web.app/)
## Notes
- Websockets are used to communicate between the page on the browser that runs posenet and the drone.
- As the model of posenet is already pre-trained and using tensorflow.js. So, it is quite fast and can run on different computers without any problems thanks to yarn, parcel and tensorflow.js, and we have configured the code of posenet to the minimal configuration to not require a lot of computation power.
- This project has been built in 1 week of working, it took a lot of time trying to make openpose and google colab working but unfortunately I had many errors and one I decided to move to posenet everything was pretty easy.
- If you have any comments about the codes to try to improve it, I will be happy if you can contact me through telegram: @hany606 or email: h.hamed.elanwar@gmail.com or do pull requests.
- If you have implemented any of the applications below, or do some improvements, we will be very happy for that.
## Future application
- [Drone wars](https://web.facebook.com/COEXDrones/photos/pcb.1129309377266616/1129308437266710/?type=3&theater): Control the drone during the drones battle using human poses. It requires high speed interaction and more precise control.
- Control a drone that draw graffiti using human poses and draw in real-time.
- Playing with balls like ping pong game with the drones. It may require 3D Human Pose estimation Algorithms.
- Control two drones by your arms and do some task together.
## Acknowledgments
- This project was part of an internship in COEX in July 2019. if you found any bugs or problems, you can contact me through telegram: @hany606 or email: h.hamed.elanwar@gmail.com.
- The above applications were thought by me and my internship supervisor Timofey.
## References
- [Human pose estimation guide](https://blog.nanonets.com/human-pose-estimation-2d-guide/)
- [Clever drones tutorials](https://clever.copterexpress.com/en/)
- [Posenet GitHub repo](https://github.com/tensorflow/tfjs-models/tree/master/posenet)
- [Posenet meduim article](https://medium.com/tensorflow/real-time-human-pose-estimation-in-the-browser-with-tensorflow-js-7dd0bc881cd5)
- [Tensorflow.js demos](https://www.tensorflow.org/js/demos)
- [Posenet overview](https://www.tensorflow.org/lite/models/pose_estimation/overview)

View File

@@ -52,4 +52,4 @@ Messages published in the topics may be viewed with the `rostopic` utility, e.g.
`/mavros/setpoint_raw/attitude` — sends [SET\_ATTITUDE\_TARGET](https://mavlink.io/en/messages/common.html#SET_ATTITUDE_TARGET) message. Allows setting the target attitude /angular velocity and throttle level. The values to be set are selected using the `type_mask` field `/mavros/setpoint_raw/attitude` — sends [SET\_ATTITUDE\_TARGET](https://mavlink.io/en/messages/common.html#SET_ATTITUDE_TARGET) message. Allows setting the target attitude /angular velocity and throttle level. The values to be set are selected using the `type_mask` field
`/mavros/setpoint_raw/global` — sends [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://mavlink.io/en/messages/common.html#SET_POSITION_TARGET_GLOBAL_INT). Allows setting the target attitude in global coordinates \(latitude, longitude, altitude\) and flight speed. **May not be supported in earlier releases of PX4** \([issue](https://github.com/PX4/Firmware/issues/7552)\). `/mavros/setpoint_raw/global` — sends [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://mavlink.io/en/messages/common.html#SET_POSITION_TARGET_GLOBAL_INT). Allows setting the target attitude in global coordinates \(latitude, longitude, altitude\) and flight speed. **Not supported in PX4** \([issue](https://github.com/PX4/Firmware/issues/7552)\).

View File

@@ -1,22 +1,21 @@
# Configuring Wi-Fi # Configuring Wi-Fi
The Raspberry Pi Wi-Fi adapter of has two main operating modes: The Raspberry Pi Wi-Fi adapter has two main operating modes:
1. **Client mode** RPi connects to an existing Wi-Fi network. 1. **Client mode** RPi connects to an existing Wi-Fi network.
2. **Access point mode** RPi creates a Wi-Fi network that you can connect to. 2. **Access point mode** RPi creates a Wi-Fi network that you can connect to.
When using [the RPi image](microsd_images.md), the Wi-Fi adapter works in the [access point mode] by default (Wi-Fi.md). On our [RPi image](microsd_images.md) the Wi-Fi adapter is confuigured to use the [access point mode](Wi-Fi.md) by default.
## Changing the password or SSID (of the network name) ## Changing the password or SSID (of the network name)
1. Edit file `/etc/wpa_supplicant/wpa_supplicant.conf` (using [SSH connection](ssh.md)): 1. Edit the `/etc/wpa_supplicant/wpa_supplicant.conf` file (using [SSH connection](ssh.md)):
```bash ```bash
sudo nano /etc/wpa_supplicant/wpa_supplicant.conf sudo nano /etc/wpa_supplicant/wpa_supplicant.conf
``` ```
To change the name of the Wi-Fi network, change the value of parameter `ssid`, to change the password, change parameter `psk`. For example: In order to change the name of the Wi-Fi network, change the value of the `ssid` parameter; to change the password, change the `psk` parameter. For example:
```txt ```txt
network={ network={
@@ -33,9 +32,9 @@ When using [the RPi image](microsd_images.md), the Wi-Fi adapter works in the [a
2. Restart Raspberry Pi. 2. Restart Raspberry Pi.
> **Caution** The length of the password for the Wi-Fi network should be **at least** 8 characters. > **Caution** The Wi-Fi network password should be **at least** 8 characters.
> >
> In case of incorrect settings `wpa_supplicant.conf`, Raspberry Pi will stop distributing Wi-Fi! > If your `wpa_supplicant.conf` is not valid, Raspberry Pi will not allow Wi-Fi connections!
## Switching adapter to the client mode ## Switching adapter to the client mode
@@ -46,39 +45,27 @@ When using [the RPi image](microsd_images.md), the Wi-Fi adapter works in the [a
sudo systemctl disable dnsmasq sudo systemctl disable dnsmasq
``` ```
2. Enable obtaining IP address on the wireless interface by the DHCP client. 2. Enable DHCP client on the wireless interface to obtain IP address. In order to do this, remove the following lines from the `etc/dhcpcd.conf` file:
For this purpose, remove the following lines
```conf ```conf
interface wlan0 interface wlan0
static ip_address=192.168.11.1/24 static ip_address=192.168.11.1/24
``` ```
from file `/etc/dhcpcd.conf` manually, or run the following commands. 3. Configure `wpa_supplicant` to connect to an existing access point. Change your `/etc/wpa_supplicant/wpa_supplicant.conf` to contain the following:
```bash
sudo sed -i 's/interface wlan0//' /etc/dhcpcd.conf
sudo sed -i 's/static ip_address=192.168.11.1\/24//' /etc/dhcpcd.conf
``` ```
3. Configure `wpa_supplicant` to connect to an existing access point.
```bash
cat << EOF | sudo tee /etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1 update_config=1
country=GB country=GB
network={ network={
ssid="CLEVER" ssid="SSID"
psk="cleverwifi" psk="password"
} }
EOF
``` ```
where `CLEVER` is the name of the network, and `cleverwifi` is the password. where `SSID` is the name of the network, and `password` is its password.
4. Restart the `dhcpcd` service. 4. Restart the `dhcpcd` service.
@@ -88,35 +75,22 @@ When using [the RPi image](microsd_images.md), the Wi-Fi adapter works in the [a
## Switching the adapter to the access point mode ## Switching the adapter to the access point mode
1. Enable the static IP address in the wireless interface. 1. Enable the static IP address in the wireless interface. Add the following lines to your `/etc/dhcpcd.conf` file:
For this purpose, add the following lines
```conf ```conf
interface wlan0 interface wlan0
static ip_address=192.168.11.1/24 static ip_address=192.168.11.1/24
``` ```
to file `/etc/dhcpcd.conf` manually, or run the following command. 2. Configure wpa_supplicant to work in the access point mode. Change your `/etc/wpa_supplicant/wpa_supplicant.conf` file to contain the following:
```bash
cat << EOF | sudo tee -a /etc/dhcpcd.conf
interface wlan0
static ip_address=192.168.11.1/24
EOF
``` ```
2. Configure wpa_supplicant to work in the access point mode.
```bash
cat << EOF | sudo tee /etc/wpa_supplicant/wpa_supplicant.conf
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1 update_config=1
country=GB country=GB
network={ network={
ssid="CLEVER-$(head -c 100 /dev/urandom | xxd -ps -c 100 | sed -e 's/[^0-9]//g' | cut -c 1-4)" ssid="CLEVER-1234"
psk="cleverwifi" psk="cleverwifi"
mode=2 mode=2
proto=RSN proto=RSN
@@ -125,23 +99,23 @@ When using [the RPi image](microsd_images.md), the Wi-Fi adapter works in the [a
group=CCMP group=CCMP
auth_alg=OPEN auth_alg=OPEN
} }
EOF
``` ```
3. Restart the `dhcpcd` service. where `CLEVER-1234` is the network name and `cleverwifi` is the password.
```bash 3. Enable the `dnsmasq` service.
sudo systemctl restart dhcpcd
```
4. Enable the `dnsmasq` service.
```bash ```bash
sudo systemctl enable dnsmasq sudo systemctl enable dnsmasq
sudo systemctl start dnsmasq sudo systemctl start dnsmasq
``` ```
4. Restart the `dhcpcd` service.
```bash
sudo systemctl restart dhcpcd
```
___ ___
Below you can read more about how RPi networking is organized. Below you can read more about how RPi networking is organized.
@@ -150,16 +124,16 @@ Below you can read more about how RPi networking is organized.
Network operation in the [image](microsd_images.md) is supported by two pre-installed services: Network operation in the [image](microsd_images.md) is supported by two pre-installed services:
* **networking** — the service enables all network interfaces at the moment of start [5]. * **networking** — the service enables all network interfaces at startup [5].
* **dhcpcd** — the service ensures configuration of addressing and routing on the interfaces obtained dynamically, or specified statically in the config file. * **dhcpcd** — the service ensures that configuration of addressing and routing on the interfaces is obtained dynamically or specified statically in the config file.
To work in the router (access point) mode, RPi requires a DHCP server. It is used to automatically send the settings of the current network to connected clients. `isc-dhcp-server` or `dnsmasq` may be used as such server. To work in the router (access point) mode, RPi requires a DHCP server. It is used to automatically send the settings of the current network to connected clients. `isc-dhcp-server` or `dnsmasq` may be used for this.
### dhcpcd ### dhcpcd
Starting with Raspbian Jessy, network settings are no longer defined in the `/etc/network/interfaces` file. Now `dhcpcd` is used for sending addressing and routing settings[4]. Starting with Raspbian Jessie, network settings are no longer defined in the `/etc/network/interfaces` file. Now `dhcpcd` is used for sending addressing and routing settings[4].
By default, a dhcp client is enabled in all interfaces. Settings of the interfaces are changed in the `/etc/dhcpcd.conf` file. To start an access point, specify a static IP address. To do this, add the following lines to the end of the file: By default, a dhcp client is enabled in all interfaces. Settings for network interfaces are changed in the `/etc/dhcpcd.conf` file. An access point should have a static IP address. To specify one, add the following lines to the end of the file:
``` ```
interface wlan0 interface wlan0
@@ -170,9 +144,9 @@ static ip_address=192.168.11.1/24
### wpa_supplicant ### wpa_supplicant
**wpa_supplicant** — the service configures the Wi-Fi adapter. The `wpa_supplicant` service runs not as standalone (although it exists as such), and runs as a `dhcpcd` child process. **wpa_supplicant** — the service configures the Wi-Fi adapter. The `wpa_supplicant` service does not run as a standalone service (although it exists as such), but is instead launched as a `dhcpcd` child process.
By default, the config file should contain path `/etc/wpa_supplicant/wpa_supplicant.conf`. By default the config file is `/etc/wpa_supplicant/wpa_supplicant.conf`.
An example of the configuration file: An example of the configuration file:
```conf ```conf
@@ -192,7 +166,7 @@ network={
} }
``` ```
Inside the config file, general `wpa_supplicant` settings, and the settings for the adapter configuration are specified. The configuration file also contains `network` section with the basic settings of the Wi-Fi network, such as network SSID, password, adapter operating mode. There may be several such sections, but only the first one is the working one. For example, if the first section contains connection to an unavailable network, the adapter will be configured according to a next valid section, if there is one. Read more about the syntax of `wpa_supplicant.conf` [TODO WIKI]. Inside the config file, general `wpa_supplicant` settings, and the settings for the adapter configuration are specified. The configuration file also contains `network` section with the basic settings of the Wi-Fi network, such as network SSID, password, adapter operating mode. There may be several `network` sections, but only the first valid one is used. For example, if the first section contains a connection to an unavailable network, the adapter will be configured according to a next valid section, if there is one. Read more about the syntax of `wpa_supplicant.conf` [TODO WIKI].
#### wpa_passphrase #### wpa_passphrase

View File

@@ -23,7 +23,7 @@ ROS_MASTER_URI=http://192.168.11.1:11311 rviz
If connection is not established, make sure the `.bashrc` of Clever contains line: If connection is not established, make sure the `.bashrc` of Clever contains line:
```(bash) ```(bash)
export ROS_IP=192.168.11.1 export ROS_HOSTNAME=`hostname`.local
``` ```
Using rviz Using rviz

View File

@@ -180,7 +180,7 @@ To unlock the copter, disable the safety button
1. Go to the Power menu. 1. Go to the Power menu.
2. Set the Number of cells — 4S. 2. Set the Number of cells — 4S.
3. Set parameter Full Voltage (per cell) - 4.20 V. 3. Set parameter Full Voltage (per cell) - 4.20 V; Empty Voltage (per cell) - 3.50V.
To save the changes, restart Pihxawk: To save the changes, restart Pihxawk:

View File

@@ -3,9 +3,9 @@ PX4 Simulation
Main article: https://dev.px4.io/en/simulation/ Main article: https://dev.px4.io/en/simulation/
PX4 simulation is possible in Linux and macOS with the use of physical environment simulation systems [jMavSim](https://pixhawk.org/dev/hil/jmavsim) and [the Gazebo](http://gazebosim.org). PX4 simulation is possible in Linux and macOS with the use of physical environment simulation systems [jMAVSim](https://pixhawk.org/dev/hil/jmavsim) and [the Gazebo](http://gazebosim.org).
jMavSim is a lightweight environment intended only for testing multi-rotor aircraft systems; Gazebo is a versatile environment for all types of robots. jMAVSim is a lightweight environment intended only for testing multi-rotor aircraft systems; Gazebo is a versatile environment for all types of robots.
Launching PX4 SITL Launching PX4 SITL
-- --
@@ -17,12 +17,12 @@ git clone https://github.com/PX4/Firmware.git
cd Firmware cd Firmware
``` ```
jMavSim jMAVSim
-- --
Main article: https://dev.px4.io/en/simulation/jmavsim.html Main article: https://dev.px4.io/en/simulation/jmavsim.html
For simulation using the jMavSim lightweight environment, use the following command: For simulation using the jMAVSim lightweight environment, use the following command:
```(bash) ```(bash)
make posix_sitl_default jmavsim make posix_sitl_default jmavsim

View File

@@ -4,15 +4,6 @@ Code examples
Python Python
--- ---
<!-- markdownlint-disable MD031 -->
> **Note** When using cyrillic simbols with UTF-8 encoding, specify the encoding in the beginning of the program:
> ```python
> # -*- coding: utf-8 -*-
> ```
<!-- markdownlint-enable MD031 -->
### # {#distance} ### # {#distance}
Calculating the distance between two points (**important**: the points are to be in the same [system of coordinates](frames.md)): Calculating the distance between two points (**important**: the points are to be in the same [system of coordinates](frames.md)):
@@ -46,7 +37,7 @@ start = get_telemetry()
print navigate(z=z, speed=0.5, frame_id='body', auto_arm=True) print navigate(z=z, speed=0.5, frame_id='body', auto_arm=True)
# Waiting for takeoff # Waiting for takeoff
while True: while not rospy.is_shutdown():
# Checking current altitude # Checking current altitude
if get_telemetry().z - start.z + z < tolerance: if get_telemetry().z - start.z + z < tolerance:
# Takeoff complete # Takeoff complete
@@ -54,6 +45,20 @@ while True:
rospy.sleep(0.2) rospy.sleep(0.2)
``` ```
This code can be wrapped in a function:
```python
def takeoff_wait(alt, speed=0.5, tolerance=0.2):
start = get_telemetry()
print navigate(z=alt, speed=speed, frame_id='body', auto_arm=True)
while not rospy.is_shutdown():
if get_telemetry().z - start.z + z < tolerance:
break
rospy.sleep(0.2)
```
### # {#block-nav} ### # {#block-nav}
Flying towards a point and waiting for copter's arrival: Flying towards a point and waiting for copter's arrival:
@@ -66,7 +71,7 @@ frame_id='aruco_map'
print navigate(frame_id=frame_id, x=1, y=2, z=3, speed=0.5) print navigate(frame_id=frame_id, x=1, y=2, z=3, speed=0.5)
# Wait for the copter to arrive at the requested point # Wait for the copter to arrive at the requested point
while True: while not rospy.is_shutdown():
telem = get_telemetry(frame_id=frame_id) telem = get_telemetry(frame_id=frame_id)
# Calculating the distance to the requested point # Calculating the distance to the requested point
if get_distance(1, 2, 3, telem.x, telem.y, telem.z) < tolerance: if get_distance(1, 2, 3, telem.x, telem.y, telem.z) < tolerance:
@@ -75,6 +80,25 @@ while True:
rospy.sleep(0.2) rospy.sleep(0.2)
``` ```
### # {#block-land}
Landing and waiting until the copter lands:
```python
land()
while get_telemetry().armed:
rospy.sleep(0.2)
```
This code can be wrapped in a function:
```python
def land_wait():
land()
while get_telemetry().armed:
rospy.sleep(0.2)
```
### # {#disarm} ### # {#disarm}
Quadcopter disarm (disabling propellers **the copter will fall down**): Quadcopter disarm (disabling propellers **the copter will fall down**):

23
docs/ru/3dscan.md Normal file
View File

@@ -0,0 +1,23 @@
# Дрон для 3D-сканирования человека
<img src="../assets/3d_drone_1.jpg" title="Сканирующий дрон">
Проект был создан совместно с компанией Texel, которая разрабатывает стационарные 3D-сканеры для создания модели человека.
Коллеги из Texel предоставили модуль, состоящий из Raspberry Pi с установленным ПО для сканирования и камеры для захвата 3D-изображения PrimeSense.
С нашей стороны был предоставлен квадрокоптер Клевер 3 с установленным оборудованием для автономного полета, а также написана полетная программа.
Мы провели множество тестов и настроек, внесли много изменений в конструкции дрона, чтобы в итоге добиться результата.
## Видео
<iframe width="966" height="543" src="https://www.youtube.com/embed/aqBION3TVhg" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## Команда
Над проектом работали:
* Тимофей Кондратьев [Copter Express] - сборка дрона, написание и отладка программы, проведение тестов;
* Антон Мальцев [Copter Express] - моделирование защиты пропеллеров;
* Андрей Посконин [Texel] - импорт ПО Texel на Raspberr Pi, совместные тесты;

View File

@@ -54,18 +54,22 @@
* [Работа с SITL](sitl.md) * [Работа с SITL](sitl.md)
* [Контейнер с симулятором](sitl_docker.md) * [Контейнер с симулятором](sitl_docker.md)
* [Автозапуск ПО](autolaunch.md) * [Автозапуск ПО](autolaunch.md)
* [Имя хоста](hostname.md)
* [Взаимодействие с Arduino](arduino.md) * [Взаимодействие с Arduino](arduino.md)
* [3G-модем](3g.md) * [3G-модем](3g.md)
* [Установка ROS Kinetic](ros-install.md) * [Установка ROS Kinetic](ros-install.md)
* Проекты на базе Клевера * Проекты на базе Клевера
* [Генератор ArUco карт](arucogenmap.md) * [Генератор ArUco карт](arucogenmap.md)
* [Модель аэротакси в городе](bigchallenges.md)
* [Шаровая защита коптера](shield.md) * [Шаровая защита коптера](shield.md)
* [Распознавание лиц](face_recognition.md) * [Распознавание лиц](face_recognition.md)
* [Подсчет количества объектов c камеры](object_counting.md) * [Подсчет количества объектов c камеры](object_counting.md)
* [Пульт на Андроид](android.md) * [Пульт на Андроид](android.md)
* [Блочный конструктор полета](clever_blocks.md) * [Блочный конструктор полета](clever_blocks.md)
* [Дрон для 3D-сканирования человека](3dscan.md)
* [CopterHack-2018](copterhack2018.md) * [CopterHack-2018](copterhack2018.md)
* [CopterHack-2017](copterhack2017.md) * [CopterHack-2017](copterhack2017.md)
* [Робокросс-2019](robocross2019.md)
* Дополнительные материалы * Дополнительные материалы
* [Олимпиада НТИ 2019](nti2019.md) * [Олимпиада НТИ 2019](nti2019.md)
* [Вклад в Клевер](contributing.md) * [Вклад в Клевер](contributing.md)

View File

@@ -10,6 +10,12 @@
## Конфигурирование ## Конфигурирование
Аргумент `aruco` в файле `~/catkin_ws/src/clever/clever/launch/clever.launch` должен быть в значении `true`:
```xml
<arg name="aruco" default="true"/>
```
Для включения распознавания карт маркеров аргументы `aruco_map` и `aruco_detect` в файле `~/catkin_ws/src/clever/clever/launch/aruco.launch` должны быть в значении `true`: Для включения распознавания карт маркеров аргументы `aruco_map` и `aruco_detect` в файле `~/catkin_ws/src/clever/clever/launch/aruco.launch` должны быть в значении `true`:
```xml ```xml
@@ -55,7 +61,7 @@ rosrun aruco_pose genmap.py length x y dist_x dist_y first > ~/catkin_ws/src/cle
rosrun aruco_pose genmap.py 0.33 2 4 1 1 0 > ~/catkin_ws/src/clever/aruco_pose/map/test_map.txt rosrun aruco_pose genmap.py 0.33 2 4 1 1 0 > ~/catkin_ws/src/clever/aruco_pose/map/test_map.txt
``` ```
Также, можно создать карту в специальном [конструкторе](arucogenmap.md) Также, можно создать карту в специальном [конструкторе](arucogenmap.md).
### Проверка ### Проверка

View File

@@ -10,7 +10,13 @@
## Настройка ## Настройка
Для включения модуля аргумент `aruco_detect` в файле `~/catkin_ws/src/clever/clever/launch/aruco.launch` должен быть в значении `true`: Аргумент `aruco` в файле `~/catkin_ws/src/clever/clever/launch/clever.launch` должен быть в значении `true`:
```xml
<arg name="aruco" default="true"/>
```
Для включения распознавания маркеров аргумент `aruco_detect` в файле `~/catkin_ws/src/clever/clever/launch/aruco.launch` должен быть в значении `true`:
```xml ```xml
<arg name="aruco_detect" default="true"/> <arg name="aruco_detect" default="true"/>

246
docs/ru/bigchallenges.md Normal file
View File

@@ -0,0 +1,246 @@
# Аэротакси
Проект команды Human Express на проектной программе "Большие Вызовы"
## Суть проекта
Крупные города страдают из-за пробок и перегруженности транспорта. Пробки на дорогах и перегруженность транспорта влечёт за собой многие неудобства. Одной из таких проблем является отсутствие возможности быстро добраться из точки А в точку Б. При этом воздушное пространство практически не используется. Предлагаемое решение заключается в создании системы, которая в режиме реального времени наблюдает и контролирует движение беспилотных летательных аппаратов. Такое решение приводит к полной автоматизации процесса полёта и исключает возможность воздушно-транспортных происшествий. В результате проделанной работы удалось сделать систему, которая состоит из нескольких дронов и сервера. Сервер прокладывает маршрут дронам из начальной точки в заданную по проложенным дорогам. Также в работу сервера входит логистика, благодаря которой беспилотники не сталкиваются в полёте.
<iframe width="966" height="543" src="https://www.youtube.com/embed/nq1DKjacs6U" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
<iframe width="966" height="543" src="https://www.youtube.com/embed/QdgZ4lzPmwQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## Настройка сервера
Чтобы скачать проект на сервер выполните команду
```bash
git clone https://github.com/Tennessium/HUEX
```
Перед началом работы с системой необходимо подключить устройство к сети WiFi (например, раздать со смартфона) установить на него необходимые библиотеки
```bash
cd HUEX/server
pip install -r requirements.txt
```
В случае необходимости вы можете изменить высоту эшелонов и разрешенные IP-адреса для доступа к Центру управления полетами в файле *server/consts.py* и настроить поле с метками в *server/static/map.txt*.
Теперь можно запустить сервер написав в командную строку
```bash
python manage.py runserver 0.0.0.0:8000
```
Чтобы перейти на веб страницу наберите в адресной строке ip адрес сервера в локальной сети и укажите порт 8000 (`http://ip:8000`).
[Как узнать ip адрес устройства](https://remontka.pro/ip-adres/)
## Настройка коптеров
В первую очередь подготовьте SD-карту с образом Clever ([Инструкция](https://clever.copterexpress.com/ru/microsd_images.html))
Чтобы скачать проект на Raspberry Pi в коптере выполните команду
```bash
git clone https://github.com/Tennessium/HUEX
```
Перед началом работы с системой необходимо перевести коптеры в режим клиента и подключить к сети WiFi. Вы можете воспользоваться [этим мануалом](https://clever.copterexpress.com/ru/network.html#переключение-адаптера-в-режим-клиента)
Однако, для упрощения развертывания системы на нескольких коптреах, рекомендуется использование нашего скрипта, лежащего в папке *copter/setup/*
- Перейдите в папку
```bash
cd HUEX/copter/setup/
```
- Используя любой редактор, в файле *networkData.txt* измените SSID и пароль сети
- Запустите скрипт
```bash
sudo bash networkEdit.sh
```
- Перезагрузите Raspberry Pi
Произведите установку и настройку ROS-пакета для LED-ленты
```bash
cd ~/catkin_ws/src
git clone https://github.com/bart02/ros-led-lib.git led
cd led
```
Воспользовавшись nano ledsub.py, измените переменную LED_COUNT на число светодиодов на вашей ленте
```bash
chmod +x ledsub.py
cd ~/catkin_ws
catkin_make
sudo systemctl enable /home/pi/catkin_ws/src/led/led.service
sudo systemctl start led
```
Установите необходимые пакеты
```bash
cd HUEX/clever
pip install -r requirements.txt
```
В файле *copter/consts.py* укажите IP-адрес сервера.
Для запуска основного скрипта воспользуйтсь нашим systemd-сервисом.
```bash
sudo systemctl enable /home/pi/HUEX/clever/setup/taxi.service
sudo systemctl start taxi.service
```
Скрипт будет запускаться автоматически при старте системы.
Для остановки можно воспользоваться командой
```bash
sudo systemctl stop taxi.service
```
## Веб-интерфейс центра управления полётами
<img src="../assets/cup.png" alt=""/>
В данном веб интерфейсе можно следить за полётами всех дронов на карте (масштабировать с помощью колёсика, передвигаять с помощью Alt). При нажатии на лебедя в правом верхнем углу все коптеры аварийно садятся, А при нажатии на значок "обновить" все коптеры автоматически удаляются, что приводит к удалению всех комманд и посадке активных на текущий момент коптеров.
С помощью инструментов в правом нижнем углу можно строить новые основания и рёбра.
## Веб-интерфейс заказа
Чтобы перейти на веб страницу наберите в адресной строке `http://ip:8000/m`, где *ip* - адрес сервера в сети.
## Визуализатор
Для наглядности работы системы был разработан 3D-визуализатор воздушного пространства. Он отображает систему эшелонов, поле ArUco-маркеров; позиции коптеров и направления их движения в real-time.
Запускать визуализатор можно после старта сервера. Компьютер должен быть подключен к той же сети, что и сервер.
```bash
cd viz
python main.py
```
Программа автоматически подгрузит карты маркеров и эшелонов. Если одна из них изменяется в процессе эксплуатации, перезагрузите программу для внесения изменений.
<img src="../assets/viz.png" alt=""/>
>Камеру можно передвигать при помощи клавиш *WASD* и поворачивать при помощи стрелок.
## DDoS-скрипт
Во время смены был написан простой скрипт, предназначенный для тестирования логистики. Его суть заключается в бесконечном заказе такси между случайными точками. Перед тем как запустить программу [*DDos.py*](https://github.com/Tennessium/HUEX/blob/master/DDos.py) замените параметр *static_path* в пятой строке на *ip* вашего сервера.
## API
На случай если вы захотите реализовать свою "обёртку" вы можете реализовать взаимодействие с сервером по средствам *HTTP/HTTPS* запросов
### /get
Возвращает телеметрию всех доступных коптеров.
Пример:
```json
{
"message": "OK",
"drones": [
{
"ip": "192.168.1.101",
"led": "#FF0000",
"status": "land",
"pose": {
"x": 0.24,
"y": 0.38,
"z": 0,
"yaw": 0
},
"voltage": 4.12,
"nextp": {
"led": "#FF0000",
"status": "land",
"pose": {
"x": 0.24,
"y": 0.38,
"z": 1.5,
"yaw": 0
}
}
}
]
}
```
Где
- **ip** - ip адрес коптера
<!-- markdownlint-disable MD044 -->
- **led** - цвет светодиодной ленты
<!-- markdownlint-restore MD044 -->
- **status** - *fly* или *land* - текущий статус коптера
- **pose** - позиция коптера (*x*, *y*, *z*, и *yaw*)
- **voltage** - напряжение на одной банке
<!-- markdownlint-disable MD044 -->
- **nextp** - отдаваемая коптеру команда на полёт (*led*, *status*, *pose* как выше)
<!-- markdownlint-restore MD044 -->
### /static/roads.json
Текущая карта дорог первого эшелона
Пример:
```json
{
"points": [
{
"x": 2.1,
"y": 3.5
},
{
"x": 0.6,
"y": 0.4
},
{
"x": 2.4,
"y": 0.5
}
],
"lines": [
{
"1": 2,
"2": 1
},
{
"1": 1,
"2": 0
}
]
}
```
Где
- **poits** - массив вершин графа и их координат
- **lines** - массив рёбер графа (*1* - точка из которой выходит ребро, *2* - точка куда это ребро направлено)
### /ask_taxi?o=x&t=y
Заказ такси из точки под номером *x* в точку под номером *y*. Точки *x* и *y* берутся из **/static/roads.json**
### /get_dist?o=x&t=y
Возвращает расстояние и цену за пролёт между точками.
Пример:
```json
{"dist": 5.3, "cost": 309}
```
Где
- **dist** - Дистанция в метрах
- **cost** - Цена в рублях (150₽ + 30₽ * *n* метров)

View File

@@ -1,79 +1,99 @@
# Калибровка камеры # Калибровка камеры
Компьютерное зрение получает все более широкое распространение. Зачастую, алгоритмы компьютерного зрения работают неточно, получая искаженное изображение с камеры, что особенно характерно для fisheye-камер. Для точной работы систем компьютерного зрения (например, для навигации по ArUco-маркерам) используемая камера должна быть откалибрована.
![img](../assets/img1.jpg) ![distorted](../assets/img1.jpg)
> Изображение "скруглено" ближе к краям. > Изображение "скруглено" ближе к краям.
Какой-либо алгоритм компьютерного зрения будет воспринимать информацию с этой картинки неправильно.
Какой-либо алгоритм компьютерного зрения будет воспринимать информацию с этой картинки неправильно. Для устранения подобных искажений камера, получающая изображения, должна быть откалибрована в соответствии со своими особенностями. ## Установка приложения
## Установка скрипта
Для начала, необходимо установить необходимые библиотеки: Для начала, необходимо установить необходимые библиотеки:
``` ```bash
pip install numpy pip install numpy
pip install opencv-python pip install opencv-python
pip install glob pip install pyyaml
pip install pyyaml pip install urllib2
pip install urllib.request pip install flask, flask-wtf
``` ```
Затем скачиваем скрипт из репозитория: Затем скачиваем исходный код из репозитория и проводим установку:
```bash ```bash
git clone https://github.com/tinderad/clever_cam_calibration.git git clone https://github.com/tinderad/calibration_web_2.7.git
``` cd calibration_web_2.7.git
Переходим в скачанную папку и устанавливаем скрипт:
```bash
cd clever_cam_calibration
sudo python setup.py build sudo python setup.py build
sudo python setup.py install sudo python setup.py install
``` ```
Если вы используете Windows, тогда скачайте архив из [репозитория](https://github.com/tinderad/clever_cam_calibration/archive/master.zip), распакуйте его и установите:
```bash
cd path\to\archive\clever_cam_calibration\
python setup.py build
python setup.py install
```
> path\to\archive - путь до распакованного архива.
## Подготовка к калибровке ## Подготовка к калибровке
Вам необходимо подготовить калибровочную мишень. Она представляет собой «шахматную доску». Файл можно взять [отсюда](https://www.oreilly.com/library/view/learning-opencv-3/9781491937983/assets/lcv3_ac01.png). Вам необходимо подготовить калибровочную мишень. Она представляет собой «шахматную доску». Файл можно взять [отсюда](https://www.oreilly.com/library/view/learning-opencv-3/9781491937983/assets/lcv3_ac01.png).
Наклейте распечатанную мишень на любую твердую поверхность. Посчитайте количество пересечений в длину и в ширину доски, измерьте размер клетки (в мм). Наклейте распечатанную мишень на любую твердую поверхность. Посчитайте количество пересечений в длину и в ширину доски, измерьте размер клетки (в мм), как указано на изображении.
![img](../assets/chessboard.jpg) ![asd](../assets/chessboard.jpg)
Включите Клевер и подключитесь к его Wi-Fi. Включите Клевер и подключитесь к его Wifi.
> Перейдите на 192.168.11.1:8080 и проверьте, получает ли компьютер изображения из топика image_raw. > Перейдите на _192.168.11.1:8080_ и проверьте, получает ли компьютер изображения из топика _image_raw_.
## Калибровка ## Калибровка
Запустите скрипт **_calibrate_cam_**: Подключитесь к Клеверу по протоколу SSH (например, при помощи PuTTY).
**Windows:** Запустите приложение:
```bash ```bash
>path\to\python\Scripts\calibrate_cam.exe >cd calibration_web_2.7/ccc_server
>python app.py
``` ```
> path\to\Python - путь до директории Python Далее вам необходимо на компьютере открыть в браузере страницу по адресу _192.168.11.1:8081_
**Linux:** > Порт можно настроить в файле _ccc_server/config.py_.
На открытой странице необходимо ввести параметры калибровочной мишени: количество перекрестий в длину и ширину, длину ребра квадрата. Для начала калибровки нажмите кнопку **_Start Calibration_**.
![asd](../assets/cam_calib1.png)
На следующей странице при помощи кнопки **_Catch photo_** можно делать фотографии калибровочной мишени.
![asd](../assets/cam_calib2.png)
Если программа нашла на изображении указанную мишень, откроется страница, на которой вам необходимо подтвердить корректность найденных перекрестий.
![asd](../assets/cam_calib3.png)
Если перекрестия были распознаныы правильно, нажмите на клавишу **_Add_**, и перейдите к получению новых фотографий. В противном же случае, ели перекристия были распознаны некорректно, пропустите данную фотографию при помощи клавиши **_Skip_**.
>В большинстве случаев найденные углы будут подсвечиваться разными цветами, но иногда подсветка будет становиться красной. это происходит в том случае, если углы распознаны, но неточно.
Чтобы откалибровать камеру, вам требуется сделать как минимум 25 фото шахматной доски с различных ракурсов. После преодоления данного порога появится кнопка **_Finish_**, по нажатию на которую начнется генерация калибровочного файла.
>Это может занять некоторое время.
На открывшейся странице выведется информация о результате калибровки: имя файла и re-projection error.
>re-projection error - отклонение от стандартной математической модели. Чем эта величина меньше, тем точнее проведена калибровка.
![asd](../assets/cam_calib4.png)
Программа обработает все полученные фотографии, и создаст **_.yaml_** файл в нынешней директории. При помощи этого файла можно будет выравнивать искажения на изображениях, полученных с этой камеры.
> Если вы поменяете разрешение получаемого изображения, вам нужно будет снова калибровать камеру.
## Предыдущая версия
Также вы можете воспользоваться предыдущей версией программы, не имеющей web-интерфейса.
Запустите скрипт **_calibrate_cam_**:
```bash ```bash
>calibrate_cam >calibrate_cam
``` ```
Задайте параметры доски: Задайте параметры мишени:
```bash ```bash
>calibrate_cam >calibrate_cam
@@ -95,8 +115,6 @@ help, catch (key: Enter), delete, restart, stop, finish
Чтобы откалибровать камеру, вам требуется сделать как минимум 25 фото шахматной доски с различных ракурсов. Чтобы откалибровать камеру, вам требуется сделать как минимум 25 фото шахматной доски с различных ракурсов.
![img](../assets/calibration.jpg)
Чтобы сделать фото, введите команду **_catch_**. Чтобы сделать фото, введите команду **_catch_**.
```bash ```bash
@@ -125,10 +143,10 @@ Calibration successful!
**Калибровка по существующим изображениям:** **Калибровка по существующим изображениям:**
Если же у вас уже есть изображения, то вы можете откалибровать камеру по ним при помощи скрипта **_calibrate_cam_ex_**. Если же у вас уже есть изображения, то вы можете откалибровать камеру по ним при помощи скрипта **_calibrate_from_dir_**.
```bash ```bash
>calibrate_cam_ex >calibrate_from_dir
``` ```
Указываем характеристики мишени, а так же путь до папки с изображениями: Указываем характеристики мишени, а так же путь до папки с изображениями:
@@ -145,32 +163,31 @@ Path: # Путь до папки с изображениями
Программа обработает все полученные фотографии, и создаст файл **_camera_info_****_._****_yaml_** в нынешней директории. При помощи этого файла можно будет выравнивать искажения на изображениях, полученных с этой камеры. Программа обработает все полученные фотографии, и создаст файл **_camera_info_****_._****_yaml_** в нынешней директории. При помощи этого файла можно будет выравнивать искажения на изображениях, полученных с этой камеры.
> Если вы поменяете разрешение получаемого изображения, вам нужно будет снова калибровать камеру.
## Исправление искажений ## Исправление искажений
За получение исправленного изображения отвечает функция **_get_undistorted_image(cv2_image, camera_info)_**: За получение исправленного изображения отвечает функция
**clever_cam_calibration._get_undistorted_image(cv2_image, camera_info)_**:
* **_cv2_image_**: Закодированное в массив cv2 изображение. * **_cv2_image_**: Закодированное в массив cv2 изображение.
* **_camera_****_­__****_info_**: Путь до файла калибровки. * **_camera_****_­__****_info_**: Путь до файла калибровки.
Функция возвращает массив cv2, в котором закодировано исправленное изображение. Функция возвращает массив cv2, в котором закодировано исправленное изображение.
> Если вы используете fisheye-камеру, поставляемую вместе с Клевером, то для обработки изображений разрешением 320x240 или 640x480 вы можете использовать уже существующие параметры калибровки. Для этого в качестве аргумента **_camera_info_** передайте параметры **_clever_cam_calibration.clevercamcalib.CLEVER_FISHEYE_CAM_320_** или **_clever_cam_calibration.clevercamcalib.CLEVER_FISHEYE_CAM_640_** соответственно. > Если вы используете fisheye-камеру, поставляемую вместе с Клевером, то для обработки изображений разрешением 320x240 или 640x480 вы можете использовать уже существующие параметры калибровки. Для этого в качестве аргумента **_camera_info_** передайте параметры **_clever_cam_calibration.CLEVER_FISHEYE_CAM_320_** или **_clever_cam_calibration.CLEVER_FISHEYE_CAM_640_** соответственно.
## Примеры работы ## Примеры работы
Изначальные изображения: Изначальные изображения:
![img](../assets/img1.jpg) ![asd](../assets/img1.jpg)
![img](../assets/img2.jpg) ![asd](../assets/img2.jpg)
Иcправленные изображения: Иcправленные изображения:
![img](../assets/calibresult.jpg) ![asd](../assets/calibresult.jpg)
![img](../assets/calibresult1.jpg) ![asd](../assets/calibresult1.jpg)
## Пример использования ## Пример использования
@@ -179,17 +196,17 @@ Path: # Путь до папки с изображениями
Данная программа получает изображения с камеры Клевера и выводит их на экран в исправленном виде, используя существующий калибровочный файл. Данная программа получает изображения с камеры Клевера и выводит их на экран в исправленном виде, используя существующий калибровочный файл.
```python ```python
import clevercamcalib.clevercamcalib as ccc import clever_cam_calibration as ccc
import cv2 import cv2
import urllib.request import urllib.request
import numpy as np import numpy as np
while True: while True:
req = urllib.request.urlopen('http://192.168.11.1:8080/snapshot?topic=/main_camera/image_raw') req = urllib.request.urlopen('http://192.168.11.1:8080/snapshot?topic=/main_camera/image_raw')
arr = np.asarray(bytearray(req.read()), dtype=np.uint8) arr = np.asarray(bytearray(req.read()), dtype=np.uint8)
image = cv2.imdecode(arr, -1) image = cv2.imdecode(arr, -1)
undistorted_img = ccc.get_undistorted_image(image, ccc.CLEVER_FISHEYE_CAM_640) undistorted_img = ccc.get_undistorted_image(image, ccc.CLEVER_FISHEYE_CAM_640)
cv2.imshow("undistort", undistorted_img) cv2.imshow("undistort", undistorted_img)
cv2.waitKey(33) cv2.waitKey(33)
cv2.destroyAllWindows() cv2.destroyAllWindows()
``` ```
@@ -198,13 +215,11 @@ cv2.destroyAllWindows()
Чтобы применить параметры калибровки к системе ArUco-навигации, требуется перенести калибровочный .yaml файл на Raspberry Pi Клевера и инициализировать его. Чтобы применить параметры калибровки к системе ArUco-навигации, требуется перенести калибровочный .yaml файл на Raspberry Pi Клевера и инициализировать его.
> Не забудьте подключиться к WiFI Клевера. > Не забудьте подключиться к WiFI Клевера.
Для передачи файла используется протокол SFTP. В данном примере используется программа WinSCP. Для передачи файла используется протокол SFTP. В данном примере используется программа WinSCP.
Подключимся к Raspberry Pi по SFTP: Подключимся к Raspberry Pi по SFTP:
> Пароль: _**raspberry**_ > Пароль: _**raspberry**_
![img](../assets/wcp1.png) ![img](../assets/wcp1.png)
Нажимаем “Войти”. Переходим в _**/home/pi/catkin_ws/src/clever/clever/camera_info/**_ и копируем туда калибровочный .yaml файл: Нажимаем “Войти”. Переходим в _**/home/pi/catkin_ws/src/clever/clever/camera_info/**_ и копируем туда калибровочный .yaml файл:
@@ -225,4 +240,4 @@ cv2.destroyAllWindows()
![img](../assets/pty3.jpg) ![img](../assets/pty3.jpg)
> Не забудьте изменить разрешение камеры. > Не забудьте изменить разрешение камеры в *main_camera.launch*.

View File

@@ -34,10 +34,61 @@
Более подробную информацию о Pull Request'ах смотрите [на GitHub](https://help.github.com/articles/about-pull-requests/) (англ.) или в [документации по git](https://git-scm.com/book/ru/v2/GitHub-Внесение-собственного-вклада-в-проекты) (русск.). Более подробную информацию о Pull Request'ах смотрите [на GitHub](https://help.github.com/articles/about-pull-requests/) (англ.) или в [документации по git](https://git-scm.com/book/ru/v2/GitHub-Внесение-собственного-вклада-в-проекты) (русск.).
## Ваш проект с Клевером ## Добавление статьи в GitBook
Если вы реализовали собственный интересный проект на Клевере, вы можете добавить статью о нем в раздел "Проекты на базе Клевера". > **Note** Если вы реализовали собственный интересный проект на Клевере, вы можете добавить статью о нем в раздел "Проекты на базе Клевера".
Подготовьте вашу статью и пришлите Pull Request с ней в [репозиторий Клевера]( https://github.com/CopterExpress/clever). Подготовьте вашу статью и пришлите Pull Request с ней в [репозиторий Клевера](https://github.com/CopterExpress/clever).
<!-- TODO --> 1. Сделайте форк репозитория Клевера:
<img src="../assets/github-fork.png" alt="GitHub Fork">
2. Склонируйте форк на компьютер:
```bash
git clone https://github.com/<USERNAME>/clever.git
```
3. Перейдите в директорию с форком и создайте новую ветку с названием вашей статьи (например `new-article`):
```bash
git checkout -b new-article
```
4. Напишите новую статью в разделе `docs/ru` или `docs/en` в формате [Markdown](https://ru.wikipedia.org/wiki/Markdown) (например `docs/ru/new_article.md`).
5. Поместите дополнительные визуальные материалы в папку `docs/assets` и оформите на них ссылки в вашей статье.
6. Добавьте статью в файл оглавления `SUMMARY.md` в том разделе, где вы её написали (например в `docs/ru/SUMMARY.md`):
```bash
...
* Дополнительные материалы
* [Олимпиада НТИ 2019](nti2019.md)
* [Вклад в Клевер](contributing.md)
* [Новая статья](new_article.md)
* [Сборка и модификация образа Клевера](image_building.md)
* [Прошивка ESC контроллеров](esc_firmware.md)
...
```
7. Сохраните состояние ваших изменений локально:
```bash
git add docs/
git commit -m "Add new article for Clever"
```
8. Загрузите вашу новую ветку с изменениями на ваш GitHub репозиторий с форком Клевера:
```bash
git push -u origin new-article
```
9. Перейдите на web страницу вашего форка и сделайте `pull request` вашей ветки в master Клевера:
<img src="../assets/github-pull-request.png" alt="GitHub Pull Request">
<img src="../assets/github-pull-request-create.png" alt="GitHub Create Pull">
10. Дождитесь комментариев на свою статью, сделайте правки, если потребуется.
11. Порадуйтесь своей новой полезной статье, опубликованной на https://clever.copterexpress.com!

View File

@@ -11,7 +11,7 @@
## Полетный контроллер / автопилот ## Полетный контроллер / автопилот
**1\.** Специализированная плата, спроектированная для управления мультикоптером, самолетом или другим аппаратом. Примеры: **1\.** Специализированная плата, спроектированная для управления мультикоптером, самолетом или другим аппаратом. Примеры:
Pixhawk, Ardupilot, Naze32, CC3D. Pixhawk, ArduPilot, Naze32, CC3D.
**2\.** Программное обеспечение для платы управления мультикоптером. Примеры: PX4, APM, CleanFlight. **2\.** Программное обеспечение для платы управления мультикоптером. Примеры: PX4, APM, CleanFlight.

31
docs/ru/hostname.md Normal file
View File

@@ -0,0 +1,31 @@
# Имя хоста
[По умолчанию](microsd_images.md) на Клевере установлено имя хоста (hostname) `clever-xxxx`, где `xxxx` случайные цифры. Имя хоста соответствует SSID [точки доступа Wi-Fi](wifi.md).
Таким образом, Клевер доступен на машинах, поддерживающих mDNS, под именем `clever-xxxx.local`. Вы можете использовать это имя для SSH-доступа на Клевер:
```bash
ssh pi@clever-xxxx.local
```
Также это имя может быть использовано вместо IP-адреса для открытия страницы Клевера в браузере и т. д.
## Изменение имени хоста
В некоторых ситуациях необходимо изменение имени хоста Клевера. Для это используйте утилиту `hostnamectl`:
```bash
sudo hostnamectl set-hostname newname
```
Где `newname` новое имя машины. Утилита `hostnamectl` поменяет имя в файле `/etc/hostname`.
Также необходимо прописывание нового имени в файл `/etc/hosts`:
```txt
127.0.1.1 newname newname.local
```
Прописывание `newname.local` необходимо, чтобы ROS смог разрешить это имя в ситуациях, когда все сетевые интерфейсы неактивны (отключение/разрыв связи Wi-Fi).
> **Note** Изменение имени хоста не повлечёт за собой изменений SSID точки доступа Wi-Fi (и наоборот, изменение SSID точки доступа не поменяет имя хоста).

View File

@@ -48,8 +48,8 @@ MAVROS подписывается на определенные ROS-топики
### Топики для посылки raw-пакетов ### Топики для посылки raw-пакетов
`/mavros/setpoint_raw/local` — отправка пакета [SET\_POSITION\_TARGET\_LOCAL\_NED](https://pixhawk.ethz.ch/mavlink/#SET_POSITION_TARGET_LOCAL_NED). Позволяет установить целевую позицию/целевую скорость и целевое рысканье/угловую скорость по рысканью. Выбор устанавливаемых величин осуществляется с помощью поля `type_mask`. `/mavros/setpoint_raw/local` — отправка пакета [SET\_POSITION\_TARGET\_LOCAL\_NED](https://mavlink.io/en/messages/common.html#SET_POSITION_TARGET_LOCAL_NED). Позволяет установить целевую позицию/целевую скорость и целевое рысканье/угловую скорость по рысканью. Выбор устанавливаемых величин осуществляется с помощью поля `type_mask`.
`/mavros/setpoint_raw/attitude` — отправка пакета [SET\_ATTITUDE\_TARGET](https://pixhawk.ethz.ch/mavlink/#SET_ATTITUDE_TARGET). Позвлояет установить целевую ориенатацию /угловые скорости и уровень газа. Выбор устанавливаемых величин осуществляется с помощью поля `type_mask` `/mavros/setpoint_raw/attitude` — отправка пакета [SET\_ATTITUDE\_TARGET](https://mavlink.io/en/messages/common.html#SET_ATTITUDE_TARGET). Позвлояет установить целевую ориенатацию /угловые скорости и уровень газа. Выбор устанавливаемых величин осуществляется с помощью поля `type_mask`
`/mavros/setpoint_raw/global` — отправка пакета [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://pixhawk.ethz.ch/mavlink/#SET_POSITION_TARGET_GLOBAL_INT). Позволяет установить целевую позицию в глобальных координатах \(ширина, долгота, высота\), а также скорости полета. **Не поддерживается в PX4** \([issue](https://github.com/PX4/Firmware/issues/7552)\). `/mavros/setpoint_raw/global` — отправка пакета [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://mavlink.io/en/messages/common.html#SET_POSITION_TARGET_GLOBAL_INT). Позволяет установить целевую позицию в глобальных координатах \(ширина, долгота, высота\), а также скорости полета. **Не поддерживается в PX4** \([issue](https://github.com/PX4/Firmware/issues/7552)\).

View File

@@ -103,23 +103,17 @@ Wi-Fi адаптер на Raspberry Pi имеет два основных реж
где `CLEVER-1234` название сети, а `cleverwifi` пароль. где `CLEVER-1234` название сети, а `cleverwifi` пароль.
3. Перезагрузите `systemd`. 3. Включите службу `dnsmasq`.
```bash
sudo systemctl daemon-reload
```
4. Включите службу `dnsmasq`.
```bash ```bash
sudo systemctl enable dnsmasq sudo systemctl enable dnsmasq
sudo systemctl start dnsmasq sudo systemctl start dnsmasq
``` ```
5. Перезагрузите службу `dhcpcd`. 4. Перезапустите службу `dhcpcd`.
```bash ```bash
sudo systemctl restart dhcpcd sudo systemctl start dhcpcd
``` ```
___ ___
@@ -137,7 +131,7 @@ ___
### dhcpcd ### dhcpcd
Начиная с Raspbian Jesse настройки сети больше не задаются в файле `/etc/network/interfaces`. Теперь за выдачу адресации и настройку маршрутизации отвечает `dhcpcd` [4]. Начиная с Raspbian Jessie настройки сети больше не задаются в файле `/etc/network/interfaces`. Теперь за выдачу адресации и настройку маршрутизации отвечает `dhcpcd` [4].
По умолчанию на всех интерфейсах включен dhcp-клиент. Настройки интерфейсов меняются в файле `/etc/dhcpcd.conf`. Для того, чтобы поднять точку доступа необходимо прописать статический ip-адрес. Для этого в конец файла необходимо добавить следующие строки: По умолчанию на всех интерфейсах включен dhcp-клиент. Настройки интерфейсов меняются в файле `/etc/dhcpcd.conf`. Для того, чтобы поднять точку доступа необходимо прописать статический ip-адрес. Для этого в конец файла необходимо добавить следующие строки:

25
docs/ru/robocross2019.md Normal file
View File

@@ -0,0 +1,25 @@
# Робокросс-2019
В июле 2019 команда Коптер Экспресс в 4-й раз подряд одержала победу на ежегодных испытаниях беспилотных транспортных средств "[Робокросс](http://russianrobotics.ru/activities/robokross-2019/)". Испытания проводятся на полигоне ГАЗ под Нижним Новгородом.
Основной задачей испытаний в категории БПЛА было локализовать и уничтожить цель — красный воздушный шар — в автономном режиме.
## Видео
<iframe width="560" height="315" src="https://www.youtube.com/embed/zMh5THdHuX8" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## Реализация
Команда использовала квадрокоптер на базе рамы F450 и [платформу Клевер](https://github.com/CopterExpress/clever). Полный итоговый исходный код доступен для изучения [на GitHub](https://github.com/CopterExpress/robocross2019/).
Разработанный пакет `robocross2019` разбит на модули: ROS-нодлет `red_dead_detection` распознает красный шар, `balloon.py` реализует высокоуровневую логику полета коптера.
## red_dead_detection
Нодлет `red_dead_detection` обеспечивает обнаружение красного шара на изображении с камеры квадрокоптера, смотрящей вперед (топики `/front_camera/image_raw` и `/front_camera/camera_info`). Используется простейший метод фильтрации изображения по цвету. Затем вычисляется геометрический центр полученных участков и производится компенсация искажений камеры (`cv::undistortPoints`).
Используя известное фокусное расстояние камеры (из топика `camera_info`) вычисляется вектор, направленный в сторону цели. Полученный вектор публикуется в топик `/red_dead_detection/direction`, причем его система координат (`frame_id` связана с расположением передней камеры `front_camera_optical`).
## balloon.py
Для полета к шару используется вектор направления `red_dead_detection/direction`, который используется как setpoint по скорости дрона. Угол по рысканью также устанавливается по направлению к шару. Цель считается уничтоженной, когда на протяжении заданного количества кадров общая площадь участка с красными пикселями меньше определенного порога.

View File

@@ -23,7 +23,7 @@ ROS_MASTER_URI=http://192.168.11.1:11311 rviz
Если соединение не устанавливается, необходимо убедиться, что в `.bashrc` Клевера присутствует строка: Если соединение не устанавливается, необходимо убедиться, что в `.bashrc` Клевера присутствует строка:
```bash ```bash
export ROS_HOSTNAME=raspberrypi.local export ROS_HOSTNAME=`hostname`.local
``` ```
Использование rviz Использование rviz

View File

@@ -182,7 +182,7 @@
1. Заходим в меню Power. 1. Заходим в меню Power.
2. Устанавливаем количество банок Number of cells - 4S. 2. Устанавливаем количество банок Number of cells - 4S.
3. Устанавливаем параметр Full Voltage (per cell) - 4.20V. 3. Устанавливаем параметры: Full Voltage (per cell) - 4.20V; Empty Voltage (per cell) - 3.50V.
Чтобы изменения сохранились, необходимо перезагрузить Pixhawk: Чтобы изменения сохранились, необходимо перезагрузить Pixhawk:

View File

@@ -4,9 +4,9 @@
Основная статья: https://dev.px4.io/en/simulation/ Основная статья: https://dev.px4.io/en/simulation/
Симуляция PX4 возможна в ОС Linux и macOS с использованием систем симуляции физической среды [jMavSim](https://pixhawk.org/dev/hil/jmavsim) и [Gazebo](http://gazebosim.org). Симуляция PX4 возможна в ОС GNU/Linux и macOS с использованием систем симуляции физической среды [jMAVSim](https://pixhawk.org/dev/hil/jmavsim) и [Gazebo](http://gazebosim.org).
jMavSim является легковесной средой, предназначенной только для тестирование мультироторных летательных систем; Gazebo универсальная среда для любых типов роботов. jMAVSim является легковесной средой, предназначенной только для тестирование мультироторных летательных систем; Gazebo универсальная среда для любых типов роботов.
## Запуск PX4 SITL ## Запуск PX4 SITL
@@ -17,11 +17,11 @@ git clone https://github.com/PX4/Firmware.git
cd Firmware cd Firmware
``` ```
## jMavSim ## jMAVSim
Основная статья: https://dev.px4.io/en/simulation/jmavsim.html Основная статья: https://dev.px4.io/en/simulation/jmavsim.html
Для симуляции с использованием легковесной среды jMavSim используйте команду: Для симуляции с использованием легковесной среды jMAVSim используйте команду:
```bash ```bash
make posix_sitl_default jmavsim make posix_sitl_default jmavsim

View File

@@ -54,7 +54,7 @@ start = get_telemetry()
print navigate(z=z, speed=0.5, frame_id='body', auto_arm=True) print navigate(z=z, speed=0.5, frame_id='body', auto_arm=True)
# Ожидаем взлета # Ожидаем взлета
while True: while not rospy.is_shutdown():
# Проверяем текущую высоту # Проверяем текущую высоту
if get_telemetry().z - start.z + z < tolerance: if get_telemetry().z - start.z + z < tolerance:
# Взлет завершен # Взлет завершен
@@ -62,6 +62,20 @@ while True:
rospy.sleep(0.2) rospy.sleep(0.2)
``` ```
Вышеприведенный код может быть обернут в функцию:
```python
def takeoff_wait(alt, speed=0.5, tolerance=0.2):
start = get_telemetry()
print navigate(z=alt, speed=speed, frame_id='body', auto_arm=True)
while not rospy.is_shutdown():
if get_telemetry().z - start.z + z < tolerance:
break
rospy.sleep(0.2)
```
### # {#block-nav} ### # {#block-nav}
Лететь в точку и ждать пока коптер долетит в нее: Лететь в точку и ждать пока коптер долетит в нее:
@@ -74,7 +88,7 @@ frame_id='aruco_map'
print navigate(frame_id=frame_id, x=1, y=2, z=3, speed=0.5) print navigate(frame_id=frame_id, x=1, y=2, z=3, speed=0.5)
# Ждем, пока коптер долетит до запрошенной точки # Ждем, пока коптер долетит до запрошенной точки
while True: while not rospy.is_shutdown():
telem = get_telemetry(frame_id=frame_id) telem = get_telemetry(frame_id=frame_id)
# Вычисляем расстояние до заданной точки # Вычисляем расстояние до заданной точки
if get_distance(1, 2, 3, telem.x, telem.y, telem.z) < tolerance: if get_distance(1, 2, 3, telem.x, telem.y, telem.z) < tolerance:
@@ -89,7 +103,7 @@ while True:
def navigate_wait(x, y, z, speed, frame_id, tolerance=0.2): def navigate_wait(x, y, z, speed, frame_id, tolerance=0.2):
navigate(x=x, y=y, z=z, speed=speed, frame_id=frame_id) navigate(x=x, y=y, z=z, speed=speed, frame_id=frame_id)
while True: while not rospy.is_shutdown():
telem = get_telemetry(frame_id=frame_id) telem = get_telemetry(frame_id=frame_id)
if get_distance(x, y, z, telem.x, telem.y, telem.z) < tolerance: if get_distance(x, y, z, telem.x, telem.y, telem.z) < tolerance:
break break
@@ -102,7 +116,7 @@ def navigate_wait(x, y, z, speed, frame_id, tolerance=0.2):
def navigate_wait(x, y, z, speed, frame_id, tolerance=0.2): def navigate_wait(x, y, z, speed, frame_id, tolerance=0.2):
navigate(x=x, y=y, z=z, speed=speed, frame_id=frame_id) navigate(x=x, y=y, z=z, speed=speed, frame_id=frame_id)
while True: while not rospy.is_shutdown():
telem = get_telemetry(frame_id='navigate_target') telem = get_telemetry(frame_id='navigate_target')
if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < tolerance: if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < tolerance:
break break
@@ -110,6 +124,25 @@ def navigate_wait(x, y, z, speed, frame_id, tolerance=0.2):
Такой код может быть использован для полета в том числе с использованием фрейма `body`. Такой код может быть использован для полета в том числе с использованием фрейма `body`.
### # {#block-land}
Посадка и ожидание окончания посадки:
```python
land()
while get_telemetry().armed:
rospy.sleep(0.2)
```
Вышеприведенный код может быть обернут в функцию:
```python
def land_wait():
land()
while get_telemetry().armed:
rospy.sleep(0.2)
```
### # {#disarm} ### # {#disarm}
Дизарм коптера (выключение винтов, **коптер упадет**): Дизарм коптера (выключение винтов, **коптер упадет**):
@@ -327,7 +360,7 @@ def flip():
while True: while True:
telem = get_telemetry() telem = get_telemetry()
if -math.pi + 0.1 < telem.roll < -0.2: if abs(telem.roll) > math.pi/2:
break break
rospy.loginfo('finish flip') rospy.loginfo('finish flip')

View File

@@ -5,7 +5,7 @@
Для доступа по SSH необходимо [подключиться к Raspberry Pi по Wi-Fi](wifi.md) (также возможно подключение через Ethernet-кабель). Для доступа по SSH необходимо [подключиться к Raspberry Pi по Wi-Fi](wifi.md) (также возможно подключение через Ethernet-кабель).
В Linux или macOS необходимо запустить Терминал и выполнить команду: В GNU/Linux или macOS необходимо запустить Терминал и выполнить команду:
```bash ```bash
ssh pi@192.168.11.1 ssh pi@192.168.11.1