Compare commits

..

10 Commits

Author SHA1 Message Date
Oleg Kalachev
0666bfdf33 Shrink install lines 2021-06-09 13:43:58 +03:00
Oleg Kalachev
7c57581c33 blocks: install programs directory 2021-06-09 13:40:58 +03:00
Oleg Kalachev
881daaa389 aruco_pose: install library file 2021-06-09 13:34:09 +03:00
Oleg Kalachev
0dcf16de6b aruco_pose: install launch and map directories 2021-06-09 13:28:49 +03:00
Oleg Kalachev
8abb40249c Fix 2021-06-09 13:27:16 +03:00
Oleg Kalachev
3ab73edb74 clover: fix installation rc and led 2021-06-09 01:36:50 +03:00
Oleg Kalachev
aeb1c8ac11 builder: source catkin_ws/devel/setup.bash on build 2021-06-09 01:35:12 +03:00
Oleg Kalachev
2d3df6a94e clover: install nodes and libraries 2021-06-09 01:31:02 +03:00
Oleg Kalachev
0249d01ca7 clover: install launch files and examples 2021-06-09 01:13:15 +03:00
Oleg Kalachev
71100a9545 image: move examples to clover package 2021-06-08 23:06:58 +03:00
177 changed files with 131622 additions and 2404 deletions

View File

@@ -1,4 +1,4 @@
name: RPi image name: Build RPi image
on: on:
push: push:
@@ -9,7 +9,7 @@ on:
types: [ created ] types: [ created ]
jobs: jobs:
build: build-image:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -18,7 +18,7 @@ jobs:
docker run --privileged --rm -v /dev:/dev -v $(pwd):/builder/repo -e TRAVIS_TAG="${{ github.event.release.tag_name }}" sfalexrog/img-tool:qemu-update docker run --privileged --rm -v /dev:/dev -v $(pwd):/builder/repo -e TRAVIS_TAG="${{ github.event.release.tag_name }}" sfalexrog/img-tool:qemu-update
- name: Compress image - name: Compress image
run: | run: |
cd images && sudo chmod -R 777 . && zip -9 $(echo clover_*).zip clover_* && ls -l . && unzip -l clover_*.zip sudo chmod -R 777 images && zip -9 $(echo images/clover_*).zip images/clover_* && ls -l images
- name: Upload image - name: Upload image
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}

View File

@@ -7,14 +7,14 @@ on:
branches: [ master ] branches: [ master ]
jobs: jobs:
melodic: build-melodic:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Native Melodic build - name: Native Melodic build
run: | run: |
docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:melodic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh docker run --rm -v $(pwd):/root/catkin_ws/src/clover ros:melodic-ros-base /root/catkin_ws/src/clover/builder/standalone-install.sh
noetic: build-noetic:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@@ -7,7 +7,7 @@ on:
branches: [ master ] branches: [ master ]
jobs: jobs:
docs: documentation:
runs-on: ubuntu-18.04 runs-on: ubuntu-18.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -18,8 +18,9 @@ jobs:
run: | run: |
sudo sh -c "echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections" sudo sh -c "echo ttf-mscorefonts-installer msttcorefonts/accepted-mscorefonts-eula select true | debconf-set-selections"
sudo apt-get update && sudo apt-get install -y calibre msttcorefonts sudo apt-get update && sudo apt-get install -y calibre msttcorefonts
builder/assets/install_gitbook.sh npm install gitbook-cli -g
npm install markdownlint-cli@0.28.1 -g # FIXME: https://github.com/DavidAnson/markdownlint/issues/435 gitbook fetch 3.2.3 && npm i npm@3.10.10 --prefix=~/.gitbook/versions/3.2.3/ # fixing https://travis-ci.org/github/CopterExpress/clover/jobs/766541125#L932
npm install markdownlint-cli -g
npm install svgexport -g npm install svgexport -g
gitbook -V gitbook -V
markdownlint -V markdownlint -V

View File

@@ -1,4 +1,4 @@
name: Editorconfig name: Editorconfig lint
on: on:
push: push:
@@ -15,4 +15,4 @@ jobs:
run: | run: |
wget --no-verbose https://github.com/okalachev/editorconfig-checker/releases/download/1.2.1-disable-spaces-amount/ec-linux-amd64 wget --no-verbose https://github.com/okalachev/editorconfig-checker/releases/download/1.2.1-disable-spaces-amount/ec-linux-amd64
chmod +x ec-linux-amd64 chmod +x ec-linux-amd64
./ec-linux-amd64 -spaces-after-tabs -e "roslib.js|ros3d.js|eventemitter2.js|json-to-pretty-yaml.js|draw.cpp|BinUtils.swift|\.idea|apps/android/app|blockly/|clover_blocks/programs/|highlight/|python.js|Assets.xcassets|test_parser_pass.txt|test_node_failure.txt|aruco_pose/vendor|\.stl|\.dxf|\.dae" ./ec-linux-amd64 -spaces-after-tabs -e "roslib.js|ros3d.js|eventemitter2.js|draw.cpp|BinUtils.swift|\.idea|apps/android/app|blockly/|clover_blocks/programs/|highlight/|python.js|Assets.xcassets|test_parser_pass.txt|test_node_failure.txt|aruco_pose/vendor|\.stl|\.dxf|\.dae"

1
.gitignore vendored
View File

@@ -6,4 +6,3 @@ _book/
package-lock.json package-lock.json
clover_blocks/programs/*.* clover_blocks/programs/*.*
!clover_blocks/programs/examples/* !clover_blocks/programs/examples/*
/.vscode/

View File

@@ -1,6 +1,6 @@
# clover🍀: create autonomous drones easily # clover🍀: create autonomous drones easily
<img src="docs/assets/clover42-main-margin.png" align="right" width="400px" alt="COEX Clover Drone"> <img src="docs/assets/clover42-main.png" align="right" width="400px" alt="COEX Clover Drone">
Clover is an open source [ROS](https://www.ros.org)-based framework, providing user-friendly tools to control [PX4](https://px4.io)-powered drones. Clover is available as a ROS package, but is shipped mainly as a preconfigured image for Raspberry Pi. Once you've installed Raspberry Pi on your drone and flashed the image to its microSD card, taking the drone up in the air is a matter of minutes. Clover is an open source [ROS](https://www.ros.org)-based framework, providing user-friendly tools to control [PX4](https://px4.io)-powered drones. Clover is available as a ROS package, but is shipped mainly as a preconfigured image for Raspberry Pi. Once you've installed Raspberry Pi on your drone and flashed the image to its microSD card, taking the drone up in the air is a matter of minutes.

View File

@@ -1,4 +1,5 @@
# iOS-приложение для управления Клевером iOS-приложение для управления Клевером
--------------------------------------
Для установки зависимостей необходим [CocoaPods](https://cocoapods.org): Для установки зависимостей необходим [CocoaPods](https://cocoapods.org):

View File

@@ -70,7 +70,7 @@ It's recommended to run it within the same nodelet manager with the camera nodel
* `~map` path to text file with markers list * `~map` path to text file with markers list
* `~frame_id` published frame id (default: `aruco_map`) * `~frame_id` published frame id (default: `aruco_map`)
* `~known_tilt` known tilt (pitch and roll) of markers map as a frame * `~known_tilt` debug image width
* `~image_width` debug image width (default: 2000) * `~image_width` debug image width (default: 2000)
* `~image_height` debug image height (default: 2000) * `~image_height` debug image height (default: 2000)
* `~image_margin`  debug image margin (default: 200) * `~image_margin`  debug image margin (default: 200)

View File

@@ -10,7 +10,7 @@
"yametrika", "yametrika",
"anchors", "anchors",
"collapsible-menu", "collapsible-menu",
"validate-links@https://github.com/okalachev/gitbook-plugin-validate-links.git", "validate-links",
"bulk-redirect@https://github.com/okalachev/gitbook-plugin-bulk-redirect.git", "bulk-redirect@https://github.com/okalachev/gitbook-plugin-bulk-redirect.git",
"sitemap@https://github.com/okalachev/plugin-sitemap.git", "sitemap@https://github.com/okalachev/plugin-sitemap.git",
"toolbar@https://github.com/hamishwillee/gitbook-plugin-toolbar.git", "toolbar@https://github.com/hamishwillee/gitbook-plugin-toolbar.git",

View File

@@ -8,9 +8,5 @@ ExecStart=/bin/bash -c ". /home/pi/catkin_ws/devel/setup.sh; \
ROS_HOSTNAME=`hostname`.local exec stdbuf -o L roslaunch clover clover.launch --wait --screen --skip-log-check \ ROS_HOSTNAME=`hostname`.local exec stdbuf -o L roslaunch clover clover.launch --wait --screen --skip-log-check \
2> >(tee /tmp/clover.err)" 2> >(tee /tmp/clover.err)"
ExecStartPre=+rm /var/log/clover.log
StandardOutput=file:/var/log/clover.log
StandardError=file:/var/log/clover.log
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View File

@@ -1,9 +0,0 @@
#!/usr/bin/env bash
# GitBook CLI is deprecated, its installation is broken.
# This script fixes it until we stop using GitBook.
export NPM_CONFIG_UNSAFE_PERM=1
npm install gitbook-cli -g
gitbook fetch 3.2.3 && npm i npm@3.10.10 --prefix=~/.gitbook/versions/3.2.3/ # fixing https://travis-ci.org/github/CopterExpress/clover/jobs/766541125#L932

View File

@@ -113,11 +113,15 @@ ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/avahi-s
# If RPi then use a one thread to build a ROS package on RPi, else use all # If RPi then use a one thread to build a ROS package on RPi, else use all
[[ $(arch) == 'armv7l' ]] && NUMBER_THREADS=1 || NUMBER_THREADS=$(nproc --all) [[ $(arch) == 'armv7l' ]] && NUMBER_THREADS=1 || NUMBER_THREADS=$(nproc --all)
# Clover # Clover
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/clover.service' '/lib/systemd/system/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/roscore.service' '/lib/systemd/system/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/noetic-rosdep-clover.yaml' '/etc/ros/rosdep/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/noetic-rosdep-clover.yaml' '/etc/ros/rosdep/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/ros_python_paths' '/etc/sudoers.d/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/ros_python_paths' '/etc/sudoers.d/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/pigpiod.service' '/lib/systemd/system/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/pigpiod.service' '/lib/systemd/system/'
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/launch.nanorc' '/usr/share/nano/' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/launch.nanorc' '/usr/share/nano/'
# ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-ros-clover.rosinstall' '/home/pi/ros_catkin_ws/' # ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${SCRIPTS_DIR}'/assets/kinetic-ros-clover.rosinstall' '/home/pi/ros_catkin_ws/'
# Add PX4 udev rules
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} copy ${REPO_DIR}'/clover/config/99-px4fmu.rules' '/lib/udev/rules.d/'
# Add rename script # Add rename script
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-ros.sh' ${REPO_URL} ${IMAGE_VERSION} false false ${NUMBER_THREADS} ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-ros.sh' ${REPO_URL} ${IMAGE_VERSION} false false ${NUMBER_THREADS}
${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh' ${BUILDER_DIR}/image-chroot.sh ${IMAGE_PATH} exec ${SCRIPTS_DIR}'/image-validate.sh'

View File

@@ -122,8 +122,9 @@ rm -rf build # remove build artifacts
echo_stamp "Build Clover documentation" echo_stamp "Build Clover documentation"
cd /home/pi/catkin_ws/src/clover cd /home/pi/catkin_ws/src/clover
builder/assets/install_gitbook.sh NPM_CONFIG_UNSAFE_PERM=true npm install gitbook-cli -g
gitbook install NPM_CONFIG_UNSAFE_PERM=true gitbook fetch 3.2.3 && npm i npm@3.10.10 --prefix=~/.gitbook/versions/3.2.3/ # fixing https://travis-ci.org/github/CopterExpress/clover/jobs/766541125#L932
NPM_CONFIG_UNSAFE_PERM=true gitbook install
gitbook build gitbook build
touch node_modules/CATKIN_IGNORE docs/CATKIN_IGNORE _book/CATKIN_IGNORE clover/www/CATKIN_IGNORE apps/CATKIN_IGNORE # ignore documentation files by catkin touch node_modules/CATKIN_IGNORE docs/CATKIN_IGNORE _book/CATKIN_IGNORE clover/www/CATKIN_IGNORE apps/CATKIN_IGNORE # ignore documentation files by catkin
@@ -155,16 +156,6 @@ echo_stamp "Make \$HOME/examples symlink"
ln -s "$(catkin_find clover examples --first-only)" /home/pi ln -s "$(catkin_find clover examples --first-only)" /home/pi
chown -Rf pi:pi /home/pi/examples chown -Rf pi:pi /home/pi/examples
echo_stamp "Make systemd services symlinks"
ln -s /home/pi/catkin_ws/src/clover/builder/assets/clover.service /lib/systemd/system/
ln -s /home/pi/catkin_ws/src/clover/builder/assets/roscore.service /lib/systemd/system/
# validate
[ -f /lib/systemd/system/clover.service ]
[ -f /lib/systemd/system/roscore.service ]
echo_stamp "Make udev rules symlink"
ln -s "$(catkin_find clover udev --first-only)"/* /lib/udev/rules.d/
echo_stamp "Setup ROS environment" echo_stamp "Setup ROS environment"
cat << EOF >> /home/pi/.bashrc cat << EOF >> /home/pi/.bashrc
LANG='C.UTF-8' LANG='C.UTF-8'

View File

@@ -31,9 +31,5 @@ cd /home/pi/catkin_ws/src/clover/builder/test/
systemctl stop roscore systemctl stop roscore
# check documented packages available
apt-cache show gst-rtsp-launch
apt-cache show openvpn
echo "Move /etc/ld.so.preload back to its original position" echo "Move /etc/ld.so.preload back to its original position"
mv /etc/ld.so.preload.disabled-for-build /etc/ld.so.preload mv /etc/ld.so.preload.disabled-for-build /etc/ld.so.preload

View File

@@ -34,7 +34,6 @@ i2cdetect -V
butterfly -h butterfly -h
# espeak --version # espeak --version
mjpg_streamer --version mjpg_streamer --version
systemctl --version
# ros stuff # ros stuff

View File

@@ -5,7 +5,7 @@ import sys
import subprocess import subprocess
EXCLUDE = ('clever4-front-white.png', 'clever4-front-white-large.png', '.DS_Store', EXCLUDE = ('clever4-front-white.png', 'clever4-front-white-large.png', '.DS_Store',
'clever4-front-black-large.png','clover42-black.png', 'clover42-main-margin.png') 'clever4-front-black-large.png','clover42-black.png')
code = 0 code = 0

View File

@@ -270,8 +270,6 @@ install(DIRECTORY launch DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
install(DIRECTORY examples DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) install(DIRECTORY examples DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
install(DIRECTORY www DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
# TODO: install www # TODO: install www
# Only install udev rules when building a Debian package # Only install udev rules when building a Debian package
@@ -280,7 +278,7 @@ string(FIND ${CMAKE_INSTALL_PREFIX} "/opt/ros" _PREFIX_INDEX)
if (${_PREFIX_INDEX} EQUAL 0) if (${_PREFIX_INDEX} EQUAL 0)
message(STATUS "Building as a Debian package - adding udev rules as installable files") message(STATUS "Building as a Debian package - adding udev rules as installable files")
install(FILES install(FILES
udev/99-px4fmu.rules config/99-px4fmu.rules
DESTINATION /lib/udev/rules.d DESTINATION /lib/udev/rules.d
) )
else() else()

View File

@@ -36,7 +36,7 @@ curl https://raw.githubusercontent.com/mavlink/mavros/master/mavros/scripts/inst
You may optionally install udev rules to provide `/dev/px4fmu` symlink to your PX4-based flight controller connected over USB. Copy `99-px4fmu.rules` to your `/lib/udev/rules.d` folder: You may optionally install udev rules to provide `/dev/px4fmu` symlink to your PX4-based flight controller connected over USB. Copy `99-px4fmu.rules` to your `/lib/udev/rules.d` folder:
```bash ```bash
cd ~/catkin_ws/src/clover/clover/udev cd ~/catkin_ws/src/clover/clover/config
sudo cp 99-px4fmu.rules /lib/udev/rules.d sudo cp 99-px4fmu.rules /lib/udev/rules.d
``` ```

View File

@@ -15,17 +15,17 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates) set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger) land = rospy.ServiceProxy('land', Trigger)
print('Take off and hover 1 m above the ground') # Take off and hover 1 m above the ground
navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True) navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True)
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) rospy.sleep(3)
print('Fly forward 1 m') # Fly forward 1 m
navigate(x=1, y=0, z=0, frame_id='body') navigate(x=1, y=0, z=0, frame_id='body')
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) rospy.sleep(3)
print('Perform landing') # Perform landing
land() land()

View File

@@ -15,23 +15,23 @@ set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates) set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger) land = rospy.ServiceProxy('land', Trigger)
print('Take off and hover 1 m above the ground') # Take off and hover 1 m above the ground
navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True) navigate(x=0, y=0, z=1, frame_id='body', auto_arm=True)
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) rospy.sleep(3)
print('Fly 1 meter above ArUco marker 0') # Fly 1 meter above ArUco marker 0
navigate(x=0, y=0, z=1, frame_id='aruco_0') navigate(x=0, y=0, z=1, frame_id='aruco_0')
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) rospy.sleep(3)
print('Fly to x=1 y=1 z=1 relative to ArUco markers map') # Fly to x=1 y=1 z=1 relative to ArUco markers map
navigate(x=1, y=1, z=1, frame_id='aruco_map') navigate(x=1, y=1, z=1, frame_id='aruco_map')
# Wait for 5 seconds # Wait for 3 seconds
rospy.sleep(5) rospy.sleep(3)
print('Perform landing') # Perform landing
land() land()

View File

@@ -1,47 +0,0 @@
# Information: https://clover.coex.tech/en/simple_offboard.html#navigateglobal
import rospy
from clover import srv
from std_srvs.srv import Trigger
import math
rospy.init_node('flight')
get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
navigate = rospy.ServiceProxy('navigate', srv.Navigate)
navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)
set_position = rospy.ServiceProxy('set_position', srv.SetPosition)
set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)
set_attitude = rospy.ServiceProxy('set_attitude', srv.SetAttitude)
set_rates = rospy.ServiceProxy('set_rates', srv.SetRates)
land = rospy.ServiceProxy('land', Trigger)
# https://clover.coex.tech/en/snippets.html#wait_arrival
def wait_arrival(tolerance=0.2):
while not rospy.is_shutdown():
telem = get_telemetry(frame_id='navigate_target')
if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < tolerance:
break
rospy.sleep(0.2)
start = get_telemetry()
if math.isnan(start.lat):
raise Exception('No global position, install and configure GPS sensor: https://clover.coex.tech/gps')
print('Start point global position: lat={}, lon={}'.format(start.lat, start.lon))
print('Take off 3 meters')
navigate(x=0, y=0, z=3, frame_id='body', auto_arm=True)
wait_arrival()
print('Fly 1 arcsecond to the North (approx. 30 meters)')
navigate_global(lat=start.lat+1.0/60/60, lon=start.lon, z=start.z+3, yaw=math.inf, speed=5)
wait_arrival()
print('Fly to home position')
navigate_global(lat=start.lat, lon=start.lon, z=start.z+3, yaw=math.inf, speed=5)
wait_arrival()
print('Land')
land()

View File

@@ -31,11 +31,11 @@ def navigate_wait(x=0, y=0, z=0, yaw=float('nan'), yaw_rate=0, speed=0.5, \
return res return res
rospy.sleep(0.2) rospy.sleep(0.2)
print('Take off 1 meter') # Take off 1 meter
navigate_wait(z=1, frame_id='body', auto_arm=True) navigate_wait(z=1, frame_id='body', auto_arm=True)
print('Fly forward 1 m') # Fly forward 1 m
navigate_wait(x=1, frame_id='body') navigate_wait(x=1, frame_id='body')
print('Land') # Land
land() land()

View File

@@ -1,15 +0,0 @@
# Information: https://clover.coex.tech/en/laser.html
import rospy
from sensor_msgs.msg import Range
rospy.init_node('process_rangefinder')
def range_callback(msg):
# Process data from the rangefinder
print('Rangefinder distance:', msg.range)
# Subscribe to laser rangefinder data
rospy.Subscriber('rangefinder/range', Range, range_callback)
rospy.spin()

View File

@@ -3,7 +3,7 @@
<arg name="aruco_map" default="false"/> <arg name="aruco_map" default="false"/>
<arg name="aruco_vpe" default="false"/> <arg name="aruco_vpe" default="false"/>
<arg name="placement" default="floor"/> <!-- markers placement: floor, ceiling, unknown --> <arg name="placement" default="floor"/> <!-- markers placement: floor, ceiling, unknown -->
<arg name="length" default="0.22"/> <!-- not-in-map markers length, m --> <arg name="length" default="0.33"/> <!-- not-in-map markers length, m -->
<arg name="map" default="map.txt"/> <!-- markers map file name --> <arg name="map" default="map.txt"/> <!-- markers map file name -->
<!-- For additional help go to https://clover.coex.tech/aruco --> <!-- For additional help go to https://clover.coex.tech/aruco -->
@@ -12,7 +12,7 @@
<node name="aruco_detect" pkg="nodelet" if="$(arg aruco_detect)" type="nodelet" args="load aruco_pose/aruco_detect main_camera_nodelet_manager" output="screen" clear_params="true" respawn="true"> <node name="aruco_detect" pkg="nodelet" if="$(arg aruco_detect)" type="nodelet" args="load aruco_pose/aruco_detect main_camera_nodelet_manager" output="screen" clear_params="true" respawn="true">
<remap from="image_raw" to="main_camera/image_raw"/> <remap from="image_raw" to="main_camera/image_raw"/>
<remap from="camera_info" to="main_camera/camera_info"/> <remap from="camera_info" to="main_camera/camera_info"/>
<remap from="map_markers" to="aruco_map/markers"/> <remap from="map_markers" to="aruco_map/markers" if="$(arg aruco_map)"/>
<param name="estimate_poses" value="true"/> <param name="estimate_poses" value="true"/>
<param name="send_tf" value="true"/> <param name="send_tf" value="true"/>
<param name="known_tilt" value="map" if="$(eval placement == 'floor')"/> <param name="known_tilt" value="map" if="$(eval placement == 'floor')"/>

View File

@@ -75,6 +75,9 @@ private:
velo_pub_ = nh_priv.advertise<geometry_msgs::TwistStamped>("angular_velocity", 1); velo_pub_ = nh_priv.advertise<geometry_msgs::TwistStamped>("angular_velocity", 1);
shift_pub_ = nh_priv.advertise<geometry_msgs::Vector3Stamped>("shift", 1); shift_pub_ = nh_priv.advertise<geometry_msgs::Vector3Stamped>("shift", 1);
flow_.integrated_xgyro = NAN; // no IMU available
flow_.integrated_ygyro = NAN;
flow_.integrated_zgyro = NAN;
flow_.time_delta_distance_us = 0; flow_.time_delta_distance_us = 0;
flow_.distance = -1; // no distance sensor available flow_.distance = -1; // no distance sensor available
flow_.temperature = 0; flow_.temperature = 0;
@@ -176,7 +179,7 @@ private:
double flow_x = atan2(points_undist[0].x, focal_length_x); double flow_x = atan2(points_undist[0].x, focal_length_x);
double flow_y = atan2(points_undist[0].y, focal_length_y); double flow_y = atan2(points_undist[0].y, focal_length_y);
// Convert to FCU frame // // Convert to FCU frame
geometry_msgs::Vector3Stamped flow_camera, flow_fcu; geometry_msgs::Vector3Stamped flow_camera, flow_fcu;
flow_camera.header.frame_id = msg->header.frame_id; flow_camera.header.frame_id = msg->header.frame_id;
flow_camera.header.stamp = msg->header.stamp; flow_camera.header.stamp = msg->header.stamp;
@@ -193,11 +196,6 @@ private:
ros::Duration integration_time = msg->header.stamp - prev_stamp_; ros::Duration integration_time = msg->header.stamp - prev_stamp_;
uint32_t integration_time_us = integration_time.toSec() * 1.0e6; uint32_t integration_time_us = integration_time.toSec() * 1.0e6;
// Calculate flow gyro
flow_.integrated_xgyro = NAN;
flow_.integrated_ygyro = NAN;
flow_.integrated_zgyro = NAN;
if (calc_flow_gyro_) { if (calc_flow_gyro_) {
try { try {
auto flow_gyro_camera = calcFlowGyro(msg->header.frame_id, prev_stamp_, msg->header.stamp); auto flow_gyro_camera = calcFlowGyro(msg->header.frame_id, prev_stamp_, msg->header.stamp);
@@ -207,7 +205,9 @@ private:
flow_.integrated_ygyro = flow_gyro_fcu.vector.y; flow_.integrated_ygyro = flow_gyro_fcu.vector.y;
flow_.integrated_zgyro = flow_gyro_fcu.vector.z; flow_.integrated_zgyro = flow_gyro_fcu.vector.z;
} catch (const tf2::TransformException& e) { } catch (const tf2::TransformException& e) {
// Transform not available, keep NANs in flow gyro // Invalidate previous frame
prev_.release();
goto publish_debug;
} }
} }

View File

@@ -183,7 +183,7 @@ inline bool waitTransform(const string& target, const string& source,
} }
} }
#define TIMEOUT(msg, timeout) (msg.header.stamp.isZero() || (ros::Time::now() - msg.header.stamp > timeout)) #define TIMEOUT(msg, timeout) (ros::Time::now() - msg.header.stamp > timeout)
bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res) bool getTelemetry(GetTelemetry::Request& req, GetTelemetry::Response& res)
{ {
@@ -441,10 +441,6 @@ void publish(const ros::Time stamp)
// publish setpoint frame // publish setpoint frame
if (!setpoint.child_frame_id.empty()) { if (!setpoint.child_frame_id.empty()) {
if (setpoint.header.stamp == position_msg.header.stamp) {
return; // avoid TF_REPEATED_DATA warnings
}
setpoint.transform.translation.x = position_msg.pose.position.x; setpoint.transform.translation.x = position_msg.pose.position.x;
setpoint.transform.translation.y = position_msg.pose.position.y; setpoint.transform.translation.y = position_msg.pose.position.y;
setpoint.transform.translation.z = position_msg.pose.position.z; setpoint.transform.translation.z = position_msg.pose.position.z;

1
clover/www/clover.err Symbolic link
View File

@@ -0,0 +1 @@
/tmp/clover.err

View File

@@ -1 +0,0 @@
/var/log/clover.log

View File

@@ -4,12 +4,12 @@
<ul> <ul>
<li><a href="docs">View documentation</a> (snapshot of <a href="https://clover.coex.tech">clover.coex.tech</a>)</li> <li><a href="docs">View documentation</a> (snapshot of <a href="https://clover.coex.tech">clover.coex.tech</a>)</li>
<li><a href="topics.html">View topics</a></li>
<li><a href="" id="wvs">View image topics</a> (<code>web_video_server</code>)</li> <li><a href="" id="wvs">View image topics</a> (<code>web_video_server</code>)</li>
<li><a href="" id="butterfly">Open web terminal</a> (<code>Butterfly</code>)</li> <li><a href="" id="butterfly">Open web terminal</a> (<code>Butterfly</code>)</li>
<li>View <a href="viz.html">View 3D visualization</a>, <a href="aruco_map.html">3D visualization for markers map</a> (<code>ros3djs</code>)</li> <li><a href="viz.html">View 3D visualization</a> (<code>ros3djs</code>)</li>
<li><a href="aruco_map.html">3D visualization for markers map</a> (<code>ros3djs</code>)</li>
<li><a href="../clover_blocks/">Blocks programming</a> (<code>Blockly</code>)</li> <li><a href="../clover_blocks/">Blocks programming</a> (<code>Blockly</code>)</li>
<li><a href="clover.log">Clover console</a> (<code>/var/log/clover.log</code>)</li> <li><a href="clover.err">Clover console</a> (<code>/tmp/clover.err</code>)</li>
</ul> </ul>
<div class="version"></div> <div class="version"></div>
@@ -18,14 +18,6 @@
document.querySelector("#wvs").href = location.protocol + '//' + location.hostname + ':8080'; document.querySelector("#wvs").href = location.protocol + '//' + location.hostname + ':8080';
document.querySelector("#butterfly").href = location.protocol + '//' + location.hostname + ':57575'; document.querySelector("#butterfly").href = location.protocol + '//' + location.hostname + ':57575';
document.querySelector("#butterfly").addEventListener('click', function(e) {
if (location.hostname == 'localhost' || location.hostname == '127.0.0.1') {
if (!confirm('Please use regular Terminal app on a local machine.\nClick OK to proceed to Butterfly anyway.')) {
e.preventDefault();
}
}
});
// Determine image version // Determine image version
fetch('clover_version').then(function(response) { fetch('clover_version').then(function(response) {
if (response.status !== 200) return; if (response.status !== 200) return;

View File

@@ -1,236 +0,0 @@
// Browserified https://www.npmjs.com/package/json-to-pretty-yaml module
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
(function() {
"use strict";
var typeOf = require('remedial').typeOf;
var trimWhitespace = require('remove-trailing-spaces');
function stringify(data) {
var handlers, indentLevel = '';
handlers = {
"undefined": function() {
// objects will not have `undefined` converted to `null`
// as this may have unintended consequences
// For arrays, however, this behavior seems appropriate
return 'null';
},
"null": function() {
return 'null';
},
"number": function(x) {
return x;
},
"boolean": function(x) {
return x ? 'true' : 'false';
},
"string": function(x) {
// to avoid the string "true" being confused with the
// the literal `true`, we always wrap strings in quotes
return JSON.stringify(x);
},
"array": function(x) {
var output = '';
if (0 === x.length) {
output += '[]';
return output;
}
indentLevel = indentLevel.replace(/$/, ' ');
x.forEach(function(y, i) {
// TODO how should `undefined` be handled?
var handler = handlers[typeOf(y)];
if (!handler) {
throw new Error('what the crap: ' + typeOf(y));
}
output += '\n' + indentLevel + '- ' + handler(y, true);
});
indentLevel = indentLevel.replace(/ /, '');
return output;
},
"object": function(x, inArray, rootNode) {
var output = '';
if (0 === Object.keys(x).length) {
output += '{}';
return output;
}
if (!rootNode) {
indentLevel = indentLevel.replace(/$/, ' ');
}
Object.keys(x).forEach(function(k, i) {
var val = x[k],
handler = handlers[typeOf(val)];
if ('undefined' === typeof val) {
// the user should do
// delete obj.key
// and not
// obj.key = undefined
// but we'll error on the side of caution
return;
}
if (!handler) {
throw new Error('what the crap: ' + typeOf(val));
}
if (!(inArray && i === 0)) {
output += '\n' + indentLevel;
}
output += k + ': ' + handler(val);
});
indentLevel = indentLevel.replace(/ /, '');
return output;
},
"function": function() {
// TODO this should throw or otherwise be ignored
return '[object Function]';
}
};
return trimWhitespace(handlers[typeOf(data)](data, true, true) + '\n');
}
window.yamlStringify = stringify;
module.exports.stringify = stringify;
}());
},{"remedial":2,"remove-trailing-spaces":3}],2:[function(require,module,exports){
/*jslint onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true */
(function () {
"use strict";
var global = Function('return this')()
, classes = "Boolean Number String Function Array Date RegExp Object".split(" ")
, i
, name
, class2type = {}
;
for (i in classes) {
if (classes.hasOwnProperty(i)) {
name = classes[i];
class2type["[object " + name + "]"] = name.toLowerCase();
}
}
function typeOf(obj) {
return (null === obj || undefined === obj) ? String(obj) : class2type[Object.prototype.toString.call(obj)] || "object";
}
function isEmpty(o) {
var i, v;
if (typeOf(o) === 'object') {
for (i in o) { // fails jslint
v = o[i];
if (v !== undefined && typeOf(v) !== 'function') {
return false;
}
}
}
return true;
}
if (!String.prototype.entityify) {
String.prototype.entityify = function () {
return this.replace(/&/g, "&amp;").replace(/</g,
"&lt;").replace(/>/g, "&gt;");
};
}
if (!String.prototype.quote) {
String.prototype.quote = function () {
var c, i, l = this.length, o = '"';
for (i = 0; i < l; i += 1) {
c = this.charAt(i);
if (c >= ' ') {
if (c === '\\' || c === '"') {
o += '\\';
}
o += c;
} else {
switch (c) {
case '\b':
o += '\\b';
break;
case '\f':
o += '\\f';
break;
case '\n':
o += '\\n';
break;
case '\r':
o += '\\r';
break;
case '\t':
o += '\\t';
break;
default:
c = c.charCodeAt();
o += '\\u00' + Math.floor(c / 16).toString(16) +
(c % 16).toString(16);
}
}
}
return o + '"';
};
}
if (!String.prototype.supplant) {
String.prototype.supplant = function (o) {
return this.replace(/{([^{}]*)}/g,
function (a, b) {
var r = o[b];
return typeof r === 'string' || typeof r === 'number' ? r : a;
}
);
};
}
if (!String.prototype.trim) {
String.prototype.trim = function () {
return this.replace(/^\s*(\S*(?:\s+\S+)*)\s*$/, "$1");
};
}
// CommonJS / npm / Ender.JS
module.exports = {
typeOf: typeOf,
isEmpty: isEmpty
};
global.typeOf = global.typeOf || typeOf;
global.isEmpty = global.isEmpty || isEmpty;
}());
},{}],3:[function(require,module,exports){
"use strict";
/**
* removeTrailingSpaces
* Remove the trailing spaces from a string.
*
* @name removeTrailingSpaces
* @function
* @param {String} input The input string.
* @returns {String} The output string.
*/
module.exports = function removeTrailingSpaces(input) {
// TODO If possible, use a regex
return input.split("\n").map(function (x) {
return x.trimRight();
}).join("\n");
};
},{}]},{},[1]);

View File

@@ -1,100 +0,0 @@
const url = 'ws://' + location.hostname + ':9090';
const ros = new ROSLIB.Ros({ url: url });
ros.on('connection', function () {
document.body.classList.add('connected');
init();
});
ros.on('close', function () {
document.body.classList.remove('connected');
setTimeout(function() {
// reconnect
ros.connect(url);
}, 2000);
});
const title = document.querySelector('h1');
const topicsList = document.querySelector('#topics');
const topicMessage = document.querySelector('#topic-message');
function viewTopicsList() {
title.innerHTML = 'Topics:';
ros.getTopics(function(topics) {
topicsList.innerHTML = topics.topics.map(function(topic, i) {
const type = topics.types[i];
if (type == 'sensor_msgs/Image') {
let url = `${location.protocol}//${location.hostname}:8080/stream_viewer?topic=${topic}`;
return `<li><a href="${url}" class=topic title=${type}>${topic}</a> &#x1F5BC;</li>`;
} else {
return `<li><a href="?topic=${topic}" class=topic title=${type}>${topic}</a></li>`;
}
}).join('');
});
}
let rosdistro;
function viewTopic(topic) {
title.innerHTML = topic;
topicMessage.style.display = 'block';
ros.getTopicType(topic, function(typeStr) {
const [pack, type] = typeStr.split('/');
let href = `https://docs.ros.org/en/${rosdistro}/api/${pack}/html/msg/${type}.html`;
title.innerHTML = `${topic} <a id="topic-type" href=${href} target="_blank">${typeStr}</a>`;
});
new ROSLIB.Topic({ ros: ros, name: topic }).subscribe(function(msg) {
document.title = topic;
if (mouseDown) return;
topicMessage.innerHTML = yamlStringify(msg); // JSON.stringify(msg, null, 4);
});
}
function viewParameters() {
title.innerHTML = 'Parameters';
topicMessage.style.display = 'block';
let names = ['aruco_detect', 'main_camera', 'main_camera_nodelet_manager', 'mavros', 'optical_flow',
'rangefinder', 'rosapi', 'rosbridge_websocket', 'rosdistro', 'roslaunch', 'rosversion',
'roswww_static', 'run_id', 'simple_offboard', 'visualization', 'web_video_server'];
let params = {};
// ros.getParams(function(params) {
Promise.all(names.map(function(name) {
return new Promise(function(resolve, reject) {
new ROSLIB.Param({ ros, name }).get(function(value) {
params[name] = value;
resolve();
})
});
})).then(function() {
console.log(params);
topicMessage.innerHTML = yamlStringify(params);
});
}
let mouseDown;
topicMessage.addEventListener('mousedown', function() { mouseDown = true; });
topicMessage.addEventListener('mouseup', function() { mouseDown = false; });
function init() {
const params = Object.fromEntries(new URLSearchParams(window.location.search).entries());
viewParameters();
return;
if (!params.topic) {
viewTopicsList();
} else {
new ROSLIB.Param({ ros: ros, name: '/rosdistro'}).get(function(value) {
rosdistro = value.trim();
viewTopic(params.topic);
});
}
}
window.ros = ros;

View File

@@ -1,27 +0,0 @@
<html lang="en">
<head>
<title>ROS topics</title>
<script src="js/roslib.js"></script>
<link rel="icon" href="data:,"> <!-- make chrome don't request icon -->
<script type="module" src="js/topics.js"></script>
<script src="js/json-to-pretty-yaml.js"></script>
<style>
#topics { line-height: 1.2em; }
#topic-view {
display: none;
}
#topic-message {
display: none;
white-space: pre;
font-family: monospace;
}
#topic-type { font-family: monospace; font-size: 0.5em; vertical-align: super; font-weight: normal; }
.topic { font-family: monospace; }
</style>
</head>
<body>
<h1>&nbsp;</h1>
<ul id="topics"></ul>
<code id="topic-message" title="Hold mouse button to pause">No messages received</code>
</body>
</html>

View File

@@ -78,8 +78,6 @@ install(DIRECTORY programs
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
) )
install(DIRECTORY www DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
############# #############
## Testing ## ## Testing ##
############# #############

View File

@@ -10,7 +10,7 @@ Internal package documentation is given below.
## Frontend ## Frontend
The frontend files are located in [`www`](./www/) subdirectory. The frontend application uses [`roslib.js`](http://wiki.ros.org/roslibjs) library for communicating with backend node and other ROS resources. The frontend files are located in [`www`](./www/) subdirectory. The frontend application uses [`roblib.js`](http://wiki.ros.org/roslibjs) library for communicating with backend node and other ROS resources.
## `clover_blocks` node ## `clover_blocks` node
@@ -30,8 +30,7 @@ The frontend files are located in [`www`](./www/) subdirectory. The frontend app
Parameters read by frontend: Parameters read by frontend:
* `~navigate_tolerance` (*float*) distance tolerance in meters, used for navigate-like blocks (default: 0.2). * `~navigate_tolerance` (*float*) distance tolerance in meters, used for navigate-like blocks (default: 0.2).
* `~navigate_global_tolerance` (*float*) distance tolerance for global coordinates navigation (default: 1). * `~yaw_tolerance` (*float*) yaw angle tolerance in degrees, used in set_yaw block (default: 20).
* `~yaw_tolerance` (*float*) yaw angle tolerance in degrees, used in set_yaw block (default: 1).
* `~sleep_time` (*float*) duration of sleep in loop cycles, used for navigate-like blocks (default: 0.2). * `~sleep_time` (*float*) duration of sleep in loop cycles, used for navigate-like blocks (default: 0.2).
* `~confirm_run` (*bool*) enable confirmation to run the program (default: true). * `~confirm_run` (*bool*) enable confirmation to run the program (default: true).

View File

@@ -31,14 +31,6 @@ function considerFrameId(e) {
this.getInput('Y').fieldRow[0].setValue('y'); this.getInput('Y').fieldRow[0].setValue('y');
this.getInput('Z').fieldRow[0].setValue('z'); this.getInput('Z').fieldRow[0].setValue('z');
} }
if (this.getInput('LAT')) { // block has global coordinates
let global = frameId.startsWith('GLOBAL');
this.getInput('LAT').setVisible(global);
this.getInput('LON').setVisible(global);
this.getInput('X').setVisible(!global);
this.getInput('Y').setVisible(!global);
this.getInput('Z').fieldRow[0].setValue(frameId == 'GLOBAL' ? 'altitude' : 'z');
}
} }
if (this.getInput('ID')) { // block has marker id field if (this.getInput('ID')) { // block has marker id field
this.getInput('ID').setVisible(frameId == 'ARUCO'); // toggle id field this.getInput('ID').setVisible(frameId == 'ARUCO'); // toggle id field
@@ -73,9 +65,6 @@ function updateSetpointBlock(e) {
Blockly.Blocks['navigate'] = { Blockly.Blocks['navigate'] = {
init: function () { init: function () {
let navFrameId = frameIds.slice();
navFrameId.push(['global', 'GLOBAL_LOCAL'])
navFrameId.push(['global, WGS 84 alt.', 'GLOBAL'])
this.appendDummyInput() this.appendDummyInput()
.appendField("navigate to point"); .appendField("navigate to point");
this.appendValueInput("X") this.appendValueInput("X")
@@ -84,20 +73,12 @@ Blockly.Blocks['navigate'] = {
this.appendValueInput("Y") this.appendValueInput("Y")
.setCheck("Number") .setCheck("Number")
.appendField("left"); .appendField("left");
this.appendValueInput("LAT")
.setCheck("Number")
.appendField("latitude")
.setVisible(false);
this.appendValueInput("LON")
.setCheck("Number")
.appendField("longitude")
.setVisible(false)
this.appendValueInput("Z") this.appendValueInput("Z")
.setCheck("Number") .setCheck("Number")
.appendField("up"); .appendField("up");
this.appendDummyInput() this.appendDummyInput()
.appendField("relative to") .appendField("relative to")
.appendField(new Blockly.FieldDropdown(navFrameId), "FRAME_ID"); .appendField(new Blockly.FieldDropdown(frameIds), "FRAME_ID");
this.appendValueInput("ID") this.appendValueInput("ID")
.setCheck("Number") .setCheck("Number")
.appendField("with ID") .appendField("with ID")
@@ -287,7 +268,7 @@ Blockly.Blocks['mode'] = {
.appendField("current flight mode"); .appendField("current flight mode");
this.setOutput(true, "String"); this.setOutput(true, "String");
this.setColour(COLOR_STATE); this.setColour(COLOR_STATE);
this.setTooltip("Returns current flight mode (POSCTL, OFFBOARD, etc)."); this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type); this.setHelpUrl(DOCS_URL + '#' + this.type);
} }
}; };
@@ -394,7 +375,7 @@ Blockly.Blocks['take_off'] = {
this.setPreviousStatement(true, null); this.setPreviousStatement(true, null);
this.setNextStatement(true, null); this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT); this.setColour(COLOR_FLIGHT);
this.setTooltip("Take off on desired altitude in meters."); this.setTooltip("Take off on desired altitude in meters");
this.setHelpUrl(DOCS_URL + '#' + this.type); this.setHelpUrl(DOCS_URL + '#' + this.type);
} }
}; };
@@ -410,7 +391,7 @@ Blockly.Blocks['land'] = {
this.setPreviousStatement(true, null); this.setPreviousStatement(true, null);
this.setNextStatement(true, null); this.setNextStatement(true, null);
this.setColour(COLOR_FLIGHT); this.setColour(COLOR_FLIGHT);
this.setTooltip("Land the drone."); this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type); this.setHelpUrl(DOCS_URL + '#' + this.type);
} }
}; };
@@ -419,10 +400,10 @@ Blockly.Blocks['global_position'] = {
init: function () { init: function () {
this.appendDummyInput() this.appendDummyInput()
.appendField("current") .appendField("current")
.appendField(new Blockly.FieldDropdown([["latitude", "LAT"], ["longitude", "LON"], ["altitude", "ALT"]]), "FIELD"); .appendField(new Blockly.FieldDropdown([["latitude", "LATITUDE"], ["longitude", "LONGITUDE"], ["altitude", "ALTITUDE"]]), "NAME");
this.setOutput(true, "Number"); this.setOutput(true, "Number");
this.setColour(COLOR_STATE); this.setColour(230);
this.setTooltip("Returns current global position (latitude, longitude, altitude above the WGS 84 ellipsoid)."); this.setTooltip("");
this.setHelpUrl(DOCS_URL + '#' + this.type); this.setHelpUrl(DOCS_URL + '#' + this.type);
} }
}; };

View File

@@ -52,8 +52,6 @@
<value name="X"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="X"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="Y"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="Y"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="Z"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="Z"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="LAT"><shadow type="math_number"><field name="NUM">47.397503</field></shadow></value>
<value name="LON"><shadow type="math_number"><field name="NUM">8.544945</field></shadow></value>
<value name="SPEED"><shadow type="math_number"><field name="NUM">0.5</field></shadow></value> <value name="SPEED"><shadow type="math_number"><field name="NUM">0.5</field></shadow></value>
<value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
</block> </block>
@@ -87,7 +85,6 @@
<value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="ID"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
</block> </block>
<block type="get_attitude"></block> <block type="get_attitude"></block>
<block type="global_position"></block>
<block type="distance"> <block type="distance">
<value name="X"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="X"><shadow type="math_number"><field name="NUM">0</field></shadow></value>
<value name="Y"><shadow type="math_number"><field name="NUM">0</field></shadow></value> <value name="Y"><shadow type="math_number"><field name="NUM">0</field></shadow></value>

View File

@@ -39,8 +39,7 @@ var workspace = Blockly.inject('blockly', {
function readParams() { function readParams() {
return Promise.all([ return Promise.all([
ros.readParam('navigate_tolerance', true, 0.2), ros.readParam('navigate_tolerance', true, 0.2),
ros.readParam('navigate_global_tolerance', true, 1), ros.readParam('yaw_tolerance', true, 20),
ros.readParam('yaw_tolerance', true, 1),
ros.readParam('sleep_time', true, 0.2), ros.readParam('sleep_time', true, 0.2),
ros.readParam('confirm_run', true, true), ros.readParam('confirm_run', true, true),
]); ]);
@@ -211,7 +210,7 @@ function loadPrograms() {
updateChanged(); updateChanged();
}, function(err) { }, function(err) {
document.querySelector('.backend-fail').style.display = 'inline'; document.querySelector('.backend-fail').style.display = 'inline';
alert(`Error loading programs list.\n\nHave you enabled 'blocks' in clover.launch?`); alert(`Error loading programs list.\n\nHave you enabled clover_blocks in clover.launch?`);
runButton.disabled = true; runButton.disabled = true;
}) })
} }

View File

@@ -33,18 +33,6 @@ const NAVIGATE_WAIT = () => `\ndef navigate_wait(x=0, y=0, z=0, speed=0.5, frame
return return
rospy.sleep(${params.sleep_time})\n`; rospy.sleep(${params.sleep_time})\n`;
const NAVIGATE_GLOBAL_WAIT = () => `\ndef navigate_global_wait(lat, lon, z, speed=0.5):
res = navigate_global(lat=lat, lon=lon, z=z, yaw=float('inf'), speed=speed)
if not res.success:
raise Exception(res.message)
while not rospy.is_shutdown():
telem = get_telemetry(frame_id='navigate_target')
if math.sqrt(telem.x ** 2 + telem.y ** 2 + telem.z ** 2) < ${params.navigate_global_tolerance}:
return
rospy.sleep(${params.sleep_time})\n`;
const LAND_WAIT = () => `\ndef land_wait(): const LAND_WAIT = () => `\ndef land_wait():
land() land()
while get_telemetry().armed: while get_telemetry().armed:
@@ -80,9 +68,6 @@ function generateROSDefinitions() {
if (rosDefinitions.offboard) { if (rosDefinitions.offboard) {
code += `get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)\n`; code += `get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)\n`;
code += `navigate = rospy.ServiceProxy('navigate', srv.Navigate)\n`; code += `navigate = rospy.ServiceProxy('navigate', srv.Navigate)\n`;
if (rosDefinitions.navigateGlobal) {
code += `navigate_global = rospy.ServiceProxy('navigate_global', srv.NavigateGlobal)\n`;
}
if (rosDefinitions.setVelocity) { if (rosDefinitions.setVelocity) {
code += `set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)\n`; code += `set_velocity = rospy.ServiceProxy('set_velocity', srv.SetVelocity)\n`;
} }
@@ -109,10 +94,6 @@ function generateROSDefinitions() {
Blockly.Python.definitions_['import_math'] = 'import math'; Blockly.Python.definitions_['import_math'] = 'import math';
code += NAVIGATE_WAIT(); code += NAVIGATE_WAIT();
} }
if (rosDefinitions.navigateGlobalWait) {
Blockly.Python.definitions_['import_math'] = 'import math';
code += NAVIGATE_GLOBAL_WAIT();
}
if (rosDefinitions.landWait) { if (rosDefinitions.landWait) {
code += LAND_WAIT(); code += LAND_WAIT();
} }
@@ -180,48 +161,24 @@ Blockly.Python.navigate = function(block) {
let x = Blockly.Python.valueToCode(block, 'X', Blockly.Python.ORDER_NONE); let x = Blockly.Python.valueToCode(block, 'X', Blockly.Python.ORDER_NONE);
let y = Blockly.Python.valueToCode(block, 'Y', Blockly.Python.ORDER_NONE); let y = Blockly.Python.valueToCode(block, 'Y', Blockly.Python.ORDER_NONE);
let z = Blockly.Python.valueToCode(block, 'Z', Blockly.Python.ORDER_NONE); let z = Blockly.Python.valueToCode(block, 'Z', Blockly.Python.ORDER_NONE);
let lat = Blockly.Python.valueToCode(block, 'LAT', Blockly.Python.ORDER_NONE); let frameId = buildFrameId(block);
let lon = Blockly.Python.valueToCode(block, 'LON', Blockly.Python.ORDER_NONE);
let wait = block.getFieldValue('WAIT') == 'TRUE';
let frameId = block.getFieldValue('FRAME_ID');
let speed = Blockly.Python.valueToCode(block, 'SPEED', Blockly.Python.ORDER_NONE); let speed = Blockly.Python.valueToCode(block, 'SPEED', Blockly.Python.ORDER_NONE);
let params = [`x=${x}`, `y=${y}`, `z=${z}`, `frame_id=${frameId}`, `speed=${speed}`];
simpleOffboard(); simpleOffboard();
// global coordinates if (block.getFieldValue('WAIT') == 'TRUE') {
if (frameId.startsWith('GLOBAL')) { rosDefinitions.navigateWait = true;
rosDefinitions.navigateGlobal = true;
simpleOffboard(); simpleOffboard();
if (frameId == 'GLOBAL') { return `navigate_wait(${params.join(', ')})\n`;
z = `${z} + get_telemetry().alt - get_telemetry().z`;
}
if (wait) {
rosDefinitions.navigateGlobalWait = true;
simpleOffboard();
return `navigate_global_wait(lat=${lat}, lon=${lon}, z=${z}, speed=${speed})\n`;
} else {
return `navigate_global(lat=${lat}, lon=${lon}, z=${z}, yaw=float('inf'), speed=${speed})\n`;
}
} else { } else {
frameId = buildFrameId(block); if (frameId != 'body') {
let params = [`x=${x}`, `y=${y}`, `z=${z}`, `frame_id=${frameId}`, `speed=${speed}`]; params.push(`yaw=float('nan')`);
if (wait) {
rosDefinitions.navigateWait = true;
simpleOffboard();
return `navigate_wait(${params.join(', ')})\n`;
} else {
if (frameId != 'body') {
params.push(`yaw=float('nan')`);
}
return `navigate(${params.join(', ')})\n`;
} }
return `navigate(${params.join(', ')})\n`;
} }
} }
@@ -358,12 +315,6 @@ Blockly.Python.get_attitude = function(block) {
return [code, Blockly.Python.ORDER_FUNCTION_CALL]; return [code, Blockly.Python.ORDER_FUNCTION_CALL];
} }
Blockly.Python.global_position = function(block) {
simpleOffboard();
var code = `get_telemetry().${block.getFieldValue('FIELD').toLowerCase()}`;
return [code, Blockly.Python.ORDER_FUNCTION_CALL];
}
Blockly.Python.distance = function(block) { Blockly.Python.distance = function(block) {
rosDefinitions.distance = true; rosDefinitions.distance = true;
simpleOffboard(); simpleOffboard();

View File

@@ -14,7 +14,7 @@ You may provide additional parameters for `spawn_drone.launch` as well:
* `rangefinder` (*boolean*, default: *true*) - controls whether the drone will have a downward-facing laser rangefinder attached; * `rangefinder` (*boolean*, default: *true*) - controls whether the drone will have a downward-facing laser rangefinder attached;
* `led` (*boolean*, default: *true*) - controls whether the drone will have a programmable LED strip (requires plugins from `clover_simulation`); * `led` (*boolean*, default: *true*) - controls whether the drone will have a programmable LED strip (requires plugins from `clover_simulation`);
* `gps` (*boolean*, default: *true*) - controls whether the drone will have a simulated GPS attached (requires plugins from `sitl_gazebo`); * `gps` (*boolean*, default: *true*) - controls whether the drone will have a simulated GPS attached (requires plugins from `sitl_gazebo`);
* `maintain_camera_rate` (*boolean*, default: *false*) - slow down the simulation to maintain camera publishing rate (internally changes the camera plugin from `libgazebo_ros_camera.so` to `libthrottling_camera.so` from [`clover_simulation`](../clover_simulation/README.md#throttling-camera-plugin-throttling_camera)). * `maintain_camera_rate` (*boolean*, default: *false*) - slow down the simultion to maintain camera publishing rate (internally changes the camera plugin from `libgazebo_ros_camera.so` to `libthrottling_camera.so` from [`clover_simulation`](../clover_simulation/README.md#throttling-camera-plugin-throttling_camera)).
For example, in order to spawn a drone without a GPS module, you may use the following command: For example, in order to spawn a drone without a GPS module, you may use the following command:

View File

@@ -1,7 +1,7 @@
<launch> <launch>
<arg name="model" default="$(find clover_description)/urdf/clover/clover4.xacro"/> <arg name="model" default="$(find clover_description)/urdf/drones/clover4.xacro"/>
<param name="robot_description" command="xacro $(arg model)"/> <param name="robot_description" command="$(find xacro)/xacro.py $(arg model)"/>
<node name="rviz" pkg="rviz" type="rviz" required="true"/> <node name="rviz" pkg="rviz" type="rviz" required="true"/>
</launch> </launch>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<robot name="clover" xmlns:xacro="http://ros.org/wiki/xacro"> <robot name="clover_camera" xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:arg name="main_camera" default="true"/> <xacro:arg name="main_camera" default="true"/>
<xacro:arg name="rangefinder" default="true"/> <xacro:arg name="rangefinder" default="true"/>
@@ -8,10 +8,10 @@
<xacro:arg name="maintain_camera_rate" default="false"/> <xacro:arg name="maintain_camera_rate" default="false"/>
<xacro:arg name="use_clover_physics" default="false"/> <xacro:arg name="use_clover_physics" default="false"/>
<xacro:include filename="clover4_base.xacro" /> <xacro:include filename="$(find clover_description)/urdf/clover/clover4_base.xacro" />
<xacro:include filename="../sensors/rpi_cam.urdf.xacro"/> <xacro:include filename="$(find clover_description)/urdf/sensors/rpi_cam.urdf.xacro"/>
<xacro:include filename="../sensors/distance_sensor.urdf.xacro"/> <xacro:include filename="$(find clover_description)/urdf/sensors/distance_sensor.urdf.xacro"/>
<xacro:include filename="../leds/led_strip.xacro"/> <xacro:include filename="$(find clover_description)/urdf/leds/led_strip.xacro"/>
<!-- Create camera plugin --> <!-- Create camera plugin -->
<xacro:if value="$(arg main_camera)"> <xacro:if value="$(arg main_camera)">
@@ -36,17 +36,11 @@
</xacro:if> </xacro:if>
<xacro:if value="$(arg gps)"> <xacro:if value="$(arg gps)">
<gazebo> <!-- Instantiate gps plugin. -->
<include> <xacro:gps_plugin_macro
<uri>model://gps</uri> namespace="${namespace}"
<pose>0.1 0 0 0 0 0</pose> gps_noise="true"
<name>gps0</name> />
</include>
<joint name='gps0_joint' type='fixed'>
<child>gps0::link</child>
<parent>base_link</parent>
</joint>
</gazebo>
</xacro:if> </xacro:if>
</robot> </robot>

View File

@@ -1,15 +1,40 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<robot name="clover" xmlns:xacro="http://ros.org/wiki/xacro"> <robot name="clover" xmlns:xacro="http://ros.org/wiki/xacro">
<!-- Properties that can be assigned at build time as arguments.
Is there a reason not to make all properties arguments?
-->
<xacro:arg name='name' default='iris' />
<xacro:arg name='mavlink_addr' default='INADDR_ANY' />
<xacro:arg name='mavlink_udp_port' default='14560' />
<xacro:arg name='mavlink_tcp_port' default='4560' />
<xacro:arg name='serial_enabled' default='false' />
<xacro:arg name='serial_device' default='/dev/ttyACM0' />
<xacro:arg name='baudrate' default='921600' />
<xacro:arg name='qgc_addr' default='INADDR_ANY' />
<xacro:arg name='qgc_udp_port' default='14550' />
<xacro:arg name='sdk_addr' default='INADDR_ANY' />
<xacro:arg name='sdk_udp_port' default='14540' />
<xacro:arg name='hil_mode' default='false' />
<xacro:arg name='hil_state_level' default='false' />
<xacro:arg name='send_vision_estimation' default='false' />
<xacro:arg name='send_odometry' default='false' />
<xacro:arg name='enable_lockstep' default='true' />
<xacro:arg name='use_tcp' default='true' />
<xacro:arg name='vehicle_is_tailsitter' default='false' />
<xacro:arg name='visual_material' default='DarkGrey' />
<xacro:arg name='enable_mavlink_interface' default='true' />
<xacro:arg name='enable_wind' default='false' /> <xacro:arg name='enable_wind' default='false' />
<!-- The following causes segfault with multiple vehicles if defaults to true!!! -->
<xacro:arg name='enable_ground_truth' default='false' />
<xacro:arg name='enable_logging' default='false' /> <xacro:arg name='enable_logging' default='false' />
<xacro:arg name='log_file' default='clover' /> <xacro:arg name='log_file' default='iris' />
<!-- macros for gazebo plugins, sensors --> <!-- macros for gazebo plugins, sensors -->
<xacro:include filename="../component_snippets.urdf.xacro" /> <xacro:include filename="$(find clover_description)/urdf/component_snippets.urdf.xacro" />
<!-- Instantiate iris "mechanics" --> <!-- Instantiate iris "mechanics" -->
<xacro:include filename="clover4_physics.xacro" /> <xacro:include filename="$(find clover_description)/urdf/clover/clover4_physics.xacro" />
<xacro:if value="$(arg enable_wind)"> <xacro:if value="$(arg enable_wind)">
<xacro:wind_plugin_macro <xacro:wind_plugin_macro
@@ -24,8 +49,126 @@
/> />
</xacro:if> </xacro:if>
<!-- Gazebo plugins --> <!-- Instantiate magnetometer plugin. -->
<xacro:include filename="$(find clover_description)/urdf/clover/clover4_gazebo.xacro" /> <xacro:magnetometer_plugin_macro
namespace="${namespace}"
pub_rate="20"
noise_density="0.0004"
random_walk="0.0000064"
bias_correlation_time="600"
mag_topic="/mag"
>
</xacro:magnetometer_plugin_macro>
<!-- Instantiate barometer plugin. -->
<xacro:barometer_plugin_macro
namespace="${namespace}"
pub_rate="10"
baro_topic="/baro"
>
</xacro:barometer_plugin_macro>
<xacro:if value="$(arg enable_mavlink_interface)">
<!-- Instantiate mavlink telemetry interface. -->
<xacro:mavlink_interface_macro
namespace="${namespace}"
imu_sub_topic="/imu"
gps_sub_topic="/gps"
mag_sub_topic="/mag"
baro_sub_topic="/baro"
mavlink_addr="$(arg mavlink_addr)"
mavlink_udp_port="$(arg mavlink_udp_port)"
mavlink_tcp_port="$(arg mavlink_tcp_port)"
serial_enabled="$(arg serial_enabled)"
serial_device="$(arg serial_device)"
baudrate="$(arg baudrate)"
qgc_addr="$(arg qgc_addr)"
qgc_udp_port="$(arg qgc_udp_port)"
sdk_addr="$(arg sdk_addr)"
sdk_udp_port="$(arg sdk_udp_port)"
hil_mode="$(arg hil_mode)"
hil_state_level="$(arg hil_state_level)"
vehicle_is_tailsitter="$(arg vehicle_is_tailsitter)"
send_vision_estimation="$(arg send_vision_estimation)"
send_odometry="$(arg send_odometry)"
enable_lockstep="$(arg enable_lockstep)"
use_tcp="$(arg use_tcp)"
>
</xacro:mavlink_interface_macro>
</xacro:if>
<!-- Mount an ADIS16448 IMU. -->
<xacro:imu_plugin_macro
namespace="${namespace}"
imu_suffix=""
parent_link="base_link"
imu_topic="/imu"
mass_imu_sensor="0.015"
gyroscope_noise_density="0.0003394"
gyroscopoe_random_walk="0.000038785"
gyroscope_bias_correlation_time="1000.0"
gyroscope_turn_on_bias_sigma="0.0087"
accelerometer_noise_density="0.004"
accelerometer_random_walk="0.006"
accelerometer_bias_correlation_time="300.0"
accelerometer_turn_on_bias_sigma="0.1960"
>
<inertia ixx="0.00001" ixy="0.0" ixz="0.0" iyy="0.00001" iyz="0.0" izz="0.00001" />
<origin xyz="0 0 0" rpy="0 0 0" />
</xacro:imu_plugin_macro>
<xacro:if value="$(arg enable_ground_truth)">
<!-- Mount an IMU providing ground truth. -->
<xacro:imu_plugin_macro
namespace="${namespace}"
imu_suffix="gt"
parent_link="base_link"
imu_topic="ground_truth/imu"
mass_imu_sensor="0.00001"
gyroscope_noise_density="0.0"
gyroscopoe_random_walk="0.0"
gyroscope_bias_correlation_time="1000.0"
gyroscope_turn_on_bias_sigma="0.0"
accelerometer_noise_density="0.0"
accelerometer_random_walk="0.0"
accelerometer_bias_correlation_time="300.0"
accelerometer_turn_on_bias_sigma="0.0"
>
<inertia ixx="0.00001" ixy="0.0" ixz="0.0" iyy="0.00001" iyz="0.0" izz="0.00001" />
<origin xyz="0 0 0" rpy="0 0 0" />
</xacro:imu_plugin_macro>
<!-- Mount a generic odometry sensor providing ground truth. -->
<xacro:odometry_plugin_macro
namespace="${namespace}/ground_truth"
odometry_sensor_suffix="gt"
parent_link="base_link"
pose_topic="pose"
pose_with_covariance_topic="pose_with_covariance"
position_topic="position"
transform_topic="transform"
odometry_topic="odometry"
parent_frame_id="world"
mass_odometry_sensor="0.00001"
measurement_divisor="1"
measurement_delay="0"
unknown_delay="0.0"
noise_normal_position="0 0 0"
noise_normal_quaternion="0 0 0"
noise_normal_linear_velocity="0 0 0"
noise_normal_angular_velocity="0 0 0"
noise_uniform_position="0 0 0"
noise_uniform_quaternion="0 0 0"
noise_uniform_linear_velocity="0 0 0"
noise_uniform_angular_velocity="0 0 0"
enable_odometry_map="false"
odometry_map=""
image_scale=""
>
<inertia ixx="0.00001" ixy="0.0" ixz="0.0" iyy="0.00001" iyz="0.0" izz="0.00001" /> <!-- [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] -->
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0" />
</xacro:odometry_plugin_macro>
</xacro:if>
<xacro:if value="$(arg enable_logging)"> <xacro:if value="$(arg enable_logging)">
<!-- Instantiate a logger --> <!-- Instantiate a logger -->

View File

@@ -1,183 +0,0 @@
<?xml version="1.0"?>
<!-- Reference: https://github.com/PX4/PX4-SITL_gazebo/blob/7505aee97d2d3112fb2bd95198946345c6fa0b07/models/iris/iris.sdf.jinja#L430 -->
<robot xmlns:xacro="http://ros.org/wiki/xacro">
<!-- IMU link -->
<link name="/imu_link">
<inertial>
<inertia ixx="0.00001" ixy="0.0" ixz="0.0" iyy="0.00001" iyz="0.0" izz="0.00001"/>
<mass value="0.015"/>
<!-- [kg] -->
<origin rpy="0 0 0" xyz="0 0 0"/>
</inertial>
</link>
<!-- IMU joint -->
<joint name="/imu_joint" type="fixed">
<origin rpy="0 0 0" xyz="0 0 0"/>
<parent link="base_link"/>
<child link="/imu_link"/>
<!-- <limit upper="0" lower="0" effort="0" velocity="0" /> -->
</joint>
<gazebo reference="/imu_joint">
<disableFixedJointLumping>true</disableFixedJointLumping>
<preserveFixedJoint>true</preserveFixedJoint>
</gazebo>
<gazebo>
<plugin name='groundtruth_plugin' filename='libgazebo_groundtruth_plugin.so'>
<robotNamespace/>
</plugin>
<plugin name='magnetometer_plugin' filename='libgazebo_magnetometer_plugin.so'>
<robotNamespace/>
<pubRate>100</pubRate>
<noiseDensity>0.0004</noiseDensity>
<randomWalk>6.4e-06</randomWalk>
<biasCorrelationTime>600</biasCorrelationTime>
<magTopic>/mag</magTopic>
</plugin>
<plugin name='barometer_plugin' filename='libgazebo_barometer_plugin.so'>
<robotNamespace/>
<pubRate>50</pubRate>
<baroTopic>/baro</baroTopic>
<baroDriftPaPerSec>0</baroDriftPaPerSec>
</plugin>
<plugin name='mavlink_interface' filename='libgazebo_mavlink_interface.so'>
<robotNamespace/>
<imuSubTopic>/imu</imuSubTopic>
<magSubTopic>/mag</magSubTopic>
<baroSubTopic>/baro</baroSubTopic>
<mavlink_addr>INADDR_ANY</mavlink_addr>
<mavlink_tcp_port>4560</mavlink_tcp_port>
<mavlink_udp_port>14560</mavlink_udp_port>
<serialEnabled>false</serialEnabled>
<serialDevice>/dev/ttyACM0</serialDevice>
<baudRate>921600</baudRate>
<qgc_addr>INADDR_ANY</qgc_addr>
<qgc_udp_port>14550</qgc_udp_port>
<sdk_addr>INADDR_ANY</sdk_addr>
<sdk_udp_port>14540</sdk_udp_port>
<hil_mode>false</hil_mode>
<hil_state_level>0</hil_state_level>
<send_vision_estimation>0</send_vision_estimation>
<send_odometry>1</send_odometry>
<enable_lockstep>1</enable_lockstep>
<use_tcp>1</use_tcp>
<motorSpeedCommandPubTopic>/gazebo/command/motor_speed</motorSpeedCommandPubTopic>
<control_channels>
<channel name='rotor1'>
<input_index>0</input_index>
<input_offset>0</input_offset>
<input_scaling>1000</input_scaling>
<zero_position_disarmed>0</zero_position_disarmed>
<zero_position_armed>100</zero_position_armed>
<joint_control_type>velocity</joint_control_type>
</channel>
<channel name='rotor2'>
<input_index>1</input_index>
<input_offset>0</input_offset>
<input_scaling>1000</input_scaling>
<zero_position_disarmed>0</zero_position_disarmed>
<zero_position_armed>100</zero_position_armed>
<joint_control_type>velocity</joint_control_type>
</channel>
<channel name='rotor3'>
<input_index>2</input_index>
<input_offset>0</input_offset>
<input_scaling>1000</input_scaling>
<zero_position_disarmed>0</zero_position_disarmed>
<zero_position_armed>100</zero_position_armed>
<joint_control_type>velocity</joint_control_type>
</channel>
<channel name='rotor4'>
<input_index>3</input_index>
<input_offset>0</input_offset>
<input_scaling>1000</input_scaling>
<zero_position_disarmed>0</zero_position_disarmed>
<zero_position_armed>100</zero_position_armed>
<joint_control_type>velocity</joint_control_type>
</channel>
<channel name='rotor5'>
<input_index>4</input_index>
<input_offset>1</input_offset>
<input_scaling>324.6</input_scaling>
<zero_position_disarmed>0</zero_position_disarmed>
<zero_position_armed>0</zero_position_armed>
<joint_control_type>velocity</joint_control_type>
<joint_control_pid>
<p>0.1</p>
<i>0</i>
<d>0</d>
<iMax>0.0</iMax>
<iMin>0.0</iMin>
<cmdMax>2</cmdMax>
<cmdMin>-2</cmdMin>
</joint_control_pid>
<joint_name>zephyr_delta_wing::propeller_joint</joint_name>
</channel>
<channel name='rotor6'>
<input_index>5</input_index>
<input_offset>0</input_offset>
<input_scaling>0.524</input_scaling>
<zero_position_disarmed>0</zero_position_disarmed>
<zero_position_armed>0</zero_position_armed>
<joint_control_type>position</joint_control_type>
<joint_name>zephyr_delta_wing::flap_left_joint</joint_name>
<joint_control_pid>
<p>10.0</p>
<i>0</i>
<d>0</d>
<iMax>0</iMax>
<iMin>0</iMin>
<cmdMax>20</cmdMax>
<cmdMin>-20</cmdMin>
</joint_control_pid>
</channel>
<channel name='rotor7'>
<input_index>6</input_index>
<input_offset>0</input_offset>
<input_scaling>0.524</input_scaling>
<zero_position_disarmed>0</zero_position_disarmed>
<zero_position_armed>0</zero_position_armed>
<joint_control_type>position</joint_control_type>
<joint_name>zephyr_delta_wing::flap_right_joint</joint_name>
<joint_control_pid>
<p>10.0</p>
<i>0</i>
<d>0</d>
<iMax>0</iMax>
<iMin>0</iMin>
<cmdMax>20</cmdMax>
<cmdMin>-20</cmdMin>
</joint_control_pid>
</channel>
<channel name='rotor8'>
<input_index>7</input_index>
<input_offset>0</input_offset>
<input_scaling>0.524</input_scaling>
<zero_position_disarmed>0</zero_position_disarmed>
<zero_position_armed>0</zero_position_armed>
<joint_control_type>position</joint_control_type>
</channel>
</control_channels>
</plugin>
<static>0</static>
<plugin name='rotors_gazebo_imu_plugin' filename='libgazebo_imu_plugin.so'>
<robotNamespace/>
<linkName>/imu_link</linkName>
<imuTopic>/imu</imuTopic>
<gyroscopeNoiseDensity>0.00018665</gyroscopeNoiseDensity>
<gyroscopeRandomWalk>3.8785e-05</gyroscopeRandomWalk>
<gyroscopeBiasCorrelationTime>1000.0</gyroscopeBiasCorrelationTime>
<gyroscopeTurnOnBiasSigma>0.0087</gyroscopeTurnOnBiasSigma>
<accelerometerNoiseDensity>0.00186</accelerometerNoiseDensity>
<accelerometerRandomWalk>0.006</accelerometerRandomWalk>
<accelerometerBiasCorrelationTime>300.0</accelerometerBiasCorrelationTime>
<accelerometerTurnOnBiasSigma>0.196</accelerometerTurnOnBiasSigma>
</plugin>
</gazebo>
</robot>

View File

@@ -6,7 +6,7 @@
--> -->
<robot name="clover" xmlns:xacro="http://ros.org/wiki/xacro"> <robot name="iris" xmlns:xacro="http://ros.org/wiki/xacro">
<!-- Properties --> <!-- Properties -->
<xacro:property name="namespace" value="" /> <xacro:property name="namespace" value="" />
<xacro:property name="rotor_velocity_slowdown_sim" value="10" /> <xacro:property name="rotor_velocity_slowdown_sim" value="10" />
@@ -84,7 +84,7 @@
</xacro:unless> </xacro:unless>
<!-- Included URDF Files --> <!-- Included URDF Files -->
<xacro:include filename="clover4_macros.xacro" /> <xacro:include filename="$(find clover_description)/urdf/clover/clover4_macros.xacro" />
<!-- Instantiate multirotor_base_macro once --> <!-- Instantiate multirotor_base_macro once -->
<xacro:clover4_base_macro <xacro:clover4_base_macro

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/xacro"> <robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:include filename="../camera_sensor.urdf.xacro"/> <xacro:include filename="$(find clover_description)/urdf/camera_sensor.urdf.xacro"/>
<xacro:macro name="distance_sensor" params="name:=lidar_vl53l1x parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 range_min:=0.01 range_max:=4.0 resolution:=0.01 rate:=10"> <xacro:macro name="distance_sensor" params="name:=lidar_vl53l1x parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 range_min:=0.01 range_max:=4.0 resolution:=0.01 rate:=10">
<joint name="${name}_joint" type="fixed"> <joint name="${name}_joint" type="fixed">

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<robot name="pinhole_camera" xmlns:xacro="http://ros.org/wiki/xacro"> <robot name="pinhole_camera" xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:include filename="../camera_sensor.urdf.xacro"/> <xacro:include filename="$(find clover_description)/urdf/camera_sensor.urdf.xacro"/>
<xacro:camera_sensor name="main_camera" width="640" height="480" rate="30" horizontal_fov="${120.0*pi/180.0}"/> <xacro:camera_sensor name="main_camera" width="640" height="480" rate="30" horizontal_fov="${120.0*pi/180.0}"/>
</robot> </robot>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/xacro"> <robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:include filename="../camera_sensor.urdf.xacro"/> <xacro:include filename="$(find clover_description)/urdf/camera_sensor.urdf.xacro"/>
<xacro:macro name="rpi_cam" params="name:=rpi_cam parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 width:=320 height:=240 rate:=40 do_throttling:=false"> <xacro:macro name="rpi_cam" params="name:=rpi_cam parent x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0 width:=320 height:=240 rate:=40 do_throttling:=false">
<joint name="${name}_joint" type="fixed"> <joint name="${name}_joint" type="fixed">

View File

@@ -55,7 +55,7 @@ A visual Gazebo plugin is used for the LED strip. An example of the plugin usage
The plugin accepts the following parameters during instantiation: The plugin accepts the following parameters during instantiation:
* `robotNamespace` (*string*, default: "") - a ROS namespace for the plugin; * `robotNamespace` (*string*, default: "") - a ROS namespace for the plugin;
* `ledCount` (*integer*, required) - total number of LEDs in a strip. * `ledCount` (*integer*, required) - total numer of LEDs in a strip.
The plugin will provide the following service: The plugin will provide the following service:

View File

@@ -1,36 +0,0 @@
#!/bin/sh
#
# @name COEX Clover Simulator
#
# @type Quadrotor X
#
. ${R}etc/init.d-posix/airframes/10016_iris # base on iris
param set ATT_W_EXT_HDG 0.5
param set ATT_EXT_HDG_M 1
param set COM_DISARM_LAND 1.0
param set LPE_FLW_SCALE 1.0
param set LPE_FLW_R 0.2
param set LPE_FLW_RR 0.0
param set LPE_FLW_QMIN 10
param set LPE_VIS_DELAY 0.0
param set LPE_VIS_Z 0.1
param set LPE_FUSION 86
param set SENS_FLOW_ROT 0
param set SENS_FLOW_MINHGT 0.01
param set SENS_FLOW_MAXHGT 4.0
param set SENS_FLOW_MAXR 10.0
param set EKF2_AID_MASK 27 # gps + flow + vis pos + vis yaw
param set EKF2_OF_DELAY 0
param set EKF2_OF_QMIN 10
param set EKF2_OF_N_MIN 0.05
param set EKF2_OF_N_MAX 0.2
param set EKF2_HGT_MODE 2
param set EKF2_EVA_NOISE 0.1
param set EKF2_EVP_NOISE 0.1
param set EKF2_EV_DELAY 0

View File

@@ -8,13 +8,12 @@
<arg name="led" default="true"/> <!-- Simulated LED strip, ws281x-like --> <arg name="led" default="true"/> <!-- Simulated LED strip, ws281x-like -->
<arg name="gps" default="false"/> <!--Simulated GPS module --> <arg name="gps" default="false"/> <!--Simulated GPS module -->
<arg name="use_clover_physics" default="false"/> <!-- Use inertial parameters from CAD models --> <arg name="use_clover_physics" default="false"/> <!-- Use inertial parameters from CAD models -->
<arg name="gui" default="true"/> <!-- Run Gazebo with GUI -->
<!-- Gazebo instance --> <!-- Gazebo instance -->
<include file="$(find gazebo_ros)/launch/empty_world.launch" if="$(eval type == 'gazebo')"> <include file="$(find gazebo_ros)/launch/empty_world.launch" if="$(eval type == 'gazebo')">
<!-- Workaround for crashes in VMware --> <!-- Workaround for crashes in VMware -->
<env name="SVGA_VGPU10" value="0"/> <env name="SVGA_VGPU10" value="0"/>
<arg name="gui" value="$(arg gui)"/> <arg name="gui" value="true"/>
<arg name="world_name" value="$(find clover_simulation)/resources/worlds/clover_aruco.world"/> <arg name="world_name" value="$(find clover_simulation)/resources/worlds/clover_aruco.world"/>
<arg name="verbose" value="true"/> <arg name="verbose" value="true"/>
</include> </include>
@@ -34,9 +33,7 @@
<arg name="use_clover_physics" value="$(arg use_clover_physics)"/> <arg name="use_clover_physics" value="$(arg use_clover_physics)"/>
</include> </include>
<node name="jmavsim" pkg="px4" type="jmavsim_run.sh" output="screen" if="$(eval type == 'jmavsim')"> <node name="jmavsim" pkg="px4" type="jmavsim_run.sh" output="screen" if="$(eval type == 'jmavsim')"/>
<env name="HEADLESS" value="1" if="$(eval not gui)"/>
</node>
<param name="use_sim_time" value="false" if="$(eval type == 'jmavsim')"/> <param name="use_sim_time" value="false" if="$(eval type == 'jmavsim')"/>
@@ -46,10 +43,10 @@
<arg name="fcu_conn" value="sitl"/> <arg name="fcu_conn" value="sitl"/>
<arg name="fcu_ip" value="127.0.0.1"/> <arg name="fcu_ip" value="127.0.0.1"/>
<arg name="gcs_bridge" value=""/> <arg name="gcs_bridge" value=""/>
<arg name="rc" default="false"/>
<arg name="web_video_server" default="false" if="$(eval type == 'jmavsim')"/> <arg name="web_video_server" default="false" if="$(eval type == 'jmavsim')"/>
<arg name="main_camera" default="false" if="$(eval type == 'jmavsim')"/> <arg name="main_camera" default="false" if="$(eval type == 'jmavsim')"/>
<arg name="aruco" default="false" if="$(eval type == 'jmavsim')"/> <arg name="aruco" default="false" if="$(eval type == 'jmavsim')"/>
<arg name="optical_flow" default="false" if="$(eval type == 'jmavsim')"/>
<arg name="led" default="false" if="$(eval type == 'jmavsim')"/> <arg name="led" default="false" if="$(eval type == 'jmavsim')"/>
</include> </include>
</launch> </launch>

View File

@@ -1,33 +0,0 @@
<?xml version="1.0"?>
<sdf version="1.5">
<model name="camera">
<static>true</static>
<link name='camera_link'>
<pose>0 0 0 0 0 0</pose>
<sensor name='camera' type='camera'>
<camera>
<horizontal_fov>2.0944</horizontal_fov>
<image>
<format>B8G8R8</format>
<width>800</width>
<height>600</height>
</image>
<clip>
<near>0.02</near>
<far>300</far>
</clip>
</camera>
<always_on>1</always_on>
<update_rate>20</update_rate>
<visualize>1</visualize>
<plugin name='camera_plugin' filename='libgazebo_ros_camera.so'>
<alwaysOn>1</alwaysOn>
<imageTopicName>image_raw</imageTopicName>
<cameraTopicName>camera_info</cameraTopicName>
<cameraName>camera</cameraName>
<frameName>/camera</frameName>
</plugin>
</sensor>
</link>
</model>
</sdf>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0"?>
<model>
<name>Camera</name>
<version>1.0</version>
<sdf version="1.5">camera.sdf</sdf>
<author>
<name>Oleg Kalachev</name>
<email>okalachev@gmail.com</email>
</author>
<description>
External camera
</description>
</model>

View File

@@ -41,4 +41,4 @@ def save_world(world, file):
''' '''
Save the world to file-like object Save the world to file-like object
''' '''
return world.write(file, encoding='unicode') return world.write(file)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View File

@@ -4,52 +4,17 @@ The fourth generation mobile communication is a convenient tool for transmitting
> **Hint** To transfer any data from your drone to the ground control station (e.g., QGroundControl) and back, you need to set up your own VPN network. > **Hint** To transfer any data from your drone to the ground control station (e.g., QGroundControl) and back, you need to set up your own VPN network.
## Connecting 4G modem to Raspberry Pi ## Connecting Raspberry Pi to the VPN
Connect a 4G modem with SIM card to the USB port of your Raspberry Pi. Connect a 4G modem with SIM card to the USB port of your Raspberry Pi.
When connected, some modems recognize in the system as a network card, without any additional settings. Note that when connected, the modem must be recognized in the system as a network card, without any additional settings.
4G modem example: *USB 4G Huawei E3372H* 4G modem example: *USB 4G Huawei E3372H*
<img src="../assets/4g/huawei.jpg" width=300 class="zoom center border"> <img src="../assets/4g/huawei.jpg" width=300 class="zoom center border">
But some other popular modems, for instance *Quectel EP06*, do not start the internet connection automatic. In this case you should use utilities like `qmi-network` and `udhcpc`. To install this utilities enter the appropriate command line: > **Hint** We suggest using the UDP transfer protocol to control the drone, which provides less delay, at the cost of no guarantee of receiving the package, which is very important during the flight.
```bash
sudo apt install libqmi-utils udhcpc
```
Next to start the internet connection proceed following:
```bash
sudo ip link set wwan0 down
echo 'Y' | sudo tee /sys/class/net/wwan0/qmi/raw_ip
sudo ip link set wwan0 up
sudo qmi-network /dev/cdc-wdm0 start
sudo udhcpc -q -f -i wwan0
```
Reed more about it in [this article](https://docs.sixfab.com/page/setting-up-a-data-connection-over-qmi-interface-using-libqmi).
<!-- markdownlint-disable MD031 -->
> **Hint** To check the internet connection enter the appropriate command line:
> ```bash
> ping -I wwan0 -c 5 8.8.8.8
> ```
<!-- -->
> **Hint** To check the speed of the internet connection you could use the `speedtest` utility:
> ```bash
> sudo apt install speedtest-cli
> speedtest
> ```
<!-- markdownlint-enable MD031 -->
## Connecting Raspberry Pi to the VPN
Create the VPN network keys to connect Raspberry Pi and the ground station. Create the VPN network keys to connect Raspberry Pi and the ground station.
@@ -75,14 +40,8 @@ If everything is done correctly, every time the system restarts, the service cli
> **Hint** Before starting work, do not forget to set up and enable VPN connection on your PC. > **Hint** Before starting work, do not forget to set up and enable VPN connection on your PC.
<!-- -->
> **Hint** Alternatively we recommend to use the [ZeroTier](zerotire_vpn.md) VPN-service.
## Copter control via QGroundControl ## Copter control via QGroundControl
> **Hint** We suggest using the UDP transfer protocol to control the drone, which provides less delay, at the cost of no guarantee of receiving the package, which is very important during the flight.
Make sure your copter and ground station are connected to your network. Make sure your copter and ground station are connected to your network.
To do this, you can use the command `ip addr`. The result will be a numbered list of the active networks enabled on your device. Note the connection with the prefix *tun* and the IP address you specify; if it is present in your list, your copter is connected to the network. To do this, you can use the command `ip addr`. The result will be a numbered list of the active networks enabled on your device. Note the connection with the prefix *tun* and the IP address you specify; if it is present in your list, your copter is connected to the network.
@@ -123,8 +82,7 @@ The drone is ready to fly!
You can stream video from almost any camera connected to your Raspberry Pi. You will need to install or [build](https://github.com/sfalexrog/gst-rtsp-launch) the *gst-rtsp-launch* package: You can stream video from almost any camera connected to your Raspberry Pi. You will need to install or [build](https://github.com/sfalexrog/gst-rtsp-launch) the *gst-rtsp-launch* package:
```bash ```bash
sudo apt update sudo apt-get install gst-rtsp-launch
sudo apt install gst-rtsp-launch
``` ```
To start the transfer of images, you must enter the appropriate command line: To start the transfer of images, you must enter the appropriate command line:
@@ -132,15 +90,12 @@ To start the transfer of images, you must enter the appropriate command line:
<a id="command_line"></a> <a id="command_line"></a>
```bash ```bash
gst-rtsp-launch "( v4l2src device=/dev/video0 ! video/x-raw,framerate=30/1,width=320,height=240 ! videoconvert ! v4l2h264enc output-io-mode=4 extra-controls=\"encode,frame_level_rate_control_enable=1,h264_profile=4,h264_level=13,video_bitrate=300000,h264_i_frame_period=5;\" ! rtph264pay name=pay0 pt=96 )" gst-rtsp-launch "( v4l2src device=/dev/video0 ! video/x-raw,framerate=30/1,width=320,height=240 ! v4l2h264enc output-io-mode=4 extra-controls=\"encode,frame_level_rate_control_enable=1,h264_profile=4,h264_level=13,video_bitrate=300000,h264_i_frame_period=5;\" ! rtph264pay name=pay0 pt=96 )"
``` ```
This command line contains the parameters of the video stream, such as the source video device, framerate, image height/width, encoding, etc. You can see more examples [in the `gst-rtsp-launch` repository](https://github.com/sfalexrog/gst-rtsp-launch/blob/master/README.md). This command line contains the parameters of the video stream, such as the source video device, framerate, image height/width, encoding, etc. You can see more examples [in the `gst-rtsp-launch` repository](https://github.com/sfalexrog/gst-rtsp-launch/blob/master/README.md).
> **Info** A Raspberry Pi camera device `/dev/video0` could be used by `clover` systemd service. In this case `gst-rtsp-launch` will not get an access to the device. For stop the `clover` run command `sudo systemctl stop clover`. Make sure the stream is received and shown in QGroundControl.
> Also you could stream from a USB-camera, for this change the source video device to `/dev/video1`.
Make sure the stream by the address `rtsp://192.168.11.1:8554/video` (the IP-address of your Raspberry Pi could be different) is received and shown in QGroundControl.
<img src="../assets/4g/video_stream.png" width=300 class="zoom center border"> <img src="../assets/4g/video_stream.png" width=300 class="zoom center border">
@@ -158,7 +113,7 @@ In order to run the file, you have to mark it as executable.
chmod a+x script_name.sh chmod a+x script_name.sh
``` ```
You can use systemd to launch this script every time on system startup. Create the `qgc_video.service` file in the `/etc/systemd/system` directory: You can use systemd to launch this script every time on system startup. Create the *qgc_video.service* file in the */etc/systemd/system* directory:
```bash ```bash
sudo nano /etc/systemd/system/qgc_video.service sudo nano /etc/systemd/system/qgc_video.service

View File

@@ -34,7 +34,7 @@
* [Marker detection](aruco_marker.md) * [Marker detection](aruco_marker.md)
* [Map-based navigation](aruco_map.md) * [Map-based navigation](aruco_map.md)
* [Optical Flow](optical_flow.md) * [Optical Flow](optical_flow.md)
* [Autonomous flight (OFFBOARD)](simple_offboard.md) * [Simple OFFBOARD](simple_offboard.md)
* [Coordinate systems (frames)](frames.md) * [Coordinate systems (frames)](frames.md)
* [Code snippets](snippets.md) * [Code snippets](snippets.md)
* [Interfacing with a laser rangefinder](laser.md) * [Interfacing with a laser rangefinder](laser.md)

View File

@@ -116,7 +116,7 @@ while (GPS_serial.available() && new_line_found == 0) {
// If the received byte does not equal a $ character, increase the message_counter variable // If the received byte does not equal a $ character, increase the message_counter variable
else if (message_counter <= 99) else if (message_counter <= 99)
message_counter++; message_counter++;
// Write the new received byte to the new position in the incoming_message array // Write the new received byte to the new position in the incoming_message array
incoming_message[message_counter] = read_serial_byte; incoming_message[message_counter] = read_serial_byte;
@@ -163,7 +163,7 @@ if (new_line_found == 1) {
lat_gps_actual /= 10; lat_gps_actual /= 10;
// Filter minutes for the GGA line multiplied by 10 // Filter minutes for the GGA line multiplied by 10
lon_gps_actual = ((int)incoming_message[33] - 48) * (long)10000000; lon_gps_actual = ((int)incoming_message[33] - 48) * (long)10000000;
lon_gps_actual += ((int)incoming_message[34] - 48) * (long)1000000; lon_gps_actual += ((int)incoming_message[34] - 48) * (long)1000000;
lon_gps_actual += ((int)incoming_message[36] - 48) * (long)100000; lon_gps_actual += ((int)incoming_message[36] - 48) * (long)100000;
lon_gps_actual += ((int)incoming_message[37] - 48) * (long)10000; lon_gps_actual += ((int)incoming_message[37] - 48) * (long)10000;
@@ -192,7 +192,7 @@ if (new_line_found == 1) {
else else
// When flying west of the prime meridian the longiude_east variable will be set to 0 // When flying west of the prime meridian the longiude_east variable will be set to 0
longiude_east = 0; longiude_east = 0;
// Filter the number of satillites from the GGA line // Filter the number of satillites from the GGA line
number_used_sats = ((int)incoming_message[46] - 48) * (long)10; number_used_sats = ((int)incoming_message[46] - 48) * (long)10;
number_used_sats += (int)incoming_message[47] - 48; number_used_sats += (int)incoming_message[47] - 48;
@@ -255,9 +255,9 @@ In the future, this will be used for the smooth drone's acceleration and deceler
### 1.5. Waypoint stabilization {#waypoint-stabilization} ### 1.5. Waypoint stabilization {#waypoint-stabilization}
```cpp ```cpp
if (waypoint_set == 1) { if (waypoint_set == 1) {
//If the waypoints are stored //If the waypoints are stored
// Adjust l_lat_waypoint // Adjust l_lat_waypoint
if (l_lat_gps_float_adjust > 1) { if (l_lat_gps_float_adjust > 1) {
l_lat_waypoint++; l_lat_waypoint++;
@@ -296,7 +296,7 @@ if (waypoint_set == 1) {
gps_lon_rotating_mem[gps_rotating_mem_location] = gps_lon_error - gps_lon_error_previous; gps_lon_rotating_mem[gps_rotating_mem_location] = gps_lon_error - gps_lon_error_previous;
// Add the new value to the long term avarage value // Add the new value to the long term avarage value
gps_lon_total_avarage += gps_lon_rotating_mem[gps_rotating_mem_location]; gps_lon_total_avarage += gps_lon_rotating_mem[gps_rotating_mem_location];
// Increase the rotating memory location // Increase the rotating memory location
gps_rotating_mem_location++; gps_rotating_mem_location++;
if (gps_rotating_mem_location == 35) if (gps_rotating_mem_location == 35)
@@ -613,7 +613,7 @@ void speed_handler(void) {
// Convert acceleration to G // Convert acceleration to G
speed /= 4096.0; speed /= 4096.0;
// Convert to m/s^2 // Convert to m/s^2
speed *= 9.81; speed *= 9.81;
// Multiply by dt to get instant speed in m/ms // Multiply by dt to get instant speed in m/ms
speed *= (millis() - speed_loop_timer); speed *= (millis() - speed_loop_timer);
@@ -621,10 +621,10 @@ void speed_handler(void) {
speed_loop_timer = millis(); speed_loop_timer = millis();
// Convert to m/s // Convert to m/s
speed /= 1000.0; speed /= 1000.0;
// Convert to km/h // Convert to km/h
speed *= 3.6; speed *= 3.6;
// Accumulate instantaneous speed // Accumulate instantaneous speed
speed_accumulator += speed; speed_accumulator += speed;

View File

@@ -43,6 +43,7 @@ marker_id marker_size x y z z_angle y_angle x_angle
`N_angle` is the angle of rotation along the `N` axis in radians. `N_angle` is the angle of rotation along the `N` axis in radians.
Файлы карт располагаются в каталоге `~/catkin_ws/src/clover/aruco_pose/map`. Название файла с картой задается в аргументе `map`:
Map files are located at the `~/catkin_ws/src/clover/aruco_pose/map` directory. Map file name is defined in the `map` argument: Map files are located at the `~/catkin_ws/src/clover/aruco_pose/map` directory. Map file name is defined in the `map` argument:
```xml ```xml
@@ -156,19 +157,13 @@ If the drone's altitude is not stable, try increasing the `MPC_Z_VEL_P` paramete
In order to navigate using markers on the ceiling, mount the onboard camera so that it points up and [adjust the camera frame accordingly](camera_setup.md). In order to navigate using markers on the ceiling, mount the onboard camera so that it points up and [adjust the camera frame accordingly](camera_setup.md).
You should also set the `placement` parameter to `ceiling` in `~/catkin_ws/src/clover/clover/launch/aruco.launch`: You should also set the `placement` parameter to `ceilin` in `~/catkin_ws/src/clover/clover/launch/aruco.launch`:
```xml ```xml
<arg name="placement" default="ceiling"/> <arg name="placement" default="ceiling"/>
``` ```
With such a camera orientation the [Optical Flow](optical_flow.md) technology cannot work, so it should be disabled in the `~/catkin_ws/src/clover/clover/launch/clover.launch` file: This will flip the `aruco_map` frame (making its **<font color=blue>z</font>** axis point downward). Thus, in order to fly 2 metres below ceiling, the `z` argument for the `navigate` service should be set to 2:
```xml
<arg name="optical_flow" default="false"/>
```
Such setup will flip the `aruco_map` frame (making its **<font color=blue>z</font>** axis point downward). Thus, in order to fly 2 metres below ceiling, the `z` argument for the `navigate` service should be set to 2:
```python ```python
navigate(x=1, y=1.1, z=2, speed=0.5, frame_id='aruco_map') navigate(x=1, y=1.1, z=2, speed=0.5, frame_id='aruco_map')

View File

@@ -16,7 +16,7 @@ Clover 2 construction kit assembly instruction
* Side guard ×16. * Side guard ×16.
* Dalprop 5045 plastic propeller ×4. * Dalprop 5045 plastic propeller ×4.
* Racerstar BR2205 2300kV brushless motor ×4. * Racerstar BR2205 2300kV brushless motor ×4.
* Speed controllers ESC, DYS XSD20A ×4. * Speed controllers ESC, DYS XSD20А ×4.
* Power controller XT60 pin ×1. * Power controller XT60 pin ×1.
* Power connector XT60 socket ×1. * Power connector XT60 socket ×1.
* Three-wire female-female flat cable ×2. * Three-wire female-female flat cable ×2.
@@ -40,9 +40,9 @@ Clover 2 construction kit assembly instruction
* 6 mm plastic legs ×28. * 6 mm plastic legs ×28.
* 30 mm plastic legs ×32. * 30 mm plastic legs ×32.
* M3x8 screws ×48. * М3х8 screws ×48.
* M3x12 screws ×24. * М3х12 screws ×24.
* M3x16 screws ×40. * М3х16 screws ×40.
* Plastic nuts ×8. * Plastic nuts ×8.
* Metal nuts ×48. * Metal nuts ×48.
* Stickers for the battery compartment ×8. * Stickers for the battery compartment ×8.
@@ -117,7 +117,7 @@ Tin wires
#### Fix the motor on the mount #### Fix the motor on the mount
* Install the motor on the engraved side of the mount. * Install the motor on the engraved side of the mount.
* Attach the motors to the mounts with M3x8 screws using a screwdriver. * Attach the motors to the mounts with М3х8 screws using a screwdriver.
![Fix the motor on the mount](../assets/brrc2205on.jpg) ![Fix the motor on the mount](../assets/brrc2205on.jpg)
@@ -248,14 +248,14 @@ Solder the 5V connector, taking into account the polarity on the contact pads.
### Installation of the power distribution board ### Installation of the power distribution board
* Fix the power board to the frame with M3x8 screws and plastic nuts. ![Installation of the PDB board](../assets/mountPDB.png) * Fix the power board to the frame with М3х8 screws and plastic nuts. ![Installation of the PDB board](../assets/mountPDB.png)
> **IMPORTANT** The white arrow on the BeeRotor board points towards the fore cutout. > **IMPORTANT** The white arrow on the BeeRotor board points towards the fore cutout.
![Installation of the PDB board](../assets/topviewmountPDB.png) ![Installation of the PDB board](../assets/topviewmountPDB.png)
#### Installation of elements #### Installation of elements
1. Install the nuts into plastic holders. ![Installation of plastic holders](../assets/holderLegs.png) 1. Install the nuts into plastic holders. ![Installation of plastic holders](../assets/holderLegs.png)
2. Fix the motor mounts to the frame with M3x16 screws. 2. Fix the motor mounts to the frame with М3х16 screws.
* The mounts are installed above the frame. * The mounts are installed above the frame.
* Plastic holders are installed beneath the frame. ![Installation of mounts](../assets/mountBeams.png) * Plastic holders are installed beneath the frame. ![Installation of mounts](../assets/mountBeams.png)
3. Arrangement of motors. Check arrangement of the motors (the motors with black nuts should be in the top left and lower right corners). ![Arrangement of motors](../assets/motorsTopview.png) 3. Arrangement of motors. Check arrangement of the motors (the motors with black nuts should be in the top left and lower right corners). ![Arrangement of motors](../assets/motorsTopview.png)
@@ -309,12 +309,12 @@ IMPORTANT NOTE about polarity
### Installation of the radio receiver ### Installation of the radio receiver
1. Install the 30 mm plastic legs on the frame with M3x8 screws. 1. Install the 30 mm plastic legs on the frame with М3х8 screws.
2. Pass the 5V power connector through the slit. ![Installation of legs and the slot](../assets/mountReceiverStud.png) 2. Pass the 5V power connector through the slit. ![Installation of legs and the slot](../assets/mountReceiverStud.png)
3. Attach the receiver to the bottom of the additional frame using double-sided adhesive tape and following the orientation of the engraved arrow. The antennas are to be pointing forward. ![Installation of the radio receiver on the deck](../assets/mountReceiverDeck.png) 3. Attach the receiver to the bottom of the additional frame using double-sided adhesive tape and following the orientation of the engraved arrow. The antennas are to be pointing forward. ![Installation of the radio receiver on the deck](../assets/mountReceiverDeck.png)
4. Install the 3-wire flat cable into the PPM / CH1 channel. ![Connecting the radio receiver](../assets/receiverPPM.png) 4. Install the 3-wire flat cable into the PPM / CH1 channel. ![Connecting the radio receiver](../assets/receiverPPM.png)
5. Pass them through the slit to the 5 V connector. 5. Pass them through the slit to the 5 V connector.
6. Screw the bottom an additional frame to the legs on the central frame with M3x8 screws. ![Bottom deck installation](../assets/mountBottomDeck.png) 6. Screw the bottom an additional frame to the legs on the central frame with М3х8 screws. ![Bottom deck installation](../assets/mountBottomDeck.png)
> **IMPORTANT** The directions of the arrows on the power supply board and the additional frame should coincide > **IMPORTANT** The directions of the arrows on the power supply board and the additional frame should coincide
### Installation of the flight controller ### Installation of the flight controller
@@ -346,22 +346,22 @@ should be increased up to 4 5.
### Installation of guard ### Installation of guard
1. Attach the lower guard with M3x16 screw to the motor mounts of the frame. ![Installation of beams guard](../assets/lowsafeDeck.png) 1. Attach the lower guard with М3х16 screw to the motor mounts of the frame. ![Installation of beams guard](../assets/lowsafeDeck.png)
2. Attach the feet to the plastic holders with M3x16 screws. ![Feet installation](../assets/safeLegs.png) 2. Attach the feet to the plastic holders with М3х16 screws. ![Feet installation](../assets/safeLegs.png)
3. Attach the 30 mm long legs to the holes of the lower guard with M3x12 screw. ![Installation of the lower radial guard](../assets/safelowRadial.png) 3. Attach the 30 mm long legs to the holes of the lower guard with М3х12 screw. ![Installation of the lower radial guard](../assets/safelowRadial.png)
4. Attach the top guard with M3x12 screws. ![Installation of the top radial guard](../assets/safehighRadial.png) 4. Attach the top guard with М3х12 screws. ![Installation of the top radial guard](../assets/safehighRadial.png)
### Installation of the battery compartment ### Installation of the battery compartment
Requires the following components: Requires the following components:
* M3x12 screws (4 pcs) * М3х12 screws (4 pcs)
* M3 nuts (4 pcs) * M3 nuts (4 pcs)
* Additional frame (1 pc) * Additional frame (1 pc)
* Battery compartment (1 pc) * Battery compartment (1 pc)
1. Attach the battery compartment on top of the additional frame with M3x12 screws and nuts. ![Installation of the battery compartment](../assets/mountHolder.png) 1. Attach the battery compartment on top of the additional frame with М3х12 screws and nuts. ![Installation of the battery compartment](../assets/mountHolder.png)
2. Attach the top additional frame to the legs with M3x8 screws. ![Installation of the battery compartment](../assets/isoViewmountHolder.png) 2. Attach the top additional frame to the legs with М3х8 screws. ![Installation of the battery compartment](../assets/isoViewmountHolder.png)
3. Install the battery into the battery compartment. 3. Install the battery into the battery compartment.
### Installation of antennas ### Installation of antennas

View File

@@ -23,7 +23,7 @@ TODO
## Motor installation ## Motor installation
1. Unpack the motors. 1. Unpack the motors.
2. Attach a motor to the motor mount with M3x6 hex screws (the shortest screws supplied with the motors). 2. Attach a motor to the motor mount with М3х6 hex screws (the shortest screws supplied with the motors).
A hex wrench is included. A hex wrench is included.
@@ -31,7 +31,7 @@ TODO
The choice is yours to use a long screw or pliers. The choice is yours to use a long screw or pliers.
4. Secure the motor mount, the lower motor mount guard and the holder with M3x12 screws, using a Phillips screwdriver. 4. Secure the motor mount, the lower motor mount guard and the holder with М3х12 screws, using a Phillips screwdriver.
5. Using a clamp connect the motor mount and its bottom guard. 5. Using a clamp connect the motor mount and its bottom guard.
Cut the remaining part of the clamp (cable tie) with scissors. Cut the remaining part of the clamp (cable tie) with scissors.
@@ -40,9 +40,9 @@ TODO
## Frame elements installation ## Frame elements installation
1. Insert the M3 plastic nuts (4 pcs) for mounting the PDB on the frame with M3x8 screws. 1. Insert the M3 plastic nuts (4 pcs) for mounting the PDB on the frame with М3х8 screws.
2. Install 6 mm legs (4 pcs) for attaching the Raspberry Pi to the frame with M3x8 screws. 2. Install 6 mm legs (4 pcs) for attaching the Raspberry Pi to the frame with М3х8 screws.
3. Attach the assembled unit to the frame with M3x16 screws, complying with the layout. 3. Attach the assembled unit to the frame with М3х16 screws, complying with the layout.
4. Install the frame for the LED strip, using the slots in the leg holders. 4. Install the frame for the LED strip, using the slots in the leg holders.
![Legs installation on the frame](../assets/en/cl3_mountElements.JPG) ![Legs installation on the frame](../assets/en/cl3_mountElements.JPG)
@@ -150,9 +150,9 @@ article [remote faults](radioerrors.md).
## Installation and connection of the Pixracer flight controller ## Installation and connection of the Pixracer flight controller
1. Install the Pixracer flight controller on double-sided 3M adhesive tape (2 3 layers). 1. Install the Pixracer flight controller on double-sided 3M adhesive tape (2 3 layers).
The flight controller may also be removed from the housing and firmly mounted on the M3x6 leg. The flight controller may also be removed from the housing and firmly mounted on the М3х6 leg.
2. Install 40 mm legs using M3x8 screws. 2. Install 40 mm legs using М3х8 screws.
Connect the POWER connector. Connect the POWER connector.
@@ -184,14 +184,14 @@ article [remote faults](radioerrors.md).
4. Assembling the mount for the RPi camera. 4. Assembling the mount for the RPi camera.
Use an M3x16 screw and an M3 nut Use an М3х16 screw and an M3 nut
![Installation of Raspberry Pi Model B](../assets/cl3_mountRaspberryPi.JPG) ![Installation of Raspberry Pi Model B](../assets/cl3_mountRaspberryPi.JPG)
## Arduino and FlySky radio receiver installation ## Arduino and FlySky radio receiver installation
1. Solder Arduino Nano micro-controller pins to its board. 1. Solder Arduino Nano micro-controller pins to its board.
2. Install the micro-controller into a special mount, and attach to the lower deck using M3x16 screws (4 pcs). 2. Install the micro-controller into a special mount, and attach to the lower deck using М3х16 screws (4 pcs).
3. Using double-sided tape, attach the receiver as shown in the picture. 3. Using double-sided tape, attach the receiver as shown in the picture.
4. Connect the ribbon cable from the radio receiver to Pixracer as shown in the picture. 4. Connect the ribbon cable from the radio receiver to Pixracer as shown in the picture.
@@ -204,12 +204,12 @@ article [remote faults](radioerrors.md).
## RPi camera installation ## RPi camera installation
1. Attach the mount for the RPi camera assembly to the lower deck with M3x12 screws (2 pcs.) 1. Attach the mount for the RPi camera assembly to the lower deck with М3х12 screws (2 pcs.)
2. Connect the ribbon cable to the RPi camera. 2. Connect the ribbon cable to the RPi camera.
3. Install the camera into the mount, secure it with M2 self-tappers. 3. Install the camera into the mount, secure it with M2 self-tappers.
4. Attach Raspberry with 30 mm legs (4 pcs.). 4. Attach Raspberry with 30 mm legs (4 pcs.).
Attach the lower deck assembly to the rack with M3x8 screws (4 pcs.) Attach the lower deck assembly to the rack with М3х8 screws (4 pcs.)
5. Install the legs into the mounts (4 pcs). 5. Install the legs into the mounts (4 pcs).
@@ -217,11 +217,11 @@ article [remote faults](radioerrors.md).
## Installation of the remaining structural elements ## Installation of the remaining structural elements
1. Install the bottom guard using M3x12 screws (8 units) and the 30 mm legs (8 pcs). 1. Install the bottom guard using М3х12 screws (8 units) and the 30 mm legs (8 pcs).
2. Install the top guard using M3x12 screws (8 pcs). 2. Install the top guard using М3х12 screws (8 pcs).
3. Insert the strap into the upper deck for attaching the battery. 3. Insert the strap into the upper deck for attaching the battery.
Secure the upper deck with M3x8 screws (4 pcs.) Secure the upper deck with М3х8 screws (4 pcs.)
![Installation of the remaining structural elements](../assets/cl3_mountOtherElements.JPG) ![Installation of the remaining structural elements](../assets/cl3_mountOtherElements.JPG)

View File

@@ -60,7 +60,7 @@ Dimensional drawing  [clover-4.2.pdf](https://github.com/CopterExpress/clove
<img src="../assets/assembling_clever4_2/frame_2.png" width=300 class="zoom border"> <img src="../assets/assembling_clever4_2/frame_2.png" width=300 class="zoom border">
</div> </div>
2. Install 2 aluminum 15mm posts on the center holes in the main deck and fix them with the M3x8 screws. 2. Install 2 15mm posts on the center holes in the main deck and fix them with the M3x8 screws.
<img src="../assets/assembling_clever4_2/frame_3.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/frame_3.png" width=300 class="zoom border center">
@@ -146,7 +146,7 @@ Install the damper struts, fix COEX Pix on them with nylon nuts.
<img src="../assets/assembling_clever4_2/fc_connection_2.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/fc_connection_2.png" width=300 class="zoom border center">
3. Install 40mm aluminum racks on the M3x10 screws. 3. Install 40mm aluminum racks on the M3x12 screws.
<div class="image-group"> <div class="image-group">
<img src="../assets/assembling_clever4_2/fc_connection_3.png" width=300 class="zoom border"> <img src="../assets/assembling_clever4_2/fc_connection_3.png" width=300 class="zoom border">
@@ -162,7 +162,7 @@ Install the damper struts, fix COEX Pix on them with nylon nuts.
<img src="../assets/assembling_clever4_2/fc_connection_6.png" width=300 class="zoom border"> <img src="../assets/assembling_clever4_2/fc_connection_6.png" width=300 class="zoom border">
</div> </div>
2. On a mounting deck, install M2.5x6 mm racks and M3x30 mm racks, fasten them with the M2.5x4 and M3x10 bolts, respectively. 2. On a mounting deck, install 6 mm racks and 30 mm racks, fasten them with the M3x5 and M3x12 bolts, respectively.
<img src="../assets/assembling_clever4_2/raspberry_1.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/raspberry_1.png" width=300 class="zoom border center">
@@ -170,7 +170,7 @@ Install the damper struts, fix COEX Pix on them with nylon nuts.
<img src="../assets/assembling_clever4_2/raspberry_2.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/raspberry_2.png" width=300 class="zoom border center">
4. Install the Raspberry Pi circuit board and fix with M2.5x4 bolts. 4. Install the Raspberry Pi circuit board and fix with nylon nuts.
<img src="../assets/assembling_clever4_2/raspberry_3.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/raspberry_3.png" width=300 class="zoom border center">
@@ -220,25 +220,25 @@ Install the damper struts, fix COEX Pix on them with nylon nuts.
<img src="../assets/assembling_clever4_2/led_4.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/led_4.png" width=300 class="zoom border center">
5. Behind fasten the legs with self-locking nuts and M3x10 screws. 5. Behind fasten the legs with self-locking nuts and M3x8 screws.
<img src="../assets/assembling_clever4_2/led_5.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/led_5.png" width=300 class="zoom border center">
6. Connect the LED strip power (red, black cables) to the short JST connector on the PDB. 6. Connect the LED strip power (red, black cables) to the short JST connector on the PDB.
<img src="../assets/assembling_soldering_clever_4/led_7.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/led_5.png" width=300 class="zoom border center">
7. Connect the signal output of the LED strip (white cable) to Raspberry Ri, to pin *GPIO21*. 7. Connect the signal output of the LED strip (white cable) to Raspberry Ri, to pin *GPIO21*.
<img src="../assets/assembling_soldering_clever_4/led_8.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/led_6.png" width=300 class="zoom border center">
## Installing guard ## Installing guard
1. Assemble the lower level of guard with 40mm racks and M3x10 screws. 1. Assemble the lower level of guard with 40mm racks and M3x12 screws.
<img src="../assets/assembling_clever4_2/guard_1.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/guard_1.png" width=300 class="zoom border center">
2. Assemble the top level of protection with the M3x10 screws. 2. Assemble the top level of protection with the M3x12 screws.
<img src="../assets/assembling_clever4_2/guard_2.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/guard_2.png" width=300 class="zoom border center">
@@ -246,7 +246,7 @@ Install the damper struts, fix COEX Pix on them with nylon nuts.
<img src="../assets/assembling_clever4_2/guard_3.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/guard_3.png" width=300 class="zoom border center">
4. Establish protection and fix on beams by means of self-locking nuts and M3x10 screws. 4. Establish protection and fix on beams by means of self-locking nuts and M3x8 screws.
<img src="../assets/assembling_clever4_2/guard_4.png" width=300 class="zoom border center"> <img src="../assets/assembling_clever4_2/guard_4.png" width=300 class="zoom border center">

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