Unified rfcat helper for sub-GHz RF work
Project description
rfox
Unified rfcat-py3 helper for sub-GHz RF capture and analysis. One CLI, one interactive menu, one capture format. Built on top of rfcat / rflib so it works with any YARDStickOne, RfCat-compatible CC1111 dongle, or DonsDongle.
./rfox # interactive menu
./rfox decode --hex aabbccdd -m manchester # direct CLI
Why rfox
rfcat ships a powerful Python library (rflib) and an interactive shell,
but day-to-day RF work tends to be the same handful of recipes — capture a
signal, sweep a band, decode some Manchester, identify a CRC, find a sync
word — repeated with slightly different parameters. The community
RfCatHelpers project showed
how useful those recipes are as standalone scripts, but each one
re-implements the same dongle init, defines its own incompatible CLI, and
uses its own ad-hoc capture format.
rfox consolidates that workflow into:
- one tool with consistent flags everywhere (
-fis always Hz,-ris always bps,-mis always one ofOOK / 2FSK / GFSK / MSK / 4FSK); - an interactive menu for when you don't remember the flags — it walks you through every option with the default shown;
- one capture file format (libpcap, DLT_USER0, RFCT pseudo-header) so anything you capture in one mode can be replayed, decoded, diffed, or CRC-checked by any other command;
- named profiles (
~/.rfcat/profiles.json) and built-in presets for common protocols, so you stop re-typing six flags for every command.
The interactive menu and the CLI share a single dispatcher, so they can never drift apart — adding a new command exposes it in both modes automatically.
Installation
rfox is a standalone tool that depends on rfcat-py3 for the underlying rflib library.
pip install rfox
Optional dependencies:
pip install 'rfox[specan]' # enables `rfox specan` (PySide6)
For development:
git clone https://github.com/qu-crypt/rfox.git
cd rfox
python3 -m venv .venv && source .venv/bin/activate
pip install -e .
System libraries:
- libusb-1.0 (Linux:
apt install libusb-1.0-0, macOS:brew install libusb, Windows: see the rfcat README) - non-root USB access on Linux requires the udev rules from the rfcat-py3 repo's
etc/udev/
Verify:
./rfox --help
./rfox devices # lists attached dongles
Quick start
Five things you'll almost certainly want to do:
# 1. Sweep a band looking for activity
./rfox sweep --start 433e6 --stop 434e6 --step 50e3 --ascii-bar
# 2. Capture five presses of a 433 MHz remote into a pcap
./rfox replay capture --preset ev1527 -n 5 -o /tmp/garage.pcap
# 3. Replay them
./rfox replay replay --input /tmp/garage.pcap --use-capture-cfg
# 4. Find the rolling-counter byte (or rule it out)
./rfox diff --input /tmp/garage.pcap
# 5. Identify the protocol's checksum
./rfox crc --input /tmp/garage.pcap
No dongle? The analysis subcommands work entirely on captures or hex strings:
./rfox decode --hex aabbccdd -m auto
./rfox find-sync --hex aaaaaad391deadbeef
./rfox crc --hex 12345612fd
./rfox diff --hex aabbcc00 --hex aabbcc01 --hex aabbcc02
Command reference
| group | command | what it does | needs hw |
|---|---|---|---|
| dongle | devices |
list connected RfCat dongles | ✓ |
specan |
open the PySide6 spectrum analyser | ✓ | |
| RX | scan |
RX loop, hex-print frames, optional pcap log | ✓ |
sweep |
RSSI sweep across --start..--stop, optional CSV |
✓ | |
logger |
headless RX → append every frame to a pcap | ✓ | |
| TX | transmit |
binary string → OOK (raw or PWM-encoded) | ✓ |
tx-hex |
raw bytes given as hex on the CLI | ✓ | |
brute |
iterate a key space and transmit each value | ✓ | |
fuzz |
bit/byte-mutate a seed frame, retransmit | ✓ | |
| capture/replay | replay capture |
record N frames to a pcap | ✓ |
replay replay |
replay a pcap | ✓ | |
| analysis | decode |
manchester/diff-manchester/PWM/raw decoders | |
find-sync |
candidate sync words in a capture | ||
find-repeats |
repeating bit patterns | ||
crc |
try CRC-8/CRC-16 polynomials over capture tails | ||
diff |
bit-by-bit diff (find rolling-counter fields) | ||
decode-wav |
OOK decoder for WAV recordings | ||
| workflow | profile {save,show,list,delete} |
named radio configs | |
preset {list,show} |
built-in protocol presets |
Each command also has full --help:
./rfox replay capture --help
./rfox find-sync --help
Capture file format
Every capture/replay command shares one libpcap file
(magic 0xa1b2c3d4, link type DLT_USER0 = 147). Each packet payload
starts with a 24-byte RFCT pseudo-header recording the radio
configuration at capture time, followed by the raw on-air bytes:
struct rfct_pseudo_hdr { // little-endian
uint8_t magic[4]; // "RFCT"
uint8_t version; // 1
uint8_t modulation; // 0=OOK 1=2FSK 2=GFSK 3=MSK 4=4FSK
uint32_t freq_hz;
uint32_t drate_bps;
uint32_t chanbw_hz;
int16_t rssi_dbm10; // dBm * 10, signed
uint16_t sync_word; // 0 if none
uint8_t payload[];
};
Read with the dataclass-based reader:
from rflib.rfox import pcap
for frame in pcap.read("garage.pcap"):
print(frame.ts, frame.cfg.summary(), frame.payload.hex())
Or open it in Wireshark / tshark — frames will appear as data under
USER0. A future Lua dissector could decode the pseudo-header field by
field, but the raw bytes are usable today with any pcap parser.
Profiles & presets
Save a configuration once, reuse it forever:
./rfox profile save mygate -f 433.92e6 -r 2400 -m OOK --chanbw 325000
./rfox replay replay --input cap.pcap --profile mygate
Or jump straight to a built-in:
./rfox preset list
./rfox scan --preset ev1527
./rfox tx-hex --preset keyfob315 --hex aabbcc
CLI flags override the profile/preset, so --preset ev1527 --freq 433.95e6
works.
Built-in presets:
| name | freq | drate | mod | typical use |
|---|---|---|---|---|
ev1527 |
433.92 MHz | 2400 | OOK | generic 433 MHz remotes |
pt2262 |
433.92 MHz | 1200 | OOK | older garage / gate openers |
keeloq |
433.92 MHz | 2000 | OOK | rolling-code keyfobs |
keyfob315 |
315 MHz | 2400 | OOK | US automotive key fobs |
srd868 |
868.35 MHz | 4800 | 2FSK | EU short-range devices |
ism915 |
915 MHz | 38 400 | 2FSK | US ISM band |
tpms433 |
433.92 MHz | 19 200 | 2FSK | tyre-pressure sensors |
Adding a new command
Drop a module under rflib/rfox/commands/, expose three functions:
HELP = "one-line description shown in --help and the menu"
def add_args(parser):
"""Attach argparse args. Use _common.add_radio_args / add_dongle_args."""
def prompt(args): # optional
"""Walk the user through args interactively."""
def run(args):
"""Do the work. Return an exit code."""
Register it in rflib/rfox/commands/__init__.py. The CLI dispatcher and
the interactive menu both pick it up automatically.
Use _common.cfg_from_args(args) to honour --preset / --profile
before merging in CLI overrides — that's how every existing command keeps
its behaviour consistent.
Tests
python -m unittest tests.test_rfox
The pcap roundtrip, presets, profiles, and analysis subcommands
(decode, find-sync, find-repeats, crc, diff, decode-wav) are
covered without hardware. The hardware-using TX commands are smoke-tested
by patching open_dongle to return FakeRfCat.
Versioning & stability
rfox is versioned independently of rfcat. Anything in
rflib.rfox.commands is part of the public CLI; flag names and the
pcap pseudo-header layout are stable within a major version. Anything
under rflib.rfox (other than the public command modules) is internal
and may change without notice.
Contributing
Pull requests welcome. Please:
- Open an issue first for non-trivial changes.
- Add a test under
tests/test_rfox.pythat exercises your code without hardware (useFakeRfCatfor TX commands). - Match the existing argparse flag style (
-f/--freqis Hz,-r/--drateis bps, etc.). - Don't add commands whose primary purpose is signal interference or disruption.
By contributing, you agree your contribution is licensed under the MIT
License (see LICENSE).
License
rfox's own source — the entry script, the rflib/rfox/ package,
and tests/test_rfox.py — is released under the MIT License. See
LICENSE.
It builds on top of rfcat-py3, which is distributed under the MIT License.
Acknowledgements
- @atlas0fd00m for the original rfcat and rflib.
- @qu-crypt for the Python 3 port (rfcat-py3) and dongle firmware.
- @AndrewMohawk — the original RfCatHelpers project that mapped out the most useful day-to-day recipes and motivated this tool.
- The Ubertooth, GNU Radio, and HackRF communities for decades of prior art in sub-GHz tooling.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rfox-1.0.3.tar.gz.
File metadata
- Download URL: rfox-1.0.3.tar.gz
- Upload date:
- Size: 33.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9098e851b8b8c57faccfdc70abbcff5f1454d84ae4c327bb2a8f16dc8899fa26
|
|
| MD5 |
1933c0bd37fcce9eda283f190d0b5b31
|
|
| BLAKE2b-256 |
72e3c007962767b62d57ee4a2fd117ccf7310ed6a9e9aa09dd780edf082f01b9
|
Provenance
The following attestation bundles were made for rfox-1.0.3.tar.gz:
Publisher:
ci.yml on qu-crypt/rfox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rfox-1.0.3.tar.gz -
Subject digest:
9098e851b8b8c57faccfdc70abbcff5f1454d84ae4c327bb2a8f16dc8899fa26 - Sigstore transparency entry: 1397074745
- Sigstore integration time:
-
Permalink:
qu-crypt/rfox@9d87b4e377a1587013abfb7424448939923da8c8 -
Branch / Tag:
refs/tags/v1.0.3 - Owner: https://github.com/qu-crypt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9d87b4e377a1587013abfb7424448939923da8c8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file rfox-1.0.3-py3-none-any.whl.
File metadata
- Download URL: rfox-1.0.3-py3-none-any.whl
- Upload date:
- Size: 37.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
044feefc4720dbd07255feb4acaf754293a9bbed0d23a02c011c455b59697928
|
|
| MD5 |
2bad229d72d3ebf5ee165b68ee941409
|
|
| BLAKE2b-256 |
70ed9b2bc569b6bce28d714abfe90c811f67dd1748edd762f395cd6638837b40
|
Provenance
The following attestation bundles were made for rfox-1.0.3-py3-none-any.whl:
Publisher:
ci.yml on qu-crypt/rfox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rfox-1.0.3-py3-none-any.whl -
Subject digest:
044feefc4720dbd07255feb4acaf754293a9bbed0d23a02c011c455b59697928 - Sigstore transparency entry: 1397074768
- Sigstore integration time:
-
Permalink:
qu-crypt/rfox@9d87b4e377a1587013abfb7424448939923da8c8 -
Branch / Tag:
refs/tags/v1.0.3 - Owner: https://github.com/qu-crypt
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@9d87b4e377a1587013abfb7424448939923da8c8 -
Trigger Event:
push
-
Statement type: