Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide consistent serial console interface for samples #2739

Merged
merged 6 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 <[email protected]> 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
Loading