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