Compare commits
15 Commits
v0.17-rc.1
...
nti2019-no
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67877457b1 | ||
|
|
ebd92b7d85 | ||
|
|
8b144f2355 | ||
|
|
90dfa4c473 | ||
|
|
af019f51f5 | ||
|
|
b4492d16fa | ||
|
|
d5cbe0c266 | ||
|
|
cd9fcb0595 | ||
|
|
f90c1a6329 | ||
|
|
c63e4265d6 | ||
|
|
60c97d2318 | ||
|
|
8dec500702 | ||
|
|
09a8f702a7 | ||
|
|
bcefb03f04 | ||
|
|
bc1ceb2fa0 |
@@ -24,8 +24,6 @@
|
||||
"Python",
|
||||
"C++",
|
||||
"PX4",
|
||||
"px4.io",
|
||||
"logs.px4.io",
|
||||
"QGroundControl",
|
||||
"QGC",
|
||||
"WireShark",
|
||||
@@ -40,10 +38,6 @@
|
||||
"RPi",
|
||||
"Linux",
|
||||
"Windows",
|
||||
"Docker",
|
||||
"Travis",
|
||||
"travis-ci.org",
|
||||
"travis-ci.com",
|
||||
"macOS",
|
||||
"iOS",
|
||||
"Android",
|
||||
|
||||
25
.travis.yml
@@ -55,18 +55,19 @@ jobs:
|
||||
- markdownlint docs
|
||||
- gitbook install
|
||||
- gitbook build
|
||||
deploy:
|
||||
provider: pages
|
||||
local-dir: _book
|
||||
skip-cleanup: true
|
||||
github-token: ${GITHUB_OAUTH_TOKEN}
|
||||
keep-history: true
|
||||
target-branch: master
|
||||
repo: CopterExpress/clever-gitbook
|
||||
fqdn: clever.copterexpress.com
|
||||
verbose: true
|
||||
on:
|
||||
branch: master
|
||||
# ***
|
||||
# Disable deployments for now, revisit this later
|
||||
# --sfalexrog, 06.02.2019
|
||||
# ***
|
||||
# deploy:
|
||||
# provider: pages
|
||||
# local-dir: _book
|
||||
# skip-cleanup: true
|
||||
# github-token: ${GITHUB_OAUTH_TOKEN}
|
||||
# keep-history: true
|
||||
# target-branch: gh-pages
|
||||
# on:
|
||||
# branch: WIP/gitbook-autobuild
|
||||
- stage: Annotate
|
||||
name: Auto-generate changelog
|
||||
language: python
|
||||
|
||||
@@ -216,7 +216,4 @@ target_link_libraries(aruco_pose
|
||||
if (CATKIN_ENABLE_TESTING)
|
||||
find_package(rostest REQUIRED)
|
||||
add_rostest(test/basic.test)
|
||||
add_rostest(test/test_parser_pass.test)
|
||||
add_rostest(test/test_parser_empty_map.test)
|
||||
add_rostest(test/test_node_failure.test)
|
||||
endif()
|
||||
|
||||
@@ -74,7 +74,6 @@ It's recommended to run it within the same nodelet manager with the camera nodel
|
||||
* `~image_width` – debug image width (default: 2000)
|
||||
* `~image_height` – debug image height (default: 2000)
|
||||
* `~image_margin` – debug image margin (default: 200)
|
||||
* `~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:
|
||||
|
||||
|
||||
22
aruco_pose/map/nti_novgorod.txt
Normal file
@@ -0,0 +1,22 @@
|
||||
34 0.33 0 0 0 0 0 0
|
||||
37 0.33 0.74 0 0 0 0 0
|
||||
100 0.05 0.37 0.2 0 0 0 0
|
||||
6 0.10 0.37 0.39 0 0 0 0
|
||||
25 0.33 0 0.54 0 0 0 0
|
||||
28 0.33 0.74 0.54 0 0 0 0
|
||||
32 0.33 2.74 -0.11 1.20 0 0 0
|
||||
29 0.33 2.41 0.69 1.20 0 0 0
|
||||
30 0.33 0 2.3 0.05 0 0 0
|
||||
31 0.33 0 2.94 0.05 0 0 0
|
||||
27 0.33 0.74 2.3 0.05 0 0 0
|
||||
26 0.33 0.74 2.94 0.05 0 0 0
|
||||
43 0.33 2.39 2.13 0.8 0 0 0
|
||||
42 0.33 2.39 3.03 0.8 0 0 0
|
||||
40 0.33 3.32 2.13 0.8 0 0 0
|
||||
41 0.33 3.32 3.03 0.8 0 0 0
|
||||
44 0.33 5.26 2.92 0.05 0 0 0
|
||||
50 0.33 5.51 3.38 0.05 0 0 0
|
||||
46 0.33 3.95 3.65 0.1 0 0 0
|
||||
36 0.33 4.76 -0.18 0.3 0 0 0
|
||||
35 0.33 5.36 -0.18 0.3 0 0 0
|
||||
33 0.33 5.2 0.61 0.3 0 0 0
|
||||
@@ -36,7 +36,6 @@
|
||||
#include <sensor_msgs/Image.h>
|
||||
#include <visualization_msgs/Marker.h>
|
||||
#include <visualization_msgs/MarkerArray.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include <aruco_pose/MarkerArray.h>
|
||||
#include <aruco_pose/Marker.h>
|
||||
@@ -179,13 +178,7 @@ public:
|
||||
double center_x = 0, center_y = 0, center_z = 0;
|
||||
alignObjPointsToCenter(obj_points, center_x, center_y, center_z);
|
||||
|
||||
// Step 1: Solve using EPnP
|
||||
valid = solvePnP(obj_points, img_points, camera_matrix_, dist_coeffs_, rvec, tvec, false, cv::SOLVEPNP_EPNP);
|
||||
// Step 2: Use iterative method to refine results
|
||||
valid &= solvePnP(obj_points, img_points, camera_matrix_, dist_coeffs_, rvec, tvec, true);
|
||||
// Step 3: Check tvec magnitude. Iterative method tends to diverge sometimes, and this divergence is not picked up
|
||||
// by OpenCV code
|
||||
valid &= norm(tvec) < 1e6;
|
||||
valid = solvePnP(obj_points, img_points, camera_matrix_, dist_coeffs_, rvec, tvec, false);
|
||||
if (!valid) goto publish_debug;
|
||||
|
||||
fillTransform(transform_.transform, rvec, tvec);
|
||||
@@ -277,50 +270,10 @@ publish_debug:
|
||||
|
||||
std::istringstream s(line);
|
||||
|
||||
// Read first character to see whether it's a comment
|
||||
char first = 0;
|
||||
if (!(s >> first)) {
|
||||
// No non-whitespace characters, must be a blank line
|
||||
if (!(s >> id >> length >> x >> y >> z >> yaw >> pitch >> roll)) {
|
||||
ROS_ERROR("aruco_map: cannot parse line: %s", line.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first == '#') {
|
||||
ROS_DEBUG("aruco_map: Skipping line as a comment: %s", line.c_str());
|
||||
continue;
|
||||
} else if (isdigit(first)) {
|
||||
// Put the digit back into the stream
|
||||
// Note that this is a non-modifying putback, so this should work with istreams
|
||||
// (see https://en.cppreference.com/w/cpp/io/basic_istream/putback)
|
||||
s.putback(first);
|
||||
} else {
|
||||
// Probably garbage data; inform user and throw an exception, possibly killing nodelet
|
||||
ROS_FATAL("aruco_map: Malformed input: %s", line.c_str());
|
||||
ros::shutdown();
|
||||
throw std::runtime_error("Malformed input");
|
||||
}
|
||||
|
||||
if (!(s >> id >> length >> x >> y)) {
|
||||
ROS_ERROR("aruco_map: Not enough data in line: %s; "
|
||||
"Each marker must have at least id, length, x, y fields", line.c_str());
|
||||
continue;
|
||||
}
|
||||
// Be less strict about z, yaw, pitch roll
|
||||
if (!(s >> z)) {
|
||||
ROS_DEBUG("aruco_map: No z coordinate provided for marker %d, assuming 0", id);
|
||||
z = 0;
|
||||
}
|
||||
if (!(s >> yaw)) {
|
||||
ROS_DEBUG("aruco_map: No yaw provided for marker %d, assuming 0", id);
|
||||
yaw = 0;
|
||||
}
|
||||
if (!(s >> pitch)) {
|
||||
ROS_DEBUG("aruco_map: No pitch provided for marker %d, assuming 0", id);
|
||||
pitch = 0;
|
||||
}
|
||||
if (!(s >> roll)) {
|
||||
ROS_DEBUG("aruco_map: No roll provided for marker %d, assuming 0", id);
|
||||
roll = 0;
|
||||
}
|
||||
addMarker(id, length, x, y, z, yaw, pitch, roll);
|
||||
}
|
||||
|
||||
@@ -386,19 +339,6 @@ publish_debug:
|
||||
void addMarker(int id, double length, double x, double y, double z,
|
||||
double yaw, double pitch, double roll)
|
||||
{
|
||||
// Check whether the id is in range for current dictionary
|
||||
int num_markers = board_->dictionary->bytesList.rows;
|
||||
if (num_markers <= id) {
|
||||
ROS_ERROR("aruco_map: Marker id %d is not in dictionary; current dictionary contains %d markers. "
|
||||
"Please see https://github.com/CopterExpress/clever/blob/master/aruco_pose/README.md#parameters for details",
|
||||
id, num_markers);
|
||||
return;
|
||||
}
|
||||
// Check if marker is already in the board
|
||||
if (std::count(board_->ids.begin(), board_->ids.end(), id) > 0) {
|
||||
ROS_ERROR("aruco_map: Marker id %d is already in the map", id);
|
||||
return;
|
||||
}
|
||||
// Create transform
|
||||
tf::Quaternion q;
|
||||
q.setRPY(roll, pitch, yaw);
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
# Default markers
|
||||
1 0.33 0 0 0 0 0 0
|
||||
2 0.33 1 0 0 0 0 0
|
||||
3 0.33 0 1 0 0 0 0
|
||||
4 0.33 1 1 0 0 0 0
|
||||
# Marker with non-zero yaw rotation
|
||||
10 0.5 0.5 2 0 1.2 0 0
|
||||
# Marker with non-zero pitch and roll rotation
|
||||
11 0.2 0.5 0.5 0 0.0 -1 1
|
||||
# Marker with yaw, pitch and roll rotation
|
||||
12 0.4 0.2 0.5 0 0.1 -1.2 -0.5
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
import unittest
|
||||
import json
|
||||
import rospy
|
||||
import rostest
|
||||
|
||||
from geometry_msgs.msg import PoseWithCovarianceStamped
|
||||
from sensor_msgs.msg import Image
|
||||
from aruco_pose.msg import MarkerArray
|
||||
from visualization_msgs.msg import MarkerArray as VisMarkerArray
|
||||
|
||||
|
||||
class TestArucoMapPass(unittest.TestCase):
|
||||
def setUp(self):
|
||||
rospy.init_node('test_parser_fail', anonymous=True)
|
||||
|
||||
def test_node_failure(self):
|
||||
try:
|
||||
markers = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
|
||||
did_post_message = True
|
||||
except rospy.exceptions.ROSException:
|
||||
did_post_message = False
|
||||
self.assertFalse(did_post_message)
|
||||
|
||||
|
||||
rostest.rosrun('aruco_pose', 'test_aruco_map', TestArucoMapPass, sys.argv)
|
||||
@@ -1,13 +0,0 @@
|
||||
<launch>
|
||||
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager"/>
|
||||
|
||||
<node name="aruco_map" pkg="nodelet" type="nodelet" args="load aruco_pose/aruco_map nodelet_manager" clear_params="true">
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="markers" to="aruco_detect/markers"/>
|
||||
<param name="type" value="map"/>
|
||||
<param name="map" value="$(find aruco_pose)/test/test_node_failure.txt"/>
|
||||
</node>
|
||||
|
||||
<test test-name="test_aruco_map_fail_dict" pkg="aruco_pose" type="test_node_failure.py"/>
|
||||
</launch>
|
||||
@@ -1,4 +0,0 @@
|
||||
# Any garbage data (pretty much anything apart from a comment starting with a hash starting
|
||||
# with a hash sign or a number) will be interpreted as garbage data; the node should fail
|
||||
# after reading it.
|
||||
// Don't try to put your comments this way, it will kill your node!
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
import unittest
|
||||
import json
|
||||
import rospy
|
||||
import rostest
|
||||
|
||||
from geometry_msgs.msg import PoseWithCovarianceStamped
|
||||
from sensor_msgs.msg import Image
|
||||
from aruco_pose.msg import MarkerArray
|
||||
from visualization_msgs.msg import MarkerArray as VisMarkerArray
|
||||
|
||||
|
||||
class TestArucoMapPass(unittest.TestCase):
|
||||
def setUp(self):
|
||||
rospy.init_node('test_parser_fail', anonymous=True)
|
||||
|
||||
|
||||
def test_node_failure(self):
|
||||
markers = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
|
||||
self.assertEquals(len(markers.markers), 0)
|
||||
|
||||
|
||||
rostest.rosrun('aruco_pose', 'test_aruco_map', TestArucoMapPass, sys.argv)
|
||||
@@ -1,13 +0,0 @@
|
||||
<launch>
|
||||
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager"/>
|
||||
|
||||
<node name="aruco_map" pkg="nodelet" type="nodelet" args="load aruco_pose/aruco_map nodelet_manager" clear_params="true">
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="markers" to="aruco_detect/markers"/>
|
||||
<param name="type" value="map"/>
|
||||
<param name="map" value="$(find aruco_pose)/test/test_parser_empty_map.txt"/>
|
||||
</node>
|
||||
|
||||
<test test-name="test_aruco_map_incomplete" pkg="aruco_pose" type="test_parser_empty_map.py"/>
|
||||
</launch>
|
||||
@@ -1,6 +0,0 @@
|
||||
# Failing markers: Not enough parameters to add a marker
|
||||
1
|
||||
2 1
|
||||
3 0.5 1
|
||||
# Failing markers: Marker IDs outside of dictionary range
|
||||
1001 1 1 0
|
||||
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
import unittest
|
||||
import json
|
||||
import rospy
|
||||
import rostest
|
||||
|
||||
from geometry_msgs.msg import PoseWithCovarianceStamped
|
||||
from sensor_msgs.msg import Image
|
||||
from aruco_pose.msg import MarkerArray
|
||||
from visualization_msgs.msg import MarkerArray as VisMarkerArray
|
||||
|
||||
|
||||
class TestArucoMapPass(unittest.TestCase):
|
||||
def setUp(self):
|
||||
rospy.init_node('test_parser_pass', anonymous=True)
|
||||
|
||||
def test_markers(self):
|
||||
markers = rospy.wait_for_message('aruco_map/visualization', VisMarkerArray, timeout=5)
|
||||
|
||||
self.assertEqual(len(markers.markers), 6)
|
||||
# FIXME: visual marker id is not ArUco marker id
|
||||
# self.assertEqual(markers.markers[0].id, 1)
|
||||
# self.assertEqual(markers.markers[1].id, 2)
|
||||
# self.assertEqual(markers.markers[2].id, 3)
|
||||
# self.assertEqual(markers.markers[3].id, 4)
|
||||
|
||||
self.assertAlmostEqual(markers.markers[0].pose.position.x, 0, places=7)
|
||||
self.assertAlmostEqual(markers.markers[0].pose.position.y, 0, places=7)
|
||||
self.assertAlmostEqual(markers.markers[0].pose.position.z, 0, places=7)
|
||||
|
||||
self.assertAlmostEqual(markers.markers[1].pose.position.x, 1, places=7)
|
||||
self.assertAlmostEqual(markers.markers[1].pose.position.y, 1, places=7)
|
||||
self.assertAlmostEqual(markers.markers[1].pose.position.z, 1, places=7)
|
||||
|
||||
self.assertAlmostEqual(markers.markers[2].pose.position.x, 1, places=7)
|
||||
self.assertAlmostEqual(markers.markers[2].pose.position.y, 0, places=7)
|
||||
self.assertAlmostEqual(markers.markers[2].pose.position.z, 0.5, places=7)
|
||||
|
||||
self.assertAlmostEqual(markers.markers[3].pose.position.x, 0, places=7)
|
||||
self.assertAlmostEqual(markers.markers[3].pose.position.y, 1, places=7)
|
||||
self.assertAlmostEqual(markers.markers[3].pose.position.z, 0, places=7)
|
||||
|
||||
self.assertAlmostEqual(markers.markers[4].pose.position.x, 1, places=7)
|
||||
self.assertAlmostEqual(markers.markers[4].pose.position.y, 0.5, places=7)
|
||||
self.assertAlmostEqual(markers.markers[4].pose.position.z, 0, places=7)
|
||||
|
||||
self.assertAlmostEqual(markers.markers[5].pose.position.x, 2.2, places=7)
|
||||
self.assertAlmostEqual(markers.markers[5].pose.position.y, 0.2, places=7)
|
||||
self.assertAlmostEqual(markers.markers[5].pose.position.z, 0, places=7)
|
||||
|
||||
self.assertAlmostEqual(markers.markers[0].scale.x, 0.33, places=7)
|
||||
self.assertAlmostEqual(markers.markers[0].scale.y, 0.33, places=7)
|
||||
self.assertAlmostEqual(markers.markers[1].scale.x, 0.225, places=7)
|
||||
self.assertAlmostEqual(markers.markers[1].scale.y, 0.225, places=7)
|
||||
self.assertAlmostEqual(markers.markers[2].scale.x, 0.45, places=7)
|
||||
self.assertAlmostEqual(markers.markers[2].scale.y, 0.45, places=7)
|
||||
self.assertAlmostEqual(markers.markers[3].scale.x, 0.15, places=7)
|
||||
self.assertAlmostEqual(markers.markers[3].scale.y, 0.15, places=7)
|
||||
self.assertAlmostEqual(markers.markers[4].scale.x, 0.25, places=7)
|
||||
self.assertAlmostEqual(markers.markers[4].scale.y, 0.25, places=7)
|
||||
self.assertAlmostEqual(markers.markers[5].scale.x, 0.35, places=7)
|
||||
self.assertAlmostEqual(markers.markers[5].scale.y, 0.35, places=7)
|
||||
|
||||
def test_map_image(self):
|
||||
img = rospy.wait_for_message('aruco_map/image', Image, timeout=5)
|
||||
self.assertEqual(img.width, 2000)
|
||||
self.assertEqual(img.height, 2000)
|
||||
self.assertEqual(img.encoding, 'mono8')
|
||||
|
||||
# def test_transforms(self):
|
||||
# pass
|
||||
|
||||
|
||||
rostest.rosrun('aruco_pose', 'test_aruco_map', TestArucoMapPass, sys.argv)
|
||||
@@ -1,13 +0,0 @@
|
||||
<launch>
|
||||
<node pkg="nodelet" type="nodelet" name="nodelet_manager" args="manager"/>
|
||||
|
||||
<node name="aruco_map" pkg="nodelet" type="nodelet" args="load aruco_pose/aruco_map nodelet_manager" clear_params="true">
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="markers" to="aruco_detect/markers"/>
|
||||
<param name="type" value="map"/>
|
||||
<param name="map" value="$(find aruco_pose)/test/test_parser_pass.txt"/>
|
||||
</node>
|
||||
|
||||
<test test-name="test_aruco_map" pkg="aruco_pose" type="test_parser_pass.py"/>
|
||||
</launch>
|
||||
@@ -1,23 +0,0 @@
|
||||
# Parser test #1 - correct file
|
||||
# 1. Commentary test
|
||||
#Commentary text without space after pound sign
|
||||
# Commentary text with space after pound sign
|
||||
# Commentary text with spaces before pound sign
|
||||
# Commentary text with tab before pound sign
|
||||
# Text with tabs before pound sign
|
||||
# Empty line test:
|
||||
|
||||
# All-whitespace line test:
|
||||
|
||||
# 2. Default coordinates test
|
||||
# Fully filled marker description, tab-delimited:
|
||||
1 0.33 0 0 0 0 0 0
|
||||
# Fully filled marker description, space-delimited:
|
||||
2 0.225 1 1 1 0 0 0
|
||||
# Default roll, pitch, yaw angles
|
||||
3 0.45 1 0 0.5
|
||||
# Default roll, pitch, yaw, z
|
||||
4 0.15 0 1
|
||||
# Inline commentary
|
||||
5 0.25 1 0.5# Comment straight after digit
|
||||
6 0.35 2.2 0.2 # Comment with a space after digit
|
||||
@@ -4,7 +4,6 @@
|
||||
"author": "Copter Express",
|
||||
"language": "ru",
|
||||
"root": "docs/",
|
||||
"gitbook": "^3.2.2",
|
||||
"plugins": [
|
||||
"youtube",
|
||||
"richquotes@https://github.com/okalachev/gitbook-plugin-richquotes.git",
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Set Clever hostname to the specified value
|
||||
|
||||
set -e
|
||||
|
||||
NEW_NAME_OPT=$1
|
||||
|
||||
if [[ -z ${NEW_NAME_OPT} ]]; then
|
||||
echo "Please specify new name for this Clever"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NEW_NAME=$(echo ${NEW_NAME_OPT} | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
echo "Setting name to ${NEW_NAME}"
|
||||
|
||||
echo "Backing up /etc/hostname"
|
||||
cp /etc/hostname /etc/hostname.bak
|
||||
echo "Writing new /etc/hostname"
|
||||
echo ${NEW_NAME} > /etc/hostname
|
||||
|
||||
echo "Backing up /etc/hosts"
|
||||
cp /etc/hosts /etc/hosts.bak
|
||||
echo "Rewriting /etc/hosts with new values"
|
||||
sed -i 's/127\.0\.1\.1.*/127.0.1.1\t'${NEW_NAME}'/g' /etc/hosts
|
||||
|
||||
echo "Changing hostname in /lib/systemd/system/roscore.env"
|
||||
sed -i 's/ROS_HOSTNAME=.*/ROS_HOSTNAME='${NEW_NAME}'.local/g' /lib/systemd/system/roscore.env
|
||||
|
||||
echo "Changing hostname in .bashrc"
|
||||
sed -i 's/export ROS_HOSTNAME=.*/export ROS_HOSTNAME='${NEW_NAME}'.local/g' /home/pi/.bashrc
|
||||
|
||||
echo "Done, reboot your Clever to see the results"
|
||||
|
||||
@@ -32,9 +32,7 @@ echo_stamp() {
|
||||
}
|
||||
|
||||
echo_stamp "Rename SSID"
|
||||
NEW_SSID='CLEVER-'$(head -c 100 /dev/urandom | xxd -ps -c 100 | sed -e "s/[^0-9]//g" | cut -c 1-4)
|
||||
sudo sed -i.OLD "s/CLEVER/${NEW_SSID}/" /etc/wpa_supplicant/wpa_supplicant.conf
|
||||
clever_rename ${NEW_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
|
||||
|
||||
echo_stamp "Harware setup"
|
||||
/root/hardware_setup.sh
|
||||
|
||||
@@ -541,12 +541,3 @@ tf2_web_republisher:
|
||||
image_publisher:
|
||||
debian:
|
||||
stretch: [ros-kinetic-image-publisher]
|
||||
raspberry-kernel-headers:
|
||||
debian:
|
||||
stretch: [raspberry-kernel-headers]
|
||||
ddynamic_reconfigure:
|
||||
debian:
|
||||
stretch: [ros-kinetic-ddynamic-reconfigure]
|
||||
realsense2_camera:
|
||||
debian:
|
||||
stretch: [ros-kinetic-realsense2-camera]
|
||||
|
||||
14
builder/assets/rosled.service
Normal file
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=ROS ws281x support
|
||||
Requires=roscore.service
|
||||
After=roscore.service
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=/lib/systemd/system/roscore.env
|
||||
ExecStart=/opt/ros/kinetic/bin/roslaunch ros_ws281x clever4.launch --wait --screen
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
TimeoutSec=infinity
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -112,8 +112,7 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/ros_pyt
|
||||
# ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-ros-clever.rosinstall' '/home/pi/ros_catkin_ws/'
|
||||
# Add PX4 udev rules
|
||||
${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} copy ${SCRIPTS_DIR}'/assets/rosled.service' '/lib/systemd/system/'
|
||||
${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'
|
||||
|
||||
|
||||
@@ -42,10 +42,9 @@ echo_stamp() {
|
||||
my_travis_retry() {
|
||||
local result=0
|
||||
local count=1
|
||||
local max_count=50
|
||||
while [ $count -le $max_count ]; do
|
||||
while [ $count -le 3 ]; do
|
||||
[ $result -ne 0 ] && {
|
||||
echo -e "\nThe command \"$@\" failed. Retrying, $count of $max_count.\n" >&2
|
||||
echo -e "\n${ANSI_RED}The command \"$@\" failed. Retrying, $count of 3.${ANSI_RESET}\n" >&2
|
||||
}
|
||||
# ! { } ignores set -e, see https://stackoverflow.com/a/4073372
|
||||
! { "$@"; result=$?; }
|
||||
@@ -54,21 +53,21 @@ my_travis_retry() {
|
||||
sleep 1
|
||||
done
|
||||
|
||||
[ $count -gt $max_count ] && {
|
||||
echo -e "\nThe command \"$@\" failed $max_count times.\n" >&2
|
||||
[ $count -gt 3 ] && {
|
||||
echo -e "\n${ANSI_RED}The command \"$@\" failed 3 times.${ANSI_RESET}\n" >&2
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
# TODO: 'kinetic-rosdep-clever.yaml' should add only if we use our repo?
|
||||
echo_stamp "Init rosdep"
|
||||
my_travis_retry rosdep init
|
||||
echo "yaml file:///etc/ros/rosdep/kinetic-rosdep-clever.yaml" >> /etc/ros/rosdep/sources.list.d/20-default.list
|
||||
my_travis_retry rosdep update
|
||||
echo_stamp "Init rosdep" \
|
||||
&& rosdep init \
|
||||
&& echo "yaml file:///etc/ros/rosdep/kinetic-rosdep-clever.yaml" >> /etc/ros/rosdep/sources.list.d/20-default.list \
|
||||
&& rosdep update
|
||||
|
||||
echo_stamp "Populate rosdep for ROS user"
|
||||
my_travis_retry sudo -u pi rosdep update
|
||||
sudo -u pi rosdep update
|
||||
|
||||
resolve_rosdep() {
|
||||
# TEMPLATE: resolve_rosdep <CATKIN_PATH> <ROS_DISTRO> <OS_DISTRO> <OS_VERSION>
|
||||
@@ -138,9 +137,9 @@ fi
|
||||
|
||||
export ROS_IP='127.0.0.1' # needed for running tests
|
||||
|
||||
echo_stamp "Reconfiguring Clever repository for simplier unshallowing"
|
||||
cd /home/pi/catkin_ws/src/clever
|
||||
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
|
||||
echo_stamp "Adding ros_ws281x ROS package"
|
||||
cd /home/pi/catkin_ws/src
|
||||
git clone https://github.com/sfalexrog/ros_ws281x
|
||||
|
||||
echo_stamp "Installing CLEVER" \
|
||||
&& cd /home/pi/catkin_ws/src/clever \
|
||||
@@ -150,7 +149,7 @@ echo_stamp "Installing CLEVER" \
|
||||
&& my_travis_retry pip install wheel \
|
||||
&& my_travis_retry pip install -r /home/pi/catkin_ws/src/clever/clever/requirements.txt \
|
||||
&& source /opt/ros/kinetic/setup.bash \
|
||||
&& catkin_make -j2 -DCMAKE_BUILD_TYPE=Release \
|
||||
&& catkin_make -j2 -DCMAKE_BUILD_TYPE=Release -DROS_WS2811_REAL_LIB=ON \
|
||||
&& catkin_make run_tests \
|
||||
&& catkin_test_results \
|
||||
&& systemctl enable roscore \
|
||||
@@ -158,6 +157,9 @@ echo_stamp "Installing CLEVER" \
|
||||
&& echo_stamp "All CLEVER was installed!" "SUCCESS" \
|
||||
|| (echo_stamp "CLEVER installation was failed!" "ERROR"; exit 1)
|
||||
|
||||
echo_stamp "Enabling ROS LED service"
|
||||
systemctl enable rosled
|
||||
|
||||
echo_stamp "Build CLEVER documentation"
|
||||
cd /home/pi/catkin_ws/src/clever
|
||||
NPM_CONFIG_UNSAFE_PERM=true npm install gitbook-cli -g
|
||||
@@ -173,8 +175,7 @@ apt-get install -y --no-install-recommends \
|
||||
ros-kinetic-rosserial \
|
||||
ros-kinetic-usb-cam \
|
||||
ros-kinetic-vl53l1x \
|
||||
ros-kinetic-opencv3=3.3.19-0stretch \
|
||||
ros-kinetic-rosshow
|
||||
ros-kinetic-opencv3=3.3.19-0stretch
|
||||
|
||||
# TODO move GeographicLib datasets to Mavros debian package
|
||||
echo_stamp "Install GeographicLib datasets (needs for mavros)" \
|
||||
|
||||
@@ -86,7 +86,7 @@ dnsmasq=2.76-5+rpt1+deb9u1 \
|
||||
tmux=2.3-4 \
|
||||
vim=2:8.0.0197-4+deb9u1 \
|
||||
cmake=3.7.2-1 \
|
||||
libjpeg8=8d1-2 \
|
||||
libjpeg8-dev=8d1-2 \
|
||||
tcpdump \
|
||||
ltrace \
|
||||
libpoco-dev=1.7.6+dfsg1-5+deb9u1 \
|
||||
@@ -99,16 +99,14 @@ libffi-dev \
|
||||
monkey=1.6.9-1 \
|
||||
pigpio python-pigpio python3-pigpio \
|
||||
i2c-tools \
|
||||
espeak espeak-data python-espeak \
|
||||
ntpdate \
|
||||
python-dev \
|
||||
python3-dev \
|
||||
python-systemd \
|
||||
&& echo_stamp "Everything was installed!" "SUCCESS" \
|
||||
|| (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.20190517-1
|
||||
apt-get install --no-install-recommends -y raspberrypi-kernel=1.20190215-1
|
||||
|
||||
# Deny byobu to check available updates
|
||||
sed -i "s/updates_available//" /usr/share/byobu/status/status
|
||||
@@ -141,6 +139,9 @@ mv /etc/monkey/sites/default /etc/monkey/sites/default.orig
|
||||
mv /root/monkey /etc/monkey/sites/default
|
||||
systemctl enable monkey.service
|
||||
|
||||
echo_stamp "Install paho-mqtt"
|
||||
my_travis_retry pip install paho-mqtt
|
||||
|
||||
echo_stamp "Install Node.js"
|
||||
cd /home/pi
|
||||
wget https://nodejs.org/dist/v10.15.0/node-v10.15.0-linux-armv6l.tar.gz
|
||||
@@ -156,9 +157,6 @@ syntax on
|
||||
autocmd BufNewFile,BufRead *.launch set syntax=xml
|
||||
EOF
|
||||
|
||||
echo_stamp "Change default keyboard layout to US"
|
||||
sed -i 's/XKBLAYOUT="gb"/XKBLAYOUT="us"/g' /etc/default/keyboard
|
||||
|
||||
echo_stamp "Attempting to kill dirmngr"
|
||||
gpgconf --kill dirmngr
|
||||
# dirmngr is only used by apt-key, so we can safely kill it.
|
||||
|
||||
@@ -25,6 +25,6 @@ import pymavlink
|
||||
from pymavlink import mavutil
|
||||
import rpi_ws281x
|
||||
import pigpio
|
||||
from espeak import espeak
|
||||
|
||||
print cv2.getBuildInformation()
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ monkey --version
|
||||
pigpiod -v
|
||||
i2cdetect -V
|
||||
butterfly -h
|
||||
espeak --version
|
||||
|
||||
# ros stuff
|
||||
|
||||
@@ -47,4 +46,4 @@ rosversion rosserial
|
||||
rosversion usb_cam
|
||||
rosversion cv_camera
|
||||
rosversion web_video_server
|
||||
rosversion rosshow
|
||||
rosversion ros_ws281x
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<launch>
|
||||
<arg name="aruco_detect" default="true"/>
|
||||
<arg name="aruco_map" default="false"/>
|
||||
<arg name="aruco_vpe" default="false"/>
|
||||
<arg name="aruco_map" default="true"/>
|
||||
<arg name="aruco_vpe" default="true"/>
|
||||
|
||||
<!-- For additional help go to https://clever.copterexpress.com/aruco.html -->
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<remap from="image_raw" to="main_camera/image_raw"/>
|
||||
<remap from="camera_info" to="main_camera/camera_info"/>
|
||||
<remap from="markers" to="aruco_detect/markers"/>
|
||||
<param name="map" value="$(find aruco_pose)/map/map.txt"/>
|
||||
<param name="map" value="$(find aruco_pose)/map/nti_novgorod.txt"/>
|
||||
<param name="known_tilt" value="map"/>
|
||||
<param name="frame_id" value="aruco_map_detected" if="$(arg aruco_vpe)"/>
|
||||
<param name="frame_id" value="aruco_map" unless="$(arg aruco_vpe)"/>
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
<arg name="web_video_server" default="true"/>
|
||||
<arg name="rosbridge" default="true"/>
|
||||
<arg name="main_camera" default="true"/>
|
||||
<arg name="optical_flow" default="false"/>
|
||||
<arg name="aruco" default="false"/>
|
||||
<arg name="rc" default="true"/>
|
||||
<arg name="rangefinder_vl53l1x" default="false"/>
|
||||
<arg name="optical_flow" default="true"/>
|
||||
<arg name="aruco" default="true"/>
|
||||
<arg name="rc" default="false"/>
|
||||
<arg name="rangefinder_vl53l1x" default="true"/>
|
||||
<arg name="arduino" default="false"/>
|
||||
|
||||
<!-- mavros -->
|
||||
<include file="$(find clever)/launch/mavros.launch">
|
||||
@@ -62,9 +63,15 @@
|
||||
<!-- vl53l1x ToF rangefinder -->
|
||||
<node name="vl53l1x" pkg="vl53l1x" type="vl53l1x_node" output="screen" if="$(arg rangefinder_vl53l1x)">
|
||||
<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 -->
|
||||
</node>
|
||||
|
||||
<!-- rc backend -->
|
||||
<node name="rc" pkg="clever" type="rc" output="screen" if="$(arg rc)"/>
|
||||
|
||||
<!-- Arduino bridge -->
|
||||
<node pkg="rosserial_python" type="serial_node.py" name="serial_node" output="screen" if="$(arg arduino)">
|
||||
<param name="port" value="/dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0"/>
|
||||
</node>
|
||||
</launch>
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
<!-- article about camera setup: https://clever.copterexpress.com/camera_frame.html -->
|
||||
|
||||
<!-- camera is oriented downward, camera cable goes backward [option 1] -->
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/>
|
||||
<!-- <node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/> -->
|
||||
|
||||
<!-- camera is oriented downward, camera cable goes forward [option 2] -->
|
||||
<!--<node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 1.5707963 0 3.1415926 base_link main_camera_optical"/>-->
|
||||
<node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="-0.04 0 -0.08 1.5707963 0 3.1415926 base_link main_camera_optical"/>
|
||||
|
||||
<!-- camera is oriented upward, camera cable goes backward [option 3] -->
|
||||
<!--<node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 0.07 1.5707963 0 0 base_link main_camera_optical"/>-->
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
<depend>mjpg-streamer</depend>
|
||||
<depend>rosbridge_server</depend>
|
||||
<depend>web_video_server</depend>
|
||||
<exec_depend>python-pymavlink</exec_depend>
|
||||
<!-- Use test_depend for packages you need only for testing: -->
|
||||
<!-- <test_depend>gtest</test_depend> -->
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
flask==0.12.3
|
||||
docopt==0.6.2
|
||||
geopy==1.11.0
|
||||
pymavlink==2.2.10
|
||||
smbus2==0.2.1
|
||||
VL53L1X==0.0.2
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import math
|
||||
import subprocess
|
||||
from subprocess import Popen, PIPE
|
||||
import re
|
||||
import traceback
|
||||
from threading import Event
|
||||
import numpy
|
||||
import rospy
|
||||
from systemd import journal
|
||||
import tf2_ros
|
||||
import tf2_geometry_msgs
|
||||
from pymavlink import mavutil
|
||||
from std_srvs.srv import Trigger
|
||||
from sensor_msgs.msg import Image, CameraInfo, NavSatFix, Imu, Range
|
||||
from mavros_msgs.msg import State, OpticalFlowRad, Mavlink
|
||||
from mavros_msgs.msg import State, OpticalFlowRad
|
||||
from mavros_msgs.srv import ParamGet
|
||||
from geometry_msgs.msg import PoseStamped, TwistStamped, PoseWithCovarianceStamped, Vector3Stamped
|
||||
from geometry_msgs.msg import PoseStamped, TwistStamped, PoseWithCovarianceStamped
|
||||
import tf.transformations as t
|
||||
from aruco_pose.msg import MarkerArray
|
||||
from mavros import mavlink
|
||||
|
||||
|
||||
# TODO: roscore is running
|
||||
# TODO: clever.service is running
|
||||
# TODO: check attitude is present
|
||||
# TODO: disk free space
|
||||
# TODO: map, base_link, body
|
||||
@@ -33,41 +28,28 @@ from mavros import mavlink
|
||||
rospy.init_node('selfcheck')
|
||||
|
||||
|
||||
tf_buffer = tf2_ros.Buffer()
|
||||
tf_listener = tf2_ros.TransformListener(tf_buffer)
|
||||
|
||||
|
||||
failures = []
|
||||
infos = []
|
||||
current_check = None
|
||||
|
||||
|
||||
def failure(text, *args):
|
||||
msg = text % args
|
||||
rospy.logwarn('%s: %s', current_check, msg)
|
||||
failures.append(msg)
|
||||
|
||||
|
||||
def info(text, *args):
|
||||
msg = text % args
|
||||
rospy.loginfo('%s: %s', current_check, msg)
|
||||
infos.append(msg)
|
||||
failures.append(text % args)
|
||||
|
||||
|
||||
def check(name):
|
||||
def inner(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
failures[:] = []
|
||||
infos[:] = []
|
||||
global current_check
|
||||
current_check = name
|
||||
try:
|
||||
fn(*args, **kwargs)
|
||||
for f in failures:
|
||||
rospy.logwarn('%s: %s', name, f)
|
||||
except Exception as e:
|
||||
for f in failures:
|
||||
rospy.logwarn('%s: %s', name, f)
|
||||
traceback.print_exc()
|
||||
rospy.logerr('%s: exception occurred', name)
|
||||
rospy.logwarn('%s: exception occurred', name)
|
||||
return
|
||||
if not failures and not infos:
|
||||
if not failures:
|
||||
rospy.loginfo('%s: OK', name)
|
||||
return wrapper
|
||||
return inner
|
||||
@@ -77,12 +59,7 @@ 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
|
||||
|
||||
res = param_get(param_id=name)
|
||||
if not res.success:
|
||||
failure('Unable to retrieve PX4 parameter %s', name)
|
||||
else:
|
||||
@@ -91,116 +68,36 @@ def get_param(name):
|
||||
return res.value.real
|
||||
|
||||
|
||||
recv_event = Event()
|
||||
link = mavutil.mavlink.MAVLink('', 255, 1)
|
||||
mavlink_pub = rospy.Publisher('mavlink/to', Mavlink, queue_size=1)
|
||||
mavlink_recv = ''
|
||||
|
||||
|
||||
def mavlink_message_handler(msg):
|
||||
global mavlink_recv
|
||||
if msg.msgid == 126:
|
||||
mav_bytes_msg = mavlink.convert_to_bytes(msg)
|
||||
mav_msg = link.decode(mav_bytes_msg)
|
||||
mavlink_recv += ''.join(chr(x) for x in mav_msg.data[:mav_msg.count])
|
||||
if 'nsh>' in mavlink_recv:
|
||||
# Remove the last line, including newline before prompt
|
||||
mavlink_recv = mavlink_recv[:mavlink_recv.find('nsh>') - 1]
|
||||
recv_event.set()
|
||||
|
||||
|
||||
mavlink_sub = rospy.Subscriber('mavlink/from', Mavlink, mavlink_message_handler)
|
||||
# FIXME: not sleeping here still breaks things
|
||||
rospy.sleep(0.5)
|
||||
|
||||
|
||||
def mavlink_exec(cmd, timeout=3.0):
|
||||
global mavlink_recv
|
||||
mavlink_recv = ''
|
||||
recv_event.clear()
|
||||
if not cmd.endswith('\n'):
|
||||
cmd += '\n'
|
||||
msg = mavutil.mavlink.MAVLink_serial_control_message(
|
||||
device=mavutil.mavlink.SERIAL_CONTROL_DEV_SHELL,
|
||||
flags=mavutil.mavlink.SERIAL_CONTROL_FLAG_RESPOND | mavutil.mavlink.SERIAL_CONTROL_FLAG_EXCLUSIVE |
|
||||
mavutil.mavlink.SERIAL_CONTROL_FLAG_MULTI,
|
||||
timeout=3,
|
||||
baudrate=0,
|
||||
count=len(cmd),
|
||||
data=map(ord, cmd.ljust(70, '\0')))
|
||||
msg.pack(link)
|
||||
ros_msg = mavlink.convert_to_rosmsg(msg)
|
||||
mavlink_pub.publish(ros_msg)
|
||||
recv_event.wait(timeout)
|
||||
return mavlink_recv
|
||||
|
||||
|
||||
@check('FCU')
|
||||
def check_fcu():
|
||||
try:
|
||||
state = rospy.wait_for_message('mavros/state', State, timeout=3)
|
||||
if not state.connected:
|
||||
failure('no connection to the FCU (check wiring)')
|
||||
return
|
||||
|
||||
# Make sure the console is available to us
|
||||
mavlink_exec('\n')
|
||||
version_str = mavlink_exec('ver all')
|
||||
if version_str == '':
|
||||
info('no version data available from SITL')
|
||||
|
||||
r = re.compile(r'^FW (git tag|version): (v?\d\.\d\.\d.*)$')
|
||||
is_clever_firmware = False
|
||||
for ver_line in version_str.split('\n'):
|
||||
match = r.search(ver_line)
|
||||
if match is not None:
|
||||
field, version = match.groups()
|
||||
info('firmware %s: %s' % (field, version))
|
||||
if 'clever' in version:
|
||||
is_clever_firmware = True
|
||||
|
||||
if not is_clever_firmware:
|
||||
failure('not running Clever PX4 firmware, check http://clever.copterexpress.com/firmware.html')
|
||||
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
if est == 1:
|
||||
info('selected estimator: LPE')
|
||||
rospy.loginfo('Selected estimator: LPE')
|
||||
fuse = get_param('LPE_FUSION')
|
||||
if fuse & (1 << 4):
|
||||
info('LPE_FUSION: land detector fusion is enabled')
|
||||
rospy.loginfo('LPE_FUSION: land detector fusion is enabled')
|
||||
else:
|
||||
info('LPE_FUSION: land detector fusion is disabled')
|
||||
rospy.loginfo('LPE_FUSION: land detector fusion is disabled')
|
||||
if fuse & (1 << 7):
|
||||
info('LPE_FUSION: barometer fusion is enabled')
|
||||
rospy.loginfo('LPE_FUSION: barometer fusion is enabled')
|
||||
else:
|
||||
info('LPE_FUSION: barometer fusion is disabled')
|
||||
rospy.loginfo('LPE_FUSION: barometer fusion is disabled')
|
||||
|
||||
elif est == 2:
|
||||
info('selected estimator: EKF2')
|
||||
rospy.loginfo('Selected estimator: EKF2')
|
||||
else:
|
||||
failure('unknown selected estimator: %s', est)
|
||||
failure('Unknown selected estimator: %s', est)
|
||||
|
||||
except rospy.ROSException:
|
||||
failure('no MAVROS state (check wiring)')
|
||||
|
||||
|
||||
def describe_direction(v):
|
||||
if v.x > 0.9:
|
||||
return 'forward'
|
||||
elif v.x < - 0.9:
|
||||
return 'backward'
|
||||
elif v.y > 0.9:
|
||||
return 'left'
|
||||
elif v.y < -0.9:
|
||||
return 'right'
|
||||
elif v.z > 0.9:
|
||||
return 'upward'
|
||||
elif v.z < -0.9:
|
||||
return 'downward'
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@check('Camera')
|
||||
def check_camera(name):
|
||||
try:
|
||||
img = rospy.wait_for_message(name + '/image_raw', Image, timeout=1)
|
||||
@@ -208,91 +105,32 @@ def check_camera(name):
|
||||
failure('%s: no images (is the camera connected properly?)', name)
|
||||
return
|
||||
try:
|
||||
camera_info = rospy.wait_for_message(name + '/camera_info', CameraInfo, timeout=1)
|
||||
info = rospy.wait_for_message(name + '/camera_info', CameraInfo, timeout=1)
|
||||
except rospy.ROSException:
|
||||
failure('%s: no calibration info', name)
|
||||
return
|
||||
|
||||
if img.width != camera_info.width:
|
||||
failure('%s: calibration width doesn\'t match image width (%d != %d)', name, camera_info.width, img.width)
|
||||
if img.height != camera_info.height:
|
||||
failure('%s: calibration height doesn\'t match image height (%d != %d))', name, camera_info.height, img.height)
|
||||
|
||||
try:
|
||||
optical = Vector3Stamped()
|
||||
optical.header.frame_id = img.header.frame_id
|
||||
optical.vector.z = 1
|
||||
cable = Vector3Stamped()
|
||||
cable.header.frame_id = img.header.frame_id
|
||||
cable.vector.y = 1
|
||||
|
||||
optical = describe_direction(tf_buffer.transform(optical, 'base_link').vector)
|
||||
cable = describe_direction(tf_buffer.transform(cable, 'base_link').vector)
|
||||
if not optical or not cable:
|
||||
info('%s: custom camera orientation detected', name)
|
||||
else:
|
||||
info('camera is oriented %s, camera cable goes %s', optical, cable)
|
||||
|
||||
except tf2_ros.TransformException:
|
||||
failure('cannot transform from base_link to camera frame')
|
||||
if img.width != info.width:
|
||||
failure('%s: calibration width doesn\'t match image width (%d != %d)', name, info.width, img.width)
|
||||
if img.height != info.height:
|
||||
failure('%s: calibration height doesn\'t match image height (%d != %d))', name, info.height, img.height)
|
||||
|
||||
|
||||
@check('Main camera')
|
||||
def check_main_camera():
|
||||
check_camera('main_camera')
|
||||
|
||||
|
||||
def is_process_running(binary, exact=False, full=False):
|
||||
try:
|
||||
args = ['pgrep']
|
||||
if exact:
|
||||
args.append('-x') # match exactly with the command name
|
||||
if full:
|
||||
args.append('-f') # use full process name to match
|
||||
args.append(binary)
|
||||
subprocess.check_output(args)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
@check('ArUco markers')
|
||||
@check('ArUco detector')
|
||||
def check_aruco():
|
||||
if is_process_running('aruco_detect', full=True):
|
||||
info('aruco_detect/length = %g m', rospy.get_param('aruco_detect/length'))
|
||||
known_tilt = rospy.get_param('aruco_detect/known_tilt')
|
||||
if known_tilt == 'map':
|
||||
known_tilt += ' (ALL markers are on the floor)'
|
||||
elif known_tilt == 'map_flipped':
|
||||
known_tilt += ' (ALL markers are on the ceiling)'
|
||||
info('aruco_detector/known_tilt = %s', known_tilt)
|
||||
try:
|
||||
rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=1)
|
||||
except rospy.ROSException:
|
||||
failure('no markers detection')
|
||||
return
|
||||
else:
|
||||
info('aruco_detect is not running')
|
||||
try:
|
||||
rospy.wait_for_message('aruco_detect/markers', MarkerArray, timeout=1)
|
||||
except rospy.ROSException:
|
||||
failure('no markers detection')
|
||||
return
|
||||
|
||||
if is_process_running('aruco_map', full=True):
|
||||
known_tilt = rospy.get_param('aruco_map/known_tilt')
|
||||
if known_tilt == 'map':
|
||||
known_tilt += ' (marker\'s map is on the floor)'
|
||||
elif known_tilt == 'map_flipped':
|
||||
known_tilt += ' (marker\'s map is on the ceiling)'
|
||||
info('aruco_map/known_tilt = %s', known_tilt)
|
||||
try:
|
||||
rospy.wait_for_message('aruco_map/pose', PoseWithCovarianceStamped, timeout=1)
|
||||
except rospy.ROSException:
|
||||
failure('no map detection')
|
||||
else:
|
||||
info('aruco_map is not running')
|
||||
try:
|
||||
rospy.wait_for_message('aruco_map/pose', PoseWithCovarianceStamped, timeout=1)
|
||||
except rospy.ROSException:
|
||||
failure('no map detection')
|
||||
|
||||
|
||||
@check('Vision position estimate')
|
||||
def check_vpe():
|
||||
vis = None
|
||||
try:
|
||||
vis = rospy.wait_for_message('mavros/vision_pose/pose', PoseStamped, timeout=1)
|
||||
except rospy.ROSException:
|
||||
@@ -300,11 +138,7 @@ def check_vpe():
|
||||
vis = rospy.wait_for_message('mavros/mocap/pose', PoseStamped, timeout=1)
|
||||
except rospy.ROSException:
|
||||
failure('no VPE or MoCap messages')
|
||||
# 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
|
||||
return
|
||||
|
||||
# check PX4 settings
|
||||
est = get_param('SYS_MC_EST_GROUP')
|
||||
@@ -316,29 +150,26 @@ def check_vpe():
|
||||
if vision_yaw_w == 0:
|
||||
failure('vision yaw weight is zero, change ATT_W_EXT_HDG parameter')
|
||||
else:
|
||||
info('Vision yaw weight: %.2f', vision_yaw_w)
|
||||
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')
|
||||
failure('vision position fusing 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)
|
||||
info('LPE_VIS_XY is %.2f m, LPE_VIS_Z is %.2f m', get_param('LPE_VIS_XY'), get_param('LPE_VIS_Z'))
|
||||
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')
|
||||
failure('vision position fusing is disabled, change EKF2_AID_MASK parameter')
|
||||
if not fuse & (1 << 4):
|
||||
failure('vision yaw fusion is disabled, change EKF2_AID_MASK parameter')
|
||||
failure('vision yaw fusing 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)
|
||||
info('EKF2_EVA_NOISE is %.3f, EKF2_EVP_NOISE is %.3f',
|
||||
get_param('EKF2_EVA_NOISE'),
|
||||
get_param('EKF2_EVP_NOISE'))
|
||||
|
||||
if not vis:
|
||||
return
|
||||
rospy.loginfo('EKF2_EVA_NOISE is %.3f, EKF2_EVP_NOISE is %.3f',
|
||||
get_param('EKF2_EVA_NOISE'),
|
||||
get_param('EKF2_EVP_NOISE'))
|
||||
|
||||
# check vision pose and estimated pose inconsistency
|
||||
try:
|
||||
@@ -409,7 +240,7 @@ def check_velocity():
|
||||
failure('vertical velocity estimation is %.2f m/s; is copter staying still?' % vert)
|
||||
|
||||
angular = velocity.twist.angular
|
||||
ANGULAR_VELOCITY_LIMIT = 0.1
|
||||
ANGULAR_VELOCITY_LIMIT = 0.01
|
||||
if abs(angular.x) > ANGULAR_VELOCITY_LIMIT:
|
||||
failure('pitch rate estimation is %.2f rad/s (%.2f deg/s); is copter staying still?',
|
||||
angular.x, math.degrees(angular.x))
|
||||
@@ -445,32 +276,32 @@ def check_optical_flow():
|
||||
if est == 1:
|
||||
fuse = get_param('LPE_FUSION')
|
||||
if not fuse & (1 << 1):
|
||||
failure('optical flow fusion is disabled, change LPE_FUSION parameter')
|
||||
failure('optical flow fusing 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):
|
||||
if scale != 0:
|
||||
failure('LPE_FLW_SCALE parameter is %.2f, but it should be 1.0', scale)
|
||||
|
||||
info('LPE_FLW_QMIN is %s, LPE_FLW_R is %.4f, LPE_FLW_RR is %.4f, SENS_FLOW_MINHGT is %.3f, SENS_FLOW_MAXHGT is %.3f',
|
||||
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'))
|
||||
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')
|
||||
failure('optical flow fusing 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)
|
||||
info('EKF2_OF_QMIN is %s, EKF2_OF_N_MIN is %.4f, EKF2_OF_N_MAX is %.4f, SENS_FLOW_MINHGT is %.3f, SENS_FLOW_MAXHGT is %.3f',
|
||||
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'))
|
||||
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:
|
||||
failure('no optical flow data (from Raspberry)')
|
||||
@@ -481,13 +312,13 @@ def check_rangefinder():
|
||||
# TODO: check FPS!
|
||||
rng = False
|
||||
try:
|
||||
rospy.wait_for_message('mavros/distance_sensor/rangefinder_sub', Range, timeout=4)
|
||||
rospy.wait_for_message('mavros/distance_sensor/rangefinder_sub', Range, timeout=0.5)
|
||||
rng = True
|
||||
except rospy.ROSException:
|
||||
failure('no rangefinder data from Raspberry')
|
||||
|
||||
try:
|
||||
rospy.wait_for_message('mavros/distance_sensor/rangefinder', Range, timeout=4)
|
||||
rospy.wait_for_message('mavros/distance_sensor/rangefinder', Range, timeout=0.5)
|
||||
rng = True
|
||||
except rospy.ROSException:
|
||||
failure('no rangefinder data from PX4')
|
||||
@@ -499,26 +330,28 @@ def check_rangefinder():
|
||||
if est == 1:
|
||||
fuse = get_param('LPE_FUSION')
|
||||
if not fuse & (1 << 5):
|
||||
info('"pub agl as lpos down" in LPE_FUSION is disabled, NOT operating over flat surface')
|
||||
rospy.loginfo('"pub agl as lpos down" in LPE_FUSION is disabled, NOT operating over flat surface')
|
||||
else:
|
||||
info('"pub agl as lpos down" in LPE_FUSION is enabled, operating over flat surface')
|
||||
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:
|
||||
info('EKF2_HGT_MODE != Range sensor, NOT operating over flat surface')
|
||||
rospy.loginfo('EKF2_HGT_MODE != Range sensor, NOT operating over flat surface')
|
||||
else:
|
||||
info('EKF2_HGT_MODE = Range sensor, operating over flat surface')
|
||||
rospy.loginfo('EKF2_HGT_MODE = Range sensor, operating over flat surface')
|
||||
aid = get_param('EKF2_RNG_AID')
|
||||
if aid != 1:
|
||||
info('EKF2_RNG_AID != 1, range sensor aiding disabled')
|
||||
rospy.loginfo('EKF2_RNG_AID != 1, range sensor aiding disabled')
|
||||
else:
|
||||
info('EKF2_RNG_AID = 1, range sensor aiding enabled')
|
||||
rospy.loginfo('EKF2_RNG_AID = 1, range sensor aiding enabled')
|
||||
|
||||
|
||||
@check('Boot duration')
|
||||
def check_boot_duration():
|
||||
output = subprocess.check_output('systemd-analyze')
|
||||
proc = Popen('systemd-analyze', stdout=PIPE)
|
||||
proc.wait()
|
||||
output = proc.communicate()[0]
|
||||
r = re.compile(r'([\d\.]+)s$')
|
||||
duration = float(r.search(output).groups()[0])
|
||||
if duration > 15:
|
||||
@@ -529,7 +362,9 @@ def check_boot_duration():
|
||||
def check_cpu_usage():
|
||||
WHITELIST = 'nodelet',
|
||||
CMD = "top -n 1 -b -i | tail -n +8 | awk '{ printf(\"%-8s\\t%-8s\\t%-8s\\n\", $1, $9, $12); }'"
|
||||
output = subprocess.check_output(CMD, shell=True)
|
||||
proc = Popen(CMD, stdout=PIPE, shell=True)
|
||||
proc.wait()
|
||||
output = proc.communicate()[0]
|
||||
processes = output.split('\n')
|
||||
for process in processes:
|
||||
if not process:
|
||||
@@ -541,68 +376,13 @@ def check_cpu_usage():
|
||||
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)
|
||||
|
||||
|
||||
@check('Image')
|
||||
def check_image():
|
||||
info('version: %s', open('/etc/clever_version').read().strip())
|
||||
|
||||
|
||||
@check('Preflight status')
|
||||
def check_preflight_status():
|
||||
# Make sure the console is available to us
|
||||
mavlink_exec('\n')
|
||||
cmdr_output = mavlink_exec('commander check')
|
||||
if cmdr_output == '':
|
||||
failure('No data from FCU')
|
||||
return
|
||||
cmdr_lines = cmdr_output.split('\n')
|
||||
r = re.compile(r'^(.*)(Preflight|Prearm) check: (.*)')
|
||||
for line in cmdr_lines:
|
||||
if 'WARN' in line:
|
||||
failure(line[line.find(']') + 2:])
|
||||
continue
|
||||
match = r.search(line)
|
||||
if match is not None:
|
||||
check_status = match.groups()[2]
|
||||
if check_status != 'OK':
|
||||
failure(' '.join([match.groups()[1], 'check:', check_status]))
|
||||
|
||||
|
||||
def selfcheck():
|
||||
check_image()
|
||||
check_clever_service()
|
||||
check_fcu()
|
||||
check_imu()
|
||||
check_local_position()
|
||||
check_velocity()
|
||||
check_global_position()
|
||||
check_preflight_status()
|
||||
check_main_camera()
|
||||
check_camera('main_camera')
|
||||
check_aruco()
|
||||
check_simpleoffboard()
|
||||
check_optical_flow()
|
||||
|
||||
@@ -70,7 +70,6 @@ ros::Duration global_position_timeout;
|
||||
ros::Duration battery_timeout;
|
||||
float default_speed;
|
||||
bool auto_release;
|
||||
bool land_only_in_offboard;
|
||||
std::map<string, string> reference_frames;
|
||||
|
||||
// Publishers
|
||||
@@ -645,12 +644,6 @@ bool land(std_srvs::Trigger::Request& req, std_srvs::Trigger::Response& res)
|
||||
|
||||
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;
|
||||
sm.request.custom_mode = "AUTO.LAND";
|
||||
|
||||
@@ -695,7 +688,6 @@ int main(int argc, char **argv)
|
||||
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("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.getParam("reference_frames", reference_frames);
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 227 KiB After Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 209 KiB |
|
Before Width: | Height: | Size: 186 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 166 KiB |
@@ -43,15 +43,12 @@
|
||||
* [Working with a LED strip on Raspberry 3](leds.md)
|
||||
* [Using rviz and rqt](rviz.md)
|
||||
* [Working with the ultrasonic distance gage](sonar.md)
|
||||
* [Working with a laser rangefinder](laser.md)
|
||||
* [PX4 Simulation](sitl.md)
|
||||
* [Software autorun](autolaunch.md)
|
||||
* [Controlling the copter from Arduino](arduino.md)
|
||||
* [Using an external 3G modem](3g.md)
|
||||
* Clever-based projects
|
||||
* [Copter spheric guard](shield.md)
|
||||
* [Face recognition system](face_recognition.md)
|
||||
* [An Android transmitter](android.md)
|
||||
* [Copter Hack 2018](copterhack2018.md)
|
||||
* [Copter Hack 2017](copterhack2017.md)
|
||||
* Supplementary materials
|
||||
@@ -59,7 +56,5 @@
|
||||
* [Flashing ESCs using BLHeliSuite](esc_firmware.md)
|
||||
* [MAVLink](mavlink.md)
|
||||
* [PX4 Logs and Topics](flight_logs.md)
|
||||
* [Camera calibration](calibration.md)
|
||||
* [Working with IR sensors on Raspberry Pi 3](ir_sensors.md)
|
||||
* Textbook
|
||||
* [Theory and Videos](lessons.md)
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
# 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.
|
||||
@@ -56,4 +56,4 @@ When scripting languages are used, [shebang] should be placed at the beginning o
|
||||
|
||||
```(bash)
|
||||
#!/usr/bin/env python
|
||||
```
|
||||
```
|
||||
@@ -13,4 +13,4 @@ The Rate Pitch and Rate Roll parameters should be the same.
|
||||
|
||||
YAW parameters should be changed individually, according to the above instruction (usually the yaw doesn't require serious adjustment, you may leave it default).
|
||||
|
||||

|
||||

|
||||
@@ -1,228 +0,0 @@
|
||||
# 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.
|
||||
|
||||

|
||||
|
||||
> 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).
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Corrected images:
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## 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**_
|
||||
|
||||

|
||||
|
||||
Press “Enter”. Go to _**/home/pi/catkin_ws/src/clever/clever/camera_info/**_, and copy the calibration .yaml file to this folder:
|
||||
|
||||

|
||||
|
||||
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:
|
||||
|
||||

|
||||
|
||||
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**_:
|
||||
|
||||

|
||||
|
||||
In line _**camera node**_, change parameter _**camera_info**_ to _**camera_info.yaml**_:
|
||||
|
||||

|
||||
|
||||
> Don't forget to change camera resolution.
|
||||
@@ -60,4 +60,4 @@ The first image — how a copter model looks in rviz with these settings, the se
|
||||
```
|
||||
|
||||
<img src="../assets/camera_option_4_rviz.png" width=400>
|
||||
<img src="../assets/camera_option_4_clever.jpg" width=400>
|
||||
<img src="../assets/camera_option_4_clever.jpg" width=400>
|
||||
@@ -34,4 +34,4 @@ Winning teams:
|
||||
4. International Post (Novosibirsk) — automatic scattering leaflets from the drone.
|
||||
5. LAMAR (Yekaterinburg) — an automatic quadcopter battery replacement station.
|
||||
|
||||
<img src="../assets/alcopter.jpg" title="Alcopter Team" height=300px>
|
||||
<img src="../assets/alcopter.jpg" title="Alcopter Team" height=300px>
|
||||
@@ -1,163 +0,0 @@
|
||||
# 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%">
|
||||
@@ -50,4 +50,4 @@ To upload the `v3` firmware to Pixhawk, you may need the `force_upload` command:
|
||||
|
||||
```
|
||||
make px4fmu-v3_default force-upload
|
||||
```
|
||||
```
|
||||
@@ -46,4 +46,4 @@ The following may be used as fastening materials:
|
||||
1. Hot-melt glue;
|
||||
1. electrical tape;
|
||||
1. zip-ties (clamps);
|
||||
1. double-sided adhesive tape.
|
||||
1. double-sided adhesive tape.
|
||||
@@ -13,4 +13,4 @@ Main frames in package `clever`:
|
||||
|
||||
> **Hint** In accordance with [the agreement](http://www.ros.org/reps/rep-0103.html), for frames associated with the copter, the X-axis directed forward, Y – to the left, and Z – up.
|
||||
|
||||
More clearly, 3D visualization of the coordinate systems can be viewed using [rviz](rviz.md).
|
||||
More clearly, 3D visualization of the coordinate systems can be viewed using [rviz](rviz.md).
|
||||
@@ -1,232 +0,0 @@
|
||||
# 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
|
||||
@@ -1,89 +0,0 @@
|
||||
# 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*).
|
||||
-->
|
||||
@@ -1,115 +0,0 @@
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
### 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 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 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.
|
||||
@@ -1,71 +0,0 @@
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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).
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
@@ -1,112 +0,0 @@
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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...
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||
|
||||
### 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.
|
||||
|
||||

|
||||
|
||||
### 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?
|
||||
@@ -52,4 +52,4 @@ Part 6
|
||||
|
||||
Autonomous flights
|
||||
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/WvIlRG7ShWA" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/WvIlRG7ShWA" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
|
||||
@@ -176,4 +176,4 @@ Usually unused.</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<span style="background: #fffee6">Yellow</span> is used for highlighting the data fields(payload). An individual set of such fields exists for every message type.
|
||||
<span style="background: #fffee6">Yellow</span> is used for highlighting the data fields(payload). An individual set of such fields exists for every message type.
|
||||
@@ -52,4 +52,4 @@ Messages published in the topics may be viewed by using the `rostopic` utility,
|
||||
|
||||
`/mavros/setpoint_raw/attitude` — sending packet [SET\_ATTITUDE\_TARGET](https://pixhawk.ethz.ch/mavlink/#SET_ATTITUDE_TARGET). Allows setting the target attitude /angular velocity and throttle level. The values to be set are selected using the `type_mask` field
|
||||
|
||||
`/mavros/setpoint_raw/global` — sending packet [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://pixhawk.ethz.ch/mavlink/#SET_POSITION_TARGET_GLOBAL_INT). Allows setting the target attitude in global coordinates \(latitude, longitude, altitude\) and flight speed. **Not supported in PX4** \([issue](https://github.com/PX4/Firmware/issues/7552)\).
|
||||
`/mavros/setpoint_raw/global` — sending packet [SET\_POSITION\_TARGET\_GLOBAL\_INT](https://pixhawk.ethz.ch/mavlink/#SET_POSITION_TARGET_GLOBAL_INT). Allows setting the target attitude in global coordinates \(latitude, longitude, altitude\) and flight speed. **Not supported in PX4** \([issue](https://github.com/PX4/Firmware/issues/7552)\).
|
||||
@@ -24,4 +24,4 @@ The version of the installed image may be found in file `/etc/clever_version`:
|
||||
|
||||
```bash
|
||||
cat /etc/clever_version
|
||||
```
|
||||
```
|
||||
@@ -69,4 +69,4 @@ Prearm checks, switching the modes and states of the copter.
|
||||
|
||||
Enabling, disabling and configuring various sensors.
|
||||
|
||||
TODO
|
||||
TODO
|
||||
@@ -43,4 +43,4 @@ If there is no communication with the flight controller, the screen of the compu
|
||||
1. Go to MENU (by holding down the “OK” button)
|
||||
2. Select menu “System setup” (Up/Down Button to navigate, OK button - to confirms the choice).
|
||||
3. Select “RX setup” > “PPM OUTPUT” > “On”
|
||||
4. Save changes (hold pressed the “CANCEL” button).
|
||||
4. Save changes (hold pressed the “CANCEL” button).
|
||||
@@ -27,4 +27,4 @@ See more:
|
||||
|
||||
* [RPi image](microsd_images.md)
|
||||
* [SSH access](ssh.md)
|
||||
* [Network setup](network.md)
|
||||
* [Network setup](network.md)
|
||||
@@ -72,4 +72,4 @@ Brief description of useful rqt plugins:
|
||||
|
||||
* `rqt_image_view` – viewing images from topics like `sensor_msgs/Image`;
|
||||
* `rqt_multiplot` – Building charts from the data from of arbitrary topics (installation: `sudo apt-get install ros-kinetic-rqt-multiplot`);
|
||||
* Bag – working with [Bag-files](http://wiki.ros.org/rosbag).
|
||||
* Bag – working with [Bag-files](http://wiki.ros.org/rosbag).
|
||||
@@ -29,4 +29,4 @@ To check the main sub systems of PX4 and the possibility of arming at the moment
|
||||
|
||||
<img src="../assets/commander-check.png">
|
||||
|
||||
When using SITL instead of the MAVLink console, use a terminal with SITL running.
|
||||
When using SITL instead of the MAVLink console, use a terminal with SITL running.
|
||||
@@ -264,4 +264,4 @@ After a scheduled landing, do the following:
|
||||
2. Disconnect the Li-ion battery on the copter.
|
||||
3. Turn off the remote.
|
||||
|
||||
Next: [Connecting Raspberry Pi to Pihxawk](connection.md).
|
||||
Next: [Connecting Raspberry Pi to Pihxawk](connection.md).
|
||||
@@ -130,4 +130,4 @@ while True:
|
||||
# if the obstacle is closer than 1 m, hanging on the spot
|
||||
set_position(x=0, y=0, z=0, frame_id='body')
|
||||
rospy.sleep(0.1)
|
||||
```
|
||||
```
|
||||
@@ -25,4 +25,4 @@ When soldering:
|
||||
|
||||
5. Carry the soldering iron by the handle, rather than the cable or the working part. During breaks, the soldering iron is to be disconnected from the mains.
|
||||
|
||||
> **Caution** In case of the soldering iron malfunction or fire, disconnect it from the mains.
|
||||
> **Caution** In case of the soldering iron malfunction or fire, disconnect it from the mains.
|
||||
@@ -38,4 +38,4 @@ Using a multimeter, you need to make sure that the voltage converters located on
|
||||
After measurement:
|
||||
|
||||
* disconnect the battery
|
||||
* turn off the multimeter
|
||||
* turn off the multimeter
|
||||
@@ -7,4 +7,4 @@ Password: `cleverwifi`.
|
||||
|
||||
<img src="../assets/ssid.png" width="300px" alt="Wi-Fi SSID">
|
||||
|
||||
To edit the Wi-Fi settings, or to obtain more detailed information about the network device on Raspberry Pi, read this [article](network.md).
|
||||
To edit the Wi-Fi settings, or to obtain more detailed information about the network device on Raspberry Pi, read this [article](network.md).
|
||||
@@ -17,7 +17,7 @@
|
||||
* Настройка
|
||||
* [Первоначальная настройка](setup.md)
|
||||
* [Полетные режимы](modes.md)
|
||||
* [Прошивка полетного контролера](firmware.md)
|
||||
* [Прошивка Pixhawk/Pixracer](firmware.md)
|
||||
* [Параметры PX4](px4_parameters.md)
|
||||
* [Настройка PID](calibratePID.md)
|
||||
* Работа с Raspberry Pi
|
||||
@@ -29,17 +29,17 @@
|
||||
* [Настройка сети RPi](network.md)
|
||||
* [Работа с QGroundControl через Wi-Fi](gcs_bridge.md)
|
||||
* [Пилотирование со смартфона](rc.md)
|
||||
* [Интерфейс UART](uart.md)
|
||||
* [Просмотр видеострима с камер](web_video_server.md)
|
||||
* [Системы координат](frames.md)
|
||||
* [Интерфейс UART](uart.md)
|
||||
* Программирование
|
||||
* [ROS](ros.md)
|
||||
* [MAVROS](mavros.md)
|
||||
* [Автономный полет (OFFBOARD)](simple_offboard.md)
|
||||
* [Автономный полет в OFFBOARD](simple_offboard.md)
|
||||
* Визуальные маркеры (ArUco)
|
||||
* [Общая информация](aruco.md)
|
||||
* [Распознавание маркеров](aruco_marker.md)
|
||||
* [Навигация по карте маркеров](aruco_map.md)
|
||||
* [Распознавание карт маркеров](aruco_map.md)
|
||||
* [Навигация по Optical Flow](optical_flow.md)
|
||||
* [Автоматическая проверка](selfcheck.md)
|
||||
* [Примеры кода](snippets.md)
|
||||
@@ -47,7 +47,6 @@
|
||||
* [Камера \(компьютерное зрение\)](camera.md)
|
||||
* [Светодиодная лента](leds.md)
|
||||
* [Визуализация с помощью rviz](rviz.md)
|
||||
* [Работа с GPIO](gpio.md)
|
||||
* [Ультразвуковой дальномер](sonar.md)
|
||||
* [Лазерный дальномер](laser.md)
|
||||
* [Работа с SITL](sitl.md)
|
||||
@@ -55,11 +54,9 @@
|
||||
* [Автозапуск ПО](autolaunch.md)
|
||||
* [Взаимодействие с Arduino](arduino.md)
|
||||
* [3G-модем](3g.md)
|
||||
* [Установка ROS Kinetic](ros-install.md)
|
||||
* Проекты на базе Клевера
|
||||
* [Шаровая защита коптера](shield.md)
|
||||
* [Распознавание лиц](face_recognition.md)
|
||||
* [Подсчет количества объектов c камеры](object_counting.md)
|
||||
* [Пульт на Андроид](android.md)
|
||||
* [Блочный конструктор полета](clever_blocks.md)
|
||||
* [CopterHack-2018](copterhack2018.md)
|
||||
@@ -67,14 +64,12 @@
|
||||
* Дополнительные материалы
|
||||
* [Олимпиада НТИ 2019](nti2019.md)
|
||||
* [Вклад в Клевер](contributing.md)
|
||||
* [Сборка и модификация образа Клевера](image_building.md)
|
||||
* [Прошивка ESC контроллеров](esc_firmware.md)
|
||||
* [Протокол MAVLink](mavlink.md)
|
||||
* [Работа с логами PX4](flight_logs.md)
|
||||
* [Калибровка камеры](calibration.md)
|
||||
* [Работа с ИК датчиками](ir_sensors.md)
|
||||
* [Подключение PX4FLOW](px4flow.md)
|
||||
* [Модуль ESP8266](esp8266_bridge.md)
|
||||
* Учебник
|
||||
* [Теория и видеоуроки](lessons.md)
|
||||
* [Учебно-методическое пособие](metod.md)
|
||||
|
||||
@@ -18,26 +18,16 @@ rosrun rosserial_arduino make_libraries.py .
|
||||
|
||||
## Настройка Raspberry Pi
|
||||
|
||||
Для запуска `rosserial` создайте файл `arduino.launch` в каталоге `~/catkin_ws/src/clever/clever/launch/` со следующим содержимым:
|
||||
|
||||
```xml
|
||||
<launch>
|
||||
<node pkg="rosserial_python" type="serial_node.py" name="serial_node" output="screen" if="$(arg arduino)">
|
||||
<param name="port" value="/dev/serial/by-id/usb-1a86_USB2.0-Serial-if00-port0"/>
|
||||
</node>
|
||||
</launch>
|
||||
```
|
||||
|
||||
Чтобы единоразово запустить программу на Arduino, можно будет воспользоваться командой:
|
||||
Чтобы единоразово запустить программу на Arduino, можно воспользоваться командой:
|
||||
|
||||
```bash
|
||||
roslaunch clever arduino.launch
|
||||
```
|
||||
|
||||
Чтобы запускать связку с Arduino при старте системы автоматически, необходимо добавить запуск созданного launch-файла в основной launch-файл Клевера (`~/catkin_ws/src/clever/clever/launch/clever.launch`). Добавьте в конец этого файла строку:
|
||||
Чтобы запускать связку с Arduino при старте системы автоматически, необходимо установить аргумент `arudino` в launch-файле Клевера (`~/catkin_ws/src/clever/clever/launch/clever.launch`):
|
||||
|
||||
```xml
|
||||
<include file="$(find clever)/launch/arduino.launch"/>
|
||||
<arg name="arduino" default="true"/>
|
||||
```
|
||||
|
||||
При изменении launch-файла необходимо перезапустить пакет `clever`:
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
> **Info** Для распознавания маркеров модуль камеры должен быть корректно подключен и [сконфигурирован](camera.md).
|
||||
|
||||
<!-- -->
|
||||
|
||||
> **Hint** Рекомендуется использование [специальной сборки PX4 для Клевера](firmware.md#прошивка-для-клевера).
|
||||
|
||||
Модуль `aruco_map` распознает карты ArUco-маркеров, как единое целое. Также возможна навигация по картам ArUco-маркеров с использованием механизма Vision Position Estimate (VPE).
|
||||
|
||||
## Конфигурирование
|
||||
@@ -90,7 +86,7 @@ rosrun aruco_pose genmap.py 0.33 2 4 1 1 0 > ~/catkin_ws/src/clever/aruco_pose/m
|
||||
|
||||
При использовании **LPE** (параметр `SYS_MC_EST_GROUP` = `local_position_estimator, attitude_estimator_q`):
|
||||
|
||||
* В параметре `LPE_FUSION` включены флажки `vision position`, `land detector`. Флажок `baro` рекомендуется отключить.
|
||||
* В параметре `LPE_FUSION` включены флажки `vision position`, `land detector`.
|
||||
* Вес угла по рысканью по зрению: `ATT_W_EXT_HDG` = 0.5
|
||||
* Включена ориентация по Yaw по зрению: `ATT_EXT_HDG_M` = 1 `Vision`.
|
||||
* Шумы позиции по зрению: `LPE_VIS_XY` = 0.1 m, `LPE_VIS_Z` = 0.1 m.
|
||||
@@ -98,8 +94,6 @@ rosrun aruco_pose genmap.py 0.33 2 4 1 1 0 > ~/catkin_ws/src/clever/aruco_pose/m
|
||||
|
||||
<!-- * Выключен компас: `ATT_W_MAG` = 0 -->
|
||||
|
||||
> **Hint** На данный момент для полета по маркерам рекомендуется использование **LPE**.
|
||||
|
||||
Для проверки правильности всех настроек можно [воспользоваться утилитой `selfcheck.py`](selfcheck.md).
|
||||
|
||||
> **Info** Для использования LPE в Pixhawk необходимо [скачать прошивку с названием `px4fmu-v2_lpe.px4`](https://github.com/PX4/Firmware/releases).
|
||||
@@ -134,7 +128,7 @@ navigate(2, 2, 2, speed=1, frame_id='aruco_map') # полет в координ
|
||||
|
||||
Для навигации по маркерам, расположенным на потолке, необходимо поставить основную камеру так, чтобы она смотрела вверх и [установить соответствующий фрейм камеры](camera_frame.md).
|
||||
|
||||
Также в файле `~/catkin_ws/src/clever/clever/launch/aruco.launch` необходимо установить параметр `known_tilt` в секциях `aruco_detect` и `aruco_map` в значение `map_flipped`:
|
||||
Также в файле `~/catkin_ws/src/clever/clever/launch/aruco.launch` необходимо установить параметр `known_tilt` в секции `aruco_map` в значение `map_flipped`:
|
||||
|
||||
```xml
|
||||
<param name="known_tilt" value="map_flipped"/>
|
||||
|
||||
@@ -221,7 +221,7 @@ TODO
|
||||
белый -> PPM
|
||||
красный -> 5V
|
||||
черный -> GND
|
||||
оранжевый, зеленый -> не используются. Выньте эти провода из разъёма или обрежьте их.
|
||||
оранжевый, зеленый -> сейчас не используются. Устанавливаются в неиспользуемые пины радиоприемника
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -87,11 +87,10 @@ Saving mode (YES - on): # Режим сохранения
|
||||
|
||||
Скрипт начнет свою работу:
|
||||
|
||||
```
|
||||
Calibration started!
|
||||
Commands:
|
||||
help, catch (key: Enter), delete, restart, stop, finish
|
||||
```
|
||||
...
|
||||
Calibration started!
|
||||
Commands:
|
||||
help, catch (key: Enter), delete, restart, stop, finish
|
||||
|
||||
Чтобы откалибровать камеру, вам требуется сделать как минимум 25 фото шахматной доски с различных ракурсов.
|
||||
|
||||
|
||||
@@ -78,8 +78,6 @@ image_pub.publish(bridge.cv2_to_imgmsg(cv_image, 'bgr8'))
|
||||
|
||||
Получаемые изображения можно просматривать используя [web_video_server](web_video_server.md).
|
||||
|
||||
> **Warning** По умолчанию web_video_server показывает изображения из топиков со сжатием (например, /main_camera/image_raw/compressed). Ноды на Python не публикуют такие топики, поэтому для их просмотра следует добавлять `&type=mjpeg` в адресную стоку страницы web_video_server или изменить параметр `default_stream_type` на `mjpeg` в файле `clever.launch`.
|
||||
|
||||
### Примеры
|
||||
|
||||
#### Работа с QR-кодами
|
||||
|
||||
@@ -40,4 +40,4 @@ sudo systemctl start clever-blocks.service
|
||||
python main.py
|
||||
```
|
||||
|
||||
После запуска Вы можете открыть веб-интерфейс для блочного программирования по адресу [192.168.11.1:5000](192.168.11.1:5000).
|
||||
После запуска Вы можете открыть веб-интерфейс для блочного программирования по адресу [192.168.11.1:5000](192.168.11.1:5000).
|
||||
@@ -34,10 +34,14 @@
|
||||
|
||||
Более подробную информацию о Pull Request'ах смотрите [на GitHub](https://help.github.com/articles/about-pull-requests/) (англ.) или в [документации по git](https://git-scm.com/book/ru/v2/GitHub-Внесение-собственного-вклада-в-проекты) (русск.).
|
||||
|
||||
<!--
|
||||
## Добавление новой статьи
|
||||
|
||||
TODO
|
||||
-->
|
||||
|
||||
## Ваш проект с Клевером
|
||||
|
||||
Если вы реализовали собственный интересный проект на Клевере, вы можете добавить статью о нем в раздел "Проекты на базе Клевера".
|
||||
|
||||
Подготовьте вашу статью и пришлите Pull Request с ней в [репозиторий Клевера](– https://github.com/CopterExpress/clever).
|
||||
|
||||
<!-- TODO -->
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Модуль ESP8266
|
||||
|
||||
> **Note** Более подробную информацию можно найти в [основной статье](https://docs.px4.io/en/telemetry/esp8266_wifi_module.html) в официальной документации и в [репозитории с прошивкой](https://github.com/dogmaphobic/mavesp8266) для ESP8266.
|
||||
|
||||

|
||||
|
||||
Полётные контроллеры семейства Pixracer поддерживают подключение Wi-Fi модулей ESP8266. Эти модули можно использовать для подключения к полётному контроллеру с компьютера или планшета, даже если бортовой компьютер (например, [Raspberry Pi](raspberry.md)) отсутствует или неисправен.
|
||||
|
||||
> **Hint** Для Клевера предпочтительнее связываться с полётным контроллером [через Raspberry Pi](gcs_bridge.md).
|
||||
|
||||
## Подготовка модуля
|
||||
|
||||
Перед началом работы в модуль ESP8266 следует загрузить прошивку с поддержкой MAVLink. Для этого потребуется:
|
||||
|
||||
* [сама прошивка](http://www.grubba.com/mavesp8266/firmware-1.2.2.bin);
|
||||
* компьютер с ОС на базе GNU/Linux;
|
||||
* USB-UART адаптер;
|
||||
* утилита [esptool](https://github.com/espressif/esptool).
|
||||
|
||||
> **Warning** Убедитесь в том, что на вашем USB-UART адаптере установлено напряжение **3.3 В**!
|
||||
|
||||
Подключите ваш модуль к USB-UART, как показано на схеме:
|
||||
|
||||

|
||||
|
||||
Скачайте [прошивку для модуля](http://www.grubba.com/mavesp8266/firmware-1.2.2.bin). Подключите USB-UART к компьютеру и посмотрите, какое устройство соответствует переходнику. Убедитесь, что у вас установлена утилита esptool (её можно поставить командой `pip install esptool`). Запустите процесс загрузки прошивки командой:
|
||||
|
||||
```bash
|
||||
esptool.py --baud 921600 --port /dev/ttyUSB0 write_flash 0x00000 firmware-1.2.2.bin
|
||||
```
|
||||
|
||||
Вместо `/dev/ttyUSB0` укажите устройство, соответствующее вашему переходнику, а вместо `firmware-1.2.2.bin` - путь к прошивке.
|
||||
|
||||
> **Hint** Если в процессе загрузки прошивки возникли проблемы, вы можете запустить процесс заново.
|
||||
|
||||
## Работа с ESP8266
|
||||
|
||||

|
||||
|
||||
Подключите ESP8266 к Pixracer так, как показано на схеме, и включите полётный контроллер. В списке доступных Wi-Fi сетей появится сеть `PixRacer` с паролем по умолчанию `pixracer`. Подключитесь к этой сети и запустите QGroundControl. Программа должна автоматически установить соединение с полётным контроллером.
|
||||
|
||||
> **Info** Если автоматическое подключение не происходит, проверьте, что в настройках QGroundControl включено автоматическое подключение по UDP.
|
||||
|
||||

|
||||
|
||||
В меню настроек полётного контроллера появится вкладка **WiFi Bridge**. В ней можно изменить некоторые параметры модуля, например, режим работы (точка доступа/клиент), название и пароль Wi-Fi сети.
|
||||
|
||||

|
||||
|
||||
> **Warning** Настоятельно рекомендуется поменять стандартные параметры сети!
|
||||
|
||||
Также эти параметры можно поменять в веб-интерфейсе модуля, доступном по умолчанию по адресу [http://192.168.4.1/setup](http://192.168.4.1/setup).
|
||||
|
||||

|
||||
@@ -1,50 +1,20 @@
|
||||
Прошивка полетного контроллера
|
||||
Прошивка Pixhawk / Pixracer
|
||||
===
|
||||
|
||||
Pixhawk или Pixracer можно прошить, используя QGroundControl или утилиты командной строки.
|
||||
|
||||
Прошивка для Клевера
|
||||
---
|
||||
Различные варианты сборок стабильных прошивок PX4 можно скачать в разделе [Releases на GitHub](https://github.com/PX4/Firmware/releases).
|
||||
|
||||
Для Клевера рекомендуется использование специальной сборки PX4, которая содержит необходимые исправления и более подходящие параметры по умолчанию. Используйте последний стабильный релиз в [GitHub-репозитории](https://github.com/CopterExpress/Firmware/releases), содержащий слово `clever`, например `v1.8.2-clever.4`.
|
||||
В названии файла прошивки кодируется информации о целевой плате и варианте сборки. Примеры:
|
||||
|
||||
<div id="release" style="display:none">
|
||||
<p>Последний стабильный релиз: <strong><a id="download-latest-release"></a></strong>.</p>
|
||||
* `px4fmu-v2_default.px4` — прошивка для Pixhawk с EKF2.
|
||||
* `px4fmu-v2_lpe.px4` — прошивка для Pixhawk с LPE.
|
||||
* `px4fmu-v4_default.px4` — прошивка для Pixracer с EKF2 и LPE (*Клевер 3*).
|
||||
* `px4fmu-v3_default.px4` — прошивка для более новых версий Pixhawk (чип ревизии 3, см. илл. + Bootloader v5) с EKF2 и LPE.
|
||||
|
||||
<ul>
|
||||
<li>Скачать файл прошивки для Pixracer (<strong>Клевер 4 / Клевер 3</strong>) – <a id="firmware-pixracer" href=""><code>px4fmu-v4_default.px4</code></a>.</li>
|
||||
<li>Скачать файл прошивки для Pixhawk (<strong>Клевер 2</strong>) – <a id="firmware-pixhawk" href=""><code>px4fmu-v2_lpe.px4</code></a>.</li>
|
||||
</ul>
|
||||
</div>
|
||||

|
||||
|
||||
<script type="text/javascript">
|
||||
// get latest release from GitHub
|
||||
fetch('https://api.github.com/repos/CopterExpress/Firmware/releases').then(function(res) {
|
||||
return res.json();
|
||||
}).then(function(data) {
|
||||
// look for stable release
|
||||
let stable;
|
||||
for (let release of data) {
|
||||
let clever = release.name.indexOf('clever') != -1;
|
||||
if (clever && !release.prerelease && !release.draft) {
|
||||
stable = release;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let el = document.querySelector('#download-latest-release');
|
||||
el.innerHTML = stable.name;
|
||||
el.href = stable.html_url;
|
||||
document.querySelector('#release').style.display = 'block';
|
||||
for (let asset of stable.assets) {
|
||||
console.log(asset.name);
|
||||
if (asset.name == 'px4fmu-v4_default.px4') {
|
||||
document.querySelector('#firmware-pixracer').href = asset.browser_download_url;
|
||||
} else if (asset.name == 'px4fmu-v2_lpe.px4') {
|
||||
document.querySelector('#firmware-pixhawk').href = asset.browser_download_url;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
> **Note** Для загрузки `px4fmu-v3_default.px4` может понадобиться использование команды `force_upload` из командной строки.
|
||||
|
||||
QGroundControl
|
||||
---
|
||||
@@ -55,21 +25,7 @@ QGroundControl
|
||||
|
||||
> **Warning** Не отключайте USB-кабель до окончания процесса прошивки.
|
||||
|
||||
<!-- TODO: Иллюстрация. -->
|
||||
|
||||
Варианты прошивок
|
||||
---
|
||||
|
||||
В названии файла прошивки кодируется информации о целевой плате и варианте сборки. Примеры:
|
||||
|
||||
* `px4fmu-v4_default.px4` — прошивка для Pixracer с EKF2 и LPE (**Клевер 3** / **Клевер 4**).
|
||||
* `px4fmu-v2_lpe.px4` — прошивка для Pixhawk с LPE (**Клевер 2**).
|
||||
* `px4fmu-v2_default.px4` — прошивка для Pixhawk с EKF2.
|
||||
* `px4fmu-v3_default.px4` — прошивка для более новых версий Pixhawk (чип ревизии 3, см. илл. + Bootloader v5) с EKF2 и LPE.
|
||||
|
||||

|
||||
|
||||
> **Note** Для загрузки `px4fmu-v3_default.px4` может понадобиться использование команды `force_upload` из командной строки.
|
||||
TODO: Иллюстрация.
|
||||
|
||||
Командная строка
|
||||
---
|
||||
|
||||
@@ -10,8 +10,12 @@
|
||||
Анализ
|
||||
---
|
||||
|
||||
<!-- markdownlint-disable MD044 -->
|
||||
|
||||
### logs.px4.io
|
||||
|
||||
<!-- markdownlint-enable MD044 -->
|
||||
|
||||
Записанные лог-файлы можно загрузить на сайт https://logs.px4.io и анализировать их через веб-интерфейс.
|
||||
|
||||
### FlightPlot
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
# Работа с GPIO
|
||||
|
||||
GPIO (General-Purpose Input/Output) – это тип пинов на Raspberry Pi, напряжение на которых можно программно подавать и измерять. Также на некоторых пинах реализован аппаратный <abbr title="Широтно-импульсная модуляция">ШИМ</abbr> (<abbr title="Pulse-width modulation">PWM</abbr>).
|
||||
|
||||
> **Info** Используйте [распиновку](https://pinout.xyz), чтобы понять, какие из пинов на Raspberry Pi поддерживают GPIO и ШИМ.
|
||||
|
||||
Для работы с GPIO на [образе для RPi](microsd_images.md) предустановлена библиотека [`pigpio`](http://abyz.me.uk/rpi/pigpio/). Чтобы взаимодействовать с этой библиотекой, запустите соответствующий демон:
|
||||
|
||||
```bash
|
||||
sudo systemctl start pigpiod.service
|
||||
```
|
||||
|
||||
Для включение автозапуска демона `pigpiod` используйте команду:
|
||||
|
||||
```bash
|
||||
sudo systemctl enable pigpiod.service
|
||||
```
|
||||
|
||||
Пример работы с библиотекой:
|
||||
|
||||
```python
|
||||
import time
|
||||
import pigpio
|
||||
|
||||
# инициализируем подключение к pigpiod
|
||||
pi = pigpio.pi()
|
||||
|
||||
# устанавливаем режим 11 пина на вывод
|
||||
pi.set_mode(11, pigpio.OUTPUT)
|
||||
|
||||
# включаем сигнал на 11 пине
|
||||
pi.write(11, 1)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# отключаем сигнал на 11 пине
|
||||
pi.write(11, 0)
|
||||
|
||||
# ...
|
||||
|
||||
# устанавливаем режим 12 пина на ввод
|
||||
pi.set_mode(12, pigpio.INPUT)
|
||||
|
||||
# считываем состояние 12 пина
|
||||
level = pi.read(12)
|
||||
```
|
||||
|
||||
Для определения номера пина используйте [распиновку Raspberry Pi](https://pinout.xyz).
|
||||
|
||||
## Подключение сервоприводов
|
||||
|
||||
Большинство сервоприводов управляются с помощью ШИМ-сигнала, причем крайним положениям привода соответствуют сигналы шириной приблизительно 1000 и 2000 мкс. Значения для конкретного сервопривода могут быть определены экспериментально.
|
||||
|
||||
Подключите сигнальный провод сервопривода к одному из GPIO-пинов Raspberry. Для управления сервоприводом, подключенного к 13 пину, используйте такой код:
|
||||
|
||||
```python
|
||||
import time
|
||||
import pigpio
|
||||
|
||||
# устанавливаем режим 13 пина на вывод
|
||||
pi.set_mode(13, pigpio.OUTPUT)
|
||||
|
||||
# устанавливаем на 13 пине ШИМ сигнал в 1000 мкс
|
||||
pi.set_servo_pulsewidth(13, 1000)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# устанавливаем на 13 пине ШИМ сигнал в 2000 мкс
|
||||
pi.set_servo_pulsewidth(13, 2000)
|
||||
```
|
||||
|
||||
## Подключение электромагнита
|
||||
|
||||

|
||||
|
||||
Для подключения электромагнита используйте полевой транзистор (MOSFET). Подключите транзистор к одному из GPIO-пинов Raspberry Pi. Для управления магнитом, подключенным к 15 пину, используйте такой код:
|
||||
|
||||
```python
|
||||
import time
|
||||
import pigpio
|
||||
|
||||
pi = pigpio.pi()
|
||||
|
||||
# устанавливаем режим 15 пина на вывод
|
||||
pi.set_mode(15, pigpio.OUTPUT)
|
||||
|
||||
# включаем электромагнит
|
||||
pi.write(15, 1)
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
# отключаем электромагнит
|
||||
pi.write(15, 0)
|
||||
```
|
||||
@@ -1,85 +0,0 @@
|
||||
# Автоматическая сборка и модификация образа Клевера
|
||||
|
||||
Иногда возникает необходимость в сборке модифицированного образа системы, например для [своего проекта](https://github.com/artem30801/CleverSwarm) на базе [Клевера](https://github.com/copterexpress/clever). За основу можно взять, например, чистый образ Raspbian Stretch и модифицировать его с нуля, пройдя те же этапы, через который проходит сборка образа Клевера, добавив свои модификации. Однако на данный момент времени сборка образа Клевера занимает [чуть больше часа](https://travis-ci.org/CopterExpress/clever), что превышает ограничения бесплатной сборки в Travis \(50 минут\). Соответственно для проектов на базе Клевера имеет смысл брать за основу уже готовый образ и кастомизировать его. Концепция и основные этапы для автоматизированной сборки изложены ниже.
|
||||
|
||||
## Концепция
|
||||
|
||||
Имеется [Docker](https://www.docker.com/) образ, который содержит инструментарий для выполнения скриптов, копирования файлов и увеличения/сжатия размера образа системы на требуемой платформе для сборки \(например сборка для Raspberry Pi 3 осуществляется через qemu-arm-static, пример Docker образа для сборки находится [здесь](https://hub.docker.com/r/goldarte/img-tool)\). При запуске Docker образа выполняется скрипт `builder/image-build.sh`, в котором описан процесс сборки \(например скачивание опорного образа - увеличение свободного места на образе - установка необходимого софта - сжатие образа\), в результате которого создаётся файл образа системы. Триггер сборки, запуск Docker образа для сборки, выкладка образа осуществляется с помощью CI \(continuous integration\) системы [Travis](https://travis-ci.com/).
|
||||
|
||||
## Добавление скриптов сборки
|
||||
|
||||
1. Для осуществления сборки образа добавьте в свой проект build скрипты, модифицирующие исходный образ. За основу можно взять скрипты из репозитория Клевера \(папка [builder](https://github.com/CopterExpress/clever/tree/master/builder)\) или из репозитория шоу дронов на основе клеверов \(тоже папка [builder](https://github.com/artem30801/CleverSwarm/tree/master/builder)\). Опорный скрипт, который исполняется безусловно Docker образом в этих проектах - `builder/image-build.sh`.
|
||||
2. Для автоматического запуска сборки в облаке добавьте в свой проект `.travis.yml` файл, описывающий последовательность этапов выполнения сборки и правила для выкладки образов. [Пример](https://github.com/CopterExpress/clever/blob/master/.travis.yml) из репозитория Клевера, [пример](https://github.com/artem30801/CleverSwarm/blob/master/.travis.yml) из репозитория шоу дронов. Документация по составлению `.travis.yml` файла находится [здесь](https://docs.travis-ci.com/user/tutorial/).
|
||||
|
||||
## Настройка инструмента сборки travis-ci.com
|
||||
|
||||
1. Войдите в [Travis](/travis-ci.com) через свой GitHub аккаунт.
|
||||
2. Проверьте, что файл `.travis.yml` добавлен правильно: выберите свой проект, нажмите Trigger build из выпадающего меню справа сверху. Сборка должна начаться и успешно завершиться через некоторое время, если всё правильно.
|
||||
3. Настройте проект. Основные настройки можно оставить по умолчанию. Если необходимы ключи авторизации \(токены\) для доступа к репозиторию \(например для того, чтобы выложить образ прикреплённым файлом в релиз\), нужно сгенерировать их в своём аккаунте и добавить под названием переменной, которая используется для передачи токена.
|
||||
|
||||
## Запуск сборки в облаке travis-ci.com
|
||||
|
||||
По умолчанию скрипт сборки из `.travis.yml` файла выполняется автоматически при любом изменении GitHub репозитория. Есть возможность добавить скрипты, которые будут выполняться только при создании релиза \(публикации тега\), пример [здесь](https://github.com/CopterExpress/clever/blob/master/.travis.yml#L35).
|
||||
|
||||
## Запуск сборки на локальной машине
|
||||
|
||||
Если есть необходимость собрать образ быстрее, чем в облаке, или поэкспериментировать со сборкой локально, можно запустить Docker образ на локальной машине. Для этого необходимо в консоли перейти в папку с репозиторием, где прописаны скрипты автоматической сборки, и запустить оттуда Docker, например \(подробнее [здесь](https://github.com/goldarte/img-tool/blob/master/README.md)\):
|
||||
|
||||
```bash
|
||||
cd repo-w-instructions
|
||||
|
||||
docker run --privileged -it --rm -v /dev:/dev -v $(pwd):/mnt goldarte/img-tool:v0.5
|
||||
```
|
||||
|
||||
## Пример запуска автоматической сборки образа из форка репозитория шоу дронов
|
||||
|
||||
* Сделайте форк [репозитория](https://github.com/artem30801/CleverSwarm):
|
||||
|
||||

|
||||
|
||||
* Склонируйте репозиторий к себе на компьютер:
|
||||
|
||||
```bash
|
||||
git clone <адрес репозитория>
|
||||
```
|
||||
|
||||
* Зайдите на travis-ci.org под своим аккаунтом GitHub.
|
||||
* Выберите среди проектов форк репозитория шоу дронов и запустите тестовую сборку, нажав Trigger build из выпадающего меню:
|
||||
|
||||

|
||||

|
||||
|
||||
* Проверьте, что сборка запустилась:
|
||||
|
||||

|
||||
|
||||
* Добавьте ключ аутентификации к вашему репозиторию для прикрепления файла образа к релизу. Зайдите в [настройки токенов](https://github.com/settings/tokens) своего аккаунта и сгенерируйте новый токен для доступа к вашему репозиторию:
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
* Скопируйте получившийся токен:
|
||||
|
||||

|
||||
|
||||
* Перейдите в настройки сборки travis-ci.com и добавьте скопированный токен под именем `GITHUB_OAUTH_TOKEN`:
|
||||
|
||||

|
||||
|
||||
* В терминале перейдите в папку со скопированным репозиторием, создайте и опубликуйте тег для создания пре-релиза, автоматической сборки и выкладки образа на GitHub:
|
||||
|
||||
```
|
||||
git tag <имя тега>
|
||||
|
||||
git push --tags
|
||||
```
|
||||
|
||||
* Дождитесь окончания сборки образа и проверьте раздел Releases в вашем репозитории:
|
||||
|
||||

|
||||

|
||||
|
||||
* Нажмите на кнопку Draft a new release и выпустите [pre-release](https://github.com/goldarte/CleverSwarm/releases/tag/v0.2-test.1) или release собранного образа и исходным кодом:
|
||||
|
||||

|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
description: Режимы полетного контроллера PX4
|
||||
---
|
||||
|
||||
Полетные режимы
|
||||
===
|
||||
|
||||
|
||||
@@ -45,27 +45,39 @@ Wi-Fi адаптер на Raspberry Pi имеет два основных реж
|
||||
sudo systemctl disable dnsmasq
|
||||
```
|
||||
|
||||
2. Включите получение IP адреса на беспроводном интерфейсе DHCP клиентом. Для этого удалите из файла `/etc/dhcpcd.conf` строки:
|
||||
2. Включите получение IP адреса на беспроводном интерфейсе DHCP клиентом.
|
||||
|
||||
Для этого удалите следующие строки
|
||||
|
||||
```conf
|
||||
interface wlan0
|
||||
static ip_address=192.168.11.1/24
|
||||
```
|
||||
|
||||
3. Настройте `wpa_supplicant` для подключения к существующей точке доступа. Для этого замените содержимое файла `/etc/wpa_supplicant/wpa_supplicant.conf` на:
|
||||
из файла `/etc/dhcpcd.conf` вручную или введите следующие команды.
|
||||
|
||||
```bash
|
||||
sudo sed -i 's/interface wlan0//' /etc/dhcpcd.conf
|
||||
sudo sed -i 's/static ip_address=192.168.11.1\/24//' /etc/dhcpcd.conf
|
||||
```
|
||||
|
||||
3. Настройте `wpa_supplicant` для подключения к существующей точке доступа.
|
||||
|
||||
```bash
|
||||
cat << EOF | sudo tee /etc/wpa_supplicant/wpa_supplicant.conf
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
country=GB
|
||||
|
||||
network={
|
||||
ssid="SSID"
|
||||
psk="password"
|
||||
ssid="CLEVER"
|
||||
psk="cleverwifi"
|
||||
}
|
||||
|
||||
EOF
|
||||
```
|
||||
|
||||
где `SSID` – название сети, а `password` – пароль.
|
||||
где `CLEVER` – название сети, а `cleverwifi` – пароль.
|
||||
|
||||
4. Перезапустите службу `dhcpcd`.
|
||||
|
||||
@@ -75,22 +87,35 @@ Wi-Fi адаптер на Raspberry Pi имеет два основных реж
|
||||
|
||||
## Переключение адаптера в режим точки доступа
|
||||
|
||||
1. Включите статический IP адрес на беспроводном интерфейсе. Для этого добавьте в файл `/etc/dhcpcd.conf` строки:
|
||||
1. Включите статический IP адрес на беспроводном интерфейсе.
|
||||
|
||||
Для этого добавьте следующие строки
|
||||
|
||||
```conf
|
||||
interface wlan0
|
||||
static ip_address=192.168.11.1/24
|
||||
```
|
||||
|
||||
2. Настроите `wpa_supplicant` на работу в режиме точки доступа. Для этого замените содержимое файла `/etc/wpa_supplicant/wpa_supplicant.conf` на:
|
||||
в файл `/etc/dhcpcd.conf` вручную или введите следующую команду
|
||||
|
||||
```bash
|
||||
cat << EOF | sudo tee -a /etc/dhcpcd.conf
|
||||
interface wlan0
|
||||
static ip_address=192.168.11.1/24
|
||||
|
||||
EOF
|
||||
```
|
||||
|
||||
2. Настроите wpa_supplicant на работу в режиме точки доступа.
|
||||
|
||||
```bash
|
||||
cat << EOF | sudo tee /etc/wpa_supplicant/wpa_supplicant.conf
|
||||
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
|
||||
update_config=1
|
||||
country=GB
|
||||
|
||||
network={
|
||||
ssid="CLEVER-1234"
|
||||
ssid="CLEVER-$(head -c 100 /dev/urandom | xxd -ps -c 100 | sed -e 's/[^0-9]//g' | cut -c 1-4)"
|
||||
psk="cleverwifi"
|
||||
mode=2
|
||||
proto=RSN
|
||||
@@ -99,9 +124,9 @@ Wi-Fi адаптер на Raspberry Pi имеет два основных реж
|
||||
group=CCMP
|
||||
auth_alg=OPEN
|
||||
}
|
||||
```
|
||||
|
||||
где `CLEVER-1234` – название сети, а `cleverwifi` – пароль.
|
||||
EOF
|
||||
```
|
||||
|
||||
3. Перезагрузите службу `dhcpcd`.
|
||||
|
||||
|
||||