RocketLogger 2.1.2
rocketlogger.c
Go to the documentation of this file.
1
32#include <argp.h>
33#include <ctype.h>
34#include <error.h>
35#include <stdint.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39
40#include <linux/limits.h>
41#include <unistd.h>
42
43#include "log.h"
44#include "rl.h"
45#include "rl_lib.h"
46#include "version.h"
47
51#define ARGP_ARGUMENTS_COUNT 1
52
53#define OPT_FILE_SIZE 1
54
55#define OPT_SAMPLES_COUNT 2
56
57#define OPT_SET_DEFAULT 3
58
59#define OPT_RESET_DEFAULT 4
60
61#define OPT_CALIBRATION 5
62
63#define OPT_JSON 6
64
65#define OPT_CLI 7
66
67#define OPT_STREAM 8
68
72const char *argp_program_version = "RocketLogger Command Line Interface";
73
77const char *argp_program_bug_address = "<https://github.com/ETHZ-TEC/RocketLogger/issues>";
78
82static char doc[] =
83 "RocketLogger CLI -- manage your RocketLogger measurements.\n"
84 "Control or configure measurements using the actions specified by ACTION. "
85 "Supported values for ACTION are:\n"
86 "\n"
87 " Measurement control:\n"
88 " start\tStart a new measurement with provided configuration\n"
89 " stop\tStop measurement running in the background\n"
90 "\n"
91 " Measurement configuration and status management:\n"
92 " config\tDisplay configuration, not starting a new or affecting a "
93 "running measurement\n"
94 " status\tDisplay the current sampling status\n";
95
99static char args_doc[] = "ACTION";
100
104static struct argp_option options[] = {
105 {0, 0, 0, OPTION_DOC, "Basic measurement configuration options:", 1},
106 {"samples", OPT_SAMPLES_COUNT, "COUNT", 0,
107 "Number of samples to record before stopping measurement (k, M, G, T "
108 "scaling suffixes can be used).",
109 0},
110 {"channel", 'c', "SELECTION", 0, "Channel selection to sample. A comma "
111 "separated list of the channel names (V1, "
112 "V2, V3, V4, I1L, I1H, I2L, and/or I2H) "
113 "or 'all' to enable all channels.",
114 0},
115 {"rate", 'r', "RATE", 0, "Sampling rate in Hz. Supported values are: 1, "
116 "10, 100, 1k, 2k, 4k, 8k, 16k, 32k, 64k.",
117 0},
118 {"update", 'u', "RATE", 0,
119 "Measurement data update rate in Hz. Supported values: 1, 2, 5, 10.", 0},
120 {"output", 'o', "FILE", 0,
121 "Store data to specified file. Use zero to disable file storage.", 0},
122 {"interactive", 'i', 0, 0,
123 "Display measurement data in the command line interface.", 0},
124 {"background", 'b', 0, 0,
125 "Start measurement in the background and exit after start.", 0},
126
127 {0, 0, 0, OPTION_DOC,
128 "Measurement configuration options for storing measurement files:", 3},
129 {"format", 'f', "FORMAT", 0, "Select file format: 'csv', 'rld'.", 0},
130 {"size", OPT_FILE_SIZE, "SIZE", 0,
131 "Select max file size (k, M, G, T scaling suffixes can be used).", 0},
132 {"comment", 'C', "COMMENT", 0, "Comment stored in file header. Comment is "
133 "ignored if file saving is disabled.",
134 0},
135
136 {0, 0, 0, OPTION_DOC, "Setting and resetting the stored default:", 4},
137 {"default", OPT_SET_DEFAULT, 0, 0, "Set current configuration as default. "
138 "Supported for all measurement "
139 "configurations.",
140 0},
141 {"reset", OPT_RESET_DEFAULT, 0, 0,
142 "Reset default configuration to factory defaults and ignores any other "
143 "provided configuration without notice. Only allowed in combination with "
144 "the 'config' action.",
145 0},
146
147 {0, 0, 0, OPTION_DOC, "Optional arguments for extended sampling features:",
148 5},
149 {"digital", 'd', "BOOL", OPTION_ARG_OPTIONAL,
150 "Enable logging of digital inputs. Enabled by default.", 0},
151 {"ambient", 'a', "BOOL", OPTION_ARG_OPTIONAL,
152 "Enable logging of ambient sensors, if available. Disabled by default.",
153 0},
154 {"aggregate", 'g', "MODE", 0, "Data aggregation mode for low sample rates. "
155 "Existing modes: 'average', 'downsample'.",
156 0},
157 {"high-range", 'h', "SELECTION", 0,
158 "Force high range measurements on selected channels. A comma separated "
159 "list of the channel names (I1H and/or I2H) or 'all' to force high range "
160 "measurements all channels. Inactive per default.",
161 0},
162 {"calibration", OPT_CALIBRATION, 0, 0, "Ignore existing calibration "
163 "values. Use this option for device "
164 "calibration measurements only.",
165 0},
166 {"web", 'w', "BOOL", OPTION_ARG_OPTIONAL,
167 "Enable data stream interface (required for web interface plotting). "
168 "Enabled per default.",
169 0},
170 {"stream", OPT_STREAM, 0, OPTION_ALIAS, 0, 0},
171
172 {0, 0, 0, OPTION_DOC, "Optional arguments for status and config actions:",
173 6},
174 {"json", OPT_JSON, 0, 0,
175 "Print configuration or status as JSON formatted string.", 0},
176 {"cli", OPT_CLI, 0, 0, "Print configuration as full CLI command.", 0},
177
178 {0, 0, 0, OPTION_DOC, "Generic program switches:", 7},
179 {"verbose", 'v', 0, 0, "Produce verbose output", 0},
180 {"quiet", 'q', 0, 0, "Do not produce any output", 0},
181 {"silent", 's', 0, OPTION_ALIAS, 0, 0},
182
183 {0, 0, 0, 0, 0, 0},
184};
185
189struct arguments {
194 bool cli;
195 bool json;
196 bool silent;
197 bool verbose;
198};
199
200/* local function declarations */
201static error_t parse_opt(int key, char *arg, struct argp_state *state);
202static void parse_bool(char const *arg, struct argp_state *state,
203 bool *const value);
204static void parse_bool_named_list(char const *arg, struct argp_state *state,
205 char const *const *const names,
206 bool *const values, int size);
207static void parse_uint32(char const *arg, struct argp_state *state,
208 uint32_t *const value);
209static void parse_uint64(char const *arg, struct argp_state *state,
210 uint64_t *const value);
211static void print_config(rl_config_t const *const config);
212static void print_version(FILE *stream, struct argp_state *state);
213
217void (*argp_program_version_hook)(FILE *, struct argp_state *) = print_version;
218
222static struct argp argp = {
223 .options = options,
224 .parser = parse_opt,
225 .args_doc = args_doc,
226 .doc = doc,
227};
228
232static char const *const log_filename = RL_MEASUREMENT_LOG_FILE;
233
241int main(int argc, char *argv[]) {
242 rl_config_t config;
243
244 // load default configuration
245 int res = rl_config_read_default(&config);
246 if (res < 0) {
247 error(EXIT_FAILURE, errno, "failed reading default configuration file");
248 }
249
250 // argument structure with default values
251 struct arguments arguments = {
252 .args = {NULL},
253 .config = &config,
254 .config_reset = false,
255 .config_set_default = false,
256 .cli = false,
257 .json = false,
258 .silent = false,
259 .verbose = false,
260 };
261
262 // parse CLI arguments and options using argp
263 int argp_status = argp_parse(&argp, argc, argv, 0, 0, &arguments);
264 if (argp_status != 0) {
265 error(0, argp_status, "argument parsing failed");
266 }
267
268 // init log module with appropriate verbosity level
269 if (arguments.verbose) {
270 rl_log_init(log_filename, RL_LOG_VERBOSE);
271 } else if (arguments.silent) {
272 rl_log_init(log_filename, RL_LOG_IGNORE);
273 } else {
274 rl_log_init(log_filename, RL_LOG_WARNING);
275 }
276
277 // set effective user ID of the process
278 int ret = setuid(0);
279 if (ret < 0) {
280 rl_log(RL_LOG_ERROR, "Failed setting effective user ID. %d message: %s",
281 errno, strerror(errno));
282 exit(EXIT_FAILURE);
283 }
284
285 rl_log(RL_LOG_VERBOSE, "running with real user ID: %d", getuid());
286 rl_log(RL_LOG_VERBOSE, "running with effective user ID: %d", geteuid());
287 rl_log(RL_LOG_VERBOSE, "running with real group ID: %d", getgid());
288 rl_log(RL_LOG_VERBOSE, "running with effective group ID: %d", getegid());
289
290
291 char const *const action = arguments.args[0];
292
293 // validate arguments
294 bool valid_action =
295 (strcmp(action, "start") == 0 || strcmp(action, "stop") == 0 ||
296 strcmp(action, "config") == 0 || strcmp(action, "status") == 0);
297 if (!valid_action) {
298 rl_log(RL_LOG_ERROR, "unknown action '%s'", action);
299 exit(EXIT_FAILURE);
300 }
301
302 if (arguments.cli && arguments.json) {
303 rl_log(RL_LOG_ERROR, "cannot format in output as JSON and and CLI "
304 "string at the same time.");
305 exit(EXIT_FAILURE);
306 }
307
308 // validate sampling configuration
309 int valid_config = rl_config_validate(&config);
310 if (valid_config < 0) {
311 rl_log(RL_LOG_ERROR, "invalid configuration, check message above");
312 exit(EXIT_FAILURE);
313 }
314
315 // reset config if requested
317 if (strcmp(action, "config") != 0) {
319 "the --reset option is only allowed for config action.");
320 exit(EXIT_FAILURE);
321 }
323 rl_log(RL_LOG_INFO, "Configuration was reset to factory default.");
324 }
325 // store config as default
329 printf("The following configuration was saved as new default:\n");
331 }
332 }
333
334 // configure and run system in the requested MODE
335 if (strcmp(action, "start") == 0) {
336 // check if already sampling
337 if (rl_is_sampling()) {
338 rl_log(RL_LOG_ERROR, "RocketLogger is still running.\n"
339 "Stop with `rocketlogger stop` first.\n");
340 exit(EXIT_FAILURE);
341 }
342
343 if (arguments.verbose) {
344 print_config(&config);
345 }
346 rl_log(RL_LOG_INFO, "Starting measurement...\n");
347 rl_run(&config);
348 }
349 if (strcmp(action, "stop") == 0) {
350 // exit with error if not sampling
351 if (!rl_is_sampling()) {
352 rl_log(RL_LOG_ERROR, "RocketLogger is not running.\n");
353 exit(EXIT_FAILURE);
354 }
355
356 if (!arguments.silent) {
357 printf("Wait for measurement to stop...\n");
358 }
359 rl_stop();
360 }
361 if (strcmp(action, "config") == 0) {
362 if (arguments.json) {
364 } else if (arguments.cli) {
366 } else {
368 }
369 }
370 if (strcmp(action, "status") == 0) {
371 rl_status_t status;
372 int res = rl_get_status(&status);
373 if (res < 0) {
374 rl_log(RL_LOG_ERROR, "Failed getting RocketLogger status (%d).\n",
375 res);
376 exit(EXIT_FAILURE);
377 }
378 if (arguments.json) {
379 rl_status_print_json(&status);
380 } else {
381 rl_status_print(&status);
382 }
383 }
384 exit(EXIT_SUCCESS);
385}
386
395static error_t parse_opt(int key, char *arg, struct argp_state *state) {
396 // get pointer to where run configuration is stored
397 struct arguments *arguments = state->input;
399
400 // parse actual argument
401 switch (key) {
402 /* options with shortcuts */
403 case 'q':
404 case 's':
405 /* quiet/silent switch: no value */
406 arguments->silent = true;
407 arguments->verbose = false;
408 break;
409 case 'v':
410 /* verbose switch: no value */
411 arguments->verbose = true;
412 arguments->silent = false;
413 break;
414 case 'b':
415 /* run in background: no value */
417 break;
418 case 'i':
419 /* display measurements interactively: no value */
421 break;
422 case 'c':
423 /* channel selection: mandatory SELECTION value */
424 parse_bool_named_list(arg, state, RL_CHANNEL_NAMES,
426 break;
427 case 'r':
428 /* sampling rate: mandatory RATE value */
429 parse_uint32(arg, state, &config->sample_rate);
430 break;
431 case 'u':
432 /* measurement update rate: mandatory RATE value */
433 parse_uint32(arg, state, &config->update_rate);
434 break;
435 case 'o':
436 /* measurement output file: mandatory FILE value */
437 if (arg != NULL) {
438 if (strlen(arg) == 1 && arg[0] == '0') {
439 config->file_enable = false;
440 } else {
441 config->file_enable = true;
442 strncpy(config->file_name, arg, PATH_MAX - 1);
443 }
444 } else {
445 argp_usage(state);
446 }
447 break;
448 case 'f':
449 /* measurement file format: mandatory FORMAT value */
450 if (strcmp(arg, "csv") == 0 || strcmp(arg, "CSV") == 0) {
452 } else if (strcmp(arg, "rld") == 0 || strcmp(arg, "RLD") == 0) {
454 } else {
455 argp_usage(state);
456 }
457 break;
458 case 'C':
459 /* measurement file comment: mandatory COMMENT value */
460 if (arg != NULL) {
461 config->file_comment = arg;
462 } else {
463 argp_usage(state);
464 }
465 break;
466 case 'd':
467 /* digital channel: optional BOOL value */
468 if (arg != NULL) {
469 parse_bool(arg, state, &config->digital_enable);
470 } else {
471 config->digital_enable = true;
472 }
473 break;
474 case 'a':
475 /* ambient sensors: optional BOOL value */
476 if (arg != NULL) {
477 parse_bool(arg, state, &config->ambient_enable);
478 } else {
479 config->ambient_enable = true;
480 }
481 break;
482 case 'g':
483 /* data aggregation mode: mandatory MODE value */
484 if (strcmp(arg, "downsample") == 0) {
486 } else if (strcmp(arg, "average") == 0) {
488 } else {
489 argp_usage(state);
490 }
491 break;
492 case 'h':
493 /* force high-range current measurement: mandatory SELECTION value */
494 parse_bool_named_list(arg, state, RL_CHANNEL_FORCE_NAMES,
497 break;
498 case 'w':
499 case OPT_STREAM:
500 /* data streaming and web interface enable: optional BOOL value */
501 if (arg != NULL) {
502 parse_bool(arg, state, &config->web_enable);
503 } else {
504 config->web_enable = true;
505 }
506 break;
507
508 /* options without shortcuts */
510 /* sample count: mandatory COUNT value */
511 parse_uint64(arg, state, &config->sample_limit);
512 break;
513 case OPT_FILE_SIZE:
514 /* maximum file size: mandatory SIZE value */
515 parse_uint64(arg, state, &config->file_size);
516 break;
517 case OPT_CLI:
518 /* CLI format the config output: no value */
519 arguments->cli = true;
520 break;
521 case OPT_JSON:
522 /* JSON format the status and config output: no value */
523 arguments->json = true;
524 break;
525 case OPT_SET_DEFAULT:
526 /* set configuration as default: no value */
528 break;
530 /* reset configuration to system default: no value */
531 arguments->config_reset = true;
532 break;
533 case OPT_CALIBRATION:
534 /* perform calibration measurement: no value */
536 break;
537
538 /* unnamed argument options */
539 case ARGP_KEY_ARG:
540 // check for too many arguments
541 if (state->arg_num >= ARGP_ARGUMENTS_COUNT) {
542 argp_usage(state);
543 }
544 arguments->args[state->arg_num] = arg;
545 break;
546 case ARGP_KEY_END:
547 // check for not enough arguments
548 if (state->arg_num < ARGP_ARGUMENTS_COUNT) {
549 argp_usage(state);
550 }
551 break;
552 default:
553 return ARGP_ERR_UNKNOWN;
554 }
555
556 return 0;
557}
558
568static void parse_bool(char const *arg, struct argp_state *state,
569 bool *const value) {
570 if (strlen(arg) == 1) {
571 if (arg[0] == '0') {
572 *value = false;
573 return;
574 } else if (arg[0] == '1') {
575 *value = true;
576 return;
577 }
578 }
579 if (strcmp(arg, "true") == 0 || strcmp(arg, "True") == 0 ||
580 strcmp(arg, "TRUE") == 0) {
581 *value = true;
582 return;
583 }
584 if (strcmp(arg, "false") == 0 || strcmp(arg, "False") == 0 ||
585 strcmp(arg, "FALSE") == 0) {
586 *value = false;
587 return;
588 }
589 argp_usage(state);
590}
591
605static void parse_bool_named_list(char const *arg, struct argp_state *state,
606 char const *const *const names,
607 bool *const values, int size) {
608 // check for all enable argument
609 if (strcmp(arg, "all") == 0) {
610 memset(values, true, size * sizeof(bool));
611 return;
612 }
613 // reset values
614 memset(values, false, size * sizeof(bool));
615
616 // split input by comma
617 char const *split_pos = arg;
618 while (*arg != '\0') {
619 // find next argument name
620 split_pos = strchr(arg, ',');
621 char arg_name[16] = {0};
622 if (split_pos == NULL) {
623 strncpy(arg_name, arg, sizeof(arg_name) - 1);
624 arg = arg + strlen(arg); // set to end to exit loop when done
625 } else {
626 strncpy(arg_name, arg, split_pos - arg);
627 arg = split_pos + 1; // next argument starts right after comma
628 }
629
630 // convert parsed name
631 char *ptr = arg_name;
632 do {
633 *ptr = toupper(*ptr);
634 } while (*ptr++);
635
636 // process channel name
637 int i = 0;
638 while (i < size) {
639 if (strcmp(arg_name, names[i]) == 0) {
640 values[i] = true;
641 break;
642 }
643 i++;
644 }
645 // check valid channel was set
646 if (i == size) {
647 argp_usage(state);
648 return;
649 }
650 }
651}
652
662static void parse_uint32(char const *arg, struct argp_state *state,
663 uint32_t *const value) {
664 uint64_t temp;
665 parse_uint64(arg, state, &temp);
666 if ((temp >> 32) == 0) {
667 *value = (uint32_t)temp;
668 } else {
669 argp_usage(state);
670 }
671}
672
682static void parse_uint64(char const *arg, struct argp_state *state,
683 uint64_t *const value) {
684 char *suffix = NULL;
685 *value = strtoull(arg, &suffix, 10);
686
687 // check for scaling suffix and apply it iteratively
688 if (suffix != NULL) {
689 switch (*suffix) {
690 case 'T':
691 *value = *value * 1000;
692 /* FALL THROUGH */
693 case 'G':
694 *value = *value * 1000;
695 /* FALL THROUGH */
696 case 'M':
697 *value = *value * 1000;
698 /* FALL THROUGH */
699 case 'k':
700 *value = *value * 1000;
701 /* FALL THROUGH */
702 case '\0':
703 break;
704 default:
705 argp_usage(state);
706 return;
707 }
708 } else {
709 argp_usage(state);
710 return;
711 }
712}
713
720static void print_version(FILE *stream, struct argp_state *state) {
721 (void)state; // suppress unused parameter warning
722 fprintf(stream, "%s %s\n", argp_program_version, PROJECT_VERSION);
723 fprintf(stream, " git@%s (%s)\n", GIT_DESCRIPTION, GIT_DATE);
724 fprintf(stream, " compiled at %s\n", COMPILE_DATE);
725}
726
732static void print_config(rl_config_t const *const config) {
733 printf("\nRocketLogger Configuration:\n");
735 printf("\n");
736}
int main()
Program main fuction.
Definition: check_config.c:147
int rl_log(rl_log_level_t log_level, char const *const format,...)
Definition: log.c:82
int rl_log_init(char const *const log_file, rl_log_level_t verbosity)
Definition: log.c:49
@ RL_LOG_ERROR
Error.
Definition: log.h:49
@ RL_LOG_WARNING
Warning.
Definition: log.h:50
@ RL_LOG_VERBOSE
Verbose.
Definition: log.h:52
@ RL_LOG_INFO
Information.
Definition: log.h:51
@ RL_LOG_IGNORE
Ignore log (only for verbosity configuration)
Definition: log.h:48
char const *const RL_CHANNEL_NAMES[RL_CHANNEL_COUNT]
RocketLogger channel names.
Definition: rl.c:95
void rl_status_print_json(rl_status_t const *const status)
Definition: rl.c:731
char const *const RL_CHANNEL_FORCE_NAMES[RL_CHANNEL_SWITCHED_COUNT]
RocketLogger force range channel names.
Definition: rl.c:99
void rl_config_print_json(rl_config_t const *const config)
Definition: rl.c:275
void rl_config_reset(rl_config_t *const config)
Definition: rl.c:360
void rl_config_print(rl_config_t const *const config)
Definition: rl.c:142
int rl_config_read_default(rl_config_t *const config)
Definition: rl.c:364
int rl_config_write_default(rl_config_t const *const config)
Definition: rl.c:414
int rl_config_validate(rl_config_t const *const config)
Definition: rl.c:431
void rl_status_print(rl_status_t const *const status)
Definition: rl.c:709
void rl_config_print_cmd(rl_config_t const *const config)
Definition: rl.c:211
@ RL_FILE_FORMAT_RLD
CSV format.
Definition: rl.h:143
@ RL_FILE_FORMAT_CSV
Definition: rl.h:142
#define RL_CHANNEL_SWITCHED_COUNT
Number of RocketLogger switched channels (allowing to force range)
Definition: rl.h:58
#define RL_MEASUREMENT_LOG_FILE
RocketLogger measurement log file.
Definition: rl.h:49
#define RL_CHANNEL_COUNT
Number of RocketLogger analog channels.
Definition: rl.h:56
@ RL_AGGREGATION_MODE_AVERAGE
Aggregate using down sampling.
Definition: rl.h:130
@ RL_AGGREGATION_MODE_DOWNSAMPLE
Definition: rl.h:129
int rl_stop(void)
Definition: rl_lib.c:185
bool rl_is_sampling(void)
Definition: rl_lib.c:59
int rl_run(rl_config_t *const config)
Definition: rl_lib.c:100
int rl_get_status(rl_status_t *const status)
Definition: rl_lib.c:77
#define OPT_SAMPLES_COUNT
Definition: rocketlogger.c:55
#define OPT_JSON
Definition: rocketlogger.c:63
const char * argp_program_version
Definition: rocketlogger.c:72
#define OPT_SET_DEFAULT
Definition: rocketlogger.c:57
#define ARGP_ARGUMENTS_COUNT
Definition: rocketlogger.c:51
#define OPT_CLI
Definition: rocketlogger.c:65
#define OPT_RESET_DEFAULT
Definition: rocketlogger.c:59
const char * argp_program_bug_address
Definition: rocketlogger.c:77
#define OPT_CALIBRATION
Definition: rocketlogger.c:61
void(* argp_program_version_hook)(FILE *, struct argp_state *)
Definition: rocketlogger.c:217
#define OPT_STREAM
Definition: rocketlogger.c:67
#define OPT_FILE_SIZE
Definition: rocketlogger.c:53
rl_config_t * config
program arguments
Definition: rocketlogger.c:191
bool config_set_default
whether to reset the stored default config
Definition: rocketlogger.c:193
bool verbose
flag for silent output
Definition: rocketlogger.c:197
bool cli
whether to save provided config as default
Definition: rocketlogger.c:194
bool json
flag for CLI command formatted config output
Definition: rocketlogger.c:195
bool silent
flag for JSON formatted output
Definition: rocketlogger.c:196
char * args[ARGP_ARGUMENTS_COUNT]
Definition: rocketlogger.c:190
bool config_reset
pointer to sampling configuration
Definition: rocketlogger.c:192
Definition: rl.h:154
bool channel_force_range[RL_CHANNEL_SWITCHED_COUNT]
Current channels to force to high range.
Definition: rl.h:170
char file_name[PATH_MAX]
Data file name.
Definition: rl.h:184
bool interactive_enable
Display measurement data interactively in CLI while sampling.
Definition: rl.h:160
char const * file_comment
File comment.
Definition: rl.h:190
uint64_t file_size
Maximum data file size.
Definition: rl.h:188
rl_aggregation_mode_t aggregation_mode
Sample aggregation mode (for sampling rates below lowest native one)
Definition: rl.h:172
bool background_enable
Put the measurement process in background after successful start.
Definition: rl.h:158
bool ambient_enable
Enable logging of ambient sensor.
Definition: rl.h:180
uint64_t sample_limit
Sample limit (0 for continuous)
Definition: rl.h:162
bool web_enable
Enable web interface connection.
Definition: rl.h:176
rl_file_format_t file_format
File format.
Definition: rl.h:186
uint32_t sample_rate
Sampling rate.
Definition: rl.h:164
bool channel_enable[RL_CHANNEL_COUNT]
Channels to sample.
Definition: rl.h:168
uint32_t update_rate
Data update rate.
Definition: rl.h:166
bool file_enable
Enable storing measurements to file.
Definition: rl.h:182
bool digital_enable
Enable digital inputs.
Definition: rl.h:174
bool calibration_ignore
Perform calibration measurement (ignore existing calibration)
Definition: rl.h:178
Definition: rl.h:201
char const *const COMPILE_DATE
Compilation date of the program.
char const *const PROJECT_VERSION
The RocketLogger software version string.
char const *const GIT_DESCRIPTION
Git code revision description of the code base.
char const *const GIT_DATE
Date of the of last git commit.