5.6 KiB
Sub Decode: ProtoPirate vs KAT
Comparison of how ProtoPirate (REFERENCES/protopirate) and KAT decode Flipper .sub (RAW) files.
File format
Both use the same Flipper SubGhz RAW format:
- Filetype:
Flipper SubGhz RAW File - Protocol:
RAW - Frequency: one value (Hz)
- RAW_Data: space-separated int32 values:
- Positive = HIGH level
- Negative = LOW level
- Magnitude = duration in microseconds
So parsing and (level, duration) stream content are the same.
ProtoPirate sub decode (REFERENCES/protopirate)
Where it lives
- Scene:
scenes/protopirate_scene_sub_decode.c - Raw reader:
helpers/raw_file_reader.c/raw_file_reader.h
Flow
-
Open file
raw_file_reader_open()uses FlipperFormat to open the file, checks header "Flipper SubGhz RAW File" and Protocol "RAW". Does not read Frequency in the reader; the scene reads that separately. -
Read metadata (scene)
InDecodeStateReadHeader/DecodeStateStartingWorkerthe scene opens the file again with FlipperFormat and reads:- Frequency (default 433920000 if missing)
- Preset (e.g. AM650, FM238) and maps it to the SubGhz preset used for the receiver.
-
Feed stream
InDecodeStateDecodingRaw:- Loop:
raw_file_reader_get_next(ctx->raw_reader, &level, &duration)to get the next (level, duration). - For each pair:
subghz_receiver_decode(app->txrx->receiver, level, duration). - Reads in chunks of 128 samples per tick (
SAMPLES_TO_READ_PER_TICK) for UI responsiveness.
- Loop:
-
On decode
When the Flipper receiver reports a decode (protopirate_sub_decode_receiver_callback):- Add the decode to history.
subghz_receiver_reset(receiver)so all decoders are reset.- Continue feeding the same file from the next sample.
So: one continuous stream from the file, (level, duration) in order; on each decode → record → reset receiver → keep going. No polarity inversion in this path. No sliding window or multiple start positions.
Raw file reader details
raw_file_reader_get_next()(raw_file_reader.c):
Reads next int32 from buffer; if buffer is empty, loads next chunk viaflipper_format_read_int32(..., "RAW_Data", ...).
level = (value >= 0),duration = abs(value). Same convention as KAT.
KAT sub import (src/app.rs + protocols/mod.rs + export/flipper.rs)
Where it lives
- Import:
src/export/flipper.rs→import_sub_raw(path)→ returns(frequency, Vec<StoredLevelDuration>). - Decode:
src/protocols/mod.rs→process_signal_stream()/process_signal_stream_inner(). - Use:
src/app.rs→ when loading a .sub file, callsimport_sub_rawthenprotocols.process_signal_stream(&pairs, frequency).
Flow
-
Parse file
import_sub_raw():- Reads whole file as text.
- Parses Frequency (default 433_920_000 if missing).
- Parses all RAW_Data lines into one list of (level, duration) with the same rule: positive ⇒ HIGH, negative ⇒ LOW, duration = abs(value) µs.
-
Decode stream
process_signal_stream(pairs, frequency):- Tries normal polarity first:
process_signal_stream_inner(pairs, frequency, false). - If that returns no decodes, tries inverted polarity:
process_signal_stream_inner(pairs, frequency, true)(flip level for every pair). - Inner loop: for each (level, duration) in order, for each decoder that supports the file frequency, call
decoder.feed(level, duration_us). If any decoder returns a decode:- Record (protocol name, decoded signal, segment of pairs).
- Reset all decoders and set
segment_startto next index. - Continue from the next pair.
- Tries normal polarity first:
So: one pass over the in-memory stream; on each decode → record → reset all decoders → continue. Difference: KAT also runs a second pass with inverted polarity if the first pass finds nothing.
Differences summary
| Aspect | ProtoPirate | KAT |
|---|---|---|
| File format | Same (Flipper RAW, positive=HIGH, negative=LOW, µs) | Same |
| Stream order | Same (sequential level/duration from file) | Same |
| Frequency | Read from file, used for preset/receiver | Read from file, used for decoder filter (2% tolerance) |
| Decode loop | One (level, duration) at a time → subghz_receiver_decode() |
One (level, duration) at a time → decoder.feed() for each decoder |
| On decode | Add to history, subghz_receiver_reset(), continue same file | Append to list, reset all decoders, continue same stream |
| Polarity | Single polarity (as in file) | Tries normal, then inverted if no decodes |
| Sliding window / multiple starts | No | No |
| “No protocol” result | Shows “No ProtoPirate protocol detected” (no “Unknown” capture) | No capture if no decoder ever returns (same idea) |
So the sub decode strategy is the same: single stream, reset-after-each-decode, no sliding window. The only functional difference is KAT’s extra inverted-polarity pass when the normal pass finds no decodes.
For a file like the VAG Suran .sub: the first HIGH pulse is 133 µs, so VAG’s Reset condition (300±79 or 500±79 µs) is never met. In both codebases the decoder would stay in Reset and never emit a decode; neither would create a capture from that stream. ProtoPirate would show “No ProtoPirate protocol detected”; KAT would add no capture. So for that case the behavior is aligned; fixing it would require something like trying decode from multiple start indices (sliding window) or trimming to the first VAG-like preamble, in either codebase.