-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparsespot.cpp
More file actions
415 lines (343 loc) · 13.3 KB
/
parsespot.cpp
File metadata and controls
415 lines (343 loc) · 13.3 KB
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
/* parse several spot formats. all call dxcLog() to save error messages.
*/
#include "HamClock.h"
/*********************************************************************************************************
*
* generic cluster server
* DX de KD0AA: 18100.0 JR1FYS FT8 LOUD in FL! 2156Z EL98
*
*********************************************************************************************************/
/* parse the given line into a new spot record.
* if ok fill in spot and return true else dxcLog and return false.
*/
bool crackClusterSpot (char line[], DXSpot &spot)
{
// fresh
spot = {};
// DX de KD0AA: 18100.0 JR1FYS FT8 LOUD in FL! 2156Z EL98
if (sscanf (line, "DX de %11[^ :]: %f %11s", spot.rx_call, &spot.kHz, spot.tx_call) != 3) {
// already logged
return (false);
}
// looks good so far, reach over to extract time and perform strict sanity check
int hr = 10*(line[70]-'0') + (line[71]-'0');
int mn = 10*(line[72]-'0') + (line[73]-'0');
if (!isdigit(line[70]) || !isdigit(line[71]) || !isdigit(line[72]) || !isdigit(line[73])
|| hr < 0 || hr > 59 || mn < 0 || mn > 60) {
dxcLog ("bogus time in spot: '%4.4s'\n", &line[70]);
return (false);
}
// spot does not include date so assume same as current
tmElements_t tm;
time_t now = myNow();
breakTime (now, tm);
tm.Hour = hr;
tm.Minute = mn;
spot.spotted = makeTime (tm);
// spot does not include mode so try to set based on freq
quietStrncpy (spot.mode, findHamMode (spot.kHz), sizeof(spot.mode));
// accommodate future from roundoff
if (spot.spotted > now) {
dxcLog ("%s %g: correcting future time %02d:%02d %ld > %ld\n", spot.tx_call, spot.kHz,
tm.Hour, tm.Minute, (long)spot.spotted, (long)(spot.spotted-SECSPERDAY));
spot.spotted -= SECSPERDAY;
}
// find locations and grids
bool ok = call2LL (spot.rx_call, spot.rx_ll) && call2LL (spot.tx_call, spot.tx_ll);
if (ok) {
ll2maidenhead (spot.rx_grid, spot.rx_ll);
ll2maidenhead (spot.tx_grid, spot.tx_ll);
}
// find DXCC
if (!call2DXCC (spot.tx_call, spot.tx_dxcc)) {
dxcLog ("no DXCC for %s\n", spot.tx_call);
return (false);
}
if (!call2DXCC (spot.rx_call, spot.rx_dxcc)) {
dxcLog ("no DXCC for %s\n", spot.rx_call);
return (false);
}
// return whether we had any luck
return (ok);
}
/*********************************************************************************************************
*
* WSJT-X
* https://sourceforge.net/p/wsjt/wsjtx/ci/master/tree/Network/NetworkMessage.hpp
* We don't enforce the Status ID to be WSJT-X so this may also work for, say, JTCluster.
*
*********************************************************************************************************/
/* given address of pointer into a WSJT-X message, extract bool and advance pointer to next field.
*/
static bool wsjtx_bool (uint8_t **bpp)
{
bool x = **bpp > 0;
*bpp += 1;
return (x);
}
/* given address of pointer into a WSJT-X message, extract uint32_t and advance pointer to next field.
* bytes are big-endian order.
*/
static uint32_t wsjtx_quint32 (uint8_t **bpp)
{
uint32_t x = ((*bpp)[0] << 24) | ((*bpp)[1] << 16) | ((*bpp)[2] << 8) | (*bpp)[3];
*bpp += 4;
return (x);
}
/* given address of pointer into a WSJT-X message, extract utf8 string and advance pointer to next field.
* N.B. returned string points into message so will only be valid as long as message memory is valid.
*/
static char *wsjtx_utf8 (uint8_t **bpp)
{
// save begining of this packet entry
uint8_t *bp0 = *bpp;
// decode length
uint32_t len = wsjtx_quint32 (bpp);
// check for flag meaning null length string same as 0 for our purposes
if (len == 0xffffffff)
len = 0;
// advance packet pointer over contents
*bpp += len;
// copy contents to front, overlaying length, to make room to add EOS
memmove (bp0, bp0+4, len);
bp0[len] = '\0';
// dxcLog ("utf8 %d '%s'\n", len, (char*)bp0);
// return address of content now within packet
return ((char *)bp0);
}
/* given address of pointer into a WSJT-X message, extract double and advance pointer to next field.
*/
static uint64_t wsjtx_quint64 (uint8_t **bpp)
{
uint64_t x;
x = ((uint64_t)(wsjtx_quint32(bpp))) << 32;
x |= wsjtx_quint32 (bpp);
return (x);
}
/* return whether the given packet contains a WSJT-X Status packet.
* if true, leave with *bpp positioned just after ID.
*/
bool wsjtxIsStatusMsg (uint8_t **bpp)
{
// crack magic header
uint32_t magic = wsjtx_quint32 (bpp);
// dxcLog ("magic 0x%x\n", magic); // RBF
if (magic != 0xADBCCBDA) {
// dxcLog ("packet received but wrong magic\n"); // RBF
return (false);
}
// crack and ignore the max schema value
(void) wsjtx_quint32 (bpp); // skip past max schema
// crack message type. we only care about Status messages which are type 1
uint32_t msgtype = wsjtx_quint32 (bpp);
// dxcLog ("type %d\n", msgtype);
if (msgtype != 1)
return (false);
// if we get this far assume packet is what we want.
// crack ID but ignore to allow compatibility with clones.
volatile char *id = wsjtx_utf8 (bpp);
(void)id; // lint
// dxcLog ("id '%s'\n", id);
// if (strcmp ("WSJT-X", id) != 0)
// return (false);
// ok!
return (true);
}
/* parse and process WSJT-X message known to be of type Status.
* if ok fill in spot and return true else dxcLog and return false.
*/
bool wsjtxParseStatusMsg (uint8_t *msg, DXSpot &spot)
{
// dxcLog ("Parsing status\n");
uint8_t **bpp = &msg; // walk along msg
// crack remaining fields down to grid
uint32_t hz = wsjtx_quint64 (bpp); // capture freq
(void) wsjtx_utf8 (bpp); // skip over mode
char *dx_call = wsjtx_utf8 (bpp); // capture DX call
(void) wsjtx_utf8 (bpp); // skip over report
(void) wsjtx_utf8 (bpp); // skip over Tx mode
(void) wsjtx_bool (bpp); // skip over Tx enabled flag
(void) wsjtx_bool (bpp); // skip over transmitting flag
(void) wsjtx_bool (bpp); // skip over decoding flag
(void) wsjtx_quint32 (bpp); // skip over Rx DF -- not always correct
(void) wsjtx_quint32 (bpp); // skip over Tx DF
char *de_call = wsjtx_utf8 (bpp); // capture DE call
char *de_grid = wsjtx_utf8 (bpp); // capture DE grid
char *dx_grid = wsjtx_utf8 (bpp); // capture DX grid
// dxcLog ("WSJT: %7d %s %s %s %s\n", hz, de_call, de_grid, dx_call, dx_grid);
// ignore if frequency is clearly bogus (which I have seen)
if (hz == 0) {
dxcLog ("%s invalid frequency: %u\n", de_call, hz);
return (false);
}
// get each ll from grids
LatLong ll_de, ll_dx;
if (!maidenhead2ll (ll_de, de_grid)) {
dxcLog ("%s invalid or missing DE grid: %s\n", de_call, de_grid);
return (false);
}
if (!maidenhead2ll (ll_dx, dx_grid)) {
dxcLog ("%s invalid or missing DX grid: %s\n", dx_call, dx_grid);
return (false);
}
// looks good, create new record
spot = {};
strncpy (spot.tx_call, dx_call, sizeof(spot.tx_call)-1); // preserve EOS
strncpy (spot.rx_call, de_call, sizeof(spot.rx_call)-1); // preserve EOS
strncpy (spot.tx_grid, dx_grid, sizeof(spot.tx_grid)-1); // preserve EOS
strncpy (spot.rx_grid, de_grid, sizeof(spot.rx_grid)-1); // preserve EOS
spot.kHz = hz*1e-3F;
spot.rx_ll = ll_de;
spot.tx_ll = ll_dx;
// time is now
spot.spotted = myNow();
// good!
return (true);
}
/*********************************************************************************************************
*
* simple XML utilities.
*
*********************************************************************************************************/
/* find <key>content</key> within xml[].
* return whether successful.
*/
static bool extractXMLElementContent (const char xml[], const char key[], char content[], size_t c_len)
{
enum {
XMLS_LOOK4_KEY,
XMLS_IN_KEY,
XMLS_IN_CONTENT,
} state = XMLS_LOOK4_KEY;
const char *key_candidate = NULL;
const char *key_content = NULL;
const size_t klen = strlen(key);
for (; *xml != '\0'; xml++) {
switch (state) {
case XMLS_LOOK4_KEY:
if (*xml == '<') {
key_candidate = NULL;
state = XMLS_IN_KEY;
}
break;
case XMLS_IN_KEY:
if (key_candidate == NULL)
key_candidate = xml; // first char
if (*xml == '>') {
if (strncasecmp (key, key_candidate, klen) == 0) {
key_content = NULL;
state = XMLS_IN_CONTENT;
} else {
state = XMLS_LOOK4_KEY;
}
}
break;
case XMLS_IN_CONTENT:
if (key_content == NULL)
key_content = xml; // first char
if (*xml == '<') {
int con_len = xml - key_content;
snprintf (content, c_len, "%.*s", con_len, key_content);
return (true);
}
break;
}
}
return (false);
}
/*********************************************************************************************************
*
* XML such as N1MM or DXLog
* https://n1mmwp.hamdocs.com/appendices/external-udp-broadcasts/
* https://dxlog.net/docs/index.php/Additional_Information#Spot
*
*********************************************************************************************************/
bool crackXMLSpot (const char xml[], DXSpot &spot)
{
// fresh
spot = {};
char buf[100];
char *endptr;
// must be a <spot> record action add
bool have_spot = strcistr (xml,"<spot>") != NULL; // not always closed??
bool action_add = extractXMLElementContent (xml, "action", buf, sizeof(buf)) && !strcasecmp (buf, "add");
if (!have_spot || !action_add)
return (false);
if (!extractXMLElementContent (xml, "spottercall", spot.rx_call, sizeof(spot.rx_call))) {
dxcLog ("UDP: no spottercall\n");
return (false);
}
if (!extractXMLElementContent (xml, "dxcall", spot.tx_call, sizeof(spot.tx_call))) {
dxcLog ("UDP: no dxcall\n");
return (false);
}
if (!extractXMLElementContent (xml, "frequency", buf, sizeof(buf))) {
dxcLog ("UDP: no frequency for %s\n", spot.tx_call);
return (false);
}
if ( ((spot.kHz = strtod (buf, &endptr)) == 0 && endptr == buf) || findHamBand(spot.kHz) == HAMBAND_NONE){
dxcLog ("UDP: bogus frequency %s for %s\n", buf, spot.tx_call);
return (false);
}
if (!extractXMLElementContent (xml, "mode", spot.mode, sizeof(spot.mode))) {
quietStrncpy (spot.mode, findHamMode (spot.kHz), sizeof(spot.mode));
dxcLog ("UDP: inferring mode for %s %s\n", spot.tx_call, spot.mode);
}
if (!extractXMLElementContent (xml, "timestamp", buf, sizeof(buf))) {
dxcLog ("UDP: no timestamp for %s\n", spot.tx_call);
return (false);
}
if ((spot.spotted = crackISO8601 (buf)) == 0) {
dxcLog ("UDP: bogus timestamp %s for %s\n", buf, spot.tx_call);
return (false);
}
if (!call2LL (spot.tx_call, spot.tx_ll)) {
dxcLog ("UDP: could not find LL for %s\n", spot.tx_call);
return (false);
}
ll2maidenhead (spot.tx_grid, spot.tx_ll);
if (!call2DXCC (spot.tx_call, spot.tx_dxcc)) {
dxcLog ("UDP: no DXCC for %s\n", spot.tx_call);
return (false);
}
if (!call2LL (spot.rx_call, spot.rx_ll)) {
dxcLog ("UDP: could find LL for %s\n", spot.rx_call);
return (false);
}
ll2maidenhead (spot.rx_grid, spot.rx_ll);
if (!call2DXCC (spot.rx_call, spot.rx_dxcc)) {
dxcLog ("UDP: no DXCC for %s\n", spot.rx_call);
return (false);
}
// yes!
return (true);
}
/*********************************************************************************************************
*
* ADIF such as Log4OM
* https://www.log4om.com/l4ong/usermanual/Log4OMNG_ENU.pdf
*
*********************************************************************************************************/
/* crack the given string presumed to contain and ADIF message.
* if ok fill in spot and return true else dxcLog and return false.
*/
bool crackADIFSpot (const char string[], DXSpot &spot)
{
GenReader gr(string, strlen (string));
DXSpot *spots = NULL;
int n_bad;
int n_good = readADIFFile (gr, spots, false, n_bad);
bool ok = false;
if (n_good > 0) {
if (n_good > 1)
dxcLog ("UDP: using only first of %d ADIF records\n", n_good);
spot = spots[0];
ok = true;
} else {
if (n_bad > 0)
dxcLog ("UDP: packet contained %d bad ADIF records\n", n_bad);
}
if (spots)
free (spots);
return (ok);
}