BFM demod: RDS GUI part #1: counters, PI and Group 0

This commit is contained in:
f4exb 2015-12-14 01:36:13 +01:00
parent 48855bfb63
commit 4d156044e4
5 changed files with 595 additions and 82 deletions

View File

@ -18,6 +18,11 @@
#include <QDockWidget>
#include <QMainWindow>
#include <QDebug>
#include "boost/format.hpp"
#include <sstream>
#include <iostream>
#include <iomanip>
#include "dsp/threadedsamplesink.h"
#include "dsp/channelizer.h"
#include "dsp/dspengine.h"
@ -30,7 +35,6 @@
#include "mainwindow.h"
#include "bfmdemodgui.h"
#include "ui_bfmdemodgui.h"
#include "bfmdemod.h"
@ -245,6 +249,7 @@ void BFMDemodGUI::on_showPilot_clicked()
void BFMDemodGUI::on_rds_clicked()
{
m_rdsParser.clearAllFields();
applySettings();
}
@ -268,6 +273,7 @@ BFMDemodGUI::BFMDemodGUI(PluginAPI* pluginAPI, QWidget* parent) :
m_pluginAPI(pluginAPI),
m_channelMarker(this),
m_basicSettingsShown(false),
m_rdsTimerCount(0),
m_channelPowerDbAvg(20,0),
m_rate(625000)
{
@ -388,6 +394,13 @@ void BFMDemodGUI::tick()
}
}
if (ui->rds->isChecked() && (m_rdsTimerCount == 0))
{
rdsUpdate();
}
m_rdsTimerCount = (m_rdsTimerCount + 1) % 25;
//qDebug() << "Pilot lock: " << m_bfmDemod->getPilotLock() << ":" << m_bfmDemod->getPilotLevel(); TODO: update a GUI item with status
}
@ -398,3 +411,75 @@ void BFMDemodGUI::channelSampleRateChanged()
ui->glSpectrum->setSampleRate(m_rate / 2);
}
void BFMDemodGUI::rdsUpdate()
{
// PI group
if (m_rdsParser.m_pi_updated)
{
ui->piLabel->setStyleSheet("QLabel { background-color : green; }");
ui->piCountText->setNum((int) m_rdsParser.m_pi_count);
QString pistring(str(boost::format("%04X") % m_rdsParser.m_pi_program_identification).c_str());
ui->piText->setText(pistring);
if (m_rdsParser.m_pi_traffic_program) {
ui->piTPIndicator->setStyleSheet("QLabel { background-color : green; }");
} else {
ui->piTPIndicator->setStyleSheet("QLabel { background:rgb(79,79,79); }");
}
ui->piType->setText(QString(m_rdsParser.pty_table[m_rdsParser.m_pi_program_type].c_str()));
ui->piCoverage->setText(QString(m_rdsParser.coverage_area_codes[m_rdsParser.m_pi_area_coverage_index].c_str()));
}
else
{
ui->piLabel->setStyleSheet("QLabel { background:rgb(79,79,79); }");
}
// G0 group
if (m_rdsParser.m_g0_updated)
{
ui->g00Label->setStyleSheet("QLabel { background-color : green; }");
ui->g00CountText->setNum((int) m_rdsParser.m_g0_count);
ui->g00ProgServiceName->setText(QString(m_rdsParser.m_g0_program_service_name));
if (m_rdsParser.m_g0_traffic_announcement) {
ui->g00TrafficAnnouncement->setStyleSheet("QLabel { background-color : green; }");
} else {
ui->g00TrafficAnnouncement->setStyleSheet("QLabel { background:rgb(79,79,79); }");
}
ui->g00MusicSpeech->setText(QString((m_rdsParser.m_g0_music_speech ? "Music" : "Speech")));
ui->g00MonoStereo->setText(QString((m_rdsParser.m_g0_mono_stereo ? "Mono" : "Stereo")));
if (m_rdsParser.m_g0_af_updated)
{
bool isFirst = true;
std::ostringstream os;
os << std::fixed << std::showpoint << std::setprecision(2);
for (std::set<double>::iterator it = m_rdsParser.m_g0_alt_freq.begin(); it != m_rdsParser.m_g0_alt_freq.end(); ++it)
{
if (*it > 76.0)
{
if (!isFirst)
{
os << ", ";
}
os << *it;
isFirst = false;
}
}
ui->g00AltFrequencies->setText(QString(os.str().c_str()));
}
}
else
{
ui->g00Label->setStyleSheet("QLabel { background:rgb(79,79,79); }");
}
// G1 group
m_rdsParser.clearUpdateFlags();
}

View File

@ -75,6 +75,7 @@ private:
ChannelMarker m_channelMarker;
bool m_basicSettingsShown;
bool m_doApplySettings;
int m_rdsTimerCount;
ThreadedSampleSink* m_threadedChannelizer;
Channelizer* m_channelizer;
@ -92,6 +93,7 @@ private:
void blockApplySettings(bool block);
void applySettings();
void rdsUpdate();
void leaveEvent(QEvent*);
void enterEvent(QEvent*);

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>335</width>
<height>333</height>
<width>446</width>
<height>729</height>
</rect>
</property>
<property name="windowTitle">
@ -399,7 +399,7 @@
<property name="geometry">
<rect>
<x>10</x>
<y>160</y>
<y>560</y>
<width>311</width>
<height>151</height>
</rect>
@ -431,6 +431,364 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="rdsContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>150</y>
<width>421</width>
<height>91</height>
</rect>
</property>
<property name="windowTitle">
<string>RDS data</string>
</property>
<layout class="QVBoxLayout" name="rdsLayout">
<property name="spacing">
<number>3</number>
</property>
<item>
<layout class="QGridLayout" name="countersLayout">
<item row="0" column="0">
<widget class="QLabel" name="piCountLabel">
<property name="text">
<string>PI</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="piCountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="g00CountLabel">
<property name="text">
<string>G00</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="g00CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="g03CountLabel">
<property name="text">
<string>G03</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="g01CountLabel">
<property name="text">
<string>G01</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="g01CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="g02CountLabel">
<property name="text">
<string>G02</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QLabel" name="g02CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="9">
<widget class="QLabel" name="g08CountLabel">
<property name="text">
<string>G08</string>
</property>
</widget>
</item>
<item row="0" column="8">
<widget class="QLabel" name="g07CountLabel">
<property name="text">
<string>G07</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QLabel" name="g03CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QLabel" name="g04CountLabel">
<property name="text">
<string>G04</string>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QLabel" name="g04CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="6">
<widget class="QLabel" name="g05CountLabel">
<property name="text">
<string>G05</string>
</property>
</widget>
</item>
<item row="1" column="6">
<widget class="QLabel" name="g05CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QLabel" name="g06CountLabel">
<property name="text">
<string>G06</string>
</property>
</widget>
</item>
<item row="1" column="7">
<widget class="QLabel" name="g06CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="1" column="8">
<widget class="QLabel" name="g07CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="1" column="9">
<widget class="QLabel" name="g08CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="10">
<widget class="QLabel" name="g09CountLabel">
<property name="text">
<string>G09</string>
</property>
</widget>
</item>
<item row="1" column="10">
<widget class="QLabel" name="g09CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="0" column="11">
<widget class="QLabel" name="g14CountLabel">
<property name="text">
<string>G14</string>
</property>
</widget>
</item>
<item row="1" column="11">
<widget class="QLabel" name="g14CountText">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="countSeparator">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="piDataLayout">
<item>
<widget class="QLabel" name="piLabel">
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>PI</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="piSeparator1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="piText">
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>0000</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="piSeparator2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="piTPIndicator">
<property name="maximumSize">
<size>
<width>20</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>TP</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="piType">
<property name="text">
<string>None</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="piCoverage">
<property name="text">
<string>Local</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="g00DataLayout">
<item>
<widget class="QLabel" name="g00Label">
<property name="maximumSize">
<size>
<width>30</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>G00</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="g00Separator1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="g00ProgServiceName">
<property name="maximumSize">
<size>
<width>80</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>........</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="g00Separator2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="g00TrafficAnnouncement">
<property name="maximumSize">
<size>
<width>20</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>TA</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="g00MusicSpeech">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Music</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="g00MonoStereo">
<property name="maximumSize">
<size>
<width>50</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Mono</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="g00AltFrequencies">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>

View File

@ -212,14 +212,47 @@ const std::string RDSParser::label_descriptions[16] = {
RDSParser::RDSParser()
{
std::memset(radiotext, ' ', sizeof(radiotext));
radiotext[sizeof(radiotext) - 1] = '\0';
clearAllFields();
}
RDSParser::~RDSParser()
{
}
void RDSParser::clearUpdateFlags()
{
m_pi_updated = false;
m_g0_updated = false;
m_g0_af_updated = false;
}
void RDSParser::clearAllFields()
{
// PI data
m_pi_count = 0;
m_pi_program_identification = 0;
m_pi_program_type = 0;
m_pi_traffic_program = false;
// Group 00 data
m_g0_count = 0;
std::memset(m_g0_program_service_name, ' ', sizeof(m_g0_program_service_name));
radiotext[sizeof(m_g0_program_service_name) - 1] = '\0';
m_g0_traffic_announcement = false;
m_g0_music_speech = false;
m_g0_mono_stereo = false;
m_g0_artificial_head = false;
m_g0_compressed = false;
m_g0_static_pty = false;
m_g0_alt_freq.clear();
// Group 02 data
std::memset(radiotext, ' ', sizeof(radiotext));
radiotext[sizeof(radiotext) - 1] = '\0';
clearUpdateFlags();
}
void RDSParser::parseGroup(unsigned int *group)
{
unsigned int group_type = (unsigned int)((group[1] >> 12) & 0xf);
@ -229,26 +262,29 @@ void RDSParser::parseGroup(unsigned int *group)
<< " type: " << group_type << (ab ? 'B' :'A')
<< " (" << rds_group_acronyms[group_type].c_str() << ")";
program_identification = group[0]; // "PI"
program_type = (group[1] >> 5) & 0x1f; // "PTY"
int pi_country_identification = (program_identification >> 12) & 0xf;
int pi_area_coverage = (program_identification >> 8) & 0xf;
unsigned char pi_program_reference_number = program_identification & 0xff;
m_pi_count++;
m_pi_updated = true;
std::string pistring = str(boost::format("%04X") % program_identification);
//send_message(0, pistring);
//send_message(2, pty_table[program_type]);
m_pi_program_identification = group[0]; // "PI"
m_pi_traffic_program = (group[1] >> 10) & 0x01; // "TP"
m_pi_program_type = (group[1] >> 5) & 0x1f; // "PTY"
m_pi_country_identification = (m_pi_program_identification >> 12) & 0xf;
m_pi_area_coverage_index = (m_pi_program_identification >> 8) & 0xf;
unsigned char pi_program_reference_number = m_pi_program_identification & 0xff;
/*
std::string pistring = str(boost::format("%04X") % m_pi_program_identification);
qDebug() << "RDSParser::parseGroup:"
<< " PI:" << pistring.c_str()
<< " - " << "PTY:" << pty_table[program_type].c_str()
<< " (country:" << (pi_country_codes[pi_country_identification - 1][0]).c_str()
<< "/" << (pi_country_codes[pi_country_identification - 1][1]).c_str()
<< "/" << (pi_country_codes[pi_country_identification - 1][2]).c_str()
<< "/" << (pi_country_codes[pi_country_identification - 1][3]).c_str()
<< "/" << (pi_country_codes[pi_country_identification - 1][4]).c_str()
<< ", area:" << coverage_area_codes[pi_area_coverage].c_str()
<< ", program:" << int(pi_program_reference_number) << ")";
<< " - " << "PTY:" << pty_table[m_pi_program_type].c_str()
<< " (country:" << (pi_country_codes[m_pi_country_identification - 1][0]).c_str()
<< "/" << (pi_country_codes[m_pi_country_identification - 1][1]).c_str()
<< "/" << (pi_country_codes[m_pi_country_identification - 1][2]).c_str()
<< "/" << (pi_country_codes[m_pi_country_identification - 1][3]).c_str()
<< "/" << (pi_country_codes[m_pi_country_identification - 1][4]).c_str()
<< ", area:" << coverage_area_codes[m_pi_area_coverage_index].c_str()
<< ", program:" << int(pi_program_reference_number) << ")";*/
switch (group_type) {
case 0:
@ -319,40 +355,47 @@ void RDSParser::decode_type0(unsigned int *group, bool B)
double af_2 = 0;
char flagstring[8] = "0000000";
traffic_program = (group[1] >> 10) & 0x01; // "TP"
traffic_announcement = (group[1] >> 4) & 0x01; // "TA"
music_speech = (group[1] >> 3) & 0x01; // "MuSp"
m_g0_count++;
m_g0_updated = true;
m_pi_traffic_program = (group[1] >> 10) & 0x01; // "TP"
m_g0_traffic_announcement = (group[1] >> 4) & 0x01; // "TA"
m_g0_music_speech = (group[1] >> 3) & 0x01; // "MuSp"
bool decoder_control_bit = (group[1] >> 2) & 0x01; // "DI"
unsigned char segment_address = group[1] & 0x03; // "DI segment"
program_service_name[segment_address * 2] = (group[3] >> 8) & 0xff;
program_service_name[segment_address * 2 + 1] = group[3] & 0xff;
m_g0_program_service_name[segment_address * 2] = (group[3] >> 8) & 0xff;
m_g0_program_service_name[segment_address * 2 + 1] = group[3] & 0xff;
/* see page 41, table 9 of the standard */
switch (segment_address) {
switch (segment_address)
{
case 0:
mono_stereo=decoder_control_bit;
break;
m_g0_mono_stereo = decoder_control_bit;
break;
case 1:
artificial_head=decoder_control_bit;
break;
m_g0_artificial_head = decoder_control_bit;
break;
case 2:
compressed=decoder_control_bit;
break;
m_g0_compressed = decoder_control_bit;
break;
case 3:
static_pty=decoder_control_bit;
break;
m_g0_static_pty = decoder_control_bit;
break;
default:
break;
break;
}
flagstring[0] = traffic_program ? '1' : '0';
flagstring[1] = traffic_announcement ? '1' : '0';
flagstring[2] = music_speech ? '1' : '0';
flagstring[3] = mono_stereo ? '1' : '0';
flagstring[4] = artificial_head ? '1' : '0';
flagstring[5] = compressed ? '1' : '0';
flagstring[6] = static_pty ? '1' : '0';
/* unused
flagstring[0] = m_pi_traffic_program ? '1' : '0';
flagstring[1] = m_g0_traffic_announcement ? '1' : '0';
flagstring[2] = m_g0_music_speech ? '1' : '0';
flagstring[3] = m_g0_mono_stereo ? '1' : '0';
flagstring[4] = m_g0_artificial_head ? '1' : '0';
flagstring[5] = m_g0_compressed ? '1' : '0';
flagstring[6] = m_g0_static_pty ? '1' : '0';*/
static std::string af_string;
if (!B)
@ -362,17 +405,25 @@ void RDSParser::decode_type0(unsigned int *group, bool B)
af_1 = decode_af(af_code_1);
af_2 = decode_af(af_code_2);
if(af_1) {
if (af_1)
{
std::pair<std::_Rb_tree_const_iterator<double>, bool> res = m_g0_alt_freq.insert(af_1/1e3);
m_g0_af_updated = m_g0_af_updated || res.second;
no_af += 1;
}
if(af_2) {
if (af_2)
{
std::pair<std::_Rb_tree_const_iterator<double>, bool> res = m_g0_alt_freq.insert(af_2/1e3);
m_g0_af_updated = m_g0_af_updated || res.second;
no_af += 2;
}
/*
std::string af1_string;
std::string af2_string;
/* only AF1 => no_af==1, only AF2 => no_af==2, both AF1 and AF2 => no_af==3 */
// only AF1 => no_af==1, only AF2 => no_af==2, both AF1 and AF2 => no_af==3
if(no_af)
{
if(af_1 > 80e3) {
@ -394,20 +445,17 @@ void RDSParser::decode_type0(unsigned int *group, bool B)
af_string = af2_string;
} else if(no_af == 3) {
af_string = str(boost::format("%s, %s") % af1_string %af2_string);
}
}*/
}
/*
qDebug() << "RDSParser::decode_type0: "
<< "\"" << std::string(program_service_name, 8).c_str()
<< "\" -" << (traffic_program ? "TP" : "!TP")
<< '-' << (traffic_announcement ? "TA" : "!TA")
<< '-' << (music_speech ? "Music" : "Speech")
<< '-' << (mono_stereo ? "MONO" : "STEREO")
<< " - AF:" << af_string.c_str();
//send_message(1, std::string(program_service_name, 8));
//send_message(3, flagstring);
//send_message(6, af_string);
<< "\"" << std::string(m_g0_program_service_name, 8).c_str()
<< "\" -" << (m_pi_traffic_program ? "TP" : "!TP")
<< '-' << (m_g0_traffic_announcement ? "TA" : "!TA")
<< '-' << (m_g0_music_speech ? "Music" : "Speech")
<< '-' << (m_g0_mono_stereo ? "MONO" : "STEREO")
<< " - AF:" << af_string.c_str();*/
}
double RDSParser::decode_af(unsigned int af_code)

View File

@ -18,6 +18,9 @@
#ifndef PLUGINS_CHANNEL_BFM_RDSPARSER_H_
#define PLUGINS_CHANNEL_BFM_RDSPARSER_H_
#include <string>
#include <set>
class RDSParser
{
public:
@ -29,6 +32,45 @@ public:
*/
void parseGroup(unsigned int *group);
void clearAllFields();
void clearUpdateFlags();
// PI data
bool m_pi_updated;
unsigned int m_pi_count;
unsigned int m_pi_program_identification;
unsigned char m_pi_program_type;
bool m_pi_traffic_program;
int m_pi_country_identification;
int m_pi_area_coverage_index;
// G0 data
bool m_g0_updated;
bool m_g0_af_updated;
unsigned int m_g0_count;
char m_g0_program_service_name[9];
bool m_g0_traffic_announcement;
bool m_g0_music_speech;
bool m_g0_mono_stereo;
bool m_g0_artificial_head;
bool m_g0_compressed;
bool m_g0_static_pty;
std::set<double> m_g0_alt_freq;
// Static tables
static const unsigned int offset_pos[5];
static const unsigned int offset_word[5];
static const unsigned int syndrome[5];
static const char * const offset_name[];
static const std::string pty_table[32];
static const std::string pi_country_codes[15][5];
static const std::string coverage_area_codes[16];
static const std::string rds_group_acronyms[16];
static const std::string language_codes[44];
static const std::string tmc_duration[8][2];
static const int optional_content_lengths[16];
static const std::string label_descriptions[16];
private:
double decode_af(unsigned int);
void decode_optional_content(int, unsigned long int *);
@ -49,36 +91,14 @@ private:
void decode_type14(unsigned int* group, bool B);
void decode_type15(unsigned int* group, bool B);
unsigned int program_identification;
unsigned char program_type;
unsigned char pi_country_identification;
unsigned char pi_area_coverage;
unsigned char pi_program_reference_number;
char radiotext[65+1];
char program_service_name[9];
char radiotext[64+1];
bool radiotext_AB_flag;
bool traffic_program;
bool traffic_announcement;
bool music_speech;
bool mono_stereo;
bool artificial_head;
bool compressed;
bool static_pty;
bool debug;
bool log;
static const unsigned int offset_pos[5];
static const unsigned int offset_word[5];
static const unsigned int syndrome[5];
static const char * const offset_name[];
static const std::string pty_table[32];
static const std::string pi_country_codes[15][5];
static const std::string coverage_area_codes[16];
static const std::string rds_group_acronyms[16];
static const std::string language_codes[44];
static const std::string tmc_duration[8][2];
static const int optional_content_lengths[16];
static const std::string label_descriptions[16];
};