Introducción
El Flipper Zero es un dispositivo multifunción de hacking, pruebas de seguridad y exploración de protocolos de radiofrecuencia (RF). Entre sus características más destacadas está la capacidad de transmitir y recibir señales SubGHz, utilizando el chip Texas Instruments CC1101, un transceptor de radio programable de bajo consumo.
SubGHz hace referencia al rango de frecuencias de radio por debajo de 1 GHz (típicamente entre 300 MHz y 928 MHz, según la región). Estas frecuencias son usadas por dispositivos como controles remotos de garajes o sensores inalámbricos (temperatura, movimiento, alarmas).
El dispositivo puede grabar señales inalámbricas y reproducirlas (dependiendo de la codificación). Soporta las modulaciones AM y FM. Por defecto se encuentran configuradas las sub-modulaciones OOK (AM) y 2FSK (FM).
OOK_270→ OOK con ancho de banda 270 kHz.OOK_650→ OOK con 650 kHz.2FSK_238→ 2-FSK con desviación de 2,38 kHz.2FSK_476→ 2-FSK con desviación de 47,6 kHz.
Esto convierte al Flipper Zero en una herramienta versátil para generar, analizar y probar señales SubGHz de manera sencilla. Se ha desarrollado un script que crea archivos en el formato .sub que soporta el dispositivo para enviar señales mediante las duraciones de los pulsos. Por ejemplo si se quiere enviar los datos binarios 1001110 y la duración mínima del pulso es de 100 ms, se enviarán los pulsos con duraciones 100 -200 300 -100
Uso del SubGhz Generator
Este script permite crear archivos .sub para el Flipper Zero en formato RAW o BinRAW, los cuales son usados para almacenar y reproducir señales capturadas o generadas. En la documentación se recoge el formato de los archivos en detalle
- RAW: Representa las señales como secuencias de tiempos (
RAW_Data) de pulsos. - BinRAW: Representa las señales como cadenas binarias organizadas en bloques de datos (
Bit_RAWyData_RAW).
El script convierte texto, una secuencia binaria o duraciones predefinidas en estos formatos para que puedan ser usados por el Flipper Zero. El script se ejecuta desde la línea de comandos:
python3 flipper_subghz.py [modo] [archivo_salida] [opciones]
Modo RAW
Genera un archivo .sub con datos en formato RAW, por ejemplo: crear un archivo desde texto codificado:
python3 flipper_subghz.py raw out.sub --freq 433920000 --preset OOK_650 --text "HELLO" --te 500
raw→ modo RAW.out.sub→ archivo de salida.--freq→ frecuencia en Hz (ej. 433.92 MHz, común en controles remotos).--preset→ configuración del CC1101 (OOK_650,OOK_270, etc.).--text→ el mensaje a convertir en bits.--te→ unidad de tiempo en microsegundos para definir la duración de cada bit.
Ejemplo con binario:
python3 flipper_subghz.py raw garage.sub --freq 433920000 --preset OOK_270 --binary 1010101 --te 600
Modo BinRAW
Genera un archivo .sub con datos en formato BinRAW, por ejemplo, con texto:
python3 flipper_subghz.py binraw out.sub --freq 433920000 --preset OOK_650 --text "HELLO" --te 600
Ejemplo con binario:
python3 flipper_subghz.py binraw garage.sub --freq 433920000 --preset 2FSK_238 --binary 11001100 --te 500
Ejemplo con bloques manuales:
python3 flipper_subghz.py binraw out.sub --freq 433920000 --preset 2FSK_476 --blocks 8:FF 16:AABB --te 600
8:FF: bloque de 8 bits con valor hexadecimalFF.16:AABB: bloque de 16 bits con valorAABB.
Ejemplos prácticos
- Recrear señal simple de un control remoto:
python3 flipper_subghz.py raw remote.sub --freq 433920000 --preset OOK_270 --binary 10110011 --te 500 - Convertir texto en señal SubGHz:
python3 flipper_subghz.py binraw text_signal.sub --freq 868350000 --preset 2FSK_238 --text "TEST" --te 700 - Definir manualmente bloques de datos para pruebas:
python3 flipper_subghz.py binraw custom.sub --freq 315000000 --preset OOK_650 --blocks 12:ABC 8:FF --te 400
Código fuente
#!/usr/bin/env python3
"""
Optimized Flipper Zero .sub generator (RAW & BinRAW)
"""
import argparse
import sys
from typing import List, Tuple
# --- Constants
MAX_RAW_VALUES_PER_LINE = 512
MAX_BINRAW_BITS = 4096
# --- Presets
PRESET_MAP = {
"OOK_270": "FuriHalSubGhzPresetOok270Async",
"OOK_650": "FuriHalSubGhzPresetOok650Async",
"2FSK_238": "FuriHalSubGhzPreset2FSKDev238Async",
"2FSK_476": "FuriHalSubGhzPreset2FSKDev476Async",
}
# --- Core Classes
class SubGhzSubFile:
FILETYPE_RAW = "Flipper SubGhz RAW File"
VERSION = 1
def __init__(self, frequency: int, preset_key: str):
if preset_key not in PRESET_MAP:
raise ValueError(f"Unknown preset '{preset_key}'. Allowed: {', '.join(PRESET_MAP.keys())}")
self.frequency = frequency
self.preset = PRESET_MAP[preset_key]
def _write_header(self, f, filetype_str: str):
f.write(f"Filetype: {filetype_str}\nVersion: {self.VERSION}\nFrequency: {self.frequency}\n".encode())
def _write_preset(self, f):
f.write(f"Preset: {self.preset}\n".encode())
def write_raw(self, filename: str, raw_timings_lines: List[List[int]]):
with open(filename, "wb") as f:
self._write_header(f, self.FILETYPE_RAW)
self._write_preset(f)
f.write(b"Protocol: RAW\n")
for line in raw_timings_lines:
for i in range(0, len(line), MAX_RAW_VALUES_PER_LINE):
chunk = line[i:i+MAX_RAW_VALUES_PER_LINE]
f.write(f"RAW_Data: {' '.join(map(str, chunk))}\n".encode())
def write_binraw(self, filename: str, total_bits: int, te: int, blocks: List[Tuple[int, bytes]]):
with open(filename, "wb") as f:
self._write_header(f, self.FILETYPE_RAW)
self._write_preset(f)
f.write(b"Protocol: BinRAW\n")
f.write(f"Bit: {total_bits}\nTE: {te}\n".encode())
for bitlen, data in blocks:
f.write(f"Bit_RAW: {bitlen}\n".encode())
f.write(f"Data_RAW: {' '.join(f'{b:02X}' for b in data)}\n".encode())
# --- Utilities
def bits_to_bytes(bits: str) -> bytes:
padded = bits + '0' * ((8 - len(bits) % 8) % 8)
return int(padded, 2).to_bytes(len(padded)//8, 'big') if bits else b''
def string_to_bits_utf8(text: str) -> str:
return ''.join(f'{b:08b}' for b in text.encode('utf-8'))
def bits_to_raw_timings(bits: str, te: int) -> List[int]:
if not bits:
return []
timings = [te] # first bit positive
last_sign = 1
last_bit = bits[0]
for bit in bits[1:]:
if bit == last_bit:
timings[-1] += te * last_sign # accumulate duration
else:
last_sign *= -1
timings.append(te * last_sign)
last_bit = bit
return timings
def split_bits_into_blocks(bits: str, max_bits: int) -> List[str]:
return [bits[i:i+max_bits] for i in range(0, len(bits), max_bits)]
# --- CLI
def build_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="flipper_subghz.py",
description="Generate Flipper Zero .sub files (RAW/BinRAW) from timings, binary strings, or UTF-8 text.",
epilog="Examples:\n"
"RAW from text: flipper_subghz.py raw out.sub --freq 433920000 --preset OOK_650 --text 'HELLO' --te 500\n"
"BinRAW from binary: flipper_subghz.py binraw out.sub --freq 433920000 --preset OOK_650 --binary 1010101 --te 600\n",
formatter_class=argparse.RawDescriptionHelpFormatter
)
subparsers = parser.add_subparsers(dest="command", required=True)
raw_p = subparsers.add_parser("raw", help="Create RAW .sub file")
raw_p.add_argument("output")
raw_p.add_argument("--freq", type=int, required=True)
raw_p.add_argument("--preset", choices=PRESET_MAP.keys(), required=True)
grp_raw = raw_p.add_mutually_exclusive_group(required=True)
grp_raw.add_argument("--timings", nargs="+")
grp_raw.add_argument("--binary")
grp_raw.add_argument("--text")
raw_p.add_argument("--te", type=int)
raw_p.add_argument("--preamble")
bin_p = subparsers.add_parser("binraw", help="Create BinRAW .sub file")
bin_p.add_argument("output")
bin_p.add_argument("--freq", type=int, required=True)
bin_p.add_argument("--preset", choices=PRESET_MAP.keys(), required=True)
grp_bin = bin_p.add_mutually_exclusive_group(required=True)
grp_bin.add_argument("--blocks", nargs="+")
grp_bin.add_argument("--binary")
grp_bin.add_argument("--text")
bin_p.add_argument("--te", type=int, required=True)
bin_p.add_argument("--preamble")
return parser
# --- Mode Handlers
def run_raw_mode(args):
sf = SubGhzSubFile(args.freq, args.preset)
if args.timings:
lines = [list(map(int, g.strip().split())) for g in args.timings]
sf.write_raw(args.output, lines)
print(f"Wrote RAW .sub to {args.output}")
return
if not args.te:
sys.exit("Error: --te required for binary/text input")
bits = args.binary if args.binary else string_to_bits_utf8(args.text)
if args.preamble:
bits = args.preamble + bits
timings = bits_to_raw_timings(bits, args.te)
sf.write_raw(args.output, [timings])
print(f"Wrote RAW .sub to {args.output}")
def run_binraw_mode(args):
sf = SubGhzSubFile(args.freq, args.preset)
if args.blocks:
blocks, total_bits = [], 0
for b in args.blocks:
bitlen_str, hexdata = b.split(":")
bitlen = int(bitlen_str)
data = bytes.fromhex(hexdata)
blocks.append((bitlen, data))
total_bits += bitlen
if total_bits > MAX_BINRAW_BITS:
sys.exit(f"Error: total bits ({total_bits}) exceed 4096 BitRAW limit.")
sf.write_binraw(args.output, total_bits, args.te, blocks)
print(f"Wrote BinRAW .sub to {args.output}, total bits: {total_bits}")
return
bits = args.binary if args.binary else string_to_bits_utf8(args.text)
if args.preamble:
bits = args.preamble + bits
total_bits = len(bits)
if total_bits > MAX_BINRAW_BITS:
sys.exit(f"Error: total bits ({total_bits}) exceed 4096 BitRAW limit.")
block_bytes = bits_to_bytes(bits)
blocks_for_file = [(total_bits, block_bytes)]
sf.write_binraw(args.output, total_bits, args.te, blocks_for_file)
print(f"Wrote BinRAW .sub to {args.output}, total bits: {total_bits}")
# --- Main
def main():
args = build_arg_parser().parse_args()
if args.command == "raw":
run_raw_mode(args)
else:
run_binraw_mode(args)
if __name__ == "__main__":
main()