Simple Menu for user config at startup etc

Files at this revision

API Documentation at this revision

Comitter:
skyscraper
Date:
Sun Mar 29 16:00:36 2020 +0000
Commit message:
Initial commit

Changed in this revision

main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed-os.lib Show annotated file Show diff for this revision Revisions of this file
userMenu.cpp Show annotated file Show diff for this revision Revisions of this file
userMenu.h Show annotated file Show diff for this revision Revisions of this file
diff -r 000000000000 -r 694155408133 main.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Sun Mar 29 16:00:36 2020 +0000
@@ -0,0 +1,42 @@
+
+
+#include "userMenu.h"
+#include <iostream>
+
+template <int N>
+int8_t
+menu(uint8_t argc, const Menu::arg *argv)
+{
+   std::cout << " This is menu " << N << "\n";
+   if ( argc > 1) {
+       std::cout << "args = " ;
+       for ( uint8_t i = 1U; i < argc; ++i){
+           if ( i > 1){
+               std::cout << ", ";
+            }
+           std::cout << argv[i].str ;
+       }
+       std::cout << '\n';
+   }
+   return 1;
+}
+
+constexpr struct Menu::command mainMenuCommands[] = {
+    {"menu1", menu<1>},
+    {"menu2", menu<2>},
+    {"menu3", menu<3>}
+};
+
+MENU(main_menu, "User_menu", mainMenuCommands);
+
+void user_menu()
+{
+   main_menu.run();
+}
+
+
+int main()
+{
+  
+    user_menu();
+}
\ No newline at end of file
diff -r 000000000000 -r 694155408133 mbed-os.lib
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed-os.lib	Sun Mar 29 16:00:36 2020 +0000
@@ -0,0 +1,1 @@
+https://github.com/ARMmbed/mbed-os.git/#83926138aae9cd77e8c74ec8678b38417bebaa3d
diff -r 000000000000 -r 694155408133 userMenu.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/userMenu.cpp	Sun Mar 29 16:00:36 2020 +0000
@@ -0,0 +1,168 @@
+
+
+//
+/// Simple commandline menu system.
+/// Originally from https://code.google.com/archive/p/ardu-imu/downloads
+
+
+#include <ctype.h>
+#include <string.h>
+
+#include <iostream>
+
+#include "userMenu.h"
+
+// statics
+char Menu::_inbuf[commandlineLenMax];
+Menu::arg Menu::_argv[argsLenMax+ 1];
+
+// constructor
+Menu::Menu(const char*prompt, const Menu::command *commands, uint8_t entries, preprompt ppfunc) :
+    _prompt(prompt),
+    _commands(commands),
+    _entries(entries),
+    _ppfunc(ppfunc)
+{
+}
+namespace {
+   // could be a eeprom option
+   // If terminal at PC end already echoes then don't echo here
+   static constexpr bool serial_echo = false;
+}
+
+// run the menu
+void
+Menu::run(void)
+{
+   for (;;) {
+      if(_ppfunc != nullptr){
+      // run the included pre-prompt function
+         if ( !_ppfunc()){
+            return;
+         }
+      }
+    //  Serial.print(_prompt);
+    //  Serial.print(']');
+      std::cout << _prompt << ']';
+      for (uint8_t len = 0,done = false; done == false;) {
+        // int c = -1;// = Serial.read();
+         int c = std::cin.get();
+         switch(c) {
+            case -1:
+               //eof
+            break;
+            case '\r': 
+               // carriage return -> process command
+               _inbuf[len] = '\0';
+               if ( serial_echo){
+                //  Serial.write('\r');
+                //  Serial.write('\n');
+                 std::cout << '\r';
+               }
+               std::cout << '\n';
+               done = true;
+            break;
+            case '\b':
+               // backspace
+               if (len > 0) {
+                  len--;
+                  if ( serial_echo){
+                   //  Serial.write('\b');
+                     std::cout << '\b';
+                  }
+                 // Serial.write(' ');
+                 // Serial.write('\b');
+                 std::cout << " \b";
+               }
+            break;
+            default:
+               // printable character
+               if (isprint(c) && (len < (commandlineLenMax - 1))) {
+                  _inbuf[len++] = c;
+                  if(serial_echo){
+                    // Serial.write((char)c);
+                      std::cout << static_cast<char>(c);
+                  }
+               }
+            break;
+         }
+      }
+      // split the input line into tokens
+      uint8_t argc = 0;
+      char      *s = nullptr;
+      _argv[argc++].str = strtok_r(_inbuf, " ", &s);
+      // XXX should an empty line by itself back out of the current menu?
+      for ( ;argc <= argsLenMax; ++argc) {
+         _argv[argc].str = strtok_r(nullptr, " ", &s);
+         if (_argv[argc].str != nullptr){
+            _argv[argc].i = atol(_argv[argc].str);
+            _argv[argc].f = atof(_argv[argc].str);  // calls strtod, > 700B !
+         }else{
+            break;
+         }
+      }
+
+      if (_argv[0].str == nullptr) {
+         continue;
+      }
+
+      // populate arguments that have not been specified with "" and 0
+      // this is safer than NULL in the case where commands may look
+      // without testing argc
+      for(int i = argc; i <= argsLenMax;++i){
+         _argv[i].str = nullptr;
+         _argv[i].i = 0;
+         _argv[i].f = 0;
+      }
+
+      bool cmd_found = false;
+      // look for a command matching the first word (note that it may be empty)
+      int i = 0;
+      for (; i < _entries; ++i) {
+         if (!strcmp(_argv[0].str, _commands[i].command)) {
+            int8_t ret = _call(i, argc);
+            cmd_found=true;
+            if (-2 == ret){
+               return;
+            }
+            break;
+         }
+      }
+
+      // implicit commands
+      if (i == _entries) {
+         if (!strcmp(_argv[0].str, "?") || (!strcmp(_argv[0].str, "help"))) {
+            _help();
+            cmd_found=true;
+         } else if (!strcmp(_argv[0].str, "exit")) {
+            return;
+         }
+      }
+
+      if (cmd_found == false){
+         //Serial.println("Invalid command, type 'help'");
+         std::cout << "Invalid command, type 'help'\n" ;
+      }
+   }
+}
+
+// display the list of commands in response to the 'help' command
+void
+Menu::_help(void)
+{
+  //  Serial.println("Commands:");
+    std::cout << "Commands:\n";
+    for (int i = 0; i < _entries; i++){
+        std::cout << "   " << _commands[i].command << '\n';
+      //  Serial.print("   ");
+      //  Serial.print(_commands[i].command);
+      //  Serial.print('\n');
+    } 
+}
+
+// run the n'th command in the menu
+int8_t
+Menu::_call(uint8_t n, uint8_t argc)
+{
+   return _commands[n].func(argc, &_argv[0]);
+}
diff -r 000000000000 -r 694155408133 userMenu.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/userMenu.h	Sun Mar 29 16:00:36 2020 +0000
@@ -0,0 +1,151 @@
+///
+/// @file   userMenu.h
+/// @brief  Simple commandline menu subsystem.
+/// @discussion
+/// Menu derived from old version of ArduPilot, used in ArduIMU.V3
+/// Originally from https://code.google.com/archive/p/ardu-imu/downloads
+///
+/// The Menu class implements a simple CLI that accepts commands typed by
+/// the user, and passes the arguments to those commands to a function
+/// defined as handing the command.
+///
+/// Commands are defined in an array of Menu::command structures passed
+/// to the constructor.  Each entry in the array defines one command.
+///
+/// Arguments passed to the handler function are pre-converted to both
+/// long and float for convenience.
+
+#ifndef SKYSCRAPER_MBED_USER_MENU_H_INCLUDED
+#define SKYSCRAPER_MBED_USER_MENU_H_INCLUDED
+
+#include <inttypes.h>
+
+//#define MENU_COMMANDLINE_MAX    32  
+//#define MENU_ARGS_MAX           4   
+//#define MENU_COMMAND_MAX        14  
+
+/// Class defining and handling one menu tree
+class Menu {
+public:
+
+
+    static constexpr uint8_t commandlineLenMax = 32U;     ///< maximum input line length
+    static constexpr uint8_t argsLenMax        = 4U;   ///< maximum number of arguments
+    static constexpr uint8_t commandNameLenMax   = 14; ///< maximum length of a command name
+    /// argument passed to a menu function
+    ///
+    /// Space-delimited arguments are parsed from the commandline and
+    /// separated into these structures.
+    ///
+    /// If the argument cannot be parsed as a float or a long, the value
+    /// of f or i respectively is undefined.  You should range-check
+    /// the inputs to your function.
+    ///
+    struct arg {
+        arg(): str{nullptr}, i{0},f{0.0}{}
+        const char  *str;           ///< string form of the argument
+        long        i;              ///< integer form of the argument (if a number)
+        float       f;              ///< floating point form of the argument (if a number)
+    };
+
+    /// menu command function
+    ///
+    /// Functions called by menu array entries are expected to be of this
+    /// type.
+    ///
+    /// @param  argc        The number of valid arguments, including the
+    ///                     name of the command in argv[0].  Will never be
+    ///                     more than argsLenMax.
+    /// @param  argv        Pointer to an array of Menu::arg structures
+    ///                     detailing any optional arguments given to the
+    ///                     command.  argv[0] is always the name of the
+    ///                     command, so that the same function can be used
+    ///                     to handle more than one command.
+    ///
+    typedef int8_t          (*func)(uint8_t argc, const struct arg *argv);
+
+    /// menu pre-prompt function
+    ///
+    /// Called immediately before waiting for the user to type a command; can be
+    /// used to display help text or status, for example.
+    ///
+    /// If this function returns false, the menu exits.
+    ///
+    typedef bool            (*preprompt)(void);
+
+    /// menu command description
+    ///
+    struct command {
+        /// Name of the command, as typed or received.
+        /// Command names are limited in size to keep this structure compact.
+        ///
+        const char  command[commandNameLenMax];
+
+        /// The function to call when the command is received.
+        ///
+        /// The argc argument will be at least 1, and no more than
+        /// argsLenMax.  The argv array will be populated with
+        /// arguments typed/received up to argsLenMax.  The command
+        /// name will always be in argv[0].
+        ///
+        /// Commands may return -2 to cause the menu itself to exit.
+        /// The "?", "help" and "exit" commands are always defined, but
+        /// can be overridden by explicit entries in the command array.
+        ///
+        int8_t          (*func)(uint8_t argc, const struct arg *argv);  ///< callback function
+    };
+
+    /// constructor
+    ///
+    /// Note that you should normally not call the constructor directly.  Use
+    /// the MENU and MENU2 macros defined below.
+    ///
+    /// @param prompt       The prompt to be displayed with this menu.
+    /// @param commands     An array of ::command structures in program memory (PROGMEM).
+    /// @param entries      The number of entries in the menu.
+    ///
+    Menu(const char *prompt, const struct command *commands, uint8_t entries, preprompt ppfunc = 0);
+
+    /// menu runner
+    void                run(void);
+
+private:
+    /// Implements the default 'help' command.
+    ///
+    void                _help(void);                    ///< implements the 'help' command
+
+    /// calls the function for the n'th menu item
+    ///
+    /// @param n            Index for the menu item to call
+    /// @param argc         Number of arguments prepared for the menu item
+    ///
+    int8_t              _call(uint8_t n, uint8_t argc);
+
+    const char          *_prompt;                       ///< prompt to display
+    const command       *_commands;                     ///< array of commands
+    const uint8_t       _entries;                       ///< size of the menu
+    const preprompt     _ppfunc;                        ///< optional pre-prompt action
+
+    static char         _inbuf[commandlineLenMax];   ///< input buffer
+    static arg          _argv[argsLenMax+ 1];       ///< arguments
+};
+
+/// Macros used to define a menu.
+///
+/// The commands argument should be an arary of Menu::command structures, one
+/// per command name.  The array does not need to be terminated with any special
+/// record.
+///
+/// Use name.run() to run the menu.
+///
+/// The MENU2 macro supports the optional pre-prompt printing function.
+///
+#define MENU(name, prompt, commands)                            \
+    static constexpr char __menu_name__ ##name[] = prompt;  \
+    static Menu name(__menu_name__ ##name, commands, sizeof(commands) / sizeof(commands[0]))
+
+#define MENU2(name, prompt, commands, preprompt)                \
+    static constexpr char __menu_name__ ##name[] = prompt;  \
+    static Menu name(__menu_name__ ##name, commands, sizeof(commands) / sizeof(commands[0]), preprompt)
+
+#endif