diff options
19 files changed, 1802 insertions, 2 deletions
diff --git a/gnuradio-runtime/include/gnuradio/pdu.h b/gnuradio-runtime/include/gnuradio/pdu.h index d7dc0a2550..b930f3d0df 100644 --- a/gnuradio-runtime/include/gnuradio/pdu.h +++ b/gnuradio-runtime/include/gnuradio/pdu.h @@ -18,6 +18,9 @@ namespace gr { namespace metadata_keys { GR_RUNTIME_API const pmt::pmt_t pdu_num(); +GR_RUNTIME_API const pmt::pmt_t tx_eob(); +GR_RUNTIME_API const pmt::pmt_t tx_time(); +GR_RUNTIME_API const pmt::pmt_t tx_sob(); } /* namespace metadata_keys */ namespace msgport_names { diff --git a/gnuradio-runtime/lib/pdu.cc b/gnuradio-runtime/lib/pdu.cc index f860bf395d..7b75fbac1a 100644 --- a/gnuradio-runtime/lib/pdu.cc +++ b/gnuradio-runtime/lib/pdu.cc @@ -22,8 +22,24 @@ const pmt::pmt_t pdu_num() static const pmt::pmt_t val = pmt::mp("pdu_num"); return val; } +const pmt::pmt_t tx_eob() +{ + static const pmt::pmt_t val = pmt::mp("tx_eob"); + return val; +} +const pmt::pmt_t tx_time() +{ + static const pmt::pmt_t val = pmt::mp("tx_time"); + return val; +} +const pmt::pmt_t tx_sob() +{ + static const pmt::pmt_t val = pmt::mp("tx_sob"); + return val; +} } /* namespace metadata_keys */ + namespace msgport_names { const pmt::pmt_t bpdu() diff --git a/gnuradio-runtime/python/gnuradio/gr/bindings/docstrings/pdu_pydoc_template.h b/gnuradio-runtime/python/gnuradio/gr/bindings/docstrings/pdu_pydoc_template.h index 95180a0309..ebc11a8351 100644 --- a/gnuradio-runtime/python/gnuradio/gr/bindings/docstrings/pdu_pydoc_template.h +++ b/gnuradio-runtime/python/gnuradio/gr/bindings/docstrings/pdu_pydoc_template.h @@ -18,6 +18,15 @@ static const char* __doc_gr_metadata_keys_pdu_num = R"doc()doc"; +static const char* __doc_gr_metadata_keys_tx_eob = R"doc()doc"; + + +static const char* __doc_gr_metadata_keys_tx_time = R"doc()doc"; + + +static const char* __doc_gr_metadata_keys_tx_sob = R"doc()doc"; + + static const char* __doc_gr_msgport_names_bpdu = R"doc()doc"; diff --git a/gnuradio-runtime/python/gnuradio/gr/bindings/pdu_python.cc b/gnuradio-runtime/python/gnuradio/gr/bindings/pdu_python.cc index e6dec295e7..407a72a823 100644 --- a/gnuradio-runtime/python/gnuradio/gr/bindings/pdu_python.cc +++ b/gnuradio-runtime/python/gnuradio/gr/bindings/pdu_python.cc @@ -14,7 +14,7 @@ /* BINDTOOL_GEN_AUTOMATIC(0) */ /* BINDTOOL_USE_PYGCCXML(0) */ /* BINDTOOL_HEADER_FILE(pdu.h) */ -/* BINDTOOL_HEADER_FILE_HASH(e6887fee8ea637d80f0ed7b6f2ffc552) */ +/* BINDTOOL_HEADER_FILE_HASH(562b6c641c014996cd7a94b2252030d1) */ /***********************************************************************************/ #include <pybind11/complex.h> @@ -48,6 +48,13 @@ void bind_pdu(py::module& m) m_metadata_keys.def( "pdu_num", &::gr::metadata_keys::pdu_num, D(metadata_keys, pdu_num)); + m_metadata_keys.def("tx_eob", &::gr::metadata_keys::tx_eob, D(metadata_keys, tx_eob)); + + m_metadata_keys.def( + "tx_time", &::gr::metadata_keys::tx_time, D(metadata_keys, tx_time)); + + m_metadata_keys.def("tx_sob", &::gr::metadata_keys::tx_sob, D(metadata_keys, tx_sob)); + py::module m_msgport_names = m.def_submodule("msgport_names"); diff --git a/gr-pdu/examples/pdu_lambda_chirp_demo.grc b/gr-pdu/examples/pdu_lambda_chirp_demo.grc new file mode 100644 index 0000000000..020c062e89 --- /dev/null +++ b/gr-pdu/examples/pdu_lambda_chirp_demo.grc @@ -0,0 +1,824 @@ +options: + parameters: + author: J. Gilbert + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: J. Gilbert + description: Use of PDU Lambda block + gen_cmake: 'On' + gen_linking: dynamic + generate_options: qt_gui + hier_block_src_path: '.:' + id: pdu_lambda_chirp_demo + 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: PDU Lambda Chirp Demo + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [8, 8] + rotation: 0 + state: enabled + +blocks: +- name: N + id: variable + parameters: + comment: '' + value: 1024*4 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [432, 12.0] + rotation: 0 + state: enabled +- name: k + id: variable_qtgui_range + parameters: + comment: '' + gui_hint: '' + label: Gain + min_len: '200' + orient: QtCore.Qt.Horizontal + rangeType: float + start: '0.1' + step: '0.01' + stop: '2' + value: '0.5' + widget: counter_slider + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [296, 12.0] + rotation: 0 + state: true +- name: samp_rate + id: variable + parameters: + comment: '' + value: '1000000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [208, 12.0] + rotation: 0 + state: enabled +- name: vec + id: variable + parameters: + comment: '' + value: pmt.init_f32vector(N, np.arange(N)) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [72, 252.0] + rotation: 0 + state: enabled +- name: demod_time_sink + 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: 1,1,1,1 + 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: msg_float + 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: '5' + ymin: '-5' + yunit: '""' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1048, 356.0] + rotation: 0 + state: enabled +- name: note + id: note + parameters: + alias: '' + comment: 'This flowgraph will generate and transmit random up or down + + chirps from a USRP using PDU Lambda and the PDU to Stream + + blocks. It is not intended to be a practical example, but it can be + + relatively easily modified to transmit meaningful signals.' + note: Flowgraph Summary + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [592, 12.0] + rotation: 0 + state: true +- name: pdu_lambda1 + id: pylambda_pdu_lambda + parameters: + affinity: '' + alias: '' + comment: Generate Random Chirp + fn: 'lambda x: k * np.complex64(np.exp(2*np.pi * 1j * x * x * (0.05 * (np.random.randint(40)-19.5) + / len(x)) ))' + key: pmt.intern("key") + maxoutbuf: '0' + metadict: Uniform Vector + minoutbuf: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [360, 324.0] + rotation: 0 + state: true +- name: pdu_lambda2 + id: pylambda_pdu_lambda + parameters: + affinity: '' + alias: '' + comment: Frequency Demod + fn: 'lambda x: np.float32(np.unwrap(np.angle(x[1:]) - np.angle(x[:-1])))' + key: pmt.intern("key") + maxoutbuf: '0' + metadict: Uniform Vector + minoutbuf: '0' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [736, 364.0] + rotation: 0 + state: enabled +- name: pdu_to_stream + id: pdu_pdu_to_stream_X + parameters: + affinity: '' + alias: '' + comment: '' + depth: '64' + early_behavior: pdu.EARLY_BURST_APPEND + maxoutbuf: '0' + minoutbuf: '0' + type: complex + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [744, 268.0] + rotation: 0 + state: true +- name: stream_time_sink + 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: N*2 + 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: [1048, 252.0] + rotation: 0 + state: true +- name: strobe + id: blocks_message_strobe + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + msg: pmt.cons(pmt.PMT_NIL, vec) + period: '250' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [64, 316.0] + rotation: 0 + state: true +- name: time_sink + 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: 0,1,1,1 + 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: msg_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: [360, 244.0] + rotation: 180 + state: true +- name: uhd_sink + 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: 915e6 + 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: '0' + 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' + gain_type0: default + gain_type1: default + gain_type10: default + gain_type11: default + gain_type12: default + gain_type13: default + gain_type14: default + gain_type15: default + gain_type16: default + gain_type17: default + gain_type18: default + gain_type19: default + gain_type2: default + gain_type20: default + gain_type21: default + gain_type22: default + gain_type23: default + gain_type24: default + gain_type25: default + gain_type26: default + gain_type27: default + gain_type28: default + gain_type29: default + gain_type3: default + gain_type30: default + gain_type31: default + gain_type4: default + gain_type5: default + gain_type6: default + gain_type7: default + gain_type8: default + gain_type9: default + 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' + 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' + start_time: '-1.0' + stream_args: '' + stream_chans: '[]' + sync: sync + time_source0: '' + time_source1: '' + time_source2: '' + time_source3: '' + time_source4: '' + time_source5: '' + time_source6: '' + time_source7: '' + type: fc32 + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [1048, 116.0] + rotation: 0 + state: disabled +- name: waterfall + id: qtgui_waterfall_sink_x + parameters: + affinity: '' + alias: '' + alpha1: '1.0' + alpha10: '1.0' + alpha2: '1.0' + alpha3: '1.0' + alpha4: '1.0' + alpha5: '1.0' + alpha6: '1.0' + alpha7: '1.0' + alpha8: '1.0' + alpha9: '1.0' + axislabels: 'True' + bw: samp_rate + color1: '0' + color10: '0' + color2: '0' + color3: '0' + color4: '0' + color5: '0' + color6: '0' + color7: '0' + color8: '0' + color9: '0' + comment: '' + fc: '0' + fftsize: '128' + freqhalf: 'True' + grid: 'False' + gui_hint: 0,0,2,1 + int_max: '10' + int_min: '-140' + label1: '' + label10: '' + label2: '' + label3: '' + label4: '' + label5: '' + label6: '' + label7: '' + label8: '' + label9: '' + legend: 'True' + maxoutbuf: '0' + minoutbuf: '0' + name: '""' + nconnections: '1' + showports: 'False' + type: msg_complex + update_time: '0.10' + wintype: window.WIN_BLACKMAN_hARRIS + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [360, 164.0] + rotation: 180 + state: true + +connections: +- [pdu_lambda1, pdu, pdu_lambda2, pdu] +- [pdu_lambda1, pdu, pdu_to_stream, pdus] +- [pdu_lambda1, pdu, time_sink, in] +- [pdu_lambda1, pdu, waterfall, in] +- [pdu_lambda2, pdu, demod_time_sink, in] +- [pdu_to_stream, '0', stream_time_sink, '0'] +- [pdu_to_stream, '0', uhd_sink, '0'] +- [strobe, strobe, pdu_lambda1, pdu] + +metadata: + file_format: 1 diff --git a/gr-pdu/examples/pdu_lambda_example.grc b/gr-pdu/examples/pdu_lambda_example.grc index 1be15164c6..2b7b280990 100644 --- a/gr-pdu/examples/pdu_lambda_example.grc +++ b/gr-pdu/examples/pdu_lambda_example.grc @@ -191,7 +191,7 @@ blocks: coordinate: [104, 276.0] rotation: 180 state: enabled -- name: note_0 +- name: note id: note parameters: alias: '' diff --git a/gr-pdu/examples/simple_pdu_to_stream_example.grc b/gr-pdu/examples/simple_pdu_to_stream_example.grc new file mode 100644 index 0000000000..f14a9a3717 --- /dev/null +++ b/gr-pdu/examples/simple_pdu_to_stream_example.grc @@ -0,0 +1,276 @@ +options: + parameters: + author: J. Gilbert + catch_exceptions: 'True' + category: '[GRC Hier Blocks]' + cmake_opt: '' + comment: '' + copyright: 2021 J. Gilbert + description: Simple example of generating periodic bursts from PDUs + gen_cmake: 'On' + gen_linking: dynamic + generate_options: qt_gui + hier_block_src_path: '.:' + id: simple_pdu_to_stream_example + 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: Simple PDU To Stream Example + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [16, 12.0] + rotation: 0 + state: enabled + +blocks: +- name: N + id: variable + parameters: + comment: '' + value: '10000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [376, 12.0] + rotation: 0 + state: enabled +- name: samp_rate + id: variable + parameters: + comment: '' + value: '32000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [208, 12.0] + rotation: 0 + state: enabled +- name: v + id: variable + parameters: + comment: '' + value: pmt.init_c32vector(N,np.arange(N).tolist()) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [456, 12.0] + rotation: 0 + state: enabled +- name: imp + id: import + parameters: + alias: '' + comment: '' + imports: import numpy as np + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [296, 12.0] + rotation: 0 + state: true +- name: multiply + id: blocks_multiply_const_vxx + parameters: + affinity: '' + alias: '' + comment: '' + const: 1/N + maxoutbuf: '0' + minoutbuf: '0' + type: complex + vlen: '1' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [872, 212.0] + rotation: 0 + state: true +- name: note + id: note + parameters: + alias: '' + comment: 'This is a simple example showing how the PDU to Stream block can + + convert PDU data to streaming data for transmission from a SDR + + such as a USRP including the addition of appropriate stream tags.' + note: Flowgraph Summary + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [624, 12.0] + rotation: 0 + state: true +- name: pdu_set + id: pdu_pdu_set + parameters: + affinity: '' + alias: '' + comment: '' + k: pmt.intern("tx_time") + maxoutbuf: '0' + minoutbuf: '0' + v: pmt.make_tuple(pmt.from_uint64(1),pmt.from_double(0.1234)) + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [360, 204.0] + rotation: 0 + state: true +- name: pdu_to_stream + id: pdu_pdu_to_stream_X + parameters: + affinity: '' + alias: '' + comment: '' + depth: '64' + early_behavior: pdu.EARLY_BURST_APPEND + maxoutbuf: '0' + minoutbuf: '0' + type: complex + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [600, 212.0] + rotation: 0 + state: true +- name: strobe + id: blocks_message_strobe + parameters: + affinity: '' + alias: '' + comment: '' + maxoutbuf: '0' + minoutbuf: '0' + msg: pmt.cons(pmt.PMT_NIL, v) + period: '1000' + states: + bus_sink: false + bus_source: false + bus_structure: null + coordinate: [72, 204.0] + rotation: 0 + state: true +- name: time_sink + 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: N*2 + 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: [1128, 196.0] + rotation: 0 + state: true + +connections: +- [multiply, '0', time_sink, '0'] +- [pdu_set, pdus, pdu_to_stream, pdus] +- [pdu_to_stream, '0', multiply, '0'] +- [strobe, strobe, pdu_set, pdus] + +metadata: + file_format: 1 diff --git a/gr-pdu/grc/pdu.tree.yml b/gr-pdu/grc/pdu.tree.yml index c1e6a5aca5..2e444e3a44 100644 --- a/gr-pdu/grc/pdu.tree.yml +++ b/gr-pdu/grc/pdu.tree.yml @@ -6,6 +6,7 @@ - pdu_pdu_remove - pdu_pdu_set - pdu_pdu_split + - pdu_pdu_to_stream_x - pdu_pdu_to_tagged_stream - pdu_tagged_stream_to_pdu - pdu_random_pdu diff --git a/gr-pdu/grc/pdu_pdu_to_stream.block.yml b/gr-pdu/grc/pdu_pdu_to_stream.block.yml new file mode 100644 index 0000000000..801fd8d5d5 --- /dev/null +++ b/gr-pdu/grc/pdu_pdu_to_stream.block.yml @@ -0,0 +1,43 @@ +id: pdu_pdu_to_stream_x +label: PDU To Stream +flags: [ python ] + +parameters: +- id: type + label: PDU Type + dtype: enum + options: [complex, float, int, short, byte] + option_labels: [Complex, Float, Int, Short, Byte] + option_attributes: + shortform: [c, f, i, s, b] + hide: part +- id: early_behavior + label: Early Behavior + dtype: enum + options: [pdu.EARLY_BURST_APPEND, pdu.EARLY_BURST_DROP, pdu.EARLY_BURST_BALK] + option_labels: [Append, Drop, Balk] +- id: depth + label: Queue Depth + dtype: int + default: '64' + hide: part + +inputs: +- domain: message + id: pdus + +outputs: +- domain: stream + dtype: ${ type } + +asserts: +- ${ depth > 2 } +- ${ depth <= 65536 } + +templates: + imports: from gnuradio import pdu + make: pdu.pdu_to_stream_${type.shortform}(${early_behavior}, ${depth}) + callbacks: + - set_max_queue_size(${depth}) + +file_format: 1 diff --git a/gr-pdu/include/gnuradio/pdu/CMakeLists.txt b/gr-pdu/include/gnuradio/pdu/CMakeLists.txt index 89057c021d..5ecb3a06a1 100644 --- a/gr-pdu/include/gnuradio/pdu/CMakeLists.txt +++ b/gr-pdu/include/gnuradio/pdu/CMakeLists.txt @@ -15,6 +15,7 @@ install(FILES pdu_remove.h pdu_set.h pdu_split.h + pdu_to_stream.h pdu_to_tagged_stream.h random_pdu.h tagged_stream_to_pdu.h diff --git a/gr-pdu/include/gnuradio/pdu/pdu_to_stream.h b/gr-pdu/include/gnuradio/pdu/pdu_to_stream.h new file mode 100644 index 0000000000..2186ca17e6 --- /dev/null +++ b/gr-pdu/include/gnuradio/pdu/pdu_to_stream.h @@ -0,0 +1,72 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 NTESS LLC. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + * + */ + +#ifndef INCLUDED_PDU_PDU_TO_STREAM_H +#define INCLUDED_PDU_PDU_TO_STREAM_H + +#include <gnuradio/pdu.h> +#include <gnuradio/pdu/api.h> +#include <gnuradio/sync_block.h> + +namespace gr { +namespace pdu { + +enum early_pdu_behavior_t { + EARLY_BURST_APPEND = 0, + EARLY_BURST_DROP, + EARLY_BURST_BALK, +}; + +/*! + * \brief Emit a PDU data as a simple GR stream. + * + * The PDU to Stream block takes data stored in the uniform vector PDU + * and emits it with UHD style tx_sob and tx_eob tags. If desired, the + * metadata dictionary can contain a tx_time tag which will append a UHD + * style tx_time tag to the tx_sob sample causing transmission at a well + * defined point in time. + * + * \ingroup pdu_utils + * + */ +template <class T> +class PDU_API pdu_to_stream : virtual public gr::sync_block +{ +public: + typedef std::shared_ptr<pdu_to_stream<T>> sptr; + + /*! + * \brief Return a shared_ptr to a new instance of pdu_utils::pdu_to_stream_x. + * + * @param early_pdu_behavior - behavior for bursts received before previous one is + * done processing + * @param max_queue_size - max number of PDUs to queue + */ + static sptr make(early_pdu_behavior_t early_pdu_behavior, + uint32_t max_queue_size = 64); + + /** + * Set Max Queue size + * + * @param size - maximum number of queued bursts + */ + virtual void set_max_queue_size(uint32_t size) = 0; +}; + +typedef pdu_to_stream<unsigned char> pdu_to_stream_b; +typedef pdu_to_stream<short> pdu_to_stream_s; +typedef pdu_to_stream<short> pdu_to_stream_i; +typedef pdu_to_stream<float> pdu_to_stream_f; +typedef pdu_to_stream<gr_complex> pdu_to_stream_c; +} // namespace pdu +} // namespace gr + +#endif /* INCLUDED_PDU_PDU_TO_STREAM_H */ diff --git a/gr-pdu/lib/CMakeLists.txt b/gr-pdu/lib/CMakeLists.txt index 331fe0c6ef..8bcb772ffc 100644 --- a/gr-pdu/lib/CMakeLists.txt +++ b/gr-pdu/lib/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(gnuradio-pdu pdu_remove_impl.cc pdu_set_impl.cc pdu_split_impl.cc + pdu_to_stream_impl.cc pdu_to_tagged_stream_impl.cc random_pdu_impl.cc tagged_stream_to_pdu_impl.cc diff --git a/gr-pdu/lib/pdu_to_stream_impl.cc b/gr-pdu/lib/pdu_to_stream_impl.cc new file mode 100644 index 0000000000..c9e2667897 --- /dev/null +++ b/gr-pdu/lib/pdu_to_stream_impl.cc @@ -0,0 +1,303 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 NTESS LLC. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "pdu_to_stream_impl.h" +#include <gnuradio/io_signature.h> +#include <gnuradio/pdu.h> + +#include <boost/date_time/posix_time/posix_time.hpp> +#include <boost/thread/thread.hpp> + +namespace gr { +namespace pdu { + +template <class T> +typename pdu_to_stream<T>::sptr +pdu_to_stream<T>::make(early_pdu_behavior_t early_pdu_behavior, uint32_t max_queue_size) +{ + return gnuradio::make_block_sptr<pdu_to_stream_impl<T>>(early_pdu_behavior, + max_queue_size); +} + +/* BEHAVIOR OF SUCCESSIVE BURSTS + + -- APPEND MODE -- + If an untimed message is received with data still queued from previous + messages, it will be appended to the end of the same burst. + If a timed message is received it will be queued and transmitted with + its own tx_sob and tx_eob tags. This will potentially cause late bursts + to be ignored downstream at the USRP sink block. + + -- DROP MODE -- + If messages are received in rapid succession, any that arrive while + there is still data queued from previous PDUs will be dropped. + + -- BALK MODE -- + Same behavior as drop mode except an error will also be emitted + + #TODO add an option such that if a burst will be late, change it to + untimed mode to prevent it from being dropped by downstream driver +*/ + +/* + * The private constructor + */ +template <class T> +pdu_to_stream_impl<T>::pdu_to_stream_impl(early_pdu_behavior_t early_pdu_behavior, + uint32_t max_queue_size) + : gr::sync_block("pdu_to_stream", + gr::io_signature::make(0, 0, 0), + gr::io_signature::make(1, 1, sizeof(T))), + d_itemsize(sizeof(T)), + d_max_queue_size(max_queue_size), + d_time_tag(pmt::PMT_NIL) +{ + d_data.clear(); + d_pdu_queue.clear(); + + if (early_pdu_behavior == pdu::EARLY_BURST_APPEND) { + d_drop_early_bursts = false; + d_early_burst_err = false; + } else if (early_pdu_behavior == pdu::EARLY_BURST_DROP) { + d_drop_early_bursts = true; + d_early_burst_err = false; + } else if (early_pdu_behavior == pdu::EARLY_BURST_BALK) { + d_drop_early_bursts = true; + d_early_burst_err = true; + } else { + throw std::invalid_argument("Invalid early burst behavior mode " + + std::to_string(early_pdu_behavior)); + } + + this->message_port_register_in(msgport_names::pdus()); + this->set_msg_handler(msgport_names::pdus(), + [this](pmt::pmt_t msg) { this->store_pdu(msg); }); +} + +/* + * Our virtual destructor. + */ +template <class T> +pdu_to_stream_impl<T>::~pdu_to_stream_impl() +{ +} + + +/* + * function validates PDUs and stores them in a queue for further processing + */ +template <class T> +void pdu_to_stream_impl<T>::store_pdu(pmt::pmt_t pdu) +{ + // check and see if there is already data in the vector, drop if not in an append mode + if (d_drop_early_bursts & (d_data.size() | d_pdu_queue.size())) { + if (d_early_burst_err) { + GR_LOG_ERROR(this->d_logger, + "PDU received before previous burst finished writing - dropped"); + } + return; + } + + // make sure PDU data is formed properly + if (!(pmt::is_pdu(pdu))) { + GR_LOG_ERROR(this->d_logger, "PMT is not a PDU, dropping"); + return; + } + + pmt::pmt_t meta = pmt::car(pdu); + pmt::pmt_t v_data = pmt::cdr(pdu); + + if (pmt::length(v_data) != 0) { + size_t v_itemsize = pmt::uniform_vector_itemsize(v_data); + if (v_itemsize != d_itemsize) { + GR_LOG_ERROR(this->d_logger, + boost::format("PDU received has incorrect itemsize (%d != %d)") % + v_itemsize % d_itemsize); + return; + } + + // pdu data is valid and nonzero length, queue it + if (d_pdu_queue.size() < d_max_queue_size) { + d_pdu_queue.push_back(pdu); + d_drop_ctr = 0; + } else { + d_drop_ctr++; + GR_LOG_WARN(this->d_logger, + boost::format("Queue full, PDU dropped (%d dropped so far)") % + d_drop_ctr); + } + } else { + GR_LOG_WARN(this->d_logger, "zero size PDU ignored"); + } + + return; +} + + +/* + * this function will pop PDUs off the queue and store the data, returns + * the number of data elements queued + */ +template <class T> +uint32_t pdu_to_stream_impl<T>::queue_data() +{ + uint32_t data_size = 0; + // this should only get called when there is data in the queue, but check + // anyway and return if it is empty + if (d_pdu_queue.empty()) { + return data_size; + } + + pmt::pmt_t pdu = d_pdu_queue.front(); + d_pdu_queue.pop_front(); + + // only validated PDUs allowed into queue, validation not required here + pmt::pmt_t meta = pmt::car(pdu); + pmt::pmt_t v_data = pmt::cdr(pdu); + + // retrieve the data + size_t nbytes = 0; + size_t nitems = 0; + const T* d_in = static_cast<const T*>(pmt::uniform_vector_elements(v_data, nbytes)); + nitems = nbytes / sizeof(T); + + // resize vector once and push elements onto back + d_data.reserve(d_data.size() + nitems); + for (size_t i = 0; i < nitems; ++i) { + d_data.push_back(d_in[i]); + } + data_size += (uint32_t)nitems; + + // check if there are future PDUs queued that are untimed and queue them also + bool done = false; + while (!done) { + if (d_pdu_queue.empty()) { + done = true; + break; + } + pmt::pmt_t pdu2 = d_pdu_queue.front(); + pmt::pmt_t meta2 = pmt::car(pdu2); + if (pmt::dict_has_key(meta2, metadata_keys::tx_time())) { + // the next PDU is timed, so we will handle that on the next call + done = true; + } else { + d_pdu_queue.pop_front(); + v_data = pmt::cdr(pdu2); + + // retrieve elements + d_in = static_cast<const T*>(pmt::uniform_vector_elements(v_data, nbytes)); + nitems = nbytes / sizeof(T); + + // resize vector once and push elements onto back + d_data.reserve(d_data.size() + nitems); + for (size_t i = 0; i < nitems; ++i) { + d_data.push_back(d_in[i]); + } + data_size += (uint32_t)nitems; + } + } + + d_tag_sob = true; + + // if the burst is timed, calculate the time tag + pmt::pmt_t pmt_time = pmt::dict_ref(meta, metadata_keys::tx_time(), pmt::PMT_NIL); + if (pmt::is_tuple(pmt_time) && pmt::length(pmt_time) >= 2 && + pmt::is_uint64(pmt::tuple_ref(pmt_time, 0)) && + pmt::is_real(pmt::tuple_ref(pmt_time, 1))) { + // it's a good tuple... + d_time_tag = pmt_time; + } + + // possibly is a pair + if (pmt::is_pair(pmt_time) && pmt::is_uint64(pmt::car(pmt_time)) && + pmt::is_real(pmt::cdr(pmt_time))) { + // it's a good pair... + d_time_tag = pmt::make_tuple(pmt::car(pmt_time), pmt::cdr(pmt_time)); + } + + return data_size; +} + +template <class T> +int pdu_to_stream_impl<T>::work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items) +{ + T* out = (T*)output_items[0]; + + uint32_t produced = 0; + int data_remaining = d_data.size(); + + // if there are no data in the queue, see if more PDUs are ready + if (data_remaining == 0) { + if (d_pdu_queue.empty()) { + // if we have nothing to do, sleep for a short duration to prevent rapid + // successive calls and then return zero items + boost::this_thread::sleep(boost::posix_time::microseconds(25)); + return 0; + } + + // fetch another PDU of data and update the size of the data + data_remaining = queue_data(); + if (data_remaining == 0) { + boost::this_thread::sleep(boost::posix_time::microseconds(25)); + return 0; + } + } /* end if data_remaining == 0 */ + + // data_remaining is not zero so go ahead and update + if (d_tag_sob) { + this->add_item_tag( + 0, this->nitems_written(0), metadata_keys::tx_sob(), pmt::PMT_T); + d_tag_sob = false; + + // if there is a time tag waiting, use it then reset the time tag + if (!pmt::eqv(d_time_tag, pmt::PMT_NIL)) { + this->add_item_tag( + 0, this->nitems_written(0), metadata_keys::tx_time(), d_time_tag); + d_time_tag = pmt::PMT_NIL; + } + } + + // if everything remaining will fit, send it and tag EOB + if (data_remaining <= noutput_items) { + memcpy(out, &d_data[0], d_itemsize * data_remaining); + // tag last item "tx_eob, True" + this->add_item_tag(0, + this->nitems_written(0) + data_remaining - 1, + metadata_keys::tx_eob(), + pmt::PMT_T); + d_data.clear(); + produced = data_remaining; + + // not everything will fit... send first noutput_items from PDU + } else { + if (noutput_items) { + memcpy(out, &d_data[0], d_itemsize * noutput_items); + d_data.erase(d_data.begin(), d_data.begin() + noutput_items); + produced = noutput_items; + } + } + + // Tell runtime system how many output items we produced. + return produced; +} + +template class pdu_to_stream<unsigned char>; +template class pdu_to_stream<short>; +template class pdu_to_stream<int>; +template class pdu_to_stream<float>; +template class pdu_to_stream<gr_complex>; +} /* namespace pdu */ +} /* namespace gr */ diff --git a/gr-pdu/lib/pdu_to_stream_impl.h b/gr-pdu/lib/pdu_to_stream_impl.h new file mode 100644 index 0000000000..8b9718dae5 --- /dev/null +++ b/gr-pdu/lib/pdu_to_stream_impl.h @@ -0,0 +1,55 @@ +/* -*- c++ -*- */ +/* + * Copyright 2021 NTESS LLC. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +#ifndef INCLUDED_PDU_TO_STREAM_IMPL_H +#define INCLUDED_PDU_TO_STREAM_IMPL_H + +#include <gnuradio/pdu/pdu_to_stream.h> + +namespace gr { +namespace pdu { + +template <class T> +class PDU_API pdu_to_stream_impl : public pdu_to_stream<T> +{ +private: + pmt::pmt_t d_msg_port_out; + bool d_early_burst_err; + bool d_drop_early_bursts; + bool d_tag_sob; + int d_type; + uint32_t d_itemsize; + uint32_t d_max_queue_size; + uint32_t d_drop_ctr; + pmt::pmt_t d_time_tag; + std::list<pmt::pmt_t> d_pdu_queue; + + std::vector<T> d_data; + + uint32_t queue_data(void); + void store_pdu(pmt::pmt_t pdu); + +public: + pdu_to_stream_impl(early_pdu_behavior_t early_pdu_behavior, + uint32_t max_queue_size = 64); + + ~pdu_to_stream_impl() override; + + void set_max_queue_size(uint32_t size) override { d_max_queue_size = size; } + + int work(int noutput_items, + gr_vector_const_void_star& input_items, + gr_vector_void_star& output_items) override; +}; + +} // namespace pdu +} // namespace gr + +#endif /* INCLUDED_PDU_TO_STREAM_IMPL_H */ diff --git a/gr-pdu/python/pdu/bindings/CMakeLists.txt b/gr-pdu/python/pdu/bindings/CMakeLists.txt index c462de7a97..832f6a84b3 100644 --- a/gr-pdu/python/pdu/bindings/CMakeLists.txt +++ b/gr-pdu/python/pdu/bindings/CMakeLists.txt @@ -10,6 +10,7 @@ list(APPEND pdu_python_files pdu_remove_python.cc pdu_set_python.cc pdu_split_python.cc + pdu_to_stream_python.cc pdu_to_tagged_stream_python.cc random_pdu_python.cc tagged_stream_to_pdu_python.cc diff --git a/gr-pdu/python/pdu/bindings/docstrings/pdu_to_stream_pydoc_template.h b/gr-pdu/python/pdu/bindings/docstrings/pdu_to_stream_pydoc_template.h new file mode 100644 index 0000000000..b0991ef41a --- /dev/null +++ b/gr-pdu/python/pdu/bindings/docstrings/pdu_to_stream_pydoc_template.h @@ -0,0 +1,15 @@ +/* + * Copyright 2021 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ +#include "pydoc_macros.h" +#define D(...) DOC(gr, pdu_utils, __VA_ARGS__) +/* + This file contains placeholders for docstrings for the Python bindings. + Do not edit! These were automatically extracted during the binding process + and will be overwritten during the build process + */ diff --git a/gr-pdu/python/pdu/bindings/pdu_to_stream_python.cc b/gr-pdu/python/pdu/bindings/pdu_to_stream_python.cc new file mode 100644 index 0000000000..9dc9259fa7 --- /dev/null +++ b/gr-pdu/python/pdu/bindings/pdu_to_stream_python.cc @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Free Software Foundation, Inc. + * + * This file is part of GNU Radio + * + * SPDX-License-Identifier: GPL-3.0-or-later + * + */ + +/***********************************************************************************/ +/* This file is automatically generated using bindtool and can be manually edited */ +/* The following lines can be configured to regenerate this file during cmake */ +/* If manual edits are made, the following tags should be modified accordingly. */ +/* BINDTOOL_GEN_AUTOMATIC(0) */ +/* BINDTOOL_USE_PYGCCXML(0) */ +/* BINDTOOL_HEADER_FILE(pdu_to_stream.h) */ +/* BINDTOOL_HEADER_FILE_HASH(2ad142c7be1b69014e96742965f63ede) */ +/***********************************************************************************/ + +#include <pybind11/complex.h> +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + +namespace py = pybind11; + +#include <gnuradio/pdu/pdu_to_stream.h> +// pydoc.h is automatically generated in the build directory +#include <pdu_to_stream_pydoc.h> + +template <typename T> +void bind_pdu_to_stream_template(py::module& m, const char* classname) +{ + using pdu_to_stream = ::gr::pdu::pdu_to_stream<T>; + + py::class_<pdu_to_stream, gr::block, gr::basic_block, std::shared_ptr<pdu_to_stream>>( + m, classname) + .def(py::init(&gr::pdu::pdu_to_stream<T>::make), + py::arg("early_pdu_behavior"), + py::arg("max_queue_size") = 64); +} + +void bind_pdu_to_stream(py::module& m) +{ + py::enum_<::gr::pdu::early_pdu_behavior_t>(m, "early_pdu_behavior_t") + .value("EARLY_BURST_APPEND", ::gr::pdu::EARLY_BURST_APPEND) // 0 + .value("EARLY_BURST_DROP", ::gr::pdu::EARLY_BURST_DROP) // 1 + .value("EARLY_BURST_BALK", ::gr::pdu::EARLY_BURST_BALK) // 2 + .export_values(); + + py::implicitly_convertible<int, gr::pdu::early_pdu_behavior_t>(); + + bind_pdu_to_stream_template<unsigned char>(m, "pdu_to_stream_b"); + bind_pdu_to_stream_template<short>(m, "pdu_to_stream_s"); + bind_pdu_to_stream_template<int>(m, "pdu_to_stream_i"); + bind_pdu_to_stream_template<float>(m, "pdu_to_stream_f"); + bind_pdu_to_stream_template<gr_complex>(m, "pdu_to_stream_c"); +} diff --git a/gr-pdu/python/pdu/bindings/python_bindings.cc b/gr-pdu/python/pdu/bindings/python_bindings.cc index 7929b89d88..5adc694b66 100644 --- a/gr-pdu/python/pdu/bindings/python_bindings.cc +++ b/gr-pdu/python/pdu/bindings/python_bindings.cc @@ -20,6 +20,7 @@ void bind_pdu_filter(py::module&); void bind_pdu_remove(py::module&); void bind_pdu_set(py::module&); void bind_pdu_split(py::module&); +void bind_pdu_to_stream(py::module&); void bind_pdu_to_tagged_stream(py::module&); void bind_random_pdu(py::module&); void bind_tagged_stream_to_pdu(py::module&); @@ -50,6 +51,7 @@ PYBIND11_MODULE(pdu_python, m) bind_pdu_remove(m); bind_pdu_set(m); bind_pdu_split(m); + bind_pdu_to_stream(m); bind_pdu_to_tagged_stream(m); bind_random_pdu(m); bind_tagged_stream_to_pdu(m); diff --git a/gr-pdu/python/pdu/qa_pdu_to_stream.py b/gr-pdu/python/pdu/qa_pdu_to_stream.py new file mode 100755 index 0000000000..c2f69c2186 --- /dev/null +++ b/gr-pdu/python/pdu/qa_pdu_to_stream.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2018-2021 National Technology & Engineering Solutions of Sandia, LLC +# (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government +# retains certain rights in this software. +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from gnuradio import gr, gr_unittest, blocks, pdu +import numpy as np +import pmt +import time + +class qa_pdu_to_bursts (gr_unittest.TestCase): + + def setUp (self): + self.tb = gr.top_block() + self.p2s = pdu.pdu_to_stream_c(pdu.EARLY_BURST_APPEND, 64) + self.vs = blocks.vector_sink_c(1) + self.tag_debug = blocks.tag_debug(gr.sizeof_gr_complex*1, '', "") + self.tag_debug.set_display(True) + + self.tb.connect((self.p2s, 0), (self.vs, 0)) + self.tb.connect((self.p2s, 0), (self.tag_debug, 0)) + + def tearDown (self): + self.tb = None + + def test_001_basic (self): + in_data = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1] + in_pdu = pmt.cons(pmt.make_dict(), pmt.init_c32vector(len(in_data), in_data)) + e_tag_0 = gr.tag_utils.python_to_tag((0, pmt.intern("tx_sob"), pmt.PMT_T, pmt.PMT_NIL)) + e_tag_1 = gr.tag_utils.python_to_tag((len(in_data)-1, pmt.intern("tx_eob"), pmt.PMT_T, pmt.PMT_NIL)) + + self.tb.start() + self.p2s.to_basic_block()._post(pmt.intern("pdus"), pmt.intern("MALFORMED PDU")) + self.p2s.to_basic_block()._post(pmt.intern("pdus"), in_pdu) + self.waitFor(lambda: len(self.vs.tags()) == 2, timeout=1.0, poll_interval=0.01) + self.tb.stop() + self.tb.wait() + + tags = self.vs.tags() + self.assertEqual(len(tags), 2) + self.assertEqual(tags[0].offset, e_tag_0.offset) + self.assertTrue(pmt.equal(tags[0].key, e_tag_0.key)) + self.assertTrue(pmt.equal(tags[0].value, e_tag_0.value)) + self.assertEqual(tags[1].offset, e_tag_1.offset) + self.assertTrue(pmt.equal(tags[1].key, e_tag_1.key)) + self.assertTrue(pmt.equal(tags[1].value, e_tag_1.value)) + self.assertTrue((in_data == np.real(self.vs.data())).all()) + + def test_002_timed (self): + in_data = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1] + tag_time = pmt.make_tuple(pmt.from_uint64(11), pmt.from_double(0.123456)) + in_dict = pmt.dict_add(pmt.make_dict(), pmt.intern("tx_time"), tag_time) + in_pdu = pmt.cons(in_dict, pmt.init_c32vector(len(in_data), in_data)) + e_tag_0 = gr.tag_utils.python_to_tag((0, pmt.intern("tx_sob"), pmt.PMT_T, pmt.PMT_NIL)) + e_tag_1 = gr.tag_utils.python_to_tag((0, pmt.intern("tx_time"), tag_time, pmt.PMT_NIL)) + e_tag_2 = gr.tag_utils.python_to_tag((len(in_data)-1, pmt.intern("tx_eob"), pmt.PMT_T, pmt.PMT_NIL)) + + self.tb.start() + self.p2s.to_basic_block()._post(pmt.intern("pdus"), pmt.intern("MALFORMED PDU")) + self.p2s.to_basic_block()._post(pmt.intern("pdus"), in_pdu) + self.waitFor(lambda: len(self.vs.tags()) == 3, timeout=1.0, poll_interval=0.01) + self.tb.stop() + self.tb.wait() + + tags = self.vs.tags() + self.assertEqual(len(tags), 3) + self.assertEqual(tags[0].offset, e_tag_0.offset) + self.assertTrue(pmt.equal(tags[0].key, e_tag_0.key)) + self.assertTrue(pmt.equal(tags[0].value, e_tag_0.value)) + self.assertEqual(tags[1].offset, e_tag_1.offset) + self.assertTrue(pmt.equal(tags[1].key, e_tag_1.key)) + self.assertTrue(pmt.equal(tags[1].value, e_tag_1.value)) + self.assertEqual(tags[2].offset, e_tag_2.offset) + self.assertTrue(pmt.equal(tags[2].key, e_tag_2.key)) + self.assertTrue(pmt.equal(tags[2].value, e_tag_2.value)) + self.assertTrue((in_data == np.real(self.vs.data())).all()) + + def test_003_timed_long (self): + in_data = np.arange(25000).tolist() + tag_time = pmt.make_tuple(pmt.from_uint64(11), pmt.from_double(0.123456)) + in_dict = pmt.dict_add(pmt.make_dict(), pmt.intern("tx_time"), tag_time) + in_pdu = pmt.cons(in_dict, pmt.init_c32vector(len(in_data), in_data)) + e_tag_0 = gr.tag_utils.python_to_tag((0, pmt.intern("tx_sob"), pmt.PMT_T, pmt.PMT_NIL)) + e_tag_1 = gr.tag_utils.python_to_tag((0, pmt.intern("tx_time"), tag_time, pmt.PMT_NIL)) + e_tag_2 = gr.tag_utils.python_to_tag((len(in_data)-1, pmt.intern("tx_eob"), pmt.PMT_T, pmt.PMT_NIL)) + + self.tb.start() + self.p2s.to_basic_block()._post(pmt.intern("pdus"), pmt.intern("MALFORMED PDU")) + self.p2s.to_basic_block()._post(pmt.intern("pdus"), in_pdu) + self.waitFor(lambda: len(self.vs.tags()) == 3, timeout=1.0, poll_interval=0.01) + self.tb.stop() + self.tb.wait() + + tags = self.vs.tags() + self.assertEqual(len(tags), 3) + self.assertEqual(tags[0].offset, e_tag_0.offset) + self.assertTrue(pmt.equal(tags[0].key, e_tag_0.key)) + self.assertTrue(pmt.equal(tags[0].value, e_tag_0.value)) + self.assertEqual(tags[1].offset, e_tag_1.offset) + self.assertTrue(pmt.equal(tags[1].key, e_tag_1.key)) + self.assertTrue(pmt.equal(tags[1].value, e_tag_1.value)) + self.assertEqual(tags[2].offset, e_tag_2.offset) + self.assertTrue(pmt.equal(tags[2].key, e_tag_2.key)) + self.assertTrue(pmt.equal(tags[2].value, e_tag_2.value)) + self.assertTrue((in_data == np.real(self.vs.data())).all()) + + +if __name__ == '__main__': + gr_unittest.run(qa_pdu_to_bursts) |