Blink.ino 12.1 KB
/*
   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 <http://www.gnu.org/licenses/> to download it.
*/

#include <CLI.h>

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 [<led_mode>]\n"        // Usage Help
                       "where <led_mode> 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 [<blink_delay>]\n"    // Usage Help
                       "\twhere <blink_delay> 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
    }
  }
}