Andy Little
/
userMenu
Simple Menu for user config at startup etc
Revision 0:694155408133, committed 2020-03-29
- Comitter:
- skyscraper
- Date:
- Sun Mar 29 16:00:36 2020 +0000
- Commit message:
- Initial commit
Changed in this revision
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