Prince AlyGabriella GGMichael MoorerJack Green
Created September 28, 2022 © MIT

Tayaran Drones

Drone swarm technology for sustainable farming

Beginner2 hours30
Tayaran Drones

Things used in this project

Software apps and online services

PX4
PX4
Robot Operating System
ROS Robot Operating System
OpenCV
OpenCV
Docker
Vagrant
QGroundControl
PX4 QGroundControl
MAVLink
PX4 MAVLink

Hand tools and fabrication machines

Multitool, Screwdriver
Multitool, Screwdriver
Soldering iron (generic)
Soldering iron (generic)
Hot glue gun (generic)
Hot glue gun (generic)

Story

Read more

Custom parts and enclosures

Drone URDF File

URDF File to use Drone in Gazebo Simulation

Schematics

Drone Schematics

Default drone schematics as provided in the guide

Code

DockerFile

Textile
Dockerfile that contains PX4 and required packages
FROM px4io/px4-dev-ros2-foxy:latest

ENV TERM=xterm
ENV DEBIAN_FRONTEND=noninteractive
ENV ARM_ABI_VER=10.3-2021.10

RUN echo 'Acquire::Check-Date false;' | tee -a /etc/apt/apt.conf.d/10-nocheckvalid \
    && apt-get update && apt-get install -y --no-install-recommends \
    libopencv-dev \
    python3-opencv \
    xmlstarlet \
    build-essential \
    iproute2 \
    libncurses-dev \
    libncurses5 \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
    && echo "source /opt/ros/foxy/setup.bash" >> ~/.bashrc \
    && wget https://developer.arm.com/-/media/Files/downloads/gnu-rm/$ARM_ABI_VER/gcc-arm-none-eabi-$ARM_ABI_VER-x86_64-linux.tar.bz2 \
    && tar xjf gcc-arm-none-eabi-$ARM_ABI_VER-x86_64-linux.tar.bz2 -C /usr/share/ \
    && ln -sf /usr/share/gcc-arm-none-eabi-10.3-2021.10/bin/arm-none-eabi-* /usr/bin/

WORKDIR /src/app

COPY enter /enter

RUN ln -sf /enter /usr/bin/enter

ENV PYTHONUNBUFFERED=1
ENV PYTHONPATH="/opt/ros/foxy/lib/python3.8/site-packages:/src/app"
ENV AMENT_PREFIX_PATH=/opt/ros/foxy
ENV ROS_VERSION=2
ENV ROS_PYTHON_VERSION=3
ENV SHLVL=1
ENV LD_LIBRARY_PATH=/opt/ros/foxy/opt/yaml_cpp_vendor/lib:/opt/ros/foxy/opt/rviz_ogre_vendor/lib:/opt/ros/foxy/lib/x86_64-linux-gnu:/opt/ros/foxy/lib
ENV ROS_LOCALHOST_ONLY=0
ENV LC_ALL=C.UTF-8
ENV PATH=/opt/ros/foxy/bin:/usr/lib/ccache:/opt/gradle/gradle-6.3-rc-4/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV CCACHE_UMASK=000
ENV ROS_DISTRO=foxy

SHELL ["/bin/bash", "-c"]

VagrantFile

Ruby
VagrantFile to create virtual machine
# -*- mode: ruby -*-
# vi: set ft=ruby :


Vagrant.configure("2") do |config|
  # It's possible to make git modules update on local machine before "up" 
  #config.trigger.before [:up] do |trigger|
  #  trigger.info = "Updating git submodules..."
  #  trigger.run = {path: "git submodule sync --recursive"}
  #end

  config.vm.box = "hashicorp/bionic64"
  config.vbguest.auto_update = false if Vagrant.has_plugin?("vagrant-vbguest")
  config.vm.provision "shell", inline: "echo 'Acquire::Check-Date false;' | tee -a /etc/apt/apt.conf.d/10-nocheckvalid"
  config.vm.provision "docker" do |d|
    #d.build_image "/vagrant"
    d.post_install_provision "shell",
    inline:"echo DOCKER_OPTS='${DOCKER_OPTS} -H unix:///var/run/docker.sock -H 0.0.0.0:2375' >> /etc/default/docker"

  end
  ssh_pub_key = File.readlines("#{Dir.home}/.ssh/id_rsa.pub").first.strip
  config.vm.provision "file", source: "~/.ssh/id_rsa.pub", destination: "~/.ssh/id_rsa.pub"
  config.vm.provision "file", source: "~/.ssh/id_rsa", destination: "~/.ssh/id_rsa"
  config.vm.provision "shell", inline: <<-SHELL
    cat /home/vagrant/.ssh/id_rsa.pub >> /home/vagrant/.ssh/authorized_keys
  SHELL

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  config.vm.synced_folder ".", "/vagrant", owner: "vagrant", group: "vagrant", automount: true, mount_options: ["uid=1000", "gid=1000","dmode=775","fmode=775"]

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
    vb.gui = true
  #   # Customize the amount of memory on the VM:
     vb.memory = "4096"
     vb.cpus = "4"
  # Enable symlink support (tested but not working at all) workaround is in the README.md
     vb.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/vagrant", "1"]
     vb.customize ["setextradata", :id, "GUI\/LastGuestSizeHint","1440,900"]
  # 3d acceleration
     vb.customize ["modifyvm", :id, "--accelerate3d","on"]
     #vb.customize ["modifyvm", :id, "--graphicscontroller","vmsvga"] # there is bug using this, as some display artifacts accurs
     vb.customize ["modifyvm", :id, "--hwvirtex","on"]
     vb.customize ["modifyvm", :id, "--ioapic","on"]
     vb.customize ["modifyvm", :id, "--vram","128"]
  # Enable USB support
     vb.customize ["modifyvm", :id, "--usb", "on"]
     vb.customize ["modifyvm", :id, "--usbehci", "on"] # for USB 2.0
#     vb.customize ["modifyvm", :id, "--usbxhci", "on"] # for USB 3.0
# Add the desired usb device, look at usb.md
#     vb.customize ["usbfilter", "add", "0",
#       "--target", :id,
#       "--name", "usbstick",
#       "--vendorid", "8644",
#       "--productid", "800B",
#       "--product", "USB Flash Disk",
#       "--manufacturer","General"]
# Attach the desired usb device on the VM startup, look at usb.md
     #vb.customize "post-boot", ["controlvm", :id, "usbattach", "p=0x800b;v=0x8644;s=0x00038015f557c434;l=0x14130000"] # macOS syntax
     #vb.customize "post-boot", ["controlvm", :id, "usbattach","{36fc9e60-c465-11cf-8056-444553540000}\\0012"] # Windows syntax
   end



  # Enable provisioning with a shell script.

  config.vm.provision "shell", privileged: true, inline: <<-SHELL
    apt-get install -y dkms
    apt-get upgrade -y
    apt-get install -y git ubuntu-desktop gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-gl libqt5gui5 libfuse2 libsdl2-2.0-0 ntpdate
    usermod -aG docker vagrant
    usermod -aG dialout vagrant
    sed -i 's/#  Automatic/Automatic/g' /etc/gdm3/custom.conf
    sed -i 's/user1/vagrant/g' /etc/gdm3/custom.conf
    echo "X-GNOME-Autostart-enabled=false" > /etc/xdg/autostart/gnome-initial-setup-first-login.desktop
    systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
    echo "export DISPLAY=:0" > /etc/profile.d/globals.sh
    if ! grep -q xhost /home/vagrant/.bashrc; then
       echo "xhost + > /dev/null" >> /home/vagrant/.bashrc
    fi
    systemctl disable apport
    # remove unecessary packages
    apt-get remove --purge -y apport whoopsie modemmanager
    rm /var/crash/*
    if ! [ -e "/home/vagrant/QGroundControl.AppImage" ]; then
      echo "QGroundControl was not found, downloading..."
      wget https://d176tv9ibo4jno.cloudfront.net/latest/QGroundControl.AppImage -O /home/vagrant/QGroundControl.AppImage
      chown vagrant:vagrant /home/vagrant/QGroundControl.AppImage
      chmod +x /home/vagrant/QGroundControl.AppImage
    fi
    sudo ntpdate pool.ntp.org
    echo "*/5 * * * * root ntpdate pool.ntp.org >> /dev/null 2>&1" > /etc/cron.d/timefix
  SHELL

  # time zone autodetection
  Vagrant.configure("2") do |config|
    require "time"
    offset = ((Time.zone_offset(Time.now.zone) / 60) / 60) + (Time.now.dst? ? 1 : 0)
    timezone_suffix = offset >= 0 ? "-#{offset.to_s}" : "+#{offset.to_s}"
    timezone = 'Etc/GMT' + timezone_suffix
    config.vm.provision :shell, privileged: true, inline: <<-'SHELL'
      ln -fs /usr/share/zoneinfo/#{timezone} /etc/localtime
      dpkg-reconfigure -f noninteractive tzdata
    SHELL
  end

  config.vm.provision :shell do |shell|
    shell.privileged = true
    shell.inline = "echo Rebooting"
    shell.reboot = true
  end

  # launch docker and redirect display to VM
  #$unprivileged = <<-SCRIPT
  config.vm.provision "shell", privileged: false, inline: <<-'SHELL'
    DISPLAY=:0 gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-battery-timeout 0
    DISPLAY=:0 gsettings set org.gnome.desktop.session idle-delay 0
    DISPLAY=:0 gsettings set org.gnome.settings-daemon.plugins.power sleep-inactive-ac-timeout 0
    DISPLAY=:0 gsettings set org.gnome.desktop.screensaver ubuntu-lock-on-suspend false
    DISPLAY=:0 gsettings set org.gnome.desktop.lockdown disable-lock-screen true
    DISPLAY=:0 gsettings set org.gnome.desktop.screensaver lock-enabled false
    # update git
    echo "Updating git modules..."
    cd /vagrant
    #git submodule sync --recursive
    git submodule update --init --recursive
    git submodule update --recursive
    echo "git is up to date"
    # Avoid the "has modification time x.x s in the future. Clock skew detected.  Your build may be incomplete." errors
    #echo "Files timestamp fix..."
    #find /vagrant/src -type f -exec touch {} +
    #echo "Files timestamp fix done."
    run=`docker container inspect -f '{{.State.Running}}' px4`
    if [ "$run" == "true" ]; then
      echo "Docker already running!!!"
      docker stop px4
      echo "Docker stopped.."
    fi
    if docker ps -aq -f status=exited -f name=px4; then
      echo "Removing docker container.."
      docker container rm px4 -f
    fi
    echo "Building docker container.."
    cd /vagrant && docker build -t px4_app .
    chmod +x /vagrant/src/app/app.py
    echo "Starting docker container.."
    docker run -d\
        --restart=always \
        --env=LOCAL_USER_ID="$(id -u)" \
        -v /vagrant:/vagrant:rw \
        -v /tmp/.X11-unix:/tmp/.X11-unix:ro \
        -e DISPLAY=:0 \
        -e LOCAL_USER_ID="$(id -u)" \
        -w /vagrant/src \
        --name=px4 px4_app sleep infinity
    DISPLAY=:0 xhost +local:
    docker exec -w /vagrant/src/PX4-Autopilot px4 make px4_sitl_default
    docker exec -w /vagrant/src px4 /enter colcon build --symlink-install
    #if ![ -e microros_ws/src/uros ]; then
    #    docker exec px4 /enter ros2 run micro_ros_setup create_agent_ws.sh
    #    mv  -v /vagrant/src/src/* /vagrant/src/microros_ws/src
    #    rm -rf /vagrant/src/src
    #fi
    docker exec  px4 /enter ros2 run micro_ros_setup build_agent.sh
    # users logged via ssh can use command: docker exec px4 gazebo ...
    # write exact ip address of the docker to the QGC config
    if ! [ -d /home/vagrant/.config/QGroundControl.org ]; then
       echo "QGC Config dir was not found, creating..."
       mkdir /home/vagrant/.config/QGroundControl.org
    fi 
    # retrieve docker ip
    echo "Retrieving docker ip..."
    DOCKER_IP=$(docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' px4)
    echo "Docker ip: $DOCKER_IP"
    if [ -e /home/vagrant/.config/QGroundControl.org/QGroundControl.ini ]; then
    # delete current LinkConfiguration definitions
       echo "QGC configuration was found, altering configuration..."
       sed -i '/^\[LinkConfigurations\]/,/^$/d' /home/vagrant/.config/QGroundControl.org/QGroundControl.ini
    fi
    # add new LinkConfiguration definitions
    echo "Configuring QGC Links..."
    cat >> /home/vagrant/.config/QGroundControl.org/QGroundControl.ini <<EOF
[LinkConfigurations]
Link0\auto=true
Link0\high_latency=false
Link0\host0=$DOCKER_IP
Link0\hostCount=1
Link0\name=gazebo
Link0\port=18570
Link0\port0=18570
Link0\type=1
count=1
EOF
  SHELL
end

enter

BatchFile
Helper file to run ros commands in docker. Since ROS requires sourcing the setup.bash. This allows you to run the ROS2 commands within docker. for example: docker exec container_name /enter colcon build
#!/bin/bash
# Initializes environment variables for docker and executes the wanted command.

# fix UserWarning: Usage of dash-separated
if [ -e /vagrant/src/offboard_ws/src/px4-offboard/setup.cfg ]; then
  sed -i 's/script-dir/script_dir/g' /vagrant/src/offboard_ws/src/px4-offboard/setup.cfg
  sed -i 's/install-scripts/install_scripts/g' /vagrant/src/offboard_ws/src/px4-offboard/setup.cfg
fi

source /opt/ros/foxy/setup.bash

if [ -e /vagrant/src/install/local_setup.bash ]; then
  source /vagrant/src/install/local_setup.bash
fi

"$@"

gitmodules

Gitignore
file to manage all modules for the project
[submodule "src/Micro-XRCE-DDS-Agent"]
	path = src/Micro-XRCE-DDS-Agent
	url = https://github.com/eProsima/Micro-XRCE-DDS-Agent.git
        ignore = dirty
[submodule "src/PX4-Autopilot"]
	path = src/PX4-Autopilot
	url = https://github.com/PX4/PX4-Autopilot.git
	branch = main
        ignore = dirty
[submodule "src/microros_ws/src/micro_ros_setup"]
	path = src/microros_ws/src/micro_ros_setup
	url = https://github.com/micro-ROS/micro_ros_setup.git
	branch = foxy
        ignore = dirty
[submodule "src/offboard_ws/src/px4_msgs"]
	path = src/offboard_ws/src/px4_msgs
	url = https://github.com/PX4/px4_msgs.git
        ignore = dirty
[submodule "src/offboard_ws/src/px4-offboard"]
	path = src/offboard_ws/src/px4-offboard
	url = https://github.com/Jaeyoung-Lim/px4-offboard.git
        ignore = dirty
[submodule "src/offboard_ws/src/px4_ros_com"]
	path = src/offboard_ws/src/px4_ros_com
	url = https://github.com/PX4/px4_ros_com.git

readme

Textile
readme from git
# tayaran_nxp

## 1 Run Vagrant Up

`vagrant up`

## 2 SSH Into Virtual Machine or Log in through GUI

`vagrant ssh` | log in to with `vagrant` as both username and password

## Gazebo Simulations
### Method 1 - Dynamic Simulation

[https://docs.px4.io/v1.12/en/simulation/gazebo_vehicles.html](https://docs.px4.io/v1.12/en/simulation/gazebo_vehicles.html)

Go inside docker container using `docker attach px4` and run
`make px4_sitl gazebo_solo` for Quadrotor

### Method 2 - Static Simulation

Go inside test_pkg, source your bash and run the following:
```
ros2 launch test_pkg drone_launch.py
```

## Testing

```
vagrant ssh
docker exec -it px4 make px4_sitl_default gazebo
```

## QGroundControl

```
vagrant ssh
./QGroundControl.AppImage
```

## ROS2 Integration
Go inside docker container using `docker exec -it px4 bash`, navigate to parent directory using `cd ../..` and run
```
source /opt/ros/foxy/setup.bash
colcon build
```

## USB Support

### Install VirtualBox Extension Pack

https://www.virtualbox.org/wiki/Downloads

### List connected usb devices

```
VBoxManage list usbhost
```

### Redirect the wanted device to the Vagrant VM

```
vb.customize ["modifyvm", :id, "--usb", "on"]
vb.customize ["modifyvm", :id, "--usbehci", "on"]
vb.customize ["usbfilter", "add", "0", 
  "--target", :id, 
  "--name", "This is the identifier",
  "--manufacturer", "SuYin",
  "--product", "Laptop_Integrated_Webcam_HD"]
```

### Example

```
VBoxManage list usbhost

  UUID:               13270e22-b0e8-4efc-bd57-75541c649dee
  VendorId:           0x8644 (8644)
  ProductId:          0x800b (800B)
  Revision:           1.0 (0100)
  Port:               0
  USB version/speed:  0/High
  Manufacturer:       General 
  Product:            USB Flash Disk  
  SerialNumber:       880000000000017E
  Address:            p=0x800b;v=0x8644;s=0x00038015f557c434;l=0x14130000
  Current State:      Busy
```

Vagrant config should be:

```
vb.customize ["modifyvm", :id, "--usb", "on"]
vb.customize ["modifyvm", :id, "--usbehci", "on"]
vb.customize ["usbfilter", "add", "0",
  "--target", :id,
  "--vendorid", "8644",
  "--productid", "800B",
  "--product", "USB Flash Disk"]
```

**This will not automatically redirect usb either**, to redirect it on VM startup we have to use the following configuration option:

```
vb.customize "post-boot", ["controlvm", :id, "usbattach", "p=0x800b;v=0x8644;s=0x00038015f557c434;l=0x14130000"]
```

The last parameter is from `VBoxManage list usbhost` **Address** line.

### Linux

Untested, but should work as expected.


### Windows

VirtualBox and cmd where vagrant is called must be running with Administrator privilegies.

On Windows machine the device address can contain **\** (slahes) and must be escaped with one more slash, for example if we have the device addres like this: `{36fc9e60-c465-11cf-8056-444553540000}\0012` we have to escape \0012 with \, the full correct vagrant config line will look like:
```
vb.customize "post-boot", ["controlvm", :id, "usbattach","{36fc9e60-c465-11cf-8056-444553540000}\\0012"]
```

### MacOS Difficulties

*NS_ERROR_FAILURE (0x80004005) Unable to create proxy device for USB*

Currently, there is no known solution for it (https://forums.virtualbox.org/viewtopic.php?f=8&t=107333). Only run it with elevated privilegies (root)
So we have to run sudo `sudo /Applications/VirtualBox.app/Contents/MacOS/VirtualBox` and then `sudo vagrant up`


## Git submodules reset

### Method 1

```
git submodule foreach --recursive git reset --hard
```
However, it may fail under some cases, especially after a merging which remove/add submodules.

### Method 2

```
# unbinds all submodules
git submodule deinit -f .
# checkout again
git submodule update --init --recursive
```

## Import Solidworks CAD into Gazebo
> Install the SW2URDF Add-in to Solidworks. See: http://wiki.ros.org/sw_urdf_exporter
> Generate URDF of the model using the Add-in. Follow this tutorial: http://wiki.ros.org/sw_urdf_exporter/Tutorials/Export%20an%20Assembly
> Using drone_launch.py as a template: select the gazebo world you want from its collection (see: https://github.com/leonhartyao/gazebo_models_worlds_collection) and add your urdf file. 

drone launch file

Python
launch file for drone and gazebo worls
"""Launch Gazebo server and client with command line arguments."""
"""Spawn robot from URDF file."""


import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.actions import ExecuteProcess
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
    use_sim_time = LaunchConfiguration('use_sim_time', default='true')
    robot_name = 'test_pkg'
    world_file_name = 'agriculture.world'

    world = os.path.join('src/test_pkg/Quadcopter2', 'worlds', world_file_name)

    urdf = os.path.join('src/test_pkg/Quadcopter2', 'urdf', 'Quadcopter2.urdf')

    xml = open(urdf, 'r').read()

    xml = xml.replace('"', '\\"')

    swpan_args = '{name: \"my_robot\", xml: \"' + xml + '\" }'

    return LaunchDescription([
        ExecuteProcess(
            cmd=['gazebo', '--verbose', world,
                 '-s', 'libgazebo_ros_factory.so'],
            output='screen'),

        ExecuteProcess(
            cmd=['ros2', 'param', 'set', '/gazebo',
                 'use_sim_time', use_sim_time],
            output='screen'),

        ExecuteProcess(
            cmd=['ros2', 'service', 'call', '/spawn_entity',
                 'gazebo_msgs/SpawnEntity', swpan_args],
            output='screen'),
    ])

tayaran_nxp repository

Complete code for out project. Including environment that contains VagrantFile to create virtual machine using vagrant that contains all required software in Docker, which interfaces with QGroundControl to control the drone and the simulation

Credits

Prince Aly

Prince Aly

1 project • 1 follower
Gabriella GG

Gabriella GG

1 project • 1 follower
Michael Moorer

Michael Moorer

1 project • 0 followers
Jack Green

Jack Green

1 project • 1 follower

Comments