0,0 → 1,155 |
// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: t -*- |
|
// |
// Simple commandline menu system. |
// |
|
#include <FastSerial.h> |
#include <AP_Common.h> |
|
#include <ctype.h> |
#include <string.h> |
#include <avr/pgmspace.h> |
|
#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, preprompt ppfunc) : |
_prompt(prompt), |
_commands(commands), |
_entries(entries), |
_ppfunc(ppfunc) |
{ |
} |
|
// run the menu |
void |
Menu::run(void) |
{ |
int8_t ret; |
uint8_t len, i; |
uint8_t argc; |
int c; |
char *s; |
|
// loop performing commands |
for (;;) { |
|
// run the pre-prompt function, if one is defined |
if ((NULL != _ppfunc) && !_ppfunc()) |
return; |
|
// loop reading characters from the input |
len = 0; |
Serial.printf("%S] ", FPSTR(_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_r(_inbuf, " ", &s); |
// XXX should an empty line by itself back out of the current menu? |
while (argc <= MENU_ARGS_MAX) { |
_argv[argc].str = strtok_r(NULL, " ", &s); |
if ('\0' == _argv[argc].str) |
break; |
_argv[argc].i = atol(_argv[argc].str); |
_argv[argc].f = atof(_argv[argc].str); // calls strtod, > 700B ! |
argc++; |
} |
|
if (_argv[0].str == NULL) { |
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 |
i = argc; |
while (i <= MENU_ARGS_MAX) { |
_argv[i].str = ""; |
_argv[i].i = 0; |
_argv[i].f = 0; |
i++; |
} |
|
bool cmd_found = false; |
// look for a command matching the first word (note that it may be empty) |
for (i = 0; i < _entries; i++) { |
if (!strcasecmp_P(_argv[0].str, _commands[i].command)) { |
ret = _call(i, argc); |
cmd_found=true; |
if (-2 == ret) |
return; |
break; |
} |
} |
|
// implicit commands |
if (i == _entries) { |
if (!strcmp(_argv[0].str, "?") || (!strcasecmp_P(_argv[0].str, PSTR("help")))) { |
_help(); |
cmd_found=true; |
} else if (!strcasecmp_P(_argv[0].str, PSTR("exit"))) { |
return; |
} |
} |
|
if (cmd_found==false) |
{ |
Serial.println("Invalid command, type '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", FPSTR(_commands[i].command)); |
} |
|
// run the n'th command in the menu |
int8_t |
Menu::_call(uint8_t n, uint8_t argc) |
{ |
func fn; |
|
fn = (func)pgm_read_pointer(&_commands[n].func); |
return(fn(argc, &_argv[0])); |
} |