commit c98972e4a9c5794b38ecf09d1630fb294399c274 Author: winterhalderp Date: Wed Jan 13 15:28:01 2021 +0100 first test push diff --git a/Links.md b/Links.md new file mode 100644 index 0000000..af8cd82 --- /dev/null +++ b/Links.md @@ -0,0 +1,4 @@ +# Links for during ROS Workshop + +ros_tutorials: https://github.com/ros/ros_tutorials.git + diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1e8a57 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +# pubsub - Publisher & Subscriber Package +Publisher & Subscriber package as template package and source code. + +Created for ROS Workshop 2020 +Roverentwicklung für Explorationsaufgaben +Institute for Space Systems +University of Stuttgart. + +Created by Patrick Winterhalder, +[IRS](https://www.irs.uni-stuttgart.de/en/), University of Stuttgart. + + + + +## Workshop Prerequisites +* Install [Ubuntu 20.04]() +* Install Visual Studio Code using [Ubuntu Software](https://wiki.ubuntuusers.de/Ubuntu_Software/) +* Install [Git](https://linuxconfig.org/how-to-install-git-on-ubuntu-20-04-lts-focal-fossa-linux) (no account required yet) +* Install [ROS2](https://index.ros.org/doc/ros2/Installation/Foxy/Linux-Install-Debians/) ("desktop" on PC, "base" on Raspberry Pi). Do install _argcomplete_, no need for _ROS 1 bridge_ or _RMW implementations_. +* Install and update _rosdep_: + * `sudo apt install python3-rosdep2 -y` + * `rosdep update` + * (`sudo rosdep init`) +* Work through ["Beginner: CLI Tools"](https://index.ros.org/doc/ros2/Tutorials/) tutorial + * [Configuring your ROS 2 environment](https://index.ros.org/doc/ros2/Tutorials/Configuring-ROS2-Environment/): + * Source setup files (underlay, overlay) + * Configure .bashrc (shell startup script) + * Add colcon_cd to .bashrc (shell startup script) + * Check environment variables (check for correct installation) + * Configure ROS_DOMAIN_ID (DDS Network Number) + * Cover turtlesim, rqt, topics, services, actions + +## Install Instructions +* Move to colcon workspace: `cd ` +* Clone repository: `git clone git://github.com/patrickw135/pubsub.git .` (include the . at the end) +* Build workspace: `colcon build` +__Note:__ Only the files inside _src/_ are of importance. +* Remember to run `source ~//install/local_setup.sh` after every build. Best would be to [add this command to _.bashrc_](https://github.com/patrickw135/pubsub/blob/master/bashrc_addons.txt) which is run everytime you start a new console. + + +## During Workshop +* Create workspace: + * Install [_colcon_](https://index.ros.org/doc/ros2/Tutorials/Colcon-Tutorial/#colcon): `sudo apt install python3-colcon-common-extensions -y` +* Create packag inside _~/{workspace_name}/src_: + * `ros2 pkg create --build-type [ament_cmake, ament-python] ` + * Go back up one layer: `cd ..` + * Build workspace: `colcon build --symlink-install` +* Add [this](https://github.com/patrickw135/pubsub/blob/main/bashrc_addons.txt) to end of .bashrc (`sudo nano .bashrc`), find all instances of "`~/ws_overlay_foxy`" and replace it with your local path to your colcon workspace +* [Instruction](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md) on how to create a custom message to interface between nodes + diff --git a/bashrc_addons.bash b/bashrc_addons.bash new file mode 100644 index 0000000..7a16d07 --- /dev/null +++ b/bashrc_addons.bash @@ -0,0 +1,73 @@ +# Add these lines to .bashrc, do this: +# Add the the following in between "#----" to the end of your .bashrc script + +# To do this open .bashrc: +# sudo nano ~/.bashrc + +# Move to end of .bashrc (arrow down) +# Highlight the lines below and copy (Ctrl+C) +# Paste into console at the end of .bashrc (Right click, paste or CTRL+LShift+V) + + + +#---------------------------------------------------------------------------------- +# ROS 2 SETUP +echo "ROS 2 Setup:" + +# Source Underlay +cmd="/opt/ros/foxy/setup.bash" +source $cmd +echo "ROS Underlay: "$cmd + +# Source Overlay +# Change to the path of your workspace's "install/local_setup.bash" file +cmd="$HOME/colcon_ws/install/local_setup.bash" +source $cmd +echo "ROS Overlay: "$cmd + +# You can add other workspaces +#cmd="$HOME/colcon_libs/install/local_setup.bash" +#source $cmd +#echo "ROS Overlay: "$cmd +echo "******************************************************" + +# ROS 2 Settings +# Print ROS Variables +#printenv | grep -i ROS +echo "ROS_VERSION: "$ROS_VERSION +echo "ROS_PYTHON_VERSION: "$ROS_PYTHON_VERSION +echo "ROS_DISTRO: "$ROS_DISTRO +echo "******************************************************" + +# Define DDS Channel +# (this is the channel your ROS system communicates at over your local network) +# To change the channel open the ROS Underlay setup file: +# sudo nano /opt/ros/foxy/setup.bash +# Here, add the following command +# export ROS_DOMAIN_ID= +# eg.: export ROS_DOMAIN_ID=69 +# Important: no spaces before and after "=" (in bash) + +# Print DDS Settings +echo "ROS 2 DDS Settings:" +export ROS_DOMAIN_ID=69 +echo "ROS_DOMAIN_ID: "$ROS_DOMAIN_ID +echo "To change: sudo nano /opt/ros/foxy/setup.bash" +echo "******************************************************" + +# Source colcon directory jump: +#source /usr/share/colcon_cd/function/colcon_cd.sh +#export _colcon_cd_root=~/colcon_ws + +# Print active Node & Topics +echo "ROS 2 Topics active:" +ros2 topic list +echo "******************************************************" +#echo "ROS 2 Nodes active:" +#ros2 node list +echo "******************************************************" +printf "\n" +echo "If your package is not listed (ros2 pkg list):" +echo " * make sure you are sourcing the correct workspace: .bashrc" +echo " * delete build/, install/ and rebuild w/o errors" +#---------------------------------------------------------------------------------- diff --git a/instructions_custom_topics.md b/instructions_custom_topics.md new file mode 100644 index 0000000..097fa68 --- /dev/null +++ b/instructions_custom_topics.md @@ -0,0 +1,186 @@ +# How to create custom ROS topics +This is a short instruction on how to create custom interfaces using ROS topics. + +For this at least two packages will be required: +* The python package contains your scripts (eg. ROS nodes) +* The CMake package contains the custom msg/srv/act files + +The CMake package is required because the custom msg/srv/act files cannot be created inside the python package as this is not supported yet. + +__Table of Content__ +* [CMake Package (eg. /pubsub_msg)](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#cmake-package-eg-pubsub_msg) + * [Create CMake Package](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#1-create-cmake-package) + * [Create Message Files](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#2-create-message-files) + * [Configure CMakeLists.txt](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#3-configure-cmakeliststxt) + * [Configure package.xml](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#4-configure-packagexml) + * [Build Package](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#5-build-package) + * [Source newly built workspace](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#6-source-newly-built-workspace) + * [Check functionality](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#7-check-functionality) +* [Python Package (eg. /pubsub)](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#python-package-eg-pubsub) + * [Create Python Package](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#1-create-python-package) + * [Write Python Scripts](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#2-write-python-scripts) + * [Configure package.xml](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#3-configure-packagexml) + * [Build Package](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#4-build-package) + * [Source newly built workspace](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#5-source-newly-built-workspace) + * [Run scripts](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#6-run-scripts) +* [Sources](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#sources) + + + + + + + +## CMake Package (eg. /pubsub_msg) +This package makes up the basis for custom ROS interfaces and contains all custom msg/srv/act files. Additionally, the special files (_CMakeLists.txt_ and _package.xml_) describe how these interface files are to be used. + +This package must be created as a CMake package: `ros2 pkg create --build-type ament-cmake ` + +This will result in an empty package structure: +* msg/srv/act directory: + * This directory contains the custom msg files (eg. CustomMsg1.msg) +* CMakeLists.txt: + * This file describes how to build this package + * Configure this file according to this [instruction](https://index.ros.org/doc/ros2/Tutorials/Custom-ROS2-Interfaces/#cmakelists-txt) +* package.xml: + * This file contains meta information about this package + * Configure this file according to this [instruction](https://index.ros.org/doc/ros2/Tutorials/Custom-ROS2-Interfaces/#package-xml) + + +### 1. Create CMake Package +* Move to your colcon workspace's src directory: `cd /src` +* (For example: `cd ~/colcon_ws/src`) +* Create CMake package: `ros2 pkg create --build-type ament_cmake +* (Here: `ros2 pkg create --build-type ament_cmake pubsub_msg`) + + +### 2. Create Message Files +* If not already available create msg directory inside package directory. +Resulting structure: /src//msg +* Move to newly created msg direcrory +* Create your own custom message files, eg. _CustomMsg1.msg_. +Give your files comprehensible names, eg. _Epossetvalues.msg_ + + +### 3. Configure CMakeLists.txt +Open CMakeLists.txt and ad these lines before `if(BUILD_TESTING)`: +`find_package(builtin_interfaces REQUIRED)` +`find_package(rosidl_default_generators REQUIRED)` +`find_package(std_msgs REQUIRED)` +`find_package(rclcpp REQUIRED)` +Then also add custom lines depending your package, here the custom message/service files are added: +`rosidl_generate_interfaces(${PROJECT_NAME}` + ` "msg/CustomMsg1.msg"` + ` "msg/CustomMsg2.msg"` + ` DEPENDENCIES builtin_interfaces` + ` )` + +### 4. Configure package.xml +In order to let the build system know what this package depends on add these lines to _package.xml_: +```xml + + builtin_interfaces + rosidl_default_generators + builtin_interfaces + rosidl_default_runtime + rosidl_interface_packages + +``` + +### 5. Build Package +* Move back to the workspace's most top layer: `cd ~/` +* Build workspace: `colcon build` +* Sucessful response: +_Starting >>> pubsub +Starting >>> pubsub_msg +Finished <<< pubsub [0.85s] +Finished <<< pubsub_msg [1.09s] +Summary: 2 packages finished [1.56s]_ + + +### 6. Source newly built workspace +* Run: `source ~//install/local_setup.bash` +* If you already [updated your .bashrc file](https://github.com/patrickw135/pubsub/blob/master/bashrc_addons.txt) you can close all open consoles and start a new console (Ctrl+Alt+T). This will source your workspace automatically, as .bashrc is run every time you start a console. +__Important__: If you use multiple workspaces make sure you have the wanted workspace defined in .bashrc! Otherwise the changes introduced when building will not be available. + + +### 7. Check functionality +Check functionality of your messages by creataing a topic using your newly created message: +* CustomMsg1: +`ros2 topic pub /chatter1 pubsub_msg/CustomMsg1 "{temperature: {24.1234, 25.9876}, pressure: {1012.556, 1013.987}, humidity: {0.002, 0.001}}" --rate 1` +* Response: +_publisher: beginning loop +publishing #1: pubsub_msg.msg.CustomMsg1(temperature=[24.12339973449707, 25.987600326538086], pressure=[1012.5560302734375, 1013.9869995117188], humidity=[0.0020000000949949026, 0.0010000000474974513])..._ + +* CustomMsg2: +`ros2 topic pub /chatter2 pubsub_msg/CustomMsg2 "{pitch_ctrl: 33.33, yaw_ctrl: 0.5}" --rate 1` +* Response: +_publisher: beginning loop +publishing #1: pubsub_msg.msg.CustomMsg2(pitch_ctrl=33.33, yaw_ctrl=0.5)..._ + + + + + +## Python Package (eg. /pubsub) +This package contains your scripts, programs and libraries. After building the workspace (`colcon build`) the custom messages are available to all other packages. + +This package can be created as a CMake (C++) package or as a python package depending on your coding preference. +* C++: `ros2 pkg create --build-type ament-cmake ` +* Python: `ros2 pkg create --build-type ament-python ` + + + directory: +* This directory contains your python scripts (eg. listener.py) +* Also place the non-standard libraries in this directory and import the library in your python scripts + + +### 1. Create Python Package +* Move to your workspace's source directory: `cd /src` +* Create python package: `rps2 pkg create --build-type ament_python ` + +### 2. Write Python Scripts +When using custom interfaces in python scripts these must be imported into the python script first +```python +from .msg import +``` +replacing `` with the package containing the custom message and `` with the message file name (excluding the file ending .msg). +However, in order to be able to import the custom message types, `` must first be known to the ROS system. This was established when creating the [CMake package](https://github.com/patrickw135/pubsub/blob/master/instructions_custom_topics.md#cmake-package-eg-pubsub_msg) containing the custom message. Additionally, you must add this dependency to the _package.xml_ of this package as stated in the next chapter. + +### 3. Configure package.xml +In addition to importing the message type into your python script you must also configure _package.xml_ adding the package dependency of where you inherite the custom message from. Add this line to _package.xml_: +```xml + std_msgs + + + package_name +``` +exchanging _package_name_ with the source package of the custom message type (here _pubsub_msg_), e.g.: +```xml + std_msgs + + + pubsub_msg +``` + +### 4. Build Package +Now you can build the Python package. +* Move to your workspace's root: `cd ~/` +* Build workspace: `colcon build --symlink-install` + +### 5. Source newly built workspace +* Run: `source ~//install/local_setup.bash` +* If you already [updated your .bashrc file](https://github.com/patrickw135/pubsub/blob/master/bashrc_addons.txt) you can close all open consoles and start a new console (Ctrl+Alt+T). This will source your workspace automatically, as .bashrc is run every time you start a console. +__Important__: If you use multiple workspaces make sure you have the wanted workspace defined in .bashrc! Otherwise the changes introduced when building will not be available. + +### 6. Run scripts +* Run Talker: `ros2 run pubsub talker` +* Run Listener: `ros2 run pubsub listener` + +The talker console should print the sent data while the listener console should print the received data. These should match. + +If anything is unclear, compare this instruction material to the files in `/pubsub` and `/pubsub_msg`. + +## Sources +[ROS2 Tutorial](https://index.ros.org/doc/ros2/Tutorials/Custom-ROS2-Interfaces/#creating-custom-ros-2-msg-and-srv-files) +[theconstructsim custom messages](https://www.theconstructsim.com/ros2-tutorials-7-how-to-create-a-ros2-custom-message-new/) diff --git a/rpi_install.bash b/rpi_install.bash new file mode 100644 index 0000000..b04b187 --- /dev/null +++ b/rpi_install.bash @@ -0,0 +1,125 @@ +# Go through these bash commands one by one +# Prerequisite is a fresh Ubuntu 20.04 !! 64bit !! OS +# Only the password was changed after first boot +# Everything else is stock + +# First Update +sudo apt-get update && sudo apt-get upgrade -y && sudo apt-get dist-upgrade -y + +# Install ROS2 Foxy Fitzroy Desktop +sudo apt update && sudo apt install locales +sudo locale-gen en_US en_US.UTF-8 +sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 +sudo apt update && sudo apt install -y curl gnupg2 lsb-release +curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - +sudo sh -c 'echo "deb [arch=$(dpkg --print-architecture)] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2-latest.list' +sudo apt update && sudo apt install -y ros-foxy-ros-base +source /opt/ros/foxy/setup.bash +# Check to see if installed correctly +echo "ROS_VERSION: "$ROS_VERSION + + +# Install rosdep +sudo apt install -y python3-rosdep2 +rosdep update + + +# Install colcon +sudo apt install -y python3-colcon-common-extensions + + +# Install pip and argcomplete +sudo apt install -y python3-pip +pip3 install -U argcomplete + + +# in between +sudo apt update && sudo apt upgrade -y + + +# Create workspace +mkdir -p ~/colcon_ws/src +cd colcon_ws +colcon build + + + + + + +# Configure .bashrc: +# The the following in between "#----" to the end of your .bashrc script +#---------------------------------------------------------------------- +# ROS 2 SETUP +echo "ROS 2 Setup:" + +# Source Underlay +cmd="/opt/ros/foxy/setup.bash" +source $cmd +echo "ROS Underlay: "$cmd + +# Source Overlay +# Change to the path of your workspace's "install/local_setup.bash" file +cmd="$HOME/colcon_ws/install/local_setup.bash" +source $cmd +echo "ROS Overlay: "$cmd + +# You can add other workspaces +#cmd="$HOME/colcon_libs/install/local_setup.bash" +#source $cmd +#echo "ROS Overlay: "$cmd +echo "******************************************************" + +# ROS 2 Settings +# Print ROS Variables +#printenv | grep -i ROS +echo "ROS_VERSION: "$ROS_VERSION +echo "ROS_PYTHON_VERSION: "$ROS_PYTHON_VERSION +echo "ROS_DISTRO: "$ROS_DISTRO +export ROS_DOMAIN_ID=69 +echo "ROS_DOMAIN_ID: "$ROS_DOMAIN_ID +echo "To change: sudo nano /opt/ros/foxy/setup.bash" +echo "******************************************************" +echo "Topics active:" +ros2 topic list +echo "******************************************************" +printf "\n" +echo "If your package is not listed (ros2 pkg list):" +echo " * make sure you are sourcing the correct workspace: .bashrc" +echo " * delete build/, install/ and rebuild w/o errors" +# Parsing of git branch +parse_git_branch() { + git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/' +} +export PS1="\u@\h \[\e[32m\]\w \[\e[91m\]\$(parse_git_branch)\[\e[00m\]$ " +#---------------------------------------------------------------------- + + + + + + +# Install network tools +sudo apt install -y wireless-tools + + +# Install Git +sudo apt install -y git + +# Setup remote git from inside workspace +cd ~/colcon_ws +git init +git remote add origin https://github.com/patrickw135/pubsub.git +git fetch --all +git reset --hard origin/master + + +# Install your project specific python packages +# You can use pip3 as the install tool, eg: +pip3 install picamera +# Or you can use the software installer: +sudo apt install python3-picamera + + +# again +sudo apt update && sudo apt upgrade -y diff --git a/src/pubsub/package.xml b/src/pubsub/package.xml new file mode 100644 index 0000000..1d20916 --- /dev/null +++ b/src/pubsub/package.xml @@ -0,0 +1,31 @@ + + + + pubsub + 0.0.0 + TODO: Package description + patrick + TODO: License declaration + + + + + + std_msgs + + + + pubsub_msg + + + + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/src/pubsub/pubsub/__init__.py b/src/pubsub/pubsub/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/pubsub/pubsub/listener.py b/src/pubsub/pubsub/listener.py new file mode 100644 index 0000000..61c5d34 --- /dev/null +++ b/src/pubsub/pubsub/listener.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#********************************************# +# Listener Template + +# Creted for: +# ROS2 Workshop 2020 +# Roverentwicklung für Explorationsaufgaben +# Institute for Space Systems +# University of Stuttgart + +# Created by Patrick Winterhalder +# IRS, University of Stuttgart +#********************************************# + +import rclpy +from rclpy.node import Node + +# Import Subscriber Library +from .pubsub_library import CustomMsg1_sub +from .pubsub_library import CustomMsg2_sub + +# Import Message Types +from pubsub_msg.msg import CustomMsg1 +from pubsub_msg.msg import CustomMsg2 + + +def main(args=None): + rclpy.init(args=args) + + # Start nodes here, should create object for every node + listener_1 = CustomMsg1_sub(NODE_NAME="pubsub_listener_1", TOPIC_NAME="/chatter1",MSG_TYPE=CustomMsg1, NUM_MSGS=0) + listener_2 = CustomMsg2_sub(NODE_NAME="pubsub_listener_2", TOPIC_NAME="/chatter2",MSG_TYPE=CustomMsg2, NUM_MSGS=0) + + + while rclpy.ok(): + try: + # Insert main looping script here... + + # Receive Topics by running nodes + rclpy.spin_once(listener_1, timeout_sec=0.01) + rclpy.spin_once(listener_2, timeout_sec=0.01) + + # Do sth if message was received + if listener_1.topic_received is True: + listener_1.topic_received = False # zurücksetzen + msg_1 = listener_1.return_msg() + #********************************* + # Do sth based on received message + #********************************* + if listener_2.topic_received is True: + listener_2.topic_received = False # zurücksetzen + msg_2 = listener_2.return_msg() + #********************************* + # Do sth based on received message + #********************************* + + + # Check if "msg_1" or "msg_2" is available as a local variable + if 'msg_1' in locals(): + # Print msg_1 + listener_1.print_msg() + if 'msg_2' in locals(): + # Print msg_2 + listener_2.print_msg() + + # Here, you can now also publish/pass on the results of this script by + # using the "MinimalPublisher" class. For this please refer to "talker.py". + + except (KeyboardInterrupt, SystemExit): + print("\n\nShutting down...") + + # Insert ".destroy_node()" here for all running nodes in this script + # eg: + listener_1.destroy_node() + listener_2.destroy_node() + rclpy.shutdown() + + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/pubsub/pubsub/pubsub_library.py b/src/pubsub/pubsub/pubsub_library.py new file mode 100644 index 0000000..60fd5b3 --- /dev/null +++ b/src/pubsub/pubsub/pubsub_library.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#********************************************# +# Publisher/Subscriber Class Library + +# Creted for: +# ROS2 Workshop 2020 +# Roverentwicklung für Explorationsaufgaben +# Institute for Space Systems +# University of Stuttgart + +# Created by Patrick Winterhalder +# IRS, University of Stuttgart +#********************************************# + +import rclpy +from rclpy.node import Node + +# How to use: +# from pubsub_library import MinimalPublisher +# from pubsub_library import MinimalSubscriber +# minimal_publisher = MinimalPublisher(NODE_NAME='minimal_pub', TOPIC_NAME='user_controller', MSG_TYPE=Usercontroller, MSG_PERIOD=0.5) +# minimal_subscriber = MinimalSubscriber(NODE_NAME='minimal_sub', TOPIC_NAME='epos_feedback', MSG_TYPE=Eposreturn) +# See --> talker.py, listener.py + + + +#******************************************************************************# +# Definition of Parent Classes + +class MinimalPublisher(Node): + + def __init__(self, NODE_NAME, TOPIC_NAME, MSG_TYPE, MSG_PERIOD): + self.PUBLISHER_NAME= NODE_NAME + self.TOPIC_NAME = TOPIC_NAME + self.CUSTOM_MSG = MSG_TYPE + self.timer_period = MSG_PERIOD # [seconds] + # Init above laying class Node + super().__init__(self.PUBLISHER_NAME) + print("\t- " + str(TOPIC_NAME) + "\n") + self.publisher_ = self.create_publisher( + self.CUSTOM_MSG, + self.TOPIC_NAME, + 10) + self.new_msg = False + # Define Node Frequency, equivalent to ~rospy.rate() + self.timer = self.create_timer(self.timer_period, self.publisher_timer) + return + + def publisher_timer(self): + if self.new_msg is True: + try: + #self.get_logger().info('Pub:') + self.publisher_.publish(self.msg) + self.new_msg = False + except TypeError: + print("[ERROR] Msg-Data-Types do not match") + return + + # Publish using Timer + def timer_publish(self, msg): + self.msg=msg + self.new_msg = True + return + + # Publish directly without Timer + def direct_publish(self, msg): + try: + #self.get_logger().info('Pub:') + self.publisher_.publish(msg) + except TypeError: + print("[ERROR] Msg-Data-Types do not match") + return + + + + + + + + + +class MinimalSubscriber(Node): + + def __init__(self, NODE_NAME, TOPIC_NAME, MSG_TYPE, NUM_MSGS): + self.NODE_NAME= NODE_NAME + self.TOPIC_NAME = TOPIC_NAME + self.CUSTOM_MSG = MSG_TYPE + self.NUM_MSGS = NUM_MSGS + self.topic_received = False + # Init above laying class Node + super().__init__(self.NODE_NAME) + self.subscription = self.create_subscription( + self.CUSTOM_MSG, # Message Type + self.TOPIC_NAME, # Topic Name + self.listener_callback, # Callback Function + self.NUM_MSGS) # List of saved messages + self.subscription # prevent unused variable warning + return + + def listener_callback(self, msg): + self.get_logger().info('I heard: "%s"' % msg.data) + return + + + + + + + + + + +#***************************************************************************# +# Child Classes inherite through super().__init__(...) from parent class +# Child's purpose is to simplify subscribing to custom messages +# For this to work you must customize "listener_callback", "return_msg" (and "print_msg") + + + +# Exemplary Subscriber Class: Inheriting from MinimalSubscriber Class +# This class is custom made for receiving the specific msg type but uses MinimalSubscriber as foundation +# Create a new class of this type for every custom msg you want to receive, customizing the listener_callback, +# return_msg (and print_msg) functions to correspond to your message + +class example_subscriber(MinimalSubscriber): + + def __init__(self, NODE_NAME, TOPIC_NAME, MSG_TYPE, NUM_MSGS): + # Init MinimalSubscriber: + super().__init__(NODE_NAME, TOPIC_NAME, MSG_TYPE, NUM_MSGS) + print("\t- " + str(TOPIC_NAME)) + # !!! CUSTOMIZE THESE VARIABLES TO WORK WITH YOUR MESSAGE FILE !!! + self.pos_actual = None + self.vel_actual = None + self.baffle_switch = None + self.current = None + self.temp = None + + + # !!! CUSTOMIZE THIS FUNCTION TO WORK WITH YOUR MESSAGE FILE !!! + def listener_callback(self, msg): # Overwrites callback from inherited class + self.topic_received = True + self.pos_actual = msg.pos_actual + self.vel_actual = msg.vel_actual + self.baffle_switch = msg.baffle_switch + self.current = msg.epos_current + self.temp = msg.epos_temp + #print_msg(self) # activate to print the received data --> customize print_msg(self) ! + return + + # !!! CUSTOMIZE THIS FUNCTION TO WORK WITH YOUR MESSAGE FILE !!! + def return_msg(self): # Extract only msg variables from "self" object + msg = self.CUSTOM_MSG() + msg.pos_actual = self.pos_actual + msg.vel_actual = self.vel_actual + msg.baffle_switch = self.baffle_switch + msg.epos_current = self.current + msg.epos_temp = self.temp + return msg + + # !!! CUSTOMIZE THIS FUNCTION TO WORK WITH YOUR MESSAGE FILE !!! (optional) + def print_msg(self): + print("[SUBSCRIBER] %s received topic:\t/%s" %(self.NODE_NAME, self.TOPIC_NAME)) + string = "Received:\t" + + # Check length of all transmitted value lists to be equal (equal number of parameters) + length = len(self.pos_actual) + if any(len(lst) != length for lst in [self.vel_actual, self.current, self.temp]): + print("[TRANSMISSION ERROR] Length of lists inside topic %s do not match" %(self.TOPIC_NAME)) + else: + complete_list = [] + complete_list.append(self.pos_actual) + complete_list.append(self.vel_actual) + complete_list.append(self.current) + complete_list.append(self.temp) + #print(complete_list) + for i in range(len(complete_list)): + for y in range(len(complete_list[0])): + string += "%.2f,\t" %(complete_list[i][y]) + string += str(self.baffle_switch) + print(string) + return + + +#************************************************************************************# +# CustomMsg1 Subscriber Class +class CustomMsg1_sub(MinimalSubscriber): + + def __init__(self, NODE_NAME, TOPIC_NAME, MSG_TYPE, NUM_MSGS): + # Init MinimalSubscriber: + super().__init__(NODE_NAME, TOPIC_NAME, MSG_TYPE, NUM_MSGS) + print("\t- " + str(TOPIC_NAME)) + # CustomMsg1 Variables + self.temperature = None + self.pressure = None + self.humidity = None + return + + + # !!! CUSTOMIZE THIS FUNCTION TO WORK WITH YOUR MESSAGE FILE !!! + def listener_callback(self, msg): # Overwrites callback from inherited class + self.topic_received = True + self.temperature = msg.temperature + self.pressure = msg.pressure + self.humidity = msg.humidity + #print_msg(self) # activate to print the received data --> customize print_msg(self) ! + return + + # !!! CUSTOMIZE THIS FUNCTION TO WORK WITH YOUR MESSAGE FILE !!! + def return_msg(self): # Extract only msg variables from "self" object + msg = self.CUSTOM_MSG() + msg.temperature = self.temperature + msg.pressure = self.pressure + msg.humidity = self.humidity + return msg + + # !!! CUSTOMIZE THIS FUNCTION TO WORK WITH YOUR MESSAGE FILE !!! (optional) + def print_msg(self): + print("[SUBSCRIBER] %s received topic:\t/%s" %(self.NODE_NAME, self.TOPIC_NAME)) + string = "Received:\t" + + # Check length of all transmitted value lists to be equal (equal number of parameters) + length = len(self.temperature) + if any(len(lst) != length for lst in [self.temperature, self.pressure, self.humidity]): + print("[TRANSMISSION ERROR] Length of lists inside topic %s do not match" %(self.TOPIC_NAME)) + else: + complete_list = [] + complete_list.append(self.temperature) + complete_list.append(self.pressure) + complete_list.append(self.humidity) + #print(complete_list) + for i in range(len(complete_list)): + for y in range(len(complete_list[0])): + string += "%.2f,\t" %(complete_list[i][y]) + print(string) + return + + + +#************************************************************************************# +# CustomMsg2 Subscriber Class +class CustomMsg2_sub(MinimalSubscriber): + + def __init__(self, NODE_NAME, TOPIC_NAME, MSG_TYPE, NUM_MSGS): + # Init MinimalSubscriber: + super().__init__(NODE_NAME, TOPIC_NAME, MSG_TYPE, NUM_MSGS) + print("\t- " + str(TOPIC_NAME)) + # CustomMsg1 Variables + self.pitch_ctrl = None + self.yaw_ctrl = None + return + + + # !!! CUSTOMIZE THIS FUNCTION TO WORK WITH YOUR MESSAGE FILE !!! + def listener_callback(self, msg): # Overwrites callback from inherited class + self.topic_received = True + self.pitch_ctrl = msg.pitch_ctrl + self.yaw_ctrl = msg.yaw_ctrl + #print_msg(self) # activate to print the received data --> customize print_msg(self) ! + return + + # !!! CUSTOMIZE THIS FUNCTION TO WORK WITH YOUR MESSAGE FILE !!! + def return_msg(self): # Extract only msg variables from "self" object + msg = self.CUSTOM_MSG() + msg.pitch_ctrl = self.pitch_ctrl + msg.yaw_ctrl = self.yaw_ctrl + return msg + + # !!! CUSTOMIZE THIS FUNCTION TO WORK WITH YOUR MESSAGE FILE !!! (optional) + def print_msg(self): + print("[SUBSCRIBER] %s received topic:\t/%s" %(self.NODE_NAME, self.TOPIC_NAME)) + string = "Received:\t" + + # Check length of all transmitted value lists to be equal (equal number of parameters) + complete_list = [] + complete_list.append([self.pitch_ctrl]) + complete_list.append([self.yaw_ctrl]) + #print(complete_list) + for i in range(len(complete_list)): + for y in range(len(complete_list[0])): + string += "%.2f,\t" %(complete_list[i][y]) + print(string) + return \ No newline at end of file diff --git a/src/pubsub/pubsub/talker.py b/src/pubsub/pubsub/talker.py new file mode 100644 index 0000000..b2edd6c --- /dev/null +++ b/src/pubsub/pubsub/talker.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#********************************************# +# Talker Template + +# Creted for: +# ROS2 Workshop 2020 +# Roverentwicklung für Explorationsaufgaben +# Institute for Space Systems +# University of Stuttgart + +# Created by Patrick Winterhalder +# IRS, University of Stuttgart +#********************************************# + +import rclpy +from rclpy.node import Node + +# Import Publisher Library (Python Library) +from .pubsub_library import MinimalPublisher + +# Import Message Types (Message Files) +from pubsub_msg.msg import CustomMsg1 +from pubsub_msg.msg import CustomMsg2 + + +def main(args=None): + rclpy.init(args=args) + + # Start nodes here, should create object for every node + talker_1 = MinimalPublisher(NODE_NAME="pubsub_talker_1", TOPIC_NAME="/chatter1", MSG_TYPE=CustomMsg1, MSG_PERIOD=0.25) + talker_2 = MinimalPublisher(NODE_NAME="pubsub_talker_2", TOPIC_NAME="/chatter2", MSG_TYPE=CustomMsg2, MSG_PERIOD=0.5) + + while rclpy.ok(): + try: + # Insert main looping script here... + + # Eg. create custom msgs, fill and send them + # CustomMsg1 to talker_1 + msg_1 = CustomMsg1() + msg_1.temperature = [24.5, 25.5, 26.5] + msg_1.pressure = [1011.5, 1012.55, 1010.11] + msg_1._humidity = [0.002, 0.0019, 0.0021] + talker_1.timer_publish(msg_1) + + # CustomMsg2 to talker_2 + msg_2 = CustomMsg2() + msg_2.pitch_ctrl = 0.0 + msg_2.yaw_ctrl = 0.22 + talker_2.timer_publish(msg_2) + + + # Insert "rclpy.spin_once(, timeout_sec=0.1)" at the end of + # "try" for every node running in this script + rclpy.spin_once(talker_1, timeout_sec=0.1) + rclpy.spin_once(talker_2, timeout_sec=0.1) + + except (KeyboardInterrupt, SystemExit): + print("\n\nShutting down...") + # Insert ".destroy_node()" here once for every node running in this script + talker_1.destroy_node() + talker_2.destroy_node() + rclpy.shutdown() + + + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/pubsub/resource/pubsub b/src/pubsub/resource/pubsub new file mode 100644 index 0000000..e69de29 diff --git a/src/pubsub/setup.cfg b/src/pubsub/setup.cfg new file mode 100644 index 0000000..d90dcb8 --- /dev/null +++ b/src/pubsub/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script-dir=$base/lib/pubsub +[install] +install-scripts=$base/lib/pubsub diff --git a/src/pubsub/setup.py b/src/pubsub/setup.py new file mode 100644 index 0000000..281eeb0 --- /dev/null +++ b/src/pubsub/setup.py @@ -0,0 +1,27 @@ +from setuptools import setup + +package_name = '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='patrick', + maintainer_email='winterhalder.p@googlemail.com', + description='TODO: Package description', + license='TODO: License declaration', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + 'talker = pubsub.talker:main', + 'listener = pubsub.listener:main' + ], + }, +) diff --git a/src/pubsub/test/test_copyright.py b/src/pubsub/test/test_copyright.py new file mode 100644 index 0000000..cc8ff03 --- /dev/null +++ b/src/pubsub/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/src/pubsub/test/test_flake8.py b/src/pubsub/test/test_flake8.py new file mode 100644 index 0000000..27ee107 --- /dev/null +++ b/src/pubsub/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/src/pubsub/test/test_pep257.py b/src/pubsub/test/test_pep257.py new file mode 100644 index 0000000..b234a38 --- /dev/null +++ b/src/pubsub/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings' diff --git a/src/pubsub_msg/CMakeLists.txt b/src/pubsub_msg/CMakeLists.txt new file mode 100644 index 0000000..583a2cd --- /dev/null +++ b/src/pubsub_msg/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.5) +project(pubsub_msg) + +# Default to C99 +if(NOT CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + +# Default to C++14 +if(NOT CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 14) +endif() + +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( REQUIRED) + + + + +# --> +# ADD THESE LINES: START HERE + +find_package(builtin_interfaces REQUIRED) +find_package(rosidl_default_generators REQUIRED) +find_package(std_msgs REQUIRED) +find_package(rclcpp REQUIRED) + +# CUSTOM LINES: CHANGE FOR YOUR FILENAMES +rosidl_generate_interfaces(${PROJECT_NAME} + "msg/CustomMsg1.msg" + "msg/CustomMsg2.msg" + DEPENDENCIES builtin_interfaces + ) + +# END HERE +# <-- + + + + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # uncomment the line when a copyright and license is not present in all source files + #set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # uncomment the line when this package is not in a git repo + #set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/src/pubsub_msg/msg/CustomMsg1.msg b/src/pubsub_msg/msg/CustomMsg1.msg new file mode 100644 index 0000000..66a846b --- /dev/null +++ b/src/pubsub_msg/msg/CustomMsg1.msg @@ -0,0 +1,8 @@ +# Header data, eg timestamp +# Problem: "header__struct.hpp: No such file or directory" +#Header header + +# Sensor Data coming back from an array of atmospheric sensors +float32[] temperature +float32[] pressure +float32[] humidity diff --git a/src/pubsub_msg/msg/CustomMsg2.msg b/src/pubsub_msg/msg/CustomMsg2.msg new file mode 100644 index 0000000..2e9603d --- /dev/null +++ b/src/pubsub_msg/msg/CustomMsg2.msg @@ -0,0 +1,7 @@ +# Header data, eg timestamp +# Problem: "header__struct.hpp: No such file or directory" +#Header header + +# User inputs, eg. for controlling a camera mast , eg. set angles [rad] +float32 pitch_ctrl +float32 yaw_ctrl diff --git a/src/pubsub_msg/package.xml b/src/pubsub_msg/package.xml new file mode 100644 index 0000000..1592b75 --- /dev/null +++ b/src/pubsub_msg/package.xml @@ -0,0 +1,34 @@ + + + + pubsub_msg + 0.0.0 + TODO: Package description + patrick + TODO: License declaration + + ament_cmake + ament_lint_auto + ament_lint_common + + + + + + builtin_interfaces + rosidl_default_generators + + builtin_interfaces + rosidl_default_runtime + + rosidl_interface_packages + + + + + + + + ament_cmake + + diff --git a/workshopinstall.sh b/workshopinstall.sh new file mode 100644 index 0000000..73e8d3f --- /dev/null +++ b/workshopinstall.sh @@ -0,0 +1,41 @@ +#!/bin/bash +sudo apt-get update && sudo apt-get upgrade -y +sudo apt-get dist-upgrade -y + +# Install Visual Studio Code +sudo apt install -y software-properties-common apt-transport-https wget +wget -q https://packages.microsoft.com/keys/microsoft.asc -O- | sudo apt-key add - +sudo add-apt-repository "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" +sudo apt update && sudo apt install -y code + +# Install Git +sudo apt install -y git + +# Install ROS2 Foxy Fitzroy Desktop +#sudo apt update && sudo apt install locales +#sudo locale-gen en_US en_US.UTF-8 +#sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 +sudo apt update && sudo apt install -y curl gnupg2 lsb-release +curl -s https://raw.githubusercontent.com/ros/rosdistro/master/ros.asc | sudo apt-key add - +sudo sh -c 'echo "deb [arch=$(dpkg --print-architecture)] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main" > /etc/apt/sources.list.d/ros2-latest.list' +sudo apt update && sudo apt install -y ros-foxy-desktop +source /opt/ros/foxy/setup.bash + +# Install pip and argcomplete +sudo apt install -y python3-pip +pip3 install -U argcomplete + +# If colcon does not build: Install pytest version>=5.0 ??? + +# Install rosdep +sudo apt install -y python3-rosdep2 +rosdep update + +# in between +sudo apt update && sudo apt upgrade -y + +# Install colcon +sudo apt install -y python3-colcon-common-extensions + +# At the end again +sudo apt update && sudo apt upgrade -y