PhaROS2

Description

Pharo Client for ROS2

Details

Source
GitHub
Dialect
pharo (25% confidence)
License
MIT
Created
Nov. 30, 2018
Updated
March 13, 2019

Categories

System / OS

README excerpt

# PhaROS2

## Installation
### ROS2 Installation
To install ROS2 on your Ubuntu follow [this link](https://index.ros.org/doc/ros2/Installation/Linux-Install-Debians/).

Before you doing that you need to do
```shell
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](https://index.ros.org/doc/ros2/Tutorials/Ament-Tutorial/)

You can find the installation for turtlebot2 with ROS2 at [this link](https://github.com/ros2/turtlebot2_demo)

*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](https://github.com/ros2/rclc) 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`.
```bash
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
```bash
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](https://pharo.org/download).
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`

```bash
#!/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.


```smalltalk
 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.

```smalltalk
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.


```smalltalk
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.

```smalltalk
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
```smalltalk
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
```smalltalk
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.
```smalltalk
Talker>>initialize
	super initialize.
	self createNode.
	self createPublisher.
```

## createNode
It's in this function, you will find the way to create a `node`.
```smalltalk
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.
```smalltalk
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
```smalltalk
Talker>>destroyNode
	(node) ifNotNil: [ 
		node destroyNode.
	]
```

## Publish in topic
```smalltalk
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.
```smalltalk
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
```smalltalk
Talker>>stop
	active := false.
	(myThread) ifNotNil: [myThread terminate].
```

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

```smalltalk
 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

```smalltalk
Object subclass: #Listener
	instanceVariableNames: 'node sub myThread active'
	classVariableNames: ''
	package: 'ROS2MyExperiment'
```

## Initialize
```smalltalk
Listener>>initialize
	super initialize.
	self createNode.
	self createSubscriber.
```
## Create node
Same thing as precedent. Or you can use the same node.
```smalltalk
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.

```smalltalk
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.
← Back to results