Skip to content

Commit

Permalink
Provide consistent serial console interface for samples (#2739)
Browse files Browse the repository at this point in the history
This PR provides a consistent mechanism for a basic serial console interface using the `LineBuffer` class.

**Refactor LineBuffer**

Implement LineBufferBase so we take non-trivial methods out of header.

**Add `bool`, `String` operators, `printTo` method&**

Simplify usage

**Add `process`, `processKey` methods and update samples**

Use LineBuffer for LiveDebug, Basic_Ota and Basic_FlashIP samples.
These new methods are used to simplify application code.

Note that the `CommandProcessing::Handler` class hasn't been updated here,
mainly because it requires customisation of EOL handling (setCommandEOL, getCommandEOL).
Presumably this is because of the variation in EOL sequences (CR, LF, CRLF or LFCR combinations).
The LineBuffer `processKey` method doesn't care, it handles all of these.
  • Loading branch information
mikee47 authored Mar 22, 2024
1 parent ffe12d6 commit fdbc1f0
Show file tree
Hide file tree
Showing 6 changed files with 299 additions and 131 deletions.
107 changes: 107 additions & 0 deletions Sming/Core/Data/Buffer/LineBuffer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* LineBuffer.h - support for buffering/editing a line of text
*
* author mikee47 <mike@sillyhouse.net> Feb 2019
*
****/

#include "LineBuffer.h"

LineBufferBase::Action LineBufferBase::process(Stream& input, ReadWriteStream& output)
{
int c;
while((c = input.read()) >= 0) {
auto action = processKey(c, &output);
if(action == Action::clear || action == Action::submit) {
return action;
}
}
return Action::none;
}

LineBufferBase::Action LineBufferBase::processKey(char key, ReadWriteStream* output)
{
auto prevKey = previousKey;
previousKey = key;

switch(key) {
case '\x1b': // ESC -> delete current commandLine
clear();
if(output) {
output->println();
}
return Action::clear;

case '\b': // delete (backspace)
case '\x7f': // xterm ctrl-?
if(!backspace()) {
return Action::none;
}
if(output) {
output->print("\b \b");
}
return Action::backspace;

case '\r':
case '\n':
// For "\r\n" or "\n\r" sequence ignore second key
if(prevKey != key && (prevKey == '\r' || prevKey == '\n')) {
previousKey = '\0';
return Action::none;
}
if(output) {
output->println();
}
return Action::submit;

default:
if(!addChar(key)) {
return Action::none;
}
if(output) {
output->print(key);
}
return Action::echo;
}
}

char LineBufferBase::addChar(char c)
{
if(c == '\n' || c == '\r') {
return '\n';
}

if(c >= 0x20 && c < 0x7f && length < (size - 1)) {
buffer[length++] = c;
buffer[length] = '\0';
return c;
}

return '\0';
}

bool LineBufferBase::backspace()
{
if(length == 0) {
return false;
}
--length;
buffer[length] = '\0';
return true;
}

bool LineBufferBase::startsWith(const char* text) const
{
auto len = strlen(text);
return memcmp(buffer, text, len) == 0;
}

bool LineBufferBase::contains(const char* text) const
{
return strstr(buffer, text) != nullptr;
}
104 changes: 68 additions & 36 deletions Sming/Core/Data/Buffer/LineBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,47 @@

#include <cstdint>
#include <cstring>
#include <WString.h>
#include <Data/Stream/ReadWriteStream.h>

/**
* @brief Class to enable buffering of a single line of text, with simple editing
* @note We define this as a template class for simplicity, no need for separate buffer memory management
*/
template <uint16_t BUFSIZE> class LineBuffer
class LineBufferBase
{
public:
LineBufferBase(char* buffer, uint16_t size) : buffer(buffer), size(size)
{
}

/**
* @brief Returned from `processKey` method directing caller
*/
enum class Action {
none, ///< Do nothing, ignore the key
clear, ///< Line is cleared: typically perform a carriage return
echo, ///< Key should be echoed
backspace, ///< Perform backspace edit, e.g. output "\b \b"
submit, ///< User hit return, process line and clear it
};

/**
* @brief Process all available data from `input`
* @param input Source of keystrokes
* @param output The output stream (e.g. Serial) for echoing
* @retval Action: none, clear or submit
*/
Action process(Stream& input, ReadWriteStream& output);

/**
* @brief Process a keypress in a consistent manner for console editing
* @param key The keypress value
* @param output The output stream (e.g. Serial) for echoing, if required
* @retval Action
*/
Action processKey(char key, ReadWriteStream* output = nullptr);

/**
* @brief Add a character to the buffer
* @retval char Character added to buffer, '\0' if ignored, '\n' if line is complete
Expand All @@ -36,6 +69,20 @@ template <uint16_t BUFSIZE> class LineBuffer
length = 0;
}

explicit operator bool() const
{
return length != 0;
}

/**
* @brief Copy buffer contents into a String
* @retval String
*/
explicit operator String() const
{
return length ? String(buffer, length) : nullptr;
}

/**
* @brief Get the text, nul-terminated
*/
Expand Down Expand Up @@ -72,44 +119,29 @@ template <uint16_t BUFSIZE> class LineBuffer
*/
bool backspace();

private:
char buffer[BUFSIZE] = {'\0'}; ///< The text buffer
uint16_t length = 0; ///< Number of characters stored
};

template <uint16_t BUFSIZE> char LineBuffer<BUFSIZE>::addChar(char c)
{
if(c == '\n' || c == '\r') {
return '\n';
}

if(c >= 0x20 && c < 0x7f && length < (BUFSIZE - 1)) {
buffer[length++] = c;
buffer[length] = '\0';
return c;
size_t printTo(Print& p) const
{
return p.write(buffer, length);
}

return '\0';
}
private:
char* buffer;
uint16_t size;
uint16_t length{0}; ///< Number of characters stored
char previousKey{'\0'}; ///< For processing CR/LF
};

template <uint16_t BUFSIZE> bool LineBuffer<BUFSIZE>::backspace()
/**
* @brief Class to enable buffering of a single line of text, with simple editing
* @note We define this as a template class for simplicity, no need for separate buffer memory management
*/
template <uint16_t BUFSIZE> class LineBuffer : public LineBufferBase
{
if(length == 0) {
return false;
} else {
--length;
buffer[length] = '\0';
return true;
public:
LineBuffer() : LineBufferBase(buffer, BUFSIZE)
{
}
}

template <uint16_t BUFSIZE> bool LineBuffer<BUFSIZE>::startsWith(const char* text) const
{
auto len = strlen(text);
return memcmp(buffer, text, len) == 0;
}

template <uint16_t BUFSIZE> bool LineBuffer<BUFSIZE>::contains(const char* text) const
{
return strstr(buffer, text) != nullptr;
}
private:
char buffer[BUFSIZE]{};
};
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ size_t Handler::process(char recvChar)
output.print(getCommandPrompt());
}
} else if(recvChar == getCommandEOL()) {
String command(commandBuf.getBuffer(), commandBuf.getLength());
processCommandLine(String(commandBuf));
commandBuf.clear();
processCommandLine(command);
} else if(recvChar == '\b' || recvChar == 0x7f) {
if(commandBuf.backspace()) {
output.print(_F("\b \b"));
Expand Down
Loading

0 comments on commit fdbc1f0

Please sign in to comment.