diff --git a/CMakeLists.txt b/CMakeLists.txt index f9cacd3..7114332 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,29 +15,32 @@ if(WIRING_PI_DEBUG) add_compile_definitions("WIRING_PI_DEBUG") endif(WIRING_PI_DEBUG) +aux_source_directory(vendor VENDOR_SRC) # vendor first to put their warnings on top aux_source_directory(. SRC_DIR) aux_source_directory(models MODELS_SRC) aux_source_directory(helpers HELPERS_SRC) aux_source_directory(handlers HANDLERS_SRC) aux_source_directory(drivers DRIVERS_SRC) +aux_source_directory(runners RUNNERS_SRC) configure_file("controller.ini" "controller.ini" COPYONLY) -target_sources(controller PRIVATE ${SRC_DIR} ${MODELS_SRC} ${HELPERS_SRC} ${HANDLERS_SRC} ${DRIVERS_SRC}) +target_sources(controller PRIVATE ${VENDOR_SRC} ${SRC_DIR} ${MODELS_SRC} ${HELPERS_SRC} ${HANDLERS_SRC} ${DRIVERS_SRC} ${RUNNERS_SRC}) target_include_directories(controller PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) +target_include_directories(controller PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor) add_custom_target(run - COMMAND ./controller + COMMAND ./controller start DEPENDS controller WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} ) add_custom_target(debug - COMMAND valgrind ./controller + COMMAND valgrind ./controller start DEPENDS controller WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} ) add_custom_target(debug-full - COMMAND valgrind --leak-check=full --show-leak-kinds=all ./controller + COMMAND valgrind --leak-check=full --show-leak-kinds=all ./controller start DEPENDS controller WORKING_DIRECTORY ${CMAKE_PROJECT_DIR} ) diff --git a/helpers/parse_cli.c b/helpers/parse_cli.c new file mode 100644 index 0000000..7f9c094 --- /dev/null +++ b/helpers/parse_cli.c @@ -0,0 +1,62 @@ +#include +#include +#include + +#include +#include +#include +#include + +static const char *const usage[] = { + "controller [options] [[--] args]", + "controller [options]", + NULL, +}; + +#define PERM_READ (1<<0) +#define PERM_WRITE (1<<1) +#define PERM_EXEC (1<<2) + +void +helpers_parse_cli(int argc, const char **argv, config_t *config) +{ + struct argparse_option options[] = + { + OPT_HELP(), + OPT_GROUP("Basic options"), + OPT_STRING('c', "config", &config->file, "path to config file", NULL, 0, OPT_NONEG), + + OPT_END(), + }; + + struct argparse argparse; + argparse_init(&argparse, options, usage, 0); + argparse_describe( + &argparse, + "\nA brief description of what the program does and how it works.", + "\nAdditional description of the program after the description of the arguments." + ); + argc = argparse_parse(&argparse, argc, argv); + + if(argc == 1) + { + if(strcmp(argv[0], "start") == 0) + { + config->run_type = RUN_TYPE_START; + return; + } + if(strcmp(argv[0], "test") == 0) + { + config->run_type = RUN_TYPE_TEST; + return; + } + LOG_FATAL("bad action '%s' given ('start', 'test')", argv[0]); + exit(1); + } + else + { + LOG_FATAL("no action given ('start', 'test')"); + exit(1); + } + return; +} diff --git a/include/config.h b/include/config.h index 24da946..fb7eac6 100644 --- a/include/config.h +++ b/include/config.h @@ -14,6 +14,8 @@ typedef struct typedef struct { + char *file; + run_type_t run_type; char name[MAX_NAME_LENGTH + 1]; uint16_t discovery_port; uint8_t relay_count; diff --git a/include/enums.h b/include/enums.h index 87f093f..c461215 100644 --- a/include/enums.h +++ b/include/enums.h @@ -39,9 +39,15 @@ typedef enum typedef enum { - RELAY_DRIVER_NONE = 0, - RELAY_DRIVER_GPIO = 1, - RELAY_DRIVER_PIFACE = 2, + RELAY_DRIVER_NONE, + RELAY_DRIVER_GPIO, + RELAY_DRIVER_PIFACE, } relay_driver_t; +typedef enum +{ + RUN_TYPE_START, + RUN_TYPE_TEST, +} run_type_t; + #endif /* CONTROLLER_ENUMS_H */ diff --git a/include/helpers.h b/include/helpers.h index 4a599fd..49d2a6f 100644 --- a/include/helpers.h +++ b/include/helpers.h @@ -2,6 +2,7 @@ #define CONTROLLER_HELPERS_H #include +#include int helper_connect_tcp_server(char* host, uint16_t port); @@ -27,4 +28,7 @@ helper_open_discovery_socket(uint16_t discovery_port); int helper_load_config(IniDispatch *disp, void *config_void); +void +helpers_parse_cli(int argc, const char **argv, config_t *config); + #endif /* CONTROLLER_HELPERS_H */ diff --git a/include/runners.h b/include/runners.h new file mode 100644 index 0000000..ce74bd2 --- /dev/null +++ b/include/runners.h @@ -0,0 +1,11 @@ +#ifndef CONTROLLER_RUNNERS_H +#define CONTROLLER_RUNNERS_H + +#include + +#include + +void +runner_test(controller_t *controller); + +#endif /* CONTROLLER_RUNNERS_H */ diff --git a/include/wiring_debug.h b/include/wiring_debug.h index cb77f65..664d88b 100644 --- a/include/wiring_debug.h +++ b/include/wiring_debug.h @@ -3,7 +3,7 @@ #include -#define LOG_WIRING_DEBUG(x, ...) LOG_TRACE(x, ##__VA_ARGS__) +#define LOG_WIRING_DEBUG(x, ...) LOG_DEBUG(x, ##__VA_ARGS__) #ifdef WIRING_PI_DEBUG #define wiringPiSetup() LOG_WIRING_DEBUG("wiringPi wiringPiSetup()") diff --git a/main.c b/main.c index 5a233a2..42b1e4f 100644 --- a/main.c +++ b/main.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,7 @@ terminate(int signum) * @return Statuscode to indicate success (0) or failure (!0) */ int -main(int argc, char** argv) +main(int argc, const char** argv) { (void)argc; (void)argv; @@ -67,16 +68,19 @@ main(int argc, char** argv) /******************** LOAD CONFIG ********************/ - char ini_file_name[] = "controller.ini"; - FILE * const ini_file = fopen(ini_file_name, "rb"); + global_config.file = "controller.ini"; + + helpers_parse_cli(argc, argv, &global_config); + + FILE * const ini_file = fopen(global_config.file, "rb"); if(ini_file == NULL) { - LOG_ERROR("%s file was not found", ini_file_name); + LOG_FATAL("config file '%s' was not found", global_config.file); exit(1); } if(load_ini_file( ini_file, INI_DEFAULT_FORMAT, NULL, helper_load_config, &global_config)) { - LOG_ERROR("unable to parse ini file"); + LOG_FATAL("unable to parse ini file"); exit(1); } @@ -120,6 +124,15 @@ main(int argc, char** argv) poll_fds[POLL_FDS_COMMAND].events = POLLIN; LOG_DEBUG("setup fd_command as %i on index %i", fd_command, POLL_FDS_COMMAND); + + /******************** CHECK FOR TESTING RUN ********************/ + + if(global_config.run_type == RUN_TYPE_TEST) + { + runner_test(this_controller); + } + + /******************** START MAIN LOOP ********************/ for(;;) diff --git a/runners/test.c b/runners/test.c new file mode 100644 index 0000000..5f22830 --- /dev/null +++ b/runners/test.c @@ -0,0 +1,30 @@ +#include + +#include +#include +#include +#include + +void +runner_test(controller_t *controller) +{ + // from x down to 0 to turn all relays off in the end + for(int test_run = 2; test_run >= 0; --test_run) + { + for(uint_fast8_t i = 0; i < controller->relay_count; ++i) + { + switch(global_config.relay_configs[i].driver) + { + case RELAY_DRIVER_GPIO: + driver_gpio_set(global_config.relay_configs[i].pin, test_run % 2); + break; + case RELAY_DRIVER_PIFACE: + driver_piface_set(global_config.relay_configs[i].pin, test_run % 2); + break; + default: + LOG_WARN("relay %d is not using a driver", i); + } + sleep(1); + } + } +} diff --git a/vendor/argparse.c b/vendor/argparse.c new file mode 100644 index 0000000..ec86bba --- /dev/null +++ b/vendor/argparse.c @@ -0,0 +1,384 @@ +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#include +#include +#include +#include +#include +#include "argparse.h" + +#define OPT_UNSET 1 +#define OPT_LONG (1 << 1) + +static const char * +prefix_skip(const char *str, const char *prefix) +{ + size_t len = strlen(prefix); + return strncmp(str, prefix, len) ? NULL : str + len; +} + +static int +prefix_cmp(const char *str, const char *prefix) +{ + for (;; str++, prefix++) + if (!*prefix) { + return 0; + } else if (*str != *prefix) { + return (unsigned char)*prefix - (unsigned char)*str; + } +} + +static void +argparse_error(struct argparse *self, const struct argparse_option *opt, + const char *reason, int flags) +{ + (void)self; + if (flags & OPT_LONG) { + fprintf(stderr, "error: option `--%s` %s\n", opt->long_name, reason); + } else { + fprintf(stderr, "error: option `-%c` %s\n", opt->short_name, reason); + } + exit(1); +} + +static int +argparse_getvalue(struct argparse *self, const struct argparse_option *opt, + int flags) +{ + const char *s = NULL; + if (!opt->value) + goto skipped; + switch (opt->type) { + case ARGPARSE_OPT_BOOLEAN: + if (flags & OPT_UNSET) { + *(int *)opt->value = *(int *)opt->value - 1; + } else { + *(int *)opt->value = *(int *)opt->value + 1; + } + if (*(int *)opt->value < 0) { + *(int *)opt->value = 0; + } + break; + case ARGPARSE_OPT_BIT: + if (flags & OPT_UNSET) { + *(int *)opt->value &= ~opt->data; + } else { + *(int *)opt->value |= opt->data; + } + break; + case ARGPARSE_OPT_STRING: + if (self->optvalue) { + *(const char **)opt->value = self->optvalue; + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(const char **)opt->value = *++self->argv; + } else { + argparse_error(self, opt, "requires a value", flags); + } + break; + case ARGPARSE_OPT_INTEGER: + errno = 0; + if (self->optvalue) { + *(int *)opt->value = strtol(self->optvalue, (char **)&s, 0); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(int *)opt->value = strtol(*++self->argv, (char **)&s, 0); + } else { + argparse_error(self, opt, "requires a value", flags); + } + if (errno) + argparse_error(self, opt, strerror(errno), flags); + if (s[0] != '\0') + argparse_error(self, opt, "expects an integer value", flags); + break; + case ARGPARSE_OPT_FLOAT: + errno = 0; + if (self->optvalue) { + *(float *)opt->value = strtof(self->optvalue, (char **)&s); + self->optvalue = NULL; + } else if (self->argc > 1) { + self->argc--; + *(float *)opt->value = strtof(*++self->argv, (char **)&s); + } else { + argparse_error(self, opt, "requires a value", flags); + } + if (errno) + argparse_error(self, opt, strerror(errno), flags); + if (s[0] != '\0') + argparse_error(self, opt, "expects a numerical value", flags); + break; + default: + assert(0); + } + +skipped: + if (opt->callback) { + return opt->callback(self, opt); + } + + return 0; +} + +static void +argparse_options_check(const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + switch (options->type) { + case ARGPARSE_OPT_END: + case ARGPARSE_OPT_BOOLEAN: + case ARGPARSE_OPT_BIT: + case ARGPARSE_OPT_INTEGER: + case ARGPARSE_OPT_FLOAT: + case ARGPARSE_OPT_STRING: + case ARGPARSE_OPT_GROUP: + continue; + default: + fprintf(stderr, "wrong option type: %d", options->type); + break; + } + } +} + +static int +argparse_short_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + if (options->short_name == *self->optvalue) { + self->optvalue = self->optvalue[1] ? self->optvalue + 1 : NULL; + return argparse_getvalue(self, options, 0); + } + } + return -2; +} + +static int +argparse_long_opt(struct argparse *self, const struct argparse_option *options) +{ + for (; options->type != ARGPARSE_OPT_END; options++) { + const char *rest; + int opt_flags = 0; + if (!options->long_name) + continue; + + rest = prefix_skip(self->argv[0] + 2, options->long_name); + if (!rest) { + // negation disabled? + if (options->flags & OPT_NONEG) { + continue; + } + // only OPT_BOOLEAN/OPT_BIT supports negation + if (options->type != ARGPARSE_OPT_BOOLEAN && options->type != + ARGPARSE_OPT_BIT) { + continue; + } + + if (prefix_cmp(self->argv[0] + 2, "no-")) { + continue; + } + rest = prefix_skip(self->argv[0] + 2 + 3, options->long_name); + if (!rest) + continue; + opt_flags |= OPT_UNSET; + } + if (*rest) { + if (*rest != '=') + continue; + self->optvalue = rest + 1; + } + return argparse_getvalue(self, options, opt_flags | OPT_LONG); + } + return -2; +} + +int +argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags) +{ + memset(self, 0, sizeof(*self)); + self->options = options; + self->usages = usages; + self->flags = flags; + self->description = NULL; + self->epilog = NULL; + return 0; +} + +void +argparse_describe(struct argparse *self, const char *description, + const char *epilog) +{ + self->description = description; + self->epilog = epilog; +} + +int +argparse_parse(struct argparse *self, int argc, const char **argv) +{ + self->argc = argc - 1; + self->argv = argv + 1; + self->out = argv; + + argparse_options_check(self->options); + + for (; self->argc; self->argc--, self->argv++) { + const char *arg = self->argv[0]; + if (arg[0] != '-' || !arg[1]) { + if (self->flags & ARGPARSE_STOP_AT_NON_OPTION) { + goto end; + } + // if it's not option or is a single char '-', copy verbatim + self->out[self->cpidx++] = self->argv[0]; + continue; + } + // short option + if (arg[1] != '-') { + self->optvalue = arg + 1; + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + while (self->optvalue) { + switch (argparse_short_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + } + continue; + } + // if '--' presents + if (!arg[2]) { + self->argc--; + self->argv++; + break; + } + // long option + switch (argparse_long_opt(self, self->options)) { + case -1: + break; + case -2: + goto unknown; + } + continue; + +unknown: + fprintf(stderr, "error: unknown option `%s`\n", self->argv[0]); + argparse_usage(self); + exit(1); + } + +end: + memmove(self->out + self->cpidx, self->argv, + self->argc * sizeof(*self->out)); + self->out[self->cpidx + self->argc] = NULL; + + return self->cpidx + self->argc; +} + +void +argparse_usage(struct argparse *self) +{ + if (self->usages) { + fprintf(stdout, "Usage: %s\n", *self->usages++); + while (*self->usages && **self->usages) + fprintf(stdout, " or: %s\n", *self->usages++); + } else { + fprintf(stdout, "Usage:\n"); + } + + // print description + if (self->description) + fprintf(stdout, "%s\n", self->description); + + fputc('\n', stdout); + + const struct argparse_option *options; + + // figure out best width + size_t usage_opts_width = 0; + size_t len; + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + len = 0; + if ((options)->short_name) { + len += 2; + } + if ((options)->short_name && (options)->long_name) { + len += 2; // separator ", " + } + if ((options)->long_name) { + len += strlen((options)->long_name) + 2; + } + if (options->type == ARGPARSE_OPT_INTEGER) { + len += strlen("="); + } + if (options->type == ARGPARSE_OPT_FLOAT) { + len += strlen("="); + } else if (options->type == ARGPARSE_OPT_STRING) { + len += strlen("="); + } + len = (len + 3) - ((len + 3) & 3); + if (usage_opts_width < len) { + usage_opts_width = len; + } + } + usage_opts_width += 4; // 4 spaces prefix + + options = self->options; + for (; options->type != ARGPARSE_OPT_END; options++) { + size_t pos = 0; + int pad = 0; + if (options->type == ARGPARSE_OPT_GROUP) { + fputc('\n', stdout); + fprintf(stdout, "%s", options->help); + fputc('\n', stdout); + continue; + } + pos = fprintf(stdout, " "); + if (options->short_name) { + pos += fprintf(stdout, "-%c", options->short_name); + } + if (options->long_name && options->short_name) { + pos += fprintf(stdout, ", "); + } + if (options->long_name) { + pos += fprintf(stdout, "--%s", options->long_name); + } + if (options->type == ARGPARSE_OPT_INTEGER) { + pos += fprintf(stdout, "="); + } else if (options->type == ARGPARSE_OPT_FLOAT) { + pos += fprintf(stdout, "="); + } else if (options->type == ARGPARSE_OPT_STRING) { + pos += fprintf(stdout, "="); + } + if (pos <= usage_opts_width) { + pad = usage_opts_width - pos; + } else { + fputc('\n', stdout); + pad = usage_opts_width; + } + fprintf(stdout, "%*s%s\n", pad + 2, "", options->help); + } + + // print epilog + if (self->epilog) + fprintf(stdout, "%s\n", self->epilog); +} + +int +argparse_help_cb(struct argparse *self, const struct argparse_option *option) +{ + (void)option; + argparse_usage(self); + exit(0); +} diff --git a/vendor/argparse.h b/vendor/argparse.h new file mode 100644 index 0000000..44ce835 --- /dev/null +++ b/vendor/argparse.h @@ -0,0 +1,130 @@ +/** + * Copyright (C) 2012-2015 Yecheng Fu + * All rights reserved. + * + * Use of this source code is governed by a MIT-style license that can be found + * in the LICENSE file. + */ +#ifndef ARGPARSE_H +#define ARGPARSE_H + +/* For c++ compatibility */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct argparse; +struct argparse_option; + +typedef int argparse_callback (struct argparse *self, + const struct argparse_option *option); + +enum argparse_flag { + ARGPARSE_STOP_AT_NON_OPTION = 1, +}; + +enum argparse_option_type { + /* special */ + ARGPARSE_OPT_END, + ARGPARSE_OPT_GROUP, + /* options with no arguments */ + ARGPARSE_OPT_BOOLEAN, + ARGPARSE_OPT_BIT, + /* options with arguments (optional or required) */ + ARGPARSE_OPT_INTEGER, + ARGPARSE_OPT_FLOAT, + ARGPARSE_OPT_STRING, +}; + +enum argparse_option_flags { + OPT_NONEG = 1, /* disable negation */ +}; + +/** + * argparse option + * + * `type`: + * holds the type of the option, you must have an ARGPARSE_OPT_END last in your + * array. + * + * `short_name`: + * the character to use as a short option name, '\0' if none. + * + * `long_name`: + * the long option name, without the leading dash, NULL if none. + * + * `value`: + * stores pointer to the value to be filled. + * + * `help`: + * the short help message associated to what the option does. + * Must never be NULL (except for ARGPARSE_OPT_END). + * + * `callback`: + * function is called when corresponding argument is parsed. + * + * `data`: + * associated data. Callbacks can use it like they want. + * + * `flags`: + * option flags. + */ +struct argparse_option { + enum argparse_option_type type; + const char short_name; + const char *long_name; + void *value; + const char *help; + argparse_callback *callback; + intptr_t data; + int flags; +}; + +/** + * argpparse + */ +struct argparse { + // user supplied + const struct argparse_option *options; + const char *const *usages; + int flags; + const char *description; // a description after usage + const char *epilog; // a description at the end + // internal context + int argc; + const char **argv; + const char **out; + int cpidx; + const char *optvalue; // current option value +}; + +// built-in callbacks +int argparse_help_cb(struct argparse *self, + const struct argparse_option *option); + +// built-in option macros +#define OPT_END() { ARGPARSE_OPT_END, 0, NULL, NULL, 0, NULL, 0, 0 } +#define OPT_BOOLEAN(...) { ARGPARSE_OPT_BOOLEAN, __VA_ARGS__ } +#define OPT_BIT(...) { ARGPARSE_OPT_BIT, __VA_ARGS__ } +#define OPT_INTEGER(...) { ARGPARSE_OPT_INTEGER, __VA_ARGS__ } +#define OPT_FLOAT(...) { ARGPARSE_OPT_FLOAT, __VA_ARGS__ } +#define OPT_STRING(...) { ARGPARSE_OPT_STRING, __VA_ARGS__ } +#define OPT_GROUP(h) { ARGPARSE_OPT_GROUP, 0, NULL, NULL, h, NULL, 0, 0 } +#define OPT_HELP() OPT_BOOLEAN('h', "help", NULL, \ + "show this help message and exit", \ + argparse_help_cb, 0, OPT_NONEG) + +int argparse_init(struct argparse *self, struct argparse_option *options, + const char *const *usages, int flags); +void argparse_describe(struct argparse *self, const char *description, + const char *epilog); +int argparse_parse(struct argparse *self, int argc, const char **argv); +void argparse_usage(struct argparse *self); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/binn.c b/vendor/binn.c similarity index 100% rename from binn.c rename to vendor/binn.c diff --git a/include/binn.h b/vendor/binn.h similarity index 100% rename from include/binn.h rename to vendor/binn.h diff --git a/confini.c b/vendor/confini.c similarity index 100% rename from confini.c rename to vendor/confini.c diff --git a/include/confini.h b/vendor/confini.h similarity index 100% rename from include/confini.h rename to vendor/confini.h