Type System

Deep dive into the type-aware data handling in HID ROS 2.

Overview

HID ROS 2 provides native support for multiple data types with automatic byte-level conversion between USB HID reports and ros2_control interfaces.

Type Conversions

Integer Types

All integer types use little-endian byte order:

// uint16 example
uint8_t bytes[2] = {0x34, 0x12};  // From USB
uint16_t value = bytes[0] | (bytes[1] << 8);  // = 0x1234 = 4660

// int16 example (two's complement)
uint8_t bytes[2] = {0xFF, 0xFF};  // From USB
int16_t value = bytes[0] | (bytes[1] << 8);  // = -1

Floating-Point Types

IEEE 754 format, little-endian:

// float32 example
uint8_t bytes[4] = {0x00, 0x00, 0x80, 0x3F};  // From USB
float value;
memcpy(&value, bytes, 4);  // = 1.0

// To ros2_control interface (always double)
double interface_value = static_cast<double>(value);

Byte Packing

Report Structure

Reports are tightly packed with no padding:

fields:
  - name: "a"
    type: "uint8"   # 1 byte
  - name: "b"
    type: "float32" # 4 bytes
  - name: "c"
    type: "int16"   # 2 bytes
# Total: 7 bytes (plus 1 byte report ID = 8 bytes)

Firmware Struct

typedef struct __attribute__((packed)) {
    uint8_t report_id;  // Offset 0
    uint8_t a;          // Offset 1
    float b;            // Offset 2 (no alignment padding!)
    int16_t c;          // Offset 6
} Report;  // Total: 8 bytes

Critical: Use __attribute__((packed)) to prevent compiler padding.

ros2_control Interface

All interfaces use double regardless of underlying type:

// State interface (read)
double value = state_interfaces_[0].get_value();

// Command interface (write)
command_interfaces_[0].set_value(3.14159);

This ensures compatibility with all controllers.

Type Safety

Schema Validation

The validator checks type consistency:

  • Valid type names

  • Reasonable array sizes

  • Report size limits

Runtime Checks

The hardware interface validates:

  • Report sizes match schema

  • Report IDs are correct

  • Conversion doesn’t overflow

Performance

Optimization

Type-aware conversion is optimized:

  • Direct memory copies for native types

  • Minimal byte manipulation

  • Zero-copy where possible

Latency

Typical latency for a 4-field report:

  • USB transfer: ~1 ms

  • Parsing: <10 μs

  • ros2_control update: ~100 μs

Total: ~1.1 ms (suitable for 1 kHz control loops)

Platform Considerations

Endianness

HID ROS 2 assumes little-endian (x86, ARM):

  • Works on most modern platforms

  • May need modification for big-endian

Alignment

Reports are packed, but modern CPUs handle unaligned access efficiently.

Floating-Point

Both firmware and ROS 2 must use IEEE 754:

  • Standard on ARM, x86

  • May differ on exotic platforms

Advanced Topics

Custom Types

For custom types, modify the hardware interface:

// Example: 24-bit signed integer
int32_t read_int24(const uint8_t* bytes) {
    int32_t value = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16);
    // Sign extend
    if (value & 0x800000) value |= 0xFF000000;
    return value;
}

Compressed Data

For bandwidth-critical applications:

  • Use smaller types (int16 instead of float32)

  • Scale values in firmware

  • Descale in controller

Example:

# Instead of float32 temperature in °C
- name: "temp"
  type: "int16"  # Temperature in 0.01°C

Firmware: report.temp = (int16_t)(temperature * 100.0f);

Controller: double temp_celsius = value / 100.0;

Debugging Type Issues

Verify Byte Order

# Monitor raw bytes
ros2 run hid_tools inspect_device --vid 0xVVVV --pid 0xPPPP

Look for pattern in bytes to verify endianness.

Check Alignment

// In firmware, verify struct size
_Static_assert(sizeof(HIDInputReport) == EXPECTED_SIZE, "Struct size mismatch");

Test Conversion

Create unit tests for type conversion:

TEST(TypeConversion, Float32) {
    uint8_t bytes[4] = {0x00, 0x00, 0x80, 0x3F};
    float result = convert_float32(bytes);
    EXPECT_FLOAT_EQ(result, 1.0f);
}

For more examples, see the test directory in hid_hardware.