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.
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
| Key | Alias | Meaning |
|---|---|---|
name=<name> | n | Descriptive name tag shown in the output. Required. |
modulation=<modulation> | m | Signal encoding (see the list below). Required. |
short=<short> | s | Nominal modulation timing, in µs. Required. |
long=<long> | l | Nominal modulation timing, in µs. |
sync=<sync> | y | Nominal sync-pulse width, in µs (optional). |
reset=<reset> | r | Maximum gap before End Of Message, in µs. |
gap=<gap> | g | Maximum gap before a new row of bits, in µs. |
tolerance=<tolerance> | t | Maximum pulse deviation, in µs (optional). |
priority=<n> | prio | Run 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.
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.
| Modulation | Coding |
|---|---|
OOK_MC_ZEROBIT | Manchester Code with a fixed leading zero bit |
OOK_PCM | Non Return to Zero coding (Pulse Code) |
OOK_RZ | Return to Zero coding (Pulse Code) |
OOK_PPM | Pulse Position Modulation |
OOK_PWM | Pulse Width Modulation |
OOK_DMC | Differential Manchester Code |
OOK_PIWM_RAW | Raw Pulse Interval and Width Modulation |
OOK_PIWM_DC | Differential Pulse Interval and Width Modulation |
OOK_MC_OSV1 | Manchester Code for OSv1 devices |
FSK_PCM | FSK Pulse Code Modulation |
FSK_PWM | FSK Pulse Width Modulation |
FSK_MC_ZEROBIT | Manchester Code with a fixed leading zero bit |
What short and long mean per modulation
The timing keys are interpreted differently for each scheme:
| Modulation | short | long | sync |
|---|---|---|---|
| PCM / RZ | Nominal width of a pulse | Nominal width of a bit period | — |
| PPM | Nominal width of a '0' gap | Nominal width of a '1' gap | — |
| PWM | Nominal width of a '1' pulse | Nominal width of a '0' pulse | Nominal 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.
| Key | Effect |
|---|---|
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. |
invert | Invert all bits. |
reflect | Reflect 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_dm | Differential Manchester decode. |
decode_mc | Manchester decode. |
unique | Suppress duplicate row output. |
countonly | Suppress 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.
preamble vs matchUse 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:
| Component | Form | Purpose |
|---|---|---|
| Bit offset | @<offset> | Where the field starts in the row (default 0). |
| Bit mask / length | {<count>}<hex> or just a number | How 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. |
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
- rtl_433 flex decoder source (
src/devices/flex.c) — authoritative key list, modulations, and thegetgetter syntax - rtl_433 README (usage and examples)
- Basic rtl_433 operation (
docs/OPERATION.md) - rtl_433 man page (
man/man1/rtl_433.1) - rtl_433 project site (triq.org)