Knowledge Base

Everything you need to know and understand to develop V2X applications.

denm-generator.py
msg = self.generate_denm()
future = self.send_request(msg)
future.add_done_callback(self.request_completed)

GNSS and IMU API Developer Guide

cube:ubx provides a modern C++ interface for accessing, decoding, and reacting to u-blox UBX messages on the cube:evk. It is designed for high-level applications that need precise GNSS, IMU, and timing data from the onboard u-blox M8U/L receiver.


1. Overview

The API wraps UBX binary messages into strongly typed C++ classes such as NavPvtView, NavEellView, NavTimeGpsView, and HnrPvtView. It integrates with gpsd and supports automatic message dispatching through the cube::ubx::Reactor.

The main components are:

  • GpsFeed – connects to a GPSD server and retrieves UBX frames
  • Reactor – dispatches UBX frames to message-specific handlers
  • MessageView<T> – lightweight wrappers around decoded UBX payloads
  • Field<T, UNIT, SCALE> – provides type-safe access with unit scaling and formatting

Together, they offer a clean, reactive model for consuming UBX messages without manual parsing.


2. Basic Example

#include <cube/ubx/field_io.hpp>
#include <cube/ubx/gps_feed.hpp>
#include <cube/ubx/messages.hpp>
#include <cube/ubx/reactor.hpp>
#include <gps.h>
#include <iostream>
int main(int argc, char** argv)
{
const char* gpsd_server = "";
const char* gpsd_port = DEFAULT_GPSD_PORT;
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " GPSD_SERVER [GPSD_PORT]" << std::endl;
return 1;
}
gpsd_server = argv[1];
if (argc > 2)
gpsd_port = argv[2];
cube::ubx::Reactor reactor;
reactor.add<cube::ubx::NavPvtView>([](const cube::ubx::NavPvtView& navpvt) {
std::cout << "UBX-NAV-PVT iTOW=" << navpvt.iTow()
<< " " << cube::ubx::io::Latitude(navpvt.lat())
<< ", " << cube::ubx::io::Longitude(navpvt.lon()) << std::endl;
});
reactor.add<cube::ubx::HnrPvtView>([](const cube::ubx::HnrPvtView& hnr) {
std::cout << "UBX-HNR-PVT speed=" << hnr.gSpeed()
<< " heading=" << hnr.headMot() << std::endl;
});
cube::ubx::GpsFeed feed(gpsd_server, gpsd_port);
while (true)
feed.fetch(reactor, std::chrono::seconds(1));
}

This program connects to GPSD, registers handlers for NAV-PVT and HNR-PVT, and prints latitude, longitude, speed, and heading once per second.

Compile it using this CMakeLists.txt alongside the main.cpp:

cmake_minimum_required(VERSION 3.16)
project(main)
set(CMAKE_CXX_STANDARD 20)
find_package(CubeUbx REQUIRED)
add_executable(${PROJECT_NAME} main.cpp)
target_link_libraries(${PROJECT_NAME} PUBLIC CubeUbx::lib)
cube:~/ubx-example$ ls
CMakeLists.txt main.cpp
cube:~/ubx-example$ mkdir build && cd build
cube:~/ubx-example/build$ cmake ..
-- The C compiler identification is GNU 11.4.0
-- The CXX compiler identification is GNU 11.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PkgConfig: /usr/bin/pkg-config (found version "0.29.2")
-- Checking for module 'libgps'
-- Found libgps, version 3.23.1
-- Configuring done
-- Generating done
-- Build files have been written to: /home/cube/ubx-example/build
cube:~/ubx-example/build$ make
[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o
[100%] Linking CXX executable main
[100%] Built target main
cube:~/ubx-example/build$ ./main 127.0.0.1
UBX-HNR-PVT speed=0.124 m/s heading=100.701°
UBX-HNR-PVT speed=0.124 m/s heading=100.701°

3. Field System

The UBX API defines a type-safe field layer that maps binary payload values to physical units.
Each field has:

  • a value type (I4, U4, etc.)
  • a unit (deg, m, mps, mps², etc.)
  • a scaling factor (e.g., 1/1000 for millimeters)
template <typename T, typename UNIT, typename SCALING = std::ratio<1>>
class Field {
public:
value_type value() const;
ScaledField<UNIT> scaled() const;
};

Example:

Field<int32_t, unit::m, std::milli> height_raw(12345);
std::cout << height_raw.scaled().value() << " m"; // 12.345 m

For readability, cube::ubx::io::Latitude() and Longitude() format degrees as N/S and E/W automatically.


4. Message Views

Each UBX message type has a corresponding View class providing access to decoded fields by name.
For example:

NavPvtView

Provides position, velocity, and accuracy data (UBX-NAV-PVT).

reactor.add<cube::ubx::NavPvtView>([](const auto& navpvt) {
std::cout << "Position: " << cube::ubx::io::Latitude(navpvt.lat())
<< ", " << cube::ubx::io::Longitude(navpvt.lon())
<< " Height: " << navpvt.height()
<< " Speed: " << navpvt.gSpeed() << std::endl;
});

HnrPvtView

High-rate navigation (IMU fused), available when high navigation rate (HNR) is enabled.

reactor.add<cube::ubx::HnrPvtView>([](const auto& hnr) {
std::cout << "Time: " << hnr.year() << "-" << (int)hnr.month()
<< "-" << (int)hnr.day() << " "
<< hnr.hour() << ":" << hnr.min() << ":" << hnr.sec()
<< " Heading: " << hnr.headMot()
<< " Speed: " << hnr.gSpeed() << std::endl;
});

Other supported message views include:

  • NavEellView – horizontal error ellipse
  • NavTimeGpsView – GPS time and leap second data
  • NavEoeView – end-of-epoch marker
  • EsfInsView – IMU inertial sensor data (accelerations and angular rates)
  • EsfMeasView – Raw, unprocessed sensor data

5. Reactor System

The cube::ubx::Reactor provides a dynamic message dispatching mechanism.

cube::ubx::Reactor reactor;
reactor.add<cube::ubx::NavEellView>([](const cube::ubx::NavEellView& eell) {
std::cout << "Error ellipse: major=" << eell.errEllipseMajor()
<< " minor=" << eell.errEllipseMinor()
<< " orient=" << eell.errEllipseOrient() << std::endl;
});

Internally, the reactor maps (MessageClass, MessageID) to the registered handler and invokes it automatically when a matching frame is fed through feed().

This makes it easy to react to specific UBX messages without manually checking IDs or parsing payloads.


6. IMU and Sensor Fusion Data

The u-blox receivers equipped with an internal IMU (such as M8U or F9R) provide inertial data through two related message families — UBX-ESF-INS and UBX-ESF-MEAS. Although both relate to motion sensing, they serve different purposes and are emitted under different conditions.

UBX-ESF-INS — Fused Inertial Navigation Data

The UBX-ESF-INS message provides fused motion and attitude information from u-blox’s internal sensor-fusion engine, which combines GNSS and IMU data. It contains already-processed angular rates and linear accelerations, along with validity flags per axis.

This message is only available once the fusion engine is active — typically after:

  • The dynamic model (e.g. automotive, pedestrian) has been selected,
  • The receiver has detected sufficient motion to initialize its sensor-fusion filter.

Before initialization, no ESF-INS frames are output. Once active, the data represents the best-estimate motion state in the navigation frame and can be used for real-time motion tracking, dead reckoning, or vehicle dynamics.

reactor.add<cube::ubx::EsfInsView>([](const cube::ubx::EsfInsView& view) {
std::cout << "UBX-ESF-INS: "
<< "iTOW=" << view.iTow().value() << " "
<< "Accel[" << view.xAccel().scaled().value() << ", "
<< view.yAccel().scaled().value() << ", "
<< view.zAccel().scaled().value() << "] m/s² "
<< "Gyro[" << view.xAngRate().scaled().value() << ", "
<< view.yAngRate().scaled().value() << ", "
<< view.zAngRate().scaled().value() << "] °/s"
<< std::endl;
});

UBX-ESF-MEAS — Raw IMU Sensor Data

The UBX-ESF-MEAS message provides the raw, unprocessed IMU readings as measured by the accelerometers and gyroscopes.
Unlike UBX-ESF-INS, it is always available — even when sensor fusion is not yet active or fully initialized.

This message allows applications to:

  • Access raw accelerations and angular rates directly,
  • Perform custom sensor fusion, filtering, or calibration,
  • Or monitor sensor health and synchronization.

Each UBX-ESF-MEAS frame contains a series of sensor measurements identified by type IDs and timestamps, such as linear acceleration on X/Y/Z or rotational rate data.

Example usage:

reactor.add<cube::ubx::EsfMeasView>([](const cube::ubx::EsfMeasView& view) {
std::cout << "UBX-ESF-MEAS: "
<< "timeTag=" << view.timeTag() << " "
<< "numMeas=" << static_cast<int>(view.numMeas()) << " "
<< "id=" << view.id() << std::endl;
for (size_t i = 0; i < view.numMeas(); ++i)
{
auto meas = view.getMeasurement(i);
std::visit([&meas](auto&& value) {
using T = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<T, std::monostate>)
{
// Skip invalid measurements
}
else if constexpr (std::is_same_v<T, cube::ubx::EsfMeasView::Acceleration>)
{
std::cout << " Accel: " << value << std::endl;
}
else if constexpr (std::is_same_v<T, cube::ubx::EsfMeasView::AngularRate>)
{
std::cout << " Gyro: " << value << std::endl;
}
else if constexpr (std::is_same_v<T, cube::ubx::EsfMeasView::Temperature>)
{
std::cout << " Temp: " << value << std::endl;
}
else if constexpr (std::is_same_v<T, cube::ubx::EsfMeasView::Speed>)
{
std::cout << " Speed: " << value << std::endl;
}
else if constexpr (std::is_same_v<T, cube::ubx::EsfMeasView::WheelTicks>)
{
std::cout << " Ticks: " << value << std::endl;
}
else if constexpr (std::is_same_v<T, cube::ubx::EsfMeasView::UnknownData>)
{
std::cout << " Unknown type " << static_cast<int>(meas.data_type)
<< ": " << value.raw_value << std::endl;
}
}, meas.data);
}
});

Choose the Correct Message

  • UBX-ESF-INS → Processed, fused motion data (only when sensor fusion is active).
  • UBX-ESF-MEAS → Raw IMU data (always available).
  • The INS output depends on successful fusion initialization, typically achieved by selecting the correct dynamic model (e.g. automotive) and driving a short distance to allow filter convergence.

7. Working with GPSD

The GpsFeed class connects to a GPSD instance on TCP port 2947 and extracts raw UBX binary frames. It is compatible with both local and remote GNSS receivers configured for UBX output.

cube::ubx::GpsFeed feed("192.168.1.20", "2947");
feed.fetch(reactor, std::chrono::seconds(1));

fetch() blocks until either a UBX frame is received or the timeout expires. Each received frame is automatically passed into the reactor for decoding and dispatch.


8. Output Formatting

The UBX API includes elegant stream operators (<<) for all unit types — degrees, meters, m/s, etc.
Example output:

UBX-NAV-PVT iTOW=603422000 48.1351° N, 11.5820° E
UBX-HNR-PVT iTOW=603423000 48.1352° N, 11.5821° E 521.2 m 1.4 m/s 178.3°

The cube::ubx::io namespace provides format helpers:

  • Latitude(field) → "48.13° N"
  • Longitude(field) → "11.58° E"

9. Available Units

UnitSymbolDescription
unit::ssseconds
unit::deg°degrees
unit::mmmeters
unit::mpsm/smeters per second
unit::mps2m/s²meters per second squared
unit::deg_per_sec°/sangular rate

10. Summary

ComponentDescription
Field<T, UNIT, SCALE>UBX binary field abstraction with scaling
ScaledField<UNIT>Floating-point version of a field
MessageView<T>Provides field access for a specific UBX message
ReactorRegisters and dispatches message handlers
GpsFeedFetches UBX frames from GPSD
io::Latitude, io::LongitudeFormat helpers for position fields
Previous
Radio