New version of the 3DOF Motion plugin is available (select "Check For Updates" to get the latest software), which lets you get motion data from shared memory by name in addition to the current UDP stream.
Here is a Python script using shared memory to get the motion data, which is easy to port to any language (C++, C#, Java, etc.):
import ctypes
from ctypes import wintypes
import time
import math
# Windows API Constants
FILE_MAP_READ = 0x04
ERROR_FILE_NOT_FOUND = 2
k32 = ctypes.windll.kernel32
# --- Kernel32 Function Declarations
k32.OpenFileMappingA.restype = wintypes.HANDLE
k32.OpenFileMappingA.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.LPCSTR]
k32.MapViewOfFile.restype = ctypes.c_void_p
k32.MapViewOfFile.argtypes = [wintypes.HANDLE, wintypes.DWORD, wintypes.DWORD, wintypes.DWORD, ctypes.c_size_t]
k32.UnmapViewOfFile.restype = wintypes.BOOL
k32.UnmapViewOfFile.argtypes = [ctypes.c_void_p]
k32.CloseHandle.restype = wintypes.BOOL
k32.CloseHandle.argtypes = [wintypes.HANDLE]
# ctypes structure definitions
class SimState(ctypes.Structure):
_pack_ = 1
_fields_ = [
("pitch", ctypes.c_double),
("yaw", ctypes.c_double),
("roll", ctypes.c_double),
("accX", ctypes.c_double),
("accY", ctypes.c_double),
("accZ", ctypes.c_double),
("pitchRate", ctypes.c_double),
("yawRate", ctypes.c_double),
("rollRate", ctypes.c_double),
("gLat", ctypes.c_double),
("gVert", ctypes.c_double),
("gLong", ctypes.c_double),
]
class SuspensionState(ctypes.Structure):
_pack_ = 1
_fields_ = [
("suspFL", ctypes.c_float),
("suspFR", ctypes.c_float),
("suspRL", ctypes.c_float),
("suspRR", ctypes.c_float),
]
class MotionPacket(ctypes.Structure):
_pack_ = 1
_fields_ = [
("packetId", ctypes.c_uint32),
("paused", ctypes.c_uint8),
("stroke", ctypes.c_uint8),
("posFL", ctypes.c_float),
("posFR", ctypes.c_float),
("posRL", ctypes.c_float),
("posRR", ctypes.c_float),
("TL", ctypes.c_float),
("rawData", SimState),
("rawSuspData", SuspensionState),
]
# Verify size matches expected 138 bytes
assert ctypes.sizeof(MotionPacket) == 138, \
f"Struct size mismatch: got {ctypes.sizeof(MotionPacket)}, expected 138"
# Reading
def read_packet(view: int) -> MotionPacket:
"""Read one packet from the mapped shared memory view."""
pkt = MotionPacket.from_address(view)
return pkt
def fmt_deg(rad: float) -> str:
return f"{math.degrees(rad):+8.2f}°"
def print_packet(pkt):
print(f" packetId: {pkt.packetId} paused: {pkt.paused} "
f"stroke: {pkt.stroke}mm")
print(f" Actuators (mm):")
print(f" A6 (FL): {pkt.posFL:8.3f} A1 (FR): {pkt.posFR:8.3f}")
print(f" A5 (RL): {pkt.posRL:8.3f} A2 (RR): {pkt.posRR:8.3f}")
print(f" TL: {pkt.TL:8.3f}")
print(f" Orientation (rad / deg):")
print(f" pitch: {pkt.rawData.pitch:+.5f} {fmt_deg(pkt.rawData.pitch)}")
print(f" yaw: {pkt.rawData.yaw:+.5f} {fmt_deg(pkt.rawData.yaw)}")
print(f" roll: {pkt.rawData.roll:+.5f} {fmt_deg(pkt.rawData.roll)}")
print(f" Acceleration (m/s²):")
print(f" X: {pkt.rawData.accX:+7.3f} Y: {pkt.rawData.accY:+7.3f} Z: {pkt.rawData.accZ:+7.3f}")
print(f" Rates (rad/s):")
print(f" pitch: {pkt.rawData.pitchRate:+7.4f} "
f"yaw: {pkt.rawData.yawRate:+7.4f} "
f"roll: {pkt.rawData.rollRate:+7.4f}")
print(f" G-forces:")
print(f" lat: {pkt.rawData.gLat:+6.3f} "
f"vert: {pkt.rawData.gVert:+6.3f} "
f"long: {pkt.rawData.gLong:+6.3f}")
print(f" Suspension (mm):")
print(f" FL: {pkt.rawSuspData.suspFL:7.2f} FR: {pkt.rawSuspData.suspFR:7.2f}")
print(f" RL: {pkt.rawSuspData.suspRL:7.2f} RR: {pkt.rawSuspData.suspRR:7.2f}")
print("CTRL+C to quit.")
# Main
def main():
shared_mem_name = "Local\\osr3dofmotiondata"
print(f"Waiting for {shared_mem_name}...")
while True:
# Open existing mapping
h_map = k32.OpenFileMappingW(FILE_MAP_READ, False, shared_mem_name)
if not h_map:
if ctypes.get_last_error() == ERROR_FILE_NOT_FOUND:
time.sleep(1)
continue
else:
print(f"Error opening mapping: {ctypes.get_last_error()}")
return
# Map the view
ptr = k32.MapViewOfFile(h_map, FILE_MAP_READ, 0, 0, 0)
if not ptr:
k32.CloseHandle(h_map)
return
print("Connected. Reading data...")
rate = 30 # ~30 Hz display refresh
print('\x1b[2J\x1b[H', end='')
try:
# Map the pointer to the structure
while True:
# Get the snapshot
data = MotionPacket.from_buffer_copy(ctypes.string_at(ptr, ctypes.sizeof(MotionPacket)))
print('\x1b[H', end='')
print_packet(data)
time.sleep(1.0 / rate)
except KeyboardInterrupt:
print("\nStopping...")
finally:
k32.UnmapViewOfFile(ptr)
k32.CloseHandle(h_map)
break
if __name__ == "__main__":
main()