Added spotting to APRS-IS for grids larger than 4 characters. Added supporting commands for QTH QTC and GRID

This commit is contained in:
Jordan Sherer 2018-08-27 21:19:38 -04:00
parent 2fa1fcd4f8
commit adcc728492
13 changed files with 553 additions and 64 deletions

250
APRSISClient.cpp Normal file
View File

@ -0,0 +1,250 @@
#include "APRSISClient.h"
#include <cmath>
#include "Radio.hpp"
#include "varicode.h"
APRSISClient::APRSISClient(QString host, quint16 port, QObject *parent):
QTcpSocket(parent),
m_host(host),
m_port(port)
{
connect(&m_timer, &QTimer::timeout, this, &APRSISClient::sendReports);
m_timer.setInterval(60*1000); // every minute
m_timer.start();
}
quint32 APRSISClient::hashCallsign(QString callsign){
// based on: https://github.com/hessu/aprsc/blob/master/src/passcode.c
QByteArray rootCall = QString(callsign.split("-").first().toUpper()).toLocal8Bit() + '\0';
quint32 hash = 0x73E2;
int i = 0;
int len = rootCall.length();
while(i+1 < len){
hash ^= rootCall.at(i) << 8;
hash ^= rootCall.at(i+1);
i += 2;
}
return hash & 0x7FFF;
}
QString APRSISClient::loginFrame(QString callsign){
auto loginFrame = QString("user %1 pass %2 ver %3\n");
loginFrame = loginFrame.arg(callsign);
loginFrame = loginFrame.arg(hashCallsign(callsign));
loginFrame = loginFrame.arg("FT8Call");
return loginFrame;
}
QList<QStringList> findall(QRegularExpression re, QString content){
int pos = 0;
QList<QStringList> all;
while(pos < content.length()){
auto match = re.match(content, pos);
if(!match.hasMatch()){
break;
}
all.append(match.capturedTexts());
pos = match.capturedEnd();
}
return all;
}
inline long
floordiv (long num, long den)
{
if (0 < (num^den))
return num/den;
else
{
ldiv_t res = ldiv(num,den);
return (res.rem)? res.quot-1
: res.quot;
}
}
// convert an arbitrary length grid locator to a high precision lat/lon
QPair<float, float> APRSISClient::grid2deg(QString locator){
QString grid = locator.toUpper();
float lat = -90;
float lon = -90;
auto lats = findall(QRegularExpression("([A-X])([A-X])"), grid);
auto lons = findall(QRegularExpression("(\\d)(\\d)"), grid);
int valx[22];
int valy[22];
int i = 0;
int tot = 0;
char A = 'A';
foreach(QStringList matched, lats){
char x = matched.at(1).at(0).toLatin1();
char y = matched.at(2).at(0).toLatin1();
valx[i*2] = x-A;
valy[i*2] = y-A;
i++;
tot++;
}
i = 0;
foreach(QStringList matched, lons){
int x = matched.at(1).toInt();
int y = matched.at(2).toInt();
valx[i*2+1]=x;
valy[i*2+1]=y;
i++;
tot++;
}
for(int i = 0; i < tot; i++){
int x = valx[i];
int y = valy[i];
int z = i - 1;
float scale = pow(10, floordiv(-(z-1), 2)) * pow(24, floordiv(-z, 2));
lon += scale * x;
lat += scale * y;
}
lon *= 2;
return {lat, lon};
}
// convert an arbitrary length grid locator to a high precision lat/lon in aprs format
QPair<QString, QString> APRSISClient::grid2aprs(QString grid){
auto geo = APRSISClient::grid2deg(grid);
auto lat = geo.first;
auto lon = geo.second;
QString latDir = "N";
if(lat < 0){
lat *= -1;
latDir = "S";
}
QString lonDir = "E";
if(lon < 0){
lon *= -1;
lonDir = "W";
}
double iLat, fLat, iLon, fLon, iLatMin, fLatMin, iLonMin, fLonMin, iLatSec, iLonSec;
fLat = modf(lat, &iLat);
fLon = modf(lon, &iLon);
fLatMin = modf(fLat * 60, &iLatMin);
fLonMin = modf(fLon * 60, &iLonMin);
iLatSec = round(fLatMin * 60);
iLonSec = round(fLonMin * 60);
if(iLatSec == 60){
iLatMin += 1;
iLatSec = 0;
}
if(iLonSec == 60){
iLonMin += 1;
iLonSec = 0;
}
if(iLatMin == 60){
iLat += 1;
iLatMin = 0;
}
if(iLonMin == 60){
iLon += 1;
iLonMin = 0;
}
double aprsLat = iLat * 100 + iLatMin + (iLatSec / 60.0);
double aprsLon = iLon * 100 + iLonMin + (iLonSec / 60.0);
return {
QString().sprintf("%07.2f%%1", aprsLat).arg(latDir),
QString().sprintf("%08.2f%%1", aprsLon).arg(lonDir)
};
}
void APRSISClient::enqueueSpot(QString theircall, QString grid, quint64 frequency, int snr){
if(m_localCall.isEmpty()) return;
auto geo = APRSISClient::grid2aprs(grid);
auto spotFrame = QString("%1>APRS,TCPIP*:=%2/%3nFT8CALL %4 %5 %6MHz %7dB\n");
spotFrame = spotFrame.arg(theircall);
spotFrame = spotFrame.arg(geo.first);
spotFrame = spotFrame.arg(geo.second);
spotFrame = spotFrame.arg(m_localCall);
spotFrame = spotFrame.arg(m_localGrid.left(4));
spotFrame = spotFrame.arg(Radio::frequency_MHz_string(frequency));
spotFrame = spotFrame.arg(Varicode::formatSNR(snr));
enqueueRaw(spotFrame);
}
void APRSISClient::enqueueMessage(QString tocall, QString message){
if(m_localCall.isEmpty()) return;
auto messageFrame = QString("%1>APRS,TCPIP*::%2:%3\n");
messageFrame = messageFrame.arg(m_localCall);
messageFrame = messageFrame.arg(tocall + QString(" ").repeated(9-tocall.length()));
messageFrame = messageFrame.arg(message);
enqueueRaw(messageFrame);
}
void APRSISClient::enqueueRaw(QString aprsFrame){
m_frameQueue.enqueue(aprsFrame);
}
void APRSISClient::processQueue(bool disconnect){
if(m_localCall.isEmpty()) return;
// 1. connect (and read)
// 2. login (and read)
// 3. for each raw frame in queue, send
// 4. disconnect
if(state() != QTcpSocket::ConnectedState){
connectToHost(m_host, m_port);
if(!waitForConnected(5000)){
qDebug() << "APRSISClient Connection Error:" << errorString();
return;
}
}
if(write(loginFrame(m_localCall).toLocal8Bit()) == -1){
qDebug() << "APRSISClient Write Login Error:" << errorString();
return;
}
while(!m_frameQueue.isEmpty()){
if(write(m_frameQueue.head().toLocal8Bit()) == -1){
qDebug() << "APRSISClient Write Error:" << errorString();
return;
}
auto frame = m_frameQueue.dequeue();
qDebug() << "APRISISClient Write:" << frame;
}
if(disconnect){
disconnectFromHost();
}
}

44
APRSISClient.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef APRSISCLIENT_H
#define APRSISCLIENT_H
#include <QTcpSocket>
#include <QQueue>
#include <QPair>
#include <QTimer>
class APRSISClient : public QTcpSocket
{
public:
APRSISClient(QString host, quint16 port, QObject *parent = nullptr);
static quint32 hashCallsign(QString callsign);
static QString loginFrame(QString callsign);
static QPair<float, float> grid2deg(QString grid);
static QPair<QString, QString> grid2aprs(QString grid);
void setLocalStation(QString mycall, QString mygrid){
m_localCall = mycall;
m_localGrid = mygrid;
}
void enqueueSpot(QString theircall, QString grid, quint64 frequency, int snr);
void enqueueMessage(QString tocall, QString message);
void enqueueMail(QString email, QString message);
void enqueueRaw(QString aprsFrame);
void processQueue(bool disconnect=false);
public slots:
void sendReports(){ processQueue(true); }
private:
QString m_localCall;
QString m_localGrid;
QQueue<QString> m_frameQueue;
QString m_host;
quint16 m_port;
QTimer m_timer;
};
#endif // APRSISCLIENT_H

View File

@ -305,6 +305,7 @@ set (wsjtx_CXXSRCS
WsprTxScheduler.cpp
varicode.cpp
SelfDestructMessageBox.cpp
APRSISClient.cpp
mainwindow.cpp
Configuration.cpp
main.cpp

View File

@ -576,7 +576,7 @@ private:
double txDelay_;
bool id_after_73_;
bool tx_QSY_allowed_;
bool spot_to_psk_reporter_;
bool spot_to_reporting_networks_;
bool transmit_directed_;
bool autoreply_off_at_startup_;
bool monitor_off_at_startup_;
@ -678,15 +678,15 @@ double Configuration::txDelay() const {return m_->txDelay_;}
qint32 Configuration::RxBandwidth() const {return m_->RxBandwidth_;}
bool Configuration::id_after_73 () const {return m_->id_after_73_;}
bool Configuration::tx_QSY_allowed () const {return m_->tx_QSY_allowed_;}
bool Configuration::spot_to_psk_reporter () const
bool Configuration::spot_to_reporting_networks () const
{
// rig must be open and working to spot externally
return is_transceiver_online () && m_->spot_to_psk_reporter_;
return is_transceiver_online () && m_->spot_to_reporting_networks_;
}
void Configuration::set_spot_to_psk_reporter (bool spot)
void Configuration::set_spot_to_reporting_networks (bool spot)
{
if(m_->spot_to_psk_reporter_ != spot){
m_->spot_to_psk_reporter_ = spot;
if(m_->spot_to_reporting_networks_ != spot){
m_->spot_to_reporting_networks_ = spot;
m_->write_settings();
}
}
@ -1264,7 +1264,7 @@ void Configuration::impl::initialize_models ()
ui_->azel_path_display_label->setText (azel_directory_.absolutePath ());
ui_->CW_id_after_73_check_box->setChecked (id_after_73_);
ui_->tx_QSY_check_box->setChecked (tx_QSY_allowed_);
ui_->psk_reporter_check_box->setChecked (spot_to_psk_reporter_);
ui_->psk_reporter_check_box->setChecked (spot_to_reporting_networks_);
ui_->transmit_directed_check_box->setChecked(transmit_directed_);
ui_->autoreply_off_check_box->setChecked (autoreply_off_at_startup_);
ui_->monitor_off_check_box->setChecked (monitor_off_at_startup_);
@ -1473,7 +1473,7 @@ void Configuration::impl::read_settings ()
autoreply_off_at_startup_ = settings_->value ("AutoreplyOFF", false).toBool ();
monitor_off_at_startup_ = settings_->value ("MonitorOFF", false).toBool ();
monitor_last_used_ = settings_->value ("MonitorLastUsed", false).toBool ();
spot_to_psk_reporter_ = settings_->value ("PSKReporter", true).toBool ();
spot_to_reporting_networks_ = settings_->value ("PSKReporter", true).toBool ();
id_after_73_ = settings_->value ("After73", false).toBool ();
tx_QSY_allowed_ = settings_->value ("TxQSYAllowed", false).toBool ();
use_dynamic_info_ = settings_->value ("AutoGrid", false).toBool ();
@ -1613,7 +1613,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("AutoreplyOFF", autoreply_off_at_startup_);
settings_->setValue ("MonitorOFF", monitor_off_at_startup_);
settings_->setValue ("MonitorLastUsed", monitor_last_used_);
settings_->setValue ("PSKReporter", spot_to_psk_reporter_);
settings_->setValue ("PSKReporter", spot_to_reporting_networks_);
settings_->setValue ("After73", id_after_73_);
settings_->setValue ("TxQSYAllowed", tx_QSY_allowed_);
settings_->setValue ("Macros", macros_.stringList ());
@ -2033,7 +2033,7 @@ void Configuration::impl::accept ()
my_qth_ = ui_->qth_message_line_edit->text().toUpper();
callsign_aging_ = ui_->callsign_aging_spin_box->value();
activity_aging_ = ui_->activity_aging_spin_box->value();
spot_to_psk_reporter_ = ui_->psk_reporter_check_box->isChecked ();
spot_to_reporting_networks_ = ui_->psk_reporter_check_box->isChecked ();
id_interval_ = ui_->CW_id_interval_spin_box->value ();
ntrials_ = ui_->sbNtrials->value ();
txDelay_ = ui_->sbTxDelay->value ();

View File

@ -112,8 +112,8 @@ public:
double txDelay() const;
bool id_after_73 () const;
bool tx_QSY_allowed () const;
bool spot_to_psk_reporter () const;
void set_spot_to_psk_reporter (bool);
bool spot_to_reporting_networks () const;
void set_spot_to_reporting_networks (bool);
bool transmit_directed() const;
bool autoreply_off_at_startup () const;
bool monitor_off_at_startup () const;

View File

@ -80,7 +80,7 @@
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;4-digit Maidenhead Locator&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maxLength">
<number>16</number>
<number>12</number>
</property>
</widget>
</item>

64
aprs.py Normal file
View File

@ -0,0 +1,64 @@
import socket
KKEY = 0x73e2
def do_hash(callsign):
rootCall = callsign.split("-")[0].upper() + '\0'
hash = KKEY
i = 0
length = len(rootCall)
while (i+1 < length):
hash ^= ord(rootCall[i])<<8
hash ^= ord(rootCall[i+1])
i += 2
return int(hash & 0x7fff)
HOST = 'rotate.aprs2.net'
PORT = 14580
print "Connecting..."
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
print "Connected..."
data = s.recv(1024)
print data
call = 'KN4CRD'
pw = do_hash(call)
ver = "FT8Call"
login = "user {} pass {} ver {}\n".format(call, pw, ver)
s.send(login)
print "Login sent...", login
data = s.recv(1024)
print data
if 1:
message = "{}>APRS,TCPIP*::EMAIL-2 :kn4crd@gmail.com testing456{{AA}}\n".format(call)
s.send(message)
if 0:
payload = ":This is a test message"
message = "{}>APRS,TCPIP*::{} {}\n".format(call, call, payload)
s.send(message)
if 0:
position = "=3352.45N/08422.71Wn"
status = "FT8CALL VIA XX9XXX/XXXX 14.082500MHz -20dB"
payload = "".join((position, status))
message = "{}>APRS,TCPIP*:{}\n".format(call, payload)
s.send(message)
print "Spot sent...", message
data = s.recv(1024)
print data
s.close()

View File

@ -1,5 +1,6 @@
//---------------------------------------------------------- MainWindow
#include "mainwindow.h"
#include <cmath>
#include <cinttypes>
#include <limits>
#include <functional>
@ -25,6 +26,7 @@
#include <QActionGroup>
#include <QSplashScreen>
#include "APRSISClient.h"
#include "revision_utils.hpp"
#include "qt_helpers.hpp"
#include "NetworkAccessManager.hpp"
@ -466,6 +468,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
version (), revision (),
m_config.udp_server_name (), m_config.udp_server_port (),
this}},
m_aprsClient {new APRSISClient{"rotate.aprs2.net", 14580, this}},
psk_Reporter {new PSK_Reporter {m_messageClient, this}},
m_i3bit {0},
m_manual {&m_network_manager},
@ -1014,7 +1017,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
setFreqOffsetForRestore(f, false);
ui->cbVHFcontest->setChecked(false); // this needs to always be false
ui->spotButton->setChecked(m_config.spot_to_psk_reporter());
ui->spotButton->setChecked(m_config.spot_to_reporting_networks());
auto enterFilter = new EnterKeyPressEater();
connect(enterFilter, &EnterKeyPressEater::enterKeyPressed, this, [this](QObject *, QKeyEvent *, bool *pProcessed){
@ -1279,6 +1282,17 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
quint8 r = 0;
quint64 v = Varicode::unpack72bits(Varicode::pack72bits((((quint64)1)<<62)-1, (1<<7)-1), &r);
qDebug() << "packing" << Varicode::pack72bits((((quint64)1)<<62)-1, (1<<7)-1) << v << r;
qDebug() << APRSISClient::grid2deg("EM73");
qDebug() << APRSISClient::grid2deg("EM73TU");
qDebug() << APRSISClient::grid2deg("EM73TU49NT");
qDebug() << APRSISClient::grid2aprs("EM73");
qDebug() << APRSISClient::grid2aprs("EM73TU");
qDebug() << APRSISClient::grid2aprs("EM73TU49NT");
qDebug() << APRSISClient::grid2aprs("FI08VE49");
qDebug() << APRSISClient::grid2aprs("OM25CU");
#endif
// this must be the last statement of constructor
@ -2035,7 +2049,7 @@ void MainWindow::fastSink(qint64 frames)
writeAllTxt(message);
bool stdMsg = decodedtext.report(m_baseCall,
Radio::base_callsign(ui->dxCallEntry->text()),m_rptRcvd);
if (stdMsg) pskPost (decodedtext);
//if (stdMsg) pskPost (decodedtext);
}
float fracTR=float(k)/(12000.0*m_TRperiod);
@ -2183,7 +2197,7 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog
on_dxGridEntry_textChanged (m_hisGrid); // recalculate distances in case of units change
enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook
preparePSKReporter();
prepareSpotting();
if(m_config.restart_audio_input ()) {
Q_EMIT startAudioInputStream (m_config.audio_input_device (),
@ -2233,9 +2247,10 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog
}
}
void MainWindow::preparePSKReporter(){
if(m_config.spot_to_psk_reporter ()){
pskSetLocal ();
void MainWindow::prepareSpotting(){
if(m_config.spot_to_reporting_networks ()){
pskSetLocal();
aprsSetLocal();
ui->spotButton->setChecked(true);
} else {
ui->spotButton->setChecked(false);
@ -2244,10 +2259,10 @@ void MainWindow::preparePSKReporter(){
void MainWindow::on_spotButton_clicked(bool checked){
// 1. save setting
m_config.set_spot_to_psk_reporter(checked);
m_config.set_spot_to_reporting_networks(checked);
// 2. prepare
preparePSKReporter();
prepareSpotting();
}
void MainWindow::on_monitorButton_clicked (bool checked)
@ -3247,12 +3262,12 @@ void::MainWindow::fast_decode_done()
writeAllTxt(message);
if(m_mode=="JT9" or m_mode=="MSK144") {
// find and extract any report for myCall
// find and extract any report for myCall
bool stdMsg = decodedtext.report(m_baseCall,
Radio::base_callsign(ui->dxCallEntry->text()), m_rptRcvd);
// extract details and send to PSKreporter
if (stdMsg) pskPost (decodedtext);
// extract details and send to PSKreporter
//if (stdMsg) pskPost (decodedtext);
}
if (tmax >= 0.0) auto_sequence (decodedtext, ui->sbFtol->value (), ui->sbFtol->value ());
}
@ -3688,7 +3703,7 @@ void MainWindow::readFromStdout() //readFromStdout
m_mode=="JT9") auto_sequence (decodedtext, 25, 50);
postDecode (true, decodedtext.string ());
// find and extract any report for myCall, but save in m_rptRcvd only if it's from DXcall
// find and extract any report for myCall, but save in m_rptRcvd only if it's from DXcall
QString rpt;
bool stdMsg = decodedtext.report(m_baseCall,
Radio::base_callsign(ui->dxCallEntry->text()), rpt);
@ -3699,10 +3714,11 @@ void MainWindow::readFromStdout() //readFromStdout
QString t=Radio::base_callsign(ui->dxCallEntry->text());
if((t==deCall or t=="") and rpt!="") m_rptRcvd=rpt;
}
// extract details and send to PSKreporter
// extract details and send to PSKreporter
int nsec=QDateTime::currentMSecsSinceEpoch()/1000-m_secBandChanged;
bool okToPost=(nsec>(4*m_TRperiod)/5);
if (stdMsg && okToPost) pskPost(decodedtext);
//if (stdMsg && okToPost) pskPost(decodedtext);
if((m_mode=="JT4" or m_mode=="JT65" or m_mode=="QRA64") and m_msgAvgWidget!=NULL) {
if(m_msgAvgWidget->isVisible()) {
@ -3811,7 +3827,8 @@ void MainWindow::auto_sequence (DecodedText const& message, unsigned start_toler
void MainWindow::pskPost (DecodedText const& decodedtext)
{
if (m_diskData || !m_config.spot_to_psk_reporter() || decodedtext.isLowConfidence ()) return;
#if 0
if (m_diskData || !m_config.spot_to_reporting_networks() || decodedtext.isLowConfidence ()) return;
QString msgmode=m_mode;
if(m_mode=="JT9+JT65") {
@ -3826,6 +3843,7 @@ void MainWindow::pskPost (DecodedText const& decodedtext)
audioFrequency=decodedtext.string().mid(16,4).toInt();
}
int snr = decodedtext.snr();
pskSetLocal ();
if(grid.contains (grid_regexp)) {
// qDebug() << "To PSKreporter:" << deCall << grid << frequency << msgmode << snr;
@ -3833,10 +3851,11 @@ void MainWindow::pskPost (DecodedText const& decodedtext)
// QString::number(snr),QString::number(QDateTime::currentDateTime().toTime_t()));
pskLogReport(msgmode, audioFrequency, snr, deCall, grid);
}
#endif
}
void MainWindow::pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid){
if(!m_config.spot_to_psk_reporter()) return;
if(!m_config.spot_to_reporting_networks()) return;
Frequency frequency = m_freqNominal + offset;
@ -3849,6 +3868,19 @@ void MainWindow::pskLogReport(QString mode, int offset, int snr, QString callsig
QString::number(QDateTime::currentDateTime().toTime_t()));
}
void MainWindow::aprsLogReport(int offset, int snr, QString callsign, QString grid){
if(!m_config.spot_to_reporting_networks()) return;
Frequency frequency = m_freqNominal + offset;
if(grid.length() < 6){
qDebug() << "APRSISClient Spot Skipped:" << callsign << grid;
return;
}
m_aprsClient->enqueueSpot(Radio::base_callsign(callsign), grid, frequency, snr);
}
void MainWindow::killFile ()
{
if (m_fnameWE.size () &&
@ -6946,6 +6978,7 @@ void MainWindow::band_changed (Frequency f)
m_bandEdited = false;
psk_Reporter->sendReport(); // Upload any queued spots before changing band
m_aprsClient->processQueue();
if (!m_transmitting) monitor (true);
if ("FreqCal" == m_mode)
{
@ -7122,18 +7155,15 @@ void MainWindow::on_qtcMacroButton_clicked(){
if(qtc.isEmpty()){
return;
}
addMessageText(qtc);
addMessageText(QString("QTC %1").arg(qtc));
}
void MainWindow::on_qthMacroButton_clicked(){
QString qth = m_config.my_qth();
if(qth.isEmpty()){
qth = m_config.my_grid();
}
if(qth.isEmpty()){
return;
}
addMessageText(qth);
addMessageText(QString("QTH %1").arg(qth));
}
void MainWindow::setSortBy(QString key, QString value){
@ -7284,6 +7314,20 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
if(m_config.transmit_directed()) toggleTx(true);
});
auto gridQueryAction = menu->addAction(QString("%1^ - What is your current grid locator?").arg(call));
gridQueryAction->setDisabled(isAllCall);
connect(gridQueryAction, &QAction::triggered, this, [this](){
QString selectedCall = callsignSelected();
if(selectedCall.isEmpty()){
return;
}
addMessageText(QString("%1^").arg(selectedCall), true);
if(m_config.transmit_directed()) toggleTx(true);
});
auto stationMessageQueryAction = menu->addAction(QString("%1&& - What is your station message?").arg(call).trimmed());
stationMessageQueryAction->setDisabled(isAllCall);
connect(stationMessageQueryAction, &QAction::triggered, this, [this](){
@ -7362,6 +7406,49 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
addMessageText(QString("%1!").arg(selectedCall), true);
});
menu->addSeparator();
auto qtcAction = menu->addAction(QString("%1 QTC message - Send my station message").arg(call).trimmed());
connect(qtcAction, &QAction::triggered, this, [this](){
QString selectedCall = callsignSelected();
if(selectedCall.isEmpty()){
return;
}
addMessageText(QString("%1 QTC %2").arg(selectedCall).arg(m_config.my_station()), true);
if(m_config.transmit_directed()) toggleTx(true);
});
auto qthAction = menu->addAction(QString("%1 QTH message - Send my station location message").arg(call).trimmed());
connect(qthAction, &QAction::triggered, this, [this](){
QString selectedCall = callsignSelected();
if(selectedCall.isEmpty()){
return;
}
addMessageText(QString("%1 QTH %2").arg(selectedCall).arg(m_config.my_qth()), true);
if(m_config.transmit_directed()) toggleTx(true);
});
auto grid = m_config.my_grid();
auto gridAction = menu->addAction(QString("%1 GRID %2 - Send my current station grid location").arg(call).arg(grid).trimmed());
connect(gridAction, &QAction::triggered, this, [this](){
QString selectedCall = callsignSelected();
if(selectedCall.isEmpty()){
return;
}
addMessageText(QString("%1 GRID %2").arg(selectedCall).arg(m_config.my_grid()), true);
if(m_config.transmit_directed()) toggleTx(true);
});
menu->addSeparator();
auto agnAction = menu->addAction(QString("%1 AGN? - Please repeat your last transmission").arg(call).trimmed());
@ -7908,8 +7995,9 @@ void MainWindow::handle_transceiver_update (Transceiver::TransceiverState const&
}
}
if (m_config.spot_to_psk_reporter ()) {
pskSetLocal ();
if (m_config.spot_to_reporting_networks ()) {
pskSetLocal();
aprsSetLocal();
}
statusChanged();
m_wideGraph->setDialFreq(m_freqNominal / 1.e6);
@ -8167,6 +8255,11 @@ void MainWindow::pskSetLocal ()
m_config.my_station(), QString {"FT8Call v" + version() }.simplified ());
}
void MainWindow::aprsSetLocal ()
{
m_aprsClient->setLocalStation(Radio::base_callsign(m_config.my_callsign()), m_config.my_grid());
}
void MainWindow::transmitDisplay (bool transmitting)
{
if (transmitting == m_transmitting) {
@ -8979,15 +9072,20 @@ void MainWindow::processCommandActivity() {
else if (d.cmd == "@" && !isAllCall) {
QString qth = m_config.my_qth();
if (qth.isEmpty()) {
QString grid = m_config.my_grid();
if (grid.isEmpty()) {
continue;
}
qth = grid;
continue;
}
reply = QString("%1 QTH %2").arg(d.from).arg(qth);
}
// QUERIED GRID
else if (d.cmd == "^" && !isAllCall) {
QString grid = m_config.my_grid();
if (grid.isEmpty()) {
continue;
}
reply = QString("%1 GRID %2").arg(d.from).arg(grid);
}
// QUERIED STATION MESSAGE
else if (d.cmd == "&" && !isAllCall) {
reply = QString("%1 QTC %2").arg(d.from).arg(m_config.my_station());
@ -9031,9 +9129,30 @@ void MainWindow::processCommandActivity() {
else if (d.cmd == "#" && !isAllCall) {
reply = QString("%1 ACK").arg(d.from);
}
// PROCESS AGN
else if (d.cmd == " AGN?" && !isAllCall && !m_lastTxMessage.isEmpty()) {
reply = m_lastTxMessage;
}
// PROCESS BUFFERED QTH
else if (d.cmd == " GRID"){
// 1. parse grids
// 2. log it to reporting networks
auto grids = Varicode::parseGrids(d.text);
foreach(auto grid, grids){
CallDetail cd = {};
cd.bits = d.bits;
cd.call = d.from;
cd.freq = d.freq;
cd.grid = grid;
cd.snr = d.snr;
cd.utcTimestamp = d.utcTimestamp;
logCallActivity(cd, true);
}
reply = QString("%1 ACK").arg(d.from);
}
// PROCESS ALERT
else if (d.cmd == "!" && !isAllCall) {
QMessageBox * msgBox = new QMessageBox(this);
msgBox->setIcon(QMessageBox::Information);
@ -9056,10 +9175,6 @@ void MainWindow::processCommandActivity() {
});
msgBox->show();
continue;
} else if (d.cmd == " AGN?" && !isAllCall && !m_lastTxMessage.isEmpty()) {
reply = m_lastTxMessage;
}
if (reply.isEmpty()) {
@ -9098,14 +9213,16 @@ void MainWindow::processSpots() {
// Process spots to be sent...
pskSetLocal();
aprsSetLocal();
while(!m_rxCallQueue.isEmpty()){
CallDetail d = m_rxCallQueue.dequeue();
if(d.call.isEmpty()){
continue;
}
qDebug() << "spotting call to psk reporter" << d.call << d.snr << d.freq;
qDebug() << "spotting call to reporting networks" << d.call << d.snr << d.freq;
pskLogReport("FT8CALL", d.freq, d.snr, d.call, d.grid);
aprsLogReport(d.freq, d.snr, d.call, d.grid);
}
}

View File

@ -44,6 +44,7 @@
#include "qpriorityqueue.h"
#include "varicode.h"
#include "MessageClient.hpp"
#include "APRSISClient.h"
#define NUM_JT4_SYMBOLS 206 //(72+31)*2, embedded sync
#define NUM_JT65_SYMBOLS 126 //63 data + 63 sync
@ -167,7 +168,7 @@ private slots:
void on_actionShow_Waterfall_triggered(bool checked);
void on_actionReset_Window_Sizes_triggered();
void on_actionSettings_triggered();
void preparePSKReporter();
void prepareSpotting();
void on_spotButton_clicked(bool checked);
void on_monitorButton_clicked (bool);
void on_actionAbout_triggered();
@ -805,6 +806,7 @@ private:
QTimer m_heartbeat;
MessageClient * m_messageClient;
PSK_Reporter *psk_Reporter;
APRSISClient * m_aprsClient;
DisplayManual m_manual;
QHash<QString, QVariant> m_pwrBandTxMemory; // Remembers power level by band
QHash<QString, QVariant> m_pwrBandTuneMemory; // Remembers power level by band for tuning
@ -831,8 +833,10 @@ private:
void transmit (double snr = 99.);
void rigFailure (QString const& reason);
void pskSetLocal ();
void aprsSetLocal ();
void pskPost(DecodedText const& decodedtext);
void pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid);
void aprsLogReport(int offset, int snr, QString callsign, QString grid);
Radio::Frequency dialFrequency();
void displayDialFrequency ();
void transmitDisplay (bool);

View File

@ -1250,6 +1250,9 @@ QTextEdit[transmitting=&quot;true&quot;] {
</item>
<item row="1" column="4">
<widget class="QPushButton" name="qthMacroButton">
<property name="visible">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
@ -1301,15 +1304,15 @@ QTextEdit[transmitting=&quot;true&quot;] {
</item>
<item row="1" column="2">
<widget class="QPushButton" name="qtcMacroButton">
<property name="visible">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<property name="visible">
<bool>true</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Send your station message&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>

View File

@ -30,7 +30,7 @@
const int nalphabet = 41;
QString alphabet = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"}; // alphabet to encode _into_ for FT8 freetext transmission
QString alphabet72 = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-+/?."};
QString grid_pattern = {R"((?<grid>[A-R]{2}[0-9]{2})+)"};
QString grid_pattern = {R"((?<grid>[A-X]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*)+)"};
QString orig_compound_callsign_pattern = {R"((?<callsign>(\d|[A-Z])+\/?((\d|[A-Z]){2,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?))"};
QString compound_callsign_pattern = {R"((?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?)\b)"};
QString pack_callsign_pattern = {R"(([0-9A-Z ])([0-9A-Z])([0-9])([A-Z ])([A-Z ])([A-Z ]))"};
@ -44,6 +44,7 @@ QMap<QString, int> directed_cmds = {
{"@", 1 }, // query qth
{"&", 2 }, // query station message
{"$", 3 }, // query station(s) heard
{"^", 4 }, // query grid
{"%", 5 }, // query pwr
{"|", 6 }, // retransmit message
@ -54,10 +55,11 @@ QMap<QString, int> directed_cmds = {
// {"/", 10 }, // unused? (can we even use stroke?)
// directed responses
{" QTC", 16 }, // this is my qtc
{" QTH", 17 }, // this is my qth
{" GRID", 15 }, // this is my current grid locator
{" QTC", 16 }, // this is my qtc message
{" QTH", 17 }, // this is my qth message
{" FB", 18 }, // fine business
{" HW CPY?", 19 }, // how do you copy?
{" HW CPY?", 19 }, // how do you copy?
{" HEARING", 20 }, // i am hearing the following stations
{" RR", 21 }, // roger roger
{" QSL?", 22 }, // do you copy?
@ -72,12 +74,12 @@ QMap<QString, int> directed_cmds = {
{" ", 31 }, // send freetext
};
QSet<int> allowed_cmds = {0, 1, 2, 3, /*4,*/ 5, 6, 7, 8, /*...*/ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
QSet<int> allowed_cmds = {0, 1, 2, 3, 4, 5, 6, 7, 8, /*...*/ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
QSet<int> buffered_cmds = {6, 7, 8};
QSet<int> buffered_cmds = {6, 7, 8, 15};
QString callsign_pattern = QString("(?<callsign>[A-Z0-9/]+)");
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|ACK|73|YES|NO|SNR|PWR|QSL[?]?|RR|HEARING|HW CPY[?]|FB|QTH|QTC|[?@&$%|!# ]))?");
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|ACK|73|YES|NO|SNR|PWR|QSL[?]?|RR|HEARING|HW CPY[?]|FB|QTH|QTC|GRID|[?@&$%|!#^ ]))?");
QString optional_grid_pattern = QString("(?<grid>\\s?[A-R]{2}[0-9]{2})?");
QString optional_extended_grid_pattern = QString("^(?<grid>\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?");
QString optional_pwr_pattern = QString("(?<pwr>(?<=PWR)\\s?\\d+\\s?[KM]?W)?");
@ -951,7 +953,7 @@ QString Varicode::unpackCallsign(quint32 value){
return callsign;
}
QString deg2grid(float dlong, float dlat){
QString Varicode::deg2grid(float dlong, float dlat){
QChar grid[6];
if(dlong < -180){
@ -984,7 +986,7 @@ QString deg2grid(float dlong, float dlat){
return QString(grid, 6);
}
QPair<float, float> grid2deg(QString const &grid){
QPair<float, float> Varicode::grid2deg(QString const &grid){
QPair<float, float> longLat;
QString g = grid;
@ -1016,7 +1018,7 @@ quint16 Varicode::packGrid(QString const& value){
return (1<<15)-1;
}
auto pair = grid2deg(grid.left(4));
auto pair = Varicode::grid2deg(grid.left(4));
int ilong = pair.first;
int ilat = pair.second + 90;
@ -1031,7 +1033,7 @@ QString Varicode::unpackGrid(quint16 value){
float dlat = value % 180 - 90;
float dlong = value / 180 * 2 - 180 + 2;
return deg2grid(dlong, dlat).left(4);
return Varicode::deg2grid(dlong, dlat).left(4);
}
// pack a number or snr into an integer between 0 & 62

View File

@ -109,6 +109,8 @@ public:
static quint32 packCallsign(QString const& value);
static QString unpackCallsign(quint32 value);
static QString deg2grid(float dlong, float dlat);
static QPair<float, float> grid2deg(QString const &grid);
static quint16 packGrid(QString const& value);
static QString unpackGrid(quint16 value);

View File

@ -71,7 +71,8 @@ SOURCES += \
varicode.cpp \
NetworkMessage.cpp \
MessageClient.cpp \
SelfDestructMessageBox.cpp
SelfDestructMessageBox.cpp \
APRSISClient.cpp
HEADERS += qt_helpers.hpp \
pimpl_h.hpp pimpl_impl.hpp \
@ -94,7 +95,8 @@ HEADERS += qt_helpers.hpp \
crc.h \
NetworkMessage.hpp \
MessageClient.hpp \
SelfDestructMessageBox.h
SelfDestructMessageBox.h \
APRSISClient.h
INCLUDEPATH += qmake_only