How to use the SDK
The pico motion-capture host fuses data from the G2-L gloves and tracker strap locators (full body) into a unified MocapFrame (1,860 bytes), then sends it to your computer over UDP. The SDK packages on this page receive, parse, display, and support secondary development with this frame.
There are four packages. Choose one for your machine and programming language:
| Your setup | Package |
|---|---|
| Windows computer | G2L_Mocap_SDK_1.0.0_windows_x86_64.zip |
| Linux PC / x86 industrial computer | G2L_Mocap_SDK_1.0.0_linux_x86_64.tar.gz |
| ARM64 board (Raspberry Pi, Jetson, RK series, etc.) | G2L_Mocap_SDK_1.0.0_linux_aarch64.tar.gz |
| Python only, without compiling | G2L_Mocap_SDK_Python_1.0.0.zip |
The first three are C++ SDKs containing headers, dynamic/static libraries, and ready-to-run programs. The Python package is a pure-Python, cross-platform implementation; the same wheel works on all three platforms.
- Write no code yet. Run the included program to confirm that data arrives and the visualization moves.
- After that works, connect the library to your own application and continuously read 64 joint poses.
Choose what you need
| Your situation | Go to |
|---|---|
| You have just unpacked the SDK and do not know the files | Files in each package |
| You want to verify the data visually before writing code | Step 1: view the motion |
| You want to inspect joint values in a terminal | Step 2: terminal data |
| You want to record and replay data | Recording and playback |
| You want to integrate the data into your application | Integrating into your application |
| You want to understand the received values | Data format |
| It does not run or no data arrives | Troubleshooting |
Extracting the packages
Windows
Right-click and extract in File Explorer, or use:
Expand-Archive -Path "G2L_Mocap_SDK_1.0.0_windows_x86_64.zip" -DestinationPath .The result is one directory, Forsense_Mocap_SDK_1.0.0_windows_x86_64, containing bin, include, lib, demo, and README.md directly inside it.
Linux
tar -xzf G2L_Mocap_SDK_1.0.0_linux_x86_64.tar.gz # x86_64
tar -xzf G2L_Mocap_SDK_1.0.0_linux_aarch64.tar.gz # ARM64The extracted directory is Forsense_Mocap_SDK_1.0.0_linux_x86_64 (note that it differs from the archive name). Paths below are relative to this directory.
Python
# Extract G2L_Mocap_SDK_Python_1.0.0.zip
# Enter the extracted dist directory. Windows "Extract All" may add another
# same-named directory; use the level containing the wheel below.
pip install forsense_mocap-1.0.0-py3-none-any.whlPython 3.8 or later is required, with no third-party dependencies. Installation is mandatory for the Python menu and forsense-recv; otherwise, Python reports No module named 'forsense_mocap'.
Files in each package
C++ SDK (same structure on Windows, Linux x86_64, and Linux aarch64)
Forsense_Mocap_SDK_1.0.0_linux_x86_64/
├── README.md In-package guide
├── include/forsense/ Public headers for integration
│ ├── protocol/ Frame format and MocapFrame definition
│ ├── codec/ Decoder.h / Encoder.h
│ ├── adapter/ UDP / BLE / WebSocket / LocalSocket / Binary adapters
│ ├── types/ Joint enums and Pose types
│ ├── pose_vizer/ Skeleton-pose WebSocket interface
│ └── tui/ Terminal UI tools
├── lib/
│ ├── linux/x86_64/ SDK shared libraries, Qt runtime, and CMake package
│ ├── linux/aarch64/ Same for ARM64
│ └── windows/x86_64/ Static import libraries (*.lib) and CMake package
├── bin/ Ready-to-run programs with runtime paths configured
└── demo/
├── README.md Example guide
├── docs/ Data-format and integration documentation
└── examples/ Example programs| File | How to use it |
|---|---|
bin/forsense_mocap_viewer | Desktop window listing 64 joint poses. The Windows build currently lacks the Qt platform plugin and cannot start. |
bin/forsense_bin_recorder | Records received UDP frames to a .bin file. |
bin/forsense_bin_player | Replays a .bin recording. |
bin/forsense_mocap_pose_vizer | Forwards MocapFrame data to the PoseVizer visualization service. |
demo/examples/recv_mocap_udp.cpp | Self-contained C++ example using protocol headers and native sockets only; no Qt, SDK library, or CMake required. |
demo/examples/compile.sh | Compiles the C++ example into recv_mocap_udp. |
demo/examples/recv_mocap_udp | Prebuilt example included in Linux packages. |
demo/examples/visual / visual.exe | Local visualization service with an HTTP server, Three.js page, and FBX model. Start this before viewing motion. |
demo/examples/udp_receiver.py | Data bridge: receives UDP, converts coordinates, and reports them to visual. It does not draw by itself. |
demo/docs/DataFormat.md | Layout of the 1,860-byte frame and CRC calculation. |
demo/docs/Integration.md | C++ integration guide. |
include/ + lib/ | The two parts needed for secondary development. |
bin/*_adapter.dll (Windows) | SDK runtime libraries required by programs in bin; do not delete them. |
Python SDK
G2L_Mocap_SDK_Python_1.0.0/
├── README.md In-package guide
├── dist/forsense_mocap-1.0.0-py3-none-any.whl Cross-platform wheel
└── demo/
├── recv_mocap_udp.py Interactive menu example
├── udp_receiver.py Drives the web visualization
├── docs/DataFormat.md Data format
└── visual / visual_linux_x86_64 / visual_linux_arrch64 / visual.exe| File | How to use it |
|---|---|
dist/*.whl | Install this before running the Python menu. Otherwise, Python reports No module named 'forsense_mocap'. Installation is described under Step 2. |
demo/recv_mocap_udp.py | Interactive menu. The in-package script fails in option 3 (Diagnostics); replace it with the corrected recv_mocap_udp.py provided here. |
demo/udp_receiver.py | Works with visual to display the model in a web page. |
demo/visual* | Local visualization service. Run it and open http://127.0.0.1:8765/. |
Differences between package demos
- The Windows package has no C++ source or
compile.shindemo/examples/; it contains only Python scripts andvisual.exe. To compile C++ on Windows, useinclude/andlib/windows/x86_64/*.lib(see C++ integration). - The two Linux packages (x86_64 / ARM64) contain the C++ source and prebuilt
recv_mocap_udp, but not the Pythonrecv_mocap_udp.py. Copy the cross-platform corrected script provided on this page if you need the Python menu.
Before the first run
MocapFrame travels over the network. The pico motion-capture host sends data to UDP port 15000 on your computer by default.
| Situation | What to do |
|---|---|
| Windows | When the firewall prompt appears, choose Allow and enable both private and public networks, or manually allow inbound UDP 15000. Otherwise, no packets arrive. |
| Linux | Programs in bin/ already have runtime library paths configured. Programs you compile need LD_LIBRARY_PATH as described below. |
| All platforms | The pico host and your computer must be on mutually reachable networks, and UDP must not be blocked by a firewall. |
Python menu (recv_mocap_udp.py or forsense-recv) | Install the wheel from the Python package's dist/ directory first. |
| Web visualization (Step 1) | No Python package is required. visual / visual.exe is standalone, and udp_receiver.py uses only the standard library. |
Step 1: view the motion without writing code
The shortest validation path uses the demo web visualization to render data from the pico host as a 3D model. This path does not depend on Qt and works the same way on Windows and Linux.
It requires two programs:
| Program | Purpose |
|---|---|
visual / visual.exe | Renderer: embedded HTTP server, Three.js page, and FBX model |
udp_receiver.py | Data bridge: receives UDP, converts coordinates, and POSTs poses to the renderer |
udp_receiver.py does not draw anything itself. By default, it sends poses to http://127.0.0.1:8765/api/state. Running it without the renderer produces repeated State update failed messages.
1. Start the renderer (visual.exe on Windows):
cd demo/examples
./visualOpen http://127.0.0.1:8765/. You can rotate the model manually even without incoming data.
2. Start the data bridge:
python udp_receiver.pyThe model now follows the live data.
Optional udp_receiver.py arguments:
| Argument | Default | Meaning |
|---|---|---|
--host | 0.0.0.0 | UDP bind address |
--port | 15000 | UDP listening port |
--url | http://127.0.0.1:8765/api/state | Renderer state endpoint |
--mode | relative | Rotation space; absolute aligns to the first frame |
--coord | mocap-to-three | Coordinate conversion; raw performs none |
The default relative mode receives relative rotations and does not require calibration.
A UDP port can be bound by only one process at a time. While
udp_receiver.pyowns port 15000, the terminal menu and desktop viewer cannot bind it. Stop the first receiver, use another port, or forward the data.
Step 2: inspect data in a terminal
The example provides Full body, Hands, Diagnostics, and Exit options:
===== Forsense Mocap CLI =====
1) Full-body data # all 64 joint poses
2) Hand data # hand joints only (15-34 left + 44-63 right)
3) Diagnostics # connection, frames, rejections, length/id/crc categories
4) Exit
===============================
Select [1-4]:- Press Ctrl+C in a data screen to return to the main menu; press it in the main menu to exit.
- Enter
m,0, orqin a sub-screen to return to the main menu. - Diagnostics refreshes every 0.5 seconds and reports an interruption after more than two seconds without data.
Linux: compile, then run
cd demo/examples
./compile.sh # creates recv_mocap_udp
./recv_mocap_udp # default port 15000
./recv_mocap_udp --port 15001 # another portThis example uses no Qt, SDK library, or CMake—only SDK protocol headers and native sockets. Manual compilation:
# Linux / macOS
g++ recv_mocap_udp.cpp -std=c++17 -I <sdk>/include -o recv_mocap_udp:: Windows (MSVC, from an x64 Native Tools command prompt)
cl /std:c++17 /EHsc /I <sdk>\include recv_mocap_udp.cpp ws2_32.lib
:: Windows (MinGW)
g++ recv_mocap_udp.cpp -std=c++17 -I <sdk>/include -lws2_32 -o recv_mocap_udpAny platform: Python menu
recv_mocap_udp.py is pure Python and works on Windows, Linux x86_64, and Linux ARM64.
Install the library first. The script imports
forsense_mocap; otherwise it immediately reports:textModuleNotFoundError: No module named 'forsense_mocap'Enter the extracted Python package's
distdirectory and run:bashpip install forsense_mocap-1.0.0-py3-none-any.whlWindows may add another same-named directory during extraction. Use the level containing
dist.Verify the installation:
bashpython -c "import forsense_mocap; print(forsense_mocap.__version__)"Output
1.0.1is expected: the wheel filename says1.0.0, but its internal version is1.0.1.The Step 1
udp_receiver.pyuses only the Python standard library and does not need this wheel. The wheel is required only forrecv_mocap_udp.pyandforsense-recv.Use the corrected
recv_mocap_udp.pyprovided here, not the one inside the package. The package copy calls a nonexistentrecv.diagnostics()method in option 3. The corrected script usesrecv.stats, prints live FPS, and has been tested with all four menu options.Replace
demo\examples\recv_mocap_udp.pyin the Windows package ordemo\recv_mocap_udp.pyin the Python package. Linux packages do not contain the Python script, so copy this file into them or run it from any directory.
The menu matches the C++ version (1 Full body / 2 Hands / 3 Diagnostics / 4 Exit). Diagnostics refreshes once per second and categorizes rejections as short packet, header, frame ID, or CRC.
Windows
python recv_mocap_udp.py # default port 15000
python recv_mocap_udp.py --port 15001Linux x86_64 / ARM64
python3 recv_mocap_udp.py # default port 15000
python3 recv_mocap_udp.py --port 15001If the port is occupied, close the process using it or choose another port.
Recording and playback
The bin/ directory contains two tools for recording a live UDP stream and analyzing it later:
| Program | Purpose |
|---|---|
forsense_bin_recorder | Records UDP MocapFrame data to a .bin file |
forsense_bin_player | Replays a .bin recording |
The recording is a continuous raw protocol-frame stream without an additional container header. Playback accepts data frames beginning with 0xAA 0x55 and reproduces timing from timestamps in each frame.
Integrating into your application
Integrate only after the visualization or terminal-data path works.
C++ integration
Files to ship
include/forsense/- SDK libraries under
lib/<platform>/ - On Windows, the
*_adapter.dll,forsense_tui.dll, and Qt 6 DLLs frombin/
Prerequisite: Qt 6 development environment with Core and Network.
CMake (recommended)
find_package(forsense_sdk CONFIG REQUIRED)
target_link_libraries(my_app PRIVATE forsense::udp_adapter)Available targets: forsense::udp_adapter, forsense::ble_adapter, forsense::websocket_adapter, forsense::local_socket_adapter, forsense::binary_adapter, forsense::pose_vizer, forsense::tui, and forsense::math, plus dependency targets for fmt, magic_enum, and eigen.
If CMake cannot find the package, set -Dforsense_sdk_DIR=<sdk>/lib/<platform>/<architecture>/cmake/forsense_sdk.
Minimal receiver
#include <forsense/adapter/UdpAdapter.h>
#include <forsense/codec/Decoder.h>
#include <forsense/protocol/human/MocapFrame.h>
fss::UdpAdapter udp;
udp.initialize();
udp.connect(QHostAddress::AnyIPv4, 15000);
fss::codec::DataDecoderT<fss::protocol::MocapFrame,
fss::protocol::MOCAP_DATA_FRAME_ID> decoder;
QObject::connect(&udp, &fss::UdpAdapter::data_received,
[&](const QByteArray &data, const QHostAddress &, quint16) {
for (char b : data)
{
if (decoder.parse(static_cast<std::uint8_t>(b)) !=
fss::codec::DataDecoderT<fss::protocol::MocapFrame,
fss::protocol::MOCAP_DATA_FRAME_ID>::Result::FRAME_READY)
continue;
const auto &frame = decoder.frame(); // CRC already verified
// frame.payload.time_us : timestamp in microseconds
// frame.payload.joint_poses[i] : joint i
// .position[0..2] : (x, y, z)
// .quaternion[0..3] : (w, x, y, z)
}
});Only one process can bind a UDP port at a time. If another receiver already owns port 15000, use another port or forward the data.
For a simpler solution without Qt, copy demo/examples/recv_mocap_udp.cpp. It uses only Decoder.h, MocapFrame.h, and native sockets.
| Information | Header |
|---|---|
| Frame structure / payload layout | include/forsense/protocol/human/MocapFrame.h |
| Frame constants and CRC algorithm | include/forsense/protocol/FrameFormat.h |
| 64-joint enum | include/forsense/types/SkeletonJoint.h |
| Pose structure | include/forsense/types/Pose.h |
| Streaming decoder | include/forsense/codec/Decoder.h |
| Encoder for test/replay frames | include/forsense/codec/Encoder.h |
| UDP input/output | include/forsense/adapter/UdpAdapter.h |
Python integration
Install the wheel from the Python package's dist/ directory:
pip install forsense_mocap-1.0.0-py3-none-any.whlMinimal callback example:
from forsense_mocap import UdpMocapReceiver, JOINT_NAMES
def on_frame(frame):
head = frame.joint("Head")
print("Head:", head.position, head.quaternion)
UdpMocapReceiver(port=15000, on_frame=on_frame).start()Polling example:
from forsense_mocap import UdpMocapReceiver
recv = UdpMocapReceiver(port=15000)
recv.start()
while True:
frame = recv.poll(timeout=1.0)
if frame is None:
continue
print(frame.timestamp_us, frame.joints[0].position)
# recv.stats.frames / recv.stats.rejected / recv.stats.rejected_causes| Name | Description |
|---|---|
JOINT_NAMES / JOINT_INDEX | 64 joint names / name-to-index mapping matching the C++ SkeletonJoint order |
MocapFrame | One frame: timestamp_us, source, joints[64], and joint(name) |
MocapJoint | position(x,y,z) and quaternion(w,x,y,z) |
parse_frame(data) | Parses and CRC-checks a frame; returns None on failure |
make_frame(joints) | Builds a frame for tests or playback |
crc32_forsense(data) | Forsense CRC-32 |
UdpMocapReceiver(port, on_frame, on_error, on_status, timeout) | UDP receiver with callback and poll() modes |
RecvResult | Statistics: frames, bytes, rejected, and rejected_causes |
The receiver also detects disconnections. After no data for longer than timeout (default two seconds), on_status receives {"connected": False, "elapsed": ...}. When data resumes, it receives {"connected": True, "recovered": True}. If the port is occupied, start() raises RuntimeError with an explanation.
Command-line tools installed with the wheel:
forsense-recv --port 15000
forsense-recv --port 15000 --joints Head Waist LeftHand RightHandData format
Frame structure
MocapFrame is fixed at 1,860 bytes, begins with AA 55, and has ID 0. All multibyte integers and float values are little-endian.
AA 55 + id(u16 LE) + length(u16 LE) + payload(1850) + crc32(u32 LE)| Field | Length | Description |
|---|---|---|
| header1 / header2 | 1 + 1 | 0xAA / 0x55 synchronization bytes |
| id | 2 | 0 (Mocap data frame) |
| length | 2 | 1850 (payload length) |
| payload | 1850 | Described below |
| crc32 | 4 | Forsense CRC-32 |
Payload layout (1,850 bytes)
| Offset | Length | Type | Member | Description |
|---|---|---|---|---|
| 0 | 8 | uint64 | time_us | System timestamp in microseconds |
| 8 | 1792 | float[64][7] | joint_poses | 64 joint poses |
| 1800 | 9 | uint8 + float[2] | left_controller | Left controller / glove state |
| 1809 | 9 | uint8 + float[2] | right_controller | Right controller / glove state |
| 1818 | 32 | uint64[4] | reserved | Reserved |
Each joint occupies 28 bytes: position[3] at offset 0 (x,y,z) and quaternion[4] at offset 12 in w, x, y, z order.
64 joint indices
| # | Joint | # | Joint | # | Joint | # | Joint |
|---|---|---|---|---|---|---|---|
| 0 | Head | 16 | LeftThumbMid | 32 | LeftPinkyMid | 48 | RightIndexNear |
| 1 | Neck | 17 | LeftThumbFar | 33 | LeftPinkyFar | 49 | RightIndexMid |
| 2 | Chest | 18 | LeftThumbEnd | 34 | LeftPinkyEnd | 50 | RightIndexFar |
| 3 | Spine1 | 19 | LeftIndexNear | 35 | RightCollar | 51 | RightIndexEnd |
| 4 | Spine2 | 20 | LeftIndexMid | 36 | RightShoulder | 52 | RightMiddleNear |
| 5 | Waist | 21 | LeftIndexFar | 37 | RightElbow | 53 | RightMiddleMid |
| 6 | LeftCollar | 22 | LeftIndexEnd | 38 | RightWrist | 54 | RightMiddleFar |
| 7 | LeftShoulder | 23 | LeftMiddleNear | 39 | RightHand | 55 | RightMiddleEnd |
| 8 | LeftElbow | 24 | LeftMiddleMid | 40 | RightHip | 56 | RightRingNear |
| 9 | LeftWrist | 25 | LeftMiddleFar | 41 | RightKnee | 57 | RightRingMid |
| 10 | LeftHand | 26 | LeftMiddleEnd | 42 | RightAnkle | 58 | RightRingFar |
| 11 | LeftHip | 27 | LeftRingNear | 43 | RightFoot | 59 | RightRingEnd |
| 12 | LeftKnee | 28 | LeftRingMid | 44 | RightThumbNear | 60 | RightPinkyNear |
| 13 | LeftAnkle | 29 | LeftRingFar | 45 | RightThumbMid | 61 | RightPinkyMid |
| 14 | LeftFoot | 30 | LeftRingEnd | 46 | RightThumbFar | 62 | RightPinkyFar |
| 15 | LeftThumbNear | 31 | LeftPinkyNear | 47 | RightThumbEnd | 63 | RightPinkyEnd |
- Full-body skeleton:
0-14and35-43(24 joints) - Left hand:
15-34(20 finger joints from the G2-L glove) - Right hand:
44-63(20 finger joints)
Array indices are fixed; do not reorder by joint name.
Position units, axis directions, and the quaternion reference frame are defined by the pico motion-capture host. The protocol does not carry unit metadata; align these settings with the host before integrating with your engine.
Controller / glove state
left_controller and right_controller each occupy 9 bytes: one-byte button_state, four-byte joystick_x, and four-byte joystick_y.
| Bit | Meaning when set | Left | Right |
|---|---|---|---|
| 0 | Input-device mode | Glove (0 means controller) | Glove (0 means controller) |
| 1 | Joystick pressed | Joystick | Joystick |
| 2 | Primary button | X | A |
| 3 | Secondary button | Y | B |
| 4 | Trigger pressed | Trigger | Trigger |
| 5 | Grip pressed | Grip | Grip |
| 6 / 7 | Reserved | Ignore | Ignore |
Both joystick axes range from -1 to 1. Left X is positive up and negative down; right X is positive right and negative left. Y is positive up and negative down on both sides.
CRC-32
Custom CRC-32: initial value 1, polynomial 0xEDB88320 (reflected), processed byte-by-byte and bit-by-bit, with no final XOR. Coverage begins at header1 and ends at the final payload byte; it excludes the CRC field itself.
def crc32_forsense(data: bytes) -> int:
crc = 1
for b in data:
crc ^= b
for _ in range(8):
crc = (crc >> 1) ^ 0xEDB88320 if crc & 1 else crc >> 1
return crc & 0xFFFFFFFFTroubleshooting
| Symptom | Solution |
|---|---|
| Windows receives no packets | Allow inbound UDP 15000 in Windows Firewall. |
recv_mocap_udp.py or forsense-recv reports No module named 'forsense_mocap' | Install the wheel from the Python package's dist directory. |
| Bind fails / port in use | Only one process can bind the port. Stop the receiver using it or select --port 15001. |
| Linux server has no graphical environment | Run xvfb-run ./bin/forsense_mocap_viewer. |
A compiled program cannot find libQt6*.so | Set export LD_LIBRARY_PATH=<sdk>/lib/linux/<architecture>:$LD_LIBRARY_PATH, or run the prebuilt program in bin/. |
find_package(forsense_sdk) fails | Add -Dforsense_sdk_DIR=<sdk>/lib/<platform>/<architecture>/cmake/forsense_sdk. |
| Data repeatedly reports an interruption | Check that the pico host is sending, the networks are mutually reachable, and UDP is not blocked. |
| Joint values look wrong | Confirm host-side units and coordinates, and use the fixed 64-joint index order. |
| The re-downloaded Windows archive extracts into three nested folders | Enter the innermost folder containing bin, include, lib, and demo; the file provided here has the extra nesting removed. |
Extraction reports an overlong ...\Eigen\...\Transform.h path | Extract to a shorter path such as D:\sdk, or use the non-nested archive provided here. |
Package demo/recv_mocap_udp.py fails in Diagnostics with AttributeError | Replace it with the corrected recv_mocap_udp.py provided here. |
Web visualization is blank / repeatedly reports State update failed | Start visual or visual.exe before udp_receiver.py; confirm that the service is listening at the configured --url on port 8765. |
| Two programs need the same data | Only one process can bind the UDP port. Let one receiver forward the data, for example with forsense_mocap_pose_vizer. |
