Add official Clover simulation config (#254)

* clover_description: Add preliminary configs/models

* clover_description: Use proper models for the drone

* clover_description: Be more specific about spawn arguments

* clover_description: Tweak parameters a bit, add collision boxes

* travis: Add .dae files to the list of ignored by eclint

* Add clover_simulation package

* clover: Add Gazebo plugin sources

* builder: Ignore clover_gazebo_plugins for actual drone

* clover_gazebo_plugins: Expose include directories for plugins

This should fix building the unit tests

* clover_gazebo_plugins: Remove dependency on gazebo_ros

This should prevent RPi image failing to build.

* travis, gitattributes: Mark clover_gazebo_plugins as vendored, stop checks

* clover_simulation: Minor package.xml fix

* clover_description: Add IMU joint preservation

Oh, Gazebo, you are ever so very helpful, it's hard to put my appreciation into words! If not for your helpful model simplification, I wouldn't have spent two hours looking through the plugin sources, the urdf sources, trying lots of
different options for the joints and links, and finally getting an answer from GazeboOverflow or however you've named your Q&A site. How wonderful it is to have an issue that makes you tear your hair out just because you know
what's better for me!

* clover_simulation: Add the bare necessities to run a simulation

* clover_gazebo_plugins: Prevent gazebo from trying to download models

For some reason the models are no longer available, so Gazebo just spends some time waiting for a timeout.

* clover_gazebo_plugins: Update Gazebo model database URI

* clover_simulation: Add script to find and launch PX4

* clover_simulation: Fix launch file

* clover_description: Add missing plugins

* simulation: Re-enable gazebo_ros dependencies

This will force rosdep to try to install gazebo_ros on the drone,
but this can be counteracted by --skip-keys rosdep option.
This does not look reliable, but I could not come up with a better
solution.

* builder: Be more resilient about apt-get errors

* builder: Remove reference to resolve_rosdep

* clover_description: Update Clover model, change xacro description

Previous xacro description file was not performing too well, so I went with
a more sensible route and started changing iris.xacro to use our Clover model.

* clover_description: Bring back constants.xacro

* clover_description: Prevent lumping for camera link/joint

* clover_description: Move near clipping plane further away

* clover_description: Allow setting width/height for rpi_cam

* clover_description: Add a Clover model with a camera

* clover_description: Remove whitespaces

* clover_description: Add drone+camera spawning .launch file

* clover_simulation: Add gazebo_ros here as well

* clover_simulation: Spawn drone with camera by default

* clover_simulation: Allow specifying data path for px4

* clover_simulation: Add startup scripts from px4

Big TODO: Clean them up eventually

* clover_simulation: Use local data files

* clover_simulation: Launch clover services by default

* clover_description: Depend on gazebo_plugins as well

libgazebo_ros_camera is in gazebo_plugins, so we need that package.

* clover_description: Fix camera_sensor description

* clover_description: Fix typo in package.xml

* clover_simulation: rename sim_gazebo.launch to simulator.launch

* clover_simulation: Don't look for ROMFS in px4_source_path

We provide our own, no reason to fail if we can't find the originals.

* clover_simulation: Remove extra CMakeLists.txt

* clover_description: Use xacro: namespace for xacro macros

* clover_description: Fix package.xml formatting

* clover_description: Better camera defaults

* clover_description: Add distance sensor

* clover_description: Add leg colliders

* clover_simulation: Actually forward vehicle name

* clover_description: Revert adding additional colliders

Unfortunately, this breaks physics too much

* clover_description: Tweak drone physics, make it more bouncy

* clover_description: Don't spawn the drone inside the floor

* clover_description: Set rangefinder min range outside drone collider

* clover_simulation: Set default flow parameters for Clover

* clover_description: Update Clover 4 model

* clover_simulation: rename sim_gazebo.launch to simulator.launch (#233)

* clover_simulation: Add workaround for Gazebo crashes in VMware

* clover_simulation: Ignore .git for now

* clover: Add "simulated" argument

* clover_simulation: Start Gazebo early

* clover_gazebo_plugins: Remove unused files

* clover_description: Allow turning sensors on and off

* clover_description: Fix rangefinder creation

* Remove unneeded stuff and use PX4 from catkin workspace

* Remove clover_gazebo_plugins

* Rename arg simulated to simulator

* clover: Change target names to avoid clashing with PX4

* Fix

* clover_simulation: Re-add deleted comments

* Add loop model

* loop.material: use tabs instead of spaces

* loop model: don’t rotate by yaw

* loop.material: turn on alpha_blend

* Rename model loop to loop_line

* Add parquet plane model

* loop_line: fix description

* Set alpha_blend for loop_solid material

* Add square line model

* Add CATKIN_IGNORE to models directory

* Add LED strip Gazebo model

* Add hardcoded URDF LED strip

* clover_description: Add LED xarco model

clover_simulation: Implement LED visual plugin and controller

* clover_simulation: Make led plugin less chatty

* clover_simulation: Depend on led_msgs

This should allow the packages to be built in the proper order.

* clover_simulation: Support building against Kinetic

* clover_simulation: Don't build plugins if Gazebo is not installed

* clover_description: Get rid of "constants" file

* clover_description: Add README

* clover_simulation: Add README

* clover_simulation: Make parquet thicker

Otherwise the rangefinder beam goes right through it.

* docs: Start working on simulation articles

* docs: Start working on the simulation overview (en)

* Add launch-file for PX4 v1.8.2

* clover_simulation: Disable GPS, use EKF2 by default

Ideally we should be using LPE, but it is broken in PX4 1.10, and our need for a somewhat working simulator is higher than for a completely correct one.

* clover_simulation: Add experimental throttling camera

* clover_simulation: Add note about throttling camera

* clover_description: Remove unused file

* clover_simulation: Link against CameraPlugin

* clover_description: Add option to use throttling_camera

* Add clover.world

* clover_description: Add calculated inertial parameters

* simulator: change default world to clover.world

* clover_simulation: Start working on ArUco generation script

Port over aruco_gazebo_gen, add more options.
Does not modify the world right now.

* clover_simulation: Make LED plugin less chatty

* clover_description: Be more ROS-like in script naming

* clover_simulation: Implement model insertion to the world

* clover_simulation: Allow specifying output model dir

* clover_description: Don't use throttling camera by default

throttling camera is still a work-in-progress, there's no reason to
enable it by default.

* clover_simulation: Use proper script name in CMakeLists

This is what typically happens when I'm rushed.

* docs: Add instructions for VM setup (en)

* clover_simulation: Remove extra spaces

* docs: Describe simulation usage (en)

* clover_simulation: Remove led_strip

* docs/assets: Crunch sim image a bit

* clover: Bump VL53L1X version

For some reason, 0.0.2 is not installable on x86.

* docs/simulation: Fix capitalization

* Remove remnants of clover_gazebo_plugins

* Remove unneeded Clover 3 model

* Remove empty.world and asphalt_plane model

* Remove unused LED strip model

* Reduce images size

* Shortened simulator related urls

Co-authored-by: Oleg Kalachev <okalachev@gmail.com>
This commit is contained in:
Alexey Rogachevskiy
2020-07-22 09:17:56 +03:00
committed by GitHub
parent b346af92ae
commit e03eaa51c4
79 changed files with 3863 additions and 32 deletions

View File

@@ -114,7 +114,7 @@ jobs:
- wget https://github.com/okalachev/editorconfig-checker/releases/download/1.2.1-disable-spaces-amount/ec-linux-amd64
- chmod +x ec-linux-amd64
script:
- ./ec-linux-amd64 -spaces-after-tabs -e "roslib.js|ros3d.js|eventemitter2.js|draw.cpp|BinUtils.swift|\.idea|apps/android/app|Assets.xcassets|test_parser_pass.txt|test_node_failure.txt|aruco_pose/vendor|\.stl|\.dxf"
- ./ec-linux-amd64 -spaces-after-tabs -e "roslib.js|ros3d.js|eventemitter2.js|draw.cpp|BinUtils.swift|\.idea|apps/android/app|Assets.xcassets|test_parser_pass.txt|test_node_failure.txt|aruco_pose/vendor|\.stl|\.dxf|\.dae"
stages:
- Build
- Annotate

View File

@@ -74,18 +74,6 @@ my_travis_retry rosdep update
echo_stamp "Populate rosdep for ROS user"
my_travis_retry sudo -u pi rosdep update
resolve_rosdep() {
# TEMPLATE: resolve_rosdep <CATKIN_PATH> <ROS_DISTRO> <OS_DISTRO> <OS_VERSION>
CATKIN_PATH=$1
ROS_DISTRO='melodic'
OS_DISTRO='debian'
OS_VERSION='buster'
echo_stamp "Installing dependencies apps with rosdep in ${CATKIN_PATH}"
cd ${CATKIN_PATH}
my_travis_retry rosdep install -y --from-paths src --ignore-src --rosdistro ${ROS_DISTRO} --os=${OS_DISTRO}:${OS_VERSION}
}
export ROS_IP='127.0.0.1' # needed for running tests
echo_stamp "Reconfiguring Clover repository for simplier unshallowing"
@@ -94,11 +82,14 @@ git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
echo_stamp "Build and install Clover"
cd /home/pi/catkin_ws
resolve_rosdep $(pwd)
# Don't try to install gazebo_ros
my_travis_retry rosdep install -y --from-paths src --ignore-src --rosdistro melodic --os=debian:buster \
--skip-keys=gazebo_ros --skip-keys=gazebo_plugins
my_travis_retry pip install wheel
my_travis_retry pip install -r /home/pi/catkin_ws/src/clover/clover/requirements.txt
source /opt/ros/melodic/setup.bash
catkin_make -j2 -DCMAKE_BUILD_TYPE=Release
# Don't build simulation plugins for actual drone
catkin_make -j2 -DCMAKE_BUILD_TYPE=Release -DCATKIN_BLACKLIST_PACKAGES=clover_gazebo_plugins
echo_stamp "Install clever package (for backwards compatibility)"
cd /home/pi/catkin_ws/src/clover/builder/assets/clever
@@ -113,7 +104,7 @@ 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
echo_stamp "Installing additional ROS packages"
apt-get install -y --no-install-recommends \
my_travis_retry apt-get install -y --no-install-recommends \
ros-melodic-dynamic-reconfigure \
ros-melodic-compressed-image-transport \
ros-melodic-rosbridge-suite \

View File

@@ -80,8 +80,9 @@ echo_stamp "Update apt cache"
apt-get update
# && apt upgrade -y
# Let's retry fetching those packages several times, just in case
echo_stamp "Software installing"
apt-get install --no-install-recommends -y \
my_travis_retry apt-get install --no-install-recommends -y \
unzip \
zip \
ipython \
@@ -115,9 +116,7 @@ python-dev \
python3-dev \
python-systemd \
mjpg-streamer \
python3-opencv \
&& echo_stamp "Everything was installed!" "SUCCESS" \
|| (echo_stamp "Some packages wasn't installed!" "ERROR"; exit 1)
python3-opencv
# Deny byobu to check available updates
sed -i "s/updates_available//" /usr/share/byobu/status/status

View File

@@ -166,13 +166,14 @@ add_library(${PROJECT_NAME}
## The recommended prefix ensures that target names across packages don't collide
add_executable(simple_offboard src/simple_offboard.cpp)
add_executable(rc src/rc.cpp)
# PX4 already has rc and led targets, so we prefix ours with clover_
add_executable(clover_rc src/rc.cpp)
add_executable(camera_markers src/camera_markers.cpp)
add_executable(vpe_publisher src/vpe_publisher.cpp)
add_executable(led src/led.cpp)
add_executable(clover_led src/led.cpp)
add_executable(shell src/shell.cpp)
@@ -181,19 +182,24 @@ target_link_libraries(simple_offboard
${GeographicLib_LIBRARIES}
)
target_link_libraries(rc ${catkin_LIBRARIES})
# Don't change actual binary names
set_target_properties(clover_rc PROPERTIES OUTPUT_NAME rc)
set_target_properties(clover_led PROPERTIES OUTPUT_NAME led)
target_link_libraries(clover_rc ${catkin_LIBRARIES})
target_link_libraries(camera_markers ${catkin_LIBRARIES})
target_link_libraries(vpe_publisher ${catkin_LIBRARIES})
target_link_libraries(led ${catkin_LIBRARIES})
target_link_libraries(clover_led ${catkin_LIBRARIES})
target_link_libraries(shell ${catkin_LIBRARIES})
add_dependencies(simple_offboard ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(led ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(clover_led ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(shell ${PROJECT_NAME}_generate_messages_cpp)

View File

@@ -13,6 +13,8 @@
<arg name="rc" default="true"/>
<arg name="shell" default="true"/>
<arg name="simulator" default="false"/> <!-- flag that we are operating on a simulated drone -->
<!-- log formatting -->
<env name="ROSCONSOLE_FORMAT" value="[${severity}] [${time}]: ${logger}: ${message}"/>
@@ -56,7 +58,9 @@
</node>
<!-- main camera -->
<include file="$(find clover)/launch/main_camera.launch" if="$(arg main_camera)"/>
<include file="$(find clover)/launch/main_camera.launch" if="$(arg main_camera)">
<arg name="simulator" value="$(arg simulator)"/>
</include>
<!-- rosbridge -->
<include file="$(find rosbridge_server)/launch/rosbridge_websocket.launch" if="$(eval rosbridge or rc)"/>
@@ -65,14 +69,16 @@
<node name="tf2_web_republisher" pkg="tf2_web_republisher" type="tf2_web_republisher" output="screen" if="$(arg rosbridge)"/>
<!-- vl53l1x ToF rangefinder -->
<node name="rangefinder" pkg="vl53l1x" type="vl53l1x_node" output="screen" if="$(arg rangefinder_vl53l1x)">
<node name="rangefinder" pkg="vl53l1x" type="vl53l1x_node" output="screen" if="$(eval rangefinder_vl53l1x and not simulator)">
<param name="frame_id" value="rangefinder"/>
<param name="min_signal" value="0.4"/>
<param name="pass_statuses" type="yaml" value="[0, 6, 7, 11]"/>
</node>
<!-- led strip -->
<include file="$(find clover)/launch/led.launch" if="$(arg led)"/>
<include file="$(find clover)/launch/led.launch" if="$(arg led)">
<arg name="simulator" value="$(arg simulator)"/>
</include>
<!-- rc backend -->
<node name="rc" pkg="clover" type="rc" output="screen" if="$(arg rc)" clear_params="true">

View File

@@ -2,11 +2,12 @@
<arg name="ws281x" default="true"/>
<arg name="led_effect" default="true"/>
<arg name="led_notify" default="true"/>
<arg name="simulator" default="false"/>
<!-- For additional help go to https://clover.coex.tech/led -->
<!-- ws281x led strip driver -->
<node pkg="ws281x" name="led" type="ws281x_node" clear_params="true" output="screen" if="$(arg ws281x)">
<node pkg="ws281x" name="led" type="ws281x_node" clear_params="true" output="screen" if="$(eval ws281x and not simulator)">
<param name="led_count" value="58"/>
<param name="gpio_pin" value="21"/>
<param name="brightness" value="64"/>

View File

@@ -3,6 +3,7 @@
<arg name="direction_z" default="down"/> <!-- direction the camera points: down, up -->
<arg name="direction_y" default="backward"/> <!-- direction the camera cable points: backward, forward -->
<arg name="simulator" default="false"/>
<node if="$(eval direction_z == 'down' and direction_y == 'backward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/>
<node if="$(eval direction_z == 'down' and direction_y == 'forward')" pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 1.5707963 0 3.1415926 base_link main_camera_optical"/>
@@ -15,7 +16,7 @@
<!-- <node pkg="tf2_ros" type="static_transform_publisher" name="main_camera_frame" args="0.05 0 -0.07 -1.5707963 0 3.1415926 base_link main_camera_optical"/> -->
<!-- camera node -->
<node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet nodelet_manager" clear_params="true">
<node pkg="nodelet" type="nodelet" name="main_camera" args="load cv_camera/CvCameraNodelet nodelet_manager" clear_params="true" unless="$(arg simulator)">
<param name="device_path" value="/dev/video0"/> <!-- v4l2 device -->
<param name="frame_id" value="main_camera_optical"/>
<param name="camera_info_url" value="file://$(find clover)/camera_info/fisheye_cam.yaml"/>

View File

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

View File

@@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.0)
project(clover_description)
find_package(catkin REQUIRED)
catkin_package()
install(DIRECTORY launch DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
install(DIRECTORY meshes DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
install(DIRECTORY urdf DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})

View File

@@ -0,0 +1,29 @@
# `clover_description` ROS package
This package contains models and URDF descriptions for the Clover 4 drone. These descriptions can be used for Gazebo simulation environment.
Note that in order to use these descriptions in Gazebo, you need to use the plugins from [PX4 `sitl_gazebo` package](https://github.com/PX4/sitl_gazebo) and `clover_simulation` package.
## Usage
The descriptions are provided as [`xacro`-enabled](https://wiki.ros.org/xacro) URDF files. A [`spawn_drone.launch`](launch/spawn_drone.launch) file that spawns the model in a running Gazebo instance is also provided for convenience.
You may provide additional parameters for `spawn_drone.launch` as well:
* `main_camera` (*boolean*, default: *true*) - controls whether the drone will have a downward-facing camera 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`);
* `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 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:
```bash
roslaunch clover_description spawn_drone.launch gps:=false
```
## Tweaking
By default, the `spawn_drone.launch` command will use the [`clover4.xacro` description file](urdf/clover/clover4.xacro). This is a "high-level" description of the drone, mainly used to attach additional sensors.
The drone "physics" may be tweaked by changing the [`clover4_physics.xacro` file](urdf/clover/clover4_physics.xacro).

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<launch>
<arg name="cmd" default="$(find xacro)/xacro $(find clover_description)/urdf/sensors/rpi_cam.urdf.xacro"/>
<param command="$(arg cmd)" name="camera_description"/>
<node name="$(anon spawn)" output="screen" pkg="gazebo_ros" type="spawn_model" args="-urdf -param camera_description -model rpi_camera -package_to_model"/>
</launch>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<launch>
<!-- Toggleable model parameters -->
<!-- Main camera -->
<arg name="main_camera" default="true"/>
<!-- Slow simulation down to maintain camera rate -->
<arg name="maintain_camera_rate" default="false"/>
<arg name="rangefinder" default="true"/>
<arg name="led" default="true"/>
<arg name="gps" default="true"/>
<!-- Use physics parameters from CAD programs -->
<arg name="use_clover_physics" default="false"/>
<arg name="cmd" default="$(find xacro)/xacro $(find clover_description)/urdf/clover/clover4.xacro main_camera:=$(arg main_camera) rangefinder:=$(arg rangefinder) led:=$(arg led) gps:=$(arg gps) maintain_camera_rate:=$(arg maintain_camera_rate) use_clover_physics:=$(arg use_clover_physics)"/>
<param command="$(arg cmd)" name="drone_description"/>
<!-- Note: -package_to_model replaces all mentions of "package://" with "model://" in urdf URIs -->
<node name="$(anon spawn)" output="screen" pkg="gazebo_ros" type="spawn_model" args="-urdf -param drone_description -model clover -z 0.3"/>
</launch>

View File

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

View File

@@ -0,0 +1,21 @@
material Polycarbonate
{
technique
{
pass
{
scene_blend alpha_blend
depth_write off
ambient 0.57 0.531 0.444 1
diffuse 0.57 0.531 0.444 1
specular 0.5 0.5 0.5 1
texture_unit
{
colour_op_ex source1 src_current src_current 0 1 0
alpha_op_ex source1 src_manual src_current 0.72
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,27 @@
<package format="2">
<name>clover_description</name>
<version>0.0.1</version>
<description>The clover_description package provides URDF models of the Clover series of quadcopters.</description>
<maintainer email="sfalexrog@gmail.com">Alexey Rogachevskiy</maintainer>
<author>Alexey Rogachevskiy</author>
<author>Andrey Ryabov</author>
<author>Arthur Golubtsov</author>
<author>Oleg Kalachev</author>
<author>Svyatoslav Zhuravlev</author>
<license>MIT</license>
<url type="website">https://github.com/CopterExpress/clover</url>
<url type="bugtracker">https://github.com/CopterExpress/clover/issues</url>
<buildtool_depend>catkin</buildtool_depend>
<depend>xacro</depend>
<exec_depend>gazebo_ros</exec_depend>
<exec_depend>gazebo_plugins</exec_depend>
<export>
<gazebo_ros gazebo_media_path="${prefix}"/>
</export>
</package>

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<robot name="camera_sensor" xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:macro name="camera_sensor_base"
params="name width height rate horizontal_fov frame_name k1 k2 k3 p1 p2 cx cy cx_prime ros_plugin_name min_rate window_size max_st_dev">
<gazebo reference="${name}_link">
<sensor type="camera" name="${name}">
<camera>
<horizontal_fov>${horizontal_fov}</horizontal_fov>
<image>
<format>B8G8R8</format>
<width>${width}</width>
<height>${height}</height>
</image>
<clip>
<near>0.02</near>
<far>1000</far>
</clip>
<distortion>
<k1>${k1}</k1>
<k2>${k2}</k2>
<k3>${k3}</k3>
<p1>${p1}</p1>
<p2>${p2}</p2>
<center>${(cx - 0.5)/ width} ${(cy - 0.5) / height}</center>
</distortion>
</camera>
<always_on>1</always_on>
<update_rate>${rate}</update_rate>
<visualize>1</visualize>
<plugin name="camera_plugin" filename="${ros_plugin_name}">
<alwaysOn>true</alwaysOn>
<imageTopicName>image_raw</imageTopicName>
<cameraTopicName>camera_info</cameraTopicName>
<updateRate>${rate}</updateRate>
<cameraName>${name}</cameraName>
<frameName>${frame_name}</frameName>
<CxPrime>${cx_prime}</CxPrime>
<Cx>${cx}</Cx>
<Cy>${cy}</Cy>
<distortionK1>${k1}</distortionK1>
<distortionK2>${k2}</distortionK2>
<distortionK3>${k3}</distortionK3>
<distortionT1>${p1}</distortionT1>
<distortionT2>${p2}</distortionT2>
<!-- throttling_camera specific options start here -->
<minUpdateRate>${min_rate}</minUpdateRate>
<windowSize>${window_size}</windowSize>
<maxStDev>${max_st_dev}</maxStDev>
</plugin>
</sensor>
</gazebo>
</xacro:macro>
<xacro:macro name="camera_sensor" params="name width height rate horizontal_fov k1:=0 k2:=0 k3:=0 p1:=0 p2:=0 do_throttling:=false">
<xacro:if value="${do_throttling}">
<xacro:property name="ros_plugin_name" value="libthrottling_camera.so"/>
</xacro:if>
<xacro:unless value="${do_throttling}">
<xacro:property name="ros_plugin_name" value="libgazebo_ros_camera.so"/>
</xacro:unless>
<xacro:camera_sensor_base
name="${name}"
width="${width}"
height="${height}"
rate="${rate}"
horizontal_fov="${horizontal_fov}"
frame_name="/${name}_optical"
k1="${k1}"
k2="${k2}"
k3="${k3}"
p1="${p1}"
p2="${p2}"
cx="${(width + 1.0)/2.0}"
cy="${(height + 1.0)/2.0}"
cx_prime="${(width + 1.0)/2.0}"
ros_plugin_name="${ros_plugin_name}"
min_rate="${rate * 0.95}"
window_size="${rate}"
max_st_dev="${3.0 / rate}"
/>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,46 @@
<?xml version="1.0"?>
<robot name="clover_camera" xmlns:xacro="http://ros.org/wiki/xacro">
<xacro:arg name="main_camera" default="true"/>
<xacro:arg name="rangefinder" default="true"/>
<xacro:arg name="led" default="true"/>
<xacro:arg name="gps" default="true"/>
<xacro:arg name="maintain_camera_rate" default="false"/>
<xacro:arg name="use_clover_physics" default="false"/>
<xacro:include filename="$(find clover_description)/urdf/clover/clover4_base.xacro" />
<xacro:include filename="$(find clover_description)/urdf/sensors/rpi_cam.urdf.xacro"/>
<xacro:include filename="$(find clover_description)/urdf/sensors/distance_sensor.urdf.xacro"/>
<xacro:include filename="$(find clover_description)/urdf/leds/led_strip.xacro"/>
<!-- Create camera plugin -->
<xacro:if value="$(arg main_camera)">
<xacro:rpi_cam name="main_camera" parent="base_link" x="0.055" y="0.0" z="-0.03" roll="0" pitch="${pi / 2}" yaw="0" width="320" height="240" rate="40" do_throttling="$(arg maintain_camera_rate)"/>
</xacro:if>
<!-- Create rangefinder plugin -->
<xacro:if value="$(arg rangefinder)">
<xacro:distance_sensor parent="base_link" x="0.0" y="0.0" z="-0.04" roll="0" pitch="${pi / 2}" yaw="0"/>
</xacro:if>
<!-- Instantiate LED strip -->
<xacro:if value="$(arg led)">
<xacro:led_strip
name="led"
parent="base_link"
radius="0.08"
bulb_radius="0.006"
led_count="58"
use_plugin="true"
z="-0.002"/>
</xacro:if>
<xacro:if value="$(arg gps)">
<!-- Instantiate gps plugin. -->
<xacro:gps_plugin_macro
namespace="${namespace}"
gps_noise="true"
/>
</xacro:if>
</robot>

View File

@@ -0,0 +1,183 @@
<?xml version="1.0"?>
<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' />
<!-- 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='log_file' default='iris' />
<!-- macros for gazebo plugins, sensors -->
<xacro:include filename="$(find clover_description)/urdf/component_snippets.urdf.xacro" />
<!-- Instantiate iris "mechanics" -->
<xacro:include filename="$(find clover_description)/urdf/clover/clover4_physics.xacro" />
<xacro:if value="$(arg enable_wind)">
<xacro:wind_plugin_macro
namespace="${namespace}"
wind_direction="0 0 1"
wind_force_mean="0.7"
xyz_offset="1 0 0"
wind_gust_direction="0 0 0"
wind_gust_duration="0"
wind_gust_start="0"
wind_gust_force_mean="0"
/>
</xacro:if>
<!-- Instantiate magnetometer plugin. -->
<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)">
<!-- Instantiate a logger -->
<xacro:bag_plugin_macro
namespace="${namespace}"
bag_file="$(arg log_file)"
rotor_velocity_slowdown_sim="${rotor_velocity_slowdown_sim}"
>
</xacro:bag_plugin_macro>
</xacro:if>
</robot>

View File

@@ -0,0 +1,235 @@
<?xml version="1.0"?>
<!--
Copyright 2015 Fadri Furrer, ASL, ETH Zurich, Switzerland
Copyright 2015 Michael Burri, ASL, ETH Zurich, Switzerland
Copyright 2015 Mina Kamel, ASL, ETH Zurich, Switzerland
Copyright 2015 Janosch Nikolic, ASL, ETH Zurich, Switzerland
Copyright 2015 Markus Achtelik, ASL, ETH Zurich, Switzerland
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!--
Clover 4 base model, collision boxes, and other fun stuff. Derived heavily from multirotor_base.xacro
from PX4 simulation config files.
-->
<robot xmlns:xacro="http://ros.org/wiki/xacro">
<!-- Main multirotor link -->
<!-- We still allow specifying a different mass and inertia matrix.
Body width and height are allowed as parameters, but are ignored for the most part.
Color is not applicable, since the model has textures and is a compound thingy anyway.
-->
<xacro:macro name="clover4_base_macro"
params="robot_namespace mass body_width body_height *inertia">
<link name="base_link"></link>
<!-- Note: For some reason this file relies on base_link_inertia becoming base_link.
I don't even want to know.
-sfalexrog
-->
<joint name="base_joint" type="fixed">
<origin xyz="0 0 0" rpy="0 0 0" />
<parent link="base_link" />
<child link="base_link_inertia" />
</joint>
<link name="base_link_inertia">
<inertial>
<mass value="${mass}" /> <!-- [kg] -->
<origin xyz="0 0 0" />
<xacro:insert_block name="inertia" />
</inertial>
<visual name="body">
<origin xyz="0 0 0.025" rpy="0 0 0" />
<geometry>
<!-- Note: Texture files are expected to be in the same directory as the model -->
<mesh filename="package://clover_description/meshes/clover4/clover_body_solid.dae"
scale="1 1 1"/>
</geometry>
</visual>
<!-- Now, a person with some experience in building urdf/xacro files would say something along the lines of:
"Hey, a link may have multiple visual tags, each with its own material!"
Hear my answer, o experienced one. First, you are absolutely correct, you may add multiple visual
elements to a single link. Second, once you try to put some transparency on your visual elements,
you'll have it vanish into thin fucking air - Gazebo (as of version 9!) gives fuck all about your
"color" attributes and whatnot. Third, oh, you want to reference the visual element from your
"gazebo" tag down the line? Well fuck you, buddy, because you can only reference links and joints!
So yeah, good luck improving this.
- sfalexrog
-->
<collision>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<box size="${body_width} ${body_width} ${body_height}" /> <!-- [m] [m] [m] -->
</geometry>
</collision>
</link>
<!-- This link is here for a single reason: to draw transparent parts (oh, and also to be the bane of my existence).
I'm going to leave helpful comments for the poor soul who's going to attempt to improve this file.
-sfalexrog
-->
<link name="base_guard_link">
<!-- So we've established we actually need a link. Now, if you look at the SDF spec for some reason
(much like I did), you'll notice you don't really need much for a link to exist. And that is a
big fucking lie. Sure, go ahead, create a link without inertial or collision properties,
see how far that gets you (spoiler: it will get you fucking nowhere, because Gazebo will
devour your puny link and all that it stands for).
- sfalexrog
-->
<inertial>
<!-- We give an oh-so-tiny mass to the link to avoid it being destroyed by Gazebo -->
<mass value="0.015"/> <!-- [kg] -->
<origin xyz="0 0 0" rpy="0 0 0"/>
<!-- We also give it some inertia, so that the physics engine does not go crazy
(and to avoid it being devoured by Gazebo)
-->
<inertia
ixx="${1/12 * 0.015 * 0.01 * 0.01}"
iyy="${1/12 * 0.015 * 0.01 * 0.01}"
izz="${1/12 * 0.015 * 0.01 * 0.01}"
ixy="0.0" ixz="0.0" iyz="0.0"
/>
</inertial>
<!-- We may want to have proper collision tucked away here at some point, though -->
<collision>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<box size="0.01 0.01 0.01" /> <!-- [m] [m] [m] -->
</geometry>
</collision>
<visual>
<!-- This origin should be the same as in "base_link_inertia" visuals -->
<origin xyz="0 0 0.025" rpy="0 0 0"/>
<geometry>
<mesh filename="package://clover_description/meshes/clover4/clover_guards_transparent.dae"
scale="1 1 1"/>
</geometry>
<!-- Let me reiterate that you can't put the "material" tag to any effect -->
</visual>
</link>
<!-- Now, the last thing to do is to attach the base_guard_link to base_link. Since
base_guard_link has no meaning by itself, it must be fixed rigidly to base_link.
Except Gazebo absolutely loves "optimizing away" your fixed joints and all
child links with them! How do we deal with that nonsense? Read on to find out!
- sfalexrog
-->
<joint name="base_guard_joint" type="fixed">
<origin xyz="0 0 0" rpy="0 0 0"/>
<parent link="base_link" />
<child link="base_guard_link" />
</joint>
<!-- attach multirotor_base_plugin to the base_link -->
<gazebo>
<plugin filename="libgazebo_multirotor_base_plugin.so" name="rosbag">
<robotNamespace>${robot_namespace}</robotNamespace>
<linkName>base_link</linkName>
<rotorVelocitySlowdownSim>${rotor_velocity_slowdown_sim}</rotorVelocitySlowdownSim>
</plugin>
</gazebo>
<!-- And here's what you've been waiting for: the way to save a fixed joint from
Gazebo's wrath! You plead for it to save your joint, and it might answer
(depending on the version, apparently).
Note that it is done in other places as well.
- sfalexrog
-->
<gazebo reference="base_guard_joint">
<disableFixedJointLumping>true</disableFixedJointLumping>
<preserveFixedJoint>true</preserveFixedJoint>
</gazebo>
<!-- ...and here's another absolutely terrible idea: you can only set a material for
the whole link, not for its part. And that's incompatible with URDF's material
definition. Yeah.
- sfalexrog
-->
<gazebo reference="base_guard_link">
<material>Polycarbonate</material>
</gazebo>
<gazebo reference="base_link">
<kp>10000.0</kp>
<kd>10.0</kd>
<maxVel>10.0</maxVel>
<minDepth>0.001</minDepth>
</gazebo>
</xacro:macro>
<!-- Rotor joint and link, modified for Clover purposes -->
<xacro:macro name="vertical_clover_rotor"
params="robot_namespace suffix direction motor_constant moment_constant parent mass_rotor radius_rotor time_constant_up time_constant_down max_rot_velocity motor_number rotor_drag_coefficient rolling_moment_coefficient mesh *origin *inertia">
<joint name="rotor_${motor_number}_joint" type="continuous">
<xacro:insert_block name="origin" />
<axis xyz="0 0 1" />
<!-- TODO(ff): not currently set because it's not yet supported -->
<!-- <limit effort="2000" velocity="${max_rot_velocity}" /> -->
<parent link="${parent}" />
<child link="rotor_${motor_number}" />
</joint>
<!-- TODO(ff): not currently set because it's not yet supported -->
<!-- <gazebo reference="rotor_${motor_number}_joint"> <axis> <xyz>0 0 1</xyz>
<limit> <velocity> ${max_rot_velocity} </velocity> </limit> </axis> </gazebo> -->
<link name="rotor_${motor_number}">
<inertial>
<mass value="${mass_rotor}" /> <!-- [kg] -->
<xacro:insert_block name="inertia" />
</inertial>
<visual>
<geometry>
<mesh filename="package://clover_description/meshes/${mesh}"
scale="1 1 1" />
</geometry>
</visual>
<collision>
<geometry>
<cylinder length="0.005" radius="${radius_rotor}" /> <!-- [m] -->
</geometry>
</collision>
</link>
<gazebo>
<plugin name="${suffix}_motor_model" filename="libgazebo_motor_model.so">
<robotNamespace>${robot_namespace}</robotNamespace>
<jointName>rotor_${motor_number}_joint</jointName>
<linkName>rotor_${motor_number}</linkName>
<turningDirection>${direction}</turningDirection>
<timeConstantUp>${time_constant_up}</timeConstantUp>
<timeConstantDown>${time_constant_down}</timeConstantDown>
<maxRotVelocity>${max_rot_velocity}</maxRotVelocity>
<motorConstant>${motor_constant}</motorConstant>
<momentConstant>${moment_constant}</momentConstant>
<commandSubTopic>/gazebo/command/motor_speed</commandSubTopic>
<motorNumber>${motor_number}</motorNumber>
<rotorDragCoefficient>${rotor_drag_coefficient}</rotorDragCoefficient>
<rollingMomentCoefficient>${rolling_moment_coefficient}</rollingMomentCoefficient>
<motorSpeedPubTopic>/motor_speed/${motor_number}</motorSpeedPubTopic>
<rotorVelocitySlowdownSim>${rotor_velocity_slowdown_sim}</rotorVelocitySlowdownSim>
<!--
<gazebo_joint_control_pid>
<p>0.1</p>
<i>0</i>
<d>0</d>
<iMax>0</iMax>
<iMin>0</iMin>
<cmdMax>3</cmdMax>
<cmdMin>-3</cmdMin>
</gazebo_joint_control_pid>
-->
</plugin>
</gazebo>
<!-- <gazebo reference="rotor_${motor_number}">
<material>Gazebo/${color}</material>
</gazebo> -->
</xacro:macro>
</robot>

View File

@@ -0,0 +1,178 @@
<?xml version="1.0"?>
<!-- Typical physics values for the Clover drone.
NB: Right now the values are wrong, and taken from the 3DR Iris drone description.
This is subject to change as correct values are calculated.
-->
<robot name="iris" xmlns:xacro="http://ros.org/wiki/xacro">
<!-- Properties -->
<xacro:property name="namespace" value="" />
<xacro:property name="rotor_velocity_slowdown_sim" value="10" />
<xacro:if value="$(arg use_clover_physics)">
<xacro:property name="mass" value="0.7" /> <!-- [kg] -->
</xacro:if>
<xacro:unless value="$(arg use_clover_physics)">
<xacro:property name="mass" value="1.5" /> <!-- [kg] -->
</xacro:unless>
<xacro:property name="body_width" value="0.35" /> <!-- [m] -->
<xacro:property name="body_height" value="0.124" /> <!-- [m] -->
<xacro:property name="mass_rotor" value="0.01" /> <!-- [kg] -->
<xacro:property name="arm_length_front_x" value="0.083" /> <!-- [m] -->
<xacro:property name="arm_length_back_x" value="0.083" /> <!-- [m] -->
<xacro:property name="arm_length_front_y" value="0.083" /> <!-- [m] -->
<xacro:property name="arm_length_back_y" value="0.083" /> <!-- [m] -->
<xacro:property name="rotor_offset_top" value="0.026" /> <!-- [m] -->
<xacro:property name="radius_rotor" value="0.06" /> <!-- [m] -->
<xacro:property name="motor_constant" value="8.54858e-06" /> <!-- [kg.m/s^2] -->
<xacro:property name="moment_constant" value="0.06" /> <!-- [m] -->
<xacro:property name="time_constant_up" value="0.0125" /> <!-- [s] -->
<xacro:property name="time_constant_down" value="0.025" /> <!-- [s] -->
<xacro:property name="max_rot_velocity" value="1100" /> <!-- [rad/s] -->
<xacro:property name="sin30" value="0.5" />
<xacro:property name="cos30" value="0.866025403784" />
<xacro:property name="sqrt2" value="1.4142135623730951" />
<xacro:property name="rotor_drag_coefficient" value="1.75e-04" />
<xacro:property name="rolling_moment_coefficient" value="0.000001" />
<xacro:property name="color" value="$(arg visual_material)" />
<!-- Property Blocks -->
<!-- Clover body inertia -->
<!-- Values from CAD program -->
<xacro:if value="$(arg use_clover_physics)">
<xacro:property name="body_inertia">
<inertia
ixx="0.0347563"
iyy="0.0458929"
izz="0.0977"
ixy="0.0" ixz="0.0" iyz="0.0" /> <!-- [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] -->
</xacro:property>
</xacro:if>
<!-- Box-like inertia -->
<xacro:unless value="$(arg use_clover_physics)">
<xacro:property name="body_inertia">
<inertia
ixx="${1/12 * mass * (body_height * body_height + body_width * body_width)}"
iyy="${1/12 * mass * (body_height * body_height + body_width * body_width)}"
izz="${1/12 * mass * (body_width * body_width + body_width * body_width)}"
ixy="0.0" ixz="0.0" iyz="0.0" /> <!-- [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] -->
</xacro:property>
</xacro:unless>
<!-- inertia of a single rotor -->
<!-- Values from CAD program -->
<xacro:if value="$(arg use_clover_physics)">
<xacro:property name="rotor_inertia">
<inertia
ixx="${1.375 * 0.001 * 0.001 * rotor_velocity_slowdown_sim}"
iyy="${2.153 * 0.001 * 0.001 * rotor_velocity_slowdown_sim}"
izz="${1.375 * 0.001 * 0.001 * rotor_velocity_slowdown_sim}"
ixy="0.0" ixz="0.0" iyz="0.0" /> <!-- [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] -->
</xacro:property>
</xacro:if>
<!-- Values for a cuboid (cuboid. Height=3mm, width=15mm) -->
<xacro:unless value="$(arg use_clover_physics)">
<xacro:property name="rotor_inertia">
<inertia
ixx="${1/12 * mass_rotor * (0.015 * 0.015 + 0.003 * 0.003) * rotor_velocity_slowdown_sim}"
iyy="${1/12 * mass_rotor * (4 * radius_rotor * radius_rotor + 0.003 * 0.003) * rotor_velocity_slowdown_sim}"
izz="${1/12 * mass_rotor * (4 * radius_rotor * radius_rotor + 0.015 * 0.015) * rotor_velocity_slowdown_sim}"
ixy="0.0" ixz="0.0" iyz="0.0" /> <!-- [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] [kg.m^2] -->
</xacro:property>
</xacro:unless>
<!-- Included URDF Files -->
<xacro:include filename="$(find clover_description)/urdf/clover/clover4_macros.xacro" />
<!-- Instantiate multirotor_base_macro once -->
<xacro:clover4_base_macro
robot_namespace="${namespace}"
mass="${mass}"
body_width="${body_width}"
body_height="${body_height}"
>
<xacro:insert_block name="body_inertia" />
</xacro:clover4_base_macro>
<!-- Instantiate rotors -->
<xacro:vertical_clover_rotor
robot_namespace="${namespace}"
suffix="front_right"
direction="ccw"
motor_constant="${motor_constant}"
moment_constant="${moment_constant}"
parent="base_link"
mass_rotor="${mass_rotor}"
radius_rotor="${radius_rotor}"
time_constant_up="${time_constant_up}"
time_constant_down="${time_constant_down}"
max_rot_velocity="${max_rot_velocity}"
motor_number="0"
rotor_drag_coefficient="${rotor_drag_coefficient}"
rolling_moment_coefficient="${rolling_moment_coefficient}"
mesh="clover4/prop_ccw.dae">
<origin xyz="${arm_length_front_x} -${arm_length_front_y} ${rotor_offset_top}" rpy="0 0 0" />
<xacro:insert_block name="rotor_inertia" />
</xacro:vertical_clover_rotor>
<xacro:vertical_clover_rotor
robot_namespace="${namespace}"
suffix="back_left"
direction="ccw"
motor_constant="${motor_constant}"
moment_constant="${moment_constant}"
parent="base_link"
mass_rotor="${mass_rotor}"
radius_rotor="${radius_rotor}"
time_constant_up="${time_constant_up}"
time_constant_down="${time_constant_down}"
max_rot_velocity="${max_rot_velocity}"
motor_number="1"
rotor_drag_coefficient="${rotor_drag_coefficient}"
rolling_moment_coefficient="${rolling_moment_coefficient}"
mesh="clover4/prop_ccw.dae">
<origin xyz="-${arm_length_back_x} ${arm_length_back_y} ${rotor_offset_top}" rpy="0 0 0" />
<xacro:insert_block name="rotor_inertia" />
</xacro:vertical_clover_rotor>
<xacro:vertical_clover_rotor robot_namespace="${namespace}"
suffix="front_left"
direction="cw"
motor_constant="${motor_constant}"
moment_constant="${moment_constant}"
parent="base_link"
mass_rotor="${mass_rotor}"
radius_rotor="${radius_rotor}"
time_constant_up="${time_constant_up}"
time_constant_down="${time_constant_down}"
max_rot_velocity="${max_rot_velocity}"
motor_number="2"
rotor_drag_coefficient="${rotor_drag_coefficient}"
rolling_moment_coefficient="${rolling_moment_coefficient}"
mesh="clover4/prop_cw.dae">
<origin xyz="${arm_length_front_x} ${arm_length_front_y} ${rotor_offset_top}" rpy="0 0 0" />
<xacro:insert_block name="rotor_inertia" />
</xacro:vertical_clover_rotor>
<xacro:vertical_clover_rotor robot_namespace="${namespace}"
suffix="back_right"
direction="cw"
motor_constant="${motor_constant}"
moment_constant="${moment_constant}"
parent="base_link"
mass_rotor="${mass_rotor}"
radius_rotor="${radius_rotor}"
time_constant_up="${time_constant_up}"
time_constant_down="${time_constant_down}"
max_rot_velocity="${max_rot_velocity}"
motor_number="3"
rotor_drag_coefficient="${rotor_drag_coefficient}"
rolling_moment_coefficient="${rolling_moment_coefficient}"
mesh="clover4/prop_cw.dae">
<origin xyz="-${arm_length_back_x} -${arm_length_back_y} ${rotor_offset_top}" rpy="0 0 0" />
<xacro:insert_block name="rotor_inertia" />
</xacro:vertical_clover_rotor>
</robot>

View File

@@ -0,0 +1,613 @@
<?xml version="1.0"?>
<!--
Copyright 2015 Fadri Furrer, ASL, ETH Zurich, Switzerland
Copyright 2015 Michael Burri, ASL, ETH Zurich, Switzerland
Copyright 2015 Mina Kamel, ASL, ETH Zurich, Switzerland
Copyright 2015 Janosch Nikolic, ASL, ETH Zurich, Switzerland
Copyright 2015 Markus Achtelik, ASL, ETH Zurich, Switzerland
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<robot xmlns:xacro="http://ros.org/wiki/xacro">
<!-- Macro to add logging to a bag file. -->
<xacro:macro name="bag_plugin_macro"
params="namespace bag_file rotor_velocity_slowdown_sim">
<gazebo>
<plugin filename="librotors_gazebo_bag_plugin.so" name="rosbag">
<robotNamespace>${namespace}</robotNamespace>
<bagFileName>${bag_file}</bagFileName>
<linkName>base_link</linkName>
<frameId>base_link</frameId>
<commandAttitudeThrustSubTopic>command/attitude</commandAttitudeThrustSubTopic>
<commandAttitudeThrustPubTopic>command/attitude</commandAttitudeThrustPubTopic>
<rotorVelocitySlowdownSim>${rotor_velocity_slowdown_sim}</rotorVelocitySlowdownSim>
</plugin>
</gazebo>
</xacro:macro>
<!-- Macro to add a camera. -->
<xacro:macro name="camera_macro"
params="namespace parent_link camera_suffix frame_rate
horizontal_fov image_width image_height image_format min_distance
max_distance noise_mean noise_stddev enable_visual *cylinder *origin">
<link name="${namespace}/camera_${camera_suffix}_link">
<collision>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<xacro:insert_block name="cylinder" />
</geometry>
</collision>
<xacro:if value="${enable_visual}">
<visual>
<origin xyz="0 0 0" rpy="0 1.57079632679 0" />
<geometry>
<xacro:insert_block name="cylinder" />
</geometry>
<material name="red" />
</visual>
</xacro:if>
<inertial>
<mass value="1e-5" />
<origin xyz="0 0 0" rpy="0 0 0" />
<inertia ixx="1e-6" ixy="0" ixz="0" iyy="1e-6" iyz="0" izz="1e-6" />
</inertial>
</link>
<joint name="${namespace}/camera_${camera_suffix}_joint" type="fixed">
<xacro:insert_block name="origin" />
<parent link="${parent_link}" />
<child link="${namespace}/camera_${camera_suffix}_link" />
</joint>
<gazebo reference="${namespace}/camera_${camera_suffix}_link">
<sensor type="camera" name="${namespace}_camera_${camera_suffix}">
<update_rate>${frame_rate}</update_rate>
<camera name="head">
<horizontal_fov>${horizontal_fov}</horizontal_fov>
<image>
<width>${image_width}</width>
<height>${image_height}</height>
<format>${image_format}</format>
</image>
<clip>
<near>${min_distance}</near>
<far>${max_distance}</far>
</clip>
<noise>
<type>gaussian</type>
<!-- Noise is sampled independently per pixel on each frame.
That pixel's noise value is added to each of its color
channels, which at that point lie in the range [0,1]. -->
<mean>${noise_mean}</mean>
<stddev>${noise_stddev}</stddev>
</noise>
</camera>
<plugin name="${namespace}_camera_${camera_suffix}_controller" filename="libgazebo_ros_camera.so">
<robotNamespace>${namespace}</robotNamespace>
<alwaysOn>true</alwaysOn>
<updateRate>${frame_rate}</updateRate>
<cameraName>camera_${camera_suffix}</cameraName>
<imageTopicName>image_raw</imageTopicName>
<cameraInfoTopicName>camera_info</cameraInfoTopicName>
<frameName>camera_${camera_suffix}_link</frameName>
<hackBaseline>0.0</hackBaseline>
<distortionK1>0.0</distortionK1>
<distortionK2>0.0</distortionK2>
<distortionK3>0.0</distortionK3>
<distortionT1>0.0</distortionT1>
<distortionT2>0.0</distortionT2>
</plugin>
</sensor>
</gazebo>
</xacro:macro>
<!-- Macro to add the controller interface. -->
<xacro:macro name="controller_plugin_macro" params="namespace imu_sub_topic">
<gazebo>
<plugin name="controller_interface" filename="libgazebo_controller_interface.so">
<robotNamespace>${namespace}</robotNamespace>
<commandAttitudeThrustSubTopic>/command/attitude</commandAttitudeThrustSubTopic>
<commandRateThrustSubTopic>/command/rate</commandRateThrustSubTopic>
<commandMotorSpeedSubTopic>/command/motor_speed</commandMotorSpeedSubTopic>
<imuSubTopic>/${imu_sub_topic}</imuSubTopic>
<motorSpeedCommandPubTopic>/gazebo/command/motor_speed</motorSpeedCommandPubTopic>
</plugin>
</gazebo>
</xacro:macro>
<!-- Macro to add the gps_plugin. -->
<xacro:macro name="gps_plugin_macro" params="namespace gps_noise">
<gazebo>
<plugin name="gps_plugin" filename="libgazebo_gps_plugin.so">
<robotNamespace>${namespace}</robotNamespace>
<gpsNoise>${gps_noise}</gpsNoise>
</plugin>
</gazebo>
</xacro:macro>
<!-- Macro to add the magnetometer_plugin. -->
<xacro:macro name="magnetometer_plugin_macro" params="namespace pub_rate noise_density random_walk bias_correlation_time mag_topic">
<gazebo>
<plugin name="magnetometer_plugin" filename="libgazebo_magnetometer_plugin.so">
<robotNamespace>${namespace}</robotNamespace>
<pubRate>${pub_rate}</pubRate>
<noiseDensity>${noise_density}</noiseDensity>
<randomWalk>${random_walk}</randomWalk>
<biasCorrelationTime>${bias_correlation_time}</biasCorrelationTime>
<magTopic>${mag_topic}</magTopic>
</plugin>
</gazebo>
</xacro:macro>
<!-- Macro to add the barometer_plugin. -->
<xacro:macro name="barometer_plugin_macro" params="namespace pub_rate baro_topic">
<gazebo>
<plugin name="barometer_plugin" filename="libgazebo_barometer_plugin.so">
<robotNamespace>${namespace}</robotNamespace>
<pubRate>${pub_rate}</pubRate>
<baroTopic>${baro_topic}</baroTopic>
</plugin>
</gazebo>
</xacro:macro>
<!-- Macro to add the mavlink interface. -->
<xacro:macro name="mavlink_interface_macro" params="namespace imu_sub_topic gps_sub_topic mag_sub_topic baro_sub_topic mavlink_addr mavlink_udp_port mavlink_tcp_port serial_enabled serial_device baudrate qgc_addr qgc_udp_port sdk_addr sdk_udp_port hil_mode hil_state_level vehicle_is_tailsitter send_vision_estimation send_odometry enable_lockstep use_tcp">
<gazebo>
<plugin name="mavlink_interface" filename="libgazebo_mavlink_interface.so">
<robotNamespace>${namespace}</robotNamespace>
<imuSubTopic>${imu_sub_topic}</imuSubTopic>
<gpsSubTopic>${gps_sub_topic}</gpsSubTopic>
<magSubTopic>${mag_sub_topic}</magSubTopic>
<baroSubTopic>${baro_sub_topic}</baroSubTopic>
<mavlink_addr>$(arg mavlink_addr)</mavlink_addr>
<mavlink_udp_port>$(arg mavlink_udp_port)</mavlink_udp_port>
<mavlink_tcp_port>$(arg mavlink_tcp_port)</mavlink_tcp_port>
<serialEnabled>$(arg serial_enabled)</serialEnabled>
<serialDevice>$(arg serial_device)</serialDevice>
<baudRate>$(arg baudrate)</baudRate>
<qgc_addr>$(arg qgc_addr)</qgc_addr>
<qgc_udp_port>$(arg qgc_udp_port)</qgc_udp_port>
<sdk_addr>$(arg sdk_addr)</sdk_addr>
<sdk_udp_port>$(arg sdk_udp_port)</sdk_udp_port>
<hil_mode>$(arg hil_mode)</hil_mode>
<hil_state_level>$(arg hil_state_level)</hil_state_level>
<vehicle_is_tailsitter>$(arg vehicle_is_tailsitter)</vehicle_is_tailsitter>
<send_vision_estimation>$(arg send_vision_estimation)</send_vision_estimation>
<send_odometry>$(arg send_odometry)</send_odometry>
<enable_lockstep>$(arg enable_lockstep)</enable_lockstep>
<use_tcp>$(arg use_tcp)</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>
<!-- joint limits [-0.524, 0.524] -->
<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>
<!-- joint limits [-0.524, 0.524] -->
<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>
</gazebo>
</xacro:macro>
<!-- Macro to add an IMU. -->
<xacro:macro name="imu_plugin_macro"
params="namespace imu_suffix parent_link imu_topic
mass_imu_sensor gyroscope_noise_density gyroscopoe_random_walk
gyroscope_bias_correlation_time gyroscope_turn_on_bias_sigma
accelerometer_noise_density accelerometer_random_walk
accelerometer_bias_correlation_time accelerometer_turn_on_bias_sigma
*inertia *origin">
<!-- IMU link -->
<link name="${namespace}/imu${imu_suffix}_link">
<inertial>
<xacro:insert_block name="inertia" />
<mass value="${mass_imu_sensor}" /> <!-- [kg] -->
<origin xyz="0 0 0" rpy="0 0 0" />
</inertial>
</link>
<!-- IMU joint -->
<!-- For some reason, PX4 (and RotorS before them) used a "revolute" joint. Since IMU should be fixed relative to the drone frame, I'm changing this to "fixed", we'll see how that goes. -sfalexrog -->
<joint name="${namespace}/imu${imu_suffix}_joint" type="fixed">
<xacro:insert_block name="origin" />
<parent link="${parent_link}" />
<child link="${namespace}/imu${imu_suffix}_link" />
<!-- <limit upper="0" lower="0" effort="0" velocity="0" /> -->
</joint>
<!-- Well, now I know why: because Gazebo helpfully tries to "lump together" all fixed joints. Apparently there's a way to disable this behavior that is implemented here in its entirety: -->
<gazebo reference="${namespace}/imu${imu_suffix}_joint">
<disableFixedJointLumping>true</disableFixedJointLumping>
<preserveFixedJoint>true</preserveFixedJoint>
</gazebo>
<gazebo>
<plugin filename="libgazebo_imu_plugin.so" name="rotors_gazebo_imu${imu_suffix}_plugin">
<!-- A good description of the IMU parameters can be found in the kalibr documentation:
https://github.com/ethz-asl/kalibr/wiki/IMU-Noise-Model-and-Intrinsics -->
<robotNamespace>${namespace}</robotNamespace> <!-- (string, required): ros namespace in which the messages are published -->
<linkName>${namespace}/imu${imu_suffix}_link</linkName> <!-- (string, required): name of the body which holds the IMU sensor -->
<imuTopic>${imu_topic}</imuTopic> <!-- (string): name of the sensor output topic and prefix of service names (defaults to imu) -->
<gyroscopeNoiseDensity>${gyroscope_noise_density}</gyroscopeNoiseDensity> <!-- Gyroscope noise density (two-sided spectrum) [rad/s/sqrt(Hz)] -->
<gyroscopeRandomWalk>${gyroscopoe_random_walk}</gyroscopeRandomWalk> <!-- Gyroscope bias random walk [rad/s/s/sqrt(Hz)] -->
<gyroscopeBiasCorrelationTime>${gyroscope_bias_correlation_time}</gyroscopeBiasCorrelationTime> <!-- Gyroscope bias correlation time constant [s] -->
<gyroscopeTurnOnBiasSigma>${gyroscope_turn_on_bias_sigma}</gyroscopeTurnOnBiasSigma> <!-- Gyroscope turn on bias standard deviation [rad/s] -->
<accelerometerNoiseDensity>${accelerometer_noise_density}</accelerometerNoiseDensity> <!-- Accelerometer noise density (two-sided spectrum) [m/s^2/sqrt(Hz)] -->
<accelerometerRandomWalk>${accelerometer_random_walk}</accelerometerRandomWalk> <!-- Accelerometer bias random walk. [m/s^2/s/sqrt(Hz)] -->
<accelerometerBiasCorrelationTime>${accelerometer_bias_correlation_time}</accelerometerBiasCorrelationTime> <!-- Accelerometer bias correlation time constant [s] -->
<accelerometerTurnOnBiasSigma>${accelerometer_turn_on_bias_sigma}</accelerometerTurnOnBiasSigma> <!-- Accelerometer turn on bias standard deviation [m/s^2] -->
</plugin>
</gazebo>
</xacro:macro>
<!-- Macro to add a generic odometry sensor. -->
<xacro:macro name="odometry_plugin_macro"
params="
namespace odometry_sensor_suffix parent_link pose_topic pose_with_covariance_topic
position_topic transform_topic odometry_topic parent_frame_id
mass_odometry_sensor measurement_divisor measurement_delay unknown_delay
noise_normal_position noise_normal_quaternion noise_normal_linear_velocity
noise_normal_angular_velocity noise_uniform_position
noise_uniform_quaternion noise_uniform_linear_velocity
noise_uniform_angular_velocity enable_odometry_map odometry_map
image_scale *inertia *origin">
<!-- odometry link -->
<link name="${namespace}/odometry_sensor${odometry_sensor_suffix}_link">
<inertial>
<xacro:insert_block name="inertia" />
<mass value="${mass_odometry_sensor}" /> <!-- [kg] -->
</inertial>
</link>
<!-- odometry joint -->
<joint name="${namespace}/odometry_sensor${odometry_sensor_suffix}_joint" type="revolute">
<parent link="${parent_link}" />
<xacro:insert_block name="origin" />
<child link="${namespace}/odometry_sensor${odometry_sensor_suffix}_link" />
<limit upper="0" lower="0" effort="0" velocity="0" />
</joint>
<gazebo>
<plugin filename="librotors_gazebo_odometry_plugin.so" name="odometry_sensor${odometry_sensor_suffix}">
<linkName>${namespace}/odometry_sensor${odometry_sensor_suffix}_link</linkName>
<robotNamespace>${namespace}</robotNamespace>
<poseTopic>${pose_topic}</poseTopic>
<poseWithCovarianceTopic>${pose_with_covariance_topic}</poseWithCovarianceTopic>
<positionTopic>${position_topic}</positionTopic>
<transformTopic>${transform_topic}</transformTopic>
<odometryTopic>${odometry_topic}</odometryTopic>
<parentFrameId>${parent_frame_id}</parentFrameId> <!-- Use the scoped link name here. e.g. Model::link. -->
<measurementDivisor>${measurement_divisor}</measurementDivisor> <!-- only every (seq % measurementDivisor) == 0 measurement is published [int] -->
<measurementDelay>${measurement_delay}</measurementDelay> <!-- time that measurement gets held back before it's published in [simulation cycles (int)] -->
<unknownDelay>${unknown_delay}</unknownDelay> <!-- additional delay, that just gets added to the timestamp [s] -->
<noiseNormalPosition>${noise_normal_position}</noiseNormalPosition> <!-- standard deviation of additive white gaussian noise [m] -->
<noiseNormalQuaternion>${noise_normal_quaternion}</noiseNormalQuaternion> <!-- standard deviation white gaussian noise [rad]: q_m = q*quaternionFromSmallAngleApproximation(noiseNormalQ) -->
<noiseNormalLinearVelocity>${noise_normal_linear_velocity}</noiseNormalLinearVelocity> <!-- standard deviation of additive white gaussian noise [m/s] -->
<noiseNormalAngularVelocity>${noise_normal_angular_velocity}</noiseNormalAngularVelocity> <!-- standard deviation of additive white gaussian noise [rad/s] -->
<noiseUniformPosition>${noise_uniform_position}</noiseUniformPosition> <!-- symmetric bounds of uniform noise [m] -->
<noiseUniformQuaternion>${noise_uniform_quaternion}</noiseUniformQuaternion> <!-- symmetric bounds of uniform noise [rad], computation see above -->
<noiseUniformLinearVelocity>${noise_uniform_linear_velocity}</noiseUniformLinearVelocity> <!-- symmetric bounds of uniform noise [m/s] -->
<noiseUniformAngularVelocity>${noise_uniform_angular_velocity}</noiseUniformAngularVelocity> <!-- symmetric bounds of uniform noise [rad/s] -->
<xacro:if value="${enable_odometry_map}">
<covarianceImage>package://rotors_gazebo/resource/${odometry_map}</covarianceImage>
<covarianceImageScale>${image_scale}</covarianceImageScale>
</xacro:if>
</plugin>
</gazebo>
</xacro:macro>
<!-- Macro to add the wind plugin. -->
<xacro:macro name="wind_plugin_macro"
params="namespace xyz_offset wind_direction wind_force_mean
wind_gust_direction wind_gust_duration wind_gust_start
wind_gust_force_mean">
<gazebo>
<plugin filename="libgazebo_wind_plugin.so" name="wind_plugin">
<frameId>base_link</frameId>
<linkName>base_link</linkName>
<robotNamespace>${namespace}</robotNamespace>
<xyzOffset>${xyz_offset}</xyzOffset> <!-- [m] [m] [m] -->
<windDirection>${wind_direction}</windDirection>
<windForceMean>${wind_force_mean}</windForceMean> <!-- [N] -->
<windGustDirection>${wind_gust_direction}</windGustDirection>
<windGustDuration>${wind_gust_duration}</windGustDuration> <!-- [s] -->
<windGustStart>${wind_gust_start}</windGustStart> <!-- [s] -->
<windGustForceMean>${wind_gust_force_mean}</windGustForceMean> <!-- [N] -->
</plugin>
</gazebo>
</xacro:macro>
<!-- VI sensor macros -->
<!-- Macro to add a VI-sensor camera. -->
<xacro:macro name="vi_sensor_camera_macro"
params="namespace parent_link camera_suffix frame_rate *origin">
<xacro:camera_macro
namespace="${namespace}"
parent_link="${parent_link}"
camera_suffix="${camera_suffix}"
frame_rate="${frame_rate}"
horizontal_fov="1.3962634"
image_width="752"
image_height="480"
image_format="L8"
min_distance="0.02"
max_distance="30"
noise_mean="0.0"
noise_stddev="0.007"
enable_visual="false">
<cylinder length="0.01" radius="0.007" />
<xacro:insert_block name="origin" />
</xacro:camera_macro>
</xacro:macro>
<!-- Macro to add a depth camera on the VI-sensor. -->
<xacro:macro name="vi_sensor_depth_macro"
params="namespace parent_link camera_suffix frame_rate *origin">
<link name="${namespace}/camera_${camera_suffix}_link">
<collision>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<cylinder length="0.01" radius="0.007" />
</geometry>
</collision>
<inertial>
<mass value="1e-5" />
<origin xyz="0 0 0" rpy="0 0 0" />
<inertia ixx="1e-6" ixy="0" ixz="0" iyy="1e-6" iyz="0" izz="1e-6" />
</inertial>
</link>
<joint name="${namespace}/camera_${camera_suffix}_joint" type="fixed">
<xacro:insert_block name="origin" />
<parent link="${parent_link}" />
<child link="${namespace}/camera_${camera_suffix}_link" />
<limit upper="0" lower="0" effort="0" velocity="0" />
</joint>
<!-- Optical center of camera -->
<link name="${namespace}/camera_${camera_suffix}_optical_center_link" />
<joint name="${namespace}/camera_${camera_suffix}_optical_center_joint" type="fixed">
<origin xyz="0 0 0" rpy="${-PI/2} 0 ${-PI/2}" />
<parent link="${namespace}/camera_${camera_suffix}_link" />
<child link="${namespace}/camera_${camera_suffix}_optical_center_link" />
<limit upper="0" lower="0" effort="0" velocity="0" />
</joint>
<gazebo reference="${namespace}/camera_${camera_suffix}_link">
<sensor type="depth" name="${namespace}_camera_{camera_suffix}">
<always_on>true</always_on>
<update_rate>${frame_rate}</update_rate>
<camera>
<horizontal_fov>2</horizontal_fov>
<image>
<format>L8</format>
<width>640</width>
<height>480</height>
</image>
<clip>
<near>0.01</near>
<far>100</far>
</clip>
</camera>
<plugin name="${namespace}_camera_{camera_suffix}" filename="libgazebo_ros_openni_kinect.so">
<robotNamespace>${namespace}</robotNamespace>
<alwaysOn>true</alwaysOn>
<baseline>0.11</baseline>
<updateRate>${frame_rate}</updateRate>
<cameraName>camera_${camera_suffix}</cameraName>
<imageTopicName>camera/image_raw</imageTopicName>
<cameraInfoTopicName>camera/camera_info</cameraInfoTopicName>
<depthImageTopicName>depth/disparity</depthImageTopicName>
<depthImageCameraInfoTopicName>depth/camera_info</depthImageCameraInfoTopicName>
<pointCloudTopicName>depth/points</pointCloudTopicName>
<frameName>camera_${camera_suffix}_optical_center_link</frameName>
<pointCloudCutoff>0.5</pointCloudCutoff>
<distortionK1>0.0</distortionK1>
<distortionK2>0.0</distortionK2>
<distortionK3>0.0</distortionK3>
<distortionT1>0.0</distortionT1>
<distortionT2>0.0</distortionT2>
</plugin>
</sensor>
</gazebo>
</xacro:macro>
<!-- VI-Sensor Macro -->
<xacro:macro name="vi_sensor_macro" params="namespace parent_link *origin">
<!-- Vi Sensor Link -->
<link name="${namespace}/vi_sensor_link">
<collision>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<box size="0.03 0.133 0.057" />
</geometry>
</collision>
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<mesh filename="package://rotors_description/meshes/vi_sensor.dae" scale="1 1 1" />
</geometry>
</visual>
<inertial>
<mass value="0.13" />
<origin xyz="0 0 0" rpy="0 0 0" />
<inertia ixx="1e-6" ixy="0" ixz="0" iyy="1e-6" iyz="0" izz="1e-6" />
</inertial>
</link>
<joint name="{namespace}_vi_sensor_joint" type="fixed">
<xacro:insert_block name="origin" />
<parent link="${parent_link}" />
<child link="${namespace}/vi_sensor_link" />
</joint>
<!-- Cameras -->
<xacro:if value="$(arg enable_cameras)">
<!-- Left Camera -->
<xacro:vi_sensor_camera_macro
namespace="${namespace}" parent_link="${namespace}/vi_sensor_link"
camera_suffix="left" frame_rate="30.0">
<origin xyz="0.015 0.055 0.0065" rpy="0 0 0" />
</xacro:vi_sensor_camera_macro>
<!-- Right Camera -->
<xacro:vi_sensor_camera_macro namespace="${namespace}"
parent_link="${namespace}/vi_sensor_link"
camera_suffix="right" frame_rate="30.0">
<origin xyz="0.015 -0.055 0.0065" rpy="0 0 0" />
</xacro:vi_sensor_camera_macro>
</xacro:if>
<!-- Depth Sensor -->
<xacro:if value="$(arg enable_depth)">
<xacro:vi_sensor_depth_macro
namespace="${namespace}" parent_link="${namespace}/vi_sensor_link"
camera_suffix="depth" frame_rate="30.0">
<origin xyz="0.015 0.055 0.0065" rpy="0 0 0" />
</xacro:vi_sensor_depth_macro>
</xacro:if>
<!-- Groundtruth -->
<xacro:if value="$(arg enable_ground_truth)">
<!-- Odometry Sensor -->
<xacro:odometry_plugin_macro
namespace="${namespace}/ground_truth"
odometry_sensor_suffix=""
parent_link="${namespace}/vi_sensor_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>
<!-- ADIS16448 IMU. -->
<xacro:imu_plugin_macro
namespace="${namespace}"
imu_suffix=""
parent_link="${namespace}/vi_sensor_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.015 0 0.0113" rpy="0 0 0" />
</xacro:imu_plugin_macro>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<robot name="led_strip" xmlns:xacro="http://ros.org/wiki/xacro">
<!-- LED strip macro
Parameters:
name: Name of the LED strip
parent: Name of the parent link (likely to be base_link)
radius: Radius of the strip
bulb_radius: Radius (for spheres)/side (for boxes) of the LED
led_count: Number of LEDs in a strip
use_plugin: Attach the controller plugin to LEDs
-->
<xacro:macro name="led_strip" params="name parent radius:=0.08 bulb_radius:=0.005 led_count:=58 shift:=0 use_plugin:=false x:=0 y:=0 z:=0 roll:=0 pitch:=0 yaw:=0">
<joint name="${name}_joint" type="fixed">
<origin xyz="${x} ${y} ${z}"
rpy="${roll} ${pitch} ${yaw}"/>
<parent link="${parent}"/>
<child link="${name}_link"/>
</joint>
<link name="${name}_link">
<inertial>
<origin xyz="0 0 0"/>
<mass value="0.0001"/>
<inertia ixx="0.001" ixy="0.0" ixz="0.0" iyy="0.001" iyz="0.0" izz="0.001"/>
</inertial>
<xacro:macro name="loop_links" params="link_i links_total">
<visual>
<origin xyz="${radius*cos(2 * pi * link_i / links_total)} ${radius*sin(2 * pi * link_i / links_total)} 0"
rpy="0 0 ${2 * pi * link_i / links_total}"/>
<geometry>
<box size="${bulb_radius} ${bulb_radius} ${bulb_radius}"/>
</geometry>
</visual>
<xacro:unless value="${link_i + 1 == links_total}">
<xacro:loop_links link_i="${link_i + 1}" links_total="${links_total}"/>
</xacro:unless>
</xacro:macro>
<xacro:loop_links link_i="0" links_total="${led_count}"/>
</link>
<xacro:if value="${use_plugin}">
<gazebo reference="${name}_link">
<visual>
<plugin name="${name}_controller" filename="libsim_leds.so">
<robotNamespace></robotNamespace>
<ledCount>${led_count}</ledCount>
</plugin>
<cast_shadows>false</cast_shadows>
<material>
<ambient>0 0 0 1</ambient>
<diffuse>0 0 0 1</diffuse>
<specular>0 0 0 1</specular>
<emissive>0 0 0 1</emissive>
</material>
</visual>
</gazebo>
</xacro:if>
<gazebo reference="${name}_joint">
<disableFixedJointLumping>true</disableFixedJointLumping>
<preserveFixedJoint>true</preserveFixedJoint>
</gazebo>
<!-- <gazebo>
<static>true</static>
</gazebo> -->
</xacro:macro>
</robot>

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/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">
<joint name="${name}_joint" type="fixed">
<origin xyz="${x} ${y} ${z}"
rpy="${roll} ${pitch} ${yaw}"/>
<parent link="${parent}"/>
<child link="${name}_link"/>
</joint>
<link name="${name}_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.001 0.005 0.005"/>
</geometry>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.001 0.001 0.005"/>
</geometry>
</collision>
<inertial>
<mass value="0.015"/>
<origin xyz="0 0 0"/>
<inertia ixx="4.15e-6" iyy="2.407e-6" izz="2.407e-6" ixy="0" ixz="0" iyz="0"/>
</inertial>
</link>
<gazebo reference="${name}_joint">
<disableFixedJointLumping>true</disableFixedJointLumping>
<preserveFixedJoint>true</preserveFixedJoint>
</gazebo>
<gazebo reference="${name}_link">
<sensor type="ray" name="${name}">
<pose>0 0 0 0 0 0</pose>
<ray>
<scan>
<horizontal>
<samples>1</samples>
<resolution>1</resolution>
<min_angle>-0</min_angle>
<max_angle>0</max_angle>
</horizontal>
</scan>
<range>
<min>0.03</min> <!-- Note: This is a hack, since the rangefinder is inside the drone collision box -->
<max>${range_max}</max>
<resolution>${resolution}</resolution>
</range>
</ray>
<plugin name="laser" filename="libgazebo_ros_range.so">
<robotNamespace></robotNamespace> <!-- FIXME: fill namespace? -->
<topicName>/rangefinder/range</topicName>
<frameName>rangefinder</frameName>
<radiation>infrared</radiation>
<fov>0.01</fov>
<gaussianNoise>0.001</gaussianNoise>
<updateRate>${rate}</updateRate>
<min_distance>${range_min}</min_distance>
<max_distance>${range_max}</max_distance>
</plugin>
<always_on>1</always_on>
<update_rate>${rate}</update_rate>
<visualize>true</visualize>
</sensor>
</gazebo>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<robot name="pinhole_camera" xmlns:xacro="http://ros.org/wiki/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}"/>
</robot>

View File

@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<robot name="rpi_camera" xmlns:xacro="http://ros.org/wiki/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">
<joint name="${name}_joint" type="fixed">
<origin xyz="${x} ${y} ${z}"
rpy="${roll} ${pitch} ${yaw}"/>
<parent link="${parent}"/>
<child link="${name}_link"/>
</joint>
<link name="${name}_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.005 0.005 0.005"/>
</geometry>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.005 0.005 0.005"/>
</geometry>
</collision>
<inertial>
<mass value="0.015"/>
<origin xyz="0 0 0"/>
<inertia ixx="4.15e-6" iyy="2.407e-6" izz="2.407e-6" ixy="0" ixz="0" iyz="0"/>
</inertial>
</link>
<gazebo reference="${name}_joint">
<disableFixedJointLumping>true</disableFixedJointLumping>
<preserveFixedJoint>true</preserveFixedJoint>
</gazebo>
<!-- <xacro:camera_sensor_base name="${name}"
frame_name="/${name}_optical"
width="${width}"
height="${height}"
rate="${rate}"
horizontal_fov="1.6075201319067056"
k1="-0.273377"
k2="0.0642871"
p1="-0.00086158"
p2="-0.000443529"
k3="-0.00599387"
cx="158.0735"
cy="108.513"
cx_prime="158.0735"
/> -->
<xacro:camera_sensor name="${name}"
width="${width}"
height="${height}"
rate="${rate}"
horizontal_fov="${120 * pi / 180}"
do_throttling="${do_throttling}"
/>
</xacro:macro>
</robot>

View File

@@ -0,0 +1,60 @@
cmake_minimum_required(VERSION 3.0)
project(clover_simulation)
find_package(catkin REQUIRED)
# Gazebo LED strip node
find_package(gazebo)
# Gazebo does not seem to export GAZEBO_FOUND, so here's a matching hack
if (NOT "${GAZEBO_CONFIG_INCLUDED}")
message(STATUS "Gazebo not found, skipping")
return()
endif()
find_package(catkin REQUIRED COMPONENTS
roscpp
led_msgs
gazebo_ros
gazebo_plugins
rospy
)
catkin_python_setup()
catkin_package(
CATKIN_DEPENDS led_msgs gazebo_ros gazebo_plugins
)
# Gazebo LED plugin
add_library(sim_leds src/sim_leds.cpp)
target_include_directories(sim_leds PRIVATE ${catkin_INCLUDE_DIRS} ${GAZEBO_INCLUDE_DIRS})
target_link_libraries(sim_leds PRIVATE ${catkin_LIBRARIES} ${GAZEBO_LIBRARIES})
target_compile_options(sim_leds PRIVATE -std=c++11)
add_dependencies(sim_leds ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
# Gazebo throttling camera plugin
# for some reason, CMake does not support per-target link directories, and Gazebo does not put
# CameraPlugin into ${GAZEBO_LIBRARIES}
link_directories(${GAZEBO_LIBRARY_DIRS})
add_library(throttling_camera src/throttling_camera.cpp)
target_include_directories(throttling_camera PRIVATE ${catkin_INCLUDE_DIRS} ${GAZEBO_INCLUDE_DIRS})
target_link_libraries(throttling_camera PRIVATE ${catkin_LIBRARIES} ${GAZEBO_LIBRARIES} CameraPlugin)
target_compile_options(throttling_camera PRIVATE -std=c++11)
add_dependencies(throttling_camera ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
install(DIRECTORY launch DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
install(DIRECTORY meshes DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
install(DIRECTORY resources DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION})
catkin_install_python(PROGRAMS scripts/aruco_gen
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)

View File

@@ -0,0 +1,82 @@
# `clover_simulation` ROS package
This package provides resources necessary for launching Gazebo simulation with Clover, along with `.launch` files for convenience.
## Launching the simulation
Simulation is launched by [`simulator.launch` file](launch/simulator.launch). This `.launch` file assumes that `px4` and `sitl_gazebo` packages are reachable from your current workspace.
The simulation may be configured by a set of arguments:
* `mav_id` (*integer*, default: *0*) - MAVLink identifier of the vehicle. **Note**: Multi-vehicle simulation is possible, but requires extensive changes to launch files;
* `est` (*string*, default: *lpe*, possible values: *lpe*, *ekf2*) - PX4 estimator selection. Note that this may be overriden in the startup scripts for your craft;
* `vehicle` (*string*, default: *clover*) - PX4 vehicle name. Depending on this parameter, different PX4 presets will be loaded. **Note**: The default value, *clover*, requires you to use [Clover-specific PX4 branch](https://github.com/CopterExpress/Firmware/tree/v1.10.1-clever);
* `main_camera` (*boolean*, default: *true*) - controls whether the drone will have a vision position estimation camera;
* `rangefinder` (*boolean*, default: *true*) - controls whether the drone will have a laser rangefinder;
* `led` (*boolean*, default: *true*) - controls whether the drone will have a programmable LED strip;
* `gps` (*boolean*, default: *true*) - controls whether the drone will have a simulated GPS module;
In order to start the simulation, run:
```bash
roslaunch clover_simulation simulator.launch
```
This will start a new Gazebo instance (using `gazebo_ros` package), load a PX4 SITL instance, spawn a Clover model and start Clover ROS nodes. The PX4 console will be accessible in the terminal where `roslaunch` was performed.
### Changing simulation speed (PX4 1.9+)
In order to run simulation faster or slower than realtime, use the `PX4_SIM_SPEED_FACTOR` environment variable, [as stated in the PX4 docs](https://dev.px4.io/v1.9.0/en/simulation/#simulation_speed).
If `PX4_SIM_SPEED_FACTOR` is not set, it is assumed that it is equal to 1.0.
Note that Gazebo may slow the simulation down automatically. This may not be handled gracefully, so if you notice Gazebo's "Real Time Factor" being significantly lower than your `PX4_SIM_SPEED_FACTOR`, be sure to adjust it accordingly.
### Changing initial world
By default, the `simulator.launch` file will start the simulation with [`resources/worlds/clover.world`](resources/worlds/clover.world) as its base world. Note that the `real_time_update_rate` is set to 250 - this is required for PX4 lockstep simulation to work correctly.
If you wish to create your own world for the simulation, be sure to derive it from `clover.world` to avoid issues with PX4 plugins.
You may set the world name in `simulator.launch` as the `world_name` parameter for `gazebo_ros` instance.
### Configuring the vehicle
`simulator.launch` utilizes the same `clover.launch` file from the `clover` ROS package, so ROS node reconfiguration is the same as on the real drone.
PX4 may be reconfigured using QGroundControl, just like a real drone. Some parameters may require rebooting the drone, which is performed by shutting the simulated environment down and restarting it.
PX4 will write its parameters and logs to `${ROS_HOME}/eeprom/parameters` and `${ROS_HOME}/log`, respectively. Note that the log directory naming schema for PX4 logs is different from ROS: PX4 creates log directories based on the current date, which makes them relatively simple to find.
## LED plugin (sim_leds)
A visual Gazebo plugin is used for the LED strip. An example of the plugin usage is provided in [`led_strip.xacro`](../clover_description/urdf/leds/led_strip.xacro).
The plugin accepts the following parameters during instantiation:
* `robotNamespace` (*string*, default: "") - a ROS namespace for the plugin;
* `ledCount` (*integer*, required) - total numer of LEDs in a strip.
The plugin will provide the following service:
`led/set_leds` ([*led_msgs/SetLEDs*](https://github.com/CopterExpress/ros_led/blob/v0.0.6/led_msgs/srv/SetLEDs.srv)) - set the LED colors to the provided values.
The plugin will provide the following topics:
`led/state` ([*led_msgs/LEDStateArray*](https://github.com/CopterExpress/ros_led/blob/v0.0.6/led_msgs/msg/LEDStateArray.msg)) - current LED strip state.
Other nodes are not expected to write to `led/state` topic.
All provided topics and services will be namespaced according to the `robotNamespace` parameter.
## Throttling camera plugin (throttling_camera)
By default, Gazebo camera sensors will use their `update_rate` parameter as an upper bound for the actual rate. This may result in much lower rates than expected. This may be fine for object recognition tasks where the camera is not the primary positioning sensor, but is not desirable in our case, when the camera is used for position calculation.
We provide a Gazebo-ROS plugin for the camera sensor that will throttle down the simulation to maintain update rate. The plugin API is based on the `gazebo_ros_camera` plugin, and respects the following parameters in SDF:
* `<minUpdateRate>` (*double*, default: same as `<updateRate>`) - least allowed publish/update rate for the camera (in Hz);
* `<windowSize>` (*integer*, default: 10) - number of last update intervals that are considered for throttling;
* `<maxStDev>` (*double*, default: 0.02) - maximum standard deviation value for update intervals.
The simulation will be slowed down if the average update rate (averaged over `<windowSize>` samples) is lower than `<minUpdateRate>` and is consistent (standard deviation over `<windowSize>` samples is less than `<maxStDev>`).

View File

@@ -0,0 +1,41 @@
<launch>
<arg name="mav_id" default="0"/>
<arg name="est" default="ekf2"/> <!-- PX4 estimator: lpe, ekf2 -->
<arg name="vehicle" default="clover"/> <!-- PX4 vehicle configuration: clover, clover_vpe -->
<arg name="main_camera" default="true"/> <!-- Simulated vision position estimation camera (optical flow, ArUco) -->
<arg name="rangefinder" default="true"/> <!-- Simulated downward-facing rangefinder, vl53l1x-like -->
<arg name="led" default="true"/> <!-- Simulated LED strip, ws281x-like -->
<arg name="gps" default="false"/> <!--Simulated GPS module -->
<arg name="use_clover_physics" default="false"/> <!-- Use inertial parameters from CAD models -->
<!-- Gazebo instance -->
<include file="$(find gazebo_ros)/launch/empty_world.launch">
<!-- Workaround for crashes in VMware -->
<env name="SVGA_VGPU10" value="0"/>
<arg name="gui" value="true"/>
<arg name="world_name" value="$(find clover_simulation)/resources/worlds/clover.world"/>
<arg name="verbose" value="true"/>
</include>
<!-- PX4 instance -->
<node name="sitl_$(arg mav_id)" pkg="px4" type="px4" output="screen" args="$(find px4)/ROMFS/px4fmu_common -s etc/init.d-posix/rcS -i $(arg mav_id)">
<env name="PX4_SIM_MODEL" value="$(arg vehicle)"/>
<env name="PX4_ESTIMATOR" value="$(arg est)"/>
</node>
<!-- Clover model -->
<include file="$(find clover_description)/launch/spawn_drone.launch">
<arg name="main_camera" value="$(arg main_camera)"/>
<arg name="rangefinder" value="$(arg rangefinder)"/>
<arg name="led" value="$(arg led)"/>
<arg name="gps" value="$(arg gps)"/>
<arg name="use_clover_physics" value="$(arg use_clover_physics)"/>
</include>
<!-- Clover services -->
<include file="$(find clover)/launch/clover.launch">
<arg name="simulator" value="true"/>
<arg name="fcu_conn" value="sitl"/>
<arg name="fcu_ip" value="127.0.0.1"/>
</include>
</launch>

View File

@@ -0,0 +1,36 @@
<launch>
<arg name="mav_id" default="0"/>
<arg name="est" default="lpe"/> <!-- PX4 estimator: lpe, ekf2 -->
<arg name="vehicle" default="iris"/>
<arg name="main_camera" default="true"/> <!-- Simulated vision position estimation camera (optical flow, ArUco) -->
<arg name="rangefinder" default="true"/> <!-- Simulated downward-facing rangefinder, vl53l1x-like -->
<arg name="led" default="true"/> <!-- Simulated LED strip, ws281x-like -->
<arg name="gps" default="false"/> <!--Simulated GPS module -->
<!-- Gazebo instance -->
<include file="$(find gazebo_ros)/launch/empty_world.launch">
<!-- Workaround for crashes in VMware -->
<env name="SVGA_VGPU10" value="0"/>
<arg name="gui" value="true"/>
<arg name="world_name" value="$(find clover_simulation)/resources/worlds/clover.world"/>
<arg name="verbose" value="true"/>
</include>
<!-- PX4 instance -->
<node name="sitl" pkg="px4" type="px4" output="screen" args="$(find px4) $(find px4)/posix-configs/SITL/init/$(arg est)/$(arg vehicle)" required="true"/>
<!-- Clover model -->
<include file="$(find clover_description)/launch/spawn_drone.launch">
<arg name="main_camera" value="$(arg main_camera)"/>
<arg name="rangefinder" value="$(arg rangefinder)"/>
<arg name="led" value="$(arg led)"/>
<arg name="gps" value="$(arg gps)"/>
</include>
<!-- Clover services -->
<include file="$(find clover)/launch/clover.launch">
<arg name="simulator" value="true"/>
<arg name="fcu_conn" value="udp"/>
<arg name="fcu_ip" value="127.0.0.1"/>
</include>
</launch>

View File

View File

@@ -0,0 +1,24 @@
<?xml version="1.0"?>
<sdf version="1.5">
<model name="loop_line">
<static>true</static>
<link name="loop_link">
<pose>0 0 1e-3 0 0 0</pose>
<visual name="loop_texture">
<cast_shadows>false</cast_shadows>
<geometry>
<box>
<size>8.0 3.2 1e-3</size>
</box>
</geometry>
<material>
<script>
<uri>model://loop_line/materials/scripts</uri>
<uri>model://loop_line/materials/textures</uri>
<name>loop_dashed</name>
</script>
</material>
</visual>
</link>
</model>
</sdf>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<model>
<name>Loop Line</name>
<version>1.0</version>
<sdf version="1.5">loop_line.sdf</sdf>
<author>
<name>Oleg Kalachev</name>
<email>okalachev@gmail.com</email>
</author>
<description>
Black loop line on the floor for recognizing by a drone
</description>
</model>

View File

@@ -0,0 +1,20 @@
material parquet
{
technique
{
pass
{
ambient 0.5 0.5 0.5 1.0
diffuse 0.5 0.5 0.5 1.0
specular 0.2 0.2 0.2 1.0 12.5
texture_unit
{
texture parquet.jpg
filtering anistropic
max_anisotropy 16
scale 0.01 0.01
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<model>
<name>Parquet Plane</name>
<version>1.0</version>
<sdf version="1.5">model.sdf</sdf>
<author>
<name>Oleg Kalachev</name>
<email>okalachev@gmail.com</email>
</author>
<description>
A parquet textured plane.
</description>
</model>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" ?>
<sdf version="1.5">
<model name="parquet_plane">
<static>true</static>
<link name="link">
<collision name="collision">
<geometry>
<box>
<size>200 200 .02</size>
</box>
</geometry>
</collision>
<visual name="visual">
<cast_shadows>false</cast_shadows>
<geometry>
<box>
<size>200 200 .02</size>
</box>
</geometry>
<material>
<script>
<uri>model://parquet_plane/materials/scripts</uri>
<uri>model://parquet_plane/materials/textures</uri>
<name>parquet</name>
</script>
</material>
</visual>
</link>
</model>
</sdf>

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,13 @@
<?xml version="1.0"?>
<model>
<name>Square Line</name>
<version>1.0</version>
<sdf version="1.5">square_line.sdf</sdf>
<author>
<name>Oleg Kalachev</name>
<email>okalachev@gmail.com</email>
</author>
<description>
Black square line on the floor for recognizing by a drone
</description>
</model>

View File

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

View File

@@ -0,0 +1,31 @@
<package format="2">
<name>clover_simulation</name>
<version>0.0.1</version>
<description>The clover_simulation package provides worlds and launch files for Gazebo.</description>
<maintainer email="okalachev@gmail.com">Oleg Kalachev</maintainer>
<author>Alexey Rogachevskiy</author>
<author>Andrey Ryabov</author>
<author>Arthur Golubtsov</author>
<author>Oleg Kalachev</author>
<author>Svyatoslav Zhuravlev</author>
<license>MIT</license>
<url type="website">https://github.com/CopterExpress/clover</url>
<url type="bugtracker">https://github.com/CopterExpress/clover/issues</url>
<buildtool_depend>catkin</buildtool_depend>
<depend>led_msgs</depend>
<depend>xacro</depend>
<depend>gazebo_ros</depend>
<depend>gazebo_plugins</depend>
<depend>rospy</depend>
<export>
<gazebo_ros gazebo_media_path="${prefix}"/>
<gazebo_ros gazebo_model_path="${prefix}/models"/>
<gazebo_ros gazebo_resource_path="${prefix}/resources"/>
</export>
</package>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" ?>
<sdf version="1.5">
<world name="default">
<!-- A global light source -->
<include>
<uri>model://sun</uri>
</include>
<include>
<uri>model://parquet_plane</uri>
</include>
<scene>
<ambient>0.8 0.8 0.8 1</ambient>
<background>0.8 0.9 1 1</background>
<shadows>false</shadows>
<grid>false</grid>
<origin_visual>false</origin_visual>
</scene>
<physics name='default_physics' default='0' type='ode'>
<gravity>0 0 -9.8066</gravity>
<ode>
<solver>
<type>quick</type>
<iters>10</iters>
<sor>1.3</sor>
<use_dynamic_moi_rescaling>0</use_dynamic_moi_rescaling>
</solver>
<constraints>
<cfm>0</cfm>
<erp>0.2</erp>
<contact_max_correcting_vel>100</contact_max_correcting_vel>
<contact_surface_layer>0.001</contact_surface_layer>
</constraints>
</ode>
<max_step_size>0.004</max_step_size>
<real_time_factor>1</real_time_factor>
<real_time_update_rate>250</real_time_update_rate>
<magnetic_field>6.0e-6 2.3e-5 -4.2e-5</magnetic_field>
</physics>
</world>
</sdf>

View File

@@ -0,0 +1,6 @@
#! /usr/bin/env python
from clover_simulation import aruco_gen
if __name__ == "__main__":
aruco_gen()

View File

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

View File

@@ -0,0 +1,93 @@
from docopt import docopt
from os import path
from sys import stdout
from .map_parser import parse
from .marker import Marker, generate_markers
from .world import load_world, add_model, save_world
USAGE = '''
aruco_gen - Script for ArUco map/model generation.
Generates ArUco models from their description and optionally
adds them to an existing Gazebo world.
Usage:
aruco_gen [--offset-x=<m>] [--offset-y=<m>] [--offset-z=<m>]
[--offset-roll=<rad>] [--offset-pitch=<rad>] [--offset-yaw=<rad>]
[--dictionary=<id>] [--single-model]
[--source-world=<path>] [--inplace]
[--model-path=<path>]
<aruco_map_file>
aruco_gen -h | --help
Options:
-h, --help Show this screen.
--offset-x=<m> X offset in meters [default: 0].
--offset-y=<m> Y offset in meters [default: 0].
--offset-z=<m> Z offset in meters [default: 0].
--offset-roll=<rad> roll offset in radians [default: 0].
--offset-pitch=<rad> pitch offset in radians [default: 0].
--offset-yaw=<rad> yaw offset in radians [default: 0].
--dictionary=<id> ArUco dictionary ID [default: 2].
--single-model Generate a single model instead of individual
markers.
--source-world=<path> Path to existing Gazebo world.
--inplace Modify source world.
--model-path=<path> Folder where generated models will be saved
[default: ~/.gazebo/models]
aruco_map_file Full path to the ArUco map file
'''
def aruco_gen():
opts = docopt(USAGE)
dictionary_id = int(opts['--dictionary'])
mapfile = opts['<aruco_map_file>']
single_model = opts['--single-model']
source_world = opts['--source-world']
inplace = opts['--inplace']
off_x = float(opts['--offset-x'])
off_y = float(opts['--offset-y'])
off_z = float(opts['--offset-z'])
off_roll = float(opts['--offset-roll'])
off_pitch = float(opts['--offset-pitch'])
off_yaw = float(opts['--offset-yaw'])
model_base_path = path.expanduser(opts['--model-path'])
markers = parse(mapfile)
if single_model:
mapname = path.split(mapfile)[-1]
model_path = path.join(model_base_path, 'aruco_{}'.format(mapname.replace('.', '_')))
generate_markers(markers, model_path, dictionary_id=dictionary_id, map_source=mapname)
else:
for marker in markers:
model_name = 'aruco_{}_{}'.format(dictionary_id, marker.id_)
model_path = path.join(model_base_path, model_name)
generate_markers([Marker(marker.id_, marker.size, 0, 0, 0, 0, 0, 0)],
model_path, dictionary_id=dictionary_id)
if source_world is not None:
world_tree = load_world(source_world)
if single_model:
world_tree = add_model(world_tree, 'aruco_{}'.format(mapname.replace('.', '_')),
off_x, off_y, off_z,
off_roll, off_pitch, off_yaw)
else:
if (abs(off_roll) > 0.001) or (abs(off_pitch) > 0.001) or (abs(off_yaw) > 0.001):
raise NotImplementedError('Sorry, angular offsets are not currently implemented for multimodel generation')
for marker in markers:
world_tree = add_model(world_tree, 'aruco_{}_{}'.format(dictionary_id, marker.id_),
off_x + marker.x, off_y + marker.y, off_z + marker.z,
marker.roll, marker.pitch, marker.yaw)
output = open(source_world, 'w') if inplace else stdout
save_world(world_tree, output)

View File

@@ -0,0 +1,45 @@
# ArUco map parser (should be kept in sync with aruco_pose)
from .marker import Marker
def _parse_line(line):
'''
Parse a line of map data, returning a Marker object if
parsing succeeded, or None if it failed.
'''
if line.startswith('#'):
return None
elems = line.split()
if len(elems) < 4:
return None
try:
id_ = int(elems[0])
size = float(elems[1])
x = float(elems[2])
y = float(elems[3])
z = float(elems[4]) if len(elems) > 4 else 0
yaw = float(elems[5]) if len(elems) > 5 else 0
pitch = float(elems[6]) if len(elems) > 6 else 0
roll = float(elems[7]) if len(elems) > 7 else 0
except:
print('Warning - marformed line: {}'.format(line, sys.exc_info()[0]))
return None
return Marker(id_, size, x, y, z, roll, pitch, yaw)
def parse(map_path):
'''
Parse a map at a given path.
map_path: Path to the ArUco map file.
Returns a list of Marker objects.
'''
markers = []
with open(map_path, 'r') as map_contents:
for line in map_contents.readlines():
parser_result = _parse_line(line)
if parser_result is not None:
markers.append(parser_result)
return markers

View File

@@ -0,0 +1,163 @@
# Marker object definition and creation
from __future__ import print_function
from collections import namedtuple
from string import Template
from os import makedirs, path
import cv2
import numpy as np
import math
Marker = namedtuple('Marker',
['id_', 'size', 'x', 'y', 'z', 'roll', 'pitch', 'yaw'])
MARKER_MODEL_CFG_TEMPLATE = Template('''
<?xml version="1.0"?>
<model>
<name>${model_name}</name>
<version>1.0</version>
<sdf version="1.6">${sdf_name}</sdf>
<author>
<name>Marker Generator script</name>
<email>marker@generator.sh</email>
</author>
<description>
${model_name}
</description>
</model>
''')
MARKER_VISUAL_TEMPLATE = Template('''
<visual name="visual_marker_${marker_id}">
<pose>${x} ${y} ${z} ${roll} ${pitch} ${yaw}</pose>
<cast_shadows>false</cast_shadows>
<geometry>
<box>
<size>${marker_full_size} ${marker_full_size} 1e-3</size>
</box>
</geometry>
<material>
<script>
<uri>model://${model_directory}/materials/scripts</uri>
<uri>model://${model_directory}/materials/textures</uri>
<name>aruco/marker_${marker_id}</name>
</script>
</material>
</visual>
''')
MARKER_MODEL_SDF_TEMPLATE = Template('''
<?xml version="1.0"?>
<sdf version="1.5">
<model name="${model_name}">
<static>true</static>
<link name="${model_name}_link">
${model_visuals}
</link>
</model>
</sdf>
''')
MARKER_MATERIAL_TEMPLATE = Template('''
material aruco/marker_${marker_id}
{
technique
{
pass
{
texture_unit
{
texture aruco_marker_${marker_id}.png
filtering none
scale 1.0 1.0
}
}
}
}
''')
def model_name(model_directory):
'''
Extract model name from model directory.
model_directory: Full path to the model.
Returns Gazebo-compatible model name (available through model:// URI schema)
'''
return path.split(model_directory)[1]
def generate_markers(markers, model_directory, dictionary_id=2, map_source=''):
'''
Generate markers from a list. Result is a single Gazebo
model with all markers inside it.
markers: A List-like object containing Marker objects.
model_directory: Target directory for the model.
dictionary_id: Predefined ArUco dictionary ID, as defined by OpenCV.
map_source: An optional string with the name of the ArUco map file.
'''
script_directory = path.join(model_directory, 'materials', 'scripts')
texture_directory = path.join(model_directory, 'materials', 'textures')
try:
if not path.exists(script_directory):
makedirs(script_directory)
if not path.exists(texture_directory):
makedirs(texture_directory)
except:
print('Could not create material/texture directories!')
aruco_dict = cv2.aruco.getPredefinedDictionary(dictionary_id)
marker_bits = aruco_dict.markerSize
marker_outer_bits = marker_bits + 2 # "black border" bits
marker_border_bits = marker_bits + 4 # "white border" bits
materials = []
visuals = []
for marker in markers:
texture_name = 'aruco_marker_{}_{}.png'.format(dictionary_id, marker.id_)
marker_image = np.zeros((marker_border_bits, marker_border_bits), dtype=np.uint8)
marker_image[:,:] = 255
marker_image[1:marker_border_bits - 1, 1:marker_border_bits - 1] = cv2.aruco.drawMarker(
aruco_dict, marker.id_, marker_outer_bits)
cv2.imwrite(path.join(texture_directory, texture_name), marker_image)
materials.append(MARKER_MATERIAL_TEMPLATE.substitute(
marker_id='{}_{}'.format(dictionary_id, marker.id_)
))
visuals.append(MARKER_VISUAL_TEMPLATE.substitute(
marker_id='{}_{}'.format(dictionary_id, marker.id_),
x=marker.x,
y=marker.y,
z=marker.z,
roll=marker.roll,
pitch=marker.pitch,
yaw=marker.yaw + (math.pi / 2),
model_directory=model_name(model_directory),
marker_full_size=marker.size * marker_border_bits / marker_outer_bits
))
with open(path.join(script_directory, 'aruco_materials.material'), 'w') as f:
f.write(''.join(materials))
with open(path.join(model_directory, 'aruco_model.sdf'), 'w') as f:
f.write(MARKER_MODEL_SDF_TEMPLATE.substitute(
model_name='aruco_{}_{}'.format(dictionary_id, len(markers)),
model_visuals=''.join(visuals)
))
with open(path.join(model_directory, 'model.config'), 'w') as f:
f.write(MARKER_MODEL_CFG_TEMPLATE.substitute(
model_name='ArUco {} (dictionary {})'.format(
markers[0].id_ if len(markers) == 1 else 'field from ' + map_source,
dictionary_id),
sdf_name='aruco_model.sdf'
))

View File

@@ -0,0 +1,43 @@
# Gazebo world changer
import xml.etree.ElementTree as ET
from string import Template
WORLD_INCLUDE = Template('''
<include>
<uri>model://${model_name}</uri>
<pose>${x} ${y} ${z} ${roll} ${pitch} ${yaw}</pose>
</include>
''')
def load_world(world_file):
'''
Load Gazebo world as an ElementTree object
'''
return ET.parse(world_file)
def add_model(world, model_name, x, y, z, roll, pitch, yaw, index=0):
'''
Create and add an element to the world
'''
world_elem = world.find('world')
model_elem = ET.fromstring(WORLD_INCLUDE.substitute(
model_name=model_name,
x=x,
y=y,
z=z,
roll=roll,
pitch=pitch,
yaw=yaw
))
world_elem.insert(index, model_elem)
return world
def save_world(world, file):
'''
Save the world to file-like object
'''
return world.write(file)

View File

@@ -0,0 +1,232 @@
#include <led_msgs/SetLEDs.h>
#include <led_msgs/LEDStateArray.h>
#include <ros/ros.h>
#include <ros/callback_queue.h>
#include <gazebo/common/Plugin.hh>
#include <gazebo/rendering/rendering.hh>
#if GAZEBO_MAJOR_VERSION >= 9
#include <ignition/math/Color.hh>
using GazeboColor = ignition::math::Color;
#else
#include <gazebo/common/Color.hh>
using GazeboColor = gazebo::common::Color;
#endif
#include <string>
#include <unordered_map>
#include <mutex>
#include <vector>
#include <thread>
namespace sim_led
{
// Forward declaration of the plugin for led controller
class LedVisualPlugin;
} //
namespace led_controller
{
/// LedController: a class that provides ROS interface for the LEDs.
class LedController
{
private:
/// Role for the current controller
enum class Role
{
Client, // Client: runs on /gazebo_gui, responsible for "preview window"
Server // Server: runs on /gazebo, responsible for renders on Gazebo sensors
} role;
// Pointers to the LED plugins that we know about
std::vector<sim_led::LedVisualPlugin*> registeredLeds;
// Mutex protecting the vector
std::mutex registryMutex;
std::string robotNamespace;
std::unique_ptr<ros::NodeHandle> nh;
ros::ServiceServer setLedsSrv;
// Note: LED state should only be published by the /gazebo node
led_msgs::LEDStateArray ledState;
ros::Publisher statePublisher;
// LED state will be read from the topic to avoid creating more services
ros::Subscriber stateSubscriber;
bool setLeds(led_msgs::SetLEDs::Request& req, led_msgs::SetLEDs::Response& resp);
void handleLedsMsg(const led_msgs::LEDStateArrayConstPtr& leds);
public:
LedController(const std::string& robotNamespace) : robotNamespace(robotNamespace)
{
// We need "libgazebo_ros_api.so" to be loaded
if (!ros::isInitialized())
{
ROS_FATAL_NAMED("LedController", "Tried to load ROS plugin when ROS Gazebo API is not loaded. Please use gazebo_ros node to"
"launch Gazebo.");
}
role = (ros::this_node::getName() == "/gazebo") ? Role::Server : Role::Client;
ROS_INFO_NAMED(("LedController_" + robotNamespace).c_str(), "LedController has started (as %s)", role == Role::Client ? "client" : "server");
nh.reset(new ros::NodeHandle(robotNamespace));
if (role == Role::Server)
{
setLedsSrv = nh->advertiseService("led/set_leds", &LedController::setLeds, this);
statePublisher = nh->advertise<led_msgs::LEDStateArray>("led/state", 1, true);
}
else
{
// LED state should be published to the "led/state" topic, so we grab our data there
stateSubscriber = nh->subscribe<led_msgs::LEDStateArray>("led/state", 1, &LedController::handleLedsMsg, this);
}
};
~LedController()
{
nh->shutdown();
}
void registerPlugin(sim_led::LedVisualPlugin* plugin, int ledIdx = 0, int totalLeds = 0)
{
assert(ledIdx < totalLeds);
std::lock_guard<std::mutex> lock(registryMutex);
if (totalLeds > 0) {
registeredLeds.resize(totalLeds);
ledState.leds.resize(totalLeds);
}
ROS_DEBUG_NAMED(("LedController_" + robotNamespace).c_str(), "Registering LED visual plugin to %s (LED id=%d)", (role == Role::Client) ? "client" : "server", ledIdx);
registeredLeds[ledIdx] = plugin;
ledState.leds[ledIdx].index = ledIdx;
if (role == Role::Server)
statePublisher.publish(ledState);
}
void unregisterPlugin(sim_led::LedVisualPlugin* plugin)
{
std::lock_guard<std::mutex> lock(registryMutex);
auto it = std::find(registeredLeds.begin(), registeredLeds.end(), plugin);
if (it != registeredLeds.end())
{
ROS_DEBUG_STREAM_NAMED(("LedController_" + robotNamespace).c_str(), "Unregistering LED visual plugin (LED id=" << (it - registeredLeds.begin()) << ")");
*it = nullptr;
}
}
};
// Guards controllers map (static to led_controller::get())
std::mutex controllerMutex;
LedController& get(std::string robotNamespace)
{
static std::unordered_map<std::string, std::unique_ptr<LedController>> controllers;
std::lock_guard<std::mutex> lock(controllerMutex);
auto it = controllers.find(robotNamespace);
if (it == controllers.end()) {
gzwarn << "Creating new LED controller for namespace " << robotNamespace << "\n";
controllers[robotNamespace].reset(new LedController(robotNamespace));
return *controllers[robotNamespace];
}
return *(it->second);
}
} // led_controller
namespace sim_led
{
class LedVisualPlugin : public gazebo::VisualPlugin {
private:
std::string ns;
gazebo::rendering::VisualPtr vptr;
public:
void Load(gazebo::rendering::VisualPtr visual, sdf::ElementPtr sdf) override {
// FIXME: This is a fragile way to guess our index
// FIXME: This is based on an assumption that the parent will have a mangled name
// FIXME: (like led_strip::base_link::base_link_fixed_joint_lump__led_00_link_visual_1)
// FIXME: This will obviously break if gazebo decides to go with something else
auto parentName = sdf->GetParent()->GetAttribute("name")->GetAsString();
auto lastDashPos = parentName.rfind('_');
int myIndex = 0;
if (lastDashPos != std::string::npos)
{
auto indexStr = parentName.substr(lastDashPos + 1);
try {
myIndex = std::stoi(indexStr);
} catch(const std::exception &e) {
gzwarn << "Failed to convert " << indexStr << " to integer: " << e.what() << ", assuming 0\n";
myIndex = 0;
}
}
else
{
gzerr << "Failed to parse parent name: " << parentName << "; could not determine our index\n";
}
ns = "";
if (sdf->HasElement("robotNamespace")) {
ns = sdf->Get<std::string>("robotNamespace");
}
if (!sdf->HasElement("ledCount")) {
gzerr << "ledCount is not set, but is required for the plugin to function correctly\n";
return;
}
int totalLeds = sdf->Get<int>("ledCount");
vptr = visual;
led_controller::get(ns).registerPlugin(this, myIndex, totalLeds);
}
~LedVisualPlugin()
{
led_controller::get(ns).unregisterPlugin(this);
}
void SetColor(const GazeboColor& emissive)
{
vptr->SetEmissive(emissive);
}
};
}
// FIXME: These two functions basically do the same thing, maybe they can be merged?
bool led_controller::LedController::setLeds(led_msgs::SetLEDs::Request &req, led_msgs::SetLEDs::Response &resp)
{
std::lock_guard<std::mutex> lock(registryMutex);
for(const auto& led : req.leds)
{
if (led.index < registeredLeds.size()) {
auto color = GazeboColor(led.r / 255.0f, led.g / 255.0f, led.b / 255.0f);
auto ledPlugin = registeredLeds[led.index];
if (ledPlugin) ledPlugin->SetColor(color);
ledState.leds[led.index].r = led.r;
ledState.leds[led.index].g = led.g;
ledState.leds[led.index].b = led.b;
}
}
statePublisher.publish(ledState);
resp.success = true;
return true;
}
void led_controller::LedController::handleLedsMsg(const led_msgs::LEDStateArrayConstPtr& leds)
{
std::lock_guard<std::mutex> lock(registryMutex);
for(const auto& led : leds->leds)
{
if (led.index < registeredLeds.size())
{
auto color = GazeboColor(led.r / 255.0f, led.g / 255.0f, led.b / 255.0f);
auto ledPlugin = registeredLeds[led.index];
if (ledPlugin) ledPlugin->SetColor(color);
}
}
}
GZ_REGISTER_VISUAL_PLUGIN(sim_led::LedVisualPlugin);

View File

@@ -0,0 +1,275 @@
#include <gazebo/plugins/CameraPlugin.hh>
// physics definitions and functions: Required to perform slowdown
#include <gazebo/physics/physics.hh>
// ROS simulated camera class with most of the boilerplate already in place
#include <gazebo_plugins/gazebo_ros_camera_utils.h>
#include <ros/ros.h>
#include <memory>
#include <deque>
#include <algorithm>
namespace
{
/**
* Simple statistics-collecting class.
*/
class AverageStat
{
private:
/** Currently collected samples. */
std::deque<double> samples;
/** Number of samples to store (also, the number of samples considered "adequate") */
size_t maxSamples;
/** Currently stored average value */
double average = 0;
/** Currently stored standard deviation value */
double stdev = 0;
/** Largest standard deviation that is considered adequate */
double maxStDev;
public:
AverageStat(size_t numSamples, double validStDev) :
samples(),
maxSamples(numSamples),
maxStDev(validStDev)
{}
/**
* Add a sample and recalculate average and standard deviation.
*
* @param sample New sampled value.
* @return New average value.
*/
double update(double sample)
{
samples.push_back(sample);
if (samples.size() > maxSamples)
{
samples.pop_front();
}
average = std::accumulate(samples.begin(), samples.end(), 0.0) / samples.size();
double stdevSquared = std::accumulate(samples.begin(), samples.end(), 0.0,
[&](double sum, double xi) {
return sum + (xi - average) * (xi - average);
}) / samples.size();
stdev = std::sqrt(stdevSquared);
return average;
}
/**
* Get current average value of all samples.
*
* @note This function will return a result even if it is not considered valid.
*/
double getAverage() const
{
return average;
}
/**
* Get current standard deviation of all samples.
*
* @note This function will return a result even if it is not considered valid.
*/
double getStDev() const
{
return stdev;
}
/**
* Check if the average value is considered "adequate".
*
* @return True if the number of samples is not less than required and standard deviation is within limits, false otherwise
*/
bool isAdequate() const
{
return (samples.size() >= maxSamples) && (stdev < maxStDev);
}
/**
* Drop all samples and start anew.
*
* @note This does not actually change average and stdev, but the stats are considered inadequate after this call.
*/
void reset()
{
samples.clear();
}
};
}
namespace throttling_camera
{
/**
* Gazebo camera plugin for a world-throttling (rate-preserving) camera.
*
* This plugin will slow the simulation down to maintain average publishing rate. Note that
* this plugin will *only* perform slowdown, it *will not* speed the simulation back up!
*/
class ThrottlingCamera : public gazebo::CameraPlugin, gazebo::GazeboRosCameraUtils
{
private:
/** A pointer to the Gazebo camera sensor. */
gazebo::sensors::SensorPtr camPtr;
/** A pointer to the current simulated world (required to change world parameters) */
gazebo::physics::WorldPtr world;
/** A pointer to the physics preset manager. Used to actually slow the simulation down. */
gazebo::physics::PresetManagerPtr presetManager;
/** Maximum update interval that is considered "okay". Should be higher than the "average" update interval to avoid extreme slowdowns */
double maxUpdateInterval;
/** Statistics for publishing time intervals. */
std::unique_ptr<AverageStat> timeSamples;
public:
ThrottlingCamera() = default;
~ThrottlingCamera() override = default;
/**
* Plugin load function. Called by Gazebo each time the plugin is instantiated.
*
* @param parent Gazebo sensor that this plugin connects to.
* @param sdf SDF element containing this plugin.
*/
void Load(gazebo::sensors::SensorPtr parent, sdf::ElementPtr sdf) override
{
if (!ros::isInitialized())
{
ROS_FATAL_NAMED("throttling_camera", "ROS node for Gazebo has not been initialized, unable to load plugin");
return;
}
ROS_DEBUG_NAMED("throttling_camera", "Initializing ROS throttling (stable-rate) camera");
CameraPlugin::Load(parent, sdf);
world = gazebo::physics::get_world(parent->WorldName());
#if GAZEBO_MAJOR_VERSION >= 8
presetManager = world->PresetMgr();
#else
presetManager = world->GetPresetManager();
#endif /* GAZEBO_MAJOR_VERSION */
// Same as in PX4
if (presetManager->CurrentProfile() != "default_physics")
{
gzwarn << "Current physics profile is not default_physics, but actually is " << presetManager->CurrentProfile() << "\n";
if (!presetManager->CurrentProfile("default_physics"))
{
gzerr << "Could not set current profile to default_physics!\n";
}
}
double minUpdateRate = parent->UpdateRate();
if (sdf->HasElement("minUpdateRate"))
{
minUpdateRate = sdf->Get<double>("minUpdateRate");
}
maxUpdateInterval = 1.0 / minUpdateRate;
size_t windowSize = 10;
if (sdf->HasElement("windowSize"))
{
windowSize = sdf->Get<size_t>("windowSize");
}
double maxStDev = 0.02;
if (sdf->HasElement("maxStDev"))
{
maxStDev = sdf->Get<double>("maxStDev");
}
timeSamples.reset(new AverageStat(windowSize, maxStDev));
camPtr = parent;
parentSensor_ = camPtr;
width_ = width;
height_ = height;
depth_ = depth;
format_ = format;
camera_ = camera;
gazebo::GazeboRosCameraUtils::Load(parent, sdf);
}
/**
* Frame callback. Called every time a new frame is rendered by the camera.
*
* Checks whether we should slow simulation down and publishes a new image
* message.
*
* @param image Image data.
* @param width Image width, in pixels.
* @param height Image height, in pixels.
* @param depth Image depth, in bytes.
* @param format Image format description string.
*/
void OnNewFrame(const unsigned char *image, unsigned int width, unsigned int height, unsigned int depth,
const std::string &format) override {
// Note: sensorUpdateTime uses simulated time
auto sensorUpdateTime = camPtr->LastMeasurementTime();
// If sensor was not active for some reason, we allow it to get new data on next frame
if (!camPtr->IsActive())
{
camPtr->SetActive(true);
last_update_time_ = sensorUpdateTime;
timeSamples->reset();
return;
}
boost::mutex::scoped_lock lock(*image_connect_count_lock_);
if (*image_connect_count_ > 0)
{
if (sensorUpdateTime < last_update_time_)
{
ROS_WARN_NAMED("throttling_camera", "Negative sensor update time difference (world reset?)");
last_update_time_ = sensorUpdateTime;
timeSamples->reset();
}
auto timeDelta = sensorUpdateTime - last_update_time_;
timeSamples->update(timeDelta.Double());
// We want to throttle the simulation down if we have measurements too far apart
if (timeSamples->isAdequate() && timeSamples->getAverage() > maxUpdateInterval)
{
ROS_INFO_STREAM_NAMED("throttling_camera", "Had average update period of "
<< timeSamples->getAverage() << " (stdev: " << timeSamples->getStDev() << ")"
<< ", but desired update period is " << update_period_
<< ", throttling simulation down");
boost::any currentRealTimeUpadteRateParam;
if (!presetManager->GetCurrentProfileParam("real_time_update_rate", currentRealTimeUpadteRateParam))
{
gzerr << "Failed to get real time update rate parameter!\n";
}
auto currentRate = boost::any_cast<double>(currentRealTimeUpadteRateParam);
// We are being somewhat aggressive here, maybe we could throttle the world
// down in steps?
double slowdownFactor = update_period_ / timeSamples->getAverage();
auto nextRate = currentRate * slowdownFactor;
if (!presetManager->SetCurrentProfileParam("real_time_update_rate", nextRate))
{
gzerr << "Failed to set real time update rate parameter!\n";
}
if (slowdownFactor < 0.5)
{
ROS_WARN_STREAM_NAMED("throttling_camera", "Simulation slowed down significantly; consider running"
"the simulation with a lower PX4_SIM_SPEED_FACTOR value (slowed down from "
<< currentRate << " to " << nextRate << " updates per second)");
}
// We're discarding old samples to avoid extensive slowdown
timeSamples->reset();
}
PutCameraData(image, sensorUpdateTime);
PublishCameraInfo(sensorUpdateTime);
}
last_update_time_ = sensorUpdateTime;
}
};
}
GZ_REGISTER_SENSOR_PLUGIN(throttling_camera::ThrottlingCamera);

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

View File

@@ -47,6 +47,11 @@
* [Using JavaScript](javascript.md)
* [ROS](ros.md)
* [MAVROS](mavros.md)
* Simulation
* [Overview](simulation.md)
* [Native setup](simulation_native.md)
* [VM setup](simulation_vm.md)
* [Usage](simulation_usage.md)
* Supplementary materials
* [COEX Pix](coex_pix.md)
* [Guide on autonomous flight](auto_setup.md)

32
docs/en/simulation.md Normal file
View File

@@ -0,0 +1,32 @@
# Simulation overview
The Clover simulation environment allows users to run and debug their code within a simulator while using most of the features available on the real drone. The simulation utilizes [PX4 SITL mode](sitl.md) and uses the same ROS code as the real drone. Most hardware is simulated as well.
## Features
Basic, user-installable environment includes:
* high-quality Clover 4 visual model;
* Gazebo plugins for Clover-specific hardware (e.g. LED strip);
* modification-friendly [`xacro`](https://wiki.ros.org/xacro) drone descriptions;
* sample models and worlds;
* [`roslaunch`](https://wiki.ros.org/roslaunch) files for quick simulation launching and configuration.
Additionally, a [virtual machine image](simulation_setup_vm.md) that mimics the real drone as closely as possible is provided with the following features:
* easy access to the simulation environment;
* Visual Studio Code editor, preconfigured to work with ROS packages;
* Monkey web server for web-based Clover plugins;
* always-running `roscore` service;
* ROS GUI tools (`rviz`, `rqt`).
## Architecture
The simulation environment is based on the following components:
* [Gazebo](http://gazebosim.org/), a state-of-the-art robotics simulator;
* [PX4](https://px4.io/), specifically its SITL (software-in-the-loop) components;
* [`sitl_gazebo`](https://github.com/PX4/sitl_gazebo) package containing Gazebo plugins for PX4;
* ROS packages and Gazebo plugins.
<!-- TODO: Write more, add a diagram, etc -->

View File

@@ -0,0 +1,84 @@
# Native setup
Setting up the simulation environment from scratch requires some effort, but results in the most performant setup, with less chance of driver issues.
Prerequisites: Ubuntu 18.04, [native ROS installation](ros-install.md).
## Create a workspace for the simulation
Throughout this guide we will be using the `catkin_ws` as the workspace name. Feel free to change it in your setup. We will be creating it in the home directory of the current user (`~`).
Create the workspace and clone Clover sources:
```bash
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws/src
git clone https://github.com/CopterExpress/clover -b clover_description
git clone https://github.com/CopterExpress/ros_led
```
Install all prerequisites using `rosdep`:
```bash
cd ~/catkin_ws
rosdep update
rosdep install --from-paths src --ignore-src -y
```
## Get PX4 sources
PX4 will be built along with the other packages in our workspace. You may clone it directly into the workspace or put it somewhere and symlink to `~/catkin_ws/src`. We will need to put its `sitl_gazebo` submodule in `~/catkin_ws/src` as well. For simplicity's sake we will clone the firmware directly to the workspace:
```bash
cd ~/catkin_ws/src
git clone --recursive https://github.com/CopterExpress/Firmware -b v1.10.1-clever
ln -s Firmware/Tools/sitl_gazebo ./sitl_gazebo
```
## Install PX4 prerequisites
PX4 comes with its own script for dependency installation. We may as well leverage it:
```bash
cd ~/catkin_ws/src/Firmware/Tools/setup
sudo ./ubuntu.sh
```
This will install everything required to build PX4 and its SITL environment.
You may want to skip installing the ARM toolchain if you're not planning on compiling PX4 for your flight controller. To do this, use the `--no-nuttx` flag:
```
sudo ./ubuntu.sh --no-nuttx
```
## Install geographiclib datasets
`mavros` requires geographiclib datasets to be present:
```bash
cd ~
wget https://raw.githubusercontent.com/mavlink/mavros/6f5bd5a1a67c19c2e605f33de296b1b1be9d02fc/mavros/scripts/install_geographiclib_datasets.sh
sudo ./install_geographiclib_datasets.sh
rm ./install_geographiclib_datasets.sh
```
## Build the simulator
With all dependencies installed, you can build your workspace:
```bash
cd ~/catkin_ws
catkin_make
```
> **Note** Some of the files - particularly Gazebo plugins - require large amounts of RAM to be built. You may wish to reduce the number of parallel jobs; the number of parallel jobs should be equal to the amount of RAM in gigabytes divided by 2 - so a 16GB machine should use no more than 8 jobs. You can specify the number of jobs using the `-j` flag: `catkin_make -j8`
## Run the simulator
In order to be sure that everything was built correctly, try running the simulator for the first time:
```bash
source ~/catkin_ws/devel/setup.bash
roslaunch clover_simulation simulator.launch
```

View File

@@ -0,0 +1,49 @@
# Using the simulator
The Clover simulation environment allows the user to test their code without any risk of equipment damage. Additionally, the [virtual machine](simulation_setup_vm.md)-based environment has additional (non-ROS) services that are present on a real drone, like Monkey web server.
## Running the simulation
After [setting up the simulation packages](simulation_setup_native.md) or [importing and running the VM](simulation_setup_vm.md), you can use `roslaunch` to start Gazebo simulation:
```bash
# Be sure to activate your workspace first
source ~/catkin_ws/devel/setup.bash
roslaunch clover_simulation simulator.launch
```
> **Note** Alternatively, if you are using the VM, just double-click on the `Gazebo PX4` icon on the desktop.
This will launch Gazebo server and client, the PX4 SITL binary and Clover nodes. The terminal in which the command was run will display diagnostic messages from the nodes and PX4, and will accept input for the PX4 command interpreter:
![Gazebo simulation screenshot](../assets/simulation_usage/01_running_gazebo.jpg)
You can use QGroundControl to configure the simulated drone parameters, plan missions (if GPS is simulated) and control the drone using a joystick:
![Gazebo and QGC](../assets/simulation_usage/02_gazebo_qgc.jpg)
You can also use [our simplified OFFBOARD control](simple_offboard.md) to control the drone, and traditional ROS GUI utilities like [rviz and rqt](rviz.md) to analyze the drone state:
![Gazebo and RQT](../assets/simulation_usage/03_gazebo_rqt.jpg)
## Configuring the simulation
The simulation can be configured by passing additional arguments to the `roslaunch` command or by changing the `~/catkin_ws/src/clover/clover_simulation/launch/simulator.launch` file. Nodes that provide [ArUco detection](aruco.md), [optical flow calculation](optical_flow.md) and other services can be configured by changing their respective `.launch` files, just like on a real drone.
### Changing the drone parameters
![vscode with simulator.launch open](../assets/simulation_usage/04_vscode_config.jpg)
You can enable or disable some of the drone sensors by changing parameters in the `simulator.launch` file. For example, in order to enable GPS, set the `gps` argument to `true`:
```xml
<arg name="gps" value="true"/>
```
Note that this will simply enable the sensor, you will have to change the PX4 estimator parameters to enable GPS fusion.
If you wish to add additional sensors or change their placement, you will have to change the drone description. The description file is located in `~/catkin_ws/src/clover/clover_description/urdf/clover/clover4.xacro`, and uses the [xacro](http://wiki.ros.org/xacro) format to build URDF description.
### Changing the default world
Gazebo plugins for the drone currently require the `real_time_update_rate` world parameter to be 250, and `max_step_size` to be 0.004. Using other values will not work. Consider using `~/catkin_ws/src/clover/clover_simulation/resources/worlds/clover.world` as a base.

64
docs/en/simulation_vm.md Normal file
View File

@@ -0,0 +1,64 @@
# Simulation VM setup
In addition to [native installation instructions](simulation_setup_native.md), we provide a [preconfigured developer virtual machine image](https://github.com/CopterExpress/clover_vm/releases). The image contains:
* Ubuntu 18.04 with XFCE lightweight desktop environment;
* ROS packages required to develop for the Clover platform;
* QGroundControl;
* preconfigured Gazebo simulation environment;
* Visual Studio Code with C++ and Python plugins.
The VM is an easy way to set up a simulation environment, but can be used as a development environment for a real drone as well.
You can download the latest VM image [in the VM releases repository](https://github.com/CopterExpress/clover_vm/releases).
> **Note** The virtual machine should be used when native installation is not feasible or possible. You may experience reduced performance in programs that use 3D rendering, like rviz and Gazebo.
## Setting up the VM
You need to use a VM manager that supports OVF format, like [Virtualbox](https://www.virtualbox.org/wiki/Downloads), [VMware Player](https://www.vmware.com/products/workstation-player.html) or [VMware Workstation](https://www.vmware.com/products/workstation-pro.html).
> **Note** At the time of writing Virtualbox had issues running the VM, particularly with 3D applications. We recommend using VMware Player or VMware Workstation if possible. The following steps assume you're using VMware Player.
Make sure that you have hardware virtualization enabled in your BIOS/UEFI (it may be supported by your hardware but turned off by default). The steps to enable virtualization differ from manufacturer to manufacturer, but should be described in your system manual. Consult your system's manufacturer if you're having trouble turning virtualization on.
1. Import the OVA archive into your virtualization environment. Use the **Open a Virtual Machine** option in VMware Player:
![Open dialog with clever-devel.ova selected](../assets/simulation_setup_vm/01_import_vm.png)
> **Note** You may see a dialog box with a warning about the VM format:
![Import failure dialog](../assets/simulation_setup_vm/02_import_failure.png)
You can safely ignore the warning and press **Retry**.
2. Right-click on the VM name and select **Virtual Machine Settings**. In the new window, set the following parameters:
* increase the amount of memory available to the virtual machine (a good rule of thumb is 2048 MB per CPU core, but no less than 4 GB):
![Increasing avaliable memory](../assets/simulation_setup_vm/03_max_memory.png)
* increase the amount available CPU cores:
![Increasing cpu cores](../assets/simulation_setup_vm/04_core_count.png)
* enable 3D acceleration:
![Enabling 3D acceleration](../assets/simulation_setup_vm/05_3d_acceleration.png)
* enable USB 2.0/3.0 controller (if you plan to connect external devices to the VM):
![USB 3.0 controller](../assets/simulation_setup_vm/06_usb_3_0.png)
* optionally enable the "bridged" network connection (if you plan to connect to a real drone):
![Enabling bridge networking](../assets/simulation_setup_vm/07_bridge_networking.png)
> **Note** Some host network adapters may not work well with the bridged network. Consider using external USB Wi-Fi adapters managed by your VM to connect to a real drone.
3. "Power on" the virtual machine. You may see a warning message about your host system not providing 3D acceleration:
![No 3D support from host](../assets/simulation_setup_vm/08_no_3d_acceleration.png)
Make sure you have the latest GPU drivers for your host system. If the warnings persist, add the following line to `clover-devel.vmx` (actual file name may differ based on the VM name):
```
mks.gl.allowBlacklistedDrivers = "TRUE"
```
You can find this file in a folder where the VM is imported to.
4. (Bridged networking only) Set up network bridge configuration in VM settings or using `vmware-netcfg` utility (in Linux):
![vmware-netcfg interface](../assets/simulation_setup_vm/09_netcfg.png)
Select `vmnet0` in the networks list, set it to *Bridged*, and choose the adapter you are planning to use to connect to drone in the drop-down menu.