From 3b41bb4dec9b6ce89e4909d20fbc7ffd764a80f3 Mon Sep 17 00:00:00 2001
From: Tom Rondeau <tom@trondeau.com>
Date: Tue, 14 Apr 2015 19:51:12 -0400
Subject: controlport: fixed up performance monitor.

Fixed a problem with display runtime or buffer graphs by not clearing
and redrawing the entire graph, just updating the height of the bars.

Only update table or graph when they are the visible elements.

Shuts down timer when the graphs are closed.
---
 .../python/gnuradio/ctrlport/gr-perf-monitorx      | 135 ++++++++++++---------
 1 file changed, 77 insertions(+), 58 deletions(-)

(limited to 'gnuradio-runtime/python/gnuradio')

diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
index 23e11d4174..c871ae9f9e 100644
--- a/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
@@ -254,18 +254,23 @@ class DataTable(QtGui.QWidget):
     def update(self):
         print "update"
 
+    def closeEvent(self, event):
+        self.timer = None
+
     def __init__(self, radioclient, G):
         QtGui.QWidget.__init__( self)
 
-        self.layout = QtGui.QVBoxLayout(self);
-        self.hlayout = QtGui.QHBoxLayout();
-        self.layout.addLayout(self.hlayout);
+        self.layout = QtGui.QVBoxLayout(self)
+        self.hlayout = QtGui.QHBoxLayout()
+        self.layout.addLayout(self.hlayout)
 
-        self.G = G;
-        self.radioclient = radioclient;
+        self.G = G
+        self.radioclient = radioclient
 
         self._keymap = None
 
+        self.disp = None
+
         # Create a combobox to set the type of statistic we want.
         self._statistic = "Instantaneous"
         self._statistics_table = {"Instantaneous": "",
@@ -276,7 +281,7 @@ class DataTable(QtGui.QWidget):
         self.stattype.addItem("Average")
         self.stattype.addItem("Variance")
         self.stattype.setMaximumWidth(200)
-        self.hlayout.addWidget(self.stattype);
+        self.hlayout.addWidget(self.stattype)
         self.stattype.currentIndexChanged.connect(self.stat_changed)
 
         # Create a checkbox to toggle sorting of graphs
@@ -287,18 +292,18 @@ class DataTable(QtGui.QWidget):
         self.checksort.stateChanged.connect(self.checksort_changed)
 
         # set up table
-        self.perfTable = Qt.QTableWidget();
+        self.perfTable = Qt.QTableWidget()
         self.perfTable.setColumnCount(2)
-        self.perfTable.verticalHeader().hide();
-        self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Runtime"] );
-        self.perfTable.horizontalHeader().setStretchLastSection(True);
+        self.perfTable.verticalHeader().hide()
+        self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Runtime"] )
+        self.perfTable.horizontalHeader().setStretchLastSection(True)
         self.perfTable.setSortingEnabled(True)
         nodes = self.G.nodes(data=True)
 
         # set up plot
         self.f = plt.figure(figsize=(10,8), dpi=90)
-        self.sp = self.f.add_subplot(111);
-        self.sp.autoscale_view(True,True,True);
+        self.sp = self.f.add_subplot(111)
+        self.sp.autoscale_view(True,True,True)
         self.sp.set_autoscale_on(True)
         self.canvas = FigureCanvas(self.f)
 
@@ -339,63 +344,71 @@ class DataTable(QtGui.QWidget):
 class DataTableBuffers(DataTable):
     def __init__(self, radioclient, G):
         super(DataTableBuffers, self).__init__(radioclient, G)
-        self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Buffer Full"] );
+        self.perfTable.setHorizontalHeaderLabels( ["Block Name", "Percent Buffer Full"] )
 
     def update(self):
         nodes = self.G.nodes();
 
         # get buffer fullness for all blocks
         kl = map(lambda x: "%s::%soutput %% full" % \
-                     (x, self._statistics_table[self._statistic]),
+                 (x, self._statistics_table[self._statistic]),
                  nodes);
         buf_knobs = self.radioclient.getKnobs(kl)
 
         # strip values out of ctrlport response
         buffer_fullness = dict(zip(
-                    map(lambda x: x.split("::")[0], buf_knobs.keys()),
-                    map(lambda x: x.value, buf_knobs.values())))
+            map(lambda x: x.split("::")[0], buf_knobs.keys()),
+            map(lambda x: x.value, buf_knobs.values())))
 
         blockport_fullness = {}
         for blk in buffer_fullness:
             bdata = buffer_fullness[blk]
             if bdata:
                 for port in range(0,len(bdata)):
-                    blockport_fullness["%s:%d"%(blk,port)] =  bdata[port];
+                    blockport_fullness["%s:%d"%(blk,port)] =  bdata[port]
 
-        self.table_update(blockport_fullness);
+        if(self.perfTable.isVisible()):
+            self.table_update(blockport_fullness);
 
-        if(self._sort):
-            sorted_fullness = sorted(blockport_fullness.iteritems(), key=operator.itemgetter(1))
-            self._keymap = map(operator.itemgetter(0), sorted_fullness)
         else:
-            if self._keymap:
-                sorted_fullness = len(self._keymap)*['',]
-                for b in blockport_fullness:
-                    sorted_fullness[self._keymap.index(b)] = (b, blockport_fullness[b])
+            if(self._sort):
+                sorted_fullness = sorted(blockport_fullness.iteritems(),
+                                         key=operator.itemgetter(1))
+                self._keymap = map(operator.itemgetter(0), sorted_fullness)
+            else:
+                if self._keymap:
+                    sorted_fullness = len(self._keymap)*['',]
+                    for b in blockport_fullness:
+                        sorted_fullness[self._keymap.index(b)] = (b, blockport_fullness[b])
+                else:
+                    sorted_fullness = blockport_fullness.items()
+
+            if(not self.disp):
+                self.disp = self.sp.bar(range(0,len(sorted_fullness)),
+                                        map(lambda x: x[1], sorted_fullness),
+                                        alpha=0.5)
+                self.sp.set_ylabel("% Buffers Full");
+                self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_fullness))))
+                self.sp.set_xticklabels(map(lambda x: "  " + x, map(lambda x: x[0], sorted_fullness)),
+                                        rotation="vertical", verticalalignment="bottom")
             else:
-                sorted_fullness = blockport_fullness.items()
+                self.sp.set_xticklabels(map(lambda x: "  " + x, map(lambda x: x[0], sorted_fullness)),
+                                        rotation="vertical", verticalalignment="bottom")
+                for r,w in zip(self.disp, sorted_fullness):
+                    r.set_height(w[1])
 
-        self.sp.clear();
-        self.sp.bar(range(0,len(sorted_fullness)), map(lambda x: x[1], sorted_fullness),
-                    alpha=0.5)
-        self.sp.set_ylabel("% Buffers Full");
-        self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_fullness))))
-        self.sp.set_xticklabels( map(lambda x: "  " + x, map(lambda x: x[0], sorted_fullness)),
-                                 rotation="vertical", verticalalignment="bottom" )
-        self.canvas.draw()
-        self.canvas.show()
+            self.canvas.draw()
 
 class DataTableRuntimes(DataTable):
     def __init__(self, radioclient, G):
         super(DataTableRuntimes, self).__init__( radioclient, G)
-        #self.perfTable.setRowCount(len( self.G.nodes() ))
 
     def update(self):
         nodes = self.G.nodes();
 
         # get work time for all blocks
         kl = map(lambda x: "%s::%swork time" % \
-                     (x, self._statistics_table[self._statistic]),
+                 (x, self._statistics_table[self._statistic]),
                  nodes);
         wrk_knobs = self.radioclient.getKnobs(kl)
 
@@ -408,31 +421,37 @@ class DataTableRuntimes(DataTable):
             map(lambda x: x.value/total_work, wrk_knobs.values())))
 
         # update table view
-        self.table_update(work_times)
+        if(self.perfTable.isVisible()):
+            self.table_update(work_times)
 
-        if(self._sort):
-            sorted_work = sorted(work_times.iteritems(), key=operator.itemgetter(1))
-            self._keymap = map(operator.itemgetter(0), sorted_work)
         else:
-            if self._keymap:
-                sorted_work = len(self._keymap)*['',]
-                for b in work_times:
-                    sorted_work[self._keymap.index(b)] = (b, work_times[b])
+            if(self._sort):
+                sorted_work = sorted(work_times.iteritems(), key=operator.itemgetter(1))
+                self._keymap = map(operator.itemgetter(0), sorted_work)
             else:
-                sorted_work = work_times.items()
-
-        self.sp.clear();
-        plt.figure(self.f.number)
-        plt.subplot(111);
-        self.sp.bar(range(0,len(sorted_work)), map(lambda x: x[1], sorted_work),
-                    alpha=0.5)
-        self.sp.set_ylabel("% Runtime");
-        self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_work))))
-        self.sp.set_xticklabels( map(lambda x: "  " + x[0], sorted_work),
-                                 rotation="vertical", verticalalignment="bottom" )
+                if self._keymap:
+                    sorted_work = len(self._keymap)*['',]
+                    for b in work_times:
+                        sorted_work[self._keymap.index(b)] = (b, work_times[b])
+                else:
+                    sorted_work = work_times.items()
+
+            f = plt.figure(self.f.number)
+            if(not self.disp):
+                self.disp = self.sp.bar(range(0,len(sorted_work)),
+                                        map(lambda x: x[1], sorted_work),
+                                        alpha=0.5)
+                self.sp.set_ylabel("% Runtime");
+                self.sp.set_xticks( map(lambda x: x+0.5, range(0,len(sorted_work))))
+                self.sp.set_xticklabels( map(lambda x: "  " + x[0], sorted_work),
+                                         rotation="vertical", verticalalignment="bottom" )
+            else:
+                self.sp.set_xticklabels( map(lambda x: "  " + x[0], sorted_work),
+                                         rotation="vertical", verticalalignment="bottom" )
+                for r,w in zip(self.disp, sorted_work):
+                    r.set_height(w[1])
 
-        self.canvas.draw();
-        self.canvas.show();
+            self.canvas.draw()
 
 class MForm(QtGui.QWidget):
     def update(self):
-- 
cgit v1.2.3


From 0c3e5e1409a4beeb0f5a85504cbe8d525e995379 Mon Sep 17 00:00:00 2001
From: Tom Rondeau <tom@trondeau.com>
Date: Wed, 22 Apr 2015 17:53:34 -0400
Subject: controlport: use proper default return value from prefs get_bool.

---
 gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

(limited to 'gnuradio-runtime/python/gnuradio')

diff --git a/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
index c871ae9f9e..cebc00dcf4 100644
--- a/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
+++ b/gnuradio-runtime/python/gnuradio/ctrlport/gr-perf-monitorx
@@ -854,10 +854,10 @@ class MForm(QtGui.QWidget):
 class MyApp(object):
     def __init__(self, args):
         p = gr.prefs()
-        cp_on = p.get_bool("ControlPort", "on", None)
-        cp_edges = p.get_bool("ControlPort", "edges_list", None)
-        pcs_on = p.get_bool("PerfCounters", "on", None)
-        pcs_exported = p.get_bool("PerfCounters", "export", None)
+        cp_on = p.get_bool("ControlPort", "on", False)
+        cp_edges = p.get_bool("ControlPort", "edges_list", False)
+        pcs_on = p.get_bool("PerfCounters", "on", False)
+        pcs_exported = p.get_bool("PerfCounters", "export", False)
         if(not (pcs_on and cp_on and pcs_exported and cp_edges)):
             print("Configuration has not turned on all of the appropriate ControlPort features:")
             print("\t[ControlPort] on = {0}".format(cp_on))
-- 
cgit v1.2.3


From 3b1b6d3fd6faa7bd1cbeb8b0ebfe42e6b9196bf7 Mon Sep 17 00:00:00 2001
From: Sean Nowlan <sean.nowlan@gtri.gatech.edu>
Date: Sat, 25 Apr 2015 19:38:33 -0400
Subject: digital: added tag propagation to burst_shaper blocks

---
 .../python/gnuradio/gr/qa_tag_utils.py             | 32 +++++++++
 gnuradio-runtime/python/gnuradio/gr/tag_utils.py   | 33 +++++++++
 .../include/gnuradio/digital/burst_shaper_XX.h.t   | 10 ++-
 gr-digital/lib/burst_shaper_XX_impl.cc.t           | 78 ++++++++++++++--------
 gr-digital/lib/burst_shaper_XX_impl.h.t            |  3 +-
 gr-digital/python/digital/qa_burst_shaper.py       | 71 ++++++++++++++++++--
 6 files changed, 193 insertions(+), 34 deletions(-)

(limited to 'gnuradio-runtime/python/gnuradio')

diff --git a/gnuradio-runtime/python/gnuradio/gr/qa_tag_utils.py b/gnuradio-runtime/python/gnuradio/gr/qa_tag_utils.py
index 1a08ac5ae6..55b62a12ac 100755
--- a/gnuradio-runtime/python/gnuradio/gr/qa_tag_utils.py
+++ b/gnuradio-runtime/python/gnuradio/gr/qa_tag_utils.py
@@ -76,6 +76,38 @@ class test_tag_utils (gr_unittest.TestCase):
         self.assertTrue(pmt.equal(t_tuple.value, value))
         self.assertEqual(t_tuple.offset, offset)
 
+    def test_003(self):
+        offsets = (6, 3, 8)
+        key = pmt.string_to_symbol('key')
+        srcid = pmt.string_to_symbol('qa_tag_utils')
+        tags = []
+
+        for k in offsets:
+            t = gr.tag_t()
+            t.offset = k
+            t.key = key
+            t.value = pmt.from_long(k)
+            t.srcid = srcid
+            tags.append(t)
+
+        for k, t in zip(sorted(offsets),
+                        sorted(tags, key=gr.tag_t_offset_compare_key())):
+            self.assertEqual(t.offset, k)
+            self.assertTrue(pmt.equal(t.key, key))
+            self.assertTrue(pmt.equal(t.value, pmt.from_long(k)))
+            self.assertTrue(pmt.equal(t.srcid, srcid))
+
+        tmin = min(tags, key=gr.tag_t_offset_compare_key())
+        self.assertEqual(tmin.offset, min(offsets))
+        self.assertTrue(pmt.equal(tmin.key, key))
+        self.assertTrue(pmt.equal(tmin.value, pmt.from_long(min(offsets))))
+        self.assertTrue(pmt.equal(tmin.srcid, srcid))
+
+        tmax = max(tags, key=gr.tag_t_offset_compare_key())
+        self.assertEqual(tmax.offset, max(offsets))
+        self.assertTrue(pmt.equal(tmax.key, key))
+        self.assertTrue(pmt.equal(tmax.value, pmt.from_long(max(offsets))))
+        self.assertTrue(pmt.equal(tmax.srcid, srcid))
 
 
 if __name__ == '__main__':
diff --git a/gnuradio-runtime/python/gnuradio/gr/tag_utils.py b/gnuradio-runtime/python/gnuradio/gr/tag_utils.py
index ba46e3f4d9..872a5b01be 100644
--- a/gnuradio-runtime/python/gnuradio/gr/tag_utils.py
+++ b/gnuradio-runtime/python/gnuradio/gr/tag_utils.py
@@ -108,3 +108,36 @@ def python_to_tag(tag_struct):
         return tag
     else:
         return None
+
+def tag_t_offset_compare_key():
+    """
+    Convert a tag_t_offset_compare function into a key=function
+    This method is modeled after functools.cmp_to_key(_func_).
+    It can be used by functions that accept a key function, such as
+    sorted(), min(), max(), etc. to compare tags by their offsets,
+    e.g., sorted(tag_list, key=gr.tag_t_offset_compare_key()).
+    """
+    class K(object):
+        def __init__(self, obj, *args):
+            self.obj = obj
+        def __lt__(self, other):
+            # x.offset < y.offset
+            return gr.tag_t_offset_compare(self.obj, other.obj)
+        def __gt__(self, other):
+            # y.offset < x.offset
+            return gr.tag_t_offset_compare(other.obj, self.obj)
+        def __eq__(self, other):
+            # not (x.offset < y.offset) and not (y.offset < x.offset)
+            return not gr.tag_t_offset_compare(self.obj, other.obj) and \
+                   not gr.tag_t_offset_compare(other.obj, self.obj)
+        def __le__(self, other):
+            # not (y.offset < x.offset)
+            return not gr.tag_t_offset_compare(other.obj, self.obj)
+        def __ge__(self, other):
+            # not (x.offset < y.offset)
+            return not gr.tag_t_offset_compare(self.obj, other.obj)
+        def __ne__(self, other):
+            # (x.offset < y.offset) or (y.offset < x.offset)
+            return gr.tag_t_offset_compare(self.obj, other.obj) or \
+                   gr.tag_t_offset_compare(other.obj, self.obj)
+    return K
diff --git a/gr-digital/include/gnuradio/digital/burst_shaper_XX.h.t b/gr-digital/include/gnuradio/digital/burst_shaper_XX.h.t
index 43d422ba80..fd7b69060e 100644
--- a/gr-digital/include/gnuradio/digital/burst_shaper_XX.h.t
+++ b/gr-digital/include/gnuradio/digital/burst_shaper_XX.h.t
@@ -49,7 +49,15 @@ namespace gr {
      * directly to the head and tail of each burst.
      *
      * Length tags will be updated to include the length of any added
-     * zero padding or phasing symbols.
+     * zero padding or phasing symbols and will be placed at the
+     * beginning of the modified tagged stream. Any other tags found at
+     * the same offset as a length tag will also be placed at the
+     * beginning of the modified tagged stream, since these tags are
+     * assumed to be associated with the burst rather than a specific
+     * sample. For example, if "tx_time" tags are used to control
+     * bursts, their offsets should be consistent with their associated
+     * burst's length tags. Tags at other offsets will be placed with
+     * the samples on which they were found.
      *
      * \li input: stream of @I_TYPE@
      * \li output: stream of @O_TYPE@
diff --git a/gr-digital/lib/burst_shaper_XX_impl.cc.t b/gr-digital/lib/burst_shaper_XX_impl.cc.t
index 398011adfb..85add49115 100644
--- a/gr-digital/lib/burst_shaper_XX_impl.cc.t
+++ b/gr-digital/lib/burst_shaper_XX_impl.cc.t
@@ -68,6 +68,7 @@ namespace gr {
         d_ncopy(0),
         d_limit(0),
         d_index(0),
+        d_length_tag_offset(0),
         d_finished(false),
         d_state(STATE_WAIT)
     {
@@ -93,8 +94,8 @@ namespace gr {
 
     void
     @IMPL_NAME@::forecast(int noutput_items,
-                          gr_vector_int &ninput_items_required) {
-        //if(d_state == STATE_COPY
+                          gr_vector_int &ninput_items_required)
+    {
         ninput_items_required[0] = noutput_items;
     }
 
@@ -111,13 +112,11 @@ namespace gr {
         int nread = 0;
         int nspace = 0;
         int nskip = 0;
-        uint64_t curr_tag_index = nitems_read(0);
+        int curr_tag_index = 0;
 
-        std::vector<tag_t> length_tags, tags;
+        std::vector<tag_t> length_tags;
         get_tags_in_window(length_tags, 0, 0, ninput_items[0], d_length_tag_key);
-        get_tags_in_window(tags, 0, 0, ninput_items[0]);
         std::sort(length_tags.rbegin(), length_tags.rend(), tag_t::offset_compare);
-        std::sort(tags.begin(), tags.end(), tag_t::offset_compare);
 
         while((nwritten < noutput_items) && (nread < ninput_items[0])) {
             if(d_finished) {
@@ -128,11 +127,13 @@ namespace gr {
             switch(d_state) {
                 case(STATE_WAIT):
                     if(!length_tags.empty()) {
-                        curr_tag_index = length_tags.back().offset;
+                        d_length_tag_offset = length_tags.back().offset;
+                        curr_tag_index = (int)(d_length_tag_offset - nitems_read(0));
                         d_ncopy = pmt::to_long(length_tags.back().value);
                         length_tags.pop_back();
-                        nskip = (int)(curr_tag_index - nread - nitems_read(0));
+                        nskip = curr_tag_index - nread;
                         add_length_tag(nwritten);
+                        propagate_tags(curr_tag_index, nwritten, 1, false);
                         enter_prepad();
                     }
                     else {
@@ -188,19 +189,22 @@ namespace gr {
     }
 
     int
-    @IMPL_NAME@::prefix_length() const {
+    @IMPL_NAME@::prefix_length() const
+    {
         return (d_insert_phasing) ?
                d_nprepad + d_up_ramp.size() : d_nprepad;
     }
 
     int
-    @IMPL_NAME@::suffix_length() const {
+    @IMPL_NAME@::suffix_length() const
+    {
         return (d_insert_phasing) ?
                d_npostpad + d_down_ramp.size() : d_npostpad;
     }
 
     void
-    @IMPL_NAME@::write_padding(@O_TYPE@ *&dst, int &nwritten, int nspace) {
+    @IMPL_NAME@::write_padding(@O_TYPE@ *&dst, int &nwritten, int nspace)
+    {
         int nprocess = std::min(d_limit - d_index, nspace);
         std::memset(dst, 0x00, nprocess * sizeof(@O_TYPE@));
         dst += nprocess;
@@ -210,8 +214,10 @@ namespace gr {
 
     void
     @IMPL_NAME@::copy_items(@O_TYPE@ *&dst, const @I_TYPE@ *&src, int &nwritten,
-                            int &nread, int nspace) {
+                            int &nread, int nspace)
+    {
         int nprocess = std::min(d_limit - d_index, nspace);
+        propagate_tags(nread, nwritten, nprocess);
         std::memcpy(dst, src, nprocess * sizeof(@O_TYPE@));
         dst += nprocess;
         nwritten += nprocess;
@@ -222,7 +228,8 @@ namespace gr {
 
     void
     @IMPL_NAME@::apply_ramp(@O_TYPE@ *&dst, const @I_TYPE@ *&src, int &nwritten,
-                            int &nread, int nspace) {
+                            int &nread, int nspace)
+    {
         int nprocess = std::min(d_limit - d_index, nspace);
         @O_TYPE@ *phasing;
         const @O_TYPE@ *ramp;
@@ -239,6 +246,7 @@ namespace gr {
         if(d_insert_phasing)
             std::memcpy(dst, phasing, nprocess * sizeof(@O_TYPE@));
         else {
+            propagate_tags(nread, nwritten, nprocess);
             VOLK_MULT_@O_TYPE@(dst, src, ramp, nprocess);
             src += nprocess;
             nread += nprocess;
@@ -259,33 +267,48 @@ namespace gr {
     }
 
     void
-    @IMPL_NAME@::propagate_tags(std::vector<tag_t> &tags, int offset)
+    @IMPL_NAME@::propagate_tags(int in_offset, int out_offset, int count, bool skip)
     {
-        // FIXME: need to handle offsets correctly
-        std::vector<tag_t>::iterator tag;
-        for(tag = tags.begin(); tag != tags.end(); tag++) {
-            tag_t new_tag = *tag;
-            new_tag.offset = nitems_written(0) + offset;
-            add_item_tag(0, new_tag);
+        uint64_t abs_start = nitems_read(0) + in_offset;
+        uint64_t abs_end = abs_start + count;
+        uint64_t abs_offset = nitems_written(0) + out_offset;
+        tag_t temp_tag;
+
+        std::vector<tag_t> tags;
+        std::vector<tag_t>::iterator it;
+
+        get_tags_in_range(tags, 0, abs_start, abs_end);
+
+        for(it = tags.begin(); it != tags.end(); it++) {
+            if(!pmt::equal(it->key, d_length_tag_key)) {
+                if(skip && (it->offset == d_length_tag_offset))
+                    continue;
+                temp_tag = *it;
+                temp_tag.offset = abs_offset + it->offset - abs_start;
+                add_item_tag(0, temp_tag);
+            }
         }
     }
 
     void
-    @IMPL_NAME@::enter_wait() {
+    @IMPL_NAME@::enter_wait()
+    {
         d_finished = true;
         d_index = 0;
         d_state = STATE_WAIT;
     }
 
     void
-    @IMPL_NAME@::enter_prepad() {
+    @IMPL_NAME@::enter_prepad()
+    {
         d_limit = d_nprepad;
         d_index = 0;
         d_state = STATE_PREPAD;
     }
 
     void
-    @IMPL_NAME@::enter_rampup() {
+    @IMPL_NAME@::enter_rampup()
+    {
         if(d_insert_phasing)
             d_limit = d_up_ramp.size();
         else
@@ -295,7 +318,8 @@ namespace gr {
     }
 
     void
-    @IMPL_NAME@::enter_copy() {
+    @IMPL_NAME@::enter_copy()
+    {
         if(d_insert_phasing)
             d_limit = d_ncopy;
         else
@@ -307,7 +331,8 @@ namespace gr {
     }
 
     void
-    @IMPL_NAME@::enter_rampdown() {
+    @IMPL_NAME@::enter_rampdown()
+    {
         if(d_insert_phasing)
             d_limit = d_down_ramp.size();
         else
@@ -317,7 +342,8 @@ namespace gr {
     }
 
     void
-    @IMPL_NAME@::enter_postpad() {
+    @IMPL_NAME@::enter_postpad()
+    {
         d_limit = d_npostpad;
         d_index = 0;
         d_state = STATE_POSTPAD;
diff --git a/gr-digital/lib/burst_shaper_XX_impl.h.t b/gr-digital/lib/burst_shaper_XX_impl.h.t
index 4fa1cad9ea..99ad7fb08a 100644
--- a/gr-digital/lib/burst_shaper_XX_impl.h.t
+++ b/gr-digital/lib/burst_shaper_XX_impl.h.t
@@ -48,6 +48,7 @@ namespace gr {
       int d_ncopy;
       int d_limit;
       int d_index;
+      uint64_t d_length_tag_offset;
       bool d_finished;
       state_t d_state;
 
@@ -57,7 +58,7 @@ namespace gr {
       void apply_ramp(@O_TYPE@ *&dst, const @I_TYPE@ *&src, int &nwritten,
                       int &nread, int nspace);
       void add_length_tag(int offset);
-      void propagate_tags(std::vector<tag_t> &tags, int offset);
+      void propagate_tags(int in_offset, int out_offset, int count, bool skip=true);
       void enter_wait();
       void enter_prepad();
       void enter_rampup();
diff --git a/gr-digital/python/digital/qa_burst_shaper.py b/gr-digital/python/digital/qa_burst_shaper.py
index b5aecd4614..f85b79ceec 100755
--- a/gr-digital/python/digital/qa_burst_shaper.py
+++ b/gr-digital/python/digital/qa_burst_shaper.py
@@ -25,6 +25,7 @@ from gnuradio import gr, gr_unittest
 from gnuradio import blocks, digital
 import pmt
 import numpy as np
+import sys
 
 def make_length_tag(offset, length):
     return gr.python_to_tag({'offset' : offset,
@@ -32,13 +33,15 @@ def make_length_tag(offset, length):
                              'value' : pmt.from_long(length),
                              'srcid' : pmt.intern('qa_burst_shaper')})
 
+def make_tag(offset, key, value):
+    return gr.python_to_tag({'offset' : offset,
+                             'key' : pmt.intern(key),
+                             'value' : value,
+                             'srcid' : pmt.intern('qa_burst_shaper')})
+
 def compare_tags(a, b):
-    a = gr.tag_to_python(a)
-    b = gr.tag_to_python(b)
-    return a.key == b.key and a.offset == b.offset and \
-           a.value == b.value
-    #return a.key == b.key and a.offset == b.offset and \
-    #       a.srcid == b.srcid and a.value == b.value
+    return a.offset == b.offset and pmt.equal(a.key, b.key) and \
+           pmt.equal(a.value, b.value)
 
 class qa_burst_shaper (gr_unittest.TestCase):
 
@@ -276,6 +279,62 @@ class qa_burst_shaper (gr_unittest.TestCase):
         for i in xrange(len(etags)):
             self.assertTrue(compare_tags(sink.tags()[i], etags[i]))
 
+    def test_tag_propagation (self):
+        prepad = 10
+        postpad = 10
+        length1 = 15
+        length2 = 25
+        gap_len = 5
+        lentag1_offset = 0
+        lentag2_offset = length1 + gap_len
+        tag1_offset = 0                     # accompanies first length tag
+        tag2_offset = length1 + gap_len     # accompanies second length tag
+        tag3_offset = 2                     # in ramp-up state
+        tag4_offset = length1 + 2           # in gap; tag will be dropped
+        tag5_offset = length1 + gap_len + 7 # in copy state
+
+        data = np.concatenate((np.ones(length1), np.zeros(gap_len),
+                               -1.0*np.ones(length2), np.zeros(10)))
+        window = np.concatenate((-2.0*np.ones(5), -4.0*np.ones(5)))
+        tags = (make_length_tag(lentag1_offset, length1),
+                make_length_tag(lentag2_offset, length2),
+                make_tag(tag1_offset, 'head', pmt.intern('tag1')),
+                make_tag(tag2_offset, 'head', pmt.intern('tag2')),
+                make_tag(tag3_offset, 'body', pmt.intern('tag3')),
+                make_tag(tag4_offset, 'body', pmt.intern('tag4')),
+                make_tag(tag5_offset, 'body', pmt.intern('tag5')))
+        expected = np.concatenate((np.zeros(prepad), window[0:5],
+                                   np.ones(length1 - len(window)), window[5:10],
+                                   np.zeros(postpad + prepad), -1.0*window[0:5],
+                                   -1.0*np.ones(length2 - len(window)),
+                                   -1.0*window[5:10], np.zeros(postpad)))
+        elentag1_offset = 0
+        elentag2_offset = length1 + prepad + postpad
+        etag1_offset = 0
+        etag2_offset = elentag2_offset
+        etag3_offset = prepad + tag3_offset
+        etag5_offset = 2*prepad + postpad + tag5_offset - gap_len
+        etags = (make_length_tag(elentag1_offset, length1 + prepad + postpad),
+                 make_length_tag(elentag2_offset, length2 + prepad + postpad),
+                 make_tag(etag1_offset, 'head', pmt.intern('tag1')),
+                 make_tag(etag2_offset, 'head', pmt.intern('tag2')),
+                 make_tag(etag3_offset, 'body', pmt.intern('tag3')),
+                 make_tag(etag5_offset, 'body', pmt.intern('tag5')))
+
+        # flowgraph
+        source = blocks.vector_source_f(data, tags=tags)
+        shaper = digital.burst_shaper_ff(window, pre_padding=prepad,
+                                         post_padding=postpad)
+        sink = blocks.vector_sink_f()
+        self.tb.connect(source, shaper, sink)
+        self.tb.run ()
+
+        # checks
+        self.assertFloatTuplesAlmostEqual(sink.data(), expected, 6)
+        for x, y in zip(sorted(sink.tags(), key=gr.tag_t_offset_compare_key()),
+                        sorted(etags, key=gr.tag_t_offset_compare_key())):
+            self.assertTrue(compare_tags(x, y))
+
 
 if __name__ == '__main__':
     gr_unittest.run(qa_burst_shaper, "qa_burst_shaper.xml")
-- 
cgit v1.2.3