diff --git a/libraries/AP_Common/AP_Common.h b/libraries/AP_Common/AP_Common.h index 5da5234e60..8ad2f0fbf5 100644 --- a/libraries/AP_Common/AP_Common.h +++ b/libraries/AP_Common/AP_Common.h @@ -6,23 +6,18 @@ // your option) any later version. // -#ifndef _AP_COMMON_H -#define _AP_COMMON_H - -#include - /// /// @file AP_Common.h /// @brief Common definitions and utility routines for the ArduPilot /// libraries. /// -/// @note For correct operation, all sketches and libraries should -/// include this header *before* any other. In -/// particular, this is critical for things like the -/// FastSerial library, which need the opportunity to -/// override parts of the Arduino infrastructure. -/// +#ifndef _AP_COMMON_H +#define _AP_COMMON_H + +#include + +#include "include/menu.h" /// simple menu subsystem //////////////////////////////////////////////////////////////////////////////// /// @name Types diff --git a/libraries/AP_Common/examples/menu/menu.pde b/libraries/AP_Common/examples/menu/menu.pde new file mode 100644 index 0000000000..4864e582d0 --- /dev/null +++ b/libraries/AP_Common/examples/menu/menu.pde @@ -0,0 +1,38 @@ + +#include +#include + +#include + +FastSerialPort0(Serial); + +int +menu_test(uint8_t argc, const Menu::arg *argv) +{ + int i; + + Serial.printf("This is a test with %d arguments\n", argc); + for (i = 1; i < argc; i++) { + Serial.printf("%d: int %ld float ", i, argv[i].i); + Serial.println(argv[i].f, 6); // gross + } +} + +const struct Menu::command top_menu_commands[] PROGMEM = { + {"test", menu_test}, +}; + +MENU(top, "menu", top_menu_commands); + +void +setup(void) +{ + Serial.begin(38400); + top.run(); +} + +void +loop(void) +{ +} + diff --git a/libraries/AP_Common/include/menu.h b/libraries/AP_Common/include/menu.h new file mode 100644 index 0000000000..498cb407c5 --- /dev/null +++ b/libraries/AP_Common/include/menu.h @@ -0,0 +1,61 @@ +// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: t -*- + +/// @file menu.h +/// @brief Simple commandline menu subsystem. + +#define MENU_COMMANDLINE_MAX 32 ///< maximum input line length +#define MENU_ARGS_MAX 4 ///< maximum number of arguments +#define MENU_COMMAND_MAX 14 ///< maximum size of a command name + +/// Class defining and handling one menu tree +class Menu { +public: + /// argument passed to a menu function + struct arg { + 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 + /// + typedef int (*func)(uint8_t argc, const struct arg *argv); + + /// menu command description + /// + /// Note that the array of menu commands is expected to be in program + /// memory. + struct command { + const char command[MENU_COMMAND_MAX]; ///< name of the command + int (*func)(uint8_t argc, const struct arg *argv); ///< callback function + }; + + /// constructor + /// + /// @param prompt The prompt to be displayed with this menu. + /// @param commands An array of ::command structures. + /// @param entries The number of entries in the menu. + /// + Menu(const char *prompt, const struct command *commands, uint8_t entries); + + /// menu runner + void run(void); + +private: + void _help(void); ///< implements the 'help' command + const char *_prompt; ///< prompt to display + const command *_commands; ///< array of commands + const uint8_t _entries; ///< size of the menu + + static char _inbuf[MENU_COMMANDLINE_MAX]; ///< input buffer + static arg _argv[MENU_ARGS_MAX + 1]; ///< arguments +}; + +/// Macro used to define a menu. +/// +/// Use name.run() to run the menu. +/// +#define MENU(name, prompt, commands) \ + static const char __menu_name__ ##name[] PROGMEM = prompt; \ + static Menu name(__menu_name__ ##name, commands, sizeof(commands) / sizeof(commands[0])) + diff --git a/libraries/AP_Common/menu.cpp b/libraries/AP_Common/menu.cpp new file mode 100644 index 0000000000..0dc426be26 --- /dev/null +++ b/libraries/AP_Common/menu.cpp @@ -0,0 +1,108 @@ +// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: t -*- + +// +// Simple commandline menu system. +// + +#include + +#include +#include +#include + +#include "include/menu.h" + +// statics +char Menu::_inbuf[MENU_COMMANDLINE_MAX]; +Menu::arg Menu::_argv[MENU_ARGS_MAX + 1]; + +// constructor +Menu::Menu(const prog_char *prompt, const Menu::command *commands, uint8_t entries) : + _prompt(prompt), + _commands(commands), + _entries(entries) +{ +} + +// run the menu +void +Menu::run(void) +{ + uint8_t len, i, ret; + uint8_t argc; + int c; + func fn; + + // loop performing commands + for (;;) { + + // loop reading characters from the input + len = 0; + Serial.printf("%S] ", _prompt); + for (;;) { + c = Serial.read(); + if (-1 == c) + continue; + // carriage return -> process command + if ('\r' == c) { + _inbuf[len] = '\0'; + Serial.write('\r'); + Serial.write('\n'); + break; + } + // backspace + if ('\b' == c) { + if (len > 0) { + len--; + Serial.write('\b'); + Serial.write(' '); + Serial.write('\b'); + continue; + } + } + // printable character + if (isprint(c) && (len < (MENU_COMMANDLINE_MAX - 1))) { + _inbuf[len++] = c; + Serial.write((char)c); + continue; + } + } + + // split the input line into tokens + argc = 0; + _argv[argc++].str = strtok(_inbuf, " "); + while (argc <= MENU_ARGS_MAX) { + _argv[argc].str = strtok(NULL, " "); + if ('\0' == _argv[argc].str) + break; + _argv[argc].i = atol(_argv[argc].str); + _argv[argc].f = atof(_argv[argc].str); // calls strtod, > 700B ! + argc++; + } + + // look for a command matching the first word (note that it may be empty) + for (i = 0; i < _entries; i++) { + if (!strcmp_P(_argv[0].str, _commands[i].command)) { + fn = (func)pgm_read_word(&_commands[i].func); + ret = fn(argc, &_argv[0]); + if (-2 == ret) + return; + } + } + + // if the menu doesn't provide more comprehensive help, print the command list + if ((i == _entries) && (!strcmp(_argv[0].str, "?") || (!strcmp_P(_argv[0].str, PSTR("help"))))) + _help(); + } +} + +// display the list of commands in response to the 'help' command +void +Menu::_help(void) +{ + int i; + + Serial.println("Commands:"); + for (i = 0; i < _entries; i++) + Serial.printf(" %S\n", _commands[i].command); +}