Browse Source

pty,flags,tooltips, shorter table, quality indicator

master
Clemens Richter 9 years ago
parent
commit
72ce0a2261
  1. 4
      grc/CMakeLists.txt
  2. 45
      grc/crfa_rds_decoder.xml
  3. 45
      grc/rds_decoder.xml
  4. 2
      include/crfa/CMakeLists.txt
  5. 56
      include/crfa/rds_decoder.h
  6. 3
      lib/CMakeLists.txt
  7. 201
      lib/constants.h
  8. 256
      lib/rds_decoder_impl.cc
  9. 70
      lib/rds_decoder_impl.h
  10. 38
      python/piechart.py
  11. 335
      python/python/rds_parser_table_qt.py
  12. 99
      python/rds_parser_table_qt.py
  13. 4
      swig/crfa_swig.i

4
grc/CMakeLists.txt

@ -16,10 +16,10 @@
# along with GNU Radio; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
install(FILES
crfa_multi_rds_printer.xml
crfa_qtguitest.xml
crfa_rds_table_qt.xml
crfa_rds_parser_table_qt.xml DESTINATION share/gnuradio/grc/blocks
crfa_rds_parser_table_qt.xml
crfa_rds_decoder.xml DESTINATION share/gnuradio/grc/blocks
)

45
grc/crfa_rds_decoder.xml

@ -0,0 +1,45 @@
<?xml version="1.0"?>
<block>
<name>RDS Decoder (cr)</name>
<key>crfa_rds_decoder</key>
<category>[crfa]</category>
<import>import crfa</import>
<make>crfa.rds_decoder($log, $debug)</make>
<param>
<name>Log</name>
<key>log</key>
<value>False</value>
<type>bool</type>
<option>
<name>Enable</name>
<key>True</key>
</option>
<option>
<name>Disable</name>
<key>False</key>
</option>
</param>
<param>
<name>Debug</name>
<key>debug</key>
<value>False</value>
<type>bool</type>
<option>
<name>Enable</name>
<key>True</key>
</option>
<option>
<name>Disable</name>
<key>False</key>
</option>
</param>
<sink>
<name>in</name>
<type>byte</type>
</sink>
<source>
<name>out</name>
<type>message</type>
<optional>1</optional>
</source>
</block>

45
grc/rds_decoder.xml

@ -0,0 +1,45 @@
<?xml version="1.0"?>
<block>
<name>RDS Decoder (cr)</name>
<key>gr_rds_decoder_cr</key>
<category>[RDS]</category>
<import>import rds</import>
<make>rds.decoder($log, $debug)</make>
<param>
<name>Log</name>
<key>log</key>
<value>False</value>
<type>bool</type>
<option>
<name>Enable</name>
<key>True</key>
</option>
<option>
<name>Disable</name>
<key>False</key>
</option>
</param>
<param>
<name>Debug</name>
<key>debug</key>
<value>False</value>
<type>bool</type>
<option>
<name>Enable</name>
<key>True</key>
</option>
<option>
<name>Disable</name>
<key>False</key>
</option>
</param>
<sink>
<name>in</name>
<type>byte</type>
</sink>
<source>
<name>out</name>
<type>message</type>
<optional>1</optional>
</source>
</block>

2
include/crfa/CMakeLists.txt

@ -22,5 +22,5 @@
########################################################################
install(FILES
api.h
DESTINATION include/crfa
rds_decoder.h DESTINATION include/crfa
)

56
include/crfa/rds_decoder.h

@ -0,0 +1,56 @@
/* -*- c++ -*- */
/*
* Copyright 2016 <+YOU OR YOUR COMPANY+>.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef INCLUDED_CRFA_RDS_DECODER_H
#define INCLUDED_CRFA_RDS_DECODER_H
#include <crfa/api.h>
#include <gnuradio/sync_block.h>
namespace gr {
namespace crfa {
/*!
* \brief <+description of block+>
* \ingroup crfa
*
*/
class CRFA_API rds_decoder : virtual public gr::sync_block
{
public:
typedef boost::shared_ptr<rds_decoder> sptr;
/*!
* \brief Return a shared_ptr to a new instance of crfa::rds_decoder.
*
* To avoid accidental use of raw pointers, crfa::rds_decoder's
* constructor is in a private implementation
* class. crfa::rds_decoder::make is the public interface for
* creating new instances.
*/
static sptr make(bool log, bool debug);
};
} // namespace crfa
} // namespace gr
#endif /* INCLUDED_CRFA_RDS_DECODER_H */

3
lib/CMakeLists.txt

@ -24,9 +24,8 @@ include(GrPlatform) #define LIB_SUFFIX
include_directories(${Boost_INCLUDE_DIR})
link_directories(${Boost_LIBRARY_DIRS})
list(APPEND crfa_sources
)
rds_decoder_impl.cc )
set(crfa_sources "${crfa_sources}" PARENT_SCOPE)
if(NOT crfa_sources)

201
lib/constants.h

@ -0,0 +1,201 @@
/*
* Copyright 2004 Free Software Foundation, Inc.
*
* This file is part of GNU Radio
*
* GNU Radio is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* GNU Radio is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GNU Radio; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
/* see page 59, Annex C, table C.1 in the standard
* offset word C' has been put at the end */
static const unsigned int offset_pos[5]={0,1,2,3,2};
static const unsigned int offset_word[5]={252,408,360,436,848};
static const unsigned int syndrome[5]={383,14,303,663,748};
static const char * const offset_name[]={"A","B","C","D","C'"};
/* Annex F of RBDS Standard Table F.1 (North America) and
* Table F.2 (Europe) */
const std::string pty_table[32][2]={
{"Undefined", "Undefined"},
{"News", "News"},
{"Current Affairs", "Information"},
{"Information", "Sports"},
{"Sport", "Talk"},
{"Education", "Rock"},
{"Drama", "Classic Rock"},
{"Culture", "Adult Hits"},
{"Science", "Soft Rock"},
{"Varied", "Top 40"},
{"Pop Music", "Country"},
{"Rock Music", "Oldies"},
{"Easy Listening", "Soft"},
{"Light Classical", "Nostalgia"},
{"Serious Classical", "Jazz"},
{"Other Music", "Classical"},
{"Weather", "Rhythm & Blues"},
{"Finance", "Soft Rhythm & Blues"},
{"Children’s Programmes", "Language"},
{"Social Affairs", "Religious Music"},
{"Religion", "Religious Talk"},
{"Phone-In", "Personality"},
{"Travel", "Public"},
{"Leisure", "College"},
{"Jazz Music", "Spanish Talk"},
{"Country Music", "Spanish Music"},
{"National Music", "Hip Hop"},
{"Oldies Music", "Unassigned"},
{"Folk Music", "Unassigned"},
{"Documentary", "Weather"},
{"Alarm Test", "Emergency Test"},
{"Alarm", "Emergency"}};
/* page 71, Annex D, table D.1 in the standard */
const std::string pi_country_codes[15][5]={
{"DE","GR","MA","__","MD"},
{"DZ","CY","CZ","IE","EE"},
{"AD","SM","PL","TR","__"},
{"IL","CH","VA","MK","__"},
{"IT","JO","SK","__","__"},
{"BE","FI","SY","__","UA"},
{"RU","LU","TN","__","__"},
{"PS","BG","__","NL","PT"},
{"AL","DK","LI","LV","SI"},
{"AT","GI","IS","LB","__"},
{"HU","IQ","MC","__","__"},
{"MT","GB","LT","HR","__"},
{"DE","LY","YU","__","__"},
{"__","RO","ES","SE","__"},
{"EG","FR","NO","BY","BA"}};
/* page 72, Annex D, table D.2 in the standard */
const std::string coverage_area_codes[16]={
"Local",
"International",
"National",
"Supra-regional",
"Regional 1",
"Regional 2",
"Regional 3",
"Regional 4",
"Regional 5",
"Regional 6",
"Regional 7",
"Regional 8",
"Regional 9",
"Regional 10",
"Regional 11",
"Regional 12"};
const std::string rds_group_acronyms[16]={
"BASIC",
"PIN/SL",
"RT",
"AID",
"CT",
"TDC",
"IH",
"RP",
"TMC",
"EWS",
"___",
"___",
"___",
"___",
"EON",
"___"};
/* page 74, Annex E, table E.1 in the standard: that's the ASCII table!!! */
/* see page 84, Annex J in the standard */
const std::string language_codes[44]={
"Unkown/not applicable",
"Albanian",
"Breton",
"Catalan",
"Croatian",
"Welsh",
"Czech",
"Danish",
"German",
"English",
"Spanish",
"Esperanto",
"Estonian",
"Basque",
"Faroese",
"French",
"Frisian",
"Irish",
"Gaelic",
"Galician",
"Icelandic",
"Italian",
"Lappish",
"Latin",
"Latvian",
"Luxembourgian",
"Lithuanian",
"Hungarian",
"Maltese",
"Dutch",
"Norwegian",
"Occitan",
"Polish",
"Portuguese",
"Romanian",
"Romansh",
"Serbian",
"Slovak",
"Slovene",
"Finnish",
"Swedish",
"Turkish",
"Flemish",
"Walloon"};
/* see page 12 in ISO 14819-1 */
const std::string tmc_duration[8][2]={
{"no duration given", "no duration given"},
{"15 minutes", "next few hours"},
{"30 minutes", "rest of the day"},
{"1 hour", "until tomorrow evening"},
{"2 hours", "rest of the week"},
{"3 hours", "end of next week"},
{"4 hours", "end of the month"},
{"rest of the day", "long period"}};
/* optional message content, data field lengths and labels
* see page 15 in ISO 14819-1 */
const int optional_content_lengths[16]={3,3,5,5,5,8,8,8,8,11,16,16,16,16,0,0};
const std::string label_descriptions[16]={
"Duration",
"Control code",
"Length of route affected",
"Speed limit advice",
"Quantifier",
"Quantifier",
"Supplementary information code",
"Explicit start time",
"Explicit stop time",
"Additional event",
"Detailed diversion instructions",
"Destination",
"RFU (Reserved for future use)",
"Cross linkage to source of problem, or another route",
"Separator",
"RFU (Reserved for future use)"};

256
lib/rds_decoder_impl.cc

@ -0,0 +1,256 @@
/* -*- c++ -*- */
/*
* Copyright 2016 <+YOU OR YOUR COMPANY+>.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#define dout debug && std::cout
#define lout log && std::cout
#include <gnuradio/io_signature.h>
#include "constants.h"
#include "rds_decoder_impl.h"
namespace gr {
namespace crfa {
rds_decoder::sptr
rds_decoder::make(bool log, bool debug)
{
return gnuradio::get_initial_sptr
(new rds_decoder_impl(log, debug));
}
/*
* The private constructor
*/
rds_decoder_impl::rds_decoder_impl(bool log, bool debug)
: gr::sync_block("rds_decoder",
gr::io_signature::make (1, 1, sizeof(char)),
gr::io_signature::make (0, 0, 0)),
log(log),
debug(debug)
{
set_output_multiple(104); // 1 RDS datagroup = 104 bits
message_port_register_out(pmt::mp("out"));
enter_no_sync();
}
/*
* Our virtual destructor.
*/
rds_decoder_impl::~rds_decoder_impl()
{
}
////////////////////////// HELPER FUNTIONS /////////////////////////
void rds_decoder_impl::enter_no_sync() {
presync = false;
d_state = NO_SYNC;
}
void rds_decoder_impl::enter_sync(unsigned int sync_block_number) {
last_wrong_blocks_counter = 0;
wrong_blocks_counter = 0;
blocks_counter = 0;
block_bit_counter = 0;
block_number = (sync_block_number + 1) % 4;
group_assembly_started = false;
d_state = SYNC;
}
/* see Annex B, page 64 of the standard */
unsigned int rds_decoder_impl::calc_syndrome(unsigned long message,
unsigned char mlen) {
unsigned long reg = 0;
unsigned int i;
const unsigned long poly = 0x5B9;
const unsigned char plen = 10;
for (i = mlen; i > 0; i--) {
reg = (reg << 1) | ((message >> (i-1)) & 0x01);
if (reg & (1 << plen)) reg = reg ^ poly;
}
for (i = plen; i > 0; i--) {
reg = reg << 1;
if (reg & (1<<plen)) reg = reg ^ poly;
}
return (reg & ((1<<plen)-1)); // select the bottom plen bits of reg
}
void rds_decoder_impl::decode_group(unsigned int *group) {
// raw data bytes, as received from RDS.
// 8 info bytes, followed by 4 RDS offset chars: ABCD/ABcD/EEEE (in US)
unsigned char bytes[12];
// RDS information words
bytes[0] = (group[0] >> 8U) & 0xffU;
bytes[1] = (group[0] ) & 0xffU;
bytes[2] = (group[1] >> 8U) & 0xffU;
bytes[3] = (group[1] ) & 0xffU;
bytes[4] = (group[2] >> 8U) & 0xffU;
bytes[5] = (group[2] ) & 0xffU;
bytes[6] = (group[3] >> 8U) & 0xffU;
bytes[7] = (group[3] ) & 0xffU;
// RDS offset words
bytes[8] = offset_chars[0];
bytes[9] = offset_chars[1];
bytes[10] = offset_chars[2];
bytes[11] = offset_chars[3];
bytes[12]=last_wrong_blocks_counter;
pmt::pmt_t data(pmt::make_blob(bytes, 13));
pmt::pmt_t meta(pmt::PMT_NIL);
pmt::pmt_t pdu(pmt::cons(meta, data)); // make PDU: (metadata, data) pair
message_port_pub(pmt::mp("out"), pdu);
}
//work function
int rds_decoder_impl::work (int noutput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items)
{
const bool *in = (const bool *) input_items[0];
dout << "RDS data decoder at work: input_items = "
<< noutput_items << ", /104 = "
<< noutput_items / 104 << std::endl;
int i=0,j;
unsigned long bit_distance, block_distance;
unsigned int block_calculated_crc, block_received_crc, checkword,dataword;
unsigned int reg_syndrome;
unsigned char offset_char('x'); // x = error while decoding the word offset
/* the synchronization process is described in Annex C, page 66 of the standard */
while (i<noutput_items) {
reg=(reg<<1)|in[i]; // reg contains the last 26 rds bits
switch (d_state) {
case NO_SYNC:
reg_syndrome = calc_syndrome(reg,26);
for (j=0;j<5;j++) {
if (reg_syndrome==syndrome[j]) {
if (!presync) {
lastseen_offset=j;
lastseen_offset_counter=bit_counter;
presync=true;
}
else {
bit_distance=bit_counter-lastseen_offset_counter;
if (offset_pos[lastseen_offset]>=offset_pos[j])
block_distance=offset_pos[j]+4-offset_pos[lastseen_offset];
else
block_distance=offset_pos[j]-offset_pos[lastseen_offset];
if ((block_distance*26)!=bit_distance) presync=false;
else {
lout << "@@@@@ Sync State Detected" << std::endl;
enter_sync(j);
}
}
break; //syndrome found, no more cycles
}
}
break;
case SYNC:
/* wait until 26 bits enter the buffer */
if (block_bit_counter<25) block_bit_counter++;
else {
good_block=false;
dataword=(reg>>10) & 0xffff;
block_calculated_crc=calc_syndrome(dataword,16);
checkword=reg & 0x3ff;
/* manage special case of C or C' offset word */
if (block_number==2) {
block_received_crc=checkword^offset_word[block_number];
if (block_received_crc==block_calculated_crc) {
good_block=true;
offset_char = 'C';
} else {
block_received_crc=checkword^offset_word[4];
if (block_received_crc==block_calculated_crc) {
good_block=true;
offset_char = 'c'; // C' (C-Tag)
} else {
wrong_blocks_counter++;
good_block=false;
}
}
}
else {
block_received_crc=checkword^offset_word[block_number];
if (block_received_crc==block_calculated_crc) {
good_block=true;
if (block_number==0) offset_char = 'A';
else if (block_number==1) offset_char = 'B';
else if (block_number==3) offset_char = 'D';
} else {
wrong_blocks_counter++;
good_block=false;
}
}
/* done checking CRC */
if (block_number==0 && good_block) {
group_assembly_started=true;
group_good_blocks_counter=1;
}
if (group_assembly_started) {
if (!good_block) group_assembly_started=false;
else {
group[block_number]=dataword;
offset_chars[block_number] = offset_char;
group_good_blocks_counter++;
}
if (group_good_blocks_counter==5) decode_group(group);
}
block_bit_counter=0;
block_number=(block_number+1) % 4;
blocks_counter++;
/* 1187.5 bps / 104 bits = 11.4 groups/sec, or 45.7 blocks/sec */
if (blocks_counter==50) {
last_wrong_blocks_counter=wrong_blocks_counter;
if (wrong_blocks_counter>35) {
lout << "@@@@@ Lost Sync (Got " << wrong_blocks_counter
<< " bad blocks on " << blocks_counter
<< " total)" << std::endl;
enter_no_sync();
} else {
lout << "@@@@@ Still Sync-ed (Got " << wrong_blocks_counter
<< " bad blocks on " << blocks_counter
<< " total)" << std::endl;
}
blocks_counter=0;
wrong_blocks_counter=0;
}
}
break;
default:
d_state=NO_SYNC;
break;
}
i++;
bit_counter++;
}
return noutput_items;
}/*work function*/
} /* namespace crfa */
} /* namespace gr */

70
lib/rds_decoder_impl.h

@ -0,0 +1,70 @@
/* -*- c++ -*- */
/*
* Copyright 2016 <+YOU OR YOUR COMPANY+>.
*
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef INCLUDED_CRFA_RDS_DECODER_IMPL_H
#define INCLUDED_CRFA_RDS_DECODER_IMPL_H
#include <crfa/rds_decoder.h>
namespace gr {
namespace crfa {
class rds_decoder_impl : public rds_decoder
{
public:
rds_decoder_impl(bool log, bool debug);
private:
~rds_decoder_impl();
// Where all the action really happens
int work(int noutput_items,
gr_vector_const_void_star &input_items,
gr_vector_void_star &output_items);
void enter_no_sync();
void enter_sync(unsigned int);
unsigned int calc_syndrome(unsigned long, unsigned char);
void decode_group(unsigned int*);
unsigned long bit_counter;
unsigned long lastseen_offset_counter, reg;
unsigned int block_bit_counter;
unsigned int wrong_blocks_counter;
unsigned int blocks_counter;
unsigned int group_good_blocks_counter;
unsigned int group[4];
unsigned char offset_chars[4]; // [ABCcDEx] (x=error)
bool debug;
bool log;
bool presync;
bool good_block;
bool group_assembly_started;
unsigned char last_wrong_blocks_counter;
unsigned char lastseen_offset;
unsigned char block_number;
enum { NO_SYNC, SYNC } d_state;
};
} // namespace crfa
} // namespace gr
#endif /* INCLUDED_CRFA_RDS_DECODER_IMPL_H */

38
python/piechart.py

@ -0,0 +1,38 @@
from PyQt4.QtGui import QGraphicsScene, QApplication, QGraphicsView, QGraphicsEllipseItem
from PyQt4 import Qt, QtCore, QtGui
from PyQt4.Qt import QColor
import sys, random,code
app = QApplication(sys.argv)
scene = QGraphicsScene()
families = [1,2,3,4,5,6,7,8,9,10]
total = 0
set_angle = 0
count1 = 0
colours = []
total = sum(families)
size=300
for count in range(len(families)):
number = []
for count in range(3):
number.append(random.randrange(0, 255))
colours.append(QColor(number[0],number[1],number[2]))
for family in families:
# Max span is 5760, so we have to calculate corresponding span angle
angle = round(float(family*5760)/total)
ellipse = QGraphicsEllipseItem(0,0,size,size)
ellipse.setPos(0,0)
ellipse.setStartAngle(set_angle)
ellipse.setSpanAngle(angle)
ellipse.setBrush(colours[count1])
set_angle += angle
count1 += 1
scene.addItem(ellipse)
view = QGraphicsView(scene)
view.show()
view.setFixedSize(size+10,size+10)
app.exec_(code.interact(local=locals()))

335
python/python/rds_parser_table_qt.py

@ -1,335 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2016 <+YOU OR YOUR COMPANY+>.
#
# This is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
import numpy
from gnuradio import gr
import code,pmt,functools
from PyQt4 import Qt, QtCore, QtGui
import pprint
pp = pprint.PrettyPrinter()
from PyQt4.QtCore import QObject, pyqtSignal
class rds_parser_table_qt_Signals(QObject):
DataUpdateEvent = QtCore.pyqtSignal(dict)
def __init__(self, parent=None):
super(QtCore.QObject, self).__init__()
class rds_parser_table_qt(gr.sync_block):
"""
docstring for block qtguitest
"""
def __init__(self,signals,nPorts):
#QObject.__init__()
gr.sync_block.__init__(self,
name="RDS Table",
in_sig=None,
out_sig=None)
for i in range(0,nPorts):
self.message_port_register_in(pmt.intern('in%d'%i))
self.set_msg_handler(pmt.intern('in%d'%i), functools.partial(self.handle_msg, port=i))
self.signals=signals
self.RTdict={}
self.RTvalid={}
self.PSNdict={}
self.PSNvalid={}
self.AFdata={}
self.blockcounts={}
self.printcounter=0
def handle_msg(self, msg, port):
#code.interact(local=locals())
array=pmt.to_python(msg)[1]
groupNR=array[2]&0b11110000
groupVar=array[2]&0b00001000
if (groupVar == 0):
groupType=str(groupNR >> 4)+"A"
else:
groupType=str(groupNR >> 4)+"B"
#print("raw:"+str(pmt.to_python(msg))+"\n")
PI="%02X%02X" %(array[0],array[1])
#print("1st block:"+str(array[0])+","+str(array[1])+"= ID: %s" %PI)
#print("2st block:"+str(array[2])+","+str(array[3])+"= type:"+groupType)
#print("3st block:"+str(array[4])+","+str(array[5]))
#print("4st block:"+str(array[6])+","+str(array[7]))
if (groupType == "0A"):#AF PSN
adr=array[3]&0b00000011
segment=self.decode_chars(chr(array[6])+chr(array[7]))
if(not self.PSNdict.has_key(PI)):#initialize dict
self.PSNdict[PI]="_"*8
self.PSNvalid[PI]=[False]*8
self.AFdata[PI]={}
#1110 0000 = no AF
#1110 0001 = 1AF
#1111 1001 = 25AF
if(array[5]>= 224 and array[5]<= 249):
print("AF1 detected")
self.AFdata[PI]['number']=array[5]-224
self.signals.DataUpdateEvent.emit({'row':port,'AF':self.AFdata[PI]})
if(array[6]>= 224 and array[6]<= 249):
print("AF2 detected")
name_list=list(self.PSNdict[PI])
if (name_list[adr*2:adr*2+2]==list(segment)):#segment already there
segmentcolor="green"
elif(name_list[adr*2:adr*2+2]==['_']*2): #segment new
segmentcolor="orange"
name_list[adr*2:adr*2+2]=segment
else:#name changed (böse)
segmentcolor="red"
name_list=['_']*8 #reset name
name_list[adr*2:adr*2+2]=segment
#reset stored text:
self.PSNdict[PI]="_"*8
self.PSNvalid[PI]=[False]*8
self.PSNvalid[PI][adr*2:adr*2+2]=[True] *2
self.PSNdict[PI]="".join(name_list)
#determine if text is valid
valid=True
for i in range(0,8):
if (not self.PSNvalid[PI][i]):
valid = False
if(valid):
textcolor="black"
else:
textcolor="gray"
formatted_text=self.color_text(self.PSNdict[PI],adr*2,adr*2+2,textcolor,segmentcolor)
self.signals.DataUpdateEvent.emit({'col':5,'row':port,'PI':PI,'PSN':formatted_text})
elif (groupType == "2A"):#RT radiotext
if(not self.RTdict.has_key(PI)):#initialize dict
self.RTdict[PI]="_"*64
self.RTvalid[PI]=[False]*64
else:
adr=array[3]&0b00001111
segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7]))
#print("RT:adress: %d, segment:%s"%(adr,segment))
#self.signals.DataUpdateEvent.emit({'col':5,'row':port,'PI':PI,'groupType':groupType,'adress':adr,'segment':segment})
text_list=list(self.RTdict[PI])
#determine text length:
try:
text_end=text_list.index('\r')
except ValueError:
text_end=64 #assume whole string is important
pass
if (text_list[adr*4:adr*4+4]==list(segment)):#segment already there
segmentcolor="green"
elif (text_list[adr*4:adr*4+4]==['_']*4):#segment new
segmentcolor="orange"
text_list[adr*4:adr*4+4]=segment
else:
segmentcolor="red"
text_list=['_']*64 #clear text
text_list[adr*4:adr*4+4]=segment
#reset stored text:
self.RTdict[PI]="_"*64
self.RTvalid[PI]=[False]*64
self.RTvalid[PI][adr*4:adr*4+4]=[True] *4
self.RTdict[PI]="".join(text_list)
#determine if (new) text is valid
valid=True
for i in range(0,text_end):
if (not self.RTvalid[PI][i]):
valid = False
if(valid):
textcolor="black"
else:
textcolor="gray"
#formatted_text="<font face='Courier New' color='%s'>%s</font><font face='Courier New' color='%s'>%s</font><font face='Courier New' color='%s'>%s</font>"% (textcolor,self.RTdict[PI][:adr*4],segmentcolor,self.RTdict[PI][adr*4:adr*4+4],textcolor,self.RTdict[PI][adr*4+4:])
formatted_text=self.color_text(self.RTdict[PI],adr*4,adr*4+4,textcolor,segmentcolor)
#print(self.RTdict[PI]+" valid:"+str(valid)+"valarr:"+str(self.RTvalid[PI]))
self.signals.DataUpdateEvent.emit({'col':5,'row':port,'PI':PI,'string':formatted_text})
#code.interact(local=locals())
elif (groupType == "4A"):#CT clock time
datecode=((array[3] & 0x03) << 15) | (array[4] <<7)|((array[5] >> 1) & 0x7f)
hours=((array[5] & 0x1) << 4) | ((array[6] >> 4) & 0x0f)
minutes=((array[6] &0x0F)<<2)|((array[7] >>6)&0x3)
offsetdir=(array[7]>>5)&0x1
local_time_offset=0.5*((array[7])&0x1F)
if(offsetdir==1):
local_time_offset*=-1
year=int((datecode - 15078.2) / 365.25)
month=int((datecode - 14956.1 - int(year * 365.25)) / 30.6001)
day=datecode - 14956 - int(year * 365.25) - int(month * 30.6001)
if(month == 14 or month == 15):#no idea why -> annex g of RDS spec
year += 1;
month -= 13
year+=1900
datestring="%02i.%02i.%4i, %02i:%02i (%+.1fh)" % (day,month,year,hours,minutes,local_time_offset)
self.signals.DataUpdateEvent.emit({'col':4,'row':port,'PI':PI,'string':datestring})
else:#other group
printfreq=100
self.printcounter+=1
if self.blockcounts.has_key(PI):#1st group on this station
if self.blockcounts[PI].has_key(groupType):#1st group of this type
self.blockcounts[PI][groupType] +=1 #increment
else:
self.blockcounts[PI][groupType] = 1 #initialize
else:
self.blockcounts[PI]={}#initialize dict
if self.printcounter == printfreq:
pp.pprint(self.blockcounts)
self.printcounter=0
#print("group of type %s not decoded on station %s"% (groupType,PI))
def decode_chars(self,charstring):
alphabet={
0b1000:u"áàéèíìóòúùÑÇŞßiIJ",
0b1001:u"âäêëîïôöûüñçş??ij",
0b1100:u"ÁÀÉÈÍÌÓÒÚÙŘČŠŽĐĿ",
0b1101:u"áàéèíìóòúùřčšžđŀ"}
charlist=list(charstring)
for i,char in enumerate(charstring):
#code.interact(local=locals())
if ord(char)<= 0b01111111:
charlist[i]=char #use ascii
else:
#split byte
alnr=(ord(char)&0xF0 )>>4 #upper 4 bit
index=ord(char)&0x0F #lower 4 bit
#code.interact(local=locals())
try:
charlist[i]=alphabet[alnr][index]
except KeyError:
charlist[i]=char
pass
return "".join(charlist)
def color_text(self, text, start,end,textcolor,segmentcolor):
formatted_text="<font face='Courier New' color='%s'>%s</font><font face='Courier New' color='%s'>%s</font><font face='Courier New' color='%s'>%s</font>"% (textcolor,text[:start],segmentcolor,text[start:end],textcolor,text[end:])
return formatted_text
class rds_parser_table_qt_Widget(QtGui.QWidget):
def __init__(self, signals,label):
print("gui initializing")
self.signals = signals
self.signals.DataUpdateEvent.connect(self.display_data)
""" Creates the QT Range widget """
QtGui.QWidget.__init__(self)
layout = Qt.QVBoxLayout()
self.label = Qt.QLabel(label)
layout.addWidget(self.label)
self.setLayout(layout)
self.table=QtGui.QTableWidget(self)
self.table.setRowCount(5)
self.table.setColumnCount(7)
self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) #disallow editing
#Data
empty_text32='________________________________'
empty_text64='________________________________________________________________'
#empty_text64='\xe4'*64
self.data = {'ID':range(1,6),
'freq':['','','',''],
'name':[],
'AF':['','','',''],
'time':[],
'text':[],
'buttons':[]}
#Enter data onto Table
horHeaders = []
for n, key in enumerate(['ID','freq','name','AF','time','text','buttons']):
#for n, key in enumerate(sorted(self.data.keys())):
horHeaders.append(key)
for m, item in enumerate(self.data[key]):
if type(item)==int:#convert ints to strings
newitem = QtGui.QTableWidgetItem(str(item))
else:
newitem = QtGui.QTableWidgetItem(item)
self.table.setItem(m, n, newitem)
for i in range(0,4):#create buttons
button=QtGui.QPushButton("play")
self.table.setCellWidget(i,self.table.columnCount()-1,button)
button.clicked.connect(self.onCLick)
for i in range(0,4):#create text labels
label=QtGui.QLabel(empty_text64)
#label.setFont(QtGui.QFont("Courier New"))
self.table.setCellWidget(i,self.table.columnCount()-2,label)
for i in range(0,4):#create name labels
label=QtGui.QLabel("_"*8)
#label.setFont(QtGui.QFont("Courier New"))
self.table.setCellWidget(i,2,label)
for i in range(0,4):#create time labels
label=QtGui.QLabel()
#label.setFont(QtGui.QFont("Courier New"))
self.table.setCellWidget(i,4,label)
#Add Header
self.table.setHorizontalHeaderLabels(horHeaders)
layout.addWidget(self.label)
layout.addWidget(self.table)
self.button = QtGui.QPushButton("i am a button")
layout.addWidget(self.button)
def display_data(self, event):
#pp.pprint(event)
if type(event)==dict and event.has_key('row'):
if event.has_key('string'):
item=self.table.cellWidget(event['row'],event['col'])
item.setText(event['string'])
if event.has_key('PI'):
#setPI
PIcol=0
self.table.item(event['row'],PIcol).setText(event['PI'])
if event.has_key('AF'):
#setAF
PIcol=3
self.table.item(event['row'],PIcol).setText(event['AF']['number'])
if event.has_key('PSN'):
#setPSN
PSNcol=2
item=self.table.cellWidget(event['row'],PSNcol)
item.setText(event['PSN'])
self.table.resizeColumnsToContents()
#def reset_color(self):
#for i in range(0,self.table.rowCount()):
#for j in range(0,self.table.columnCount()):
#item = self.table.item(i,j)
##code.interact(local=locals())
##print(item.type())
#if item != '':
#try:
#item.setTextColor(QtCore.Qt.black)
#except:
#pass
def onCLick(self):
print("button clicked")
#self.reset_color()
#pp.pprint(event)
if __name__ == "__main__":
from PyQt4 import Qt
import sys
# def valueChanged(frequency):
# print("Value updated - " + str(frequency))
app = Qt.QApplication(sys.argv)
# widget = RangeWidget(Range(0, 100, 10, 1, 100), valueChanged, "Test", "counter_slider", int)
mainobj= rds_parser_table_qt_Signals()
#mainobj=None
widget = rds_parser_table_qt_Widget(mainobj,"TestLabel")
widget.show()
widget.setWindowTitle("Test Qt gui")
widget.setGeometry(200,200,600,300)
#code.interact(local=locals())
sys.exit(app.exec_())
widget = None

99
python/rds_parser_table_qt.py

@ -52,6 +52,8 @@ class rds_parser_table_qt(gr.sync_block):
self.printcounter=0
self.ODA_application_names={}
self.TMC_data={}
self.colorder=['ID','freq','name','PTY','AF','time','text','quality','buttons']
#workdir="/user/wire2/richter/hackrf_prototypes/"
workdir="/media/clemens/intdaten/uni_bulk/forschungsarbeit/hackrf_prototypes/"
reader = csv.reader(open(workdir+'RDS_ODA AIDs_names_only.csv'), delimiter=',', quotechar='"')
reader.next()#skip header
@ -69,6 +71,12 @@ class rds_parser_table_qt(gr.sync_block):
reader = csv.reader(open(workdir+'event-list_code+de-name_sort.csv'), delimiter=',', quotechar='"')
#no header
self.ecl_dict=dict((int(rows[0]),rows[1]) for rows in reader)
#read PTY list
f=open(workdir+'pty-list.csv')
reader = csv.reader(f, delimiter=',', quotechar='"')
reader.next()#skip header
self.pty_dict=dict((int(rows[0]),rows[1]) for rows in reader)
f.close()
def handle_msg(self, msg, port):
#code.interact(local=locals())
array=pmt.to_python(msg)[1]
@ -79,20 +87,40 @@ class rds_parser_table_qt(gr.sync_block):
else:
groupType=str(groupNR >> 4)+"B"
PI="%02X%02X" %(array[0],array[1])
TP=(array[2]>>2)&0x1
block2=(array[2]<<8)|(array[3]) #block2
PTY=(block2>>5)&0x1F
wrong_blocks=int(array[12])
#initialize dict 1st packet from station:
if not self.RDS_data.has_key(PI):
self.RDS_data[PI]={}
self.RDS_data[PI]["blockcounts"]={}
self.RDS_data[PI]["blockcounts"]["any"]=0
self.RDS_data[PI]["AID_list"]={}
self.RDS_data[PI]["PSN"]="_"*8
self.RDS_data[PI]["PSN_valid"]=[False]*8
self.RDS_data[PI]["AF"]={}
self.RDS_data[PI]["DI"]=[2,2,2,2]
print("found station %s"%PI)
self.RDS_data[PI]["blockcounts"]["any"]+=1
if self.RDS_data[PI]["blockcounts"]["any"]==5:
self.RDS_data[PI]["blockcounts"]["any"]=0
dots="."*self.RDS_data[PI]["blockcounts"]["any"]
self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'PTY':self.pty_dict[PTY],'TP':TP,'wrong_blocks':wrong_blocks,'dots':dots})
if (groupType == "0A"):#AF PSN
adr=array[3]&0b00000011
segment=self.decode_chars(chr(array[6])+chr(array[7]))
d=(array[3]>>2)&0x1
self.RDS_data[PI]["DI"][3-adr]=d
#DI[0]=d0 0=Mono 1=Stereo
#d1 Not artificial head Artificial head
#d2 Not compressed Compressed
#d3 Static PTY Dynamic PTY
TA=(array[3]>>4)&0x1
MS=(array[3]>>3)&0x1
flag_string="TP:%i, TA:%i, MS:%i, DI:%s"%(TP,TA,MS,str(self.RDS_data[PI]["DI"]))
self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'flags':flag_string})
#1110 0000 = no AF
#1110 0001 = 1AF
#1111 1001 = 25AF
@ -129,13 +157,14 @@ class rds_parser_table_qt(gr.sync_block):
else:
textcolor="gray"
formatted_text=self.color_text(self.RDS_data[PI]["PSN"],adr*2,adr*2+2,textcolor,segmentcolor)
self.signals.DataUpdateEvent.emit({'col':5,'row':port,'PI':PI,'PSN':formatted_text})
self.signals.DataUpdateEvent.emit({'row':port,'PI':PI,'PSN':formatted_text})
elif (groupType == "2A"):#RT radiotext
if(not self.RDS_data[PI].has_key("RT")):#initialize variables
self.RDS_data[PI]["RT"]="_"*64
self.RDS_data[PI]["RT_valid"]=[False]*64
self.RDS_data[PI]["RT_all_valid"]=False
else:
adr=array[3]&0b00001111
segment=self.decode_chars(chr(array[4])+chr(array[5])+chr(array[6])+chr(array[7]))
@ -178,8 +207,8 @@ class rds_parser_table_qt(gr.sync_block):
formatted_text=self.color_text(self.RDS_data[PI]["RT"],adr*4,adr*4+4,textcolor,segmentcolor)
#print(self.RDS_data[PI]["RT"]+" valid:"+str(valid)+"valarr:"+str(self.RDS_data[PI]["RT_valid"]))
self.signals.DataUpdateEvent.emit({'col':5,'row':port,'PI':PI,'string':formatted_text})
rtcol=self.colorder.index('text')
self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'string':formatted_text})
#code.interact(local=locals())
elif (groupType == "3A"):#ODA announcements (contain application ID "AID")
AID=(array[6]<<8)|(array[7])#combine 2 bytes into 1 block
@ -205,15 +234,20 @@ class rds_parser_table_qt(gr.sync_block):
local_time_offset=0.5*((array[7])&0x1F)
if(offsetdir==1):
local_time_offset*=-1
year=int((datecode - 15078.2) / 365.25)
year=int((datecode - 15078.2) / 365.25)
month=int((datecode - 14956.1 - int(year * 365.25)) / 30.6001)
day=datecode - 14956 - int(year * 365.25) - int(month * 30.6001)
if(month == 14 or month == 15):#no idea why -> annex g of RDS spec
year += 1;
month -= 13
year+=1900
datestring="%02i.%02i.%4i, %02i:%02i (%+.1fh)" % (day,month,year,hours,minutes,local_time_offset)
self.signals.DataUpdateEvent.emit({'col':4,'row':port,'PI':PI,'string':datestring})
#month was off by one different rounding in c and python?
month-=1
#datestring="%02i.%02i.%4i, %02i:%02i (%+.1fh)" % (day,month,year,hours,minutes,local_time_offset)
timestring="%02i:%02i (%+.1fh)" % (hours,minutes,local_time_offset)
datestring="%02i.%02i.%4i" % (day,month,year)
ctcol=self.colorder.index('time')
self.signals.DataUpdateEvent.emit({'col':ctcol,'row':port,'PI':PI,'string':timestring,'tooltip':datestring})
#TMC-alert-c (grouptype mostly 8A):
elif self.RDS_data[PI]["AID_list"].has_key(52550) and self.RDS_data[PI]["AID_list"][52550]["groupType"]==groupType:
tmc_x=array[3]&0x1f #lower 5 bit of block2
@ -285,7 +319,7 @@ class rds_parser_table_qt(gr.sync_block):
tag2_len=int(tag2&(2**5-1))
if not self.RDS_data[PI].has_key("RT+"):
self.RDS_data[PI]["RT+"]={}
if(self.RDS_data[PI].has_key("RT") and self.RDS_data[PI]["RT_all_valid"]):
if(self.RDS_data[PI].has_key("RT") and self.RDS_data[PI]["RT_all_valid"]):#TODO better (more fine grained) detection of valid RT+ info
rt=self.RDS_data[PI]["RT"]
if not tag1_type=="DUMMY_CLASS":
self.RDS_data[PI]["RT+"][tag1_type]=rt[tag1_start:tag1_start+tag1_len+1]
@ -297,7 +331,9 @@ class rds_parser_table_qt(gr.sync_block):
artist=rt[tag1_start:tag1_start+tag1_len+1]
song=rt[tag2_start:tag2_start+tag2_len+1]
formatted_text="%s by %s"%(song,artist)
self.signals.DataUpdateEvent.emit({'col':6,'row':port,'PI':PI,'string':formatted_text})
rtcol=self.colorder.index('text')
self.signals.DataUpdateEvent.emit({'col':rtcol,'row':port,'PI':PI,'tooltip':formatted_text})
#self.signals.DataUpdateEvent.emit({'col':8,'row':port,'PI':PI,'string':formatted_text})
elif(not tag1_type=="ITEM.ARTIST" and not tag1_type=="DUMMY_CLASS"):
print("%s:RT+: tag1_type:%s, tag2_type:%s"%(PI,tag1_type,tag2_type))
else:#other group
@ -373,7 +409,7 @@ class rds_parser_table_qt_Widget(QtGui.QWidget):
self.setLayout(layout)
self.table=QtGui.QTableWidget(self)
self.table.setRowCount(5)
self.table.setColumnCount(8)
self.table.setColumnCount(9)
self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers) #disallow editing
#Data
empty_text32='________________________________'
@ -382,14 +418,18 @@ class rds_parser_table_qt_Widget(QtGui.QWidget):
self.data = {'ID':range(1,6),
'freq':['','','',''],
'name':[ QtGui.QLabel() for i in range(4)],
'PTY':[ QtGui.QLabel() for i in range(4)],
#'flags':[ QtGui.QLabel() for i in range(4)],
'AF':['','','',''],
'time':[ QtGui.QLabel() for i in range(4)],
'text':[ QtGui.QLabel("_"*64) for i in range(4)],
'RT+':[ QtGui.QLabel() for i in range(4)],
#'RT+':[ QtGui.QLabel() for i in range(4)],
'quality':[ QtGui.QLabel() for i in range(4)],
'buttons':[]}
#Enter data onto Table
self.colorder=['ID','freq','name','PTY','AF','time','text','quality','buttons']
horHeaders = []
for n, key in enumerate(['ID','freq','name','AF','time','text','RT+','buttons']):
for n, key in enumerate(self.colorder):
#for n, key in enumerate(sorted(self.data.keys())):
horHeaders.append(key)
for m, item in enumerate(self.data[key]):
@ -407,31 +447,52 @@ class rds_parser_table_qt_Widget(QtGui.QWidget):
button.clicked.connect(self.onCLick)
#Add Header
self.table.setHorizontalHeaderLabels(horHeaders)
self.tmc_message_label=QtGui.QLabel("TMC messages:")
self.event_filter=QtGui.QLineEdit()#QPlainTextEdit ?
self.location_filter=QtGui.QLineEdit()
layout.addWidget(self.label)
layout.addWidget(self.table)
self.button = QtGui.QPushButton("i am a button")
layout.addWidget(self.button)
layout.addWidget(self.tmc_message_label)
layout.addWidget(self.event_filter)
layout.addWidget(self.location_filter)
def display_data(self, event):
#pp.pprint(event)
if type(event)==dict and event.has_key('row'):
if type(event)==dict and event.has_key('row'):
if event.has_key('wrong_blocks'):
item=self.table.cellWidget(event['row'],self.colorder.index('quality'))
quality_string="%i%% %s"% (100-2*event['wrong_blocks'],event['dots'])
item.setText(quality_string)
if event.has_key('PTY'):
item=self.table.cellWidget(event['row'],self.colorder.index('PTY'))
item.setText(event['PTY'])
if event.has_key('flags'):
item=self.table.cellWidget(event['row'],self.colorder.index('PTY'))
item.setToolTip(Qt.QString(event['flags']))
if event.has_key('string'):
item=self.table.cellWidget(event['row'],event['col'])
item.setText(event['string'])
if event.has_key('tooltip'):
item=self.table.cellWidget(event['row'],event['col'])
item.setToolTip(Qt.QString(event['tooltip']))
if event.has_key('PI'):
#setPI
PIcol=0
rtpcol=6
PIcol=self.colorder.index('ID')
#rtpcol=self.colorder.index('RT+')
rtcol=self.colorder.index('text')
if not self.table.item(event['row'],PIcol).text() == event['PI']:
self.table.cellWidget(event['row'],rtpcol).setText("")#clear RT+ on changed PI
#self.table.cellWidget(event['row'],rtpcol).setText("")#clear RT+ on changed PI
self.table.cellWidget(event['row'],rtcol).setToolTip(Qt.QString(""))
self.table.item(event['row'],PIcol).setText(event['PI'])
if event.has_key('AF'):
#setAF
PIcol=3
PIcol=self.colorder.index('AF')
self.table.item(event['row'],PIcol).setText(event['AF']['number'])
if event.has_key('PSN'):
#setPSN
PSNcol=2
PSNcol=self.colorder.index('name')
item=self.table.cellWidget(event['row'],PSNcol)
item.setText(event['PSN'])
self.table.resizeColumnsToContents()

4
swig/crfa_swig.i

@ -8,6 +8,8 @@
%include "crfa_swig_doc.i"
%{
#include "crfa/rds_decoder.h"
%}
%include "crfa/rds_decoder.h"
GR_SWIG_BLOCK_MAGIC2(crfa, rds_decoder);

Loading…
Cancel
Save