/PhaROS2

Pharo Client for ROS2

Primary LanguageSmalltalkMIT LicenseMIT

PhaROS2

Installation

ROS2 Installation

To install ROS2 on your Ubuntu follow this link.

Before you doing that you need to do

sudo apt install python3-vcstools python-vcstools python-pip
sudo pip install vcstools

In addition of ROS2, you need to setup a ROS2_workspace to compile library for ROS2. Just follow this tutorial

You can find the installation for turtlebot2 with ROS2 at this link

Note: All of my devellopement was done on Ubuntu 18.04.1 LTS with ROS2 Bouncy

RCLC

Just before enter in Pharo side, you need to install RCLC. RCLC is jeust an adaptation of RCL but with different simplification to handle more easly the different method of RCL. Here you have the link to Github of the RCLC. To install this Library, juste clone this repo into your ros2_ws/src.

PhaROSmsgs

Due to some difficulty and the implementation of ROS2 Type Support, a lybrary is use to handle the Type support between ROS2 and Pharo. This library is empty and dynamicly generated by Pharo.

The installation is the same as RCLC, you juste need to clone the PhaROS_msgs git into your ~/ros2_sw/src.

Ros2_ws Compilation

To compile, you need to be in ros2 environment.

ros2ify
cd ~/ros2_ws
src/ament_tools/scripts/ament.py build --build-tests --symlink-install

If the script run entirely, you can should except to have at least libpharosmsgs.so and librclc.so. Try to perform

cd ~/ros2_ws/install/lib
ros2ify
ldd librclc.so
ldd libpharosmsgs.so

Both will have different Ros2 linked libraries

Creation of new Pharo Image

Use PharoLauncher follow this link. You only have to uncompress the folder to install it.

Pharo 7.0 new image

Once you have run pharolauncher. Create and start a Pharo 7.0 empty image. Do nothing in this image because the Pharo-vm was not run inside ROS2 environment.

Start a PhaROS2 Image in ROS2 environment

Pharo Image should be run in ROS2 environment.

To improve the simplicity, I've write a Shell-Script to run your PhaROS2 Image in ROS2 environment. To use this script, name the image in PharoLauncher: PhaROS2

#!/bin/bash
export ROS_DOMAIN_ID=10
source /opt/ros/bouncy/setup.bash

cd ~/Pharo/vms/70-x64/
./pharo ../../images/PhaROS2/PhaROS2

echo
echo
echo
echo '--------------------------'
echo '--        PhaRO2        --'
echo '-- Press enter to quit  --'
echo '--------------------------'
read

Note: ROS_DOMAIN_ID permit to split the network in 256 (0->255) ROS2 domain. Each sub-domain is independant.

Dependicie in Pharo

You need to use some depencie to use PhaROS2. We need to install OSProcess and CommandShell to execute PhaROS2 correctly.

 Gofer new
	squeaksource: 'OSProcess';
	package: 'OSProcess';
	load. 

 Gofer new
	squeaksource: 'CommandShell';
	package: 'CommandShell-Piping';
	load.

Import in new Pharo Image

With Iceberg import PhaROS2 project host on GitHub: https://github.com/CARMinesDouai/PhaROS2.git and load PhaROS2 package

Before run the test case, you have to change different library location according to your installation.

RCLC class>>ffiLibraryName
PhaROS_Msgs class >>ffiLibraryName
PhaROS_Msgs class >> Path categorie

To ensure everything work correctly, try to run the Test cases.

Utilisation of PhaROS2

Type support

ROS2 like ROS1 is based on type so you need to create type before using it on PhaROS2

This is the way you have to get the correct structure. All subsctructure will be create, and a dictionary is used to optimize the creation.

The PharROS2TypeBrowser is a singleton.

PhaROS2TypeBrowser instance ros2Type: 'geometry_msgs/Twist'.
PhaROS2TypeBrowser instance ros2Type: 'geometry_msgs/TwistWithCovarianceStamped'.
PhaROS2TypeBrowser instance ros2Type: 'std_msgs/String'.

All of your type is a subclass of PhaROS2_Type.

If you want to reset the PhaROS2TypeBrowser, you can execute the following code. They will delete all PhaROS2_type and ROS2TypeSupport_struct subclasses and the dictionary will be erase.

PhaROS2TypeBrowser reset.

Note: The subclasses of ROS2_TypeSupport_struct is not for users. Users only use PhaROS2_Type subclasses

To have an object for a message type, you have to use this method

PhaROS_Msgs typeSupport: aTypeSupport
PhaROS_Msgs typeSupport: 'std_msgs/String' "To have a String message type support"

PhaROS_Msgs is based on the pharosmsgs library in your ros2_ws. This librarie is dynamicaly change by Pharo when it's needed.

Note: Unfortunetly the automatic recompilation does'nt work... you have yo run: src/ament_tools/scripts/ament.py build --symlink-install --only-packages pharosmsgs manualy in you ros2_ws

Talker

The goal of this section is to able to have a fully functional Talker with phaROS2. In my implementation the talker need to have a thread to publish a message. But you can of course use the subscriber callback to publish an answer of each received messages.

Talker class creation

Object subclass: #Talker
	instanceVariableNames: 'node pub myThread active'
	classVariableNames: ''
	package: 'ROS2TalkerListener'

Explanation:

  • Instance variables
    • node: Contain the ROS2_Node
    • pub: Contain the ROS_Publisher
    • myThread: Will keep the thread to publish a message
    • active: To stop the thread without myThread terminate
  • They will no have class varibales but you will be free to add as your convinence

Initialize

Your initialize will be as simple as possible.

Talker>>initialize
	super initialize.
	self createNode.
	self createPublisher.

createNode

It's in this function, you will find the way to create a node.

Talker>>createNode
	node := ROS2_Node new.
	node namespace: '/namespace'.
	node nodeName: 'myTalker'.
	node registerNode.

Note: You can place varibales instead of "/namespace"or "myTalker".

createPublisher

This function will create a publisher. You have more option on publisher.

Talker>createPublisher
	pub := ROS2_Publisher new.
	pub topicName: 'talker'.
	pub parentNode: node.
	pub typeSupportName: 'std_msgs/Float64'.
	pub queueSize: 10.
	pub registerPublisher.

Node: You can delete a publisher by using: pub destroyPublsher

As you see, you're not obliged to call PhaROS2TypeBrowser instance need: 'std_msgs/Float64'.. It's done when you set the typeSupportName: aType on a publisher or subscriber.

Destroying a Node

To stop a node, you juste have to call aNode destroyNode. The destruction of the node will automaticaly destroy all publisher and subscribers attach to this node. Add this function to your Talker class

Talker>>destroyNode
	(node) ifNotNil: [ 
		node destroyNode.
	]

Publish in topic

Talker>>loopFunction
	| toPublish allocatedString |
	[ active ] whileTrue: [ 
		toPublish := PhaROS_Msgs typeSupport: 'std_msgs/String'.
		allocatedString := PhaROS2_allocation stringToPointer: 'Hello World With phaROS2'.
		toPublish rosidl_message_type_support data: allocatedString.
		pub publish: toPublish .
	(Delay forMilliseconds: 1000) wait.
	 ]

For example: You juste have to get the TypeSupport by calling the correct function.

Thread Handling

Start the thread

This function is used to start the thread.

Talker>>start
	active := true.
	myThread := [ self loopFunction. ] fork.

Stop the thread

This function is used to stop the Thread. By warn, the node and the publisher will not be delete by stopping the thread

Talker>>stop
	active := false.
	(myThread) ifNotNil: [myThread terminate].

Utilisation

To create and execute the pusliher, you have to play this playgound code

 aTalker := Talker new.
 aTalker start.
 
 "Use to stop the thread"
 aTalker stop.
 
 "Use to delete the node"
 aTalker destroyNode.

Listener

The listener node is more complex due to the callback needed.

Class creation

As the same as the Talker, the Listener need some variables Explanation:

  • Instance variables
    • node: Contain the ROS2_Node
    • sub: Contain the ROS_Subscriber
    • myThread: Will keep the thread to publish a message
    • active: To stop the thread without myThread terminate
  • They will no have class varibales but you will be free to add as your convinence
Object subclass: #Listener
	instanceVariableNames: 'node sub myThread active'
	classVariableNames: ''
	package: 'ROS2MyExperiment'

Initialize

Listener>>initialize
	super initialize.
	self createNode.
	self createSubscriber.

Create node

Same thing as precedent. Or you can use the same node.

Litener>>createNode
	node := ROS2_Node new.
	node namespace: '/namespace'.
	node nodeName: 'myListener'.
	node registerNode.

Creation of subscriber

As you see, you found almost the same things as the publisher. But they have a callback block. As you see, you juste have to put a Pharo block of code with one parameter. The parameter is a PhaROS2_Type subsclass according to the typeSupport. So you directly have access to the data.

Listener>>createSubscriber
	| callbackBlock |
	sub := ROS2_Subscriber new.
	sub topicName: '/listener'.
	sub parentNode: node.
	sub typeSupportName: 'std_msgs/String'.
	sub queueSize: 10.
	sub ignoreLocalPublication: true.
	callbackBlock := [ :data | self myCallback: data].
	sub callback: callbackBlock.
	sub registerSub.

Node: You can delete a subscriber by using: sub destroySubscriber

The callback

The callback function is used to treat the data send by ROS2. In my case I will juste print the data on the Transcript.

In general, the data is a PhaROS2_Type subclasses according to the type the topic. For example, il you use std_msgs/String type, data will be Std_msgs_String object.

Listener>>myCallback: data
	| toPrint |
	toPrint := data data.
	Transcript crShow: toPrint.

Thread

As the same as the publisher, you have to handle a Thread.

Spin the node

The fact of a node need to be spined, is laready handle by PhaROS2. To handle this, you juste have to call the start and stop function.

To handle this, you can just add these two functions on you Listener class.

Listener>>start
	node startSpin.
Listener>>stop
	node stopSpin.

Destroy the node

To stop the node on the ROS side, we wil add the same function as previous

Listeger>>destroyNode
	(node) ifNotNil: [ 
		node destroyNode.
	]

Utilisation

To use the subscriber, you have to run this code un your playground

 aListener := Listener new.
 aListener start.
 
 "Use to stop the thread"
 aListener stop.
 
 "Use to delete the node"
 aListener destroyNode.

ROS2 side

Once you have on PhaROS2 a talker and listener, you can see he result in your terminal. I consider your terminal is in ROS2 environment. If not do a ros2ify

Here you can find some command to know if your talker/listener work correctly.

First you need to know if the node is created. Run: ros2 node list to list all ROS2 node. Your node should appear.

To check oll the topic, you have to check the topic list with ros2 topic list. Once you have the complete name and topic, you can retreive differents informations about the topic by running ros2 topic info /myTopicName. You can see the number of publisher and subscriber to this topic.

Finaly to see the message send by the publisher you have to run ros2 tpoic echo /myTopicName. The message send by PhaROS2 will be print on your Terminal. To kill this terminal Listener, you simply have to use ctrl-c.

To test the subcriber you can publish a message in your terminal. To do that juste run: ros2 topic pub /myTopicName std_msgs/String "data: 'Hello PhaROS2'". The message Hello PhaROS2 will triggered the callback in Pharo. Note: Please ensure the thread used to spin the node is active in Pharo side

Turtlebot2 controller

To perform this part, you need to have a Turtlebot2, and Two machine.

  • Machine A:
    • Will be placed on Trutlebot
    • Need to have Ros2 and Ros1 installed to manage the Turtlebot
    • They also received the joy information
  • Machine B:
    • You need to have Ros2
    • You need to have PhaROS2.

Turtlebot 2 computer

Connect the Joystick receiver and the turtlebot to the computer.

Turtlebot node

ros1ify
ros2ify
ros2 run turtlebot2_drivers kobuki_node

After this code, the Turtlebot wil make some sound.

Joy node

ros2ify
ros2 run joy joy_node

The joystick is now handle. The last step to move the robot is to transform the information from the joy_node into cmd_vel. And this part will be done in PhaROS2

PhaROS2 machine

When you have download the PhaROS2 git in Pharo, you have Turtlebot2 package.

turtleBotNode := Turtlebot2_Demo new.

turtleBotNode startAutoSpin.
turtleBotNode stopAutoSpin.

turtleBotNode defaultMove.

turtleBotNode stop.

First you create a simple Turtlebot2_Demo object.

The defaultMove will move forward the robot. You need to manuly call this function ctrl-d to see the turtlebot move.

The two function startAutoSpin and stopAutoSpin handle a Thread to send the correct information receive by the joy_node and publish in cmd_vel.

The last function stop will delete all publishers, subscribers and the node.

what's happened

ROS2 is a Middleware. So the node is broadcast on the network. The informations send by the joy_node is send throught the network to PhaROS2 turtlebot2_demo. It's catch and treat by pharo. Once the cmd_vel message is create, the information go on the network one more to the kobuki_node. The turtlebot start moving.