377 lines
10 KiB
C++
377 lines
10 KiB
C++
|
#include "cmd.h"
|
||
|
|
||
|
// Initiator.
|
||
|
// Size is the number of commands, default callback is called if a command is
|
||
|
// not found.
|
||
|
Cmd::Cmd(size_t size, CmdFunction defaultCallback) {
|
||
|
m_size = size;
|
||
|
m_commands = (const char **)malloc(sizeof(const char *) * m_size);
|
||
|
m_functions = (CmdFunction *)malloc(sizeof(CmdFunction) * m_size);
|
||
|
m_defaultFunction = defaultCallback;
|
||
|
}
|
||
|
|
||
|
// Return the set size of the command array.
|
||
|
size_t Cmd::GetSize() { return m_size; }
|
||
|
|
||
|
// Add a command to the function list. If list is too full, false is returned.
|
||
|
bool Cmd::AddCmd(const char *cmd, CmdFunction function) {
|
||
|
if (m_nextCmd >= m_size) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
m_commands[m_nextCmd] = cmd;
|
||
|
m_functions[m_nextCmd] = function;
|
||
|
|
||
|
m_nextCmd++;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Get array of all commands.
|
||
|
const char **Cmd::GetCmds() { return m_commands; }
|
||
|
|
||
|
// Rather or not we echo back to serial characters received.
|
||
|
bool Cmd::GetEcho() { return m_echo; }
|
||
|
void Cmd::SetEcho(bool echo) { m_echo = echo; }
|
||
|
|
||
|
// The separator for command parsing.
|
||
|
const char *Cmd::GetSeparator() { return m_separator; }
|
||
|
void Cmd::SetSeparator(const char *separator) { m_separator = separator; }
|
||
|
|
||
|
// Indicates the command line.
|
||
|
const char *Cmd::GetLineIndicator() { return m_line_indicator; }
|
||
|
void Cmd::SetLineIndicator(const char *line_indicator) {
|
||
|
m_line_indicator = line_indicator;
|
||
|
}
|
||
|
|
||
|
// Buffer size configuration.
|
||
|
size_t Cmd::GetBufferSize() { return m_buffer_size; }
|
||
|
void Cmd::SetBufferSize(size_t bufferSize) { m_buffer_size = bufferSize; }
|
||
|
|
||
|
// Return current buffer.
|
||
|
const char *Cmd::GetBuffer() { return m_buffer; }
|
||
|
|
||
|
// Resets the buffer and prints
|
||
|
void Cmd::StartNewBuffer() {
|
||
|
free(m_buffer);
|
||
|
m_buffer = NULL;
|
||
|
Serial.print(m_line_indicator);
|
||
|
}
|
||
|
|
||
|
// Print the current buffer.
|
||
|
void Cmd::PrintBuffer() {
|
||
|
// If we're processing a command, do not print.
|
||
|
if (m_processing) {
|
||
|
return;
|
||
|
}
|
||
|
// Set the last character in the buffer to null to terminate.
|
||
|
m_buffer[m_buffer_read] = '\0';
|
||
|
// Print indicator and buffer.
|
||
|
Serial.print(m_line_indicator);
|
||
|
Serial.print(m_buffer);
|
||
|
// Move cursor to correct location.
|
||
|
for (unsigned int i = m_buffer_cursor; i < m_buffer_read; i++) {
|
||
|
Serial.write('\x08');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Parse next token.
|
||
|
char *Cmd::Parse() { return strtok(NULL, m_separator); }
|
||
|
|
||
|
// Print help for the current command.
|
||
|
void Cmd::PrintHelp() {
|
||
|
// Copy buffer to token buffer.
|
||
|
m_bufferTok = (char *)malloc(m_buffer_size);
|
||
|
strcpy(m_bufferTok, m_buffer);
|
||
|
|
||
|
// Tokenize buffer based on separator and get first token being the command.
|
||
|
char *cmd = strtok(m_bufferTok, m_separator);
|
||
|
|
||
|
// Printing help means the command line is currently the line we're on.
|
||
|
Serial.println();
|
||
|
|
||
|
// To prevent buffer printing during a command execution.
|
||
|
m_processing = true;
|
||
|
// Look for the command that matches.
|
||
|
bool foundCmd = false;
|
||
|
// Only scan for command if specified.
|
||
|
if (cmd != NULL) {
|
||
|
for (unsigned int i = 0; i < m_size && m_commands[i] != NULL; i++) {
|
||
|
if (strcasecmp_P(cmd, m_commands[i]) == 0) {
|
||
|
// If command matches, call its function and tell it we're asking for
|
||
|
// help.
|
||
|
m_functions[i](this, cmd, true);
|
||
|
foundCmd = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If command wasn't found, call the default callback and tell it we're asking
|
||
|
// for help.
|
||
|
if (!foundCmd) {
|
||
|
Serial.println("Calling default function");
|
||
|
m_defaultFunction(this, cmd, true);
|
||
|
}
|
||
|
m_processing = false;
|
||
|
|
||
|
// Print the buffer now that help was provided.
|
||
|
Serial.println();
|
||
|
PrintBuffer();
|
||
|
|
||
|
// Free memory used by the token buffer.
|
||
|
free(m_bufferTok);
|
||
|
m_bufferTok = NULL;
|
||
|
}
|
||
|
|
||
|
// Parse buffer for command.
|
||
|
void Cmd::ParseBuffer() {
|
||
|
// Copy buffer to token buffer.
|
||
|
m_bufferTok = (char *)malloc(m_buffer_size);
|
||
|
strcpy(m_bufferTok, m_buffer);
|
||
|
|
||
|
// Tokenize buffer based on separator and get first token being the command.
|
||
|
char *cmd = strtok(m_bufferTok, m_separator);
|
||
|
|
||
|
// To prevent buffer printing during a command execution.
|
||
|
m_processing = true;
|
||
|
// Look for the command that matches.
|
||
|
bool foundCmd = false;
|
||
|
// Only scan for command if specified.
|
||
|
if (cmd != NULL) {
|
||
|
for (unsigned int i = 0; i < m_size && m_commands[i] != NULL; i++) {
|
||
|
if (strcasecmp_P(cmd, m_commands[i]) == 0) {
|
||
|
// If command matches, call its function.
|
||
|
m_functions[i](this, cmd, false);
|
||
|
foundCmd = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If command wasn't found, call the default callback.
|
||
|
if (!foundCmd) {
|
||
|
m_defaultFunction(this, cmd, false);
|
||
|
}
|
||
|
m_processing = false;
|
||
|
|
||
|
// Free memory used by the token buffer.
|
||
|
free(m_bufferTok);
|
||
|
m_bufferTok = NULL;
|
||
|
}
|
||
|
|
||
|
// Main command loop, call in the main loop of your program.
|
||
|
void Cmd::Loop() {
|
||
|
// If no serial data is available, we do not have anything to process.
|
||
|
if (!Serial.available()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// If buffer is not allocated, let's reset.
|
||
|
if (m_buffer == NULL) {
|
||
|
m_buffer = (char *)malloc(m_buffer_size);
|
||
|
m_buffer_read = 0;
|
||
|
m_buffer_cursor = 0;
|
||
|
}
|
||
|
|
||
|
// Start read and read all available data in serial RX buffer.
|
||
|
bool receivedEndLine = false;
|
||
|
size_t availableData = Serial.available();
|
||
|
for (size_t i = 1; i <= availableData; i++) {
|
||
|
char byteRead = Serial.read();
|
||
|
|
||
|
// If we're currently reading an escape character, verify state.
|
||
|
if (m_buffer_reading_esc == 1) {
|
||
|
// If we get the bracket, we need to move on to the next step in reading
|
||
|
// an escape character. Otherwise, we are not reading an escape character.
|
||
|
if (byteRead == '[') {
|
||
|
m_buffer_reading_esc = 2;
|
||
|
continue;
|
||
|
} else {
|
||
|
m_buffer_reading_esc = 0;
|
||
|
}
|
||
|
} else if (m_buffer_reading_esc == 2) {
|
||
|
// Process escape character read.
|
||
|
switch (byteRead) {
|
||
|
case 'D': // Move cursor left
|
||
|
m_buffer_reading_esc = 0;
|
||
|
if (m_buffer_cursor <= 0) {
|
||
|
continue;
|
||
|
}
|
||
|
m_buffer_cursor--;
|
||
|
Serial.print("\x1b[D");
|
||
|
break;
|
||
|
case 'C': // Move cursor right
|
||
|
m_buffer_reading_esc = 0;
|
||
|
if (m_buffer_cursor >= m_buffer_read) {
|
||
|
continue;
|
||
|
}
|
||
|
m_buffer_cursor++;
|
||
|
Serial.print("\x1b[C");
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
m_buffer_reading_esc = 0;
|
||
|
break;
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Check if ascii byte.
|
||
|
bool is_ascii = byteRead >= 32 && byteRead <= 126;
|
||
|
|
||
|
// If we're to echo and its an ascii byte, send the byte we read back to the
|
||
|
// serial console.
|
||
|
if (m_echo && is_ascii) {
|
||
|
Serial.write(byteRead);
|
||
|
}
|
||
|
|
||
|
// If escape character received, move into escape reading mode.
|
||
|
if (byteRead == '\x1b') {
|
||
|
m_buffer_reading_esc = 1;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// If backspace or delete key.
|
||
|
if (byteRead == 8 || byteRead == 127) {
|
||
|
// Only perform character delete if buffer exists and cursor isn't at
|
||
|
// start.
|
||
|
if (m_buffer_read != 0 && m_buffer_cursor != 0) {
|
||
|
// If cursor isn't at start, we need to re-print the line minus the
|
||
|
// character deleted.
|
||
|
if (m_buffer_cursor != m_buffer_read) {
|
||
|
// Create new buffer for re-writing current buffer.
|
||
|
char *buf = (char *)malloc(m_buffer_size);
|
||
|
// Copy current buffer.
|
||
|
strcpy(buf, m_buffer);
|
||
|
// Clear the line from the curosr.
|
||
|
Serial.print("\x08\x1b[1P");
|
||
|
// Print and re-write the buffer from the cursor location minus
|
||
|
// character deleted.
|
||
|
for (unsigned int i = m_buffer_cursor; i < m_buffer_read; i++) {
|
||
|
m_buffer[i - 1] = buf[i];
|
||
|
Serial.write(buf[i]);
|
||
|
}
|
||
|
// Now that the buffer has been re-written, we can free the buffer.
|
||
|
free(buf);
|
||
|
// Move the cursor back to where it should be.
|
||
|
for (unsigned int i = m_buffer_cursor; i < m_buffer_read; i++) {
|
||
|
Serial.write('\x08');
|
||
|
}
|
||
|
} else {
|
||
|
// As we're not deleting from cursor location, we can just clear the
|
||
|
// last character via this escape.
|
||
|
Serial.print("\x08\x1b[K");
|
||
|
}
|
||
|
// Wipe the last byte in the buffer in both cases.
|
||
|
m_buffer_read--;
|
||
|
m_buffer_cursor--;
|
||
|
m_buffer[m_buffer_read] = '\0';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If begining of line requested.
|
||
|
if (byteRead == 1) {
|
||
|
// Move cursor to beginning of line.
|
||
|
while (m_buffer_cursor != 0) {
|
||
|
m_buffer_cursor--;
|
||
|
Serial.print("\x1b[D");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If end of line requested.
|
||
|
if (byteRead == 5) {
|
||
|
// Move cursor to end of line.
|
||
|
while (m_buffer_cursor < m_buffer_read) {
|
||
|
m_buffer_cursor++;
|
||
|
Serial.print("\x1b[C");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If cancel or exit received.
|
||
|
if (byteRead == 3 || byteRead == 4) {
|
||
|
Serial.println();
|
||
|
// Clear buffer, start new line,
|
||
|
StartNewBuffer();
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Clear screen.
|
||
|
if (byteRead == 12) {
|
||
|
m_buffer[m_buffer_read] = '\0';
|
||
|
Serial.print("\x1b[H\x1b[J");
|
||
|
PrintBuffer();
|
||
|
}
|
||
|
|
||
|
// If end of line.
|
||
|
if (byteRead == '\r') {
|
||
|
receivedEndLine = true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// If help requested, print it.
|
||
|
if (byteRead == '?') {
|
||
|
m_buffer[m_buffer_read] = '\0';
|
||
|
PrintHelp();
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// If not ascii, we only allow ascii on the cli.
|
||
|
if (!is_ascii) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// If cursor is not at end, we need to write new byte where cursor is.
|
||
|
if (m_buffer_cursor != m_buffer_read) {
|
||
|
// Copy current buffer for re-write.
|
||
|
char *buf = (char *)malloc(m_buffer_size);
|
||
|
strcpy(buf, m_buffer);
|
||
|
// Set current cursor location byte to newly read byte.
|
||
|
m_buffer[m_buffer_cursor] = byteRead;
|
||
|
// From cursor location, re-write the buffer with old buffer data and
|
||
|
// print to the console.
|
||
|
for (unsigned int i = m_buffer_cursor; i < m_buffer_read; i++) {
|
||
|
m_buffer[i + 1] = buf[i];
|
||
|
Serial.write(buf[i]);
|
||
|
}
|
||
|
// We're done with the buffer copy.
|
||
|
free(buf);
|
||
|
// Move cursor back to where it was.
|
||
|
for (unsigned int i = m_buffer_cursor; i < m_buffer_read; i++) {
|
||
|
Serial.write('\x08');
|
||
|
}
|
||
|
} else {
|
||
|
// Otherwise new byte can go to end of buffer.
|
||
|
m_buffer[m_buffer_read] = byteRead;
|
||
|
}
|
||
|
// We read a new byte, increment the cursor location and read index.
|
||
|
m_buffer_read++;
|
||
|
m_buffer_cursor++;
|
||
|
|
||
|
// If we're going to overflow next read, we need to stop that.
|
||
|
if (m_buffer_read >= (m_buffer_size - 1)) {
|
||
|
Serial.println("Data too large.");
|
||
|
|
||
|
// Clear the buffer, and start new line.
|
||
|
StartNewBuffer();
|
||
|
|
||
|
// Clear the serial buffer.
|
||
|
while (Serial.available()) {
|
||
|
Serial.read();
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If we received end of line in read, we need to parse the buffer.
|
||
|
if (receivedEndLine) {
|
||
|
// Print new line and null terminate the buffer.
|
||
|
Serial.println();
|
||
|
m_buffer[m_buffer_read] = '\0';
|
||
|
|
||
|
// Parse the buffer.
|
||
|
ParseBuffer();
|
||
|
|
||
|
// Start new buffer.
|
||
|
StartNewBuffer();
|
||
|
}
|
||
|
}
|