Skip to main content

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.

info

If you have read Building your First ARA::COM App you can skip the below sections and directly goto Code Generation (SrcGen)

note

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.

HellowWorld Service
HellowWorld  Renderer 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