I hope you are all well.
I’ve promised two new subtopics: DDS in a stand-alone c++ executable, and ROS 2 connectivity. This thread we will discuss the DDS stand-alone: OpenDDS Node
For those of you following these software dives, as mentioned, the repo above is a work in progress. Enough said.
What is this thing? you ask. Well, the application code is a skeleton, or template of how to interact with the DDS publish/subscribe architecture. The application can do whatever you would like it to do, within computational feasibility. You can read topics, write topics, and process data within your network using the OCI implementation of the DDS standards. You can also hook up to other DDS providers, like RTI or PrismTech. In the node code on the repo, we are using OCI OpenDDS, which does not require a license to use and develop, to subscribe and publish to the RTI Connext DDS impl that supplies the pub/sub layer for the Trident.
Let’s first look at the directory structure of the Node:
|–OpenDDS (submodule of project)
OpenDDS folder contains the IDL that we generate to communicate with the trident. For the example in the repo, I’ve reproduced the rov_depth topic IDL files, from the Openrov-dds folder in the GitLab repo from OpenROV. openrov-dds We also have the Scripts folder and Source folder. You only need to concern yourself with the IDL folder at the top layer and the Scripts folder. The other Source and its subsequent subfolders contain generated code that is populated by the build-idl-cpp.sh script found in Scripts.
The build script takes the initial IDL files in the high level IDL folder and generates all the needed code that DDS uses to marshal message types across the network. serialization/de-serialization, etc. As you will see in the main source files for the application, you need only call types from the TypeSupport files for the DDS message types. Fortunately, I’ve highlighted the areas in the code you need to modify for your own purposes…mostly.
We’ve added the IDLs needed to read depth data. All that is required is to the the build script.
All the needed code will be populated under the Generated and IDL folders in Source of the OpenDDS high level directory. Example output below. Do not mind the error/warnings. That is saying that we’ve match names of data values and sturcture types in the IDL, with only case differeing the two. This is from a newer impl of OpenDDS. Within the script, I’ve added the build flag -Cw with changes the error to a warning and continues compiling the needed support code for the IDL files.
With the DDS generated code for our data, let’s get out of the OpenDDS folder and move to the high level ThirdPartyLibs folder. There’s nothing here. This folder is a place holder for other things you may want to add to the node like another IMU, or image processing libraries, or whatever. You can take a look at the imu branches of the repo as examples. Beware however, those branches are very outdated.
Within the Source folder, we see a Config, Scripts, and Utility folders as well as the main, CApp, and CAppNodeImpl files. The Config folder holds a post build script that is run by cmake that copies the Config folder into the build folder of the project. The importance of the Config folder is that it contains logging configuration and, more importantly, RTPS configuration for the node. The utility folder holds the easy logging header, general methods that may or may not be helpful, and the cntrl-c sigint handling code. You shouldn’t have to bother with any of this.
Let’s now talk about the meat of the application code that lives in Source:
These five files constitute all application classes and logic to, in this example, read ROV depth information in real time as it’s being published from the Trident. Exciting.
CAppNode is the parent class of CAppNodeImpl. It includes all of the DDS related files needed for this example.
This class also des something very useful in the OpenDDS world. It inherits from DataReaderListener which is a abstract class that requires certain methods to be implemented in an application that would like to subscribe to topics. This methods:
are important…and it is up to the developer to implement them. In this example, and in most cases, the on_data_available method is where you will put the code to read and display/manipulate data received over the network. And we have.
You should not need to modify the CAppNode class and impl at all…for most things…mostly. The .cpp of this class, the impl file for those of you not so c++ knowledgeable, is where the virtual methods from DataListener are stubbed out. This allows us to override what functions we wish/need for our application.
This leads us to our actual application code that lives in CAppNodeImp and main.cpp
Let’s talk about main.cpp. This is out launcher, our application driver so to type. Things we need to beware of, DOMAIN_ID 0. This is needed since the Trident operated in Domain 0 in DDS world. There is also a missing parameter that needs to come in from the command line, however, I have not implemented it yet. That is the Partition ID. This is needed by the readers and writers for the Trident. Each trident has it’s own partition. You need to know what this is. I use RTI’s Admin console to explore the DDS topics and get the partition id. In a near future update, I will have this as a command line parameter.
main.cpp also uses unique pointers…enough confusing c++ stuff. Main set’s up the application, reads in command line information and starts the app.
Finally, getting to CAppNodeImpl.