1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 09:18:54 -05:00

Merge pull request #2276 from srcejon/pager_notifications

Pager notifications
This commit is contained in:
Edouard Griffiths 2024-10-12 08:32:21 +02:00 committed by GitHub
commit f169cb5159
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 1274 additions and 141 deletions

View File

@ -80,6 +80,15 @@
"cacheVariables": { "cacheVariables": {
"ENABLE_QT6": "ON" "ENABLE_QT6": "ON"
} }
},
{
"name": "default-qt6-windows",
"inherits": "default-windows",
"binaryDir": "${sourceDir}/build-qt6",
"cacheVariables": {
"ENABLE_QT6": "ON",
"CMAKE_PREFIX_PATH": "C:/Qt/6.7.3/msvc2022_64;C:/Applications/boost_1_81_0"
}
} }
], ],
"buildPresets": [ "buildPresets": [
@ -94,6 +103,10 @@
{ {
"name": "default-qt6", "name": "default-qt6",
"configurePreset": "default-qt6" "configurePreset": "default-qt6"
},
{
"name": "default-qt6-windows",
"configurePreset": "default-qt6-windows"
} }
] ]
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -29,16 +29,25 @@ if(NOT SERVER_MODE)
pagerdemodgui.ui pagerdemodgui.ui
pagerdemodcharsetdialog.cpp pagerdemodcharsetdialog.cpp
pagerdemodcharsetdialog.ui pagerdemodcharsetdialog.ui
pagerdemodnotificationdialog.cpp
pagerdemodnotificationdialog.ui
pagerdemodfilterdialog.cpp
pagerdemodfilterdialog.ui
pagerdemodicons.qrc
) )
set(demodpager_HEADERS set(demodpager_HEADERS
${demodpager_HEADERS} ${demodpager_HEADERS}
pagerdemodgui.h pagerdemodgui.h
pagerdemodcharsetdialog.h pagerdemodcharsetdialog.h
pagerdemodnotificationdialog.h
) )
set(TARGET_NAME ${PLUGINS_PREFIX}demodpager) set(TARGET_NAME ${PLUGINS_PREFIX}demodpager)
set(TARGET_LIB "Qt::Widgets") set(TARGET_LIB "Qt::Widgets")
set(TARGET_LIB_GUI "sdrgui") set(TARGET_LIB_GUI "sdrgui")
if(Qt${QT_DEFAULT_MAJOR_VERSION}TextToSpeech_FOUND)
list(APPEND TARGET_LIB_GUI Qt::TextToSpeech)
endif()
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR}) set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else() else()
set(TARGET_NAME ${PLUGINSSRV_PREFIX}demodpagersrv) set(TARGET_NAME ${PLUGINSSRV_PREFIX}demodpagersrv)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -31,6 +31,7 @@
#include "dsp/dspcommands.h" #include "dsp/dspcommands.h"
#include "device/deviceapi.h" #include "device/deviceapi.h"
#include "util/db.h" #include "util/db.h"
#include "util/csv.h"
#include "maincore.h" #include "maincore.h"
MESSAGE_CLASS_DEFINITION(PagerDemod::MsgConfigurePagerDemod, Message) MESSAGE_CLASS_DEFINITION(PagerDemod::MsgConfigurePagerDemod, Message)
@ -141,7 +142,7 @@ bool PagerDemod::handleMessage(const Message& cmd)
{ {
if (MsgConfigurePagerDemod::match(cmd)) if (MsgConfigurePagerDemod::match(cmd))
{ {
MsgConfigurePagerDemod& cfg = (MsgConfigurePagerDemod&) cmd; const MsgConfigurePagerDemod& cfg = (const MsgConfigurePagerDemod&) cmd;
qDebug() << "PagerDemod::handleMessage: MsgConfigurePagerDemod"; qDebug() << "PagerDemod::handleMessage: MsgConfigurePagerDemod";
applySettings(cfg.getSettings(), cfg.getForce()); applySettings(cfg.getSettings(), cfg.getForce());
@ -149,7 +150,7 @@ bool PagerDemod::handleMessage(const Message& cmd)
} }
else if (DSPSignalNotification::match(cmd)) else if (DSPSignalNotification::match(cmd))
{ {
DSPSignalNotification& notif = (DSPSignalNotification&) cmd; const DSPSignalNotification& notif = (const DSPSignalNotification&) cmd;
m_basebandSampleRate = notif.getSampleRate(); m_basebandSampleRate = notif.getSampleRate();
m_centerFrequency = notif.getCenterFrequency(); m_centerFrequency = notif.getCenterFrequency();
// Forward to the sink // Forward to the sink
@ -166,7 +167,7 @@ bool PagerDemod::handleMessage(const Message& cmd)
else if (MsgPagerMessage::match(cmd)) else if (MsgPagerMessage::match(cmd))
{ {
// Forward to GUI // Forward to GUI
MsgPagerMessage& report = (MsgPagerMessage&)cmd; const MsgPagerMessage& report = (const MsgPagerMessage&)cmd;
if (getMessageQueueToGUI()) if (getMessageQueueToGUI())
{ {
MsgPagerMessage *msg = new MsgPagerMessage(report); MsgPagerMessage *msg = new MsgPagerMessage(report);
@ -200,7 +201,7 @@ bool PagerDemod::handleMessage(const Message& cmd)
<< report.getDateTime().time().toString() << "," << report.getDateTime().time().toString() << ","
<< QString("%1").arg(report.getAddress(), 7, 10, QChar('0')) << "," << QString("%1").arg(report.getAddress(), 7, 10, QChar('0')) << ","
<< QString::number(report.getFunctionBits()) << "," << QString::number(report.getFunctionBits()) << ","
<< "\"" << report.getAlphaMessage() << "\"," << CSV::escape(report.getAlphaMessage()) << ","
<< report.getNumericMessage() << "," << report.getNumericMessage() << ","
<< QString::number(report.getEvenParityErrors()) << "," << QString::number(report.getEvenParityErrors()) << ","
<< QString::number(report.getBCHParityErrors()) << "\n"; << QString::number(report.getBCHParityErrors()) << "\n";

View File

@ -131,7 +131,7 @@ bool PagerDemodBaseband::handleMessage(const Message& cmd)
if (MsgConfigurePagerDemodBaseband::match(cmd)) if (MsgConfigurePagerDemodBaseband::match(cmd))
{ {
QMutexLocker mutexLocker(&m_mutex); QMutexLocker mutexLocker(&m_mutex);
MsgConfigurePagerDemodBaseband& cfg = (MsgConfigurePagerDemodBaseband&) cmd; const MsgConfigurePagerDemodBaseband& cfg = (const MsgConfigurePagerDemodBaseband&) cmd;
qDebug() << "PagerDemodBaseband::handleMessage: MsgConfigurePagerDemodBaseband"; qDebug() << "PagerDemodBaseband::handleMessage: MsgConfigurePagerDemodBaseband";
applySettings(cfg.getSettings(), cfg.getForce()); applySettings(cfg.getSettings(), cfg.getForce());
@ -141,7 +141,7 @@ bool PagerDemodBaseband::handleMessage(const Message& cmd)
else if (DSPSignalNotification::match(cmd)) else if (DSPSignalNotification::match(cmd))
{ {
QMutexLocker mutexLocker(&m_mutex); QMutexLocker mutexLocker(&m_mutex);
DSPSignalNotification& notif = (DSPSignalNotification&) cmd; const DSPSignalNotification& notif = (const DSPSignalNotification&) cmd;
qDebug() << "PagerDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate(); qDebug() << "PagerDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
setBasebandSampleRate(notif.getSampleRate()); setBasebandSampleRate(notif.getSampleRate());
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate())); m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));

View File

@ -0,0 +1,46 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QCheckBox>
#include "pagerdemodfilterdialog.h"
PagerDemodFilterDialog::PagerDemodFilterDialog(PagerDemodSettings *settings,
QWidget* parent) :
QDialog(parent),
ui(new Ui::PagerDemodFilterDialog),
m_settings(settings)
{
ui->setupUi(this);
ui->matchLastOnly->setChecked(m_settings->m_duplicateMatchLastOnly);
ui->matchMessageOnly->setChecked(m_settings->m_duplicateMatchMessageOnly);
}
PagerDemodFilterDialog::~PagerDemodFilterDialog()
{
delete ui;
}
void PagerDemodFilterDialog::accept()
{
m_settings->m_duplicateMatchLastOnly = ui->matchLastOnly->isChecked();
m_settings->m_duplicateMatchMessageOnly = ui->matchMessageOnly->isChecked();
QDialog::accept();
}

View File

@ -0,0 +1,41 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMODFILTERDIALOG_H
#define INCLUDE_PAGERDEMODFILTERDIALOG_H
#include "ui_pagerdemodfilterdialog.h"
#include "pagerdemodsettings.h"
class PagerDemodFilterDialog : public QDialog {
Q_OBJECT
public:
explicit PagerDemodFilterDialog(PagerDemodSettings* settings, QWidget* parent = nullptr);
~PagerDemodFilterDialog();
private slots:
void accept() override;
private:
Ui::PagerDemodFilterDialog* ui;
PagerDemodSettings *m_settings;
};
#endif // INCLUDE_PAGERDEMODFILTERDIALOG_H

View File

@ -0,0 +1,118 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PagerDemodFilterDialog</class>
<widget class="QDialog" name="PagerDemodFilterDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>396</width>
<height>167</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::PreventContextMenu</enum>
</property>
<property name="windowTitle">
<string>Duplicate Filtering</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Duplicate Filtering</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="matchMessageOnlyLabel">
<property name="text">
<string>Match message only</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="matchMessageOnly">
<property name="toolTip">
<string>Whether both the address and message must match or only the message to be considered a duplicate</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="matchLastOnlyLabel">
<property name="text">
<string>Match last message only</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="matchLastOnly">
<property name="toolTip">
<string>Whether to match with only the last message or any message in the table</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PagerDemodFilterDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PagerDemodFilterDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -16,8 +16,6 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>. // // along with this program. If not, see <http://www.gnu.org/licenses/>. //
/////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////
#include <limits>
#include <ctype.h>
#include <QDockWidget> #include <QDockWidget>
#include <QDebug> #include <QDebug>
#include <QAction> #include <QAction>
@ -26,6 +24,7 @@
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QScrollBar> #include <QScrollBar>
#include <QProcess>
#include "pagerdemodgui.h" #include "pagerdemodgui.h"
@ -36,6 +35,8 @@
#include "plugin/pluginapi.h" #include "plugin/pluginapi.h"
#include "util/db.h" #include "util/db.h"
#include "util/csv.h" #include "util/csv.h"
#include "util/units.h"
#include "gui/crightclickenabler.h"
#include "gui/basicchannelsettingsdialog.h" #include "gui/basicchannelsettingsdialog.h"
#include "dsp/dspengine.h" #include "dsp/dspengine.h"
#include "gui/dialogpositioner.h" #include "gui/dialogpositioner.h"
@ -43,6 +44,10 @@
#include "pagerdemod.h" #include "pagerdemod.h"
#include "pagerdemodcharsetdialog.h" #include "pagerdemodcharsetdialog.h"
#include "pagerdemodnotificationdialog.h"
#include "pagerdemodfilterdialog.h"
#include "SWGMapItem.h"
void PagerDemodGUI::resizeTable() void PagerDemodGUI::resizeTable()
{ {
@ -50,15 +55,15 @@ void PagerDemodGUI::resizeTable()
// Trailing spaces are for sort arrow // Trailing spaces are for sort arrow
int row = ui->messages->rowCount(); int row = ui->messages->rowCount();
ui->messages->setRowCount(row + 1); ui->messages->setRowCount(row + 1);
ui->messages->setItem(row, MESSAGE_COL_DATE, new QTableWidgetItem("Fri Apr 15 2016-")); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_DATE, new QTableWidgetItem("Fri Apr 15 2016--"));
ui->messages->setItem(row, MESSAGE_COL_TIME, new QTableWidgetItem("10:17:00")); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_TIME, new QTableWidgetItem("10:17:00"));
ui->messages->setItem(row, MESSAGE_COL_ADDRESS, new QTableWidgetItem("1000000")); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_ADDRESS, new QTableWidgetItem("1000000"));
ui->messages->setItem(row, MESSAGE_COL_MESSAGE, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ")); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_MESSAGE, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ"));
ui->messages->setItem(row, MESSAGE_COL_FUNCTION, new QTableWidgetItem("0")); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_FUNCTION, new QTableWidgetItem("0"));
ui->messages->setItem(row, MESSAGE_COL_ALPHA, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ")); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_ALPHA, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZABCEDGHIJKLMNOPQRSTUVWXYZ"));
ui->messages->setItem(row, MESSAGE_COL_NUMERIC, new QTableWidgetItem("123456789123456789123456789123456789123456789123456789")); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_NUMERIC, new QTableWidgetItem("123456789123456789123456789123456789123456789123456789"));
ui->messages->setItem(row, MESSAGE_COL_EVEN_PE, new QTableWidgetItem("0")); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_EVEN_PE, new QTableWidgetItem("0"));
ui->messages->setItem(row, MESSAGE_COL_BCH_PE, new QTableWidgetItem("0")); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_BCH_PE, new QTableWidgetItem("0"));
ui->messages->resizeColumnsToContents(); ui->messages->resizeColumnsToContents();
ui->messages->removeRow(row); ui->messages->removeRow(row);
} }
@ -144,11 +149,101 @@ bool PagerDemodGUI::deserialize(const QByteArray& data)
} }
} }
QString PagerDemodGUI::selectMessage(int functionBits, const QString &numericMessage, const QString &alphaMessage) const
{
QString message;
// Standard way of choosing numeric or alpha decode isn't followed widely
if (m_settings.m_decode == PagerDemodSettings::Standard)
{
// Encoding is based on function bits
if (functionBits == 0) {
message = numericMessage;
} else {
message = alphaMessage;
}
}
else if (m_settings.m_decode == PagerDemodSettings::Inverted)
{
// Encoding is based on function bits, but inverted from standard
if (functionBits == 3) {
message = numericMessage;
} else {
message = alphaMessage;
}
}
else if (m_settings.m_decode == PagerDemodSettings::Numeric)
{
// Always display as numeric
message = numericMessage;
}
else if (m_settings.m_decode == PagerDemodSettings::Alphanumeric)
{
// Always display as alphanumeric
message = alphaMessage;
}
else
{
// Guess at what the encoding is
QString numeric = numericMessage;
QString alpha = alphaMessage;
bool done = false;
if (!done)
{
// If alpha contains control characters, possibly numeric
for (int i = 0; i < alpha.size(); i++)
{
if (iscntrl(alpha[i].toLatin1()) && !isspace(alpha[i].toLatin1()))
{
message = numeric;
done = true;
break;
}
}
}
if (!done) {
// Possibly not likely to get only longer than 15 digits
if (numeric.size() > 15)
{
done = true;
message = alpha;
}
}
if (!done) {
// Default to alpha
message = alpha;
}
}
return message;
}
// Add row to table // Add row to table
void PagerDemodGUI::messageReceived(const QDateTime dateTime, int address, int functionBits, void PagerDemodGUI::messageReceived(const QDateTime dateTime, int address, int functionBits,
const QString &numericMessage, const QString &alphaMessage, const QString &numericMessage, const QString &alphaMessage,
int evenParityErrors, int bchParityErrors) int evenParityErrors, int bchParityErrors)
{ {
QString message = selectMessage(functionBits, numericMessage, alphaMessage);
QString addressString = QString("%1").arg(address, 7, 10, QChar('0'));
// Should we ignore the message if it is a duplicate?
if (m_settings.m_filterDuplicates && (ui->messages->rowCount() > 0))
{
int startRow = m_settings.m_duplicateMatchLastOnly ? ui->messages->rowCount() - 1 : 0;
for (int row = startRow; row < ui->messages->rowCount(); row++)
{
QString prevAddress = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_ADDRESS)->text();
QString prevMessage = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_MESSAGE)->text();
if ((message == prevMessage) && (m_settings.m_duplicateMatchMessageOnly || (addressString == prevAddress)))
{
// Ignore this message
return;
}
}
}
// Is scroll bar at bottom // Is scroll bar at bottom
QScrollBar *sb = ui->messages->verticalScrollBar(); QScrollBar *sb = ui->messages->verticalScrollBar();
bool scrollToBottom = sb->value() == sb->maximum(); bool scrollToBottom = sb->value() == sb->maximum();
@ -167,79 +262,19 @@ void PagerDemodGUI::messageReceived(const QDateTime dateTime, int address, int f
QTableWidgetItem *numericItem = new QTableWidgetItem(); QTableWidgetItem *numericItem = new QTableWidgetItem();
QTableWidgetItem *evenPEItem = new QTableWidgetItem(); QTableWidgetItem *evenPEItem = new QTableWidgetItem();
QTableWidgetItem *bchPEItem = new QTableWidgetItem(); QTableWidgetItem *bchPEItem = new QTableWidgetItem();
ui->messages->setItem(row, MESSAGE_COL_DATE, dateItem); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_DATE, dateItem);
ui->messages->setItem(row, MESSAGE_COL_TIME, timeItem); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_TIME, timeItem);
ui->messages->setItem(row, MESSAGE_COL_ADDRESS, addressItem); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_ADDRESS, addressItem);
ui->messages->setItem(row, MESSAGE_COL_MESSAGE, messageItem); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_MESSAGE, messageItem);
ui->messages->setItem(row, MESSAGE_COL_FUNCTION, functionItem); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_FUNCTION, functionItem);
ui->messages->setItem(row, MESSAGE_COL_ALPHA, alphaItem); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_ALPHA, alphaItem);
ui->messages->setItem(row, MESSAGE_COL_NUMERIC, numericItem); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_NUMERIC, numericItem);
ui->messages->setItem(row, MESSAGE_COL_EVEN_PE, evenPEItem); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_EVEN_PE, evenPEItem);
ui->messages->setItem(row, MESSAGE_COL_BCH_PE, bchPEItem); ui->messages->setItem(row, PagerDemodSettings::MESSAGE_COL_BCH_PE, bchPEItem);
dateItem->setText(dateTime.date().toString()); dateItem->setText(dateTime.date().toString());
timeItem->setText(dateTime.time().toString()); timeItem->setText(dateTime.time().toString());
addressItem->setText(QString("%1").arg(address, 7, 10, QChar('0'))); addressItem->setText(addressString);
// Standard way of choosing numeric or alpha decode isn't followed widely messageItem->setText(message);
if (m_settings.m_decode == PagerDemodSettings::Standard)
{
// Encoding is based on function bits
if (functionBits == 0) {
messageItem->setText(numericMessage);
} else {
messageItem->setText(alphaMessage);
}
}
else if (m_settings.m_decode == PagerDemodSettings::Inverted)
{
// Encoding is based on function bits, but inverted from standard
if (functionBits == 3) {
messageItem->setText(numericMessage);
} else {
messageItem->setText(alphaMessage);
}
}
else if (m_settings.m_decode == PagerDemodSettings::Numeric)
{
// Always display as numeric
messageItem->setText(numericMessage);
}
else if (m_settings.m_decode == PagerDemodSettings::Alphanumeric)
{
// Always display as alphanumeric
messageItem->setText(alphaMessage);
}
else
{
// Guess at what the encoding is
QString numeric = numericMessage;
QString alpha = alphaMessage;
bool done = false;
if (!done)
{
// If alpha contains control characters, possibly numeric
for (int i = 0; i < alpha.size(); i++)
{
if (iscntrl(alpha[i].toLatin1()) && !isspace(alpha[i].toLatin1()))
{
messageItem->setText(numeric);
done = true;
break;
}
}
}
if (!done) {
// Possibly not likely to get only longer than 15 digits
if (numeric.size() > 15)
{
done = true;
messageItem->setText(alpha);
}
}
if (!done) {
// Default to alpha
messageItem->setText(alpha);
}
}
functionItem->setText(QString("%1").arg(functionBits)); functionItem->setText(QString("%1").arg(functionBits));
alphaItem->setText(alphaMessage); alphaItem->setText(alphaMessage);
numericItem->setText(numericMessage); numericItem->setText(numericMessage);
@ -250,6 +285,7 @@ void PagerDemodGUI::messageReceived(const QDateTime dateTime, int address, int f
if (scrollToBottom) { if (scrollToBottom) {
ui->messages->scrollToBottom(); ui->messages->scrollToBottom();
} }
checkNotification(row);
} }
bool PagerDemodGUI::handleMessage(const Message& message) bool PagerDemodGUI::handleMessage(const Message& message)
@ -257,7 +293,7 @@ bool PagerDemodGUI::handleMessage(const Message& message)
if (PagerDemod::MsgConfigurePagerDemod::match(message)) if (PagerDemod::MsgConfigurePagerDemod::match(message))
{ {
qDebug("PagerDemodGUI::handleMessage: PagerDemod::MsgConfigurePagerDemod"); qDebug("PagerDemodGUI::handleMessage: PagerDemod::MsgConfigurePagerDemod");
const PagerDemod::MsgConfigurePagerDemod& cfg = (PagerDemod::MsgConfigurePagerDemod&) message; const PagerDemod::MsgConfigurePagerDemod& cfg = (const PagerDemod::MsgConfigurePagerDemod&) message;
m_settings = cfg.getSettings(); m_settings = cfg.getSettings();
blockApplySettings(true); blockApplySettings(true);
ui->scopeGUI->updateSettings(); ui->scopeGUI->updateSettings();
@ -268,7 +304,7 @@ bool PagerDemodGUI::handleMessage(const Message& message)
} }
else if (PagerDemod::MsgPagerMessage::match(message)) else if (PagerDemod::MsgPagerMessage::match(message))
{ {
PagerDemod::MsgPagerMessage& report = (PagerDemod::MsgPagerMessage&) message; const PagerDemod::MsgPagerMessage& report = (const PagerDemod::MsgPagerMessage&) message;
messageReceived(report.getDateTime(), report.getAddress(), report.getFunctionBits(), messageReceived(report.getDateTime(), report.getAddress(), report.getFunctionBits(),
report.getNumericMessage(), report.getAlphaMessage(), report.getNumericMessage(), report.getAlphaMessage(),
report.getEvenParityErrors(), report.getBCHParityErrors()); report.getEvenParityErrors(), report.getBCHParityErrors());
@ -276,7 +312,7 @@ bool PagerDemodGUI::handleMessage(const Message& message)
} }
else if (DSPSignalNotification::match(message)) else if (DSPSignalNotification::match(message))
{ {
DSPSignalNotification& notif = (DSPSignalNotification&) message; const DSPSignalNotification& notif = (const DSPSignalNotification&) message;
m_deviceCenterFrequency = notif.getCenterFrequency(); m_deviceCenterFrequency = notif.getCenterFrequency();
m_basebandSampleRate = notif.getSampleRate(); m_basebandSampleRate = notif.getSampleRate();
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2); ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
@ -398,7 +434,7 @@ void PagerDemodGUI::filterRow(int row)
if (m_settings.m_filterAddress != "") if (m_settings.m_filterAddress != "")
{ {
QRegExp re(m_settings.m_filterAddress); QRegExp re(m_settings.m_filterAddress);
QTableWidgetItem *fromItem = ui->messages->item(row, MESSAGE_COL_ADDRESS); QTableWidgetItem *fromItem = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_ADDRESS);
if (!re.exactMatch(fromItem->text())) { if (!re.exactMatch(fromItem->text())) {
hidden = true; hidden = true;
} }
@ -479,7 +515,8 @@ PagerDemodGUI::PagerDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas
m_deviceCenterFrequency(0), m_deviceCenterFrequency(0),
m_basebandSampleRate(1), m_basebandSampleRate(1),
m_doApplySettings(true), m_doApplySettings(true),
m_tickCount(0) m_tickCount(0),
m_speech(nullptr)
{ {
setAttribute(Qt::WA_DeleteOnClose, true); setAttribute(Qt::WA_DeleteOnClose, true);
m_helpURL = "plugins/channelrx/demodpager/readme.md"; m_helpURL = "plugins/channelrx/demodpager/readme.md";
@ -526,6 +563,9 @@ PagerDemodGUI::PagerDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Bas
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor())); connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages())); connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
CRightClickEnabler *filterDuplicatesRightClickEnabler = new CRightClickEnabler(ui->filterDuplicates);
connect(filterDuplicatesRightClickEnabler, SIGNAL(rightClick(const QPoint &)), this, SLOT(on_filterDuplicates_rightClicked(const QPoint &)));
// Resize the table using dummy data // Resize the table using dummy data
resizeTable(); resizeTable();
// Allow user to reorder columns // Allow user to reorder columns
@ -575,6 +615,7 @@ void PagerDemodGUI::customContextMenuRequested(QPoint pos)
PagerDemodGUI::~PagerDemodGUI() PagerDemodGUI::~PagerDemodGUI()
{ {
clearFromMap();
delete ui; delete ui;
} }
@ -638,6 +679,8 @@ void PagerDemodGUI::displaySettings()
ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename)); ui->logFilename->setToolTip(QString(".csv log filename: %1").arg(m_settings.m_logFilename));
ui->logEnable->setChecked(m_settings.m_logEnabled); ui->logEnable->setChecked(m_settings.m_logEnabled);
ui->filterDuplicates->setChecked(m_settings.m_filterDuplicates);
// Order and size columns // Order and size columns
QHeaderView *header = ui->messages->horizontalHeader(); QHeaderView *header = ui->messages->horizontalHeader();
@ -656,6 +699,7 @@ void PagerDemodGUI::displaySettings()
getRollupContents()->restoreState(m_rollupState); getRollupContents()->restoreState(m_rollupState);
updateAbsoluteCenterFrequency(); updateAbsoluteCenterFrequency();
blockApplySettings(false); blockApplySettings(false);
enableSpeechIfNeeded();
} }
void PagerDemodGUI::leaveEvent(QEvent* event) void PagerDemodGUI::leaveEvent(QEvent* event)
@ -693,12 +737,39 @@ void PagerDemodGUI::tick()
void PagerDemodGUI::on_charset_clicked() void PagerDemodGUI::on_charset_clicked()
{ {
PagerDemodCharsetDialog dialog(&m_settings); PagerDemodCharsetDialog dialog(&m_settings);
if (dialog.exec() == QDialog::Accepted) new DialogPositioner(&dialog, true);
{ if (dialog.exec() == QDialog::Accepted) {
applySettings(); applySettings();
} }
} }
void PagerDemodGUI::on_notifications_clicked()
{
PagerDemodNotificationDialog dialog(&m_settings);
new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted)
{
enableSpeechIfNeeded();
applySettings();
}
}
void PagerDemodGUI::on_filterDuplicates_clicked(bool checked)
{
m_settings.m_filterDuplicates = checked;
applySettings();
}
void PagerDemodGUI::on_filterDuplicates_rightClicked(const QPoint &p)
{
(void) p;
PagerDemodFilterDialog dialog(&m_settings);
new DialogPositioner(&dialog, true);
if (dialog.exec() == QDialog::Accepted) {
applySettings();
}
}
void PagerDemodGUI::on_logEnable_clicked(bool checked) void PagerDemodGUI::on_logEnable_clicked(bool checked)
{ {
@ -813,6 +884,8 @@ void PagerDemodGUI::makeUIConnections()
QObject::connect(ui->udpEnabled, &QCheckBox::clicked, this, &PagerDemodGUI::on_udpEnabled_clicked); QObject::connect(ui->udpEnabled, &QCheckBox::clicked, this, &PagerDemodGUI::on_udpEnabled_clicked);
QObject::connect(ui->udpAddress, &QLineEdit::editingFinished, this, &PagerDemodGUI::on_udpAddress_editingFinished); QObject::connect(ui->udpAddress, &QLineEdit::editingFinished, this, &PagerDemodGUI::on_udpAddress_editingFinished);
QObject::connect(ui->udpPort, &QLineEdit::editingFinished, this, &PagerDemodGUI::on_udpPort_editingFinished); QObject::connect(ui->udpPort, &QLineEdit::editingFinished, this, &PagerDemodGUI::on_udpPort_editingFinished);
QObject::connect(ui->notifications, &QToolButton::clicked, this, &PagerDemodGUI::on_notifications_clicked);
QObject::connect(ui->filterDuplicates, &ButtonSwitch::clicked, this, &PagerDemodGUI::on_filterDuplicates_clicked);
QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &PagerDemodGUI::on_logEnable_clicked); QObject::connect(ui->logEnable, &ButtonSwitch::clicked, this, &PagerDemodGUI::on_logEnable_clicked);
QObject::connect(ui->logFilename, &QToolButton::clicked, this, &PagerDemodGUI::on_logFilename_clicked); QObject::connect(ui->logFilename, &QToolButton::clicked, this, &PagerDemodGUI::on_logFilename_clicked);
QObject::connect(ui->logOpen, &QToolButton::clicked, this, &PagerDemodGUI::on_logOpen_clicked); QObject::connect(ui->logOpen, &QToolButton::clicked, this, &PagerDemodGUI::on_logOpen_clicked);
@ -824,3 +897,178 @@ void PagerDemodGUI::updateAbsoluteCenterFrequency()
{ {
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset); setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
} }
// Initialise text to speech engine
// This takes 10 seconds on some versions of Linux, so only do it, if user actually
// has speech notifications configured
void PagerDemodGUI::enableSpeechIfNeeded()
{
#ifdef QT_TEXTTOSPEECH_FOUND
if (m_speech) {
return;
}
for (const auto& notification : m_settings.m_notificationSettings)
{
if (!notification->m_speech.isEmpty())
{
qDebug() << "PagerDemodGUI: Enabling text to speech";
m_speech = new QTextToSpeech(this);
return;
}
}
#endif
}
void PagerDemodGUI::checkNotification(int row)
{
QString address = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_ADDRESS)->text();
QString message = ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_MESSAGE)->text();
for (int i = 0; i < m_settings.m_notificationSettings.size(); i++)
{
QString match;
switch (m_settings.m_notificationSettings[i]->m_matchColumn)
{
case PagerDemodSettings::MESSAGE_COL_ADDRESS:
match = address;
break;
case PagerDemodSettings::MESSAGE_COL_MESSAGE:
match = message;
break;
}
if (!match.isEmpty())
{
if (m_settings.m_notificationSettings[i]->m_regularExpression.isValid())
{
QRegularExpressionMatch matchResult = m_settings.m_notificationSettings[i]->m_regularExpression.match(match);
if (matchResult.hasMatch())
{
if (m_settings.m_notificationSettings[i]->m_highlight) {
ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_MESSAGE)->setForeground(QBrush(m_settings.m_notificationSettings[i]->m_highlightColor));
}
if (!m_settings.m_notificationSettings[i]->m_speech.isEmpty())
{
QString speech = subStrings(address, message, matchResult, m_settings.m_notificationSettings[i]->m_speech);
speechNotification(speech);
}
if (!m_settings.m_notificationSettings[i]->m_command.isEmpty())
{
QString command = subStrings(address, message, matchResult, m_settings.m_notificationSettings[i]->m_command);
commandNotification(command);
}
if (m_settings.m_notificationSettings[i]->m_plotOnMap)
{
float latitude;
float longitude;
if (Units::stringToLatitudeAndLongitude(message, latitude, longitude, false))
{
QDateTime dateTime;
dateTime.setDate(QDate::fromString(ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_DATE)->text()));
dateTime.setTime(QTime::fromString(ui->messages->item(row, PagerDemodSettings::MESSAGE_COL_TIME)->text()));
sendToMap(address, message, latitude, longitude, dateTime);
}
}
}
}
}
}
}
QString PagerDemodGUI::subStrings(const QString& address, const QString& message, const QRegularExpressionMatch& match, const QString &string) const
{
QString s = string;
s = s.replace("${address}", address);
s = s.replace("${message}", message);
for (int i = 0; i < match.capturedTexts().size(); i++)
{
QString escape = QString("${%1}").arg(i);
s = s.replace(escape, match.capturedTexts()[i]);
}
return s;
}
void PagerDemodGUI::speechNotification(const QString &speech)
{
#ifdef QT_TEXTTOSPEECH_FOUND
if (m_speech) {
m_speech->say(speech);
} else {
qWarning() << "PagerDemodGUI::speechNotification: Unable to say " << speech;
}
#else
qWarning() << "PagerDemodGUI::speechNotification: TextToSpeech not supported. Unable to say " << speech;
#endif
}
void PagerDemodGUI::commandNotification(const QString &command)
{
#if QT_CONFIG(process)
QStringList allArgs = QProcess::splitCommand(command);
if (allArgs.size() > 0)
{
QString program = allArgs[0];
allArgs.pop_front();
QProcess::startDetached(program, allArgs);
}
#else
qWarning() << "PagerDemodGUI::commandNotification: QProcess not supported. Can't run: " << command;
#endif
}
void PagerDemodGUI::sendToMap(const QString& address, const QString& message, float latitude, float longitude, QDateTime dateTime)
{
QList<ObjectPipe*> mapPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_pagerDemod, "mapitems", mapPipes);
for (const auto& pipe : mapPipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(address));
swgMapItem->setLatitude(latitude);
swgMapItem->setLongitude(longitude);
swgMapItem->setAltitude(0);
swgMapItem->setAltitudeReference(1); // CLAMP_TO_GROUND
swgMapItem->setFixedPosition(false);
swgMapItem->setPositionDateTime(new QString(dateTime.toString(Qt::ISODateWithMs)));
swgMapItem->setImageRotation(0);
swgMapItem->setText(new QString(message));
swgMapItem->setImage(new QString(QString("pager.png")));
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_pagerDemod, swgMapItem);
messageQueue->push(msg);
}
m_mapItems.insert(address);
}
// Clear all items from map
void PagerDemodGUI::clearFromMap()
{
for (const auto& address : m_mapItems)
{
QList<ObjectPipe*> mapPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(m_pagerDemod, "mapitems", mapPipes);
for (const auto& pipe : mapPipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(address));
swgMapItem->setImage(new QString(QString("")));
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_pagerDemod, swgMapItem);
messageQueue->push(msg);
}
}
m_mapItems.clear();
}

View File

@ -20,6 +20,7 @@
#define INCLUDE_PAGERDEMODGUI_H #define INCLUDE_PAGERDEMODGUI_H
#include <QMenu> #include <QMenu>
#include <QTextToSpeech>
#include "channel/channelgui.h" #include "channel/channelgui.h"
#include "dsp/channelmarker.h" #include "dsp/channelmarker.h"
@ -45,6 +46,7 @@ class PagerDemodGUI : public ChannelGUI {
Q_OBJECT Q_OBJECT
public: public:
static PagerDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel); static PagerDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
virtual void destroy(); virtual void destroy();
@ -86,12 +88,18 @@ private:
QMenu *messagesMenu; // Column select context menu QMenu *messagesMenu; // Column select context menu
#ifdef QT_TEXTTOSPEECH_FOUND
QTextToSpeech *m_speech;
#endif
QSet<QString> m_mapItems;
explicit PagerDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0); explicit PagerDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
virtual ~PagerDemodGUI(); virtual ~PagerDemodGUI();
void blockApplySettings(bool block); void blockApplySettings(bool block);
void applySettings(bool force = false); void applySettings(bool force = false);
void displaySettings(); void displaySettings();
QString selectMessage(int functionBits, const QString &numericMessage, const QString &alphaMessage) const;
void messageReceived(const QDateTime dateTime, int address, int functionBits, void messageReceived(const QDateTime dateTime, int address, int functionBits,
const QString &numericMessage, const QString &alphaMessage, const QString &numericMessage, const QString &alphaMessage,
int evenParityErrors, int bchParityErrors); int evenParityErrors, int bchParityErrors);
@ -105,17 +113,13 @@ private:
void resizeTable(); void resizeTable();
QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot); QAction *createCheckableItem(QString& text, int idx, bool checked, const char *slot);
enum MessageCol { void enableSpeechIfNeeded();
MESSAGE_COL_DATE, void checkNotification(int row);
MESSAGE_COL_TIME, void speechNotification(const QString &speech);
MESSAGE_COL_ADDRESS, void commandNotification(const QString &command);
MESSAGE_COL_MESSAGE, QString subStrings(const QString& address, const QString& message, const QRegularExpressionMatch& match, const QString &string) const;
MESSAGE_COL_FUNCTION, void sendToMap(const QString& address, const QString& message, float latitide, float longitude, QDateTime dateTime);
MESSAGE_COL_ALPHA, void clearFromMap();
MESSAGE_COL_NUMERIC,
MESSAGE_COL_EVEN_PE,
MESSAGE_COL_BCH_PE
};
private slots: private slots:
void on_deltaFrequency_changed(qint64 value); void on_deltaFrequency_changed(qint64 value);
@ -131,6 +135,9 @@ private slots:
void on_udpPort_editingFinished(); void on_udpPort_editingFinished();
void on_channel1_currentIndexChanged(int index); void on_channel1_currentIndexChanged(int index);
void on_channel2_currentIndexChanged(int index); void on_channel2_currentIndexChanged(int index);
void on_notifications_clicked();
void on_filterDuplicates_clicked(bool checked=false);
void on_filterDuplicates_rightClicked(const QPoint &);
void on_logEnable_clicked(bool checked=false); void on_logEnable_clicked(bool checked=false);
void on_logFilename_clicked(); void on_logFilename_clicked();
void on_logOpen_clicked(); void on_logOpen_clicked();

View File

@ -29,7 +29,7 @@
</font> </font>
</property> </property>
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::StrongFocus</enum> <enum>Qt::FocusPolicy::StrongFocus</enum>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Pager Demodulator</string> <string>Pager Demodulator</string>
@ -110,7 +110,7 @@
<cursorShape>PointingHandCursor</cursorShape> <cursorShape>PointingHandCursor</cursorShape>
</property> </property>
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::StrongFocus</enum> <enum>Qt::FocusPolicy::StrongFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Demod shift frequency from center in Hz</string> <string>Demod shift frequency from center in Hz</string>
@ -127,14 +127,14 @@
<item> <item>
<widget class="Line" name="line"> <widget class="Line" name="line">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer"> <spacer name="horizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@ -152,7 +152,7 @@
<string>Channel power</string> <string>Channel power</string>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
<enum>Qt::RightToLeft</enum> <enum>Qt::LayoutDirection::RightToLeft</enum>
</property> </property>
<property name="text"> <property name="text">
<string>0.0</string> <string>0.0</string>
@ -209,7 +209,7 @@
<item> <item>
<widget class="Line" name="line_5"> <widget class="Line" name="line_5">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -252,7 +252,7 @@
<number>100</number> <number>100</number>
</property> </property>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -268,14 +268,14 @@
<string>10.0k</string> <string>10.0k</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="Line" name="line_2"> <widget class="Line" name="line_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -316,7 +316,7 @@
<number>24</number> <number>24</number>
</property> </property>
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -332,7 +332,7 @@
<string>2.4k</string> <string>2.4k</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> <set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -368,7 +368,7 @@
<item> <item>
<widget class="Line" name="line_10"> <widget class="Line" name="line_10">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -413,7 +413,7 @@
<item> <item>
<widget class="Line" name="line_11"> <widget class="Line" name="line_11">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -479,7 +479,7 @@
<item> <item>
<spacer name="horizontalSpacer_2"> <spacer name="horizontalSpacer_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@ -494,22 +494,29 @@
<item> <item>
<widget class="Line" name="line_6"> <widget class="Line" name="line_6">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="udpLayout"> <layout class="QHBoxLayout" name="udpLayout">
<item>
<widget class="QLabel" name="udpLabel">
<property name="text">
<string>UDP</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QCheckBox" name="udpEnabled"> <widget class="QCheckBox" name="udpEnabled">
<property name="toolTip"> <property name="toolTip">
<string>Forward messages via UDP</string> <string>Forward messages via UDP</string>
</property> </property>
<property name="layoutDirection"> <property name="layoutDirection">
<enum>Qt::RightToLeft</enum> <enum>Qt::LayoutDirection::RightToLeft</enum>
</property> </property>
<property name="text"> <property name="text">
<string>UDP</string> <string/>
</property> </property>
</widget> </widget>
</item> </item>
@ -522,7 +529,7 @@
</size> </size>
</property> </property>
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::ClickFocus</enum> <enum>Qt::FocusPolicy::ClickFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Destination UDP address</string> <string>Destination UDP address</string>
@ -541,7 +548,7 @@
<string>:</string> <string>:</string>
</property> </property>
<property name="alignment"> <property name="alignment">
<set>Qt::AlignCenter</set> <set>Qt::AlignmentFlag::AlignCenter</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -560,7 +567,7 @@
</size> </size>
</property> </property>
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::ClickFocus</enum> <enum>Qt::FocusPolicy::ClickFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Destination UDP port</string> <string>Destination UDP port</string>
@ -576,7 +583,7 @@
<item> <item>
<spacer name="horizontalSpacer_4"> <spacer name="horizontalSpacer_4">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@ -591,7 +598,7 @@
<item> <item>
<widget class="Line" name="line_3"> <widget class="Line" name="line_3">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -614,7 +621,7 @@
<item> <item>
<spacer name="horizontalSpacer_3"> <spacer name="horizontalSpacer_3">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
@ -624,6 +631,43 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<widget class="ButtonSwitch" name="filterDuplicates">
<property name="maximumSize">
<size>
<width>24</width>
<height>16777215</height>
</size>
</property>
<property name="toolTip">
<string>Filter duplicate messages. Right click for options.</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="pagerdemodicons.qrc">
<normaloff>:/icons/filterduplicate.png</normaloff>:/icons/filterduplicate.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="notifications">
<property name="toolTip">
<string>Open notifications dialog</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/mono.png</normaloff>:/mono.png</iconset>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item> <item>
<widget class="ButtonSwitch" name="logEnable"> <widget class="ButtonSwitch" name="logEnable">
<property name="maximumSize"> <property name="maximumSize">
@ -736,7 +780,7 @@
<string>Received messages</string> <string>Received messages</string>
</property> </property>
<property name="editTriggers"> <property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set> <set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property> </property>
<column> <column>
<property name="text"> <property name="text">
@ -1056,6 +1100,7 @@
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../../sdrgui/resources/res.qrc"/> <include location="../../../sdrgui/resources/res.qrc"/>
<include location="pagerdemodicons.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>icons/filterduplicate.png</file>
</qresource>
</RCC>

View File

@ -0,0 +1,173 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QComboBox>
#include <QPushButton>
#include <QCheckBox>
#include <QHBoxLayout>
#include "gui/tablecolorchooser.h"
#include "pagerdemodnotificationdialog.h"
#include "pagerdemodgui.h"
// Map main table column numbers to combo box indices
std::vector<int> PagerDemodNotificationDialog::m_columnMap = {
PagerDemodSettings::MESSAGE_COL_ADDRESS, PagerDemodSettings::MESSAGE_COL_MESSAGE
};
PagerDemodNotificationDialog::PagerDemodNotificationDialog(PagerDemodSettings *settings,
QWidget* parent) :
QDialog(parent),
ui(new Ui::PagerDemodNotificationDialog),
m_settings(settings)
{
ui->setupUi(this);
resizeTable();
for (int i = 0; i < m_settings->m_notificationSettings.size(); i++) {
addRow(m_settings->m_notificationSettings[i]);
}
}
PagerDemodNotificationDialog::~PagerDemodNotificationDialog()
{
delete ui;
qDeleteAll(m_colorGUIs);
}
void PagerDemodNotificationDialog::accept()
{
qDeleteAll(m_settings->m_notificationSettings);
m_settings->m_notificationSettings.clear();
for (int i = 0; i < ui->table->rowCount(); i++)
{
PagerDemodSettings::NotificationSettings *notificationSettings = new PagerDemodSettings::NotificationSettings();
int idx = ((QComboBox *)ui->table->cellWidget(i, NOTIFICATION_COL_MATCH))->currentIndex();
notificationSettings->m_matchColumn = m_columnMap[idx];
notificationSettings->m_regExp = ui->table->item(i, NOTIFICATION_COL_REG_EXP)->data(Qt::DisplayRole).toString().trimmed();
notificationSettings->m_speech = ui->table->item(i, NOTIFICATION_COL_SPEECH)->data(Qt::DisplayRole).toString().trimmed();
notificationSettings->m_command = ui->table->item(i, NOTIFICATION_COL_COMMAND)->data(Qt::DisplayRole).toString().trimmed();
notificationSettings->m_highlight = !m_colorGUIs[i]->m_noColor;
notificationSettings->m_highlightColor = m_colorGUIs[i]->m_color;
notificationSettings->m_plotOnMap = ((QCheckBox *) ui->table->cellWidget(i, NOTIFICATION_COL_PLOT_ON_MAP))->isChecked();
notificationSettings->updateRegularExpression();
m_settings->m_notificationSettings.append(notificationSettings);
}
QDialog::accept();
}
void PagerDemodNotificationDialog::resizeTable()
{
PagerDemodSettings::NotificationSettings dummy;
dummy.m_matchColumn = PagerDemodSettings::MESSAGE_COL_ADDRESS;
dummy.m_regExp = "1234567";
dummy.m_speech = "${message}";
dummy.m_command = "cmail.exe -to:user@host.com \"-subject: Paging ${address}\" \"-body: ${message}\"";
dummy.m_highlight = true;
dummy.m_plotOnMap = true;
addRow(&dummy);
ui->table->resizeColumnsToContents();
ui->table->selectRow(0);
on_remove_clicked();
ui->table->selectRow(-1);
}
void PagerDemodNotificationDialog::on_add_clicked()
{
addRow();
}
// Remove selected row
void PagerDemodNotificationDialog::on_remove_clicked()
{
// Selection mode is single, so only a single row should be returned
QModelIndexList indexList = ui->table->selectionModel()->selectedRows();
if (!indexList.isEmpty())
{
int row = indexList.at(0).row();
ui->table->removeRow(row);
m_colorGUIs.removeAt(row);
}
}
void PagerDemodNotificationDialog::addRow(PagerDemodSettings::NotificationSettings *settings)
{
int row = ui->table->rowCount();
ui->table->setSortingEnabled(false);
ui->table->setRowCount(row + 1);
QComboBox *match = new QComboBox();
TableColorChooser *highlight;
if (settings) {
highlight = new TableColorChooser(ui->table, row, NOTIFICATION_COL_HIGHLIGHT, !settings->m_highlight, settings->m_highlightColor);
} else {
highlight = new TableColorChooser(ui->table, row, NOTIFICATION_COL_HIGHLIGHT, false, QColor(Qt::red).rgba());
}
m_colorGUIs.append(highlight);
QCheckBox *plotOnMap = new QCheckBox();
plotOnMap->setChecked(false);
QWidget *matchWidget = new QWidget();
QHBoxLayout *pLayout = new QHBoxLayout(matchWidget);
pLayout->addWidget(match);
pLayout->setAlignment(Qt::AlignCenter);
pLayout->setContentsMargins(0, 0, 0, 0);
matchWidget->setLayout(pLayout);
match->addItem("Address");
match->addItem("Message");
QTableWidgetItem *regExpItem = new QTableWidgetItem();
QTableWidgetItem *speechItem = new QTableWidgetItem();
QTableWidgetItem *commandItem = new QTableWidgetItem();
if (settings != nullptr)
{
for (unsigned int i = 0; i < m_columnMap.size(); i++)
{
if (m_columnMap[i] == settings->m_matchColumn)
{
match->setCurrentIndex(i);
break;
}
}
regExpItem->setData(Qt::DisplayRole, settings->m_regExp);
speechItem->setData(Qt::DisplayRole, settings->m_speech);
commandItem->setData(Qt::DisplayRole, settings->m_command);
plotOnMap->setChecked(settings->m_plotOnMap);
}
else
{
match->setCurrentIndex(0);
regExpItem->setData(Qt::DisplayRole, ".*");
speechItem->setData(Qt::DisplayRole, "${message}");
#ifdef _MSC_VER
commandItem->setData(Qt::DisplayRole, "cmail.exe -to:user@host.com \"-subject: Paging ${address}\" \"-body: ${message}\"");
#else
commandItem->setData(Qt::DisplayRole, "sendmail -s \"Paging ${address}: ${message}\" user@host.com");
#endif
}
ui->table->setCellWidget(row, NOTIFICATION_COL_MATCH, match);
ui->table->setItem(row, NOTIFICATION_COL_REG_EXP, regExpItem);
ui->table->setItem(row, NOTIFICATION_COL_SPEECH, speechItem);
ui->table->setItem(row, NOTIFICATION_COL_COMMAND, commandItem);
ui->table->setCellWidget(row, NOTIFICATION_COL_PLOT_ON_MAP, plotOnMap);
ui->table->setSortingEnabled(true);
}

View File

@ -0,0 +1,61 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program 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 as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_PAGERDEMODNOTIFICATIONDIALOG_H
#define INCLUDE_PAGERDEMODNOTIFICATIONDIALOG_H
#include <QHash>
#include "ui_pagerdemodnotificationdialog.h"
#include "pagerdemodsettings.h"
class TableColorChooser;
class PagerDemodNotificationDialog : public QDialog {
Q_OBJECT
public:
explicit PagerDemodNotificationDialog(PagerDemodSettings* settings, QWidget* parent = 0);
~PagerDemodNotificationDialog();
private:
void resizeTable();
private slots:
void accept() override;
void on_add_clicked();
void on_remove_clicked();
void addRow(PagerDemodSettings::NotificationSettings *settings=nullptr);
private:
Ui::PagerDemodNotificationDialog* ui;
PagerDemodSettings *m_settings;
QList<TableColorChooser *> m_colorGUIs;
enum NotificationCol {
NOTIFICATION_COL_MATCH,
NOTIFICATION_COL_REG_EXP,
NOTIFICATION_COL_SPEECH,
NOTIFICATION_COL_COMMAND,
NOTIFICATION_COL_HIGHLIGHT,
NOTIFICATION_COL_PLOT_ON_MAP
};
static std::vector<int> m_columnMap;
};
#endif // INCLUDE_PagerDEMODNOTIFICATIONDIALOG_H

View File

@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PagerDemodNotificationDialog</class>
<widget class="QDialog" name="PagerDemodNotificationDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1100</width>
<height>400</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::ContextMenuPolicy::PreventContextMenu</enum>
</property>
<property name="windowTitle">
<string>Notifications</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0">
<widget class="QTableWidget" name="table">
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
<column>
<property name="text">
<string>Match</string>
</property>
<property name="toolTip">
<string>ADS-B data to match</string>
</property>
</column>
<column>
<property name="text">
<string>Reg Exp</string>
</property>
<property name="toolTip">
<string>Regular expression to match with</string>
</property>
</column>
<column>
<property name="text">
<string>Speech</string>
</property>
<property name="toolTip">
<string>Speech for the computer to read when a match is made</string>
</property>
</column>
<column>
<property name="text">
<string>Command</string>
</property>
<property name="toolTip">
<string>Command/script to execute when a match is made</string>
</property>
</column>
<column>
<property name="text">
<string>Highlight</string>
</property>
</column>
<column>
<property name="text">
<string>Plot on Map</string>
</property>
</column>
</widget>
</item>
<item row="5" column="0">
<layout class="QHBoxLayout" name="buttonsHorizontalLayout">
<item>
<widget class="QToolButton" name="add">
<property name="toolTip">
<string>Add device set control</string>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="remove">
<property name="toolTip">
<string>Remove device set control</string>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item>
<spacer name="buttonsHorizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>table</tabstop>
<tabstop>add</tabstop>
<tabstop>remove</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PagerDemodNotificationDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PagerDemodNotificationDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -20,6 +20,7 @@
#include <QColor> #include <QColor>
#include <QDataStream> #include <QDataStream>
#include <QDebug>
#include "util/simpleserializer.h" #include "util/simpleserializer.h"
#include "settings/serializable.h" #include "settings/serializable.h"
@ -59,6 +60,9 @@ void PagerDemodSettings::resetToDefaults()
m_reverse = false; m_reverse = false;
m_workspaceIndex = 0; m_workspaceIndex = 0;
m_hidden = false; m_hidden = false;
m_filterDuplicates = false;
m_duplicateMatchMessageOnly = false;
m_duplicateMatchLastOnly = false;
for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++)
{ {
@ -110,6 +114,11 @@ QByteArray PagerDemodSettings::serialize() const
s.writeBlob(29, m_geometryBytes); s.writeBlob(29, m_geometryBytes);
s.writeBool(30, m_hidden); s.writeBool(30, m_hidden);
s.writeList(31, m_notificationSettings);
s.writeBool(32, m_filterDuplicates);
s.writeBool(33, m_duplicateMatchMessageOnly);
s.writeBool(34, m_duplicateMatchLastOnly);
for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) { for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) {
s.writeS32(100 + i, m_messageColumnIndexes[i]); s.writeS32(100 + i, m_messageColumnIndexes[i]);
} }
@ -205,6 +214,12 @@ bool PagerDemodSettings::deserialize(const QByteArray& data)
d.readBlob(29, &m_geometryBytes); d.readBlob(29, &m_geometryBytes);
d.readBool(30, &m_hidden, false); d.readBool(30, &m_hidden, false);
d.readList(31, &m_notificationSettings);
d.readBool(32, &m_filterDuplicates);
d.readBool(33, &m_duplicateMatchMessageOnly);
d.readBool(34, &m_duplicateMatchLastOnly);
for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) { for (int i = 0; i < PAGERDEMOD_MESSAGE_COLUMNS; i++) {
d.readS32(100 + i, &m_messageColumnIndexes[i], i); d.readS32(100 + i, &m_messageColumnIndexes[i], i);
} }
@ -237,3 +252,80 @@ void PagerDemodSettings::deserializeIntList(const QByteArray& data, QList<qint32
(*stream) >> ints; (*stream) >> ints;
delete stream; delete stream;
} }
PagerDemodSettings::NotificationSettings::NotificationSettings() :
m_matchColumn(PagerDemodSettings::MESSAGE_COL_ADDRESS),
m_highlight(false),
m_highlightColor(Qt::red),
m_plotOnMap(false)
{
}
void PagerDemodSettings::NotificationSettings::updateRegularExpression()
{
m_regularExpression.setPattern(m_regExp);
m_regularExpression.optimize();
if (!m_regularExpression.isValid()) {
qDebug() << "PagerDemodSettings::NotificationSettings: Regular expression is not valid: " << m_regExp;
}
}
QByteArray PagerDemodSettings::NotificationSettings::serialize() const
{
SimpleSerializer s(1);
s.writeS32(1, m_matchColumn);
s.writeString(2, m_regExp);
s.writeString(3, m_speech);
s.writeString(4, m_command);
s.writeBool(5, m_highlight);
s.writeS32(6, m_highlightColor);
s.writeBool(7, m_plotOnMap);
return s.final();
}
bool PagerDemodSettings::NotificationSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid()) {
return false;
}
if (d.getVersion() == 1)
{
QByteArray blob;
d.readS32(1, &m_matchColumn);
d.readString(2, &m_regExp);
d.readString(3, &m_speech);
d.readString(4, &m_command);
d.readBool(5, &m_highlight, false);
d.readS32(6, &m_highlightColor, QColor(Qt::red).rgba());
d.readBool(7, &m_plotOnMap, false);
updateRegularExpression();
return true;
}
else
{
return false;
}
}
QDataStream& operator<<(QDataStream& out, const PagerDemodSettings::NotificationSettings *settings)
{
out << settings->serialize();
return out;
}
QDataStream& operator>>(QDataStream& in, PagerDemodSettings::NotificationSettings*& settings)
{
settings = new PagerDemodSettings::NotificationSettings();
QByteArray data;
in >> data;
settings->deserialize(data);
return in;
}

View File

@ -23,6 +23,7 @@
#include <QByteArray> #include <QByteArray>
#include <QString> #include <QString>
#include <QRegularExpression>
#include "dsp/dsptypes.h" #include "dsp/dsptypes.h"
@ -33,6 +34,35 @@ class Serializable;
struct PagerDemodSettings struct PagerDemodSettings
{ {
enum MessageCol {
MESSAGE_COL_DATE,
MESSAGE_COL_TIME,
MESSAGE_COL_ADDRESS,
MESSAGE_COL_MESSAGE,
MESSAGE_COL_FUNCTION,
MESSAGE_COL_ALPHA,
MESSAGE_COL_NUMERIC,
MESSAGE_COL_EVEN_PE,
MESSAGE_COL_BCH_PE
};
struct NotificationSettings {
int m_matchColumn;
QString m_regExp;
QString m_speech;
QString m_command;
bool m_highlight;
qint32 m_highlightColor;
bool m_plotOnMap;
QRegularExpression m_regularExpression;
NotificationSettings();
void updateRegularExpression();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
qint32 m_baud; //!< 512, 1200 or 2400 qint32 m_baud; //!< 512, 1200 or 2400
qint32 m_inputFrequencyOffset; qint32 m_inputFrequencyOffset;
Real m_rfBandwidth; Real m_rfBandwidth;
@ -73,6 +103,12 @@ struct PagerDemodSettings
QByteArray m_geometryBytes; QByteArray m_geometryBytes;
bool m_hidden; bool m_hidden;
QList<NotificationSettings *> m_notificationSettings;
bool m_filterDuplicates;
bool m_duplicateMatchMessageOnly;
bool m_duplicateMatchLastOnly;
int m_messageColumnIndexes[PAGERDEMOD_MESSAGE_COLUMNS];//!< How the columns are ordered in the table int m_messageColumnIndexes[PAGERDEMOD_MESSAGE_COLUMNS];//!< How the columns are ordered in the table
int m_messageColumnSizes[PAGERDEMOD_MESSAGE_COLUMNS]; //!< Size of the columns in the table int m_messageColumnSizes[PAGERDEMOD_MESSAGE_COLUMNS]; //!< Size of the columns in the table

View File

@ -1,4 +1,4 @@
<h1>Pager demodulator plugin</h1> <h1>Pager demodulator plugin</h1>
<h2>Introduction</h2> <h2>Introduction</h2>
@ -38,7 +38,7 @@ Specifies the pager modulation. Currently only POCSAG is supported.
POCSAG uses FSK with 4.5kHz frequency shift, at 512, 1200 or 2400 baud. POCSAG uses FSK with 4.5kHz frequency shift, at 512, 1200 or 2400 baud.
High frequency is typically 0, with low 1, but occasionally this appears to be reversed, so the demodulator supports either. High frequency is typically 0, with low 1, but occasionally this appears to be reversed, so the demodulator supports either.
Data is framed as specified in ITU-R M.584-2: https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf Data is framed as specified in [ITU-R M.584-2](https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.584-2-199711-I!!PDF-E.pdf)
<h3>7: Baud</h3> <h3>7: Baud</h3>
@ -86,15 +86,45 @@ IP address of the host to forward received messages to via UDP.
UDP port number to forward received messages to. UDP port number to forward received messages to.
<h3>15: Start/stop Logging Messages to .csv File</h3> <h3>15: Filter Duplicates</h3>
Check to filter (discard) duplicate messages. Right click to show the Duplicate Filter options dialog:
- Match message only: When unchecked, compare address and message. When checked, compare only message, ignoring the address.
- Match last message only: When unchecked the message is compared against all messages in the table. When checked, the message is compared against the last received message only.
<h3>16: Open Notifications Dialog</h3>
When clicked, opens the Notifications Dialog, which allows speech notifications or programs/scripts to be run when messages matching user-defined rules are received.
By running a program such as [cmail](https://www.inveigle.net/cmail/download) on Windows or sendmail on Linux, e-mail notifications can be sent containing the received message.
Messages can be highlighted in a user-defined colour, selected in the Highlight column.
By checking Plot on Map, if a message contains a position specified as latitude and longitude, the message can be displayed on the [Map](../../feature/map/readme.md) feature.
The format of the coordinates should follow [ISO 6709](https://en.wikipedia.org/wiki/ISO_6709), E.g: 50°4046″N 95°4826″W or -23.342,5.234
Here are a few examples:
![Notifications Dialog](../../../doc/img/PagerDemod_plugin_notifications.png)
In the Speech and Command strings, variables can be used to substitute data from the received message:
* ${address},
* ${message},
* ${1}, ${2}... are replaced with the string from the corresponding capture group in the regular expression.
To experiment with regular expressions, try [https://regexr.com/](https://regexr.com/).
<h3>17: Start/stop Logging Messages to .csv File</h3>
When checked, writes all received messages to a .csv file. When checked, writes all received messages to a .csv file.
<h3>16: .csv Log Filename</h3> <h3>18: .csv Log Filename</h3>
Click to specify the name of the .csv file which received messages are logged to. Click to specify the name of the .csv file which received messages are logged to.
<h3>17: Read Data from .csv File</h3> <h3>19: Read Data from .csv File</h3>
Click to specify a previously written .csv log file, which is read and used to update the table. Click to specify a previously written .csv log file, which is read and used to update the table.

View File

@ -25,6 +25,7 @@
<file>map/airport_small.png</file> <file>map/airport_small.png</file>
<file>map/heliport.png</file> <file>map/heliport.png</file>
<file>map/waypoint.png</file> <file>map/waypoint.png</file>
<file>map/pager.png</file>
<file>map/map3d.html</file> <file>map/map3d.html</file>
<file>data/transmitters.csv</file> <file>data/transmitters.csv</file>
</qresource> </qresource>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@ -38,6 +38,7 @@ const QStringList MapSettings::m_pipeTypes = {
QStringLiteral("FT8Demod"), QStringLiteral("FT8Demod"),
QStringLiteral("HeatMap"), QStringLiteral("HeatMap"),
QStringLiteral("ILSDemod"), QStringLiteral("ILSDemod"),
QStringLiteral("PagerDemod"),
QStringLiteral("Radiosonde"), QStringLiteral("Radiosonde"),
QStringLiteral("StarTracker"), QStringLiteral("StarTracker"),
QStringLiteral("SatelliteTracker"), QStringLiteral("SatelliteTracker"),
@ -55,6 +56,7 @@ const QStringList MapSettings::m_pipeURIs = {
QStringLiteral("sdrangel.channel.ft8demod"), QStringLiteral("sdrangel.channel.ft8demod"),
QStringLiteral("sdrangel.channel.heatmap"), QStringLiteral("sdrangel.channel.heatmap"),
QStringLiteral("sdrangel.channel.ilsdemod"), QStringLiteral("sdrangel.channel.ilsdemod"),
QStringLiteral("sdrangel.channel.pagerdemod"),
QStringLiteral("sdrangel.feature.radiosonde"), QStringLiteral("sdrangel.feature.radiosonde"),
QStringLiteral("sdrangel.feature.startracker"), QStringLiteral("sdrangel.feature.startracker"),
QStringLiteral("sdrangel.feature.satellitetracker"), QStringLiteral("sdrangel.feature.satellitetracker"),
@ -96,6 +98,7 @@ MapSettings::MapSettings() :
m_itemSettings.insert("StarTracker", new MapItemSettings("StarTracker", true, QColor(230, 230, 230), true, true, 3)); m_itemSettings.insert("StarTracker", new MapItemSettings("StarTracker", true, QColor(230, 230, 230), true, true, 3));
m_itemSettings.insert("SatelliteTracker", new MapItemSettings("SatelliteTracker", true, QColor(0, 0, 255), true, false, 0, modelMinPixelSize)); m_itemSettings.insert("SatelliteTracker", new MapItemSettings("SatelliteTracker", true, QColor(0, 0, 255), true, false, 0, modelMinPixelSize));
m_itemSettings.insert("Beacons", new MapItemSettings("Beacons", true, QColor(255, 0, 0), false, true, 8)); m_itemSettings.insert("Beacons", new MapItemSettings("Beacons", true, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("PagerDemod", new MapItemSettings("PagerDemod", true, QColor(200, 191, 231), true, false, 11));
m_itemSettings.insert("Radiosonde", new MapItemSettings("Radiosonde", true, QColor(102, 0, 102), true, false, 11, modelMinPixelSize)); m_itemSettings.insert("Radiosonde", new MapItemSettings("Radiosonde", true, QColor(102, 0, 102), true, false, 11, modelMinPixelSize));
m_itemSettings.insert("Radio Time Transmitters", new MapItemSettings("Radio Time Transmitters", true, QColor(255, 0, 0), false, true, 8)); m_itemSettings.insert("Radio Time Transmitters", new MapItemSettings("Radio Time Transmitters", true, QColor(255, 0, 0), false, true, 8));
m_itemSettings.insert("Radar", new MapItemSettings("Radar", true, QColor(255, 0, 0), false, true, 8)); m_itemSettings.insert("Radar", new MapItemSettings("Radar", true, QColor(255, 0, 0), false, true, 8));

View File

@ -16,7 +16,8 @@ On top of this, it can plot data from other plugins, such as:
* Radials and estimated position from the VOR localizer feature, * Radials and estimated position from the VOR localizer feature,
* ILS course line and glide path from the ILS Demodulator, * ILS course line and glide path from the ILS Demodulator,
* DSC geographic call areas, * DSC geographic call areas,
* SID paths. * SID paths,
* Pager messages that contain coordinates.
As well as internet and built-in data sources: As well as internet and built-in data sources:
@ -313,6 +314,7 @@ Mapbox: https://www.mapbox.com/ Cesium: https://www.cesium.com Bing: https://www
Ionosonde data and MUF/coF2 contours from [KC2G](https://prop.kc2g.com/) with source data from [GIRO](https://giro.uml.edu/) and [NOAA NCEI](https://www.ngdc.noaa.gov/stp/iono/ionohome.html). Ionosonde data and MUF/coF2 contours from [KC2G](https://prop.kc2g.com/) with source data from [GIRO](https://giro.uml.edu/) and [NOAA NCEI](https://www.ngdc.noaa.gov/stp/iono/ionohome.html).
Sea Marks are from OpenSeaMap: https://www.openseamap.org/ Sea Marks are from OpenSeaMap: https://www.openseamap.org/
Railways are from OpenRailwayMap: https://www.openrailwaymap.org/ Railways are from OpenRailwayMap: https://www.openrailwaymap.org/
@ -324,6 +326,7 @@ World icons created by turkkub from Flaticon: https://www.flaticon.com
Layers and Boat icons created by Freepik from Flaticon: https://www.flaticon.com Layers and Boat icons created by Freepik from Flaticon: https://www.flaticon.com
Railway icons created by Prosymbols Premium from Flaticon: https://www.flaticon.com Railway icons created by Prosymbols Premium from Flaticon: https://www.flaticon.com
Satellite icons created by SyafriStudio from Flaticon: https://www.flaticon.com Satellite icons created by SyafriStudio from Flaticon: https://www.flaticon.com
Pager icons created by xnimrodx from Flaticon: https://www.flaticon.com
3D models are by various artists under a variety of licenses. See: https://github.com/srcejon/sdrangel-3d-models 3D models are by various artists under a variety of licenses. See: https://github.com/srcejon/sdrangel-3d-models

View File

@ -167,3 +167,11 @@ QHash<QString, int> CSV::readHeader(QTextStream &in, QStringList requiredColumns
return colNumbers; return colNumbers;
} }
QString CSV::escape(const QString& string)
{
QString s = string;
s.replace('"', "\"\"");
s = QString("\"%1\"").arg(s);
return s;
}

View File

@ -52,6 +52,8 @@ struct SDRBASE_API CSV {
static bool readRow(QTextStream &in, QStringList *row, char seperator=','); static bool readRow(QTextStream &in, QStringList *row, char seperator=',');
static QHash<QString, int> readHeader(QTextStream &in, QStringList requiredColumns, QString &error, char seperator=','); static QHash<QString, int> readHeader(QTextStream &in, QStringList requiredColumns, QString &error, char seperator=',');
static QString escape(const QString& string);
}; };
#endif /* INCLUDE_CSV_H */ #endif /* INCLUDE_CSV_H */

View File

@ -269,11 +269,15 @@ public:
// Try to convert a string to latitude and longitude. Returns false if not recognised format. // Try to convert a string to latitude and longitude. Returns false if not recognised format.
// https://en.wikipedia.org/wiki/ISO_6709 specifies a standard syntax // https://en.wikipedia.org/wiki/ISO_6709 specifies a standard syntax
// We support both decimal and DMS formats // We support both decimal and DMS formats
static bool stringToLatitudeAndLongitude(const QString& string, float& latitude, float& longitude) static bool stringToLatitudeAndLongitude(const QString& string, float& latitude, float& longitude, bool exact=true)
{ {
QRegularExpressionMatch match; QRegularExpressionMatch match;
QRegularExpression decimal(QRegularExpression::anchoredPattern("(-?[0-9]+(\\.[0-9]+)?) *,? *(-?[0-9]+(\\.[0-9]+)?)")); QString decimalPattern = "(-?[0-9]+(\\.[0-9]+)?) *,? *(-?[0-9]+(\\.[0-9]+)?)";
if (exact) {
decimalPattern = QRegularExpression::anchoredPattern(decimalPattern);
}
QRegularExpression decimal(decimalPattern);
match = decimal.match(string); match = decimal.match(string);
if (match.hasMatch()) if (match.hasMatch())
{ {
@ -282,7 +286,11 @@ public:
return true; return true;
} }
QRegularExpression dms(QRegularExpression::anchoredPattern(QString("([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([NS]) *,? *([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([EW])").arg(QChar(0xb0)))); QString dmsPattern = QString("([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([NS]) *,? *([0-9]+)[%1d]([0-9]+)['m]([0-9]+(\\.[0-9]+)?)[\"s]([EW])").arg(QChar(0xb0));
if (exact) {
dmsPattern = QRegularExpression::anchoredPattern(dmsPattern);
}
QRegularExpression dms(dmsPattern);
match = dms.match(string); match = dms.match(string);
if (match.hasMatch()) if (match.hasMatch())
{ {
@ -303,7 +311,11 @@ public:
return true; return true;
} }
QRegularExpression dms2(QRegularExpression::anchoredPattern(QString("([0-9]+)([NS])([0-9]{2})([0-9]{2}) *,?([0-9]+)([EW])([0-9]{2})([0-9]{2})"))); QString dms2Pattern = "([0-9]+)([NS])([0-9]{2})([0-9]{2}) *,?([0-9]+)([EW])([0-9]{2})([0-9]{2})";
if (exact) {
dms2Pattern = QRegularExpression::anchoredPattern(dms2Pattern);
}
QRegularExpression dms2(dms2Pattern);
match = dms2.match(string); match = dms2.match(string);
if (match.hasMatch()) if (match.hasMatch())
{ {
@ -325,7 +337,11 @@ public:
} }
// 512255.5900N 0024400.6105W as used on aviation charts // 512255.5900N 0024400.6105W as used on aviation charts
QRegularExpression dms3(QRegularExpression::anchoredPattern(QString("(\\d{2})(\\d{2})((\\d{2})(\\.\\d+)?)([NS]) *,?(\\d{3})(\\d{2})((\\d{2})(\\.\\d+)?)([EW])"))); QString dms3Pattern = "(\\d{2})(\\d{2})((\\d{2})(\\.\\d+)?)([NS]) *,?(\\d{3})(\\d{2})((\\d{2})(\\.\\d+)?)([EW])";
if (exact) {
dms3Pattern = QRegularExpression::anchoredPattern(dms3Pattern);
}
QRegularExpression dms3(dms3Pattern);
match = dms3.match(string); match = dms3.match(string);
if (match.hasMatch()) if (match.hasMatch())
{ {