Compare commits

...

187 Commits

Author SHA1 Message Date
Oleg Kalachev
93b146986a Remove apt-cache clean call 2024-04-15 00:15:41 +03:00
Oleg Kalachev
96c67db541 Upload image to artifacts on manual workflow run 2024-04-14 21:32:05 +03:00
Oleg Kalachev
fdc650ec2a Make apt store all downloaded deb-files 2024-04-14 18:52:17 +03:00
Oleg Kalachev
936efa985d Make clover rosdep file more priority to fix build 2024-04-13 21:32:55 +03:00
Oleg Kalachev
c55e0cb7e1 Fix geographiclib dependency 2024-04-13 19:06:44 +03:00
Oleg Kalachev
b8344dbb84 Add dictionary parameter to aruco.launch 2024-04-13 16:57:29 +03:00
Qandra Si
3b7242f3d6 docs: Add advanced SSH article (SSH keys) (#503)
---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2024-03-12 19:33:55 +03:00
Oleg Kalachev
cfeff0c74d Use installed GeographicLib CMake file
As it's done in mavros https://github.com/mavlink/mavros/pull/1775
2024-03-08 02:45:28 +03:00
Qandra Si
7d022a5af1 docs: improve UART connection to FC section (#502)
* добавил схему подключения fc-uart-rpi, дополнил и уточнил настройки для старой версии px4 (в списке рекомендуемых всё ещё 1.8.2), поправил мелкие опечатки и формулировки в eng-версии, вставил (для общности) изображение не только для fc-uart-rpi, но и для fc-usb-rpi, проверил работоспособность инструкции на своём комплекте с PX4 и прошивкой v1.8.2 (работает с rpi v2.22, и v2.23, и последней v2.24)

---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2024-03-04 19:42:04 +03:00
Oleg Kalachev
ebd9c03251 docs: fix broken image in Flysky RC article 2024-02-23 23:17:10 +03:00
Oleg Kalachev
5755300d3a Install image_geometry and dynamic_reconfigure as clover dependencies 2024-02-21 15:04:56 +03:00
Oleg Kalachev
8c5551b00b docs: fix anchor link in snippets 2024-02-13 19:22:02 +03:00
Oleg Kalachev
42c26aa645 docs: add a snippet for moving objects in Gazebo 2024-02-13 15:45:27 +03:00
Qandra Si
f91dc4df71 docs: warnings about usage of v1.10- firmwares and QGC (#500)
* сведения о совместимости QGC v4.2.0 / v4.3.0 и прошивок до /после v1.8, v1.0 и готовящейся к выпуску v1.15

* Remove unintended change

* Edit and shorten warnings

---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2024-02-08 19:15:03 +03:00
Oleg Kalachev
e31b69a790 Add possibility to trigger workflows manually 2024-01-22 01:35:52 +03:00
Oleg Kalachev
7251a76315 image: symlink assets instead of copying in documentation to save space 2023-11-04 01:28:00 +03:00
Oleg Kalachev
921e09c392 docs: minor fixes 2023-11-02 17:27:06 +03:00
Oleg Kalachev
9e69bdb01b docs: fix building by new assets size excludes 2023-11-02 06:57:02 +03:00
Oleg Kalachev
50495a9de9 docs: publish mechanical gripper model 2023-11-02 06:52:18 +03:00
Oleg Kalachev
12ccd919a2 docs: fix camera orientation setup example 2023-10-29 14:51:39 +03:00
Oleg Kalachev
f0eacfc0f7 aruco_pose: make dynamic reconfigure generator work with newer versions of OpenCV (#495) 2023-10-14 17:28:37 +03:00
Oleg Kalachev
742d0535c3 docs: add information about EKF2 parameter in PX4 1.14+ 2023-10-11 10:27:05 +03:00
Oleg Kalachev
af1b993e64 led_effect: add led parameter to specify led namespace
When using ROS namespace, subscription to mavros topics is broken
2023-10-11 10:06:18 +03:00
Oleg Kalachev
d3bda9df48 docs: add some additional tests to testing list 2023-10-11 08:30:00 +03:00
Oleg Kalachev
939086362a Run rectify nodelet in tests 2023-10-11 06:40:50 +03:00
Oleg Kalachev
7cf14373b0 main_camera.launch: argument for running image rectification nodelet 2023-10-11 00:51:02 +03:00
Oleg Kalachev
f428dfdb50 image: install stereo-msgs package 2023-10-10 08:46:03 +03:00
Oleg Kalachev
76982dc198 image: install nodelet-topic-tools package 2023-10-10 08:43:20 +03:00
Oleg Kalachev
29f01c25e0 selfcheck.py: support PX4 v1.14 EKF2 aiding parameters change
EKF2_AID_MASK has been split (EKF2_EV_CTRL, EKF2_GPS_CTRL, EKF2_OF_CTRL)
EKF2_HGT_MODE renamed to EKF2_HGT_REF
EKF2_RNG_AID is removed
2023-10-10 08:25:27 +03:00
Oleg Kalachev
7ca0ede1d7 selfcheck.py: cast parameter to int when performing bitwise operations 2023-10-10 07:47:52 +03:00
Oleg Kalachev
c3d87b1608 Update udev rules using data from PX4 sources
Adding Cube Orange, Holybro 6X and many more popular boards
2023-10-10 07:35:42 +03:00
Oleg Kalachev
47901dcff2 selfcheck.py: check udev rules presence and give more useful hint if no mavros state 2023-10-10 06:45:47 +03:00
Oleg Kalachev
9404d4be6d Use image_geometry library in red circle following example 2023-09-20 02:44:26 +03:00
Oleg Kalachev
ad51d86464 docs: add terrain frame to frames list 2023-09-19 15:54:20 +03:00
Oleg Kalachev
9a713057b6 image: add image_geometry package 2023-08-15 17:49:55 +07:00
Oleg Kalachev
7b591d350c aruco_map: fix publishing detected markers count 2023-08-01 17:09:10 +07:00
Oleg Kalachev
2f8915ce31 aruco_map: add ability to pass number of detected markers to Pose covariance field 2023-07-30 14:04:59 +07:00
Oleg Kalachev
6fb84ae584 Remove unneeded line 2023-07-22 13:36:14 +03:00
Oleg Kalachev
bf4f680164 aruco_detect: don't convert image to bgr8 as this is not needed 2023-07-18 17:47:13 +03:00
Oleg Kalachev
c0baf30c96 Move rangefinder frame node out of mavros.launch 2023-07-15 22:05:24 +03:00
Oleg Kalachev
8f2c3b2e55 vpe_publisher: fix code aligning 2023-07-15 20:04:27 +03:00
Oleg Kalachev
6423eb91a2 vpe_publisher: fix calculating the offset in topic mode 2023-07-15 19:15:31 +03:00
Oleg Kalachev
22d7236a47 docs: publish CopterHack-2023 results 2023-05-28 10:05:04 +03:00
Oleg Kalachev
91d33a5961 docs: minor fixes 2023-05-28 09:59:27 +03:00
Oleg Kalachev
2997951371 docs: fix Atena article links to gitbook 2023-05-27 06:26:27 +03:00
Oleg Kalachev
a2ffcf381c docs: workaround for inter-lingual links inarticle 2023-05-27 05:46:49 +03:00
Oleg Kalachev
9aab324f60 docs: enable markdownlint for Atena CopterHack-2023 article 2023-05-23 00:28:54 +03:00
José Carlos Andrade do Nascimento
984fb39b85 docs: Swarm in Blocks 2 (Atena) (CopterHack 2023) (#471)
* Create swarm_in_blocks2.md

* Delete swarm_in_blocks2.md

* Create swarm_in_blocks_2.md

* markdown fixed

* Update swarm_in_blocks_2.md

* markdown first version

* Update swarm_in_blocks_2.md

* markdown fixed

* changing images

* Update swarm_in_blocks_2.md

* Lowercase asset file extension

* Some editing

* linking asstes

* docs: team image link fixed

* removing raw assets from pr

* docs: removing all unused assets

* docs: doc checking unused files

* Reduce logo image size

* Lowercase logo image file

* Rename logo directory

* Fix external images urls, some fix to whitespace

* Add link to CopterHack page in the header

* Add article to summary and CopterHack page

---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2023-05-22 23:55:28 +03:00
Oleg Kalachev
3a1a9d486c docs: fix some issues with CopterHack-2023 articles 2023-05-20 08:26:40 +03:00
Oleg Kalachev
55297696d6 docs: add CopterHack articles to summary 2023-05-20 07:49:46 +03:00
Oleg Kalachev
371f244228 docs: update CopterHack teams table 2023-05-20 07:16:49 +03:00
Juli-Shvetsova
ab3e7ac951 docs: CH2023 - Liceu128 (CopterHack-2023) (#473)
* Create liceu128.md

* Update liceu128.md

* Update liceu128.md

* Update liceu128.md

* Edit article

* final liceu128.md

* Edit article

---------

Co-authored-by: микемка <mikemka@vk.com>
Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2023-05-20 07:05:52 +03:00
Max
cdd6195f0b docs: Advanced clover simulation platform (CopterHack-2023) (#472)
* Create advanced_clover_simulator_platform.md

* Write better description

* AdvancedClover article finished

* Some editing

* Reduce images size

---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2023-05-20 07:04:55 +03:00
ssmith-81
c9b015148f docs: MoCap-Clover (CopterHack-2023) (#470)
* Create MoCap-Clover

* Rename MoCap-Clover to mocap_clover.md

* Create mocap_logo

* Add files via upload

* Update mocap_clover.md

* Update mocap_clover.md

update

* Update mocap_clover.md

* Update mocap_clover.md

* Add files via upload

* Update mocap_clover.md

* Update mocap_clover.md

* Add files via upload

* Update mocap_clover.md

* Update mocap_clover.md

* Update mocap_clover.md

* Update mocap_clover.md

* Add files via upload

* Update mocap_clover.md

* Add files via upload

* Update mocap_clover.md

* Add files via upload

* Update mocap_clover.md

* Update mocap_clover.md

* Update mocap_clover.md

* Edit article

* Remove unneeded asset

* Reduce sizes of some assets

* Update mocap_clover.md

* Update mocap_clover.md

* Delete docs/assets/mocap_clover directory

* Fix again headers anchors

* Create test

* Add files via upload

* Update mocap_clover.md

* Add files via upload

* Delete test

* Update mocap_clover.md

---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2023-05-20 07:00:39 +03:00
Lukerrr
2054472c23 docs: C305: Radio-Navigation System (CopterHack-2023) (#468)
* Create nav-beacon

* Update and rename article

* Fixed article issues

* Update nav-beacon.md

* Update nav-beacon.md

* Update nav-beacon.md

* Update nav-beacon.md

* Update nav-beacon.md

* Update nav-beacon.md

* Edit article

---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2023-05-20 06:58:51 +03:00
DJS Phoenix
b1084f99b9 docs: DJS PHOENIX (CopterHack-2023) (#462)
* Create djs_phoenix_chetak.md

* Update djs_phoenix_chetak.md

* Update djs_phoenix_chetak.md

* Update djs_phoenix_chetak.md

* Edit article

* Move English article to en/ subfolder

---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2023-05-20 06:58:11 +03:00
Mikhail Kuznetsov
c5f405c4d9 docs: Clover Cloud Platform CopterHack 2023 (#455)
* Create clover-cloud-platform.md

* md fix

* fix link to repositories

* Update clover-cloud-platform.md

* Editing

---------

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2023-05-20 06:57:23 +03:00
Oleg Kalachev
099d39d42d docs: some updates to version warnings 2023-05-20 04:43:32 +03:00
Oleg Kalachev
c9035790f2 image: loose required Python libraries versions, add missing validation 2023-04-17 22:54:18 +03:00
Oleg Kalachev
95da57fea1 docs: fix obsolete link to Ubuntu Focal desktop image 2023-04-12 21:32:38 +03:00
Oleg Kalachev
ad0138cd26 Merge pull request #488 from CopterExpress/v0.24-release
V0.24 release changes
2023-04-12 02:04:59 +03:00
Oleg Kalachev
d6101dc0a3 ci: add secret variable to temporarily freeze updating docs website 2023-04-12 01:34:34 +03:00
Oleg Kalachev
cbba62d165 blocks: add block for reading RC values 2023-04-11 19:35:00 +03:00
Oleg Kalachev
28ddbbcdf9 docs: add version warnings to camera articles 2023-04-11 19:21:53 +03:00
Oleg Kalachev
cac6b59a56 Set the version to 0.24.0 in ROS packages 2023-04-11 15:28:38 +03:00
Oleg Kalachev
c82490a0c1 Set terrain_frame_mode to range by default addressing CopterExpress/clover_vm#14 2023-04-11 15:27:39 +03:00
Oleg Kalachev
808726b4b7 Try to tix the build 2023-04-11 13:33:35 +03:00
Oleg Kalachev
19fde7095f simple_offboard: add test for land service 2023-04-11 01:40:58 +03:00
Oleg Kalachev
5e9f442996 simple_offboard: add terrain_frame_mode parameter, CopterExpress/clover_vm#14
`altitude` mode takes the current altitude from the estimator
`range` mode takes the current altitude from a simple range topic
2023-04-11 01:28:38 +03:00
Oleg Kalachev
68903373b0 simple_offboard: reset stored setpoint on auto_arm only if needed to be armed
CopterExpress/clover_vm#13
2023-04-11 00:57:43 +03:00
Oleg Kalachev
ae05710a37 blocks: fix set_yaw block implementation (#487) 2023-03-27 19:18:21 +03:00
Oleg Kalachev
4c576ba5d4 builder: print largest installed packages 2023-02-22 00:42:06 +03:00
Oleg Kalachev
ffd8b98e53 Merge branch 'master' into v0.24-release 2023-02-07 10:06:27 +03:00
Oleg Kalachev
69deeae32f blocks: document ~print topic of the main node 2023-02-07 10:06:09 +03:00
Oleg Kalachev
df66deb32c docs: add running flight autotests to testing plan 2023-02-07 10:03:41 +03:00
Oleg Kalachev
87a51221bc docs: update documentation for autonomous flights 2023-02-07 10:02:02 +03:00
Oleg Kalachev
08bda736e9 aruco_detect: fix drawing markers axis 2023-01-26 18:16:24 +03:00
Oleg Kalachev
56a2be8170 docs: add redirect from /red_circle to camera article 2023-01-13 12:59:43 +03:00
Oleg Kalachev
59518fddd1 examples: add program to recognize and follow the red circle 2023-01-13 12:59:26 +03:00
Oleg Kalachev
25ae694d1f simulator: add red circle model for recognizing 2023-01-13 12:58:51 +03:00
Oleg Kalachev
f78a03ec89 Change default EKF2_HGT_MODE to 3 (vision) 2023-01-13 12:06:55 +03:00
Oleg Kalachev
0cfdac43ec Significant update to simple_offboard node
* Allow using nans for most of services parameters
* Add terrain frame
* Remove yaw_rate parameter from most services
* Add set_yaw and set_yaw_rate services
* Correct order for pitch and roll everywhere to match XYZ convention
* Add simple_offboard/state topic
* Add essential tests
* Stop publishing setpoints when land called
2023-01-12 11:00:05 +03:00
Oleg Kalachev
cb2850b1d4 docs: update CopterHack-2023 project link 2023-01-12 00:45:27 +03:00
Oleg Kalachev
460c3fdbe1 Whitespaces fixes 2022-12-29 05:54:32 +03:00
Oleg Kalachev
e3fb7cf28e Merge branch 'master' into v0.24-release 2022-12-29 05:53:12 +03:00
Oleg Kalachev
3b930d48d2 Update build passing badge in readme 2022-12-21 11:34:04 +03:00
murata,katsutoshi
f3aadd11ec docs: change the item name in summary (#480) 2022-12-06 00:01:28 +03:00
Oleg Kalachev
976c7114e5 docs: update motion capture project link 2022-11-26 21:58:44 +01:00
Oleg Kalachev
d8662007fe docs: add teams list for CopterHack-2023 2022-11-23 23:21:11 +01:00
Oleg Kalachev
ac1ac33a1a Merge branch 'master' into v0.24-release 2022-11-12 01:35:11 +06:00
Oleg Kalachev
95db8ba1b1 aruco_pose: known_tilt => known_vertical, add flip_vertical parameter (#476)
* aruco_pose: rename parameter known_tilt to known_vertical

* More clean variable names

* aruco_pose: add flip_vertical parameter and get rid of map_flipped

* selfcheck.py: support flip_vertical parameter

* aruco_pose: document flip_vertical parameter

* selfcheck.py: fix known_vertical description

* Fix editorconfig
2022-11-12 01:33:15 +06:00
Oleg Kalachev
94a95b28b3 Minor typo 2022-11-11 06:07:55 +06:00
Oleg Kalachev
d4a83bdf58 autotest: run aruco test without optical flow 2022-11-11 05:58:49 +06:00
Oleg Kalachev
cb1773b708 selfcheck.py: skip optical_flow check if it's not running 2022-11-11 05:46:58 +06:00
Oleg Kalachev
5afbcff949 vpe_publisher: fix a bug when the first pose arrives at the start of clock (simulation) 2022-11-11 05:43:16 +06:00
Oleg Kalachev
3870e62be7 Merge branch 'master' into v0.24-release 2022-11-10 22:26:59 +06:00
Oleg Kalachev
f719406c8b Remove www directory update from clover.launch and update it on demand only (#475) 2022-11-10 22:25:44 +06:00
Oleg Kalachev
72f8d901d5 ci: set cancel-in-progress only for deploy job not the whole docs wf 2022-11-10 20:04:05 +06:00
Oleg Kalachev
393801b023 Fix ROS tools tests considering some of them exit with 64 on usage 2022-11-10 19:57:02 +06:00
Oleg Kalachev
a0322c55f2 Fix ROS tools tests 2022-11-10 17:56:23 +06:00
Oleg Kalachev
3662f512a7 docs: update in wall aruco article 2022-11-10 05:06:46 +06:00
Oleg Kalachev
277eb7297f image: test basic ros tools work 2022-11-10 04:37:30 +06:00
Oleg Kalachev
e719b0f1e2 selfcheck.py: print fcu_url value if no connection to fcu 2022-11-09 23:46:05 +06:00
Oleg Kalachev
e65d380b4b Minor camera example fix 2022-11-08 22:43:08 +06:00
Oleg Kalachev
8fe34e90e6 Depend on docopt in package.xml instead of requirements.txt 2022-11-08 16:07:36 +06:00
Oleg Kalachev
54ab5ab4b5 selfcheck.py: make output colored only in a tty 2022-11-08 06:47:01 +06:00
Oleg Kalachev
2cda68ae4a selfcheck.py: don't fall if aruco_detect/length is not set 2022-11-08 06:46:29 +06:00
Oleg Kalachev
d24b6617a4 selfcheck.py: don't fall when aruco_map/known_tilt is not set 2022-11-08 06:41:21 +06:00
Oleg Kalachev
640ec1ee1a Add clover package dependency on pytest 2022-11-08 03:37:12 +06:00
Oleg Kalachev
96ea78f141 image: check rosserial version only on rpi image 2022-11-07 20:47:18 +06:00
Oleg Kalachev
5e3b07ff5e Add a basic example script on working with the camera 2022-11-07 20:09:15 +06:00
Oleg Kalachev
92748a760b simulation: remove redundant warning on creating a new LedController 2022-11-07 19:00:02 +06:00
Oleg Kalachev
8512e8a045 image: check rosshow version only on rpi image 2022-11-07 18:30:54 +06:00
Oleg Kalachev
8b1b365e67 image: check compressed_image_transport on rpi image only 2022-11-07 18:21:55 +06:00
Oleg Kalachev
2cd77662df image: check version of rosbridge_server which clover package depends on instead of rosbridge_suite 2022-11-07 18:21:32 +06:00
Oleg Kalachev
64f939d7ed image: updates to tests for VM image 2022-11-06 04:15:32 +06:00
Oleg Kalachev
9a8aa00bc7 Run map_flipped frame only when markers placement is ceiling 2022-11-06 02:02:43 +06:00
Oleg Kalachev
3f3d1cd220 image: don’t test mjpg_streamer on vm image 2022-11-06 01:43:03 +06:00
Oleg Kalachev
9c34d7722c image: don’t test butterfly on vm image 2022-11-06 00:25:23 +06:00
Oleg Kalachev
19e0d725b0 image: test ptvsd on the RPi image only 2022-11-05 22:47:39 +06:00
Oleg Kalachev
6fafaf3184 Fix Python tests for VM 2022-11-05 20:48:38 +06:00
Oleg Kalachev
8f09df6b34 Optimize shell tests for vm image 2022-11-05 17:45:10 +06:00
Oleg Kalachev
c5d01c678a image and vm image: validate python is python2 (for now) 2022-11-05 17:26:19 +06:00
Oleg Kalachev
2b13aa02eb docs: make /camera redirect to English version of the article 2022-11-04 03:10:56 +06:00
Oleg Kalachev
45042cd6f5 docs: updates to camera and computer vision article 2022-11-04 03:05:59 +06:00
Oleg Kalachev
ec9ddf5fd2 selfcheck.py: read VM image version from /etc/clover_vm_version 2022-11-03 18:36:18 +06:00
Oleg Kalachev
c5399868cb selfcheck.py: remove obsolete todos 2022-11-03 18:28:22 +06:00
Oleg Kalachev
a6cee773ab selfcheck.py: fix reading diagnostics considering there might be multiple nodes publishing them 2022-11-03 18:23:54 +06:00
Oleg Kalachev
d03cfe00ca selfcheck.py: handle inability to read time sync offset 2022-11-03 17:53:59 +06:00
Oleg Kalachev
0fb101cc59 selfcheck.py: add time sync offset report 2022-11-03 17:47:53 +06:00
Oleg Kalachev
0d44ff3993 selfcheck.py: handle nicely when a PX4 parameter cannot be retrieved 2022-11-03 17:47:34 +06:00
Oleg Kalachev
dc5da00abd selfcheck.py: print reports when there was exception in check 2022-11-03 06:56:07 +06:00
Oleg Kalachev
4f00960db3 Minor fix to long_callback decorator example 2022-11-02 16:09:56 +06:00
Oleg Kalachev
ce0b4eb428 Implement long_callback Python decorator addressing #218
Clover package Python library added.
2022-11-02 06:38:36 +06:00
Oleg Kalachev
ccbd1cbad9 selfcheck.py: make output colored 2022-10-31 04:24:13 +06:00
Oleg Kalachev
4b397670a1 selfcheck.py: make report more compact by removing severity levels
The severity level is visible from the color
2022-10-31 03:08:30 +06:00
Oleg Kalachev
89bfc150f3 selfcheck.py: split up estimator's and flow sensor's parameters reports 2022-10-31 02:41:21 +06:00
Oleg Kalachev
2dda726d3e clover.launch: turn on disable_on_vpe by default 2022-10-31 02:32:45 +06:00
Oleg Kalachev
6b05cb34e5 optical_flow: add disable_on_vpe parameter (#461) 2022-10-31 02:31:47 +06:00
Oleg Kalachev
22293c2220 aruco.launch: set use_map_markers parameter to true 2022-10-31 02:30:19 +06:00
Oleg Kalachev
38a3f656ab selfcheck.py: add parameter to print the time spent on each check
Usage:

rosrun clover selfcheck.py _time:=1
2022-10-31 02:28:02 +06:00
Oleg Kalachev
2e79979411 selfcheck.py: implement experimental parameter for parallel checks run
Run:

rosrun clover selfcheck.py _parallel:=1
2022-10-31 02:25:53 +06:00
Oleg Kalachev
b165e154f5 selfcheck.py: decrease some timeouts to speed up check 2022-10-31 02:14:02 +06:00
Oleg Kalachev
99fad312c5 aruco_detect: wait util map is published to avoid race condition 2022-10-31 01:37:06 +06:00
Elena Seliverstova
ee17a3bada docs: update course contest deadline 2022-10-30 21:38:40 +03:00
Elena Seliverstova
1461dd22f4 Update deadline 2022-10-30 21:22:04 +03:00
Oleg Kalachev
2c07bbffe3 aruco_map: fix visualization markers' orientation
Was by mistake uninitialized
2022-10-30 23:16:03 +06:00
Oleg Kalachev
0b62f677af aruco_detect: consider markers size from markers map
`use_map_markers` parameter added
2022-10-30 22:28:33 +06:00
Oleg Kalachev
070c23be53 selfcheck.py: shorten some reports replacing 'is' with '=' 2022-10-30 21:00:45 +06:00
Oleg Kalachev
c907e6041a selfcheck.py: skip battery check in simulation 2022-10-30 20:44:13 +06:00
Oleg Kalachev
69d5d1e521 selfcheck.py: don't fail when there is no vpe and vpe_publisher is not running 2022-10-30 19:48:42 +06:00
Oleg Kalachev
1700ad24df selfcheck.py: don't failure when no vpe and drone is on the floor 2022-10-30 19:21:42 +06:00
Oleg Kalachev
6361984794 selfcheck.py: don't fail when marker's map not detected 2022-10-30 19:21:10 +06:00
Oleg Kalachev
7f31fdd320 docs: minor cleanup in snippets 2022-10-30 18:39:51 +06:00
Oleg Kalachev
f9450fe03d selfcheck.py: skip px4 version check in SITL 2022-10-30 05:34:57 +06:00
Oleg Kalachev
b41cb6b581 selfcheck.py: minor fix 2022-10-30 05:10:54 +06:00
Oleg Kalachev
b855c4586a led: allow to use namespaced mavros (from #439)
rosout_agg cannot be namespaced:
f5fa3a1687/tools/rosout/rosout.cpp (L127)

Co-Authored-By: Playergeek181 <93044104+Playergeek181@users.noreply.github.com>
2022-10-30 04:05:53 +06:00
Oleg Kalachev
26245dfb42 docs: add snippet on how to check if code is running inside simulation 2022-10-26 03:31:00 +06:00
Oleg Kalachev
d6f9327ede simulation: set distance sensor's fov to 27 deg
As vl53l1x rangefinder specification stances
2022-10-25 05:10:25 +06:00
Oleg Kalachev
0f5f111f46 docs: minor fix 2022-10-13 02:05:56 +06:00
Oleg Kalachev
4e9d8a64d0 simple_offboard: test for simple_offboard/release service presence 2022-10-13 00:08:35 +06:00
Oleg Kalachev
94171d51ac simple_offboard: implement simple_offboard/release service 2022-10-13 00:07:27 +06:00
Oleg Kalachev
eb448ae0e7 main_camera.launch: run image_raw_throttled topic by default (#248) 2022-10-12 00:25:12 +06:00
Oleg Kalachev
c0707e066a actions: build Debian packages and upload to artifacts (#458) 2022-10-05 16:10:17 +06:00
Oleg Kalachev
91c6998633 docs: add snippet to subscribe and decode incoming mavlink messages 2022-09-29 13:58:02 +05:00
Sergey Stetsky
7b431fa021 docs: add command for updating markers map in the sim (#456)
Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2022-09-23 20:15:57 +03:00
Oleg Kalachev
1e12498cb2 Install GeographicLib datasets in build workflow 2022-09-14 14:19:06 +03:00
Oleg Kalachev
43037f515d aruco_detect: check for duplicates in marker transforms, send all transforms in one message 2022-09-14 12:55:27 +03:00
Oleg Kalachev
2ea848721c aruco_pose: add duplicate test to CMakeLists.txt 2022-09-14 12:46:04 +03:00
Oleg Kalachev
d06b0a0cd2 aruco_pose: implement test for TF_REPEATED_DATA when multiple markers with the same ID 2022-09-14 12:35:16 +03:00
Oleg Kalachev
1efe10c9dd Simplify script for testing native Noetic build 2022-09-10 15:26:34 +03:00
Oleg Kalachev
24cd1f6fac Show number of messages received in topic viewer 2022-09-10 08:08:09 +03:00
Oleg Kalachev
5223bef5e7 Fix error when viewing messages without header in topic viewer 2022-09-10 01:31:38 +03:00
Oleg Kalachev
105eac7e1d clover.launch: make force_init argument overridable externally 2022-09-08 14:42:45 +03:00
Oleg Kalachev
c1d6ed27aa mavros.launch: fix fcu_url for hitl connection 2022-09-08 01:32:51 +03:00
Oleg Kalachev
614784e949 mavros.launch: add hitl option for fcu_conn argument 2022-09-07 00:55:13 +03:00
Oleg Kalachev
9376c017b4 selfcheck.py: skip boot duration check on not Clover image 2022-09-03 22:53:38 +03:00
Oleg Kalachev
b5d300e218 optical_flow: timeout for previous frame
For cases when optical flow is dynamically disabled and enabled back
2022-09-03 07:26:25 +03:00
Oleg Kalachev
efb44484b0 Remove obsolete note from readme 2022-08-26 22:51:08 +03:00
Oleg Kalachev
0a2ad3d64f docs: remove some obsolete notes about renaming 2022-08-26 22:50:55 +03:00
oponfil
ffe2d3d5e4 docs: update connection article (en) (#363)
* Add instructions on how to connect FMU to Raspberry Pi by UART

* Remove sitl connection section

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2022-08-26 22:36:11 +03:00
oponfil
81f4795aec docs: update connection article (#362)
* Add instructions on how to connect FMU to Raspberry Pi by UART

* Remove inactual sitl connection

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2022-08-26 22:35:40 +03:00
oponfil
596ed3dcf2 docs: docs: correction in mavros article on global setpoints (en) (#358)
Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2022-08-26 22:09:56 +03:00
oponfil
63c71fc331 docs: correction in mavros article on global setpoints (#357)
Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
2022-08-26 22:08:22 +03:00
Oleg Kalachev
0efb249d9b docs: update PX4 to v1.12.3 in native simulation installation article 2022-08-26 16:05:28 +03:00
Oleg Kalachev
47c6e5aa9b docs: comment out link to install_software.sh in simulation installation manual
The link confuses users so they often try to run it
2022-08-25 18:28:52 +03:00
Oleg Kalachev
687a4f50fd Fix python-pymavlink installation adding some lxml dependencies
Otherwise installation falls with:
Error: Please make sure the libxml2 and libxslt development packages are installed
2022-08-24 22:42:18 +03:00
153 changed files with 4694 additions and 829 deletions

View File

@@ -7,6 +7,7 @@ on:
branches: [ master ] branches: [ master ]
release: release:
types: [ created ] types: [ created ]
workflow_dispatch:
jobs: jobs:
build: build:
@@ -27,3 +28,10 @@ jobs:
prerelease: true prerelease: true
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Upload image to artifacts
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/upload-artifact@v3
with:
name: image
path: images/clover_*.zip
retention-days: 1

View File

@@ -5,6 +5,7 @@ on:
branches: [ '*' ] branches: [ '*' ]
pull_request: pull_request:
branches: [ master ] branches: [ master ]
workflow_dispatch:
jobs: jobs:
# melodic: # melodic:
@@ -16,8 +17,36 @@ jobs:
# docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:melodic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh # docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:melodic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh
noetic: noetic:
runs-on: ubuntu-latest runs-on: ubuntu-latest
container: ros:noetic-ros-base
defaults:
run:
working-directory: catkin_ws
shell: bash
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Native Noetic build with:
run: | path: catkin_ws/src/clover
docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:noetic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh - name: Install requirements
run: apt-get update && apt-get -y install python3-pip fakeroot python3-bloom debhelper dpkg-dev
- name: Install dependencies
run: rosdep update && rosdep install --from-paths src --ignore-src -y
- name: Install GeographicLib datasets
run: wget -qO- https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/install_geographiclib_datasets.sh | bash
- name: catkin_make
run: source /opt/ros/$ROS_DISTRO/setup.bash && catkin_make
- name: Run tests
run: source devel/setup.bash && catkin_make run_tests && catkin_test_results
- name: Build Debian packages
run: |
source devel/setup.bash
for file in `find . -name "package.xml"`; do
cd $(dirname ${file})
bloom-generate rosdebian --os-name ubuntu --os-version $(lsb_release -cs) --ros-distro $ROS_DISTRO
fakeroot debian/rules binary
cd -
done
- uses: actions/upload-artifact@v3
with:
name: debian-packages
path: catkin_ws/src/clover/*.deb
retention-days: 1

View File

@@ -5,16 +5,13 @@ on:
branches: [ '*' ] branches: [ '*' ]
pull_request: pull_request:
branches: [ '*' ] branches: [ '*' ]
workflow_dispatch:
permissions: permissions:
contents: read contents: read
pages: write pages: write
id-token: write id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -75,6 +72,9 @@ jobs:
deploy-docs: deploy-docs:
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
concurrency:
group: "pages"
cancel-in-progress: true
environment: environment:
name: github-pages name: github-pages
url: ${{ steps.deployment.outputs.page_url }} url: ${{ steps.deployment.outputs.page_url }}
@@ -82,5 +82,8 @@ jobs:
needs: docs needs: docs
steps: steps:
- name: Deploy to GitHub Pages - name: Deploy to GitHub Pages
env:
FREEZE_DOCS: ${{ secrets.FREEZE_DOCS }}
if: ${{ !env.FREEZE_DOCS }}
id: deployment id: deployment
uses: actions/deploy-pages@v1 uses: actions/deploy-pages@v1

View File

@@ -5,6 +5,7 @@ on:
branches: [ '*' ] branches: [ '*' ]
pull_request: pull_request:
branches: [ master ] branches: [ master ]
workflow_dispatch:
jobs: jobs:
editorconfig: editorconfig:

View File

@@ -20,7 +20,7 @@ Clover drone is used on a wide range of educational events, including [Copter Ha
Preconfigured image for Raspberry Pi with installed and configured software, ready to fly, is available [in the Releases section](https://github.com/CopterExpress/clover/releases). Preconfigured image for Raspberry Pi with installed and configured software, ready to fly, is available [in the Releases section](https://github.com/CopterExpress/clover/releases).
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/CopterExpress/clover/CI) ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/CopterExpress/clover/build-image.yaml?branch=master)
![GitHub all releases](https://img.shields.io/github/downloads/CopterExpress/clover/total) ![GitHub all releases](https://img.shields.io/github/downloads/CopterExpress/clover/total)
Image features: Image features:

View File

@@ -251,4 +251,5 @@ if (CATKIN_ENABLE_TESTING)
add_rostest(test/test_node_failure.test) add_rostest(test/test_node_failure.test)
add_rostest(test/largemap.test) add_rostest(test/largemap.test)
add_rostest(test/crash_opencv.test) add_rostest(test/crash_opencv.test)
add_rostest(test/duplicate.test)
endif() endif()

View File

@@ -43,7 +43,8 @@ It's recommended to run it within the same nodelet manager with the camera nodel
* `~frame_id_prefix` (*string*) prefix for TF transforms names, marker's ID is appended (default: `aruco_`) * `~frame_id_prefix` (*string*) prefix for TF transforms names, marker's ID is appended (default: `aruco_`)
* `~length` (*double*) markers' sides length * `~length` (*double*) markers' sides length
* `~length_override` (*map*) lengths of markers with specified ids * `~length_override` (*map*) lengths of markers with specified ids
* `~known_tilt` (*string*) known tilt (pitch and roll) of all the markers as a frame * `~known_vertical` (*string*) known vertical (Z axis) of all the markers as a frame
* `~flip_vertical` flip vertical vector
### Topics ### Topics
@@ -71,7 +72,8 @@ It's recommended to run it within the same nodelet manager with the camera nodel
* `~map` path to text file with markers list * `~map` path to text file with markers list
* `~frame_id` published frame id (default: `aruco_map`) * `~frame_id` published frame id (default: `aruco_map`)
* `~known_tilt` known tilt (pitch and roll) of markers map as a frame * `~known_vertical` known vertical (Z axis) of markers map as a frame
* `~flip_vertical` flip vertical vector
* `~image_width` debug image width (default: 2000) * `~image_width` debug image width (default: 2000)
* `~image_height` debug image height (default: 2000) * `~image_height` debug image height (default: 2000)
* `~image_margin`  debug image margin (default: 200) * `~image_margin`  debug image margin (default: 200)

View File

@@ -4,7 +4,10 @@ PACKAGE = "aruco_pose"
from dynamic_reconfigure.parameter_generator_catkin import * from dynamic_reconfigure.parameter_generator_catkin import *
import cv2.aruco import cv2.aruco
p = cv2.aruco.DetectorParameters_create() try:
p = cv2.aruco.DetectorParameters_create()
except AttributeError:
p = cv2.aruco.DetectorParameters()
gen = ParameterGenerator() gen = ParameterGenerator()

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<package format="2"> <package format="3">
<name>aruco_pose</name> <name>aruco_pose</name>
<version>0.23.0</version> <version>0.24.0</version>
<description>Positioning with ArUco markers</description> <description>Positioning with ArUco markers</description>
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer> <maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
@@ -28,6 +28,8 @@
<depend>sensor_msgs</depend> <depend>sensor_msgs</depend>
<depend>rostest</depend> <depend>rostest</depend>
<depend>dynamic_reconfigure</depend> <depend>dynamic_reconfigure</depend>
<depend condition="$ROS_PYTHON_VERSION == 2">python-docopt</depend>
<depend condition="$ROS_PYTHON_VERSION == 3">python3-docopt</depend>
<test_depend>image_publisher</test_depend> <test_depend>image_publisher</test_depend>
<test_depend>ros_pytest</test_depend> <test_depend>ros_pytest</test_depend>

View File

@@ -50,6 +50,7 @@
#include <aruco_pose/DetectorConfig.h> #include <aruco_pose/DetectorConfig.h>
#include <aruco_pose/SetMarkers.h> #include <aruco_pose/SetMarkers.h>
#include "draw.h"
#include "utils.h" #include "utils.h"
#include <memory> #include <memory>
#include <functional> #include <functional>
@@ -71,11 +72,12 @@ private:
ros::Publisher markers_pub_, vis_markers_pub_; ros::Publisher markers_pub_, vis_markers_pub_;
ros::Subscriber map_markers_sub_; ros::Subscriber map_markers_sub_;
ros::ServiceServer set_markers_srv_; ros::ServiceServer set_markers_srv_;
bool estimate_poses_, send_tf_, auto_flip_; bool estimate_poses_, send_tf_, flip_vertical_, auto_flip_, use_map_markers_;
bool waiting_for_map_;
double length_; double length_;
ros::Duration transform_timeout_; ros::Duration transform_timeout_;
std::unordered_map<int, double> length_override_; std::unordered_map<int, double> length_override_;
std::string frame_id_prefix_, known_tilt_; std::string frame_id_prefix_, known_vertical_;
Mat camera_matrix_, dist_coeffs_; Mat camera_matrix_, dist_coeffs_;
aruco_pose::MarkerArray array_; aruco_pose::MarkerArray array_;
std::unordered_set<int> map_markers_ids_; std::unordered_set<int> map_markers_ids_;
@@ -95,6 +97,8 @@ public:
dictionary = nh_priv_.param("dictionary", 2); dictionary = nh_priv_.param("dictionary", 2);
estimate_poses_ = nh_priv_.param("estimate_poses", true); estimate_poses_ = nh_priv_.param("estimate_poses", true);
send_tf_ = nh_priv_.param("send_tf", true); send_tf_ = nh_priv_.param("send_tf", true);
use_map_markers_ = nh_priv_.param("use_map_markers", false);
waiting_for_map_ = use_map_markers_;
if (estimate_poses_ && !nh_priv_.getParam("length", length_)) { if (estimate_poses_ && !nh_priv_.getParam("length", length_)) {
NODELET_FATAL("can't estimate marker's poses as ~length parameter is not defined"); NODELET_FATAL("can't estimate marker's poses as ~length parameter is not defined");
ros::shutdown(); ros::shutdown();
@@ -102,7 +106,8 @@ public:
readLengthOverride(nh_priv_); readLengthOverride(nh_priv_);
transform_timeout_ = ros::Duration(nh_priv_.param("transform_timeout", 0.02)); transform_timeout_ = ros::Duration(nh_priv_.param("transform_timeout", 0.02));
known_tilt_ = nh_priv_.param<std::string>("known_tilt", ""); known_vertical_ = nh_priv_.param("known_vertical", nh_priv_.param("known_tilt", std::string(""))); // known_tilt is an old name
flip_vertical_ = nh_priv_.param<bool>("flip_vertical", false);
auto_flip_ = nh_priv_.param("auto_flip", false); auto_flip_ = nh_priv_.param("auto_flip", false);
frame_id_prefix_ = nh_priv_.param<std::string>("frame_id_prefix", "aruco_"); frame_id_prefix_ = nh_priv_.param<std::string>("frame_id_prefix", "aruco_");
@@ -133,14 +138,15 @@ private:
void imageCallback(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr &cinfo) void imageCallback(const sensor_msgs::ImageConstPtr& msg, const sensor_msgs::CameraInfoConstPtr &cinfo)
{ {
if (!enabled_) return; if (!enabled_) return;
if (waiting_for_map_) return;
Mat image = cv_bridge::toCvShare(msg, "bgr8")->image; Mat image = cv_bridge::toCvShare(msg)->image;
vector<int> ids; vector<int> ids;
vector<vector<cv::Point2f>> corners, rejected; vector<vector<cv::Point2f>> corners, rejected;
vector<cv::Vec3d> rvecs, tvecs; vector<cv::Vec3d> rvecs, tvecs;
vector<cv::Point3f> obj_points; vector<cv::Point3f> obj_points;
geometry_msgs::TransformStamped snap_to; geometry_msgs::TransformStamped vertical;
// Detect markers // Detect markers
cv::aruco::detectMarkers(image, dictionary_, corners, ids, parameters_, rejected); cv::aruco::detectMarkers(image, dictionary_, corners, ids, parameters_, rejected);
@@ -175,18 +181,20 @@ private:
} }
} }
if (!known_tilt_.empty()) { if (!known_vertical_.empty()) {
try { try {
snap_to = tf_buffer_->lookupTransform(msg->header.frame_id, known_tilt_, vertical = tf_buffer_->lookupTransform(msg->header.frame_id, known_vertical_,
msg->header.stamp, transform_timeout_); msg->header.stamp, transform_timeout_);
} catch (const tf2::TransformException& e) { } catch (const tf2::TransformException& e) {
NODELET_WARN_THROTTLE(5, "can't snap: %s", e.what()); NODELET_WARN_THROTTLE(5, "can't retrieve known vertical: %s", e.what());
} }
} }
} }
array_.markers.reserve(ids.size()); array_.markers.reserve(ids.size());
aruco_pose::Marker marker; aruco_pose::Marker marker;
vector<geometry_msgs::TransformStamped> transforms;
transforms.reserve(ids.size());
geometry_msgs::TransformStamped transform; geometry_msgs::TransformStamped transform;
transform.header.stamp = msg->header.stamp; transform.header.stamp = msg->header.stamp;
transform.header.frame_id = msg->header.frame_id; transform.header.frame_id = msg->header.frame_id;
@@ -199,25 +207,38 @@ private:
if (estimate_poses_) { if (estimate_poses_) {
fillPose(marker.pose, rvecs[i], tvecs[i]); fillPose(marker.pose, rvecs[i], tvecs[i]);
// snap orientation (if enabled and snap frame available) // apply known vertical (if enabled and vertical frame available)
if (!known_tilt_.empty() && !snap_to.header.frame_id.empty()) { if (!known_vertical_.empty() && !vertical.header.frame_id.empty()) {
snapOrientation(marker.pose.orientation, snap_to.transform.rotation, auto_flip_); applyVertical(marker.pose.orientation, vertical.transform.rotation, false, auto_flip_);
} }
// TODO: check IDs are unique
if (send_tf_) { if (send_tf_) {
transform.child_frame_id = getChildFrameId(ids[i]); transform.child_frame_id = getChildFrameId(ids[i]);
// check if such static transform is in the map // check if such static transform is in the map
if (map_markers_ids_.find(ids[i]) == map_markers_ids_.end()) { if (map_markers_ids_.find(ids[i]) == map_markers_ids_.end()) {
transform.transform.rotation = marker.pose.orientation; // check if a markers with that id is already added
fillTranslation(transform.transform.translation, tvecs[i]); bool send = true;
br_->sendTransform(transform); for (auto &t : transforms) {
if (t.child_frame_id == transform.child_frame_id) {
send = false;
break;
}
}
if (send) {
transform.transform.rotation = marker.pose.orientation;
fillTranslation(transform.transform.translation, tvecs[i]);
transforms.push_back(transform);
}
} }
} }
} }
array_.markers.push_back(marker); array_.markers.push_back(marker);
} }
if (send_tf_) {
br_->sendTransform(transforms);
}
} }
markers_pub_.publish(array_); markers_pub_.publish(array_);
@@ -244,8 +265,7 @@ private:
cv::aruco::drawDetectedMarkers(debug, corners, ids); // draw markers cv::aruco::drawDetectedMarkers(debug, corners, ids); // draw markers
if (estimate_poses_) if (estimate_poses_)
for (unsigned int i = 0; i < ids.size(); i++) for (unsigned int i = 0; i < ids.size(); i++)
cv::aruco::drawAxis(debug, camera_matrix_, dist_coeffs_, _drawAxis(debug, camera_matrix_, dist_coeffs_, rvecs[i], tvecs[i], getMarkerLength(ids[i]));
rvecs[i], tvecs[i], getMarkerLength(ids[i]));
cv_bridge::CvImage out_msg; cv_bridge::CvImage out_msg;
out_msg.header.frame_id = msg->header.frame_id; out_msg.header.frame_id = msg->header.frame_id;
@@ -380,7 +400,13 @@ private:
map_markers_ids_.clear(); map_markers_ids_.clear();
for (auto const& marker : msg.markers) { for (auto const& marker : msg.markers) {
map_markers_ids_.insert(marker.id); map_markers_ids_.insert(marker.id);
if (use_map_markers_) {
if (length_override_.find(marker.id) == length_override_.end()) {
length_override_[marker.id] = marker.length;
}
}
} }
waiting_for_map_ = false;
} }
void paramCallback(aruco_pose::DetectorConfig &config, uint32_t level) void paramCallback(aruco_pose::DetectorConfig &config, uint32_t level)

View File

@@ -81,9 +81,9 @@ private:
bool enabled_ = true; bool enabled_ = true;
std::string type_; std::string type_;
visualization_msgs::MarkerArray vis_array_; visualization_msgs::MarkerArray vis_array_;
std::string known_tilt_, map_, markers_frame_, markers_parent_frame_; std::string known_vertical_, map_, markers_frame_, markers_parent_frame_;
int image_width_, image_height_, image_margin_; int image_width_, image_height_, image_margin_;
bool auto_flip_, image_axis_; bool flip_vertical_, auto_flip_, image_axis_, put_markers_count_to_covariance_;
public: public:
virtual void onInit() virtual void onInit()
@@ -104,12 +104,14 @@ public:
type_ = nh_priv_.param<std::string>("type", "map"); type_ = nh_priv_.param<std::string>("type", "map");
transform_.child_frame_id = nh_priv_.param<std::string>("frame_id", "aruco_map"); transform_.child_frame_id = nh_priv_.param<std::string>("frame_id", "aruco_map");
known_tilt_ = nh_priv_.param<std::string>("known_tilt", ""); known_vertical_ = nh_priv_.param("known_vertical", nh_priv_.param("known_tilt", std::string(""))); // known_tilt is an old name
flip_vertical_ = nh_priv_.param<bool>("flip_vertical", false);
auto_flip_ = nh_priv_.param("auto_flip", false); auto_flip_ = nh_priv_.param("auto_flip", false);
image_width_ = nh_priv_.param("image_width" , 2000); image_width_ = nh_priv_.param("image_width" , 2000);
image_height_ = nh_priv_.param("image_height", 2000); image_height_ = nh_priv_.param("image_height", 2000);
image_margin_ = nh_priv_.param("image_margin", 200); image_margin_ = nh_priv_.param("image_margin", 200);
image_axis_ = nh_priv_.param("image_axis", true); image_axis_ = nh_priv_.param("image_axis", true);
put_markers_count_to_covariance_ = nh_priv_.param("put_markers_count_to_covariance", false);
markers_parent_frame_ = nh_priv_.param<std::string>("markers/frame_id", transform_.child_frame_id); markers_parent_frame_ = nh_priv_.param<std::string>("markers/frame_id", transform_.child_frame_id);
markers_frame_ = nh_priv_.param<std::string>("markers/child_frame_id_prefix", ""); markers_frame_ = nh_priv_.param<std::string>("markers/child_frame_id_prefix", "");
@@ -177,7 +179,21 @@ public:
corners.push_back(marker_corners); corners.push_back(marker_corners);
} }
if (known_tilt_.empty()) { if (put_markers_count_to_covariance_) {
// HACK: pass markers count using covariance field
int valid_markers = 0;
for (auto const &marker : markers->markers) {
for (auto const &board_marker : board_->ids) {
if (board_marker == marker.id) {
valid_markers++;
break;
}
}
}
pose_.pose.covariance[0] = valid_markers;
}
if (known_vertical_.empty()) {
// simple estimation // simple estimation
valid = cv::aruco::estimatePoseBoard(corners, ids, board_, camera_matrix_, dist_coeffs_, valid = cv::aruco::estimatePoseBoard(corners, ids, board_, camera_matrix_, dist_coeffs_,
rvec, tvec, false); rvec, tvec, false);
@@ -191,7 +207,7 @@ public:
} else { } else {
Mat obj_points, img_points; Mat obj_points, img_points;
// estimation with "snapping" // estimation with known vertical
cv::aruco::getBoardObjectAndImagePoints(board_, corners, ids, obj_points, img_points); cv::aruco::getBoardObjectAndImagePoints(board_, corners, ids, obj_points, img_points);
if (obj_points.empty()) goto publish_debug; if (obj_points.empty()) goto publish_debug;
@@ -203,11 +219,11 @@ public:
fillTransform(transform_.transform, rvec, tvec); fillTransform(transform_.transform, rvec, tvec);
try { try {
geometry_msgs::TransformStamped snap_to = tf_buffer_.lookupTransform(markers->header.frame_id, geometry_msgs::TransformStamped vertical = tf_buffer_.lookupTransform(markers->header.frame_id,
known_tilt_, markers->header.stamp, ros::Duration(0.02)); known_vertical_, markers->header.stamp, ros::Duration(0.02));
snapOrientation(transform_.transform.rotation, snap_to.transform.rotation, auto_flip_); applyVertical(transform_.transform.rotation, vertical.transform.rotation, flip_vertical_, auto_flip_);
} catch (const tf2::TransformException& e) { } catch (const tf2::TransformException& e) {
NODELET_WARN_THROTTLE(1, "can't snap: %s", e.what()); NODELET_WARN_THROTTLE(1, "can't retrieve known vertical: %s", e.what());
} }
geometry_msgs::TransformStamped shift; geometry_msgs::TransformStamped shift;
@@ -503,7 +519,7 @@ publish_debug:
vis_marker.pose.position.x = x; vis_marker.pose.position.x = x;
vis_marker.pose.position.y = y; vis_marker.pose.position.y = y;
vis_marker.pose.position.z = z; vis_marker.pose.position.z = z;
tf::quaternionTFToMsg(q, marker.pose.orientation); tf::quaternionTFToMsg(q, vis_marker.pose.orientation);
vis_marker.frame_locked = true; vis_marker.frame_locked = true;
vis_array_.markers.push_back(vis_marker); vis_array_.markers.push_back(vis_marker);

View File

@@ -106,26 +106,25 @@ inline bool isFlipped(tf::Quaternion& q)
return (abs(pitch) > M_PI / 2) || (abs(roll) > M_PI / 2); return (abs(pitch) > M_PI / 2) || (abs(roll) > M_PI / 2);
} }
/* Set roll and pitch from "from" to "to", keeping yaw */ /* Apply a vertical to an orientation */
inline void snapOrientation(geometry_msgs::Quaternion& to, const geometry_msgs::Quaternion& from, bool auto_flip = false) inline void applyVertical(geometry_msgs::Quaternion& orientation, const geometry_msgs::Quaternion& vertical,
bool flip_vertical = false, bool auto_flip = false) // editorconfig-checker-disable-line
{ {
tf::Quaternion _from, _to; tf::Quaternion _vertical, _orientation;
tf::quaternionMsgToTF(from, _from); tf::quaternionMsgToTF(vertical, _vertical);
tf::quaternionMsgToTF(to, _to); tf::quaternionMsgToTF(orientation, _orientation);
if (auto_flip) { if (flip_vertical || (auto_flip && !isFlipped(_orientation))) {
if (!isFlipped(_from)) { static const tf::Quaternion flip = tf::createQuaternionFromRPY(M_PI, 0, 0);
static const tf::Quaternion flip = tf::createQuaternionFromRPY(M_PI, 0, 0); _vertical *= flip; // flip vertical
_from *= flip; // flip "from"
}
} }
auto diff = tf::Matrix3x3(_to).transposeTimes(tf::Matrix3x3(_from)); auto diff = tf::Matrix3x3(_orientation).transposeTimes(tf::Matrix3x3(_vertical));
double _, yaw; double _, yaw;
diff.getRPY(_, _, yaw); diff.getRPY(_, _, yaw);
auto q = tf::createQuaternionFromRPY(0, 0, -yaw); auto q = tf::createQuaternionFromRPY(0, 0, -yaw);
_from = _from * q; // set yaw from "to" to "from" _vertical = _vertical * q; // set yaw from orientation to vertical
tf::quaternionTFToMsg(_from, to); // set "from" to "to" tf::quaternionTFToMsg(_vertical, orientation); // set vertical to orientation
} }
inline void transformToPose(const geometry_msgs::Transform& transform, geometry_msgs::Pose& pose) inline void transformToPose(const geometry_msgs::Transform& transform, geometry_msgs::Pose& pose)

View File

@@ -6,7 +6,7 @@ import tf2_geometry_msgs
from geometry_msgs.msg import PoseWithCovarianceStamped from geometry_msgs.msg import PoseWithCovarianceStamped
from sensor_msgs.msg import Image from sensor_msgs.msg import Image
from aruco_pose.msg import MarkerArray from aruco_pose.msg import MarkerArray
from visualization_msgs.msg import MarkerArray as VisMarkerArray from visualization_msgs.msg import MarkerArray as VisMarkerArray, Marker as VisMarker
@pytest.fixture @pytest.fixture
@@ -199,6 +199,36 @@ def test_map_markers(node):
def test_map_visualization(node): def test_map_visualization(node):
vis = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5) vis = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
assert len(vis.markers) == 7
assert vis.markers[0].header.frame_id == 'aruco_map'
assert vis.markers[0].type == VisMarker.CUBE
assert vis.markers[0].action == VisMarker.ADD
assert vis.markers[0].pose.position.x == 0
assert vis.markers[0].pose.position.y == 0
assert vis.markers[0].pose.position.z == 0
assert vis.markers[0].pose.orientation.x == 0
assert vis.markers[0].pose.orientation.y == 0
assert vis.markers[0].pose.orientation.z == 0
assert vis.markers[0].pose.orientation.w == 1
assert vis.markers[0].scale.x == approx(0.33)
assert vis.markers[0].scale.y == approx(0.33)
assert vis.markers[0].scale.z == approx(0.001)
assert vis.markers[1].pose.position.x == 1
assert vis.markers[1].pose.position.y == 0
assert vis.markers[1].pose.position.z == 0
assert vis.markers[1].pose.orientation.x == 0
assert vis.markers[1].pose.orientation.y == 0
assert vis.markers[1].pose.orientation.z == 0
assert vis.markers[1].pose.orientation.w == 1
# non-zero yaw marker:
assert vis.markers[4].scale.x == approx(0.5)
assert vis.markers[4].pose.position.x == approx(0.5)
assert vis.markers[4].pose.position.y == 2
assert vis.markers[4].pose.position.z == 0
assert vis.markers[4].pose.orientation.x == 0
assert vis.markers[4].pose.orientation.y == 0
assert vis.markers[4].pose.orientation.z == approx(0.5646424733950354)
assert vis.markers[4].pose.orientation.w == approx(0.8253356149096783)
def test_map_debug(node): def test_map_debug(node):
img = rospy.wait_for_message('aruco_map/debug', Image, timeout=5) img = rospy.wait_for_message('aruco_map/debug', Image, timeout=5)

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -0,0 +1,8 @@
import pytest
import subprocess
def test_no_tf_repeated_data():
# `/rosout` acts weirdly inside rostest, so using a subprocess
cmd = """python -c 'import rospy, tf; rospy.init_node("foo"); listener = tf.TransformListener(); rospy.sleep(2)'"""
output = str(subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT))
assert 'TF_REPEATED_DATA' not in output, 'TF_REPEATED_DATA was logged on duplicate markers'

View File

@@ -0,0 +1,21 @@
<launch>
<node pkg="image_publisher" type="image_publisher" name="main_camera" args="$(find aruco_pose)/test/duplicate.png">
<param name="frame_id" value="main_camera_optical"/>
<param name="publish_rate" value="10"/>
<param name="camera_info_url" value="file://$(find aruco_pose)/test/camera_info.yaml" />
</node>
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager" required="true"/>
<node pkg="nodelet" clear_params="true" type="nodelet" name="aruco_detect" args="load aruco_pose/aruco_detect nodelet_manager" required="true">
<remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/>
<param name="length" value="0.33"/>
<param name="estimate_poses" value="true"/>
<param name="send_tf" value="true"/>
<param name="cornerRefinementMethod" value="1"/>
</node>
<param name="test_module" value="$(find aruco_pose)/test/duplicate.py"/>
<test test-name="aruco_pose_test" pkg="ros_pytest" type="ros_pytest_runner"/>
</launch>

View File

@@ -16,3 +16,726 @@ web_video_server:
ws281x: ws281x:
debian: debian:
buster: [ros-noetic-ws281x] buster: [ros-noetic-ws281x]
catkin:
debian:
buster: [ros-noetic-catkin]
genmsg:
debian:
buster: [ros-noetic-genmsg]
gencpp:
debian:
buster: [ros-noetic-gencpp]
geneus:
debian:
buster: [ros-noetic-geneus]
genlisp:
debian:
buster: [ros-noetic-genlisp]
gennodejs:
debian:
buster: [ros-noetic-gennodejs]
genpy:
debian:
buster: [ros-noetic-genpy]
bond_core:
debian:
buster: [ros-noetic-bond-core]
cmake_modules:
debian:
buster: [ros-noetic-cmake-modules]
class_loader:
debian:
buster: [ros-noetic-class-loader]
common_msgs:
debian:
buster: [ros-noetic-common-msgs]
common_tutorials:
debian:
buster: [ros-noetic-common-tutorials]
cpp_common:
debian:
buster: [ros-noetic-cpp-common]
desktop:
debian:
buster: [ros-noetic-desktop]
diagnostics:
debian:
buster: [ros-noetic-diagnostics]
executive_smach:
debian:
buster: [ros-noetic-executive-smach]
geometry:
debian:
buster: [ros-noetic-geometry]
geometry_tutorials:
debian:
buster: [ros-noetic-geometry-tutorials]
gl_dependency:
debian:
buster: [ros-noetic-gl-dependency]
image_common:
debian:
buster: [ros-noetic-image-common]
image_pipeline:
debian:
buster: [ros-noetic-image-pipeline]
image_transport_plugins:
debian:
buster: [ros-noetic-image-transport-plugins]
laser_pipeline:
debian:
buster: [ros-noetic-laser-pipeline]
mavlink:
debian:
buster: [ros-noetic-mavlink]
media_export:
debian:
buster: [ros-noetic-media-export]
message_generation:
debian:
buster: [ros-noetic-message-generation]
message_runtime:
debian:
buster: [ros-noetic-message-runtime]
mk:
debian:
buster: [ros-noetic-mk]
nodelet_core:
debian:
buster: [ros-noetic-nodelet-core]
orocos_kdl:
debian:
buster: [ros-noetic-orocos-kdl]
perception:
debian:
buster: [ros-noetic-perception]
perception_pcl:
debian:
buster: [ros-noetic-perception-pcl]
python_orocos_kdl:
debian:
buster: [ros-noetic-python-orocos-kdl]
qt_dotgraph:
debian:
buster: [ros-noetic-qt-dotgraph]
qt_gui:
debian:
buster: [ros-noetic-qt-gui]
qt_gui_py_common:
debian:
buster: [ros-noetic-qt-gui-py-common]
qwt_dependency:
debian:
buster: [ros-noetic-qwt-dependency]
robot:
debian:
buster: [ros-noetic-robot]
ros:
debian:
buster: [ros-noetic-ros]
ros_base:
debian:
buster: [ros-noetic-ros-base]
ros_comm:
debian:
buster: [ros-noetic-ros-comm]
ros_core:
debian:
buster: [ros-noetic-ros-core]
ros_environment:
debian:
buster: [ros-noetic-ros-environment]
ros_tutorials:
debian:
buster: [ros-noetic-ros-tutorials]
rosapi:
debian:
buster: [ros-noetic-rosapi]
rosbag_migration_rule:
debian:
buster: [ros-noetic-rosbag-migration-rule]
rosbash:
debian:
buster: [ros-noetic-rosbash]
rosboost_cfg:
debian:
buster: [ros-noetic-rosboost-cfg]
rosbridge_server:
debian:
buster: [ros-noetic-rosbridge-server]
rosbridge_suite:
debian:
buster: [ros-noetic-rosbridge-suite]
rosbuild:
debian:
buster: [ros-noetic-rosbuild]
rosclean:
debian:
buster: [ros-noetic-rosclean]
roscpp_core:
debian:
buster: [ros-noetic-roscpp-core]
roscpp_traits:
debian:
buster: [ros-noetic-roscpp-traits]
roscreate:
debian:
buster: [ros-noetic-roscreate]
rosgraph:
debian:
buster: [ros-noetic-rosgraph]
roslang:
debian:
buster: [ros-noetic-roslang]
roslint:
debian:
buster: [ros-noetic-roslint]
roslisp:
debian:
buster: [ros-noetic-roslisp]
rosmake:
debian:
buster: [ros-noetic-rosmake]
rosmaster:
debian:
buster: [ros-noetic-rosmaster]
rospack:
debian:
buster: [ros-noetic-rospack]
roslib:
debian:
buster: [ros-noetic-roslib]
rosparam:
debian:
buster: [ros-noetic-rosparam]
rospy:
debian:
buster: [ros-noetic-rospy]
rosserial:
debian:
buster: [ros-noetic-rosserial]
rosserial_msgs:
debian:
buster: [ros-noetic-rosserial-msgs]
rosserial_python:
debian:
buster: [ros-noetic-rosserial-python]
rosservice:
debian:
buster: [ros-noetic-rosservice]
rostime:
debian:
buster: [ros-noetic-rostime]
roscpp_serialization:
debian:
buster: [ros-noetic-roscpp-serialization]
python_qt_binding:
debian:
buster: [ros-noetic-python-qt-binding]
roslaunch:
debian:
buster: [ros-noetic-roslaunch]
rosunit:
debian:
buster: [ros-noetic-rosunit]
angles:
debian:
buster: [ros-noetic-angles]
libmavconn:
debian:
buster: [ros-noetic-libmavconn]
rosconsole:
debian:
buster: [ros-noetic-rosconsole]
pluginlib:
debian:
buster: [ros-noetic-pluginlib]
qt_gui_cpp:
debian:
buster: [ros-noetic-qt-gui-cpp]
resource_retriever:
debian:
buster: [ros-noetic-resource-retriever]
rosconsole_bridge:
debian:
buster: [ros-noetic-rosconsole-bridge]
roslz4:
debian:
buster: [ros-noetic-roslz4]
rosserial_client:
debian:
buster: [ros-noetic-rosserial-client]
rostest:
debian:
buster: [ros-noetic-rostest]
rqt_action:
debian:
buster: [ros-noetic-rqt-action]
rqt_bag:
debian:
buster: [ros-noetic-rqt-bag]
rqt_bag_plugins:
debian:
buster: [ros-noetic-rqt-bag-plugins]
rqt_common_plugins:
debian:
buster: [ros-noetic-rqt-common-plugins]
rqt_console:
debian:
buster: [ros-noetic-rqt-console]
rqt_dep:
debian:
buster: [ros-noetic-rqt-dep]
rqt_graph:
debian:
buster: [ros-noetic-rqt-graph]
rqt_gui:
debian:
buster: [ros-noetic-rqt-gui]
rqt_logger_level:
debian:
buster: [ros-noetic-rqt-logger-level]
rqt_moveit:
debian:
buster: [ros-noetic-rqt-moveit]
rqt_msg:
debian:
buster: [ros-noetic-rqt-msg]
rqt_nav_view:
debian:
buster: [ros-noetic-rqt-nav-view]
rqt_plot:
debian:
buster: [ros-noetic-rqt-plot]
rqt_pose_view:
debian:
buster: [ros-noetic-rqt-pose-view]
rqt_publisher:
debian:
buster: [ros-noetic-rqt-publisher]
rqt_py_console:
debian:
buster: [ros-noetic-rqt-py-console]
rqt_reconfigure:
debian:
buster: [ros-noetic-rqt-reconfigure]
rqt_robot_dashboard:
debian:
buster: [ros-noetic-rqt-robot-dashboard]
rqt_robot_monitor:
debian:
buster: [ros-noetic-rqt-robot-monitor]
rqt_robot_plugins:
debian:
buster: [ros-noetic-rqt-robot-plugins]
rqt_robot_steering:
debian:
buster: [ros-noetic-rqt-robot-steering]
rqt_runtime_monitor:
debian:
buster: [ros-noetic-rqt-runtime-monitor]
rqt_service_caller:
debian:
buster: [ros-noetic-rqt-service-caller]
rqt_shell:
debian:
buster: [ros-noetic-rqt-shell]
rqt_srv:
debian:
buster: [ros-noetic-rqt-srv]
rqt_tf_tree:
debian:
buster: [ros-noetic-rqt-tf-tree]
rqt_top:
debian:
buster: [ros-noetic-rqt-top]
rqt_topic:
debian:
buster: [ros-noetic-rqt-topic]
rqt_web:
debian:
buster: [ros-noetic-rqt-web]
smach:
debian:
buster: [ros-noetic-smach]
smclib:
debian:
buster: [ros-noetic-smclib]
std_msgs:
debian:
buster: [ros-noetic-std-msgs]
actionlib_msgs:
debian:
buster: [ros-noetic-actionlib-msgs]
bond:
debian:
buster: [ros-noetic-bond]
diagnostic_msgs:
debian:
buster: [ros-noetic-diagnostic-msgs]
geometry_msgs:
debian:
buster: [ros-noetic-geometry-msgs]
eigen_conversions:
debian:
buster: [ros-noetic-eigen-conversions]
kdl_conversions:
debian:
buster: [ros-noetic-kdl-conversions]
nav_msgs:
debian:
buster: [ros-noetic-nav-msgs]
rosbridge_msgs:
debian:
buster: [ros-noetic-rosbridge-msgs]
rosgraph_msgs:
debian:
buster: [ros-noetic-rosgraph-msgs]
rosmsg:
debian:
buster: [ros-noetic-rosmsg]
rqt_py_common:
debian:
buster: [ros-noetic-rqt-py-common]
shape_msgs:
debian:
buster: [ros-noetic-shape-msgs]
smach_msgs:
debian:
buster: [ros-noetic-smach-msgs]
std_srvs:
debian:
buster: [ros-noetic-std-srvs]
tf2_msgs:
debian:
buster: [ros-noetic-tf2-msgs]
tf2:
debian:
buster: [ros-noetic-tf2]
tf2_eigen:
debian:
buster: [ros-noetic-tf2-eigen]
trajectory_msgs:
debian:
buster: [ros-noetic-trajectory-msgs]
control_msgs:
debian:
buster: [ros-noetic-control-msgs]
urdf_parser_plugin:
debian:
buster: [ros-noetic-urdf-parser-plugin]
urdfdom_py:
debian:
buster: [ros-noetic-urdfdom-py]
uuid_msgs:
debian:
buster: [ros-noetic-uuid-msgs]
geographic_msgs:
debian:
buster: [ros-noetic-geographic-msgs]
vision_opencv:
debian:
buster: [ros-noetic-vision-opencv]
visualization_msgs:
debian:
buster: [ros-noetic-visualization-msgs]
visualization_tutorials:
debian:
buster: [ros-noetic-visualization-tutorials]
viz:
debian:
buster: [ros-noetic-viz]
webkit_dependency:
debian:
buster: [ros-noetic-webkit-dependency]
xmlrpcpp:
debian:
buster: [ros-noetic-xmlrpcpp]
roscpp:
debian:
buster: [ros-noetic-roscpp]
bondcpp:
debian:
buster: [ros-noetic-bondcpp]
bondpy:
debian:
buster: [ros-noetic-bondpy]
nodelet:
debian:
buster: [ros-noetic-nodelet]
nodelet_tutorial_math:
debian:
buster: [ros-noetic-nodelet-tutorial-math]
pluginlib_tutorials:
debian:
buster: [ros-noetic-pluginlib-tutorials]
roscpp_tutorials:
debian:
buster: [ros-noetic-roscpp-tutorials]
rosout:
debian:
buster: [ros-noetic-rosout]
camera_calibration:
debian:
buster: [ros-noetic-camera-calibration]
diagnostic_aggregator:
debian:
buster: [ros-noetic-diagnostic-aggregator]
diagnostic_updater:
debian:
buster: [ros-noetic-diagnostic-updater]
diagnostic_common_diagnostics:
debian:
buster: [ros-noetic-diagnostic-common-diagnostics]
dynamic_reconfigure:
debian:
buster: [ros-noetic-dynamic-reconfigure]
filters:
debian:
buster: [ros-noetic-filters]
joint_state_publisher:
debian:
buster: [ros-noetic-joint-state-publisher]
message_filters:
debian:
buster: [ros-noetic-message-filters]
rosauth:
debian:
buster: [ros-noetic-rosauth]
rosbag_storage:
debian:
buster: [ros-noetic-rosbag-storage]
rosnode:
debian:
buster: [ros-noetic-rosnode]
rospy_tutorials:
debian:
buster: [ros-noetic-rospy-tutorials]
rosshow:
debian:
buster: [ros-noetic-rosshow]
rostopic:
debian:
buster: [ros-noetic-rostopic]
rqt_gui_cpp:
debian:
buster: [ros-noetic-rqt-gui-cpp]
rqt_gui_py:
debian:
buster: [ros-noetic-rqt-gui-py]
self_test:
debian:
buster: [ros-noetic-self-test]
smach_ros:
debian:
buster: [ros-noetic-smach-ros]
tf2_py:
debian:
buster: [ros-noetic-tf2-py]
topic_tools:
debian:
buster: [ros-noetic-topic-tools]
rosbag:
debian:
buster: [ros-noetic-rosbag]
actionlib:
debian:
buster: [ros-noetic-actionlib]
actionlib_tutorials:
debian:
buster: [ros-noetic-actionlib-tutorials]
diagnostic_analysis:
debian:
buster: [ros-noetic-diagnostic-analysis]
nodelet_topic_tools:
debian:
buster: [ros-noetic-nodelet-topic-tools]
roswtf:
debian:
buster: [ros-noetic-roswtf]
rqt_launch:
debian:
buster: [ros-noetic-rqt-launch]
sensor_msgs:
debian:
buster: [ros-noetic-sensor-msgs]
camera_calibration_parsers:
debian:
buster: [ros-noetic-camera-calibration-parsers]
cv_bridge:
debian:
buster: [ros-noetic-cv-bridge]
image_geometry:
debian:
buster: [ros-noetic-image-geometry]
image_transport:
debian:
buster: [ros-noetic-image-transport]
camera_info_manager:
debian:
buster: [ros-noetic-camera-info-manager]
compressed_depth_image_transport:
debian:
buster: [ros-noetic-compressed-depth-image-transport]
compressed_image_transport:
debian:
buster: [ros-noetic-compressed-image-transport]
cv_camera:
debian:
buster: [ros-noetic-cv-camera]
image_proc:
debian:
buster: [ros-noetic-image-proc]
image_publisher:
debian:
buster: [ros-noetic-image-publisher]
map_msgs:
debian:
buster: [ros-noetic-map-msgs]
mavros_msgs:
debian:
buster: [ros-noetic-mavros-msgs]
pcl_msgs:
debian:
buster: [ros-noetic-pcl-msgs]
pcl_conversions:
debian:
buster: [ros-noetic-pcl-conversions]
polled_camera:
debian:
buster: [ros-noetic-polled-camera]
rqt_image_view:
debian:
buster: [ros-noetic-rqt-image-view]
stereo_msgs:
debian:
buster: [ros-noetic-stereo-msgs]
image_view:
debian:
buster: [ros-noetic-image-view]
rosbridge_library:
debian:
buster: [ros-noetic-rosbridge-library]
stereo_image_proc:
debian:
buster: [ros-noetic-stereo-image-proc]
tf2_ros:
debian:
buster: [ros-noetic-tf2-ros]
depth_image_proc:
debian:
buster: [ros-noetic-depth-image-proc]
mavros:
debian:
buster: [ros-noetic-mavros]
tf:
debian:
buster: [ros-noetic-tf]
interactive_markers:
debian:
buster: [ros-noetic-interactive-markers]
interactive_marker_tutorials:
debian:
buster: [ros-noetic-interactive-marker-tutorials]
laser_geometry:
debian:
buster: [ros-noetic-laser-geometry]
laser_assembler:
debian:
buster: [ros-noetic-laser-assembler]
laser_filters:
debian:
buster: [ros-noetic-laser-filters]
pcl_ros:
debian:
buster: [ros-noetic-pcl-ros]
tf2_geometry_msgs:
debian:
buster: [ros-noetic-tf2-geometry-msgs]
image_rotate:
debian:
buster: [ros-noetic-image-rotate]
tf2_kdl:
debian:
buster: [ros-noetic-tf2-kdl]
tf2_web_republisher:
debian:
buster: [ros-noetic-tf2-web-republisher]
tf_conversions:
debian:
buster: [ros-noetic-tf-conversions]
theora_image_transport:
debian:
buster: [ros-noetic-theora-image-transport]
turtlesim:
debian:
buster: [ros-noetic-turtlesim]
turtle_actionlib:
debian:
buster: [ros-noetic-turtle-actionlib]
turtle_tf:
debian:
buster: [ros-noetic-turtle-tf]
turtle_tf2:
debian:
buster: [ros-noetic-turtle-tf2]
urdf:
debian:
buster: [ros-noetic-urdf]
kdl_parser:
debian:
buster: [ros-noetic-kdl-parser]
kdl_parser_py:
debian:
buster: [ros-noetic-kdl-parser-py]
mavros_extras:
debian:
buster: [ros-noetic-mavros-extras]
robot_state_publisher:
debian:
buster: [ros-noetic-robot-state-publisher]
rviz:
debian:
buster: [ros-noetic-rviz]
librviz_tutorial:
debian:
buster: [ros-noetic-librviz-tutorial]
rqt_rviz:
debian:
buster: [ros-noetic-rqt-rviz]
rviz_plugin_tutorials:
debian:
buster: [ros-noetic-rviz-plugin-tutorials]
rviz_python_tutorial:
debian:
buster: [ros-noetic-rviz-python-tutorial]
urdf_tutorial:
debian:
buster: [ros-noetic-urdf-tutorial]
usb_cam:
debian:
buster: [ros-noetic-usb-cam]
visualization_marker_tutorials:
debian:
buster: [ros-noetic-visualization-marker-tutorials]
vl53l1x:
debian:
buster: [ros-noetic-vl53l1x]
xacro:
debian:
buster: [ros-noetic-xacro]
ddynamic_reconfigure:
debian:
buster: [ros-noetic-ddynamic-reconfigure]
librealsense2:
debian:
buster: [ros-noetic-librealsense2]
realsense2_camera:
debian:
buster: [ros-noetic-realsense2-camera]
realsense2_description:
debian:
buster: [ros-noetic-realsense2-description]
geographiclib:
debian:
buster: [libgeographic-dev]

View File

@@ -58,4 +58,7 @@ sed -i 's/#SystemMaxUse=/SystemMaxUse=200M/' /etc/systemd/journald.conf
echo_stamp "Move /etc/ld.so.preload out of the way" echo_stamp "Move /etc/ld.so.preload out of the way"
mv /etc/ld.so.preload /etc/ld.so.preload.disabled-for-build mv /etc/ld.so.preload /etc/ld.so.preload.disabled-for-build
echo_stamp "Setup apt so it store all the downloaded packages"
echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/02keep-debs
echo_stamp "End of init image" echo_stamp "End of init image"

View File

@@ -49,7 +49,7 @@ echo_stamp() {
my_travis_retry() { my_travis_retry() {
local result=0 local result=0
local count=1 local count=1
local max_count=50 local max_count=5
while [ $count -le $max_count ]; do while [ $count -le $max_count ]; do
[ $result -ne 0 ] && { [ $result -ne 0 ] && {
echo -e "\nThe command \"$@\" failed. Retrying, $count of $max_count.\n" >&2 echo -e "\nThe command \"$@\" failed. Retrying, $count of $max_count.\n" >&2
@@ -72,7 +72,7 @@ my_travis_retry() {
echo_stamp "Init rosdep" echo_stamp "Init rosdep"
my_travis_retry rosdep init my_travis_retry rosdep init
# FIXME: Re-add this after missing packages are built # FIXME: Re-add this after missing packages are built
echo "yaml file:///etc/ros/rosdep/${ROS_DISTRO}-rosdep-clover.yaml" >> /etc/ros/rosdep/sources.list.d/20-default.list echo "yaml file:///etc/ros/rosdep/${ROS_DISTRO}-rosdep-clover.yaml" >> /etc/ros/rosdep/sources.list.d/10-clover.list
my_travis_retry rosdep update my_travis_retry rosdep update
echo_stamp "Populate rosdep for ROS user" echo_stamp "Populate rosdep for ROS user"
@@ -125,11 +125,12 @@ cd /home/pi/catkin_ws/src/clover
builder/assets/install_gitbook.sh builder/assets/install_gitbook.sh
gitbook install gitbook install
gitbook build gitbook build
# replace assets copy to assets symlink to save space
rm -rf _book/assets && ln -s ../docs/assets _book/assets
touch node_modules/CATKIN_IGNORE docs/CATKIN_IGNORE _book/CATKIN_IGNORE clover/www/CATKIN_IGNORE apps/CATKIN_IGNORE # ignore documentation files by catkin touch node_modules/CATKIN_IGNORE docs/CATKIN_IGNORE _book/CATKIN_IGNORE clover/www/CATKIN_IGNORE apps/CATKIN_IGNORE # ignore documentation files by catkin
echo_stamp "Installing additional ROS packages" echo_stamp "Installing additional ROS packages"
my_travis_retry apt-get install -y --no-install-recommends \ my_travis_retry apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-dynamic-reconfigure \
ros-${ROS_DISTRO}-rosbridge-suite \ ros-${ROS_DISTRO}-rosbridge-suite \
ros-${ROS_DISTRO}-rosserial \ ros-${ROS_DISTRO}-rosserial \
ros-${ROS_DISTRO}-usb-cam \ ros-${ROS_DISTRO}-usb-cam \
@@ -137,7 +138,9 @@ my_travis_retry apt-get install -y --no-install-recommends \
ros-${ROS_DISTRO}-ws281x \ ros-${ROS_DISTRO}-ws281x \
ros-${ROS_DISTRO}-rosshow \ ros-${ROS_DISTRO}-rosshow \
ros-${ROS_DISTRO}-cmake-modules \ ros-${ROS_DISTRO}-cmake-modules \
ros-${ROS_DISTRO}-image-view ros-${ROS_DISTRO}-image-view \
ros-${ROS_DISTRO}-nodelet-topic-tools \
ros-${ROS_DISTRO}-stereo-msgs
# TODO move GeographicLib datasets to Mavros debian package # TODO move GeographicLib datasets to Mavros debian package
echo_stamp "Install GeographicLib datasets (needed for mavros)" \ echo_stamp "Install GeographicLib datasets (needed for mavros)" \
@@ -151,6 +154,9 @@ catkin_make run_tests #&& catkin_test_results
echo_stamp "Change permissions for catkin_ws" echo_stamp "Change permissions for catkin_ws"
chown -Rf pi:pi /home/pi/catkin_ws chown -Rf pi:pi /home/pi/catkin_ws
echo_stamp "Update www"
sudo -u pi sh -c ". devel/setup.sh && rosrun clover www"
echo_stamp "Make \$HOME/examples symlink" echo_stamp "Make \$HOME/examples symlink"
ln -s "$(catkin_find clover examples --first-only)" /home/pi ln -s "$(catkin_find clover examples --first-only)" /home/pi
chown -Rf pi:pi /home/pi/examples chown -Rf pi:pi /home/pi/examples
@@ -178,7 +184,7 @@ EOF
# Restore original sources.list # Restore original sources.list
#mv /var/sources.list.bak /etc/apt/sources.list #mv /var/sources.list.bak /etc/apt/sources.list
# Clean apt cache # Clean apt cache
apt-get clean -qq > /dev/null # apt-get clean -qq > /dev/null
# Remove local mirror repository key # Remove local mirror repository key
#apt-key del COEX-MIRROR #apt-key del COEX-MIRROR

View File

@@ -37,3 +37,7 @@ apt-cache show openvpn
echo "Move /etc/ld.so.preload back to its original position" echo "Move /etc/ld.so.preload back to its original position"
mv /etc/ld.so.preload.disabled-for-build /etc/ld.so.preload mv /etc/ld.so.preload.disabled-for-build /etc/ld.so.preload
echo "Largest packages installed"
sudo -E sh -c 'apt-get install -y debian-goodies'
dpigs -H -n 100

View File

@@ -2,6 +2,7 @@
# validate all required modules installed # validate all required modules installed
import os
import rospy import rospy
from geometry_msgs.msg import PoseStamped from geometry_msgs.msg import PoseStamped
from sensor_msgs.msg import Range, BatteryState from sensor_msgs.msg import Range, BatteryState
@@ -22,6 +23,7 @@ from clover.srv import GetTelemetry, Navigate, NavigateGlobal, SetPosition, SetV
from led_msgs.srv import SetLEDs from led_msgs.srv import SetLEDs
from led_msgs.msg import LEDStateArray, LEDState from led_msgs.msg import LEDStateArray, LEDState
from aruco_pose.msg import Marker, MarkerArray, Point2D from aruco_pose.msg import Marker, MarkerArray, Point2D
from clover import long_callback
import dynamic_reconfigure.client import dynamic_reconfigure.client
@@ -31,9 +33,15 @@ import tf2_geometry_msgs
import VL53L1X import VL53L1X
import pymavlink import pymavlink
from pymavlink import mavutil from pymavlink import mavutil
import rpi_ws281x from image_geometry import PinholeCameraModel, StereoCameraModel
import pigpio
# from espeak import espeak # from espeak import espeak
from pyzbar import pyzbar from pyzbar import pyzbar
import docopt
import geopy
import flask
print(cv2.getBuildInformation()) print(cv2.getBuildInformation())
if not os.environ.get('VM'):
import rpi_ws281x
import pigpio

View File

@@ -6,16 +6,10 @@ set -ex
# validate required software is installed # validate required software is installed
python --version
python2 --version python2 --version
python3 --version python3 --version
ipython --version
ipython3 --version ipython3 --version
# ptvsd does not have a stand-alone binary
python -m ptvsd --version
python3 -m ptvsd --version
node -v node -v
npm -v npm -v
@@ -25,42 +19,77 @@ lsof -v
git --version git --version
vim --version vim --version
pip --version pip --version
pip2 --version
pip3 --version pip3 --version
tcpdump --version tcpdump --version
monkey --version monkey --version
pigpiod -v
i2cdetect -V
butterfly -h
# espeak --version # espeak --version
mjpg_streamer --version
systemctl --version systemctl --version
if [ -z $VM ]; then
# rpi only software
python --version
ipython --version
pip2 --version
# `python` is python2 for now
[[ $(python -c 'import sys;print(sys.version_info.major)') == "2" ]]
# ptvsd does not have a stand-alone binary
python -m ptvsd --version
python3 -m ptvsd --version
pigpiod -v
i2cdetect -V
butterfly -h
mjpg_streamer --version
fi
# ros stuff # ros stuff
roscore -h roscore -h
rosversion clover rosversion clover
rosversion aruco_pose rosversion aruco_pose
rosversion vl53l1x
rosversion mavros rosversion mavros
rosversion mavros_extras rosversion mavros_extras
rosversion ws281x rosversion ws281x
rosversion led_msgs rosversion led_msgs
rosversion dynamic_reconfigure rosversion dynamic_reconfigure
rosversion tf2_web_republisher rosversion tf2_web_republisher
rosversion compressed_image_transport rosversion rosbridge_server
rosversion rosbridge_suite
rosversion rosserial
rosversion usb_cam rosversion usb_cam
rosversion cv_camera rosversion cv_camera
rosversion web_video_server rosversion web_video_server
rosversion rosshow
rosversion nodelet rosversion nodelet
rosversion image_view rosversion image_view
# validate some versions
[[ $(rosversion cv_camera) == "0.5.1" ]] # patched version with init fix
[[ $(rosversion ws281x) == "0.0.13" ]] [[ $(rosversion ws281x) == "0.0.13" ]]
if [ -z $VM ]; then
rosversion compressed_image_transport
rosversion rosshow
rosversion vl53l1x
rosversion rosserial
[[ $(rosversion cv_camera) == "0.5.1" ]] # patched version with init fix
fi
# determine user home directory
[ $VM ] && H="/home/clover" || H="/home/pi"
# test basic ros tool work
source $H/catkin_ws/devel/setup.bash
roscd
rosrun
rosmsg
rossrv
rosnode || [ $? -eq 64 ] # usage output code is 64
rostopic || [ $? -eq 64 ]
rosservice || [ $? -eq 64 ]
rosparam
roslaunch -h
# validate examples are present # validate examples are present
[[ $(ls /home/pi/examples/*) ]] [[ $(ls $H/examples/*) ]]
# validate web tools present
[ -d $H/.ros/www ]
[ "$(readlink $H/.ros/www/clover)" = "$H/catkin_ws/src/clover/clover/www" ]
[ "$(readlink $H/.ros/www/clover_blocks)" = "$H/catkin_ws/src/clover/clover_blocks/www" ]

View File

@@ -18,7 +18,7 @@ EXCLUDE = 'rviz.png', 'ssid.png', 'sitl_docker_demo.png', 'qgc-params.png', 'but
'qgc-battery.png', 'qgc-radio.png', 'qgc-cal-acc.png', 'qgc-esc.png', 'qgc-cal-compass.png', \ 'qgc-battery.png', 'qgc-radio.png', 'qgc-cal-acc.png', 'qgc-esc.png', 'qgc-cal-compass.png', \
'qgc.png', 'qgc-parameters.png', 'clever4-front-white-large.png', 'qgc-modes.png', \ 'qgc.png', 'qgc-parameters.png', 'clever4-front-white-large.png', 'qgc-modes.png', \
'qgc-requires-setup.png', 'clever4-front-white.png', 'clever4-kit-white.png', '26_1.png', 'battery_holder.stl', \ 'qgc-requires-setup.png', 'clever4-front-white.png', 'clever4-kit-white.png', '26_1.png', 'battery_holder.stl', \
'camera_case.stl', 'camera_mount.stl' 'camera_case.stl', 'camera_mount.stl', 'grip_right.stl', 'grip_left.stl'
code = 0 code = 0

View File

@@ -30,6 +30,8 @@ find_package(catkin REQUIRED COMPONENTS
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
# https://github.com/mavlink/mavros/blob/7f1a8/mavros/CMakeLists.txt#L42
set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH};/usr/share/cmake/geographiclib")
find_package(GeographicLib REQUIRED) find_package(GeographicLib REQUIRED)
# Workaround for OpenCV 3/4 support # Workaround for OpenCV 3/4 support
@@ -53,7 +55,7 @@ find_package(OpenCV ${_opencv_version} REQUIRED
## Uncomment this if the package has a setup.py. This macro ensures ## Uncomment this if the package has a setup.py. This macro ensures
## modules and global scripts declared therein get installed ## modules and global scripts declared therein get installed
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
# catkin_python_setup() catkin_python_setup()
################################################ ################################################
## Declare ROS messages, services and actions ## ## Declare ROS messages, services and actions ##
@@ -80,11 +82,10 @@ find_package(OpenCV ${_opencv_version} REQUIRED
## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...)
## Generate messages in the 'msg' folder ## Generate messages in the 'msg' folder
# add_message_files( add_message_files(
# FILES FILES
# Message1.msg State.msg
# Message2.msg )
# )
## Generate services in the 'srv' folder ## Generate services in the 'srv' folder
add_service_files( add_service_files(
@@ -92,6 +93,9 @@ add_service_files(
GetTelemetry.srv GetTelemetry.srv
Navigate.srv Navigate.srv
NavigateGlobal.srv NavigateGlobal.srv
SetAltitude.srv
SetYaw.srv
SetYawRate.srv
SetPosition.srv SetPosition.srv
SetVelocity.srv SetVelocity.srv
SetAttitude.srv SetAttitude.srv
@@ -306,4 +310,5 @@ endif()
if (CATKIN_ENABLE_TESTING) if (CATKIN_ENABLE_TESTING)
find_package(rostest REQUIRED) find_package(rostest REQUIRED)
add_rostest(test/basic.test) add_rostest(test/basic.test)
add_rostest(test/offboard.test)
endif() endif()

View File

@@ -1,18 +0,0 @@
# taken from: https://github.com/mavlink/mavros/blob/master/libmavconn/cmake/Modules/FindGeographicLib.cmake
# Look for GeographicLib
#
# Set
# GEOGRAPHICLIB_FOUND = TRUE
# GeographicLib_INCLUDE_DIRS = /usr/local/include
# GeographicLib_LIBRARIES = /usr/local/lib/libGeographic.so
# GeographicLib_LIBRARY_DIRS = /usr/local/lib
find_path (GeographicLib_INCLUDE_DIRS NAMES GeographicLib/Config.h)
find_library (GeographicLib_LIBRARIES NAMES Geographic)
include (FindPackageHandleStandardArgs)
find_package_handle_standard_args (GeographicLib DEFAULT_MSG
GeographicLib_LIBRARIES GeographicLib_INCLUDE_DIRS)
mark_as_advanced (GeographicLib_LIBRARIES GeographicLib_INCLUDE_DIRS)

64
clover/examples/camera.py Normal file
View File

@@ -0,0 +1,64 @@
# Information: https://clover.coex.tech/camera
# Example on basic working with the camera and image processing:
# - cuts out a central square from the camera image;
# - publishes this cropped image to the topic `/cv/center`;
# - computes the average color of it;
# - prints its name to the console.
import rospy
import cv2
from sensor_msgs.msg import Image
from cv_bridge import CvBridge
from clover import long_callback
rospy.init_node('cv')
bridge = CvBridge()
printed_color = None
center_pub = rospy.Publisher('~center', Image, queue_size=1)
def get_color_name(h):
if h < 15: return 'red'
elif h < 30: return 'orange'
elif h < 60: return 'yellow'
elif h < 90: return 'green'
elif h < 120: return 'cyan'
elif h < 150: return 'blue'
elif h < 170: return 'magenta'
else: return 'red'
@long_callback
def image_callback(msg):
img = bridge.imgmsg_to_cv2(msg, 'bgr8')
# convert to HSV to work with color hue
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# cut out a central square
w = img.shape[1]
h = img.shape[0]
r = 20
center = img_hsv[h // 2 - r:h // 2 + r, w // 2 - r:w // 2 + r]
# compute and print the average hue
mean_hue = center[:, :, 0].mean()
color = get_color_name(mean_hue)
global printed_color
if color != printed_color:
print(color)
printed_color = color
# publish the cropped image
center = cv2.cvtColor(center, cv2.COLOR_HSV2BGR)
center_pub.publish(bridge.cv2_to_imgmsg(center, 'bgr8'))
# process every frame:
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1)
# process 5 frames per second:
# image_sub = rospy.Subscriber('main_camera/image_raw_throttled', Image, image_callback, queue_size=1)
rospy.spin()

View File

@@ -16,11 +16,8 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates) set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger) land = rospy.ServiceProxy('land', Trigger)
def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \ def navigate_wait(x=0, y=0, z=0, yaw=math.nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
frame_id='body', tolerance=0.2, auto_arm=False): res = navigate(x=x, y=y, z=z, yaw=yaw, speed=speed, frame_id=frame_id, auto_arm=auto_arm)
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
frame_id=frame_id, auto_arm=auto_arm)
if not res.success: if not res.success:
return res return res

View File

@@ -0,0 +1,91 @@
# This example makes the drone find and follow the red circle.
# To test in the simulator, place 'Red Circle' model on the floor.
# More information: https://clover.coex.tech/red_circle
# Input topic: main_camera/image_raw (camera image)
# Output topics:
# cv/mask (red color mask)
# cv/red_circle (position of the center of the red circle in 3D space)
import rospy
import cv2
import numpy as np
from math import nan
from sensor_msgs.msg import Image, CameraInfo
from geometry_msgs.msg import PointStamped, Point
from cv_bridge import CvBridge
from clover import long_callback, srv
import tf2_ros
import tf2_geometry_msgs
import image_geometry
rospy.init_node('cv', disable_signals=True) # disable signals to allow interrupting with ctrl+c
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
bridge = CvBridge()
tf_buffer = tf2_ros.Buffer()
tf_listener = tf2_ros.TransformListener(tf_buffer)
mask_pub = rospy.Publisher('~mask', Image, queue_size=1)
point_pub = rospy.Publisher('~red_circle', PointStamped, queue_size=1)
# read camera info
camera_model = image_geometry.PinholeCameraModel()
camera_model.fromCameraInfo(rospy.wait_for_message('main_camera/camera_info', CameraInfo))
def img_xy_to_point(xy, dist):
xy_rect = camera_model.rectifyPoint(xy)
ray = camera_model.projectPixelTo3dRay(xy_rect)
return Point(x=ray[0] * dist, y=ray[1] * dist, z=dist)
def get_center_of_mass(mask):
M = cv2.moments(mask)
if M['m00'] == 0:
return None
return M['m10'] // M['m00'], M['m01'] // M['m00']
follow_red_circle = False
@long_callback
def image_callback(msg):
img = bridge.imgmsg_to_cv2(msg, 'bgr8')
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# we need to use two ranges for red color
mask1 = cv2.inRange(img_hsv, (0, 150, 150), (15, 255, 255))
mask2 = cv2.inRange(img_hsv, (160, 150, 150), (180, 255, 255))
# combine two masks using bitwise OR
mask = cv2.bitwise_or(mask1, mask2)
# publish the mask
if mask_pub.get_num_connections() > 0:
mask_pub.publish(bridge.cv2_to_imgmsg(mask, 'mono8'))
# calculate x and y of the circle
xy = get_center_of_mass(mask)
if xy is None:
return
# calculate and publish the position of the circle in 3D space
altitude = get_telemetry('terrain').z
xy3d = img_xy_to_point(xy, altitude)
target = PointStamped(header=msg.header, point=xy3d)
point_pub.publish(target)
if follow_red_circle:
# follow the target
setpoint = tf_buffer.transform(target, 'map', timeout=rospy.Duration(0.2))
set_position(x=setpoint.point.x, y=setpoint.point.y, z=nan, yaw=nan, frame_id=setpoint.header.frame_id)
# process each camera frame:
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1)
rospy.loginfo('Hit enter to follow the red circle')
input()
follow_red_circle = True
rospy.spin()

View File

@@ -16,10 +16,12 @@
<remap from="image_raw" to="main_camera/image_raw"/> <remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/> <remap from="camera_info" to="main_camera/camera_info"/>
<remap from="map_markers" to="aruco_map/map"/> <remap from="map_markers" to="aruco_map/map"/>
<param name="dictionary" value="2"/> <!-- DICT_4X4_250 -->
<param name="estimate_poses" value="true"/> <param name="estimate_poses" value="true"/>
<param name="send_tf" value="true"/> <param name="send_tf" value="true"/>
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/> <param name="use_map_markers" value="true"/>
<param name="known_tilt" value="map_flipped" if="$(eval placement == 'ceiling')"/> <param name="known_vertical" value="map" if="$(eval placement == 'floor' or placement == 'ceiling')"/>
<param name="flip_vertical" value="true" if="$(eval placement == 'ceiling')"/>
<param name="length" value="$(arg length)"/> <param name="length" value="$(arg length)"/>
<param name="transform_timeout" value="0.1"/> <param name="transform_timeout" value="0.1"/>
<!-- aruco detector parameters --> <!-- aruco detector parameters -->
@@ -35,8 +37,8 @@
<remap from="camera_info" to="main_camera/camera_info"/> <remap from="camera_info" to="main_camera/camera_info"/>
<remap from="markers" to="aruco_detect/markers"/> <remap from="markers" to="aruco_detect/markers"/>
<param name="map" value="$(find aruco_pose)/map/$(arg map)"/> <param name="map" value="$(find aruco_pose)/map/$(arg map)"/>
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/> <param name="known_vertical" value="map" if="$(eval placement == 'floor' or placement == 'ceiling')"/>
<param name="known_tilt" value="map_flipped" if="$(eval placement == 'ceiling')"/> <param name="flip_vertical" value="true" if="$(eval placement == 'ceiling')"/>
<param name="image_axis" value="true"/> <param name="image_axis" value="true"/>
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/> <param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/>
<param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/> <param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/>

View File

@@ -12,7 +12,7 @@
<arg name="led" default="true"/> <arg name="led" default="true"/>
<arg name="blocks" default="false"/> <arg name="blocks" default="false"/>
<arg name="rc" default="false"/> <arg name="rc" default="false"/>
<arg name="force_init" value="true"/> <!-- force estimator to init by publishing zero pose --> <arg name="force_init" default="true"/> <!-- force estimator to init by publishing zero pose -->
<arg name="simulator" default="false"/> <!-- flag that we are operating on a simulated drone --> <arg name="simulator" default="false"/> <!-- flag that we are operating on a simulated drone -->
@@ -45,13 +45,13 @@
<remap from="camera_info" to="main_camera/camera_info"/> <remap from="camera_info" to="main_camera/camera_info"/>
<param name="calc_flow_gyro" value="true"/> <param name="calc_flow_gyro" value="true"/>
<param name="roi_rad" value="0.8"/> <param name="roi_rad" value="0.8"/>
<param name="disable_on_vpe" value="true"/>
</node> </node>
<node pkg="tf2_ros" type="static_transform_publisher" name="map_flipped_frame" args="0 0 0 3.1415926 3.1415926 0 map map_flipped"/>
<!-- simplified offboard control --> <!-- simplified offboard control -->
<node name="simple_offboard" pkg="clover" type="simple_offboard" output="screen" clear_params="true"> <node name="simple_offboard" pkg="clover" type="simple_offboard" output="screen" clear_params="true">
<param name="reference_frames/main_camera_optical" value="map"/> <param name="reference_frames/main_camera_optical" value="map"/>
<param name="terrain_frame_mode" value="range"/>
</node> </node>
<!-- main camera --> <!-- main camera -->
@@ -72,6 +72,9 @@
<param name="pass_statuses" type="yaml" value="[0, 6, 7, 11]"/> <param name="pass_statuses" type="yaml" value="[0, 6, 7, 11]"/>
</node> </node>
<!-- rangefinder's frame -->
<node pkg="tf2_ros" type="static_transform_publisher" name="rangefinder_frame" args="0 0 -0.05 0 1.5707963268 0 base_link rangefinder" if="$(arg rangefinder_vl53l1x)"/>
<!-- led strip --> <!-- led strip -->
<include file="$(find clover)/launch/led.launch" if="$(arg led)"> <include file="$(find clover)/launch/led.launch" if="$(arg led)">
<arg name="simulator" value="$(arg simulator)"/> <arg name="simulator" value="$(arg simulator)"/>
@@ -86,8 +89,4 @@
<param name="use_fake_gcs" value="false"/> <param name="use_fake_gcs" value="false"/>
</node> </node>
<!-- Update static directory -->
<node pkg="roswww_static" name="roswww_static" type="main.py" clear_params="true">
<param name="default_package" value="clover"/>
</node>
</launch> </launch>

View File

@@ -21,7 +21,8 @@
</node> </node>
<!-- high level led effects control, events notification with leds --> <!-- high level led effects control, events notification with leds -->
<node pkg="clover" name="led_effect" type="led" ns="led" clear_params="true" output="screen" if="$(arg led_effect)"> <node pkg="clover" name="led_effect" type="led" clear_params="true" output="screen" if="$(arg led_effect)">
<param name="led" value="led"/>
<param name="blink_rate" value="2"/> <param name="blink_rate" value="2"/>
<param name="fade_period" value="0.5"/> <param name="fade_period" value="0.5"/>
<param name="rainbow_period" value="5"/> <param name="rainbow_period" value="5"/>

View File

@@ -4,6 +4,9 @@
<arg name="direction_z" default="down"/> <!-- direction the camera points: down, up --> <arg name="direction_z" default="down"/> <!-- direction the camera points: down, up -->
<arg name="direction_y" default="backward"/> <!-- direction the camera cable points: backward, forward --> <arg name="direction_y" default="backward"/> <!-- direction the camera cable points: backward, forward -->
<arg name="device" default="/dev/video0"/> <!-- v4l2 device --> <arg name="device" default="/dev/video0"/> <!-- v4l2 device -->
<arg name="throttled_topic" default="true"/> <!-- enable throttled image topic -->
<arg name="throttled_topic_rate" default="5.0"/> <!-- throttled image topic rate -->
<arg name="rectify" default="false"/> <!-- enable rectification -->
<arg name="simulator" default="false"/> <arg name="simulator" default="false"/>
<node if="$(eval direction_z == 'down' and direction_y == 'backward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/> <node if="$(eval direction_z == 'down' and direction_y == 'backward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/>
@@ -43,4 +46,15 @@
<node pkg="clover" type="camera_markers" ns="main_camera" name="main_camera_markers"> <node pkg="clover" type="camera_markers" ns="main_camera" name="main_camera_markers">
<param name="scale" value="3.0"/> <param name="scale" value="3.0"/>
</node> </node>
<!-- image topic throttled -->
<node pkg="topic_tools" name="main_camera_throttle" type="throttle" ns="main_camera"
args="messages image_raw $(arg throttled_topic_rate) image_raw_throttled" if="$(arg throttled_topic)"/>
<!-- rectified image topic -->
<node pkg="nodelet" type="nodelet" name="rectify" args="load image_proc/rectify main_camera_nodelet_manager" if="$(arg rectify)">
<remap from="image_mono" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/>
<remap from="image_rect" to="main_camera/image_rect"/>
</node>
</launch> </launch>

View File

@@ -1,5 +1,5 @@
<launch> <launch>
<arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl --> <arg name="fcu_conn" default="usb"/> <!-- options: usb, uart, tcp, udp, sitl, hitl -->
<arg name="fcu_ip" default="127.0.0.1"/> <arg name="fcu_ip" default="127.0.0.1"/>
<arg name="fcu_sys_id" default="1"/> <arg name="fcu_sys_id" default="1"/>
<arg name="gcs_bridge" default="tcp"/> <arg name="gcs_bridge" default="tcp"/>
@@ -23,6 +23,9 @@
<!-- sitl since PX4 1.9.0 --> <!-- sitl since PX4 1.9.0 -->
<param name="fcu_url" value="udp://@$(arg fcu_ip):14580" if="$(eval fcu_conn == 'sitl')"/> <param name="fcu_url" value="udp://@$(arg fcu_ip):14580" if="$(eval fcu_conn == 'sitl')"/>
<!-- hitl connection (to gazebo_mavlink_interface plugin) -->
<param name="fcu_url" value="udp://$(arg fcu_ip):14540@" if="$(eval fcu_conn == 'hitl')"/>
<!-- set target_system_id --> <!-- set target_system_id -->
<param name="target_system_id" value="$(arg fcu_sys_id)" /> <param name="target_system_id" value="$(arg fcu_sys_id)" />
@@ -74,9 +77,6 @@
covariance: 1 # cm covariance: 1 # cm
</rosparam> </rosparam>
<!-- Rangefinders frame -->
<node pkg="tf2_ros" type="static_transform_publisher" name="rangefinder_frame" args="0 0 -0.05 0 1.5707963268 0 base_link rangefinder"/>
<!-- Copter visualization --> <!-- Copter visualization -->
<node name="visualization" pkg="mavros_extras" type="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"/>

View File

@@ -1,4 +1,4 @@
<launch> <launch>
<!-- shurtcut for running the simulation (`roslaunch clover simulator.launch`) --> <!-- shortcut for running the simulation (`roslaunch clover simulator.launch`) -->
<include file="$(find clover_simulation)/launch/simulator.launch"/> <include file="$(find clover_simulation)/launch/simulator.launch"/>
</launch> </launch>

38
clover/msg/State.msg Normal file
View File

@@ -0,0 +1,38 @@
uint8 MODE_NONE = 0
uint8 MODE_NAVIGATE = 1
uint8 MODE_NAVIGATE_GLOBAL = 2
uint8 MODE_POSITION = 3
uint8 MODE_VELOCITY = 4
uint8 MODE_ATTITUDE = 5
uint8 MODE_RATES = 6
uint8 YAW_MODE_YAW = 0
uint8 YAW_MODE_YAW_RATE = 1
uint8 YAW_MODE_YAW_TOWARDS = 2
# type of offboard control
uint8 mode
uint8 yaw_mode
# targets
float32 x
float32 y
float32 z
float32 speed
float32 lat
float32 lon
float32 vx
float32 vy
float32 vz
float32 roll
float32 pitch
float32 yaw
float32 roll_rate
float32 pitch_rate
float32 yaw_rate
float32 thrust
# frames of reference
string xy_frame_id
string z_frame_id
string yaw_frame_id

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<package format="3"> <package format="3">
<name>clover</name> <name>clover</name>
<version>0.23.0</version> <version>0.24.0</version>
<description>The Clover package</description> <description>The Clover package</description>
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer> <maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
@@ -37,12 +37,15 @@
<depend>rosbridge_server</depend> <depend>rosbridge_server</depend>
<depend>web_video_server</depend> <depend>web_video_server</depend>
<depend>tf2_web_republisher</depend> <depend>tf2_web_republisher</depend>
<depend>libxml2</depend>
<depend>libxslt</depend>
<depend condition="$ROS_PYTHON_VERSION == 2">python-lxml</depend> <depend condition="$ROS_PYTHON_VERSION == 2">python-lxml</depend>
<depend condition="$ROS_PYTHON_VERSION == 3">python3-lxml</depend> <depend condition="$ROS_PYTHON_VERSION == 3">python3-lxml</depend>
<depend>dynamic_reconfigure</depend> <depend>dynamic_reconfigure</depend>
<depend>image_proc</depend>
<depend>image_geometry</depend>
<exec_depend>python-pymavlink</exec_depend> <exec_depend>python-pymavlink</exec_depend>
<!-- Use test_depend for packages you need only for testing: --> <test_depend>ros_pytest</test_depend>
<!-- <test_depend>gtest</test_depend> -->
<!-- The export tag contains other, unspecified, tags --> <!-- The export tag contains other, unspecified, tags -->
<export> <export>

View File

@@ -1,5 +1,4 @@
flask==1.1.1 flask
docopt==0.6.2 geopy
geopy==1.11.0 smbus2
smbus2==0.3.0 VL53L1X
VL53L1X==0.0.5

11
clover/setup.py Normal file
View File

@@ -0,0 +1,11 @@
## ! DO NOT MANUALLY INVOKE THIS setup.py, USE CATKIN INSTEAD
from distutils.core import setup
from catkin_pkg.python_setup import generate_distutils_setup
# fetch values from package.xml
setup_args = generate_distutils_setup(
packages=['clover'],
package_dir={'': 'src'})
setup(**setup_args)

View File

@@ -13,7 +13,12 @@ from util import handle_response
rospy.init_node('autotest_aruco', disable_signals=True) # disable signals to allow interrupting with ctrl+c rospy.init_node('autotest_aruco', disable_signals=True) # disable signals to allow interrupting with ctrl+c
flow_client = dynamic_reconfigure.client.Client('optical_flow') try:
flow_client = dynamic_reconfigure.client.Client('optical_flow', timeout=2)
except rospy.ROSException:
flow_client = None
print('Cannot configure optical flow, skip')
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry) get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate)) navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
land = handle_response(rospy.ServiceProxy('land', Trigger)) land = handle_response(rospy.ServiceProxy('land', Trigger))
@@ -30,11 +35,8 @@ def print_current_map_position():
dist = rospy.wait_for_message('rangefinder/range', Range).range dist = rospy.wait_for_message('rangefinder/range', Range).range
print('Map position:\tx={:.1f}\ty={:.1f}\tz={:.1f}\tyaw={:.1f}\tdist={:.2f}'.format(telem.x, telem.y, telem.z, telem.yaw, dist)) print('Map position:\tx={:.1f}\ty={:.1f}\tz={:.1f}\tyaw={:.1f}\tdist={:.2f}'.format(telem.x, telem.y, telem.z, telem.yaw, dist))
def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \ def navigate_wait(x=0, y=0, z=0, yaw=math.nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
frame_id='body', tolerance=0.2, auto_arm=False): res = navigate(x=x, y=y, z=z, yaw=yaw, speed=speed, frame_id=frame_id, auto_arm=auto_arm)
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
frame_id=frame_id, auto_arm=auto_arm)
if not res.success: if not res.success:
return res return res
@@ -67,12 +69,13 @@ input('Go to center %g %g 1.5 speed 5 [enter] ' % (center_x, center_y))
navigate_wait(x=center_x, y=center_y, z=1.5, speed=5, frame_id='aruco_map') navigate_wait(x=center_x, y=center_y, z=1.5, speed=5, frame_id='aruco_map')
print_current_map_position() print_current_map_position()
input('Disable optical flow and keep hovering [enter] ') if flow_client:
flow_client.update_configuration({'enabled': False}) input('Disable optical flow and keep hovering [enter] ')
rospy.sleep(5) flow_client.update_configuration({'enabled': False})
rospy.sleep(5)
input('Enable optical flow back [enter] ') input('Enable optical flow back [enter] ')
flow_client.update_configuration({'enabled': True}) flow_client.update_configuration({'enabled': True})
input('Go to side 1 %g 2 heading top [enter] ' % (center_y)) input('Go to side 1 %g 2 heading top [enter] ' % (center_y))
navigate_wait(x=1, y=center_y, z=2, yaw=1.57, frame_id='aruco_map') navigate_wait(x=1, y=center_y, z=2, yaw=1.57, frame_id='aruco_map')

View File

@@ -2,7 +2,7 @@
import rospy import rospy
import math import math
from math import nan from math import nan, inf
import signal import signal
import sys import sys
from clover import srv from clover import srv
@@ -15,6 +15,8 @@ rospy.init_node('autotest_flight', disable_signals=True) # disable signals to al
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry) get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate)) navigate = handle_response(rospy.ServiceProxy('navigate', srv.Navigate))
navigate_global = handle_response(rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)) navigate_global = handle_response(rospy.ServiceProxy('navigate_global', srv.NavigateGlobal))
set_yaw = handle_response(rospy.ServiceProxy('set_yaw', srv.SetYaw))
set_yaw_rate = handle_response(rospy.ServiceProxy('set_yaw_rate', srv.SetYawRate))
set_position = handle_response(rospy.ServiceProxy('set_position', srv.SetPosition)) set_position = handle_response(rospy.ServiceProxy('set_position', srv.SetPosition))
set_velocity = handle_response(rospy.ServiceProxy('set_velocity', srv.SetVelocity)) set_velocity = handle_response(rospy.ServiceProxy('set_velocity', srv.SetVelocity))
set_attitude = handle_response(rospy.ServiceProxy('set_attitude', srv.SetAttitude)) set_attitude = handle_response(rospy.ServiceProxy('set_attitude', srv.SetAttitude))
@@ -28,11 +30,8 @@ def interrupt(sig, frame):
signal.signal(signal.SIGINT, interrupt) signal.signal(signal.SIGINT, interrupt)
def navigate_wait(x=0, y=0, z=0, yaw=nan, yaw_rate=0, speed=0.5, \ def navigate_wait(x=0, y=0, z=0, yaw=nan, speed=0.5, frame_id='body', tolerance=0.2, auto_arm=False):
frame_id='body', tolerance=0.2, auto_arm=False): res = navigate(x=x, y=y, z=z, yaw=yaw, speed=speed, frame_id=frame_id, auto_arm=auto_arm)
res = navigate(x=x, y=y, z=z, yaw=yaw, yaw_rate=yaw_rate, speed=speed, \
frame_id=frame_id, auto_arm=auto_arm)
if not res.success: if not res.success:
return res return res
@@ -69,17 +68,17 @@ set_velocity(vx=1, vy=0.0, vz=0, frame_id='body')
rospy.sleep(2) rospy.sleep(2)
set_position(frame_id='body') set_position(frame_id='body')
input('Rotate right 90° [enter] ') input('Rotate right 90° using set_yaw [enter] ')
navigate(yaw=-math.pi / 2, frame_id='navigate_target') set_yaw(yaw=-math.pi / 2, frame_id='navigate_target')
rospy.sleep(3) rospy.sleep(3)
input('Use set_attitude to fly backwards [enter]') input('Use set_attitude to fly backwards [enter]')
set_attitude(pitch=-0.3, roll=0, yaw=0, thrust=0.5, frame_id='body') set_attitude(roll=0, pitch=-0.3, yaw=0, thrust=0.5, frame_id='body')
rospy.sleep(0.3) rospy.sleep(0.3)
set_position(frame_id='body') set_position(frame_id='body')
input('Use set_attitude to fly right [enter]') input('Use set_attitude to fly right [enter]')
set_attitude(pitch=0, roll=0.3, yaw=0, thrust=0.5, frame_id='body') set_attitude(roll=0.3, pitch=0, yaw=0, thrust=0.5, frame_id='body')
rospy.sleep(0.5) rospy.sleep(0.5)
set_position(frame_id='body') set_position(frame_id='body')
@@ -88,13 +87,13 @@ set_rates(roll_rate=1.2, thrust=0.5)
rospy.sleep(0.4) rospy.sleep(0.4)
set_position(frame_id='body') set_position(frame_id='body')
input('Rotate 360° to the right using yaw_rate [enter]') input('Rotate 360° to the right using set_yaw_rate [enter]')
set_position(x=nan, y=nan, z=nan, frame_id='body', yaw=nan, yaw_rate=-1) set_yaw_rate(yaw_rate=-1)
rospy.sleep(2 * math.pi) rospy.sleep(2 * math.pi)
set_position(frame_id='body') set_position(frame_id='body')
input('Return to start point [enter]') input('Return to start point heading forward [enter]')
navigate_wait(x=start.x, y=start.y, z=start.z, yaw=start.yaw, speed=1, frame_id='map') navigate_wait(x=start.x, y=start.y, z=start.z, yaw=inf, speed=1, frame_id='map')
input('Land [enter]') input('Land [enter]')
land() land()

View File

@@ -0,0 +1,35 @@
import rospy
from threading import Thread, Event
def long_callback(fn):
"""
Decorator fixing a rospy issue for long-running topic callbacks, primarily
for image processing.
See: https://github.com/ros/ros_comm/issues/1901.
Usage example:
@long_callback
def image_callback(msg):
# perform image processing
# ...
rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1)
"""
e = Event()
def thread():
while not rospy.is_shutdown():
e.wait()
e.clear()
fn(thread.current_msg)
thread.current_msg = None
Thread(target=thread, daemon=True).start()
def wrapper(msg):
thread.current_msg = msg
e.set()
return wrapper

View File

@@ -309,18 +309,22 @@ int main(int argc, char **argv)
nh_priv.param("notify/low_battery/threshold", low_battery_threshold, 3.7); nh_priv.param("notify/low_battery/threshold", low_battery_threshold, 3.7);
nh_priv.param("notify/error/ignore", error_ignore, {}); nh_priv.param("notify/error/ignore", error_ignore, {});
ros::service::waitForService("set_leds"); // cannot work without set_leds service std::string led; // led namespace
set_leds_srv = nh.serviceClient<led_msgs::SetLEDs>("set_leds", true); nh_priv.param("led", led, std::string("led"));
if (!led.empty()) led += "/";
ros::service::waitForService(led + "set_leds"); // cannot work without set_leds service
set_leds_srv = nh.serviceClient<led_msgs::SetLEDs>(led + "set_leds", true);
// wait for leds count info // wait for leds count info
handleState(*ros::topic::waitForMessage<led_msgs::LEDStateArray>("state", nh)); handleState(*ros::topic::waitForMessage<led_msgs::LEDStateArray>(led + "state", nh));
auto state_sub = nh.subscribe("state", 1, &handleState); auto state_sub = nh.subscribe(led + "state", 1, &handleState);
auto set_effect = nh.advertiseService("set_effect", &setEffect); auto set_effect = nh.advertiseService(led + "set_effect", &setEffect);
auto mavros_state_sub = nh.subscribe("/mavros/state", 1, &handleMavrosState); auto mavros_state_sub = nh.subscribe("mavros/state", 1, &handleMavrosState);
auto battery_sub = nh.subscribe("/mavros/battery", 1, &handleBattery); auto battery_sub = nh.subscribe("mavros/battery", 1, &handleBattery);
auto rosout_sub = nh.subscribe("/rosout_agg", 1, &handleLog); auto rosout_sub = nh.subscribe("/rosout_agg", 1, &handleLog);
timer = nh.createTimer(ros::Duration(0), &proceed, false, false); timer = nh.createTimer(ros::Duration(0), &proceed, false, false);

View File

@@ -25,6 +25,7 @@
#include <dynamic_reconfigure/server.h> #include <dynamic_reconfigure/server.h>
#include <mavros_msgs/OpticalFlowRad.h> #include <mavros_msgs/OpticalFlowRad.h>
#include <sensor_msgs/Imu.h> #include <sensor_msgs/Imu.h>
#include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/Vector3Stamped.h> #include <geometry_msgs/Vector3Stamped.h>
#include <geometry_msgs/PointStamped.h> #include <geometry_msgs/PointStamped.h>
#include <geometry_msgs/TwistStamped.h> #include <geometry_msgs/TwistStamped.h>
@@ -57,6 +58,9 @@ private:
std::unique_ptr<tf2_ros::TransformListener> tf_listener_; std::unique_ptr<tf2_ros::TransformListener> tf_listener_;
bool calc_flow_gyro_; bool calc_flow_gyro_;
float flow_gyro_default_; float flow_gyro_default_;
bool disable_on_vpe_;
ros::Subscriber vpe_sub_;
ros::Time last_vpe_time_;
std::shared_ptr<dynamic_reconfigure::Server<clover::FlowConfig>> dyn_srv_; std::shared_ptr<dynamic_reconfigure::Server<clover::FlowConfig>> dyn_srv_;
void onInit() void onInit()
@@ -87,6 +91,11 @@ private:
img_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this); img_sub_ = it.subscribeCamera("image_raw", 1, &OpticalFlow::flow, this);
disable_on_vpe_ = nh_priv.param("disable_on_vpe", false);
if (disable_on_vpe_) {
vpe_sub_ = nh.subscribe("mavros/vision_pose/pose", 1, &OpticalFlow::vpeCallback, this);
}
dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<clover::FlowConfig>>(nh_priv); dyn_srv_ = std::make_shared<dynamic_reconfigure::Server<clover::FlowConfig>>(nh_priv);
dynamic_reconfigure::Server<clover::FlowConfig>::CallbackType cb; dynamic_reconfigure::Server<clover::FlowConfig>::CallbackType cb;
@@ -121,6 +130,12 @@ private:
{ {
if (!enabled_) return; if (!enabled_) return;
if (disable_on_vpe_ &&
!last_vpe_time_.isZero() &&
(msg->header.stamp - last_vpe_time_).toSec() < 0.1) {
return;
}
parseCameraInfo(cinfo); parseCameraInfo(cinfo);
auto img = cv_bridge::toCvShare(msg, "mono8")->image; auto img = cv_bridge::toCvShare(msg, "mono8")->image;
@@ -154,7 +169,7 @@ private:
img.convertTo(curr_, CV_32F); img.convertTo(curr_, CV_32F);
if (prev_.empty()) { if (prev_.empty() || (msg->header.stamp - prev_stamp_).toSec() > 0.1) { // outdated previous frame
prev_ = curr_.clone(); prev_ = curr_.clone();
prev_stamp_ = msg->header.stamp; prev_stamp_ = msg->header.stamp;
cv::createHanningWindow(hann_, curr_.size(), CV_32F); cv::createHanningWindow(hann_, curr_.size(), CV_32F);
@@ -236,6 +251,14 @@ private:
prev_ = curr_.clone(); prev_ = curr_.clone();
prev_stamp_ = msg->header.stamp; prev_stamp_ = msg->header.stamp;
// Publish estimated angular velocity
geometry_msgs::TwistStamped velo;
velo.header.stamp = msg->header.stamp;
velo.header.frame_id = fcu_frame_id_;
velo.twist.angular.x = flow_fcu.vector.x / integration_time.toSec();
velo.twist.angular.y = flow_fcu.vector.y / integration_time.toSec();
velo_pub_.publish(velo);
publish_debug: publish_debug:
// Publish debug image // Publish debug image
if (img_pub_.getNumSubscribers() > 0) { if (img_pub_.getNumSubscribers() > 0) {
@@ -248,14 +271,6 @@ publish_debug:
out_msg.image = img; out_msg.image = img;
img_pub_.publish(out_msg.toImageMsg()); img_pub_.publish(out_msg.toImageMsg());
} }
// Publish estimated angular velocity
geometry_msgs::TwistStamped velo;
velo.header.stamp = msg->header.stamp;
velo.header.frame_id = fcu_frame_id_;
velo.twist.angular.x = flow_fcu.vector.x / integration_time.toSec();
velo.twist.angular.y = flow_fcu.vector.y / integration_time.toSec();
velo_pub_.publish(velo);
} }
} }
@@ -284,6 +299,10 @@ publish_debug:
prev_ = Mat(); // clear previous frame prev_ = Mat(); // clear previous frame
} }
} }
void vpeCallback(const geometry_msgs::PoseStamped& vpe) {
last_vpe_time_ = vpe.header.stamp;
}
}; };
PLUGINLIB_EXPORT_CLASS(OpticalFlow, nodelet::Nodelet) PLUGINLIB_EXPORT_CLASS(OpticalFlow, nodelet::Nodelet)

View File

@@ -9,13 +9,14 @@
# The above copyright notice and this permission notice shall be included in all # The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software. # copies or substantial portions of the Software.
import os import os, sys
import math import math
import subprocess import subprocess
import re import re
from collections import OrderedDict from collections import OrderedDict
import traceback import traceback
from threading import Event import threading
from threading import Event, Thread, Lock
import numpy import numpy
import rospy import rospy
import tf2_ros import tf2_ros
@@ -27,24 +28,16 @@ from mavros_msgs.msg import State, OpticalFlowRad, Mavlink
from mavros_msgs.srv import ParamGet from mavros_msgs.srv import ParamGet
from geometry_msgs.msg import PoseStamped, TwistStamped, PoseWithCovarianceStamped, Vector3Stamped from geometry_msgs.msg import PoseStamped, TwistStamped, PoseWithCovarianceStamped, Vector3Stamped
from visualization_msgs.msg import MarkerArray as VisualizationMarkerArray from visualization_msgs.msg import MarkerArray as VisualizationMarkerArray
from diagnostic_msgs.msg import DiagnosticArray
import tf.transformations as t import tf.transformations as t
from aruco_pose.msg import MarkerArray from aruco_pose.msg import MarkerArray
from mavros import mavlink from mavros import mavlink
import locale import locale
# TODO: check attitude is present
# TODO: disk free space
# TODO: map, base_link, body
# TODO: rc service
# TODO: perform commander check, ekf2 status on PX4
# TODO: check if FCU params setter succeed
# TODO: selfcheck ROS service (with blacklists for checks)
rospy.init_node('selfcheck') rospy.init_node('selfcheck')
os.environ['ROSCONSOLE_FORMAT']='[${severity}]: ${message}' os.environ['ROSCONSOLE_FORMAT']='${message}'
# use user's locale to convert numbers, etc # use user's locale to convert numbers, etc
locale.setlocale(locale.LC_ALL, '') locale.setlocale(locale.LC_ALL, '')
@@ -53,46 +46,68 @@ tf_buffer = tf2_ros.Buffer()
tf_listener = tf2_ros.TransformListener(tf_buffer) tf_listener = tf2_ros.TransformListener(tf_buffer)
failures = [] thread_local = threading.local()
infos = [] reports_lock = Lock()
current_check = None
# formatting colors
if sys.stdout.isatty():
GREY = '\033[90m'
GREEN = '\033[92m'
RED = '\033[31m'
END = '\033[0m'
else:
GREY = GREEN = RED = END = ''
def failure(text, *args): def failure(text, *args):
msg = text % args msg = text % args
rospy.logwarn('%s: %s', current_check, msg) thread_local.reports += [{'failure': msg}]
failures.append(msg)
def info(text, *args): def info(text, *args):
msg = text % args msg = text % args
rospy.loginfo('%s: %s', current_check, msg) thread_local.reports += [{'info': msg}]
infos.append(msg)
def check(name): def check(name):
def inner(fn): def inner(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
failures[:] = [] start = rospy.get_time()
infos[:] = [] thread_local.reports = []
global current_check
current_check = name
try: try:
fn(*args, **kwargs) fn(*args, **kwargs)
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
rospy.logerr('%s: exception occurred', name) rospy.logerr('%s: exception occurred', name)
return with reports_lock:
if not failures and not infos: for report in thread_local.reports:
rospy.loginfo('%s: OK', name) if 'failure' in report:
rospy.logerr('%s: %s', name, report['failure'])
elif 'info' in report:
rospy.loginfo(GREY + name + END + ': ' + report['info'])
if not thread_local.reports:
rospy.loginfo(GREY + name + END + ': ' + GREEN + 'OK' + END)
if rospy.get_param('~time', False):
rospy.loginfo('%s: %.1f sec', name, rospy.get_time() - start)
return wrapper return wrapper
return inner return inner
def ff(value, precision=2):
# safely format float or int
if value is None:
return RED + '???' + END
if isinstance(value, float):
return ('{:.' + str(precision + 1) + '}').format(value)
elif isinstance(value, int):
return str(value)
param_get = rospy.ServiceProxy('mavros/param/get', ParamGet) param_get = rospy.ServiceProxy('mavros/param/get', ParamGet)
def get_param(name): def get_param(name, default=None, strict=True):
try: try:
res = param_get(param_id=name) res = param_get(param_id=name)
except rospy.ServiceException as e: except rospy.ServiceException as e:
@@ -100,13 +115,19 @@ def get_param(name):
return None return None
if not res.success: if not res.success:
failure('unable to retrieve PX4 parameter %s', name) if strict:
failure('unable to retrieve PX4 parameter %s', name)
return default
else: else:
if res.value.integer != 0: if res.value.integer != 0:
return res.value.integer return res.value.integer
return res.value.real return res.value.real
def get_paramf(name, precision=2):
return ff(get_param(name), precision)
recv_event = Event() recv_event = Event()
link = mavutil.mavlink.MAVLink('', 255, 1) link = mavutil.mavlink.MAVLink('', 255, 1)
mavlink_pub = rospy.Publisher('mavlink/to', Mavlink, queue_size=1) mavlink_pub = rospy.Publisher('mavlink/to', Mavlink, queue_size=1)
@@ -151,6 +172,24 @@ def mavlink_exec(cmd, timeout=3.0):
return mavlink_recv return mavlink_recv
def read_diagnostics(name, key):
e = Event()
def cb(msg):
for status in msg.status:
if status.name.lower() == name.lower():
for value in status.values:
if value.key.lower() == key.lower():
cb.value = value.value
e.set()
return
cb.value = None
sub = rospy.Subscriber('/diagnostics', DiagnosticArray, cb)
e.wait(1.0) # wait to read all the diagnostics from nodes publishing them
sub.unregister()
return cb.value
BOARD_ROTATIONS = { BOARD_ROTATIONS = {
0: 'no rotation', 0: 'no rotation',
1: 'yaw 45°', 1: 'yaw 45°',
@@ -196,34 +235,36 @@ def check_fcu():
state = rospy.wait_for_message('mavros/state', State, timeout=3) state = rospy.wait_for_message('mavros/state', State, timeout=3)
if not state.connected: if not state.connected:
failure('no connection to the FCU (check wiring)') failure('no connection to the FCU (check wiring)')
info('fcu_url = %s', rospy.get_param('mavros/fcu_url', '?'))
return return
clover_tag = re.compile(r'-cl[oe]ver\.\d+$') if not is_process_running('px4', exact=True): # can't use px4 console in SITL
clover_fw = False clover_tag = re.compile(r'-cl[oe]ver\.\d+$')
clover_fw = False
# Make sure the console is available to us # Make sure the console is available to us
mavlink_exec('\n') mavlink_exec('\n')
version_str = mavlink_exec('ver all') version_str = mavlink_exec('ver all')
if version_str == '': if version_str == '':
info('no version data available from SITL') info('no version data available from SITL')
for line in version_str.split('\n'): for line in version_str.split('\n'):
if line.startswith('FW version: '): if line.startswith('FW version: '):
info(line[len('FW version: '):]) info(line[len('FW version: '):])
elif line.startswith('FW git tag: '): # only Clover's firmware elif line.startswith('FW git tag: '): # only Clover's firmware
tag = line[len('FW git tag: '):] tag = line[len('FW git tag: '):]
clover_fw = clover_tag.search(tag) clover_fw = clover_tag.search(tag)
info(tag) info(tag)
elif line.startswith('HW arch: '): elif line.startswith('HW arch: '):
info(line[len('HW arch: '):]) info(line[len('HW arch: '):])
if not clover_fw: if not clover_fw:
info('not Clover PX4 firmware, check https://clover.coex.tech/firmware') info('not Clover PX4 firmware, check https://clover.coex.tech/firmware')
est = get_param('SYS_MC_EST_GROUP') est = get_param('SYS_MC_EST_GROUP')
if est == 1: if est == 1:
info('selected estimator: LPE') info('selected estimator: LPE')
fuse = get_param('LPE_FUSION') fuse = int(get_param('LPE_FUSION'))
if fuse & (1 << 4): if fuse & (1 << 4):
info('LPE_FUSION: land detector fusion is enabled') info('LPE_FUSION: land detector fusion is enabled')
else: else:
@@ -255,21 +296,35 @@ def check_fcu():
if cbrk_usb_chk != 197848: if cbrk_usb_chk != 197848:
failure('set parameter CBRK_USB_CHK to 197848 for flying with USB connected') failure('set parameter CBRK_USB_CHK to 197848 for flying with USB connected')
if not is_process_running('px4', exact=True): # skip battery check in SITL
try:
battery = rospy.wait_for_message('mavros/battery', BatteryState, timeout=3)
if not battery.cell_voltage:
failure('cell voltage is not available, https://clover.coex.tech/power')
else:
cell = battery.cell_voltage[0]
if cell > 4.3 or cell < 3.0:
failure('incorrect cell voltage: %.2f V, https://clover.coex.tech/power', cell)
elif cell < 3.7:
failure('critically low cell voltage: %.2f V, recharge battery', cell)
except rospy.ROSException:
failure('no battery state')
# time sync check
try: try:
battery = rospy.wait_for_message('mavros/battery', BatteryState, timeout=3) info('time sync offset: %.2f s', float(read_diagnostics('mavros: Time Sync', 'Estimated time offset (s)')))
if not battery.cell_voltage: except:
failure('cell voltage is not available, https://clover.coex.tech/power') failure('cannot read time sync offset')
else:
cell = battery.cell_voltage[0]
if cell > 4.3 or cell < 3.0:
failure('incorrect cell voltage: %.2f V, https://clover.coex.tech/power', cell)
elif cell < 3.7:
failure('critically low cell voltage: %.2f V, recharge battery', cell)
except rospy.ROSException:
failure('no battery state')
except rospy.ROSException: except rospy.ROSException:
failure('no MAVROS state (check wiring)') failure('no MAVROS state')
fcu_url = rospy.get_param('mavros/fcu_url', '?')
if fcu_url == '/dev/px4fmu':
if not os.path.exists('/lib/udev/rules.d/99-px4fmu.rules'):
info('udev rules are not installed, install udev rules or change usb_device to /dev/ttyACM0 in mavros.launch')
else:
info('udev did\'t recognize px4fmu device, check wiring or change usb_device to /dev/ttyACM0 in mavros.launch')
info('fcu_url = %s', rospy.get_param('mavros/fcu_url', '?'))
def describe_direction(v): def describe_direction(v):
@@ -346,19 +401,24 @@ def is_process_running(binary, exact=False, full=False):
@check('ArUco markers') @check('ArUco markers')
def check_aruco(): def check_aruco():
markers = None
if is_process_running('aruco_detect', full=True): if is_process_running('aruco_detect', full=True):
try: try:
info('aruco_detect/length = %g m', rospy.get_param('aruco_detect/length')) info('aruco_detect/length = %g m', rospy.get_param('aruco_detect/length', '?'))
except KeyError: except KeyError:
failure('aruco_detect/length parameter is not set') failure('aruco_detect/length parameter is not set')
known_tilt = rospy.get_param('aruco_detect/known_tilt', '') known_vertical = rospy.get_param('aruco_detect/known_vertical', '')
if known_tilt == 'map': flip_vertical = rospy.get_param('aruco_detect/flip_vertical', False)
known_tilt += ' (ALL markers are on the floor)' description = ''
elif known_tilt == 'map_flipped': if known_vertical == 'map' and not flip_vertical:
known_tilt += ' (ALL markers are on the ceiling)' description = ' (all markers are on the floor)'
info('aruco_detector/known_tilt = %s', known_tilt) elif known_vertical == 'map' and flip_vertical:
description = ' (all markers are on the ceiling)'
info('aruco_detect/known_vertical = %s', known_vertical)
info('aruco_detect/flip_vertical = %s%s', flip_vertical, description)
try: try:
rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=1) markers = rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=0.8)
except rospy.ROSException: except rospy.ROSException:
failure('no markers detection') failure('no markers detection')
return return
@@ -367,42 +427,61 @@ def check_aruco():
return return
if is_process_running('aruco_map', full=True): if is_process_running('aruco_map', full=True):
known_tilt = rospy.get_param('aruco_map/known_tilt', '') known_vertical = rospy.get_param('aruco_map/known_vertical', '')
if known_tilt == 'map': flip_vertical = rospy.get_param('aruco_map/flip_vertical', False)
known_tilt += ' (marker\'s map is on the floor)' description = ''
elif known_tilt == 'map_flipped': if known_vertical == 'map' and not flip_vertical:
known_tilt += ' (marker\'s map is on the ceiling)' description += ' (markers map is on the floor)'
info('aruco_map/known_tilt = %s', known_tilt) elif known_vertical == 'map' and flip_vertical:
description += ' (markers map is on the ceiling)'
info('aruco_map/known_vertical = %s', known_vertical)
info('aruco_map/flip_vertical = %s%s', flip_vertical, description)
try: try:
visualization = rospy.wait_for_message('aruco_map/visualization', VisualizationMarkerArray, timeout=1) visualization = rospy.wait_for_message('aruco_map/visualization', VisualizationMarkerArray, timeout=0.8)
info('map has %s markers', len(visualization.markers)) info('map has %s markers', len(visualization.markers))
except: except:
failure('cannot read aruco_map/visualization topic') failure('cannot read aruco_map/visualization topic')
try: try:
rospy.wait_for_message('aruco_map/pose', PoseWithCovarianceStamped, timeout=1) rospy.wait_for_message('aruco_map/pose', PoseWithCovarianceStamped, timeout=0.8)
except rospy.ROSException: except rospy.ROSException:
failure('no map detection') if not markers:
info('no map detection as no markers detection')
elif not markers.markers:
info('no map detection as no markers detected')
else:
failure('no map detection')
else: else:
info('aruco_map is not running') info('aruco_map is not running')
def is_on_the_floor():
try:
dist = rospy.wait_for_message('rangefinder/range', Range, timeout=1)
return dist.range < 0.3
except rospy.ROSException:
return False
@check('Vision position estimate') @check('Vision position estimate')
def check_vpe(): def check_vpe():
vis = None vis = None
try: try:
vis = rospy.wait_for_message('mavros/vision_pose/pose', PoseStamped, timeout=1) vis = rospy.wait_for_message('mavros/vision_pose/pose', PoseStamped, timeout=0.8)
except rospy.ROSException: except rospy.ROSException:
try: try:
vis = rospy.wait_for_message('mavros/mocap/pose', PoseStamped, timeout=1) vis = rospy.wait_for_message('mavros/mocap/pose', PoseStamped, timeout=0.8)
except rospy.ROSException: except rospy.ROSException:
failure('no VPE or MoCap messages') if not is_process_running('vpe_publisher', full=True):
# check if vpe_publisher is running info('no vision position estimate, vpe_publisher is not running')
try: elif rospy.get_param('aruco_map/known_vertical', '') == 'map' \
subprocess.check_output(['pgrep', '-x', 'vpe_publisher']) and rospy.get_param('aruco_map/flip_vertical', False):
except subprocess.CalledProcessError: failure('no vision position estimate, markers are on the ceiling')
return # it's not running, skip following checks elif is_on_the_floor():
info('no vision position estimate, the drone is on the floor')
else:
failure('no vision position estimate')
# check PX4 settings # check PX4 settings
est = get_param('SYS_MC_EST_GROUP') est = get_param('SYS_MC_EST_GROUP')
@@ -414,26 +493,37 @@ def check_vpe():
if vision_yaw_w == 0: if vision_yaw_w == 0:
failure('vision yaw weight is zero, change ATT_W_EXT_HDG parameter') failure('vision yaw weight is zero, change ATT_W_EXT_HDG parameter')
else: else:
info('Vision yaw weight: %.2f', vision_yaw_w) info('vision yaw weight: %s', ff(vision_yaw_w))
fuse = get_param('LPE_FUSION') fuse = int(get_param('LPE_FUSION'))
if not fuse & (1 << 2): if not fuse & (1 << 2):
failure('vision position fusion is disabled, change LPE_FUSION parameter') failure('vision position fusion is disabled, change LPE_FUSION parameter')
delay = get_param('LPE_VIS_DELAY') delay = get_param('LPE_VIS_DELAY')
if delay != 0: if delay != 0:
failure('LPE_VIS_DELAY parameter is %s, but it should be zero', delay) failure('LPE_VIS_DELAY = %s, but it should be zero', delay)
info('LPE_VIS_XY is %.2f m, LPE_VIS_Z is %.2f m', get_param('LPE_VIS_XY'), get_param('LPE_VIS_Z')) info('LPE_VIS_XY = %s m, LPE_VIS_Z = %s m', get_paramf('LPE_VIS_XY'), get_paramf('LPE_VIS_Z'))
elif est == 2: elif est == 2:
fuse = get_param('EKF2_AID_MASK') ev_ctrl = get_param('EKF2_EV_CTRL', strict=False)
if not fuse & (1 << 3): if ev_ctrl is not None: # PX4 after v1.14
failure('vision position fusion is disabled, change EKF2_AID_MASK parameter') ev_ctrl = int(ev_ctrl)
if not fuse & (1 << 4): if not ev_ctrl & (1 << 0):
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter') failure('vision horizontal position fusion is disabled, change EKF2_EV_CTRL parameter')
if not ev_ctrl & (1 << 1):
failure('vision vertical position fusion is disabled, change EKF2_EV_CTRL parameter')
if not ev_ctrl & (1 << 3):
failure('vision yaw fusion is disabled, change EKF2_EV_CTRL parameter')
else: # PX4 before v1.14
fuse = int(get_param('EKF2_AID_MASK'))
if not fuse & (1 << 3):
failure('vision position fusion is disabled, change EKF2_AID_MASK parameter')
if not fuse & (1 << 4):
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
delay = get_param('EKF2_EV_DELAY') delay = get_param('EKF2_EV_DELAY')
if delay != 0: if delay != 0:
failure('EKF2_EV_DELAY is %.2f, but it should be zero', delay) failure('EKF2_EV_DELAY = %.2f, but it should be zero', delay)
info('EKF2_EVA_NOISE is %.3f, EKF2_EVP_NOISE is %.3f', info('EKF2_EVA_NOISE = %s, EKF2_EVP_NOISE = %s',
get_param('EKF2_EVA_NOISE'), get_paramf('EKF2_EVA_NOISE', 3),
get_param('EKF2_EVP_NOISE')) get_paramf('EKF2_EVP_NOISE', 3))
if not vis: if not vis:
return return
@@ -531,15 +621,25 @@ def check_velocity():
@check('Global position (GPS)') @check('Global position (GPS)')
def check_global_position(): def check_global_position():
try: try:
rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=1) rospy.wait_for_message('mavros/global_position/global', NavSatFix, timeout=0.8)
except rospy.ROSException: except rospy.ROSException:
info('no global position') info('no global position')
if get_param('SYS_MC_EST_GROUP') == 2 and (get_param('EKF2_AID_MASK') & (1 << 0)): if get_param('SYS_MC_EST_GROUP') == 2:
failure('enabled GPS fusion may suppress vision position aiding') gps_ctrl = get_param('EKF2_GPS_CTRL', strict=False)
if gps_ctrl is not None: # PX4 after v1.14
if int(gps_ctrl) & (1 << 0):
failure('GPS fusion enabled may suppress vision position aiding, change EKF2_GPS_CTRL')
else: # PX4 before v1.14
if int(get_param('EKF2_AID_MASK', 0)) & (1 << 0):
failure('GPS fusion enabled may suppress vision position aiding, change EKF2_AID_MASK')
@check('Optical flow') @check('Optical flow')
def check_optical_flow(): def check_optical_flow():
if not is_process_running('optical_flow', full=True):
info('optical_flow is not running')
return
# TODO:check FPS! # TODO:check FPS!
try: try:
rospy.wait_for_message('mavros/px4flow/raw/send', OpticalFlowRad, timeout=0.5) rospy.wait_for_message('mavros/px4flow/raw/send', OpticalFlowRad, timeout=0.5)
@@ -547,40 +647,49 @@ def check_optical_flow():
# check PX4 settings # check PX4 settings
rot = get_param('SENS_FLOW_ROT') rot = get_param('SENS_FLOW_ROT')
if rot != 0: if rot != 0:
failure('SENS_FLOW_ROT parameter is %s, but it should be zero', rot) failure('SENS_FLOW_ROT = %s, but it should be zero', rot)
est = get_param('SYS_MC_EST_GROUP') est = get_param('SYS_MC_EST_GROUP')
if est == 1: if est == 1:
fuse = get_param('LPE_FUSION') fuse = int(get_param('LPE_FUSION'))
if not fuse & (1 << 1): if not fuse & (1 << 1):
failure('optical flow fusion is disabled, change LPE_FUSION parameter') failure('optical flow fusion is disabled, change LPE_FUSION parameter')
if not fuse & (1 << 1): if not fuse & (1 << 1):
failure('flow gyro compensation is disabled, change LPE_FUSION parameter') failure('flow gyro compensation is disabled, change LPE_FUSION parameter')
scale = get_param('LPE_FLW_SCALE') scale = get_param('LPE_FLW_SCALE', 1)
if not numpy.isclose(scale, 1.0): if not numpy.isclose(scale, 1.0):
failure('LPE_FLW_SCALE parameter is %.2f, but it should be 1.0', scale) failure('LPE_FLW_SCALE = %.2f, but it should be 1.0', scale)
info('LPE_FLW_QMIN is %s, LPE_FLW_R is %.4f, LPE_FLW_RR is %.4f, SENS_FLOW_MINHGT is %.3f, SENS_FLOW_MAXHGT is %.3f', info('LPE_FLW_QMIN = %s, LPE_FLW_R = %s, LPE_FLW_RR = %s',
get_param('LPE_FLW_QMIN'), get_paramf('LPE_FLW_QMIN'),
get_param('LPE_FLW_R'), get_paramf('LPE_FLW_R', 4),
get_param('LPE_FLW_RR'), get_paramf('LPE_FLW_RR', 4))
get_param('SENS_FLOW_MINHGT'),
get_param('SENS_FLOW_MAXHGT'))
elif est == 2: elif est == 2:
fuse = get_param('EKF2_AID_MASK') of_ctrl = get_param('EKF2_OF_CTRL', strict=False)
if not fuse & (1 << 1): if of_ctrl is not None: # PX4 after v1.14
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter') if of_ctrl == 0:
delay = get_param('EKF2_OF_DELAY') failure('optical flow fusion is disabled, change EKF2_OF_CTRL')
else: # PX4 before v1.14
fuse = int(get_param('EKF2_AID_MASK', 0))
if not fuse & (1 << 1):
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
delay = get_param('EKF2_OF_DELAY', 0)
if delay != 0: if delay != 0:
failure('EKF2_OF_DELAY is %.2f, but it should be zero', delay) failure('EKF2_OF_DELAY = %.2f, but it should be zero', delay)
info('EKF2_OF_QMIN is %s, EKF2_OF_N_MIN is %.4f, EKF2_OF_N_MAX is %.4f, SENS_FLOW_MINHGT is %.3f, SENS_FLOW_MAXHGT is %.3f', info('EKF2_OF_QMIN = %s, EKF2_OF_N_MIN = %s, EKF2_OF_N_MAX = %s',
get_param('EKF2_OF_QMIN'), get_paramf('EKF2_OF_QMIN'),
get_param('EKF2_OF_N_MIN'), get_paramf('EKF2_OF_N_MIN', 4),
get_param('EKF2_OF_N_MAX'), get_paramf('EKF2_OF_N_MAX', 4))
get_param('SENS_FLOW_MINHGT'), info('SENS_FLOW_MINHGT = %s, SENS_FLOW_MAXHGT = %s', get_paramf('SENS_FLOW_MINHGT', 3), get_paramf('SENS_FLOW_MAXHGT', 3))
get_param('SENS_FLOW_MAXHGT'))
except rospy.ROSException: except rospy.ROSException:
failure('no optical flow data (from Raspberry)') if rospy.get_param('optical_flow/disable_on_vpe', False):
try:
rospy.wait_for_message('mavros/vision_pose/pose', PoseStamped, timeout=1)
info('no optical flow as disable_on_vpe is true')
except:
failure('no optical flow on RPi, disable_on_vpe is true, but no vision pose also')
else:
failure('no optical flow on RPi')
@check('Rangefinder') @check('Rangefinder')
@@ -604,27 +713,34 @@ def check_rangefinder():
est = get_param('SYS_MC_EST_GROUP') est = get_param('SYS_MC_EST_GROUP')
if est == 1: if est == 1:
fuse = get_param('LPE_FUSION') fuse = int(get_param('LPE_FUSION', 0))
if not fuse & (1 << 5): if not fuse & (1 << 5):
info('"pub agl as lpos down" in LPE_FUSION is disabled, NOT operating over flat surface') info('"pub agl as lpos down" in LPE_FUSION is disabled, NOT operating over flat surface')
else: else:
info('"pub agl as lpos down" in LPE_FUSION is enabled, operating over flat surface') info('"pub agl as lpos down" in LPE_FUSION is enabled, operating over flat surface')
elif est == 2: elif est == 2:
hgt = get_param('EKF2_HGT_MODE') hgt = get_param('EKF2_HGT_REF', strict=False)
if hgt is None: # PX4 before v1.14
hgt = get_param('EKF2_HGT_MODE')
if hgt != 2: if hgt != 2:
info('EKF2_HGT_MODE != Range sensor, NOT operating over flat surface') info('EKF2_HGT_MODE != Range sensor, NOT operating over flat surface')
else: else:
info('EKF2_HGT_MODE = Range sensor, operating over flat surface') info('EKF2_HGT_MODE = Range sensor, operating over flat surface')
aid = get_param('EKF2_RNG_AID') aid = get_param('EKF2_RNG_AID', strict=False)
if aid != 1: if aid is not None: # PX4 before v1.14
info('EKF2_RNG_AID != 1, range sensor aiding disabled') if aid != 1:
else: info('EKF2_RNG_AID != 1, range sensor aiding disabled')
info('EKF2_RNG_AID = 1, range sensor aiding enabled') else:
info('EKF2_RNG_AID = 1, range sensor aiding enabled')
@check('Boot duration') @check('Boot duration')
def check_boot_duration(): def check_boot_duration():
if not os.path.exists('/etc/clover_version'):
info('skip check')
return # Don't check not on Clover's image
output = subprocess.check_output('systemd-analyze').decode() output = subprocess.check_output('systemd-analyze').decode()
r = re.compile(r'([\d\.]+)s\s*$', flags=re.MULTILINE) r = re.compile(r'([\d\.]+)s\s*$', flags=re.MULTILINE)
duration = float(r.search(output).groups()[0]) duration = float(r.search(output).groups()[0])
@@ -634,7 +750,7 @@ def check_boot_duration():
@check('CPU usage') @check('CPU usage')
def check_cpu_usage(): def check_cpu_usage():
WHITELIST = 'nodelet', 'gzclient', 'gzserver' WHITELIST = 'nodelet', 'gzclient', 'gzserver', 'selfcheck.py'
CMD = "top -n 1 -b -i | tail -n +8 | awk '{ printf(\"%-8s\\t%-8s\\t%-8s\\n\", $1, $9, $12); }'" CMD = "top -n 1 -b -i | tail -n +8 | awk '{ printf(\"%-8s\\t%-8s\\t%-8s\\n\", $1, $9, $12); }'"
output = subprocess.check_output(CMD, shell=True).decode() output = subprocess.check_output(CMD, shell=True).decode()
processes = output.split('\n') processes = output.split('\n')
@@ -703,7 +819,10 @@ def check_image():
try: try:
info('version: %s', open('/etc/clover_version').read().strip()) info('version: %s', open('/etc/clover_version').read().strip())
except IOError: except IOError:
info('no /etc/clover_version file, not the Clover image?') try:
info('VM version: %s', open('/etc/clover_vm_version').read().strip())
except IOError:
info('no /etc/clover_version file, not the Clover image?')
@check('Preflight status') @check('Preflight status')
@@ -814,26 +933,47 @@ def check_board():
info('could not open /proc/device-tree/model, not a Raspberry Pi?') info('could not open /proc/device-tree/model, not a Raspberry Pi?')
def parallel_for(fns):
threads = []
for fn in fns:
thread = Thread(target=fn)
thread.start()
threads.append(thread)
for thread in threads:
thread.join()
def consequentially_for(fns):
for fn in fns:
fn()
def selfcheck(): def selfcheck():
check_image() checks = [
check_board() check_image,
check_clover_service() check_board,
check_network() check_clover_service,
check_fcu() check_network,
check_imu() check_fcu,
check_local_position() check_imu,
check_velocity() check_local_position,
check_global_position() check_velocity,
check_preflight_status() check_global_position,
check_main_camera() check_preflight_status,
check_aruco() check_main_camera,
check_simpleoffboard() check_aruco,
check_optical_flow() check_simpleoffboard,
check_vpe() check_optical_flow,
check_rangefinder() check_vpe,
check_rpi_health() check_rangefinder,
check_cpu_usage() check_rpi_health,
check_boot_duration() check_cpu_usage,
check_boot_duration,
]
if rospy.get_param('~parallel', False):
parallel_for(checks)
else:
consequentially_for(checks)
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -23,12 +23,14 @@
#include <tf2_ros/static_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 <std_srvs/Trigger.h> #include <std_srvs/Trigger.h>
#include <geometry_msgs/PointStamped.h>
#include <geometry_msgs/PoseStamped.h> #include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/TwistStamped.h> #include <geometry_msgs/TwistStamped.h>
#include <geometry_msgs/Vector3Stamped.h> #include <geometry_msgs/Vector3Stamped.h>
#include <geometry_msgs/QuaternionStamped.h> #include <geometry_msgs/QuaternionStamped.h>
#include <sensor_msgs/NavSatFix.h> #include <sensor_msgs/NavSatFix.h>
#include <sensor_msgs/BatteryState.h> #include <sensor_msgs/BatteryState.h>
#include <sensor_msgs/Range.h>
#include <mavros_msgs/CommandBool.h> #include <mavros_msgs/CommandBool.h>
#include <mavros_msgs/SetMode.h> #include <mavros_msgs/SetMode.h>
#include <mavros_msgs/PositionTarget.h> #include <mavros_msgs/PositionTarget.h>
@@ -37,14 +39,19 @@
#include <mavros_msgs/State.h> #include <mavros_msgs/State.h>
#include <mavros_msgs/StatusText.h> #include <mavros_msgs/StatusText.h>
#include <mavros_msgs/ManualControl.h> #include <mavros_msgs/ManualControl.h>
#include <mavros_msgs/Altitude.h>
#include <clover/GetTelemetry.h> #include <clover/GetTelemetry.h>
#include <clover/Navigate.h> #include <clover/Navigate.h>
#include <clover/NavigateGlobal.h> #include <clover/NavigateGlobal.h>
#include <clover/SetAltitude.h>
#include <clover/SetYaw.h>
#include <clover/SetYawRate.h>
#include <clover/SetPosition.h> #include <clover/SetPosition.h>
#include <clover/SetVelocity.h> #include <clover/SetVelocity.h>
#include <clover/SetAttitude.h> #include <clover/SetAttitude.h>
#include <clover/SetRates.h> #include <clover/SetRates.h>
#include <clover/State.h>
using std::string; using std::string;
using std::isnan; using std::isnan;
@@ -54,6 +61,7 @@ using namespace clover;
using mavros_msgs::PositionTarget; using mavros_msgs::PositionTarget;
using mavros_msgs::AttitudeTarget; using mavros_msgs::AttitudeTarget;
using mavros_msgs::Thrust; using mavros_msgs::Thrust;
using mavros_msgs::Altitude;
// tf2 // tf2
tf2_ros::Buffer tf_buffer; tf2_ros::Buffer tf_buffer;
@@ -79,35 +87,43 @@ float default_speed;
bool auto_release; bool auto_release;
bool land_only_in_offboard, nav_from_sp, check_kill_switch; bool land_only_in_offboard, nav_from_sp, check_kill_switch;
std::map<string, string> reference_frames; std::map<string, string> reference_frames;
string terrain_frame_mode;
// Publishers // Publishers
ros::Publisher attitude_pub, attitude_raw_pub, position_pub, position_raw_pub, rates_pub, thrust_pub; ros::Publisher attitude_pub, attitude_raw_pub, position_pub, position_raw_pub, rates_pub, thrust_pub, state_pub;
// Service clients // Service clients
ros::ServiceClient arming, set_mode; ros::ServiceClient arming, set_mode;
// Containers // Containers
ros::Timer setpoint_timer; ros::Timer setpoint_timer;
tf::Quaternion tq;
PoseStamped position_msg; PoseStamped position_msg;
PositionTarget position_raw_msg; PositionTarget position_raw_msg;
AttitudeTarget att_raw_msg; //TwistStamped rates_msg;
Thrust thrust_msg;
TwistStamped rates_msg;
TransformStamped target, setpoint; TransformStamped target, setpoint;
geometry_msgs::TransformStamped body; geometry_msgs::TransformStamped body;
geometry_msgs::TransformStamped terrain;
// State // State
PoseStamped nav_start; PoseStamped nav_start;
PoseStamped setpoint_position, setpoint_position_transformed; PointStamped setpoint_position;
Vector3Stamped setpoint_velocity, setpoint_velocity_transformed; PointStamped setpoint_altitude;
QuaternionStamped setpoint_attitude, setpoint_attitude_transformed; Vector3Stamped setpoint_velocity;
float setpoint_yaw_rate; float setpoint_yaw, setpoint_roll, setpoint_pitch;
Vector3 setpoint_rates;
string yaw_frame_id;
float setpoint_thrust;
float nav_speed; float nav_speed;
float setpoint_lat = NAN, setpoint_lon = NAN;
bool busy = false; bool busy = false;
bool wait_armed = false; bool wait_armed = false;
bool nav_from_sp_flag = false; bool nav_from_sp_flag = false;
// Last published
PoseStamped setpoint_pose_local;
Vector3Stamped setpoint_velocity_local;
float yaw_local;
enum setpoint_type_t { enum setpoint_type_t {
NONE, NONE,
NAVIGATE, NAVIGATE,
@@ -115,7 +131,10 @@ enum setpoint_type_t {
POSITION, POSITION,
VELOCITY, VELOCITY,
ATTITUDE, ATTITUDE,
RATES RATES,
_ALTITUDE,
_YAW,
_YAW_RATE,
}; };
enum setpoint_type_t setpoint_type = NONE; enum setpoint_type_t setpoint_type = NONE;
@@ -170,7 +189,7 @@ void handleLocalPosition(const PoseStamped& pose)
{ {
local_position = pose; local_position = pose;
publishBodyFrame(); publishBodyFrame();
// TODO: terrain?, home? // TODO: home?
} }
// wait for transform without interrupting publishing setpoints // wait for transform without interrupting publishing setpoints
@@ -188,6 +207,29 @@ inline bool waitTransform(const string& target, const string& source,
return false; return false;
} }
void publishTerrain(const double distance, const ros::Time& stamp)
{
if (!waitTransform(local_frame, body.child_frame_id, stamp, ros::Duration(0.1))) return;
auto t = tf_buffer.lookupTransform(local_frame, body.child_frame_id, stamp);
t.child_frame_id = terrain.child_frame_id;
t.transform.translation.z -= distance;
static_transform_broadcaster->sendTransform(t);
}
void handleAltitude(const Altitude& alt)
{
if (!std::isfinite(alt.bottom_clearance)) return;
publishTerrain(alt.bottom_clearance, alt.header.stamp);
}
void handleRange(const Range& range)
{
if (!std::isfinite(range.range)) return;
// TODO: check it's facing down
publishTerrain(range.range, range.header.stamp);
}
#define TIMEOUT(msg, timeout) (msg.header.stamp.isZero() || (ros::Time::now() - msg.header.stamp > timeout)) #define TIMEOUT(msg, timeout) (msg.header.stamp.isZero() || (ros::Time::now() - msg.header.stamp > timeout))
bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res) bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
@@ -207,11 +249,11 @@ bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
res.vx = NAN; res.vx = NAN;
res.vy = NAN; res.vy = NAN;
res.vz = NAN; res.vz = NAN;
res.pitch = NAN;
res.roll = NAN; res.roll = NAN;
res.pitch = NAN;
res.yaw = NAN; res.yaw = NAN;
res.pitch_rate = NAN;
res.roll_rate = NAN; res.roll_rate = NAN;
res.pitch_rate = NAN;
res.yaw_rate = NAN; res.yaw_rate = NAN;
res.voltage = NAN; res.voltage = NAN;
res.cell_voltage = NAN; res.cell_voltage = NAN;
@@ -341,20 +383,20 @@ inline float getDistance(const Point& from, const Point& to)
return hypot(from.x - to.x, from.y - to.y, from.z - to.z); return hypot(from.x - to.x, from.y - to.y, from.z - to.z);
} }
void getNavigateSetpoint(const ros::Time& stamp, float speed, Point& nav_setpoint) void getNavigateSetpoint(const ros::Time& stamp, const float speed, Point& nav_setpoint)
{ {
if (wait_armed) { if (wait_armed) {
// don't start navigating if we're waiting arming // don't start navigating if we're waiting arming
nav_start.header.stamp = stamp; nav_start.header.stamp = stamp;
} }
float distance = getDistance(nav_start.pose.position, setpoint_position_transformed.pose.position); float distance = getDistance(nav_start.pose.position, setpoint_pose_local.pose.position);
float time = distance / speed; float time = distance / speed;
float passed = std::min((stamp - nav_start.header.stamp).toSec() / time, 1.0); float passed = std::min((stamp - nav_start.header.stamp).toSec() / time, 1.0);
nav_setpoint.x = nav_start.pose.position.x + (setpoint_position_transformed.pose.position.x - nav_start.pose.position.x) * passed; nav_setpoint.x = nav_start.pose.position.x + (setpoint_pose_local.pose.position.x - nav_start.pose.position.x) * passed;
nav_setpoint.y = nav_start.pose.position.y + (setpoint_position_transformed.pose.position.y - nav_start.pose.position.y) * passed; nav_setpoint.y = nav_start.pose.position.y + (setpoint_pose_local.pose.position.y - nav_start.pose.position.y) * passed;
nav_setpoint.z = nav_start.pose.position.z + (setpoint_position_transformed.pose.position.z - nav_start.pose.position.z) * passed; nav_setpoint.z = nav_start.pose.position.z + (setpoint_pose_local.pose.position.z - nav_start.pose.position.z) * passed;
} }
PoseStamped globalToLocal(double lat, double lon) PoseStamped globalToLocal(double lat, double lon)
@@ -385,44 +427,101 @@ PoseStamped globalToLocal(double lat, double lon)
return pose; return pose;
} }
// publish navigate_target frame
void publishTarget(ros::Time stamp, bool _static = false)
{
bool single_frame = (setpoint_position.header.frame_id == setpoint_altitude.header.frame_id);
// handle yaw for target frame
if (setpoint_yaw_type == YAW || setpoint_yaw_type == YAW_RATE) { // use last set yaw for yaw_rate
if (setpoint_altitude.header.frame_id == yaw_frame_id) {
target.transform.rotation = tf::createQuaternionMsgFromYaw(setpoint_yaw);
} else {
single_frame = false;
target.transform.rotation = tf::createQuaternionMsgFromYaw(yaw_local);
}
} else if (setpoint_yaw_type == TOWARDS) {
single_frame = false;
target.transform.rotation = tf::createQuaternionMsgFromYaw(yaw_local);
}
if (_static && single_frame) {
// publish at user's command, if all frames are the same
target.header.frame_id = setpoint_position.header.frame_id;
target.header.stamp = stamp;
target.transform.translation.x = setpoint_position.point.x;
target.transform.translation.y = setpoint_position.point.y;
target.transform.translation.z = setpoint_position.point.z;
} else if (!_static) {
// publish at each iteration, if frames are different
target.header = setpoint_pose_local.header;
target.transform.translation.x = setpoint_pose_local.pose.position.x;
target.transform.translation.y = setpoint_pose_local.pose.position.y;
target.transform.translation.z = setpoint_pose_local.pose.position.z;
}
static_transform_broadcaster->sendTransform(target);
}
void publish(const ros::Time stamp) void publish(const ros::Time stamp)
{ {
if (setpoint_type == NONE) return; if (setpoint_type == NONE) return;
position_raw_msg.header.stamp = stamp; position_raw_msg.header.stamp = stamp;
thrust_msg.header.stamp = stamp;
rates_msg.header.stamp = stamp;
try { // transform position
// transform position and/or yaw if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION || setpoint_type == VELOCITY || setpoint_type == ATTITUDE) { setpoint_position.header.stamp = stamp;
setpoint_position.header.stamp = stamp; setpoint_altitude.header.stamp = stamp;
tf_buffer.transform(setpoint_position, setpoint_position_transformed, local_frame, ros::Duration(0.05)); // transform xy
try {
auto xy = tf_buffer.transform(setpoint_position, local_frame, ros::Duration(0.05));
setpoint_pose_local.header = xy.header;
setpoint_pose_local.pose.position.x = xy.point.x;
setpoint_pose_local.pose.position.y = xy.point.y;
} catch (tf2::TransformException& ex) {
// can't transform xy, use last known
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
} }
// transform altitude
// transform velocity try {
if (setpoint_type == VELOCITY) { setpoint_pose_local.pose.position.z = tf_buffer.transform(setpoint_altitude, local_frame, ros::Duration(0.05)).point.z;
setpoint_velocity.header.stamp = stamp; } catch (tf2::TransformException& ex) {
tf_buffer.transform(setpoint_velocity, setpoint_velocity_transformed, local_frame, ros::Duration(0.05)); // can't transform altitude, use last known
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
} }
} catch (const tf2::TransformException& e) {
ROS_WARN_THROTTLE(10, "can't transform");
} }
// transform yaw
if (setpoint_yaw_type == YAW) {
try {
QuaternionStamped q;
q.header.stamp = stamp;
q.header.frame_id = yaw_frame_id;
q.quaternion = tf::createQuaternionMsgFromYaw(setpoint_yaw);
yaw_local = tf2::getYaw(tf_buffer.transform(q, local_frame, ros::Duration(0.05)).quaternion);
} catch (tf2::TransformException& ex) {
// can't transform yaw, use last known
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
}
}
// compute navigate setpoint
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) { if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) {
position_msg.pose.orientation = setpoint_position_transformed.pose.orientation; // copy yaw
getNavigateSetpoint(stamp, nav_speed, position_msg.pose.position); getNavigateSetpoint(stamp, nav_speed, position_msg.pose.position);
if (setpoint_yaw_type == TOWARDS) { if (setpoint_yaw_type == TOWARDS) {
double yaw_towards = atan2(position_msg.pose.position.y - nav_start.pose.position.y, yaw_local = atan2(position_msg.pose.position.y - nav_start.pose.position.y,
position_msg.pose.position.x - nav_start.pose.position.x); position_msg.pose.position.x - nav_start.pose.position.x);
position_msg.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(0, 0, yaw_towards);
} }
position_msg.pose.orientation = tf::createQuaternionMsgFromYaw(yaw_local);
} }
if (setpoint_type == POSITION) { if (setpoint_type == POSITION) {
position_msg = setpoint_position_transformed; position_msg = setpoint_pose_local;
position_msg.pose.orientation = tf::createQuaternionMsgFromYaw(yaw_local);
} }
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) { if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL) {
@@ -439,14 +538,14 @@ void publish(const ros::Time stamp)
PositionTarget::IGNORE_AFY + PositionTarget::IGNORE_AFY +
PositionTarget::IGNORE_AFZ + PositionTarget::IGNORE_AFZ +
PositionTarget::IGNORE_YAW; PositionTarget::IGNORE_YAW;
position_raw_msg.yaw_rate = setpoint_yaw_rate; position_raw_msg.yaw_rate = setpoint_rates.z;
position_raw_msg.position = position_msg.pose.position; position_raw_msg.position = position_msg.pose.position;
position_raw_pub.publish(position_raw_msg); position_raw_pub.publish(position_raw_msg);
} }
// publish setpoint frame // publish setpoint frame
if (!setpoint.child_frame_id.empty()) { if (!setpoint.child_frame_id.empty()) {
if (setpoint.header.stamp == position_msg.header.stamp) { if (setpoint.header.stamp >= position_msg.header.stamp) {
return; // avoid TF_REPEATED_DATA warnings return; // avoid TF_REPEATED_DATA warnings
} }
@@ -458,9 +557,22 @@ void publish(const ros::Time stamp)
setpoint.header.stamp = position_msg.header.stamp; setpoint.header.stamp = position_msg.header.stamp;
transform_broadcaster->sendTransform(setpoint); transform_broadcaster->sendTransform(setpoint);
} }
// publish dynamic target frame
publishTarget(stamp);
} }
if (setpoint_type == VELOCITY) { if (setpoint_type == VELOCITY) {
// transform velocity to local frame
setpoint_velocity.header.stamp = stamp;
try {
setpoint_velocity_local = tf_buffer.transform(setpoint_velocity, local_frame, ros::Duration(0.05));
} catch (tf2::TransformException& ex) {
// can't transform velocity, use last known
ROS_WARN_THROTTLE(10, "can't transform: %s", ex.what());
}
// publish velocity
position_raw_msg.type_mask = PositionTarget::IGNORE_PX + position_raw_msg.type_mask = PositionTarget::IGNORE_PX +
PositionTarget::IGNORE_PY + PositionTarget::IGNORE_PY +
PositionTarget::IGNORE_PZ + PositionTarget::IGNORE_PZ +
@@ -468,14 +580,22 @@ void publish(const ros::Time stamp)
PositionTarget::IGNORE_AFY + PositionTarget::IGNORE_AFY +
PositionTarget::IGNORE_AFZ; PositionTarget::IGNORE_AFZ;
position_raw_msg.type_mask += setpoint_yaw_type == YAW ? PositionTarget::IGNORE_YAW_RATE : PositionTarget::IGNORE_YAW; position_raw_msg.type_mask += setpoint_yaw_type == YAW ? PositionTarget::IGNORE_YAW_RATE : PositionTarget::IGNORE_YAW;
position_raw_msg.velocity = setpoint_velocity_transformed.vector; position_raw_msg.velocity = setpoint_velocity_local.vector;
position_raw_msg.yaw = tf2::getYaw(setpoint_position_transformed.pose.orientation); position_raw_msg.yaw = yaw_local;
position_raw_msg.yaw_rate = setpoint_yaw_rate; position_raw_msg.yaw_rate = setpoint_rates.z;
position_raw_pub.publish(position_raw_msg); position_raw_pub.publish(position_raw_msg);
} }
if (setpoint_type == ATTITUDE) { if (setpoint_type == ATTITUDE) {
attitude_pub.publish(setpoint_position_transformed); PoseStamped msg;
msg.header.stamp = stamp;
msg.header.frame_id = local_frame;
msg.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(setpoint_roll, setpoint_pitch, yaw_local);
attitude_pub.publish(msg);
Thrust thrust_msg;
thrust_msg.header.stamp = stamp;
thrust_msg.thrust = setpoint_thrust;
thrust_pub.publish(thrust_msg); thrust_pub.publish(thrust_msg);
} }
@@ -484,11 +604,12 @@ void publish(const ros::Time stamp)
// thrust_pub.publish(thrust_msg); // thrust_pub.publish(thrust_msg);
// mavros rates topics waits for rates in local frame // mavros rates topics waits for rates in local frame
// use rates in body frame for simplicity // use rates in body frame for simplicity
AttitudeTarget att_raw_msg;
att_raw_msg.header.stamp = stamp; att_raw_msg.header.stamp = stamp;
att_raw_msg.header.frame_id = fcu_frame; att_raw_msg.header.frame_id = fcu_frame;
att_raw_msg.type_mask = AttitudeTarget::IGNORE_ATTITUDE; att_raw_msg.type_mask = AttitudeTarget::IGNORE_ATTITUDE;
att_raw_msg.body_rate = rates_msg.twist.angular; att_raw_msg.body_rate = setpoint_rates;
att_raw_msg.thrust = thrust_msg.thrust; att_raw_msg.thrust = setpoint_thrust;
attitude_raw_pub.publish(att_raw_msg); attitude_raw_pub.publish(att_raw_msg);
} }
} }
@@ -528,10 +649,59 @@ inline void checkState()
throw std::runtime_error("No connection to FCU, https://clover.coex.tech/connection"); throw std::runtime_error("No connection to FCU, https://clover.coex.tech/connection");
} }
void publishState()
{
clover::State msg;
msg.mode = setpoint_type;
msg.yaw_mode = setpoint_yaw_type;
if (setpoint_position.header.frame_id.empty()) {
msg.x = NAN;
msg.y = NAN;
msg.z = NAN;
} else {
msg.x = setpoint_position.point.x;
msg.y = setpoint_position.point.y;
msg.z = setpoint_altitude.point.z;
}
msg.speed = nav_speed;
msg.lat = setpoint_lat;
msg.lon = setpoint_lon;
msg.vx = setpoint_velocity.vector.x;
msg.vy = setpoint_velocity.vector.y;
msg.vz = setpoint_velocity.vector.z;
msg.roll = setpoint_roll;
msg.pitch = setpoint_pitch;
msg.yaw = !yaw_frame_id.empty() ? setpoint_yaw : NAN;
msg.roll_rate = setpoint_rates.x;
msg.pitch_rate = setpoint_rates.y;
msg.yaw_rate = setpoint_rates.z;
msg.thrust = setpoint_thrust;
if (setpoint_type == VELOCITY) {
msg.xy_frame_id = setpoint_velocity.header.frame_id;
msg.z_frame_id = setpoint_velocity.header.frame_id;
} else {
msg.xy_frame_id = setpoint_position.header.frame_id;
msg.z_frame_id = setpoint_altitude.header.frame_id;
}
msg.yaw_frame_id = yaw_frame_id;
state_pub.publish(msg);
}
inline float safe(float value) {
return std::isfinite(value) ? value : 0;
}
#define ENSURE_FINITE(var) { if (!std::isfinite(var)) throw std::runtime_error(#var " argument cannot be NaN or Inf"); } #define ENSURE_FINITE(var) { if (!std::isfinite(var)) throw std::runtime_error(#var " argument cannot be NaN or Inf"); }
#define ENSURE_NON_INF(var) { if (std::isinf(var)) throw std::runtime_error(#var " argument cannot be Inf"); }
bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, float vy, float vz, bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, float vy, float vz,
float pitch, float roll, float yaw, float pitch_rate, float roll_rate, float yaw_rate, // editorconfig-checker-disable-line float roll, float pitch, float yaw, float roll_rate, float pitch_rate, float yaw_rate, // editorconfig-checker-disable-line
float lat, float lon, float thrust, float speed, string frame_id, bool auto_arm, // editorconfig-checker-disable-line float lat, float lon, float thrust, float speed, string frame_id, bool auto_arm, // editorconfig-checker-disable-line
uint8_t& success, string& message) // editorconfig-checker-disable-line uint8_t& success, string& message) // editorconfig-checker-disable-line
{ {
@@ -558,69 +728,40 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
auto search = reference_frames.find(frame_id); auto search = reference_frames.find(frame_id);
const string& reference_frame = search == reference_frames.end() ? frame_id : search->second; const string& reference_frame = search == reference_frames.end() ? frame_id : search->second;
// Serve "partial" commands ENSURE_NON_INF(x);
ENSURE_NON_INF(y);
ENSURE_NON_INF(z);
ENSURE_NON_INF(speed); // TODO: allow inf
ENSURE_NON_INF(vx);
ENSURE_NON_INF(vy);
ENSURE_NON_INF(vz);
ENSURE_NON_INF(roll);
ENSURE_NON_INF(pitch);
ENSURE_NON_INF(roll_rate);
ENSURE_NON_INF(pitch_rate);
ENSURE_NON_INF(yaw_rate);
ENSURE_NON_INF(thrust);
if (!auto_arm && std::isfinite(yaw) && if (sp_type == NAVIGATE_GLOBAL) {
isnan(x) && isnan(y) && isnan(z) && isnan(vx) && isnan(vy) && isnan(vz) &&
isnan(pitch) && isnan(roll) && isnan(thrust) &&
isnan(lat) && isnan(lon)) {
// change only the yaw
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == VELOCITY) {
if (!waitTransform(setpoint_position.header.frame_id, frame_id, stamp, transform_timeout))
throw std::runtime_error("Can't transform from " + frame_id + " to " + setpoint_position.header.frame_id);
message = "Changing yaw only";
QuaternionStamped q;
q.header.frame_id = frame_id;
q.header.stamp = stamp;
q.quaternion = tf::createQuaternionMsgFromYaw(yaw); // TODO: pitch=0, roll=0 is not totally correct
setpoint_position.pose.orientation = tf_buffer.transform(q, setpoint_position.header.frame_id).quaternion;
setpoint_yaw_type = YAW;
goto publish_setpoint;
} else {
throw std::runtime_error("Setting yaw is possible only when position or velocity setpoints active");
}
}
if (!auto_arm && std::isfinite(yaw_rate) &&
isnan(x) && isnan(y) && isnan(z) && isnan(vx) && isnan(vy) && isnan(vz) &&
isnan(pitch) && isnan(roll) && isnan(yaw) && isnan(thrust) &&
isnan(lat) && isnan(lon)) {
// change only the yaw rate
if (setpoint_type == POSITION || setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == VELOCITY) {
message = "Changing yaw rate only";
setpoint_yaw_type = YAW_RATE;
setpoint_yaw_rate = yaw_rate;
goto publish_setpoint;
} else {
throw std::runtime_error("Setting yaw rate is possible only when position or velocity setpoints active");
}
}
// Serve normal commands
if (sp_type == NAVIGATE || sp_type == POSITION) {
ENSURE_FINITE(x);
ENSURE_FINITE(y);
ENSURE_FINITE(z);
} else if (sp_type == NAVIGATE_GLOBAL) {
ENSURE_FINITE(lat); ENSURE_FINITE(lat);
ENSURE_FINITE(lon); ENSURE_FINITE(lon);
ENSURE_FINITE(z); }
} else if (sp_type == VELOCITY) {
ENSURE_FINITE(vx); if (isfinite(x) != isfinite(y)) {
ENSURE_FINITE(vy); throw std::runtime_error("x and y can be set only together");
ENSURE_FINITE(vz); }
} else if (sp_type == ATTITUDE) {
ENSURE_FINITE(pitch); if (isfinite(yaw_rate)) {
ENSURE_FINITE(roll); if (sp_type > RATES && setpoint_type == ATTITUDE) {
ENSURE_FINITE(thrust); throw std::runtime_error("Yaw rate cannot be set in attitude mode.");
} else if (sp_type == RATES) { }
ENSURE_FINITE(pitch_rate); }
ENSURE_FINITE(roll_rate);
ENSURE_FINITE(thrust); // set_altitude
if (sp_type == _ALTITUDE) {
if (setpoint_type == VELOCITY || setpoint_type == ATTITUDE || setpoint_type == RATES) {
throw std::runtime_error("Altitude cannot be set in velocity, attitude or rates mode.");
}
} }
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) { if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
@@ -634,20 +775,13 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
speed = default_speed; speed = default_speed;
} }
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) {
if (yaw_rate != 0 && !std::isnan(yaw))
throw std::runtime_error("Yaw value should be NaN for setting yaw rate");
if (std::isnan(yaw_rate) && std::isnan(yaw))
throw std::runtime_error("Both yaw and yaw_rate cannot be NaN");
}
if (sp_type == NAVIGATE_GLOBAL) { if (sp_type == NAVIGATE_GLOBAL) {
if (TIMEOUT(global_position, global_position_timeout)) if (TIMEOUT(global_position, global_position_timeout))
throw std::runtime_error("No global position"); throw std::runtime_error("No global position");
} }
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY || sp_type == ATTITUDE) { // if any value need to be transformed to reference frame
if (isfinite(x) || isfinite(y) || isfinite(z) || isfinite(vx) || isfinite(vy) || isfinite(vz) || isfinite(yaw)) {
// make sure transform from frame_id to reference frame available // make sure transform from frame_id to reference frame available
if (!waitTransform(reference_frame, frame_id, stamp, transform_timeout)) if (!waitTransform(reference_frame, frame_id, stamp, transform_timeout))
throw std::runtime_error("Can't transform from " + frame_id + " to " + reference_frame); throw std::runtime_error("Can't transform from " + frame_id + " to " + reference_frame);
@@ -664,15 +798,27 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
auto xy_in_req_frame = tf_buffer.transform(pose_local, frame_id); 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;
setpoint_lat = lat;
setpoint_lon = lon;
} }
// Everything fine - switch setpoint type // Everything fine - switch setpoint type
setpoint_type = sp_type; if (sp_type <= RATES) {
setpoint_type = sp_type;
}
if (sp_type != NAVIGATE && sp_type != NAVIGATE_GLOBAL) { if (setpoint_type != NAVIGATE && setpoint_type != NAVIGATE_GLOBAL) {
nav_from_sp_flag = false; nav_from_sp_flag = false;
} }
bool to_auto_arm = auto_arm && (state.mode != "OFFBOARD" || !state.armed);
if (to_auto_arm || setpoint_type == VELOCITY || setpoint_type == ATTITUDE || setpoint_type == RATES) {
// invalidate position setpoint
setpoint_position.header.frame_id = "";
setpoint_altitude.header.frame_id = "";
yaw_frame_id = "";
}
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) { if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL) {
// starting point // starting point
if (nav_from_sp && nav_from_sp_flag) { if (nav_from_sp && nav_from_sp_flag) {
@@ -681,89 +827,139 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
} else { } else {
nav_start = local_position; nav_start = local_position;
} }
nav_speed = speed;
if (!isnan(speed)) {
nav_speed = speed;
}
nav_from_sp_flag = true; nav_from_sp_flag = true;
} }
// if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) { // handle position
// if (std::isnan(yaw) && yaw_rate == 0) { if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
// // keep yaw unchanged
// // TODO: this is incorrect, because we need yaw in desired frame
// yaw = tf2::getYaw(local_position.pose.orientation);
// }
// }
if (sp_type == POSITION || sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == VELOCITY || sp_type == ATTITUDE) { PointStamped desired;
// destination point and/or attitude desired.header.frame_id = frame_id;
PoseStamped ps; desired.header.stamp = stamp;
ps.header.frame_id = frame_id; desired.point.x = safe(x);
ps.header.stamp = stamp; desired.point.y = safe(y);
ps.pose.position.x = x; desired.point.z = safe(z);
ps.pose.position.y = y;
ps.pose.position.z = z;
ps.pose.orientation.w = 1.0; // Ensure quaternion is always valid
if (sp_type == ATTITUDE) { // transform to reference frame
ps.pose.position.x = 0; desired = tf_buffer.transform(desired, reference_frame);
ps.pose.position.y = 0;
ps.pose.position.z = 0; // set horizontal position
ps.pose.orientation = tf::createQuaternionMsgFromRollPitchYaw(roll, pitch, yaw); if (isfinite(x) && isfinite(y)) {
} else if (std::isnan(yaw)) { setpoint_position = desired;
setpoint_yaw_type = YAW_RATE; } else if (setpoint_position.header.frame_id.empty()) {
setpoint_yaw_rate = yaw_rate; // TODO: use transform for current stamp
} else if (std::isinf(yaw) && yaw > 0) { setpoint_position.header = local_position.header;
// yaw towards setpoint_position.point = local_position.pose.position;
setpoint_yaw_type = TOWARDS;
yaw = 0;
setpoint_yaw_rate = 0;
} else {
setpoint_yaw_type = YAW;
setpoint_yaw_rate = 0;
ps.pose.orientation = tf::createQuaternionMsgFromYaw(yaw);
} }
tf_buffer.transform(ps, setpoint_position, reference_frame); // set altitude
if (isfinite(z)) {
setpoint_altitude = desired;
} else if (setpoint_altitude.header.frame_id.empty()) {
setpoint_altitude.header = local_position.header;
setpoint_altitude.point = local_position.pose.position;
}
} }
// handle velocity
if (sp_type == VELOCITY) { if (sp_type == VELOCITY) {
Vector3Stamped vel; // TODO: allow setting different modes by altitude and xy
vel.header.frame_id = frame_id; Vector3Stamped desired;
vel.header.stamp = stamp; desired.header.frame_id = frame_id;
vel.vector.x = vx; desired.header.stamp = stamp;
vel.vector.y = vy; desired.vector.x = safe(vx);
vel.vector.z = vz; desired.vector.y = safe(vy);
tf_buffer.transform(vel, setpoint_velocity, reference_frame); desired.vector.z = safe(vz);
// transform to reference frame
desired = tf_buffer.transform(desired, reference_frame);
setpoint_velocity.header = desired.header;
// set horizontal velocity
if (isfinite(vx) && isfinite(vy)) {
setpoint_velocity.vector.x = desired.vector.x;
setpoint_velocity.vector.y = desired.vector.y;
}
// set vertical velocity
if (isfinite(vz)) {
setpoint_velocity.vector.z = desired.vector.z;
}
} }
if (sp_type == ATTITUDE || sp_type == RATES) { // handle yaw
thrust_msg.thrust = thrust; if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY || sp_type == ATTITUDE || sp_type == _YAW) {
if (isfinite(yaw)) {
setpoint_yaw_type = YAW;
QuaternionStamped desired;
desired.header.frame_id = frame_id;
desired.header.stamp = stamp;
desired.quaternion = tf::createQuaternionMsgFromYaw(yaw);
// transform to reference frame
desired = tf_buffer.transform(desired, reference_frame);
setpoint_yaw = tf2::getYaw(desired.quaternion);
yaw_frame_id = reference_frame;
} else if (isinf(yaw) && yaw > 0) {
// yaw towards
setpoint_yaw_type = TOWARDS;
} else if (yaw_frame_id.empty() || sp_type == _YAW) {
// yaw is nan and not set previously OR set_yaw(yaw=nan) was called
setpoint_yaw_type = YAW;
setpoint_yaw = tf2::getYaw(local_position.pose.orientation); // set yaw to current yaw
yaw_frame_id = local_position.header.frame_id;
}
} }
if (sp_type == RATES) { // handle roll
rates_msg.twist.angular.x = roll_rate; if (isfinite(roll)) {
rates_msg.twist.angular.y = pitch_rate; setpoint_roll = roll;
rates_msg.twist.angular.z = yaw_rate; }
// handle pitch
if (isfinite(pitch)) {
setpoint_pitch = pitch;
}
// handle yaw rate
if (isfinite(yaw_rate)) {
setpoint_yaw_type = YAW_RATE;
setpoint_rates.z = yaw_rate;
}
// handle pitch rate
if (isfinite(roll_rate)) {
setpoint_rates.x = roll_rate;
}
// handle roll rate
if (isfinite(pitch_rate)) {
setpoint_rates.y = pitch_rate;
}
// handle thrust
if (isfinite(thrust)) {
setpoint_thrust = thrust;
} }
wait_armed = auto_arm; wait_armed = auto_arm;
publish_setpoint:
publish(stamp); // calculate initial transformed messages first publish(stamp); // calculate initial transformed messages first
setpoint_timer.start(); setpoint_timer.start();
// publish target frame if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
if (!target.child_frame_id.empty()) { publishTarget(stamp, true);
if (setpoint_type == NAVIGATE || setpoint_type == NAVIGATE_GLOBAL || setpoint_type == POSITION) {
target.header.frame_id = setpoint_position.header.frame_id;
target.header.stamp = stamp;
target.transform.translation.x = setpoint_position.pose.position.x;
target.transform.translation.y = setpoint_position.pose.position.y;
target.transform.translation.z = setpoint_position.pose.position.z;
target.transform.rotation = setpoint_position.pose.orientation;
static_transform_broadcaster->sendTransform(target);
}
} }
publishState();
if (auto_arm) { if (auto_arm) {
offboardAndArm(); offboardAndArm();
wait_armed = false; wait_armed = false;
@@ -788,27 +984,39 @@ publish_setpoint:
} }
bool navigate(Navigate::Request& req, Navigate::Response& res) { bool navigate(Navigate::Request& req, Navigate::Response& res) {
return serve(NAVIGATE, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message); return serve(NAVIGATE, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
} }
bool navigateGlobal(NavigateGlobal::Request& req, NavigateGlobal::Response& res) { bool navigateGlobal(NavigateGlobal::Request& req, NavigateGlobal::Response& res) {
return serve(NAVIGATE_GLOBAL, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, req.lat, req.lon, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message); return serve(NAVIGATE_GLOBAL, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, req.lat, req.lon, NAN, req.speed, req.frame_id, req.auto_arm, res.success, res.message);
}
bool setAltitude(SetAltitude::Request& req, SetAltitude::Response& res) {
return serve(_ALTITUDE, NAN, NAN, req.z, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, false, res.success, res.message);
}
bool setYaw(SetYaw::Request& req, SetYaw::Response& res) {
return serve(_YAW, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, false, res.success, res.message);
}
bool setYawRate(SetYawRate::Request& req, SetYawRate::Response& res) {
return serve(_YAW_RATE, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, "", false, res.success, res.message);
} }
bool setPosition(SetPosition::Request& req, SetPosition::Response& res) { bool setPosition(SetPosition::Request& req, SetPosition::Response& res) {
return serve(POSITION, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message); return serve(POSITION, req.x, req.y, req.z, NAN, NAN, NAN, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
} }
bool setVelocity(SetVelocity::Request& req, SetVelocity::Response& res) { bool setVelocity(SetVelocity::Request& req, SetVelocity::Response& res) {
return serve(VELOCITY, NAN, NAN, NAN, req.vx, req.vy, req.vz, NAN, NAN, req.yaw, NAN, NAN, req.yaw_rate, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message); return serve(VELOCITY, NAN, NAN, NAN, req.vx, req.vy, req.vz, NAN, NAN, req.yaw, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.frame_id, req.auto_arm, res.success, res.message);
} }
bool setAttitude(SetAttitude::Request& req, SetAttitude::Response& res) { bool setAttitude(SetAttitude::Request& req, SetAttitude::Response& res) {
return serve(ATTITUDE, NAN, NAN, NAN, NAN, NAN, NAN, req.pitch, req.roll, req.yaw, NAN, NAN, NAN, NAN, NAN, req.thrust, NAN, req.frame_id, req.auto_arm, res.success, res.message); return serve(ATTITUDE, NAN, NAN, NAN, NAN, NAN, NAN, req.roll, req.pitch, req.yaw, NAN, NAN, NAN, NAN, NAN, req.thrust, NAN, req.frame_id, req.auto_arm, res.success, res.message);
} }
bool setRates(SetRates::Request& req, SetRates::Response& res) { bool setRates(SetRates::Request& req, SetRates::Response& res) {
return serve(RATES, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.pitch_rate, req.roll_rate, req.yaw_rate, NAN, NAN, req.thrust, NAN, "", req.auto_arm, res.success, res.message); return serve(RATES, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, NAN, req.roll_rate, req.pitch_rate, req.yaw_rate, NAN, NAN, req.thrust, NAN, "", req.auto_arm, res.success, res.message);
} }
bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res) bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
@@ -840,9 +1048,7 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
auto start = ros::Time::now(); auto start = ros::Time::now();
while (ros::ok()) { while (ros::ok()) {
if (state.mode == "AUTO.LAND") { if (state.mode == "AUTO.LAND") {
res.success = true; break;
busy = false;
return true;
} }
if (ros::Time::now() - start > land_timeout) if (ros::Time::now() - start > land_timeout)
throw std::runtime_error("Land request timed out"); throw std::runtime_error("Land request timed out");
@@ -851,6 +1057,18 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
r.sleep(); r.sleep();
} }
// stop setpoints and invalidate position setpoint
setpoint_timer.stop();
setpoint_type = NONE;
setpoint_position.header.frame_id = "";
setpoint_altitude.header.frame_id = "";
yaw_frame_id = "";
publishState();
res.success = true;
busy = false;
return true;
} catch (const std::exception& e) { } catch (const std::exception& e) {
res.message = e.what(); res.message = e.what();
ROS_INFO("%s", e.what()); ROS_INFO("%s", e.what());
@@ -860,6 +1078,18 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
return false; return false;
} }
bool release(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
{
setpoint_timer.stop();
setpoint_type = NONE;
setpoint_position.header.frame_id = "";
setpoint_altitude.header.frame_id = "";
yaw_frame_id = "";
publishState();
res.success = true;
return true;
}
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
ros::init(argc, argv, "simple_offboard"); ros::init(argc, argv, "simple_offboard");
@@ -881,6 +1111,8 @@ int main(int argc, char **argv)
nh_priv.param("check_kill_switch", check_kill_switch, true); nh_priv.param("check_kill_switch", check_kill_switch, true);
nh_priv.param("default_speed", default_speed, 0.5f); nh_priv.param("default_speed", default_speed, 0.5f);
nh_priv.param<string>("body_frame", body.child_frame_id, "body"); nh_priv.param<string>("body_frame", body.child_frame_id, "body");
nh_priv.param<string>("terrain_frame", terrain.child_frame_id, "terrain");
nh_priv.param<string>("terrain_frame_mode", terrain_frame_mode, "altitude");
nh_priv.getParam("reference_frames", reference_frames); nh_priv.getParam("reference_frames", reference_frames);
// Default reference frames // Default reference frames
@@ -916,6 +1148,20 @@ int main(int argc, char **argv)
auto manual_control_sub = nh.subscribe(mavros + "/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>); auto manual_control_sub = nh.subscribe(mavros + "/manual_control/control", 1, &handleMessage<mavros_msgs::ManualControl, manual_control>);
auto local_position_sub = nh.subscribe(mavros + "/local_position/pose", 1, &handleLocalPosition); auto local_position_sub = nh.subscribe(mavros + "/local_position/pose", 1, &handleLocalPosition);
ros::Subscriber altitude_sub;
if (!body.child_frame_id.empty() && !terrain.child_frame_id.empty()) {
terrain.header.frame_id = local_frame;
if (terrain_frame_mode == "altitude") {
altitude_sub = nh.subscribe(mavros + "/altitude", 1, &handleAltitude);
} else if (terrain_frame_mode == "range") {
string range_topic = nh_priv.param("range_topic", string("rangefinder/range"));
altitude_sub = nh.subscribe(range_topic, 1, &handleRange);
} else {
ROS_FATAL("Unknown terrain_frame_mode: %s, valid values: altitude, range", terrain_frame_mode.c_str());
ros::shutdown();
}
}
// Setpoint publishers // Setpoint publishers
position_pub = nh.advertise<PoseStamped>(mavros + "/setpoint_position/local", 1); position_pub = nh.advertise<PoseStamped>(mavros + "/setpoint_position/local", 1);
position_raw_pub = nh.advertise<PositionTarget>(mavros + "/setpoint_raw/local", 1); position_raw_pub = nh.advertise<PositionTarget>(mavros + "/setpoint_raw/local", 1);
@@ -924,15 +1170,22 @@ int main(int argc, char **argv)
rates_pub = nh.advertise<TwistStamped>(mavros + "/setpoint_attitude/cmd_vel", 1); rates_pub = nh.advertise<TwistStamped>(mavros + "/setpoint_attitude/cmd_vel", 1);
thrust_pub = nh.advertise<Thrust>(mavros + "/setpoint_attitude/thrust", 1); thrust_pub = nh.advertise<Thrust>(mavros + "/setpoint_attitude/thrust", 1);
// State publisher
state_pub = nh_priv.advertise<clover::State>("state", 1, true);
// Service servers // Service servers
auto gt_serv = nh.advertiseService("get_telemetry", &getTelemetry); auto gt_serv = nh.advertiseService("get_telemetry", &getTelemetry);
auto na_serv = nh.advertiseService("navigate", &navigate); auto na_serv = nh.advertiseService("navigate", &navigate);
auto ng_serv = nh.advertiseService("navigate_global", &navigateGlobal); auto ng_serv = nh.advertiseService("navigate_global", &navigateGlobal);
auto sl_serv = nh.advertiseService("set_altitude", &setAltitude);
auto ya_serv = nh.advertiseService("set_yaw", &setYaw);
auto yr_serv = nh.advertiseService("set_yaw_rate", &setYawRate);
auto sp_serv = nh.advertiseService("set_position", &setPosition); auto sp_serv = nh.advertiseService("set_position", &setPosition);
auto sv_serv = nh.advertiseService("set_velocity", &setVelocity); auto sv_serv = nh.advertiseService("set_velocity", &setVelocity);
auto sa_serv = nh.advertiseService("set_attitude", &setAttitude); auto sa_serv = nh.advertiseService("set_attitude", &setAttitude);
auto sr_serv = nh.advertiseService("set_rates", &setRates); auto sr_serv = nh.advertiseService("set_rates", &setRates);
auto ld_serv = nh.advertiseService("land", &land); auto ld_serv = nh.advertiseService("land", &land);
auto rl_serv = nh_priv.advertiseService("release", &release);
// Setpoint timer // Setpoint timer
setpoint_timer = nh.createTimer(ros::Duration(1 / nh_priv.param("setpoint_rate", 30.0)), &publishSetpoint, false, false); setpoint_timer = nh.createTimer(ros::Duration(1 / nh_priv.param("setpoint_rate", 30.0)), &publishSetpoint, false, false);
@@ -940,7 +1193,7 @@ int main(int argc, char **argv)
position_msg.header.frame_id = local_frame; position_msg.header.frame_id = local_frame;
position_raw_msg.header.frame_id = local_frame; position_raw_msg.header.frame_id = local_frame;
position_raw_msg.coordinate_frame = PositionTarget::FRAME_LOCAL_NED; position_raw_msg.coordinate_frame = PositionTarget::FRAME_LOCAL_NED;
rates_msg.header.frame_id = fcu_frame; //rates_msg.header.frame_id = fcu_frame;
ROS_INFO("ready"); ROS_INFO("ready");
ros::spin(); ros::spin();

View File

@@ -11,12 +11,14 @@
#include <string> #include <string>
#include <ros/ros.h> #include <ros/ros.h>
#include <tf/transform_datatypes.h>
#include <tf2/transform_datatypes.h> #include <tf2/transform_datatypes.h>
#include <tf2_ros/buffer.h> #include <tf2_ros/buffer.h>
#include <tf2_ros/transform_listener.h> #include <tf2_ros/transform_listener.h>
#include <tf2_ros/static_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 <geometry_msgs/TransformStamped.h> #include <geometry_msgs/TransformStamped.h>
#include <geometry_msgs/Quaternion.h>
#include <geometry_msgs/PoseStamped.h> #include <geometry_msgs/PoseStamped.h>
#include <geometry_msgs/PoseWithCovarianceStamped.h> #include <geometry_msgs/PoseWithCovarianceStamped.h>
#include <std_srvs/Trigger.h> #include <std_srvs/Trigger.h>
@@ -25,7 +27,7 @@
using std::string; using std::string;
using namespace geometry_msgs; using namespace geometry_msgs;
bool reset_flag = false; bool reset_flag = true; // offset should be reset on the start
string local_frame_id, frame_id, child_frame_id, offset_frame_id; string local_frame_id, frame_id, child_frame_id, offset_frame_id;
tf2_ros::Buffer tf_buffer; tf2_ros::Buffer tf_buffer;
ros::Publisher vpe_pub; ros::Publisher vpe_pub;
@@ -66,6 +68,13 @@ inline Pose getPose(const PoseStampedConstPtr& pose) { return pose->pose; }
inline Pose getPose(const PoseWithCovarianceStampedConstPtr& pose) { return pose->pose.pose; } inline Pose getPose(const PoseWithCovarianceStampedConstPtr& pose) { return pose->pose.pose; }
inline void keepYaw(Quaternion& quaternion)
{
tf::Quaternion q;
q.setRPY(0, 0, tf::getYaw(quaternion));
tf::quaternionTFToMsg(q, quaternion);
}
template <typename T> template <typename T>
void callback(const T& msg) void callback(const T& msg)
{ {
@@ -88,10 +97,29 @@ void callback(const T& msg)
if (!offset_frame_id.empty()) { if (!offset_frame_id.empty()) {
if (reset_flag || msg->header.stamp - vpe.header.stamp > offset_timeout) { if (reset_flag || msg->header.stamp - vpe.header.stamp > offset_timeout) {
// calculate the offset // calculate the offset
offset = tf_buffer.lookupTransform(local_frame_id, frame_id, if (!frame_id.empty()) {
msg->header.stamp, ros::Duration(0.02)); // calculate from TF
// offset.header.frame_id = vpe.header.frame_id; offset = tf_buffer.lookupTransform(local_frame_id, frame_id,
offset.child_frame_id = offset_frame_id; msg->header.stamp, ros::Duration(0.02));
// offset.header.frame_id = vpe.header.frame_id;
offset.child_frame_id = offset_frame_id;
} else {
// calculate transform between pose in vpe frame and pose in local frame
TransformStamped local_pose = tf_buffer.lookupTransform(local_frame_id, child_frame_id,
msg->header.stamp, ros::Duration(0.02));
keepYaw(local_pose.transform.rotation);
tf::Transform vpeTransform, poseTransform;
tf::poseMsgToTF(vpe.pose, vpeTransform);
tf::transformMsgToTF(local_pose.transform, poseTransform);
tf::Transform offset_tf = vpeTransform.inverseTimes(poseTransform);
tf::transformTFToMsg(offset_tf, offset.transform);
offset.header.frame_id = local_frame_id;
offset.header.stamp = msg->header.stamp;
offset.child_frame_id = offset_frame_id;
}
br.sendTransform(offset); br.sendTransform(offset);
reset_flag = false; reset_flag = false;
ROS_INFO("offset reset"); ROS_INFO("offset reset");
@@ -122,8 +150,9 @@ int main(int argc, char **argv) {
tf2_ros::TransformListener tf_listener(tf_buffer); tf2_ros::TransformListener tf_listener(tf_buffer);
nh_priv.param<string>("frame_id", frame_id, ""); nh_priv.param<string>("frame_id", frame_id, ""); // name for used visual pose frame
nh_priv.param<string>("offset_frame_id", offset_frame_id, ""); nh_priv.param<string>("offset_frame_id", offset_frame_id, ""); // name for published offset frame
nh.param<string>("mavros/local_position/frame_id", local_frame_id, "map"); nh.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
nh.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link"); nh.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link");
offset_timeout = ros::Duration(nh_priv.param("offset_timeout", 3.0)); offset_timeout = ros::Duration(nh_priv.param("offset_timeout", 3.0));

4
clover/src/www Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
export ROSWWW_DEFAULT=clover
rosrun roswww_static update

View File

@@ -13,11 +13,11 @@ float32 alt
float32 vx float32 vx
float32 vy float32 vy
float32 vz float32 vz
float32 pitch
float32 roll float32 roll
float32 pitch
float32 yaw float32 yaw
float32 pitch_rate
float32 roll_rate float32 roll_rate
float32 pitch_rate
float32 yaw_rate float32 yaw_rate
float32 voltage float32 voltage
float32 cell_voltage float32 cell_voltage

View File

@@ -2,7 +2,6 @@ float32 x
float32 y float32 y
float32 z float32 z
float32 yaw float32 yaw
float32 yaw_rate
float32 speed float32 speed
string frame_id string frame_id
bool auto_arm bool auto_arm

View File

@@ -2,7 +2,6 @@ float64 lat
float64 lon float64 lon
float32 z float32 z
float32 yaw float32 yaw
float32 yaw_rate
float32 speed float32 speed
string frame_id string frame_id
bool auto_arm bool auto_arm

View File

@@ -0,0 +1,5 @@
float32 z
string frame_id
---
bool success
string message

View File

@@ -1,5 +1,5 @@
float32 pitch
float32 roll float32 roll
float32 pitch
float32 yaw float32 yaw
float32 thrust float32 thrust
string frame_id string frame_id

View File

@@ -2,7 +2,6 @@ float32 x
float32 y float32 y
float32 z float32 z
float32 yaw float32 yaw
float32 yaw_rate
string frame_id string frame_id
bool auto_arm bool auto_arm
--- ---

View File

@@ -1,5 +1,5 @@
float32 pitch_rate
float32 roll_rate float32 roll_rate
float32 pitch_rate
float32 yaw_rate float32 yaw_rate
float32 thrust float32 thrust
bool auto_arm bool auto_arm

View File

@@ -2,7 +2,6 @@ float32 vx
float32 vy float32 vy
float32 vz float32 vz
float32 yaw float32 yaw
float32 yaw_rate
string frame_id string frame_id
bool auto_arm bool auto_arm
--- ---

5
clover/srv/SetYaw.srv Normal file
View File

@@ -0,0 +1,5 @@
float32 yaw
string frame_id
---
bool success
string message

View File

@@ -0,0 +1,4 @@
float32 yaw_rate
---
bool success
string message

View File

@@ -3,6 +3,7 @@ import rospy
import pytest import pytest
from mavros_msgs.msg import State from mavros_msgs.msg import State
from clover import srv from clover import srv
import time
@pytest.fixture() @pytest.fixture()
def node(): def node():
@@ -24,6 +25,7 @@ def test_simple_offboard_services_available():
rospy.wait_for_service('set_attitude', timeout=5) rospy.wait_for_service('set_attitude', timeout=5)
rospy.wait_for_service('set_rates', timeout=5) rospy.wait_for_service('set_rates', timeout=5)
rospy.wait_for_service('land', timeout=5) rospy.wait_for_service('land', timeout=5)
rospy.wait_for_service('simple_offboard/release', timeout=5)
def test_web_video_server(node): def test_web_video_server(node):
try: try:
@@ -59,3 +61,18 @@ def test_blocks(node):
t.join() t.join()
assert wait_print.result == 'test' assert wait_print.result == 'test'
def test_long_callback():
from clover import long_callback
from time import sleep
# very basic test for long_callback
@long_callback
def cb(i):
cb.counter += i
cb.counter = 0
cb(2)
sleep(0.1)
cb(3)
sleep(1)
assert cb.counter == 5

View File

@@ -37,6 +37,19 @@
<node name="clover_blocks" pkg="clover_blocks" type="clover_blocks" output="screen" required="true"/> <node name="clover_blocks" pkg="clover_blocks" type="clover_blocks" output="screen" required="true"/>
<node pkg="topic_tools" name="main_camera_throttle" type="throttle" ns="main_camera"
args="messages image_raw 5.0 image_raw_throttled" required="true"/>
<node pkg="nodelet" type="nodelet" name="main_camera_nodelet_manager" args="manager" output="screen" required="true">
<param name="num_worker_threads" value="2"/>
</node>
<node pkg="nodelet" type="nodelet" name="rectify" args="load image_proc/rectify main_camera_nodelet_manager" required="true">
<remap from="image_mono" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/>
<remap from="image_rect" to="main_camera/image_rect"/>
</node>
<param name="test_module" value="$(find clover)/test/basic.py"/> <param name="test_module" value="$(find clover)/test/basic.py"/>
<test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner"/> <test test-name="basic_test" pkg="ros_pytest" type="ros_pytest_runner"/>
</launch> </launch>

437
clover/test/offboard.py Executable file
View File

@@ -0,0 +1,437 @@
import rospy
import pytest
from pytest import approx
import threading
import mavros_msgs.msg
from mavros_msgs.srv import SetMode
from geometry_msgs.msg import PoseStamped
from clover import srv
from clover.msg import State
from std_srvs.srv import Trigger
from math import nan, inf
import tf2_ros
import tf2_geometry_msgs
@pytest.fixture()
def node():
return rospy.init_node('offboard_test', anonymous=True)
@pytest.fixture
def tf_buffer():
buf = tf2_ros.Buffer()
tf2_ros.TransformListener(buf)
return buf
def get_state():
return rospy.wait_for_message('/simple_offboard/state', State, timeout=1)
def get_navigate_target(tf_buffer):
target = tf_buffer.lookup_transform('map', 'navigate_target', rospy.get_rostime(), rospy.Duration(1))
assert target.child_frame_id == 'navigate_target'
return target
def test_offboard(node, tf_buffer):
navigate = rospy.ServiceProxy('navigate', srv.Navigate)
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
set_altitude = rospy.ServiceProxy('set_altitude', srv.SetAltitude)
set_yaw = rospy.ServiceProxy('set_yaw', srv.SetYaw)
set_yaw_rate = rospy.ServiceProxy('set_yaw_rate', srv.SetYawRate)
set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)
set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
land = rospy.ServiceProxy('land', Trigger)
res = navigate()
assert res.success == False
assert res.message.startswith('State timeout')
telem = get_telemetry()
assert telem.connected == False
# mocked state publisher
state_pub = rospy.Publisher('/mavros/state', mavros_msgs.msg.State, latch=True, queue_size=1)
state_msg = mavros_msgs.msg.State(mode='OFFBOARD', armed=True)
def publish_state():
r = rospy.Rate(2)
while not rospy.is_shutdown():
state_msg.header.stamp = rospy.Time.now()
state_pub.publish(state_msg)
r.sleep()
# start publishing state
threading.Thread(target=publish_state, daemon=True).start()
rospy.sleep(0.5)
# set_mode service mock
def set_mode(req):
state_msg.mode = req.custom_mode # set mocked mode to requested
return True,
rospy.Service('/mavros/set_mode', SetMode, set_mode)
telem = get_telemetry()
assert telem.connected == False
res = navigate()
assert res.success == False
assert res.message.startswith('No connection to FCU')
state_msg.connected = True
rospy.sleep(1)
telem = get_telemetry()
assert telem.connected == True
res = navigate()
assert res.success == False
assert res.message.startswith('No local position')
local_position_pub = rospy.Publisher('/mavros/local_position/pose', PoseStamped, latch=True, queue_size=1)
local_position_msg = PoseStamped()
local_position_msg.header.frame_id = 'map'
local_position_msg.pose.position.x = 1
local_position_msg.pose.position.y = 2
local_position_msg.pose.position.z = 3
local_position_msg.pose.orientation.w = 1
def publish_local_position():
r = rospy.Rate(30)
while not rospy.is_shutdown():
local_position_msg.header.stamp = rospy.Time.now()
local_position_pub.publish(local_position_msg)
r.sleep()
# start publishing local position
threading.Thread(target=publish_local_position, daemon=True).start()
rospy.sleep(0.5)
# check body frame
body = tf_buffer.lookup_transform('map', 'body', rospy.get_rostime(), rospy.Duration(1))
assert body.child_frame_id == 'body'
assert body.transform.translation.x == approx(1)
assert body.transform.translation.y == approx(2)
assert body.transform.translation.z == approx(3)
res = navigate(x=3, y=2, z=1, frame_id='map')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 3
assert state.y == 2
assert state.z == 1
assert state.yaw == 0
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'map'
assert state.yaw_frame_id == 'map'
target = get_navigate_target(tf_buffer)
assert target.header.frame_id == 'map'
assert target.transform.translation.x == approx(3)
assert target.transform.translation.y == approx(2)
assert target.transform.translation.z == approx(1)
assert target.transform.rotation.x == 0
assert target.transform.rotation.y == 0
assert target.transform.rotation.z == 0
assert target.transform.rotation.w == 1
# try to set only the y
res = navigate(x=nan, y=1, z=nan)
assert res.success == False
assert res.message.startswith('x and y can be set only together')
# set z in body frame
res = navigate(x=nan, y=nan, z=1, frame_id='body')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 3
assert state.y == 2
assert state.z == 4
assert state.yaw == 0
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'map'
assert state.yaw_frame_id == 'map'
# set xy in test frame
res = navigate(x=1, y=2, z=nan, frame_id='test')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 1
assert state.y == 2
assert state.z == 4
assert state.yaw == 0
assert state.xy_frame_id == 'test'
assert state.z_frame_id == 'map'
assert state.yaw_frame_id == 'test'
# auto_arm should not invalidate the setpoint if not effective
res = navigate(x=nan, y=nan, z=1, frame_id='map', auto_arm=True)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 1
assert state.y == 2
assert state.z == 1
assert state.yaw == 0
assert state.xy_frame_id == 'test'
assert state.z_frame_id == 'map'
assert state.yaw_frame_id == 'map'
# auto_arm should invalidate the setpoint if effective
state_msg.mode = 'STABILIZED' # pretend we are not in OFFBOARD mode
rospy.sleep(1)
res = navigate(x=nan, y=nan, z=1, frame_id='map', auto_arm=True)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 1
assert state.y == 2
assert state.z == 1
assert state.yaw == 0
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'map'
assert state.yaw_frame_id == 'map'
state_msg.mode = 'OFFBOARD'
rospy.sleep(1)
# set_attitude should invalidate the setpoint
res = set_attitude()
assert res.success == True
res = navigate(x=5, y=6, z=nan, yaw=nan, frame_id='map')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 5
assert state.y == 6
assert state.z == 3
assert state.yaw == 0
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'map'
assert state.yaw_frame_id == 'map'
# test set_altitude
res = set_altitude(z=7, frame_id='test')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 5
assert state.y == 6
assert state.z == 7
assert state.yaw == 0
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'test'
assert state.yaw_frame_id == 'map'
# test set_yaw
res = set_yaw(yaw=0.5, frame_id='test2')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 5
assert state.y == 6
assert state.z == 7
assert state.yaw == 0.5
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'test'
assert state.yaw_frame_id == 'test2'
# test set_yaw_rate
res = set_yaw_rate(yaw_rate=2)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
assert state.x == 5
assert state.y == 6
assert state.z == 7
assert state.yaw_rate == 2
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'test'
# navigate(yaw=nan) should keep yaw rate mode
res = navigate(x=nan, y=nan, z=nan, yaw=nan)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
assert state.x == 5
assert state.y == 6
assert state.z == 7
assert state.yaw_rate == 2
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'test'
# set_yaw(nan) should change back to yaw mode
res = set_yaw(yaw=nan)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_NAVIGATE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.yaw == 0
assert state.yaw_frame_id == 'map'
# test set_position
res = set_position(x=nan, y=nan, z=13, yaw=nan, frame_id='test2')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_POSITION
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 5
assert state.y == 6
assert state.z == 13
assert state.yaw == 0
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'test2'
assert state.yaw_frame_id == 'map'
# set_altitude should not change the mode
res = set_altitude(z=3, frame_id='test')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_POSITION
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 5
assert state.y == 6
assert state.z == 3
assert state.yaw == 0
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'test'
assert state.yaw_frame_id == 'map'
# set_yaw should not change the main mode
res = set_yaw(yaw=1, frame_id='test2')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_POSITION
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.x == 5
assert state.y == 6
assert state.z == 3
assert state.yaw == 1
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'test'
assert state.yaw_frame_id == 'test2'
# test set_velocity
res = set_velocity(vx=1, frame_id='body')
state = get_state()
assert state.mode == State.MODE_VELOCITY
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.vx == 1
assert state.vy == 0
assert state.vz == 0
assert state.yaw == 0
assert state.xy_frame_id == 'map'
assert state.z_frame_id == 'map'
assert state.yaw_frame_id == 'map'
# set_altitude should not work in velocity mode
res = set_altitude(z=3, frame_id='test')
assert res.success == False
assert res.message.startswith('Altitude cannot be set in')
# test set_attitude
res = set_attitude(roll=0.1, pitch=0.2, yaw=0.3, thrust=0.5)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_ATTITUDE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.roll == approx(0.1)
assert state.pitch == approx(0.2)
assert state.yaw == approx(0.3)
assert state.thrust == approx(0.5)
assert state.yaw_frame_id == 'map'
msg = rospy.wait_for_message('/mavros/setpoint_attitude/attitude', PoseStamped, timeout=3)
# Tait-Bryan ZYX angle (rzyx) converted to quaternion
assert msg.pose.orientation.x == approx(0.0342708)
assert msg.pose.orientation.y == approx(0.10602051)
assert msg.pose.orientation.z == approx(0.14357218)
assert msg.pose.orientation.w == approx(0.98334744)
msg = rospy.wait_for_message('/mavros/setpoint_attitude/thrust', mavros_msgs.msg.Thrust, timeout=3)
assert msg.thrust == approx(0.5)
# set_yaw should work in attitude mode
res = set_yaw(yaw=0.7, frame_id='test2')
assert res.success == True
state = get_state()
assert state.mode == State.MODE_ATTITUDE
assert state.yaw_mode == State.YAW_MODE_YAW
assert state.roll == approx(0.1)
assert state.pitch == approx(0.2)
assert state.yaw == approx(0.7)
assert state.thrust == approx(0.5)
assert state.yaw_frame_id == 'test2'
# set_yaw_rate should not work in attitude mode
res = set_yaw_rate(yaw_rate=0.3)
assert res.success == False
assert res.message.startswith('Yaw rate cannot be set in')
# test set_rates
res = set_rates(roll_rate=nan, pitch_rate=nan, yaw_rate=0.3, thrust=0.6)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_RATES
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
assert state.roll_rate == approx(0)
assert state.pitch_rate == approx(0)
assert state.yaw_rate == approx(0.3)
assert state.thrust == approx(0.6)
msg = rospy.wait_for_message('/mavros/setpoint_raw/attitude', mavros_msgs.msg.AttitudeTarget, timeout=3)
assert msg.thrust == approx(0.6)
res = set_rates(roll_rate=0.3, pitch_rate=0.2, yaw_rate=0.1, thrust=0.4)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_RATES
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
assert state.roll_rate == approx(0.3)
assert state.pitch_rate == approx(0.2)
assert state.yaw_rate == approx(0.1)
assert state.thrust == approx(0.4)
res = set_rates(roll_rate=nan, pitch_rate=nan, yaw_rate=nan, thrust=0.3)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_RATES
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
assert state.roll_rate == approx(0.3)
assert state.pitch_rate == approx(0.2)
assert state.yaw_rate == approx(0.1)
assert state.thrust == approx(0.3)
msg = rospy.wait_for_message('/mavros/setpoint_raw/attitude', mavros_msgs.msg.AttitudeTarget, timeout=3)
assert msg.type_mask == mavros_msgs.msg.AttitudeTarget.IGNORE_ATTITUDE
assert msg.body_rate.x == approx(0.3)
assert msg.body_rate.y == approx(0.2)
assert msg.body_rate.z == approx(0.1)
# set_yaw_rate should work in rates mode
res = set_yaw_rate(yaw_rate=0.4)
assert res.success == True
state = get_state()
assert state.mode == State.MODE_RATES
assert state.yaw_mode == State.YAW_MODE_YAW_RATE
assert state.roll_rate == approx(0.3)
assert state.pitch_rate == approx(0.2)
assert state.yaw_rate == approx(0.4)
assert state.thrust == approx(0.3)
res = set_rates(roll_rate=inf)
assert res.success == False
assert res.message == 'roll_rate argument cannot be Inf'
# test land service
res = land()
assert res.success == True
assert state_msg.mode == 'AUTO.LAND' # check that the mode was set correctly

10
clover/test/offboard.test Normal file
View File

@@ -0,0 +1,10 @@
<launch>
<node name="simple_offboard" pkg="clover" type="simple_offboard" required="true" output="screen"/>
<node pkg="tf2_ros" type="static_transform_publisher" name="test_frame" args="10 20 30 0 0 0 map test"/>
<node pkg="tf2_ros" type="static_transform_publisher" name="test2_frame" args="100 200 300 0 0 0 map test2"/>
<param name="test_module" value="$(find clover)/test/offboard.py"/>
<test test-name="offboard_test" pkg="ros_pytest" type="ros_pytest_runner"/>
</launch>

View File

@@ -1,17 +1,54 @@
# PixHawk (px4fmu-v2), px4fmu-v3 # Source files: PX4-Autopilot/boards/**/nuttx-config/nsh/defconfig
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0011", ATTRS{product}=="PX4 FMU v2.x", SYMLINK+="px4fmu"
# PixRacer (px4fmu-v4)
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0012", ATTRS{product}=="PX4 FMU v4.x", SYMLINK+="px4fmu"
# px4fmu-v5
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0032", ATTRS{product}=="PX4 FMU v5.x", SYMLINK+="px4fmu"
# auav
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0021", ATTRS{product}=="PX4 AUAV x2.1", SYMLINK+="px4fmu"
# crazyflie
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{product}=="PX4 Crazyflie v2.0", SYMLINK+="px4fmu"
# px4fmu-v4pro
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0013", ATTRS{product}=="PX4 FMU v4.x PRO", SYMLINK+="px4fmu"
# Omnibus
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="PX4 OmnibusF4SD", SYMLINK+="px4fmu"
# CUAV X7 Pro
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV X7Pro", SYMLINK+="px4fmu"
# Additional info:
# https://docs.px4.io/main/en/flight_controller/
# https://github.com/mavlink/qgroundcontrol/blob/master/src/comm/USBBoardInfo.json
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="PX4 GNF405", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="PX4 OmnibusF4SD", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{product}=="PX4 Crazyflie v2.0", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="1FC9", ATTRS{idProduct}=="001c", ATTRS{product}=="PX4 FMUK66 v3.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="1FC9", ATTRS{idProduct}=="001c", ATTRS{product}=="PX4 FMUK66 E", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="1FC9", ATTRS{idProduct}=="001d", ATTRS{product}=="PX4 FMURT1062 v1.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0001", ATTRS{product}=="DiatoneMambaF405 MK2", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a32f", ATTRS{product}=="PX4 FMU ModalAI FCv1", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="a330", ATTRS{product}=="PX4 FMU ModalAI FCv2", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0012", ATTRS{product}=="PX4 FMU UVify Core", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteH7", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteH7v2", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="004b", ATTRS{product}=="PX4 DurandalV1", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteF7", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="0050", ATTRS{product}=="PX4 KakuteH7Mini-nand", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="004E", ATTRS{product}=="PX4 PIX32V5", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0061", ATTRS{product}=="PX4 ATL Mantis-EDU", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV Nora", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3163", ATTRS{idProduct}=="004c", ATTRS{product}=="PX4 CUAV X7Pro", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="1B8C", ATTRS{idProduct}=="0036", ATTRS{product}=="MatekH743-mini", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="1B8C", ATTRS{idProduct}=="0036", ATTRS{product}=="MatekH743", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="120A", ATTRS{idProduct}=="1004", ATTRS{product}=="Matekgnssm9nf4", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="1209", ATTRS{idProduct}=="1013", ATTRS{product}=="MatekH743", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="0037", ATTRS{product}=="PX4 FMU SmartAP AIRLink", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2DAE", ATTRS{idProduct}=="1058", ATTRS{product}=="CubeOrange+", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2DAE", ATTRS{idProduct}=="1012", ATTRS{product}=="CubeYellow", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="2DAE", ATTRS{idProduct}=="1016", ATTRS{product}=="CubeOrange", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0035", ATTRS{product}=="PX4 FMU v6X.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0038", ATTRS{product}=="PX4 FMU v6C.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0033", ATTRS{product}=="PX4 FMU v5X.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="1B8C", ATTRS{idProduct}=="0036", ATTRS{product}=="PX4 FMU v6U.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0013", ATTRS{product}=="PX4 FMU v4.x PRO", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0011", ATTRS{product}=="PX4 FMU v2.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0012", ATTRS{product}=="PX4 FMU v4.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0032", ATTRS{product}=="PX4 FMU v5.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3162", ATTRS{idProduct}=="004b", ATTRS{product}=="PX4 SP RACING H7 EXTREME", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0030", ATTRS{product}=="MindPX FMU v2.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="3185", ATTRS{idProduct}=="0039", ATTRS{product}=="ARK FMU v6X.x", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0016", ATTRS{product}=="PX4 FreeFly RTK GPS", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1024", ATTRS{product}=="mRoControlZeroH7 OEM", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1017", ATTRS{product}=="mRoPixracerPro", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1023", ATTRS{product}=="mRoControlZeroH7", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="008D", ATTRS{product}=="mRoControlZeroF7", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0021", ATTRS{product}=="PX4 AUAV X2.1", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="1022", ATTRS{product}=="mRoControlZero Classic", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="26ac", ATTRS{idProduct}=="0088", ATTRS{product}=="mRo x2.1-777", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="35a7", ATTRS{idProduct}=="0002", ATTRS{product}=="FCC-R1", SYMLINK+="px4fmu"
SUBSYSTEM=="tty", ATTRS{idVendor}=="35a7", ATTRS{idProduct}=="0001", ATTRS{product}=="FCC-K1", SYMLINK+="px4fmu"

View File

@@ -40,6 +40,7 @@ function viewTopicsList() {
let rosdistro; let rosdistro;
function viewTopic(topic) { function viewTopic(topic) {
let counter = 0;
let index = '<a href=topics.html>Topics</a>'; let index = '<a href=topics.html>Topics</a>';
title.innerHTML = `${index}: ${topic}`; title.innerHTML = `${index}: ${topic}`;
topicMessage.style.display = 'block'; topicMessage.style.display = 'block';
@@ -51,10 +52,11 @@ function viewTopic(topic) {
}); });
new ROSLIB.Topic({ ros: ros, name: topic }).subscribe(function(msg) { new ROSLIB.Topic({ ros: ros, name: topic }).subscribe(function(msg) {
counter++;
document.title = topic; document.title = topic;
if (mouseDown) return; if (mouseDown) return;
if (msg.header.stamp) { if (msg.header && msg.header.stamp) {
if (params.date || params.offset) { if (params.date || params.offset) {
let date = new Date(msg.header.stamp.secs * 1e3 + msg.header.stamp.nsecs * 1e-6); let date = new Date(msg.header.stamp.secs * 1e3 + msg.header.stamp.nsecs * 1e-6);
if (params.date) msg.header.date = date.toISOString(); if (params.date) msg.header.date = date.toISOString();
@@ -62,7 +64,8 @@ function viewTopic(topic) {
} }
} }
topicMessage.innerHTML = yamlStringify(msg); // JSON.stringify(msg, null, 4); let txt = `<div class=counter>${counter} received</div>${yamlStringify(msg)}`; // JSON.stringify(msg, null, 4);
topicMessage.innerHTML = txt;
}); });
} }

View File

@@ -15,6 +15,7 @@
white-space: pre; white-space: pre;
font-family: monospace; font-family: monospace;
} }
.counter { color: #b9b9b9; margin-bottom: 1em; }
#topic-type { font-family: monospace; font-size: 0.5em; vertical-align: super; font-weight: normal; } #topic-type { font-family: monospace; font-size: 0.5em; vertical-align: super; font-weight: normal; }
.topic { font-family: monospace; } .topic { font-family: monospace; }
body.closed { background-color: rgb(207, 207, 207); } body.closed { background-color: rgb(207, 207, 207); }

View File

@@ -47,6 +47,7 @@ http://<hostname>/clover_blocks/?navigate_tolerance=0.5&sleep_time=0.1
* `~running` ([*std_msgs/Bool*](http://docs.ros.org/noetic/api/std_msgs/html/msg/Bool.html)) indicates if the program is currently running. * `~running` ([*std_msgs/Bool*](http://docs.ros.org/noetic/api/std_msgs/html/msg/Bool.html)) indicates if the program is currently running.
* `~block` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) current executing block (maximum topic rate is limited). * `~block` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) current executing block (maximum topic rate is limited).
* `~print` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html)) user program output messages (published in *print* blocks).
* `~error` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html))  user program errors and exceptions. * `~error` ([*std_msgs/String*](http://docs.ros.org/noetic/api/std_msgs/html/msg/String.html))  user program errors and exceptions.
* `~prompt` ([*clover_blocks/Prompt*](msg/Prompt.msg)) user input request (includes random request ID string). * `~prompt` ([*clover_blocks/Prompt*](msg/Prompt.msg)) user input request (includes random request ID string).

View File

@@ -1,7 +1,7 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<package format="2"> <package format="2">
<name>clover_blocks</name> <name>clover_blocks</name>
<version>0.23.0</version> <version>0.24.0</version>
<description>Blockly programming support for Clover</description> <description>Blockly programming support for Clover</description>
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer> <maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
<license>MIT</license> <license>MIT</license>

View File

@@ -15,6 +15,7 @@ const COLOR_GPIO = 200;
const DOCS_URL = 'https://clover.coex.tech/en/blocks.html'; const DOCS_URL = 'https://clover.coex.tech/en/blocks.html';
var frameIds = [["body", "BODY"], ["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"], ["map", "MAP"]]; var frameIds = [["body", "BODY"], ["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"], ["map", "MAP"]];
var frameIdsWithTerrain = frameIds.concat([["terrain", "TERRAIN"]]);
function considerFrameId(e) { function considerFrameId(e) {
if (!(e instanceof Blockly.Events.Change || e instanceof Blockly.Events.Create)) return; if (!(e instanceof Blockly.Events.Change || e instanceof Blockly.Events.Create)) return;
@@ -22,7 +23,7 @@ function considerFrameId(e) {
var frameId = this.getFieldValue('FRAME_ID'); var frameId = this.getFieldValue('FRAME_ID');
// set appropriate coordinates labels // set appropriate coordinates labels
if (this.getInput('X')) { // block has x-y-z fields if (this.getInput('X')) { // block has x-y-z fields
if (frameId == 'BODY' || frameId == 'NAVIGATE_TARGET' || frameId == 'BASE_LINK') { if (frameId == 'BODY' || frameId == 'NAVIGATE_TARGET' || frameId == 'BASE_LINK' || frameId == 'TERRAIN') {
this.getInput('X').fieldRow[0].setValue('forward'); this.getInput('X').fieldRow[0].setValue('forward');
this.getInput('Y').fieldRow[0].setValue('left'); this.getInput('Y').fieldRow[0].setValue('left');
this.getInput('Z').fieldRow[0].setValue('up'); this.getInput('Z').fieldRow[0].setValue('up');
@@ -59,8 +60,8 @@ function updateSetpointBlock(e) {
this.getInput('VY').setVisible(velocity); this.getInput('VY').setVisible(velocity);
this.getInput('VZ').setVisible(velocity); this.getInput('VZ').setVisible(velocity);
this.getInput('YAW').setVisible(attitude); this.getInput('YAW').setVisible(attitude);
this.getInput('PITCH').setVisible(attitude);
this.getInput('ROLL').setVisible(attitude); this.getInput('ROLL').setVisible(attitude);
this.getInput('PITCH').setVisible(attitude);
this.getInput('THRUST').setVisible(attitude); this.getInput('THRUST').setVisible(attitude);
this.getInput('RELATIVE_TO').setVisible(type != 'RATES'); this.getInput('RELATIVE_TO').setVisible(type != 'RATES');
@@ -73,7 +74,7 @@ function updateSetpointBlock(e) {
Blockly.Blocks['navigate'] = { Blockly.Blocks['navigate'] = {
init: function () { init: function () {
let navFrameId = frameIds.slice(); let navFrameId = frameIdsWithTerrain.slice();
navFrameId.push(['global', 'GLOBAL_LOCAL']) navFrameId.push(['global', 'GLOBAL_LOCAL'])
navFrameId.push(['global, WGS 84 alt.', 'GLOBAL']) navFrameId.push(['global, WGS 84 alt.', 'GLOBAL'])
this.appendDummyInput() this.appendDummyInput()
@@ -163,14 +164,14 @@ Blockly.Blocks['setpoint'] = {
this.appendValueInput("VZ") this.appendValueInput("VZ")
.setCheck("Number") .setCheck("Number")
.appendField("vz"); .appendField("vz");
this.appendValueInput("PITCH")
.setCheck("Number")
.appendField("pitch")
.setVisible(false);
this.appendValueInput("ROLL") this.appendValueInput("ROLL")
.setCheck("Number") .setCheck("Number")
.appendField("roll") .appendField("roll")
.setVisible(false); .setVisible(false);
this.appendValueInput("PITCH")
.setCheck("Number")
.appendField("pitch")
.setVisible(false);
this.appendValueInput("YAW") this.appendValueInput("YAW")
.setCheck("Number") .setCheck("Number")
.appendField("yaw") .appendField("yaw")
@@ -213,7 +214,7 @@ Blockly.Blocks['get_position'] = {
.appendField("current") .appendField("current")
.appendField(new Blockly.FieldDropdown([["x", "X"], ["y", "Y"], ["z", "Z"], ["vx", "VX"], ["vy", "VY"], ["vz", "VZ"]]), "FIELD") .appendField(new Blockly.FieldDropdown([["x", "X"], ["y", "Y"], ["z", "Z"], ["vx", "VX"], ["vy", "VY"], ["vz", "VZ"]]), "FIELD")
.appendField("relative to") .appendField("relative to")
.appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID"); .appendField(new Blockly.FieldDropdown(frameIdsWithTerrain), "FRAME_ID");
this.appendValueInput("ID") this.appendValueInput("ID")
.setCheck("Number") .setCheck("Number")
.appendField("with ID") .appendField("with ID")
@@ -247,7 +248,7 @@ Blockly.Blocks['get_attitude'] = {
init: function () { init: function () {
this.appendDummyInput() this.appendDummyInput()
.appendField("current") .appendField("current")
.appendField(new Blockly.FieldDropdown([["pitch", "PITCH"], ["roll", "ROLL"], ["pitch rate", "PITCH_RATE"], ["roll rate", "ROLL_RATE"], ["yaw rate", "YAW_RATE"]]), "FIELD"); .appendField(new Blockly.FieldDropdown([["roll", "ROLL"], ["pitch", "PITCH"], ["roll rate", "ROLL_RATE"], ["pitch rate", "PITCH_RATE"], ["yaw rate", "YAW_RATE"]]), "FIELD");
this.setOutput(true, "Number"); this.setOutput(true, "Number");
this.setColour(COLOR_STATE); this.setColour(COLOR_STATE);
this.setTooltip("Returns current orientation or angle rates in degree or degree per second (not radian)."); this.setTooltip("Returns current orientation or angle rates in degree or degree per second (not radian).");
@@ -268,6 +269,19 @@ Blockly.Blocks['voltage'] = {
} }
}; };
Blockly.Blocks['get_rc'] = {
init: function () {
this.appendDummyInput()
.appendField("RC channel")
this.appendValueInput("CHANNEL")
.setCheck("Number");
this.setInputsInline(true);
this.setOutput(true, "Number");
this.setColour(COLOR_STATE);
this.setTooltip("Returns current RC channel value.");
this.setHelpUrl(DOCS_URL + '#' + this.type);
}
}
Blockly.Blocks['armed'] = { Blockly.Blocks['armed'] = {
init: function () { init: function () {
@@ -509,7 +523,7 @@ Blockly.Blocks['distance'] = {
.appendField("z"); .appendField("z");
this.appendDummyInput() this.appendDummyInput()
.appendField("relative to") .appendField("relative to")
.appendField(new Blockly.FieldDropdown([["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"]]), "FRAME_ID"); .appendField(new Blockly.FieldDropdown([["markers map", "ARUCO_MAP"], ["marker", "ARUCO"], ["last navigate target", "NAVIGATE_TARGET"], ["terrain", "TERRAIN"]]), "FRAME_ID");
this.appendValueInput("ID") this.appendValueInput("ID")
.setCheck("Number") .setCheck("Number")
.appendField("with ID") .appendField("with ID")

View File

@@ -69,8 +69,8 @@
<value name="VX"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="VX"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="VY"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="VY"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="VZ"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="VZ"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="PITCH"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="ROLL"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="ROLL"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="PITCH"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="YAW"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="YAW"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="THRUST"><shadow type="math_number"><field name="NUM">0.5</field></shadow></value> <value name="THRUST"><shadow type="math_number"><field name="NUM">0.5</field></shadow></value>
<value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
@@ -100,6 +100,9 @@
<block type="mode"></block> <block type="mode"></block>
<block type="armed"></block> <block type="armed"></block>
<block type="voltage"></block> <block type="voltage"></block>
<block type="get_rc">
<value name="CHANNEL"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
</block>
</category> </category>
<category name="LED" colour="#02d754"> <category name="LED" colour="#02d754">
<block type="set_effect"> <block type="set_effect">

View File

@@ -81,7 +81,10 @@ function generateROSDefinitions() {
code += `get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)\n`; code += `get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)\n`;
code += `navigate = rospy.ServiceProxy('navigate', srv.Navigate)\n`; code += `navigate = rospy.ServiceProxy('navigate', srv.Navigate)\n`;
if (rosDefinitions.navigateGlobal) { if (rosDefinitions.navigateGlobal) {
code += `navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)\n`; code += `navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)\n`;
}
if (rosDefinitions.setYaw) {
code += `set_yaw = rospy.ServiceProxy('set_yaw', srv.SetYaw)\n`;
} }
if (rosDefinitions.setVelocity) { if (rosDefinitions.setVelocity) {
code += `set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)\n`; code += `set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)\n`;
@@ -276,10 +279,11 @@ Blockly.Python.angle = function(block) {
} }
Blockly.Python.set_yaw = function(block) { Blockly.Python.set_yaw = function(block) {
rosDefinitions.setYaw = true;
simpleOffboard(); simpleOffboard();
let yaw = Blockly.Python.valueToCode(block, 'YAW', Blockly.Python.ORDER_NONE); let yaw = Blockly.Python.valueToCode(block, 'YAW', Blockly.Python.ORDER_NONE);
let frameId = buildFrameId(block); let frameId = buildFrameId(block);
let code = `navigate(x=float('nan'), y=float('nan'), z=float('nan'), yaw=${yaw}, frame_id=${frameId})\n`; let code = `set_yaw(yaw=${yaw}, frame_id=${frameId})\n`;
if (block.getFieldValue('WAIT') == 'TRUE') { if (block.getFieldValue('WAIT') == 'TRUE') {
rosDefinitions.waitYaw = true; rosDefinitions.waitYaw = true;
simpleOffboard(); simpleOffboard();
@@ -328,11 +332,11 @@ Blockly.Python.setpoint = function(block) {
} else if (type == 'ATTITUDE') { } else if (type == 'ATTITUDE') {
rosDefinitions.setAttitude = true; rosDefinitions.setAttitude = true;
simpleOffboard(); simpleOffboard();
return `set_attitude(pitch=${pitch}, roll=${roll}, yaw=${yaw}, thrust=${thrust}, frame_id=${frameId})\n`; return `set_attitude(roll=${roll}, pitch=${pitch}, yaw=${yaw}, thrust=${thrust}, frame_id=${frameId})\n`;
} else if (type == 'RATES') { } else if (type == 'RATES') {
rosDefinitions.setRates = true; rosDefinitions.setRates = true;
simpleOffboard(); simpleOffboard();
return `set_rates(pitch_rate=${pitch}, roll_rate=${roll}, yaw_rate=${yaw}, thrust=${thrust})\n`; return `set_rates(roll_rate=${roll}, pitch_rate=${pitch}, yaw_rate=${yaw}, thrust=${thrust})\n`;
} }
} }
@@ -398,6 +402,12 @@ Blockly.Python.voltage = function(block) {
return [code, Blockly.Python.ORDER_FUNCTION_CALL]; return [code, Blockly.Python.ORDER_FUNCTION_CALL];
} }
Blockly.Python.get_rc = function(block) {
Blockly.Python.definitions_['import_rcin'] = 'from mavros_msgs.msg import RCIn';
var channel = Blockly.Python.valueToCode(block, 'CHANNEL', Blockly.Python.ORDER_NONE);
return [`rospy.wait_for_message('mavros/rc/in', RCIn).channels[${channel}]`, Blockly.Python.ORDER_FUNCTION_CALL]
}
function parseColor(color) { function parseColor(color) {
return { return {
r: parseInt(color.substr(2, 2), 16), r: parseInt(color.substr(2, 2), 16),

View File

@@ -1,6 +1,6 @@
<package format="2"> <package format="2">
<name>clover_description</name> <name>clover_description</name>
<version>0.23.0</version> <version>0.24.0</version>
<description>The clover_description package provides URDF models of the Clover series of quadcopters.</description> <description>The clover_description package provides URDF models of the Clover series of quadcopters.</description>
<maintainer email="sfalexrog@gmail.com">Alexey Rogachevskiy</maintainer> <maintainer email="sfalexrog@gmail.com">Alexey Rogachevskiy</maintainer>

View File

@@ -2,7 +2,7 @@
<robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/xacro"> <robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:include filename="../camera_sensor.urdf.xacro"/> <xacro:include filename="../camera_sensor.urdf.xacro"/>
<xacro:macro name="distance_sensor" params="name:=lidar_vl53l1x parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 range_min:=0.01 range_max:=4.0 resolution:=0.01 rate:=10"> <xacro:macro name="distance_sensor" params="name:=lidar_vl53l1x parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 range_min:=0.01 range_max:=4.0 resolution:=0.01 rate:=10 fov:=0.471239">
<joint name="${name}_joint" type="fixed"> <joint name="${name}_joint" type="fixed">
<origin xyz="${x} ${y} ${z}" <origin xyz="${x} ${y} ${z}"
rpy="${roll} ${pitch} ${yaw}"/> rpy="${roll} ${pitch} ${yaw}"/>
@@ -58,7 +58,7 @@
<topicName>/rangefinder/range</topicName> <topicName>/rangefinder/range</topicName>
<frameName>rangefinder</frameName> <frameName>rangefinder</frameName>
<radiation>infrared</radiation> <radiation>infrared</radiation>
<fov>0.01</fov> <fov>${fov}</fov>
<gaussianNoise>0.001</gaussianNoise> <gaussianNoise>0.001</gaussianNoise>
<updateRate>${rate}</updateRate> <updateRate>${rate}</updateRate>
<min_distance>${range_min}</min_distance> <min_distance>${range_min}</min_distance>

View File

@@ -10,7 +10,7 @@ The simulation may be configured by a set of arguments:
* `mav_id` (*integer*, default: *0*) - MAVLink identifier of the vehicle. **Note**: Multi-vehicle simulation is possible, but requires extensive changes to launch files; * `mav_id` (*integer*, default: *0*) - MAVLink identifier of the vehicle. **Note**: Multi-vehicle simulation is possible, but requires extensive changes to launch files;
* `est` (*string*, default: *lpe*, possible values: *lpe*, *ekf2*) - PX4 estimator selection. Note that this may be overriden in the startup scripts for your craft; * `est` (*string*, default: *lpe*, possible values: *lpe*, *ekf2*) - PX4 estimator selection. Note that this may be overriden in the startup scripts for your craft;
* `vehicle` (*string*, default: *clover*) - PX4 vehicle name. Depending on this parameter, different PX4 presets will be loaded. **Note**: The default value, *clover*, requires you to use [Clover-specific PX4 branch](https://github.com/CopterExpress/Firmware/tree/v1.10.1-clever); * `vehicle` (*string*, default: *clover*) - PX4 vehicle name. Depending on this parameter, different PX4 presets will be loaded.
* `main_camera` (*boolean*, default: *true*) - controls whether the drone will have a vision position estimation camera; * `main_camera` (*boolean*, default: *true*) - controls whether the drone will have a vision position estimation camera;
* `rangefinder` (*boolean*, default: *true*) - controls whether the drone will have a laser rangefinder; * `rangefinder` (*boolean*, default: *true*) - controls whether the drone will have a laser rangefinder;
* `led` (*boolean*, default: *true*) - controls whether the drone will have a programmable LED strip; * `led` (*boolean*, default: *true*) - controls whether the drone will have a programmable LED strip;

View File

@@ -31,7 +31,7 @@ param set-default EKF2_OF_DELAY 0
param set-default EKF2_OF_QMIN 10 param set-default EKF2_OF_QMIN 10
param set-default EKF2_OF_N_MIN 0.05 param set-default EKF2_OF_N_MIN 0.05
param set-default EKF2_OF_N_MAX 0.2 param set-default EKF2_OF_N_MAX 0.2
param set-default EKF2_HGT_MODE 2 # 0 = baro, 1 = gps, 2 = range, 3 = vision param set-default EKF2_HGT_MODE 3 # 0 = baro, 1 = gps, 2 = range, 3 = vision
param set-default EKF2_EVA_NOISE 0.1 param set-default EKF2_EVA_NOISE 0.1
param set-default EKF2_EVP_NOISE 0.1 param set-default EKF2_EVP_NOISE 0.1
param set-default EKF2_EV_DELAY 0 param set-default EKF2_EV_DELAY 0

View File

@@ -0,0 +1,16 @@
material red_circle
{
technique
{
pass
{
scene_blend alpha_blend
texture_unit
{
texture red_circle.png
filtering none
scale 1.0 1.0
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<model>
<name>Red Circle</name>
<version>1.0</version>
<sdf version="1.5">red_circle.sdf</sdf>
<author>
<name>Oleg Kalachev</name>
<email>okalachev@gmail.com</email>
</author>
<description>
Red circle of size 40 cm on the floor for recognizing by a drone
</description>
</model>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<sdf version="1.5">
<model name="red_circle">
<static>true</static>
<link name="red_circle_link">
<pose>0 0 1e-3 0 0 0</pose>
<visual name="red_circle_texture">
<cast_shadows>false</cast_shadows>
<geometry>
<box>
<size>0.4 0.4 1e-3</size>
</box>
</geometry>
<material>
<script>
<uri>model://red_circle/materials/scripts</uri>
<uri>model://red_circle/materials/textures</uri>
<name>red_circle</name>
</script>
</material>
</visual>
</link>
</model>
</sdf>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<title>
red_circle
</title><g fill="red">
<circle cx="10.05" cy="10.05" r="9.9"/>
</g></svg>

After

Width:  |  Height:  |  Size: 221 B

View File

@@ -1,6 +1,6 @@
<package format="2"> <package format="3">
<name>clover_simulation</name> <name>clover_simulation</name>
<version>0.23.0</version> <version>0.24.0</version>
<description>The clover_simulation package provides worlds and launch files for Gazebo.</description> <description>The clover_simulation package provides worlds and launch files for Gazebo.</description>
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer> <maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
@@ -22,6 +22,8 @@
<depend>gazebo_ros</depend> <depend>gazebo_ros</depend>
<depend>gazebo_plugins</depend> <depend>gazebo_plugins</depend>
<depend>rospy</depend> <depend>rospy</depend>
<depend condition="$ROS_PYTHON_VERSION == 2">python-docopt</depend>
<depend condition="$ROS_PYTHON_VERSION == 3">python3-docopt</depend>
<export> <export>
<gazebo_ros gazebo_media_path="${prefix}"/> <gazebo_ros gazebo_media_path="${prefix}"/>

View File

@@ -65,7 +65,8 @@ public:
} }
role = (ros::this_node::getName() == "/gazebo") ? Role::Server : Role::Client; role = (ros::this_node::getName() == "/gazebo") ? Role::Server : Role::Client;
ROS_INFO_NAMED(("LedController_" + robotNamespace).c_str(), "LedController has started (as %s)", role == Role::Client ? "client" : "server"); ROS_INFO_NAMED(("LedController_" + robotNamespace).c_str(), "LedController has started (as %s) in namespace '%s'",
role == Role::Client ? "client" : "server", robotNamespace.c_str());
nh.reset(new ros::NodeHandle(robotNamespace)); nh.reset(new ros::NodeHandle(robotNamespace));
@@ -109,7 +110,6 @@ LedController& get(std::string robotNamespace)
std::lock_guard<std::mutex> lock(controllerMutex); std::lock_guard<std::mutex> lock(controllerMutex);
auto it = controllers.find(robotNamespace); auto it = controllers.find(robotNamespace);
if (it == controllers.end()) { if (it == controllers.end()) {
gzwarn << "Creating new LED controller for namespace " << robotNamespace << "\n";
controllers[robotNamespace].reset(new LedController(robotNamespace)); controllers[robotNamespace].reset(new LedController(robotNamespace));
return *controllers[robotNamespace]; return *controllers[robotNamespace];
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 368 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

View File

@@ -36,7 +36,7 @@
* [Optical Flow](optical_flow.md) * [Optical Flow](optical_flow.md)
* [Autonomous flight (OFFBOARD)](simple_offboard.md) * [Autonomous flight (OFFBOARD)](simple_offboard.md)
* [Coordinate systems (frames)](frames.md) * [Coordinate systems (frames)](frames.md)
* [Code snippets](snippets.md) * [Code examples](snippets.md)
* [Interfacing with a laser rangefinder](laser.md) * [Interfacing with a laser rangefinder](laser.md)
* [LED strip](leds.md) * [LED strip](leds.md)
* [Working with GPIO](gpio.md) * [Working with GPIO](gpio.md)
@@ -57,6 +57,7 @@
* [COEX Pix](coex_pix.md) * [COEX Pix](coex_pix.md)
* [COEX PDB](coex_pdb.md) * [COEX PDB](coex_pdb.md)
* [COEX GPS](coex_gps.md) * [COEX GPS](coex_gps.md)
* [Using SSH keys](ssh_keys.md)
* [Guide on autonomous flight](auto_setup.md) * [Guide on autonomous flight](auto_setup.md)
* [Hostname](hostname.md) * [Hostname](hostname.md)
* [PX4 Simulation](sitl.md) * [PX4 Simulation](sitl.md)
@@ -105,6 +106,12 @@
* [Video contest](video_contest.md) * [Video contest](video_contest.md)
* [Educational contests](educational_contests.md) * [Educational contests](educational_contests.md)
* [Clover-based projects](projects.md) * [Clover-based projects](projects.md)
* [Clover Cloud Platform](clover-cloud-platform.md)
* [Autonomous Racing Drone](djs_phoenix_chetak.md)
* [Motion Capture System](mocap_clover.md)
* [Swarm in Blocks 2](swarm_in_blocks_2.md)
* [Advanced Clover 2](advanced_clover_simulator_platform.md)
* [Network of charging stations](liceu128.md)
* [Swarm-in-blocks](swarm_in_blocks.md) * [Swarm-in-blocks](swarm_in_blocks.md)
* [Obstacle avoidance using artificial potential fields method](obstacle-avoidance-potential-fields.md) * [Obstacle avoidance using artificial potential fields method](obstacle-avoidance-potential-fields.md)
* [The Clover Rescue Project](clover-rescue-team.md) * [The Clover Rescue Project](clover-rescue-team.md)

View File

@@ -0,0 +1,161 @@
# Advanced Clover 3: The Platform
[CopterHack-2023](copterhack2023.md), team **FTL**.
## Team Information
```cpp
#include "veryInterestingCommandDescription.h"
```
Team members:
- Maxim Ramanouski, [@max8rr8](https://t.me/max8rr8).
Country: Belarus.
## Project Description
Last year at CopterHack 2022, we created a [project](../ru/advanced_clover_simulator.html) that simplified the simulation of Clover, and in 2021, we created a [project](../ru/advanced_clover.html) that simplified the development of products for Clover (IDE and library for writing). The time has come to combine them and achieve unlimited power.
### Project Idea
The idea of the project is to combine CloverIDE and CloverSim (a tool for running Clover simulations). Thus, a platform is planned that allows developing products based on Clover using a simulator and an advanced IDE. The platform will include the following features:
- Add a web interface that allows using CloverSim without touching the command line.
- Work both in the browser (without installing anything) and from CLI.
- Have a course that covers different aspects of clover.
- Simplify installation, especially in WSL.
- Running a simulation on a remote device (more powerful computer or cloud).
### Project videos
Video presentation of the project: [link](https://www.youtube.com/watch?v=T4RU9sfxsSI).
Live presentation at CopterHack: TBD.
CLI demonstration: [link](https://www.youtube.com/watch?v=Ao-ukR58sSQ).
## Installation
Installation process is described in the [project documentation](https://ftl-team.github.io/clover_sim/#/?id=installation).
## Usage
The CloverSim platform offers a seamless workflow for users:
1. Users can effortlessly select or create a workspace and task and
launch them with ease.
![Step 1 screenshot](../assets/ftl/acp_workflow1.png)
2. After launching the simulation, users are presented with CloverSim WebUI that
provides them with an intuitive way to view their scores and progress,
control the simulator, and access task descriptions and scoring information.
From it users can open terminal, gzweb and more importantly they can easily
access the CloverSim IDE to solve task.
![Step 2 screenshot](../assets/ftl/acp_workflow2.png)
3. The IDE provides a full suite of tools and features for writing and
debugging code. One example is autocompletion to help streamline the
development process, making it more efficient and effective.
![Step 3 screenshot](../assets/ftl/acp_workflow3.png)
4. Users can launch their programs with ease and monitor its progress via
the GZWeb, CopterStatus, and SimulatorStatus views of the IDE.
![Step 4 screenshot](../assets/ftl/acp_workflow4.png)
5. Users can track their progress and scores in real-time and effortlessly
restart the simulator if necessary. Additionally, different randomization
seed can be set to check various inputs and outcomes.
We also have video demonstration/tutorial: [link](https://www.youtube.com/watch?v=aPOPHD3M3ZM).
## More features
- Easy installation process.
- Efficient simulation launch, surpassing traditional virtual machines.
- Generation of dynamic Gazebo worlds with randomization based on seed.
- Real-time task completion verification and score presentation.
- Execution with security in isolated containers.
- Multiple project capability without the need for multiple virtual machine images.
- WebUI for ease of use, removing the need to use the command line.
- IDE similar to VSCode with support for C++ and Python, including autocompletion and autoformatting.
- Custom-patched GZWeb with bug fixes and additional features, including the display of the Clover LED strip.
- GZWeb provides a follow-objects feature superior to that of Gazebo.
- IDE includes tools to interact with ROS, such as topic visualization, service calling, and image topic visualization.
- IDE also includes Copter Status, displaying most of the drone's information, including position, camera, and LED strip, in one view.
- IDE integrates with the simulator by providing control from it, viewing task descriptions, and opening GZWeb.
We also have developed a learning course based on CloverSim: [link](https://github.com/FTL-team/CloverSim_course). It currently has the following tasks:
- 1_thesquare - First task of CloverSim course with goal to fly square.
- 2_iseeall - Task that teaches how to interact with camera.
- 3_landmid - Find and land onto randomly positioned object.
- 4_flybyline - Flying along the line.
- 5_posknown - Find position of objects relative to ArUco map.
## More details
At this point, our platform consists of four major parts:
- [CloverSim](https://github.com/FTL-team/clover_sim) - tool that manages simulation.
- [CloverSim Basefs](https://github.com/FTL-team/clover_sim_basefs) - container image that is used in simulator.
- [Clover IDE](https://github.com/FTL-team/cloverIDE) - clover ide tools and theia.
- [CloverSim course](https://github.com/FTL-team/CloverSim_course) - course with tasks based on our platform.
### CloverSim
The simulation architecture is a continuation of work from CopterHack 2022, but while 2022 version was closer to Proof-of-Concept, the updated version is more robust.
There are three major difference in simulator architecture
- Replacement of `systemd-nspawn` with `runc` provides us higher degree of container control and seemingless support of non-systemd systems, for example WSL.
- Migration to squash fs images, which greatly reduced size of installed CloverSim from 13 gigabytes to just 3.5 gigabytes.
- Tasks are now mounted instead of being copied and also build before starting.
Because of the way catkin_make works, it is incredibly slow when new packages are added (whole cmake configuration is rerun for all packages). catkin_make provides a way to build only some packages, but it caches this packages and to reset this cache you need to recompile whole catkin_make. But we have found a solution: `catkin_make -DCATKIN_WHITELIST_PACKAGES="task;CloverSim" --build build_CloverSim` This command, builds only CloverSim and task package in separate build directory, this drastically reduces time that catkin_make takes, and keeps expected behavior of catkin_make without arguments.
There are also differences in tool that launches simulation:
- Client-server architecture allows us to create web UI and run CloverSim on server.
- More robust error handling improves user experience.
- Rewritten in rust, better reliability and development experience.
### CloverSim basefs
Version 2 integrates CloverIDE into system. We also updated clover in simulator to v0.23 and added web terminal. Basefs is now squashed and doesn't require additional installation. It also uses patched(by us) version of gzweb that is more suitable for our use-case:
- Unlike original GZWeb assets can be dynamically loaded, which is required to support dynamically generated tasks.
- It also implements multiple bugfixes for rendering, UI.
- Fixed performance, original gzweb had two constantly running loops that used 200% of cpu. We fixed this by instead using synchronization primitives.
- Clover LED strip is rendered, our gzweb connects to ROS and pulls LED data from there to render LED strip like Gazebo does.
- Users can now follow-objects like in Gazebo better actually.
- Reconnect on disconnect, when simulator is restarted gzweb looses connection and it now can automatically reconnect.
Patched gzweb available there: [FTL-team/gzweb](https://github.com/FTL-team/gzweb).
### CloverIDE
CloverIDE got some updates too:
- We have updated theia and extensions used.
- Better C++ support via clangd.
- Clover IDE tools can now reconnect after simulator restart.
- Copter Status now displays LED strip status.
- Tools ui has better support for different themes.
But the most important change is CloverSim integration, there are new tools (task description, simulator control and gzweb). While gzweb tool is just an iframe (though it's very cool to have it in IDE).
Task description and simulator control are more interesting as they have to interact with both IDE and CloverSim, to maintain different versions support we use quite interesting trick, extension webview after being initialized dynamically loads JavaScript from CloverSim. That provides better integration between two.
### CloverSim course
CloverSim course is a new part of our platform. It uses robust task API of CloverSim to create practical learning course. It currently teaches different aspects of clover development that i encountered during my participation in different contests involving clover. But we are happy to accpet suggestions about other aspects we should teach in out course.
## Conclusion
This project is a final (or maybe there is more?) project of our advanced clover saga. AdvancedClover is a project that is easy to use and greatly improves experience during learning about clover, participating in clover based competitions and development clover based projects. We thank COEX team for their support and look forward to further cooperation.

View File

@@ -72,12 +72,6 @@ Sample code to fly to a point 1 metre to the left and 2 metres above marker with
navigate(frame_id='aruco_7', x=-1, y=0, z=2) navigate(frame_id='aruco_7', x=-1, y=0, z=2)
``` ```
Sample code to rotate counterclockwise while hovering 1.5 metres above marker id 10:
```python
navigate(frame_id='aruco_10', x=0, y=0, z=1.5, yaw_rate=0.5)
```
Note that if the required marker isn't detected for 0.5 seconds after the `navigate` command, the command will be ignored. Note that if the required marker isn't detected for 0.5 seconds after the `navigate` command, the command will be ignored.
These frames may also be used in other services that accept TF frames (like `get_telemetry`). The following code will get the drone's position relative to the marker with id 3: These frames may also be used in other services that accept TF frames (like `get_telemetry`). The following code will get the drone's position relative to the marker with id 3:

View File

@@ -2,7 +2,7 @@
<img src="../assets/blocks/blockly.svg" width=200 align="right"> <img src="../assets/blocks/blockly.svg" width=200 align="right">
Visual blocks programming feature has been added to the [RPi image](image.md), starting with the version **0.21**. Blocks programming is implemented using [Google Blockly](https://developers.google.com/blockly) platform. Blocks programming integration can lower the entry barrier to a minimum. Visual blocks programming feature has been added to the [RPi image](image.md), starting with the version **0.21**. Blocks programming is implemented using [Google Blockly](https://developers.google.com/blockly) library. Blocks programming integration can lower the entry barrier to a minimum.
## Configuration ## Configuration

View File

@@ -1,6 +1,6 @@
# Working with the camera # Working with the camera
> **Note** In the image version **0.20** `clever` package was renamed to `clover`. See [previous version of the article](https://github.com/CopterExpress/clover/blob/v0.19/docs/en/camera.md) for older images. > **Note** The following applies to the [image version **0.24**](https://github.com/CopterExpress/clover/releases/tag/v0.24), which is not yet released. Older documentation is still available for [for version **0.23**](https://github.com/CopterExpress/clover/blob/f78a03ec8943b596d5a99b893188a159d5319888/docs/en/camera.md).
Make sure the camera is enabled in the `~/catkin_ws/src/clover/clover/launch/clover.launch` file: Make sure the camera is enabled in the `~/catkin_ws/src/clover/clover/launch/clover.launch` file:
@@ -16,7 +16,7 @@ The `clover` service must be restarted after the launch-file has been edited:
sudo systemctl restart clover sudo systemctl restart clover
``` ```
You may use rqt or [web_video_server](web_video_server.md) to view the camera stream. You may use [rqt](rviz.md) or [web_video_server](web_video_server.md) to view the camera stream.
## Troubleshooting ## Troubleshooting
@@ -54,8 +54,6 @@ The [SD card image](image.md) comes with a preinstalled [OpenCV](https://opencv.
### Python ### Python
Main article: http://wiki.ros.org/cv_bridge/Tutorials/ConvertingBetweenROSImagesAndOpenCVImagesPython.
An example of creating a subscriber for a topic with an image from the main camera for processing with OpenCV: An example of creating a subscriber for a topic with an image from the main camera for processing with OpenCV:
```python ```python
@@ -63,12 +61,14 @@ import rospy
import cv2 import cv2
from sensor_msgs.msg import Image from sensor_msgs.msg import Image
from cv_bridge import CvBridge from cv_bridge import CvBridge
from clover import long_callback
rospy.init_node('computer_vision_sample') rospy.init_node('cv')
bridge = CvBridge() bridge = CvBridge()
@long_callback
def image_callback(data): def image_callback(data):
cv_image = bridge.imgmsg_to_cv2(data, 'bgr8') # OpenCV image img = bridge.imgmsg_to_cv2(data, 'bgr8') # OpenCV image
# Do any image processing with cv2... # Do any image processing with cv2...
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback) image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback)
@@ -76,19 +76,31 @@ image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback)
rospy.spin() rospy.spin()
``` ```
> **Note** Image processing may take significant time to finish. This can cause an [issue](https://github.com/ros/ros_comm/issues/1901) in rospy library, which would lead to processing stale camera frames. To solve this problem you need to use `long_callback` decorator from `clover` library, as in the example above.
#### Limiting CPU usage
When using the `main_camera/image_raw` topic, the script will process the maximum number of frames from the camera, actively utilizing the CPU (up to 100%). In tasks, where processing each camera frame is not critical, you can use the topic, where the frames are published at rate 5 Hz: `main_camera/image_raw_throttled`:
```python
image_sub = rospy.Subscriber('main_camera/image_raw_throttled', Image, image_callback, queue_size=1)
```
#### Publishing images
To debug image processing, you can publish a separate topic with the processed image: To debug image processing, you can publish a separate topic with the processed image:
```python ```python
image_pub = rospy.Publisher('~debug', Image) image_pub = rospy.Publisher('~debug', Image)
``` ```
Publishing the processed image (at the end of the image_callback function): Publishing the processed image:
```python ```python
image_pub.publish(bridge.cv2_to_imgmsg(cv_image, 'bgr8')) image_pub.publish(bridge.cv2_to_imgmsg(img, 'bgr8'))
``` ```
The obtained images can be viewed using [web_video_server](web_video_server.md). The published images can be viewed using [web_video_server](web_video_server.md) or [rqt](rviz.md).
#### Retrieving one frame #### Retrieving one frame
@@ -99,7 +111,7 @@ import rospy
from sensor_msgs.msg import Image from sensor_msgs.msg import Image
from cv_bridge import CvBridge from cv_bridge import CvBridge
rospy.init_node('computer_vision_sample') rospy.init_node('cv')
bridge = CvBridge() bridge = CvBridge()
# ... # ...
@@ -121,38 +133,32 @@ QR codes recognition in Python:
```python ```python
import rospy import rospy
from pyzbar import pyzbar from pyzbar import pyzbar
import cv2
from cv_bridge import CvBridge from cv_bridge import CvBridge
from sensor_msgs.msg import Image from sensor_msgs.msg import Image
from clover import long_callback
rospy.init_node('cv')
bridge = CvBridge() bridge = CvBridge()
rospy.init_node('barcode_test') @long_callback
def image_callback(msg):
# Image subscriber callback function img = bridge.imgmsg_to_cv2(msg, 'bgr8')
def image_callback(data): barcodes = pyzbar.decode(img)
cv_image = bridge.imgmsg_to_cv2(data, 'bgr8') # OpenCV image
barcodes = pyzbar.decode(cv_image)
for barcode in barcodes: for barcode in barcodes:
b_data = barcode.data.decode("utf-8") b_data = barcode.data.decode('utf-8')
b_type = barcode.type b_type = barcode.type
(x, y, w, h) = barcode.rect (x, y, w, h) = barcode.rect
xc = x + w/2 xc = x + w/2
yc = y + h/2 yc = y + h/2
print("Found {} with data {} with center at x={}, y={}".format(b_type, b_data, xc, yc)) print('Found {} with data {} with center at x={}, y={}'.format(b_type, b_data, xc, yc))
image_sub = rospy.Subscriber('main_camera/image_raw', Image, image_callback, queue_size=1) image_sub = rospy.Subscriber('main_camera/image_raw_throttled', Image, image_callback, queue_size=1)
rospy.spin() rospy.spin()
``` ```
The script will take up to 100% CPU capacity. To slow down the script artificially, you can use [throttling](http://wiki.ros.org/topic_tools/throttle) of frames from the camera, for example, at 5 Hz (`main_camera.launch`): > **Hint** See other computer vision examples in the `~/examples` directory of the [RPi image](image.md).
```xml
<node pkg="topic_tools" name="cam_throttle" type="throttle"
args="messages main_camera/image_raw 5.0 main_camera/image_raw_throttled"/>
```
The topic for the subscriber in this case should be changed for `main_camera/image_raw_throttled`.
## Video recording ## Video recording

View File

@@ -30,6 +30,16 @@ Print path to the current directory:
pwd pwd
``` ```
Go to the user's home directory:
```bash
# all three commands are equivalent, where the tilde character (~) is an abbreviated
# path entry to the home directory, and the $HOME variable stores this path
cd
cd ~
cd $HOME
```
Print contents of the `file.py` file: Print contents of the `file.py` file:
```bash ```bash

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