Initial Check-in of CLI Library

This commit is contained in:
2019-04-06 15:20:52 +02:00
commit 47b5f8d1ea
15 changed files with 2477 additions and 0 deletions

178
src/CLI.cpp Normal file
View File

@@ -0,0 +1,178 @@
/*
* CLI.cpp - CLI library for Arduino/ESP8266 and others implementation
*
* Version 1.0, latest version, documentation and bugtracker available at:
* https://gitlab.lindenaar.net/arduino/CLI
*
* Copyright (c) 2019 Frederik Lindenaar
*
* This library 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>
#define CHAR_TAB '\t'
#define CHAR_BS '\b'
#define CHAR_DELETE char(127)
#define CHAR_CR '\r'
#define CHAR_LF '\n'
const char CLI_DEFAULT_PROMPT[] PROGMEM = "\n> ";
const char CLI_PREFIX_PARAMETER_ERROR[] PROGMEM = "Invalid parameters for ";
const char CLI_PREFIX_UNKNOWN_COMMAND[] PROGMEM = "Unknown command ";
size_t CLI::print_P (const char *str PROGMEM) {
size_t len = 0;
if (str) {
while (char c = pgm_read_byte(str++)) {
write(c);
len++;
}
}
return len;
}
void CLI::print2digits(uint8_t num, char filler, uint8_t base) {
if (num < base) print(filler);
print(num, base);
}
void CLI::print_mem(uint16_t addr, const uint8_t buff[], uint8_t len, uint8_t width) {
if (addr < 0x1000) print(' ');
if (addr < 0x100) print(' ');
if (addr < 0x10) print(' ');
print(addr, HEX);
print(':');
for (uint8_t i = 0; i < len; i++) {
print(' ');
if (buff[i] < 0x10) print(0);
print(buff[i], HEX);
}
while (width > len++) print_P(PSTR(" "));
print('\t');
for (uint8_t i = 0; i < len; i++)
write(isprint(buff[i]) ? buff[i] : '.');
println();
}
void CLI::printtab() {
print(CHAR_TAB);
};
CLI::CLI(Stream &stream, const char *banner PROGMEM, const char *prompt PROGMEM) : _stream(&stream), _banner(banner) {
_state = STATE_INIT;
_command_count = 0;
_prompt = (prompt) ? prompt : CLI_DEFAULT_PROMPT;
};
void CLI::add_command(CLI_Command *cmd) {
if (_command_count < CLI_MAX_CMDS)
_commands[_command_count++] = cmd;
};
CLI_Command *CLI::get_command(int index) {
return (index < _command_count) ? _commands[index] : NULL;
}
CLI_Command *CLI::get_command(const char *name, int namelen) {
for (int i = 0; i < _command_count; i++) {
if (_commands[i]->matches(name, namelen)) return _commands[i];
}
return NULL;
}
#if CLI_MAX_CMDS < 255
uint8_t CLI::find_command(const char *name, int namelen) {
uint8_t idx = _command_count;
#else
uint16_t CLI::find_command(const char *name, int namelen) {
uint16_t idx = _command_count;
#endif
while (idx-- && !_commands[idx]->matches(name, namelen));
return idx;
}
bool CLI::process() { // Workhorse of the CLI
if (_state == STATE_INIT) { // INIT: print banner (if set)
if (_banner) {
print_P(_banner);
println();
}
_state = STATE_READY;
} else if (_state == STATE_READY) { // READY: print prompt & init
print_P(_prompt);
memset(_cmdbuffer, 0, _line_len);
_line_len = 0;
_current_cmd = NULL;
_state = STATE_INPUT;
} else if (_state == STATE_INPUT) { // INPUT: process user input
while (available()) { // loop while stream input
int c = read(); // get next char
if (c == CHAR_DELETE) { // Process BACKSPACE key
print_P(PSTR("\b \b"));
if (_line_len) _cmdbuffer[_line_len--] = 0;
} else if (c == CHAR_CR) { // Process ENTER key
println();
_state = STATE_PARSE; // Change state to PARSE
return false; // and stop processing input
} else if (c != CHAR_LF && isprint(c)) { // Process other valid input
if (_line_len < CLI_MAX_LINE - 1) { // add to line if space left
_cmdbuffer[_line_len++] = c;
write(c); // print char to echo input
} else { // if not, beep & ignore
write(char(7));
}
}
}
} else if (_state == STATE_PARSE) { // PARSE: parse user command
const char *cmd = CLI_STR_skipSep(_cmdbuffer); // skip initial whitespace
if (*cmd && cmd - _cmdbuffer < _line_len) { // do we have a command?
for (char *p = _cmdbuffer + _line_len - 1; *p == ' '; p--, _line_len--)
*p = 0; // clear trailing whitespace
const char *sep = CLI_STR_findSep(cmd); // find end of command
if (_current_cmd = get_command(cmd, sep - cmd)) { // known command?
sep = CLI_STR_skipSep(sep); // get start of params
if (_current_cmd->setparams((*sep) ? sep : 0)) {// parse params
_state = STATE_EXEC; // all OK, change state
return true; // and stop processing
} else print_P(CLI_PREFIX_PARAMETER_ERROR); // print invalid parameters
} else print_P(CLI_PREFIX_UNKNOWN_COMMAND) ; // print unknown command
_stream->write(cmd, sep - cmd); // print command name
println(); // and add newline
}
_state = STATE_READY; // if we get here: READY
} else if (_state == STATE_EXEC) { // EXEC: execute the command
if (_current_cmd->execute(*this)) { // Execute, false when done
return true; // not yet done, return true
} else {
_state = STATE_READY; // done, READY
}
}
return false; // default result false (done)
}
CLI_Command::CLI_Command(CLI &cli, const char *command, const char *description, const char *usagehelp) : command(command), description(description), usagehelp(usagehelp) {
cli.add_command(this);
};
bool CLI_Command::matches(const char *cmd, uint8_t cmdlen) {
return pgm_read_byte(&command[cmdlen]) == 0 && strncmp_P(cmd, command, cmdlen) == 0;
};
bool CLI_Command::setparams(const char *params) {
return !params;
}

182
src/CLI.h Normal file
View File

@@ -0,0 +1,182 @@
/*
* CLI.h - CLI library for Arduino/ESP8266 and others definitions
*
* Version 1.0, latest version, documentation and bugtracker available at:
* https://gitlab.lindenaar.net/arduino/CLI
*
* Copyright (c) 2019 Frederik Lindenaar
*
* This library 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 <Stream.h>
#ifndef CLI_H
#define CLI_H
#ifndef CLI_MAX_CMDS
#define CLI_MAX_CMDS 16
#endif // CLI_MAX_CMDS
#ifndef CLI_MAX_LINE
#define CLI_MAX_LINE 80
#endif // CLI_MAX_LINE
class CLI;
struct CLI_Command_Param {
const char *param PROGMEM;
union {
int int16;
uint16_t uint16;
int8_t int8;
uint8_t uint8;
int32_t int32;
uint32_t uint32;
char *str PROGMEM;
};
};
struct CLI_Command_Flags {
const char *command;
uint8_t cmdlen, flags, intparam;
};
const char *CLI_STR_skipSep(const char *str);
const char *CLI_STR_findSep(const char *str);
const char *CLI_STR_parseInt(const char *, int &, int = -32768, int = 32767);
const char *CLI_STR_parse_HEX_byte(const char *, uint8_t &);
const struct CLI_Command_Param *CLI_STR_parseParam_P (const char *, const struct CLI_Command_Param params[]);
const char *CLI_STR_parseFlags_P (const char *, const struct CLI_Command_Flags params[], uint8_t, uint8_t, uint8_t *);
class CLI_Command {
friend class Help_Command;
friend class CLI;
protected:
const char *command, *description, *usagehelp;
bool matches(const char *cmd, uint8_t cmdlen);
public:
CLI_Command(CLI &cli , const char *command PROGMEM, const char *description PROGMEM, const char *help PROGMEM = NULL);
virtual bool setparams(const char *params);
virtual bool execute(CLI &cli) = 0;
};
class Help_Command : public CLI_Command {
CLI &_cli;
#if CLI_MAX_CMDS < 255
uint8_t _cmd_idx;
#else
uint16_t _cmd_idx;
#endif
bool _cmd_list;
public:
Help_Command(CLI &cli);
bool setparams(const char *);
bool execute(CLI &cli);
};
class EEPROM_Dump_Command : public CLI_Command {
int _offset;
public:
EEPROM_Dump_Command(CLI &cli);
bool setparams(const char *);
bool execute(CLI &cli);
};
class Reset_Command : public CLI_Command {
int _resetting;
public:
Reset_Command(CLI &cli);
bool execute(CLI &cli);
};
class I2C_Scan_Command : public CLI_Command {
uint8_t _address, _found;
public:
I2C_Scan_Command(CLI &cli);
bool setparams(const char *);
bool execute(CLI &cli);
};
class I2C_Dump_Command : public CLI_Command {
uint8_t _address;
bool _large;
uint16_t _length, _offset;
public:
I2C_Dump_Command(CLI &cli);
bool setparams(const char *);
bool execute(CLI &cli);
};
class CLI : public Stream {
enum CLI_State { STATE_INIT, STATE_READY, STATE_INPUT, STATE_PARSE, STATE_EXEC } _state;
#if CLI_MAX_CMDS < 255
uint8_t _command_count;
#else
uint16_t _command_count;
#endif
CLI_Command *_commands[CLI_MAX_CMDS];
#if CLI_MAX_LINE < 255
uint8_t _line_len;
#else
uint16_t _line_len;
#endif
char _cmdbuffer[CLI_MAX_LINE];
// const char *_params;
CLI_Command *_current_cmd;
protected:
Stream *_stream;
const char *_banner, *_prompt;
public:
CLI(Stream &stream, const char *banner PROGMEM = NULL, const char *prompt PROGMEM = NULL);
void add_command(CLI_Command *cmd);
CLI_Command *get_command(int);
CLI_Command *get_command(const char *, int);
#if CLI_MAX_CMDS < 256
uint8_t find_command(const char *, int);
inline uint8_t command_count() {
#else
uint16_t find_command(const char *, int);
inline uint16_t command_count() {
#endif
return _command_count;
};
inline int available() {
return _stream->available();
};
inline int peek() {
return _stream->peek();
};
inline int read() {
return _stream->read();
};
inline size_t write(uint8_t c) {
return _stream->write(c);
};
size_t print_P (const char *str PROGMEM);
void print2digits(uint8_t num, char filler = '0', uint8_t base=10);
void print_mem(uint16_t addr, const uint8_t buff[], uint8_t len, uint8_t width=16);
void printtab();
bool process();
};
#endif // CLI_H

110
src/CLI_Utils.cpp Normal file
View File

@@ -0,0 +1,110 @@
/*
* CLI_Utils.cpp - CLI library for Arduino/ESP8266 and others utility functions
*
* Version 1.0, latest version, documentation and bugtracker available at:
* https://gitlab.lindenaar.net/arduino/CLI
*
* Copyright (c) 2019 Frederik Lindenaar
*
* This library 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>
const char *CLI_STR_skipSep(const char *str) {
while (*str == ' ') str++;
return str;
};
const char *CLI_STR_findSep(const char *str) {
while (*str && *str != ' ') str++;
return str;
};
const char *CLI_STR_parseInt(const char *str, int &value, int minvalue, int maxvalue) {
int v = 0;
const char *p = str;
bool negative = *p == '-';
if (negative || *p == '+') p++;
while (*p >= '0' && *p <= '9') {
v *= 10;
v += *(p++) - '0';
}
if (str != p && v >= minvalue && v <= maxvalue) {
value = (negative) ? -v : v;
return p;
}
return 0;
}
uint8_t parseHexNibble(char c) {
if(c >= 'a' && c <= 'f') {
return c - 'a' + 10;
} else if(c >= 'A' && c <= 'F') {
return c - 'A' + 10;
} else if(c >= '0' && c <= '9') {
return c - '0';
} else return 0xff;
}
const char *CLI_STR_parse_HEX_byte(const char *str, uint8_t &value) {
if (str) {
uint8_t v;
if((v = parseHexNibble(*(str++))) <= 0xf) {
value = v << 4;
if((v = parseHexNibble(*(str++))) <= 0xf) {
value |= v;
return str;
}
}
}
return NULL;
}
const struct CLI_Command_Param *CLI_STR_parseParam_P (const char *param, const struct CLI_Command_Param params[]) {
for (const struct CLI_Command_Param *p = params; pgm_read_ptr(&p->param); p++)
if (!strcmp_P(param, (char *)pgm_read_ptr(&p->param))) return p;
return NULL;
}
const char *CLI_STR_parseFlags_P (const char *str, const struct CLI_Command_Flags params[], uint8_t param_count, uint8_t mask, uint8_t *flags) {
uint8_t f = 0;
while (*str) {
const struct CLI_Command_Flags *p = params;
while (p <= &params[param_count] && strncmp_P(str, pgm_read_word(&p->command), pgm_read_byte(&p->cmdlen)) ) p++;
uint8_t flags = pgm_read_byte(&p->flags);
if (p > &params[param_count] || (f != 0 && ((f & mask) != (flags & mask)))) return 0;
f |= flags;
str = CLI_STR_skipSep(str + pgm_read_byte(&p->cmdlen));
if (uint8_t intparam = pgm_read_byte(&p->intparam)) {
int i;
if (str = CLI_STR_parseInt(str, i)) {
if (pgm_read_byte(&p->cmdlen) == 0)
if (int divider = pgm_read_word(&p->command)) {
i /= divider & 0xff;
i += divider >> 8;
}
if (i <= (intparam & 0x1f)) {
f |= i << (intparam >> 5);
str = CLI_STR_skipSep(str);
continue;
}
}
return 0;
}
}
if (*str) return 0;
*flags = f;
return str;
}

98
src/Commands.cpp Normal file
View File

@@ -0,0 +1,98 @@
/*
* CLI_Commands.cpp - CLI library for Arduino/ESP8266 and others general commands
*
* Version 1.0, latest version, documentation and bugtracker available at:
* https://gitlab.lindenaar.net/arduino/CLI
*
* Copyright (c) 2019 Frederik Lindenaar
*
* This library 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>
#include <avr/eeprom.h>
Help_Command::Help_Command(CLI & cli) :
CLI_Command(cli, PSTR("help"), PSTR("Display command help"),
PSTR("Usage: help [<command>]\n"
"\tdisplays usage help for <command> or lists available commands\n"
"\twhen called without parameters\n")), _cli(cli) { };
bool Help_Command::setparams(const char *params) {
if (_cmd_list = !params) {
_cmd_idx = 0;
return true;
} else {
_cmd_idx = _cli.find_command(params, strlen(params));
return _cmd_idx < _cli.command_count();
}
}
bool Help_Command::execute(CLI &cli) {
if(CLI_Command *cmd = cli.get_command(_cmd_idx)) {
if (_cmd_list) {
if (_cmd_idx == 0) cli.print_P(PSTR("Known Commands:\n"));
_cmd_idx++;
cli.printtab();
if (cli.print_P(cmd->command) < 8) cli.printtab();
cli.printtab();
cli.print_P(cmd->description);
cli.println();
return true;
} else {
cli.print_P(cmd->description);
cli.println();
if (cmd->usagehelp) cli.print_P(cmd->usagehelp);
}
} else if (_cmd_list) {
cli.print_P((_cmd_idx)
? PSTR("\nfor more information on a command use \"help <command>\"")
: PSTR("No Commands"));
}
cli.println();
return false;
}
EEPROM_Dump_Command::EEPROM_Dump_Command(CLI & cli) : CLI_Command(cli,
PSTR("eeprom_dump"), PSTR("Show EEPROM contents in HEX and ASCII")) { };
bool EEPROM_Dump_Command::setparams(const char *params) {
_offset = 0;
return !params && E2END > 0;
}
bool EEPROM_Dump_Command::execute(CLI &cli) {
if (_offset == 0) {
cli.print_P(PSTR("EEPROM Size: "));
cli.print(E2END + 1);
cli.print_P(PSTR(" Bytes\n\n"));
}
uint8_t buffer[16];
eeprom_read_block(&buffer, (void *)_offset, sizeof(buffer));
cli.print_mem(_offset, buffer, sizeof(buffer));
_offset += sizeof(buffer);
return _offset <= E2END;
}
Reset_Command::Reset_Command(CLI & cli) : CLI_Command(cli,
PSTR("reset"), PSTR("Restart microcontroller")) { };
void(* resetFunc) (void) = 0; //declare reset function @ address 0
bool Reset_Command::execute(CLI &cli) {
if (_resetting == 2000) resetFunc();
if (!_resetting) cli.print(PSTR("resetting...\n\n"));
_resetting++;
return true;
}

110
src/I2C_Commands.cpp Normal file
View File

@@ -0,0 +1,110 @@
/*
* CLI_I2C_Commands.cpp - CLI library for Arduino/ESP8266 - I2C Commands implementation
*
* Version 1.0, latest version, documentation and bugtracker available at:
* https://gitlab.lindenaar.net/arduino/CLI
*
* Copyright (c) 2019 Frederik Lindenaar
*
* This library 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>
#include <Wire.h>
I2C_Scan_Command::I2C_Scan_Command(CLI & cli) : CLI_Command(cli,
PSTR("i2c_scan"), PSTR("Scan I2C bus for slave devices")) { };
bool I2C_Scan_Command::setparams(const char *params) {
_address = _found = 0;
return !params;
}
bool I2C_Scan_Command::execute(CLI &cli) {
if (_address < 127) {
if (_address == 0) cli.println(F("Scanning I2C..."));
Wire.beginTransmission(_address);
uint8_t error = Wire.endTransmission();
if (error == 0) {
cli.print(F("I2C device found at address 0x"));
if (_address < 0x10) cli.print("0");
cli.println(_address, HEX);
_found++;
} else if (error == 4) {
cli.print(F("Unknown error at address 0x"));
if (_address < 0x10) cli.print("0");
cli.println(_address, HEX);
}
_address++;
return true;
} else {
if (_found == 0)
cli.print_P(PSTR("No I2C devices found"));
return false;
}
}
I2C_Dump_Command::I2C_Dump_Command(CLI & cli) : CLI_Command(cli,
PSTR("i2c_dump"), PSTR("Display I2C EEPROM/Memory in HEX and ASCII"),
PSTR("Usage: i2c_dump 0x<id> <size> [<skip>] [single]\n"
"where:\t<id>\tHEX I2C device ID\n"
"\t<size>\tsize of memory\n"
"\t<skip>\t(optional) start offset\n"
"\tsingle\tto enforce 1-byte addressing")) { };
bool I2C_Dump_Command::setparams(const char *params) {
if (params && *(params++) == '0' && *(params++) == 'x') {
if ((params = CLI_STR_parse_HEX_byte(params, _address)) && *params == ' ') {
if(params = CLI_STR_parseInt(CLI_STR_skipSep(params), (int &)_length)) {
_offset = 0;
_large = _length > 0xff;
if (*params == ' ') {
params = CLI_STR_skipSep(params);
if(const char *p = CLI_STR_parseInt(params, (int &)_offset))
params = CLI_STR_skipSep(p);
if (strcmp_P(params, PSTR("single")) == 0) {
if (_large) return false;
params += 6;
}
}
return *params == 0;
}
}
}
return false;
}
bool I2C_Dump_Command::execute(CLI &cli) {
uint8_t buffer[16], len = 0;
Wire.beginTransmission(_address);
if (_large) Wire.write((uint8_t)(_offset >> 8)); // MSB
Wire.write((uint8_t)(_offset & 0xff)); // LSB
if (Wire.endTransmission() != 0) {
cli.print(F("No I2C device at 0x"));
cli.print(_address, HEX);
cli.println();
return false;
}
if (_offset == 0) cli.println(F("I2C EEPROM/Memory: "));
Wire.requestFrom(_address, (uint8_t) (_length - _offset < sizeof(buffer)) ? _length - _offset : sizeof(buffer));
while (Wire.available() && len < sizeof(buffer)) buffer[len++] = Wire.read();
cli.print_mem(_offset, buffer, len);
_offset += len;
return _offset < _length;
}