gif gif

The interesting corner

gif gif

ROS notes

ROS

PX4 kan met ROS aangestuurd worden: ROS setup. Alleen ROS 1 is verouderd, er is een betere versie die aangeraden wordt: ROS 2. De verbindingslaag tussen PX4 en ROS2 is de PX4-ROS2 bridge (GitHub). Dit gebruikt XRCE-DDS. Dit zorgt ervoor dat uORB Messaging op een companion computer gepublished en subscribed kan worden alsof het gewone ROS 2 topics zijn. PX4 gebruikt een implementatie eProsima Micro XRCE-DDS gebruikt. Dit is een DDS-XRCE implementatie voor resource constrained devices. DDS (Data Distribution Service) is een data communicatieprotocol dat met publishen en subscriben werkt en er voor zorgt dat data op de juiste tijd aankomt. DDS-XRCE zorgt ervoor dat devices met weinig resources kunnen communiceren met een groter DDS netwerk waarbij de XRCE Agent als een server gedraagt tussen de devices en het DDS netwerk. De architectuur tussen ROS 2 met XRCE-DDS op de companion computer en de PX4 flight controller:

XRCE-DDS PX4 ROS 2 architecture

Volgens de ROS 2 user guide van PX4 werkt de XRCE-DDS middleware alleen op PX4 versie v1.14. Deze is nog in beta. Voor stable versie v1.13 kan de microRTPS middleware gebruikt worden.

PX4 ROS2 offboard control example: arm drone, stijg 5 meter op en blijf hangen (DIT IS VOOR V1.14). Voor V1.13 kan DEZE offboard TUTORIAL gebruikt worden. Er is ook een handige gistover hoe offboard control werkt. Deze werkt met microROS.

[VIDEO] ROS2 in PX4: Technical Details of a Seamless Transition to XRCE-DDS.. - Pablo Garrido & Nuno Marques

Ardupilot kan met ROS aangestuurd worden door MAVROS. MAVROS is in pre-alpha development voor ROS2. Support is dus limited.

Understanding ROS topics

MicroRTPS op PX4 v1.13

Volgens de docs werkt de XRCE middleware alleen met PX4 v1.14, en niet met PX4 v1.13, die ik heb. v1.14 is nog maar in beta. Volgens de docs werkt ros2 op v1.13 met een MicroRTPS verbinding. Dit gebruikt nog steeds eProsima Fast-DDS.

Ik heb de guide gevolgd om PX4-ROS 2 bridge te installeren. Eerst heb ik de guide voor Fast DDS gevolgd. Tijdens het installeren kreeg ik een error tijdens het uitvoeren van make -j$(nproc --all)

/home/ubuntu/FastDDS-2.0.2/src/cpp/security/authentication/PKIDH.cpp:824:33: error: invalid conversion from ‘const dh_st*’ to ‘DH*’ {aka ‘dh_st*’} [-fpermissive]
  824 |                 EVP_PKEY_get0_DH(dhkey);
      |                 ~~~~~~~~~~~~~~~~^~~~~~~
      |                                 |
      |                                 const dh_st*


/home/ubuntu/FastDDS-2.0.2/src/cpp/security/authentication/PKIDH.cpp:861:37: error: invalid conversion from ‘const ec_key_st*’ to ‘EC_KEY*’ {aka ‘ec_key_st*’} [-fpermissive]
  861 |                 EVP_PKEY_get0_EC_KEY(dhkey);
      |                 ~~~~~~~~~~~~~~~~~~~~^~~~~~~
      |                                     |
      |                                     const ec_key_st*
            

Tried a dirty fix:

#if IS_OPENSSL_1_1 && 0
                 EVP_PKEY_get0_EC_KEY(dhkey);
#else
                 dhkey->pkey.ec;
#endif // if IS_OPENSSL_1_1
            

But this gave a similar error:

/home/ubuntu/FastDDS-2.0.2/src/cpp/security/authentication/PKIDH.cpp: In function ‘bool store_dh_public_key(EVP_PKEY*, int, std::vector<unsigned char>&, eprosima::fastrtps::rtps::security::SecurityException&)’:
  /home/ubuntu/FastDDS-2.0.2/src/cpp/security/authentication/PKIDH.cpp:826:22: error: invalid use of incomplete type ‘EVP_PKEY’ {aka ‘struct evp_pkey_st’}
    826 |                 dhkey->pkey.dh;
        |  
            

Tried the OpenSSL part to get openssl 1.1 from this gist. After installing and running cmake -DTHIRDPARTY=ON -DSECURITY=ON .. I got the error Could NOT find GMock (missing: GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY). Fixed with this post by installing libgmock-dev. After deleting everything in the build folder and installing OpenSSL 1_0_2g (added -DOPENSSL_ROOT_DIR with path from this answer), I compiled with that version:


ubuntu@ubuntu:~/FastDDS-2.0.2/build$ cmake -DTHIRDPARTY=ON -DSECURITY=ON -DOPENSSL_ROOT_DIR=/usr/local/ssl/* ..
-- Setting build type to 'Release' as none was specified.
-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring Fast RTPS
-- Version: 2.0.2
-- To change the version modify the file configure.ac
-- Performing Test SUPPORTS_CXX14
-- Performing Test SUPPORTS_CXX14 - Success
-- Performing Test SUPPORTS_CXX1Y
-- Performing Test SUPPORTS_CXX1Y - Success
-- Performing Test SUPPORTS_CXX11
-- Performing Test SUPPORTS_CXX11 - Success
-- fastcdr library found...
-- Looking for pthread.h
-- Looking for pthread.h - found
-- Looking for pthread_kill
-- Looking for pthread_kill - not found
-- Looking for pthread_create in pthreads
-- Looking for pthread_create in pthreads - not found
-- Looking for pthread_create in pthread
-- Looking for pthread_create in pthread - found
-- Found Threads: TRUE
-- Thirdparty/boost compiled OK
setting OpenSSL ROOT DIR
OpenSSL root dir set to /usr/local/ssl/*
-- Found OpenSSL: /usr/local/ssl/lib/libcrypto.so (found version "1.0.2g")
-- OpenSSL library 1.0.2g found...
-- Found GTest: /usr/lib/aarch64-linux-gnu/cmake/GTest/GTestConfig.cmake (found version "1.11.0")
-- Found GMock: /usr/lib/aarch64-linux-gnu/libgmock.a
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ubuntu/FastDDS-2.0.2/build
            

De log van de installatie van FastDDS (make commando) is hier in de library te vinden. De log van de installatie van FastRTPS-Gen is hier in de library te vinden. Ik heb ook de fastddsgen package geinstalleerd.

Building was a nightmare. The pi did not have enough resources to build the px4_ros_com package. I tried with branch release/1.13, main branch and both didn't work. I then tried to set up the ROS 2 environment on my laptop the same way and build from there. Both release/1.13 and main branches tried. Building worked but when transferring to Pi the nodes could not be run. I tried it at home and on the 5G Hub:

building process 1 building process 1 building process 1

Trying with Ubuntu 20.04 and ROS 2 Foxy

Humble and Ubuntu 22.04 didn't work, the examples use ROS 2 foxy and Ubuntu 20.04, so I tried to use that. I followed the guide. It still hangs, and the problem is the same as in this question.

Tip: gebruik script om alle commando's en outputs in een file op te slaan.

I tried building overnight with ubuntu 20 and ROS foxy. on the release/1.13 branch it gave an error in the offboard control example. Then I tried to build on the main branch.

New raspberry Pi with 8GB of RAM

Nieuwe raspberry pi met 8GB ram besteld omdat het builden van de px4_ros_com niet lukte met 2gb. Hierna werkte het wel gelijk. De output is hier te vinden. Summary: 3 packages finished [1min 39s]. Nu wilde ik wel proberen of het werkt met ubuntu 22 en ros 2 humble. Hiervoor heb ik eerst een backup image gemaakt van de ubuntu 20 installatie. Dit heb ik niet gedaan, want volgens de REP-2000 heeft ROS 2 Foxy geen Ubuntu 22 support.

SD kaart kapot, nieuwe geflashed. Kon wel verbinding maken met PX4, maar kreeg geen messages terug. deze link kan misschien een oplossing zijn.

Trying with PX4 v1.14 and MicroXRCE-DDS

As a last resort and as Benja said in my question, I tried putting PX4 v1.14 on the pixhawk and using XRCE-DDS under Ubuntu 20 with ROS 2 Foxy. I installed everything and I managed to get the XRCE bridge working, with bidirectional communication. The logs are available here in The Library.

According to the Offboard Control docs, I need to send OffboardControlMode messages according to the type of control I want. Given that I don't have GPS and want to control the drone with the raspberry pi, I need to send messages that have the attitude boolean set to true. This needs to be sent continuously. After that, if I want to control the drone, I have to send VehicleAttitudeSetpoint messages. Looking at the dds topics, I need to publish these to the /fmu/in/offboard_control_mode topic. When running this node and also running the MicroXRCE-DDS bridge, arming the drone worked, but after a while the drone got disarmed. The log can be found here.

Installing ROS

Om te zien welke platforms en languages gesupport worden: REP 2000. In de Install Guide staat dat je Ubuntu op de pi moet runnen of raspbian moet gebruiken met Docker.

Klein probleempje: ROS 2 werkt op Raspberry Pi OS alleen op Docker, en ik wil liever dat het bare metal runt want dat zorgt voor minder overhead. In dit artikel over waarom ROS en docker niet goed samenwerken staat:

  1. No dedicated high-level interfaces for accessing low-level hardware
  2. Om GPIO te accessen moet je de -priviliged flag gebruiken, waardoor je meer van de OS dan nodig blootstelt en de sandbox omgeving kwijt raakt -> security probleem
  3. Omdat er geen predifined interfaces zijn om containers met elkaar te laten praten moet je bridges en tunnels opzetten
  4. Docker is goed voor code die soms kort runt, niet voor iets wat constant draait.

Je kan ROS 2 ook op de pi runnen door ubuntu op de pi te installeren, maar dan werkt raspi-config miss niet goed. Je kan raspi-config wel op de pi op ubuntu installeren en ook de GPIO pins gebruiken

Oplossing: ubuntu 22.04 server op de pi. Waarom 22.04? -> volgens REP 2000 heeft deze Tier 1 support en wordt regelmatig bijgewerkt. Server omdat minder overhead en geen desktop nodig.

Gekozen voor Humble Hawksbill omdat dat de nieuwste en at the time of writing currently supported versie is.
Waarom geen rolling? -> (van de Rolling docs) Rolling Ridley is continuously updated and is subject to in-place updates which will at times include breaking changes. It is used for ROS 2 development and by maintainers who want their packages released and ready for the next stable distribution. We recommend that most users of ROS 2 use the latest stable distribution.

Ik heb een Domain ID geset: 0 (echo "export ROS_DOMAIN_ID=0" >> ~/.bashrc). The domain ID is used by DDS to compute the UDP ports that will be used for discovery and communication

Hierna Colcon geïnstalleerd:

ubuntu@raspberrypi-5g-drone: sudo apt install python3-colcon-common-extensions                  
                      

Hierna een workspace aangemaakt:

ubuntu@raspberrypi-5g-drone: mkdir -p ~/ros2_ws/src
ubuntu@raspberrypi-5g-drone: cd ~/ros2_ws
ubuntu@raspberrypi-5g-drone: git clone https://github.com/ros2/examples src/examples -b humble
ubuntu@raspberrypi-5g-drone: colcon build --symlink-install
Starting >>> examples_rclcpp_async_client
Starting >>> examples_rclcpp_cbg_executor
Starting >>> examples_rclcpp_minimal_action_client
Starting >>> examples_rclcpp_minimal_action_server
--- stderr: examples_rclcpp_cbg_executor
CMake Error at CMakeLists.txt:2 (project):
No CMAKE_CXX_COMPILER could be found.

Tell CMake where to find the compiler by setting either the environment
variable "CXX" or the CMake cache entry CMAKE_CXX_COMPILER to the full path
to the compiler, or to the compiler name if it is in the PATH.


---
Failed   <<< examples_rclcpp_cbg_executor [1.62s, exited with code 1]
                   Aborted  <<< examples_rclcpp_minimal_action_client [1.60s]
                   Aborted  <<< examples_rclcpp_minimal_action_server [1.58s]
                   Aborted  <<< examples_rclcpp_async_client [1.66s]

                   Summary: 0 packages finished [2.53s]
                   1 package failed: examples_rclcpp_cbg_executor
                   3 packages aborted: examples_rclcpp_async_client examples_rclcpp_minimal_action_client examples_rclcpp_minimal_action_server
                   4 packages had stderr output: examples_rclcpp_async_client examples_rclcpp_cbg_executor examples_rclcpp_minimal_action_client examples_rclcpp_minimal_action_server
                   18 packages not processed
                      

Zoals te zien kreeg ik de error No CMAKE_CXX_COMPILER could be found. Opgelost met sudo apt-get update && sudo apt-get install build-essential

Setup colcon cd met:

echo "source /usr/share/colcon_cd/function/colcon_cd.sh" >> ~/.bashrc
echo "export _colcon_cd_root=/opt/ros/humble/" >> ~/.bashrc
echo "source /usr/share/colcon_argcomplete/hook/colcon-argcomplete.bash" >> ~/.bashrc
                      

Hierna de examples gecloned:

ubuntu@raspberrypi-5g-drone:~/ros2_ws/src$ git clone https://github.com/ros/ros_tutorials.git -b humble-devel
ubuntu@raspberrypi-5g-drone:~/ros2_ws$ sudo apt install python3-rosdep2
ubuntu@raspberrypi-5g-drone:~/ros2_ws$ rosdep install -i --from-path src --rosdistro humble -y

ERROR: your rosdep installation has not been initialized yet.  Please run:

rosdep update

ubuntu@raspberrypi-5g-drone:~/ros2_ws$ rosdep update
reading in sources list data from /etc/ros/rosdep/sources.list.d
Hit file:///usr/share/python3-rosdep2/debian.yaml
Hit https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/osx-homebrew.yaml
Hit https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/base.yaml
Hit https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/python.yaml
Hit https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/ruby.yaml
Hit https://raw.githubusercontent.com/ros/rosdistro/master/releases/fuerte.yaml
Query rosdistro index https://raw.githubusercontent.com/ros/rosdistro/master/index-v4.yaml
Skip end-of-life distro "ardent"
Skip end-of-life distro "bouncy"
Skip end-of-life distro "crystal"
Skip end-of-life distro "dashing"
Skip end-of-life distro "eloquent"
Add distro "foxy"
Skip end-of-life distro "galactic"
Skip end-of-life distro "groovy"
Add distro "humble"
Skip end-of-life distro "hydro"
Skip end-of-life distro "indigo"
Skip end-of-life distro "jade"
Skip end-of-life distro "kinetic"
Skip end-of-life distro "lunar"
Add distro "melodic"
Add distro "noetic"
Add distro "rolling"
updated cache in /home/ubuntu/.ros/rosdep/sources.cache
ubuntu@raspberrypi-5g-drone:~/ros2_ws$ rosdep install -i --from-path src --rosdistro humble -y                
#All required rosdeps installed successfully
                      

The install section for ROS for in the .bashrc file can be found here.

Configuring for PX4-ROS 2 bridge

from the user guide. Cloned the px4_msgs and px4_ros_com repositories and ran the install script with ./build_ros2_workspace.bash --ros_distro humble --ros_path ~/ros2_ws/install/setup.bash --verbose.

creating a sample package

A ROS 2 package can be made using C++ or Python. De package zit in de src map van de ros2_ws map.

package in C++:

ubuntu@raspberrypi-5g-drone:~/ros2_ws/src$ ros2 pkg create --build-type ament_cmake --node-name my_node my_package
going to create a new package
package name: my_package
destination directory: /home/ubuntu/ros2_ws/src
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['ubuntu <ubuntu@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_cmake
dependencies: []
node_name: my_node
creating folder ./my_package
creating ./my_package/package.xml
creating source and include folder
creating folder ./my_package/src
creating folder ./my_package/include/my_package
creating ./my_package/CMakeLists.txt
creating ./my_package/src/my_node.cpp

[WARNING]: Unknown license 'TODO: License declaration'.  This has been set in the package.xml, but no LICENSE file has been created.
It is recommended to use one of the ament license identitifers:
Apache-2.0
BSL-1.0
BSD-2.0
BSD-2-Clause
BSD-3-Clause
GPL-3.0-only
LGPL-3.0-only
MIT
MIT-0
ubuntu@raspberrypi-5g-drone:~/ros2_ws/src$ ls
my_package  ros_tutorials
ubuntu@raspberrypi-5g-drone:~/ros2_ws$ colcon build --packages-select my_package
Starting >>> my_package
Finished <<< my_package [1.08s]

                   Summary: 1 package finished [1.79s]
                   ubuntu@raspberrypi-5g-drone:~/ros2_ws$ . install/setup.bash
                   ubuntu@raspberrypi-5g-drone:~/ros2_ws$ ros2 run my_package my_node
                   hello world my_package package
                      

Content van my_node.cpp:

#include <cstdio>

int main(int argc, char ** argv)
{
(void) argc;
(void) argv;

printf("hello world my_package package\n");
return 0;
}
                      

package in Python:

ros2 pkg create --build-type ament_python --node-name my_node_py my_package_py
going to create a new package
package name: my_package_py
destination directory: /home/ubuntu/ros2_ws
package format: 3
version: 0.0.0
description: TODO: Package description
maintainer: ['ubuntu <ubuntu@todo.todo>']
licenses: ['TODO: License declaration']
build type: ament_python
dependencies: []
node_name: my_node_py
creating folder ./my_package_py
creating ./my_package_py/package.xml
creating source folder
creating folder ./my_package_py/my_package_py
creating ./my_package_py/setup.py
creating ./my_package_py/setup.cfg
creating folder ./my_package_py/resource
creating ./my_package_py/resource/my_package_py
creating ./my_package_py/my_package_py/__init__.py
creating folder ./my_package_py/test
creating ./my_package_py/test/test_copyright.py
creating ./my_package_py/test/test_flake8.py
creating ./my_package_py/test/test_pep257.py
creating ./my_package_py/my_package_py/my_node_py.py

[WARNING]: Unknown license 'TODO: License declaration'.  This has been set in the package.xml, but no LICENSE file has been created.
It is recommended to use one of the ament license identitifers:
Apache-2.0
BSL-1.0
BSD-2.0
BSD-2-Clause
BSD-3-Clause
GPL-3.0-only
LGPL-3.0-only
MIT
MIT-0
ubuntu@raspberrypi-5g-drone:~/ros2_ws$ colcon build --packages-select my_package_py
Starting >>> my_package_py
--- stderr: my_package_py
/usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
warnings.warn(
---
Finished <<< my_package_py [3.72s]

                   Summary: 1 package finished [4.37s]
                   1 package had stderr output: my_package_py
                   ubuntu@raspberrypi-5g-drone:~/ros2_ws$ . install/setup.bash
                   ubuntu@raspberrypi-5g-drone:~/ros2_ws$ ros2 run my_package_py my_node_py
                   Hi from my_package_py.
                      

Content van my_node_py.py:

def main():
print('Hi from my_package_py.')


if __name__ == '__main__':
main()
                      

Creating a publisher/subscriber node in C++

from this tutorial
The code for the publisher:

// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// 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.

#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}

private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};

int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}                
                      

publisher package.xml:

Publisher CMakeLists.txt:

cmake_minimum_required(VERSION 3.8)
project(cpp_pubsub)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})

if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()

ament_package()
                      

Code for the subscriber:

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}

private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}
                      

The package.xml does not need to be changed to incorporate the subscriber. The CMakeLists.txt file does need some additions to be able to build the subscriber node:

cmake_minimum_required(VERSION 3.8)
project(cpp_pubsub)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})

if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# comment the line when a copyright and license is added to all source files
set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# comment the line when this package is in a git repo and when
# a copyright and license is added to all source files
set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()

ament_package()
                      

Installing dependencies and building package:

ubuntu@raspberrypi-5g-drone:~/ros2_ws$ rosdep install -i --from-path src --rosdistro humble -y
#All required rosdeps installed successfully
ubuntu@raspberrypi-5g-drone:~/ros2_ws$ colcon build --packages-select cpp_pubsub
Starting >>> cpp_pubsub
[Processing: cpp_pubsub]
Finished <<< cpp_pubsub [45.2s]

                   Summary: 1 package finished [46.2s]              
                      

After running the nodes, you can see them talking to eachother:

nodes talking to eachother

Creating a publisher/subscriber node in Python

From this tutorial

I created the package with the ros2 pkg create --build-type ament_python py_pubsub command. Then I downloaded the publisher script which looks like this:

import rclpy
from rclpy.node import Node

from std_msgs.msg import String


class MinimalPublisher(Node):

def __init__(self):
  super().__init__('minimal_publisher')
  self.publisher_ = self.create_publisher(String, 'topic', 10)
  timer_period = 0.5  # seconds
  self.timer = self.create_timer(timer_period, self.timer_callback)
  self.i = 0

def timer_callback(self):
  msg = String()
  msg.data = 'Hello World: %d' % self.i
  self.publisher_.publish(msg)
  self.get_logger().info('Publishing: "%s"' % msg.data)
  self.i += 1


def main(args=None):
rclpy.init(args=args)

minimal_publisher = MinimalPublisher()

rclpy.spin(minimal_publisher)

# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
minimal_publisher.destroy_node()
rclpy.shutdown()


if __name__ == '__main__':
main()
                      

The publisher package.xml file looks like this:

The publisher setup.py file looks like this:

from setuptools import setup

package_name = 'py_pubsub'

setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
  ('share/ament_index/resource_index/packages',
      ['resource/' + package_name]),
  ('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='ubuntu',
maintainer_email='ubuntu@todo.todo',
description='Example of publisher/subscriber nodes in Python',
license='Apache License 2.0',
tests_require=['pytest'],
entry_points={
  'console_scripts': [
      'talker = py_pubsub.publisher_member_function:main',
  ],
},
)
                      

The subscriber script looks like this:

# Copyright 2016 Open Source Robotics Foundation, Inc.
#
# 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.

import rclpy
from rclpy.node import Node

from std_msgs.msg import String


class MinimalSubscriber(Node):

def __init__(self):
  super().__init__('minimal_subscriber')
  self.subscription = self.create_subscription(
      String,
      'topic',
      self.listener_callback,
      10)
  self.subscription  # prevent unused variable warning

def listener_callback(self, msg):
  self.get_logger().info('I heard: "%s"' % msg.data)


def main(args=None):
rclpy.init(args=args)

minimal_subscriber = MinimalSubscriber()

rclpy.spin(minimal_subscriber)

# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
minimal_subscriber.destroy_node()
rclpy.shutdown()


if __name__ == '__main__':
main()                           
                      

I added the line 'listener = py_pubsub.subscriber_member_function:main', to tthe entry_points section of the setup.py file. I ran both nodes and you can see that they are talking to eachother:

result of nodes talking to eachother

Conclusion: which language to use?

I think I will use C++ for the nodes of the drone, here is why:

  1. It's faster than Python, which is good because the sensors need to be read very quickly
  2. It uses less configuration files as seen in the examples above
  3. It's easier to do embedded actions on the Pi in C++ than in Python

I found out that ROS2 nodes can talk to eachother regardless of the language they are written in. I will thus use C++ for embedded nodes (reading sensor data) and Python for other nodes (communication)

Analyse ROS2 onderwerpen

from the wiki:

Topics

Zie bovenstaande

Interfaces

ROS applications typically communicate through interfaces of one of three types: messages, services and actions. These interfaces are defined in .msg, .srv and .action files, respectively.

Er zijn ook een aantal nieuwe features in ROS 2 interfaces. Dit zijn bounded arrays (int32[<=5] bat), bounded strings (string<=5 bar) en default values (int32 X 123).

Messages

msg: .msg files are simple text files that describe the fields of a ROS message. They are used to generate source code for messages in different languages.

.msg files zitten in de msg/ directory van een package en hebben fields en constants

Field names zijn snake_case en kunnen een default value bevatten (achter de naam)

Messages kunnen ook arrays bevatten:

int32[] unbounded_integer_array
int32[5] five_integers_array
int32[<=5] up_to_five_integers_array

string string_of_unbounded_size
string<=10 up_to_ten_characters_string

string[<=5] up_to_five_unbounded_strings
string<=10[] unbounded_array_of_string_up_to_ten_characters_each
string<=10[<=5] up_to_five_strings_up_to_ten_characters_each
            

Constants: Each constant definition is like a field description with a default value, except that this value can never be changed programatically. This value assignment is indicated by use of an equal ‘=’ sign: constanttype CONSTANTNAME=constantvalue. Constant names have to be upper case.

Services

srv: .srv files describe a service. They are composed of two parts: a request and a response. The request and response are message declarations. They are located in the /srv dir of a package.

Services zijn declared met :

input_type input_name
---
output_type output_name

For example:

string str
---
string str

Or:

#request constants
int8 FOO=1
int8 BAR=2
#request fields
int8 foobar
another_pkg/AnotherMessage msg
---
#response constants
uint32 SECRET=123456
#response fields
another_pkg/YetAnotherMessage val
CustomMessageDefinedInThisPackage value
uint32 an_integer
        

Services are another method of communication for nodes in the ROS graph. Services are based on a call-and-response model, versus topics’ publisher-subscriber model. While topics allow nodes to subscribe to data streams and get continual updates, services only provide data when they are specifically called by a client.

services overview

Gebruiken voor aansturen drone? (set positie of volg route)

Actions (source)

action: .action files describe actions. They are composed of three parts: a goal, a result, and feedback. Each part is a message declaration itself.

Actions are intended for long running tasks. They consist of three parts: a goal, feedback, and a result. Actions are built on topics and services. Their functionality is similar to services, except actions are preemptable (you can cancel them while executing). They also provide steady feedback, as opposed to services which return a single response.

actions overview

Het aansturen van de turtlesim werkt met actions, ik kan actions ook gebruiken voor het manual aansturen van de drone als deze aan het vliegen is.

Executors (source)

Execution management in ROS 2 is explicated by the concept of Executors. they use 1 or more threads of the underlying OS to invoke callbacks of subscriptions, timers, service servers, action servers, etc. on incoming messages and events.

When you call rclcpp::spin(node), it calls the simplest executor: the Single-Threaded Executor. The current thread starts querying the rcl and middleware layers for incoming messages and other events and calls the corresponding callback functions until the node shuts down. There are three Executor types:

types of executors

[VIDEO] Understanding ROS2 Executables and Nodes

  • The Multi-Threaded Executor creates a configurable number of threads to allow for processing multiple messages or events in parallel
  • The Static Single-Threaded Executor optimizes the runtime costs for scanning the structure of a node in terms of subscriptions, timers, service servers, action servers, etc. It performs this scan only once when the node is added, while the other two executors regularly scan for such changes. Therefore, the Static Single-Threaded Executor should be used only with nodes that create all subscriptions, timers, etc. during initialization.
  • The Single Threaded Executor starts one thread on which it listens for messages and callbacks

Callback groups

Callbacks for nodes can be organized in groups. These groups must be stored throughout the execution of a node (for instance as a class member of that node).

A tutorial on callback groups can be found here. There are 2 callback groups:

  • Mutually exclusive: Callbacks of this group must not be executed in parallel.
  • Reentrant: Callbacks of this group may be executed in parallel.

There's also an example on how to prioritize callbacks (goed voor drone sensordata?)

Because some problems can arise with callback priorities, you can use the WaitSet class of rclcpp (reference) to wait directly on subscriptions, timers, service servers, action servers, etc. instead of using an Executor. An example can be seen here.

Guard conditions

A guard condition is a condition that can be waited on in a single wait set and asynchronously triggered. rclcpp reference

Parameters (source)

Associated with individual nodes

Parameters are used before starting a node and are used during execution of a node. They are kind of like preprocessor variables in C.

addressed by node name, node namespace, parameter name, and parameter namespace. Providing a parameter namespace is optional.

Each parameter consists of a key, a value, and a descriptor. The key is a string and the value is one of the following types: bool, int64, float64, string, byte[], bool[], int64[], float64[] or string[]. By default all descriptors are empty, but can contain parameter descriptions, value ranges, type information, and additional constraints.

Er is een Parameters tutorial.

Parameters need to be declared in a class (in C++ or Python)

A node can have 2 callbacks for parameter changes: a callback before the parameter is changed and a callback after. The first callback is a set_parameter callback. The second callback is a on_parameter_event callback.