RAPP Platform Wiki
v0.6.0
RAPP Platform is a collection of ROS nodes and back-end processes that aim to deliver ready-to-use generic services to robots
|
In this example, a basic guide will be presented that will cover the basics of creating an example RAPP Platform service. We assume that you have successfully installed the RAPP Platform and run the tests, if not please see our installation guide located at
https://github.com/rapp-project/rapp-platform/wiki/How-can-I-set-up-the-RAPP-Platform-in-my-PC%3F
It is addressed to the novice user and as such, experienced users may find it too detailed or need to skip over some sections. This tutorial assumes some basic ROS knowledge. Users not familiar with ROS are advised to first complete relevant ROS tutorials that can be found at
http://wiki.ros.org/ROS/Tutorials
By the end of the tutorial you should be able to call the service itself and obtain meaningful results. What you will learn:
In this section we will cover how to create a basic ROS RAPP Platform service. Let us assume that the service we should to create takes as input two integer number, adds them together and returns the result.
The first thing to do is to create a new ROS Node which will be responsible for the service we want to implement. In order to create a new ROS node, navigate within the rapp-platform directory and create a new package:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/ $ catkin_create_pkg rapp_example_service std_msgs rospy rapp_platform_ros_communications ```
This command creates a new package named rapp_example_service and adds the its dependencies upon the std_msgs, rospy and rapp_platform_ros_communications packages. You can now navigate within the newly created package, and create a directory named cfg:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_example_service/ $ mkdir cfg $ cd cfg $ touch rapp_example_service_params.yaml $ echo 'rapp_example_service_topic: /rapp/rapp_example_service/add_two_integers' >> rapp_example_service_params.yaml ```
We declared was the name of the service within the .yaml file. Now we will navigate back into the rapp_example_service package and create the launch dir.
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_example_service $ mkdir launch $ cd launch $ touch rapp_example_service.launch ```
The content of rapp_example_service.launch
follows:
```xml <launch> <node name="rapp_example_service_node" pkg="rapp_example_service" type="rapp_example_service_main.py" output="screen"> <rosparam file="$(find rapp_example_service)/cfg/rapp_example_service_params.yaml" command="load"> </launch> ```
This file declares the launcher of the service.
Now, we will create the source code files according to the coding standards of the RAPP project.
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_example_service/src $ touch rapp_example_service_main.py ```
The content of rapp_example_service_main.py
follows
```python #!/usr/bin/env python
import rospy from rapp_example_service import ExampleService
if name == "__main__": rospy.init_node('ExampleService') ExampleServicesNode = ExampleService() rospy.spin() ```
We have declared the main function of the ROS node and launched the ROS node with the rospy.spin() command. Now we will create the rapp_example_service.py
below:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_example_service/src $ touch rapp_example_service.py ```
The content of rapp_example_service.py
follows:
```python #!/usr/bin/env python
import rospy from add_two_integers import AddTwoIntegers from rapp_platform_ros_communications.srv import ( tutorialExampleServiceSrv, tutorialExampleServiceSrvRequest, tutorialExampleServiceSrvResponse)
class ExampleService:
def __init__(self): self.serv_topic = rospy.get_param("rapp_example_service_topic") if(not self.serv_topic): rospy.logerror("rapp_example_service_topic") self.serv=rospy.Service(self.serv_topic, tutorialExampleServiceSrv, self.tutorialExampleServiceDataHandler) def tutorialExampleServiceDataHandler(self,req): res = tutorialExampleServiceSrvResponse() it = AddTwoIntegers() res=it.addTwoIntegersFunction(req) return res
```
In this file, we initially declare python dependencies, one of them is the AddTwoIntegers class which we will define in a new file next. As you can see, we have imported an srv message files that needs to be created and declared in the rapp_platform_ros_communications package. We will cover this shortly. We also imported the name of the service from the .yaml file located in the cfg folder by the rospy.get_param() command. The return message of the service is handled by the DataHandler function we created, named tutorialExampleServiceDataHandler.
The last file we need to create is the add_two_integers.py
.
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_example_service/src $ touch add_two_integers.py ```
The content of add_two_integers.py
follows:
```python #!/usr/bin/env python
import rospy
from rapp_platform_ros_communications.srv import ( tutorialExampleServiceSrv, tutorialExampleServiceSrvRequest, tutorialExampleServiceSrvResponse)
class AddTwoIntegers:
def addTwoIntegersFunction(self,req): res = tutorialExampleServiceSrvResponse() res.additionResult=req.a+req.b return res
```
In this file, initially we define the imports again and following we define the AddTwoIntegers class and within it a simple function named addTwoIntegersFunction that accepts the service requirements as input and returns the service response. We initially construct the response named res, and we assign to the addition value the addition of the parameters a and b.
The srv file defines what the service will accept as input and what it will return as output. These must be declared in a special file, called the service's srv file. RAPP Platform conveniently places all ROS service srv files, as well as ROS service message files in the
/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_platform_ros_communications/
package. Let us assume that the service we should to create takes as input two integer number, adds them together and returns the result. First we need to create the .srv file declaring the inputs and outputs of the service as shown:
```bash $ cd /rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_platform_ros_communications/srv $ mkdir ExampleServices $ cd ExampleServices $ touch tutorialExampleServiceSrv.srv ```
The content of tutorialExampleServiceSrv.srv
follows:
```yaml Header header int32 a
int32 additionResult ```
Toe declare the .srv file in the package, open the /rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_platform_ros_communications/rapp_platform_ros_communications/CMakeLists.txt
file and add the following line within the add_service_files()
block,
``` ExampleServices/tutorialExampleServiceSrv.srv ```
The whole file now should look like this:
```cmake cmake_minimum_required(VERSION 2.8.3) project(rapp_platform_ros_communications) set(ROS_BUILD_TYPE Release)
find_package(catkin REQUIRED COMPONENTS message_generation message_runtime std_msgs geometry_msgs nav_msgs )
add_message_files( FILES StringArrayMsg.msg CognitiveExercisePerformanceRecordsMsg.msg MailMsg.msg NewsStoryMsg.msg WeatherForecastMsg.msg ArrayCognitiveExercisePerformanceRecordsMsg.msg CognitiveExercisesMsg.msg )
add_service_files( FILES
/ExampleServices/tutorialExampleServiceSrv.srv
/CognitiveExercise/testSelectorSrv.srv /CognitiveExercise/recordUserCognitiveTestPerformanceSrv.srv /CognitiveExercise/cognitiveTestCreatorSrv.srv /CognitiveExercise/userScoresForAllCategoriesSrv.srv /CognitiveExercise/userScoreHistoryForAllCategoriesSrv.srv /CognitiveExercise/returnTestsOfTypeSubtypeDifficultySrv.srv
/HumanDetection/HumanDetectionRosSrv.srv
/CaffeWrapper/imageClassificationSrv.srv /CaffeWrapper/ontologyClassBridgeSrv.srv /CaffeWrapper/registerImageToOntologySrv.srv
/DbWrapper/checkIfUserExistsSrv.srv /DbWrapper/getUserOntologyAliasSrv.srv /DbWrapper/getUserLanguageSrv.srv /DbWrapper/registerUserOntologyAliasSrv.srv /DbWrapper/getUserPasswordSrv.srv /DbWrapper/getUsernameAssociatedWithApplicationTokenSrv.srv /DbWrapper/createNewPlatformUserSrv.srv /DbWrapper/createNewApplicationTokenSrv.srv /DbWrapper/checkActiveApplicationTokenSrv.srv /DbWrapper/checkActiveRobotSessionSrv.srv /DbWrapper/addStoreTokenToDeviceSrv.srv /DbWrapper/validateUserRoleSrv.srv /DbWrapper/validateExistingPlatformDeviceTokenSrv.srv /DbWrapper/removePlatformUserSrv.srv /DbWrapper/createNewCloudAgentServiceSrv.srv /DbWrapper/createNewCloudAgentSrv.srv /DbWrapper/getCloudAgentServiceTypeAndHostPortSrv.srv
/OntologyWrapper/createInstanceSrv.srv /OntologyWrapper/ontologySubSuperClassesOfSrv.srv /OntologyWrapper/ontologyIsSubSuperClassOfSrv.srv /OntologyWrapper/ontologyLoadDumpSrv.srv /OntologyWrapper/ontologyInstancesOfSrv.srv /OntologyWrapper/assertRetractAttributeSrv.srv /OntologyWrapper/returnUserInstancesOfClassSrv.srv /OntologyWrapper/createOntologyAliasSrv.srv /OntologyWrapper/userPerformanceCognitveTestsSrv.srv /OntologyWrapper/createCognitiveExerciseTestSrv.srv /OntologyWrapper/cognitiveTestsOfTypeSrv.srv /OntologyWrapper/recordUserPerformanceCognitiveTestsSrv.srv /OntologyWrapper/clearUserPerformanceCognitveTestsSrv.srv /OntologyWrapper/registerImageObjectToOntologySrv.srv /OntologyWrapper/retractUserOntologyAliasSrv.srv
/FaceDetection/FaceDetectionRosSrv.srv
/NewsExplorer/NewsExplorerSrv.srv /Geolocator/GeolocatorSrv.srv
/WeatherReporter/WeatherReporterCurrentSrv.srv /WeatherReporter/WeatherReporterForecastSrv.srv
/QrDetection/QrDetectionRosSrv.srv
/Email/SendEmailSrv.srv /Email/ReceiveEmailSrv.srv
/SpeechDetectionGoogleWrapper/SpeechToTextSrv.srv
/SpeechDetectionSphinx4Wrapper/SpeechRecognitionSphinx4Srv.srv /SpeechDetectionSphinx4Wrapper/SpeechRecognitionSphinx4ConfigureSrv.srv /SpeechDetectionSphinx4Wrapper/SpeechRecognitionSphinx4TotalSrv.srv
/AudioProcessing/AudioProcessingDetectSilenceSrv.srv /AudioProcessing/AudioProcessingTransformAudioSrv.srv
/TextToSpeechEspeak/TextToSpeechSrv.srv
/HazardDetection/LightCheckRosSrv.srv /HazardDetection/DoorCheckRosSrv.srv
/PathPlanning/PathPlanningRosSrv.srv /Costmap2d/Costmap2dRosSrv.srv /PathPlanning/MapServer/MapServerGetMapRosSrv.srv /PathPlanning/MapServer/MapServerUploadMapRosSrv.srv
/ApplicationAuthentication/UserTokenAuthenticationSrv.srv /ApplicationAuthentication/UserLoginSrv.srv /ApplicationAuthentication/AddNewUserFromStoreSrv.srv /ApplicationAuthentication/AddNewUserFromPlatformSrv.srv )
generate_messages( DEPENDENCIES std_msgs # Or other packages containing msgs geometry_msgs nav_msgs )
catkin_package(
CATKIN_DEPENDS message_generation message_runtime std_msgs geometry_msgs ) ```
This line will declare the srv file we created and stage it to be compiled.
Now we need to recompile the package. We will navigate to the root RAPP Platform catkin workspace directory and compile the code as shown below:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/ $ catkin_make –pkg rapp_platform_ros_communications ```
this will recompile only the specific package. Sometimes it is preferable to recompile the whole project, in that case please delete the folders build and devel and compile the whole project, as shown below:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/ $ rm -rf ./build ./devel $ catkin_make ```
In order to test that our ROS service works:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/ $ roslaunch rapp_example_service rapp_example_service.launch ```
With our service launched, use a different terminal and type:
```bash $ rosservice call /rapp/rapp_example_service/add_two_integers ```
and immediately press tab twice in order to auto complete the service requirements. Please assign values on the parameters a and b and hit enter. Voila! You can see the result in the additionResult parameter.
Read on How-to-create-a-HOP-service-for-a-ROS-service, if you have not done so already.
Head to the rapp_web_services
directory of the rapp-platform
and create the source file for the Web Service implementation:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_web_services/services $ mkdir example_web_service $ cd rapp_example_web_service $ touch svc.js ```
Open the svc.js file with your favorite editor and implement the client-response and ros-msg objects. The Web-Service response message inlcude the sum_result
and error
properties. The ROS Service request message has two integer properties, a
and b
```js var clientRes = function(sum_result, error) { sum_result = sum_result || 0; error = error || ''; return { sum_result: sum_result, error: error } }
var rosReqMsg = function(a, b) { a = a || 0; b = b || 0; return { a: a, b: b } } ```
The rapp_example_service ROS Service url path is /rapp/rapp_example_service/add_two_integers
```js var rosSrvUrlPath = "/rapp/rapp_example_service/add_two_integers" ```
Next you need to implement the WebService onrequest callback function. This is the function that will be called as long as a request for the rapp_example_web_service arrives.
```js function svcImpl(req, resp, ros) { // Get values of 'a' and 'b' from request body. var numA = req.body.a; var numB = req.body.b;
// Create the rosMsg var rosMsg = new rosReqMsg(numA, numB);
/***
function callback(rosResponse){ // Get the sum result value from ROS Service response message. var response = clientRes( rosResponse.additionResult ); // Return the sum result, of numbers 'a' and 'b', to the client. resp.sendJson(response); }
/***
function onerror(e){ // Respond a "Server Error". HTTP Error 501 - Internal Server Error resp.sendServerError(); }
/***
ros.callService(rosSrvUrlPath, rosMsg, {success: callback, fail: onerror}); } ```
Export the service onrequest callback function (svcImpl):
```js module.exports = svcImpl; ```
Add the rapp_example_web_service
entry in services.json file:
```json "rapp_example_web_service": { "launch": true, "anonymous": false, "name": "rapp_example_web_service", "url_name": "add_two_ints", "namespace": "", "ros_connection": true, "timeout": 45000 } ```
The Web Service will listen at http://localhost:9001/hop/add_two_ints
as defined by the url_name
value.
You can set the url path for the Web Service to be rapp_example_web_service/add_two_ints
by setting the url_name and namespace respectively:
```json "rapp_example_web_service": { "launch": true, "anonymous": false, "name": "rapp_example_web_service", "url_name": "add_two_ints", "namespace": "rapp_example_web_service", "ros_connection": true, "timeout": 45000 } ```
We have also set this Web Service to timeout after 45 seconds (timeout
). This is critical when WebService-to-ROS communcation bridge breaks!
For simplicity, we will configure this WebService to be launched under an already existed Web Worker thread (main-1).
The workers.json file contains Web Workers entries. Add the rapp_example_web_service
service under the main-1
worker:
```json "main-1": { "launch": true, "path": "workers/main1.js", "services": [ ... "rapp_example_web_service" ] } ```
The newly implemented rapp_example_web_service
Web Service is ready to be launched. Launch the RAPP Platform Web Server:
```bash $ cd ~/rapp_platform/rapp-platform-catkin-ws/src/rapp-platform/rapp_web_services $ pm2 start server.yaml ```
If you dont want to launch the Web Server using pm2 process manager, just execute the run.sh
script in the same directory:
```bash $ ./run.sh ```
You will notice the following output from the logs:
```bash info: [] Registered worker service {http://localhost:9001/hop/add_two_ints} under worker thread {main-1} info: [] { worker: 'main-1', path: '/hop/add_two_ints', url: 'http://localhost:9001/hop/add_two_ints', frame: [Function] } ```
All set! The RAPP Platform accepts requests for the rapp_example_web_service
at http://rapp-platform-local:9001/hop/add_two_ints
You can test it using curl
from commandline:
```bash $ curl –data "a=100&b=20" http://localhost:9001/hop/add_two_ints ```
Notice that the RAPP Platform will return Authentication Failure
(HTTP 401 Unauthorized Error). This is because the RAPP Platform uses token-based application authentication mechanisms to authenticate incoming client requests. You will have to pass a valid token to the request headers. By default, the RAPP Platform database includes a user rapp
and the token is rapp_token. Pass that token value to the Accept-Token
field of the request header:
```bash $ curl -H "Accept-Token: rapp_token" –data "a=100&b=20" http://localhost:9001/hop/add_two_ints ```
The output should now be:
```bash {sum_result: 120, error: ''} ```
First, read on How to write the API for a HOP service, if you have not done so already.
Head to the RappCloud/CloudMsgs directory of the RappCloud module and create a file named AddTwoInts.py
```bash $ cd ~/rapp_platform/rapp_platform_catkin_ws/src/rapp-api/python/RappCloud/CloudMsgs $ touch AddTwoInts.py ```
The contents of the AddTwoInts.py source file should be:
```python from RappCloud.Objects import ( File, Payload)
from Cloud import ( CloudMsg, CloudRequest, CloudResponse)
class AddTwoInts(CloudMsg): """ Add Two Integers Exqample CloudMsg object"""
class Request(CloudRequest): """ Add Two Integers Cloud Request object. AddTwoInts.Request """ def init(self, **kwargs): """! Constructor
**kwargs | - Keyword arguments. Apply values to the request attributes.
|
self.a = 0
self.b = 0
super(AddTwoInts.Request, self).__init__(**kwargs)
def make_payload(self): """ Create and return the Payload of the Request. """ return Payload(a=self.a, b=self.b) def make_files(self): """ Create and return Array of File objects of the Request. """ return [] class Response(CloudResponse): """ Add Two Integers Cloud Response object. AddTwoInts.Response """ def __init__(self, **kwargs): """! Constructor @param **kwargs - Keyword arguments. Apply values to the request attributes. - @ref error - @ref sum_result """ ## Error message self.error = '' ## The sum result of numbers a and b self.sum_result = 0 ## Apply passed keyword arguments to the Request object. super(AddTwoInts.Response, self).__init__(**kwargs) def __init__(self, **kwargs): """! Constructor @param **kwargs - Keyword arguments. Apply values to the request attributes. - @ref Request.a - @ref Request.b """ # Create and hold the Request object for this CloudMsg self.req = AddTwoInts.Request() # Create and hold the Response object for this CloudMsg self.resp = AddTwoInts.Response() super(AddTwoInts, self).__init__(svcname='add_two_ints', **kwargs)
```
Finally append the following line of code in the RappCloud/CloudMsgs/__init__.py
file:
```python from AddTwoInts import AddTwoInts ```
Now everything is in place to call the newly created RAPP Platform Service, using the python implementation of the rapp-platform-api. An example is presented below:
```python from RappCloud.CloudMsgs import AddTwoInts from RappCloud import RappPlatformService
svcClient = RappPlatformService(persistent=True, timeout=1000) msg = AddTwoInts(a=5, b=4)
response svcClient.call(msg)
print response.sum_result
9
```