KAT scripts
demod_sub_to_bits.py
Demodulate Flipper SubGhz RAW .sub files into a stream of 1 and 0 bits using duration-based on-off keying (OOK). The script parses level+duration pulses, classifies each as short or long against configurable timing, then outputs bits according to the chosen encoding.
Input format
The script reads Flipper-style .sub files:
- RAW_Data: space-separated signed integers (one per pulse).
- Positive value = HIGH level, negative = LOW; magnitude = duration in microseconds.
- Optional Frequency: line (Hz). If missing, 433 920 000 is assumed.
Same convention as KAT’s .sub import and ProtoPirate’s raw file reader.
Encoding modes
| Mode | Description |
|---|---|
| pwm | One bit per pulse: short duration → 0, long duration → 1. Unmatched durations are skipped. Default. |
| manchester | Short/long pulses fed into a Manchester state machine (Ford V0–style). Outputs decoded data bits. Long gaps reset state. |
| raw | No duration decoding: one character per pulse, 1 = HIGH, 0 = LOW. |
Timing parameters
Pulses are classified using nominal short/long durations and a tolerance:
- --te-short — Nominal short duration (µs). Default: 250.
- --te-long — Nominal long duration (µs). Default: 500.
- --te-delta — Tolerance (µs): a pulse is “short” if
|duration - te_short| ≤ te_delta, “long” if|duration - te_long| ≤ te_delta. Default: 100.
Examples:
- Ford V0–style:
--te-short 250 --te-long 500 --te-delta 100 - VAG/VW 500 µs:
--te-short 500 --te-long 1000 --te-delta 120
Usage
# PWM decode (short→0, long→1), default 250/500 µs
python3 scripts/demod_sub_to_bits.py path/to/file.sub
# PWM with VAG-like timing
python3 scripts/demod_sub_to_bits.py path/to/file.sub --te-short 500 --te-long 1000 --te-delta 120
# Manchester decode (e.g. Ford)
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding manchester --te-short 250 --te-long 500
# Wrap output to 80 characters per line
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding pwm --wrap 80
# Only decode HIGH pulses (or --pulse-level low)
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding pwm --pulse-level high
# Raw level stream (no duration decoding)
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding raw
# Print level:duration for each pulse (no bits)
python3 scripts/demod_sub_to_bits.py path/to/file.sub --with-durations
Options summary
| Option | Default | Description |
|---|---|---|
--encoding |
pwm | raw, pwm, or manchester |
--te-short |
250 | Nominal short pulse (µs) |
--te-long |
500 | Nominal long pulse (µs) |
--te-delta |
100 | Timing tolerance (µs) |
--pulse-level |
both | For PWM: high, low, or both |
--gap-us |
10000 | Manchester: gap (µs) that resets state |
--wrap |
0 | Wrap bit string every N characters (0 = no wrap) |
--with-durations |
off | Print level:duration instead of bits |
Output
- Decoded bit string is printed to stdout (e.g.
1011001...or wrapped lines). - A single comment line (pulse count, frequency, encoding, timing) is printed to stderr.
Requirements
- Python 3.9+ (for
list[tuple[...]]type hints; can be relaxed if needed). - No extra dependencies; uses only the standard library.
Inspecting raw pulses (e.g. for VAG)
To see the first pulses of a .sub file (level and duration in µs) without decoding:
python3 scripts/demod_sub_to_bits.py path/to/file.sub --encoding raw --with-durations
VAG Type 3/4 expects: first pulse HIGH ~500 µs, then LOW ~500 µs, repeated (preamble); after ≥41 such pairs, HIGH ~1000 µs then LOW ~500 µs (sync), then 3×750 µs, then data. If the first pulse is LOW or durations are outside 500±79/80, the decoder will not lock.