summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gnuradio-runtime/include/gnuradio/pdu.h3
-rw-r--r--gnuradio-runtime/lib/pdu.cc16
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/bindings/docstrings/pdu_pydoc_template.h9
-rw-r--r--gnuradio-runtime/python/gnuradio/gr/bindings/pdu_python.cc9
-rw-r--r--gr-pdu/examples/pdu_lambda_chirp_demo.grc824
-rw-r--r--gr-pdu/examples/pdu_lambda_example.grc2
-rw-r--r--gr-pdu/examples/simple_pdu_to_stream_example.grc276
-rw-r--r--gr-pdu/grc/pdu.tree.yml1
-rw-r--r--gr-pdu/grc/pdu_pdu_to_stream.block.yml43
-rw-r--r--gr-pdu/include/gnuradio/pdu/CMakeLists.txt1
-rw-r--r--gr-pdu/include/gnuradio/pdu/pdu_to_stream.h72
-rw-r--r--gr-pdu/lib/CMakeLists.txt1
-rw-r--r--gr-pdu/lib/pdu_to_stream_impl.cc303
-rw-r--r--gr-pdu/lib/pdu_to_stream_impl.h55
-rw-r--r--gr-pdu/python/pdu/bindings/CMakeLists.txt1
-rw-r--r--gr-pdu/python/pdu/bindings/docstrings/pdu_to_stream_pydoc_template.h15
-rw-r--r--gr-pdu/python/pdu/bindings/pdu_to_stream_python.cc57
-rw-r--r--gr-pdu/python/pdu/bindings/python_bindings.cc2
-rwxr-xr-xgr-pdu/python/pdu/qa_pdu_to_stream.py114
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)