How to Send BNO085 IMU Rotation + Translation + 9DOF via UDP to Unreal Engine Actor in Blueprint?

Hi everyone! I’m new to Unreal Engine and working on a hardware project using the ESP32 with a BNO085 IMU sensor. I’m trying to get full 9-DOF motion tracking in Unreal Engine via UDP communication.

What I Have Working:

  • I’m already sending rotation data (3DOF) from the BNO085 via quaternion, using an ESP32 and UDP over Wi-Fi.
  • The data is being received in Unreal Engine 5 using Blueprints.
  • The actor in UE5 rotates correctly based on that data (roll, pitch, yaw).

What I’m Struggling With:

  • I now want to add translation (position) data: X, Y, Z — to get full 9DOF tracking.

  • I don’t want the translation in quaternion — just as a vector (in millimeters).

  • I’m confused about:

    • How to get the position data (x, y, z) from the BNO085 and send it via UDP along with quaternion.
    • How to modify my Blueprint in Unreal Engine to apply both rotation and translation.

:package: My Setup:

  • ESP32 with BNO085 connected via I2C.
  • Sending quaternion rotation as comma-separated strings via UDP every 100ms.
  • Here’s the current C++ (Arduino) code I’m using for rotation:
    include <WiFi.h>
    include <WiFiUdp.h>
    include <Wire.h>
    include <Adafruit_BNO08x.h>
    include <sh2_SensorValue.h>
    include <math.h>

// Wi-Fi Config
const char* ssid = “test”;
const char* password = “12345678”;
const char* udpAddress = “*******7”; // PC IP
const int udpPort = 4210;

WiFiUDP udp;
Adafruit_BNO08x bno085;
sh2_SensorValue_t sensorValue;

// Wi-Fi Connect Function
void connectToWiFi() {
Serial.print(“Connecting to Wi-Fi”);
WiFi.begin(ssid, password);
int retries = 0;
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(“.”);
retries++;
if (retries > 20) {
Serial.println(“:cross_mark: WiFi Failed!”);
return;
}
}
Serial.println(“”);
Serial.print(":white_check_mark: Connected! IP: ");
Serial.println(WiFi.localIP());
}

// Quaternion to Euler (Yaw-Pitch-Roll) Conversion
void quaternionToEuler(float qx, float qy, float qz, float qw, float& roll, float& pitch, float& yaw) {
roll = atan2(2.0f * (qw * qx + qy * qz), 1.0f - 2.0f * (qx * qx + qy * qy));
pitch = asin(2.0f * (qw * qy - qz * qx));
yaw = atan2(2.0f * (qw * qz + qx * qy), 1.0f - 2.0f * (qy * qy + qz * qz));
}

// Euler (with flipped yaw) back to Quaternion
void eulerToQuaternion(float roll, float pitch, float yaw, float& qx, float& qy, float& qz, float& qw) {
float cy = cos(yaw * 0.5);
float sy = sin(yaw * 0.5);
float cp = cos(pitch * 0.5);
float sp = sin(pitch * 0.5);
float cr = cos(roll * 0.5);
float sr = sin(roll * 0.5);

qw = cr * cp * cy + sr * sp * sy;
qx = sr * cp * cy - cr * sp * sy;
qy = cr * sp * cy + sr * cp * sy;
qz = cr * cp * sy - sr * sp * cy;
}

void setup() {
Serial.begin(115200);
delay(1000);

connectToWiFi();
Wire.begin();

if (!bno085.begin_I2C()) {
Serial.println(“:cross_mark: Failed to find BNO085”);
while (1) delay(10);
}
Serial.println(“:white_check_mark: BNO085 found!”);

bno085.enableReport(SH2_ROTATION_VECTOR);
delay(100);
}

void loop() {
if (bno085.wasReset()) {
Serial.println(“:warning: BNO085 was reset. Re-enabling report.”);
bno085.enableReport(SH2_ROTATION_VECTOR);
}

if (bno085.getSensorEvent(&sensorValue) && sensorValue.sensorId == SH2_ROTATION_VECTOR) {
float qw = sensorValue.un.rotationVector.real;
float qx = sensorValue.un.rotationVector.i;
float qy = sensorValue.un.rotationVector.j;
float qz = sensorValue.un.rotationVector.k;

// Convert to Euler
float roll, pitch, yaw;
quaternionToEuler(qx, qy, qz, qw, roll, pitch, yaw);

// Invert Yaw
yaw *= -1.0f;

// Convert back to Quaternion
float qx_new, qy_new, qz_new, qw_new;
eulerToQuaternion(roll, pitch, yaw, qx_new, qy_new, qz_new, qw_new);

// Format Output
String output = String(qx_new, 5) + "," +
                String(qy_new, 5) + "," +
                String(qz_new, 5) + "," +
                String(qw_new, 5) + ",";

// Send over UDP
udp.beginPacket(udpAddress, udpPort);
udp.print(output);
udp.endPacket();

// Print to Serial
Serial.println(output);

}

delay(100);
}
My Questions:

  1. How can I enable translation (position) data from BNO085 using Arduino?
  2. What’s the best way to format and send both rotation and translation together via UDP?
  3. How should I update my UE5 Blueprints to use both SetActorRotation and SetActorLocation based on the incoming UDP string?
  4. Is the RVR Stabilized Accelerometer report the right way to get position?

Goal:

A UE5 actor that both rotates and moves according to real-world IMU motion (full 9DOF), all controlled via UDP from ESP32.

Any help, code examples, or pointers would mean a lot. :folded_hands:

Thank you!