Compare commits

..

132 Commits

Author SHA1 Message Date
Oleg Kalachev
20a229a954 docs: add link to raspberry article in glossary 2019-08-09 00:50:30 +03:00
Oleg Kalachev
aae9eec42f docs: add more info to glossary 2019-08-09 00:48:56 +03:00
Oleg Kalachev
6e4e25a2cb docs: merge safety articles 2019-08-08 22:31:32 +03:00
Oleg Kalachev
11555d7d70 docs: start working on clever 4 instruction 2019-05-13 08:30:04 +03:00
Oleg Kalachev
66e21443a9 docs: update en 2019-05-13 07:07:36 +03:00
Oleg Kalachev
81c3d6d42b docs: small addition to contributing 2019-05-13 06:32:08 +03:00
Oleg Kalachev
9f4df86cae docs: make markdownlint happy 2019-05-13 06:27:56 +03:00
Oleg Kalachev
beca5f25e9 gitbook: set gitbook version to ^3.2.2 2019-05-13 03:01:08 +03:00
Ilya Petrov
10785183e1 docs: update in ceiling markers config 2019-05-11 14:29:40 +03:00
Oleg Kalachev
7c5af5f494 selfcheck.py: code syle 2019-05-11 14:19:42 +03:00
Oleg Kalachev
cabe76a607 selfcheck.py: "fusing" -> "fusion" in warnings 2019-05-11 14:04:03 +03:00
Oleg Kalachev
d2912aebd8 selfcheck.py: increase angular velocity limit 2019-05-11 10:16:27 +03:00
Oleg Kalachev
efb9bf2f7a selfcheck.py: don't fall when mavros/param/get is unavailable 2019-05-11 10:16:27 +03:00
Oleg Kalachev
7f8b78ad7d selfcheck.py: code style 2019-05-11 10:16:27 +03:00
Oleg Kalachev
34095bfaa7 selfcheck.py: check PX4 VPE parameters when vpe_publisher is running 2019-05-11 10:16:27 +03:00
Arthur
747f26742d Edit instruction about the connection of pixracer and RC 2019-05-08 17:33:00 +03:00
Oleg Kalachev
31f586070d image: fix espeak installation 2019-05-08 08:54:06 +03:00
sfalexrog
021aa69110 clever: Selfcheck improvements 2019-05-08 07:54:40 +03:00
sfalexrog
b5a01e6a7e selfcheck: Check logs for errors 2019-05-08 07:54:40 +03:00
Oleg Kalachev
5b02f59583 image: add espeak 2019-05-08 05:15:13 +03:00
Alamoris
1c33102b8f docs: revision of the article about sitl (#114)
* Add rus article about install ROS

* Add lines

* Add article about launching gazebo

* Some fix

* Add fix

* Fix

* Text fix

* Fix MD exceptions

* docs: edit sitl articles

* article fix
2019-05-08 04:18:09 +03:00
Oleg Kalachev
bca7445ebe docs: some fixes 2019-05-08 02:23:09 +03:00
Oleg Kalachev
d47a95e134 docs: add some rules to markdownlint + some editing 2019-05-08 02:19:42 +03:00
Oleg Kalachev
064e402a2d selfcheck.py: show error when exception occurred in check 2019-05-07 23:46:34 +03:00
Arthur Golubtsov
5b617d91a9 docs: add article about clever image building (#121)
* Add article about clever image building

* Image building article: fix text style and add link to SUMMARY.md
2019-05-07 17:28:30 +03:00
sfalexrog
2a4dce3e09 builder: Show actual retry count 2019-05-06 21:27:04 +03:00
sfalexrog
cf9b7abcfa builder: Try even harder to update rosdep 2019-05-06 20:02:18 +03:00
Oleg Kalachev
751caa906c Revert "Verify Google Webmaster (again)"
This reverts commit ff244345a7.
2019-05-06 06:55:17 +03:00
Oleg Kalachev
ff244345a7 Verify Google Webmaster (again) 2019-05-06 06:48:53 +03:00
sfalexrog
c73c7857c6 builder: Try harder to update rosdep 2019-05-05 11:04:08 +03:00
Oleg Kalachev
b9b4d762ea docs: make markdownlint happy 2019-05-05 00:25:17 +03:00
mmkuznecov
8929fd534f docs: article about object counting (#120)
* Add files via upload

* Add files via upload

* Update SUMMARY.md

* Update SUMMARY.md

* Update object_counting.md

* Update object_counting.md

* Update object_counting.md

* Update object_counting.md

* Add files via upload

* Update object_counting.md

* Delete giff.gif

* Delete people_static.gif

* Update object_counting.md
2019-05-04 22:57:29 +03:00
sfalexrog
dff4487d9b travis: Deploy docs to master 2019-05-04 19:14:24 +03:00
sfalexrog
00c12ed305 travis: Deploy to github pages 2019-05-04 19:08:25 +03:00
Oleg Kalachev
91cae1e4c6 Revert "Verify Google Webmaster"
This reverts commit 73f600b41b.
2019-05-01 18:23:06 +03:00
Oleg Kalachev
328572f7b1 Gitbook update 2019-05-01 18:02:53 +03:00
Oleg Kalachev
73f600b41b Verify Google Webmaster 2019-05-01 17:53:41 +03:00
Arthur
2a5d511b2b gen_changelog: Fix script failure when there is no tag in repo 2019-04-30 13:40:14 +03:00
sfalexrog
bc0039ccb7 ESP8266 module documentation (#117)
* docs: Start working on ESP8266 article

* docs: More work on esp8266 article

* docs: esp8266 article fixes
2019-04-29 17:59:51 +03:00
Ilya Petrov
378efd9fab dictionary parameter of aruco_map nodelet
aruco_map nodelet has it's own dictionary parameter
2019-04-29 00:00:34 +03:00
Oleg Kalachev
c2b974f407 docs: clarify summary a little 2019-04-25 23:29:40 +03:00
Oleg Kalachev
1778f1d9eb docs: remove Cyrillic comments from simple_offboard program stub 2019-04-25 23:12:57 +03:00
Oleg Kalachev
25ca8f8b97 Remove rangefinder offset 2019-04-25 19:00:31 +03:00
Oleg Kalachev
94c191f65f selfcheck.py: fix checking LPE_FLW_SCALE parameter 2019-04-23 01:59:54 +03:00
Andrei Korigodski
6f95037e56 docs: update article on GPIO 2019-04-18 18:41:38 +03:00
Oleg Kalachev
17916f931c docs: add article on GPIO 2019-04-18 18:21:59 +03:00
sfalexrog
d09dfba905 docs: Add note about web_video_server and Python 2019-04-16 15:44:57 +03:00
sfalexrog
e631459181 ros: Add rosshow package 2019-04-16 14:52:16 +03:00
Oleg Kalachev
86da07bca8 simple_offboard: param for performing land only in offboard mode 2019-04-13 16:10:04 +03:00
Oleg Kalachev
bb9f56f6a6 docs: nti: add info on utf coding in Python scripts 2019-04-11 14:03:02 +03:00
Oleg Kalachev
a37f58ada9 image: update Linux kernel version 2019-04-11 11:33:53 +03:00
Oleg Kalachev
a3bc692679 docs: pep-8 2019-04-10 14:30:41 +03:00
Oleg Kalachev
bd8b17a51d docs: restore info on led ros node in nti2019 2019-04-10 14:29:43 +03:00
Oleg Kalachev
9ac980f278 selfcheck.py: increase rangefinder timeout to 4 2019-04-10 13:46:17 +03:00
Oleg Kalachev
742041448d docs: edit nti article a little 2019-04-10 11:41:37 +03:00
Oleg Kalachev
fe83930e42 docs: remote info on ros node from nti 2019 2019-04-10 10:24:17 +03:00
Oleg Kalachev
a93ae126bc docs: very little fix 2019-04-10 10:07:00 +03:00
sfalexrog
199247c745 docs: Style fix 2019-04-10 09:50:34 +03:00
sfalexrog
4746f3181d docs: Add ros_ws281x example 2019-04-10 02:03:20 +03:00
Oleg Kalachev
74f84a22d9 docs: edit nti2019.md 2019-04-09 20:40:49 +03:00
Oleg Kalachev
d37ac64f64 docs: small fix for markdownlint 2019-04-09 18:03:01 +03:00
sfalexrog
3f94335554 docs: Add MQTT publishing example 2019-04-09 16:23:42 +03:00
sfalexrog
1d591965a3 init_rpi: Fix typo in sed command 2019-04-09 10:31:12 +03:00
sfalexrog
7519958698 init: Automatically set Clever hostname 2019-04-08 20:16:48 +03:00
Oleg Kalachev
be7624b309 docs: edit nti article 2019-04-08 16:11:09 +03:00
sfalexrog
e9e8c84ddf aruco_pose: Try to draw as much of axes as possible 2019-04-07 22:46:38 +03:00
Oleg Kalachev
6535943cc8 docs: fix 2019-04-07 19:15:05 +03:00
Oleg Kalachev
375b19146c docs: changes 2019-04-07 19:14:34 +03:00
Oleg Kalachev
77602021ae Merge branch 'master' of github.com:CopterExpress/clever 2019-04-07 19:12:40 +03:00
sfalexrog
0df66a8df7 Add annotated MQTT sample 2019-04-07 17:28:42 +03:00
Oleg Kalachev
ae9302bfc2 Remove +x flag from main_camera.launch 2019-04-07 16:35:29 +03:00
Oleg Kalachev
993cc50276 docs: edit nti.md 2019-04-07 16:12:02 +03:00
Oleg Kalachev
06bf2d5b56 docs: add some info on using clever for NTI 2019 2019-04-06 19:54:26 +03:00
Oleg Kalachev
db03222a19 docs: add some info on nti 2019 2019-04-04 22:14:24 +03:00
sfalexrog
fea6992964 builder: Fix kernel version 2019-04-04 20:54:42 +03:00
Oleg Kalachev
67ddfa6c5e docs: NTI olympics 2019 page stub created 2019-04-04 19:50:04 +03:00
sfalexrog
9e77a11cf5 builder: Update kernel version 2019-04-03 14:14:20 +03:00
Oleg Kalachev
928d2e38d4 gitbook: remove yandex webmaster validation 2019-04-03 00:01:46 +03:00
Oleg Kalachev
eb9b621662 gitbook: fix for yandex webmaster 2019-04-02 23:33:40 +03:00
Oleg Kalachev
dfd6736fb0 gitbook: remove redirect for a while 2019-04-02 23:29:37 +03:00
Oleg Kalachev
08ea466232 Yandex.Webmaster verification 2019-04-02 23:26:11 +03:00
sfalexrog
07b6dcde51 builder: Update base image 2019-04-02 20:47:33 +03:00
sfalexrog
66aa4729ad docs: Minor px4flow article fix 2019-04-02 18:07:39 +03:00
Oleg Kalachev
bb825c3c30 docs: edit px4flow article a little 2019-03-29 18:24:17 +03:00
sfalexrog
32635def32 PX4FLOW article (#113)
* px4flow: Start writing documentation

* px4flow: Add more information

* docs/px4flow: Pixracer connection and configuration

* docs/px4flow: Lint fixes
2019-03-29 18:21:25 +03:00
Alamoris
0342e7da39 Small fix 2019-03-29 17:23:22 +03:00
Oleg Kalachev
995a1395de Add articles on new aruco navigation to gitbook 2019-03-28 00:30:54 +03:00
Oleg Kalachev
99f207d0f6 selfcheck.py: small fix 2019-03-27 20:49:58 +03:00
Oleg Kalachev
29c401e5fa selfcheck.py: show failures when exception occurred 2019-03-27 20:49:58 +03:00
Arthur
b3c0e2d290 image: add ros and python paths for working with sudo 2019-03-27 13:29:40 +03:00
Arthur
d053571053 builder: remove get-pip.py after installation 2019-03-27 12:45:13 +03:00
Arthur
e59a0221ca image: change restart option for clever.service and roscore.service from on-abort to on-failure 2019-03-27 12:40:44 +03:00
Oleg Kalachev
b53bf19c8d simple_offboard: comment out incorrect yaw calculation if yaw=nan 2019-03-27 08:11:54 +03:00
Oleg Kalachev
b6c493513c vpe_publisher: reduce offset_timeout to make vpe faults less likely 2019-03-27 02:24:59 +03:00
Oleg Kalachev
24bf9f8907 ios app: add privacy policy in English 2019-03-27 00:17:07 +03:00
Oleg Kalachev
3338d42a77 iOS app: add confidentiality policy 2019-03-26 23:32:32 +03:00
sfalexrog
27e890825d docs/flow: Add note about SENS_FLOW_MAXR 2019-03-26 23:30:03 +03:00
Oleg Kalachev
68f810cd1a builder: remove python-rosinstall-generator version fix 2019-03-26 22:10:13 +03:00
Oleg Kalachev
0c872a101f gitbook: add link to the English version 2019-03-26 21:54:00 +03:00
sfalexrog
04c33d5b03 aruco_pose/draw: Be more strict about drawing axis 2019-03-21 21:52:20 +03:00
Oleg Kalachev
b4e8d9b18a aruco_pose: little style fix 2019-03-21 20:58:44 +03:00
Oleg Kalachev
e601080a95 aruco_pose: add param auto_flip 2019-03-21 20:55:35 +03:00
Oleg Kalachev
84c16a7296 aruco_pose: fix snapping alrogithm 2019-03-21 20:55:35 +03:00
Oleg Kalachev
c227910431 aruco_pose: fix office ceiling map 2019-03-21 20:55:35 +03:00
sfalexrog
acec09192b aruco_map: Try to fix frame drawing bug
`cv::aruco::drawAxis` would attempt to draw detected frame origin even when it's behind the camera. This resulted in an invalid, flipped frame displayed in `/aruco_map/debug`.

This commit prevents drawing frame axis if the frame origin (projected to the screen space) is behind the screen plane.
2019-03-20 16:14:29 +03:00
sfalexrog
03584e410b docs: Update docker SITL docs 2019-03-19 14:49:33 +03:00
Oleg Kalachev
c22f8b2a7c docs: small fix 2019-03-19 01:20:30 +03:00
Oleg Kalachev
445b6022c6 gitbook: add some redirects 2019-03-19 00:58:10 +03:00
Oleg Kalachev
c2994e520a gitbook: redirect from aruco/ 2019-03-19 00:43:26 +03:00
Oleg Kalachev
d2b13aff92 optical_flow: use try when transform from camera to base_link frame 2019-03-18 23:50:57 +03:00
garinegor
63d3449cc5 Added clever blocks programming (#111)
* added clever blocks markdown

* added the 2nd way of downloading  project

* spelling

* deleted 2 extra cd's
2019-03-17 20:06:44 +03:00
Oleg Kalachev
7c29d9d75a selfcheck.py: improve range sensor checking 2019-03-17 00:43:44 +03:00
Oleg Kalachev
fbaece0f88 selfcheck.py: check VPE settings for EKF2 2019-03-17 00:36:12 +03:00
Oleg Kalachev
4721f39c24 selfcheck.py: check optical flow parameters for EKF2 2019-03-17 00:31:17 +03:00
Oleg Kalachev
407e40136f docs: add more info on selfcheck 2019-03-16 04:21:38 +03:00
Oleg Kalachev
89bd502216 selfcheck.py: check some PX4 parameters 2019-03-16 04:09:39 +03:00
Oleg Kalachev
6e6aace884 Typo 2019-03-16 03:08:49 +03:00
Oleg Kalachev
ad0f952f74 Add page for 3D visualization of markers map 2019-03-15 21:46:51 +03:00
Oleg Kalachev
05791bb0bf genmap.py: remove unused arguments 2019-03-15 19:56:34 +03:00
Alamoris
9cbfc5b687 Fix top left setting 2019-03-15 19:51:10 +03:00
sfalexrog
df0f1c9df0 SITL: Address requested changes 2019-03-15 19:05:59 +03:00
sfalexrog
46ce55f7dd SITL: Docker-based SITL documentation 2019-03-15 19:05:59 +03:00
Oleg Kalachev
60ebdab19f selfcheck.py: fix rangefinder checking 2019-03-15 17:39:24 +03:00
Alamoris
bfcba26df2 Delete extra photos 2019-03-15 02:20:05 +03:00
Oleg Kalachev
cac05d5231 docs: more info on optical flow 2019-03-15 00:03:42 +03:00
Oleg Kalachev
fd2f0a5394 docs: remove experimental warning from flow article 2019-03-15 00:00:16 +03:00
Oleg Kalachev
3906c4242b docs: typo 2019-03-14 23:45:49 +03:00
Oleg Kalachev
6fae8df7f6 docs: tune optical flow settings 2019-03-14 23:20:09 +03:00
Oleg Kalachev
7f70e0e2e4 docs: tune vision variances 2019-03-14 23:00:13 +03:00
Oleg Kalachev
3aedddd97f docs: autogenerate link to latest clever firmware 2019-03-14 22:59:57 +03:00
Oleg Kalachev
cdda65fe92 docs: clever 3 assemble: add info on 4 ESCs 2019-03-14 19:50:15 +03:00
sfalexrog
6e31667ca1 travis: Generate changelog for tags 2019-03-14 17:03:55 +03:00
155 changed files with 3658 additions and 433 deletions

View File

@@ -24,6 +24,8 @@
"Python", "Python",
"C++", "C++",
"PX4", "PX4",
"px4.io",
"logs.px4.io",
"QGroundControl", "QGroundControl",
"QGC", "QGC",
"WireShark", "WireShark",
@@ -38,6 +40,10 @@
"RPi", "RPi",
"Linux", "Linux",
"Windows", "Windows",
"Docker",
"Travis",
"travis-ci.org",
"travis-ci.com",
"macOS", "macOS",
"iOS", "iOS",
"Android", "Android",

View File

@@ -10,10 +10,11 @@ env:
- IMAGE_NAME="$(basename -s '.git' ${TARGET_REPO})_${IMAGE_VERSION}.img" - IMAGE_NAME="$(basename -s '.git' ${TARGET_REPO})_${IMAGE_VERSION}.img"
git: git:
depth: 50 depth: 50
matrix: jobs:
fast_finish: true fast_finish: true
include: include:
- name: "Raspberry Pi Image Build" - stage: Build
name: "Raspberry Pi Image Build"
cache: cache:
directories: directories:
- imgcache - imgcache
@@ -40,7 +41,8 @@ matrix:
tags: true tags: true
draft: true draft: true
name: ${TRAVIS_TAG} name: ${TRAVIS_TAG}
- name: "Documentation" - stage: Build
name: "Documentation"
language: node_js language: node_js
node_js: node_js:
- "10" - "10"
@@ -53,20 +55,40 @@ matrix:
- markdownlint docs - markdownlint docs
- gitbook install - gitbook install
- gitbook build - gitbook build
# *** deploy:
# Disable deployments for now, revisit this later provider: pages
# --sfalexrog, 06.02.2019 local-dir: _book
# *** skip-cleanup: true
# deploy: github-token: ${GITHUB_OAUTH_TOKEN}
# provider: pages keep-history: true
# local-dir: _book target-branch: master
# skip-cleanup: true repo: CopterExpress/clever-gitbook
# github-token: ${GITHUB_OAUTH_TOKEN} fqdn: clever.copterexpress.com
# keep-history: true verbose: true
# target-branch: gh-pages on:
# on: branch: master
# branch: WIP/gitbook-autobuild deploy:
provider: pages
local-dir: _book
skip-cleanup: true
github-token: ${GITHUB_OAUTH_TOKEN}
keep-history: false
target-branch: master
repo: okalachev/cl4wip
verbose: true
on:
branch: clever4
- stage: Annotate
name: Auto-generate changelog
language: python
python: 3.6
install:
- pip install GitPython PyGithub
script:
- PYTHONUNBUFFERED=1 python ./gen_changelog.py
stages:
- Build
- Annotate
# More info there # More info there
# https://github.com/travis-ci/travis-ci/issues/6893 # https://github.com/travis-ci/travis-ci/issues/6893
# https://docs.travis-ci.com/user/customizing-the-build/ # https://docs.travis-ci.com/user/customizing-the-build/

View File

@@ -1,5 +1,4 @@
iOS-приложение для управления Клевером # iOS-приложение для управления Клевером
--------------------------------------
Для установки зависимостей необходим [CocoaPods](https://cocoapods.org): Для установки зависимостей необходим [CocoaPods](https://cocoapods.org):
@@ -8,3 +7,11 @@ pod install
``` ```
Для разработки и сборки откройте в XCode файл `cleverrc.xcworkspace`. Для разработки и сборки откройте в XCode файл `cleverrc.xcworkspace`.
## Политика конфиденциальности
App Store приложение CLEVER RC не собирает и не хранит каких-либо личных данных пользователя.
## Privacy policy
The App Store app CLEVER RC does not collect and store any personal user data.

View File

@@ -74,6 +74,7 @@ It's recommended to run it within the same nodelet manager with the camera nodel
* `~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)
* `~dictionary` (*int*)  ArUco dictionary (default: 2) - should be the same as `dictionary` parameter of `aruco_detect` nodelet
Map file has one marker per line with the following line format: Map file has one marker per line with the following line format:

View File

@@ -1,8 +1,8 @@
14 0.365 0.000 0.0 0 0 0 0 14 0.365 0.000 0.0 0 0 0 0
15 0.365 1.335 0.0 0 0 0 0 15 0.365 1.335 0.0 0 0 0 0
30 0.365 2.865 0.0 0 0 0 0 30 0.365 2.865 0.0 0 0 0 0
31 0.365 4.200 0.0 0 0 0 0 31 0.365 4.200 0.0 0 0 0 0
12 0.365 0.000 1.8 0 0 0 0 12 0.365 0.000 1.8 0 0 0 0
13 0.365 1.335 1.8 0 0 0 0 13 0.365 1.335 1.8 0 0 0 0
28 0.365 2.865 1.8 0 0 0 0 28 0.365 2.865 1.8 0 0 0 0
29 0.365 4.200 1.8 0 0 0 0 29 0.365 4.200 1.8 0 0 0 0
@@ -22,10 +22,10 @@
5 0.365 1.335 9.0 0 0 0 0 5 0.365 1.335 9.0 0 0 0 0
20 0.365 2.865 9.0 0 0 0 0 20 0.365 2.865 9.0 0 0 0 0
21 0.365 4.200 9.0 0 0 0 0 21 0.365 4.200 9.0 0 0 0 0
2 0.365 0.000 0.8 0 0 0 0 2 0.365 0.000 10.8 0 0 0 0
3 0.365 1.335 0.8 0 0 0 0 3 0.365 1.335 10.8 0 0 0 0
18 0.365 2.865 0.8 0 0 0 0 18 0.365 2.865 10.8 0 0 0 0
19 0.365 4.200 0.8 0 0 0 0 19 0.365 4.200 10.8 0 0 0 0
1 0.365 0.000 2.6 0 0 0 0 1 0.365 0.000 12.6 0 0 0 0
0 0.365 1.335 2.6 0 0 0 0 0 0.365 1.335 12.6 0 0 0 0
16 0.365 2.865 2.6 0 0 0 0 16 0.365 2.865 12.6 0 0 0 0

View File

@@ -62,7 +62,7 @@ private:
image_transport::Publisher debug_pub_; image_transport::Publisher debug_pub_;
image_transport::CameraSubscriber img_sub_; image_transport::CameraSubscriber img_sub_;
ros::Publisher markers_pub_, vis_markers_pub_; ros::Publisher markers_pub_, vis_markers_pub_;
bool estimate_poses_, send_tf_; bool estimate_poses_, send_tf_, auto_flip_;
double length_; double length_;
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_tilt_;
@@ -87,6 +87,8 @@ public:
readLengthOverride(); readLengthOverride();
nh_priv_.param<std::string>("known_tilt", known_tilt_, ""); nh_priv_.param<std::string>("known_tilt", known_tilt_, "");
nh_priv_.param("auto_flip", auto_flip_, false);
nh_priv_.param<std::string>("frame_id_prefix", frame_id_prefix_, "aruco_"); nh_priv_.param<std::string>("frame_id_prefix", frame_id_prefix_, "aruco_");
camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F); camera_matrix_ = cv::Mat::zeros(3, 3, CV_64F);
@@ -177,7 +179,7 @@ private:
// snap orientation (if enabled and snap frame available) // snap orientation (if enabled and snap frame available)
if (!known_tilt_.empty() && !snap_to.header.frame_id.empty()) { if (!known_tilt_.empty() && !snap_to.header.frame_id.empty()) {
snapOrientation(marker.pose.orientation, snap_to.transform.rotation); snapOrientation(marker.pose.orientation, snap_to.transform.rotation, auto_flip_);
} }
// TODO: check IDs are unique // TODO: check IDs are unique

View File

@@ -73,6 +73,7 @@ private:
visualization_msgs::MarkerArray vis_array_; visualization_msgs::MarkerArray vis_array_;
std::string known_tilt_; std::string known_tilt_;
int image_width_, image_height_, image_margin_; int image_width_, image_height_, image_margin_;
bool auto_flip_;
public: public:
virtual void onInit() virtual void onInit()
@@ -95,6 +96,7 @@ public:
nh_priv_.param<std::string>("type", type, "map"); nh_priv_.param<std::string>("type", type, "map");
nh_priv_.param<std::string>("frame_id", transform_.child_frame_id, "aruco_map"); nh_priv_.param<std::string>("frame_id", transform_.child_frame_id, "aruco_map");
nh_priv_.param<std::string>("known_tilt", known_tilt_, ""); nh_priv_.param<std::string>("known_tilt", known_tilt_, "");
nh_priv_.param("auto_flip", auto_flip_, false);
nh_priv_.param("image_width", image_width_, 2000); nh_priv_.param("image_width", image_width_, 2000);
nh_priv_.param("image_height", image_height_, 2000); nh_priv_.param("image_height", image_height_, 2000);
nh_priv_.param("image_margin", image_margin_, 200); nh_priv_.param("image_margin", image_margin_, 200);
@@ -183,7 +185,7 @@ public:
try { try {
geometry_msgs::TransformStamped snap_to = tf_buffer_.lookupTransform(markers->header.frame_id, geometry_msgs::TransformStamped snap_to = tf_buffer_.lookupTransform(markers->header.frame_id,
known_tilt_, markers->header.stamp, ros::Duration(0.02)); known_tilt_, markers->header.stamp, ros::Duration(0.02));
snapOrientation(transform_.transform.rotation, snap_to.transform.rotation); snapOrientation(transform_.transform.rotation, snap_to.transform.rotation, auto_flip_);
} catch (const tf2::TransformException& e) { } catch (const tf2::TransformException& e) {
ROS_WARN_THROTTLE(1, "aruco_map: can't snap: %s", e.what()); ROS_WARN_THROTTLE(1, "aruco_map: can't snap: %s", e.what());
} }
@@ -217,7 +219,7 @@ publish_debug:
Mat mat = cv_bridge::toCvCopy(image, "bgr8")->image; // copy image as we're planning to modify it Mat mat = cv_bridge::toCvCopy(image, "bgr8")->image; // copy image as we're planning to modify it
cv::aruco::drawDetectedMarkers(mat, corners, ids); // draw detected markers cv::aruco::drawDetectedMarkers(mat, corners, ids); // draw detected markers
if (valid) { if (valid) {
cv::aruco::drawAxis(mat, camera_matrix_, dist_coeffs_, rvec, tvec, 1.0); // draw board axis _drawAxis(mat, camera_matrix_, dist_coeffs_, rvec, tvec, 1.0); // draw board axis
} }
cv_bridge::CvImage out_msg; cv_bridge::CvImage out_msg;
out_msg.header.frame_id = image->header.frame_id; out_msg.header.frame_id = image->header.frame_id;

View File

@@ -6,6 +6,22 @@
using namespace cv; using namespace cv;
using namespace cv::aruco; using namespace cv::aruco;
static void _cvProjectPoints2( const CvMat* object_points, const CvMat* rotation_vector,
const CvMat* translation_vector, const CvMat* camera_matrix,
const CvMat* distortion_coeffs, CvMat* image_points,
CvMat* dpdrot CV_DEFAULT(NULL), CvMat* dpdt CV_DEFAULT(NULL),
CvMat* dpdf CV_DEFAULT(NULL), CvMat* dpdc CV_DEFAULT(NULL),
CvMat* dpddist CV_DEFAULT(NULL),
double aspect_ratio CV_DEFAULT(0));
static void _projectPoints( InputArray objectPoints,
InputArray rvec, InputArray tvec,
InputArray cameraMatrix, InputArray distCoeffs,
OutputArray imagePoints,
OutputArray jacobian = noArray(),
double aspectRatio = 0 );
void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginSize, void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginSize,
int borderBits) { int borderBits) {
@@ -85,3 +101,697 @@ void _drawPlanarBoard(Board *_board, Size outSize, OutputArray _img, int marginS
BORDER_TRANSPARENT); BORDER_TRANSPARENT);
} }
} }
/* Draw a (potentially partially visible) line. */
static void linePartial(InputOutputArray image, Point3f p1, Point3f p2, const Scalar& color,
int thickness = 1, int lineType = LINE_8, int shift = 0)
{
// If both points are behind the screen, don't draw anything
if (p1.z <= 0 && p2.z <= 0)
{
return;
}
Point2f p1p{p1.x, p1.y};
Point2f p2p{p2.x, p2.y};
// If points are on the different sides of the plane, compute intersection point
if (p1.z * p2.z < 0)
{
// Compute intersection point with the screen
// We denote alpha as such:
// xi = (1 - alpha) * x1 + alpha * x2
// yi = (1 - alpha) * y1 + alpha * y2
// zi = (1 - alpha) * z1 + alpha * z2 = 0
// Thus, alpha can be expressed as
// alpha = z1 / (z1 - z2)
float alpha = p1.z / (p1.z - p2.z);
Point2f pi{(1 - alpha) * p1.x + alpha * p2.x, (1 - alpha) * p1.y + alpha * p2.y};
// Now, if z1 is negative, we draw the line from (xi, yi) to (x2, y2), else we draw from (x1, y1) to (xi, yi)
if (p1.z < 0)
{
p1p = pi;
}
else
{
p2p = pi;
}
}
line(image, p1p, p2p, color, thickness, lineType, shift);
}
void _drawAxis(InputOutputArray _image, InputArray _cameraMatrix, InputArray _distCoeffs,
InputArray _rvec, InputArray _tvec, float length) {
CV_Assert(_image.getMat().total() != 0 &&
(_image.getMat().channels() == 1 || _image.getMat().channels() == 3));
CV_Assert(length > 0);
// project axis points
std::vector< Point3f > axisPoints;
axisPoints.push_back(Point3f(0, 0, 0));
axisPoints.push_back(Point3f(length, 0, 0));
axisPoints.push_back(Point3f(0, length, 0));
axisPoints.push_back(Point3f(0, 0, length));
std::vector< Point3f > imagePointsZ;
_projectPoints(axisPoints, _rvec, _tvec, _cameraMatrix, _distCoeffs, imagePointsZ);
// draw axis lines
linePartial(_image, imagePointsZ[0], imagePointsZ[1], Scalar(0, 0, 255), 3);
linePartial(_image, imagePointsZ[0], imagePointsZ[2], Scalar(0, 255, 0), 3);
linePartial(_image, imagePointsZ[0], imagePointsZ[3], Scalar(255, 0, 0), 3);
}
static CvMat _cvMat(const cv::Mat& m)
{
CvMat self;
CV_DbgAssert(m.dims <= 2);
self = cvMat(m.rows, m.dims == 1 ? 1 : m.cols, m.type(), m.data);
self.step = (int)m.step[0];
self.type = (self.type & ~cv::Mat::CONTINUOUS_FLAG) | (m.flags & cv::Mat::CONTINUOUS_FLAG);
return self;
}
static void _projectPoints( InputArray _opoints,
InputArray _rvec,
InputArray _tvec,
InputArray _cameraMatrix,
InputArray _distCoeffs,
OutputArray _ipoints,
OutputArray _jacobian,
double aspectRatio )
{
Mat opoints = _opoints.getMat();
int npoints = opoints.checkVector(3), depth = opoints.depth();
CV_Assert(npoints >= 0 && (depth == CV_32F || depth == CV_64F));
CvMat dpdrot, dpdt, dpdf, dpdc, dpddist;
CvMat *pdpdrot=0, *pdpdt=0, *pdpdf=0, *pdpdc=0, *pdpddist=0;
CV_Assert( _ipoints.needed() );
_ipoints.create(npoints, 1, CV_MAKETYPE(depth, 3), -1, true);
Mat imagePoints = _ipoints.getMat();
CvMat c_imagePoints = _cvMat(imagePoints);
CvMat c_objectPoints = _cvMat(opoints);
Mat cameraMatrix = _cameraMatrix.getMat();
Mat rvec = _rvec.getMat(), tvec = _tvec.getMat();
CvMat c_cameraMatrix = _cvMat(cameraMatrix);
CvMat c_rvec = _cvMat(rvec), c_tvec = _cvMat(tvec);
double dc0buf[5]={0};
Mat dc0(5,1,CV_64F,dc0buf);
Mat distCoeffs = _distCoeffs.getMat();
if( distCoeffs.empty() )
distCoeffs = dc0;
CvMat c_distCoeffs = _cvMat(distCoeffs);
int ndistCoeffs = distCoeffs.rows + distCoeffs.cols - 1;
Mat jacobian;
if( _jacobian.needed() )
{
_jacobian.create(npoints*2, 3+3+2+2+ndistCoeffs, CV_64F);
jacobian = _jacobian.getMat();
pdpdrot = &(dpdrot = _cvMat(jacobian.colRange(0, 3)));
pdpdt = &(dpdt = _cvMat(jacobian.colRange(3, 6)));
pdpdf = &(dpdf = _cvMat(jacobian.colRange(6, 8)));
pdpdc = &(dpdc = _cvMat(jacobian.colRange(8, 10)));
pdpddist = &(dpddist = _cvMat(jacobian.colRange(10, 10+ndistCoeffs)));
}
_cvProjectPoints2( &c_objectPoints, &c_rvec, &c_tvec, &c_cameraMatrix, &c_distCoeffs,
&c_imagePoints, pdpdrot, pdpdt, pdpdf, pdpdc, pdpddist, aspectRatio );
}
namespace _detail
{
template <typename FLOAT>
void computeTiltProjectionMatrix(FLOAT tauX,
FLOAT tauY,
Matx<FLOAT, 3, 3>* matTilt = 0,
Matx<FLOAT, 3, 3>* dMatTiltdTauX = 0,
Matx<FLOAT, 3, 3>* dMatTiltdTauY = 0,
Matx<FLOAT, 3, 3>* invMatTilt = 0)
{
FLOAT cTauX = cos(tauX);
FLOAT sTauX = sin(tauX);
FLOAT cTauY = cos(tauY);
FLOAT sTauY = sin(tauY);
Matx<FLOAT, 3, 3> matRotX = Matx<FLOAT, 3, 3>(1,0,0,0,cTauX,sTauX,0,-sTauX,cTauX);
Matx<FLOAT, 3, 3> matRotY = Matx<FLOAT, 3, 3>(cTauY,0,-sTauY,0,1,0,sTauY,0,cTauY);
Matx<FLOAT, 3, 3> matRotXY = matRotY * matRotX;
Matx<FLOAT, 3, 3> matProjZ = Matx<FLOAT, 3, 3>(matRotXY(2,2),0,-matRotXY(0,2),0,matRotXY(2,2),-matRotXY(1,2),0,0,1);
if (matTilt)
{
// Matrix for trapezoidal distortion of tilted image sensor
*matTilt = matProjZ * matRotXY;
}
if (dMatTiltdTauX)
{
// Derivative with respect to tauX
Matx<FLOAT, 3, 3> dMatRotXYdTauX = matRotY * Matx<FLOAT, 3, 3>(0,0,0,0,-sTauX,cTauX,0,-cTauX,-sTauX);
Matx<FLOAT, 3, 3> dMatProjZdTauX = Matx<FLOAT, 3, 3>(dMatRotXYdTauX(2,2),0,-dMatRotXYdTauX(0,2),
0,dMatRotXYdTauX(2,2),-dMatRotXYdTauX(1,2),0,0,0);
*dMatTiltdTauX = (matProjZ * dMatRotXYdTauX) + (dMatProjZdTauX * matRotXY);
}
if (dMatTiltdTauY)
{
// Derivative with respect to tauY
Matx<FLOAT, 3, 3> dMatRotXYdTauY = Matx<FLOAT, 3, 3>(-sTauY,0,-cTauY,0,0,0,cTauY,0,-sTauY) * matRotX;
Matx<FLOAT, 3, 3> dMatProjZdTauY = Matx<FLOAT, 3, 3>(dMatRotXYdTauY(2,2),0,-dMatRotXYdTauY(0,2),
0,dMatRotXYdTauY(2,2),-dMatRotXYdTauY(1,2),0,0,0);
*dMatTiltdTauY = (matProjZ * dMatRotXYdTauY) + (dMatProjZdTauY * matRotXY);
}
if (invMatTilt)
{
FLOAT inv = 1./matRotXY(2,2);
Matx<FLOAT, 3, 3> invMatProjZ = Matx<FLOAT, 3, 3>(inv,0,inv*matRotXY(0,2),0,inv,inv*matRotXY(1,2),0,0,1);
*invMatTilt = matRotXY.t()*invMatProjZ;
}
}
}
static const char* cvDistCoeffErr = "Distortion coefficients must be 1x4, 4x1, 1x5, 5x1, 1x8, 8x1, 1x12, 12x1, 1x14 or 14x1 floating-point vector";
static void _cvProjectPoints2Internal( const CvMat* objectPoints,
const CvMat* r_vec,
const CvMat* t_vec,
const CvMat* A,
const CvMat* distCoeffs,
CvMat* imagePoints, CvMat* dpdr CV_DEFAULT(NULL),
CvMat* dpdt CV_DEFAULT(NULL), CvMat* dpdf CV_DEFAULT(NULL),
CvMat* dpdc CV_DEFAULT(NULL), CvMat* dpdk CV_DEFAULT(NULL),
CvMat* dpdo CV_DEFAULT(NULL),
double aspectRatio CV_DEFAULT(0) )
{
Ptr<CvMat> matM, _m;
Ptr<CvMat> _dpdr, _dpdt, _dpdc, _dpdf, _dpdk;
Ptr<CvMat> _dpdo;
int i, j, count;
int calc_derivatives;
const CvPoint3D64f* M;
CvPoint3D64f* m;
double r[3], R[9], dRdr[27], t[3], a[9], k[14] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0}, fx, fy, cx, cy;
Matx33d matTilt = Matx33d::eye();
Matx33d dMatTiltdTauX(0,0,0,0,0,0,0,-1,0);
Matx33d dMatTiltdTauY(0,0,0,0,0,0,1,0,0);
CvMat _r, _t, _a = cvMat( 3, 3, CV_64F, a ), _k;
CvMat matR = cvMat( 3, 3, CV_64F, R ), _dRdr = cvMat( 3, 9, CV_64F, dRdr );
double *dpdr_p = 0, *dpdt_p = 0, *dpdk_p = 0, *dpdf_p = 0, *dpdc_p = 0;
double* dpdo_p = 0;
int dpdr_step = 0, dpdt_step = 0, dpdk_step = 0, dpdf_step = 0, dpdc_step = 0;
int dpdo_step = 0;
bool fixedAspectRatio = aspectRatio > FLT_EPSILON;
if( !CV_IS_MAT(objectPoints) || !CV_IS_MAT(r_vec) ||
!CV_IS_MAT(t_vec) || !CV_IS_MAT(A) ||
/*!CV_IS_MAT(distCoeffs) ||*/ !CV_IS_MAT(imagePoints) )
CV_Error( CV_StsBadArg, "One of required arguments is not a valid matrix" );
int total = objectPoints->rows * objectPoints->cols * CV_MAT_CN(objectPoints->type);
if(total % 3 != 0)
{
//we have stopped support of homogeneous coordinates because it cause ambiguity in interpretation of the input data
CV_Error( CV_StsBadArg, "Homogeneous coordinates are not supported" );
}
count = total / 3;
if( CV_IS_CONT_MAT(objectPoints->type) &&
(CV_MAT_DEPTH(objectPoints->type) == CV_32F || CV_MAT_DEPTH(objectPoints->type) == CV_64F)&&
((objectPoints->rows == 1 && CV_MAT_CN(objectPoints->type) == 3) ||
(objectPoints->rows == count && CV_MAT_CN(objectPoints->type)*objectPoints->cols == 3) ||
(objectPoints->rows == 3 && CV_MAT_CN(objectPoints->type) == 1 && objectPoints->cols == count)))
{
matM.reset(cvCreateMat( objectPoints->rows, objectPoints->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(objectPoints->type)) ));
cvConvert(objectPoints, matM);
}
else
{
// matM = cvCreateMat( 1, count, CV_64FC3 );
// cvConvertPointsHomogeneous( objectPoints, matM );
CV_Error( CV_StsBadArg, "Homogeneous coordinates are not supported" );
}
if( CV_IS_CONT_MAT(imagePoints->type) &&
(CV_MAT_DEPTH(imagePoints->type) == CV_32F || CV_MAT_DEPTH(imagePoints->type) == CV_64F) &&
((imagePoints->rows == 1 && CV_MAT_CN(imagePoints->type) == 3) ||
(imagePoints->rows == count && CV_MAT_CN(imagePoints->type)*imagePoints->cols == 3) ||
(imagePoints->rows == 3 && CV_MAT_CN(imagePoints->type) == 1 && imagePoints->cols == count)))
{
_m.reset(cvCreateMat( imagePoints->rows, imagePoints->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(imagePoints->type)) ));
cvConvert(imagePoints, _m);
}
else
{
// _m = cvCreateMat( 1, count, CV_64FC2 );
CV_Error( CV_StsBadArg, "Homogeneous coordinates are not supported" );
}
M = (CvPoint3D64f*)matM->data.db;
m = (CvPoint3D64f*)_m->data.db;
if( (CV_MAT_DEPTH(r_vec->type) != CV_64F && CV_MAT_DEPTH(r_vec->type) != CV_32F) ||
(((r_vec->rows != 1 && r_vec->cols != 1) ||
r_vec->rows*r_vec->cols*CV_MAT_CN(r_vec->type) != 3) &&
((r_vec->rows != 3 && r_vec->cols != 3) || CV_MAT_CN(r_vec->type) != 1)))
CV_Error( CV_StsBadArg, "Rotation must be represented by 1x3 or 3x1 "
"floating-point rotation vector, or 3x3 rotation matrix" );
if( r_vec->rows == 3 && r_vec->cols == 3 )
{
_r = cvMat( 3, 1, CV_64FC1, r );
cvRodrigues2( r_vec, &_r );
cvRodrigues2( &_r, &matR, &_dRdr );
cvCopy( r_vec, &matR );
}
else
{
_r = cvMat( r_vec->rows, r_vec->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(r_vec->type)), r );
cvConvert( r_vec, &_r );
cvRodrigues2( &_r, &matR, &_dRdr );
}
if( (CV_MAT_DEPTH(t_vec->type) != CV_64F && CV_MAT_DEPTH(t_vec->type) != CV_32F) ||
(t_vec->rows != 1 && t_vec->cols != 1) ||
t_vec->rows*t_vec->cols*CV_MAT_CN(t_vec->type) != 3 )
CV_Error( CV_StsBadArg,
"Translation vector must be 1x3 or 3x1 floating-point vector" );
_t = cvMat( t_vec->rows, t_vec->cols, CV_MAKETYPE(CV_64F,CV_MAT_CN(t_vec->type)), t );
cvConvert( t_vec, &_t );
if( (CV_MAT_TYPE(A->type) != CV_64FC1 && CV_MAT_TYPE(A->type) != CV_32FC1) ||
A->rows != 3 || A->cols != 3 )
CV_Error( CV_StsBadArg, "Instrinsic parameters must be 3x3 floating-point matrix" );
cvConvert( A, &_a );
fx = a[0]; fy = a[4];
cx = a[2]; cy = a[5];
if( fixedAspectRatio )
fx = fy*aspectRatio;
if( distCoeffs )
{
if( !CV_IS_MAT(distCoeffs) ||
(CV_MAT_DEPTH(distCoeffs->type) != CV_64F &&
CV_MAT_DEPTH(distCoeffs->type) != CV_32F) ||
(distCoeffs->rows != 1 && distCoeffs->cols != 1) ||
(distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 4 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 5 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 8 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 12 &&
distCoeffs->rows*distCoeffs->cols*CV_MAT_CN(distCoeffs->type) != 14) )
CV_Error( CV_StsBadArg, cvDistCoeffErr );
_k = cvMat( distCoeffs->rows, distCoeffs->cols,
CV_MAKETYPE(CV_64F,CV_MAT_CN(distCoeffs->type)), k );
cvConvert( distCoeffs, &_k );
if(k[12] != 0 || k[13] != 0)
{
_detail::computeTiltProjectionMatrix(k[12], k[13],
&matTilt, &dMatTiltdTauX, &dMatTiltdTauY);
}
}
if( dpdr )
{
if( !CV_IS_MAT(dpdr) ||
(CV_MAT_TYPE(dpdr->type) != CV_32FC1 &&
CV_MAT_TYPE(dpdr->type) != CV_64FC1) ||
dpdr->rows != count*2 || dpdr->cols != 3 )
CV_Error( CV_StsBadArg, "dp/drot must be 2Nx3 floating-point matrix" );
if( CV_MAT_TYPE(dpdr->type) == CV_64FC1 )
{
_dpdr.reset(cvCloneMat(dpdr));
}
else
_dpdr.reset(cvCreateMat( 2*count, 3, CV_64FC1 ));
dpdr_p = _dpdr->data.db;
dpdr_step = _dpdr->step/sizeof(dpdr_p[0]);
}
if( dpdt )
{
if( !CV_IS_MAT(dpdt) ||
(CV_MAT_TYPE(dpdt->type) != CV_32FC1 &&
CV_MAT_TYPE(dpdt->type) != CV_64FC1) ||
dpdt->rows != count*2 || dpdt->cols != 3 )
CV_Error( CV_StsBadArg, "dp/dT must be 2Nx3 floating-point matrix" );
if( CV_MAT_TYPE(dpdt->type) == CV_64FC1 )
{
_dpdt.reset(cvCloneMat(dpdt));
}
else
_dpdt.reset(cvCreateMat( 2*count, 3, CV_64FC1 ));
dpdt_p = _dpdt->data.db;
dpdt_step = _dpdt->step/sizeof(dpdt_p[0]);
}
if( dpdf )
{
if( !CV_IS_MAT(dpdf) ||
(CV_MAT_TYPE(dpdf->type) != CV_32FC1 && CV_MAT_TYPE(dpdf->type) != CV_64FC1) ||
dpdf->rows != count*2 || dpdf->cols != 2 )
CV_Error( CV_StsBadArg, "dp/df must be 2Nx2 floating-point matrix" );
if( CV_MAT_TYPE(dpdf->type) == CV_64FC1 )
{
_dpdf.reset(cvCloneMat(dpdf));
}
else
_dpdf.reset(cvCreateMat( 2*count, 2, CV_64FC1 ));
dpdf_p = _dpdf->data.db;
dpdf_step = _dpdf->step/sizeof(dpdf_p[0]);
}
if( dpdc )
{
if( !CV_IS_MAT(dpdc) ||
(CV_MAT_TYPE(dpdc->type) != CV_32FC1 && CV_MAT_TYPE(dpdc->type) != CV_64FC1) ||
dpdc->rows != count*2 || dpdc->cols != 2 )
CV_Error( CV_StsBadArg, "dp/dc must be 2Nx2 floating-point matrix" );
if( CV_MAT_TYPE(dpdc->type) == CV_64FC1 )
{
_dpdc.reset(cvCloneMat(dpdc));
}
else
_dpdc.reset(cvCreateMat( 2*count, 2, CV_64FC1 ));
dpdc_p = _dpdc->data.db;
dpdc_step = _dpdc->step/sizeof(dpdc_p[0]);
}
if( dpdk )
{
if( !CV_IS_MAT(dpdk) ||
(CV_MAT_TYPE(dpdk->type) != CV_32FC1 && CV_MAT_TYPE(dpdk->type) != CV_64FC1) ||
dpdk->rows != count*2 || (dpdk->cols != 14 && dpdk->cols != 12 && dpdk->cols != 8 && dpdk->cols != 5 && dpdk->cols != 4 && dpdk->cols != 2) )
CV_Error( CV_StsBadArg, "dp/df must be 2Nx14, 2Nx12, 2Nx8, 2Nx5, 2Nx4 or 2Nx2 floating-point matrix" );
if( !distCoeffs )
CV_Error( CV_StsNullPtr, "distCoeffs is NULL while dpdk is not" );
if( CV_MAT_TYPE(dpdk->type) == CV_64FC1 )
{
_dpdk.reset(cvCloneMat(dpdk));
}
else
_dpdk.reset(cvCreateMat( dpdk->rows, dpdk->cols, CV_64FC1 ));
dpdk_p = _dpdk->data.db;
dpdk_step = _dpdk->step/sizeof(dpdk_p[0]);
}
if( dpdo )
{
if( !CV_IS_MAT( dpdo ) || ( CV_MAT_TYPE( dpdo->type ) != CV_32FC1
&& CV_MAT_TYPE( dpdo->type ) != CV_64FC1 )
|| dpdo->rows != count * 2 || dpdo->cols != count * 3 )
CV_Error( CV_StsBadArg, "dp/do must be 2Nx3N floating-point matrix" );
if( CV_MAT_TYPE( dpdo->type ) == CV_64FC1 )
{
_dpdo.reset( cvCloneMat( dpdo ) );
}
else
_dpdo.reset( cvCreateMat( 2 * count, 3 * count, CV_64FC1 ) );
cvZero(_dpdo);
dpdo_p = _dpdo->data.db;
dpdo_step = _dpdo->step / sizeof( dpdo_p[0] );
}
calc_derivatives = dpdr || dpdt || dpdf || dpdc || dpdk || dpdo;
for( i = 0; i < count; i++ )
{
double X = M[i].x, Y = M[i].y, Z = M[i].z;
double x = R[0]*X + R[1]*Y + R[2]*Z + t[0];
double y = R[3]*X + R[4]*Y + R[5]*Z + t[1];
double z = R[6]*X + R[7]*Y + R[8]*Z + t[2];
double r2, r4, r6, a1, a2, a3, cdist, icdist2;
double xd, yd, xd0, yd0, invProj;
Vec3d vecTilt;
Vec3d dVecTilt;
Matx22d dMatTilt;
Vec2d dXdYd;
double z0 = z;
z = z ? 1./z : 1;
x *= z; y *= z;
r2 = x*x + y*y;
r4 = r2*r2;
r6 = r4*r2;
a1 = 2*x*y;
a2 = r2 + 2*x*x;
a3 = r2 + 2*y*y;
cdist = 1 + k[0]*r2 + k[1]*r4 + k[4]*r6;
icdist2 = 1./(1 + k[5]*r2 + k[6]*r4 + k[7]*r6);
xd0 = x*cdist*icdist2 + k[2]*a1 + k[3]*a2 + k[8]*r2+k[9]*r4;
yd0 = y*cdist*icdist2 + k[2]*a3 + k[3]*a1 + k[10]*r2+k[11]*r4;
// additional distortion by projecting onto a tilt plane
vecTilt = matTilt*Vec3d(xd0, yd0, 1);
invProj = vecTilt(2) ? 1./vecTilt(2) : 1;
xd = invProj * vecTilt(0);
yd = invProj * vecTilt(1);
m[i].x = xd*fx + cx;
m[i].y = yd*fy + cy;
m[i].z = z; // Just put the projected Z coordinate here, we mainly care about the sign
if( calc_derivatives )
{
if( dpdc_p )
{
dpdc_p[0] = 1; dpdc_p[1] = 0; // dp_xdc_x; dp_xdc_y
dpdc_p[dpdc_step] = 0;
dpdc_p[dpdc_step+1] = 1;
dpdc_p += dpdc_step*2;
}
if( dpdf_p )
{
if( fixedAspectRatio )
{
dpdf_p[0] = 0; dpdf_p[1] = xd*aspectRatio; // dp_xdf_x; dp_xdf_y
dpdf_p[dpdf_step] = 0;
dpdf_p[dpdf_step+1] = yd;
}
else
{
dpdf_p[0] = xd; dpdf_p[1] = 0;
dpdf_p[dpdf_step] = 0;
dpdf_p[dpdf_step+1] = yd;
}
dpdf_p += dpdf_step*2;
}
for (int row = 0; row < 2; ++row)
for (int col = 0; col < 2; ++col)
dMatTilt(row,col) = matTilt(row,col)*vecTilt(2)
- matTilt(2,col)*vecTilt(row);
double invProjSquare = (invProj*invProj);
dMatTilt *= invProjSquare;
if( dpdk_p )
{
dXdYd = dMatTilt*Vec2d(x*icdist2*r2, y*icdist2*r2);
dpdk_p[0] = fx*dXdYd(0);
dpdk_p[dpdk_step] = fy*dXdYd(1);
dXdYd = dMatTilt*Vec2d(x*icdist2*r4, y*icdist2*r4);
dpdk_p[1] = fx*dXdYd(0);
dpdk_p[dpdk_step+1] = fy*dXdYd(1);
if( _dpdk->cols > 2 )
{
dXdYd = dMatTilt*Vec2d(a1, a3);
dpdk_p[2] = fx*dXdYd(0);
dpdk_p[dpdk_step+2] = fy*dXdYd(1);
dXdYd = dMatTilt*Vec2d(a2, a1);
dpdk_p[3] = fx*dXdYd(0);
dpdk_p[dpdk_step+3] = fy*dXdYd(1);
if( _dpdk->cols > 4 )
{
dXdYd = dMatTilt*Vec2d(x*icdist2*r6, y*icdist2*r6);
dpdk_p[4] = fx*dXdYd(0);
dpdk_p[dpdk_step+4] = fy*dXdYd(1);
if( _dpdk->cols > 5 )
{
dXdYd = dMatTilt*Vec2d(
x*cdist*(-icdist2)*icdist2*r2, y*cdist*(-icdist2)*icdist2*r2);
dpdk_p[5] = fx*dXdYd(0);
dpdk_p[dpdk_step+5] = fy*dXdYd(1);
dXdYd = dMatTilt*Vec2d(
x*cdist*(-icdist2)*icdist2*r4, y*cdist*(-icdist2)*icdist2*r4);
dpdk_p[6] = fx*dXdYd(0);
dpdk_p[dpdk_step+6] = fy*dXdYd(1);
dXdYd = dMatTilt*Vec2d(
x*cdist*(-icdist2)*icdist2*r6, y*cdist*(-icdist2)*icdist2*r6);
dpdk_p[7] = fx*dXdYd(0);
dpdk_p[dpdk_step+7] = fy*dXdYd(1);
if( _dpdk->cols > 8 )
{
dXdYd = dMatTilt*Vec2d(r2, 0);
dpdk_p[8] = fx*dXdYd(0); //s1
dpdk_p[dpdk_step+8] = fy*dXdYd(1); //s1
dXdYd = dMatTilt*Vec2d(r4, 0);
dpdk_p[9] = fx*dXdYd(0); //s2
dpdk_p[dpdk_step+9] = fy*dXdYd(1); //s2
dXdYd = dMatTilt*Vec2d(0, r2);
dpdk_p[10] = fx*dXdYd(0);//s3
dpdk_p[dpdk_step+10] = fy*dXdYd(1); //s3
dXdYd = dMatTilt*Vec2d(0, r4);
dpdk_p[11] = fx*dXdYd(0);//s4
dpdk_p[dpdk_step+11] = fy*dXdYd(1); //s4
if( _dpdk->cols > 12 )
{
dVecTilt = dMatTiltdTauX * Vec3d(xd0, yd0, 1);
dpdk_p[12] = fx * invProjSquare * (
dVecTilt(0) * vecTilt(2) - dVecTilt(2) * vecTilt(0));
dpdk_p[dpdk_step+12] = fy*invProjSquare * (
dVecTilt(1) * vecTilt(2) - dVecTilt(2) * vecTilt(1));
dVecTilt = dMatTiltdTauY * Vec3d(xd0, yd0, 1);
dpdk_p[13] = fx * invProjSquare * (
dVecTilt(0) * vecTilt(2) - dVecTilt(2) * vecTilt(0));
dpdk_p[dpdk_step+13] = fy * invProjSquare * (
dVecTilt(1) * vecTilt(2) - dVecTilt(2) * vecTilt(1));
}
}
}
}
}
dpdk_p += dpdk_step*2;
}
if( dpdt_p )
{
double dxdt[] = { z, 0, -x*z }, dydt[] = { 0, z, -y*z };
for( j = 0; j < 3; j++ )
{
double dr2dt = 2*x*dxdt[j] + 2*y*dydt[j];
double dcdist_dt = k[0]*dr2dt + 2*k[1]*r2*dr2dt + 3*k[4]*r4*dr2dt;
double dicdist2_dt = -icdist2*icdist2*(k[5]*dr2dt + 2*k[6]*r2*dr2dt + 3*k[7]*r4*dr2dt);
double da1dt = 2*(x*dydt[j] + y*dxdt[j]);
double dmxdt = (dxdt[j]*cdist*icdist2 + x*dcdist_dt*icdist2 + x*cdist*dicdist2_dt +
k[2]*da1dt + k[3]*(dr2dt + 4*x*dxdt[j]) + k[8]*dr2dt + 2*r2*k[9]*dr2dt);
double dmydt = (dydt[j]*cdist*icdist2 + y*dcdist_dt*icdist2 + y*cdist*dicdist2_dt +
k[2]*(dr2dt + 4*y*dydt[j]) + k[3]*da1dt + k[10]*dr2dt + 2*r2*k[11]*dr2dt);
dXdYd = dMatTilt*Vec2d(dmxdt, dmydt);
dpdt_p[j] = fx*dXdYd(0);
dpdt_p[dpdt_step+j] = fy*dXdYd(1);
}
dpdt_p += dpdt_step*2;
}
if( dpdr_p )
{
double dx0dr[] =
{
X*dRdr[0] + Y*dRdr[1] + Z*dRdr[2],
X*dRdr[9] + Y*dRdr[10] + Z*dRdr[11],
X*dRdr[18] + Y*dRdr[19] + Z*dRdr[20]
};
double dy0dr[] =
{
X*dRdr[3] + Y*dRdr[4] + Z*dRdr[5],
X*dRdr[12] + Y*dRdr[13] + Z*dRdr[14],
X*dRdr[21] + Y*dRdr[22] + Z*dRdr[23]
};
double dz0dr[] =
{
X*dRdr[6] + Y*dRdr[7] + Z*dRdr[8],
X*dRdr[15] + Y*dRdr[16] + Z*dRdr[17],
X*dRdr[24] + Y*dRdr[25] + Z*dRdr[26]
};
for( j = 0; j < 3; j++ )
{
double dxdr = z*(dx0dr[j] - x*dz0dr[j]);
double dydr = z*(dy0dr[j] - y*dz0dr[j]);
double dr2dr = 2*x*dxdr + 2*y*dydr;
double dcdist_dr = (k[0] + 2*k[1]*r2 + 3*k[4]*r4)*dr2dr;
double dicdist2_dr = -icdist2*icdist2*(k[5] + 2*k[6]*r2 + 3*k[7]*r4)*dr2dr;
double da1dr = 2*(x*dydr + y*dxdr);
double dmxdr = (dxdr*cdist*icdist2 + x*dcdist_dr*icdist2 + x*cdist*dicdist2_dr +
k[2]*da1dr + k[3]*(dr2dr + 4*x*dxdr) + (k[8] + 2*r2*k[9])*dr2dr);
double dmydr = (dydr*cdist*icdist2 + y*dcdist_dr*icdist2 + y*cdist*dicdist2_dr +
k[2]*(dr2dr + 4*y*dydr) + k[3]*da1dr + (k[10] + 2*r2*k[11])*dr2dr);
dXdYd = dMatTilt*Vec2d(dmxdr, dmydr);
dpdr_p[j] = fx*dXdYd(0);
dpdr_p[dpdr_step+j] = fy*dXdYd(1);
}
dpdr_p += dpdr_step*2;
}
if( dpdo_p )
{
double dxdo[] = { z * ( R[0] - x * z * z0 * R[6] ),
z * ( R[1] - x * z * z0 * R[7] ),
z * ( R[2] - x * z * z0 * R[8] ) };
double dydo[] = { z * ( R[3] - y * z * z0 * R[6] ),
z * ( R[4] - y * z * z0 * R[7] ),
z * ( R[5] - y * z * z0 * R[8] ) };
for( j = 0; j < 3; j++ )
{
double dr2do = 2 * x * dxdo[j] + 2 * y * dydo[j];
double dr4do = 2 * r2 * dr2do;
double dr6do = 3 * r4 * dr2do;
double da1do = 2 * y * dxdo[j] + 2 * x * dydo[j];
double da2do = dr2do + 4 * x * dxdo[j];
double da3do = dr2do + 4 * y * dydo[j];
double dcdist_do
= k[0] * dr2do + k[1] * dr4do + k[4] * dr6do;
double dicdist2_do = -icdist2 * icdist2
* ( k[5] * dr2do + k[6] * dr4do + k[7] * dr6do );
double dxd0_do = cdist * icdist2 * dxdo[j]
+ x * icdist2 * dcdist_do + x * cdist * dicdist2_do
+ k[2] * da1do + k[3] * da2do + k[8] * dr2do
+ k[9] * dr4do;
double dyd0_do = cdist * icdist2 * dydo[j]
+ y * icdist2 * dcdist_do + y * cdist * dicdist2_do
+ k[2] * da3do + k[3] * da1do + k[10] * dr2do
+ k[11] * dr4do;
dXdYd = dMatTilt * Vec2d( dxd0_do, dyd0_do );
dpdo_p[i * 3 + j] = fx * dXdYd( 0 );
dpdo_p[dpdo_step + i * 3 + j] = fy * dXdYd( 1 );
}
dpdo_p += dpdo_step * 2;
}
}
}
if( _m != imagePoints )
cvConvert( _m, imagePoints );
if( _dpdr != dpdr )
cvConvert( _dpdr, dpdr );
if( _dpdt != dpdt )
cvConvert( _dpdt, dpdt );
if( _dpdf != dpdf )
cvConvert( _dpdf, dpdf );
if( _dpdc != dpdc )
cvConvert( _dpdc, dpdc );
if( _dpdk != dpdk )
cvConvert( _dpdk, dpdk );
if( _dpdo != dpdo )
cvConvert( _dpdo, dpdo );
}
static void _cvProjectPoints2( const CvMat* objectPoints,
const CvMat* r_vec,
const CvMat* t_vec,
const CvMat* A,
const CvMat* distCoeffs,
CvMat* imagePoints, CvMat* dpdr,
CvMat* dpdt, CvMat* dpdf,
CvMat* dpdc, CvMat* dpdk,
double aspectRatio )
{
_cvProjectPoints2Internal( objectPoints, r_vec, t_vec, A, distCoeffs, imagePoints, dpdr, dpdt,
dpdf, dpdc, dpdk, NULL, aspectRatio );
}

View File

@@ -4,3 +4,5 @@
#include <opencv2/aruco.hpp> #include <opencv2/aruco.hpp>
void _drawPlanarBoard(cv::aruco::Board *_board, cv::Size outSize, cv::OutputArray _img, int marginSize, int borderBits); void _drawPlanarBoard(cv::aruco::Board *_board, cv::Size outSize, cv::OutputArray _img, int marginSize, int borderBits);
void _drawAxis(cv::InputOutputArray image, cv::InputArray cameraMatrix, cv::InputArray distCoeffs,
cv::InputArray rvec, cv::InputArray tvec, float length);

View File

@@ -14,8 +14,6 @@ Options:
<y> Marker count along Y axis <y> Marker count along Y axis
<dist_x> Distance between markers along X axis <dist_x> Distance between markers along X axis
<dist_y> Distance between markers along Y axis <dist_y> Distance between markers along Y axis
<sep_x> Space beetween markers along X axis
<sep_y> Space beetween markers along Y axis
<first> First marker ID <first> First marker ID
--top-left First marker is on top-left (not bottom-left) --top-left First marker is on top-left (not bottom-left)
""" """
@@ -35,7 +33,7 @@ dist_x = float(arguments['<dist_x>'])
dist_y = float(arguments['<dist_y>']) dist_y = float(arguments['<dist_y>'])
top_left = arguments['--top-left'] top_left = arguments['--top-left']
max_y = markers_y * length max_y = (markers_y - 1) * dist_y
for y in range(markers_y): for y in range(markers_y):
for x in range(markers_x): for x in range(markers_x):

View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cmath>
#include <ros/ros.h> #include <ros/ros.h>
#include <tf/transform_datatypes.h> #include <tf/transform_datatypes.h>
#include <geometry_msgs/Quaternion.h> #include <geometry_msgs/Quaternion.h>
@@ -90,14 +91,33 @@ inline void fillTranslation(geometry_msgs::Vector3& translation, const cv::Vec3d
translation.z = tvec[2]; translation.z = tvec[2];
} }
inline void snapOrientation(geometry_msgs::Quaternion& to, const geometry_msgs::Quaternion& from) inline bool isFlipped(tf::Quaternion& q)
{ {
tf::Quaternion q; double yaw, pitch, roll;
q.setRPY(0, 0, -tf::getYaw(to) + tf::getYaw(from)); tf::Matrix3x3(q).getEulerYPR(yaw, pitch, roll);
tf::Quaternion pq; return (abs(pitch) > M_PI / 2) || (abs(roll) > M_PI / 2);
tf::quaternionMsgToTF(from, pq); }
pq = pq * q;
tf::quaternionTFToMsg(pq, to); /* Set roll and pitch from "from" to "to", keeping yaw */
inline void snapOrientation(geometry_msgs::Quaternion& to, const geometry_msgs::Quaternion& from, bool auto_flip = false)
{
tf::Quaternion _from, _to;
tf::quaternionMsgToTF(from, _from);
tf::quaternionMsgToTF(to, _to);
if (auto_flip) {
if (!isFlipped(_from)) {
static const tf::Quaternion flip = tf::createQuaternionFromRPY(M_PI, 0, 0);
_from *= flip; // flip "from"
}
}
auto diff = tf::Matrix3x3(_to).transposeTimes(tf::Matrix3x3(_from));
double _, yaw;
diff.getRPY(_, _, yaw);
auto q = tf::createQuaternionFromRPY(0, 0, -yaw);
_from = _from * q; // set yaw from "to" to "from"
tf::quaternionTFToMsg(_from, to); // set "from" to "to"
} }
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

@@ -4,6 +4,7 @@
"author": "Copter Express", "author": "Copter Express",
"language": "ru", "language": "ru",
"root": "docs/", "root": "docs/",
"gitbook": "^3.2.2",
"plugins": [ "plugins": [
"youtube", "youtube",
"richquotes@https://github.com/okalachev/gitbook-plugin-richquotes.git", "richquotes@https://github.com/okalachev/gitbook-plugin-richquotes.git",

View File

@@ -7,7 +7,8 @@ After=roscore.service
User=pi User=pi
EnvironmentFile=/lib/systemd/system/roscore.env EnvironmentFile=/lib/systemd/system/roscore.env
ExecStart=/opt/ros/kinetic/bin/roslaunch clever clever.launch --wait --screen ExecStart=/opt/ros/kinetic/bin/roslaunch clever clever.launch --wait --screen
Restart=on-abort Restart=on-failure
RestartSec=3
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

35
builder/assets/clever_rename Executable file
View File

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

View File

@@ -32,7 +32,9 @@ echo_stamp() {
} }
echo_stamp "Rename SSID" echo_stamp "Rename SSID"
sudo sed -i.OLD "s/CLEVER/CLEVER-$(head -c 100 /dev/urandom | xxd -ps -c 100 | sed -e 's/[^0-9]//g' | cut -c 1-4)/g" /etc/wpa_supplicant/wpa_supplicant.conf NEW_SSID='CLEVER-'$(head -c 100 /dev/urandom | xxd -ps -c 100 | sed -e "s/[^0-9]//g" | cut -c 1-4)
sudo sed -i.OLD "s/CLEVER/${NEW_SSID}/" /etc/wpa_supplicant/wpa_supplicant.conf
clever_rename ${NEW_SSID}
echo_stamp "Harware setup" echo_stamp "Harware setup"
/root/hardware_setup.sh /root/hardware_setup.sh

View File

@@ -0,0 +1,8 @@
Defaults env_keep += "PYTHONPATH"
Defaults env_keep += "PATH"
Defaults env_keep += "ROS_ROOT"
Defaults env_keep += "ROS_MASTER_URI"
Defaults env_keep += "ROS_PACKAGE_PATH"
Defaults env_keep += "ROS_LOCATIONS"
Defaults env_keep += "ROS_HOME"
Defaults env_keep += "ROS_LOG_DIR"

View File

@@ -6,7 +6,8 @@ After=network.target
User=pi User=pi
EnvironmentFile=/lib/systemd/system/roscore.env EnvironmentFile=/lib/systemd/system/roscore.env
ExecStart=/opt/ros/kinetic/bin/roscore ExecStart=/opt/ros/kinetic/bin/roscore
Restart=on-abort Restart=on-failure
RestartSec=3
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -11,7 +11,7 @@
set -e # Exit immidiately on non-zero result set -e # Exit immidiately on non-zero result
SOURCE_IMAGE="https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2018-06-29/2018-06-27-raspbian-stretch-lite.zip" SOURCE_IMAGE="https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2018-11-15/2018-11-13-raspbian-stretch-lite.zip"
export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:='noninteractive'} export DEBIAN_FRONTEND=${DEBIAN_FRONTEND:='noninteractive'}
export LANG=${LANG:='C.UTF-8'} export LANG=${LANG:='C.UTF-8'}
@@ -108,9 +108,12 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clever.
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/roscore.env' '/lib/systemd/system/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/roscore.env' '/lib/systemd/system/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/roscore.service' '/lib/systemd/system/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/roscore.service' '/lib/systemd/system/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-rosdep-clever.yaml' '/etc/ros/rosdep/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-rosdep-clever.yaml' '/etc/ros/rosdep/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/ros_python_paths' '/etc/sudoers.d/'
# ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-ros-clever.rosinstall' '/home/pi/ros_catkin_ws/' # ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-ros-clever.rosinstall' '/home/pi/ros_catkin_ws/'
# Add PX4 udev rules # Add PX4 udev rules
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/99-px4fmu.rules' '/lib/udev/rules.d/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/99-px4fmu.rules' '/lib/udev/rules.d/'
# Add rename script
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clever_rename' '/usr/local/bin/clever_rename'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-ros.sh' ${REPO_URL} ${IMAGE_VERSION} false false ${NUMBER_THREADS} ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-ros.sh' ${REPO_URL} ${IMAGE_VERSION} false false ${NUMBER_THREADS}
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh'

View File

@@ -42,9 +42,10 @@ echo_stamp() {
my_travis_retry() { my_travis_retry() {
local result=0 local result=0
local count=1 local count=1
while [ $count -le 3 ]; do local max_count=50
while [ $count -le $max_count ]; do
[ $result -ne 0 ] && { [ $result -ne 0 ] && {
echo -e "\n${ANSI_RED}The command \"$@\" failed. Retrying, $count of 3.${ANSI_RESET}\n" >&2 echo -e "\nThe command \"$@\" failed. Retrying, $count of $max_count.\n" >&2
} }
# ! { } ignores set -e, see https://stackoverflow.com/a/4073372 # ! { } ignores set -e, see https://stackoverflow.com/a/4073372
! { "$@"; result=$?; } ! { "$@"; result=$?; }
@@ -53,21 +54,21 @@ my_travis_retry() {
sleep 1 sleep 1
done done
[ $count -gt 3 ] && { [ $count -gt $max_count ] && {
echo -e "\n${ANSI_RED}The command \"$@\" failed 3 times.${ANSI_RESET}\n" >&2 echo -e "\nThe command \"$@\" failed $max_count times.\n" >&2
} }
return $result return $result
} }
# TODO: 'kinetic-rosdep-clever.yaml' should add only if we use our repo? # TODO: 'kinetic-rosdep-clever.yaml' should add only if we use our repo?
echo_stamp "Init rosdep" \ echo_stamp "Init rosdep"
&& rosdep init \ my_travis_retry rosdep init
&& echo "yaml file:///etc/ros/rosdep/kinetic-rosdep-clever.yaml" >> /etc/ros/rosdep/sources.list.d/20-default.list \ echo "yaml file:///etc/ros/rosdep/kinetic-rosdep-clever.yaml" >> /etc/ros/rosdep/sources.list.d/20-default.list
&& rosdep update my_travis_retry rosdep update
echo_stamp "Populate rosdep for ROS user" echo_stamp "Populate rosdep for ROS user"
sudo -u pi rosdep update my_travis_retry sudo -u pi rosdep update
resolve_rosdep() { resolve_rosdep() {
# TEMPLATE: resolve_rosdep <CATKIN_PATH> <ROS_DISTRO> <OS_DISTRO> <OS_VERSION> # TEMPLATE: resolve_rosdep <CATKIN_PATH> <ROS_DISTRO> <OS_DISTRO> <OS_VERSION>
@@ -168,7 +169,8 @@ apt-get install -y --no-install-recommends \
ros-kinetic-rosserial \ ros-kinetic-rosserial \
ros-kinetic-usb-cam \ ros-kinetic-usb-cam \
ros-kinetic-vl53l1x \ ros-kinetic-vl53l1x \
ros-kinetic-opencv3=3.3.19-0stretch ros-kinetic-opencv3=3.3.19-0stretch \
ros-kinetic-rosshow
# TODO move GeographicLib datasets to Mavros debian package # TODO move GeographicLib datasets to Mavros debian package
echo_stamp "Install GeographicLib datasets (needs for mavros)" \ echo_stamp "Install GeographicLib datasets (needs for mavros)" \

View File

@@ -91,7 +91,7 @@ tcpdump \
ltrace \ ltrace \
libpoco-dev=1.7.6+dfsg1-5+deb9u1 \ libpoco-dev=1.7.6+dfsg1-5+deb9u1 \
python-rosdep \ python-rosdep \
python-rosinstall-generator=0.1.14-1 \ python-rosinstall-generator \
python-wstool=0.1.17-1 \ python-wstool=0.1.17-1 \
python-rosinstall=0.7.8-1 \ python-rosinstall=0.7.8-1 \
build-essential=12.3 \ build-essential=12.3 \
@@ -99,12 +99,17 @@ libffi-dev \
monkey=1.6.9-1 \ monkey=1.6.9-1 \
pigpio python-pigpio python3-pigpio \ pigpio python-pigpio python3-pigpio \
i2c-tools \ i2c-tools \
espeak espeak-data python-espeak \
ntpdate \ ntpdate \
python-dev \ python-dev \
python3-dev \ python3-dev \
python-systemd \
&& echo_stamp "Everything was installed!" "SUCCESS" \ && echo_stamp "Everything was installed!" "SUCCESS" \
|| (echo_stamp "Some packages wasn't installed!" "ERROR"; exit 1) || (echo_stamp "Some packages wasn't installed!" "ERROR"; exit 1)
echo_stamp "Updating kernel to fix camera bug"
apt-get install --no-install-recommends -y raspberrypi-kernel=1.20190401-1
# Deny byobu to check available updates # Deny byobu to check available updates
sed -i "s/updates_available//" /usr/share/byobu/status/status sed -i "s/updates_available//" /usr/share/byobu/status/status
# sed -i "s/updates_available//" /home/pi/.byobu/status # sed -i "s/updates_available//" /home/pi/.byobu/status
@@ -113,6 +118,7 @@ echo_stamp "Installing pip"
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py python3 get-pip.py
python get-pip.py python get-pip.py
rm get-pip.py
#my_travis_retry pip install --upgrade pip #my_travis_retry pip install --upgrade pip
#my_travis_retry pip3 install --upgrade pip #my_travis_retry pip3 install --upgrade pip

View File

@@ -25,6 +25,6 @@ import pymavlink
from pymavlink import mavutil from pymavlink import mavutil
import rpi_ws281x import rpi_ws281x
import pigpio import pigpio
from espeak import espeak
print cv2.getBuildInformation() print cv2.getBuildInformation()

View File

@@ -28,6 +28,7 @@ monkey --version
pigpiod -v pigpiod -v
i2cdetect -V i2cdetect -V
butterfly -h butterfly -h
espeak --version
# ros stuff # ros stuff
@@ -46,3 +47,4 @@ rosversion rosserial
rosversion usb_cam rosversion usb_cam
rosversion cv_camera rosversion cv_camera
rosversion web_video_server rosversion web_video_server
rosversion rosshow

View File

@@ -63,7 +63,6 @@
<!-- vl53l1x ToF rangefinder --> <!-- vl53l1x ToF rangefinder -->
<node name="vl53l1x" pkg="vl53l1x" type="vl53l1x_node" output="screen" if="$(arg rangefinder_vl53l1x)"> <node name="vl53l1x" pkg="vl53l1x" type="vl53l1x_node" output="screen" if="$(arg rangefinder_vl53l1x)">
<param name="frame_id" value="rangefinder"/> <param name="frame_id" value="rangefinder"/>
<param name="offset" value="-0.05"/>
<remap from="~range" to="mavros/distance_sensor/rangefinder_sub"/> <!-- redirect data to FCU --> <remap from="~range" to="mavros/distance_sensor/rangefinder_sub"/> <!-- redirect data to FCU -->
</node> </node>

0
clever/launch/main_camera.launch Executable file → Normal file
View File

View File

@@ -161,7 +161,12 @@ private:
flow_camera.header.stamp = msg->header.stamp; flow_camera.header.stamp = msg->header.stamp;
flow_camera.vector.x = flow_y; // +y means counter-clockwise rotation around Y axis flow_camera.vector.x = flow_y; // +y means counter-clockwise rotation around Y axis
flow_camera.vector.y = -flow_x; // +x means clockwise rotation around X axis flow_camera.vector.y = -flow_x; // +x means clockwise rotation around X axis
tf_buffer_.transform(flow_camera, flow_fcu, fcu_frame_id_); try {
tf_buffer_.transform(flow_camera, flow_fcu, fcu_frame_id_);
} catch (const tf2::TransformException& e) {
// transform is not available yet
return;
}
// Calculate integration time // Calculate integration time
ros::Duration integration_time = msg->header.stamp - prev_stamp_; ros::Duration integration_time = msg->header.stamp - prev_stamp_;

View File

@@ -1,19 +1,21 @@
#!/usr/bin/env python #!/usr/bin/env python
import math import math
from subprocess import Popen, PIPE import subprocess
import re import re
import traceback import traceback
import numpy
import rospy import rospy
from std_srvs.srv import Trigger from std_srvs.srv import Trigger
from sensor_msgs.msg import Image, CameraInfo, NavSatFix, Imu, Range from sensor_msgs.msg import Image, CameraInfo, NavSatFix, Imu, Range
from mavros_msgs.msg import State, OpticalFlowRad from mavros_msgs.msg import State, OpticalFlowRad
from geometry_msgs.msg import PoseStamped, TwistStamped from mavros_msgs.srv import ParamGet
from geometry_msgs.msg import PoseStamped, TwistStamped, PoseWithCovarianceStamped
import tf.transformations as t import tf.transformations as t
from aruco_pose.msg import MarkerArray
from systemd import journal
# TODO: roscore is running
# TODO: clever.service is running
# TODO: check attitude is present # TODO: check attitude is present
# TODO: disk free space # TODO: disk free space
# TODO: map, base_link, body # TODO: map, base_link, body
@@ -42,8 +44,10 @@ def check(name):
for f in failures: for f in failures:
rospy.logwarn('%s: %s', name, f) rospy.logwarn('%s: %s', name, f)
except Exception as e: except Exception as e:
for f in failures:
rospy.logwarn('%s: %s', name, f)
traceback.print_exc() traceback.print_exc()
rospy.logwarn('%s: exception occurred', name) rospy.logerr('%s: exception occurred', name)
return return
if not failures: if not failures:
rospy.loginfo('%s: OK', name) rospy.loginfo('%s: OK', name)
@@ -51,12 +55,49 @@ def check(name):
return inner return inner
param_get = rospy.ServiceProxy('mavros/param/get', ParamGet)
def get_param(name):
try:
res = param_get(param_id=name)
except rospy.ServiceException as e:
failure('%s: %s', name, str(e))
return None
if not res.success:
failure('Unable to retrieve PX4 parameter %s', name)
else:
if res.value.integer != 0:
return res.value.integer
return res.value.real
@check('FCU') @check('FCU')
def check_fcu(): def check_fcu():
try: try:
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)')
est = get_param('SYS_MC_EST_GROUP')
if est == 1:
rospy.loginfo('Selected estimator: LPE')
fuse = get_param('LPE_FUSION')
if fuse & (1 << 4):
rospy.loginfo('LPE_FUSION: land detector fusion is enabled')
else:
rospy.loginfo('LPE_FUSION: land detector fusion is disabled')
if fuse & (1 << 7):
rospy.loginfo('LPE_FUSION: barometer fusion is enabled')
else:
rospy.loginfo('LPE_FUSION: barometer fusion is disabled')
elif est == 2:
rospy.loginfo('Selected estimator: EKF2')
else:
failure('Unknown selected estimator: %s', est)
except rospy.ROSException: except rospy.ROSException:
failure('no MAVROS state (check wiring)') failure('no MAVROS state (check wiring)')
@@ -95,6 +136,7 @@ def check_aruco():
@check('Vision position estimate') @check('Vision position estimate')
def check_vpe(): def check_vpe():
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=1)
except rospy.ROSException: except rospy.ROSException:
@@ -102,7 +144,45 @@ def check_vpe():
vis = rospy.wait_for_message('mavros/mocap/pose', PoseStamped, timeout=1) vis = rospy.wait_for_message('mavros/mocap/pose', PoseStamped, timeout=1)
except rospy.ROSException: except rospy.ROSException:
failure('no VPE or MoCap messages') failure('no VPE or MoCap messages')
return # check if vpe_publisher is running
try:
subprocess.check_output(['pgrep', '-x', 'vpe_publisher'])
except subprocess.CalledProcessError:
return # it's not running, skip following checks
# check PX4 settings
est = get_param('SYS_MC_EST_GROUP')
if est == 1:
ext_yaw = get_param('ATT_EXT_HDG_M')
if ext_yaw != 1:
failure('vision yaw is disabled, change ATT_EXT_HDG_M parameter')
vision_yaw_w = get_param('ATT_W_EXT_HDG')
if vision_yaw_w == 0:
failure('vision yaw weight is zero, change ATT_W_EXT_HDG parameter')
else:
rospy.loginfo('Vision yaw weight: %.2f', vision_yaw_w)
fuse = get_param('LPE_FUSION')
if not fuse & (1 << 2):
failure('vision position fusion is disabled, change LPE_FUSION parameter')
delay = get_param('LPE_VIS_DELAY')
if delay != 0:
failure('LPE_VIS_DELAY parameter is %s, but it should be zero', delay)
rospy.loginfo('LPE_VIS_XY is %.2f m, LPE_VIS_Z is %.2f m', get_param('LPE_VIS_XY'), get_param('LPE_VIS_Z'))
elif est == 2:
fuse = get_param('EKF2_AID_MASK')
if not fuse & (1 << 3):
failure('vision position fusion is disabled, change EKF2_AID_MASK parameter')
if not fuse & (1 << 4):
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
delay = get_param('EKF2_EV_DELAY')
if delay != 0:
failure('EKF2_EV_DELAY is %.2f, but it should be zero', delay)
rospy.loginfo('EKF2_EVA_NOISE is %.3f, EKF2_EVP_NOISE is %.3f',
get_param('EKF2_EVA_NOISE'),
get_param('EKF2_EVP_NOISE'))
if not vis:
return
# check vision pose and estimated pose inconsistency # check vision pose and estimated pose inconsistency
try: try:
@@ -173,7 +253,7 @@ def check_velocity():
failure('vertical velocity estimation is %.2f m/s; is copter staying still?' % vert) failure('vertical velocity estimation is %.2f m/s; is copter staying still?' % vert)
angular = velocity.twist.angular angular = velocity.twist.angular
ANGULAR_VELOCITY_LIMIT = 0.01 ANGULAR_VELOCITY_LIMIT = 0.1
if abs(angular.x) > ANGULAR_VELOCITY_LIMIT: if abs(angular.x) > ANGULAR_VELOCITY_LIMIT:
failure('pitch rate estimation is %.2f rad/s (%.2f deg/s); is copter staying still?', failure('pitch rate estimation is %.2f rad/s (%.2f deg/s); is copter staying still?',
angular.x, math.degrees(angular.x)) angular.x, math.degrees(angular.x))
@@ -200,6 +280,42 @@ def check_optical_flow():
# 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)
# check PX4 settings
rot = get_param('SENS_FLOW_ROT')
if rot != 0:
failure('SENS_FLOW_ROT parameter is %s, but it should be zero', rot)
est = get_param('SYS_MC_EST_GROUP')
if est == 1:
fuse = get_param('LPE_FUSION')
if not fuse & (1 << 1):
failure('optical flow fusion is disabled, change LPE_FUSION parameter')
if not fuse & (1 << 1):
failure('flow gyro compensation is disabled, change LPE_FUSION parameter')
scale = get_param('LPE_FLW_SCALE')
if not numpy.isclose(scale, 1.0):
failure('LPE_FLW_SCALE parameter is %.2f, but it should be 1.0', scale)
rospy.loginfo('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',
get_param('LPE_FLW_QMIN'),
get_param('LPE_FLW_R'),
get_param('LPE_FLW_RR'),
get_param('SENS_FLOW_MINHGT'),
get_param('SENS_FLOW_MAXHGT'))
elif est == 2:
fuse = get_param('EKF2_AID_MASK')
if not fuse & (1 << 1):
failure('optical flow fusion is disabled, change EKF2_AID_MASK parameter')
delay = get_param('EKF2_OF_DELAY')
if delay != 0:
failure('EKF2_OF_DELAY is %.2f, but it should be zero', delay)
rospy.loginfo('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',
get_param('EKF2_OF_QMIN'),
get_param('EKF2_OF_N_MIN'),
get_param('EKF2_OF_N_MAX'),
get_param('SENS_FLOW_MINHGT'),
get_param('SENS_FLOW_MAXHGT'))
except rospy.ROSException: except rospy.ROSException:
failure('no optical flow data (from Raspberry)') failure('no optical flow data (from Raspberry)')
@@ -207,21 +323,46 @@ def check_optical_flow():
@check('Rangefinder') @check('Rangefinder')
def check_rangefinder(): def check_rangefinder():
# TODO: check FPS! # TODO: check FPS!
rng = False
try: try:
rospy.wait_for_message('mavros/distance_sensor/rangefinder_3_sub', Range, timeout=0.5) rospy.wait_for_message('mavros/distance_sensor/rangefinder_sub', Range, timeout=4)
rng = True
except rospy.ROSException: except rospy.ROSException:
failure('no randefinder data from Raspberry') failure('no rangefinder data from Raspberry')
try: try:
rospy.wait_for_message('mavros/distance_sensor/rangefinder_0', Range, timeout=0.5) rospy.wait_for_message('mavros/distance_sensor/rangefinder', Range, timeout=4)
rng = True
except rospy.ROSException: except rospy.ROSException:
failure('no rangefinder data from PX4') failure('no rangefinder data from PX4')
if not rng:
return
est = get_param('SYS_MC_EST_GROUP')
if est == 1:
fuse = get_param('LPE_FUSION')
if not fuse & (1 << 5):
rospy.loginfo('"pub agl as lpos down" in LPE_FUSION is disabled, NOT operating over flat surface')
else:
rospy.loginfo('"pub agl as lpos down" in LPE_FUSION is enabled, operating over flat surface')
elif est == 2:
hgt = get_param('EKF2_HGT_MODE')
if hgt != 2:
rospy.loginfo('EKF2_HGT_MODE != Range sensor, NOT operating over flat surface')
else:
rospy.loginfo('EKF2_HGT_MODE = Range sensor, operating over flat surface')
aid = get_param('EKF2_RNG_AID')
if aid != 1:
rospy.loginfo('EKF2_RNG_AID != 1, range sensor aiding disabled')
else:
rospy.loginfo('EKF2_RNG_AID = 1, range sensor aiding enabled')
@check('Boot duration') @check('Boot duration')
def check_boot_duration(): def check_boot_duration():
proc = Popen('systemd-analyze', stdout=PIPE) output = subprocess.check_output('systemd-analyze')
proc.wait()
output = proc.communicate()[0]
r = re.compile(r'([\d\.]+)s$') r = re.compile(r'([\d\.]+)s$')
duration = float(r.search(output).groups()[0]) duration = float(r.search(output).groups()[0])
if duration > 15: if duration > 15:
@@ -232,9 +373,7 @@ def check_boot_duration():
def check_cpu_usage(): def check_cpu_usage():
WHITELIST = 'nodelet', WHITELIST = 'nodelet',
CMD = "top -n 1 -b -i | tail -n +8 | awk '{ printf(\"%-8s\\t%-8s\\t%-8s\\n\", $1, $9, $12); }'" CMD = "top -n 1 -b -i | tail -n +8 | awk '{ printf(\"%-8s\\t%-8s\\t%-8s\\n\", $1, $9, $12); }'"
proc = Popen(CMD, stdout=PIPE, shell=True) output = subprocess.check_output(CMD, shell=True)
proc.wait()
output = proc.communicate()[0]
processes = output.split('\n') processes = output.split('\n')
for process in processes: for process in processes:
if not process: if not process:
@@ -246,7 +385,34 @@ def check_cpu_usage():
cpu.strip(), cmd.strip(), pid.strip()) cpu.strip(), cmd.strip(), pid.strip())
@check('clever.service')
def check_clever_service():
output = subprocess.check_output('systemctl show -p ActiveState --value clever.service'.split())
if 'inactive' in output:
failure('clever.service is not running, try sudo systemctl restart clever')
return
j = journal.Reader()
j.this_boot()
j.add_match(_SYSTEMD_UNIT='clever.service')
j.add_disjunction()
j.add_match(UNIT='clever.service')
node_errors = []
r = re.compile(r'^(.*)\[(FATAL|ERROR)\] \[\d+.\d+\]: (.*)$')
for event in j:
msg = event['MESSAGE']
if ('Stopped Clever ROS package' in msg) or ('Started Clever ROS package' in msg):
node_errors = []
elif ('[ERROR]' in msg) or ('[FATAL]' in msg):
msg = r.search(msg).groups()[2]
if msg in node_errors:
continue
node_errors.append(msg)
for error in node_errors:
failure(error)
def selfcheck(): def selfcheck():
check_clever_service()
check_fcu() check_fcu()
check_imu() check_imu()
check_local_position() check_local_position()

View File

@@ -70,6 +70,7 @@ ros::Duration global_position_timeout;
ros::Duration battery_timeout; ros::Duration battery_timeout;
float default_speed; float default_speed;
bool auto_release; bool auto_release;
bool land_only_in_offboard;
std::map<string, string> reference_frames; std::map<string, string> reference_frames;
// Publishers // Publishers
@@ -528,12 +529,13 @@ bool serve(enum setpoint_type_t sp_type, float x, float y, float z, float vx, fl
nav_speed = speed; nav_speed = speed;
} }
if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) { // if (sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == POSITION || sp_type == VELOCITY) {
if (std::isnan(yaw) && yaw_rate == 0) { // if (std::isnan(yaw) && yaw_rate == 0) {
// keep yaw unchanged // // keep yaw unchanged
yaw = tf2::getYaw(local_position.pose.orientation); // // 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) { if (sp_type == POSITION || sp_type == NAVIGATE || sp_type == NAVIGATE_GLOBAL || sp_type == VELOCITY || sp_type == ATTITUDE) {
// destination point and/or yaw // destination point and/or yaw
@@ -643,6 +645,12 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
checkState(); checkState();
if (land_only_in_offboard) {
if (state.mode != "OFFBOARD") {
throw std::runtime_error("Copter is not in OFFBOARD mode");
}
}
static mavros_msgs::SetMode sm; static mavros_msgs::SetMode sm;
sm.request.custom_mode = "AUTO.LAND"; sm.request.custom_mode = "AUTO.LAND";
@@ -687,6 +695,7 @@ int main(int argc, char **argv)
nh.param<string>("mavros/local_position/tf/child_frame_id", fcu_frame, "base_link"); nh.param<string>("mavros/local_position/tf/child_frame_id", fcu_frame, "base_link");
nh_priv.param("target_frame", target.child_frame_id, string("navigate_target")); nh_priv.param("target_frame", target.child_frame_id, string("navigate_target"));
nh_priv.param("auto_release", auto_release, true); nh_priv.param("auto_release", auto_release, true);
nh_priv.param("land_only_in_offboard", land_only_in_offboard, true);
nh_priv.param("default_speed", default_speed, 0.5f); nh_priv.param("default_speed", default_speed, 0.5f);
nh_priv.getParam("reference_frames", reference_frames); nh_priv.getParam("reference_frames", reference_frames);

View File

@@ -116,7 +116,7 @@ int main(int argc, char **argv) {
nh_priv.param<string>("offset_frame_id", offset_frame_id, ""); nh_priv.param<string>("offset_frame_id", offset_frame_id, "");
nh_priv.param<string>("mavros/local_position/frame_id", local_frame_id, "map"); nh_priv.param<string>("mavros/local_position/frame_id", local_frame_id, "map");
nh_priv.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link"); nh_priv.param<string>("mavros/local_position/tf/child_frame_id", child_frame_id, "base_link");
offset_timeout = ros::Duration(nh_priv.param("offset_timeout", 5.0)); offset_timeout = ros::Duration(nh_priv.param("offset_timeout", 3.0));
if (!frame_id.empty()) { if (!frame_id.empty()) {
ROS_INFO("vpe_publisher: using data from TF"); ROS_INFO("vpe_publisher: using data from TF");

19
clever/www/aruco_map.html Normal file
View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="js/three.min.js"></script>
<script type="text/javascript" src="js/eventemitter2.js"></script>
<script type="text/javascript" src="js/roslib.js"></script>
<script type="text/javascript" src="js/ros3d.js"></script>
<title>Aruco Map visualization</title>
</head>
<body>
<div id="viz"></div>
<script type="text/javascript" src="js/viz.js"></script>
<script>
setScene('aruco_map');
addArucoMap();
addAxes();
</script>
</body>
</html>

View File

@@ -5,6 +5,7 @@
<li><a href="" id="wvs">View image topics</a> (<code>web_video_server</code>)</li> <li><a href="" id="wvs">View image topics</a> (<code>web_video_server</code>)</li>
<li><a href="" id="butterfly">Open web terminal</a> (<code>Butterfly</code>)</li> <li><a href="" id="butterfly">Open web terminal</a> (<code>Butterfly</code>)</li>
<li><a href="viz.html">View 3D visualization</a> (<code>ros3djs</code>)</li> <li><a href="viz.html">View 3D visualization</a> (<code>ros3djs</code>)</li>
<li><a href="aruco_map.html">3D visualization for markers map</a> (<code>ros3djs</code>)</li>
</ul> </ul>
<script type="text/javascript"> <script type="text/javascript">

View File

@@ -20,50 +20,75 @@ ros.on('close', function() {
titleEl.innerText = 'Disconnected'; titleEl.innerText = 'Disconnected';
}); });
var viewer = new ROS3D.Viewer({ var viewer, tfClient;
divID: 'viz',
width: 1000,
height: 600,
antialias: true
});
var tfClient = new ROSLIB.TFClient({ function setScene(fixedFrame) {
ros: ros, viewer = new ROS3D.Viewer({
angularThres: 0.01, divID: 'viz',
transThres: 0.01, width: 1000,
rate: 10.0, height: 600,
fixedFrame : 'map' antialias: true
}); });
// vehicle markers tfClient = new ROSLIB.TFClient({
var vehicleMarkers = new ROS3D.MarkerArrayClient({ ros: ros,
ros: ros, angularThres: 0.01,
tfClient: tfClient, transThres: 0.01,
topic: '/vehicle_marker', rate: 10.0,
rootObject: viewer.scene fixedFrame : fixedFrame
}); });
// camera markers var map = new ROS3D.Grid({
var cameraMarkers = new ROS3D.MarkerArrayClient({ ros: ros,
ros: ros, tfClient: tfClient,
tfClient: tfClient, rootObject: viewer.scene
topic: '/main_camera/camera_markers', });
rootObject: viewer.scene
});
// detected aruco markers viewer.scene.add(map);
var cameraMarkers = new ROS3D.MarkerArrayClient({ }
ros: ros,
tfClient: tfClient,
topic: '/aruco_detect/visualization',
rootObject: viewer.scene
});
var map = new ROS3D.Grid({ function addAxes() {
ros: ros, var axes = new ROS3D.Axes({
tfClient: tfClient, ros: ros,
// frameID: 'map', tfClient: tfClient,
rootObject: viewer.scene rootObject: viewer.scene
}); });
viewer.scene.add(axes);
}
viewer.scene.add(map); function addVehicle() {
new ROS3D.MarkerArrayClient({
ros: ros,
tfClient: tfClient,
topic: '/vehicle_marker',
rootObject: viewer.scene
});
}
function addCamera() {
new ROS3D.MarkerArrayClient({
ros: ros,
tfClient: tfClient,
topic: '/main_camera/camera_markers',
rootObject: viewer.scene
});
}
function addAruco() {
new ROS3D.MarkerArrayClient({
ros: ros,
tfClient: tfClient,
topic: '/aruco_detect/visualization',
rootObject: viewer.scene
});
}
function addArucoMap() {
new ROS3D.MarkerArrayClient({
ros: ros,
tfClient: tfClient,
topic: '/aruco_map/visualization',
rootObject: viewer.scene
});
}

View File

@@ -10,5 +10,11 @@
<body> <body>
<div id="viz"></div> <div id="viz"></div>
<script type="text/javascript" src="js/viz.js"></script> <script type="text/javascript" src="js/viz.js"></script>
<script>
setScene('map');
addVehicle();
addCamera();
addAruco();
</script>
</body> </body>
</html> </html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 901 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

BIN
docs/assets/op.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
docs/assets/px4flow_top.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 473 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 298 KiB

View File

@@ -43,12 +43,15 @@
* [Working with a LED strip on Raspberry 3](leds.md) * [Working with a LED strip on Raspberry 3](leds.md)
* [Using rviz and rqt](rviz.md) * [Using rviz and rqt](rviz.md)
* [Working with the ultrasonic distance gage](sonar.md) * [Working with the ultrasonic distance gage](sonar.md)
* [Working with a laser rangefinder](laser.md)
* [PX4 Simulation](sitl.md) * [PX4 Simulation](sitl.md)
* [Software autorun](autolaunch.md) * [Software autorun](autolaunch.md)
* [Controlling the copter from Arduino](arduino.md) * [Controlling the copter from Arduino](arduino.md)
* [Using an external 3G modem](3g.md) * [Using an external 3G modem](3g.md)
* Clever-based projects * Clever-based projects
* [Copter spheric guard](shield.md) * [Copter spheric guard](shield.md)
* [Face recognition system](face_recognition.md)
* [An Android transmitter](android.md)
* [Copter Hack 2018](copterhack2018.md) * [Copter Hack 2018](copterhack2018.md)
* [Copter Hack 2017](copterhack2017.md) * [Copter Hack 2017](copterhack2017.md)
* Supplementary materials * Supplementary materials
@@ -56,5 +59,7 @@
* [Flashing ESCs using BLHeliSuite](esc_firmware.md) * [Flashing ESCs using BLHeliSuite](esc_firmware.md)
* [MAVLink](mavlink.md) * [MAVLink](mavlink.md)
* [PX4 Logs and Topics](flight_logs.md) * [PX4 Logs and Topics](flight_logs.md)
* [Camera calibration](calibration.md)
* [Working with IR sensors on Raspberry Pi 3](ir_sensors.md)
* Textbook * Textbook
* [Theory and Videos](lessons.md) * [Theory and Videos](lessons.md)

141
docs/en/android.md Normal file
View File

@@ -0,0 +1,141 @@
# An Android transmitter
As early as in the frosty January 2018, all owners of Apple mobile devices got a nice Wi-Fi piloting app for iOS. And now, a year later, such an application is available for another operating system. The latest version may be downloaded [**here**](https://vk.com/away.php?to=https%3A%2F%2Fplay.google.com%2Fstore%2Fapps%2Fdetails%3Fid%3Dexpress.copter.cleverrc&cc_key=).
## Introduction
In this article, I will tell you how to write your own or modify an existing transmitter for Android yourself. We will use the popular language *Kotlin*, and we will use *Android Studio* for an IDE. For those who never used it, I recommend reading the following [*materials*](https://www.google.com/search?ei=xQxDXMH0C8OOmgW4mYigDQ&q=%D0%A7%D1%82%D0%BE+%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C+%D0%B5%D1%81%D0%BB%D0%B8+%D1%8F+%D0%BD%D0%B5+%D1%83%D0%BC%D0%B5%D1%8E+%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C+%D0%BF%D0%BE%D0%B4+%D0%B0%D0%BD%D0%B4%D1%80%D0%BE%D0%B8%D0%B4%3F&oq=%D0%A7%D1%82%D0%BE+%D0%B4%D0%B5%D0%BB%D0%B0%D1%82%D1%8C+%D0%B5%D1%81%D0%BB%D0%B8+%D1%8F+%D0%BD%D0%B5+%D1%83%D0%BC%D0%B5%D1%8E+%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D1%8C+%D0%BF%D0%BE%D0%B4+%D0%B0%D0%BD%D0%B4%D1%80%D0%BE%D0%B8%D0%B4%3F&gs_l=psy-ab.3...4413.17423..17726...9.0..2.442.4577.45j5j1j0j1....2..0....1..gws-wiz.....6..0i71j35i39j0i131j0j0i67j0i131i67j0i22i30j33i22i29i30j33i21j33i160.0bZz-WGxoHY). The entire application code can be found [**here**](https://github.com/Tennessium/android). If you want to immediately get an app to further tuning, run the following command:
```Bash
git clone https://github.com/Tennessium/android
```
However, to make you fully understand the application, I will tell you about each stage of the project, as if you were building it from scratch.
## Wrapper
Let's start with the simplest thing — the appearance of our application. At [**GitHub**](https://github.com/CopterExpress/clever/tree/master/apps/android/app/src/main/assets), you can find *HTML*, *CSS* and *JavaScript* files, which make up the web page to be used for controlling the copter. To have this page displayed in our application, do the following:
1. Create folder **assets** in the main folder of the app named **app**
2. Add to it all files from [here](https://github.com/CopterExpress/clever/tree/master/apps/android/app/src/main/assets)
If you reached this stage, you already have the web page you want, congratulations! Now we have to display it somehow in the app. To do this, in class *activity* in method **onCreate**, write the following code:
```Kotlin
main_web.loadUrl("file:///android_asset/index.html")
```
Where *main_web* is the ID of your *WebView*, which is in the *xml* file of the *activity* selected by you.
Unfortunately, the quadcopter transmitter requires the entire screen of the device, while the interface elements of the system interfere with full-fledged use of the program. For this purpose, at the beginning of method **onCreate**, call the following function:
```Kotlin
private fun fullScreenCall() {
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
if (Build.VERSION.SDK_INT < 19) {
val v = this.window.decorView
v.systemUiVisibility = View.GONE
} else {
//for higher API versions.
val decorView = window.decorView
val uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
decorView.systemUiVisibility = uiOptions
}
}
```
This feature allows getting rid of the system interface elements. Let's go ahead.
This is how the transmitter looks at this stage:
<img src="../assets/IMG_4397.PNG" width="50%">
If you run your application, you will see that the sticks are not functioning. This is due to the fact that *JavaScript* is disabled in our page. To enable it, write the following code:
```Kotlin
main_web.settings.apply {
domStorageEnabled = true
javaScriptEnabled = true
loadWithOverviewMode = true
useWideViewPort = true
setSupportZoom(false)
}
```
This piece of code allows the page to use *JavaScript* and at the same time prepares for the next stage - **logics**.
## Receiving data from the web page
To let your phone receive data from the *HTML page*, create a class for interacting with the web interface
```Kotlin
class WebAppInterface(c: Context) {
@JavascriptInterface
public fun postMessage(message: String) {
val data = JSONObject(message)
send("255.255.255.255", 35602, pack(
data.getInt("x").toShort(),
data.getInt("y").toShort(),
data.getInt("z").toShort(),
data.getInt("r").toShort()))
}
}
```
This class will receive messages from the web page sent by the *postMessage* where argument *message* is the message from the page.
Now we have to link classes **WebAppInterface** and **MainActivity**. For this you have to add just one line to method **onCreate**:
```Kotlin
main_web.addJavascriptInterface(WebAppInterface(this), "appInterface")
```
## Sending data to the copter
**Important!**
For working in Internet in the *Android* platform, add the following line to tag *manifest* in file **AndroidManifest.xml**:
```XML
<uses-permission android:name="android.permission.INTERNET"/>
```
It will grant your application access to the Internet, and the ability to send data via **Wi-Fi**. And you will now learn how to do that. Let's go ahead.
You have probably noticed function *send* in class **WebAppInterface**. It is this function that sends data to the copter. Let's declare it **outside classes**:
```Kotlin
fun send(host: String, port: Int, data: ByteArray, senderPort: Int = 0): Boolean {
var ret = false
var socket: DatagramSocket? = null
try {
socket = DatagramSocket(senderPort)
val address = InetAddress.getByName(host)
val packet = DatagramPacket(data, data.size, address, port)
socket.send(packet)
ret = true
} catch (e: Exception) {
e.printStackTrace()
} finally {
socket?.close()
}
return ret
}
```
This function sends data via the [*user datagram protocol*](https://www.google.com/search?q=udp+%D0%BF%D1%80%D0%BE%D1%82%D0%BE%D0%BA%D0%BE%D0%BB&oq=udp+&aqs=chrome.0.69i59j69i57j35i39j0l3.1434j1j7&sourceid=chrome&ie=UTF-8) to the copter. The program sends **bytes**, so it would be a good idea to declare the function for creating an array of **bytes** from four variables:
```Kotlin
fun pack(x: Short, y: Short, z: Short, r: Short): ByteArray {
val pump_on_buf: ByteBuffer = ByteBuffer.allocate(8)
pump_on_buf.putShort(r)
pump_on_buf.putShort(z)
pump_on_buf.putShort(y)
pump_on_buf.putShort(x)
return pump_on_buf.array().reversedArray()
}
```
## Summary
Now your app has the full functionality of its analog for **iOS**. You can customize it as you wish. For any questions about the app, contact us in Telegram @Tenessinum.

228
docs/en/calibration.md Normal file
View File

@@ -0,0 +1,228 @@
# Camera calibration
Computer vision is becoming more and more widespread. Often, computer vision algorithms are not precise and obtain distorted images from the camera, which is especially true for fisheye cameras.
![img](../assets/img1.jpg)
> The image is "rounded" closer to the edge.
Any computer vision algorithm will perceive the picture incorrectly. To remove such distortion, the camera that receives the image is to be calibrated in accordance with its own peculiarities.
## Script installation
First, you have to install the necessary libraries:
```
pip install numpy
pip install opencv-python
pip install glob
pip install pyyaml
pip install urllib.request
```
Then download the script from the repository:
```(bash)
git clone https://github.com/tinderad/clever_cam_calibration.git
```
Go to the downloaded folder and install the script:
```(bash)
cd clever_cam_calibration
sudo python setup.py build
sudo python setup.py install
```
If you are using Windows, download the archive from the [repository](https://github.com/tinderad/clever_cam_calibration/archive/master.zip), unzip it and install:
```(bash)
cd path\to\archive\clever_cam_calibration\
python setup.py build
python setup.py install
```
> path\to\archive path to unpacked archive.
## Preparing for calibration
You will have to prepare a calibration target. It looks like a chessboard. The file is available for downloading [here](https://www.oreilly.com/library/view/learning-opencv-3/9781491937983/assets/lcv3_ac01.png).
Glue a printed target to any solid surface. Count the number of intersections on the board lengthwise and widthwise, measure the size of a cell (mm).
![img](../assets/chessboard.jpg)
Turn on Clever and connect to its Wi-Fi.
> Navigate to 192.168.11.1:8080 and check whether the computer receives images from the image_raw topic.
## Calibration
Run script **_calibrate_cam_**:
**Windows:**
```(bash)
>path\to\python\Scripts\calibrate_cam.exe
```
> path\to\Python path to the Python folder
**Linux:**
```(bash)
>calibrate_cam
```
Specify board parameters:
```(bash)
>calibrate_cam
Chessboard width: # Intersections widthwise
Chessboard height: # Intersections heightwise
Square size: # Length of cell edge (mm)
Saving mode (YES - on): # Save mode
```
> Save mode: if enabled, all received pictures will be saved in the current folder.
The script will start running:
```
Calibration started!
Commands:
help, catch (key: Enter), delete, restart, stop, finish
```
To calibrate the camera, make at least 25 photos of the chessboard at various angles.
![img](../assets/calibration.jpg)
To make a photo, enter command **_catch_**.
```(bash)
>catch
```
The program will inform you about the calibration status.
```(bash)
...
Chessboard not found, now 0 (25 required)
> # Enter
---
Image added, now 1 (25 required)
```
> Instead of entering command **_catch_** each time, you can just press **_Enter_** (enter a blank line).
After you have made a sufficient number of images, enter command **_finish_**.
```(bash)
...
>finish
Calibration successful!
```
### Calibration by the existing images
If you already have images, you can calibrate the camera by them with the help of script **_calibrate_cam_ex_**.
```(bash)
>calibrate_cam_ex
```
Specify target characteristics and the path to the folder with images:
```(bash)
>calibrate_cam_ex
Chessboard width: # Intersections widthwise
Chessboard height: # Intersections heightwise
Square size: # Length of cell edge (mm)
Path: # Path to the folder with images
```
Apart from that, this script works similarly to **_calibrate_cam_**.
The program will process all received pictures, and create file **_camera_info_****_._****_yaml_** in the current folder. Using this file, you can equalize distortions in the images obtained from this camera.
> If you change the resolution of the received image, you will have to re-calibrate the camera.
## Correcting distortions
Function **_get_undistorted_image(cv2_image, camera_info)_** is responsible for obtaining a corrected image:
* **_cv2_image_**: An image encoded into a cv2 array.
* **_camera_****___****_info_**: The path to the calibration file.¬
The function returns a cv2 array, into which the corrected image is coded.
> If you are using a fisheye camera provided with Clever, for processing images with resolution 320x240 or 640x480, you can use the existing calibration settings. To do this, pass parameters **_clever_cam_calibration.clevercamcalib.CLEVER_FISHEYE_CAM_320_** or **_clever_cam_calibration.clevercamcalib.CLEVER_FISHEYE_CAM_640_** as argument **_camera_info_**, respectively.
## Examples of operation
Source images:
![img](../assets/img1.jpg)
![img](../assets/img2.jpg)
Corrected images:
![img](../assets/calibresult.jpg)
![img](../assets/calibresult1.jpg)
## An example of usage
**Processing image stream from the camera**.
This program receives images from the camera on Clever and displays them on the screen in corrected for, using the existing calibration file.
```python
import clevercamcalib.clevercamcalib as ccc
import cv2
import urllib.request
import numpy as np
while True:
req = urllib.request.urlopen('http://192.168.11.1:8080/snapshot?topic=/main_camera/image_raw')
arr = np.asarray(bytearray(req.read()), dtype=np.uint8)
image = cv2.imdecode(arr, -1)
undistorted_img = ccc.get_undistorted_image(image, ccc.CLEVER_FISHEYE_CAM_640)
cv2.imshow("undistort", undistorted_img)
cv2.waitKey(33)
cv2.destroyAllWindows()
```
## The usage for ArUco
To apply the calibration parameters to the ArUco navigation system, move the calibration .yaml file to Raspberry Pi of Clever, and initialize it.
> Don't forget to connect to Wi-Fi of Clever.
The SFTP protocol is used for transferring the file. This example, WinSCP program is used.
Connect to Raspberry Pi via SFTP:
> Password: _**raspberry**_
![img](../assets/wcp1.png)
Press “Enter”. Go to _**/home/pi/catkin_ws/src/clever/clever/camera_info/**_, and copy the calibration .yaml file to this folder:
![img](../assets/wcp2.jpg)
Now we have to select this file in ArUco configuration. Connection via SSH is used for this purpose. This example, PuTTY program is used.
Connect to Raspberry Pi via SSH:
![img](../assets/pty1.jpg)
Log in with username _**pi**_ and password _**raspberry**_, go to directory _**/home/pi/catkin_ws/src/clever/clever/launch**_ and start editing configuration _**main_camera.launch**_:
![img](../assets/pty2.jpg)
In line _**camera node**_, change parameter _**camera_info**_ to _**camera_info.yaml**_:
![img](../assets/pty3.jpg)
> Don't forget to change camera resolution.

163
docs/en/face_recognition.md Normal file
View File

@@ -0,0 +1,163 @@
# Face recognition system
## Introduction
Recently, face recognition systems have been getting a wider use, the application scope of this technology is really expansive: from regular selfie drones to police drones. Everywhere it is being integrated into various devices. The recognition process itself is really fascinating, and that's what inspired me to create a project associated with it. The purpose of my internship project was to create a simple open source system for face recognition with a Clever quadcopter. The program takes images from the quadcopter's camera and processes it on a PC. Therefore, all other instructions are executed on a PC.
## Development
The first task was finding a recognition algorithm. As a solution to the problem, [a ready API for Python](https://github.com/ageitgey/face_recognition) was chosen. This API combines several advantages: recognition speed and accuracy, and ease of use.
## Installation
First, you have to install all the necessary libraries:
```(bash)
pip install face_recognition
pip install opencv-python
```
Then download the script from the repository:
```(bash)
git clone https://github.com/mmkuznecov/face_recognition_from_clever.git
```
## Code explanation
Enable libraries:
```python
import face_recognition
import cv2
import os
import urllib.request
import numpy as np
```
***This part of the code is intended for Python 3. In Python 2.7, enable urllib2 instead of urllib:***
```python
import urllib2
```
Create a list of encodings for images and a list of names:
```python
faces_images=[]
for i in os.listdir('faces/'):
faces_images.append(face_recognition.load_image_file('faces/'+i))
known_face_encodings=[]
for i in faces_images:
known_face_encodings.append(face_recognition.face_encodings(i)[0])
known_face_names=[]url
for i in os.listdir('faces/'):
i=i.split('.')[0]
known_face_names.append(i)
```
***Addition: all images are stored in folder faces in format name.jpg***
<img src="../assets/screen.jpg" width="50%">
<img src="../assets/Mikhail.jpg" width="30%">
<img src="../assets/Timofey.jpg" width="30%">
Initialize some variables:
```python
face_locations = []
face_encodings = []
face_names = []
process_this_frame = True
```
Get the image from the server, and convert it to format cv2:
```python
req = urllib.request.urlopen('http://192.168.11.1:8080/snapshot?topic=/main_camera/image_raw')
arr = np.asarray(bytearray(req.read()), dtype=np.uint8)
frame = cv2.imdecode(arr, -1)
```
***For Python 2.7:***
```python
req = urllib2.urlopen('http://192.168.11.1:8080/snapshot?topic=/main_camera/image_raw')
arr = np.asarray(bytearray(req.read()), dtype=np.uint8)
frame = cv2.imdecode(arr, -1)
```
Further explanation of the code is available at GitHub of the used API in the comments to [the next script](https://github.com/ageitgey/face_recognition/blob/master/examples/facerec_from_webcam_faster.py)
## Using
It is enough to connect to "Clever" via Wi-Fi and check whether the video stream from the camera is working correctly.
Then just run the script:
```(bash)
python recog.py
```
And the output:
<img src="../assets/Mikhail_output.jpg" width="50%">
<img src="../assets/Timofey_output.jpg" width="50%">
## Possible difficulties
When the script is started, the following error may pop up:
```python
known_face_encodings.append(face_recognition.face_encodings(i)[0])
IndexError: list index out of range
```
In this case, try to edit the images in folder faces, perhaps the program cannot recognize faces in the images due to poor quality.
## Using the calibration
To improve recognition accuracy, you can use camera calibration. The calibration module may be installed using [a special package](https://github.com/tinderad/clever_cam_calibration). Instructions for installation and use are available in file calibration.md. The program that uses the calibration package is named recog_undist.py
**Code brief explanation:**
Enable installed package:
```python
import clever_cam_calibration.clevercamcalib as ccc
```
Add the following lines:
```python
height_or, width_or, depth_or = frame.shape
```
This way, you will obtain information about image size, where height_or is the height of the initial image in pixels, and width_or is the width of the initial image.
Then correct distortions in the initial image, and get its parameters:
```python
if height_or==240 and width_or==320:
frame=ccc.get_undistorted_image(frame,ccc.CLEVER_FISHEYE_CAM_320)
elif height_or==480 and width_or==640:
frame=ccc.get_undistorted_image(frame,ccc.CLEVER_FISHEYE_CAM_640)
else:
frame=ccc.get_undistorted_image(frame,input("Input your path to the .yaml file: "))
height_unz, width_unz, depth_unz = frame.shape
```
***In this case, we pass argument ссс.CLEVER_FISHEYE_CAM_640, since the resolution of the image in this example, is 640x480; you can also use ссс.CLEVER_FISHEYE_CAM_320 for resolution 320x240, otherwise you will have to send the path to the .yaml calibration file as the second argument.***
Finally, return the image to its initial size:
```python
frame=cv2.resize(frame,(0,0), fx=(width_or/width_unz),fy=(height_or/height_unz))
```
This was, you can significantly improve recognition accuracy since the image processed will not be so badly distorted.
<img src="../assets/misha_calib.jpg" width="50%">
<img src="../assets/tim_calib.jpg" width="50%">

232
docs/en/ir_sensors.md Normal file
View File

@@ -0,0 +1,232 @@
# Working with IR sensors on Raspberry Pi 3
Infrared sensors are a convenient tool for transmitting any commands to the copter. They are flexible in configuration, and interaction with them is possible in Python.
## Connecting the IR receiver
Most IR receivers operate and are connected the same way. Such receivers have 3 pins for connecting: G/GND — ground V/VCC — 5V power, S/OUT — signal.
<img src="../assets/IR_reciver_connection.png" height="500px" alt="ir reciver connection to raspberry">
> **Hint** The signal port doesn't have to be connected to port GPIO 17; this pin may be changed during the [in/out port settings](#in/out).
## Configuring the IR receiver to work with the LIRC module
LIRC (Linux Infrared Remote Control) is a stable and time-proven open source library, which allows sending and receiving commands via an infrared port. LIRC is supported by Raspbian.
To install LIRC and related modules, connect your Raspberry Pi to the Internet and run the console command:
```(bash)
sudo apt-get update
sudo apt-get install lirc
sudo apt-get install python-lirc
pip install py-irsend
```
> **Hint** To correctly edit the system files, superuser privileges are required; when calling a text editor, use `sudo`.
<a name="in/out"></a>
After installing the module, edit file `/etc/modules` and add line:
```
lirc_dev
lirc_rpi gpio_in_pin=18 gpio_out_pin=17
```
Where:
+ `gpio_in_pin` is the input pin from the receiver
+ `gpio_out_pin` is the transmitter output pin
Update the following line in file `/boot/config.txt`:
```
dtoverlay=lirc-rpi,gpio_in_pin=18,gpio_out_pin=17
```
Add the following lines to file `/etc/lirc/hardware.conf`. Is this file does not exist, create it yourself.
```
LIRCD_ARGS="--uinput --listen"
LOAD_MODULES=true
DRIVER="default"
DEVICE="/dev/lirc0"
MODULES="lirc_rpi"
```
Update the following lines in file `/etc/lirc/lirc_options.conf`
```
driver = default
device = /dev/lirc0
```
All required settings are made, you now have to restart your Raspberry Pi device to complete the installation. To do so, run:
```(bash)
sudo reboot
```
After rebooting, check its status by calling command:
```(bash)
sudo /etc/init.d/lircd status
```
If everything has been done correctly, the status should be `active`.
To check whether the installed module LIRC is running, disable daemon `lircd`, and call the appropriate command:
```(bash)
sudo /etc/init.d/lircd stop
mode2 -d /dev/lirc0
```
Now point the IR transmitter on your device and tap a few keys. You should see something like this:
```
space 402351
pulse 135
space 7085
pulse 85
space 2903
pulse 560
space 1706
pulse 535
```
> **Hint** If you are using an IR transmitter (a TV remote, an air conditioner remote, etc. and you are not getting the signal when checking, your remote is evidently using another signal frequency. When using receivers such as TSOP 22XX, the operating frequency of the signal reception will be in the range between 30 and 50 kHz.
## Write your configuration of the IR transmitter
<a name="remote_control"></a>
If you want to use your own IR transmitter, you will have to write its specific settings using the supplied module `irrecord`. For this purpose, disable daemon `lircd`, and call the appropriate command. During transmitter calibration, stick to all written instructions.
> **Hint** Please note that the last step of the calibration will be specifying the names of the keys that you will want to decode programmatically. To view the list of available names, call command `irrecord --list-namespace`.
```(bash)
irrecord -d /dev/lirc0 ~/lircd.conf
```
If you have managed to successfully write the configuration of your transmitter, file `your-name.lircd.conf` should appear in folder `/home/pi/`. Now you need to move the written configuration file to working folder `lirc`, and restart the daemon:
```
sudo cp ~/your-name.lircd.conf /etc/lirc/lircd.conf
sudo /etc/init.d/lircd restart
```
To check whether the written configuration is recognized, call the appropriate module. Now when you tap the keys that you have specified in the previously created configuration, the terminal will show debug information about which key has been pressed.
```
irw
```
> **Caution** when working with some transmitters, there are situations where the bit descriptions of keys are redundant; in this case, command `irw` may fail. To correct this error, open file `etc/lirc/lircd.conf` and check what the description of your keys looks like; if it looks like `KEY_1 0x00FF6897 0x7EE0CF2C` and in all lines the second digits match, you have to remove it, so that lines with keys assignment looks like `KEY_1 0x00FF6897` and all digits in them are unique. After completing these steps, close the file and restart the daemon.
If you did everything correctly, upon tapping a key, you will see the output similar to:
```
0000000000ff6897 00 KEY_1 pult
0000000000ff6897 01 KEY_1 pult
0000000000ff9867 00 KEY_2 pult
0000000000ff9867 01 KEY_2 pult
```
This means that your configuration is correctly detected by the program, and now you can program the desired action for tapping appropriate keys.
## Working with IR sensors in Python
To be able to use signals from the IR receiver in Python programs, you'll need package `python-lirc`. [Install it](#install), if necessary.
For correct obtaining information, create file `lircrc` in your own script, which will store settings of your keys and the program response upon calling.
This file is created in the folder from which your script will be called, `/home/pi/` by default.
To create the required file, use any text editor:
```(bash)
sudo nano .lircrc
```
The format of this file should be something like this:
```
begin
prog = myprogram
button = KEY_1
config = one
end
begin
prog = myprogram
button = KEY_2
config = two
end
```
Where:
+ `prog` is the name of the program that you will call from your script
+ `key` is the name of the key that you entered during transmitter setup
+ `config` is the information to be passed to your program upon tapping a specified key
All settings are now made, and you can proceed directly to programming IR signals.
For this purpose, create a Python script that will accept the values of the keys pressed and perform required action s accordingly.
An example of such a script:
```python
import lirc
import fly_module
# ...
sockid = lirc.init('myprogram')
inf = lirc.nextcode()
if inf[0] == 1:
print('You pressed key 1')
elif inf[0] == 2:
print('You pressed key 2')
lirc.deinit()
```
## Working with the IR transmitter
To work with the IR transmitter, connect it to the ports specified [during setup](#in/out).
<img src="../assets/IR_transmitter_connection.png" height="500px" alt="IR transmitter connection to raspberry">
<img src="../assets/IR_transmitter.png" height="200px" alt="IR transmitter scheme">
> **Hint** if you are using a ready IR transmitter board, connect it to required pins of Raspberry in accordance with pins marking, in the same way as with the receiver.
If everything has been properly connected, you will be able to send signals specified in [transmitter settings](#remote_control) using the command:
```(bash)
irsend SEND_ONCE deviceName keyName
```
Where:
+ SEND_ONCE is the parameter responsible for sending a single signal, or sending a signal from a depressed and held down key
+ deviceName is the name of the transmitter specified during [setup](#remote_control)
+ keyName is the name of one of the keys specified during transmitter configuration
To work with `irsend` inside your script, you'll need module `python-irsend`; if necessary, [install it](#install).
To use `irsend`, import the library and call the appropriate command:
```python
from py_irsend import irsend
irsend.send_once('YourRemote', ['YourKey'])
```
Where:
+ YourRemote is the name of your transmitter specified during setup
+ YourKey is the name of one of the buttons specified during setup

89
docs/en/laser.md Normal file
View File

@@ -0,0 +1,89 @@
# Working with a laser rangefinder
## Rangefinder VL53L1X
The rangefinder model recommended for Clever is STM VL53L1X. This rangefinder can measure distances from 0 to 4 m while ensuring high measurement accuracy.
The [image for Raspberry Pi](microsd_images.md) contains pre-installed corresponding ROS driver.
### Connecting to Raspberry Pi
> **Note** For correct operation of a laser rangefinder with a flight countroller <a id="download-firmware" href="https://github.com/CopterExpress/Firmware/releases">custom PX4 firmware</a> is needed. See more about firmware in [corresponding article](firmware.md).
<script type="text/javascript">
fetch('https://api.github.com/repos/CopterExpress/Firmware/releases').then(res => res.json()).then(function(data) {
for (let release of data) {
if (!release.prerelease && !release.draft && release.tag_name.includes('-clever.')) {
document.querySelector('#download-firmware').href = release.html_url;
return;
}
}
});
</script>
Connect the rangefinder to pins 3V, GND, SCL and SDA via the I²C interface:
<img src="../assets/raspberry-vl53l1x.png" alt="Connecting VL53L1X" height=600>
If the pin marked GND is occupied, you can use another free one using the [pinout](https://pinout.xyz).
> **Hint** Via the I²C interface, you can connect several peripheral devices simultaneously. For this purpose, use a parallel connection.
### Enabling
[Connect via SSH](ssh.md) and edit file `~/catkin_ws/src/clever/clever/launch/clever.launch` so that driver VL53L1X is enabled:
```xml
<arg name="rangefinder_vl53l1x" default="true"/>
```
By default, the rangefinder driver sends the data to Pixhawk (via topic `/mavros/distance_sensor/rangefinder_sub`). To view data from the topic, use command:
```(bash)
rostopic echo mavros/distance_sensor/rangefinder_sub
```
### PX4 settings
To use the rangefinder data in [PX4 must be configured](px4_parameters.md).
When using EKF2 (`SYS_MC_EST_GROUP` = `ekf2`):
* `EKF2_HGT_MODE` = `2` (Range sensor) when flying over horizontal floor;
* `EKF2_RNG_AID` = `1` (Range aid enabled) in other cases.
When using LPE (`SYS_MC_EST_GROUP` = `local_position_estimator, attitude_estimator_q`):
* The "pub agl as lpos down" flag is ticked in the `LPE_FUSION` parameter when flying over horizontal floor.
### Obtaining data from Python
To obtain data from the topic, create a subscriber:
```python
from sensor_msgs.msg import Range
# ...
def range_callback(msg):
# Processing new data from the rangefinder
print 'Rangefinder distance:', msg.range
rospy.Subscriber('mavros/distance_sensor/rangefinder_sub', Range, range_callback)
```
### Data visualization
To build a chart using the data from the rangefinder, one can use rqt_multiplot.
rviz may be used for data visualization. To do this, add a topic of the `sensor_msgs/Range` type to visualization:
<img src="../assets/rviz-range.png" alt="Range in rviz">
See [read more about rviz and rqt](rviz.md).
<!--
### Connecting to Pixhawk / Pixracer
Support for rangefinder VL53L1X is not yet implemented in the PX4 firmware (in version *1.8.2*).
-->

115
docs/en/lesson3.md Normal file
View File

@@ -0,0 +1,115 @@
Lesson # 3 "Theory of soldering"
======================
Soldering metal is a rather complex physicochemical process; however, it boils down to fairly simple techniques and operations. To solder properly without wandering in the wilds of the theory, one should exactly follow the rules of soldering. This especially applies to the choice of the soldering method, the solder and the flux, depending on the kind of items connected and the requirements to the soldered joint.
What is soldering?
----------------
Soldering at home includes the following technological operations:
1. The surfaces to be soldered are to be cleaned from dirt, corrosion, etc. They are to be sanded until shine is seen, i.e., until no visible traces of oxides are to be visible;
2. Apply flux, i.e., a substance that removes oxide residues and does not allow oxidation of the surfaces during the further process. For applying to surfaces to be tinned, solid or liquid fluxes and flux pastes are recommended;
3. Then the surfaces are tinned — melted solder (an alloy specially intended for soldering) is applied to them, it spreads in a thin film and chemically combines with the base metal;
4. The items to be soldered are mechanically connected with a cable, or using forceps, pliers, vise, clamps, etc. More flux is applied to prevent oxidation of heated solder;
5. More heated solder (maybe another one) is applied, until a seam of desired quality is obtained;
6. If a soldering iron with a tinned tip was used for soldering, after work, it should be cleaned and coated with an inactive flux. To ensure high quality of soldering, a conventional soldering iron should be kept with its tip fluxed!
Cleanup
--------
Cleanup after cleaning is the first tricky soldering operation. Never use abrasives for this purpose! The smallest abrasive particles get embedded in the metal, and it is impossible to completely remove them. Subsequently, they become the foci of the processes that destroy the seam. The surface are cleaned up for soldering using files, grinders (various types of scrapers), or just with a knife. But best way, especially if current-carrying wires are being prepared for soldering, is covering them with activated flux, which should be carefully removed after soldering. It is easily done with a toothbrush moistened with alcohol.
How to tin/solder, and what to use?
-----------------------
For the following operations, you will need a special electric-heating tool: a soldering iron or a blowtorch. At home, an electric soldering iron with a tin-plated copper tip is most often used for soldering.
![scheme](../assets/4_1.png)
Tinning should be done as follows:
* Thin wires should be tinned easily, without pressure, by moving the tin on the bare ends of the wire at one and the opposite end until the solder melts. The wire is held with its end down. The excessive drop of the solder that collects at the end is removed with the soldering iron.
* Thick wires are tinned by moving the tip in a spiral back and forth.
* To flat slim long items, the solder is to be applied to the end of the tip, and the top is moved along. When un-tinned edges of the item are seen after the tip, more flux is applied to the untinned part, and another drop of tin is used for tinning.
* A long wider item — same as above, but the tip is moved in S-turns.
* A wide item — the tip moves in a spiral from the center to the edges.
Special aspects of soldering wires
--------------------------
Is the preliminary joint of soldered items, most problems occur with wires: one has to touch them with hands, which results in fouling of the surface of the metal; besides, seams between wires have to withstand mechanical loads more frequently than other soldered joints.
### Twisted wires
Before wires are soldered, they should be properly twisted. The main methods of twisting wires for soldering are shown in the Figure. Each of them has its own purpose:
* Band twisting is used for rigid (thick, one core) current carrying wires, i.e., the wires that transmit electric power, especially outside wires. Band twisting ensures sufficient electric contact even with insufficient soldering, or overheating of oxidized seam.
* Groove twists are used for wires with easily-melting insulation (common PVC, polyethylene) when complete spread of the solder with minimal heating is required. Groove twists are only heated along the groove.
* Simple twists may be used to connect both single core and multicore wires immediately after insulation stripping (shiny).
* Simple consecutive twisting, the so-called British twisting, is used for connecting current-carrying wires of flexible cables with the cross-section up to 1.4 sq. mm not subjected to regular high mechanical loads, e.g., electrical extension cords or temporary connections.
![scheme](../assets/4_2.png)
Electrical wires subjected to regular and/or constant mechanical loads are to be stranded. They are to be twisted as shown in the Figure below: the ends are spread apart, the "brooms" are slid into each other and twisted the British way. Soldering is performed with the use of a fusible solder with high tensile strength, e.g. POSK-50 (see below) with activated flux that does not require residue removal, also see below. Parallel (dead end) twists of wires with the cross section greater than 0.7 sq. mm should be soldered preferably by dipping into molten solder, see below. Otherwise, the wires re to be either heated for a long time, or with a too powerful soldering iron, which results in damaged insulation, and the flux boiling away prematurely.
What can be soldered, but should not be soldered
Flexible coaxial cables and cables for computer networks such as twisted pair, are not intended for soldering. An experienced cableman that has a good idea about electrodynamics of signal lines can in exceptional cases make a coupling with them. But when made by a layman, even if he is otherwise a qualified electronic engineer and installer, the throughput and noise immunity of the line will fall below the permissible level, down to total loss.
How to clean and preserve the soldering tip
The soldering tip is to be cleaned of excess solder by rubbing on soft porous or fibrous lining. Polyurethane foam is used most frequently, but it is not the best choice: it burns and sticks to the tip. The best material for cleaning is natural felt or basalt cardboard. But still better is two-stage cleaning, first with a sponge made of a metal strip, and with felt afterwards. After cleaning, the soldering iron is to be turned off, the tip is immersed into hard colophony, and some time is waited until it stops bubbling. After that, the tip is taken out, and the soldering iron is held tip down for the excess colophony to drain. Upon complete cooling, the soldering iron may be sent for storage.
Solders and fluxes
--------------
Solders from POS-90 to Avia-2 are soft solders for low temperature soldering. They guarantee only electrical contact. POS-30 and POS-40 are used for soldering copper, brass, bronze with inactive fluxes, and the same with steel and steel to steel with active fluxes. POSSR-15 may be used for soldering dipped galvanized steel with inactive fluxes; other solders in this corrode the zinc down to the steel and the soldering falls off soon. 34A, MF-1 and RSr-25 are solid solders for high temperature brazing. Solder 34A nay be used for soldering aluminum in the flame (see further about soldering aluminum) with special fluxes, see further, too. Solder MF1 is used for soldering copper to steel with activated flux. "Low strength requirements" in this case means that the strength of the joint will be closer to that of copper than of steel. When used with a dry soldering iron, PSr-25 is suitable for soldering jewelry, Tiffany stained glass, etc.
### Fluxes
Soldering fluxes are divided into neutral (inactive, acid-free) that do not interact chemically with the base metal, or interact to a negligible extent, activated fluxes that chemically interact with the base metal upon heating, and active (acid) that interact with cold base metal. As far as fluxes are concerned, our age has brought the largest number of advances which are mostly good, but let's start with the unpleasant ones. The first one is the fact that there is not technically pure acetone for washing soldering, since it is used it is used in the clandestine production of drugs, and has a narcotic action by itself. Substitutes for technical acetone are solvents 646 and 647.
The second one is that zinc chloride in activated flux pastes is often replaced with borax. Hydrochloric acid is a highly toxic volatile chemically aggressive substance; zinc chloride is also toxic and when heated, sublimates, i.e., it evaporates without melting. Borax is safe, but when heated, produces a large amount of crystallization water, which slightly affects soldering quality.
![flux](../assets/4_3.png)
Soldering joints made with the spirit-colophony flux are to be washed: colophony contains succinic acid, which destroys metal upon prolonged contact. In addition, accidentally spilled spirit-colophony flux instantaneously spreads over a large area and turns into an extremely sticky stuff that takes very long to dry, and its stains cannot be removed from clothes, furniture, or floor and walls. In general, spirit-colophony is a good soldering flux, but not for rubbernecks and butterfingers. A full-fledged substitute for the spirit-colophony flux, but not so nasty if handled carelessly, is the flux named TAGS. If steel parts are more massive than allowed for soldering with soldering acid and the soldering should be stronger, flux F38 is used for soldering. It is a universal flux that can be used with virtually any metals in any combination, including aluminum, but the strength of the joint with it has not been standardized. We'll yet return to soldering aluminum.
### Other types of soldering
Tinkerers also often use a dry soldering iron with an untinned tip, the so-called soldering pencil, pos. 1 in the Figure. It is good where solder spreading outside the area of soldering is unacceptable: in jewelry, stained glass, soldered items of applied art. Dry soldering is sometimes used for soldering surface mounted chips, with the distance between pins of 1.25 or 0.625 mm, but it is risky even for experienced professionals: the poor thermal contact requires excessive power of the soldering iron and prolonged heating, while it is impossible to ensure heating stability during manual soldering. White resin POSK-40, 45, or 50, and flux pastes that do not require residue removal are used for dry soldering.
![scheme](../assets/4_4.png)
### Small-scale soldering
Soldering of printed circuit boards has its own peculiarities. Wires are not tinned, as pins of the components and chips are already tinned. In the amateur conditions, first, tinning current-carrying paths makes little sense, if the device operates at frequencies up to 40 50 MHz. In industrial production, circuit-board are tinned using low-temperature methods, e.g., by sputtering or electroplating. Heating with a soldering iron tracks along the entire length deteriorates their adhesion with the substrate and increases the probability of delamination. After installation of components, it is best to have the circuit-board covered with varnish Copper will immediately darken, but the efficiency of the device will not be affected, if only we are not talking about the microwave frequencies.
### Soldering electronic components on a printed circuit board
Then, take a look at something ugly on the left in the next Figure. Such poor quality could, even in the Ministry of electronics industry could be the reason for transferring fitters to movers or helpers. The reason was not even appearance or overuse of expensive solder, but, firstly, the fact that the mount pads and the components overheated while these drops of solder cooled down. Large and heavy drops of solder are pretty inert weights for the already weakened paths. Radio enthusiasts are well familiar with the effect: when one, two or more paths peel off from a circuit board that has fallen on the floor. Even without waiting for the first re-soldering
![soldering](../assets/4_5.png)
Soldering drops on circuit boards should have around shape, be smooth, and have the height not exceeding 0.7 of the mounting pad diameter, see right in the Figure. The ends of pins should slightly protrude from the drops. By the way, the circuit-board is completely self-made. There is a way to make a homemade circuit board as crisp and precise as a factory-made one, and place whatever inscriptions on it. White sports are glares from the varnish during under the flashlight. The drops are sagged in and shrunk - it is also a defect. A sagged in drop means that the soldier was not enough, and shrunk drop also means that air has penetrated the solder. If the device is not working or there are suspected dry joints, check these locations first.
### Chips, soldering
Chips in DIP-packages re soldered like other electronic components. Soldering iron power should be up to 25 watts. The solder should be POS-61; the flux should be TAGS or spirit colophony. Is remainders should be washed away with acetone or its substitute: alcohol poorly washes away colophony, and it cannot be removed between the pins even with a brush or cloth. As to chips and microchips, soldering them manually is highly not recommended by professionals of any level: it is a lottery that is very unlikely to win and very likely to lose. If it comes to repairing phones and tablets, you will have to buy a soldering station. Using it is not much harder than using a hand soldering iron, see video below, the price of a decent soldering station is quite affordable today
### What else?
Oh yeah, stands for soldering irons. A classic stand is shown in the figure in the left; it is suitable for any soldering irons with rod tips. It's up to you where to place trays for the solder and colophony - it is not regulated. For low-power soldering irons with a skirt, simplified stands with a bracket in the center are suitable.
![soldering](../assets/4_6.png)
Soldering stations are equipped with mainly spring or tubular socket cradles for soldering irons. The hot part of the tool is inaccessible in them, however it is easy to miss when soldering small components. But one should never, and it is expressly forbidden in the safety rules, make a stand of materials at hand, where the soldering iron rests on trays with consumables, as shown in the Figure on the right.
### Reference questions
1. What substance prevents oxidation?
2. List the main steps of soldering.
3. What is tinning?
4. In what cases soldering should not be used?
5. Which flux is better for soldering chips.

71
docs/en/lesson4.md Normal file
View File

@@ -0,0 +1,71 @@
Lesson # 4 "Aerodynamics of the flight. Propeller"
========================================
Aerodynamics of the propeller
-----------------------
A propeller is a blade unit rotated by the motor and used for converting the torque of the motor into the thrust.
The screw (propeller) rotates in place. With that, the air flows vertically downwards. This is one of the modes of the so-called axial screw airflow. On one of the blades, two small sections are marked: one (A) — closer to the rotation axis, the other (B) — at the end of the blade. During screw rotation, both sections will circumscribe concentric circles. It is clear that the length of the circle circumscribed by the element "B", and hence its speed relative to the air are greater than those of element "A". In other words, the speed of the blade element relative to the air depends on the distance to the rotation axis. The longer the distance, the greater the speed of the element. It is clear that at the axis of rotation, the speed will be zero, and at the end of the blade, it will be the maximum.
![rotation](../assets/7_1.png)
The blade cross section in this area has the shape of a streamlined profile. When air flows past this profile at the angle of incidence, the lifting force Y and the drag force X appear, which are calculated using special formulas. By breaking the blade into many small sections, one can determine their lifting forces and the drag forces, and by adding the appropriate forces of all sections, one can determine the lifting force and the drag force of one blade. (From the mathematical point of view, this operation is called integrating along the span of the blade). The lifting force (or the thrust) of the entire screw is obtained by multiplying the lifting force of one blade by the number of blades.
The edge effect. The magnitude of the screw thrust is calculated by the method described above with a certain error, which is determined by several reasons. One of them is not considering the so-called edge effect. The edge effect is manifested in the fact that the air tends to equalize the pressure above the blade and under the blade by flowing over the edge of the blade.
![rotation](../assets/7_2.png)
In this case, flowing over occurs on both on outer and inner edges of the blade. And since the lifting force occurs due to the pressure difference on the top and the bottom surfaces of the blade, any equalization of these pressures causes the loss of the lifting force.
Parameters of propellers
---------------------
There are many types of airscrews which may be used with varying degree of success.
One should consider the following parameters:
1. **Propeller diameter.** Larger propellers require more power of the motor for spinning. Make sure that the motor can develop the required power. Large and heavy propeller cells to have more inertia, therefore they cannot accelerate instantly, which will affect the copter's maneuverability.
2. **Prop pitch.** It is indicated by the second digit after "x" in the propeller brand; it may also be indicated by the third and the fourth digits of the brand - e.g., 1260 are propellers with the pitch of 6.0 inches. Physically, it is the air column that the propeller moves downwards in one revolution. The larger the pitch, the greater the lifting force. Naturally, there are reasonable limits: for example, 14х7 propellers have greater lifting force than 14х5 propellers. By the way, for the ideal case, the pitch of the propeller multiplied by the number of revolutions per second gives the speed of the air flow from the propeller.
3. **The number of blades.** In the classic case, there are two blades. However, propellers with three blades have greater lifting force - roughly equivalent to that of a two-blade propeller with the diameter 1 inch larger and the pitch 1 inch greater.
4. **Propeller constant**, the so-called Prop-Const, strongly affects the lifting force and the power of the motor required to spin the propeller, because physically this constant indicates the magnitude of the losses for air resistance during propeller rotation: the thinner the material the propeller is made of, the smaller this constant, and the smaller the power of the motor required for spinning the prop.
### Screws layout
Building a quadcopter requires two pairs of bidirectional screws, building a hexacopter requires 3 pairs, etc.
![rotation](../assets/7_3.png)
5\. **Direction of screws rotation** - classic - two screws counterclockwise, the other two screws clockwise in quadcopters.
6\. **Propellers workmanship quality** is also important. In the practice, it means that you should always balance the propellers to minimize vibration, which gradually destructs mechanical parts and drives periscopes crazy, deteriorating the flight properties of the quadcopter.
Building a quadcopter requires two pairs of bidirectional screws, building a hexacopter requires 3 pairs, etc.
Choosing the propeller
----------------
It is hard to imagine a propulsion source that would be more versatile than the propeller.
However, not everybody clearly understands how to correctly calculate the parameters of the propeller. Using the -and-see method, we sometimes lose a lot of time and effort on picking up dozens of various propellers in the hope of finding the one that would provide optimal thrust with specific motor and vehicle.
Calculating and choosing an air screw for the motor and a specific copter is a complex and a delicate task.
The source data for choosing the screws for DIY drone kits are usually the power of the motor Nmot (W), the airscrew rotation speed NS (rpm), and the maximum (flight) speed Vmax (m/s).
One should face the fact that no calculation will let you immediately and accurately determine all parameters of a fixed-pitch propeller. Exact calculation of such screws is a very difficult task. Even the most careful calculations do not allow getting an ideal propulsion unit for a specific vehicle. It is only during testing that it becomes clear how the crew should be modified, whether the pitch should be increased or decreased. The methods provided here allow choosing the initial screw if one can say so, a first approximation screw. And it is only the testing that will show, whether further modification of the screw will be required to fit your vehicle.
If the screw diameter should be decreased, it is sometimes recommended increasing the pitch or the width of the blades. Indeed, this helps take all the power from the motor, however efficiency of the propulsion unit inevitably drops.
it is very important to remember that a high-speed copter or requires a small-diameter high-speed propeller, and a low-speed one requires a large-diameter low-speed one.
The following method of choosing a screw for an amateur copter would seem to be reasonable: First, in accordance with the layout, choose the maximum possible diameter of the screw: consider all permissible gaps between the ends of the blades and the structure and other parameters. Then choose the motors according to the requirements of the model. There are also situations where the propeller is chosen for the motor.
So, we have to choose a motor and a propeller. How can one do it without using cumbersome formulas and complex calculations? Below, the choice of propellers is shown based on the motors chosen. However, this method is also suitable for choosing a motor for a propeller, if performed in reverse order.
For example, let's take motor X2204S 2300kv from the SunnySky company. Go to the manufacturer's website, and find the motor. The description contains a table for choosing the propeller (prop).
![rotation](../assets/7_4.png)
### Reference questions
1. How is thrust formed in a propeller?
2. How can one determine the prop pitch from the name of its brand?
3. What is propeller constant?
4. What is the purpose of using propellers rotating clockwise and counterclockwise in a copter?
5. What are the source data for choosing a screw for a copter?
6. What characteristics of the propeller are required for a high-speed and a low-speed copter?
7. Using a table for motor X2204S 2300kv, find the propeller that would develop the maximum speed.

112
docs/en/lesson6.md Normal file
View File

@@ -0,0 +1,112 @@
Lesson 6 "Fundamentals of electromagnetism. Types of motors"
===================================================
Basic laws of electromagnetism
---------------------------------
### Ampere's law
**Ampere's law** — the law of electric currents interaction. It was first discovered by Andre Marie Ampere in 1820 for direct current. From the Ampere's law it follows that parallel conductors with electric currents flowing in the same direction attract, and with electric currents flowing in the opposite direction, repel.
![low](../assets/8_1.png)
Ohm's Law
---------
**Ohm's law** is a physical law that determines the relationship between the electromotive force of a source (or electric voltage) with the current flowing in the conductor, and the resistance of the conductor. It was discovered by Georg Ohm in 1826, and named in his honor.
Coulomb's Law
------------
**Coulomb's law** is the law that describes the force between stationary point-like electric charges.
"The force of interaction of two point-like charges in the vacuum is directed along the straight line that connects these charges, proportionally to their magnitudes and inversely proportionally to the distance squared between them. It is the force of attraction if the charges are different, and the force of repulsion if the charges are the same."
Types of motors
---------------
Each motor has certain distinctive properties, which determine the scope of use, where it would be most appropriate. Synchronous, asynchronous, direct current, brush-type, brushless, valve-inductor, stepper ones...
![engine](../assets/8_2.png)
### A DC motor (DCM)
Motors of this type used in most old toys. A battery and two wires to the contacts. Such a motor contains a commutator installed on the shaft, which switches the windings depending on the rotor position. Direct current applied to the motor runs alternatively in some and other parts of the winding thus creating torque.
![engine](../assets/8_3.png)
DC motors may be both small (a vibro in your telephone), and rather large — usually up to a megawatt. For example, the photo below shows a traction motor of an electric locomotive with the power of 810 kW and voltage of 1,500 V.
### A universal commutator motor
Oddly enough, this is the most common motor in the household use, the name of which is the least known. Why did this happen? Its design and features are the same as those of a DC motor, therefore it is mentioned in textbooks usually in the very end of the chapter.
![engine](../assets/8_4.png)
This type of engine is more widely available in household appliances where it is required to adjust the rotation speed: drills, washing machines (not with "direct drive"), vacuum cleaners, etc. Why is it so popular? Due to the simplicity of regulation. Like in an AC motor, it can be adjusted by voltage, through a symistor (bidirectional thyristor) for AC power. The control circuit may be so simple, which is placed, for example, directly in the "trigger" of the power tool, and requires neither microcontroller, nor a PWM, nor a rotor positioning sensor.
### Asynchronous motor
Asynchronous motors are used in the household: in the devices where there is no need to adjust the rotation speed. Most often it is the so-called "capacitor" engines, or, equivalently, "single-phase" asynchronous engines. But actually, from the point of view of the motor, it is correct to say "two-phase", simply one phase of the motor is connected to the AC network directly, and the other — through a capacitor. The capacitor causes a voltage phase shift in the other winding, which allows creating a rotating elliptical magnetic field. Usually such motors are used in exhaust fans, refrigerators, small pumps, etc.
### Synchronous motor
There are several subtypes of synchronous motors — with magnets (PMSM) and without magnets (with an excitation winding and slip rings), with sinusoidal or trapezoidal EMF (brushless DC motors, BLDC). Some step motors may also be included here. Before the era of power semiconductor electronics, the destiny of synchronous machines was being used as alternators (almost all alternators in all power plants are synchronous machines), as well as powerful drives for any serious load in the industry.
![engine](../assets/8_5.png)
### Comparison of brushed and brushless motors
In radio controlled models with electric motors, brush and brushless motors are used.
A brief comparison of the types of engines: brush-type motors develop lower speed. Brushless motors can develop more speed, and are also more durable.
![engine](../assets/8_6.png)
### Brush-type motors
These have brush-collector units, which ensure movement of radio-controlled models. The collector is essentially a set of contacts on the rotor and brushes — sliding contacts, which are located outside the rotor.
How it works: Works from DC. I.e., by applying voltage from a DC source (a battery) one makes it move. To change the direction, simply reverse current polarity. This is a fairly simple mechanism, and therefore, motors of the brush type are cheaper. This type of motors belongs to the earlier type with the **efficiency** of **60 %**, as calculated by specialists.
**Advantages of brush-type motors in radio-controlled models** include:
* Light weight of the motor
* Small size of the motor
* Lover cost of the motor
* Repairability
**Disadvantages of brush-type motors:**
* Lower efficiency of the motor
* Lower maximum developed speed
* Mechanical work of brushes and collector may result in sparkling if overheated
* Rapid wear
Brushless motors, in which the moving part is the stator, are more efficient than brush-type motors. This is achieved due to the absence of brushes. However, since motor design is much more complicated, they are more expensive.
**Advantages:**
* High motor efficiency — up to 92 %
* Higher maximum developed speed
* More wear resistant due to the closed type of the motor
* Better protected from moisture, dust and dirt
**Disadvantages:**
* High cost
* More complicated repair
### Reference questions
1. What behavior of conductors with electric currents follows from the Ampere's law?
2. According to Coulomb's law, how do two point charges interact in vacuum?
3. What is the main difference between brush-type and brushless motors?
4. What are the characteristics of brushless motors that make them suitable to be used in quadcopters?

View File

@@ -5,7 +5,7 @@
«Клевер» — это учебный конструктор программируемого квадрокоптера, состоящего из популярных открытых компонентов, а также набор необходимой документации и библиотек для работы с ним. «Клевер» — это учебный конструктор программируемого квадрокоптера, состоящего из популярных открытых компонентов, а также набор необходимой документации и библиотек для работы с ним.
Набор включает в себя полетный контроллер Pixhawk/Pixracer с полетным стеком PX4, Raspberry Pi 3 в качестве управлящего бортового компьютера, модуль камеры для реализации полетов с использованием компьютерного зрения, а также набор различных датчиков и другой периферии. Набор включает в себя полетный контроллер Pixhawk/Pixracer с полетным стеком PX4, Raspberry Pi 3 в качестве управляющего бортового компьютера, модуль камеры для реализации полетов с использованием компьютерного зрения, а также набор различных датчиков и другой периферии.
На базе точно такой же платформы были созданы многие «большие» проекты компании Copter Express, например, дроны для [пиар-акций по автономной доставке пиццы](https://www.youtube.com/watch?v=hmkAoZOtF58) (Самара, Казань); дрон-доставщик кофе в Сколково, мониторинговый дрон с зарядной станцией, дроны-победители на полевых испытаниях «[Робокросс-2016](https://www.youtube.com/watch?v=dGbDaz_VmYU)», «[Робокросс-2017](https://youtu.be/AQnd2CRczbQ)» и многие другие. На базе точно такой же платформы были созданы многие «большие» проекты компании Copter Express, например, дроны для [пиар-акций по автономной доставке пиццы](https://www.youtube.com/watch?v=hmkAoZOtF58) (Самара, Казань); дрон-доставщик кофе в Сколково, мониторинговый дрон с зарядной станцией, дроны-победители на полевых испытаниях «[Робокросс-2016](https://www.youtube.com/watch?v=dGbDaz_VmYU)», «[Робокросс-2017](https://youtu.be/AQnd2CRczbQ)» и многие другие.
@@ -15,6 +15,8 @@
Также у нас есть чат для программистов, которые разрабатывают под PX4, автономную навигацию в помещениях и рои дронов https://t.me/DroneCode. Также у нас есть чат для программистов, которые разрабатывают под PX4, автономную навигацию в помещениях и рои дронов https://t.me/DroneCode.
The English version of this documentation [is available](../en/).
Образ для Raspberry Pi Образ для Raspberry Pi
---------------------- ----------------------

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