RocketLogger 2.1.1
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(void)
Definition gpiod_test.c:43
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
#define OPT_JSON
const char * argp_program_version
#define OPT_SET_DEFAULT
#define ARGP_ARGUMENTS_COUNT
#define OPT_CLI
#define OPT_RESET_DEFAULT
const char * argp_program_bug_address
#define OPT_CALIBRATION
void(* argp_program_version_hook)(FILE *, struct argp_state *)
#define OPT_STREAM
#define OPT_FILE_SIZE
rl_config_t * config
program arguments
bool config_set_default
whether to reset the stored default config
bool verbose
flag for silent output
bool cli
whether to save provided config as default
bool json
flag for CLI command formatted config output
bool silent
flag for JSON formatted output
char * args[ARGP_ARGUMENTS_COUNT]
bool config_reset
pointer to sampling configuration
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
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.