ubloxcfg
u-blox 9 configuration helpers
cfgtool.c
1 // u-blox 9 positioning receivers configuration tool
2 //
3 // Copyright (c) 2020 Philippe Kehl (flipflip at oinkzwurgl dot org),
4 // https://oinkzwurgl.org/hacking/ubloxcfg
5 //
6 // This program is free software: you can redistribute it and/or modify it under the terms of the
7 // GNU General Public License as published by the Free Software Foundation, either version 3 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
11 // even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 // See the GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License along with this program.
15 // If not, see <https://www.gnu.org/licenses/>.
16 
17 #include <stddef.h>
18 #include <inttypes.h>
19 #include <string.h>
20 #include <ctype.h>
21 #include <unistd.h>
22 #include <errno.h>
23 #include <stdarg.h>
24 #include <fcntl.h>
25 
26 #include "ff_debug.h"
27 #include "ff_stuff.h"
28 
29 #include "cfgtool_util.h"
30 #include "cfgtool_cfg2ubx.h"
31 #include "cfgtool_rx2cfg.h"
32 #include "cfgtool_cfg2rx.h"
33 #include "cfgtool_dump.h"
34 #include "cfgtool_uc2cfg.h"
35 #include "cfgtool_cfginfo.h"
36 #include "cfgtool_parse.h"
37 #include "cfgtool_reset.h"
38 #include "cfgtool_status.h"
39 #include "config.h"
40 
41 /* ****************************************************************************************************************** */
42 
43 typedef struct CMD_s
44 {
45  const char *name;
46  bool need_i;
47  bool need_o;
48  bool need_p;
49  bool need_l;
50  bool need_r;
51  bool may_r;
52  bool may_n;
53  const char *info;
54  const char *(*help)(void);
55  int (*run)(void);
56 
57 } CMD_t;
58 
59 typedef struct ARGS_s
60 {
61  const CMD_t *cmd;
62 
63  const char *inName;
64  FILE *inFile;
65 
66  const char *outName;
67  FILE *outFile;
68  bool outOverwrite;
69 
70  const char *rxPort;
71  const char *cfgLayer;
72  const char *resetType;
73  bool useUnknown;
74  bool extraInfo;
75  bool applyConfig;
76  bool noProbe;
77 
78 } ARGS_t;
79 
80 static ARGS_t gArgs;
81 
82 static int rx2cfg(void) { return rx2cfgRun( gArgs.rxPort, gArgs.cfgLayer, gArgs.useUnknown); }
83 static int rx2list(void) { return rx2listRun(gArgs.rxPort, gArgs.cfgLayer, gArgs.useUnknown); }
84 static int cfg2rx(void) { return cfg2rxRun( gArgs.rxPort, gArgs.cfgLayer, gArgs.resetType, gArgs.applyConfig); }
85 static int cfg2ubx(void) { return cfg2ubxRun(gArgs.cfgLayer, gArgs.extraInfo); }
86 static int cfg2hex(void) { return cfg2hexRun(gArgs.cfgLayer, gArgs.extraInfo); }
87 static int cfg2c(void) { return cfg2cRun( gArgs.cfgLayer, gArgs.extraInfo); }
88 static int uc2cfg(void) { return uc2cfgRun(); }
89 static int cfginfo(void) { return cfginfoRun(); }
90 static int dump(void) { return dumpRun( gArgs.rxPort, gArgs.extraInfo, gArgs.noProbe); }
91 static int parse(void) { return parseRun( gArgs.extraInfo); }
92 static int reset(void) { return resetRun( gArgs.rxPort, gArgs.resetType); }
93 static int status(void) { return statusRun( gArgs.rxPort, gArgs.extraInfo, gArgs.noProbe); }
94 
95 const CMD_t kCmds[] =
96 {
97  { .name = "cfg2rx", .info = "Configure a receiver from a configuration file", .help = cfg2rxHelp, .run = cfg2rx,
98  .need_i = true, .need_o = false, .need_p = true, .need_l = true, .may_r = true, .may_n = false },
99 
100  { .name = "rx2cfg", .info = "Create configuration file from config in a receiver", .help = rx2cfgHelp, .run = rx2cfg,
101  .need_i = false, .need_o = true, .need_p = true, .need_l = true, .need_r = false, .may_n = false },
102 
103  { .name = "rx2list", .info = "Like rx2cfg but output a flat list of key-value pairs", .help = rx2listHelp, .run = rx2list,
104  .need_i = false, .need_o = true, .need_p = true, .need_l = true, .need_r = false, .may_n = false },
105 
106  { .name = "cfg2ubx", .info = "Convert config file to UBX-CFG-VALSET message(s)", .help = cfg2ubxHelp, .run = cfg2ubx,
107  .need_i = true, .need_o = true, .need_p = false, .need_l = true, .need_r = false, .may_n = false },
108 
109  { .name = "cfg2hex", .info = "Like cfg2ubx but prints a hex dump of the message(s)", .help = NULL, .run = cfg2hex,
110  .need_i = true, .need_o = true, .need_p = false, .need_l = true, .need_r = false, .may_n = false },
111 
112  { .name = "cfg2c", .info = "Like cfg2ubx but prints a c source code of the message(s)", .help = NULL, .run = cfg2c,
113  .need_i = true, .need_o = true, .need_p = false, .need_l = true, .need_r = false, .may_n = false },
114 
115  { .name = "uc2cfg", .info = "Convert u-center config file to sane config file", .help = uc2cfgHelp, .run = uc2cfg,
116  .need_i = true, .need_o = true, .need_p = false, .need_l = false, .need_r = false, .may_n = false },
117 
118  { .name = "cfginfo", .info = "Print information about known configuration items etc.", .help = cfginfoHelp, .run = cfginfo,
119  .need_i = false, .need_o = true, .need_p = false, .need_l = false, .need_r = false, .may_n = false },
120 
121  { .name = "dump", .info = "Connects to receiver and prints received message frames", .help = dumpHelp, .run = dump,
122  .need_i = false, .need_o = true, .need_p = true, .need_l = false, .need_r = false, .may_n = true },
123 
124  { .name = "parse", .info = "Parse file and output message frames", .help = parseHelp, .run = parse,
125  .need_i = true, .need_o = true, .need_p = false, .need_l = false, .need_r = false, .may_n = false },
126 
127  { .name = "reset", .info = "Reset receiver", .help = resetHelp, .run = reset,
128  .need_i = false, .need_o = false, .need_p = true, .need_l = false, .need_r = true, .may_n = false },
129 
130  { .name = "status", .info = "Connects to receiver and prints status", .help = statusHelp, .run = status,
131  .need_i = false, .need_o = true, .need_p = true, .need_l = false, .need_r = false, .may_n = true },
132 
133 };
134 
135 const char * const kTitleStr =
136  // -----------------------------------------------------------------------------
137  "cfgtool "CONFIG_VERSION" -- u-blox 9 configuration interface tool\n"
138  "\n";
139 
140 const char * const kCopyrightStr =
141  // -----------------------------------------------------------------------------
142  " Copyright (c) 2020 Philippe Kehl (flipflip at oinkzwurgl dot org)\n"
143  " https://oinkzwurgl.org/hacking/ubloxcfg/\n"
144  "\n";
145 
146 const char * const kVersionStr =
147  // -----------------------------------------------------------------------------
148  " version: "CONFIG_VERSION", git hash: "CONFIG_GITHASH", build date and time: "CONFIG_DATE" "CONFIG_TIME"\n"
149  "\n";
150 
151 const char * const kHelpStr =
152  // -----------------------------------------------------------------------------
153  "Usage:\n"
154  "\n"
155  " cfgtool -h | -H | -h <command> | -V\n"
156  " cfgtool [-v] [-q] [...] <command>\n"
157  "\n"
158  // -----------------------------------------------------------------------------
159  "Where:\n"
160  "\n"
161  " -h Prints help summary\n"
162  " -H Prints full help\n"
163  " -h <command> Prints help for a command\n"
164  " -V Displays version and license information\n"
165  "\n"
166  " -v / -q Increases / decreases verbosity\n"
167  "\n"
168  " <command> Command\n"
169  "\n"
170  " And depending on the <command>:\n"
171  "\n"
172  " -i <infile> Input file (default: '-', i.e. standard input)\n"
173  " -o <outfile> Output file (default: '-', i.e. standard output)\n"
174  " -y Overwrite output file if it already exists\n"
175  " -p <serial> Serial port where the receiver is connected:\n"
176  " [ser://]<device>[:<baudrate>]\n"
177  " tcp://<host>:<port>[:<baudrate>]\n"
178  " telnet://<host>:<port>[:<baudrate>]\n"
179  " -l <layer(s)> Configuration layer(s) to use:\n"
180  " RAM, BBR, Flash, Default\n"
181  " -r <reset> Reset mode to use to reset the receiver:\n"
182  " soft, hard, hot, warm, cold, default, factory,\n"
183  " stop, start, gnss\n"
184  " -u Use unknown (undocumented) configuation items\n"
185  " -x Output extra information (comments, hex dumps, ...)\n"
186  " -a Activate configuration after storing\n"
187  " -n Do not probe/autobaud receiver, use passive reading only\n"
188  "\n"
189  // -----------------------------------------------------------------------------
190  " Available <commands>s:\n"
191  "\n";
192 
193 const char * const kLicenseHelp =
194  // -----------------------------------------------------------------------------
195  "License:\n"
196  "\n"
197  " This program is free software: you can redistribute it and/or modify it\n"
198  " under the terms of the GNU General Public License as published by the Free\n"
199  " Software Foundation, either version 3 of the License, or (at your option)\n"
200  " any later version.\n"
201  "\n"
202  " This program is distributed in the hope that it will be useful, but WITHOUT\n"
203  " ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n"
204  " FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for\n"
205  " more details.\n"
206  "\n"
207  " You should have received a copy of the GNU General Public License along with\n"
208  " this program. If not, see https://www.gnu.org/licenses/.\n"
209  "\n"
210  "Third-party code:\n"
211  "\n"
212  " This program includes CRC24Q routines from the GPSD project, under a\n"
213  " BSD-2-Clause license. See source code or https://gitlab.com/gpsd/.\n"
214  "\n";
215 
216 const char * const kPortHelp =
217  "Serial ports:\n"
218  "\n"
219  " Local serial ports: [ser://]<device>[@<baudrate>], where:\n"
220  "\n"
221 #ifdef _WIN
222  " <device> COM1, COM23, etc.\n"
223 #else
224  " <device> /dev/ttyUSB0, /dev/ttyACM1, /dev/serial/..., etc.\n"
225 #endif
226  " <baudrate> Baudrate (optional)\n"
227  "\n"
228  " Note that 'ser://' is the default and can be omitted. If no <baudrate>\n"
229  " is specified, it is be automatically detected. That is, '-p <device>'\n"
230  " works in most cases.\n"
231  "\n"
232 #ifndef _WIN
233  " It is recommended to use /dev/serial/by-path/... device names for USB\n"
234  " (CDC ACM) connections as the names remain after a hardware reset and\n"
235  " USB re-enumeration. See the 'reset' command.\n"
236  "\n"
237 #endif
238  " Raw TCP/IP ports: tcp://<addr>:<port>, where:\n"
239  "\n"
240  " <addr> Hostname or IP address\n"
241  " <port> Port number\n"
242  "\n"
243  " Telnet TCP/IP ports: telnet://<addr>:<port>[@<baudrate>], where\n"
244  "\n"
245  " <addr> Hostname or IP address\n"
246  " <port> Port number\n"
247  " <baudrate> Baudrate (optional)\n"
248  "\n"
249  " This uses telnet (RFC854, etc.) in-band control using com port control\n"
250  " option (RFC2217) to set the baudrate on the remote serial port. This\n"
251  " works for example with ser2net(8) and some hardware RS232 servers.\n"
252  "\n"
253  " A minimal ser2net command line that should work is:\n"
254  " ser2net -d -C \"12345:telnet:0:/dev/ttyUSB0: remctl\"\n"
255  " This should allow using '-p telnet://localhost:12345'.\n"
256  "\n";
257 
258 const char * const kLayersHelp =
259  // -----------------------------------------------------------------------------
260  "Configuration layers:\n"
261  "\n"
262  " RAM Current(ly used) configuration, has all items\n"
263  " BBR Battery-backed RAM item storage, may have no items at all\n"
264  " Flash Flash (if available) items storage, may have no items at all\n"
265  " Default Default configuration, has all items\n"
266  "\n";
267 
268 const char * const kExitHelp =
269  "Exit status:\n"
270  "\n"
271  " Command successful: "STRINGIFY(EXIT_SUCCESS)"\n"
272  " Bad command-line arguments: "STRINGIFY(EXIT_BADARGS)"\n"
273  " Detecting receiver failed: "STRINGIFY(EXIT_RXFAIL)"\n"
274  " No data from receiver: "STRINGIFY(EXIT_RXNODATA)"\n"
275  " Unspecified error: "STRINGIFY(EXIT_OTHERFAIL)"\n"
276  "\n";
277 
278 const char * const kGreeting =
279  "Happy hacking! :-)\n"
280  "\n";
281 
282 #define _ARGS_STR(_flag_, _var_) \
283  else if (strcmp(_flag_, argv[argIx]) == 0) \
284  { \
285  if ((argIx + 1) < argc) \
286  { \
287  _var_ = argv[argIx + 1]; \
288  argIx++; \
289  } \
290  else \
291  { \
292  argOk = false; \
293  } \
294  }
295 
296 #define _ARGS_BOOL(_flag_, _var_, _ARGS_) \
297  else if (strcmp(_flag_, argv[argIx]) == 0) \
298  { \
299  _var_ = _ARGS_; \
300  }
301 
302 void printHelp(const bool full)
303 {
304  fputs(kTitleStr, stdout);
305  fputs(kCopyrightStr, stdout);
306  fputs(kHelpStr, stdout);
307  for (int ix = 0; ix < NUMOF(kCmds); ix++)
308  {
309  fprintf(stdout, " %-14s %s\n", kCmds[ix].name, kCmds[ix].info);
310  }
311  fputs("\n", stdout);
312  if (full)
313  {
314  fputs(kLicenseHelp, stdout);
315  fputs(kPortHelp, stdout);
316  fputs(kLayersHelp, stdout);
317  fputs(kExitHelp, stdout);
318  for (int ix = 0; ix < NUMOF(kCmds); ix++)
319  {
320  if (kCmds[ix].help != NULL)
321  {
322  fputs(kCmds[ix].help(), stdout);
323  }
324  }
325  }
326  fputs(kGreeting, stdout);
327 }
328 
329 void printVersion(void)
330 {
331  fputs(kTitleStr, stdout);
332  fputs("Version:\n\n", stdout);
333  fputs(kVersionStr, stdout);
334  fputs("Copyright:\n\n", stdout);
335  fputs(kCopyrightStr, stdout);
336  fputs(kLicenseHelp, stdout);
337  fputs(kGreeting, stdout);
338 }
339 
340 int main(int argc, char **argv)
341 {
342  memset(&gArgs, 0, sizeof(gArgs));
343 
344  DEBUG_CFG_t debugCfg =
345  {
346  .level = DEBUG_LEVEL_PRINT,
347 #ifdef _WIN32
348  .colour = true,
349 #else
350  .colour = isatty(fileno(stderr)) == 1,
351 #endif
352  .mark = NULL,
353  .func = NULL,
354  .arg = NULL,
355  };
356  debugSetup(&debugCfg);
357  bool doHelp = false;
358 
359  for (int argIx = 1; argIx < argc; argIx++)
360  {
361  bool argOk = true;
362  if (strcmp("-h", argv[argIx]) == 0)
363  {
364  doHelp = true;
365  }
366  else if (strcmp("-H", argv[argIx]) == 0)
367  {
368  printHelp(true);
369  exit(EXIT_SUCCESS);
370  }
371  else if (strcmp("-V", argv[argIx]) == 0)
372  {
373  printVersion();
374  exit(EXIT_SUCCESS);
375  }
376  else if (strcmp("-v", argv[argIx]) == 0)
377  {
378  debugCfg.level++;
379  }
380  else if (strcmp("-q", argv[argIx]) == 0)
381  {
382  debugCfg.level--;
383  }
384  _ARGS_STR("-i", gArgs.inName)
385  _ARGS_STR("-o", gArgs.outName)
386  _ARGS_STR("-p", gArgs.rxPort)
387  _ARGS_STR("-l", gArgs.cfgLayer)
388  _ARGS_STR("-r", gArgs.resetType)
389  _ARGS_BOOL("-u", gArgs.useUnknown, true)
390  _ARGS_BOOL("-x", gArgs.extraInfo, true)
391  _ARGS_BOOL("-a", gArgs.applyConfig, true)
392  _ARGS_BOOL("-y", gArgs.outOverwrite, true)
393  _ARGS_BOOL("-n", gArgs.noProbe, true)
394  else if (gArgs.cmd != NULL)
395  {
396  argOk = false;
397  }
398  else
399  {
400  for (int ix = 0; ix < NUMOF(kCmds); ix++)
401  {
402  if (strcmp(kCmds[ix].name, argv[argIx]) == 0)
403  {
404  gArgs.cmd = &kCmds[ix];
405  break;
406  }
407  }
408  if (gArgs.cmd == NULL)
409  {
410  argOk = false;
411  }
412  }
413 
414  if (!argOk)
415  {
416  WARNING("Illegal argument '%s'!", argv[argIx]);
417  gArgs.cmd = NULL;
418  doHelp = false;
419  break;
420  }
421  }
422 
423  if (gArgs.cmd != NULL)
424  {
425  debugCfg.mark = gArgs.cmd->name;
426  }
427  debugSetup(&debugCfg);
428 
429  if (doHelp)
430  {
431  if (gArgs.cmd != NULL)
432  {
433  fputs(kTitleStr, stdout);
434  if (gArgs.cmd->help != NULL)
435  {
436  fputs(gArgs.cmd->help(), stdout);
437  }
438  fputs(kGreeting, stdout);
439  }
440  else
441  {
442  printHelp(false);
443  }
444  exit(EXIT_SUCCESS);
445  }
446 
447  bool res = true;
448  if (gArgs.cmd == NULL)
449  {
450  WARNING("Try '%s -h'.", argv[0]);
451  res = false;;
452  }
453 
454  // Open input file
455  if (res && gArgs.cmd->need_i)
456  {
457  if ( (gArgs.inName == NULL) || ((gArgs.inName[0] == '-') && (gArgs.inName[1] == '\0')) )
458  {
459  gArgs.inName = "-";
460  gArgs.inFile = stdin;
461  DEBUG("input from stdin");
462 #ifndef _WIN32
463  int fd = fileno(gArgs.inFile);
464  const int flags = fcntl(fd, F_GETFL, 0);
465  int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
466  if ( ((flags < 0)) || (r < 0) )
467  {
468  WARNING("Failed setting stdin to non-blocking: %s", strerror(errno));
469  res = false;
470  }
471 #else
472  // FIXME: for windows?
473 #endif
474  }
475  else if (gArgs.inName[0] == '\0')
476  {
477  WARNING("Need '-i <infile>' argument!");
478  res = false;
479  }
480  else
481  {
482  DEBUG("Opening input: %s", gArgs.inName);
483  gArgs.inFile = fopen(gArgs.inName, "r");
484  if (gArgs.inFile == NULL)
485  {
486  WARNING("Failed opening '%s' for reading: %s!\n",
487  gArgs.inName, strerror(errno));
488  res = false;
489  }
490  }
491  }
492  else if ( (gArgs.cmd != NULL) && !gArgs.cmd->need_i && (gArgs.inName != NULL) )
493  {
494  WARNING("Illegal argument '-i %s'!", gArgs.inName);
495  res = false;
496  }
497  ioSetInput(gArgs.inName, gArgs.inFile);
498 
499  // Open output file
500  if (res && gArgs.cmd->need_o)
501  {
502  if ( (gArgs.outName == NULL) || ((gArgs.outName[0] == '-') && (gArgs.outName[1] == '\0')) )
503  {
504  gArgs.outName = "-";
505  gArgs.outFile = stdout;
506  DEBUG("output to stdout");
507  }
508  else if (gArgs.outName[0] == '\0')
509  {
510  WARNING("Need '-o <infile>' argument!");
511  res = false;
512  }
513  else
514  {
515  // Don't open now, ioWriteOutput() will do that
516  }
517  }
518  ioSetOutput(gArgs.outName, gArgs.outFile, gArgs.outOverwrite);
519 
520  // Require -p arg?
521  if ( (gArgs.cmd != NULL) && gArgs.cmd->need_p )
522  {
523  if ( (gArgs.rxPort == NULL) || (gArgs.rxPort[0] == '\0') )
524  {
525  WARNING("Need '-p <port>' argument!");
526  res = false;
527  }
528  }
529  else if ( (gArgs.cmd != NULL) && !gArgs.cmd->need_p && (gArgs.rxPort != NULL) )
530  {
531  WARNING("Illegal argument '-p %s'!", gArgs.rxPort);
532  res = false;
533  }
534 
535  // Require -l arg?
536  if ((gArgs.cmd != NULL) && gArgs.cmd->need_l)
537  {
538  if ( (gArgs.cfgLayer == NULL) || (gArgs.cfgLayer[0] == '\0') )
539  {
540  WARNING("Need '-l <layer(s)>' argument!");
541  res = false;
542  }
543  }
544  else if ( (gArgs.cmd != NULL) && !gArgs.cmd->need_l && (gArgs.cfgLayer != NULL) )
545  {
546  WARNING("Illegal argument '-l %s'!", gArgs.cfgLayer);
547  res = false;
548  }
549 
550  // Require -r arg?
551  if ( (gArgs.cmd != NULL) && gArgs.cmd->need_r )
552  {
553  if ( (gArgs.resetType == NULL) || (gArgs.resetType[0] == '\0') )
554  {
555  WARNING("Need '-r <mode>' argument!");
556  res = false;
557  }
558  }
559  else if ( (gArgs.cmd != NULL) && !(gArgs.cmd->need_r || gArgs.cmd->may_r) && (gArgs.resetType != NULL) )
560  {
561  WARNING("Illegal argument '-r %s'!", gArgs.resetType);
562  res = false;
563  }
564 
565  // May use -n arg?
566  if ( (gArgs.cmd != NULL) && (!gArgs.cmd->may_n && gArgs.noProbe) )
567  {
568  WARNING("Illegal argument '-n'!");
569  res = false;
570  }
571 
572  // Are we happy with the arguments?
573  if (!res)
574  {
575  exit(EXIT_BADARGS);
576  }
577 
578  // Execute
579  DEBUG("args: inName=%s outName=%s rxPort=%s cfgLayer=%s useUnknown=%d",
580  gArgs.inName, gArgs.outName, gArgs.rxPort, gArgs.cfgLayer, gArgs.useUnknown);
581  const int exitCode = gArgs.cmd->run();
582 
583  return exitCode;
584 }
585 
586 /* ****************************************************************************************************************** */
587 // eof
Definition: cfgtool.c:43
Definition: cfgtool.c:59