301 lines
7.4 KiB
C++
301 lines
7.4 KiB
C++
#include "APRSISClient.h"
|
|
|
|
#include <cmath>
|
|
|
|
#include "varicode.h"
|
|
|
|
const int PACKET_TIMEOUT_SECONDS = 300;
|
|
|
|
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 60 seconds
|
|
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, QString comment){
|
|
if(m_localCall.isEmpty()) return;
|
|
|
|
auto geo = APRSISClient::grid2aprs(grid);
|
|
auto spotFrame = QString("%1>%2,APRS,TCPIP*:=%3/%4nFT8CALL %5\n");
|
|
spotFrame = spotFrame.arg(theircall);
|
|
spotFrame = spotFrame.arg(m_localCall);
|
|
spotFrame = spotFrame.arg(geo.first);
|
|
spotFrame = spotFrame.arg(geo.second);
|
|
spotFrame = spotFrame.arg(comment.left(43));
|
|
enqueueRaw(spotFrame);
|
|
}
|
|
|
|
void APRSISClient::enqueueThirdParty(QString theircall, QString payload){
|
|
if(m_localPasscode != hashCallsign(m_localCall)){
|
|
return;
|
|
}
|
|
|
|
auto frame = QString("%1>%2,APRS,TCPIP*:%3\n");
|
|
frame = frame.arg(theircall);
|
|
frame = frame.arg(m_localCall);
|
|
frame = frame.arg(payload);
|
|
enqueueRaw(frame);
|
|
}
|
|
|
|
void APRSISClient::enqueueRaw(QString aprsFrame){
|
|
m_frameQueue.enqueue({ aprsFrame, QDateTime::currentDateTimeUtc() });
|
|
}
|
|
|
|
void APRSISClient::processQueue(bool disconnect){
|
|
// don't process queue if we haven't set our local callsign
|
|
if(m_localCall.isEmpty()) return;
|
|
|
|
// don't process queue if there's nothing to process
|
|
if(m_frameQueue.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;
|
|
}
|
|
}
|
|
|
|
auto re = QRegExp("(full|unavailable|busy)");
|
|
auto line = QString(readLine());
|
|
if(line.toLower().indexOf(re) >= 0){
|
|
qDebug() << "APRSISClient Connection Busy:" << line;
|
|
return;
|
|
}
|
|
|
|
if(write(loginFrame(m_localCall).toLocal8Bit()) == -1){
|
|
qDebug() << "APRSISClient Write Login Error:" << errorString();
|
|
return;
|
|
}
|
|
|
|
if(!waitForReadyRead(5000)){
|
|
qDebug() << "APRSISClient Login Error: Server Not Responding";
|
|
return;
|
|
}
|
|
|
|
line = QString(readAll());
|
|
if(line.toLower().indexOf(re) >= 0){
|
|
qDebug() << "APRSISClient Server Busy:" << line;
|
|
return;
|
|
}
|
|
|
|
while(!m_frameQueue.isEmpty()){
|
|
auto pair = m_frameQueue.head();
|
|
auto frame = pair.first;
|
|
auto timestamp = pair.second;
|
|
|
|
// if the packet is older than the timeout, drop it.
|
|
if(timestamp.secsTo(QDateTime::currentDateTimeUtc()) > PACKET_TIMEOUT_SECONDS){
|
|
qDebug() << "APRSISClient Packet Timeout:" << frame;
|
|
m_frameQueue.dequeue();
|
|
continue;
|
|
}
|
|
|
|
QByteArray data = frame.toLocal8Bit();
|
|
if(write(data) == -1){
|
|
qDebug() << "APRSISClient Write Error:" << errorString();
|
|
return;
|
|
}
|
|
|
|
if(!waitForBytesWritten(5000)){
|
|
qDebug() << "APRSISClient Cannot Write Error: Write Timeout";
|
|
return;
|
|
}
|
|
|
|
qDebug() << "APRSISClient Write:" << data;
|
|
|
|
if(waitForReadyRead(5000)){
|
|
line = QString(readLine());
|
|
|
|
qDebug() << "APRSISClient Read:" << line;
|
|
|
|
if(line.toLower().indexOf(re) >= 0){
|
|
qDebug() << "APRSISClient Cannot Write Error:" << line;
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_frameQueue.dequeue();
|
|
}
|
|
|
|
if(disconnect){
|
|
disconnectFromHost();
|
|
}
|
|
}
|