Building your First App
This guide will step you through the process of creating a barebones Hello World app on the Nebula platform.
If you have read Building your First ARA::COM App you can skip the below sections and directly goto Writing publisher interface for One-to-One Communication
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 using the Pulsar Managed application framework: We can implement the HelloWorld service application a PeriodicTask thus sending the "Hello World" message during every invocations. The HelloWorldRenderer application can be data triggered, i.e., it can listen for the "Hello World" broadcasts and process it as it becomes available.
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. In order to generate the source code using the tool, run the below command:
nebcodegen --idl <idlpath> --out <outpath>
Here, idlPath is the source idl file path and outpath shall be any folder where the generated source will be saved.
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
└── types
└── nebcommonTypes.hpp
copy the generated source folder to the src-gen folder in the project folder created in Create Project Folder
Writing publisher interface for One-to-One Communication
Step 1: Include necessary headers and initialize a logger for this server interface.
nebula::platform::Logger logger_ { nebula::platform::Logger("HelloWorldServer") };
Step 2: Define the encapsulated MyTask interface as below: Here, *.cpp contains an implementation of the server interface. .
class MyTask : public nebula::exec::task::Task {
bool init();
bool cleanup();
bool run();
private:
std::shared_ptr<tutorial::examples::HelloWorldStub> helloWorldService;
};
Here helloWorldService
member variable will hold the stub instance.
Step 3: MyTask::init() is required to configure/setup pre-requisites and start the service.
bool MyTask::init() {
// Initiate the task; Start the service
std::string inst = "tutorial.examples.HelloWorld_0x0064";
helloWorldService = std::make_shared<tutorial::examples::HelloWorldStub>(inst);
helloWorldService->OfferService();
return true;
}
Here HelloWorld service stub is created and started offering the service.
Step 4: MyTask::cleanup() is required to clean up existing resources and stop the service.
bool MyTask::cleanup() {
// Clean-up resources
return true;
}
Step 5: MyTask::run() performs the actual task of sending the topic events at set frequency.
bool MyTask::run() {
// send the message "Hello World"
if(helloWorldService) {
std::string message{"Hello World"};
helloWorldService->HellowWorldEvent.Send(message);
}
return true;
}
here in the run
method, which is invoked periodically, the "Hello World" broadcast is sent.
Step 6: Finally, set the periodicity of the task as below. This service publishes events periodically every 100ms. This can be done in the special appInit function, which will be called by the framework for initializing applications
void appInit() {
auto nsv = std::chrono::duration_cast<std::chrono::nanoseconds>(100ms);
nebula::exec::task::PeriodicTask<MyTask, 100000000> task;
// Wait
while(true) {
usleep(nsv.count() * 100);
}
}
Update CMakeLists file
Before building the project, 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")
SET(PRJ_SRC
${GEN_SRCS}
src/main.cc # or any app specific files
)
add_executable(${PRJ_NAME} ${PRJ_SRC})
Build
Follow the build guidelines as used for any CMake based project. Create the build/ directory if not already present.
cd build/
cmake ..
Please note that here the parameter as mentioned in Build App are not needed, as the application is created using the SDK createApp tool.
Subscriber Implementation
Now we will create the Subscriber application for this scenario.
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: Include necessary headers and initialize a logger for this client/subscriber interface.
neb::platform::Logger logger_ {neb::platform::Logger("HelloWorldRenderer") };
Step 2: Define the encapsulated MyTask interface for client as below: Here, *.cpp contains an implementation of the client interface.
/**
* @brief Data triggered task
* Listen for a particular topic, and when it is available, process it.
*/
class MyTask : public nebula::exec::task::Task {
public:
MyTask(nebula::comm::EventElement<HelloWorldDataProvider>& element) : element_(element) {}
private:
nebula::comm::EventElement<HelloWorldDataProvider>& element_;
};
here element_
is an accessor to get the event data whenever its available
Step 3: MyTask::init() is required to configure/setup pre-requisites and start the client service.
bool MyTask::init() {
return true;
}
Step 4: MyTask::cleanup() is required to clean up existing resources and stop the client service.
bool MyTask::cleanUp() {
// Task Cleanup; _free_ resources if any
return true;
}
Step 5: MyTask::run() performs the actual task of collecting the topic events from server.
/**
* @brief Triggered when new data for the topic is available
*
* @return true
* @return false
*/
bool MyTask::run() {
auto data = element_.getEventData<HelloWorldDataProvider::HellowWorldEventType>();
for(auto d : data) {
//Process Data
}
return true;
}
here the run
method is invoked whenever data is available, when that happen, this implementation read the data and process it (e.g. display in console).
Step 6: Ensure to setup the DataTriggeredTask.
nebula::comm::EventElement<HelloWorldDataProvider> helloWorld{HelloWorldDataProvider::Events::HellowWorldEvent};
MyTask task{helloWorld};
nebula::exec::task::DataTriggeredTask dTask(task, helloWorld);
Next step is to update CMake and building the application. Those steps are same as described in the previous section.