Skip to main content

The -X flex decoder

rtl_433 ships with hundreds of compiled-in C decoders, but you do not have to write C to support a new device. The flex decoder lets you describe a decoder entirely on the command line (or in a config file) as a single key=value spec passed to -X. This is the fastest way to prototype support for an unrecognised remote, sensor, or doorbell: capture its pulses, describe the modulation and timing, optionally match a fixed pattern, and pull out named fields with get.

-X <spec> where <spec> is "key=value[,key=value...]"

-X can be given more than once to add several flex decoders, and passing -X help prints the full built-in reference.

Let rtl_433 write the first draft for you

Run the analyzer with -A on a recorded or live signal. When it recognises a pulse train it prints a suggested -X line you can copy, tweak, and refine. The conf/ folder in the source tree also contains real flex specs you can crib from.

The spec at a glance

A flex spec is a flat, comma-separated list of keys. A minimal decoder needs a name, a modulation, and at least a short width and a reset limit; everything else is optional. Short single-letter aliases exist for the common keys.

# A doorbell decoder — verbatim from the rtl_433 -X help text
rtl_433 -X "n=doorbell,m=OOK_PWM,s=400,l=800,r=7000,g=1000,match={24}0xa9878c,repeats>=3"

Common keys

KeyAliasMeaning
name=<name>nDescriptive name tag shown in the output. Required.
modulation=<modulation>mSignal encoding (see the list below). Required.
short=<short>sNominal modulation timing, in µs. Required.
long=<long>lNominal modulation timing, in µs.
sync=<sync>yNominal sync-pulse width, in µs (optional).
reset=<reset>rMaximum gap before End Of Message, in µs.
gap=<gap>gMaximum gap before a new row of bits, in µs.
tolerance=<tolerance>tMaximum pulse deviation, in µs (optional).
priority=<n>prioRun this decoder only as a fallback.

<short>, <long> and <sync> are nominal timings; <reset>, <gap> and <tolerance> are maximum timings. The exact meaning of short and long depends on the modulation — see below.

Required fields

The flex decoder always refuses a spec that is missing a name, a modulation, a short width, or a reset limit. Depending on the modulation it will also require a long width (for every modulation except the Manchester-ZEROBIT ones) or a tolerance (for OOK_DMC, OOK_PIWM_RAW, and OOK_PIWM_DC), and will print a "Bad flex spec" message telling you which one is missing.

Modulations

The modulation (m) key takes exactly one of the following values. OOK (on-off keying) covers most cheap 433.92/315 MHz remotes and sensors; FSK is common on 868/915 MHz devices.

ModulationCoding
OOK_MC_ZEROBITManchester Code with a fixed leading zero bit
OOK_PCMNon Return to Zero coding (Pulse Code)
OOK_RZReturn to Zero coding (Pulse Code)
OOK_PPMPulse Position Modulation
OOK_PWMPulse Width Modulation
OOK_DMCDifferential Manchester Code
OOK_PIWM_RAWRaw Pulse Interval and Width Modulation
OOK_PIWM_DCDifferential Pulse Interval and Width Modulation
OOK_MC_OSV1Manchester Code for OSv1 devices
FSK_PCMFSK Pulse Code Modulation
FSK_PWMFSK Pulse Width Modulation
FSK_MC_ZEROBITManchester Code with a fixed leading zero bit

What short and long mean per modulation

The timing keys are interpreted differently for each scheme:

Modulationshortlongsync
PCM / RZNominal width of a pulseNominal width of a bit period
PPMNominal width of a '0' gapNominal width of a '1' gap
PWMNominal width of a '1' pulseNominal width of a '0' pulseNominal width of the sync pulse (optional)

The common gap and reset keys apply to all modulations: gap is the maximum gap before a new row of bits begins, and reset is the maximum gap before the decoder treats the message as complete.

Matching and filtering

Once the raw bits are demodulated, these options decide whether a row counts as a match and how it is cleaned up before output.

KeyEffect
bits=<n>Only match if at least one row has <n> bits.
rows=<n>Only match if there are <n> rows.
repeats=<n>Only match if some row is repeated <n> times.
match=<bits>Only match if the given <bits> are found anywhere in a row.
preamble=<bits>Match and align the row at the <bits> preamble.
invertInvert all bits.
reflectReflect each byte (MSB-first to MSB-last).
decode_uart=<8n1|8n2|8o1>UART decode: 8n1 (10-to-8), 8n2, or 8o1 (11-to-8).
decode_dmDifferential Manchester decode.
decode_mcManchester decode.
uniqueSuppress duplicate row output.
countonlySuppress detailed row output (report only the count).

For bits, rows and repeats you can use >= and <= to set a lower or upper bound — e.g. repeats>=3 matches rows repeated at least three times.

<bits> for match and preamble is a row spec written as {<bit count>}<bits as hex number>, for example match={24}0xa9878c. The same {count}hex form is used for the getter mask described next. A match, preamble, or getter mask is limited to a single bit row of up to 1024 bits.

note
preamble vs match

Use match to reject rows that do not contain a known pattern. Use preamble when the device prefixes every frame with a sync word and you want the decoder to shift the row so your bit offsets start right after that preamble — this makes the offsets in your get fields stable.

Extracting named fields with get

By default a flex decoder emits the whole row as a hex data field. The get key pulls a specific run of bits out of the row and presents it as a named field, optionally formatted and mapped to friendly labels. A single spec may contain several get clauses.

The colon-separated components of a get value are:

ComponentFormPurpose
Bit offset@<offset>Where the field starts in the row (default 0).
Bit mask / length{<count>}<hex> or just a numberHow many bits to take, optionally masked.
Name<name>The output field name. Required.
Format%<printf-format>Optional printf-style format for the value.
Mapping[<key>:<label> ...]Optional lookup that replaces a raw value with a label.
Illustrative example

The following spec is for illustration only — the offsets, widths, and labels are made up to show the shape of get, not to decode any real device. Substitute the timings and bit positions you measured from your own capture.

rtl_433 -X "n=mysensor,m=OOK_PWM,s=500,l=1000,r=8000,bits=36,\
get=@0:{8}:id,\
get=@8:{12}:temperature_C:%.1f,\
get=@20:{1}:battery:[0:LOW 1:OK]"

Here the first byte becomes an id, the next 12 bits a formatted temperature_C, and one bit a battery field rendered as LOW or OK via the mapping. A get clause with no matching mapping entry falls back to printing the numeric value (using the %format if supplied).

From flex spec to a config file

Anything you can pass to -X you can also store, so you do not have to retype a long spec. Put the keys in a config file (rtl_433 searches ./, $XDG_CONFIG_HOME/rtl_433/, and the system SYSCONFDIR) and load it with -c:

rtl_433 -c myflex.conf

Once your prototype is solid and you want it merged into rtl_433 itself, the flex spec is an excellent starting point for a proper C decoder — the timings, match pattern, and field offsets carry over directly.

Sources