1#!/usr/bin/env python
2#
3# Copyright 2015 Free Software Foundation
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 3, or (at your option)
8# any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; see the file COPYING.  If not, write to
17# the Free Software Foundation, Inc., 51 Franklin Street,
18# Boston, MA 02110-1301, USA.
19#
20
21from __future__ import print_function
22from __future__ import division
23from __future__ import unicode_literals
24
25import sys
26import matplotlib
27matplotlib.use("QT4Agg")
28import matplotlib.pyplot as plt
29import matplotlib.animation as animation
30from gnuradio.ctrlport.GNURadioControlPortClient import (
31    GNURadioControlPortClient, TTransportException,
32)
33import numpy
34
35"""
36If a host is running the ATSC receiver chain with ControlPort
37turned on, this script will connect to the host using the hostname and
38port pair of the ControlPort instance and display metrics of the
39receiver. The ATSC publishes information about the success of the
40Reed-Solomon decoder and Viterbi metrics for use here in displaying
41the link quality. This also gets the equalizer taps of the receiver
42and displays the frequency response.
43"""
44
45class atsc_ctrlport_monitor(object):
46    def __init__(self, host, port):
47        argv = [None, host, port]
48        radiosys = GNURadioControlPortClient(argv=argv, rpcmethod='thrift')
49        self.radio = radiosys.client
50        print(self.radio)
51
52
53        vt_init_key = 'dtv_atsc_viterbi_decoder0::decoder_metrics'
54        data = self.radio.getKnobs([vt_init_key])[vt_init_key]
55        init_metric = numpy.mean(data.value)
56        self._viterbi_metric = 100*[init_metric,]
57
58        table_col_labels = ('Num Packets', 'Error Rate', 'Packet Error Rate',
59                            'Viterbi Metric', 'SNR')
60
61        self._fig = plt.figure(1, figsize=(12,12), facecolor='w')
62        self._sp0 = self._fig.add_subplot(4,1,1)
63        self._sp1 = self._fig.add_subplot(4,1,2)
64        self._sp2 = self._fig.add_subplot(4,1,3)
65        self._plot_taps = self._sp0.plot([], [], 'k', linewidth=2)
66        self._plot_psd  = self._sp1.plot([], [], 'k', linewidth=2)
67        self._plot_data = self._sp2.plot([], [], 'ok', linewidth=2, markersize=4, alpha=0.05)
68
69        self._ax2 = self._fig.add_subplot(4,1,4)
70        self._table = self._ax2.table(cellText=[len(table_col_labels)*['0']],
71                                      colLabels=table_col_labels,
72                                      loc='center')
73        self._ax2.axis('off')
74        cells = self._table.properties()['child_artists']
75        for c in cells:
76            c.set_lw(0.1) # set's line width
77            c.set_ls('solid')
78            c.set_height(0.2)
79
80        ani = animation.FuncAnimation(self._fig, self.update_data, frames=200,
81                                      fargs=(self._plot_taps[0], self._plot_psd[0],
82                                             self._plot_data[0], self._table),
83                                      init_func=self.init_function,
84                                      blit=True)
85        plt.show()
86
87    def update_data(self, x, taps, psd, syms, table):
88        try:
89            eqdata_key = 'dtv_atsc_equalizer0::taps'
90            symdata_key = 'dtv_atsc_equalizer0::data'
91            rs_nump_key = 'dtv_atsc_rs_decoder0::num_packets'
92            rs_numbp_key = 'dtv_atsc_rs_decoder0::num_bad_packets'
93            rs_numerrs_key = 'dtv_atsc_rs_decoder0::num_errors_corrected'
94            vt_metrics_key = 'dtv_atsc_viterbi_decoder0::decoder_metrics'
95            snr_key = 'probe2_f0::SNR'
96
97            data = self.radio.getKnobs([])
98            eqdata = data[eqdata_key]
99            symdata = data[symdata_key]
100            rs_num_packets = data[rs_nump_key]
101            rs_num_bad_packets = data[rs_numbp_key]
102            rs_num_errors_corrected = data[rs_numerrs_key]
103            vt_decoder_metrics = data[vt_metrics_key]
104            snr_est = data[snr_key]
105
106            vt_decoder_metrics = numpy.mean(vt_decoder_metrics.value)
107            self._viterbi_metric.pop()
108            self._viterbi_metric.insert(0, vt_decoder_metrics)
109
110        except TTransportException:
111            sys.stderr.write("Lost connection, exiting")
112            sys.exit(1)
113
114        ntaps = len(eqdata.value)
115        taps.set_ydata(eqdata.value)
116        taps.set_xdata(list(range(ntaps)))
117        self._sp0.set_xlim(0, ntaps)
118        self._sp0.set_ylim(min(eqdata.value), max(eqdata.value))
119
120        fs = 6.25e6
121        freq = numpy.linspace(-fs / 2, fs / 2, 10000)
122        H = numpy.fft.fftshift(numpy.fft.fft(eqdata.value, 10000))
123        HdB = 20.0*numpy.log10(abs(H))
124        psd.set_ydata(HdB)
125        psd.set_xdata(freq)
126        self._sp1.set_xlim(0, fs / 2)
127        self._sp1.set_ylim([min(HdB), max(HdB)])
128        self._sp1.set_yticks([min(HdB), max(HdB)])
129        self._sp1.set_yticklabels(["min", "max"])
130
131        nsyms = len(symdata.value)
132        syms.set_ydata(symdata.value)
133        syms.set_xdata(nsyms*[0,])
134        self._sp2.set_xlim([-1, 1])
135        self._sp2.set_ylim([-10, 10])
136
137        per = float(rs_num_bad_packets.value) / float(rs_num_packets.value)
138        ber = float(rs_num_errors_corrected.value) / float(187*rs_num_packets.value)
139
140        table._cells[(1,0)]._text.set_text("{0}".format(rs_num_packets.value))
141        table._cells[(1,1)]._text.set_text("{0:.2g}".format(ber))
142        table._cells[(1,2)]._text.set_text("{0:.2g}".format(per))
143        table._cells[(1,3)]._text.set_text("{0:.1f}".format(numpy.mean(self._viterbi_metric)))
144        table._cells[(1,4)]._text.set_text("{0:.4f}".format(snr_est.value[0]))
145
146        return (taps, psd, syms, table)
147
148    def init_function(self):
149        return self._plot_taps + self._plot_psd + self._plot_data
150
151if __name__ == "__main__":
152    host = sys.argv[1]
153    port = sys.argv[2]
154    m = atsc_ctrlport_monitor(host, port)
155