File: SpotClient.cpp

package info (click to toggle)
js8call 2.5.0%2Bds-1
  • links: PTS, VCS
  • area: main
  • in suites: forky
  • size: 24,724 kB
  • sloc: cpp: 562,639; sh: 898; python: 132; ansic: 102; makefile: 4
file content (211 lines) | stat: -rw-r--r-- 7,616 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
#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)