4 Signal Preprocessing
Craig edited this page 2023-02-07 15:51:42 -06:00

GNU Radio Companion

GNU Radio can be used to preprocess and display the signal sent to rtl_433. This can be helpful when processing IQ samples from a HackRF. The HackRF has a minimum sample rate (bandwidth) of 2 Msps and has a significant DC offset spike, which seems to cause rtl_433 to fail to detect weak signals. In this case, it is beneficial to preprocess the signal before sending it into rtl_433. GNU Radio can be used for this. The trick is to create a named pipe and set GNU Radio to output to that pipe. Then, use the -r option in rtl_433 to read from that named pipe. Because of the behavior of a named pipe, rtl_433 won't read off the end of the pipe and exit when finished like it would when reading from a file. Instead, it will block as it waits for more samples from GNU radio. Moreover, no data is saved to disk with this method. The details for how to achieve this are as follows:

  1. Create a named pipe as follows. Note that the name is important. The rtl_433 program will use this name to detect the sample rate and bandwidth. See "File names" for details on how this works. In this example, we use a sample rate of 500 ksps and a center frequency of 433.95 Mhz, with 16-bit signed interleaved IQ samples (.cs16).

    mkfifo 001_433.95Mhz_500.0ksps.cs16
    
  2. Start rtl_433 with the required options. It seems that -Y autolevel is necessary for this method as the noise floor can change significantly depending on how the gain is configured in the GNU Radio. In this example, we also add -M time:iso to get an absolute timestamp (instead of a relative count of the number of seconds since the program started).

    rtl_433 -r 001_433.95Mhz_500.0ksps.cs16 -Y autolevel -M time:iso
    
  3. Add a Complex To IShort block and set the scale factor to 32767 (as the docs suggest). This will interleave the IQ samples as 16-bit signed integers, so that we match the file format we specified above. Now, add a File Sink. Set the file name to match the name of the pipe created with mkfifo above and set the input type to "short".

  4. Last, start the flowgraph and rtl_433 should begin displaying output (showing that the noise level is being adjusted). If a signal is received, the output data will be displayed in the terminal window running rtl_433.

Note: This method has only been tested on Linux.

Example Flowgraph

Setting all of this up every time you want to use GNU Radio can be a pain, especially if you adjust the center frequency or bandwidth often - both of which require changing the file name. The example GNU Radio companion flowgraph below automatically creates the correctly-named FIFO (named pipe) in the /tmp/ directory and launches rtl_433 with the parameters needed to pull from that pipe. On exit, it cleans up the named pipe. This means you can change the center frequency or bandwidth and just click the "run" button in GNU Radio companion to start the flowgraph. The flowgraph is available below in the Flowgraph Source section.

Example GNU Radio Companion flowgraph that connects to rtl_433

A screenshot of this flowgraph running from the terminal is shown below. Example GNU Radio Companion flowgraph while is is running

Flowgraph Source

The following is the source code for the rtl_433_connect.grc example GNU Radio Companion flowgraph. Copy and paste this into a file named rtl_433_connect.grc and open in GNU Radio Companion to use it.

options:
  parameters:
    author: surface
    catch_exceptions: 'True'
    category: '[GRC Hier Blocks]'
    cmake_opt: ''
    comment: ''
    copyright: ''
    description: ''
    gen_cmake: 'On'
    gen_linking: dynamic
    generate_options: qt_gui
    hier_block_src_path: '.:'
    id: rtl_433_connect
    max_nouts: '0'
    output_language: python
    placement: (0,0)
    qt_qss_theme: ''
    realtime_scheduling: ''
    run: 'True'
    run_command: '{python} -u {filename}'
    run_options: prompt
    sizing_mode: fixed
    thread_safe_setters: ''
    title: RTL_433 Connect
    window_size: (1000,1000)
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [8, 8]
    rotation: 0
    state: enabled

blocks:
- name: baseband_freq
  id: variable_qtgui_range
  parameters:
    comment: 'Frequency the HackRF

      is tuned to'
    gui_hint: ''
    label: ''
    min_len: '200'
    orient: QtCore.Qt.Horizontal
    rangeType: float
    start: 10e6
    step: 5e6
    stop: 3000e6
    value: 433e6
    widget: counter_slider
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [280, 8.0]
    rotation: 0
    state: true
- name: file_sink_output
  id: variable
  parameters:
    comment: 'The file is named such that

      rtl_433 can parse out the center

      frequency and sample rate.'
    value: '"/tmp/initial-grc-output"'
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [864, 16.0]
    rotation: 0
    state: enabled
- name: filter_bandwidth
  id: variable
  parameters:
    comment: ''
    value: 75e3
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [24, 168.0]
    rotation: 0
    state: enabled
- name: filter_decimation
  id: variable
  parameters:
    comment: ''
    value: '30'
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [592, 16.0]
    rotation: 0
    state: enabled
- name: filter_samp_rate
  id: variable
  parameters:
    comment: Filter output sample rate
    value: samp_rate / filter_decimation
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [592, 88.0]
    rotation: 0
    state: enabled
- name: filter_transition_width
  id: variable
  parameters:
    comment: ''
    value: 5e3
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [144, 168.0]
    rotation: 0
    state: enabled
- name: output_file_name
  id: variable
  parameters:
    comment: 'The file is named such that

      rtl_433 can parse out the center

      frequency and sample rate.'
    value: '"/tmp/001_" + str(tune_freq / 1e6) + "Mhz_" + "{:.3f}".format(filter_samp_rate
      / 1e3) + "ksps.cs16"'
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [600, 184.0]
    rotation: 0
    state: enabled
- name: samp_rate
  id: variable
  parameters:
    comment: ''
    value: 5e6
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [176, 8.0]
    rotation: 0
    state: enabled
- name: tune_freq
  id: variable
  parameters:
    comment: "Frequency we are \ntuned to in software"
    value: 433.95e6
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [448, 16.0]
    rotation: 0
    state: enabled
- name: blocks_complex_to_interleaved_short_1
  id: blocks_complex_to_interleaved_short
  parameters:
    affinity: ''
    alias: ''
    comment: ''
    maxoutbuf: '0'
    minoutbuf: '0'
    scale_factor: '32767'
    vector_output: 'False'
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [608, 448.0]
    rotation: 0
    state: true
- name: blocks_file_sink_0
  id: blocks_file_sink
  parameters:
    affinity: ''
    alias: ''
    append: 'True'
    comment: ''
    file: file_sink_output
    type: short
    unbuffered: 'False'
    vlen: '1'
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [800, 440.0]
    rotation: 0
    state: enabled
- name: freq_xlating_fir_filter_xxx_0
  id: freq_xlating_fir_filter_xxx
  parameters:
    affinity: ''
    alias: ''
    center_freq: tune_freq - baseband_freq
    comment: ''
    decim: filter_decimation
    maxoutbuf: '0'
    minoutbuf: '0'
    samp_rate: samp_rate
    taps: firdes.low_pass(1, samp_rate, filter_bandwidth, filter_transition_width)
    type: ccc
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [352, 416.0]
    rotation: 0
    state: true
- name: import_0
  id: import
  parameters:
    alias: ''
    comment: ''
    imports: import os
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [768, 16.0]
    rotation: 0
    state: true
- name: osmosdr_source_0
  id: osmosdr_source
  parameters:
    affinity: ''
    alias: ''
    ant0: ''
    ant1: ''
    ant10: ''
    ant11: ''
    ant12: ''
    ant13: ''
    ant14: ''
    ant15: ''
    ant16: ''
    ant17: ''
    ant18: ''
    ant19: ''
    ant2: ''
    ant20: ''
    ant21: ''
    ant22: ''
    ant23: ''
    ant24: ''
    ant25: ''
    ant26: ''
    ant27: ''
    ant28: ''
    ant29: ''
    ant3: ''
    ant30: ''
    ant31: ''
    ant4: ''
    ant5: ''
    ant6: ''
    ant7: ''
    ant8: ''
    ant9: ''
    args: '""'
    bb_gain0: '35'
    bb_gain1: '20'
    bb_gain10: '20'
    bb_gain11: '20'
    bb_gain12: '20'
    bb_gain13: '20'
    bb_gain14: '20'
    bb_gain15: '20'
    bb_gain16: '20'
    bb_gain17: '20'
    bb_gain18: '20'
    bb_gain19: '20'
    bb_gain2: '20'
    bb_gain20: '20'
    bb_gain21: '20'
    bb_gain22: '20'
    bb_gain23: '20'
    bb_gain24: '20'
    bb_gain25: '20'
    bb_gain26: '20'
    bb_gain27: '20'
    bb_gain28: '20'
    bb_gain29: '20'
    bb_gain3: '20'
    bb_gain30: '20'
    bb_gain31: '20'
    bb_gain4: '20'
    bb_gain5: '20'
    bb_gain6: '20'
    bb_gain7: '20'
    bb_gain8: '20'
    bb_gain9: '20'
    bw0: 3e6
    bw1: '0'
    bw10: '0'
    bw11: '0'
    bw12: '0'
    bw13: '0'
    bw14: '0'
    bw15: '0'
    bw16: '0'
    bw17: '0'
    bw18: '0'
    bw19: '0'
    bw2: '0'
    bw20: '0'
    bw21: '0'
    bw22: '0'
    bw23: '0'
    bw24: '0'
    bw25: '0'
    bw26: '0'
    bw27: '0'
    bw28: '0'
    bw29: '0'
    bw3: '0'
    bw30: '0'
    bw31: '0'
    bw4: '0'
    bw5: '0'
    bw6: '0'
    bw7: '0'
    bw8: '0'
    bw9: '0'
    clock_source0: ''
    clock_source1: ''
    clock_source2: ''
    clock_source3: ''
    clock_source4: ''
    clock_source5: ''
    clock_source6: ''
    clock_source7: ''
    comment: ''
    corr0: '0'
    corr1: '0'
    corr10: '0'
    corr11: '0'
    corr12: '0'
    corr13: '0'
    corr14: '0'
    corr15: '0'
    corr16: '0'
    corr17: '0'
    corr18: '0'
    corr19: '0'
    corr2: '0'
    corr20: '0'
    corr21: '0'
    corr22: '0'
    corr23: '0'
    corr24: '0'
    corr25: '0'
    corr26: '0'
    corr27: '0'
    corr28: '0'
    corr29: '0'
    corr3: '0'
    corr30: '0'
    corr31: '0'
    corr4: '0'
    corr5: '0'
    corr6: '0'
    corr7: '0'
    corr8: '0'
    corr9: '0'
    dc_offset_mode0: '0'
    dc_offset_mode1: '0'
    dc_offset_mode10: '0'
    dc_offset_mode11: '0'
    dc_offset_mode12: '0'
    dc_offset_mode13: '0'
    dc_offset_mode14: '0'
    dc_offset_mode15: '0'
    dc_offset_mode16: '0'
    dc_offset_mode17: '0'
    dc_offset_mode18: '0'
    dc_offset_mode19: '0'
    dc_offset_mode2: '0'
    dc_offset_mode20: '0'
    dc_offset_mode21: '0'
    dc_offset_mode22: '0'
    dc_offset_mode23: '0'
    dc_offset_mode24: '0'
    dc_offset_mode25: '0'
    dc_offset_mode26: '0'
    dc_offset_mode27: '0'
    dc_offset_mode28: '0'
    dc_offset_mode29: '0'
    dc_offset_mode3: '0'
    dc_offset_mode30: '0'
    dc_offset_mode31: '0'
    dc_offset_mode4: '0'
    dc_offset_mode5: '0'
    dc_offset_mode6: '0'
    dc_offset_mode7: '0'
    dc_offset_mode8: '0'
    dc_offset_mode9: '0'
    freq0: baseband_freq
    freq1: 100e6
    freq10: 100e6
    freq11: 100e6
    freq12: 100e6
    freq13: 100e6
    freq14: 100e6
    freq15: 100e6
    freq16: 100e6
    freq17: 100e6
    freq18: 100e6
    freq19: 100e6
    freq2: 100e6
    freq20: 100e6
    freq21: 100e6
    freq22: 100e6
    freq23: 100e6
    freq24: 100e6
    freq25: 100e6
    freq26: 100e6
    freq27: 100e6
    freq28: 100e6
    freq29: 100e6
    freq3: 100e6
    freq30: 100e6
    freq31: 100e6
    freq4: 100e6
    freq5: 100e6
    freq6: 100e6
    freq7: 100e6
    freq8: 100e6
    freq9: 100e6
    gain0: '15'
    gain1: '10'
    gain10: '10'
    gain11: '10'
    gain12: '10'
    gain13: '10'
    gain14: '10'
    gain15: '10'
    gain16: '10'
    gain17: '10'
    gain18: '10'
    gain19: '10'
    gain2: '10'
    gain20: '10'
    gain21: '10'
    gain22: '10'
    gain23: '10'
    gain24: '10'
    gain25: '10'
    gain26: '10'
    gain27: '10'
    gain28: '10'
    gain29: '10'
    gain3: '10'
    gain30: '10'
    gain31: '10'
    gain4: '10'
    gain5: '10'
    gain6: '10'
    gain7: '10'
    gain8: '10'
    gain9: '10'
    gain_mode0: 'False'
    gain_mode1: 'False'
    gain_mode10: 'False'
    gain_mode11: 'False'
    gain_mode12: 'False'
    gain_mode13: 'False'
    gain_mode14: 'False'
    gain_mode15: 'False'
    gain_mode16: 'False'
    gain_mode17: 'False'
    gain_mode18: 'False'
    gain_mode19: 'False'
    gain_mode2: 'False'
    gain_mode20: 'False'
    gain_mode21: 'False'
    gain_mode22: 'False'
    gain_mode23: 'False'
    gain_mode24: 'False'
    gain_mode25: 'False'
    gain_mode26: 'False'
    gain_mode27: 'False'
    gain_mode28: 'False'
    gain_mode29: 'False'
    gain_mode3: 'False'
    gain_mode30: 'False'
    gain_mode31: 'False'
    gain_mode4: 'False'
    gain_mode5: 'False'
    gain_mode6: 'False'
    gain_mode7: 'False'
    gain_mode8: 'False'
    gain_mode9: 'False'
    if_gain0: '40'
    if_gain1: '20'
    if_gain10: '20'
    if_gain11: '20'
    if_gain12: '20'
    if_gain13: '20'
    if_gain14: '20'
    if_gain15: '20'
    if_gain16: '20'
    if_gain17: '20'
    if_gain18: '20'
    if_gain19: '20'
    if_gain2: '20'
    if_gain20: '20'
    if_gain21: '20'
    if_gain22: '20'
    if_gain23: '20'
    if_gain24: '20'
    if_gain25: '20'
    if_gain26: '20'
    if_gain27: '20'
    if_gain28: '20'
    if_gain29: '20'
    if_gain3: '20'
    if_gain30: '20'
    if_gain31: '20'
    if_gain4: '20'
    if_gain5: '20'
    if_gain6: '20'
    if_gain7: '20'
    if_gain8: '20'
    if_gain9: '20'
    iq_balance_mode0: '0'
    iq_balance_mode1: '0'
    iq_balance_mode10: '0'
    iq_balance_mode11: '0'
    iq_balance_mode12: '0'
    iq_balance_mode13: '0'
    iq_balance_mode14: '0'
    iq_balance_mode15: '0'
    iq_balance_mode16: '0'
    iq_balance_mode17: '0'
    iq_balance_mode18: '0'
    iq_balance_mode19: '0'
    iq_balance_mode2: '0'
    iq_balance_mode20: '0'
    iq_balance_mode21: '0'
    iq_balance_mode22: '0'
    iq_balance_mode23: '0'
    iq_balance_mode24: '0'
    iq_balance_mode25: '0'
    iq_balance_mode26: '0'
    iq_balance_mode27: '0'
    iq_balance_mode28: '0'
    iq_balance_mode29: '0'
    iq_balance_mode3: '0'
    iq_balance_mode30: '0'
    iq_balance_mode31: '0'
    iq_balance_mode4: '0'
    iq_balance_mode5: '0'
    iq_balance_mode6: '0'
    iq_balance_mode7: '0'
    iq_balance_mode8: '0'
    iq_balance_mode9: '0'
    maxoutbuf: '0'
    minoutbuf: '0'
    nchan: '1'
    num_mboards: '1'
    sample_rate: samp_rate
    sync: sync
    time_source0: ''
    time_source1: ''
    time_source2: ''
    time_source3: ''
    time_source4: ''
    time_source5: ''
    time_source6: ''
    time_source7: ''
    type: fc32
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [48, 268.0]
    rotation: 0
    state: true
- name: qtgui_waterfall_sink_x_0
  id: qtgui_waterfall_sink_x
  parameters:
    affinity: ''
    alias: ''
    alpha1: '1.0'
    alpha10: '1.0'
    alpha2: '1.0'
    alpha3: '1.0'
    alpha4: '1.0'
    alpha5: '1.0'
    alpha6: '1.0'
    alpha7: '1.0'
    alpha8: '1.0'
    alpha9: '1.0'
    axislabels: 'True'
    bw: filter_samp_rate
    color1: '0'
    color10: '0'
    color2: '0'
    color3: '0'
    color4: '0'
    color5: '0'
    color6: '0'
    color7: '0'
    color8: '0'
    color9: '0'
    comment: ''
    fc: tune_freq
    fftsize: '8192'
    freqhalf: 'True'
    grid: 'False'
    gui_hint: ''
    int_max: '10'
    int_min: '-140'
    label1: ''
    label10: ''
    label2: ''
    label3: ''
    label4: ''
    label5: ''
    label6: ''
    label7: ''
    label8: ''
    label9: ''
    legend: 'True'
    maxoutbuf: '0'
    minoutbuf: '0'
    name: '""'
    nconnections: '1'
    showports: 'False'
    type: complex
    update_time: '0.025'
    wintype: window.WIN_BLACKMAN_hARRIS
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [616, 360.0]
    rotation: 0
    state: enabled
- name: qtgui_waterfall_sink_x_0_0
  id: qtgui_waterfall_sink_x
  parameters:
    affinity: ''
    alias: ''
    alpha1: '1.0'
    alpha10: '1.0'
    alpha2: '1.0'
    alpha3: '1.0'
    alpha4: '1.0'
    alpha5: '1.0'
    alpha6: '1.0'
    alpha7: '1.0'
    alpha8: '1.0'
    alpha9: '1.0'
    axislabels: 'True'
    bw: samp_rate
    color1: '0'
    color10: '0'
    color2: '0'
    color3: '0'
    color4: '0'
    color5: '0'
    color6: '0'
    color7: '0'
    color8: '0'
    color9: '0'
    comment: ''
    fc: baseband_freq
    fftsize: '8192'
    freqhalf: 'True'
    grid: 'False'
    gui_hint: ''
    int_max: '10'
    int_min: '-140'
    label1: ''
    label10: ''
    label2: ''
    label3: ''
    label4: ''
    label5: ''
    label6: ''
    label7: ''
    label8: ''
    label9: ''
    legend: 'True'
    maxoutbuf: '0'
    minoutbuf: '0'
    name: '""'
    nconnections: '1'
    showports: 'False'
    type: complex
    update_time: '0.025'
    wintype: window.WIN_BLACKMAN_hARRIS
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [336, 296.0]
    rotation: 0
    state: disabled
- name: snippet_0
  id: snippet
  parameters:
    alias: ''
    code: '# Remove the file GNU Radio creates initially.

      os.system("rm " + self.file_sink_output)


      # Create the named pipe that we will output to.

      os.system("mkfifo " + self.output_file_name)


      # Start the RTL_433 program to read from our FIFO in the background

      os.system("rtl_433 -r " + self.output_file_name + " -Y autolevel -M time:iso
      -M level -M protocol &")


      # Set the file sink output to use our named pipe.

      self.set_file_sink_output(self.output_file_name)'
    comment: 'Named pipe setup code

      and execution of rtl_433'
    priority: '0'
    section: main_after_init
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [848, 200.0]
    rotation: 0
    state: enabled
- name: snippet_0_0
  id: snippet
  parameters:
    alias: ''
    code: '# Remove the named pipe

      os.system("rm " + self.output_file_name)'
    comment: Named pipe cleanup code
    priority: '0'
    section: main_after_stop
  states:
    bus_sink: false
    bus_source: false
    bus_structure: null
    coordinate: [848, 320.0]
    rotation: 0
    state: enabled

connections:
- [blocks_complex_to_interleaved_short_1, '0', blocks_file_sink_0, '0']
- [freq_xlating_fir_filter_xxx_0, '0', blocks_complex_to_interleaved_short_1, '0']
- [freq_xlating_fir_filter_xxx_0, '0', qtgui_waterfall_sink_x_0, '0']
- [osmosdr_source_0, '0', freq_xlating_fir_filter_xxx_0, '0']
- [osmosdr_source_0, '0', qtgui_waterfall_sink_x_0_0, '0']

metadata:
  file_format: 1
  grc_version: 3.10.5.0