Your SICK ID gives you access to our extensive range of services. This includes direct online orders, price and availability checks, and access to our digital services.
LiDAR: Parsing SICK Compact Format Binary Telegrams with Kaitai Struct
Article No: KA-10748
Version: 1.1
Subject to change without notice
This article explains how to use Kaitai Struct to parse SICK Compact Format UDP telegrams, covering Scan Data (Type 1), IMU (Type 2), and Encoder (Type 4).
Related Products
LRS4000
picoScan100
multiScan100
Table of Contents
Applies to picoScan100, picoScan150, multiScan100, and LRS4000 devices that output data in the SICK Compact Format.
Official source
All .ksy files referenced on this page are maintained in the public
SICKAG/KaitaiStructDescriptions
repository. Each file below links directly to the current released version on GitHub
rather than a copy pasted into this page. Field names in the descriptive tables below are
a conceptual overview; treat the linked .ksy source as ground truth if the two
ever disagree.
Overview
1 - What Is Kaitai Struct?
Whenever a sensor transmits data as a binary stream, someone has to write code that
reads those bytes and turns them into meaningful values. The conventional approach is
to write that code by hand, in whatever language the project uses, which leads to
repetitive, platform-specific parsers that are hard to maintain and even harder
to share across teams.
Kaitai Struct solves this by separating the description of
a binary format from its implementation. You write a single
.ksy file that declares what each field is, how large it is, and how
to interpret it. From that one description the Kaitai Struct Compiler
(ksc) generates a ready-to-use parser class in any supported
language (C++, Python, C#, Java, JavaScript, Go, Rust, and more) without
requiring you to write a single line of parsing code.
Write once, use everywhere. One .ksy file produces parsers in a dozen languages.
Self-documenting. The format definition is readable YAML; it also serves as the format specification.
Tooling included. A browser-based IDE and a hex visualizer let you debug formats against real captured data without writing any code at all.
Free & open-source. The compiler is GPLv3; all runtime libraries are MIT licensed.
Try it without installing anything
The Kaitai Web IDE runs entirely in your browser at
ide.kaitai.io.
You can paste a .ksy file, load a captured binary, and immediately
inspect every parsed field in an interactive tree, no compiler, no runtime, no
installation required.
Protocol
2 - The SICK Compact Format
SICK LiDAR devices stream measurement data as UDP datagrams over the network.
Each datagram carries exactly one compact telegram.
Regardless of the payload type, every telegram is wrapped in the same four-field
outer frame:
Magic
4 bytes
0x02 0x02 0x02 0x02
Telegram Type
u32 le
1 / 2 / 4 / …
Payload
variable
type-specific data
Checksum
u32 le
CRC32
Magic (field start_of_frame): four 0x02 bytes that mark the start of every compact telegram. A receiver uses this pattern to locate frames inside a raw byte stream.
Telegram type (field telegram_type): a 32-bit integer that identifies the payload type. The value drives a switch in the Kaitai description so the correct parser is invoked automatically.
Payload: type-specific data described by the individual .ksy files. The sections below cover the three active types.
Checksum: CRC32 computed over start_of_frame + telegram_type + payload. Used by the receiver to detect corrupted datagrams.
The entry point for Kaitai is compact_frame.ksy.
It imports the payload .ksy files and uses a
switch-on: telegram_type expression to dispatch to the right
sub-parser automatically:
Entry point of the format. Declares the outer frame (start_of_frame, telegram_type, checksum) and dispatches to the matching payload sub-type via switch-on: telegram_type.
The Scan Data telegram carries the raw distance and intensity measurements
from one scan segment. A stream of these telegrams, one per sensor rotation
segment, is assembled by the receiver into complete 360° scan frames.
Each frame contains a point cloud that can be projected into 3D Cartesian
coordinates using the azimuth and elevation angles embedded in the metadata.
The payload is structured as a header followed by one or more
modules. Each module covers a contiguous angular range
and carries its own metadata and the raw beam data for every measurement
point inside that range.
Header fields
Field
Type
Description
telegram_counter
u64
Counts every telegram sent since the device started. Starts at 1. Use to detect missing frames.
timestamp_transmit
u64 (µs UTC)
Device system time at transmission, in microseconds since 1970-01-01 00:00 UTC.
telegram_version
u32
Format revision. Version 3 is the base; version 4 adds a per-module distance_scaling_factor.
next_module_size
u32
Byte size of the first module. Used by the parser to advance through modules sequentially.
Module metadata fields (repeated per module)
Field
Type
Description
segment_counter
u64
Consecutive segment index since device start.
frame_number
u64
Full scan revolution counter. All modules with the same frame_number belong to one complete frame.
sender_id
u32
Device serial number. Useful when multiple sensors send to the same host.
num_lines_in_module
u32
Number of scan layers (rows) in this module.
num_beams_per_scan
u32
Number of beams per layer. All layers in a module have the same beam count.
num_echos_per_beam
u32
Number of echoes per beam (1 for single-echo, up to 3 for multi-echo devices).
phi[]
f32[] per layer
Elevation angle of each layer in radians. One value per num_lines_in_module.
theta_start[] / theta_stop[]
f32[] per layer
Start and end azimuth angles (radians) for the beams in each layer.
distance_scaling_factor
f32 (v4 only)
Multiply distance_raw by this value to get distance in mm. Enables sub-millimetre resolution or ranges above 65 535 mm.
next_module_size
u32
Size of the next module in bytes. Zero means this is the last module.
data_content_echos
u8 bitfield
Bit 0 = distance present, bit 1 = RSSI present. Guards conditional echo fields.
data_content_beams
u8 bitfield
Bit 0 = beam properties present, bit 1 = per-beam azimuth present.
Beam data fields (repeated per beam, per echo)
Field
Type
Description
distance_raw
u16
Raw distance. Multiply by distance_scaling_factor (v4) or treat as mm (v3). Present when data_content_echos.distance = 1.
rssi
u16
Received signal strength. Dimensionless, device-specific range. Present when data_content_echos.rssi = 1.
theta
u16
Per-beam azimuth, encoded as an integer. Convert: angle_rad = (theta − 16384) / 5215. Present when data_content_beams.azimuth_angles = 1.
properties
u8 bitfield
Bit 0 = reflector detected. Present when data_content_beams.beam_properties = 1.
Use case: 3D point cloud reconstruction
Given phi (elevation), theta (azimuth), and
distance, a 3D Cartesian point is:
x = d · cos(phi) · cos(theta),
y = d · cos(phi) · sin(theta),
z = d · sin(phi).
The Kaitai-generated class makes all three values accessible as simple object attributes, no manual offset arithmetic required.
Type 2
4 - IMU Data
The IMU telegram delivers inertial measurements from the built-in
Inertial Measurement Unit. It carries linear acceleration, angular velocity,
and an orientation estimate as a quaternion, all time-stamped to the same
clock as the scan data. This makes it possible to correct for sensor motion
during data acquisition.
The IMU telegram is structurally simpler than the Scan Data telegram:
it has no variable-length modules and no conditional fields. The payload
is a flat sequence of fixed-size values.
Field
Type
Description
telegram_version
u32
Format revision. Currently always 1.
acceleration.x / .y / .z
3 × f32
Linear acceleration along the sensor X, Y, Z axes in m/s².
angular_velocity.x / .y / .z
3 × f32
Angular velocity around the sensor X, Y, Z axes in rad/s.
orientation.w / .x / .y / .z
4 × f32
Sensor orientation as a unit quaternion (w, x, y, z). Can be converted to roll/pitch/yaw using standard formulas.
timestamp
u64 (µs UTC)
Sensor time in microseconds since 1970-01-01 00:00 UTC. Shares the same time base as timestamp_transmit in Type 1.
The Encoder telegram reports the current state of an external rotary encoder
connected to the device. The encoder is typically attached to the axis of
an external conveyor or rotating mount, so the sensor knows the precise
angular position of the object being scanned at the time of each measurement.
The telegram is emitted together with each Primary Data frame and carries
a frame_number field that links it to the corresponding scan frame.
Field
Type
Description
telegram_counter
u64
Counts all telegrams since device start.
timestamp_transmit
u64 (µs UTC)
Sensor time at transmission in microseconds since 1970-01-01 00:00 UTC.
telegram_version
u32
Format revision. Currently always 1.
payload_length
u32
Payload size in bytes, not including the CRC32 trailer.
sender_id
u32
Device serial number.
frame_number
u64
Links this encoder snapshot to the Primary Data frame with the same frame_number.
tick_counter_value
u32
Current encoder tick count. Divide by the configured ticks-per-revolution to get the angular position.
tick_counter_value_at_ref_1_signal
u32
Tick count at the last rising edge of the reference signal 1 (zero-mark).
tick_counter_value_at_ref_2_signal
u32
Tick count at the last rising edge of the reference signal 2.
speed_value
f32
Encoder speed in the unit configured on the device (typically counts/second).
The header fields (telegram_counter, transmit_timestamp,
telegram_version, payload_length, sender_id) live
in the shared header type below, imported from compact_header.ksy.
The four steps below take you from zero to a working parser for any of the
three telegram types described in this document. No programming language is
assumed - Kaitai targets whichever language you already use.
1
Get the .ksy files
Clone or download the
SICKAG/KaitaiStructDescriptions
repository. All format files live under formats/: the entry point
compact_frame.ksy, the shared compact_header.ksy, and the
payload files for the types you want to parse
(type_1_primary_data_spherical_coordinates.ksy, type_2_imu.ksy,
type_4_encoder.ksy).
2
Try it in the browser first
Open ide.kaitai.io,
drag compact_frame.ksy into the editor panel, then drag a
captured .bin file into the hex view. Kaitai parses the
binary immediately and shows every field in an interactive tree.
This is the fastest way to verify that a captured frame is well-formed.
3
Generate a parser in your language
Download the Kaitai Struct Compiler (ksc) from
kaitai.io/#download.
Java 8 or later is required. Then run:
Replace python with any supported target:
csharp, cpp_stl, java,
javascript, go, rust, and more.
The compiler reads all imported .ksy files automatically
and writes one source file per type into the output directory.
4
Add the runtime and parse
Each target language has a small runtime library (install it from
kaitai.io
or the language's package manager). Add the generated source files
and the runtime to your project, then parse:
// Open a captured binary file and wrap it in a Kaitai stream.
stream = open_binary("captured_frame.bin")
frame = CompactFrame.parse(stream)
// Dispatch on telegram_type to access the typed payload.
if frame.telegram_type == 1 // Scan Data (Type 1)
for each mod in frame.payload.module
use mod.metadata.num_beams_per_scan
use mod.metadata.frame_number
for each beam in mod.beams
for each line in beam.lines
for each echo in line.echos
use echo.distance, echo.rssi
else if frame.telegram_type == 2 // IMU (Type 2)
imu = frame.payload
use imu.acceleration.x, imu.acceleration.y, imu.acceleration.z
use imu.angular_velocity.x, imu.angular_velocity.y, imu.angular_velocity.z
use imu.orientation.w, imu.orientation.x, imu.orientation.y, imu.orientation.z
else if frame.telegram_type == 4 // Encoder (Type 4)
enc = frame.payload
use enc.tick_count
use enc.speed
use enc.frame_number // links to the matching Type 1 frame
The same field names appear in every language, only the syntax
differs. The Kaitai documentation at
doc.kaitai.io
shows the equivalent access pattern for each supported target.