diff --git a/README.md b/README.md
index 4a488b916b2544e94c5012a859c359a234a6d748..16919f59cd7402bb18908696df1b20d7d0c1a79c 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,77 @@
 # gr-cdc
 
-GNU Radio out-of-tree (OOT) module for ELEN90089_2021_SM1 
\ No newline at end of file
+GNU Radio out-of-tree (OOT) module for ELEN90089\_2021\_SM1 
+
+This module contains code for running the Dynamic Spectrum Access scenario
+for the CDC design project.
+
+## Installation
+
+To install, first clone the gr-cdc repository.
+
+```
+$ git clone https://gitlab.eng.unimelb.edu.au/elen90089-cdc/gr-cdc.git
+$ cd gr-cdc/
+```
+
+Then build and install the module.
+
+```
+$ mkdir build
+$ cd build/
+$ cmake ..
+$ make
+$ sudo make install
+```
+You see a number of blocks installed in GRC under the \[cdc\] category.
+
+
+## Primary User Reference Code
+
+GRC applications for running the Primary User Tx and Rx can be found in the
+`./examples/` directory:
+
+- `pu_tx_1_channel.grc`
+- `pu_tx_4_channel.grc`
+- `pu_rx_1_channel.grc`
+- `pu_rx_4_channel.grc`
+
+The central DSA database code has not yet been integrated into the PU Tx/Rx
+applications.
+
+## DSA Database
+
+Example usage of interfacing with the DSA database code is also available in
+the `./examples/` directory.
+
+- `test_dsa_database.grc`
+
+If you want to test out this code, ensure you have first started an instance
+of the database running on the local machine.
+
+```
+$ ./run_dsa_database.py
+```
+
+The key block that will need to be integrated into your secondary link is the
+*DSA DB Connect*. This block should be used at both SU Tx and Rx. Request to the
+DSA database can then be made by passing commands to the *DSA DB Connect* block
+in the form of
+[GNU Radio Messages](https://wiki.gnuradio.org/index.php/Message_Passing).
+
+Example message formation for various command is shown in the following.
+
+```
+# get packet at Tx
+msg = pmt.cons(pmt.intern('get_pkt'), pmt.PMT_NIL)
+
+# put packet
+msg = pmt.cons(pmt.intern('put_pkt), payload)
+
+# get current KPI report
+msg = pmt.cons(pmt.intern('get_kpi'), pmt.PMT_NIL)
+
+# get current PU mode (development only)
+msg = pmt.cons(pmt.intern('get_mode'), pmt.PMT_NIL)
+```
+
diff --git a/examples/pu_rx_1_channel.grc b/examples/pu_rx_1_channel.grc
new file mode 100644
index 0000000000000000000000000000000000000000..38f3859acd10d3afbcb637682e8612c48d1a782e
--- /dev/null
+++ b/examples/pu_rx_1_channel.grc
@@ -0,0 +1,636 @@
+options:
+  parameters:
+    author: ''
+    category: '[GRC Hier Blocks]'
+    cmake_opt: ''
+    comment: ''
+    copyright: University of Melbourne
+    description: ''
+    gen_cmake: 'On'
+    gen_linking: dynamic
+    generate_options: qt_gui
+    hier_block_src_path: '.:'
+    id: pu_rx_1_channel
+    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: Primary User Rx - 1 Channel
+    window_size: ''
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [8, 8]
+    rotation: 0
+    state: enabled
+
+blocks:
+- name: decim_factor
+  id: variable
+  parameters:
+    comment: ''
+    value: '5'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [608, 12]
+    rotation: 0
+    state: enabled
+- name: fft_len
+  id: variable
+  parameters:
+    comment: ''
+    value: '64'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [710, 12]
+    rotation: 0
+    state: enabled
+- name: rf_freq
+  id: variable_qtgui_range
+  parameters:
+    comment: ''
+    gui_hint: ''
+    label: RF Frequency
+    min_len: '200'
+    orient: Qt.Horizontal
+    rangeType: float
+    start: 70e6
+    step: 1e3
+    stop: 6e9
+    value: 2.45e9
+    widget: counter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [270, 12]
+    rotation: 0
+    state: true
+- name: rx_gain
+  id: variable_qtgui_range
+  parameters:
+    comment: ''
+    gui_hint: ''
+    label: Rx Gain
+    min_len: '200'
+    orient: Qt.Horizontal
+    rangeType: float
+    start: '0'
+    step: '1'
+    stop: '76'
+    value: '40'
+    widget: counter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [404, 12]
+    rotation: 0
+    state: true
+- name: samp_rate
+  id: variable
+  parameters:
+    comment: ''
+    value: 2e6
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [184, 12]
+    rotation: 0
+    state: enabled
+- name: blocks_tagged_stream_to_pdu_0
+  id: blocks_tagged_stream_to_pdu
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    tag: packet_len
+    type: byte
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [583, 307]
+    rotation: 0
+    state: true
+- name: digital_ofdm_rx_0
+  id: digital_ofdm_rx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    cp_len: fft_len // 4
+    fft_len: fft_len
+    header_mod: '"BPSK"'
+    log: 'False'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    occupied_carriers: ([ii for ii in range(-26, 27) if ii not in (-21, -7, 7, 21)],)
+    packet_len_key: rx_len
+    payload_mod: '"QPSK"'
+    pilot_carriers: ((-21, -7, 7, 21,),)
+    pilot_symbols: ((1, 1, 1, -1,),)
+    scramble_bits: 'False'
+    sync_word1: '[0., 0., 0., 0., 0., 0., 0., 1.41421356, 0., -1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 0., 0., 0., 0., 0.]'
+    sync_word2: '[0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1,
+      1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 0, 1, -1, 1, 1, 1, -1, 1, 1,
+      1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 0, 0, 0, 0,
+      0] '
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [329, 227]
+    rotation: 0
+    state: true
+- name: import_0_1
+  id: import
+  parameters:
+    alias: ''
+    comment: ''
+    imports: from gnuradio import filter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [523, 12]
+    rotation: 0
+    state: enabled
+- name: qtgui_freq_sink_x_0
+  id: qtgui_freq_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'
+    autoscale: 'False'
+    average: '1.0'
+    axislabels: 'True'
+    bw: samp_rate
+    color1: '"blue"'
+    color10: '"dark blue"'
+    color2: '"red"'
+    color3: '"green"'
+    color4: '"black"'
+    color5: '"cyan"'
+    color6: '"magenta"'
+    color7: '"yellow"'
+    color8: '"dark red"'
+    color9: '"dark green"'
+    comment: ''
+    ctrlpanel: 'False'
+    fc: rf_freq
+    fftsize: '1024'
+    freqhalf: 'True'
+    grid: 'False'
+    gui_hint: ''
+    label: Relative Gain
+    label1: ''
+    label10: ''''''
+    label2: ''''''
+    label3: ''''''
+    label4: ''''''
+    label5: ''''''
+    label6: ''''''
+    label7: ''''''
+    label8: ''''''
+    label9: ''''''
+    legend: 'True'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    name: '""'
+    nconnections: '1'
+    showports: 'False'
+    tr_chan: '0'
+    tr_level: '0.0'
+    tr_mode: qtgui.TRIG_MODE_FREE
+    tr_tag: '""'
+    type: complex
+    units: dB
+    update_time: '0.10'
+    width1: '1'
+    width10: '1'
+    width2: '1'
+    width3: '1'
+    width4: '1'
+    width5: '1'
+    width6: '1'
+    width7: '1'
+    width8: '1'
+    width9: '1'
+    wintype: firdes.WIN_BLACKMAN_hARRIS
+    ymax: '10'
+    ymin: '-140'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [328, 435]
+    rotation: 0
+    state: true
+- name: uhd_usrp_source_0
+  id: uhd_usrp_source
+  parameters:
+    affinity: ''
+    alias: ''
+    ant0: RX2
+    ant1: RX2
+    ant10: RX2
+    ant11: RX2
+    ant12: RX2
+    ant13: RX2
+    ant14: RX2
+    ant15: RX2
+    ant16: RX2
+    ant17: RX2
+    ant18: RX2
+    ant19: RX2
+    ant2: RX2
+    ant20: RX2
+    ant21: RX2
+    ant22: RX2
+    ant23: RX2
+    ant24: RX2
+    ant25: RX2
+    ant26: RX2
+    ant27: RX2
+    ant28: RX2
+    ant29: RX2
+    ant3: RX2
+    ant30: RX2
+    ant31: RX2
+    ant4: RX2
+    ant5: RX2
+    ant6: RX2
+    ant7: RX2
+    ant8: RX2
+    ant9: RX2
+    bw0: '0'
+    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'
+    center_freq0: rf_freq
+    center_freq1: '0'
+    center_freq10: '0'
+    center_freq11: '0'
+    center_freq12: '0'
+    center_freq13: '0'
+    center_freq14: '0'
+    center_freq15: '0'
+    center_freq16: '0'
+    center_freq17: '0'
+    center_freq18: '0'
+    center_freq19: '0'
+    center_freq2: '0'
+    center_freq20: '0'
+    center_freq21: '0'
+    center_freq22: '0'
+    center_freq23: '0'
+    center_freq24: '0'
+    center_freq25: '0'
+    center_freq26: '0'
+    center_freq27: '0'
+    center_freq28: '0'
+    center_freq29: '0'
+    center_freq3: '0'
+    center_freq30: '0'
+    center_freq31: '0'
+    center_freq4: '0'
+    center_freq5: '0'
+    center_freq6: '0'
+    center_freq7: '0'
+    center_freq8: '0'
+    center_freq9: '0'
+    clock_rate: 0e0
+    clock_source0: ''
+    clock_source1: ''
+    clock_source2: ''
+    clock_source3: ''
+    clock_source4: ''
+    clock_source5: ''
+    clock_source6: ''
+    clock_source7: ''
+    comment: ''
+    dc_offs_enb0: '""'
+    dc_offs_enb1: '""'
+    dc_offs_enb10: '""'
+    dc_offs_enb11: '""'
+    dc_offs_enb12: '""'
+    dc_offs_enb13: '""'
+    dc_offs_enb14: '""'
+    dc_offs_enb15: '""'
+    dc_offs_enb16: '""'
+    dc_offs_enb17: '""'
+    dc_offs_enb18: '""'
+    dc_offs_enb19: '""'
+    dc_offs_enb2: '""'
+    dc_offs_enb20: '""'
+    dc_offs_enb21: '""'
+    dc_offs_enb22: '""'
+    dc_offs_enb23: '""'
+    dc_offs_enb24: '""'
+    dc_offs_enb25: '""'
+    dc_offs_enb26: '""'
+    dc_offs_enb27: '""'
+    dc_offs_enb28: '""'
+    dc_offs_enb29: '""'
+    dc_offs_enb3: '""'
+    dc_offs_enb30: '""'
+    dc_offs_enb31: '""'
+    dc_offs_enb4: '""'
+    dc_offs_enb5: '""'
+    dc_offs_enb6: '""'
+    dc_offs_enb7: '""'
+    dc_offs_enb8: '""'
+    dc_offs_enb9: '""'
+    dev_addr: '""'
+    dev_args: '""'
+    gain0: rx_gain
+    gain1: '0'
+    gain10: '0'
+    gain11: '0'
+    gain12: '0'
+    gain13: '0'
+    gain14: '0'
+    gain15: '0'
+    gain16: '0'
+    gain17: '0'
+    gain18: '0'
+    gain19: '0'
+    gain2: '0'
+    gain20: '0'
+    gain21: '0'
+    gain22: '0'
+    gain23: '0'
+    gain24: '0'
+    gain25: '0'
+    gain26: '0'
+    gain27: '0'
+    gain28: '0'
+    gain29: '0'
+    gain3: '0'
+    gain30: '0'
+    gain31: '0'
+    gain4: '0'
+    gain5: '0'
+    gain6: '0'
+    gain7: '0'
+    gain8: '0'
+    gain9: '0'
+    iq_imbal_enb0: '""'
+    iq_imbal_enb1: '""'
+    iq_imbal_enb10: '""'
+    iq_imbal_enb11: '""'
+    iq_imbal_enb12: '""'
+    iq_imbal_enb13: '""'
+    iq_imbal_enb14: '""'
+    iq_imbal_enb15: '""'
+    iq_imbal_enb16: '""'
+    iq_imbal_enb17: '""'
+    iq_imbal_enb18: '""'
+    iq_imbal_enb19: '""'
+    iq_imbal_enb2: '""'
+    iq_imbal_enb20: '""'
+    iq_imbal_enb21: '""'
+    iq_imbal_enb22: '""'
+    iq_imbal_enb23: '""'
+    iq_imbal_enb24: '""'
+    iq_imbal_enb25: '""'
+    iq_imbal_enb26: '""'
+    iq_imbal_enb27: '""'
+    iq_imbal_enb28: '""'
+    iq_imbal_enb29: '""'
+    iq_imbal_enb3: '""'
+    iq_imbal_enb30: '""'
+    iq_imbal_enb31: '""'
+    iq_imbal_enb4: '""'
+    iq_imbal_enb5: '""'
+    iq_imbal_enb6: '""'
+    iq_imbal_enb7: '""'
+    iq_imbal_enb8: '""'
+    iq_imbal_enb9: '""'
+    lo_export0: 'False'
+    lo_export1: 'False'
+    lo_export10: 'False'
+    lo_export11: 'False'
+    lo_export12: 'False'
+    lo_export13: 'False'
+    lo_export14: 'False'
+    lo_export15: 'False'
+    lo_export16: 'False'
+    lo_export17: 'False'
+    lo_export18: 'False'
+    lo_export19: 'False'
+    lo_export2: 'False'
+    lo_export20: 'False'
+    lo_export21: 'False'
+    lo_export22: 'False'
+    lo_export23: 'False'
+    lo_export24: 'False'
+    lo_export25: 'False'
+    lo_export26: 'False'
+    lo_export27: 'False'
+    lo_export28: 'False'
+    lo_export29: 'False'
+    lo_export3: 'False'
+    lo_export30: 'False'
+    lo_export31: 'False'
+    lo_export4: 'False'
+    lo_export5: 'False'
+    lo_export6: 'False'
+    lo_export7: 'False'
+    lo_export8: 'False'
+    lo_export9: 'False'
+    lo_source0: internal
+    lo_source1: internal
+    lo_source10: internal
+    lo_source11: internal
+    lo_source12: internal
+    lo_source13: internal
+    lo_source14: internal
+    lo_source15: internal
+    lo_source16: internal
+    lo_source17: internal
+    lo_source18: internal
+    lo_source19: internal
+    lo_source2: internal
+    lo_source20: internal
+    lo_source21: internal
+    lo_source22: internal
+    lo_source23: internal
+    lo_source24: internal
+    lo_source25: internal
+    lo_source26: internal
+    lo_source27: internal
+    lo_source28: internal
+    lo_source29: internal
+    lo_source3: internal
+    lo_source30: internal
+    lo_source31: internal
+    lo_source4: internal
+    lo_source5: internal
+    lo_source6: internal
+    lo_source7: internal
+    lo_source8: internal
+    lo_source9: internal
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    nchan: '1'
+    norm_gain0: 'False'
+    norm_gain1: 'False'
+    norm_gain10: 'False'
+    norm_gain11: 'False'
+    norm_gain12: 'False'
+    norm_gain13: 'False'
+    norm_gain14: 'False'
+    norm_gain15: 'False'
+    norm_gain16: 'False'
+    norm_gain17: 'False'
+    norm_gain18: 'False'
+    norm_gain19: 'False'
+    norm_gain2: 'False'
+    norm_gain20: 'False'
+    norm_gain21: 'False'
+    norm_gain22: 'False'
+    norm_gain23: 'False'
+    norm_gain24: 'False'
+    norm_gain25: 'False'
+    norm_gain26: 'False'
+    norm_gain27: 'False'
+    norm_gain28: 'False'
+    norm_gain29: 'False'
+    norm_gain3: 'False'
+    norm_gain30: 'False'
+    norm_gain31: 'False'
+    norm_gain4: 'False'
+    norm_gain5: 'False'
+    norm_gain6: 'False'
+    norm_gain7: 'False'
+    norm_gain8: 'False'
+    norm_gain9: 'False'
+    num_mboards: '1'
+    otw: ''
+    rx_agc0: Default
+    rx_agc1: Default
+    rx_agc10: Default
+    rx_agc11: Default
+    rx_agc12: Default
+    rx_agc13: Default
+    rx_agc14: Default
+    rx_agc15: Default
+    rx_agc16: Default
+    rx_agc17: Default
+    rx_agc18: Default
+    rx_agc19: Default
+    rx_agc2: Default
+    rx_agc20: Default
+    rx_agc21: Default
+    rx_agc22: Default
+    rx_agc23: Default
+    rx_agc24: Default
+    rx_agc25: Default
+    rx_agc26: Default
+    rx_agc27: Default
+    rx_agc28: Default
+    rx_agc29: Default
+    rx_agc3: Default
+    rx_agc30: Default
+    rx_agc31: Default
+    rx_agc4: Default
+    rx_agc5: Default
+    rx_agc6: Default
+    rx_agc7: Default
+    rx_agc8: Default
+    rx_agc9: Default
+    samp_rate: samp_rate
+    sd_spec0: ''
+    sd_spec1: ''
+    sd_spec2: ''
+    sd_spec3: ''
+    sd_spec4: ''
+    sd_spec5: ''
+    sd_spec6: ''
+    sd_spec7: ''
+    show_lo_controls: 'False'
+    stream_args: ''
+    stream_chans: '[]'
+    sync: none
+    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: [80, 259]
+    rotation: 0
+    state: true
+
+connections:
+- [digital_ofdm_rx_0, '0', blocks_tagged_stream_to_pdu_0, '0']
+- [uhd_usrp_source_0, '0', digital_ofdm_rx_0, '0']
+- [uhd_usrp_source_0, '0', qtgui_freq_sink_x_0, '0']
+
+metadata:
+  file_format: 1
diff --git a/examples/pu_rx_4_channel.grc b/examples/pu_rx_4_channel.grc
new file mode 100644
index 0000000000000000000000000000000000000000..0b5bfee568d1fb512a61fd4b29565e22958a96af
--- /dev/null
+++ b/examples/pu_rx_4_channel.grc
@@ -0,0 +1,898 @@
+options:
+  parameters:
+    author: ''
+    category: '[GRC Hier Blocks]'
+    cmake_opt: ''
+    comment: ''
+    copyright: University of Melbourne
+    description: ''
+    gen_cmake: 'On'
+    gen_linking: dynamic
+    generate_options: qt_gui
+    hier_block_src_path: '.:'
+    id: pu_rx_1_channel
+    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: Primary User Rx - 1 Channel
+    window_size: ''
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [8, 8]
+    rotation: 0
+    state: enabled
+
+blocks:
+- name: decim_factor
+  id: variable
+  parameters:
+    comment: ''
+    value: '5'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [554, 11]
+    rotation: 0
+    state: enabled
+- name: fft_len
+  id: variable
+  parameters:
+    comment: ''
+    value: '64'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [656, 10]
+    rotation: 0
+    state: enabled
+- name: rf_freq
+  id: variable_qtgui_range
+  parameters:
+    comment: ''
+    gui_hint: ''
+    label: RF Frequency
+    min_len: '200'
+    orient: Qt.Horizontal
+    rangeType: float
+    start: 70e6
+    step: 1e3
+    stop: 6e9
+    value: 2.45e9
+    widget: counter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [302, 11]
+    rotation: 0
+    state: true
+- name: rx_gain
+  id: variable_qtgui_range
+  parameters:
+    comment: ''
+    gui_hint: ''
+    label: Rx Gain
+    min_len: '200'
+    orient: Qt.Horizontal
+    rangeType: float
+    start: '0'
+    step: '1'
+    stop: '76'
+    value: '40'
+    widget: counter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [437, 11]
+    rotation: 0
+    state: true
+- name: samp_rate
+  id: variable
+  parameters:
+    comment: ''
+    value: 2.5e6
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [183, 73]
+    rotation: 0
+    state: enabled
+- name: usrp_samp_rate
+  id: variable
+  parameters:
+    comment: ''
+    value: 10e6
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [184, 12]
+    rotation: 0
+    state: enabled
+- name: blocks_tagged_stream_to_pdu_0
+  id: blocks_tagged_stream_to_pdu
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    tag: packet_len
+    type: byte
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [857, 221]
+    rotation: 0
+    state: true
+- name: blocks_tagged_stream_to_pdu_0_0
+  id: blocks_tagged_stream_to_pdu
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    tag: packet_len
+    type: byte
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [873, 428]
+    rotation: 0
+    state: true
+- name: blocks_tagged_stream_to_pdu_0_0_0
+  id: blocks_tagged_stream_to_pdu
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    tag: packet_len
+    type: byte
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [872, 634]
+    rotation: 0
+    state: true
+- name: blocks_tagged_stream_to_pdu_0_0_0_0
+  id: blocks_tagged_stream_to_pdu
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    tag: packet_len
+    type: byte
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [867, 841]
+    rotation: 0
+    state: true
+- name: digital_ofdm_rx_0
+  id: digital_ofdm_rx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    cp_len: fft_len // 4
+    fft_len: fft_len
+    header_mod: '"BPSK"'
+    log: 'False'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    occupied_carriers: ([ii for ii in range(-26, 27) if ii not in (-21, -7, 7, 21)],)
+    packet_len_key: rx_len
+    payload_mod: '"QPSK"'
+    pilot_carriers: ((-21, -7, 7, 21,),)
+    pilot_symbols: ((1, 1, 1, -1,),)
+    scramble_bits: 'False'
+    sync_word1: '[0., 0., 0., 0., 0., 0., 0., 1.41421356, 0., -1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 0., 0., 0., 0., 0.]'
+    sync_word2: '[0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1,
+      1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 0, 1, -1, 1, 1, 1, -1, 1, 1,
+      1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 0, 0, 0, 0,
+      0] '
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [614, 141]
+    rotation: 0
+    state: true
+- name: digital_ofdm_rx_0_0
+  id: digital_ofdm_rx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    cp_len: fft_len // 4
+    fft_len: fft_len
+    header_mod: '"BPSK"'
+    log: 'False'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    occupied_carriers: ([ii for ii in range(-26, 27) if ii not in (-21, -7, 7, 21)],)
+    packet_len_key: rx_len
+    payload_mod: '"QPSK"'
+    pilot_carriers: ((-21, -7, 7, 21,),)
+    pilot_symbols: ((1, 1, 1, -1,),)
+    scramble_bits: 'False'
+    sync_word1: '[0., 0., 0., 0., 0., 0., 0., 1.41421356, 0., -1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 0., 0., 0., 0., 0.]'
+    sync_word2: '[0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1,
+      1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 0, 1, -1, 1, 1, 1, -1, 1, 1,
+      1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 0, 0, 0, 0,
+      0] '
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [615, 348]
+    rotation: 0
+    state: true
+- name: digital_ofdm_rx_0_0_0
+  id: digital_ofdm_rx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    cp_len: fft_len // 4
+    fft_len: fft_len
+    header_mod: '"BPSK"'
+    log: 'False'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    occupied_carriers: ([ii for ii in range(-26, 27) if ii not in (-21, -7, 7, 21)],)
+    packet_len_key: rx_len
+    payload_mod: '"QPSK"'
+    pilot_carriers: ((-21, -7, 7, 21,),)
+    pilot_symbols: ((1, 1, 1, -1,),)
+    scramble_bits: 'False'
+    sync_word1: '[0., 0., 0., 0., 0., 0., 0., 1.41421356, 0., -1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 0., 0., 0., 0., 0.]'
+    sync_word2: '[0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1,
+      1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 0, 1, -1, 1, 1, 1, -1, 1, 1,
+      1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 0, 0, 0, 0,
+      0] '
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [615, 554]
+    rotation: 0
+    state: true
+- name: digital_ofdm_rx_0_0_0_0
+  id: digital_ofdm_rx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    cp_len: fft_len // 4
+    fft_len: fft_len
+    header_mod: '"BPSK"'
+    log: 'False'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    occupied_carriers: ([ii for ii in range(-26, 27) if ii not in (-21, -7, 7, 21)],)
+    packet_len_key: rx_len
+    payload_mod: '"QPSK"'
+    pilot_carriers: ((-21, -7, 7, 21,),)
+    pilot_symbols: ((1, 1, 1, -1,),)
+    scramble_bits: 'False'
+    sync_word1: '[0., 0., 0., 0., 0., 0., 0., 1.41421356, 0., -1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 0., 0., 0., 0., 0.]'
+    sync_word2: '[0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1,
+      1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 0, 1, -1, 1, 1, 1, -1, 1, 1,
+      1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 0, 0, 0, 0,
+      0] '
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [616, 761]
+    rotation: 0
+    state: true
+- name: freq_xlating_fft_filter_ccc_0
+  id: freq_xlating_fft_filter_ccc
+  parameters:
+    affinity: ''
+    alias: ''
+    center_freq: -3.75e6
+    comment: ''
+    decim: decim_factor
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    nthreads: '4'
+    samp_delay: '0'
+    samp_rate: usrp_samp_rate
+    taps: filter.firdes.low_pass(1,usrp_samp_rate, samp_rate/2.0*0.98, 5000)
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [355, 189]
+    rotation: 0
+    state: enabled
+- name: freq_xlating_fft_filter_ccc_0_0
+  id: freq_xlating_fft_filter_ccc
+  parameters:
+    affinity: ''
+    alias: ''
+    center_freq: -1.25e6
+    comment: ''
+    decim: decim_factor
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    nthreads: '4'
+    samp_delay: '0'
+    samp_rate: usrp_samp_rate
+    taps: filter.firdes.low_pass(1,usrp_samp_rate, samp_rate/2.0*0.98, 5000)
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [354, 396]
+    rotation: 0
+    state: enabled
+- name: freq_xlating_fft_filter_ccc_0_0_0
+  id: freq_xlating_fft_filter_ccc
+  parameters:
+    affinity: ''
+    alias: ''
+    center_freq: 1.25e6
+    comment: ''
+    decim: decim_factor
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    nthreads: '4'
+    samp_delay: '0'
+    samp_rate: usrp_samp_rate
+    taps: filter.firdes.low_pass(1,usrp_samp_rate, samp_rate/2.0*0.98, 5000)
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [353, 602]
+    rotation: 0
+    state: enabled
+- name: freq_xlating_fft_filter_ccc_0_0_0_0
+  id: freq_xlating_fft_filter_ccc
+  parameters:
+    affinity: ''
+    alias: ''
+    center_freq: 3.75e6
+    comment: ''
+    decim: decim_factor
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    nthreads: '4'
+    samp_delay: '0'
+    samp_rate: usrp_samp_rate
+    taps: filter.firdes.low_pass(1,usrp_samp_rate, samp_rate/2.0*0.98, 5000)
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [351, 809]
+    rotation: 0
+    state: enabled
+- name: import_0_1
+  id: import
+  parameters:
+    alias: ''
+    comment: ''
+    imports: from gnuradio import filter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [555, 72]
+    rotation: 0
+    state: enabled
+- name: qtgui_freq_sink_x_0
+  id: qtgui_freq_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'
+    autoscale: 'False'
+    average: '1.0'
+    axislabels: 'True'
+    bw: samp_rate
+    color1: '"blue"'
+    color10: '"dark blue"'
+    color2: '"red"'
+    color3: '"green"'
+    color4: '"black"'
+    color5: '"cyan"'
+    color6: '"magenta"'
+    color7: '"yellow"'
+    color8: '"dark red"'
+    color9: '"dark green"'
+    comment: ''
+    ctrlpanel: 'False'
+    fc: rf_freq
+    fftsize: '1024'
+    freqhalf: 'True'
+    grid: 'False'
+    gui_hint: ''
+    label: Relative Gain
+    label1: ''
+    label10: ''''''
+    label2: ''''''
+    label3: ''''''
+    label4: ''''''
+    label5: ''''''
+    label6: ''''''
+    label7: ''''''
+    label8: ''''''
+    label9: ''''''
+    legend: 'True'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    name: '""'
+    nconnections: '1'
+    showports: 'False'
+    tr_chan: '0'
+    tr_level: '0.0'
+    tr_mode: qtgui.TRIG_MODE_FREE
+    tr_tag: '""'
+    type: complex
+    units: dB
+    update_time: '0.10'
+    width1: '1'
+    width10: '1'
+    width2: '1'
+    width3: '1'
+    width4: '1'
+    width5: '1'
+    width6: '1'
+    width7: '1'
+    width8: '1'
+    width9: '1'
+    wintype: firdes.WIN_BLACKMAN_hARRIS
+    ymax: '10'
+    ymin: '-140'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [60, 389]
+    rotation: 180
+    state: true
+- name: uhd_usrp_source_0
+  id: uhd_usrp_source
+  parameters:
+    affinity: ''
+    alias: ''
+    ant0: RX2
+    ant1: RX2
+    ant10: RX2
+    ant11: RX2
+    ant12: RX2
+    ant13: RX2
+    ant14: RX2
+    ant15: RX2
+    ant16: RX2
+    ant17: RX2
+    ant18: RX2
+    ant19: RX2
+    ant2: RX2
+    ant20: RX2
+    ant21: RX2
+    ant22: RX2
+    ant23: RX2
+    ant24: RX2
+    ant25: RX2
+    ant26: RX2
+    ant27: RX2
+    ant28: RX2
+    ant29: RX2
+    ant3: RX2
+    ant30: RX2
+    ant31: RX2
+    ant4: RX2
+    ant5: RX2
+    ant6: RX2
+    ant7: RX2
+    ant8: RX2
+    ant9: RX2
+    bw0: '0'
+    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'
+    center_freq0: rf_freq
+    center_freq1: '0'
+    center_freq10: '0'
+    center_freq11: '0'
+    center_freq12: '0'
+    center_freq13: '0'
+    center_freq14: '0'
+    center_freq15: '0'
+    center_freq16: '0'
+    center_freq17: '0'
+    center_freq18: '0'
+    center_freq19: '0'
+    center_freq2: '0'
+    center_freq20: '0'
+    center_freq21: '0'
+    center_freq22: '0'
+    center_freq23: '0'
+    center_freq24: '0'
+    center_freq25: '0'
+    center_freq26: '0'
+    center_freq27: '0'
+    center_freq28: '0'
+    center_freq29: '0'
+    center_freq3: '0'
+    center_freq30: '0'
+    center_freq31: '0'
+    center_freq4: '0'
+    center_freq5: '0'
+    center_freq6: '0'
+    center_freq7: '0'
+    center_freq8: '0'
+    center_freq9: '0'
+    clock_rate: 0e0
+    clock_source0: ''
+    clock_source1: ''
+    clock_source2: ''
+    clock_source3: ''
+    clock_source4: ''
+    clock_source5: ''
+    clock_source6: ''
+    clock_source7: ''
+    comment: ''
+    dc_offs_enb0: '""'
+    dc_offs_enb1: '""'
+    dc_offs_enb10: '""'
+    dc_offs_enb11: '""'
+    dc_offs_enb12: '""'
+    dc_offs_enb13: '""'
+    dc_offs_enb14: '""'
+    dc_offs_enb15: '""'
+    dc_offs_enb16: '""'
+    dc_offs_enb17: '""'
+    dc_offs_enb18: '""'
+    dc_offs_enb19: '""'
+    dc_offs_enb2: '""'
+    dc_offs_enb20: '""'
+    dc_offs_enb21: '""'
+    dc_offs_enb22: '""'
+    dc_offs_enb23: '""'
+    dc_offs_enb24: '""'
+    dc_offs_enb25: '""'
+    dc_offs_enb26: '""'
+    dc_offs_enb27: '""'
+    dc_offs_enb28: '""'
+    dc_offs_enb29: '""'
+    dc_offs_enb3: '""'
+    dc_offs_enb30: '""'
+    dc_offs_enb31: '""'
+    dc_offs_enb4: '""'
+    dc_offs_enb5: '""'
+    dc_offs_enb6: '""'
+    dc_offs_enb7: '""'
+    dc_offs_enb8: '""'
+    dc_offs_enb9: '""'
+    dev_addr: '""'
+    dev_args: '""'
+    gain0: rx_gain
+    gain1: '0'
+    gain10: '0'
+    gain11: '0'
+    gain12: '0'
+    gain13: '0'
+    gain14: '0'
+    gain15: '0'
+    gain16: '0'
+    gain17: '0'
+    gain18: '0'
+    gain19: '0'
+    gain2: '0'
+    gain20: '0'
+    gain21: '0'
+    gain22: '0'
+    gain23: '0'
+    gain24: '0'
+    gain25: '0'
+    gain26: '0'
+    gain27: '0'
+    gain28: '0'
+    gain29: '0'
+    gain3: '0'
+    gain30: '0'
+    gain31: '0'
+    gain4: '0'
+    gain5: '0'
+    gain6: '0'
+    gain7: '0'
+    gain8: '0'
+    gain9: '0'
+    iq_imbal_enb0: '""'
+    iq_imbal_enb1: '""'
+    iq_imbal_enb10: '""'
+    iq_imbal_enb11: '""'
+    iq_imbal_enb12: '""'
+    iq_imbal_enb13: '""'
+    iq_imbal_enb14: '""'
+    iq_imbal_enb15: '""'
+    iq_imbal_enb16: '""'
+    iq_imbal_enb17: '""'
+    iq_imbal_enb18: '""'
+    iq_imbal_enb19: '""'
+    iq_imbal_enb2: '""'
+    iq_imbal_enb20: '""'
+    iq_imbal_enb21: '""'
+    iq_imbal_enb22: '""'
+    iq_imbal_enb23: '""'
+    iq_imbal_enb24: '""'
+    iq_imbal_enb25: '""'
+    iq_imbal_enb26: '""'
+    iq_imbal_enb27: '""'
+    iq_imbal_enb28: '""'
+    iq_imbal_enb29: '""'
+    iq_imbal_enb3: '""'
+    iq_imbal_enb30: '""'
+    iq_imbal_enb31: '""'
+    iq_imbal_enb4: '""'
+    iq_imbal_enb5: '""'
+    iq_imbal_enb6: '""'
+    iq_imbal_enb7: '""'
+    iq_imbal_enb8: '""'
+    iq_imbal_enb9: '""'
+    lo_export0: 'False'
+    lo_export1: 'False'
+    lo_export10: 'False'
+    lo_export11: 'False'
+    lo_export12: 'False'
+    lo_export13: 'False'
+    lo_export14: 'False'
+    lo_export15: 'False'
+    lo_export16: 'False'
+    lo_export17: 'False'
+    lo_export18: 'False'
+    lo_export19: 'False'
+    lo_export2: 'False'
+    lo_export20: 'False'
+    lo_export21: 'False'
+    lo_export22: 'False'
+    lo_export23: 'False'
+    lo_export24: 'False'
+    lo_export25: 'False'
+    lo_export26: 'False'
+    lo_export27: 'False'
+    lo_export28: 'False'
+    lo_export29: 'False'
+    lo_export3: 'False'
+    lo_export30: 'False'
+    lo_export31: 'False'
+    lo_export4: 'False'
+    lo_export5: 'False'
+    lo_export6: 'False'
+    lo_export7: 'False'
+    lo_export8: 'False'
+    lo_export9: 'False'
+    lo_source0: internal
+    lo_source1: internal
+    lo_source10: internal
+    lo_source11: internal
+    lo_source12: internal
+    lo_source13: internal
+    lo_source14: internal
+    lo_source15: internal
+    lo_source16: internal
+    lo_source17: internal
+    lo_source18: internal
+    lo_source19: internal
+    lo_source2: internal
+    lo_source20: internal
+    lo_source21: internal
+    lo_source22: internal
+    lo_source23: internal
+    lo_source24: internal
+    lo_source25: internal
+    lo_source26: internal
+    lo_source27: internal
+    lo_source28: internal
+    lo_source29: internal
+    lo_source3: internal
+    lo_source30: internal
+    lo_source31: internal
+    lo_source4: internal
+    lo_source5: internal
+    lo_source6: internal
+    lo_source7: internal
+    lo_source8: internal
+    lo_source9: internal
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    nchan: '1'
+    norm_gain0: 'False'
+    norm_gain1: 'False'
+    norm_gain10: 'False'
+    norm_gain11: 'False'
+    norm_gain12: 'False'
+    norm_gain13: 'False'
+    norm_gain14: 'False'
+    norm_gain15: 'False'
+    norm_gain16: 'False'
+    norm_gain17: 'False'
+    norm_gain18: 'False'
+    norm_gain19: 'False'
+    norm_gain2: 'False'
+    norm_gain20: 'False'
+    norm_gain21: 'False'
+    norm_gain22: 'False'
+    norm_gain23: 'False'
+    norm_gain24: 'False'
+    norm_gain25: 'False'
+    norm_gain26: 'False'
+    norm_gain27: 'False'
+    norm_gain28: 'False'
+    norm_gain29: 'False'
+    norm_gain3: 'False'
+    norm_gain30: 'False'
+    norm_gain31: 'False'
+    norm_gain4: 'False'
+    norm_gain5: 'False'
+    norm_gain6: 'False'
+    norm_gain7: 'False'
+    norm_gain8: 'False'
+    norm_gain9: 'False'
+    num_mboards: '1'
+    otw: ''
+    rx_agc0: Default
+    rx_agc1: Default
+    rx_agc10: Default
+    rx_agc11: Default
+    rx_agc12: Default
+    rx_agc13: Default
+    rx_agc14: Default
+    rx_agc15: Default
+    rx_agc16: Default
+    rx_agc17: Default
+    rx_agc18: Default
+    rx_agc19: Default
+    rx_agc2: Default
+    rx_agc20: Default
+    rx_agc21: Default
+    rx_agc22: Default
+    rx_agc23: Default
+    rx_agc24: Default
+    rx_agc25: Default
+    rx_agc26: Default
+    rx_agc27: Default
+    rx_agc28: Default
+    rx_agc29: Default
+    rx_agc3: Default
+    rx_agc30: Default
+    rx_agc31: Default
+    rx_agc4: Default
+    rx_agc5: Default
+    rx_agc6: Default
+    rx_agc7: Default
+    rx_agc8: Default
+    rx_agc9: Default
+    samp_rate: usrp_samp_rate
+    sd_spec0: ''
+    sd_spec1: ''
+    sd_spec2: ''
+    sd_spec3: ''
+    sd_spec4: ''
+    sd_spec5: ''
+    sd_spec6: ''
+    sd_spec7: ''
+    show_lo_controls: 'False'
+    stream_args: ''
+    stream_chans: '[]'
+    sync: none
+    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: [68, 238]
+    rotation: 0
+    state: true
+
+connections:
+- [digital_ofdm_rx_0, '0', blocks_tagged_stream_to_pdu_0, '0']
+- [digital_ofdm_rx_0_0, '0', blocks_tagged_stream_to_pdu_0_0, '0']
+- [digital_ofdm_rx_0_0_0, '0', blocks_tagged_stream_to_pdu_0_0_0, '0']
+- [digital_ofdm_rx_0_0_0_0, '0', blocks_tagged_stream_to_pdu_0_0_0_0, '0']
+- [freq_xlating_fft_filter_ccc_0, '0', digital_ofdm_rx_0, '0']
+- [freq_xlating_fft_filter_ccc_0_0, '0', digital_ofdm_rx_0_0, '0']
+- [freq_xlating_fft_filter_ccc_0_0_0, '0', digital_ofdm_rx_0_0_0, '0']
+- [freq_xlating_fft_filter_ccc_0_0_0_0, '0', digital_ofdm_rx_0_0_0_0, '0']
+- [uhd_usrp_source_0, '0', freq_xlating_fft_filter_ccc_0, '0']
+- [uhd_usrp_source_0, '0', freq_xlating_fft_filter_ccc_0_0, '0']
+- [uhd_usrp_source_0, '0', freq_xlating_fft_filter_ccc_0_0_0, '0']
+- [uhd_usrp_source_0, '0', freq_xlating_fft_filter_ccc_0_0_0_0, '0']
+- [uhd_usrp_source_0, '0', qtgui_freq_sink_x_0, '0']
+
+metadata:
+  file_format: 1
diff --git a/examples/pu_tx_1_channel.grc b/examples/pu_tx_1_channel.grc
new file mode 100644
index 0000000000000000000000000000000000000000..d7dfb96589612a0555e9f207e41ed47c62bbbeff
--- /dev/null
+++ b/examples/pu_tx_1_channel.grc
@@ -0,0 +1,817 @@
+options:
+  parameters:
+    author: ''
+    category: '[GRC Hier Blocks]'
+    cmake_opt: ''
+    comment: ''
+    copyright: University of Melbourne
+    description: ''
+    gen_cmake: 'On'
+    gen_linking: dynamic
+    generate_options: qt_gui
+    hier_block_src_path: '.:'
+    id: pu_tx_1_channel
+    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: DSA Primary User Tx  - 1 Channel
+    window_size: ''
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [8, 8]
+    rotation: 0
+    state: enabled
+
+blocks:
+- name: fft_len
+  id: variable
+  parameters:
+    comment: ''
+    value: '64'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [267, 8]
+    rotation: 0
+    state: enabled
+- name: pdu_size
+  id: variable
+  parameters:
+    comment: ''
+    value: '64'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [259, 71]
+    rotation: 0
+    state: enabled
+- name: rf_freq
+  id: variable_qtgui_range
+  parameters:
+    comment: ''
+    gui_hint: ''
+    label: RF Frequency
+    min_len: '200'
+    orient: Qt.Horizontal
+    rangeType: float
+    start: 70e6
+    step: 1e3
+    stop: 6e9
+    value: 2.45e9
+    widget: counter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [337, 8]
+    rotation: 0
+    state: true
+- name: samp_rate
+  id: variable
+  parameters:
+    comment: ''
+    value: '2000000'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [182, 8]
+    rotation: 0
+    state: enabled
+- name: tx_gain
+  id: variable_qtgui_range
+  parameters:
+    comment: ''
+    gui_hint: ''
+    label: Tx Gain
+    min_len: '200'
+    orient: Qt.Horizontal
+    rangeType: int
+    start: '0'
+    step: '1'
+    stop: '92'
+    value: '40'
+    widget: counter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [472, 8]
+    rotation: 0
+    state: true
+- name: blocks_multiply_const_vxx_0
+  id: blocks_multiply_const_vxx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    const: '0.025'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    type: complex
+    vlen: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [427, 454]
+    rotation: 0
+    state: true
+- name: blocks_pdu_to_tagged_stream_0
+  id: blocks_pdu_to_tagged_stream
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    tag: packet_len
+    type: byte
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [458, 186]
+    rotation: 0
+    state: enabled
+- name: blocks_random_pdu_0
+  id: blocks_random_pdu
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    length_modulo: '2'
+    mask: '0xFF'
+    maxoutbuf: '0'
+    maxsize: '2000'
+    minoutbuf: '0'
+    minsize: '50'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [1150, 299]
+    rotation: 0
+    state: true
+- name: blocks_tagged_stream_to_pdu_0
+  id: blocks_tagged_stream_to_pdu
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    tag: packet_len
+    type: complex
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [925, 186]
+    rotation: 0
+    state: true
+- name: blocks_throttle_0
+  id: blocks_throttle
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    ignoretag: 'True'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    samples_per_second: samp_rate
+    type: complex
+    vlen: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [667, 491]
+    rotation: 0
+    state: true
+- name: cdc_dsa_db_connect_0
+  id: cdc_dsa_db_connect
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    host: 127.0.0.1
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    pktsize: pdu_size
+    port: '9000'
+    radiotype: dsa.RADIO_PU_TX
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [229, 241]
+    rotation: 0
+    state: disabled
+- name: cdc_dsa_pu_scenario_0
+  id: cdc_dsa_pu_scenario
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    num_outputs: '1'
+    num_packets: '100'
+    num_queued: '5'
+    scenario: '-1'
+    seed: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [235, 406]
+    rotation: 0
+    state: true
+- name: cdc_fixed_pdu_generator_0
+  id: cdc_fixed_pdu_generator
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    payload: '[ii for ii in range(pdu_size)]'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [229, 186]
+    rotation: 0
+    state: true
+- name: digital_ofdm_tx_0
+  id: digital_ofdm_tx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    cp_len: fft_len // 4
+    fft_len: fft_len
+    header_mod: '"BPSK"'
+    log: 'False'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    occupied_carriers: ([ii for ii in range(-26, 27) if ii not in (-21, -7, 7, 21)],)
+    packet_len_key: packet_len
+    payload_mod: '"QPSK"'
+    pilot_carriers: ((-21, -7, 7, 21,),)
+    pilot_symbols: ((1, 1, 1, -1,),)
+    rolloff: '0'
+    scramble_bits: 'False'
+    sync_word1: '[0., 0., 0., 0., 0., 0., 0., 1.41421356, 0., -1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 0., 0., 0., 0., 0.]'
+    sync_word2: '[0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1,
+      1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 0, 1, -1, 1, 1, 1, -1, 1, 1,
+      1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 0, 0, 0, 0,
+      0] '
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [693, 98]
+    rotation: 0
+    state: true
+- name: qtgui_freq_sink_x_0
+  id: qtgui_freq_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'
+    autoscale: 'False'
+    average: '1.0'
+    axislabels: 'True'
+    bw: samp_rate
+    color1: '"blue"'
+    color10: '"dark blue"'
+    color2: '"red"'
+    color3: '"green"'
+    color4: '"black"'
+    color5: '"cyan"'
+    color6: '"magenta"'
+    color7: '"yellow"'
+    color8: '"dark red"'
+    color9: '"dark green"'
+    comment: ''
+    ctrlpanel: 'False'
+    fc: '0'
+    fftsize: '720'
+    freqhalf: 'True'
+    grid: 'False'
+    gui_hint: ''
+    label: Relative Gain
+    label1: ''
+    label10: ''''''
+    label2: ''''''
+    label3: ''''''
+    label4: ''''''
+    label5: ''''''
+    label6: ''''''
+    label7: ''''''
+    label8: ''''''
+    label9: ''''''
+    legend: 'True'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    name: '""'
+    nconnections: '1'
+    showports: 'False'
+    tr_chan: '0'
+    tr_level: '0.0'
+    tr_mode: qtgui.TRIG_MODE_FREE
+    tr_tag: '""'
+    type: complex
+    units: dB
+    update_time: '0.10'
+    width1: '1'
+    width10: '1'
+    width2: '1'
+    width3: '1'
+    width4: '1'
+    width5: '1'
+    width6: '1'
+    width7: '1'
+    width8: '1'
+    width9: '1'
+    wintype: firdes.WIN_BLACKMAN_hARRIS
+    ymax: '10'
+    ymin: '-140'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [865, 495]
+    rotation: 0
+    state: true
+- name: qtgui_time_sink_x_0
+  id: qtgui_time_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'
+    autoscale: 'False'
+    axislabels: 'True'
+    color1: blue
+    color10: dark blue
+    color2: red
+    color3: green
+    color4: black
+    color5: cyan
+    color6: magenta
+    color7: yellow
+    color8: dark red
+    color9: dark green
+    comment: ''
+    ctrlpanel: 'False'
+    entags: 'True'
+    grid: 'False'
+    gui_hint: ''
+    label1: Signal 1
+    label10: Signal 10
+    label2: Signal 2
+    label3: Signal 3
+    label4: Signal 4
+    label5: Signal 5
+    label6: Signal 6
+    label7: Signal 7
+    label8: Signal 8
+    label9: Signal 9
+    legend: 'True'
+    marker1: '-1'
+    marker10: '-1'
+    marker2: '-1'
+    marker3: '-1'
+    marker4: '-1'
+    marker5: '-1'
+    marker6: '-1'
+    marker7: '-1'
+    marker8: '-1'
+    marker9: '-1'
+    name: '""'
+    nconnections: '1'
+    size: '720'
+    srate: samp_rate
+    stemplot: 'False'
+    style1: '1'
+    style10: '1'
+    style2: '1'
+    style3: '1'
+    style4: '1'
+    style5: '1'
+    style6: '1'
+    style7: '1'
+    style8: '1'
+    style9: '1'
+    tr_chan: '0'
+    tr_delay: '0'
+    tr_level: '0.1'
+    tr_mode: qtgui.TRIG_MODE_FREE
+    tr_slope: qtgui.TRIG_SLOPE_POS
+    tr_tag: '""'
+    type: complex
+    update_time: '0.10'
+    width1: '1'
+    width10: '1'
+    width2: '1'
+    width3: '1'
+    width4: '1'
+    width5: '1'
+    width6: '1'
+    width7: '1'
+    width8: '1'
+    width9: '1'
+    ylabel: Amplitude
+    ymax: '1'
+    ymin: '-1'
+    yunit: '""'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [865, 596]
+    rotation: 0
+    state: true
+- name: uhd_usrp_sink_0
+  id: uhd_usrp_sink
+  parameters:
+    affinity: ''
+    alias: ''
+    ant0: TX/RX
+    ant1: TX/RX
+    ant10: TX/RX
+    ant11: TX/RX
+    ant12: TX/RX
+    ant13: TX/RX
+    ant14: TX/RX
+    ant15: TX/RX
+    ant16: TX/RX
+    ant17: TX/RX
+    ant18: TX/RX
+    ant19: TX/RX
+    ant2: TX/RX
+    ant20: TX/RX
+    ant21: TX/RX
+    ant22: TX/RX
+    ant23: TX/RX
+    ant24: TX/RX
+    ant25: TX/RX
+    ant26: TX/RX
+    ant27: TX/RX
+    ant28: TX/RX
+    ant29: TX/RX
+    ant3: TX/RX
+    ant30: TX/RX
+    ant31: TX/RX
+    ant4: TX/RX
+    ant5: TX/RX
+    ant6: TX/RX
+    ant7: TX/RX
+    ant8: TX/RX
+    ant9: TX/RX
+    bw0: '0'
+    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'
+    center_freq0: rf_freq
+    center_freq1: '0'
+    center_freq10: '0'
+    center_freq11: '0'
+    center_freq12: '0'
+    center_freq13: '0'
+    center_freq14: '0'
+    center_freq15: '0'
+    center_freq16: '0'
+    center_freq17: '0'
+    center_freq18: '0'
+    center_freq19: '0'
+    center_freq2: '0'
+    center_freq20: '0'
+    center_freq21: '0'
+    center_freq22: '0'
+    center_freq23: '0'
+    center_freq24: '0'
+    center_freq25: '0'
+    center_freq26: '0'
+    center_freq27: '0'
+    center_freq28: '0'
+    center_freq29: '0'
+    center_freq3: '0'
+    center_freq30: '0'
+    center_freq31: '0'
+    center_freq4: '0'
+    center_freq5: '0'
+    center_freq6: '0'
+    center_freq7: '0'
+    center_freq8: '0'
+    center_freq9: '0'
+    clock_rate: 0e0
+    clock_source0: ''
+    clock_source1: ''
+    clock_source2: ''
+    clock_source3: ''
+    clock_source4: ''
+    clock_source5: ''
+    clock_source6: ''
+    clock_source7: ''
+    comment: ''
+    dev_addr: '""'
+    dev_args: '""'
+    gain0: tx_gain
+    gain1: '0'
+    gain10: '0'
+    gain11: '0'
+    gain12: '0'
+    gain13: '0'
+    gain14: '0'
+    gain15: '0'
+    gain16: '0'
+    gain17: '0'
+    gain18: '0'
+    gain19: '0'
+    gain2: '0'
+    gain20: '0'
+    gain21: '0'
+    gain22: '0'
+    gain23: '0'
+    gain24: '0'
+    gain25: '0'
+    gain26: '0'
+    gain27: '0'
+    gain28: '0'
+    gain29: '0'
+    gain3: '0'
+    gain30: '0'
+    gain31: '0'
+    gain4: '0'
+    gain5: '0'
+    gain6: '0'
+    gain7: '0'
+    gain8: '0'
+    gain9: '0'
+    len_tag_name: ''
+    lo_export0: 'False'
+    lo_export1: 'False'
+    lo_export10: 'False'
+    lo_export11: 'False'
+    lo_export12: 'False'
+    lo_export13: 'False'
+    lo_export14: 'False'
+    lo_export15: 'False'
+    lo_export16: 'False'
+    lo_export17: 'False'
+    lo_export18: 'False'
+    lo_export19: 'False'
+    lo_export2: 'False'
+    lo_export20: 'False'
+    lo_export21: 'False'
+    lo_export22: 'False'
+    lo_export23: 'False'
+    lo_export24: 'False'
+    lo_export25: 'False'
+    lo_export26: 'False'
+    lo_export27: 'False'
+    lo_export28: 'False'
+    lo_export29: 'False'
+    lo_export3: 'False'
+    lo_export30: 'False'
+    lo_export31: 'False'
+    lo_export4: 'False'
+    lo_export5: 'False'
+    lo_export6: 'False'
+    lo_export7: 'False'
+    lo_export8: 'False'
+    lo_export9: 'False'
+    lo_source0: internal
+    lo_source1: internal
+    lo_source10: internal
+    lo_source11: internal
+    lo_source12: internal
+    lo_source13: internal
+    lo_source14: internal
+    lo_source15: internal
+    lo_source16: internal
+    lo_source17: internal
+    lo_source18: internal
+    lo_source19: internal
+    lo_source2: internal
+    lo_source20: internal
+    lo_source21: internal
+    lo_source22: internal
+    lo_source23: internal
+    lo_source24: internal
+    lo_source25: internal
+    lo_source26: internal
+    lo_source27: internal
+    lo_source28: internal
+    lo_source29: internal
+    lo_source3: internal
+    lo_source30: internal
+    lo_source31: internal
+    lo_source4: internal
+    lo_source5: internal
+    lo_source6: internal
+    lo_source7: internal
+    lo_source8: internal
+    lo_source9: internal
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    nchan: '1'
+    norm_gain0: 'False'
+    norm_gain1: 'False'
+    norm_gain10: 'False'
+    norm_gain11: 'False'
+    norm_gain12: 'False'
+    norm_gain13: 'False'
+    norm_gain14: 'False'
+    norm_gain15: 'False'
+    norm_gain16: 'False'
+    norm_gain17: 'False'
+    norm_gain18: 'False'
+    norm_gain19: 'False'
+    norm_gain2: 'False'
+    norm_gain20: 'False'
+    norm_gain21: 'False'
+    norm_gain22: 'False'
+    norm_gain23: 'False'
+    norm_gain24: 'False'
+    norm_gain25: 'False'
+    norm_gain26: 'False'
+    norm_gain27: 'False'
+    norm_gain28: 'False'
+    norm_gain29: 'False'
+    norm_gain3: 'False'
+    norm_gain30: 'False'
+    norm_gain31: 'False'
+    norm_gain4: 'False'
+    norm_gain5: 'False'
+    norm_gain6: 'False'
+    norm_gain7: 'False'
+    norm_gain8: 'False'
+    norm_gain9: 'False'
+    num_mboards: '1'
+    otw: ''
+    samp_rate: samp_rate
+    sd_spec0: ''
+    sd_spec1: ''
+    sd_spec2: ''
+    sd_spec3: ''
+    sd_spec4: ''
+    sd_spec5: ''
+    sd_spec6: ''
+    sd_spec7: ''
+    show_lo_controls: 'False'
+    stream_args: ''
+    stream_chans: '[]'
+    sync: none
+    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: [666, 361]
+    rotation: 0
+    state: enabled
+- name: virtual_sink_0
+  id: virtual_sink
+  parameters:
+    alias: ''
+    comment: ''
+    stream_id: cmd
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [437, 402]
+    rotation: 0
+    state: true
+- name: virtual_sink_1
+  id: virtual_sink
+  parameters:
+    alias: ''
+    comment: ''
+    stream_id: waveform
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [1172, 186]
+    rotation: 0
+    state: true
+- name: virtual_source_0
+  id: virtual_source
+  parameters:
+    alias: ''
+    comment: ''
+    stream_id: cmd
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [22, 186]
+    rotation: 0
+    state: enabled
+- name: virtual_source_1
+  id: virtual_source
+  parameters:
+    alias: ''
+    comment: ''
+    stream_id: waveform
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [18, 438]
+    rotation: 0
+    state: true
+
+connections:
+- [blocks_multiply_const_vxx_0, '0', blocks_throttle_0, '0']
+- [blocks_multiply_const_vxx_0, '0', uhd_usrp_sink_0, '0']
+- [blocks_pdu_to_tagged_stream_0, '0', digital_ofdm_tx_0, '0']
+- [blocks_tagged_stream_to_pdu_0, pdus, virtual_sink_1, '0']
+- [blocks_throttle_0, '0', qtgui_freq_sink_x_0, '0']
+- [blocks_throttle_0, '0', qtgui_time_sink_x_0, '0']
+- [cdc_dsa_db_connect_0, pkt, blocks_pdu_to_tagged_stream_0, pdus]
+- [cdc_dsa_pu_scenario_0, '0', blocks_multiply_const_vxx_0, '0']
+- [cdc_dsa_pu_scenario_0, cmd, virtual_sink_0, '0']
+- [cdc_fixed_pdu_generator_0, pdu, blocks_pdu_to_tagged_stream_0, pdus]
+- [digital_ofdm_tx_0, '0', blocks_tagged_stream_to_pdu_0, '0']
+- [virtual_source_0, '0', cdc_dsa_db_connect_0, cmd]
+- [virtual_source_0, '0', cdc_fixed_pdu_generator_0, generate]
+- [virtual_source_1, '0', cdc_dsa_pu_scenario_0, pkt]
+
+metadata:
+  file_format: 1
diff --git a/examples/pu_tx_4_channel.grc b/examples/pu_tx_4_channel.grc
new file mode 100644
index 0000000000000000000000000000000000000000..bf45d607e28a4976c8662b09f0720723a1c173b5
--- /dev/null
+++ b/examples/pu_tx_4_channel.grc
@@ -0,0 +1,1057 @@
+options:
+  parameters:
+    author: ''
+    category: '[GRC Hier Blocks]'
+    cmake_opt: ''
+    comment: ''
+    copyright: ''
+    description: ''
+    gen_cmake: 'On'
+    gen_linking: dynamic
+    generate_options: qt_gui
+    hier_block_src_path: '.:'
+    id: tx_ofdm_4chan
+    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: OFDM Transmitter - 4 Channel
+    window_size: ''
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [8, 8]
+    rotation: 0
+    state: enabled
+
+blocks:
+- name: decim_factor
+  id: variable
+  parameters:
+    comment: ''
+    value: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [431, 8]
+    rotation: 0
+    state: enabled
+- name: fft_len
+  id: variable
+  parameters:
+    comment: ''
+    value: '64'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [261, 8]
+    rotation: 0
+    state: enabled
+- name: interp_factor
+  id: variable
+  parameters:
+    comment: ''
+    value: '5'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [330, 8]
+    rotation: 0
+    state: enabled
+- name: pdu_size
+  id: variable
+  parameters:
+    comment: ''
+    value: '64'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [255, 69]
+    rotation: 0
+    state: enabled
+- name: rf_freq
+  id: variable_qtgui_range
+  parameters:
+    comment: ''
+    gui_hint: ''
+    label: RF Frequency
+    min_len: '200'
+    orient: Qt.Horizontal
+    rangeType: float
+    start: 70e6
+    step: 1e3
+    stop: 6e9
+    value: 920e6
+    widget: counter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [1422, 211]
+    rotation: 0
+    state: true
+- name: samp_rate
+  id: variable
+  parameters:
+    comment: ''
+    value: '10000000'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [176, 8]
+    rotation: 0
+    state: enabled
+- name: taps
+  id: variable
+  parameters:
+    comment: ''
+    value: filter.firdes.low_pass(1, samp_rate, 0.98e6,0.5e6)
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [533, 8]
+    rotation: 0
+    state: enabled
+- name: tx_gain
+  id: variable_qtgui_range
+  parameters:
+    comment: ''
+    gui_hint: ''
+    label: Tx Gain
+    min_len: '200'
+    orient: Qt.Horizontal
+    rangeType: int
+    start: '0'
+    step: '1'
+    stop: '92'
+    value: '40'
+    widget: counter
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [1301, 211]
+    rotation: 0
+    state: true
+- name: analog_sig_source_x_0
+  id: analog_sig_source_x
+  parameters:
+    affinity: ''
+    alias: ''
+    amp: '1'
+    comment: ''
+    freq: 1.25e6
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    offset: '0'
+    phase: '0'
+    samp_rate: samp_rate
+    type: complex
+    waveform: analog.GR_COS_WAVE
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [502, 407]
+    rotation: 0
+    state: enabled
+- name: analog_sig_source_x_0_0
+  id: analog_sig_source_x
+  parameters:
+    affinity: ''
+    alias: ''
+    amp: '1'
+    comment: ''
+    freq: -1.25e6
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    offset: '0'
+    phase: '0'
+    samp_rate: samp_rate
+    type: complex
+    waveform: analog.GR_COS_WAVE
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [504, 629]
+    rotation: 0
+    state: enabled
+- name: analog_sig_source_x_0_0_0
+  id: analog_sig_source_x
+  parameters:
+    affinity: ''
+    alias: ''
+    amp: '1'
+    comment: ''
+    freq: 3.75e6
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    offset: '0'
+    phase: '0'
+    samp_rate: samp_rate
+    type: complex
+    waveform: analog.GR_COS_WAVE
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [507, 850]
+    rotation: 0
+    state: enabled
+- name: analog_sig_source_x_0_0_0_0
+  id: analog_sig_source_x
+  parameters:
+    affinity: ''
+    alias: ''
+    amp: '1'
+    comment: ''
+    freq: -3.75e6
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    offset: '0'
+    phase: '0'
+    samp_rate: samp_rate
+    type: complex
+    waveform: analog.GR_COS_WAVE
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [511, 1070]
+    rotation: 0
+    state: enabled
+- name: blocks_add_xx_0
+  id: blocks_add_xx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    num_inputs: '4'
+    type: complex
+    vlen: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [970, 358]
+    rotation: 0
+    state: true
+- name: blocks_multiply_const_vxx_0
+  id: blocks_multiply_const_vxx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    const: '0.025'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    type: complex
+    vlen: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [1086, 402]
+    rotation: 0
+    state: true
+- name: blocks_multiply_xx_0
+  id: blocks_multiply_xx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    num_inputs: '2'
+    type: complex
+    vlen: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [756, 341]
+    rotation: 0
+    state: enabled
+- name: blocks_multiply_xx_0_0
+  id: blocks_multiply_xx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    num_inputs: '2'
+    type: complex
+    vlen: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [755, 563]
+    rotation: 0
+    state: enabled
+- name: blocks_multiply_xx_0_0_0
+  id: blocks_multiply_xx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    num_inputs: '2'
+    type: complex
+    vlen: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [754, 784]
+    rotation: 0
+    state: enabled
+- name: blocks_multiply_xx_0_0_0_0
+  id: blocks_multiply_xx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    num_inputs: '2'
+    type: complex
+    vlen: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [752, 1004]
+    rotation: 0
+    state: enabled
+- name: blocks_pdu_to_tagged_stream_0
+  id: blocks_pdu_to_tagged_stream
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    tag: packet_len
+    type: byte
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [432, 150]
+    rotation: 0
+    state: true
+- name: blocks_tagged_stream_to_pdu_0
+  id: blocks_tagged_stream_to_pdu
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    tag: packet_len
+    type: complex
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [930, 150]
+    rotation: 0
+    state: true
+- name: blocks_throttle_0
+  id: blocks_throttle
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    ignoretag: 'True'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    samples_per_second: samp_rate
+    type: complex
+    vlen: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [1293, 490]
+    rotation: 0
+    state: true
+- name: cdc_dsa_db_connect_0
+  id: cdc_dsa_db_connect
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    host: 127.0.0.1
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    pktsize: pdu_size
+    port: '9000'
+    radiotype: dsa.RADIO_PU_TX
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [213, 197]
+    rotation: 0
+    state: disabled
+- name: cdc_dsa_pu_scenario_0
+  id: cdc_dsa_pu_scenario
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    num_outputs: '4'
+    num_packets: '50'
+    num_queued: '50'
+    scenario: '-1'
+    seed: '1'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [241, 310]
+    rotation: 0
+    state: true
+- name: cdc_fixed_pdu_generator_0
+  id: cdc_fixed_pdu_generator
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    payload: '[ii for ii in range(pdu_size)]'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [211, 150]
+    rotation: 0
+    state: true
+- name: digital_ofdm_tx_0
+  id: digital_ofdm_tx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    cp_len: fft_len // 4
+    fft_len: fft_len
+    header_mod: '"BPSK"'
+    log: 'False'
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    occupied_carriers: ([ii for ii in range(-26, 27) if ii not in (-21, -7, 7, 21)],)
+    packet_len_key: packet_len
+    payload_mod: '"QPSK"'
+    pilot_carriers: ((-21, -7, 7, 21,),)
+    pilot_symbols: ((1, 1, 1, -1,),)
+    rolloff: '0'
+    scramble_bits: 'False'
+    sync_word1: '[0., 0., 0., 0., 0., 0., 0., 1.41421356, 0., -1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356,
+      0., -1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., -1.41421356,
+      0., 1.41421356, 0., 1.41421356, 0., 1.41421356, 0., 0., 0., 0., 0., 0.]'
+    sync_word2: '[0, 0, 0, 0, 0, 0, -1, -1, -1, -1, 1, 1, -1, -1, -1, 1, -1, 1, 1,
+      1, 1, 1, -1, -1, -1, -1, -1, 1, -1, -1, 1, -1, 0, 1, -1, 1, 1, 1, -1, 1, 1,
+      1, -1, 1, 1, 1, 1, -1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 0, 0, 0, 0,
+      0] '
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [689, 62]
+    rotation: 0
+    state: true
+- name: qtgui_sink_x_0
+  id: qtgui_sink_x
+  parameters:
+    affinity: ''
+    alias: ''
+    bw: samp_rate
+    comment: ''
+    fc: '0'
+    fftsize: '1024'
+    gui_hint: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    name: '"OFDM Transmit Waveform - 4 Channels"'
+    plotconst: 'True'
+    plotfreq: 'True'
+    plottime: 'True'
+    plotwaterfall: 'True'
+    rate: '10'
+    showports: 'False'
+    showrf: 'False'
+    type: complex
+    wintype: firdes.WIN_BLACKMAN_hARRIS
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [1505, 474]
+    rotation: 0
+    state: true
+- name: qtgui_time_sink_x_0
+  id: qtgui_time_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'
+    autoscale: 'False'
+    axislabels: 'True'
+    color1: blue
+    color10: dark blue
+    color2: red
+    color3: green
+    color4: black
+    color5: cyan
+    color6: magenta
+    color7: yellow
+    color8: dark red
+    color9: dark green
+    comment: ''
+    ctrlpanel: 'False'
+    entags: 'True'
+    grid: 'False'
+    gui_hint: ''
+    label1: Signal 1
+    label10: Signal 10
+    label2: Signal 2
+    label3: Signal 3
+    label4: Signal 4
+    label5: Signal 5
+    label6: Signal 6
+    label7: Signal 7
+    label8: Signal 8
+    label9: Signal 9
+    legend: 'True'
+    marker1: '-1'
+    marker10: '-1'
+    marker2: '-1'
+    marker3: '-1'
+    marker4: '-1'
+    marker5: '-1'
+    marker6: '-1'
+    marker7: '-1'
+    marker8: '-1'
+    marker9: '-1'
+    name: '""'
+    nconnections: '1'
+    size: '1024'
+    srate: samp_rate
+    stemplot: 'False'
+    style1: '1'
+    style10: '1'
+    style2: '1'
+    style3: '1'
+    style4: '1'
+    style5: '1'
+    style6: '1'
+    style7: '1'
+    style8: '1'
+    style9: '1'
+    tr_chan: '0'
+    tr_delay: '0'
+    tr_level: '0.0'
+    tr_mode: qtgui.TRIG_MODE_FREE
+    tr_slope: qtgui.TRIG_SLOPE_POS
+    tr_tag: '""'
+    type: complex
+    update_time: '0.10'
+    width1: '1'
+    width10: '1'
+    width2: '1'
+    width3: '1'
+    width4: '1'
+    width5: '1'
+    width6: '1'
+    width7: '1'
+    width8: '1'
+    width9: '1'
+    ylabel: Amplitude
+    ymax: '1'
+    ymin: '-1'
+    yunit: '""'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [1504, 583]
+    rotation: 0
+    state: true
+- name: rational_resampler_xxx_0_0
+  id: rational_resampler_xxx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    decim: decim_factor
+    fbw: '0'
+    interp: interp_factor
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    taps: taps
+    type: ccc
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [525, 313]
+    rotation: 0
+    state: enabled
+- name: rational_resampler_xxx_0_0_0
+  id: rational_resampler_xxx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    decim: decim_factor
+    fbw: '0'
+    interp: interp_factor
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    taps: taps
+    type: ccc
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [527, 535]
+    rotation: 0
+    state: enabled
+- name: rational_resampler_xxx_0_0_0_0
+  id: rational_resampler_xxx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    decim: decim_factor
+    fbw: '0'
+    interp: interp_factor
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    taps: taps
+    type: ccc
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [529, 756]
+    rotation: 0
+    state: enabled
+- name: rational_resampler_xxx_0_0_0_0_0
+  id: rational_resampler_xxx
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    decim: decim_factor
+    fbw: '0'
+    interp: interp_factor
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    taps: taps
+    type: ccc
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [533, 976]
+    rotation: 0
+    state: enabled
+- name: uhd_usrp_sink_0
+  id: uhd_usrp_sink
+  parameters:
+    affinity: ''
+    alias: ''
+    ant0: TX/RX
+    ant1: TX/RX
+    ant10: TX/RX
+    ant11: TX/RX
+    ant12: TX/RX
+    ant13: TX/RX
+    ant14: TX/RX
+    ant15: TX/RX
+    ant16: TX/RX
+    ant17: TX/RX
+    ant18: TX/RX
+    ant19: TX/RX
+    ant2: TX/RX
+    ant20: TX/RX
+    ant21: TX/RX
+    ant22: TX/RX
+    ant23: TX/RX
+    ant24: TX/RX
+    ant25: TX/RX
+    ant26: TX/RX
+    ant27: TX/RX
+    ant28: TX/RX
+    ant29: TX/RX
+    ant3: TX/RX
+    ant30: TX/RX
+    ant31: TX/RX
+    ant4: TX/RX
+    ant5: TX/RX
+    ant6: TX/RX
+    ant7: TX/RX
+    ant8: TX/RX
+    ant9: TX/RX
+    bw0: '0'
+    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'
+    center_freq0: rf_freq
+    center_freq1: '0'
+    center_freq10: '0'
+    center_freq11: '0'
+    center_freq12: '0'
+    center_freq13: '0'
+    center_freq14: '0'
+    center_freq15: '0'
+    center_freq16: '0'
+    center_freq17: '0'
+    center_freq18: '0'
+    center_freq19: '0'
+    center_freq2: '0'
+    center_freq20: '0'
+    center_freq21: '0'
+    center_freq22: '0'
+    center_freq23: '0'
+    center_freq24: '0'
+    center_freq25: '0'
+    center_freq26: '0'
+    center_freq27: '0'
+    center_freq28: '0'
+    center_freq29: '0'
+    center_freq3: '0'
+    center_freq30: '0'
+    center_freq31: '0'
+    center_freq4: '0'
+    center_freq5: '0'
+    center_freq6: '0'
+    center_freq7: '0'
+    center_freq8: '0'
+    center_freq9: '0'
+    clock_rate: 0e0
+    clock_source0: ''
+    clock_source1: ''
+    clock_source2: ''
+    clock_source3: ''
+    clock_source4: ''
+    clock_source5: ''
+    clock_source6: ''
+    clock_source7: ''
+    comment: ''
+    dev_addr: '""'
+    dev_args: '""'
+    gain0: tx_gain
+    gain1: '0'
+    gain10: '0'
+    gain11: '0'
+    gain12: '0'
+    gain13: '0'
+    gain14: '0'
+    gain15: '0'
+    gain16: '0'
+    gain17: '0'
+    gain18: '0'
+    gain19: '0'
+    gain2: '0'
+    gain20: '0'
+    gain21: '0'
+    gain22: '0'
+    gain23: '0'
+    gain24: '0'
+    gain25: '0'
+    gain26: '0'
+    gain27: '0'
+    gain28: '0'
+    gain29: '0'
+    gain3: '0'
+    gain30: '0'
+    gain31: '0'
+    gain4: '0'
+    gain5: '0'
+    gain6: '0'
+    gain7: '0'
+    gain8: '0'
+    gain9: '0'
+    len_tag_name: ''
+    lo_export0: 'False'
+    lo_export1: 'False'
+    lo_export10: 'False'
+    lo_export11: 'False'
+    lo_export12: 'False'
+    lo_export13: 'False'
+    lo_export14: 'False'
+    lo_export15: 'False'
+    lo_export16: 'False'
+    lo_export17: 'False'
+    lo_export18: 'False'
+    lo_export19: 'False'
+    lo_export2: 'False'
+    lo_export20: 'False'
+    lo_export21: 'False'
+    lo_export22: 'False'
+    lo_export23: 'False'
+    lo_export24: 'False'
+    lo_export25: 'False'
+    lo_export26: 'False'
+    lo_export27: 'False'
+    lo_export28: 'False'
+    lo_export29: 'False'
+    lo_export3: 'False'
+    lo_export30: 'False'
+    lo_export31: 'False'
+    lo_export4: 'False'
+    lo_export5: 'False'
+    lo_export6: 'False'
+    lo_export7: 'False'
+    lo_export8: 'False'
+    lo_export9: 'False'
+    lo_source0: internal
+    lo_source1: internal
+    lo_source10: internal
+    lo_source11: internal
+    lo_source12: internal
+    lo_source13: internal
+    lo_source14: internal
+    lo_source15: internal
+    lo_source16: internal
+    lo_source17: internal
+    lo_source18: internal
+    lo_source19: internal
+    lo_source2: internal
+    lo_source20: internal
+    lo_source21: internal
+    lo_source22: internal
+    lo_source23: internal
+    lo_source24: internal
+    lo_source25: internal
+    lo_source26: internal
+    lo_source27: internal
+    lo_source28: internal
+    lo_source29: internal
+    lo_source3: internal
+    lo_source30: internal
+    lo_source31: internal
+    lo_source4: internal
+    lo_source5: internal
+    lo_source6: internal
+    lo_source7: internal
+    lo_source8: internal
+    lo_source9: internal
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    nchan: '1'
+    norm_gain0: 'False'
+    norm_gain1: 'False'
+    norm_gain10: 'False'
+    norm_gain11: 'False'
+    norm_gain12: 'False'
+    norm_gain13: 'False'
+    norm_gain14: 'False'
+    norm_gain15: 'False'
+    norm_gain16: 'False'
+    norm_gain17: 'False'
+    norm_gain18: 'False'
+    norm_gain19: 'False'
+    norm_gain2: 'False'
+    norm_gain20: 'False'
+    norm_gain21: 'False'
+    norm_gain22: 'False'
+    norm_gain23: 'False'
+    norm_gain24: 'False'
+    norm_gain25: 'False'
+    norm_gain26: 'False'
+    norm_gain27: 'False'
+    norm_gain28: 'False'
+    norm_gain29: 'False'
+    norm_gain3: 'False'
+    norm_gain30: 'False'
+    norm_gain31: 'False'
+    norm_gain4: 'False'
+    norm_gain5: 'False'
+    norm_gain6: 'False'
+    norm_gain7: 'False'
+    norm_gain8: 'False'
+    norm_gain9: 'False'
+    num_mboards: '1'
+    otw: ''
+    samp_rate: samp_rate
+    sd_spec0: ''
+    sd_spec1: ''
+    sd_spec2: ''
+    sd_spec3: ''
+    sd_spec4: ''
+    sd_spec5: ''
+    sd_spec6: ''
+    sd_spec7: ''
+    show_lo_controls: 'False'
+    stream_args: ''
+    stream_chans: '[]'
+    sync: none
+    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: [1317, 346]
+    rotation: 0
+    state: disabled
+- name: virtual_sink_0
+  id: virtual_sink
+  parameters:
+    alias: ''
+    comment: ''
+    stream_id: waveform
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [1188, 150]
+    rotation: 0
+    state: true
+- name: virtual_sink_1
+  id: virtual_sink
+  parameters:
+    alias: ''
+    comment: ''
+    stream_id: cmd
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [465, 264]
+    rotation: 0
+    state: true
+- name: virtual_source_0
+  id: virtual_source
+  parameters:
+    alias: ''
+    comment: ''
+    stream_id: cmd
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [9, 150]
+    rotation: 0
+    state: true
+- name: virtual_source_1
+  id: virtual_source
+  parameters:
+    alias: ''
+    comment: ''
+    stream_id: waveform
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [12, 370]
+    rotation: 0
+    state: true
+
+connections:
+- [analog_sig_source_x_0, '0', blocks_multiply_xx_0, '1']
+- [analog_sig_source_x_0_0, '0', blocks_multiply_xx_0_0, '1']
+- [analog_sig_source_x_0_0_0, '0', blocks_multiply_xx_0_0_0, '1']
+- [analog_sig_source_x_0_0_0_0, '0', blocks_multiply_xx_0_0_0_0, '1']
+- [blocks_add_xx_0, '0', blocks_multiply_const_vxx_0, '0']
+- [blocks_multiply_const_vxx_0, '0', blocks_throttle_0, '0']
+- [blocks_multiply_const_vxx_0, '0', uhd_usrp_sink_0, '0']
+- [blocks_multiply_xx_0, '0', blocks_add_xx_0, '0']
+- [blocks_multiply_xx_0_0, '0', blocks_add_xx_0, '1']
+- [blocks_multiply_xx_0_0_0, '0', blocks_add_xx_0, '2']
+- [blocks_multiply_xx_0_0_0_0, '0', blocks_add_xx_0, '3']
+- [blocks_pdu_to_tagged_stream_0, '0', digital_ofdm_tx_0, '0']
+- [blocks_tagged_stream_to_pdu_0, pdus, virtual_sink_0, '0']
+- [blocks_throttle_0, '0', qtgui_sink_x_0, '0']
+- [blocks_throttle_0, '0', qtgui_time_sink_x_0, '0']
+- [cdc_dsa_db_connect_0, pkt, blocks_pdu_to_tagged_stream_0, pdus]
+- [cdc_dsa_pu_scenario_0, '0', rational_resampler_xxx_0_0, '0']
+- [cdc_dsa_pu_scenario_0, '1', rational_resampler_xxx_0_0_0, '0']
+- [cdc_dsa_pu_scenario_0, '2', rational_resampler_xxx_0_0_0_0, '0']
+- [cdc_dsa_pu_scenario_0, '3', rational_resampler_xxx_0_0_0_0_0, '0']
+- [cdc_dsa_pu_scenario_0, cmd, virtual_sink_1, '0']
+- [cdc_fixed_pdu_generator_0, pdu, blocks_pdu_to_tagged_stream_0, pdus]
+- [digital_ofdm_tx_0, '0', blocks_tagged_stream_to_pdu_0, '0']
+- [rational_resampler_xxx_0_0, '0', blocks_multiply_xx_0, '0']
+- [rational_resampler_xxx_0_0_0, '0', blocks_multiply_xx_0_0, '0']
+- [rational_resampler_xxx_0_0_0_0, '0', blocks_multiply_xx_0_0_0, '0']
+- [rational_resampler_xxx_0_0_0_0_0, '0', blocks_multiply_xx_0_0_0_0, '0']
+- [virtual_source_0, '0', cdc_dsa_db_connect_0, cmd]
+- [virtual_source_0, '0', cdc_fixed_pdu_generator_0, generate]
+- [virtual_source_1, '0', cdc_dsa_pu_scenario_0, pkt]
+
+metadata:
+  file_format: 1
diff --git a/examples/run_dsa_database.py b/examples/run_dsa_database.py
new file mode 100755
index 0000000000000000000000000000000000000000..8563efee20ef08b3f030ec513d0a888f7865cab3
--- /dev/null
+++ b/examples/run_dsa_database.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python3
+
+import cdc
+
+host = '127.0.0.1'
+port = 9000
+shared_mode = True
+mtu = 1500
+
+iface = cdc.dsa_database(host, port, share_mode, mtu)
+
+while True:
+    pass
diff --git a/examples/test_dsa_database.grc b/examples/test_dsa_database.grc
new file mode 100644
index 0000000000000000000000000000000000000000..5f426d5f2969ee1f5d3298767ebc2a7f2e06e2ea
--- /dev/null
+++ b/examples/test_dsa_database.grc
@@ -0,0 +1,237 @@
+options:
+  parameters:
+    author: ''
+    category: '[GRC Hier Blocks]'
+    cmake_opt: ''
+    comment: ''
+    copyright: University of Melbourne
+    description: ''
+    gen_cmake: 'On'
+    gen_linking: dynamic
+    generate_options: qt_gui
+    hier_block_src_path: '.:'
+    id: test_dsa_database
+    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: DSA Database Test
+    window_size: ''
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [9, 6]
+    rotation: 0
+    state: enabled
+
+blocks:
+- name: blocks_message_debug_0
+  id: blocks_message_debug
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [630, 62]
+    rotation: 0
+    state: true
+- name: blocks_message_strobe_0
+  id: blocks_message_strobe
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    msg: pmt.cons(pmt.intern('get_pkt'), pmt.from_long(1))
+    period: '10'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [68, 153]
+    rotation: 0
+    state: enabled
+- name: blocks_message_strobe_0_0
+  id: blocks_message_strobe
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    msg: pmt.cons(pmt.intern('get_kpi'), pmt.PMT_NIL)
+    period: '1000'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [74, 235]
+    rotation: 0
+    state: enabled
+- name: blocks_message_strobe_0_1
+  id: blocks_message_strobe
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    msg: pmt.cons(pmt.intern('get_pkt'), pmt.from_long(1))
+    period: '20'
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [70, 342]
+    rotation: 0
+    state: enabled
+- name: cdc_dsa_db_connect_0
+  id: cdc_dsa_db_connect
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    host: 127.0.0.1
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    pktsize: '1024'
+    port: '9000'
+    radiotype: dsa.RADIO_PU_TX
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [344, 137]
+    rotation: 0
+    state: true
+- name: cdc_dsa_db_connect_0_0
+  id: cdc_dsa_db_connect
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    host: 127.0.0.1
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    pktsize: '1024'
+    port: '9000'
+    radiotype: dsa.RADIO_SU_TX
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [340, 326]
+    rotation: 0
+    state: enabled
+- name: cdc_dsa_db_connect_0_0_0
+  id: cdc_dsa_db_connect
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    host: 127.0.0.1
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    pktsize: '64'
+    port: '9000'
+    radiotype: dsa.RADIO_SU_RX
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [805, 322]
+    rotation: 0
+    state: enabled
+- name: cdc_dsa_db_connect_1
+  id: cdc_dsa_db_connect
+  parameters:
+    affinity: ''
+    alias: ''
+    comment: ''
+    host: 127.0.0.1
+    maxoutbuf: '0'
+    minoutbuf: '0'
+    pktsize: '64'
+    port: '9000'
+    radiotype: dsa.RADIO_PU_RX
+  states:
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [812, 133]
+    rotation: 0
+    state: enabled
+- name: epy_block_0
+  id: epy_block
+  parameters:
+    _source_code: "\"\"\"\n\"\"\"\n\nimport numpy as np\nfrom gnuradio import gr\n\
+      import pmt\n\nclass put_pkt(gr.sync_block):\n\n    def __init__(self):\n   \
+      \     gr.sync_block.__init__(\n            self,\n            name='DSA Put\
+      \ Packet',   # will show up in GRC\n            in_sig=None,\n            out_sig=None\n\
+      \        )\n        self.message_port_register_in(pmt.intern('pdu'))\n     \
+      \   self.message_port_register_out(pmt.intern('pkt'))\n        self.set_msg_handler(pmt.intern('pdu'),\
+      \ self.handle_msg)\n\n    def handle_msg(self, msg):\n        pkt = pmt.cons(pmt.intern('put_pkt'),\
+      \ pmt.cdr(msg))\n        self.message_port_pub(pmt.intern('pkt'), pkt)\n"
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+  states:
+    _io_cache: ('DSA Put Packet', 'put_pkt', [], [('pdu', 'message', 1)], [('pkt',
+      'message', 1)], '', [])
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [583, 181]
+    rotation: 0
+    state: enabled
+- name: epy_block_0_0
+  id: epy_block
+  parameters:
+    _source_code: "\"\"\"\n\"\"\"\n\nimport numpy as np\nfrom gnuradio import gr\n\
+      import pmt\n\nclass put_pkt(gr.sync_block):\n\n    def __init__(self):\n   \
+      \     gr.sync_block.__init__(\n            self,\n            name='DSA Put\
+      \ Packet',   # will show up in GRC\n            in_sig=None,\n            out_sig=None\n\
+      \        )\n        self.message_port_register_in(pmt.intern('pdu'))\n     \
+      \   self.message_port_register_out(pmt.intern('pkt'))\n        self.set_msg_handler(pmt.intern('pdu'),\
+      \ self.handle_msg)\n\n    def handle_msg(self, msg):\n        pkt = pmt.cons(pmt.intern('put_pkt'),\
+      \ pmt.cdr(msg))\n        self.message_port_pub(pmt.intern('pkt'), pkt)\n"
+    affinity: ''
+    alias: ''
+    comment: ''
+    maxoutbuf: '0'
+    minoutbuf: '0'
+  states:
+    _io_cache: ('DSA Put Packet', 'put_pkt', [], [('pdu', 'message', 1)], [('pkt',
+      'message', 1)], '', [])
+    bus_sink: false
+    bus_source: false
+    bus_structure: null
+    coordinate: [595, 370]
+    rotation: 0
+    state: enabled
+
+connections:
+- [blocks_message_strobe_0, strobe, cdc_dsa_db_connect_0, cmd]
+- [blocks_message_strobe_0_0, strobe, cdc_dsa_db_connect_0, cmd]
+- [blocks_message_strobe_0_1, strobe, cdc_dsa_db_connect_0_0, cmd]
+- [cdc_dsa_db_connect_0, info, blocks_message_debug_0, print]
+- [cdc_dsa_db_connect_0, pkt, epy_block_0, pdu]
+- [cdc_dsa_db_connect_0_0, pkt, epy_block_0_0, pdu]
+- [epy_block_0, pkt, cdc_dsa_db_connect_1, cmd]
+- [epy_block_0_0, pkt, cdc_dsa_db_connect_0_0_0, cmd]
+
+metadata:
+  file_format: 1
diff --git a/grc/CMakeLists.txt b/grc/CMakeLists.txt
index 106888917252a7591927b9dd115d7230b3ca7762..6df79221e204825df08da874c940f72f9ca334f1 100644
--- a/grc/CMakeLists.txt
+++ b/grc/CMakeLists.txt
@@ -21,5 +21,7 @@ install(FILES
     cdc_dsa_pu_scenario.block.yml
     cdc_dsa_db_connect.block.yml
     cdc_dsa_database.block.yml
-    cdc_dsa_db_interface.block.yml DESTINATION share/gnuradio/grc/blocks
+    cdc_dsa_db_interface.block.yml
+    cdc_fixed_pdu_generator.block.yml
+    DESTINATION share/gnuradio/grc/blocks
 )
diff --git a/grc/cdc_dsa_db_connect.block.yml b/grc/cdc_dsa_db_connect.block.yml
index c1eb2224de5f167ecb65dd8bebc77fda2d4c34f1..9241c6797607bef8350cd5b88298fa173be70c50 100644
--- a/grc/cdc_dsa_db_connect.block.yml
+++ b/grc/cdc_dsa_db_connect.block.yml
@@ -2,8 +2,10 @@ id: cdc_dsa_db_connect
 label: DSA DB Connect
 category: '[cdc]'
 templates:
-  imports: import cdc
-  make: cdc.dsa_db_connect(${host}, ${port}, ${pktsize})
+  imports: |-
+      import cdc
+      from cdc import dsa
+  make: cdc.dsa_db_connect(${host}, ${port}, ${pktsize}, ${radiotype})
 
 parameters:
 - id: host
@@ -18,11 +20,17 @@ parameters:
   label: Pktsize (bytes)
   dtype: int
   default: 64
+- id: radiotype
+  label: Radio Type
+  dtype: enum
+  options: [dsa.RADIO_PU_TX, dsa.RADIO_PU_RX, dsa.RADIO_SU_TX, dsa.RADIO_SU_RX]
+  option_labels: [Primary TX, Primary RX, Secondary TX, Secondary RX]
 
 inputs:
 - label: cmd
   domain: message
   optional: true
+
 outputs:
 - label: info
   domain: message
diff --git a/grc/cdc_fixed_pdu_generator.block.yml b/grc/cdc_fixed_pdu_generator.block.yml
new file mode 100644
index 0000000000000000000000000000000000000000..29dd22d13a5e22a1f5c362b43d3935716fbbc3b5
--- /dev/null
+++ b/grc/cdc_fixed_pdu_generator.block.yml
@@ -0,0 +1,23 @@
+id: cdc_fixed_pdu_generator
+label: Fixed PDU Generator
+category: '[cdc]'
+flags: [ python ]
+
+templates:
+  imports: import cdc
+  make: cdc.fixed_pdu_generator(${payload})
+
+parameters:
+- id: payload
+  label: Payload
+  dtype: raw
+
+inputs:
+- label: generate
+  domain: message
+
+outputs:
+- label: pdu
+  domain: message
+
+file_format: 1
diff --git a/include/cdc/CMakeLists.txt b/include/cdc/CMakeLists.txt
index fa7442535ce531201676079a2d14f38aad93eadb..4ab4ae27d13bc4ea6d7cb4a82ce083b3c5fdb5f5 100644
--- a/include/cdc/CMakeLists.txt
+++ b/include/cdc/CMakeLists.txt
@@ -23,6 +23,7 @@
 ########################################################################
 install(FILES
     api.h
+    dsa.h
     dsa_pu_scenario.h
     dsa_db_connect.h
     dsa_database.h
diff --git a/include/cdc/dsa.h b/include/cdc/dsa.h
new file mode 100644
index 0000000000000000000000000000000000000000..a24d9eb04a92501e6b312b21a60462756cecb7e1
--- /dev/null
+++ b/include/cdc/dsa.h
@@ -0,0 +1,69 @@
+/* -*- c++ -*- */
+/*
+ * Copyright 2021 University of Melbourne.
+ *
+ * This is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3, or (at your option)
+ * any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this software; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef INCLUDED_CDC_DSA_H
+#define INCLUDED_CDC_DSA_H
+
+#include <cdc/api.h>
+
+namespace gr {
+namespace cdc {
+
+/*!
+ * \brief 
+ */
+class CDC_API dsa_kpi_report
+{
+public:        
+    int pu_thruput_kbps = 0; //!< primary user throughput (kbps)
+    int pu_offered_kbps = 0; //!< primary user offered throughput (kbps)
+    int su_thruput_kbps = 0; //!< secondary user throughput (kbps)
+    int su_offered_kbps = 0; //!< secondary user offered throughput (kbps)
+};
+
+class CDC_API dsa
+{
+public:
+    enum radio_type
+    {
+        RADIO_PU_TX = 0, //!< primary user transmitter
+        RADIO_PU_RX = 1, //!< primary user receiver
+        RADIO_SU_TX = 2, //!< secondary user transmitter
+        RADIO_SU_RX = 3, //!< secondary user receiver
+    };
+
+    enum request_type
+    {
+        REQUEST_CONNECT = 1,   //!< database connect request
+        REQUEST_GET_PKT = 2,   //!< get new packet request
+        REQUEST_PUT_PKT = 3,   //!< report correctly received packet request
+        REQUEST_GET_KPI = 4,   //!< get current KPI report request
+        REQUEST_GET_MODE = 5,  //!< get current primary user mode request
+        REQUEST_PUT_MODE = 6,  //!< report current primary user mode request
+        REQUEST_RESET_KPI = 7, //!< reset statistics request
+    };
+
+};
+
+} // namespace cdc
+} // namespace gr
+
+#endif /* INCLUDED_CDC_DSA_H */
+
diff --git a/include/cdc/dsa_database.h b/include/cdc/dsa_database.h
index 5bbb7dab691529fb50ab9c61ea266b31c4eb76a5..f72a79871c613806d255bb93f51eb7c7d68baa2c 100644
--- a/include/cdc/dsa_database.h
+++ b/include/cdc/dsa_database.h
@@ -22,48 +22,22 @@
 #define INCLUDED_CDC_DSA_DATABASE_H
 
 #include <cdc/api.h>
+#include <cdc/dsa.h>
+#include <chrono>
 #include <boost/asio.hpp>
-#include <gnuradio/thread/thread.h>
 #include <boost/enable_shared_from_this.hpp>
+#include <gnuradio/thread/thread.h>
 
 namespace gr {
 namespace cdc {
 
-enum dsa_radio_t
-{
-    DSA_RADIO_PU_TX = 1,
-    DSA_RADIO_PU_RX = 2,
-    DSA_RADIO_SU_TX = 3,
-    DSA_RADIO_SU_RX = 4,
-};
-
-enum dsa_request_t
-{
-    DSA_REQUEST_CONNECT = 1,
-    DSA_REQUEST_GET_PKT = 2,
-    DSA_REQUEST_PUT_PKT = 3,
-    DSA_REQUEST_GET_KPI = 4,
-    DSA_REQUEST_GET_MODE = 5,
-    DSA_REQUEST_PUT_MODE = 6,
-    DSA_REQUEST_RESET_KPI = 7,
-};
-
-struct dsa_kpi_report_t
-{
-    int pu_thruput_kbps;
-    int pu_offered_kbps;
-    int su_thruput_kbps;
-    int su_offered_kbps;
-    int pu_rcvd_bytes;
-    int pu_offered_bytes;
-    int su_rcvd_bytes;
-    int su_offered_bytes;
-};
-
 /*!
- * \brief Central database for monitoring primary and secondary link
- * statistics in dynamic spectrum access scenario.
+ * \brief Monitor statistics of dynamic spectrum access scenario.
+ * \ingroup cdc
  *
+ * \details
+ * Implements a central database to monitor the statistics of 
+ * primary and secondary links in CDC dynamic spectrum access scenario.
  */
 class CDC_API dsa_database
     : public boost::enable_shared_from_this<gr::cdc::dsa_database>
@@ -76,18 +50,36 @@ public:
                  bool share_mode,
                  int mtu = 1500);
     ~dsa_database();
-    
+
     sptr base() { return shared_from_this(); };
 
-    //! Start accepting connections to database.
+    //! Start accepting connections and tracking statistics
     void start();
 
+    //! Stop accepting connections and tracking statistics
     void stop();
 
-    dsa_kpi_report_t get_kpi_report() { return d_report; };
+    //! Return current link statistics as KPI report
+    dsa_kpi_report get_kpi_report();
+
+    //! Reset link statistics
+    void reset_stats();
 
-    void reset_kpi_report() { d_report = dsa_kpi_report_t{}; };
+    //! Returns reported primary user operation mode
+    int get_pu_mode();
 
+    //! Updates primary user operation mode
+    void set_pu_mode(int pu_mode);
+
+    /*!
+     * \brief DSA Database Constructor
+     *
+     * \param host          Name or IP address of interface to bind to
+     * \param port          Port number on which to accept incoming connections
+     * \param share_mode    Answer primary user mode requests from secondary link
+                            connections (development mode)
+     * \param mtu           Maximum transmission unit (in bytes)
+     */
     static sptr make(std::string host,
                      int port,
                      bool share_mode,
@@ -98,20 +90,30 @@ private:
     int d_port;
     bool d_share_mode;
     int d_mtu;
-    dsa_kpi_report_t d_report = {};
-    int d_pu_mode = -1;
-
+    
     boost::asio::io_context d_io_context;
     gr::thread::thread d_listener;
-    std::vector<gr::thread::thread *> d_handlers;
-    bool d_stop = false;
+    gr::thread::mutex d_mtx_thr;
+    std::vector<bool> d_radios;
+    bool d_stop = true;
+
+    // statistics
+    gr::thread::mutex d_mtx_stats;
+    std::chrono::time_point<std::chrono::steady_clock> d_timepoint;
+    int d_pu_mode = -1;
+    double d_pu_bytes = 0;
+    double d_pu_offer = 0;
+    double d_su_bytes = 0;
+    double d_su_offer = 0;
 
     void accept_handler();
 
     void request_handler(std::shared_ptr<boost::asio::ip::tcp::socket> sock,
-                         dsa_radio_t radio);
+                         dsa::radio_type radio);
+
+    void update_stats(dsa::radio_type radio,
+                      int nbytes);
 
-    void update_stats(dsa_radio_t radio, int nbytes);
 };
 
 } // namespace cdc
diff --git a/include/cdc/dsa_db_connect.h b/include/cdc/dsa_db_connect.h
index d24180fcd588def77a4032f93eeca9853029e682..ea8f3d51b9f96e5630962ea574217b03bbcccefb 100644
--- a/include/cdc/dsa_db_connect.h
+++ b/include/cdc/dsa_db_connect.h
@@ -22,33 +22,36 @@
 #define INCLUDED_CDC_DSA_DB_CONNECT_H
 
 #include <cdc/api.h>
+#include <cdc/dsa.h>
 #include <gnuradio/sync_block.h>
 
 namespace gr {
-  namespace cdc {
+namespace cdc {
+
+/*!
+ * \brief Connect to DSA database server.
+ * \ingroup cdc
+ */
+class CDC_API dsa_db_connect : virtual public gr::sync_block
+{
+public:
+    typedef boost::shared_ptr<dsa_db_connect> sptr;
 
     /*!
-     * \brief <+description of block+>
-     * \ingroup cdc
-     *
-     */
-    class CDC_API dsa_db_connect : virtual public gr::sync_block
-    {
-     public:
-      typedef boost::shared_ptr<dsa_db_connect> sptr;
-
-      /*!
-       * \brief Return a shared_ptr to a new instance of cdc::dsa_db_connect.
-       *
-       * To avoid accidental use of raw pointers, cdc::dsa_db_connect's
-       * constructor is in a private implementation
-       * class. cdc::dsa_db_connect::make is the public interface for
-       * creating new instances.
-       */
-      static sptr make(std::string host, int port, int pktsize);
-    };
-
-  } // namespace cdc
+    * \brief DSA DB Connect Constructor
+    *
+    * \param host       The name or IP address of DSA database server
+    * \param port       Destination port to connect to on server
+    * \param pktsize    Size of requested packets (in bytes)
+    * \param radio      DSA radio type (Primary Tx, Primary Rx, ...)
+    */
+    static sptr make(std::string host,
+                     int port,
+                     int pktsize,
+                     dsa::radio_type radio);
+};
+
+} // namespace cdc
 } // namespace gr
 
 #endif /* INCLUDED_CDC_DSA_DB_CONNECT_H */
diff --git a/include/cdc/dsa_db_interface.h b/include/cdc/dsa_db_interface.h
index 986a9ebe7c31dfc959266351edd960d6eb9c6215..9f93249289d409d453a6af1625640eaa424788c4 100644
--- a/include/cdc/dsa_db_interface.h
+++ b/include/cdc/dsa_db_interface.h
@@ -22,16 +22,18 @@
 #define INCLUDED_CDC_DSA_DB_INTERFACE_H
 
 #include <cdc/api.h>
-#include <cdc/dsa_database.h>
+#include <cdc/dsa.h>
 #include <gnuradio/block.h>
+#include <boost/asio.hpp>
 #include <boost/enable_shared_from_this.hpp>
 
+
 namespace gr {
 namespace cdc {
 
 /*!
- * \brief <+description+>
- *
+ * \brief Interface to remote DSA database.
+ * \ingroup cdc
  */
 class CDC_API dsa_db_interface
     : public boost::enable_shared_from_this<gr::cdc::dsa_db_interface> 
@@ -44,24 +46,44 @@ public:
 
     sptr base() { return shared_from_this(); };
 
+    /*!
+     * \brief Connect to DSA database
+     *
+     * \param host      Name or IP address of DSA database server
+     * \param port      Destination port number of database
+     * \param radio     DSA radio type (primary tx, primary rx, ...)
+     */
     void connect(std::string host,
                  int port,
-                 int radio);
+                 dsa::radio_type radio);
+
+    //! Disconnect from database server
+    void disconnect();
 
+    //! Get a new data packet of size NBYTES from database
     pmt::pmt_t get_packet(int nbytes);
 
-    void put_packet(pmt::pmt_t pdu);
+    //! Report a correctly received data packet to database
+    void put_packet(pmt::pmt_t& packet);
 
-    //dsa_kpi_report_t get_kpi_report(void);
+    //! Get current statistics as KPI report from database
+    dsa_kpi_report get_kpi_report(void);
         
+    //! Get current primary user mode from database (development only)
     int get_mode(void);
 
+    //! Update primary user mode in remote database
     void put_mode(int mode);
 
+    /*!
+     * \brief DSA DB Interface Constructor
+     *
+     * \param mtu       Maximum transmission unit (in bytes)
+     */
     static sptr make(int mtu = 1500);
 
 private:
-    int d_radio = -1;
+    dsa::radio_type d_radio;
     std::vector<char> d_buf;
     boost::asio::io_context d_io_context;
     std::unique_ptr<boost::asio::ip::tcp::socket> d_socket;
diff --git a/include/cdc/dsa_pu_scenario.h b/include/cdc/dsa_pu_scenario.h
index a1f2a750a55676a1fba7dc0134f9f07962e65e20..674cb4a56319cfa7e2d478a6b5c34e935b9a67c9 100644
--- a/include/cdc/dsa_pu_scenario.h
+++ b/include/cdc/dsa_pu_scenario.h
@@ -25,30 +25,39 @@
 #include <gnuradio/sync_block.h>
 
 namespace gr {
-  namespace cdc {
+namespace cdc {
+
+/*!
+ * \brief Implements primary user scenario control for CDC dynamic spectrum
+          access project.
+ * \ingroup cdc
+ *
+ */
+class CDC_API dsa_pu_scenario : virtual public gr::sync_block
+{
+public:
+    typedef boost::shared_ptr<dsa_pu_scenario> sptr;
 
     /*!
-     * \brief <+description of block+>
-     * \ingroup cdc
-     *
-     */
-    class CDC_API dsa_pu_scenario : virtual public gr::sync_block
-    {
-     public:
-      typedef boost::shared_ptr<dsa_pu_scenario> sptr;
-
-      /*!
-       * \brief Return a shared_ptr to a new instance of cdc::dsa_pu_scenario.
-       *
-       * To avoid accidental use of raw pointers, cdc::dsa_pu_scenario's
-       * constructor is in a private implementation
-       * class. cdc::dsa_pu_scenario::make is the public interface for
-       * creating new instances.
-       */
-      static sptr make(int scenario, int num_packets, int num_queued, int seed);
-    };
-
-  } // namespace cdc
+    * \brief DSA PU Scenario Constructor
+    *
+    * \param scenario       Primary user scenario - bitmap of active channels,
+    *                       e.g., for 4 PU channels SCENARIO = 10 = 0b1010
+                            indicates channels 2 and 4 are active. Set to -1
+    *                       for randomly selected scenarios.
+    * \param num_packets    Number of packets to send before updating random
+                            scenario.
+    * \param num_queued     Number of packets to queue before beginning
+    *                       transmission. 
+    * \param seed           Seed used used in random scenario selection.
+    */
+    static sptr make(int scenario,
+                     int num_packets,
+                     int num_queued,
+                     int seed);
+};
+
+} // namespace cdc
 } // namespace gr
 
 #endif /* INCLUDED_CDC_DSA_PU_SCENARIO_H */
diff --git a/lib/dsa_database.cc b/lib/dsa_database.cc
index c6df5dd11b61778de8acc14c38c2c4abb247049d..18c7530a05cfe14f196259ffa64035725b353760 100644
--- a/lib/dsa_database.cc
+++ b/lib/dsa_database.cc
@@ -26,7 +26,8 @@
 #include <cdc/dsa_database.h>
 #include <iostream>
 
-//using boost::asio;
+using std::chrono::steady_clock;
+using boost::asio::ip::tcp;
 
 namespace gr {
 namespace cdc {
@@ -43,12 +44,15 @@ dsa_database::dsa_database(std::string host,
                            int port,
                            bool share_mode,
                            int mtu)
-  :  d_host(host),
-     d_port(port),
-     d_share_mode(share_mode),
-     d_mtu(mtu)
+  : d_host(host),
+    d_port(port),
+    d_share_mode(share_mode),
+    d_mtu(mtu)
 {
-    d_handlers.resize(4, nullptr);
+    d_radios.resize(4, false);
+    d_timepoint = steady_clock::time_point::min();
+
+    start();
 }
 
 dsa_database::~dsa_database()
@@ -59,80 +63,140 @@ dsa_database::~dsa_database()
 void
 dsa_database::start()
 {
-   d_stop = false;
-   d_listener = gr::thread::thread([this]{ accept_handler(); }); 
+    if (d_stop)
+    {
+        d_stop = false;
+        d_listener = gr::thread::thread([this]{ accept_handler(); }); 
+    }
 }
 
 void
 dsa_database::stop()
 {
-    d_stop = true;
+    if (!d_stop) 
+    {
+        d_stop = true;
+        d_listener.join();
+    }
 }
 
-/*dsa_kpi_report_t
+dsa_kpi_report
 dsa_database::get_kpi_report()
 {
-    return d_report;
-}*/
+    dsa_kpi_report report;
+
+    gr::thread::scoped_lock(d_mtx_stats);
+
+    if (d_timepoint != steady_clock::time_point::min())
+    {
+        auto now = steady_clock::now();
+        std::chrono::duration<double> dur = (now - d_timepoint);
+
+        double pu_thruput = 8 * d_pu_bytes / (1000 * dur.count());
+        double pu_offered = 8 * d_pu_offer / (1000 * dur.count());
+        double su_thruput = 8 * d_su_bytes / (1000 * dur.count());
+        double su_offered = 8 * d_su_offer / (1000 * dur.count());
+        report.pu_thruput_kbps = pu_thruput;
+        report.pu_offered_kbps = pu_offered;
+        report.su_thruput_kbps = su_thruput;
+        report.su_offered_kbps = su_offered;
+   }
+
+    return report;
+}
+
+void
+dsa_database::reset_stats()
+{
+    gr::thread::scoped_lock(d_mtx_stats);
+
+    d_timepoint = steady_clock::now();
+    d_pu_bytes = 0;
+    d_pu_offer = 0;
+    d_su_bytes = 0;
+    d_su_offer = 0;
+}
+
+int
+dsa_database::get_pu_mode()
+{
+    gr::thread::scoped_lock(d_mtx_stats);
+    return d_pu_mode;
+}
+
+void
+dsa_database::set_pu_mode(int pu_mode)
+{
+    gr::thread::scoped_lock(d_mtx_stats);
+    d_pu_mode = pu_mode;
+} 
 
 void
 dsa_database::accept_handler()
 {
-    boost::asio::ip::tcp::acceptor acceptor(
-        d_io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), d_port)); 
     boost::system::error_code ec;
-        
+    tcp::endpoint endpoint(tcp::v4(), d_port);
+    tcp::acceptor acceptor(d_io_context, endpoint);
+    acceptor.set_option(tcp::acceptor::reuse_address(true));
+
+    std::vector<std::shared_ptr<gr::thread::thread>> threads(4);
+
     while (!d_stop)
     {
-        std::cout << "Waiting for connection request...";
-        auto sock = std::make_shared<boost::asio::ip::tcp::socket>(d_io_context);
-        acceptor.accept(*sock);
-        std::cout << "received" << std::endl;
+        auto sock = std::make_shared<tcp::socket>(d_io_context);
+        acceptor.accept(*sock, ec);
+        if (ec) break;
 
         // read connection message
         std::vector<char> buf(2);
         boost::asio::read(*sock, boost::asio::buffer(buf.data(), buf.size()), ec);
-        dsa_request_t req = static_cast<dsa_request_t>(buf[0]);
-        dsa_radio_t radio = static_cast<dsa_radio_t>(buf[1]);
-        
-        // cleanup closed connections
-        for (int i=0; i < d_handlers.size(); i++)
+
+        if (!ec)
         {
-            if (d_handlers[i] != nullptr && d_handlers[i]->joinable())
+            dsa::request_type req = static_cast<dsa::request_type>(buf[0]);
+            dsa::radio_type radio = static_cast<dsa::radio_type>(buf[1]);
+
+            bool in_use;
             {
-                d_handlers[i]->join();
-                delete d_handlers[i];
-                d_handlers[i] = nullptr;
+                gr::thread::scoped_lock(d_mtx_thr);
+                in_use = d_radios[radio];
             }
-        }
 
-        // launch thread to handle requests
-        if (req == DSA_REQUEST_CONNECT && !d_handlers[radio]) 
-        {
-            d_handlers[radio] = new gr::thread::thread(
-                boost::bind(&dsa_database::request_handler, this, sock, radio));
-        }
-        else
-        {
-            sock->close();
+            if (!in_use && req == dsa::REQUEST_CONNECT) 
+            {
+                threads[radio].reset(new gr::thread::thread(
+                    boost::bind(&dsa_database::request_handler, this, sock, radio)));
+                std::cout << "DSA DB: new connection - radio:" << radio << std::endl;
+            }
+            else
+            {
+                sock->close();
+            }
         }
-    } 
+    }
+
+    for (int i = 0; i < threads.size(); i++)
+        threads[i].reset();
 }
 
 void
-dsa_database::request_handler(std::shared_ptr<boost::asio::ip::tcp::socket> sock,
-                              dsa_radio_t radio)
+dsa_database::request_handler(std::shared_ptr<tcp::socket> sock,
+                              dsa::radio_type radio)
 {
+    boost::system::error_code ec;
     std::vector<char> buf(d_mtu);
 
     while (!d_stop && sock->is_open())
     {
-        boost::asio::read(*sock, boost::asio::buffer(buf.data(), 1));
+        boost::asio::read(*sock, boost::asio::buffer(buf.data(), 1), ec);
+        if (ec) break;
 
-        dsa_request_t req = static_cast<dsa_request_t>(buf[0]);
-        if (req == DSA_REQUEST_GET_PKT) 
+        dsa::request_type req = static_cast<dsa::request_type>(buf[0]);
+        if (req == dsa::REQUEST_GET_PKT) 
         {
-            boost::asio::read(*sock, boost::asio::buffer(buf.data(), 4));  
+            boost::asio::read(*sock, boost::asio::buffer(buf.data(), 4), ec);
+            if (ec) break;
+
             int nbytes;
             memcpy(&nbytes, buf.data(), sizeof(int));
             nbytes = ntohl(nbytes);
@@ -140,55 +204,56 @@ dsa_database::request_handler(std::shared_ptr<boost::asio::ip::tcp::socket> sock
             // TODO: randomly generate data
             for (int i=0; i < nbytes; i++) buf[i] = static_cast<char>(i);
 
-            boost::asio::write(*sock, boost::asio::buffer(buf.data(), nbytes));     
+            boost::asio::write(*sock, boost::asio::buffer(buf.data(), nbytes));
             update_stats(radio, nbytes);
         }
-        else if (req == DSA_REQUEST_PUT_PKT)
+        else if (req == dsa::REQUEST_PUT_PKT)
         {
-            boost::asio::read(*sock, boost::asio::buffer(buf.data(), 4));
+            boost::asio::read(*sock, boost::asio::buffer(buf.data(), 4), ec);
+            if (ec) break;
+
             int nbytes;
             memcpy(&nbytes, buf.data(), sizeof(int));
             nbytes = ntohl(nbytes);
+            boost::asio::read(*sock, boost::asio::buffer(buf.data(), nbytes), ec);
+            if (ec) break;
 
-            boost::asio::read(*sock, boost::asio::buffer(buf.data(), nbytes));
+            // TODO: check packet validity
             update_stats(radio, nbytes);
         }
-        else if (req == DSA_REQUEST_GET_KPI) 
+        else if (req == dsa::REQUEST_GET_KPI) 
         {
-            dsa_kpi_report_t report = d_report;//get_kpi_report();
-            
-            report.pu_thruput_kbps  = htonl(report.pu_thruput_kbps);
-            report.pu_offered_kbps  = htonl(report.pu_offered_kbps);
-            report.su_thruput_kbps  = htonl(report.su_thruput_kbps);
-            report.su_offered_kbps  = htonl(report.su_offered_kbps);
-            report.pu_rcvd_bytes    = htonl(report.pu_rcvd_bytes);
-            report.pu_offered_bytes = htonl(report.pu_offered_bytes);
-            report.su_rcvd_bytes    = htonl(report.su_rcvd_bytes);
-            report.su_offered_bytes = htonl(report.su_offered_bytes);
-            memcpy(buf.data(), &report, sizeof(dsa_kpi_report_t));
-
-            boost::asio::write(*sock, boost::asio::buffer(buf.data(), sizeof(dsa_kpi_report_t)));
+            dsa_kpi_report report = get_kpi_report();
+            report.pu_thruput_kbps = htonl(report.pu_thruput_kbps);
+            report.pu_offered_kbps = htonl(report.pu_offered_kbps);
+            report.su_thruput_kbps = htonl(report.su_thruput_kbps);
+            report.su_offered_kbps = htonl(report.su_offered_kbps);
+
+            memcpy(buf.data(), &report, sizeof(dsa_kpi_report));
+            boost::asio::write(*sock, boost::asio::buffer(buf.data(), sizeof(dsa_kpi_report)));
         }
-        else if (req == DSA_REQUEST_GET_MODE)
+        else if (req == dsa::REQUEST_GET_MODE)
         {
             int mode = htonl(d_pu_mode);
             memcpy(buf.data(), &mode, 4);
             boost::asio::write(*sock, boost::asio::buffer(buf.data(), 4));
         }
-        else if (req == DSA_REQUEST_PUT_MODE)
+        else if (req == dsa::REQUEST_PUT_MODE)
         {
-            boost::asio::read(*sock, boost::asio::buffer(buf, sizeof(int)));
+            boost::asio::read(*sock, boost::asio::buffer(buf, sizeof(int)), ec);
+            if (ec) break;
+
             int mode;
             memcpy(&mode, buf.data(), sizeof(int));
             d_pu_mode = ntohl(mode);
         }
-        else if (req == DSA_REQUEST_RESET_KPI)
+        else if (req == dsa::REQUEST_RESET_KPI)
         {
-            if(radio == DSA_RADIO_PU_TX ||
-               radio == DSA_RADIO_PU_RX ||
-                d_share_mode)
+            if(radio == dsa::RADIO_PU_TX ||
+               radio == dsa::RADIO_PU_RX ||
+               d_share_mode)
             {
-                //reset_kpi_report();
+                reset_stats();
             }
         }
         else // invalid request
@@ -198,28 +263,35 @@ dsa_database::request_handler(std::shared_ptr<boost::asio::ip::tcp::socket> sock
 
     }
     sock->close();
+
+    {
+        gr::thread::scoped_lock(d_mtx_thread);
+        d_radios[radio] = false;
+    }
 }
 
 void
-dsa_database::update_stats(dsa_radio_t radio, int nbytes)
+dsa_database::update_stats(dsa::radio_type radio, int nbytes)
 {
-    // stats mutex
-
-    // start timer
+    gr::thread::scoped_lock(d_mtx_stats);
 
+    // start timer on first get_pkt request
+    if (d_timepoint == steady_clock::time_point::min())
+        d_timepoint = steady_clock::now();
+    
     switch(radio)
     {
-    case DSA_RADIO_PU_TX:
-        d_report.pu_offered_bytes += nbytes;
+    case dsa::RADIO_PU_TX:
+        d_pu_offer += nbytes;
         break;
-    case DSA_RADIO_PU_RX:
-        d_report.pu_rcvd_bytes += nbytes;
+    case dsa::RADIO_PU_RX:
+        d_pu_bytes += nbytes;
         break;
-    case DSA_RADIO_SU_TX:
-        d_report.su_offered_bytes += nbytes;
+    case dsa::RADIO_SU_TX:
+        d_su_offer += nbytes;
         break;
-    case DSA_RADIO_SU_RX:
-        d_report.su_rcvd_bytes += nbytes;
+    case dsa::RADIO_SU_RX:
+        d_su_bytes += nbytes;
         break;
     default:
         break;
diff --git a/lib/dsa_db_connect_impl.cc b/lib/dsa_db_connect_impl.cc
index bc11abd0bab2ea8b0dd88f97542fa4e9a6d7cf37..996c468473718eb165fc5d755fad9f8118c091dc 100644
--- a/lib/dsa_db_connect_impl.cc
+++ b/lib/dsa_db_connect_impl.cc
@@ -26,91 +26,113 @@
 #include "dsa_db_connect_impl.h"
 
 namespace gr {
-  namespace cdc {
-
-    dsa_db_connect::sptr
-    dsa_db_connect::make(std::string host, int port, int pktsize)
-    {
-      return gnuradio::get_initial_sptr
-        (new dsa_db_connect_impl(host, port, pktsize));
-    }
+namespace cdc {
+
+dsa_db_connect::sptr
+dsa_db_connect::make(std::string host,
+                     int port,
+                     int pktsize,
+                     dsa::radio_type radio)
+{
+    return gnuradio::get_initial_sptr(
+        new dsa_db_connect_impl(host, port, pktsize, radio));
+}
 
+/*
+ * The private constructor
+ */
+dsa_db_connect_impl::dsa_db_connect_impl(std::string host,
+                                         int port,
+                                         int pktsize,
+                                         dsa::radio_type radio)
+    : gr::sync_block("dsa_db_connect",
+          gr::io_signature::make(0, 0, 0),
+          gr::io_signature::make(0, 0, 0)),
+      d_pktsize(pktsize),
+      d_iface(1500)
+{
+    message_port_register_in(pmt::mp("cmd"));
+    message_port_register_out(pmt::mp("info"));
+    message_port_register_out(pmt::mp("pkt"));
+
+    set_msg_handler(pmt::mp("cmd"),
+                    boost::bind(&dsa_db_connect_impl::handle_cmd, this, _1));
+
+    d_iface.connect(host, port, radio);
+}
 
-    /*
-     * The private constructor
-     */
-    dsa_db_connect_impl::dsa_db_connect_impl(std::string host, int port, int pktsize)
-      : gr::sync_block("dsa_db_connect",
-              gr::io_signature::make(0, 0, 0),
-              gr::io_signature::make(0, 0, 0)),
-        d_packet(pktsize)
+/*
+ * Our virtual destructor.
+ */
+dsa_db_connect_impl::~dsa_db_connect_impl()
+{
+    d_iface.disconnect();
+}
+
+void
+dsa_db_connect_impl::handle_cmd(pmt::pmt_t msg)
+{
+    assert(pmt::pmt::is_pair(msg));
+    std::string cmd = pmt::symbol_to_string(pmt::car(msg));
+    pmt::pmt_t cdr = pmt::cdr(msg);
+
+    if (cmd == "get_pkt") 
     {
-        message_port_register_in(pmt::mp("cmd"));
-        message_port_register_out(pmt::mp("info"));
-        message_port_register_out(pmt::mp("pkt"));
-
-        set_msg_handler(pmt::mp("cmd"),
-                        boost::bind(&dsa_db_connect_impl::handle_cmd, this, _1));
-
-        connect(host, port);
+        int pktcnt = pmt::to_long(cdr);
+        for (int i = 0; i < pktcnt; i++)
+        {
+            pmt::pmt_t pdu = d_iface.get_packet(d_pktsize);
+            message_port_pub(pmt::mp("pkt"), pdu);
+        }
     }
-
-    /*
-     * Our virtual destructor.
-     */
-    dsa_db_connect_impl::~dsa_db_connect_impl()
+    else if (cmd == "put_pkt")
     {
+        d_iface.put_packet(cdr); 
     }
-
-    void
-    dsa_db_connect_impl::connect(std::string host, int port)
+    else if (cmd == "get_kpi")
     {
-        // TODO
+        dsa_kpi_report report = d_iface.get_kpi_report();
+
+        pmt::pmt_t kpi = pmt::make_dict();
+        kpi = pmt::dict_add(kpi,
+                            pmt::mp("pu_thruput_kbps"),
+                            pmt::mp(report.pu_thruput_kbps));
+        kpi = pmt::dict_add(kpi,
+                            pmt::mp("pu_offered_kbps"),
+                            pmt::mp(report.pu_offered_kbps));
+        kpi = pmt::dict_add(kpi,
+                            pmt::mp("su_thruput_kbps"),
+                            pmt::mp(report.su_thruput_kbps));
+        kpi = pmt::dict_add(kpi,
+                            pmt::mp("su_offered_kbps"),
+                            pmt::mp(report.su_offered_kbps));
+
+        message_port_pub(pmt::mp("info"), pmt::cons(pmt::mp("kpi"), kpi));
     }
-
-    void
-    dsa_db_connect_impl::handle_cmd(pmt::pmt_t msg)
+    else if (cmd == "get_mode")
     {
-        assert(pmt::pmt::is_pair(msg));
-        std::string cmd = pmt::symbol_to_string(pmt::car(msg));
-
-        if (cmd == "get_pkt") 
-        {
-            int cnt = pmt::to_long(pmt::cdr(msg));
-
-            // TODO get PDU from database
-            for (int i=0; i < d_packet.size(); i++)
-                d_packet[i] = i;
-
-            // create and send PDU
-            pmt::pmt_t pdu = pmt::make_blob(d_packet.data(), d_packet.size());
-            for (int i = 0; i < cnt; i++)
-                message_port_pub(pmt::mp("pkt"), pmt::cons(pmt::PMT_NIL, pdu));
-        }
-        else if (cmd == "put_pkt")
-        {
-        }
-        else if (cmd == "get_kpi")
-        {
-            message_port_pub(pmt::mp("info"), pmt::cons(pmt::mp("kpi"), pmt::mp(1)));
-        } 
-        else if (cmd == "get_mode")
-        {
-            message_port_pub(pmt::mp("info"), pmt::cons(pmt::mp("mode"), pmt::mp(2)));
-        } 
-        else
-        {
-        }
+        int mode = d_iface.get_mode();
+        message_port_pub(pmt::mp("info"),
+                         pmt::cons(pmt::mp("mode"), pmt::mp(mode)));
+    } 
+    else if (cmd == "put_mode")
+    {
+        d_iface.put_mode(pmt::to_long(cdr));
     }
-
-    int
-    dsa_db_connect_impl::work(int noutput_items,
-        gr_vector_const_void_star &input_items,
-        gr_vector_void_star &output_items)
+    else
     {
-        return 0;
+        // invalid command
     }
+}
+
+int
+dsa_db_connect_impl::work(int noutput_items,
+                          gr_vector_const_void_star &input_items,
+                          gr_vector_void_star &output_items)
+{
+    return 0;
+}
 
-  } /* namespace cdc */
+} /* namespace cdc */
 } /* namespace gr */
 
diff --git a/lib/dsa_db_connect_impl.h b/lib/dsa_db_connect_impl.h
index 9252ee735d79d96d831251f4a36566f76605374b..e18e36048312b192f9d808ec9c896defc6e3242d 100644
--- a/lib/dsa_db_connect_impl.h
+++ b/lib/dsa_db_connect_impl.h
@@ -22,30 +22,33 @@
 #define INCLUDED_CDC_DSA_DB_CONNECT_IMPL_H
 
 #include <cdc/dsa_db_connect.h>
+#include <cdc/dsa_db_interface.h>
 
 namespace gr {
-  namespace cdc {
-
-    class dsa_db_connect_impl : public dsa_db_connect
-    {
-    private:
-        std::vector<char> d_packet;
-
-        void connect(std::string host, int port);
-      
-        void handle_cmd(pmt::pmt_t msg);
-
-    public:
-        dsa_db_connect_impl(std::string host, int port, int pktsize);
-        ~dsa_db_connect_impl();
-
-        // Where all the action really happens
-        int work(int noutput_items,
-                 gr_vector_const_void_star &input_items,
-                 gr_vector_void_star &output_items);
-    };
-
-  } // namespace cdc
+namespace cdc {
+
+class dsa_db_connect_impl : public dsa_db_connect
+{
+private:
+    int d_pktsize;
+    dsa_db_interface d_iface;
+
+    void handle_cmd(pmt::pmt_t msg);
+
+public:
+    dsa_db_connect_impl(std::string host,
+                        int port,
+                        int pktsize,
+                        dsa::radio_type radio);
+    ~dsa_db_connect_impl();
+
+    // Where all the action really happens
+    int work(int noutput_items,
+             gr_vector_const_void_star &input_items,
+             gr_vector_void_star &output_items);
+};
+
+} // namespace cdc
 } // namespace gr
 
 #endif /* INCLUDED_CDC_DSA_DB_CONNECT_IMPL_H */
diff --git a/lib/dsa_db_interface.cc b/lib/dsa_db_interface.cc
index 51ea982b981c4adb5bfc5bf8b53fa95c07533018..ff058947cbdbe47f584c99a42f7a7e73be7a8ca4 100644
--- a/lib/dsa_db_interface.cc
+++ b/lib/dsa_db_interface.cc
@@ -26,6 +26,8 @@
 #include <cdc/dsa_db_interface.h>
 #include <boost/asio.hpp>
 
+using boost::asio::ip::tcp;
+
 namespace gr {
 namespace cdc {
 
@@ -44,81 +46,89 @@ dsa_db_interface::~dsa_db_interface() { }
 void
 dsa_db_interface::connect(std::string host,
                           int port,
-                          int radio)
+                          dsa::radio_type radio)
 {
-    boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port);
+    tcp::endpoint endpoint(tcp::v4(), port);
     boost::system::error_code ec;
-        
-    d_socket.reset(new boost::asio::ip::tcp::socket(d_io_context)); 
+
+    d_socket.reset(new tcp::socket(d_io_context));
     d_socket->connect(endpoint, ec);
-    d_socket->set_option(boost::asio::ip::tcp::no_delay(true));
+    d_socket->set_option(tcp::no_delay(true));
 
     if (ec)
         throw boost::system::system_error(ec);
 
     // send connect message
-    d_buf[0] = static_cast<char>(DSA_REQUEST_CONNECT);
+    d_buf[0] = static_cast<char>(dsa::REQUEST_CONNECT);
     d_buf[1] = static_cast<char>(radio);
     boost::asio::write(*d_socket, boost::asio::buffer(d_buf.data(), 2));
-        
+
     d_radio = radio;
 }
 
+void
+dsa_db_interface::disconnect()
+{
+    if (d_socket->is_open())
+    {
+        d_socket->close();
+    }
+}
+
 pmt::pmt_t
 dsa_db_interface::get_packet(int nbytes)
 {
-    d_buf[0] = static_cast<char>(DSA_REQUEST_GET_PKT);
+    d_buf[0] = static_cast<char>(dsa::REQUEST_GET_PKT);
     int n = htonl(nbytes);
     memcpy(d_buf.data() + 1, &n, sizeof(int));
     boost::asio::write(*d_socket, boost::asio::buffer(d_buf.data(), 5));
 
     boost::asio::read(*d_socket, boost::asio::buffer(d_buf.data(), nbytes));
-    pmt::pmt_t pkt = pmt::make_blob(d_buf.data(), nbytes);
-    
-    return pmt::cons(pmt::PMT_NIL, pkt);
+    pmt::pmt_t payload = pmt::make_blob(d_buf.data(), nbytes);
+
+    return pmt::cons(pmt::PMT_NIL, payload);
 }
 
 void
-dsa_db_interface::put_packet(pmt::pmt_t pdu)
+dsa_db_interface::put_packet(pmt::pmt_t& packet)
 {
-    d_buf[0] = static_cast<char>(DSA_REQUEST_PUT_PKT);
+    d_buf[0] = static_cast<char>(dsa::REQUEST_PUT_PKT);
 
-    pmt::pmt_t vec = pmt::car(pdu);
-    size_t     len = pmt::blob_length(vec);
+    int len = pmt::blob_length(packet);
+    assert(len < d_buf.size() - 1);
+
+    int nbytes = htonl(len);
+    memcpy(d_buf.data() + 1, &nbytes, sizeof(int));
 
     size_t io(0);
-    const uint8_t* ptr = (const uint8_t*)uniform_vector_elements(vec, io);
-    memcpy(d_buf.data() + 1, ptr, len);
+    const uint8_t* ptr = (const uint8_t*)uniform_vector_elements(packet, io);
+    memcpy(d_buf.data() + 5, ptr, len);
 
-    boost::asio::write(*d_socket, boost::asio::buffer(d_buf.data(), len + 1));
+    boost::asio::write(*d_socket, boost::asio::buffer(d_buf.data(), len + 5));
 }
 
-/*dsa_kpi_report_t
+dsa_kpi_report
 dsa_db_interface::get_kpi_report(void)
 {
-    d_buf[0] = static_cast<char>(DSA_REQUEST_GET_KPI);
+    d_buf[0] = static_cast<char>(dsa::REQUEST_GET_KPI);
     boost::asio::write(*d_socket, boost::asio::buffer(d_buf.data(), 1));
-    
-    dsa_kpi_report_t report;
-    boost::asio::read(*d_socket, boost::asio::buffer(d_buf.data(), sizeof(dsa_kpi_report_t)));
-
-    memcpy(&report, d_buf.data(), sizeof(dsa_kpi_report_t));
-    report.pu_thruput_kbps  = ntohl(report.pu_thruput_kbps);
-    report.pu_offered_kbps  = ntohl(report.pu_offered_kbps);
-    report.su_thruput_kbps  = ntohl(report.su_thruput_kbps);
-    report.su_offered_kbps  = ntohl(report.su_offered_kbps);
-    report.pu_rcvd_bytes    = ntohl(report.pu_rcvd_bytes);
-    report.pu_offered_bytes = ntohl(report.pu_offered_bytes);
-    report.su_rcvd_bytes    = ntohl(report.su_rcvd_bytes);
-    report.su_offered_bytes = ntohl(report.su_offered_bytes);
+
+    boost::asio::read(*d_socket, boost::asio::buffer(d_buf.data(), sizeof(dsa_kpi_report)));
+
+    dsa_kpi_report report;
+    memcpy(&report, d_buf.data(), sizeof(dsa_kpi_report));
+    report.pu_thruput_kbps = ntohl(report.pu_thruput_kbps);
+    report.pu_offered_kbps = ntohl(report.pu_offered_kbps);
+    report.su_thruput_kbps = ntohl(report.su_thruput_kbps);
+    report.su_offered_kbps = ntohl(report.su_offered_kbps);
 
     return report;
-}*/
-    
+}
+
 int
 dsa_db_interface::get_mode(void)
 {
-    d_buf[0] = static_cast<char>(DSA_REQUEST_GET_MODE);
+    d_buf[0] = static_cast<char>(dsa::REQUEST_GET_MODE);
     boost::asio::write(*d_socket, boost::asio::buffer(d_buf.data(), 1));
 
     int mode;
@@ -131,7 +141,7 @@ dsa_db_interface::get_mode(void)
 void
 dsa_db_interface::put_mode(int mode)
 {
-    d_buf[0] = static_cast<char>(DSA_REQUEST_PUT_MODE);
+    d_buf[0] = static_cast<char>(dsa::REQUEST_PUT_MODE);
     mode = htonl(mode);
     memcpy(d_buf.data() + 1, &mode, sizeof(4));
     boost::asio::write(*d_socket, boost::asio::buffer(d_buf.data(), 5));
diff --git a/lib/dsa_pu_scenario_impl.cc b/lib/dsa_pu_scenario_impl.cc
index 51130fa3ec7f67fc69e970f19afc8f75e1884ebb..75ae03b79e046d5cba8f788584fc2e59fec3fa99 100644
--- a/lib/dsa_pu_scenario_impl.cc
+++ b/lib/dsa_pu_scenario_impl.cc
@@ -26,117 +26,137 @@
 #include "dsa_pu_scenario_impl.h"
 
 namespace gr {
-  namespace cdc {
+namespace cdc {
 
-    dsa_pu_scenario::sptr
-    dsa_pu_scenario::make(int scenario, int num_packets, int num_queued, int seed)
-    {
-      return gnuradio::get_initial_sptr
-        (new dsa_pu_scenario_impl(scenario, num_packets, num_queued, seed));
-    }
+dsa_pu_scenario::sptr
+dsa_pu_scenario::make(int scenario,
+                      int num_packets,
+                      int num_queued,
+                      int seed)
+{
+    return gnuradio::get_initial_sptr
+      (new dsa_pu_scenario_impl(scenario, num_packets, num_queued, seed));
+}
 
 
-    /*
-     * The private constructor
-     */
-    dsa_pu_scenario_impl::dsa_pu_scenario_impl(int scenario, int num_packets, int num_queued, int seed)
-      : gr::sync_block("dsa_pu_scenario",
-              gr::io_signature::make(0, 0, 0),
-              gr::io_signature::make(1, -1, sizeof(gr_complex))),
-        d_scenario(scenario),
-        d_num_packets(num_packets),
-        d_num_queued(num_queued),
-        d_engine(seed),
-        d_msg_queue(0)
-    {
-        // message port I/O
-        message_port_register_in(pmt::mp("pkt"));
-        message_port_register_out(pmt::mp("cmd"));
-        set_msg_handler(pmt::mp("pkt"), boost::bind(&dsa_pu_scenario_impl::handle_pkt, this, _1));
-        
-        d_random = (d_scenario < 0); 
+/*
+ * The private constructor
+ */
+dsa_pu_scenario_impl::dsa_pu_scenario_impl(int scenario,
+                                           int num_packets,
+                                           int num_queued,
+                                           int seed)
+    : gr::sync_block("dsa_pu_scenario",
+          gr::io_signature::make(0, 0, 0),
+          gr::io_signature::make(1, -1, sizeof(gr_complex))),
+      d_scenario(scenario),
+      d_num_packets(num_packets),
+      d_num_queued(num_queued),
+      d_engine(seed),
+      d_msg_queue(num_queued)
+{
+    // message port I/O
+    message_port_register_in(pmt::mp("pkt"));
+    message_port_register_out(pmt::mp("cmd"));
+    set_msg_handler(pmt::mp("pkt"),
+                    boost::bind(&dsa_pu_scenario_impl::handle_pkt, this, _1));
+
+    d_random = (d_scenario < 0); 
+}
+
+/*
+ * Our virtual destructor.
+ */
+dsa_pu_scenario_impl::~dsa_pu_scenario_impl()
+{
+    delete d_uniform;
+}
+
+void
+dsa_pu_scenario_impl::handle_pkt(pmt::pmt_t msg)
+{
+    d_msg_queue.insert_tail(msg); 
+    if (!d_pkt_len)
+    {    
+        pmt::pmt_t vect = pmt::cdr(msg);
+        d_pkt_len = pmt::blob_length(vect) / sizeof(gr_complex);
     }
+}
 
-    /*
-     * Our virtual destructor.
-     */
-    dsa_pu_scenario_impl::~dsa_pu_scenario_impl()
+void
+dsa_pu_scenario_impl::update_scenario(int n_chan)
+{
+    if (!d_active.size())
     {
-        delete d_uniform;
+        d_active.resize(n_chan);
+            
+        int n_scenarios = (0x1 << n_chan);
+        d_uniform = new std::uniform_int_distribution<int>(0, n_scenarios - 1);
     }
 
-    void
-    dsa_pu_scenario_impl::handle_pkt(pmt::pmt_t msg)
+    if (d_random && d_pkt_cnt <= 0)
     {
-        d_msg_queue.insert_tail(msg); 
-        pmt::pmt_t vect = pmt::cdr(msg);
-        d_pkt_len = pmt::blob_length(vect) / sizeof(gr_complex);
-        d_pkt_req--;
+        d_scenario = (*d_uniform)(d_engine);
+        //message_port_pub(pmt::mp("cmd"), pmt::cons(pmt::mp("put_mode"), pmt::mp(d_scenario)));
+        d_pkt_cnt = d_num_packets;
     }
 
-    void
-    dsa_pu_scenario_impl::update_scenario(int n_chan)
-    {
-        if (!d_active.size())
-        {
-            d_active.resize(n_chan);
-            
-            int n_scenarios = (0x1 << n_chan);
-            d_uniform = new std::uniform_int_distribution<int>(0, n_scenarios - 1);
-        }
+    for (int i_chan=0; i_chan < n_chan; i_chan++)
+        d_active[i_chan] = d_scenario & (0x1 << i_chan);
+}
 
-        if (d_random && d_pkt_cnt <= 0)
-        {
-            d_scenario = (*d_uniform)(d_engine);
-            message_port_pub(pmt::mp("cmd"), pmt::cons(pmt::mp("put_mode"), pmt::mp(d_scenario)));
-            d_pkt_cnt = d_num_packets;
-        }
+int
+dsa_pu_scenario_impl::work(int noutput_items,
+                           gr_vector_const_void_star &input_items,
+                           gr_vector_void_star &output_items)
+{
+    int n_chan = output_items.size();
+    int n_out = 0;
 
-        for (int i_chan=0; i_chan < d_active.size(); i_chan++)
-            d_active[i_chan] = d_scenario & (0x1 << i_chan);        
-    }
+    update_scenario(n_chan);
+    
+    assert(noutput_items >= d_pkt_len);
 
-    int
-    dsa_pu_scenario_impl::work(int noutput_items,
-                               gr_vector_const_void_star &input_items,
-                               gr_vector_void_star &output_items)
+    // output 1 packet per active channel
+    if (d_msg_queue.count() >= n_chan)
     {
-        int n_chan = output_items.size();
-        int n_out = 0;
-        
-        update_scenario(n_chan);
-        
-        // output 1 packet per active channel
-        if (d_msg_queue.count() >= n_chan)
+
+        for(int i_chan = 0; i_chan < n_chan; i_chan++) 
         {
-            for(int i_chan=0; i_chan < n_chan; i_chan++) 
+            gr_complex *out = (gr_complex *)output_items[i_chan];
+            if (d_active[i_chan])
+            {
+                size_t io(0);
+                pmt::pmt_t vect = pmt::cdr(d_msg_queue.delete_head());
+                const uint8_t* ptr = (uint8_t *)uniform_vector_elements(vect, io);
+                memcpy(out, ptr, d_pkt_len * sizeof(gr_complex));
+                d_pkt_req--;
+            }
+            else // inactive channel
             {
-                gr_complex *out = (gr_complex *)output_items[i_chan];
-                if (d_active[i_chan]) 
-                {
-                    size_t io(0);
-                    pmt::pmt_t vect = pmt::cdr(d_msg_queue.delete_head());
-                    const uint8_t* ptr = (uint8_t *)uniform_vector_elements(vect, io);
-                    memcpy(out, ptr, d_pkt_len * sizeof(gr_complex));
-                }
-                else // inactive channel
-                {
-                    memset(out, 0x00, d_pkt_len * sizeof(gr_complex));
-                }
+                memset(out, 0x00, d_pkt_len * sizeof(gr_complex));
             }
-            d_pkt_cnt--;
-            n_out = d_pkt_len;
         }
+        n_out = d_pkt_len;
+        d_pkt_cnt--;
+    }
 
-        // request more packets if our queue is low
-        int num_request = d_num_queued - d_msg_queue.count() - d_pkt_req;
-        message_port_pub(pmt::mp("cmd"), pmt::cons(pmt::mp("get_pkt"), pmt::mp(num_request)));
+    // request more packets if our queue is low
+    int num_request = d_num_queued - d_pkt_req;
+    if (num_request > 0)
+    {
+        for (int i = 0; i < num_request; i++)
+        {
+            message_port_pub(pmt::mp("cmd"),
+                             pmt::cons(pmt::mp("get_pkt"), pmt::mp(num_request)));
+        }
         d_pkt_req += num_request;
-
-        // Tell runtime system how many output items we produced.
-        return n_out;
     }
 
-  } /* namespace cdc */
+    // Tell runtime system how many output items we produced.
+    return n_out;
+}
+
+} /* namespace cdc */
 } /* namespace gr */
 
diff --git a/lib/dsa_pu_scenario_impl.h b/lib/dsa_pu_scenario_impl.h
index fe800b978d2b55b7d66c5128a4cc347c788aa15d..8c4a029a0898c5e3fab68fd327356121cf182f7b 100644
--- a/lib/dsa_pu_scenario_impl.h
+++ b/lib/dsa_pu_scenario_impl.h
@@ -26,44 +26,42 @@
 #include <random>
 
 namespace gr {
-  namespace cdc {
+namespace cdc {
 
-    class dsa_pu_scenario_impl : public dsa_pu_scenario
-    {
-    private:
-        int d_scenario;    // binary map of active channels (-1 is randomly changing scenario)
-        int d_num_packets; // packets per scenario (if random)
-        int d_num_queued;  // queued packets to buffer (increase to avoid underflows)
+class dsa_pu_scenario_impl : public dsa_pu_scenario
+{
+private:
+    int d_scenario;
+    int d_num_packets;
+    int d_num_queued;
       
-        gr::messages::msg_queue d_msg_queue;
-        std::default_random_engine d_engine;
-        std::uniform_int_distribution<int>* d_uniform;
-        std::vector<bool> d_active;
-        bool d_random;
-        int d_pkt_len = 0;
-        int d_pkt_cnt = 0;
-        int d_pkt_req = 0;
+    gr::messages::msg_queue d_msg_queue;
+    std::default_random_engine d_engine;
+    std::uniform_int_distribution<int>* d_uniform;
+    std::vector<bool> d_active;
+    bool d_random;
+    int d_pkt_len = 0;
+    int d_pkt_req = 0;
+    int d_pkt_cnt = 0;
 
-        void handle_pkt(pmt::pmt_t msg);
+    void handle_pkt(pmt::pmt_t msg);
 
-        void update_scenario(int n_chan);
-  
+    void update_scenario(int n_chan);
 
-     public:
-        dsa_pu_scenario_impl(int scenario,
-                             int num_packets,
-                             int num_queued,
-                             int seed);
-        ~dsa_pu_scenario_impl();
+public:
+    dsa_pu_scenario_impl(int scenario,
+                         int num_packets,
+                         int num_queued,
+                         int seed);
+    ~dsa_pu_scenario_impl();
 
-        // Where all the action really happens
-        int work(int noutput_items,
-                 gr_vector_const_void_star &input_items,
-                 gr_vector_void_star &output_items
-      );
-    };
+    int work(int noutput_items,
+             gr_vector_const_void_star &input_items,
+             gr_vector_void_star &output_items);
 
-  } // namespace cdc
+};
+
+} // namespace cdc
 } // namespace gr
 
 #endif /* INCLUDED_CDC_DSA_PU_SCENARIO_IMPL_H */
diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt
index 5606ecf269873f66231cbf8e9c74905e05e38595..7cca66fa3aee5b9c851d9f854db46e7eb83c268d 100644
--- a/python/CMakeLists.txt
+++ b/python/CMakeLists.txt
@@ -32,6 +32,7 @@ endif()
 GR_PYTHON_INSTALL(
     FILES
     __init__.py
+    fixed_pdu_generator.py
     DESTINATION ${GR_PYTHON_DIR}/cdc
 )
 
diff --git a/python/__init__.py b/python/__init__.py
index d47a67d4bc20e5f62f6a39477d9fe214adee1fff..467035aaac0d3eb9a12c68fb2e9ce554620b9bbf 100644
--- a/python/__init__.py
+++ b/python/__init__.py
@@ -33,3 +33,4 @@ except ImportError:
 
 # import any pure python here
 #
+from .fixed_pdu_generator import fixed_pdu_generator
diff --git a/python/fixed_pdu_generator.py b/python/fixed_pdu_generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..dcffb8e374995a51f6025c4be4b7eeb44e0dc19b
--- /dev/null
+++ b/python/fixed_pdu_generator.py
@@ -0,0 +1,29 @@
+#
+# Copyright 2021 University of Melbourne.
+#
+
+import numpy as np
+from gnuradio import gr
+import pmt
+
+class fixed_pdu_generator(gr.sync_block):
+    """
+    This block sends a PDU with a specified payload whenever a message is
+    received on its input message port.
+    """
+    def __init__(self, payload=[]):
+        gr.sync_block.__init__(
+            self,
+            name='fixed_pdu_generator',
+            in_sig=None,
+            out_sig=None,
+        )
+        self.message_port_register_in(pmt.intern('generate'))
+        self.message_port_register_out(pmt.intern('pdu'))
+        self.set_msg_handler(pmt.intern('generate'), self.handle_msg)
+        self.payload = payload
+
+    def handle_msg(self, msg):
+        pdu = pmt.cons(
+            pmt.PMT_NIL, pmt.init_u8vector(len(self.payload), self.payload))
+        self.message_port_pub(pmt.intern('pdu'), pdu)
diff --git a/swig/cdc_swig.i b/swig/cdc_swig.i
index eaf747fab5399f419e9f3f8f2f347d174d143f70..5d817a3fd7da89890702ec9300d95f9f51600934 100644
--- a/swig/cdc_swig.i
+++ b/swig/cdc_swig.i
@@ -8,15 +8,16 @@
 %include "cdc_swig_doc.i"
 
 %{
+#include "cdc/dsa.h"
 #include "cdc/dsa_pu_scenario.h"
 #include "cdc/dsa_db_connect.h"
 #include "cdc/dsa_database.h"
 #include "cdc/dsa_db_interface.h"
 %}
 
+%include "cdc/dsa.h"
 %include "cdc/dsa_pu_scenario.h"
 GR_SWIG_BLOCK_MAGIC2(cdc, dsa_pu_scenario);
-
 %include "cdc/dsa_db_connect.h"
 GR_SWIG_BLOCK_MAGIC2(cdc, dsa_db_connect);
 %include "cdc/dsa_database.h"