Building your First App Using Python
This guide will step you through the process of creating a barebones Hello World app using python on the Nebula platform.
If you have read Building your First ARA::COM App you can skip the below sections and directly goto Code Generation (SrcGen)
Pulsar python binding are based on Python 3.0, and thus this tutorial also use Python 3.0. Please make sure that you have a working Python 3.0 or greater development environment.
Interface Definition using IDL
To develop any Service application using pulsar communication framework, first step is to define the service interface using Interface Definition Language(IDL). The following sections provide example IDLs suitable for the communication scenarios explained. To define your own IDL files please refer to the "Pulsar - IDL Guide" document.
Hello World Application
In One to One communication, a Service application provide services and a client application consumes the service. In this setup there exists only one instance of the server and client connects to that particular instance.
To showcase this pattern a scenario as shown in the image is considered: the HelloWorld Service provides a service and HelloWorldRenderer consume the service.
For this tutorial purpose, the HelloWorld service is designed to be sending (broadcast) periodic "Hello World" messages. The client HelloWorldRenderer app listens for those messages and prints the received message on the console.
This can be easily implemented in Python, thanks to the python binding generated by the Pulsar framework. The python binding provide similar APIs as ARA::COM which can be imported and used from any python application. So similar to First ARA::COM Application, we can implement the HelloWorld service application as a ARA::COM Skeleton instance and sending the "Hello World" message periodically. The HelloWorldRenderer application can be a ARA::COM Proxy which will find the ARA::COM Skeleton instance and subscribe for the HelloWorld messages , i.e., it can listen for the "Hello World" broadcasts and process it as it becomes available. All these using python APIs.
Publisher Implementation
The following sections will explain the step by step method required to develop and build this kind of application using Pulsar SDK.
Create Project Folder
Step 1: In the SDK root directory, one can find two folders - apps/ and install/. Go to install/tools/ folder.
Step 2: Open a Linux terminal, and run the following:
$ createapp.sh -n <AppName> -d <AppProjectPath>
The above tool takes two parameter namely,
-n : name of the project and
-d: the path where this directory should be created.
Here, our project structure would be created in "< AppProjectPath >" directory with the name "< AppName >"
Step 3: Go to "< AppProjectPath/AppName >" The folder structure should look like below:
AppName
├── CMakeLists.txt
├── cmake
│ └── pulsarbuild.cmake
├── etc
│ └── em_manifest.json
├── src
└── src-gen
Here, src/ directory should contain the main code for publish or subscribe. The src-gen/ directory shall contain the generated source code from IDL files.
IDL
As explained before for Service Applications the IDL need to be created first. For the we will use the IDL definition as given below. Create a text file with this content and save it with ".idl" extension.
package tutorial.examples
interface HelloWorld {
version { major 1 minor 0 }
broadcast HellowWorld {
out {
String message
}
}
}
deployment for tutorial.examples.HelloWorld {
SomeIpServiceID = 1000
instance {
SomeIpInstanceID = 100
}
broadcast HellowWorld {
SomeIpEventID = 32769
SomeIpEventGroups = { 1 }
SomeIpEventReliable = false
}
}
As can be seen from the IDL, it define a simple single instance service with a single HelloWorld broadcast. To know more about the IDL please see Interface Definition using IDL
Code Generation (SrcGen)
After defining the IDL next step is to generate Stub and Proxy code from it. For that we need to use Pulsar code generation tool. However unlike First ARA::COM Application, we need to additionally generate python binding code for the Skeleton and Proxy, this need to be indicated using additional parameters to the tool In order to generate the source code using the tool, run the below command:
nebcodegen --idl <idlpath> --out <outpath> --pybind --pyroot <rootname>
Here, idlPath is the source idl file path and outpath shall be any folder where the generated source will be saved. rootname will be the root module where the generated classes will be included. This rootname is important and we will use it in cmake file.
The generated source code directory looks like below:
.
├── HelloWorld
│ ├── common
│ │ ├── HelloWorld.hpp
│ │ ├── HelloWorldInstance.hpp
│ │ ├── HelloWorldSomeIPDeployment.cpp
│ │ ├── HelloWorldSomeIPDeployment.hpp
│ │ └── HelloWorldTypes.hpp
│ ├── dataprovider
│ │ ├── HelloWorldDataProvider.cpp
│ │ └── HelloWorldDataProvider.hpp
│ ├── proxy
│ │ ├── HelloWorldSomeIPProxy.cpp
│ │ └── HelloWorldSomeIPProxy.hpp
│ └── skeleton
│ └── HelloWorldStub.hpp
├── python
│ ├── HelloWorld
│ │ └── HelloWorldPyBinding.hpp
│ └── PyBinding.cc
└── types
└── nebcommonTypes.hpp
copy the generated source folder to the src-gen folder in the project folder created in Create Project Folder
Setting up workspace for python binding build
Pulsar framework use pybind11 for binding C++ APIs to python. To use the binding in python, we need to build the binding code to generate native python module. For this we need to first include the pybind11 framework as part of the workspace. The easiest way is to clone the pybind11 repository directly to the workspace. Perform the following operations at the workspace folder.
$ git clone https://github.com/pybind/pybind11.git --branch stable
Update CMakeLists file
Now to generate the python module, ensure to add Project Specific Contents within CMakeLists.txt as below:
# Project Specific Contents
file(GLOB_RECURSE GEN_SRCS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src-gen/*.cpp")
add_subdirectory(pybind11)
pybind11_add_module(pulsarcomm ${GEN_SRCS} src-gen/python/PyBinding.cc)
Please note that the pulsarcomm name given here MUST be the exact same rootname given during Code Generation (SrcGen)
Build the binding module
Follow the build guidelines as used for any CMake based project. Create the build/ directory if not already present.
$ cd build/
$ cmake ..
$ make
Writing publisher interface for One-to-One Communication
Step 1: Import the necessary modules for the server implementation
from root.tutorial.examples.skeleton import PyHelloWorld # Skeleton class
from time import sleep # for periodic sending
# flag controlling application exit behavior
# in actual situations, it is assumed to manipulated from outside
# here it is always `false`
exitFlag = False;
As you can see the generated skeleton class will be located in <rootname>.<Interface package hierarchy>.skeleton, and the class name will be interface name with "Py" prefix.
Step 2: Define a method which, on invocation starts the server. Also instantiate the skeleton object.
# this function start the server and periodically send "Hellow World" data through event
def startServer():
# instantiate the skeleton object,
# instance Id is passed as parameter
stub = PyHelloWorld("tutorial.examples.HelloWorld_0x0064")
Here the stub
global variable will hold the skeleton instance.
Step 3: Once skeleton is instantiated we can start offering the service as shown in code below.
# start offering the service
stub.offerService()
Here HelloWorld service stub is created and started offering the service.
Step 4: Final task in startServer
is to send the "Hellow World" message periodically.
# periodically send the event
while(not exitFlag):
# send the message
stub.HellowWorldEvent.send("Hello World")
# wait for 100ms
sleep(100 / 1000)
Step 5: Define an entry point and call the startServer
function
# process entry point
if __name__ == "__main__":
startServer()
Next step is to run the python script using any Python3 tool you might have
Subscriber Implementation
Now we will create the Subscriber application for this scenario. Here as the native module built for skeleton already contain the proxy class also, you can skip few of the below steps and directly start with step Writing subscriber interface for One-to-One Communication
Create Project Folder
Step 1: In the SDK root directory, one can find two folders - apps/ and install/. Go to install/tools/ folder.
Step 2: Open a Linux terminal, and run the following:
$ createapp.sh -n <AppName> -d <AppProjectPath>
The above tool takes two parameter namely,
-n : name of the project and
-d: the path where this directory should be created.
Here, our project structure would be created in "< AppProjectPath >" directory with the name "< AppName >"
Step 3: Go to "< AppProjectPath/AppName >" The folder structure should look like below:
AppName
├── CMakeLists.txt
├── cmake
│ └── pulsarbuild.cmake
├── etc
│ └── em_manifest.json
├── src
└── src-gen
Here, src/ directory should contain the main code for publish or subscribe. The src-gen/ directory shall contain the generated source code from IDL files.
IDL Subscriber
Subscriber will use the same IDL created for the Publisher and will generated source code from that.
Step 1: Use the IDL created in IDL
Step 2: Generate the source code from IDL and copy to the application folder as mentioned in Code Generation (SrcGen)
Writing subscriber interface for One-to-One Communication
Step 1: Import the necessary modules and do global initialization.
from root.tutorial.examples.proxy import PyHelloWorldProxy # proxy class
from time import sleep # for delay
# global proxy object
# will be initialized when client is started
proxy = None
# flag controlling application exit behavior
# in actual situations, it is assumed to manipulated from outside
# here it is always `false`
exitFlag = False
As you can see the generated proxy class will be located in <rootname>.<Interface package hierarchy>.proxy, and the class name will be interface name with "Py" prefix and "Proxy" suffix.
Step 2: Define a method which will handle all the event notification from the communication framework.
# call back function defined to be called when any events are available
def dataCallback():
# if global proxy is not set, its an error condition
if proxy == None:
print("Invalid proxy object")
return
# read the event samples from middleware
samples = proxy.HellowWorld_event.getSamples()
# if no data available, inform the user about it
if len(samples) == 0:
print("No data available")
return
# print the received data
print("Data: ", samples)
As mentioned this method will be called whenever event data is available. When that happen, we first verify the validity of proxy
object.
After that event data is read using getSamples
API. It return a list of values. If the list contain any data that is printed, also when there is no data in the list that is also notified and function is returned.
Step 3: Define a method to start the client and to do the processing.
# this function start the client operations
def startClient():
# this function will be manipulating global proxy
global proxy
Step 4: Instantiate the client object and start finding the service.
#create proxy object, instance Id is passed as parameter
proxy = PyHelloWorldProxy("tutorial.examples.HelloWorld_0x0064")
# look for service availability
# this API return when a service is available
proxy.findService()
Step 5: Once the service is found, set the event notifier call back and then subscribe to the event.
# set up event data callback
proxy.HellowWorld_event.setReceiveHandler(dataCallback)
# finally subscribe for the event
proxy.HellowWorld_event.subscribe()
Step 6: Define an entry point and start the client operations by calling ``````
# process entry point
if __name__ == "__main__":
startClient()
while(not exitFlag):
sleep(1)
Next step is to run the python script using any Python3 tool you might have