SELKIELogger  1.0.0
ExtractSatInfo.c
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2023 Swansea University
3  *
4  * This file is part of the SELKIELogger suite of tools.
5  *
6  * SELKIELogger is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License as published by the Free
8  * Software Foundation, either version 3 of the License, or (at your option)
9  * any later version.
10  *
11  * SELKIELogger is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  * more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with this SELKIELogger product.
18  * If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include <errno.h>
22 #include <libgen.h>
23 #include <stdbool.h>
24 #include <stdio.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <zlib.h>
28 
29 #include "SELKIELoggerBase.h"
30 #include "SELKIELoggerMP.h"
31 
32 #include "version.h"
33 
48 int main(int argc, char *argv[]) {
49  program_state state = {0};
50  state.verbose = 1;
51 
52  char *outFileName = NULL;
53  bool doGZ = true;
54  bool clobberOutput = false;
55 
56  char *usage =
57  "Usage: %1$s [-v] [-q] [-f] [-Z] [-o outfile] DATFILE\n"
58  "\t-v\tIncrease verbosity\n"
59  "\t-q\tDecrease verbosity\n"
60  "\t-f\tOverwrite existing output files\n"
61  "\t-Z\tDisable gzipped output\n"
62  "\t-o\tWrite output to named file\n"
63  "\nVersion: " GIT_VERSION_STRING "\n"
64  "Output file name will be generated based on input file name and compression flags, unless set by -o option\n";
65 
66  opterr = 0; // Handle errors ourselves
67  int go = 0;
68  bool doUsage = false;
69  while ((go = getopt(argc, argv, "vqfZo:")) != -1) {
70  switch (go) {
71  case 'v':
72  state.verbose++;
73  break;
74  case 'q':
75  state.verbose--;
76  break;
77  case 'f':
78  clobberOutput = true;
79  break;
80  case 'Z':
81  doGZ = false;
82  break;
83  case 'o':
84  if (outFileName) {
85  log_error(
86  &state,
87  "Only a single output file name can be provided");
88  doUsage = true;
89  } else {
90  outFileName = strdup(optarg);
91  }
92  break;
93  case '?':
94  log_error(&state, "Unknown option `-%c'", optopt);
95  doUsage = true;
96  }
97  }
98 
99  // Should be 1 spare arguments: The file to convert
100  if (argc - optind != 1) {
101  log_error(&state, "Invalid arguments");
102  doUsage = true;
103  }
104 
105  if (doUsage) {
106  fprintf(stderr, usage, argv[0]);
107  free(outFileName);
108  destroy_program_state(&state);
109  return -1;
110  }
111 
112  char *inFileName = strdup(argv[optind]);
113  FILE *inFile = fopen(inFileName, "rb");
114  if (inFile == NULL) {
115  log_error(&state, "Unable to open input file");
116  free(outFileName);
117  free(inFileName);
118  destroy_program_state(&state);
119  return -1;
120  }
121 
122  if (outFileName == NULL) {
123  // Work out output file name
124  // Split into base and dirnames so that we're don't accidentally split the
125  // path on a .
126  char *inF1 = strdup(inFileName);
127  char *inF2 = strdup(inFileName);
128  char *dn = dirname(inF1);
129  char *bn = basename(inF2);
130 
131  // Find last . in file name, if any
132  char *ep = strrchr(bn, '.');
133 
134  // If no ., use full basename length, else use length to .
135  int bnl = 0;
136  if (ep == NULL) {
137  bnl = strlen(bn);
138  } else {
139  bnl = ep - bn;
140  }
141  // New basename is old basename up to . (or end, if absent)
142  char *nbn = calloc(bnl + 1, sizeof(char));
143  strncpy(nbn, bn, bnl);
144  if (doGZ) {
145  if (asprintf(&outFileName, "%s/%s.satinfo.csv.gz", dn, nbn) <= 0) {
146  free(nbn);
147  free(inF1);
148  free(inF2);
149  fclose(inFile);
150  free(outFileName);
151  free(inFileName);
152  destroy_program_state(&state);
153  return -1;
154  }
155  } else {
156  if (asprintf(&outFileName, "%s/%s.satinfo.csv", dn, nbn) <= 0) {
157  free(nbn);
158  free(inF1);
159  free(inF2);
160  fclose(inFile);
161  free(outFileName);
162  free(inFileName);
163  destroy_program_state(&state);
164  return -1;
165  }
166  }
167  free(nbn);
168  free(inF1);
169  free(inF2);
170  }
171 
172  errno = 0;
173  char fmode[5] = {'w', 'b', 0, 0};
174 
175  if (doGZ) {
176  if (clobberOutput) {
177  // wb7: Write, binary, comp. level 7
178  fmode[2] = 7;
179  } else {
180  // wbx7: Create, binary, comp. level 7
181  fmode[2] = 'x';
182  fmode[3] = 7;
183  }
184  } else {
185  if (clobberOutput) {
186  // wbT: Write, binary, do not gzip
187  fmode[2] = 'T';
188  } else {
189  // wbxT: Create, binary, do not gzip
190  fmode[2] = 'x';
191  fmode[3] = 'T';
192  }
193  }
194 
195  // Use zib functions, but in transparent mode if not compressing
196  // Avoids wrapping every write function with doGZ checks
197  gzFile outFile = gzopen(outFileName, fmode);
198  if (outFile == NULL) {
199  log_error(&state, "Unable to open output file");
200  log_error(&state, "%s ", strerror(errno));
201  fclose(inFile);
202  free(outFileName);
203  free(inFileName);
204  destroy_program_state(&state);
205  return -1;
206  }
207  log_info(&state, 1, "Writing %s output to %s", doGZ ? "compressed" : "uncompressed",
208  outFileName);
209  free(outFileName);
210  outFileName = NULL;
211 
212  state.started = 1;
213  int msgCount = 0;
214  struct stat inStat = {0};
215  if (fstat(fileno(inFile), &inStat) != 0) {
216  log_error(&state, "Unable to get input file status: %s", strerror(errno));
217  free(inFileName);
218  fclose(inFile);
219  gzclose(outFile);
220  destroy_program_state(&state);
221  return -1;
222  }
223  long inSize = inStat.st_size;
224  long inPos = 0;
225  int progress = 0;
226  if (inSize >= 1E9) {
227  log_info(&state, 1, "Reading %.2fGB of data from %s", inSize / 1.0E9, inFileName);
228  } else if (inSize >= 1E6) {
229  log_info(&state, 1, "Reading %.2fMB of data from %s", inSize / 1.0E6, inFileName);
230  } else if (inSize >= 1E3) {
231  log_info(&state, 1, "Reading %.2fkB of data from %s", inSize / 1.0E3, inFileName);
232  } else {
233  log_info(&state, 1, "Reading %ld bytes of data from %s", inSize, inFileName);
234  }
235  free(inFileName);
236  inFileName = NULL;
237 
238  char *GNSS[] = {"GPS", "SBAS", "Galileo", "BeiDou", "IMES", "QZSS", "GLONASS"};
239  gzprintf(outFile,
240  "TOW,Source,GNSS,SatID,SNR,Elevation,Azimuth,Residual,Quality,SatUsed\n");
241  while (!(feof(inFile))) {
242  // Read message from data file
243  msg_t tmp = {0};
244  if (!mp_readMessage(fileno(inFile), &tmp)) {
245  if (tmp.data.value == 0xAA || tmp.data.value == 0xEE) {
246  log_error(&state,
247  "Error reading messages from file (Code: 0x%52x)\n",
248  (uint8_t)tmp.data.value);
249  }
250  if (tmp.data.value == 0xFD) {
251  // No more data, exit cleanly
252  log_info(&state, 1, "End of file reached");
253  }
254  break;
255  }
256  if (tmp.source >= SLSOURCE_GPS && tmp.source < (SLSOURCE_GPS + 0x10)) {
257  const uint8_t *data = tmp.data.bytes;
258  // Only interested in raw messages from GPS
259  // Only looking for UBX 01:35 messages
260  if (tmp.type == 0x03 && (data[0] == 0xb5 && data[1] == 0x62 &&
261  data[2] == 0x01 && data[3] == 0x35)) {
262  const uint32_t tow = data[6] + (data[7] << 8) + (data[8] << 16) +
263  (data[9] << 24);
264  const uint8_t numSats = data[11];
265 
266  for (int i = 0; i < numSats; i++) {
267  const uint8_t gnssID = data[14 + 12 * i];
268  const uint8_t satID = data[15 + 12 * i];
269  const uint8_t SNR = data[16 + 12 * i];
270  const int8_t elev = data[17 + 12 * i];
271  const int16_t azi =
272  data[18 + 12 * i] + (data[19 + 12 * i] << 8);
273  const int16_t res =
274  (data[20 + 12 * i] + (data[21 + 12 * i] << 8)) /
275  10;
276  const uint8_t qual = data[22 + 12 * i] & 0x07;
277  const uint8_t inUse = (data[22 + 12 * i] & 0x08) / 0x08;
278  gzprintf(outFile, "%u,%02X,%s,%u,%u,%d,%d,%d,%u,%u\n", tow,
279  tmp.source, GNSS[gnssID], satID, SNR, elev, azi,
280  res, qual, inUse);
281  }
282  }
283  msgCount++;
284  }
285  msg_destroy(&tmp);
286  inPos = ftell(inFile);
287  if (((((1.0 * inPos) / inSize) * 100) - progress) >= 5) {
288  progress = (((1.0 * inPos) / inSize) * 100);
289  log_info(&state, 2, "Progress: %d%% (%ld / %ld)", progress, inPos, inSize);
290  }
291  }
292  fclose(inFile);
293  gzclose(outFile);
294 
295  log_info(&state, 1, "%d messages processed", msgCount);
296  destroy_program_state(&state);
297  return 0;
298 }
int main(int argc, char *argv[])
int msgCount[PGN_MAX]
Definition: N2KClassify.c:51
void msg_destroy(msg_t *msg)
Destroy a message.
Definition: messages.c:349
bool mp_readMessage(int handle, msg_t *out)
Static wrapper around mp_readMessage_buf.
Definition: MPSerial.c:66
#define SLSOURCE_GPS
GPS (or other satellite navigation) sources.
Definition: sources.h:62
void destroy_program_state(program_state *s)
Cleanly destroy program state.
Definition: logging.c:207
void log_info(const program_state *s, const int level, const char *format,...)
Output formatted information message at a given level.
Definition: logging.c:125
void log_error(const program_state *s, const char *format,...)
Output formatted error message.
Definition: logging.c:47
Queuable message.
Definition: messages.h:71
msg_data_t data
Embedded data.
Definition: messages.h:76
uint8_t type
Message type. Common types to be documented.
Definition: messages.h:73
uint8_t source
Maps to a specific sensor unit or data source.
Definition: messages.h:72
Program state and logging information.
Definition: logging.h:40
int verbose
Current log verbosity (console output)
Definition: logging.h:43
bool started
Indicates startup completed.
Definition: logging.h:41
float value
Generic numerical data.
Definition: messages.h:46
uint8_t * bytes
Our "raw" binary type.
Definition: messages.h:48
#define GIT_VERSION_STRING
Git version description.
Definition: version.h.in:13