Pharo Client for ROS2
# 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.