/* Blink.ino - CLI library sample implementing a CLI for the Blink sample code Version 1.0, latest version, documentation and bugtracker available at: https://gitlab.lindenaar.net/arduino/CLI Copyright (c) 2019 Frederik Lindenaar This example shows how to build a simple command line to control the default Blink example of the Arduino IDE. This requires additional code to control the parameters (maintained within the C++ objects for the commands) as well as a different main loop logic to become a real-time process with a Command Line in te backgrouns while blinking and no longer using delay() for timing. The implementation provides the following commands available through the Serial Monitor (available from the Tools menu of the Arduino IDE): - mode display and/or set the LED mode (either on, off or blink) - delay display and/or set the blinking delay for the led - faster double the blinking rate (half the delay) - slower half the blinking rate (double the delay) - help show the available commands and how to use (builtin command) For each of the commands an CLI_Command derived class is implemented with : - Private variables to hold the internal state of the command - Object constructor to set default values - a setparams() method to handle parameters (when applicable) - a execute() method for the implementation of the command - Getter (and Setter) methods to access object state (where applicable) This sketch is free software: you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation, or (at your option) a later version of the license. This code 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, visit to download it. */ #include enum LED_MODE { OFF, ON, BLINKING }; // LED modes implemented/supported #define BLINK_MIN_DELAY 25 // min blink delay is 25ms #define BLINK_MAX_DELAY 10000 // max blink delay is 10s // Convert macro to string (https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html) #define TO_STRING(val) #val #define VAL_TO_STRING(val) TO_STRING(val) // Store mode command parameters as static PROGMEM strings (needed below) const char CMD_MODE_PARAM_ON[] PROGMEM = "on"; const char CMD_MODE_PARAM_OFF[] PROGMEM = "off"; const char CMD_MODE_PARAM_BLINK[] PROGMEM = "blink"; // struct stored in PROGMEM to map the parameters for mode command to a value const struct CLI_Command_Param Mode_Command_Parameters[] PROGMEM = { { CMD_MODE_PARAM_ON, ON, }, { CMD_MODE_PARAM_OFF, OFF, }, { CMD_MODE_PARAM_BLINK, BLINKING, }, { NULL } }; class Mode_Command : CLI_Command { // Implementation of the mode command LED_MODE _mode; // Private variable to store current mode public: Mode_Command(CLI &cli, LED_MODE mode = OFF) : // Constructor with default initial mode CLI_Command(cli, // CLI to register with PSTR("mode"), // Command name PSTR("Set and/or show LED mode"), // Description PSTR("Usage:\tmode []\n" // Usage Help "where is one of:\n" "\ton\tturn LED on\n" "\toff\tturn LED off\n" "\tblink\tblink the LED\n")), _mode(mode) { }; bool setparams(const char *params) { // Called once before execute to process parameters if (params) { // Check if we have parameters // Use the CLI_STR_parseParam_P utility function to parse the parameter const struct CLI_Command_Param *p = CLI_STR_parseParam_P(params, Mode_Command_Parameters); if (!p) return false; // return false in case of an invalid parameter _mode = pgm_read_word(&p->uint16); // set led_mode to the one of the parameter if (_mode != BLINKING) // ensure LED state == led_mode if not blinking digitalWrite(LED_BUILTIN, _mode == ON); } return true; // Return true in case of no or a valid parameter } bool execute(CLI &cli) { // Implementation, called as long as it returns true cli.print_P(PSTR("LED mode is ")); // Simply show the LED mode (need to do that always) cli.print_P((_mode == ON) ? PSTR("ON") : (_mode == OFF) ? PSTR("OFF") : (_mode == BLINKING) ? PSTR("blink") : PSTR("unknown")); return false; // return false as we're done } inline LED_MODE get() { // Getter for the mode return _mode; } inline void set(LED_MODE mode) { // Setter for the mode _mode = mode; } }; class Delay_Command : CLI_Command { // Implementation of the delay command unsigned int _delay; // Private variable to store current delay public: Delay_Command(CLI &cli) : // Constructor CLI_Command(cli, // CLI to register with PSTR("delay"), // Command name PSTR("set and/or show LED blink delay"), // Description PSTR("Usage:\tdelay []\n" // Usage Help "\twhere is the delay in milliseconds (integer between " VAL_TO_STRING(BLINK_MIN_DELAY) " and " VAL_TO_STRING(BLINK_MAX_DELAY) ")\n")) { _delay = 1000; }; bool setparams(const char *params) { // Called once before execute to process parameters if (params) { // Check if we have parameters int d; params = CLI_STR_parseInt(params, d); // parse params using CLI_STR_parseInt utility function if (params && !*params) { // check if an int was found and reached end of params return set(d); // return true if all OK or false when not within range } return false; // return false if no int or more param input was found } return true; // return true in case of no or a valid parameter } bool execute(CLI &cli) { // Implementation, called as long as it returns true cli.print_P(PSTR("blink delay ")); // Simply show the blink delay (need to do that always) cli.print(_delay); cli.print_P(PSTR(" milliseconds\n")); return false; // return false as we're done } inline unsigned int get() { // Getter for the mode return _delay; } inline bool set(unsigned int d) { // Setter for the mode, checking it is within range if (d < BLINK_MIN_DELAY || d > BLINK_MAX_DELAY) return false; // Return false if new value is out of bounds _delay = d; // store new value return true; // return true as new value is OK } }; class Faster_Command : CLI_Command { // Implementation of the faster command Delay_Command &_delay; // Private variable to store delay command public: Faster_Command(CLI &cli, Delay_Command &delay) : // Constructor, requires delay command reference CLI_Command(cli, // CLI to register with PSTR("faster"), // Command name PSTR("Blink faster")), // Description, no Usage Help provided _delay(delay) { }; // Store reference to delay command, empty constructor // Please note: no parameters will be accepted (using default setparams() implementation) bool execute(CLI &cli) { // Implementation, called as long as it returns true if (_delay.set(_delay.get() / 2)) { // Half the blink time, returns false if outside range _delay.setparams(0); // clear _delay's parameters as we call it's execute() next _delay.execute(cli); // call _delay's execute() method to print the delay } else { cli.print_P(PSTR("Can't blink faster"));// print an error message if half blink delay is out of bounds } return false; // return false as we're done } }; class Slower_Command : CLI_Command { // Implementation of the slower command Delay_Command &_delay; // Private variable to store delay command public: Slower_Command(CLI &cli, Delay_Command &delay) : // Constructor, requires delay command reference CLI_Command(cli, // CLI to register with PSTR("slower"), // Command name PSTR("Blink slower")), // Description, no Usage Help provided _delay(delay) { }; // Store reference to delay command, empty constructor // Please note: no parameters will be accepted (using default setparams() implementation) bool execute(CLI &cli) { // Implementation, called as long as it returns true if (_delay.set(_delay.get() * 2)) { // Half the blink time, returns false if outside range _delay.setparams(0); // clear _delay's parameters as we call it's execute() next _delay.execute(cli); // call _delay's execute() method to print the delay } else { cli.print_P(PSTR("Can't blink slower"));// print an error message if half blink delay is out of bounds } return false; // return false as we're done } }; // Initialize the Blink Command Line Interface const char banner[] PROGMEM = "Blink Sample CLI"; // Banner to show upon startup of the CLI CLI CLI(Serial, banner); // Initialize the CLI, telling it to attach to Serial Mode_Command Mode(CLI, BLINKING); // Initialize/Register mode command, BLINKING initially Delay_Command Delay(CLI); // Initialize/Register delay command Faster_Command Faster(CLI, Delay); // Initialize/Register faster command Slower_Command Slower(CLI, Delay); // Initialize/Register slower command Help_Command Help(CLI); // Initialize/Register (built-in) help command // the setup function runs once when you reset or power the board void setup() { // initialize digital pin LED_BUILTIN as an output. pinMode(LED_BUILTIN, OUTPUT); // Initialize the Serial port for the CLI while (!Serial); // For Leonardo: wait for serial USB to connect Serial.begin(9600); } // variable to be preserved unsigned long last_blink = 0; // the loop function runs over and over again forever void loop() { // First handle CLI, if this returns true, a command is running and the code // block with blink logic is skipped till the command has finished executing if (!CLI.process()) { // check if mode is blinking and last_blink was longer ago than delay // millis() will wrap every 49 days, below approach is wrap-proof if (Mode.get() == BLINKING && millis() - last_blink > Delay.get()) { last_blink = millis(); // led state will change, store when digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // invert LED PIN } } }