Skip to content

Latest commit

 

History

History
838 lines (691 loc) · 29.8 KB

File metadata and controls

838 lines (691 loc) · 29.8 KB

UCOM Decoder

Important

The materials in this repository are relevant to the ucoming NavSuite 3.15 release (UCOM Version 1).

This repository contains the code to build and install the UCOM Decoder Library, as well as an example tool which can convert UCOM data from UDP stream or file to CSV file.

Third-Party Libraries

This project makes use of the following third-party open-source libraries:

  • nlohmann/json for JSON parsing
  • pybind11 for C++ / Python bindings

See the relevant sub-folder for their licences.

Clone the repository

git clone --recursive https://github.com/OxfordTechnicalSolutions/ucom-decoder.git

The repository must be cloned recursively as the UCOM decoder uses a sub-module for the JSON parser.

Build

Visual Studio

The project is set up to allow cross-platform build (Windows / Linux) using Visual Studio 2022. The Linux build would most commonly use WSL2, but it is possible to use other remote hosts or build in Linux directly.

To start, open the project folder in Visual Studio. VS should automatically detect the CMake files and make the configurations available in the menu.

Quick Start

Select the host, e.g.: Local Machine for Windows build WSL: Ubuntu for Linux build

Select the build configuration, e.g.: x64 Debug

Select the 'Startup Item', e.g.: ucom_decoder.exe

Run the selected configuration

Linux

mkdir build && cd build
cmake ..
cmake --build .

To build and install the library, replace cmake --build . with

cmake --build . --target install

If there are permission errors, re-run the above command as root with sudo.

The executable is found in the build/ucom_to_csv directory named ucom_to_csv.

UCOM to CSV command-line options

Running UCOM to CSV with no arguments produces the following output:

Usage: ucom_to_csv [options] -u <.dbu filename>
Options:
  -h                     Help - displays this message
  -f <input file>        Extract data from a file instead of UDP stream
  -m <id1 id2 id3 ...>   Message IDs to process, e.g. 0 1 3
  -c <packets>           Number of packets to process (UDP stream only)
  -t <duration>          Maximum capture duration in seconds (UDP stream only)
  -i <address>           Source IP address (UDP stream only)
  -o <output file>       Output file prefix (default is output_)
  -a                     Disable user-abort

Example Data

The example_data directory contains:

  • UCOM file, logged from an OXTS INS
  • .cfg file, showing how the unit is configured
  • mobile.dbu file (in dbu sub-directory) - contains message definitions which are parsed by the decoder. This allows the decoder flexibility to decode new messages when provided with updated definitions.

To use this data with the UCOM to CSV tool:

./ucom_to_csv -u ../../example_data/dbu/mobile.dbu -f ../../example_data/input.ucom

Python

There is a Python version of the UCOM decoder SDK.

Requirements

The code has been tested with Python 3.12. It may work with earlier versions, but if any issues with package dependencies are encountered then it is recommended to upgrade to at least Python 3.12.

Installation

  1. Create a working folder and clone the UCOM_decoder repository:
mkdir ucompy_test
cd ucompy_test
git clone https://github.com/OxfordTechnicalSolutions/ucom-decoder.git --recursive ucom_decoder
  1. Create Python virtual environment:
    NB Use the appropriate command (instead of python) to invoke the Python interpreter on your system, e.g. py, python, python3.
python -m venv venv

[Windows Command Prompt]

venv\Scripts\activate.bat

[Windows Powershell]

venv\Scripts\Activate.ps1

[Linux]

source venv/bin/activate
  1. Install the oxts.ucompy module:
python -m pip install ucom_decoder/ucom_decoder_py
  1. Test the installation:
    a. Start Python interactive shell
python

Enter the text following the prompts (>>>) line by line. You should see the output shown:

>>> from oxts.ucompy import UcomDbu
>>> u = UcomDbu("ucom_decoder/example_data/dbu/oxts.dbu")
>>> u.get_valid()
True 
>>> u.get_filename()
'ucom_decoder/example_data/dbu/oxts.dbu'
>>> quit()

b. Run the ucom_to_csv example:

ucom_decoder\ucom_decoder_py\examples\ucom_to_csv\test.bat
  1. Uninstall:
python -m pip uninstall ucompy

Tests

All tests

To run all of the automated tests, first build ucom_to_csv and then from the ucom_decoder/test/ folder, run:

Windows

.\run_tests.bat

Linux

./run_tests.sh

The automated unit tests run on the Python bindings to simultaneously test the bindings and the underlying (bound) C++ code.

There is also a test of the overall (C++) decoder functionality using automatically-generated UCOM data. A range of known values are encoded into UCOM packets. These packets are then decoded (using ucom_to_csv) and the extracted values are compared with the original values.

Unit tests

The Python unit tests can be run on their own by changing to the ucom_decoder_py/tests folder and running:

Windows

.\run_tests.bat

Linux

./run_tests.sh

Decoding UCOM

Overview

UCOM provides the facility to customise the data that is output by an OXTS INS by using user-defined messages. These user-defined messages are specified in a mobile.dbu file. For ease of explanation, hereafter the term DBU will be used to refer to .dbu files, such as oxts.dbu and mobile.dbu, or their content.

UCOM

UCOM Packet

UCOM data is sent in the form of packets that are made up of:

  • 16-byte header
  • Variable length payload
  • CRC integrity check (32-bit)

Header

  • Bytes 14-15 : Payload length in bytes

Payload

The data associated with each signal is contained in the payload as a sequence of bytes whose layout is determined by the messages defined in the DBU.

Each message contains an array of signals (named 'SignalsInMessage' in the DBU), and each signal contains a number of properties (key : value pairs). One of these properties is DataType. When decoding UCOM DataType is used to determine the size in bytes of the signal data in the payload.

Example:

[DBU excerpt]

           "SignalsInMessage": [
                {
                    "SourceID": "TIME",
                    "SignalID": "GNSST",
                    "Unit": "s",
                    "ScaleFactor": 1,
                    "Offset": 0,
                    "DataType": "S64"
                },
                {
                    "SourceID": "BNS_SDN",
                    "SignalID": "Lat",
                    "Unit": "deg",
                    "ScaleFactor": 1,
                    "Offset": 0,
                    "DataType": "F64"
                },
                {
                    "SourceID": "BNS_SDN",
                    "SignalID": "Lon",
                    "Unit": "deg",
                    "ScaleFactor": 1,
                    "Offset": 0,
                    "DataType": "F64"
                },

These three signals are each 64 bits - or 8 bytes - long (as are the vast majority of the signals), so their representation in the payload will be as follows:

A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 B2 B3 B4 B5 B6 B7 C0 C1 C2 C3 C4 C5 C6 C7 

where A0 ... A7, B0 ... B7, C0 ... C7 are the bytes representing the three messages A = GNSST, B = Lat, C = Lon above.

If message A is 8 bytes, message B is 1 byte and message C is 4 bytes:

A0 A1 A2 A3 A4 A5 A6 A7 B0 C0 C1 C2 C3

For full details of the UCOM protocol see the UCOM manual.

Decoding

The steps required to decode UCOM are:

  1. Extract message and signal information from the DBU
  2. Read UCOM data (from file, UDP etc.)
    a. Decode the header b. Decode the payload c. Calculate the CRC
  3. Repeat from 2. as required

ucom_to_csv

Description

This example code demonstrates usage of the UCOM decoder SDK to decode UCOM data and write it to .csv files.

Windows

Clone repo (see above)

Open a 'Developer Command Prompt for VS'

Navigate to the UCOM_decoder folder and then build

mkdir build && cd build
cmake ..
cmake --build . --config Release

Start capturing UCOM packets from UDP:

cd ..
mkdir test && cd test
..\build\ucom_to_csv\Release\ucom_to_csv.exe -u ..\example_data\dbu\oxts.dbu -i any

ucom_to_csv arguments

Calling ucom_to_csv.exe with no arguments (or with -h) will display the options:

Usage: ucom_to_csv [options] -u <.dbu filename>
Options:
  -h                     Help - displays this message
  -f <input file>        Extract data from a file instead of UDP stream
  -m <id1 id2 id3 ...>   Message IDs to process, e.g. 0 1 3
  -c <packets>           Number of packets to process (UDP stream only)
  -t <duration>          Maximum capture duration in seconds (UDP stream only)
  -i <address>           Source IP address (UDP stream only)
  -o <output file>       Output file prefix (default is output_)
  -a                     Disable user-abort

The -u argument is required as it specifies the DBU that is needed for decoding the UCOM data.

Usually, when capturing packets from UDP, you will specify the source IP address using the '-i' flag, e.g. '-i 192.168.1.100' will only capture packets originating from the INS with that IP address. If there is only one INS connected to your network, then you can specify 'any' and there will be no IP filtering, i.e. use '-i any'.

SDK

C++ API

class UcomDbu

Description

This class accepts a DBU filename as an argument to its constructor and parses the JSON contained in the DBU to generate collections of UcomMessages and UcomSignals.

UcomDbu(std::string filename)
Constructs a UcomDbu instance from a DBU file . The file must be valid JSON conforming to the DBU schema.
std::string get_filename()
Gets the filename of the DBU.
bool get_valid()
Gets the 'valid' status of the instance. Returns true if the DBU file was parsed without error; false otherwise.
bool message_id_exists(uint16_t message_id)
Returns true if exists in the collection of messages in the DBU; false otherwise.
std::map<uint16_t, UcomMessage>& get_messages()
Gets a collection containing all of the UCOM message definitions in the DBU, stored as key : value pairs whose key is the message ID.
std::list<uint16_t>& get_message_ids()
Gets a collection containing the message IDs of all of the UCOM message definitions in the DBU.
const UcomMessage& get_message(int id)
Gets the UcomMessage whose ID is <id>
const std::vector<ucom_signal_ptr_t> &get_signals(uint16_t message_id)
Gets a collection containing all of the signals in the UcomMessage whose ID is <message_id>.
static OxTS::Enum::BASIC_TYPE get_data_type(const std::string& data_type)
Gets the OxTS::Enum::BASIC_TYPE from the string representation of the type.

class UcomData

Description

This class decodes a UCOM packet and allows read-access to the signal values contained within. To decode a UCOM packet, a corresponding valid UcomDbu instance is required to provide the necessary signal layout information.

UcomData(const uint8_t* data, int size, UcomDbu& dbu)
Constructs a UcomData instance from the byte array passed as the <data> argument, whose length in bytes is contained in <size>. <dbu> is a reference to a valid UcomDbu instance.
static const int peek(const uint8_t* data, int max_size, bool& need_more_data)
Inspect <data> to determine if it contains a candidate UCOM packet.
Returns:
  • -1 : if no candidate found or if potential candidate found but more data is needed (need_more_data is true)
  • packet length : if candidate found
const std::string get_csv() const
Gets the signal values as a comma-separated string
std::string to_string()
Gets a string representation of the instance, consisting of message ID, message version and payload length
uint16_t get_message_id()
Gets the message ID
uint16_t get_message_version()
Gets the message version
uint8_t get_time_frame()
Gets the time frame of the 'Arbitrary Time' value
int64_t get_arbitrary_time()
Gets the 'Arbitrary Time' value
uint16_t get_payload_length()
Gets the payload length (in bytes)
size_t get_signal_count()
Gets the signal count
uint32_t get_calc_crc()
Gets the calculated CRC
bool get_valid()
Gets the 'valid' status of the signal. Returns true if the UCOM packet has been successfully decoded; false otherwise
bool get(std::string signal_id, UcomDbu& dbu, double &value)
Gets the value of the signal whose ID is <signal_id> as a double. The value is assigned to the argument <value>. Returns true if the signal value is successfully assigned; false otherwise (e.g. if the signal ID doesn't exist)

class UcomMessage

Description

Represents a UCOM message. Allows read-access to the signals contained within. Derives from json class.

UcomMessage(json message)
Constructs a UcomMessage instance from a JSON representation (usually part of a DBU)
bool is_valid()
Returns true if the UcomMessage is valid; false otherwise
int get_id()
Gets the ID of the message
std::string get_header()
Gets a comma-separated string of the names of the signals contained in the message (mainly intended for use when generating CSV output). The string is prepended with 'Time (<MessageTiming>)', representing the 'Arbitrary Time' time frame, e.g. 'Time (SDN)'
size_t get_signal_count()
Gets the number of signals contained in the message
const std::vector<ucom_signal_ptr_t> &get_signals()
Gets a collection of (smart, shared) pointers to the signals contained in the message
const ucom_signal_ptr_t get_signal(std::string id)
Gets a (smart, shared) pointer to the signal whose ID is <id>
int get_signal_index(std::string id)
Gets the zero-based index into the signals collection of the signal whose ID is <id>

class UcomSignal

Description

Represents a UcomSignal. Contains the meta-data required to decode a signal from a data stream

UcomSignal(json signal)
Constructs a UcomSignal instance from a JSON representation (usually part of a DBU)
UcomSignal(std::string signal_id, UcomSignal::SignalType signal_type)
Constructs a UcomSignal instance from the signal ID and signal type
std::string get_signal_id()
Gets the signal ID
const OxTS::Enum::BASIC_TYPE get_data_type()
Gets the signal data type

Python API

class UcomDbu

Description

This class accepts a DBU filename as an argument to its constructor and parses the JSON contained in the DBU to generate collections of UcomMessages and UcomSignals.

UcomDbu(filename: str) -> UcomDbu
Constructs a UcomDbu instance from a DBU file . The file must be valid JSON conforming to the DBU schema.
get_filename() -> str
Gets the filename of the DBU.
get_valid() -> bool
Gets the 'valid' status of the instance. Returns True if the DBU file was parsed without error; False otherwise.
message_id_exists(message_id: int) -> bool
Returns True if exists in the collection of messages in the DBU; False otherwise.
get_messages() -> dict[int, UcomMessage]
Gets a dictionary containing all of the UCOM message definitions in the DBU, stored as key : value pairs whose key is the message ID.
get_message_ids() -> list[int]
Gets a list containing the message IDs of all of the UCOM message definitions in the DBU.
get_message(id: int) -> UcomMessage
Gets the UcomMessage whose ID is <id>
get_signals(message_id: int) -> list[UcomSignal]
Gets a list containing all of the signals in the UcomMessage whose ID is <message_id>.
get_data_type(data_type: str) -> OxTS::Enum::BASIC_TYPE
Gets the OxTS::Enum::BASIC_TYPE from the string representation of the type.

class UcomData

Description

This class decodes a UCOM packet and allows read-access to the signal values contained within. To decode a UCOM packet, a corresponding valid UcomDbu instance is required to provide the necessary signal layout information.

UcomData(data: str, size: int, dbu: UcomDbu) -> UcomData
Constructs a UcomData instance from the byte array passed as the <data> argument, whose length in bytes is contained in <size>. <dbu> is a valid UcomDbu instance.
peek(data: str, max_size: int) -> tuple[int,
Inspect <data> to determine if it contains a candidate UCOM packet.
Returns:
  • [-1, False] : if no candidate found
  • [-1, True] : if potential candidate found but more data is needed
  • [packet length, False] : if candidate found
get_csv() -> str
Gets the signal values as a comma-separated string
to_string() -> str
Gets a string representation of the instance, consisting of message ID, message version and payload length
get_message_id() -> int
Gets the message ID
get_message_version() -> int
Gets the message version
get_time_frame() -> int
Gets the time frame of the 'Arbitrary Time' value
get_arbitrary_time() -> int
Gets the 'Arbitrary Time' value
get_payload_length() -> int
Gets the payload length (in bytes)
get_signal_count() -> int
Gets the signal count
get_calc_crc() -> int
Gets the calculated CRC
get_valid() -> bool
Gets the 'valid' status of the signal. Returns True if the UCOM packet has been successfully decoded; False otherwise
get(signal_id: str, dbu: UcomDbu) -> tuple[bool, float]
Gets the value of the signal whose ID is <signal_id>. Returns [True, value] if the signal ID exists and value contains valid data; [False, 0.0] otherwise

class UcomMessage

Description

Represents a UCOM message. Allows read-access to the signals contained within. Derives from json class.

UcomMessage(message: json) -> UcomMessage
Constructs a UcomMessage instance from a JSON representation (usually part of a DBU)
is_valid() -> bool
Returns True if the UcomMessage is valid; False otherwise
get_id() -> int
Gets the ID of the message
get_header() -> str
Gets a comma-separated string of the names of the signals contained in the message (mainly intended for use when generating CSV output). The string is prepended with 'Time (<MessageTiming>)', representing the 'Arbitrary Time' time frame, e.g. 'Time (SDN)'
get_signal_count() -> int
Gets the number of signals contained in the message
get_signals() -> list[UcomSignal]
Gets a list of signals contained in the message
get_signal(id: str) -> UcomSignal
Gets the signal whose ID is <id>
get_signal_index(id: str) -> int
Gets the zero-based index into the signals collection of the signal whose ID is <id>

class UcomSignal

Description

Represents a UcomSignal. Contains the meta-data required to decode a signal from a data stream

UcomSignal(signal: json) -> UcomSignal
Constructs a UcomSignal instance from a JSON representation (usually part of a DBU)
get_signal_id() -> str
Gets the signal ID
get_data_type() -> OxTS::Enum::BASIC_TYPE
Gets the signal data type

Examples

Warning

The example pictures may not reflect the current state of the messages within oxts.dbu file however the code examples should still work and you should see data reflective of the provided DBU file. If you do not, please raise an issue or see OXTS support.

C++

1. Parse DBU and display message info

#include <iostream>

// Include the header file for UcomDbu
#include "ucom\ucom_dbu.hpp"


int main()
{
    // Parse the DBU
    UcomDbu dbu("..\\..\\oxts.dbu");
    std::cout << (dbu.get_valid() ? "DBU valid" : "DBU invalid") << std::endl;

    // Display the message IDs defined in the DBU
    for (auto id : dbu.get_message_ids())
        std::cout << id << ' ';
    std::cout << std::endl;

    // Display the header of message ID 0
    std::cout << "Header: " << dbu.get_message(0).get_header() << std::endl;

}

Generates the following output:

image

2. Decoding data

#include <iostream>
#include "ucom\ucom_dbu.hpp"
#include "ucom\ucom_data.hpp"
#include "ucom\example_ucom_data.hpp"

int main(int argc, char *argv[])
{
    // Parse the DBU
    UcomDbu dbu("..\\..\\oxts.dbu");

    // Decode the data
    UcomData data(_data, sizeof(_data), dbu);
    std::cout << "UcomData: " << (data.get_valid() ? "valid" : "invalid") << std::endl;
    double value;
    std::cout << "Values: " << std::endl;
    // Iterate over the signal collection and display the values
    for (auto signal : dbu.get_message(data.get_message_id()).get_signals())
    {
        std::string signal_id = signal->get_signal_id();
        if (data.get(signal_id, dbu, value))
            std::cout << signal_id << ": " << value << '\n';
    }
}

Generates the following output:

image

Python

1. Parse DBU and display message info

from oxts.ucompy import UcomDbu

 # Parse the DBU
dbu = UcomDbu("ucom_decoder/oxts.dbu")
print("DBU valid" if dbu.get_valid() else "DBU invalid")

# Display the message IDs defined in the DBU
for  id in dbu.get_message_ids():
    print(f"{id} ")

# Display the header of message ID 0
print(f"Header: {dbu.get_message(0).get_header()}")

Generates the following output:

image

2. Decoding data

from oxts.ucompy import UcomDbu, UcomData

_data = (
    b'\x55\x4d\x00\x00\x01\xcc\xb2\x9a\xd0\x3c\x00\x00\x00\x00\xd0\x00' + 
    b'\xc4\x85\x05\x79\x00\x00\x00\x00\x34\x33\x33\x33\x33\x33\x26\x40' + 
    b'\x9a\x99\x99\x99\x99\x19\x35\x40\x9a\x99\x99\x99\x99\x19\x3f\x40' + 
    b'\xcd\xcc\xcc\xcc\xcc\x8c\x44\x40\xcd\xcc\xcc\xcc\xcc\x8c\x49\x40' + 
    b'\xcd\xcc\xcc\xcc\xcc\x8c\x4e\x40\x67\x66\x66\x66\x66\xc6\x51\x40' + 
    b'\x66\x66\x66\x66\x66\x46\x54\x40\x66\x66\x66\x66\x66\xc6\x56\x40' + 
    b'\x66\x66\x66\x66\x66\x46\x59\x40\x66\x66\x66\x66\x66\xc6\x5b\x40' + 
    b'\x66\x66\x66\x66\x66\x46\x5e\x40\x33\x33\x33\x33\x33\x63\x60\x40' + 
    b'\x33\x33\x33\x33\x33\xa3\x61\x40\x33\x33\x33\x33\x33\xe3\x62\x40' + 
    b'\x33\x33\x33\x33\x33\x23\x64\x40\x33\x33\x33\x33\x33\x63\x65\x40' + 
    b'\x33\x33\x33\x33\x33\xa3\x66\x40\x33\x33\x33\x33\x33\xe3\x67\x40' + 
    b'\x33\x33\x33\x33\x33\x23\x69\x40\x33\x33\x33\x33\x33\x63\x6a\x40' + 
    b'\x33\x33\x33\x33\x33\xa3\x6b\x40\x33\x33\x33\x33\x33\xe3\x6c\x40' + 
    b'\x33\x33\x33\x33\x33\x23\x6e\x40\x33\x33\x33\x33\x33\x63\x6f\x40' + 
    b'\xb7\xef\x80\xa4')

# Parse the DBU
dbu = UcomDbu("ucom_decoder/oxts.dbu")

# Decode the data
data = UcomData(_data, len(_data), dbu)
print(f"UcomData: {'valid' if data.get_valid() else 'invalid'}")

# double value;
print(f"Signal values in message {data.get_message_id()}: ")
# Iterate over the signal collection and display the values
for signal in dbu.get_message(data.get_message_id()).get_signals():
    signal_id = signal.get_signal_id()
    success, value = data.get(signal_id, dbu)
    print(f"{signal_id} : {value:.2f}")

Generates the following output:

image

Using UCOM decoder library for development

Visual Studio - Windows

These instructions are for Visual Studio 2022, and assume that you have already cloned and built UCOM decoder in Windows.

Create a project

  1. Open Visual Studio
  2. Create new project
  3. Select:
    - C++
    - Windows
    - Console
  4. Select the required project type, e.g. Console App, click Next
  5. Select the location and enter a project name
  6. Click Create

Set Includes and Library Dependencies

  • Right-click project folder in Solution Explorer and select Properties.
  • Select Configuration Properties | C/C++ | General and in Additional Include Directories add the paths to ucom_decoder\include and ucom_decoder\json\include.
  • Select Configuration Properties | Linker | General and in Additional Library Directories add the path to the directory that contains ucom_decoder.lib (something like build\ucom_decoder\Debug for a Debug build).
  • Select Configuration Properties | Linker | Input and in Additional Dependencies add ucom_decoder.lib.

Add the required include files, and code. For example:

#include <iostream>
#include "ucom\ucom_dbu.hpp"
#include "ucom\ucom_data.hpp"
#include "ucom\example_ucom_data.hpp"

int main()
{
    std::cout << "Ucom Decoder Example\n";

    UcomDbu dbu("..\\..\\ucom_decoder\\example_data\\dbu\\oxts.dbu");
    if (!dbu.get_valid())
    {
        std::cout << "Error getting DBU, exiting..." << std::endl;
        return 1;
    }
    UcomData data(_data, sizeof(_data), dbu);

    std::cout << "Message ID: " << data.get_message_id() << "\n";
    
    auto msg = dbu.get_message(data.get_message_id());
    double value = 0;
    for (auto signal : msg.get_signals())
    {
        std::cout << signal->get_signal_id() << ": ";
        if (data.get(signal->get_signal_id(), dbu, value))
            std::cout << std::setprecision(2) << std::fixed << value << '\n';
        else
            std::cout << "not found\n";
    }
    return 0;
}

Build and run:

image

Visual Studio Code - Linux (WSL2)

These instructions assume that you have already cloned, built and installed UCOM decoder in Linux.

Create CMakeLists.txt

cmake_minimum_required(VERSION 3.10)

project(ucom_decoder_example)

add_executable(${PROJECT_NAME} src/main.cpp)
target_include_directories(${PROJECT_NAME} 
    PRIVATE ${CMAKE_SOURCE_DIR}/../ucom_decoder/ucom_decoder/include
    PRIVATE ${CMAKE_SOURCE_DIR}/../ucom_decoder/ucom_decoder/json/include
)

target_link_libraries(${PROJECT_NAME} ucom_decoder)

Write your program code

#include <iostream>
#include "ucom/ucom_dbu.hpp"
#include "ucom/ucom_data.hpp"
#include "ucom/example_ucom_data.hpp"

int main()
{
    std::cout << "Ucom Decoder Example\n";

    UcomDbu dbu("../../ucom_decoder/example_data/dbu/oxts.dbu");
    if (!dbu.get_valid())
    {
        std::cout << "Error getting DBU, exiting..." << std::endl;
        return 1;
    }
    UcomData data(_data, sizeof(_data), dbu);

    std::cout << "Message ID: " << data.get_message_id() << "\n";
    
    auto msg = dbu.get_message(data.get_message_id());
    double value = 0;
    for (auto signal : msg.get_signals())
    {
        std::cout << signal->get_signal_id() << ": ";
        if (data.get(signal->get_signal_id(), dbu, value))
            std::cout << std::setprecision(2) << std::fixed << value << '\n';
        else
            std::cout << "not found\n";
    }
}

Configure, build and run

mkdir build && cd build
cmake ..
cmake --build .

./ucom_decoder_example

Output will be the same as for Visual Studio / Windows above.