Merged master 8748

This commit is contained in:
Jordan Sherer
2018-08-05 11:33:30 -04:00
parent 8f8772f1bd
commit 62899069bf
1095 changed files with 31298 additions and 367679 deletions
@@ -0,0 +1,245 @@
//
// UDPDaemon - an example console application that utilizes the WSJT-X
// messaging facility
//
// This application is only provided as a simple console application
// example to demonstrate the WSJT-X messaging facility. It allows
// the user to set the server details either as a unicast UDP server
// or, if a multicast group address is provided, as a multicast
// server. The benefit of the multicast server is that multiple
// servers can be active at once each receiving all WSJT-X broadcast
// messages and each able to respond to individual WSJT_X clients. To
// utilize the multicast group features each WSJT-X client must set
// the same multicast group address as the UDP server address for
// example 239.255.0.0 for a site local multicast group.
//
//
#include <iostream>
#include <exception>
#include <QCoreApplication>
#include <QCommandLineParser>
#include <QDateTime>
#include <QTime>
#include <QHash>
#include <QDebug>
#include "MessageServer.hpp"
#include "Radio.hpp"
#include "qt_helpers.hpp"
using port_type = MessageServer::port_type;
using Frequency = MessageServer::Frequency;
class Client
: public QObject
{
Q_OBJECT
public:
explicit Client (QString const& id, QObject * parent = nullptr)
: QObject {parent}
, id_ {id}
, dial_frequency_ {0u}
{
}
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& /*dx_call*/
, QString const& /*report*/, QString const& /*tx_mode*/, bool /*tx_enabled*/
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
, QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/
, bool /* watchdog_timeout */, QString const& sub_mode, bool /*fast_mode*/)
{
if (id == id_)
{
if (f != dial_frequency_)
{
std::cout << tr ("%1: Dial frequency changed to %2").arg (id_).arg (f).toStdString () << std::endl;
dial_frequency_ = f;
}
if (mode + sub_mode != mode_)
{
std::cout << tr ("%1: Mode changed to %2").arg (id_).arg (mode + sub_mode).toStdString () << std::endl;
mode_ = mode + sub_mode;
}
}
}
Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime time, qint32 snr
, float delta_time, quint32 delta_frequency, QString const& mode
, QString const& message, bool low_confidence, bool off_air)
{
if (client_id == id_)
{
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
<< "Dt:" << delta_time << "Df:" << delta_frequency
<< "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high")
<< "On air:" << !off_air;
std::cout << tr ("%1: Decoded %2").arg (id_).arg (message).toStdString () << std::endl;
}
}
Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime time, qint32 snr
, float delta_time, Frequency delta_frequency, qint32 drift, QString const& callsign
, QString const& grid, qint32 power, bool off_air)
{
if (client_id == id_)
{
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
<< "Dt:" << delta_time << "Df:" << delta_frequency
<< "drift:" << drift;
std::cout << tr ("%1: WSPR decode %2 grid %3 power: %4").arg (id_).arg (callsign).arg (grid).arg (power).toStdString ()
<< "On air:" << !off_air << std::endl;
}
}
Q_SLOT void qso_logged (QString const&client_id, QDateTime time_off, QString const& dx_call, QString const& dx_grid
, Frequency dial_frequency, QString const& mode, QString const& report_sent
, QString const& report_received, QString const& tx_power
, QString const& comments, QString const& name, QDateTime time_on
, QString const& operator_call, QString const& my_call, QString const& my_grid)
{
if (client_id == id_)
{
qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:" << dx_call << "grid:" << dx_grid
<< "freq:" << dial_frequency << "mode:" << mode << "rpt_sent:" << report_sent
<< "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments
<< "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call
<< "my_grid:" << my_grid;
std::cout << QByteArray {80, '-'}.data () << '\n';
std::cout << tr ("%1: Logged %2 grid: %3 power: %4 sent: %5 recd: %6 freq: %7 time_off: %8 op: %9 my_call: %10 my_grid: %11")
.arg (id_).arg (dx_call).arg (dx_grid).arg (tx_power).arg (report_sent).arg (report_received)
.arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call)
.arg (my_call).arg (my_grid).toStdString ()
<< std::endl;
}
}
Q_SLOT void logged_ADIF (QString const&client_id, QByteArray const& ADIF)
{
if (client_id == id_)
{
qDebug () << "ADIF:" << ADIF;
std::cout << QByteArray {80, '-'}.data () << '\n';
std::cout << ADIF.data () << std::endl;
}
}
private:
QString id_;
Frequency dial_frequency_;
QString mode_;
};
class Server
: public QObject
{
Q_OBJECT
public:
Server (port_type port, QHostAddress const& multicast_group)
: server_ {new MessageServer {this}}
{
// connect up server
connect (server_, &MessageServer::error, [this] (QString const& message) {
std::cerr << tr ("Network Error: %1").arg ( message).toStdString () << std::endl;
});
connect (server_, &MessageServer::client_opened, this, &Server::add_client);
connect (server_, &MessageServer::client_closed, this, &Server::remove_client);
server_->start (port, multicast_group);
}
private:
void add_client (QString const& id, QString const& version, QString const& revision)
{
auto client = new Client {id};
connect (server_, &MessageServer::status_update, client, &Client::update_status);
connect (server_, &MessageServer::decode, client, &Client::decode_added);
connect (server_, &MessageServer::WSPR_decode, client, &Client::beacon_spot_added);
connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged);
connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF);
clients_[id] = client;
server_->replay (id);
std::cout << "Discovered WSJT-X instance: " << id.toStdString ();
if (version.size ())
{
std::cout << " v" << version.toStdString ();
}
if (revision.size ())
{
std::cout << " (" << revision.toStdString () << ")";
}
std::cout << std::endl;
}
void remove_client (QString const& id)
{
auto iter = clients_.find (id);
if (iter != std::end (clients_))
{
clients_.erase (iter);
(*iter)->deleteLater ();
}
std::cout << "Removed WSJT-X instance: " << id.toStdString () << std::endl;
}
MessageServer * server_;
// maps client id to clients
QHash<QString, Client *> clients_;
};
#include "UDPDaemon.moc"
int main (int argc, char * argv[])
{
QCoreApplication app {argc, argv};
try
{
setlocale (LC_NUMERIC, "C"); // ensure number forms are in
// consistent format, do this after
// instantiating QApplication so
// that GUI has correct l18n
app.setApplicationName ("WSJT-X UDP Message Server Daemon");
app.setApplicationVersion ("1.0");
QCommandLineParser parser;
parser.setApplicationDescription ("\nWSJT-X UDP Message Server Daemon.");
auto help_option = parser.addHelpOption ();
auto version_option = parser.addVersionOption ();
QCommandLineOption port_option (QStringList {"p", "port"},
app.translate ("UDPDaemon",
"Where <PORT> is the UDP service port number to listen on.\n"
"The default service port is 2237."),
app.translate ("UDPDaemon", "PORT"),
"2237");
parser.addOption (port_option);
QCommandLineOption multicast_addr_option (QStringList {"g", "multicast-group"},
app.translate ("UDPDaemon",
"Where <GROUP> is the multicast group to join.\n"
"The default is a unicast server listening on all interfaces."),
app.translate ("UDPDaemon", "GROUP"));
parser.addOption (multicast_addr_option);
parser.process (app);
Server server {static_cast<port_type> (parser.value (port_option).toUInt ()), QHostAddress {parser.value (multicast_addr_option)}};
return app.exec ();
}
catch (std::exception const & e)
{
std::cerr << "Error: " << e.what () << '\n';
}
catch (...)
{
std::cerr << "Unexpected error\n";
}
return -1;
}
@@ -1,208 +0,0 @@
#include "adif.h"
#include <QFile>
#include <QTextStream>
#include <QDebug>
/*
<CALL:4>W1XT<BAND:3>20m<FREQ:6>14.076<GRIDSQUARE:4>DM33<MODE:4>JT65<RST_RCVD:3>-21<RST_SENT:3>-14<QSO_DATE:8>20110422<TIME_ON:4>0417<TIME_OFF:4>0424<TX_PWR:1>4<COMMENT:34>1st JT65A QSO. Him: mag loop 20W<STATION_CALLSIGN:6>VK3ACF<MY_GRIDSQUARE:6>qf22lb<eor>
<CALL:6>IK1SOW<BAND:3>20m<FREQ:6>14.076<GRIDSQUARE:4>JN35<MODE:4>JT65<RST_RCVD:3>-19<RST_SENT:3>-11<QSO_DATE:8>20110422<TIME_ON:4>0525<TIME_OFF:4>0533<TX_PWR:1>3<STATION_CALLSIGN:6>VK3ACF<MY_GRIDSQUARE:6>qf22lb<eor>
<CALL:6:S>W4ABC> ...
*/
void ADIF::init(QString filename)
{
_filename = filename;
_data.clear();
}
QString ADIF::_extractField(const QString line, const QString fieldName)
{
int fieldNameIndex = line.indexOf(fieldName,0,Qt::CaseInsensitive);
if (fieldNameIndex >=0)
{
int closingBracketIndex = line.indexOf('>',fieldNameIndex);
int fieldLengthIndex = line.indexOf(':',fieldNameIndex); // find the size delimiter
int dataTypeIndex = -1;
if (fieldLengthIndex >= 0)
{
dataTypeIndex = line.indexOf(':',fieldLengthIndex+1); // check for a second : indicating there is a data type
if (dataTypeIndex > closingBracketIndex)
dataTypeIndex = -1; // second : was found but it was beyond the closing >
}
if ((closingBracketIndex > fieldNameIndex) && (fieldLengthIndex > fieldNameIndex) && (fieldLengthIndex< closingBracketIndex))
{
int fieldLengthCharCount = closingBracketIndex - fieldLengthIndex -1;
if (dataTypeIndex >= 0)
fieldLengthCharCount -= 2; // data type indicator is always a colon followed by a single character
QString fieldLengthString = line.mid(fieldLengthIndex+1,fieldLengthCharCount);
int fieldLength = fieldLengthString.toInt();
if (fieldLength > 0)
{
QString field = line.mid(closingBracketIndex+1,fieldLength);
return field;
}
}
}
return "";
}
void ADIF::load()
{
_data.clear();
QFile inputFile(_filename);
if (inputFile.open(QIODevice::ReadOnly))
{
QTextStream in(&inputFile);
while ( !in.atEnd() )
{
QString line = in.readLine();
QSO q;
q.call = _extractField(line,"CALL:");
q.band = _extractField(line,"BAND:");
q.mode = _extractField(line,"MODE:");
q.date = _extractField(line,"QSO_DATE:");
if (q.call != "")
_data.insert(q.call,q);
}
inputFile.close();
}
}
void ADIF::add(const QString call, const QString band, const QString mode, const QString date)
{
QSO q;
q.call = call;
q.band = band;
q.mode = mode;
q.date = date;
_data.insert(q.call,q);
//qDebug() << "Added as worked:" << call << band << mode << date;
}
// return true if in the log same band and mode (where JT65 == JT9)
bool ADIF::match(const QString call, const QString band, const QString mode)
{
QList<QSO> qsos = _data.values(call);
if (qsos.size()>0)
{
QSO q;
foreach(q,qsos)
{
if ( (band.compare(q.band,Qt::CaseInsensitive) == 0)
|| (band=="")
|| (q.band==""))
{
if (
(
((mode.compare("JT65",Qt::CaseInsensitive)==0) || (mode.compare("JT9",Qt::CaseInsensitive)==0))
&&
((q.mode.compare("JT65",Qt::CaseInsensitive)==0) || (q.mode.compare("JT9",Qt::CaseInsensitive)==0))
)
|| (mode.compare(q.mode,Qt::CaseInsensitive)==0)
|| (mode=="")
|| (q.mode=="")
)
return true;
}
}
}
return false;
}
QList<QString> ADIF::getCallList()
{
QList<QString> p;
QMultiHash<QString,QSO>::const_iterator i = _data.constBegin();
while (i != _data.constEnd())
{
p << i.key();
++i;
}
return p;
}
int ADIF::getCount()
{
return _data.size();
}
// open ADIF file and append the QSO details. Return true on success
bool ADIF::addQSOToFile(const QString hisCall, const QString hisGrid, const QString mode, const QString rptSent, const QString rptRcvd, const QString dateOn, const QString timeOn, QString dateOff, const QString timeOff, const QString band,
const QString comments, const QString name, const QString strDialFreq, const QString m_myCall, const QString m_myGrid, const QString m_txPower)
{
QFile f2(_filename);
if (!f2.open(QIODevice::Text | QIODevice::Append))
return false;
else
{
QTextStream out(&f2);
if (f2.size()==0)
out << "WSJT-X ADIF Export<eoh>" << endl; // new file
QString t;
t="<call:" + QString::number(hisCall.length()) + ">" + hisCall;
t+=" <gridsquare:" + QString::number(hisGrid.length()) + ">" + hisGrid;
t+=" <mode:" + QString::number(mode.length()) + ">" + mode;
t+=" <rst_sent:" + QString::number(rptSent.length()) + ">" + rptSent;
t+=" <rst_rcvd:" + QString::number(rptRcvd.length()) + ">" + rptRcvd;
t+=" <qso_date:8>" + dateOn;
t+=" <time_on:4>" + timeOn;
t+=" <qso_date_off:8>" + dateOff;
t+=" <time_off:4>" + timeOff;
t+=" <band:" + QString::number(band.length()) + ">" + band;
t+=" <freq:" + QString::number(strDialFreq.length()) + ">" + strDialFreq;
t+=" <station_callsign:" + QString::number(m_myCall.length()) + ">" +
m_myCall;
t+=" <my_gridsquare:" + QString::number(m_myGrid.length()) + ">" +
m_myGrid;
if(m_txPower!="") t+= " <tx_pwr:" + QString::number(m_txPower.length()) +
">" + m_txPower;
if(comments!="") t+=" <comment:" + QString::number(comments.length()) +
">" + comments;
if(name!="") t+=" <name:" + QString::number(name.length()) +
">" + name;
t+=" <eor>";
out << t << endl;
f2.close();
}
return true;
}
QString ADIF::bandFromFrequency(double dialFreq)
{
QString band="";
if(dialFreq>0.135 and dialFreq<0.139) band="2200m";
else if(dialFreq>0.45 and dialFreq<0.55) band="630m";
else if(dialFreq>1.8 and dialFreq<2.0) band="160m";
else if(dialFreq>3.5 and dialFreq<4.0) band="80m";
else if(dialFreq>5.1 and dialFreq<5.45) band="60m";
else if(dialFreq>7.0 and dialFreq<7.3) band="40m";
else if(dialFreq>10.0 and dialFreq<10.15) band="30m";
else if(dialFreq>14.0 and dialFreq<14.35) band="20m";
else if(dialFreq>18.068 and dialFreq<18.168) band="17m";
else if(dialFreq>21.0 and dialFreq<21.45) band="15m";
else if(dialFreq>24.890 and dialFreq<24.990) band="12m";
else if(dialFreq>28.0 and dialFreq<29.7) band="10m";
else if(dialFreq>50.0 and dialFreq<54.0) band="6m";
else if(dialFreq>70.0 and dialFreq<71.0) band="4m";
else if(dialFreq>144.0 and dialFreq<148.0) band="2m";
else if(dialFreq>222.0 and dialFreq<225.0) band="1.25m";
else if(dialFreq>420.0 and dialFreq<450.0) band="70cm";
else if(dialFreq>902.0 and dialFreq<928.0) band="33cm";
else if(dialFreq>1240.0 and dialFreq<1300.0) band="23cm";
else if(dialFreq>2300.0 and dialFreq<2450.0) band="13cm";
else if(dialFreq>3300.0 and dialFreq<3500.0) band="9cm";
else if(dialFreq>5650.0 and dialFreq<5925.0) band="6cm";
else if(dialFreq>10000.0 and dialFreq<10500.0) band="3cm";
else if(dialFreq>24000.0 and dialFreq<24250.0) band="1.25cm";
else if(dialFreq>47000.0 and dialFreq<47200.0) band="6mm";
else if(dialFreq>75500.0 and dialFreq<81000.0) band="4mm";
return band;
}
@@ -1,118 +0,0 @@
program ft8sim
! Generate simulated data for a 15-second HF/6m mode using 8-FSK.
! Output is saved to a *.wav file.
use wavhdr
include 'ft8_params.f90' !Set various constants
type(hdr) h !Header for .wav file
character arg*12,fname*17,sorm*1
character msg*22,msgsent*22
complex c0(0:NMAX-1)
complex c(0:NMAX-1)
integer itone(NN)
integer*1 msgbits(KK)
integer*2 iwave(NMAX) !Generated full-length waveform
! Get command-line argument(s)
nargs=iargc()
if(nargs.ne.8) then
print*,'Usage: ft8sim "message" sorm f0 DT fdop del nfiles snr'
print*,'Example: ft8sim "K1ABC W9XYZ EN37" m 1500.0 0.0 0.1 1.0 10 -18'
print*,'sorm: "s" for single signal at 1500 Hz, "m" for 25 signals'
print*,'f0 is ignored when sorm = m'
go to 999
endif
call getarg(1,msg) !Message to be transmitted
call getarg(2,sorm) !s for single signal, m for multiple sigs
if(sorm.eq."s") then
print*,"Generating single signal at 1500 Hz."
nsig=1
elseif( sorm.eq."m") then
print*,"Generating 25 signals per file."
nsig=25
else
print*,"sorm parameter must be s (single) or m (multiple)."
goto 999
endif
call getarg(3,arg)
read(arg,*) f0 !Frequency (only used for single-signal)
call getarg(4,arg)
read(arg,*) xdt !Time offset from nominal (s)
call getarg(5,arg)
read(arg,*) fspread !Watterson frequency spread (Hz)
call getarg(6,arg)
read(arg,*) delay !Watterson delay (ms)
call getarg(7,arg)
read(arg,*) nfiles !Number of files
call getarg(8,arg)
read(arg,*) snrdb !SNR_2500
twopi=8.0*atan(1.0)
fs=12000.0 !Sample rate (Hz)
dt=1.0/fs !Sample interval (s)
tt=NSPS*dt !Duration of symbols (s)
baud=1.0/tt !Keying rate (baud)
bw=8*baud !Occupied bandwidth (Hz)
txt=NZ*dt !Transmission length (s)
bandwidth_ratio=2500.0/(fs/2.0)
sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*snrdb)
if(snrdb.gt.90.0) sig=1.0
txt=NN*NSPS/12000.0
call genft8(msg,msgsent,msgbits,itone) !Source-encode, then get itone()
write(*,1000) f0,xdt,txt,snrdb,bw,msgsent
1000 format('f0:',f9.3,' DT:',f6.2,' TxT:',f6.1,' SNR:',f6.1, &
' BW:',f4.1,2x,a22)
write(*,'(28i1,1x,28i1)') msgbits(1:56)
write(*,'(16i1)') msgbits(57:72)
write(*,'(3i1)') msgbits(73:75)
write(*,'(12i1)') msgbits(76:87)
! call sgran()
do ifile=1,nfiles
c=0.
do isig=1,nsig
c0=0.
if(nsig.eq.25) then
f0=(isig+2)*100.0
endif
k=-1 + nint((xdt+0.5+0.01*gran())/dt)
! k=-1 + nint((xdt+0.5)/dt)
phi=0.0
do j=1,NN !Generate complex waveform
dphi=twopi*(f0+itone(j)*baud)*dt
do i=1,NSPS
k=k+1
phi=mod(phi+dphi,twopi)
if(k.ge.0 .and. k.lt.NMAX) c0(k)=cmplx(cos(phi),sin(phi))
enddo
enddo
if(fspread.ne.0.0 .or. delay.ne.0.0) call watterson(c0,NMAX,fs,delay,fspread)
c=c+sig*c0
enddo
if(snrdb.lt.90) then
do i=0,NMAX-1 !Add gaussian noise at specified SNR
xnoise=gran()
ynoise=gran()
c(i)=c(i) + cmplx(xnoise,ynoise)
enddo
endif
fac=32767.0
rms=100.0
if(snrdb.ge.90.0) iwave(1:NMAX)=nint(fac*real(c))
if(snrdb.lt.90.0) iwave(1:NMAX)=nint(rms*real(c))
h=default_header(12000,NMAX)
write(fname,1102) ifile
1102 format('000000_',i6.6,'.wav')
open(10,file=fname,status='unknown',access='stream')
write(10) h,iwave !Save to *.wav file
close(10)
write(*,1110) ifile,xdt,f0,snrdb,fname
1110 format(i4,f7.2,f8.2,f7.1,2x,a17)
enddo
999 end program ft8sim