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$ lsCMakeLists.txt main.cppcube:~/ubx-example$ mkdir build && cd buildcube:~/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/buildcube:~/ubx-example/build$ make[ 50%] Building CXX object CMakeFiles/main.dir/main.cpp.o[100%] Linking CXX executable main[100%] Built target maincube:~/ubx-example/build$ ./main 127.0.0.1UBX-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/1000for 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 mFor 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° EUBX-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
| Unit | Symbol | Description |
|---|---|---|
unit::s | s | seconds |
unit::deg | ° | degrees |
unit::m | m | meters |
unit::mps | m/s | meters per second |
unit::mps2 | m/s² | meters per second squared |
unit::deg_per_sec | °/s | angular rate |
10. Summary
| Component | Description |
|---|---|
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 |
Reactor | Registers and dispatches message handlers |
GpsFeed | Fetches UBX frames from GPSD |
io::Latitude, io::Longitude | Format helpers for position fields |

