mirror of
https://github.com/JS8Call-improved/JS8Call-improved
synced 2026-08-13 17:47:36 -04:00
Rename all header files with .h extension Fix function declaration in HamlibTransceiver.h that overrode member function but was not marked override Remove unused variable in Modulator.cpp Remove unused files from source tree Remove duplicate LazyFillComboBox files Reformat codebase to LLVM C++ standard
211 lines
7.4 KiB
C++
211 lines
7.4 KiB
C++
#include "SpotClient.h"
|
|
#include "JS8_Include/pimpl_impl.h"
|
|
#include "JS8_Main/Message.h"
|
|
#include "moc_SpotClient.cpp"
|
|
#include <QHostInfo>
|
|
#include <QLoggingCategory>
|
|
#include <QNetworkDatagram>
|
|
#include <QQueue>
|
|
#include <QTimer>
|
|
#include <QUdpSocket>
|
|
|
|
Q_DECLARE_LOGGING_CATEGORY(spotclient_js8)
|
|
|
|
/******************************************************************************/
|
|
// Constants
|
|
/******************************************************************************/
|
|
|
|
namespace {
|
|
constexpr auto SEND_INTERVAL = std::chrono::seconds(60);
|
|
}
|
|
|
|
/******************************************************************************/
|
|
// Local Utilities
|
|
/******************************************************************************/
|
|
|
|
namespace {
|
|
template <typename T> bool changeValue(T &stored, T const &update) {
|
|
if (stored == update)
|
|
return false;
|
|
stored = update;
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
/******************************************************************************/
|
|
// Private Implementation
|
|
/******************************************************************************/
|
|
|
|
class SpotClient::impl final : public QUdpSocket {
|
|
Q_OBJECT
|
|
|
|
public:
|
|
// Constructor
|
|
|
|
impl(QString const &name, quint16 const port, QString const &version,
|
|
SpotClient *self)
|
|
: QUdpSocket{self}, self_{self}, name_{name}, port_{port},
|
|
version_{version}, send_{new QTimer{this}} {}
|
|
|
|
// Intended to be called on the thread that starts us, which can be
|
|
// the main thread, if we're not going to be moved to a background
|
|
// thread.
|
|
|
|
void start() {
|
|
// Note that With UDP, error reporting is not guaranteed, which is not
|
|
// the same as a guarantee of no error reporting. Typically, a packet
|
|
// arriving on a port where there is no listener will trigger an ICMP
|
|
// Port Unreachable message back to the sender, and some implementations
|
|
// e.g., Windows, will report that to the application on the next
|
|
// attempt to transmit to the same destination.
|
|
|
|
connect(this, &QUdpSocket::errorOccurred, [this](SocketError const e) {
|
|
if (e != ConnectionRefusedError) {
|
|
Q_EMIT self_->error(errorString());
|
|
}
|
|
});
|
|
|
|
// Start a host lookup for the name we were provided. If it succeeds,
|
|
// use the first address in the list. If it fails, then we've missed
|
|
// what was our one and only shot at this.
|
|
|
|
QHostInfo::lookupHost(name_, this, [this](QHostInfo const &info) {
|
|
if (auto const &list = info.addresses(); !list.isEmpty()) {
|
|
host_ = list.first();
|
|
|
|
qCDebug(spotclient_js8)
|
|
<< "SpotClient Host:" << name_ << host_.toString();
|
|
|
|
bind(host_.protocol() == IPv6Protocol ? QHostAddress::AnyIPv6
|
|
: QHostAddress::AnyIPv4);
|
|
|
|
send_->start(SEND_INTERVAL);
|
|
} else {
|
|
Q_EMIT self_->error(
|
|
QString{"Host lookup failed: %1"}.arg(info.errorString()));
|
|
valid_ = false;
|
|
queue_.clear();
|
|
}
|
|
});
|
|
|
|
// Empty the queue every time our timer goes off.
|
|
|
|
connect(send_, &QTimer::timeout, this, [this]() {
|
|
while (!queue_.isEmpty()) {
|
|
writeDatagram(queue_.dequeue().toJson(), host_, port_);
|
|
}
|
|
sent_++;
|
|
});
|
|
}
|
|
|
|
// Sent as the "BY" value on command and spot sends; contains the call
|
|
// sign and grid of the local station, as set by setLocalStation().
|
|
|
|
QVariantMap by() {
|
|
return {
|
|
{"CALLSIGN", QVariant(call_)},
|
|
{"GRID", QVariant(grid_)},
|
|
};
|
|
}
|
|
|
|
// Data members
|
|
|
|
SpotClient *self_;
|
|
QString name_;
|
|
quint16 port_;
|
|
QString version_;
|
|
QTimer *send_;
|
|
QHostAddress host_;
|
|
QQueue<Message> queue_;
|
|
bool valid_ = true;
|
|
bool once_ = false;
|
|
int sent_ = 0;
|
|
QString call_;
|
|
QString grid_;
|
|
QString info_;
|
|
};
|
|
|
|
/******************************************************************************/
|
|
// Implementation
|
|
/******************************************************************************/
|
|
|
|
#include "SpotClient.moc"
|
|
|
|
// Constructor
|
|
|
|
SpotClient::SpotClient(QString const &name, quint16 const port,
|
|
QString const &version, QObject *parent)
|
|
: QObject{parent}, m_{name, port, version, this} {}
|
|
|
|
void SpotClient::start() {
|
|
if (!m_->once_) {
|
|
m_->once_ = true;
|
|
m_->start();
|
|
}
|
|
}
|
|
|
|
void SpotClient::setLocalStation(QString const &callsign, QString const &grid,
|
|
QString const &info) {
|
|
qCDebug(spotclient_js8) << "SpotClient Set Local Station:" << callsign
|
|
<< "grid:" << grid << "info:" << info;
|
|
|
|
auto const changed = changeValue(m_->call_, callsign) +
|
|
changeValue(m_->grid_, grid) +
|
|
changeValue(m_->info_, info);
|
|
|
|
// Send local information to network on change, or once every 15 minutes.
|
|
|
|
if (m_->valid_ && (changed || m_->sent_ % 15 == 0)) {
|
|
m_->queue_.enqueue({"RX.LOCAL",
|
|
"",
|
|
{{"CALLSIGN", QVariant(callsign)},
|
|
{"GRID", QVariant(grid)},
|
|
{"INFO", QVariant(info)},
|
|
{"VERSION", QVariant(m_->version_)}}});
|
|
}
|
|
}
|
|
|
|
void SpotClient::enqueueCmd(QString const &cmd, QString const &from,
|
|
QString const &to, QString const &relayPath,
|
|
QString const &text, QString const &grid,
|
|
QString const &extra, int const submode,
|
|
int const dial, int const offset, int const snr) {
|
|
if (m_->valid_) {
|
|
m_->queue_.enqueue({"RX.DIRECTED",
|
|
"",
|
|
{{"BY", QVariant(m_->by())},
|
|
{"CMD", QVariant(cmd)},
|
|
{"FROM", QVariant(from)},
|
|
{"TO", QVariant(to)},
|
|
{"PATH", QVariant(relayPath)},
|
|
{"TEXT", QVariant(text)},
|
|
{"GRID", QVariant(grid)},
|
|
{"EXTRA", QVariant(extra)},
|
|
{"FREQ", QVariant(dial + offset)},
|
|
{"DIAL", QVariant(dial)},
|
|
{"OFFSET", QVariant(offset)},
|
|
{"SNR", QVariant(snr)},
|
|
{"SPEED", QVariant(submode)}}});
|
|
}
|
|
}
|
|
|
|
void SpotClient::enqueueSpot(QString const &callsign, QString const &grid,
|
|
int const submode, int const dial,
|
|
int const offset, int const snr) {
|
|
if (m_->valid_) {
|
|
m_->queue_.enqueue({"RX.SPOT",
|
|
"",
|
|
{{"BY", QVariant(m_->by())},
|
|
{"CALLSIGN", QVariant(callsign)},
|
|
{"GRID", QVariant(grid)},
|
|
{"FREQ", QVariant(dial + offset)},
|
|
{"DIAL", QVariant(dial)},
|
|
{"OFFSET", QVariant(offset)},
|
|
{"SNR", QVariant(snr)},
|
|
{"SPEED", QVariant(submode)}}});
|
|
}
|
|
}
|
|
|
|
/******************************************************************************/
|
|
|
|
Q_LOGGING_CATEGORY(spotclient_js8, "spotclient.js8", QtWarningMsg)
|