forked from wb2osz/direwolf
-
Notifications
You must be signed in to change notification settings - Fork 2
/
log.c
368 lines (295 loc) · 9.28 KB
/
log.c
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
//
// This file is part of Dire Wolf, an amateur radio packet TNC.
//
// Copyright (C) 2014, 2015 John Langner, WB2OSZ
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
/*------------------------------------------------------------------
*
* File: log.c
*
* Purpose: Save received packets to a log file.
*
* Description: Rather than saving the raw, sometimes rather cryptic and
* unreadable, format, write separated properties into
* CSV format for easy reading and later processing.
*
*
*------------------------------------------------------------------*/
#include <stdio.h>
#include <time.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include "direwolf.h"
#include "ax25_pad.h"
#include "textcolor.h"
#include "decode_aprs.h"
#include "log.h"
/*
* CSV format needs quotes if value contains comma or quote.
*/
static void quote_for_csv (char *out, const char *in) {
const char *p;
char *q = out;
int need_quote = 0;
for (p = in; *p != '\0'; p++) {
if (*p == ',' || *p == '"') {
need_quote = 1;
break;
}
}
if (need_quote) {
*q++ = '"';
for (p = in; *p != '\0'; p++) {
if (*p == '"') {
*q++ = *p;
}
*q++ = *p;
}
*q++ = '"';
*q = '\0';
}
else {
strcpy (out, in);
}
}
/*------------------------------------------------------------------
*
* Function: log_init
*
* Purpose: Initialization at start of application.
*
* Inputs: path - Path of log file directory.
* Use "." for current directory.
* Empty string disables feature.
*
* Global Out: g_log_dir - Save directory here for later use.
* g_log_fp - File pointer for writing.
* g_open_fname - Name of currently open file.
*
*------------------------------------------------------------------*/
static char g_log_dir[80];
static FILE *g_log_fp;
static char g_open_fname[20];
void log_init (char *path)
{
struct stat st;
strcpy (g_log_dir, "");
g_log_fp = NULL;
strcpy (g_open_fname, "");
if (strlen(path) == 0) {
return;
}
if (stat(path,&st) == 0) {
// Exists, but is it a directory?
if (S_ISDIR(st.st_mode)) {
// Specified directory exists.
strcpy (g_log_dir, path);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Log file location \"%s\" is not a directory.\n", path);
dw_printf ("Using current working directory \".\" instead.\n");
strcpy (g_log_dir, ".");
}
}
else {
// Doesn't exist. Try to create it.
// parent directory must exist.
// We don't create multiple levels like "mkdir -p"
#if __WIN32__
if (_mkdir (path) == 0) {
#else
if (mkdir (path, 0777) == 0) {
#endif
// Success.
text_color_set(DW_COLOR_INFO);
dw_printf ("Log file location \"%s\" has been created.\n", path);
strcpy (g_log_dir, path);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf ("Failed to create log file location \"%s\".\n", path);
dw_printf ("%s\n", strerror(errno));
dw_printf ("Using current working directory \".\" instead.\n");
strcpy (g_log_dir, ".");
}
}
}
/*------------------------------------------------------------------
*
* Function: log_write
*
* Purpose: Save information to log file.
*
* Inputs: chan - Radio channel where heard.
*
* A - Explode information from APRS packet.
*
* pp - Received packet object.
*
* alevel - audio level.
*
* retries - Amount of effort to get a good CRC.
*
*------------------------------------------------------------------*/
void log_write (int chan, decode_aprs_t *A, packet_t pp, alevel_t alevel, retry_t retries)
{
time_t now; // make 'now' a parameter so we can process historical data ???
char fname[20];
struct tm tm;
if (strlen(g_log_dir) == 0) return;
// Generate the file name from current date, UTC.
now = time(NULL);
gmtime_r (&now, &tm);
// Microsoft doesn't recognize %F as equivalent to %Y-%m-%d
strftime (fname, sizeof(fname), "%Y-%m-%d.log", &tm);
// Close current file if name has changed
if (g_log_fp != NULL && strcmp(fname, g_open_fname) != 0) {
log_term ();
}
// Open for append if not already open.
if (g_log_fp == NULL) {
char full_path[120];
struct stat st;
int already_there;
strcpy (full_path, g_log_dir);
#if __WIN32__
strcat (full_path, "\\");
#else
strcat (full_path, "/");
#endif
strcat (full_path, fname);
// See if it already exists.
// This is used later to write a header if it did not exist already.
already_there = stat(full_path,&st) == 0;
text_color_set(DW_COLOR_INFO);
dw_printf("Opening log file \"%s\".\n", fname);
g_log_fp = fopen (full_path, "a");
if (g_log_fp != NULL) {
strcpy (g_open_fname, fname);
}
else {
text_color_set(DW_COLOR_ERROR);
dw_printf("Can't open log file \"%s\" for write.\n", full_path);
dw_printf ("%s\n", strerror(errno));
strcpy (g_open_fname, "");
return;
}
// Write a header suitable for importing into a spreadsheet
// only if this will be the first line.
if ( ! already_there) {
fprintf (g_log_fp, "chan,utime,isotime,source,heard,level,error,dti,name,symbol,latitude,longitude,speed,course,altitude,frequency,offset,tone,system,status,comment\n");
}
}
if (g_log_fp != NULL) {
char itime[24];
char heard[AX25_MAX_ADDR_LEN+1];
int h;
char stemp[256];
char slat[16], slon[16], sspd[12], scse[12], salt[12];
char sfreq[20], soffs[10], stone[10];
char sdti[10];
char sname[24];
char ssymbol[8];
char smfr[60];
char sstatus[40];
char stelemetry[200];
char scomment[256];
char alevel_text[32];
// Microsoft doesn't recognize %T as equivalent to %H:%M:%S
strftime (itime, sizeof(itime), "%Y-%m-%dT%H:%M:%SZ", &tm);
/* Who are we hearing? Original station or digipeater? */
/* Similar code in direwolf.c. Combine into one function? */
strcpy(heard, "");
if (pp != NULL) {
if (ax25_get_num_addr(pp) == 0) {
/* Not AX.25. No station to display below. */
h = -1;
strcpy (heard, "");
}
else {
h = ax25_get_heard(pp);
ax25_get_addr_with_ssid(pp, h, heard);
}
if (h >= AX25_REPEATER_2 &&
strncmp(heard, "WIDE", 4) == 0 &&
isdigit(heard[4]) &&
heard[5] == '\0') {
ax25_get_addr_with_ssid(pp, h-1, heard);
strcat (heard, "?");
}
}
ax25_alevel_to_text (alevel, alevel_text);
// Might need to quote anything that could contain comma or quote.
strcpy(sdti, "");
if (pp != NULL) {
stemp[0] = ax25_get_dti(pp);
stemp[1] = '\0';
quote_for_csv (sdti, stemp);
}
quote_for_csv (sname, (strlen(A->g_name) > 0) ? A->g_name : A->g_src);
stemp[0] = A->g_symbol_table;
stemp[1] = A->g_symbol_code;
stemp[2] = '\0';
quote_for_csv (ssymbol, stemp);
quote_for_csv (smfr, A->g_mfr);
quote_for_csv (sstatus, A->g_mic_e_status);
quote_for_csv (stelemetry, A->g_telemetry);
quote_for_csv (scomment, A->g_comment);
strcpy (slat, ""); if (A->g_lat != G_UNKNOWN) sprintf (slat, "%.6f", A->g_lat);
strcpy (slon, ""); if (A->g_lon != G_UNKNOWN) sprintf (slon, "%.6f", A->g_lon);
strcpy (sspd, ""); if (A->g_speed != G_UNKNOWN) sprintf (sspd, "%.1f", DW_MPH_TO_KNOTS(A->g_speed));
strcpy (scse, ""); if (A->g_course != G_UNKNOWN) sprintf (scse, "%.1f", A->g_course);
strcpy (salt, ""); if (A->g_altitude != G_UNKNOWN) sprintf (salt, "%.1f", DW_FEET_TO_METERS(A->g_altitude));
strcpy (sfreq, ""); if (A->g_freq != G_UNKNOWN) sprintf (sfreq, "%.3f", A->g_freq);
strcpy (soffs, ""); if (A->g_offset != G_UNKNOWN) sprintf (soffs, "%+d", A->g_offset);
strcpy (stone, ""); if (A->g_tone != G_UNKNOWN) sprintf (stone, "%.1f", A->g_tone);
if (A->g_dcs != G_UNKNOWN) sprintf (stone, "D%03o", A->g_dcs);
fprintf (g_log_fp, "%d,%d,%s,%s,%s,%s,%d,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s\n",
chan, (int)now, itime,
A->g_src, heard, alevel_text, (int)retries, sdti,
sname, ssymbol,
slat, slon, sspd, scse, salt,
sfreq, soffs, stone,
smfr, sstatus, stelemetry, scomment);
fflush (g_log_fp);
}
} /* end log_write */
/*------------------------------------------------------------------
*
* Function: log_term
*
* Purpose: Close any open log file.
* Called when exiting or when date changes.
*
*------------------------------------------------------------------*/
void log_term (void)
{
if (g_log_fp != NULL) {
text_color_set(DW_COLOR_INFO);
dw_printf("Closing log file \"%s\".\n", g_open_fname);
fclose (g_log_fp);
g_log_fp = NULL;
strcpy (g_open_fname, "");
}
} /* end log_term */
/* end log.c */