Radio API Developer Guide
The Radio API provides a modern C interface for direct access to the cube:radio subsystem. It allows developers to initialize the radio, configure parameters, and transmit or receive V2X frames over both ITS-G5 / DSRC and C-V2X / LTE-V2X technologies.
1. Overview
The API abstracts the underlying hardware (Qualcomm V2X 200 Chipset, prev. Autotalks SECTON/PLUTON2) into a unified C interface that works for both technologies. All cube-radio-* tools (such as cube-radio-transmit, cube-radio-receive, and cube-radio-pcap) are built on top of this API.
With it, developers can:
✅ Initialize and attach to a radio
✅ Configure channel, power, and modulation settings
✅ Transmit and receive PDUs (frames)
✅ Query link status and CBR (Channel Busy Ratio)
The Cube Radio API unifies DSRC and C-V2X into a single C interface — ideal for direct integration, testing, and V2X application development.
2. Core API Structures
CubeRadioTechnology
tech.h
The CubeRadioTechnology enumeration identifies which V2X communication technology is currently active. It distinguishes between ITS-G5 (DSRC) and C-V2X (LTE-V2X) — the two physical layers supported by the radio. This value determines which configuration and PDU structures are used, ensuring proper message formatting and protocol handling.
typedef enum { CUBE_RADIO_UNDEFINED = 0, CUBE_RADIO_ITS_G5 = 1, CUBE_RADIO_CV2X = 2,} CubeRadioTechnology;Applications typically set this field when preparing a CubeRadioConfiguration before calling cube_radio_configure(). It also appears in status queries to indicate the currently attached radio technology.
struct CubeRadioConfiguration cfg = { .tech = CUBE_RADIO_ITS_G5, .g5 = { .channel = CUBE_RADIO_G5_CCH },};cube_radio_configure(radio, &cfg);ITS-G5 Configuration
configuration.h
The ITS-G5 configuration defines the operating channel and parameters used when the radio is running in ETSI ITS-G5 mode.
enum CubeRadioG5Channel { CUBE_RADIO_G5_CCH = 0, /* 5.90 GHz */ CUBE_RADIO_G5_SCH1 = 1, /* 5.88 GHz */ CUBE_RADIO_G5_SCH2 = 2, /* 5.89 GHz */};
struct CubeRadioG5Configuration { enum CubeRadioG5Channel channel;};The channel parameter determines the center frequency, permitted power limits, and default data rate according to the selected regulatory and safety band definitions according to ETSI EN 302 663.
Example — selecting the 5.88 GHz service channel:
struct CubeRadioConfiguration cfg = { .tech = CUBE_RADIO_ITS_G5, .g5 = { .channel = CUBE_RADIO_G5_SCH1 },};cube_radio_configure(radio, &cfg);Developers can switch channels to evaluate performance or interoperability across different ITS-G5 frequency allocations.
C-V2X Configuration
configuration.h
The C-V2X configuration defines the parameters for operation in LTE-V2X or 5G NR V2X mode. It specifies both the radio access technology (RAT) and the message family used to interpret and format transmitted frames. The message family affects the compliance of PC5 sidelink messages according to standards required in certain regions, such as ETSI ITS, ISO, or IEEE 1609.
enum CubeRadioCv2xAccessTechnology { CUBE_RADIO_CV2X_RAT_UNSPECIFIED = 0, CUBE_RADIO_CV2X_RAT_LTE = 1, CUBE_RADIO_CV2X_RAT_5GNR = 2,};
enum CubeRadioCv2xMessageFamily { CUBE_RADIO_CV2X_MESSAGE_FAMILY_NONE = 0, CUBE_RADIO_CV2X_MESSAGE_FAMILY_IEEE1609 = 1, CUBE_RADIO_CV2X_MESSAGE_FAMILY_ISO = 2, CUBE_RADIO_CV2X_MESSAGE_FAMILY_ETSI_ITS = 3, CUBE_RADIO_CV2X_MESSAGE_FAMILY_CCSA = 4,};
struct CubeRadioCv2xConfiguration { enum CubeRadioCv2xAccessTechnology rat; enum CubeRadioCv2xMessageFamily family;};Example — configuring LTE-V2X with the ETSI ITS message family:
struct CubeRadioConfiguration cfg = { .tech = CUBE_RADIO_CV2X, .cv2x = { .rat = CUBE_RADIO_CV2X_RAT_LTE, .family = CUBE_RADIO_CV2X_MESSAGE_FAMILY_ETSI_ITS, },};cube_radio_configure(radio, &cfg);This example activates the LTE PC5 radio stack with ETSI ITS-compatible message framing — suitable for cooperative ITS and CAM/DENM transmission.
Note on C-V2X Configuration
The C-V2X RRC (Radio Resource Control) defines the actual physical and link-layer parameters. You cannot change these values at runtime via this API. See /system/radio/#3-c-v2-x-configuration for details on how to modify RRC configuration files.
General Configuration
The general radio configuration structure is used to initialize and switch between the supported V2X technologies — ITS-G5 and C-V2X. It provides a single entry point for defining all technology-specific parameters before activating or reconfiguring the radio.
struct CubeRadioConfiguration { CubeRadioTechnology tech; union { struct CubeRadioG5Configuration g5; struct CubeRadioCv2xConfiguration cv2x; };};This structure is passed to cube_radio_configure() when setting up the radio.
- ´tech´ determines which radio technology is active.
- Depending on the selected ´tech´, either the g5 or cv2x configuration block must be filled.
- Once configured, the parameters are applied using cube_radio_configure().
Example — configuring ITS-G5 operation on the 5.90 GHz control channel:
struct CubeRadioConfiguration cfg = { .tech = CUBE_RADIO_ITS_G5, .g5 = { .channel = CUBE_RADIO_G5_CCH },};cube_radio_configure(radio, &cfg);Example — using C-V2X LTE operation with ETSI ITS message family:
struct CubeRadioConfiguration cfg = { .tech = CUBE_RADIO_CV2X, .cv2x = { .rat = CUBE_RADIO_CV2X_RAT_LTE, .family = CUBE_RADIO_CV2X_MESSAGE_FAMILY_ETSI_ITS, },};cube_radio_configure(radio, &cfg);Power Level
power.h
The power level structure defines the transmit power used for sending V2X frames. It uses 1/8 dBm resolution to allow fine-grained adjustment of output levels, which can be important when testing link quality, range, or regulatory limits.
struct CubeRadioPowerLevel { enum CubeRadioPowerLevelType type; /* usually CUBE_RADIO_POWERLEVEL_DBM8 */ union { int16_t dbm8; /* 1/8 dBm resolution */ };};For example, to configure a transmit power of 23 dBm:
struct CubeRadioPowerLevel level = { .type = CUBE_RADIO_POWERLEVEL_DBM8, .dbm8 = 23 * 8};Power level settings are applied automatically when a frame is transmitted through the corresponding PDU (CubeRadioG5Pdu or CubeRadioCv2xPdu). They can also be used to compare received frame power levels (RSSI) for link characterization or antenna evaluation.
ITS-G5 PDU
pdu.h
The ITS-G5 PDU (Protocol Data Unit) defines the metadata and transmission parameters for an ITS-G5 frame. Each PDU describes how a payload is sent — including source and destination MAC addresses, transmit power, data rate (MCS), and QoS priority.
struct CubeRadioG5Pdu { CubeRadioG5MacAddr src; // Source MAC address CubeRadioG5MacAddr dest; // Destination MAC address struct CubeRadioPowerLevel power_level; // TX power in 1/8 dBm struct CubeRadioG5DataRate data_rate; // PHY data rate / MCS uint8_t user_priority; // IEEE 802.11p user priority (0–7)};The data rate field uses the CubeRadioG5DataRate structure to select a predefined modulation and coding scheme (MCS) index. Alternatively, you can select a MCS by requesting a particular data rate given in 500 kbps steps.
enum CubeRadioG5DataRateType { CUBE_RADIO_G5_DATARATE_UNSPECIFIED = 0, CUBE_RADIO_G5_DATARATE_MCS_INDEX = 1, CUBE_RADIO_G5_DATARATE_500KBPS = 2,};
enum CubeRadioG5McsIndex { CUBE_RADIO_MCS_BPSK_12 = 0, // ~3 Mbps CUBE_RADIO_MCS_BPSK_34 = 1, // ~4.5 Mbps CUBE_RADIO_MCS_QPSK_12 = 2, // ~6 Mbps CUBE_RADIO_MCS_QPSK_34 = 3, // ~9 Mbps CUBE_RADIO_MCS_16QAM_12 = 4, // ~12 Mbps CUBE_RADIO_MCS_16QAM_34 = 5, // ~18 Mbps CUBE_RADIO_MCS_64QAM_23 = 6, // ~24 Mbps CUBE_RADIO_MCS_64QAM_34 = 7, // ~27 Mbps};
struct CubeRadioG5DataRate { enum CubeRadioG5DataRateType type; union { enum CubeRadioG5McsIndex mcs_index; unsigned steps_of_500kbps; // e.g., 12 = 6 Mbps };};Example — Transmitting a Frame with QPSK ½ at 20 dBm:
struct CubeRadioG5Pdu pdu = {0};memset(&pdu, 0, sizeof(pdu));
// Broadcast framememset(pdu.dest, 0xFF, sizeof(pdu.dest));
// Power and data ratepdu.power_level.type = CUBE_RADIO_POWERLEVEL_DBM8;pdu.power_level.dbm8 = 20 * 8; // 20 dBmpdu.data_rate.type = CUBE_RADIO_G5_DATARATE_MCS_INDEX;pdu.data_rate.mcs_index = CUBE_RADIO_MCS_QPSK_12; // 6 Mbpspdu.user_priority = 3; // medium-priority traffic
// Transmit with payloadconst char payload[] = "Hello ITS-G5 World!";cube_radio_transmit_g5(radio, &pdu, payload, sizeof(payload));When receiving, the PDU metadata (RSSI, MCS, etc.) is populated automatically by the radio driver and returned by cube_radio_receive_g5(). CubeRadioG5Pdu encapsulates all relevant metadata for an ITS-G5 frame, including addressing, transmit power, data rate, and priority. It allows developers to precisely control or inspect radio-level transmission parameters when sending or receiving V2X messages.
C-V2X PDU
pdu.h
The C-V2X PDU (Protocol Data Unit) defines the metadata associated with a sidelink frame in LTE-V2X or 5G NR V2X mode. It provides identifiers for source and destination Layer-2 IDs, transmit power, and priority, enabling structured and standards-compliant message exchange between nearby vehicles or infrastructure.
struct CubeRadioCv2xPdu { CubeRadioCv2xL2Id src; // Source Layer-2 ID (24-bit) CubeRadioCv2xL2Id dest; // Destination Layer-2 ID (24-bit) struct CubeRadioPowerLevel power_level; // TX power (1/8 dBm) uint8_t pppp; // Priority and packet properties};Each Layer-2 ID is represented as a 24-bit identifier (uint8_t[3]). The pppp field corresponds to the 4-bit priority and packet control field defined in the 3GPP sidelink PC5 specification.
Example — Transmitting a C-V2X Frame at 23 dBm:
struct CubeRadioCv2xPdu pdu = {0};memset(&pdu, 0, sizeof(pdu));
// Broadcast destination (0xFFFFFF)pdu.dest[0] = 0xFF;pdu.dest[1] = 0xFF;pdu.dest[2] = 0xFF;
// Transmit powerpdu.power_level.type = CUBE_RADIO_POWERLEVEL_DBM8;pdu.power_level.dbm8 = 23 * 8; // 23 dBm
// Priority (0–15)pdu.pppp = 4;
// Payloadconst char payload[] = "Hello C-V2X World!";cube_radio_transmit_cv2x(radio, &pdu, payload, sizeof(payload));When receiving a frame, the metadata (including the received power and source Layer-2 ID) is filled automatically by the radio driver and returned by cube_radio_receive_cv2x().
3. API Usage Workflow
Step 1 – Attach to the Radio
struct CubeRadio* radio = cube_radio_attach("dsrc");This opens a connection to the radio daemon (DSRC or C-V2X).
Step 2 – Configure
struct CubeRadioConfiguration cfg = { .tech = CUBE_RADIO_ITS_G5, .g5 = { .channel = CUBE_RADIO_G5_CCH },};
cube_radio_configure(radio, &cfg);Step 3 – Transmit a Frame
uint8_t payload[] = "Hello ITS-G5";
struct CubeRadioG5Pdu pdu = { 0 };pdu.data_rate.type = CUBE_RADIO_G5_DATARATE_MCS_INDEX;pdu.data_rate.mcs_index = CUBE_RADIO_MCS_11P_6MBIT;
cube_radio_transmit_g5(radio, &pdu, payload, sizeof(payload));Step 4 – Receive a Frame
uint8_t rx_buf[1500] = {0};size_t rx_len = sizeof(rx_buf);struct CubeRadioG5Pdu rx_pdu;
cube_radio_ec rc = cube_radio_receive_g5(radio, &rx_pdu, rx_buf, &rx_len);if (rc == CUBE_RADIO_SUCCESS) { printf("Received %zu bytes from %02X:%02X:%02X:%02X:%02X:%02X\n", rx_len, rx_pdu.src[0], rx_pdu.src[1], rx_pdu.src[2], rx_pdu.src[3], rx_pdu.src[4], rx_pdu.src[5]);
// Convert RSSI from 1/8 dBm units double rssi_dbm = rx_pdu.power_level.dbm8 / 8.0; printf("RSSI: %.1f dBm\n", rssi_dbm);
printf("Payload (ASCII): %.*s\n", (int)rx_len, rx_buf);} else { fprintf(stderr, "Failed to receive frame (error %d)\n", rc);}Step 5 – Fetch CBR
The Channel Busy Ratio (CBR) indicates the percentage of time the radio channel is occupied by transmissions. It is defined by ETSI for congestion control and performance monitoring ITS-G5.
struct CubeRadioCbrSample cbr;cube_radio_fetch_cbr(radio, &cbr);printf("Channel Busy Ratio: %.2f%%\n", cbr.num * 100.0 / cbr.den);Step 6 – Detach
cube_radio_detach(radio);Complete Example
The following example demonstrates the complete workflow: attach → configure → transmit → receive → monitor → detach.
#include <stdio.h>#include <string.h>#include <cube/radio/radio.h>#include <cube/radio/configuration.h>#include <cube/radio/pdu.h>#include <cube/radio/error.h>
int main(void) { cube_radio_ec rc; // Step 1: Attach to the radio struct CubeRadio* radio = cube_radio_attach("dsrc"); if (!radio) { fprintf(stderr, "Failed to attach to radio\n"); return 1; } // Step 2: Configure for ITS-G5 operation struct CubeRadioConfiguration cfg = { .tech = CUBE_RADIO_ITS_G5, .g5 = { .channel = CUBE_RADIO_G5_CCH }, }; rc = cube_radio_configure(radio, &cfg); if (rc != CUBE_RADIO_SUCCESS) { fprintf(stderr, "Configuration failed: %d\n", rc); cube_radio_detach(radio); return 1; } // Step 3: Transmit a frame uint8_t payload[] = "Hello ITS-G5 World!"; struct CubeRadioG5Pdu tx_pdu = {0}; memset(tx_pdu.dest, 0xFF, sizeof(tx_pdu.dest)); // Broadcast tx_pdu.power_level.type = CUBE_RADIO_POWERLEVEL_DBM8; tx_pdu.power_level.dbm8 = 20 * 8; // 20 dBm tx_pdu.data_rate.type = CUBE_RADIO_G5_DATARATE_MCS_INDEX; tx_pdu.data_rate.mcs_index = CUBE_RADIO_MCS_QPSK_12; // 6 Mbps tx_pdu.user_priority = 3; rc = cube_radio_transmit_g5(radio, &tx_pdu, payload, sizeof(payload)); if (rc != CUBE_RADIO_SUCCESS) { fprintf(stderr, "Transmission failed: %d\n", rc); } else { printf("Transmitted %zu bytes\n", sizeof(payload)); } // Step 4: Receive a frame uint8_t rx_buf[1500] = {0}; size_t rx_len = sizeof(rx_buf); struct CubeRadioG5Pdu rx_pdu; rc = cube_radio_receive_g5(radio, &rx_pdu, rx_buf, &rx_len); if (rc == CUBE_RADIO_SUCCESS) { printf("Received %zu bytes from %02X:%02X:%02X:%02X:%02X:%02X\n", rx_len, rx_pdu.src[0], rx_pdu.src[1], rx_pdu.src[2], rx_pdu.src[3], rx_pdu.src[4], rx_pdu.src[5]); double rssi_dbm = rx_pdu.power_level.dbm8 / 8.0; printf("RSSI: %.1f dBm\n", rssi_dbm); printf("Payload: %.*s\n", (int)rx_len, rx_buf); } // Step 5: Fetch CBR struct CubeRadioCbrSample cbr; rc = cube_radio_fetch_cbr(radio, &cbr); if (rc == CUBE_RADIO_SUCCESS) { printf("Channel Busy Ratio: %.2f%%\n", cbr.num * 100.0 / cbr.den); } // Step 6: Detach cube_radio_detach(radio); printf("Radio detached\n"); return 0;}Compile it using this CMakeLists.txt alongside the main.cpp:
cmake_minimum_required(VERSION 3.16)project(main)find_package(CubeRadio REQUIRED)add_executable(${PROJECT_NAME} main.c)target_link_libraries(${PROJECT_NAME} PUBLIC CubeRadio::user)cube:~/radio-example$ lsCMakeLists.txt main.ccube:~/radio-example$ mkdir build && cd buildcube:~/radio-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-- Configuring done-- Generating done-- Build files have been written to: /home/cube/radio-example/buildcube:~/radio-example/build$ make[ 50%] Building C object CMakeFiles/main.dir/main.c.o[100%] Linking C executable main[100%] Built target maincube:~/radio-example/build$ ./main Transmitted 20 bytesRadio detached4. Example: C-V2X Transmission
struct CubeRadio* radio = cube_radio_attach("cv2x");
struct CubeRadioConfiguration cfg = { .tech = CUBE_RADIO_CV2X, .cv2x = { .rat = CUBE_RADIO_CV2X_RAT_LTE, .family = CUBE_RADIO_CV2X_MESSAGE_FAMILY_ETSI_ITS, },};
cube_radio_configure(radio, &cfg);
uint8_t payload[] = "Hello C-V2X";struct CubeRadioCv2xPdu pdu = { 0 };
cube_radio_transmit_cv2x(radio, &pdu, payload, sizeof(payload));
cube_radio_detach(radio);This example transmits a raw payload using the C-V2X sidelink. For performance or compliance testing, parameters such as the RAT and family can be changed — but the RRC file must define the actual radio configuration.
5. Status and Polling
The Cube Radio API provides mechanisms to query the current radio link state and to poll for events such as received frames, CBR updates, or transmit readiness. These functions are essential for building responsive applications that process radio activity without blocking.
struct CubeRadioStatus { CubeRadioTechnology technology; // ITS-G5 or C-V2X bool command_link; // Control link active bool data_link; // Data socket connected bool cbr_link; // CBR (Channel Busy Ratio) socket connected};Query the current status using cube_radio_status():
struct CubeRadioStatus status;cube_radio_ec rc = cube_radio_status(radio, &status);
if (rc == CUBE_RADIO_SUCCESS) { printf("Active technology: %s\n", (status.technology == CUBE_RADIO_ITS_G5) ? "ITS-G5" : (status.technology == CUBE_RADIO_CV2X) ? "C-V2X" : "Undefined");
printf("Command link: %s\n", status.command_link ? "up" : "down"); printf("Data link: %s\n", status.data_link ? "up" : "down"); printf("CBR link: %s\n", status.cbr_link ? "up" : "down");} else { fprintf(stderr, "Failed to get radio status (error %d)\n", rc);}Applications can use cube_radio_poll() to wait for new data, transmit readiness, or CBR updates. This function allows users to easily realize loops processing radio events.
Available polling flags:
#define CUBE_RADIO_POLL_TX 0x01 // Transmission ready#define CUBE_RADIO_POLL_RX 0x02 // Data received#define CUBE_RADIO_POLL_CBR 0x04 // CBR data availableExample — wait for new data or a CBR sample:
int events = 0;cube_radio_ec rc = cube_radio_poll(radio, &events, 1000); // 1000 ms timeout
if (rc == CUBE_RADIO_SUCCESS) { if (events & CUBE_RADIO_POLL_RX) printf("Incoming frame detected\n"); if (events & CUBE_RADIO_POLL_CBR) printf("New CBR sample available\n"); if (events & CUBE_RADIO_POLL_TX) printf("Radio ready for transmission\n");} else if (rc == CUBE_RADIO_EAGAIN) { printf("No events detected within timeout\n");} else { fprintf(stderr, "Polling failed (error %d)\n", rc);}The polling interface allows event-driven designs to react immediately to received frames or radio state changes — without relying on busy-wait loops or delays.
6. Error Handling
error.h
The Cube Radio API functions return standardized error codes to indicate operation results or failure reasons. These codes are defined in the CubeRadioErrorCodes enumeration and used consistently across all API calls.
enum CubeRadioErrorCodes { CUBE_RADIO_SUCCESS = 0, // Operation completed successfully
CUBE_RADIO_EUNSUPPORTED, // Feature or operation not supported by this radio CUBE_RADIO_EINVALID, // Invalid argument or configuration CUBE_RADIO_EAGAIN, // Temporary condition, retry later (non-blocking) CUBE_RADIO_EBUFSIZE, // Provided buffer too small for operation CUBE_RADIO_ESOCKET, // Low-level socket I/O error CUBE_RADIO_EPEER, // Peer behaved unexpectedly or sent invalid data CUBE_RADIO_EPROTO, // Protocol version or structure mismatch CUBE_RADIO_EBUSY, // Radio or resource currently busy CUBE_RADIO_ECALLBACK, // Callback reference invalid or failed CUBE_RADIO_EPOLL, // Polling mechanism (epoll/select) failure CUBE_RADIO_ETHREAD, // Thread or concurrency error CUBE_RADIO_EINTERNAL, // Internal radio stack or firmware error};Typical Usage Example:
cube_radio_ec rc = cube_radio_transmit_g5(radio, &pdu, payload, len);if (rc != CUBE_RADIO_SUCCESS) { fprintf(stderr, "Transmission failed: error code %d\n", rc);
switch (rc) { case CUBE_RADIO_EBUSY: fprintf(stderr, "Radio is busy — retry later.\n"); break; case CUBE_RADIO_EINVALID: fprintf(stderr, "Invalid configuration or argument.\n"); break; case CUBE_RADIO_EUNSUPPORTED: fprintf(stderr, "Requested feature is not supported.\n"); break; default: fprintf(stderr, "Unknown or internal error occurred.\n"); break; }}Developers should always check return values and handle recoverable conditions (CUBE_RADIO_EAGAIN, CUBE_RADIO_EBUSY) gracefully. Errors such as CUBE_RADIO_EPROTO or CUBE_RADIO_EINTERNAL typically indicate configuration mismatches or hardware-level problems.
All Cube Radio API functions return a CubeRadioErrorCodes value. A return code of CUBE_RADIO_SUCCESS means the call succeeded, while any other code indicates a recoverable or fatal condition that should be handled appropriately.
7. Summary
| Function | Description |
|---|---|
cube_radio_attach() | Connect to the radio interface |
cube_radio_configure() | Apply radio configuration |
cube_radio_transmit_g5() / cube_radio_transmit_cv2x() | Send frame |
cube_radio_receive_g5() / cube_radio_receive_cv2x() | Receive frame |
cube_radio_fetch_cbr() | Get CBR measurement |
cube_radio_status() | Query link and tech state |
cube_radio_detach() | Close and clean up |
The Cube Radio API provides a unified, C-based control layer for ITS-G5 and C-V2X radios, including configuration, transmission, reception, and monitoring capabilities.

