diff --git a/examples-api-use/demo-main.cc b/examples-api-use/demo-main.cc index 40b8ae5..1d4d3a2 100644 --- a/examples-api-use/demo-main.cc +++ b/examples-api-use/demo-main.cc @@ -3,7 +3,8 @@ // This code is public domain // (but note, once linked against the led-matrix library, this is // covered by the GPL v2) - +// +// This is a grab-bag of various demos and not very readable. #include "led-matrix.h" #include "threaded-canvas-manipulator.h" #include "transformer.h" @@ -21,6 +22,9 @@ using std::min; using std::max; +#define TERM_ERR "\033[1;31m" +#define TERM_NORM "\033[0m" + using namespace rgb_matrix; /* @@ -1012,26 +1016,22 @@ private: static int usage(const char *progname) { fprintf(stderr, "usage: %s -D [optional parameter]\n", progname); - fprintf(stderr, "Options:\n" - "\t-r : Panel rows. '16' for 16x32 (1:8 multiplexing),\n" - "\t '32' for 32x32 (1:16), '8' for 1:4 multiplexing; " - "64 for 1:32 multiplexing. " - "Default: 32\n" - "\t-P : For Plus-models or RPi2: parallel chains. 1..3. " - "Default: 1\n" - "\t-c : Daisy-chained boards. Default: 1.\n" - "\t-L : 'Large' display, composed out of 4 times 32x32\n" - "\t-p : Bits used for PWM. Something between 1..11\n" - "\t-l : Don't do luminance correction (CIE1931)\n" - "\t-D : Always needs to be set\n" - "\t-d : run as daemon. Use this when starting in\n" - "\t /etc/init.d, but also when running without\n" - "\t terminal (e.g. cron).\n" - "\t-t : Run for these number of seconds, then exit.\n" - "\t (if neither -d nor -t are supplied, " + fprintf(stderr, "Options:\n"); + RGBMatrix::Options::FlagUsageMessage(); + fprintf(stderr, + "\t-L : 'Large' display, composed out of 4 times 32x32\n" + "\t-p : Bits used for PWM. Something between 1..11\n" + "\t-D : Always needs to be set\n" + "\t-d : run as daemon. Use this when starting " + "in\n" + "\t /etc/init.d, but also when " + "running without\n" + "\t terminal (e.g. cron).\n" + "\t-t : Run for these number of seconds, then exit.\n" + "\t (if neither -d nor -t are supplied, " "waits for )\n" - "\t-b : Sets brightness percent. Default: 100.\n" - "\t-R : Sets the rotation of matrix. " + "\t-b : Sets brightness percent. Default: 100.\n" + "\t-R : Sets the rotation of matrix. " "Allowed: 0, 90, 180, 270. Default: 0.\n"); fprintf(stderr, "Demos, choosen with -D\n"); fprintf(stderr, "\t0 - some rotating square\n" @@ -1066,6 +1066,11 @@ int main(int argc, char *argv[]) { const char *demo_parameter = NULL; + // First, let's consume the flags for the options. + if (!options.InitializeFromFlags(&argc, &argv)) { + return usage(argv[0]); + } + int opt; while ((opt = getopt(argc, argv, "dlD:t:r:P:c:p:b:m:LR:")) != -1) { switch (opt) { @@ -1081,18 +1086,6 @@ int main(int argc, char *argv[]) { runtime_seconds = atoi(optarg); break; - case 'r': - options.rows = atoi(optarg); - break; - - case 'P': - options.parallel = atoi(optarg); - break; - - case 'c': - options.chain_length = atoi(optarg); - break; - case 'm': scroll_ms = atoi(optarg); break; @@ -1120,6 +1113,28 @@ int main(int argc, char *argv[]) { rotation = atoi(optarg); break; + // These used to be options we understood, but deprecate now. Accept them + // for now, but tell the user. + case 'r': + options.rows = atoi(optarg); + fprintf(stderr, TERM_ERR "-r is a deprecated option. " + "Please use --led-rows=%d instead!\n" TERM_NORM, options.rows); + break; + + case 'P': + options.parallel = atoi(optarg); + fprintf(stderr, TERM_ERR "-P is a deprecated option. " + "Please use --led-parallel=%d instead!\n" TERM_NORM, + options.parallel); + break; + + case 'c': + options.chain_length = atoi(optarg); + fprintf(stderr, TERM_ERR "-c is a deprecated option. " + "Please use --led-chain=%d instead!\n" TERM_NORM, + options.chain_length); + break; + default: /* '?' */ return usage(argv[0]); } @@ -1129,44 +1144,32 @@ int main(int argc, char *argv[]) { demo_parameter = argv[optind]; } + std::string err; + if (!options.Validate(&err)) { + fprintf(stderr, "%s", err.c_str()); + return 1; + } + if (demo < 0) { - fprintf(stderr, "Expected required option -D \n"); + fprintf(stderr, TERM_ERR "Expected required option -D \n" TERM_NORM); return usage(argv[0]); } - if (getuid() != 0) { - fprintf(stderr, "Must run as root to be able to access /dev/mem\n" - "Prepend 'sudo' to the command:\n\tsudo %s ...\n", argv[0]); - return 1; - } - - if (options.rows != 8 && options.rows != 16 - && options.rows != 32 && options.rows != 64) { - fprintf(stderr, "Rows can one of 8, 16, 32 or 64 " - "for 1:4, 1:8, 1:16 and 1:32 multiplexing respectively.\n"); - return 1; - } - - if (options.chain_length < 1) { - fprintf(stderr, "Chain outside usable range\n"); - return 1; - } - if (options.chain_length > 8) { - fprintf(stderr, "That is a long chain. Expect some flicker.\n"); - } - if (options.parallel < 1 || options.parallel > 3) { - fprintf(stderr, "Parallel outside usable range.\n"); - return 1; - } - if (brightness < 1 || brightness > 100) { - fprintf(stderr, "Brightness is outside usable range.\n"); + fprintf(stderr, TERM_ERR "Brightness is outside usable range.\n" TERM_NORM); return 1; } if (rotation % 90 != 0) { - fprintf(stderr, "Rotation %d not allowed! " - "Only 0, 90, 180 and 270 are possible.\n", rotation); + fprintf(stderr, TERM_ERR "Rotation %d not allowed! " + "Only 0, 90, 180 and 270 are possible.\n" TERM_NORM, rotation); + return 1; + } + + if (getuid() != 0) { + fprintf(stderr, TERM_ERR "Must run as root to be able to access /dev/mem\n" + "Prepend 'sudo' to the command:\n\tsudo %s ...\n" TERM_NORM, + argv[0]); return 1; } diff --git a/examples-api-use/minimal-example.cc b/examples-api-use/minimal-example.cc index 7a0309f..80672f1 100644 --- a/examples-api-use/minimal-example.cc +++ b/examples-api-use/minimal-example.cc @@ -47,9 +47,15 @@ int main(int argc, char *argv[]) { * Set up the RGBMatrix. It implements a 'Canvas' interface. */ RGBMatrix::Options options; - options.rows = 32; // A 32x32 display. Use 16 when this is a 16x32 display. - options.chain_length = 1; // Number of boards chained together. - options.parallel = 1; // Number of chains in parallel (1..3). + if (!options.InitializeFromFlags(&argc, &argv)) { + options.FlagUsageMessage(); + return 1; + } + std::string err; + if (!options.Validate(&err)) { + fprintf(stderr, "%s", err.c_str()); + return 1; + } Canvas *canvas = new RGBMatrix(&io, options); DrawOnCanvas(canvas); // Using the canvas. diff --git a/examples-api-use/text-example.cc b/examples-api-use/text-example.cc index 91a27d2..582ecf6 100644 --- a/examples-api-use/text-example.cc +++ b/examples-api-use/text-example.cc @@ -19,17 +19,14 @@ static int usage(const char *progname) { fprintf(stderr, "usage: %s [options]\n", progname); fprintf(stderr, "Reads text from stdin and displays it. " "Empty string: clear screen\n"); - fprintf(stderr, "Options:\n" - "\t-f : Use given font.\n" - "\t-r : Display rows. 16 for 16x32, 32 for 32x32. " - "Default: 32\n" - "\t-P : For Plus-models or RPi2: parallel chains. 1..3. " - "Default: 1\n" - "\t-c : Daisy-chained boards. Default: 1.\n" - "\t-b : Sets brightness percent. Default: 100.\n" - "\t-x : X-Origin of displaying text (Default: 0)\n" - "\t-y : Y-Origin of displaying text (Default: 0)\n" - "\t-C : Color. Default 255,255,0\n"); + fprintf(stderr, "Options:\n"); + RGBMatrix::Options::FlagUsageMessage(); + fprintf(stderr, + "\t-f : Use given font.\n" + "\t-b : Sets brightness percent. Default: 100.\n" + "\t-x : X-Origin of displaying text (Default: 0)\n" + "\t-y : Y-Origin of displaying text (Default: 0)\n" + "\t-C : Color. Default 255,255,0\n"); return 1; } @@ -45,12 +42,14 @@ int main(int argc, char *argv[]) { int y_orig = -1; int brightness = 100; + // First, let's consume the flags for the options. + if (!options.InitializeFromFlags(&argc, &argv)) { + return usage(argv[0]); + } + int opt; - while ((opt = getopt(argc, argv, "r:P:c:x:y:f:C:b:")) != -1) { + while ((opt = getopt(argc, argv, "x:y:f:C:b:")) != -1) { switch (opt) { - case 'r': options.rows = atoi(optarg); break; - case 'P': options.parallel = atoi(optarg); break; - case 'c': options.chain_length = atoi(optarg); break; case 'b': brightness = atoi(optarg); break; case 'x': x_orig = atoi(optarg); break; case 'y': y_orig = atoi(optarg); break; @@ -80,24 +79,12 @@ int main(int argc, char *argv[]) { return usage(argv[0]); } - if (options.rows != 8 && options.rows != 16 - && options.rows != 32 && options.rows != 64) { - fprintf(stderr, "Rows can one of 8, 16, 32 or 64 " - "for 1:4, 1:8, 1:16 and 1:32 multiplexing respectively.\n"); + std::string err; + if (!options.Validate(&err)) { + fprintf(stderr, "%s", err.c_str()); return 1; } - if (options.chain_length < 1) { - fprintf(stderr, "Chain outside usable range\n"); - return 1; - } - if (options.chain_length > 8) { - fprintf(stderr, "That is a long chain. Expect some flicker.\n"); - } - if (options.parallel < 1 || options.parallel > 3) { - fprintf(stderr, "Parallel outside usable range.\n"); - return 1; - } if (brightness < 1 || brightness > 100) { fprintf(stderr, "Brightness is outside usable range.\n"); return 1; diff --git a/include/led-matrix.h b/include/led-matrix.h index e3a54b7..52419f8 100644 --- a/include/led-matrix.h +++ b/include/led-matrix.h @@ -21,6 +21,7 @@ #define RPI_RGBMATRIX_H #include +#include #include #include "gpio.h" @@ -50,6 +51,23 @@ public: struct Options { Options(); // Creates a default option set. + // Validate the options and possibly output a message to + bool Validate(std::string *err); + + // --led_rows, --led_chain, --led_parallel + // This modifies the argc and argv, so use in main such as + // int main(int argc, char *argv[]) { + // RGBMatrix::Options options; + // if (!options.InitializeFromFlags(&argc, &argv)) { + // return 1; + // } + // // ... now do the relevant stuff. + // } + bool InitializeFromFlags(int *argc, char ***argv); + + // Usage message that explains the available flags. + static void FlagUsageMessage(); + // The "rows" are the number // of rows supported by the display, so 32 or 16. Default: 32. int rows; diff --git a/lib/Makefile b/lib/Makefile index 441fa6a..facb754 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -3,7 +3,8 @@ # So # -lrgbmatrix ## -OBJECTS=gpio.o led-matrix.o framebuffer.o thread.o bdf-font.o graphics.o transformer.o led-matrix-c.o +OBJECTS=gpio.o led-matrix.o options-initialize.o framebuffer.o \ + thread.o bdf-font.o graphics.o transformer.o led-matrix-c.o TARGET=librgbmatrix ### diff --git a/lib/led-matrix.cc b/lib/led-matrix.cc index d8b2cf3..9151efd 100644 --- a/lib/led-matrix.cc +++ b/lib/led-matrix.cc @@ -119,7 +119,7 @@ private: unsigned requested_frame_multiple_; }; -// Some defaults. +// Some defaults. See options-initialize.cc for the command line parsing. RGBMatrix::Options::Options() : rows(32), chain_length(1), parallel(1) {} RGBMatrix::RGBMatrix(GPIO *io, const Options &options) diff --git a/lib/options-initialize.cc b/lib/options-initialize.cc new file mode 100644 index 0000000..2e6d664 --- /dev/null +++ b/lib/options-initialize.cc @@ -0,0 +1,128 @@ +// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*- +// Copyright (C) 2013, 2016 Henner Zeller +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation version 2. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see + +#include "led-matrix.h" +#include +#include +#include +#include + +namespace rgb_matrix { +namespace { +typedef char** argv_iterator; + +static bool ConsumeIntFlag(const char *flag_name, + argv_iterator &pos, const argv_iterator end, + int *result_value, int *error) { + const char *option = *pos; + const size_t flag_len = strlen(flag_name); + if (strncmp(option, flag_name, flag_len) != 0) + return false; // not consumed. + const char *value; + if (option[flag_len] == '=') // --option=42 # value in same arg + value = option + flag_len + 1; + else if (pos + 1 < end) { // --option 42 # value in next arg + value = *(++pos); + } else { + fprintf(stderr, "Parameter expected after %s\n", flag_name); + ++*error; + return true; // consumed, but error. + } + char *end_value = NULL; + int val = strtol(value, &end_value, 10); + if (!*value || *end_value) { + fprintf(stderr, "Couldn't parse parameter %s=%s " + "(Expected number but '%s' looks funny)\n", + flag_name, value, end_value); + ++*error; + return true; // consumed, but error + } + *result_value = val; + return true; // consumed. +} + +static bool OptFlagInit(int &argc, char **&argv, RGBMatrix::Options *mopts) { + argv_iterator it = &argv[0]; + argv_iterator end = it + argc; + + std::vector unused_options; + unused_options.push_back(*it++); // Not interested in program name + + int err = 0; + bool posix_end_option_seen = false; + for (/**/; it < end; ++it) { + posix_end_option_seen |= (strcmp(*it, "--") == 0); + if (!posix_end_option_seen) { + if (ConsumeIntFlag("--led-rows", it, end, &mopts->rows, &err)) + continue; + if (ConsumeIntFlag("--led-chain", it, end, &mopts->chain_length, &err)) + continue; + if (ConsumeIntFlag("--led-parallel", it, end, &mopts->parallel, &err)) + continue; + } + unused_options.push_back(*it); + } + + if (err > 0) { + return false; + } + + // Success. Re-arrange flags to only include the ones not consumed. + argc = (int) unused_options.size(); + for (int i = 0; i < argc; ++i) { + argv[i] = unused_options[i]; + } + return true; +} +} // namespace + +bool RGBMatrix::Options::InitializeFromFlags(int *argc, char ***argv) { + // Unfortunately, we can't use getopt_long(), as it does not provide a + // way to only fish out some of the flags and leave the rest as-is without + // much complaining. So we have to do this here ourselves. + return OptFlagInit(*argc, *argv, this); +} + +void RGBMatrix::Options::FlagUsageMessage() { + fprintf(stderr, + "\t--led-rows : Panel rows. 8, 16, 32 or 64. " + "Default: 32\n" + "\t--led-parallel : For Plus-models or RPi2: parallel " + "chains. 1..3. Default: 1\n" + "\t--led-chain : Number of daisy-chained boards. " + "Default: 1.\n"); +} + +bool RGBMatrix::Options::Validate(std::string *err) { + bool any_error = false; + if (rows != 8 && rows != 16 && rows != 32 && rows != 64) { + err->append("Invalid number or panel rows. " + "Should be one of 8, 16, 32 or 64\n"); + any_error = true; + } + + if (chain_length < 1) { + err->append("Chain-length outside usable range\n"); + any_error = true; + } + + if (parallel < 1 || parallel > 3) { + err->append("Parallel outside usable range.\n"); + any_error = true; + } + return !any_error; +} + +} // namespace rgb_matrix diff --git a/utils/led-image-viewer.cc b/utils/led-image-viewer.cc index a7df11d..d130d83 100644 --- a/utils/led-image-viewer.cc +++ b/utils/led-image-viewer.cc @@ -43,6 +43,9 @@ static void InterruptHandler(int signo) { interrupt_received = true; } +#define TERM_ERR "\033[1;31m" +#define TERM_NORM "\033[0m" + namespace { // Preprocess as much as possible, so that we can just exchange full frames // on VSync. @@ -147,16 +150,14 @@ static void DisplayAnimation(const std::vector &frames, static int usage(const char *progname) { fprintf(stderr, "usage: %s [options] \n", progname); - fprintf(stderr, "Options:\n" - "\t-r : Panel rows. '16' for 16x32 (1:8 multiplexing),\n" - "\t '32' for 32x32 (1:16), '8' for 1:4 multiplexing; 64 for 1:32 multiplexing." - "Default: 32\n" - "\t-P : For Plus-models or RPi2: parallel chains. 1..3. " - "Default: 1\n" - "\t-c : Daisy-chained boards. Default: 1.\n" - "\t-L : Large 64x64 display made from four 32x32 in a chain\n" - "\t-d : Run as daemon.\n" - "\t-b : Sets brightness percent. Default: 100.\n"); + fprintf(stderr, "Options:\n"); + RGBMatrix::Options::FlagUsageMessage(); + fprintf(stderr, + "\t-L : Large 64x64 display made " + "from four 32x32 in a chain\n" + "\t-d : Run as daemon.\n" + "\t-b : Sets brightness percent. " + "Default: 100.\n"); return 1; } @@ -169,12 +170,14 @@ int main(int argc, char *argv[]) { bool large_display = false; // example for using Transformers bool as_daemon = false; + // First, let's consume the flags for the options. + if (!options.InitializeFromFlags(&argc, &argv)) { + return usage(argv[0]); + } + int opt; while ((opt = getopt(argc, argv, "r:P:c:p:b:dL")) != -1) { switch (opt) { - case 'r': options.rows = atoi(optarg); break; - case 'P': options.parallel = atoi(optarg); break; - case 'c': options.chain_length = atoi(optarg); break; case 'p': pwm_bits = atoi(optarg); break; case 'd': as_daemon = true; break; case 'b': brightness = atoi(optarg); break; @@ -183,30 +186,40 @@ int main(int argc, char *argv[]) { options.rows = 32; large_display = true; break; + + // These used to be options we understood, but deprecate now. Accept them + // for now, but tell the user. + case 'r': + options.rows = atoi(optarg); + fprintf(stderr, TERM_ERR "-r is a deprecated option. " + "Please use --led-rows=%d instead!\n" TERM_NORM, options.rows); + break; + + case 'P': + options.parallel = atoi(optarg); + fprintf(stderr, TERM_ERR "-P is a deprecated option. " + "Please use --led-parallel=%d instead!\n" TERM_NORM, + options.parallel); + break; + + case 'c': + options.chain_length = atoi(optarg); + fprintf(stderr, TERM_ERR "-c is a deprecated option. " + "Please use --led-chain=%d instead!\n" TERM_NORM, + options.chain_length); + break; + default: return usage(argv[0]); } } - if (options.rows != 8 && options.rows != 16 - && options.rows != 32 && options.rows != 64) { - fprintf(stderr, "Rows can one of 8, 16, 32 or 64 " - "for 1:4, 1:8, 1:16 and 1:32 multiplexing respectively.\n"); + std::string err; + if (!options.Validate(&err)) { + fprintf(stderr, "%s", err.c_str()); return 1; } - if (options.chain_length < 1) { - fprintf(stderr, "Chain outside usable range\n"); - return usage(argv[0]); - } - if (options.chain_length > 8) { - fprintf(stderr, "That is a long chain. Expect some flicker.\n"); - } - if (options.parallel < 1 || options.parallel > 3) { - fprintf(stderr, "Parallel outside usable range.\n"); - return usage(argv[0]); - } - if (brightness < 1 || brightness > 100) { fprintf(stderr, "Brightness is outside usable range.\n"); return usage(argv[0]);