OpenDaVINCI Tutorials

How to use concurrency

How to use concurrency

OpenDaVINCI has a built-in platform-independent concurrency engine. Concurrency is realized on Linux-based and BSD-based operating systems using pthreads and on Windows by using C++11 features. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/service

In order to use concurrency in a user-supplied module, you will find a simple example below.

MyService.hpp:

#include <opendavinci/odcore/base/Service.h>

// Concurrency is provided by the class odcore::base::Service.
class MyService : public odcore::base::Service {

    // Your class needs to implement the method void beforeStop().
    virtual void beforeStop();

    // Your class needs to implement the method void run().
    virtual void run();

};

MyService.cpp:

#include <stdint.h>
#include <iostream>
#include <opendavinci/odcore/base/Thread.h>

#include "MyService.hpp"

using namespace std;

// Your class needs to implement the method void beforeStop().
void MyService::beforeStop() {
    // This block is executed right before
    // the thread will be stopped.
    cout << "This method is called right before "
         << "isRunning will return false." << endl;
}

// Your class needs to implement the method void run().
void MyService::run() {
    // Here, you can do some initialization of
    // resources (e.g. data structures and the like).
    cout << "Here, I can do some initialization as the "
         << "calling thread, which will start this service, "
         << "will be blocked until serviceReady() has been called." << endl;

    serviceReady();

    // This is the body of the concurrently executed method.
    while (isRunning()) {
        cout << "This message is printed every second." << endl;

        const uint32_t ONE_SECOND = 1000 * 1000;
        odcore::base::Thread::usleepFor(ONE_SECOND);
    }
}

int32_t main(int32_t argc, char **argv) {
    MyService s;

    s.start();
    const uint32_t ONE_SECOND = 1000 * 1000;
    odcore::base::Thread::usleepFor(10 * ONE_SECOND);

    s.stop();
}

Your class needs to derive from odcore::base::Service, which is provided in #include <opendavinci/odcore/base/Service.h> in the include directory opendavinci. This class provides two methods that need to be implemented in deriving classes: void beforeStop() and void run(). The former method is called from an outside thread intending to stop the concurrently executing thread; thus any shared resources can be released properly for example. The latter method will be executed in a new thread running concurrently to the calling thread.

To detach the execution of the newly created thread from the calling one, the method serviceReady() needs to be called to signal to the calling thread that the new thread is initialized and will now enter, e.g., its main processing loop; the calling thread is blocked from any further execution until this method is called. This synchronization dependency ensures that both resources that need to be provided by the operating system to run a thread are available and ready, and any shared resources like data structures that are needed from a deriving class are set up and ready.

You can compile and link the example assuming the file is called MyService.cpp:

g++ -std=c++11 -I /usr/include -c MyService.cpp -o MyService.o
g++ -o service MyService.o -lopendavinci -lpthread

Running the example above will print the following on the console:

$ ./service
Here, I can do some initialization as the calling thread, which will start this service, will be blocked until serviceReady() has been called.
This message is printed every second.
This message is printed every second.
This message is printed every second.
This message is printed every second.
This message is printed every second.
This message is printed every second.
This message is printed every second.
This message is printed every second.
This message is printed every second.
This message is printed every second.
This method is called right before isRunning will return false.

How to use realtime concurrency

OpenDaVINCI has a built-in realtime concurrency engine available only on Linux platforms with rt-preempt kernels. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/realtimeservice

In order to use realtime concurrency in a user-supplied module, you will find a simple example below.

MyRealtimeService.hpp:

#include <opendavinci/odcore/base/RealtimeService.h>

// Realtime concurrency is provided by the class odcore::base::RealtimeService.
class MyRealtimeService : public odcore::base::RealtimeService {

    public:
        MyRealtimeService(const enum PERIOD &period);

        // Your class needs to implement the method void nextTimeSlice().
        virtual void nextTimeSlice();

};

MyRealtimeService.cpp:

#include <stdint.h>
#include <iostream>
#include <opendavinci/odcore/base/Thread.h>

#include "MyRealtimeService.hpp"

using namespace std;

MyRealtimeService::MyRealtimeService(const enum PERIOD &period) :
    odcore::base::RealtimeService(period) {}

void MyRealtimeService::nextTimeSlice() {
    cout << "This message is printed every 100 ms." << endl;
}

int32_t main(int32_t argc, char **argv) {
    MyRealtimeService rts(odcore::base::RealtimeService::ONEHUNDREDMILLISECONDS);

    rts.start();

    const uint32_t ONE_SECOND = 1000 * 1000;
    odcore::base::Thread::usleepFor(5 * ONE_SECOND);

    rts.stop();
}

Your class needs to derive from odcore::base::RealtimeService, which is provided in #include <opendavinci/odcore/base/RealtimeService.h> in the include directory opendavinci. This class provides one method that needs to be implemented in deriving classes: void nextTimeSlice(). This method will be called with the specified periodicity.

Furthermore, the deriving class needs to pass the desired periodicity to the superclass RealtimeService.

You can compile and link the example:

g++ -std=c++11 -I /usr/include -c MyRealtimeService.cpp -o MyRealtimeService.o
g++ -o realtimeservice MyRealtimeService.o -lopendavinci -lpthread -lrt

The resulting program can be run as superuser (as the scheduling properties will be adjusted) and will print the following on the console:

$ sudo ./realtimeservice
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.
This message is printed every 100 ms.

Dining Philosophers

This example illustrates the known “Dining Philosophers” problem. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/diningphilosophers

The background for this example is well described here at https://en.wikipedia.org/wiki/Dining_philosophers_problem

How to exchange data in distributed processes

Using shared memory for processes on the same machine

How to exchange data between processes

OpenDaVINCI has a built-in interprocess communication engine realized with shared memory and semaphores. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/ipcsharedmemory

In order to use shared memory to exchange data between your processes on a local machine, you will find a simple example below. To share data between two processes on the same machine, the example is split into a producer and a consumer example. The former will create the shared memory segment, while the latter is attaching to the shared memory segment to consume the data.

ipcsharedmemoryproducer.cpp:

#include <stdint.h>
#include <iostream>
#include <string>
#include <memory>
#include <opendavinci/odcore/base/Lock.h>
#include <opendavinci/odcore/base/Thread.h>
#include <opendavinci/odcore/wrapper/SharedMemory.h>
#include <opendavinci/odcore/wrapper/SharedMemoryFactory.h>

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore;
using namespace odcore::wrapper;

int32_t main(int32_t argc, char **argv) {
    const string NAME = "MySharedMemory";
    const uint32_t SIZE = 26;

    // We are using OpenDaVINCI's std::shared_ptr to automatically
    // release any acquired resources.
    try {
        std::shared_ptr<SharedMemory> sharedMemory(SharedMemoryFactory::createSharedMemory(NAME, SIZE));

        if (sharedMemory->isValid()) {
            uint32_t counter = 20;
            while (counter-- > 0) {
                {
                    // Using a scoped lock to lock and automatically unlock a shared memory segment.
                    odcore::base::Lock l(sharedMemory);
                    char *p = static_cast<char*>(sharedMemory->getSharedMemory());
                    for (uint32_t i = 0; i < sharedMemory->getSize(); i++) {
                        char c = (char) (65 + ((i+counter)%26));
                        p[i] = c;
                    }
                }

                // Sleep some time.
                const uint32_t ONE_SECOND = 1000 * 1000;
                odcore::base::Thread::usleepFor(ONE_SECOND);
            }
        }
    }
    catch(string &exception) {
        cerr << "Shared memory could not created: " << exception << endl;
    }
}

The producer process needs to include <opendavinci/odcore/wrapper/SharedMemory.h> and <opendavinci/odcore/wrapper/SharedMemoryFactory.h> that encapsulate the platform-specific implementations.

SharedMemoryFactory provides a static method called createSharedMemory that allows you to create a shared memory segment that is automatically protected with a system semaphore. Therefore, a name and the size in bytes for the shared memory segment need to be provided.

SharedMemoryFactory returns the SharedMemory automatically wrapped into a std::shared_ptr that takes care of releasing system resources when exiting your program.

Once you have the std::shared_ptr at hand, you can check its validity by calling bool isValid(). If the shared memory is valid, you can request exclusive access to it by using a scoped lock provided by the class odcore::base::Lock. A scoped lock will automatically release a concurrently accessed resource when the current scope is left. As soon as it gets available, you can access it by calling getSharedMemory() returning a char*.

You can compile and link the producer example as follows:

g++ -std=c++11 -I /usr/include -c ipcsharedmemoryproducer.cpp -o ipcsharedmemoryproducer.o
g++ -o ipcsharedmemoryproducer ipcsharedmemoryproducer.o -lopendavinci -lpthread

ipcsharedmemoryconsumer.cpp:

#include <stdint.h>
#include <iostream>
#include <string>
#include <memory>
#include <opendavinci/odcore/base/Lock.h>
#include <opendavinci/odcore/base/Thread.h>
#include <opendavinci/odcore/wrapper/SharedMemory.h>
#include <opendavinci/odcore/wrapper/SharedMemoryFactory.h>

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore;
using namespace odcore::wrapper;

int32_t main(int32_t argc, char **argv) {
    const string NAME = "MySharedMemory";

    // We are using OpenDaVINCI's std::shared_ptr to automatically
    // release any acquired resources.
    try {
        std::shared_ptr<SharedMemory> sharedMemory(SharedMemoryFactory::attachToSharedMemory(NAME));

        if (sharedMemory->isValid()) {
            uint32_t counter = 10;
            while (counter-- > 0) {
                string s;
                {
                    // Using a scoped lock to lock and automatically unlock a shared memory segment.
                    odcore::base::Lock l(sharedMemory);
                    char *p = static_cast<char*>(sharedMemory->getSharedMemory());
                    s = string(p);
                }

                cout << "Content of shared memory: '" << s << "'" << endl;

                // Sleep some time.
                const uint32_t ONE_SECOND = 1000 * 1000;
                odcore::base::Thread::usleepFor(0.5 * ONE_SECOND);
            }
        }
    }
    catch(string &exception) {
        cerr << "Shared memory could not created: " << exception << endl;
    }
}

The consumer process needs to include <opendavinci/odcore/wrapper/SharedMemory.h> and <opendavinci/odcore/wrapper/SharedMemoryFactory.h> that encapsulate the platform-specific implementations as well.

On the consumer side, SharedMemoryFactory provides a static method called attachToSharedMemory that allows you to attach to an existing shared memory segment. Thus, only the name needs to be provided as OpenDaVINCI automatically encodes the the size of the shared memory additionally into the shared memory segment.

SharedMemoryFactory returns the SharedMemory automatically wrapped into a std::shared_ptr that takes care of releasing system resources when exiting your program.

Once you have the std::shared_ptr at hand, you can check its validity by calling bool isValid(). If the shared memory is valid, you can request exclusive access to it by using a scoped lock provided by the class odcore::base::Lock. A scoped lock will automatically release a concurrently accessed resource when the current scope is left. As soon as it gets available, you can access it by calling getSharedMemory() returning a char*.

You can compile and link the consumer example as follows:

g++ -std=c++11 -I /usr/include -c ipcsharedmemoryconsumer.cpp -o ipcsharedmemoryconsumer.o
g++ -o ipcsharedmemoryconsumer ipcsharedmemoryconsumer.o -lopendavinci -lpthread

To test the program, simply run the producer:

$ ./ipcsharedmemoryproducer

followed by running the consumer that is printing to the console:

$ ./ipcsharedmemoryconsumer
Content of shared memory: 'QRSTUVWXYZABCDEFGHIJKLMNOP'
Content of shared memory: 'PQRSTUVWXYZABCDEFGHIJKLMNO'
Content of shared memory: 'PQRSTUVWXYZABCDEFGHIJKLMNO'
Content of shared memory: 'OPQRSTUVWXYZABCDEFGHIJKLMN'
Content of shared memory: 'OPQRSTUVWXYZABCDEFGHIJKLMN'
Content of shared memory: 'NOPQRSTUVWXYZABCDEFGHIJKLM'
Content of shared memory: 'NOPQRSTUVWXYZABCDEFGHIJKLM'
Content of shared memory: 'MNOPQRSTUVWXYZABCDEFGHIJKL'
Content of shared memory: 'MNOPQRSTUVWXYZABCDEFGHIJKL'
Content of shared memory: 'LMNOPQRSTUVWXYZABCDEFGHIJK'

You can inspect the system resources when running ipcsharedmemoryconsumer:

$ ipcs
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
...
0x0000006a 1900559    odv        600        30         1

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

Serial connection

How to receive bytes from a serial connection

OpenDaVINCI has a built-in serial port handling engine based on https://github.com/wjwwood/serial. This library realizes serial communication on Linux- and BSD-based platforms as well as on Windows. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/serialreceivebytes

In order to receive bytes over a serial link, you will find a simple example below.

SerialReceiveBytes.hpp:

#include <opendavinci/odcore/io/StringListener.h>

// This class will handle the bytes received via a serial link.
class SerialReceiveBytes : public odcore::io::StringListener {

    // Your class needs to implement the method void nextString(const std::string &s).
    virtual void nextString(const std::string &s);
};

To receive any data, we firstly declare a class that implements the interface odcore::io::StringListener. This method will handle any bytes received from the low level SerialPort. Here, your application should realize an application-specific protocol.

SerialReceiveBytes.cpp:

#include <stdint.h>
#include <iostream>
#include <string>
#include <memory>
#include <opendavinci/odcore/base/Thread.h>
#include <opendavinci/odcore/wrapper/SerialPort.h>
#include <opendavinci/odcore/wrapper/SerialPortFactory.h>

#include "SerialReceiveBytes.hpp"

using namespace std;

void SerialReceiveBytes::nextString(const string &s) {
    cout << "Received " << s.length() << " bytes containing '" << s << "'" << endl;
}

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore;
using namespace odcore::wrapper;

int32_t main(int32_t argc, char **argv) {
    const string SERIAL_PORT = "/dev/pts/20";
    const uint32_t BAUD_RATE = 19200;

    // We are using OpenDaVINCI's std::shared_ptr to automatically
    // release any acquired resources.
    try {
        std::shared_ptr<SerialPort>
            serial(SerialPortFactory::createSerialPort(SERIAL_PORT, BAUD_RATE));

        // This instance will handle any bytes that are received
        // from our serial port.
        SerialReceiveBytes handler;
        serial->setStringListener(&handler);

        // Start receiving bytes.
        serial->start();

        const uint32_t ONE_SECOND = 1000 * 1000;
        odcore::base::Thread::usleepFor(10 * ONE_SECOND);

        // Stop receiving bytes and unregister our handler.
        serial->stop();
        serial->setStringListener(NULL);
    }
    catch(string &exception) {
        cerr << "Error while creating serial port: " << exception << endl;
    }
}

To receive bytes from a serial link, your application needs to include <opendavinci/odcore/wrapper/SerialPort.h> and <opendavinci/odcore/wrapper/SerialPortFactory.h> that encapsulate the platform-specific implementations.

SerialPortFactory provides a static method called createSerialPort that allows you to receive bytes from a serial link. On success, this call will return a pointer to a SerialPort instance that is used to handle the data transfer. On failure, the method createSerialPort will throw an exception of type string with an error message.

If the serial port could be successfully created, we register our StringListener at the newly created SerialPort to be invoked when new bytes are available to be interpreted by a user-supplied protocol.

Once we have registered our StringListener, the SerialPort is simply started and the main thread is falling asleep for a while in our example. After some time, the program will stop receiving bytes, unregister the StringListener, and release the system resources.

To conveniently handle the resource management of releasing the acquired system resources, a std::shared_ptr is used that automatically releases memory that is no longer used.

Please note that once you have stopped SerialPort you cannot reuse it and thus, you need to create a new one.

You can compile and link the example:

g++ -std=c++11 -I /usr/include -c SerialReceiveBytes.cpp -o SerialReceiveBytes.o
g++ -o serialreceivebytes SerialReceiveBytes.o -lopendavinci -lpthread

To test the program, we create a simple virtual serial port on Linux using the tool socat:

$ socat -d -d pty,raw,echo=0 pty,raw,echo=0
2015/06/13 11:17:17 socat[2737] N PTY is /dev/pts/19
2015/06/13 11:17:17 socat[2737] N PTY is /dev/pts/20
2015/06/13 11:17:17 socat[2737] N starting data transfer loop with FDs [3,3] and [5,5]

Please note that the tutorial program uses /dev/pts/20 to send data to; in the case that your setup has a different pts from socat, you need to adjust the source code.

Now, you can start the resulting program to listen for data:

$ ./serialreceivebytes

Next, we simply pipe some data through the other end of the virtual port:

$ echo "Hello World" > /dev/pts/19

The resulting program will print:

Received partial string of length 12 bytes containing 'Hello World
'

UDP connection

How to send bytes to a UDP server

OpenDaVINCI has a built-in UDP handling engine realized on Linux-based and BSD-based operating systems using POSIX sockets and on Windows using WinSock. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/udpsendbytes

In order to send bytes to a UDP server, you will find a simple example below.

UDPSendBytes.cpp:

#include <stdint.h>
#include <iostream>
#include <string>
#include <memory>
#include <opendavinci/odcore/io/udp/UDPSender.h>
#include <opendavinci/odcore/io/udp/UDPFactory.h>

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore;
using namespace odcore::io::udp;

int32_t main(int32_t argc, char **argv) {
    const string RECEIVER = "127.0.0.1";
    const uint32_t PORT = 1234;

    // We are using OpenDaVINCI's std::shared_ptr to automatically
    // release any acquired resources.
    try {
        std::shared_ptr<UDPSender> udpsender(UDPFactory::createUDPSender(RECEIVER, PORT));

        udpsender->send("Hello World\r\n");
    }
    catch(string &exception) {
        cerr << "Data could not be sent: " << exception << endl;
    }
}

To send bytes over UDP to a UDP socket, your application needs to include <opendavinci/odcore/io/udp/UDPSender.h> and <opendavinci/odcore/io/udp/UDPFactory.h> that encapsulate the platform-specific implementations.

UDPFactory provides a static method called createUDPSender that allows you to send bytes to the specified UDP server. On success, this call will return a pointer to a UDPServer instance that is used to handle the data transfer. On failure, the method createUDPSender will throw an exception of type string with an error message.

If the connection could be successfully established, the method send can be used to send data of type string to the specified UDP server.

To conveniently handle the resource management of releasing the acquired system resources, a std::shared_ptr is used that automatically releases memory that is no longer used.

You can compile and link the example:

g++ -std=c++11 -I /usr/include -c UDPSendBytes.cpp -o UDPSendBytes.o
g++ -o udpsendbytes UDPSendBytes.o -lopendavinci -lpthread

To test the program, we create a simple UDP server awaiting data by using the tool ``nc``(netcat):

$ nc -l -u -p 1234

The resulting program can be run:

$ ./udpsendbytes
$

The tool nc will print Hello World and then terminate as the connection is closed on exiting udpsendbytes.

How to receive bytes as a UDP server

OpenDaVINCI has a built-in UDP handling engine realized on Linux-based and BSD-based operating systems using POSIX sockets and on Windows using WinSock. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/udpreceivebytes

In order to receive bytes as a UDP server, you will find a simple example below.

UDPReceiveBytes.hpp:

#include <opendavinci/odcore/io/StringListener.h>

// This class will handle the bytes received via a UDP socket.
class UDPReceiveBytes : public odcore::io::StringListener {

    // Your class needs to implement the method void nextString(const std::string &s).
    virtual void nextString(const std::string &s);
};

To receive any data, we firstly declare a class that implements the interface odcore::io::StringListener. This class will be registered as listener to our UDP socket that we create later.

UDPReceiveBytes.cpp:

#include <stdint.h>
#include <iostream>
#include <string>
#include <memory>
#include <opendavinci/odcore/base/Thread.h>
#include <opendavinci/odcore/io/udp/UDPReceiver.h>
#include <opendavinci/odcore/io/udp/UDPFactory.h>

#include "UDPReceiveBytes.hpp"

using namespace std;

void UDPReceiveBytes::nextString(const string &s) {
    cout << "Received " << s.length() << " bytes containing '" << s << "'" << endl;
}

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore;
using namespace odcore::io;
using namespace odcore::io::udp;

int32_t main(int32_t argc, char **argv) {
    const string RECEIVER = "0.0.0.0";
    const uint32_t PORT = 1234;

    // We are using OpenDaVINCI's std::shared_ptr to automatically
    // release any acquired resources.
    try {
        std::shared_ptr<UDPReceiver>
            udpreceiver(UDPFactory::createUDPReceiver(RECEIVER, PORT));

        // This instance will handle any bytes that are received
        // by our UDP socket.
        UDPReceiveBytes handler;
        udpreceiver->setStringListener(&handler);

        // Start receiving bytes.
        udpreceiver->start();

        const uint32_t ONE_SECOND = 1000 * 1000;
        odcore::base::Thread::usleepFor(10 * ONE_SECOND);

        // Stop receiving bytes and unregister our handler.
        udpreceiver->stop();
        udpreceiver->setStringListener(NULL);
    }
    catch(string &exception) {
        cerr << "Error while creating UDP receiver: " << exception << endl;
    }
}

To receive bytes from a UDP socket, your application needs to include <opendavinci/odcore/io/udp/UDPReceiver.h> and <opendavinci/odcore/io/udp/UDPFactory.h> that encapsulate the platform-specific implementations.

UDPFactory provides a static method called createUDPReceiver that allows you to receive bytes from a listening UDP socket. On success, this call will return a pointer to a UDPReceiver instance that is used to handle the data transfer. On failure, the method createUDPReceiver will throw an exception of type string with an error message.

If the UDP socket could be successfully created, we register our StringListener at the newly created UDPReceiver to be invoked when new bytes are available. Handling the bytes between the UDP socket and the StringListener notification is happening in the same thread.

Once we have registered our StringListener, the UDPReceiver is simply started and the main thread is falling asleep for a while in our example. After some time, the program will stop receiving bytes, unregister the StringListener, and release the system resources.

To conveniently handle the resource management of releasing the acquired system resources, a std::shared_ptr is used that automatically releases memory that is no longer used.

Please note that once you have stopped UDPReceiver you cannot reuse it and thus, you need to create a new one.

You can compile and link the example:

g++ -std=c++11 -I /usr/include -c UDPReceiveBytes.cpp -o UDPReceiveBytes.o
g++ -o udpreceivebytes UDPReceiveBytes.o -lopendavinci -lpthread

The resulting program can be run:

$ ./udpreceivebytes

To test the program, we pipe a string through the tool nc:

$ echo "Hello World" | nc -u 127.0.0.1 1234

Our program udpreceivebytes will print Hello World.

How to receive packets as a UDP server

OpenDaVINCI has a built-in UDP handling engine realized on Linux-based and BSD-based operating systems using POSIX sockets and on Windows using WinSock. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/udpreceivepackets

In order to receive packets as a UDP server, you will find a simple example below.

UDPReceivePackets.hpp:

#include <opendavinci/odcore/io/PacketListener.h>

// This class will handle packets received via a UDP socket.
class UDPReceivePackets : public odcore::wrapper::PacketListener {

    // Your class needs to implement the method void void nextPacket(const odcore::io::Packet &p).
    virtual void nextPacket(const odcore::io::Packet &p);
};

To receive any packets, we firstly declare a class that implements the interface odcore::wrapper::PacketListener. This class will be registered as listener to our UDP socket that we create later. Packets provide the sending address in addition to the pure StringListener interface.

UDPReceivePackets.cpp:

#include <stdint.h>
#include <iostream>
#include <string>
#include <memory>
#include <opendavinci/odcore/base/Thread.h>
#include <opendavinci/odcore/io/udp/UDPReceiver.h>
#include <opendavinci/odcore/io/udp/UDPFactory.h>

#include "UDPReceivePackets.hpp"

using namespace std;

void UDPReceivePackets::nextPacket(const odcore::data::Packet &p) {
    cout << "Received a packet from " << p.getSender() << " at "
         << p.getReceived().toString() << " "
         << "with " << p.getData().length() << " bytes containing '"
         << p.getData() << "'" << endl;
}

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore;
using namespace odcore::io;
using namespace odcore::io::udp;

int32_t main(int32_t argc, char **argv) {
    const string RECEIVER = "0.0.0.0";
    const uint32_t PORT = 1234;

    // We are using OpenDaVINCI's std::shared_ptr to automatically
    // release any acquired resources.
    try {
        std::shared_ptr<UDPReceiver>
            udpreceiver(UDPFactory::createUDPReceiver(RECEIVER, PORT));

        // This instance will handle any packets that are received
        // by our UDP socket.
        UDPReceivePackets handler;
        udpreceiver->setPacketListener(&handler);

        // Start receiving bytes.
        udpreceiver->start();

        const uint32_t ONE_SECOND = 1000 * 1000;
        odcore::base::Thread::usleepFor(10 * ONE_SECOND);

        // Stop receiving bytes and unregister our handler.
        udpreceiver->stop();
        udpreceiver->setPacketListener(NULL);
    }
    catch(string &exception) {
        cerr << "Error while creating UDP receiver: " << exception << endl;
    }
}

To receive a packet from a UDP socket, your application needs to include <opendavinci/odcore/io/udp/UDPReceiver.h> and <opendavinci/odcore/io/udp/UDPFactory.h> that encapsulate the platform-specific implementations.

UDPFactory provides a static method called createUDPReceiver that allows you to receive bytes from a listening UDP socket. On success, this call will return a pointer to a UDPReceiver instance that is used to handle the data transfer. On failure, the method createUDPReceiver will throw an exception of type string with an error message.

If the UDP socket could be successfully created, we register our PacketListener at the newly created UDPReceiver to be invoked when a new packet is available. In contrast to the StringListener, the data processing for handling Packets is decoupled between the low level UDP socket and the user-supplied handler. Thus, whenever a new Packet has been received, it is enqueued and the user application is invoked from a separate thread to avoid unnecessary waiting times.

Once we have registered our PacketListener, the UDPReceiver is simply started and the main thread is falling asleep for a while in our example. After some time, the program will stop receiving bytes, unregister the PacketListener, and release the system resources.

To conveniently handle the resource management of releasing the acquired system resources, a std::shared_ptr is used that automatically releases memory that is no longer used.

Please note that once you have stopped UDPReceiver you cannot reuse it and thus, you need to create a new one.

You can compile and link the example:

g++ -std=c++11 -I /usr/include -c UDPReceivePackets.cpp -o UDPReceivePackets.o
g++ -o udpreceivepackets UDPReceivePackets.o -lopendavinci -lpthread

The resulting program can be run:

$ ./udpreceivepackets

To test the program, we pipe a string through the tool nc:

$ echo "Hello World" | nc -u 127.0.0.1 1234

Our program udpreceivepackets will print:

Received a packet from 127.0.0.1, with 13 bytes containing 'Hello World!
'

TCP connection

How to send bytes to a TCP server

OpenDaVINCI has a built-in TCP connection handling engine realized on Linux-based and BSD-based operating systems using POSIX sockets and on Windows using WinSock. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/tcpsendbytes

In order to send bytes to an existing TCP server, you will find a simple example below.

TCPSendBytes.cpp:

#include <stdint.h>
#include <iostream>
#include <string>
#include <memory>
#include <opendavinci/odcore/io/tcp/TCPConnection.h>
#include <opendavinci/odcore/io/tcp/TCPFactory.h>

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore;
using namespace odcore::io::tcp;

int32_t main(int32_t argc, char **argv) {
    const string RECEIVER = "127.0.0.1";
    const uint32_t PORT = 1234;

    // We are using OpenDaVINCI's std::shared_ptr to automatically
    // release any acquired resources.
    try {
        std::shared_ptr<TCPConnection>
            connection(TCPFactory::createTCPConnectionTo(RECEIVER, PORT));

        connection->send("Hello World\r\n");
    }
    catch(string &exception) {
        cerr << "Data could not be sent: " << exception << endl;
    }
}

To send bytes over a TCP link to a TCP server, your application needs to include <opendavinci/odcore/io/tcp/TCPConnection.h> and <opendavinci/odcore/io/tcp/TCPFactory.h> that encapsulate the platform-specific implementations.

TCPFactory provides a static method called createTCPConnectionTo that tries to connect to the specified TCP server. On success, this call will return a pointer to a TCPConnection instance that is used to handle the data transfer. On failure, the method createTCPConnectionTo will throw an exception of type string with an error message.

If the connection could be successfully established, the method send can be used to send data of type string to the other end of the TCP link.

To conveniently handle the resource management of releasing the acquired system resources, a std::shared_ptr is used that automatically releases memory that is no longer used.

You can compile and link the example:

g++ -std=c++11 -I /usr/include -c TCPSendBytes.cpp -o TCPSendBytes.o
g++ -o tcpsendbytes TCPSendBytes.o -lopendavinci -lpthread

To test the program, we create a simple TCP server awaiting connection by using the tool nc:

$ nc -l -p 1234

The resulting program can be run:

$ ./tcpsendbytes
$

The tool nc will print Hello World and then terminate as the connection is closed on exiting tcpsendbytes.

In the case of a failed connection attempt, the following exception will be printed on the console:

$ ./tcpsendbytes
Data could not be sent: [PosixTCPConnection] Error connecting to socket

How to receive bytes as a TCP server

OpenDaVINCI has a built-in TCP handling engine realized on Linux-based and BSD-based operating systems using POSIX sockets and on Windows using WinSock. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/tcpreceivebytes

In order to receive bytes as a TCP server, you will find a simple example below.

TCPReceiveBytes.hpp:

#include <opendavinci/odcore/io/ConnectionListener.h>
#include <opendavinci/odcore/io/StringListener.h>
#include <opendavinci/odcore/io/TCPAcceptorListener.h>
#include <opendavinci/odcore/io/TCPConnection.h>

// This class will handle newly accepted TCP connections.
class TCPReceiveBytes :
    public odcore::io::ConnectionListener,
    public odcore::io::StringListener,
    public odcore::io::tcp::TCPAcceptorListener {

    // Your class needs to implement the method void nextString(const std::string &s).
    virtual void nextString(const std::string &s);

    // Your class needs to implement the method void onNewConnection(std::shared_ptr<odcore::io::tcp::TCPConnection> connection).
    virtual void onNewConnection(std::shared_ptr<odcore::io::tcp::TCPConnection> connection);

    // Your class should implement the method void handleConnectionError() to handle connection errors (like terminated connections).
    virtual void handleConnectionError();
};

To receive any data, we firstly declare a class that implements the interfaces odcore::io::StringListener to receive bytes and odcore::io::tcp::TCPAcceptorListener where any newly accepted connections will be reported to. This class will be registered as listener to our accepting TCP socket that we create later. In addition, we also register a odcore::io::ConnectionLister that is invoked in the case of any connection error like the connecting client has closed the connection.

TCPReceiveBytes.cpp:

#include <stdint.h>
#include <iostream>
#include <string>
#include <memory>
#include <opendavinci/odcore/base/Thread.h>
#include <opendavinci/odcore/io/tcp/TCPAcceptor.h>
#include <opendavinci/odcore/io/tcp/TCPFactory.h>

#include "TCPReceiveBytes.hpp"

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore;
using namespace odcore::io;
using namespace odcore::io:tcp;

void TCPReceiveBytes::handleConnectionError() {
    cout << "Connection terminated." << endl;
}

void TCPReceiveBytes::nextString(const std::string &s) {
    cout << "Received " << s.length() << " bytes containing '" << s << "'" << endl;
}

void TCPReceiveBytes::onNewConnection(std::shared_ptr<odcore::io::tcp::TCPConnection> connection) {
    if (connection.isValid()) {
        cout << "Handle a new connection." << endl;

        // Set this class as StringListener to receive
        // data from this connection.
        connection->setStringListener(this);

        // Set this class also as ConnectionListener to
        // handle errors originating from this connection.
        connection->setConnectionListener(this);

        // Start receiving data from this connection.
        connection->start();

        // We keep this connection open only for one
        // second before we close it.
        const uint32_t ONE_SECOND = 1000 * 1000;
        odcore::base::Thread::usleepFor(ONE_SECOND);

        // Stop this connection.
        connection->stop();

        // Unregister the listeners.
        connection->setStringListener(NULL);
        connection->setConnectionListener(NULL);
    }
}

int32_t main(int32_t argc, char **argv) {
    const uint32_t PORT = 1234;

    // We are using OpenDaVINCI's std::shared_ptr to automatically
    // release any acquired resources.
    try {
        std::shared_ptr<TCPAcceptor>
            tcpacceptor(TCPFactory::createTCPAcceptor(PORT));

        // This instance will handle any new connections.
        TCPReceiveBytes handler;
        tcpacceptor->setAcceptorListener(&handler);

        // Start accepting new connections.
        tcpacceptor->start();

        const uint32_t ONE_SECOND = 1000 * 1000;
        odcore::base::Thread::usleepFor(10 * ONE_SECOND);

        // Stop accepting new connections and unregister our handler.
        tcpacceptor->stop();
        tcpacceptor->setAcceptorListener(NULL);
    }
    catch(string &exception) {
        cerr << "Error while creating TCP receiver: " << exception << endl;
    }
}

The outlined implementation will provide an overview of how to get notified about newly connecting clients using TCP; your application should track new connections in a vector for instance and manage their individual connection status properly.

To receive bytes from a TCP socket, your application needs to include <opendavinci/odcore/io/tcp/TCPAcceptor.h> and <opendavinci/odcore/io/tcp/TCPFactory.h> that encapsulate the platform-specific implementations.

TCPFactory provides a static method called createTCPAcceptor that allows you to accept new TCP connections. Every new connection is wrapped into a pointer to an instance of TCPConnection that needs to be handled by a TCPAcceptorListener. The task for the TCPAcceptorListener is to get the new TCPConnection, register a StringListener to receive bytes and a ConnectionListener that is called when an error for this TCP connection occurs, e.g. the client closes the connection.

TCPFactory will return a pointer to the TCPAcceptor, where our TCPReceiveBytes handler in turn is registered to handle incoming connection. On failure, the method createTCPAcceptor will throw an exception of type string with an error message.

If the TCPAcceptor could be successfully created, we register our TCPReceiveBytes to handle new connections. Afterwards, we start our TCPAcceptor to wait for incoming TCP connections. After some time, the program will stop waiting for new connections, unregister the TCPReceiveBytes, and release the system resources.

To conveniently handle the resource management of releasing the acquired system resources, a std::shared_ptr is used that automatically releases memory that is no longer used.

Please note that once you have stopped TCPAcceptor you cannot reuse it and thus, you need to create a new one.

You can compile and link the example:

g++ -std=c++11 -I /usr/include -c TCPReceiveBytes.cpp -o TCPReceiveBytes.o
g++ -o tcpreceivebytes TCPReceiveBytes.o -lopendavinci -lpthread

The resulting program can be run:

$ ./tcpreceivebytes

To test the program, we use the test program tcpsendbytes as described here https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/tcpsendbytes:

$ ./tcpsendbytes

Our program tcpreceivebytes will print:

Handle a new connection.
Received 13 bytes containing 'Hello World
'
Connection terminated.

How to design distributed software systems

How to setup the build environment for your software system

This example demonstrates how to setup your build environment.

“Hello World” example - compiling manually

Let’s assume you have implemented a basic “Hello World” application to start with as shown in the following.

HelloWorldExample.h:

#include <opendavinci/odcore/base/TimeTriggeredConferenceClientModule.h>

class HelloWorldExample : public odcore::base::module::TimeTriggeredConferenceClientModule {
    private:
        HelloWorldExample(const HelloWorldExample &/*obj*/);
        HelloWorldExample& operator=(const HelloWorldExample &/*obj*/);

    public:
        /**
         * Constructor.
         *
         * @param argc Number of command line arguments.
         * @param argv Command line arguments.
         */
        HelloWorldExample(const int32_t &argc, char **argv);

        virtual ~HelloWorldExample();

        odcore::data::dmcp::ModuleExitCodeMessage::ModuleExitCode body();

    private:
        virtual void setUp();

        virtual void tearDown();
};

The accompanying implementation for this header file is provided in the following.

HelloWorldExample.cpp:

#include <iostream>

#include "HelloWorldExample.h"

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore::base::module;

HelloWorldExample::HelloWorldExample(const int32_t &argc, char **argv) :
    TimeTriggeredConferenceClientModule(argc, argv, "HelloWorldExample")
{}

HelloWorldExample::~HelloWorldExample() {}

void HelloWorldExample::setUp() {
    cout << "This method is called before the component's body is executed." << endl;
}

void HelloWorldExample::tearDown() {
    cout << "This method is called after the program flow returns from the component's body." << endl;
}

odcore::data::dmcp::ModuleExitCodeMessage::ModuleExitCode HelloWorldExample::body() {
    cout << "Hello OpenDaVINCI World!" << endl;

    return odcore::data::dmcp::ModuleExitCodeMessage::OKAY;
}

To start the component, we define the main() function in a separate file.

HelloWorldExampleMain.cpp:

#include "HelloWorldExample.h"

int32_t main(int32_t argc, char **argv) {
    HelloWorldExample hwe(argc, argv);

    return hwe.runModule();
}

Now, we have three files, HelloWorldExample.h, HelloWorldExample.cpp, and HelloWorldExampleMain.cpp. The first two files contain the software component for the “Hello World” example, the latter file is simply starting the component.

Now, you can compile and link the example manually:

$ g++ -std=c++11 -I /usr/include -c HelloWorldExample.cpp -o HelloWorldExample.o
$ g++ -std=c++11 -I /usr/include -c HelloWorldExampleMain.cpp -o HelloWorldExampleMain.o
$ g++ -o helloworldexample HelloWorldExampleMain.o HelloWorldExample.o -lopendavinci -lpthread

“Hello World” example - compiling using CMake

Next, we are going to extend the previous example and introduce CMake as the build system. The advantage of CMake is that you describe the build of your software independent from the build tools like make, KDevelop, or the like.

We create a file named CMakeLists.txt to describe the actual source code artifacts that comprise our final binary. After the definition of the CMake version that is required (2.8) in our example, we name the project that we are building. Next, we need to declare the dependency to OpenDaVINCI (cf. (1)). As OpenDaVINCI can be either installed to /usr from the pre-compiled packages or in a specific directory, we need to adjust this possibility accordingly in the CMakeLists.txt. Therefore, OpenDaVINCI provides a CMake-script to determine the correct settings for locating OpenDaVINCI’s include headers and linker settings. This script considers the parameter OPENDAVINCI_DIR where we can specify at commandline where CMake shall search for OpenDaVINCI. If this parameter is left unset, OpenDaVINCI is assumed to be installed in your distributions default locations, like /usr or /usr/local. Next, FIND_PACKAGE(...) is called to actually find OpenDaVINCI.

If the FIND_PACKAGE(...) succeeds, the variables OPENDAVINCI_INCLUDE_DIRS and OPENDAVINCI_LIBRARIES. We are using the former to refer to the include headers so that HelloWorldExample.h can be compiled using the OpenDaVINCI classes.

Next, we specify the sources (cf. (3)) that are required to create the binary. Afterwards, we define the executable and the libraries that are required to link. The last line specifies where the resulting shall be installed to:

CMAKE_MINIMUM_REQUIRED (VERSION 2.8)

PROJECT (helloworldexample)

# Compile flags to enable C++11.
SET (CXX_OPTIONS       "-std=c++11")
SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC ${CXX_OPTIONS} -pipe")

# (1) Find OpenDaVINCI.
IF("${OPENDAVINCI_DIR}" STREQUAL "")
    SET(OPENDAVINCI_DIR "${CMAKE_INSTALL_PREFIX}")
ELSE()
    SET (CMAKE_MODULE_PATH "${OPENDAVINCI_DIR}/share/cmake-${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}/Modules" ${CMAKE_MODULE_PATH})
ENDIF()
FIND_PACKAGE (OpenDaVINCI REQUIRED)

# (2) Set header files from OpenDaVINCI.
INCLUDE_DIRECTORIES (${OPENDAVINCI_INCLUDE_DIRS})

# (3) Build the project.
SET(SOURCES HelloWorldExample.cpp HelloWorldExampleMain.cpp)
ADD_EXECUTABLE (helloworldexample ${SOURCES})
TARGET_LINK_LIBRARIES (helloworldexample ${OPENDAVINCI_LIBRARIES})

# (4) Install the binary.
INSTALL(TARGETS helloworldexample RUNTIME DESTINATION bin COMPONENT tutorials)

Having the CMakeLists.txt file enables us to create the build environment. Therefore, we first create a build folder to separate the compiled object code from the sources:

$ mkdir build
$ cd build

Next, we call CMake to create the build environment for us (make for instance):

$ cmake ..

In this case, the OpenDaVINCI libraries would be expected to reside at /usr (or /usr/local). If you have the OpenDaVINCI libraries installed at a different location, you need to call CMake using the commandline parameter OPENDAVINCI_DIR:

$ cmake -D OPENDAVINCI_DIR=<location of OpenDaVINCI include files and library> ..

The aforementioned call would result in trying to install the binary to /usr (or /usr/local). To specify a different installation folder, you need to invoke CMake as follows:

$ cmake -D OPENDAVINCI_DIR=<location of OpenDaVINCI include files and library> -D CMAKE_INSTALL_PREFIX=<location where you want to install the binaries> ..

After running cmake, we run make to build the binary:

$ make

How to design a time-triggered software component

OpenDaVINCI allows the development of distributed software components. These components can run on the same machine or on different ones using various types of communication and scheduling. Both are provided transparently to the user of the OpenDaVINCI middleware.

This example demonstrates how to design a time-triggered software component. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/timetrigger

“Hello World” example

A time-triggered software component is derived from odcore::base::TimeTriggeredConferenceClientModule, provided in <opendavinci/odcore/base/module/TimeTriggeredConferenceClientModule.h>.

TimeTriggerExample.h:

#include <opendavinci/odcore/base/TimeTriggeredConferenceClientModule.h>

class TimeTriggerExample : public odcore::base::module::TimeTriggeredConferenceClientModule {
    private:
        TimeTriggerExample(const TimeTriggerExample &/*obj*/);
        TimeTriggerExample& operator=(const TimeTriggerExample &/*obj*/);

    public:
        /**
         * Constructor.
         *
         * @param argc Number of command line arguments.
         * @param argv Command line arguments.
         */
        TimeTriggerExample(const int32_t &argc, char **argv);

        virtual ~TimeTriggerExample();

        odcore::data::dmcp::ModuleExitCodeMessage::ModuleExitCode body();

    private:
        virtual void setUp();

        virtual void tearDown();
};

The class odcore::base::module::TimeTriggeredConferenceClientModule provides three methods that need to be implemented by the user: setUp(), body(), and tearDown(). These methods reflect the basic runtime cycle of a software component: An initialization phase, followed by a time-triggered execution of an algorithm implemented in the method body(), before the cycle ends with a call to tearDown() intended to clean up any acquired resources.

In addition, this class uses a private and unimplemented copy constructor and assignment operator so that the compiler can warn if an object of this class is unintentionally copied or assigned.

The accompanying implementation for this header file is provided in the following.

TimeTriggerExample.cpp:

#include <iostream>

#include "TimeTriggerExample.h"

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore::base::module;

TimeTriggerExample::TimeTriggerExample(const int32_t &argc, char **argv) :
    TimeTriggeredConferenceClientModule(argc, argv, "TimeTriggerExample")
{}

TimeTriggerExample::~TimeTriggerExample() {}

void TimeTriggerExample::setUp() {
    cout << "This method is called before the component's body is executed." << endl;
}

void TimeTriggerExample::tearDown() {
    cout << "This method is called after the program flow returns from the component's body." << endl;
}

odcore::data::dmcp::ModuleExitCodeMessage::ModuleExitCode TimeTriggerExample::body() {
    cout << "Hello OpenDaVINCI World!" << endl;

    return odcore::data::dmcp::ModuleExitCodeMessage::OKAY;
}

int32_t main(int32_t argc, char **argv) {
    TimeTriggerExample tte(argc, argv);

    return tte.runModule();
}

Firstly, the constructor is implemented, delegating any commandline arguments to the constructor of the class TimeTriggeredConferenceClientModule to obey the design principle:

Design Principle “Single-Point-of-Truth - SPoT”: Favor a centrally maintained configuration over distributed and undocumented commandline parameters

The third parameter to the constructor of TimeTriggeredConferenceClientModule is the name of this module, which is used to structure the centrally maintained configuration file.

The implementation of the methods setUp() and tearDown() simply contain explanatory text. They are meant to be used to acquire system resources or to open peripheral components like cameras or sensors.

The main method body() is meant to be used for the implementation of the main data processing algorithm. In this example, it simply prints an explanatory message. The main method returns the return code 0 encoded as OKAY.

The main function is simply instantiating an object of the class TimeTriggerExample and runs it by calling the method runModule() that is provided from its super-classes.

You can compile and link the example:

$ g++ -std=c++11 -I /usr/include -c TimeTriggerExample.cpp -o TimeTriggerExample.o
$ g++ -o timetriggerexample TimeTriggerExample.o -lopendavinci -lpthread

To test the program, we need to run the software component life-cycle management tool odsupercomponent; details for that tool are provided in its accompanying manual page (man odsupercomponent). To use this tool it is required to provide a configuration file. As the aforementioned example application does not use any configuration data, we simply create an empty file:

$ touch configuration

If OpenDaVINCI is used on a Linux host without a network connection, the local loopback device lo needs to be configured to allow UDP multicast sessions before odsupercomponent can be started:

$ sudo ifconfig lo multicast

$ sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev lo

If you use the ip command, you need to do:

$ sudo ip link set lo multicast on

$ sudo ip route add 224.0.0.0/4 dev lo

Next, we can run the life-cycle management application odsupercomponent:

$ odsupercomponent --cid=111 --configuration=/path/to/configuration

The first parameter specifies a unique container conference session identifier from within the range [2,254]. Thus, it is possible to host several sessions on the same host.

Now, you can start the example application providing the same container conference session identifier:

$ ./timetriggerexample --cid=111

The application will print the following on the console:

This method is called before the component's body is executed.
Hello OpenDaVINCI World!
This method is called after the program flow returns from the component's body.

If the container conference session identifier is omitted, the following exception will be thrown:

terminate called after throwing an instance of 'odcore::exceptions::InvalidArgumentException'
  what():  InvalidArgumentException: Invalid number of arguments. At least a conference group id (--cid=) needed. at /home/berger/GITHUB/Mini-Smart-Vehicles/sources/OpenDaVINCI-msv/libopendavinci/src/core/base/AbstractCIDModule.cpp: 53
Aborted

If no odsupercomponent is running, the application will exit with return code 4.

Adding configuration parameters

The next example demonstrates how to specify and use configuration parameters. Therefore, the implementation of body() is changed to firstly print further information about the runtime configuration and secondly, to access configuration data.

TimeTriggerExample.cpp:

#include <iostream>

#include "TimeTriggerExample.h"

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore::base;

TimeTriggerExample::TimeTriggerExample(const int32_t &argc, char **argv) :
    TimeTriggeredConferenceClientModule(argc, argv, "TimeTriggerExample")
{}

TimeTriggerExample::~TimeTriggerExample() {}

void TimeTriggerExample::setUp() {
    cout << "This method is called before the component's body is executed." << endl;
}

void TimeTriggerExample::tearDown() {
    cout << "This method is called after the program flow returns from the component's body." << endl;
}

odcore::data::dmcp::ModuleExitCodeMessage::ModuleExitCode TimeTriggerExample::body() {
    cout << "Hello OpenDaVINCI World!" << endl;

    cout << "This is my name: " << getName() << endl;
    cout << "This is my execution frequency: " << getFrequency() << endl;
    cout << "This is my identifier: " << getIdentifier() << endl;

    cout << "  " << getKeyValueConfiguration().getValue<string>("timetriggerexample.key1") << endl;
    cout << "  " << getKeyValueConfiguration().getValue<uint32_t>("timetriggerexample.key2") << endl;
    cout << "  " << getKeyValueConfiguration().getValue<float>("timetriggerexample.key3") << endl;
    cout << "  " << getKeyValueConfiguration().getValue<string>("timetriggerexample.key4") << endl;
    cout << "  " << (getKeyValueConfiguration().getValue<bool>("timetriggerexample.key5") == 1) << endl;

    return odcore::data::dmcp::ModuleExitCodeMessage::OKAY;
}

int32_t main(int32_t argc, char **argv) {
    TimeTriggerExample tte(argc, argv);

    return tte.runModule();
}

The super-classes provide methods to get information about the runtime configuration of a software component. getName() simply returns the name as specified to the constructor TimeTriggeredConferenceClientModule. getFrequency() returns the execution frequency for the software component; its value can be adjusted by specifying the commandline parameter --freq= to the software component. The last method getIdentifier() returns a unique identifier that can be specified at commandline by using the parameter --id= to distinguish several instances of the same software component; its use is shown for the configuration parameter timetriggerexample.key4 below.

The configuration file is adjusted as follows as an example:

# This is an example configuration file.
timetriggerexample.key1 = value1
timetriggerexample.key2 = 1234
timetriggerexample.key3 = 42.32

timetriggerexample.key4 = Default
timetriggerexample:1.key4 = ValueForComponent1
timetriggerexample:2.key4 = ValueForComponent2

timetriggerexample.key5 = 1

This configuration file is parsed by odsupercomponent and used to provide component-dependent subsets from this file. The general format is:

<application name> . <key> [:<identifier>] = <value>

The application name is used to structure the content of the file; in this example, timetriggerexample specifies all parameters that are provided from odsupercomponent to our application. The application itself uses the template method getKeyValueConfiguration().getValue<T>(const string &key) to retrieve values provided in the required data type. To access the numerical value for the second key, the application would access the value as follows:

uint32_t value = getKeyValueConfiguration().getValue<uint32_t>("timetriggerexample.key2");

The object handling the application-specific key-value-configuration is case-insensitive regarding the keys; in any case, the application’s name needs to precede a key’s name.

In the configuration, a special section can be specified using the name global. preceding a set of keys. All keys with this preceding name are provided to all applications and thus, shared among them.

If the same software component needs to be used with different configuration parameters, OpenDaVINCI offers the commandline parameter --id= so that different instances of the same application can be distinguished in the configuration. In our example, the key named timetriggerexample.key4 provides different values regarding the commandline parameters. For example, if the application is started as follows:

$ ./timetriggerexample --cid=111

the following request:

cout << "  " << getKeyValueConfiguration().getValue<string>("timetriggerexample.key4") << endl;

would return the value Default. If, in contrast, the application is started by specifying the identifier 1:

$ ./timetriggerexample --cid=111 --id=2

the request would return the value ValueForComponent2.

Adding time-based algorithm triggering

The next example demonstrates how to use frequency-based algorithm execution. Therefore, the implementation of body() is changed to add a loop that is intended to be executed until the module is stopped.

TimeTriggerExample.cpp:

#include <iostream>

#include "TimeTriggerExample.h"

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore::base;

TimeTriggerExample::TimeTriggerExample(const int32_t &argc, char **argv) :
    TimeTriggeredConferenceClientModule(argc, argv, "TimeTriggerExample")
{}

TimeTriggerExample::~TimeTriggerExample() {}

void TimeTriggerExample::setUp() {
    cout << "This method is called before the component's body is executed." << endl;
}

void TimeTriggerExample::tearDown() {
    cout << "This method is called after the program flow returns from the component's body." << endl;
}

odcore::data::dmcp::ModuleExitCodeMessage::ModuleExitCode TimeTriggerExample::body() {
    cout << "Hello OpenDaVINCI World!" << endl;

    cout << "This is my name: " << getName() << endl;
    cout << "This is my execution frequency: " << getFrequency() << endl;
    cout << "This is my identifier: " << getIdentifier() << endl;

    cout << "  " << getKeyValueConfiguration().getValue<string>("timetriggerexample.key1") << endl;
    cout << "  " << getKeyValueConfiguration().getValue<uint32_t>("timetriggerexample.key2") << endl;
    cout << "  " << getKeyValueConfiguration().getValue<float>("timetriggerexample.key3") << endl;
    cout << "  " << getKeyValueConfiguration().getValue<string>("timetriggerexample.key4") << endl;
    cout << "  " << (getKeyValueConfiguration().getValue<bool>("timetriggerexample.key5") == 1) << endl;

    while (getModuleStateAndWaitForRemainingTimeInTimeslice() == odcore::data::dmcp::ModuleStateMessage::RUNNING) {
        cout << "Inside the main processing loop." << endl;
    }

    return odcore::data::dmcp::ModuleExitCodeMessage::OKAY;
}

int32_t main(int32_t argc, char **argv) {
    TimeTriggerExample tte(argc, argv);

    return tte.runModule();
}

The method getModuleStateAndWaitForRemainingTimeInTimeslice() is provided from the super-classes and enforces a specific runtime execution frequency. The frequency can be specified by the commandline parameter --freq= in Hertz. For example, running the program as follows:

$ ./timetriggerexample --cid=111 --freq=2

would print Inside the main processing loop. two times per second. Thus, the method getModuleStateAndWaitForRemainingTimeInTimeslice() calculates how much time from the current time slice has been consumed (in this case, 500ms would be available per time slice) from the algorithm in the while-loop body, and would simply sleep for the rest of the current time slice.

The program can be terminated by pressing Ctrl-C, which would result in setting the module state to not running, leaving the while-loop body, and calling the method tearDown(). Furthermore, stopping odsupercomponent would also result in stopping automatically all dependent software components.

Real-time scheduling

The standard Linux kernel can meet soft real-time requirements. For time-critical algorithms requiring hard real-time, the Linux kernel with the CONFIG_PREEMPT_RT configuration item enabled can be used. More information is available here: https://rt.wiki.kernel.org/index.php/RT_PREEMPT_HOWTO.

To run an application with real-time prioritization, it must be linked with the real-time library rt:

$ g++ -std=c++11 -I /usr/include -c TimeTriggerExample.cpp -o TimeTriggerExample.o
$ g++ -o timetriggerexample TimeTriggerExample.o -lopendavinci -lpthread -lrt

On execution, simply specify the parameter --realtime= from within the range [1,49] to enable real-time scheduling transparently. In addition, you need to run the application with superuser privileges to allow the configuration of the correct scheduling priority as follows:

$ sudo ./timetriggerexample --cid=111 --freq=10 --realtime=20 --verbose=1

The output of the application would look like:

Creating multicast UDP receiver at 225.0.0.111:12175.
Creating multicast UDP receiver at 225.0.0.111:19751.
(ClientModule) discovering supercomponent...
(ClientModule) supercomponent found at IP: 10.0.2.15, Port: 19866, managedLevel: 0
(ClientModule) connecting to supercomponent...
(DMCP-ConnectionClient) sending configuration request...IP: 10.0.2.15, Port: 19866, managedLevel: 0
(DMCP-Client) Received Configuration
timetriggerexample.key1=value1
timetriggerexample.key2=1234
timetriggerexample.key3=42.32
timetriggerexample.key4=Default
timetriggerexample.key5=1

(ClientModule) connecting to supercomponent...done - managed level: 0
This method is called before the component's body is executed.
Hello OpenDaVINCI World!
This is my name: TimeTriggerExample
This is my execution frequency: 10
This is my identifier:
  value1
  1234
  42.32
  Default
Starting next cycle at 1437420074s/101149us.
Inside the main processing loop.
Starting next cycle at 1437420074s/201230us.
Inside the main processing loop.
Starting next cycle at 1437420074s/301194us.
Inside the main processing loop.
Starting next cycle at 1437420074s/400376us.
Inside the main processing loop.
Starting next cycle at 1437420074s/501003us.
Inside the main processing loop.
Starting next cycle at 1437420074s/601151us.
Inside the main processing loop.
Starting next cycle at 1437420074s/700427us.
Inside the main processing loop.
Starting next cycle at 1437420074s/800241us.
Inside the main processing loop.
Starting next cycle at 1437420074s/900387us.
Inside the main processing loop.
Starting next cycle at 1437420075s/209us.
Inside the main processing loop.
...

Please observe that your implementation within the body() shall not allocate further memory to avoid unexpected page faults resulting in a risk to miss deadlines.

How to design a data-triggered software component

OpenDaVINCI allows the development of distributed software components. These components can run on the same machine or on different ones using various types of communication and scheduling. Both is provided transparently to the user of the OpenDaVINCI middleware.

This example demonstrates how to design a data-triggered software component. The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/datatrigger

The example consists of two components: TimeTriggeredSender that is supposed to send regularly data updates and DataTriggeredReceiver, which will be notified by any data sent via network. For further explanations about a time-triggered modules, please see: https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/timetrigger

Time-triggered sender

A time-triggered software component is derived from odcore::base::TimeTriggeredConferenceClientModule, provided in <opendavinci/odcore/base/module/TimeTriggeredConferenceClientModule.h> to realize the sending functionality.

TimeTriggeredSender.h:

#include <opendavinci/odcore/base/module/TimeTriggeredConferenceClientModule.h>

class TimeTriggeredSender : public odcore::base::module::TimeTriggeredConferenceClientModule {
    private:
        /**
         * "Forbidden" copy constructor. Goal: The compiler should warn
         * already at compile time for unwanted bugs caused by any misuse
         * of the copy constructor.
         *
         * @param obj Reference to an object of this class.
         */
        TimeTriggeredSender(const TimeTriggeredSender &/*obj*/);

        /**
         * "Forbidden" assignment operator. Goal: The compiler should warn
         * already at compile time for unwanted bugs caused by any misuse
         * of the assignment operator.
         *
         * @param obj Reference to an object of this class.
         * @return Reference to this instance.
         */
        TimeTriggeredSender& operator=(const TimeTriggeredSender &/*obj*/);

    public:
        /**
         * Constructor.
         *
         * @param argc Number of command line arguments.
         * @param argv Command line arguments.
         */
        TimeTriggeredSender(const int32_t &argc, char **argv);

        virtual ~TimeTriggeredSender();

        odcore::data::dmcp::ModuleExitCodeMessage::ModuleExitCode body();

    private:
        virtual void setUp();

        virtual void tearDown();
};

The class odcore::base::module::TimeTriggeredConferenceClientModule provides three methods that need to be implemented by the user: setUp(), body(), and tearDown(). These methods reflect the basic runtime cycle of a software component: An initialization phase, followed by a time-triggered execution of an algorithm implemented in the method body(), before the cycle ends with a call to tearDown() intended to clean up any acquired resources.

In addition, this class uses a private and unimplemented copy constructor and assignment operator so that the compiler can warn if an object of this class is unintentionally copied or assigned.

The accompanying implementation for this header file is provided in the following.

TimeTriggeredSender.cpp:

#include <iostream>

#include "opendavinci/odcore/data/TimeStamp.h"

#include "TimeTriggeredSender.h"

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore::base::module;
using namespace odcore::data;

TimeTriggeredSender::TimeTriggeredSender(const int32_t &argc, char **argv) :
    TimeTriggeredConferenceClientModule(argc, argv, "TimeTriggeredSender")
    {}

TimeTriggeredSender::~TimeTriggeredSender() {}

void TimeTriggeredSender::setUp() {
    cout << "This method is called before the component's body is executed." << endl;
}

void TimeTriggeredSender::tearDown() {
    cout << "This method is called after the program flow returns from the component's body." << endl;
}

odcore::data::dmcp::ModuleExitCodeMessage::ModuleExitCode TimeTriggeredSender::body() {
    uint32_t i = 0;
    while (getModuleStateAndWaitForRemainingTimeInTimeslice() == odcore::data::dmcp::ModuleStateMessage::RUNNING) {
        cout << "Sending " << i << "-th time stamp data...";
        TimeStamp ts(i, 2*i++);
        Container c(ts);
        getConference().send(c);
        cout << "done." << endl;
    }

    return odcore::data::dmcp::ModuleExitCodeMessage::OKAY;
}

int32_t main(int32_t argc, char **argv) {
    TimeTriggeredSender tts(argc, argv);

    return tts.runModule();
}

Firstly, the constructor is implemented, delegating any commandline arguments to the constructor of the class TimeTriggeredConferenceClientModule to obey the design principle:

Design Principle “Single-Point-of-Truth - SPoT”: Favor a centrally maintained configuration over distributed and undocumented commandline parameters

The third parameter to the constructor of TimeTriggeredConferenceClientModule is the name of this module, which is used to structure the centrally maintained configuration file.

The implementation of the methods setUp() and tearDown() simply contain explanatory text. They are meant to be used to acquire system resources or to open peripheral components like cameras or sensors.

The main method body() is meant to be used for the implementation of the main data processing algorithm. The main while-loop is executed based on the specified runtime frequency of the software component. To send data with OpenDaVINCI, it must be packed into a Container that adds additional information like type of the contained payload, the sent time point when the container left the sending software computer (for instance a sending computer), and the time point, when the container was received at the other end (e.g. another computer).

As an example, we simply send an instance of the class TimeStamp where we pass some example data to its constructor. Next, we create a Container by encapsulating object to be sent.

To finally send data with OpenDaVINCI, we use the method getConference().send(Container &c) provided for any class deriving from TimeTriggeredConferenceClientModule. The main communication principle provided with OpenDaVINCI is publish/subscribe: https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern. Depending on the command line parameters passed to odsupercomponent, the concrete communication is realized either as packets sent via UDP multicast, or via odsupercomponent acting as a central communication hub (this functionality is for instance necessary for distributed simulations). For the user application, the concrete pattern in use is transparent and our data is simply handed over to OpenDaVINCI to conduct the necessary steps by calling getConference().send(c). The main method returns the return code 0 encoded as OKAY.

The main function is simply instantiating an object of the class TimeTriggerExample and runs it by calling the method runModule() that is provided from its super-classes.

You can compile and link the example:

$ g++ -std=c++11 -I /usr/include -c TimeTriggeredSender.cpp -o TimeTriggeredSender.o
$ g++ -o timetriggeredsender TimeTriggeredSender.o -lopendavinci -lpthread

Data-triggered receiver

To receive the sent data, a data-triggered software component is derived from odcore::base::DataTriggeredConferenceClientModule, provided in <opendavinci/odcore/base/module/DataTriggeredConferenceClientModule.h> to realize the receiving functionality.

DataTriggeredReceiver.h:

#include <opendavinci/odcore/base/module/DataTriggeredConferenceClientModule.h>

class DataTriggeredReceiver : public odcore::base::module::DataTriggeredConferenceClientModule {
    private:
        /**
         * "Forbidden" copy constructor. Goal: The compiler should warn
         * already at compile time for unwanted bugs caused by any misuse
         * of the copy constructor.
         *
         * @param obj Reference to an object of this class.
         */
        DataTriggeredReceiver(const DataTriggeredReceiver &/*obj*/);

        /**
         * "Forbidden" assignment operator. Goal: The compiler should warn
         * already at compile time for unwanted bugs caused by any misuse
         * of the assignment operator.
         *
         * @param obj Reference to an object of this class.
         * @return Reference to this instance.
         */
        DataTriggeredReceiver& operator=(const DataTriggeredReceiver &/*obj*/);

    public:
        /**
         * Constructor.
         *
         * @param argc Number of command line arguments.
         * @param argv Command line arguments.
         */
        DataTriggeredReceiver(const int32_t &argc, char **argv);

        virtual ~DataTriggeredReceiver();

        virtual void nextContainer(odcore::data::Container &c);

    private:
        virtual void setUp();

        virtual void tearDown();
};

The class odcore::base::module::DataTriggeredConferenceClientModule provides three methods that need to be implemented by the user: setUp(), tearDowny(), and nextContainer(odcore::data::Container &c). These methods reflect the basic runtime cycle of a software component: An initialization phase, followed by a data-triggered execution of an algorithm implemented in the method nextContainer(), before the cycle ends with a call to tearDown() intended to clean up any acquired resources.

In addition, this class uses a private and unimplemented copy constructor and assignment operator so that the compiler can warn if an object of this class is unintentionally copied or assigned.

The accompanying implementation for this header file is provided in the following.

DataTriggeredReceiver.cpp:

#include <iostream>

#include "DataTriggeredReceiver.h"
#include "opendavinci/odcore/data/TimeStamp.h"

using namespace std;

// We add some of OpenDaVINCI's namespaces for the sake of readability.
using namespace odcore::base::module;
using namespace odcore::data;

DataTriggeredReceiver::DataTriggeredReceiver(const int32_t &argc, char **argv) :
    DataTriggeredConferenceClientModule(argc, argv, "DataTriggeredReceiver")
{}

DataTriggeredReceiver::~DataTriggeredReceiver() {}

void DataTriggeredReceiver::setUp() {
    cout << "This method is called before the component's body is executed." << endl;
}

void DataTriggeredReceiver::tearDown() {
    cout << "This method is called after the program flow returns from the component's body." << endl;
}

void DataTriggeredReceiver::nextContainer(Container &c) {
    cout << "Received container of type " << c.getDataType() <<
                              " sent at " << c.getSentTimeStamp().getYYYYMMDD_HHMMSSms() <<
                          " received at " << c.getReceivedTimeStamp().getYYYYMMDD_HHMMSSms() << endl;

    if (c.getDataType() == TimeStamp::ID()) {
        TimeStamp ts = c.getData<TimeStamp>();
        cout << "Received the following time stamp: " << ts.toString() << endl;
    }
}

int32_t main(int32_t argc, char **argv) {
    DataTriggeredReceiver dtr(argc, argv);

    return dtr.runModule();
}

Firstly, the constructor is implemented, delegating any commandline arguments to the constructor of the class DataTriggeredConferenceClientModule to obey the design principle:

Design Principle “Single-Point-of-Truth - SPoT”: Favor a centrally maintained configuration over distributed and undocumented commandline parameters

The third parameter to the constructor of DataTriggeredConferenceClientModule is the name of this module, which is used to structure the centrally maintained configuration file.

The implementation of the methods setUp() and tearDown() simply contain explanatory text. They are meant to be used to acquire system resources or to open peripheral components like cameras or sensors.

The data-triggered method nextContainer(odcore::data::Container &c) is called whenever a new Container is received. The first lines simply print some meta-information about received container like contained data type as an enum-encoded number, time stamp when the container left the sending software component, and the time stamp when it was received at our end. As we are interested in data of type TimeStamp::ID(), we are checking for that type.

Once we have received the data of interest, the content of the container is unpacked by using the template method Container::getData<T>() where we specify with T the desired type. In our case, we access its content by specifying the type TimeStamp. Finally, the values of TimeStamp are printed to stdout by using the data structure’s method toString().

The main function is simply instantiating an object of the class TimeTriggerExample and runs it by calling the method runModule() that is provided from its super-classes.

You can compile and link the example:

$ g++ -std=c++11 -I /usr/include -c DataTriggeredReceiver.cpp -o DataTriggeredReceiver.o
$ g++ -o datatriggeredreceiver DataTriggeredReceiver.o -lopendavinci -lpthread

Running the example program

To test the programs, we need to run the software component life-cycle management tool odsupercomponent; details for that tool are provided in its accompanying manual page (man odsupercomponent). To use this tool it is required to provide a configuration file. As the aforementioned example applications do not use any configuration data, we simply create an empty file:

$ touch configuration

If OpenDaVINCI is used on a Linux host without a network connection, the local loopback device lo needs to be configured to allow UDP multicast sessions before odsupercomponent can be started:

$ sudo ifconfig lo multicast

$ sudo route add -net 224.0.0.0 netmask 240.0.0.0 dev lo

If you use the ip command, you need to do:

$ sudo ip link set lo multicast on

$ sudo ip route add 224.0.0.0/4 dev lo

Next, we can run the life-cycle management application odsupercomponent:

$ odsupercomponent --cid=111 --configuration=/path/to/configuration

The first parameter specifies a unique container conference session identifier from within the range [2,254]. Thus, it is possible to host several sessions on the same host.

Now, you can start the data triggered receiver application providing the same container conference session identifier:

$ ./datatriggeredreceiver --cid=111

The application will start to print something similar to the following on the console:

Received container of type 8 sent at 2015-07-31 13:53:23.847738 received at 2015-07-31 13:53:23.848420
Received container of type 8 sent at 2015-07-31 13:53:25.849773 received at 2015-07-31 13:53:25.850541
Received container of type 8 sent at 2015-07-31 13:53:27.851393 received at 2015-07-31 13:53:27.851924
Received container of type 8 sent at 2015-07-31 13:53:29.852550 received at 2015-07-31 13:53:29.853406
Received container of type 8 sent at 2015-07-31 13:53:31.854014 received at 2015-07-31 13:53:31.854474
...

Containers of this type carry information about ModuleStatistics that are used and evaluated by odsupercomponent.

Next, we start the time triggered sender providing the same container conference session identifier:

$ ./timetriggeredsender --cid=111

The application will start to print the following on the console:

Sending 0-th time stamp data...done.
Sending 1-th time stamp data...done.
Sending 2-th time stamp data...done.
Sending 3-th time stamp data...done.
...

The data-triggered application in turn will print the following on the console:

...
Received container of type 12 sent at 2015-07-31 13:53:33.68143 received at 2015-07-31 13:53:33.68858
Received the following time stamp: 1s/0us.
Received container of type 8 sent at 2015-07-31 13:53:33.855026 received at 2015-07-31 13:53:33.855697
Received container of type 12 sent at 2015-07-31 13:53:34.67304 received at 2015-07-31 13:53:34.67797
Received the following time stamp: 2s/2us.
Received container of type 12 sent at 2015-07-31 13:53:35.68291 received at 2015-07-31 13:53:35.69396
Received the following time stamp: 3s/4us.
Received container of type 8 sent at 2015-07-31 13:53:35.856238 received at 2015-07-31 13:53:35.856762
Received container of type 12 sent at 2015-07-31 13:53:36.68194 received at 2015-07-31 13:53:36.69174
Received the following time stamp: 4s/6us.
Received container of type 12 sent at 2015-07-31 13:53:37.67420 received at 2015-07-31 13:53:37.68540
Received the following time stamp: 5s/8us.
Received container of type 8 sent at 2015-07-31 13:53:37.858281 received at 2015-07-31 13:53:37.858938
Received container of type 12 sent at 2015-07-31 13:53:38.67384 received at 2015-07-31 13:53:38.67959
Received the following time stamp: 6s/10us.
Received container of type 12 sent at 2015-07-31 13:53:39.67400 received at 2015-07-31 13:53:39.68423
Received the following time stamp: 7s/12us.
...

If the container conference session identifier is omitted, the following exception will be thrown:

terminate called after throwing an instance of 'core::exceptions::InvalidArgumentException'
  what():  InvalidArgumentException: Invalid number of arguments. At least a conference group id (--cid=) needed. at /home/berger/GITHUB/Mini-Smart-Vehicles/sources/OpenDaVINCI/libopendavinci/src/core/base/module/AbstractCIDModule.cpp: 53
Aborted

If no odsupercomponent is running, the application will exit with return code 4.

How to use simulation and visualization

This tutorial explains how to use OpenDaVINCI for simulation and visualization (Ubuntu 14.04).

Installation

OpenDaVINCI has several extended libraries for simulation and visualization. The first extended library is odsimulation-odsimtools, which contains components such as odsimvehicle, odsimcamera, and odsimirus, for simulation. The second extended library is opendavinci-odcockpit used for visualization. odcockpit supports visualization for both simulation and real systems.

The tutorial on installing precompiled OpenDaVINCI libraries (http://opendavinci.readthedocs.org/en/latest/installation.pre-compiled.html#adding-opendavinci-to-your-ubuntu-14-04-linux-distribution) does not install these two libraries by default. In order to install odsimulation-odsimtools and opendavinci-odcockpit for simulation and visualization purposes, replace Step 4 at http://opendavinci.readthedocs.org/en/latest/installation.pre-compiled.html#adding-opendavinci-to-your-ubuntu-14-04-linux-distribution:

$ sudo apt-get install opendavinci-lib opendavinci-odtools opendavinci-odsupercomponent

with:

$ sudo apt-get install opendavinci-lib opendavinci-odtools opendavinci-odsupercomponent odsimulation-odsimtools opendavinci-odcockpit

After the installation, you will find all the OpenDaVINCI binaries (including binaries for simulation and visualization: odsimvehicle, odsimcamera, odsimirus, and odcockpit) in /usr/bin/.

Running the boxparker example in simulation

The boxparker example demonstrates a self-parking algorithm in the OpenDaVINCI simulation environment. A tutorial on how to build the boxparker from sources and run it in Docker image can be found at https://github.com/se-research/OpenDaVINCI/tree/master/automotive/miniature/boxparker. This tutorial gives instructions on running boxparker in simulation without Docker. The boxparker example involves the execution of (1) odsupercomponent: the lifecycle management component of OpenDaVINCI; (2) odsimvehicle: for vehicle movement simulation; (3) odsimirus: for generating distances from obstacles in simulation; (4) odcockpit: the visualization tool; (5) boxparker: the boxparker application.

This tutorial assumes that (1) OpenDaVINCI has been installed at /opt/od/ after being built from sources; and (2) boxparker has been compiled by following https://github.com/se-research/OpenDaVINCI/tree/master/automotive/miniature/boxparker. Then the binaries of the first 4 components can be found at /opt/od/bin, while the boxparker binary should be at automotive/miniature/boxparker/build within the OpenDaVINCI source code folder.

First, download the configuration and scenario files from http://www.cse.chalmers.se/~bergerc/dit168/example.zip, including a configuration file used by odsupercomponent, and two scenario files Car1.objx and Parking_boxes.scnx. Copy these files to /opt/od/bin/ and then go to this directory:

$ cd /opt/od/bin/

Run odsupercomponent:

$ LD_LIBRARY_PATH=/opt/od/lib ./odsupercomponent --cid=111

In a new terminal, run odsimvehicle:

$ LD_LIBRARY_PATH=/opt/od/lib ./odsimvehicle --cid=111 --freq=10

In a new terminal, run odsimirus:

$ LD_LIBRARY_PATH=/opt/od/lib ./odsimirus --cid=111 --freq=10

In a new terminal, run odcockpit:

$ LD_LIBRARY_PATH=/opt/od/lib ./odcockpit --cid=111

In odcockpit window, open the BirdsEyeMap plugin. You will see a static car and its surroundings for parking.

Go to the boxparker binary directory and run boxparker in a new terminal:

$ ./boxparker --cid=111 --freq=10

The car in the BirdsEyeMap window in odcockpit will start moving and stop after it successfully finds a parking slot. Meanwhile, you can open the IrUsMap window and IrUsCharts window on the left side to observe the infrared and ultrasonic sensor data readings.

Noise model for odsimirus

OpenDaVINCI provides a noise model for the odsimirus component to test the robustness of an algorithm (e.g., lane following) in simulation. The noise model is specified in the configuration file for odsupercomponent. For instance, the following specifies the noise model for sensor 0:

  • odsimirus.sensor0.faultModel.noise = 0.1
  • odsimirus.sensor0.faultModel.skip = 0.05

The first parameter ranges from 0 to 1 and reflects an additional noise to be added from the range [-1,+1]. The implementation is at https://github.com/se-research/OpenDaVINCI/blob/master/libopendlv/src/model/PointSensor.cpp#L164.

The second parameter ranges from 0 to 1 and reflects the dropped frames/missing readings. The implementation is at https://github.com/se-research/OpenDaVINCI/blob/master/libopendlv/src/model/PointSensor.cpp#L187.

The figure below shows a visualization screenshot using the aforementioned parameters.

_images/noiseModel.png

How to read content from a zip file

OpenDaVINCI has a built-in function for decompressing zip files.

The sources for this example are available at https://github.com/se-research/OpenDaVINCI/tree/master/tutorials/readzipfile

In order to read content from a zip file, you will find a simple example below.

readzipfile.cpp:

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <opendavinci/odcore/wrapper/CompressionFactory.h>
#include <opendavinci/odcore/wrapper/DecompressedData.h>

using namespace std;

int32_t main(int32_t argc, char **argv) {
    // Read a zip file in binary form. It is originally a csv file containing sample data of a 64-layer Velodyne sensor.
    //The csv file stores the data of 10 points, including the x, y, z coordinate and intensity value of each point. All values are represented by float numbers.
    fstream fin("velodyneDataPoint.zip", ios::binary | ios::in);
    std::shared_ptr<odcore::wrapper::DecompressedData> dd = odcore::wrapper::CompressionFactory::getContents(fin);  //Decompress the zip file and use a shared pointer to point to the decompressed data
    fin.close();
    vector<string> entries = dd->getListOfEntries();
    //In this example, the decompressed data contains only one entry, which is the name of the csv file before compression
    //entries.at(0) returns the first entry
    cout<<"Number if entries: "<<entries.size()<<"; the name of the first entry: "<<entries.at(0)<<endl;
    std::shared_ptr<istream> stream = dd->getInputStreamFor(entries.at(0));//Prepare an input stream from the csv file to fill a buffer
    stringstream decompressedData;  //a buffer storing all the information of the csv file
    if(stream.get()){
        char c;
        while(stream->get(c)){
            decompressedData<<c;  //Fill the buffer
        }
    }
    //Now decompressedData contains all the data from the original csv file. Use decompressedData to extract information from the csv file
    string value;
    getline(decompressedData,value); //Skip the title row of the csv file
    vector<float> xDataV;
    vector<float> yDataV;
    vector<float> zDataV;
    vector<float> intensityV;
    while(getline(decompressedData,value)){  //Read the csv file row by row
        stringstream lineStream(value);
        string cell;
        getline(lineStream,cell,',');  //Read the x, y, z coordinate and intensity of each point
        xDataV.push_back(stof(cell));
        getline(lineStream,cell,',');
        yDataV.push_back(stof(cell));
        getline(lineStream,cell,',');
        zDataV.push_back(stof(cell));
        getline(lineStream,cell,',');
        intensityV.push_back(stof(cell));
    }
    cout<<"Number of rows read: "<<xDataV.size()<<endl;  //Display the number of points extracted from the csv file
    for(uint32_t i=0;i<10;i++){
        cout<<"x: "<<xDataV[i]<<", y: "<<yDataV[i]<<", z: "<<zDataV[i]<<", intensity: "<<intensityV[i]<<endl;  //List the x, y, z coordinate and intensity of the 10 points
    }
}

This example reads a zip file velodyneDataPoint.zip which is original a csv file velodyneDataPoint.csv before compression. The csv file contains sample data of a 64-layer Velodyne sensor. It stores the x, y, z coordinate and intensity value of 10 points. Hence, the csv file has 11 rows (one row for each point plus the title row on top) and 4 columns (x, y, z, intensity). All values are represented by float numbers.

To read content from a zip file, your application needs to include <opendavinci/odcore/wrapper/CompressionFactory.h> and <opendavinci/odcore/wrapper/DecompressedData.h> that encapsulate the platform-specific implementations.

CompressionFactory provides a static method called getContents that allows you to decompress a zip file. To conveniently handle the resource management of releasing the acquired system resources, a std::shared_ptr is used that automatically releases memory that is no longer used.

A getListOfEntries method is used to return the list of entries in the decompressed data. A zip file containing multiple files has multiple entries, one entry for each file. velodyneDataPoint.zip contains 1 entry, which is the name of the csv file before compression: velodyneDataPoint.csv, which is returned by entries.at(0) in this example. Then getInputStreamFor(entries.at(0)) prepares an input stream from the csv file to fill a buffer decompressedData that stores all the information of the csv file. decompressedData will be used to extract rows and columns from the csv file.

You can compile and link the example:

g++ -std=c++11 -I /usr/include -c readzipfile.cpp -o readzipfile.o
g++ -o readzipfile readzipfile.o -lopendavinci -lpthread

To test the program, run:

$ ./readzipfile
$

The x, y, z coordinate and intensity of the 10 points extracted from the csv file will be printed to the console:

Number of entries: 1; the name of the first entry: velodyneDataPoint.csv
Number of rows read: 89105
x: 1.24821, y: 13.424, z: -1.52942, intensity: 22
x: 0.851616, y: 14.2127, z: -1.54146, intensity: 28
x: -0.777795, y: 18.337, z: 0.287256, intensity: 102
x: -1.46898, y: 18.3918, z: 0.386122, intensity: 69
x: 0.26113, y: 14.4262, z: -1.48172, intensity: 34
x: -0.272345, y: 14.8803, z: -1.43373, intensity: 40
x: 0.38511, y: 12.197, z: -1.66423, intensity: 32
x: -0.0547113, y: 12.806, z: -1.67453, intensity: 27
x: -0.86384, y: 15.1217, z: -1.37554, intensity: 37
x: -1.45687, y: 15.7513, z: -1.34796, intensity: 44