Matrix Board Communication in C++

authors:Joost Baars
date:June 2020

Description

This page describes the matrix board demonstrator in C++. The matrix board demonstrator is explained in Matrix Board Communication. This page starts with how the application can be built and executed. Afterward, the structure of the application is described in detail.

The matrix board C++ directory can be found in src/demonstrators/MatrixBoard/C++/. This directory contains the matrix board application in C++.

This matrix board application contains a similar implementation compared to the poc3 of the C implementation (Matrix Board Communication in C). Therefore, this is a dynamic implementation of the matrix board application.

Dependencies

The necessary dependencies for succesful compilation of the matrix board application in C++ are the following:

  • CMake
  • C++17 compiler (Gcc 7+ for example)
  • Cyclone DDS library

Installing Cyclone DDS and the other dependencies can be found in: Clean Install with internet.

Building

First, go to the C++ matrix board directory (see Description).

Execute the following commands:

mkdir build && cd build
cmake ..
make

These commands compile the code and create the executable for the C++ matrix board application.

Execution

The application must be executed using one parameter. The application can be executed using the following command:

./MatrixBoard <Matrixboard ID>

A more thorough description of the parameters can be found when executing the application with no parameters.

Note

Execution of the program

The <Matrixboard ID> parameter should be unique for every matrix board. Additionally, it may not be less than 1.

The order at which matrix boards are started does not matter.

Example

For example, two matrix boards can be started to see the communication between the two applications.

Open two terminals and go to the location where you built the matrix board application.

Execute the following in one terminal

./MatrixBoard 10

And the following command in the other terminal:

./MatrixBoard 15

When the second matrix board is started, logging should be shown. The logging that can occur is described in the chapter below.

Logging information

The matrix board application prints information to the terminal/standard output. This logging contains information that is classified with different tags.

Each tag is described below.

INFO

The INFO tag shows general information of the matrix board. It contains a starting message. The INFO tag is also used to display the speed of the matrix board. If the maximum speed changes, the new maximum speed is printed using the INFO tag.

When new traffic data is received, it is also shown with the INFO tag. The newly received traffic data is printed towards the terminal including the matrix board ID from which the traffic data came.

CONNECTED or DISCONNECTED

The CONNECTED and DISCONNECTED tag are shown when changes occur with the subsequent matrix boards.

An example of the logging can be seen below. In this example, only one subsequent matrix board is connected. There is no second subsequent matrix board on the network. The connected subsequent matrix board has an ID of 5.

[2020-06-04 00:10:07.677]: CONNECTED: Subsequent matrix board: 5
[2020-06-04 00:10:07.678]: DISCONNECTED: After the subsequent matrix board disconnected!

Each of the two subsequent matrix boards has its own message regarding if it is connected or not.

ERROR

The ERROR tag is used to show that an error occurred with the communication or the matrix board.

Implementation

This matrix board implementation works dynamically. Therefore, the order of adding matrix boards does not matter as well as the ID’s of the matrix boards.

For example, a matrix board with ID 1 can have a matrix board with ID 1000 as the subsequent matrix board (if no other matrix boards exist in between).

In the deployment diagram below, the communication between different sub-classes can be seen. The clouds in the diagram are the different DDS topics. This diagram only shows the communication of one of the matrix boards.

node "Matrix board application" as MBC1 {
 cloud Topic
 frame Sensor
 frame Display
 frame SubsequentBoards
 frame OwnMatrixBoard
 frame ActiveMatrixBoardManager
 frame MatrixBoard
}

node "Subsequent matrix board" as MBC2 {
 cloud "Topic " as topic1
}

node "After subsequent matrix board" as MBC3 {
 cloud " Topic" as topic2
}

cloud GlobalTopic
cloud DebugTopic

actor User

User -right-> MatrixBoard : ID

ActiveMatrixBoardManager --> GlobalTopic : ID
GlobalTopic --> ActiveMatrixBoardManager : Active matrix boards
ActiveMatrixBoardManager --> MatrixBoard : Subsequent 2 board ID's

Sensor --> MatrixBoard : Sensor data
MatrixBoard --> OwnMatrixBoard : Sensor data
OwnMatrixBoard --> Topic : Sensor data

MatrixBoard --> Display : Maximum speed
Display -left-> User : Maximum speed

SubsequentBoards --> MatrixBoard : Sensor data subsequent boards
topic1 --> SubsequentBoards : Sensor data
topic2 --> SubsequentBoards : Sensor data

SubsequentBoards -right-> DebugTopic : Connect/disconnect timestamps
OwnMatrixBoard -right-> DebugTopic : Connect/disconnect timestamps

MBC2 <-> GlobalTopic

MBC3 <-> GlobalTopic

This diagram shows that there is one “global” topic, which is a topic that does not belong to a specific matrix board. All matrix boards write to that topic once at the beginning of their lifecycle. Afterward, they only read the ID’s of the other matrix boards from that topic.

The other topics belong to a matrix board. The matrix board that belongs to the topic (same ID) writes its sensor data to that topic. The preceding two matrix boards read the data from that particular topic.

The debug topic can be used to calculate the time between connecting and being connected to a topic. As well as disconnecting and being disconnected from a topic. This is not done in the matrix board application, but the possibility is there because of the debug topic.

Class diagram

The class diagram is shown below. This class diagram shows that the intelligence of the system is the MatrixBoard class. This class executes everything and contains a run() function that runs the matrix board application.

package matrixboard #DDDDFF {

class ActiveMatrixBoardManager {
    +void initialize()
    +bool checkChanges()
    +SubsequentMatrixBoards getNextBoards()
    - MatrixBoardsMap _sharedMatrixBoards
    - SubsequentMatrixBoards _subsequentMatrixBoards
    - int _id
    - string _baseTopicName
    - void removeMatrixBoard(handle)
    - void addMatrixBoard()
    - bool updateBoards
}
class DDSActiveMatrixBoard {
    +dds_liveliness_changed_satus_t getStatus()
    +MessageInfo readMessage()
    +writeUserID(id)
    -void configureQoS(dds_qos_t*)
    -void configureListener(dds_listener_t*)
}

class SubsequentMatrixBoards <<struct>> {
    +unsigned int subsequent
    +unsigned int afterSubsequent
}

class CarSensor{
    struct Settings
    +unsigned int countedCars()
    +void changeSettings(Settings)
}
class MatrixBoardDisplay {
    +unsigned int getDisplayValue()
    +void setDisplayValue(unsigned int)
}
class SpecificMatrixBoard {
    +void initialize()
    __inherited__
    +bool checkNewData()
    +MBCData_Msg getLastMessage()
}
class SubsequentMatrixBoards <<struct>> {
    +unsigned int subsequent
    +unsigned int afterSubsequent
}
' matrixboard package end
}

class DDSBasics <<abstract>> {
    +void write()
    +void registerToTopic(string)
    +MsgType getLastMessage()
    +bool checkNewData()
    #virtual void configureQoS(dds_qos_t*)
    #virtual void configureListener(dds_listener_t*)
    #dds_entity_t _reader
    #dds_entity_t _writer
    #dds_entity_t _topic
    #dds_entity_t *_participant
}
package Debug #D0FFD0 {
class DebugTopicManager <<static>>{
    +void initialize(dds_entity_t *participant)
    +void sendConnectTime()
    +void sendDeviceRegistered()
    +void sendDeviceRegistered(const string customTimestamp)
    +void sendDeviceDisconnected()
    +void sendDeviceDisconnected(const string customTimestamp)
}

class MessageTypes <<enum class>> {
    +CONNECTING
    +DEVICE_CONNECTED
    +DISCONNECTING
    +DEVICE_DISCONNECTED
}
class DebugTopicDDS {
    +void initialize()
}
}

class MatrixBoardApplication {
    +void run()
    +void stop()
}

main *-- MatrixBoardApplication

DDSBasics o-- DDSActiveMatrixBoard
ActiveMatrixBoardManager *-- DDSActiveMatrixBoard

DDSBasics o-- SpecificMatrixBoard
DDSBasics o-- DebugTopicDDS

DebugTopicManager *-- DebugTopicDDS
DebugTopicManager *-- MessageTypes

MatrixBoardApplication *-- ActiveMatrixBoardManager
MatrixBoardApplication *-- "3" SpecificMatrixBoard
MatrixBoardApplication *-- MatrixBoardDisplay
MatrixBoardApplication *-- CarSensor

ActiveMatrixBoardManager *-- SubsequentMatrixBoards

Each part of DDS is separated into its own class. This is divided into 2 classes and one abstract base class.

The ActiveMatrixBoardManager class manages the “global” topic and can return the ID’s of the two subsequent matrix boards.

The DDSActiveMatrixBoard class manages the DDS part of the “global” topic.

The SpecificMatrixBoard manages a matrix board topic with a certain ID. This class can read and write from the topic it is connected to. This class is used for the 2 subsequent matrix boards (read-only) and for its own matrix board (write-only). Therefore, there are 3 classes of these.

There are two packages in this class diagram. One for the matrix board functionalities. The other is the debug package. The DebugTopicManager class can be used for sending debug messages to a DDS debug topic (using the DebugTopicDDS class). The DebugTopicManager class is a fully static class, so the debug functions are easily accessible in the matrix board.

Global topic / ActiveMatrixBoards class

This chapter describes the application in a technical manner.

The ActiveMatrixBoards class contains the logic for defining the subsequent 2 matrix boards. This class uses the DDSActiveMatrixBoard class for communication with the “global” topic.

The settings for the “global” topic are configured in the DDSActiveMatrixBoard class. The “global” topic is configured to have a history of 1 and a durability transient local. The history of 1 ensures that the last message of each writer is stored and can be resent. The durability transient local ensures that if a new reader connects, it receives previously send messages from the writer. This durability setting also specifies that the previously sent messages are stored by the writer. This is important because if the writer disconnects, we don’t want to see it’s message anymore. This combination makes sure that the last message of a writer is resent to a newly connected reader, but only if that writer still exists.

Each matrix board only writes once to the “global” topic, containing the ID of that matrix board. This message will automatically resend if a new matrix board connects (this is done by DDS). Therefore, all matrix boards know the ID of every other matrix board. Because when they connect, they receive the last message of each matrix board (containing the ID). And if they are alive when a new matrix board connects, it just reads the message of the newly connected matrix board.

The liveliness is also configured for the “global” topic. This is used for removing disconnected matrix boards and reconnecting to a different matrix board that is alive. DDS uses handles for each DDS instance. This handle is added to every DDS message and is also added to the liveliness status. Therefore, this can be used to know what matrix board disconnected when the liveliness changes.

The ActiveMatrixBoardManager class contains an ordered map with the ID and the handle of each matrix board. When a matrix board disconnects, this matrix board is removed from the map based on its handle. This map is ordered based on the ID of the matrix board. Therefore, the subsequent matrix boards can easily be found within the map. Scalability wise, this map becomes larger as more matrix boards are on the network. Therefore, this method is not ideal for a fully scalable matrix board network.

When the ActiveMatrixBoardManager class registers to the topic, it reads all existing messages from the topic and fills the ordered map with the existing matrix boards. If there is a liveliness update, it checks if a device is added or removed. If a device got added, it reads the new message on the topic. If a device got removed, it reads the handle of the disconnected matrix board and removes that device from the map. Afterward, it checks if the subsequent matrix boards got changed. If so, this is updated to the new ID’s.

MatrixBoard class

This class executes everything and contains the run loop for the matrix board application.

At the beginning of the run loop, the registerToTopic() functions are executed for both the OwnMatrixBoard and the ActiveMatrixBoards classes. This initializes DDS to register to the topic. Furthermore, a timer is initialized to send the traffic data to its own topic with a certain interval. This timer is set on 1 second. So traffic data is sent to its own topic every second.

There are mainly 3 parts in the matrix board run loop. The first is checking the timer. If the timer has passed, the newest sensor data is requested and send to its own topic using the OwnMatrixBoard class. Additionally, the display of the matrix board is also updated in this timer.

The second part is checking for updates on the “global” topic. This is done by executing the checkChanges() function in the ActiveMatrixBoards class. If there are changes, the `SpecificMatrixBoard objects that point to the subsequent matrix boards are updated. These objects are stored in a so-called smart-pointer which handles the allocation and deallocation of the resource. This smart-pointer is simply set to a new SpecificMatrixBoard object when the ID’s of the subsequent matrix boards changes.

The third and last part of the run loop reads the traffic data from the 2 subsequent matrix boards (if they exist). First, the smart-pointer is checked if there is an object stored. If so, the checkNewData() function from the SpecificMatrixBoard class is executed. At last, this data is read using getLastMessage() function if new data is received. If there was no object stored in the smart-pointer in the first place, then nothing would happen. As there is apparently no subsequent matrix board.

Specific Matrixboard class

This class connects to a specific matrix board topic. Each matrix board is connected to its own topic and writes its own sensor data towards that topic. Additionally, each matrix board is connected to 2 subsequent matrix board topics (if they exist). Therefore, there are a maximum of 3 specific matrix board objects created.

The specific matrix board class creates its own DDS participant. This is done because specific matrix board objects should be easily deletable and new objects for different topics should be easily creatable. If the participant was not managed by the specific matrix board, then the previously allocated readers or writers of the specific matrix board would stay alive when the specific matrix board object would be removed. This results in no liveliness change when it should. Additionally, this would use more memory and would have a negative impact on the dynamic aspect of the matrix board application.

The specific matrix board has by default a liveliness callback function for the writer and a different callback function for the reader. The reader gets a callback if the liveliness changes of the writer on the topic. The writer gets a callback when a reader connects or disconnects from the topic. The use of these callbacks are only the debug messages. To detect when a device connects/disconnects from a certain topic.

Debug topic

The class diagram also contains a debug topic. This debug topic is added for debug purposes. A timestamp is sent to the debug topic when a matrix board connects to a specific matrix board topic and when it is connected to that topic. When a device disconnects from the specific matrix board topic, a timestamp is also sent to the debug topic.

The debug functionalities can be used to see when a device connects or disconnects. Additionally, the time of connecting/disconnecting can be measured.