From 9221104977059eacad6033fe5ffdaffa980f1059 Mon Sep 17 00:00:00 2001 From: Rhys Weatherley Date: Sat, 12 Mar 2016 05:51:33 +1000 Subject: [PATCH] Special handling for TCP clients in the shell --- libraries/Terminal/Shell.cpp | 65 ++++++++++++++++++- libraries/Terminal/Shell.h | 5 ++ libraries/Terminal/Terminal.cpp | 10 ++- libraries/Terminal/Terminal.h | 1 + .../examples/TelnetServer/TelnetServer.ino | 8 +-- 5 files changed, 80 insertions(+), 9 deletions(-) diff --git a/libraries/Terminal/Shell.cpp b/libraries/Terminal/Shell.cpp index d5ff8965..e6f98a6d 100644 --- a/libraries/Terminal/Shell.cpp +++ b/libraries/Terminal/Shell.cpp @@ -133,6 +133,7 @@ Shell::Shell() , historyPosn(0) , prom("> ") , hideChars(false) + , isClient(false) { } @@ -152,7 +153,7 @@ Shell::~Shell() * \param maxHistory The number of commands to allocate in the history * stack for scrolling back through using Up/Down arrow keys. * \param mode The terminal mode to operate in, Terminal::Serial or - * Terminal::Telnet. + * Terminal::Telnet. Default is Terminal::Serial. * \return Returns true if the shell was initialized, or false if there * is insufficient memory for the history stack. * @@ -169,6 +170,44 @@ Shell::~Shell() * \sa end(), setPrompt() */ bool Shell::begin(Stream &stream, size_t maxHistory, Terminal::Mode mode) +{ + if (!beginShell(stream, maxHistory, mode)) + return false; + isClient = false; + return true; +} + +/** + * \brief Begin shell handling on a connected TCP client. + * + * \param client The client to apply the shell to. This must be a + * connected TCP client. + * \param maxHistory The number of commands to allocate in the history + * stack for scrolling back through using Up/Down arrow keys. + * \param mode The terminal mode to operate in, Terminal::Serial or + * Terminal::Telnet. Default is Terminal::Telnet. + * \return Returns true if the shell was initialized, or false if there + * is insufficient memory for the history stack. + * + * This override is provided as a convenience for starting a shell on a + * TCP connection. This function also modifies the behaviour of the + * builtin "exit" command to forcibly stop the TCP connection rather + * than returning to the login prompt. + * + * \sa end(), setPrompt() + */ +bool Shell::begin(Client &client, size_t maxHistory, Terminal::Mode mode) +{ + if (!beginShell(client, maxHistory, mode)) + return false; + isClient = true; + return true; +} + +/** + * \brief Internal implementation of begin(). + */ +bool Shell::beginShell(Stream &stream, size_t maxHistory, Terminal::Mode mode) { // Initialize the Terminal base class with the underlying stream. Terminal::begin(stream, mode); @@ -217,6 +256,7 @@ void Shell::end() historyWrite = 0; historyPosn = 0; hideChars = false; + isClient = false; } /** @cond */ @@ -236,6 +276,12 @@ static char const builtin_cmd_help_alt[] PROGMEM = "?"; */ void Shell::loop() { + // If the stream is a TCP client, then check for disconnection. + if (isClient && !((Client *)stream())->connected()) { + end(); + return; + } + // Read the next key and bail out if none. We only process a single // key each time we enter this function to prevent other tasks in the // system from becoming starved of time resources if the bytes are @@ -499,6 +545,21 @@ void Shell::help() } } +/** + * \brief Exit from the shell back to the login prompt. + * + * If the underlying stream is a TCP client, then this function will + * stop the client, causing disconnection. + */ +void Shell::exit() +{ + Stream *stream = this->stream(); + if (isClient) { + end(); + ((Client *)stream)->stop(); + } +} + /** * \brief Executes the command in the buffer. */ @@ -538,6 +599,8 @@ void Shell::execute() if (!strcmp_P(argv0, builtin_cmd_help) || !strcmp_P(argv0, builtin_cmd_help_alt)) { help(); + } else if (!strcmp_P(argv0, builtin_cmd_exit)) { + exit(); } else { static char const unknown_cmd[] PROGMEM = "Unknown command: "; writeProgMem(unknown_cmd); diff --git a/libraries/Terminal/Shell.h b/libraries/Terminal/Shell.h index 477769ba..191bb34d 100644 --- a/libraries/Terminal/Shell.h +++ b/libraries/Terminal/Shell.h @@ -24,6 +24,7 @@ #define SHELL_h #include "Terminal.h" +#include class Shell; class ShellArguments; @@ -65,6 +66,7 @@ public: virtual ~Shell(); bool begin(Stream &stream, size_t maxHistory = 0, Terminal::Mode mode = Serial); + bool begin(Client &client, size_t maxHistory = 0, Terminal::Mode mode = Telnet); void end(); void loop(); @@ -78,6 +80,7 @@ public: void setHideCharacters(bool hide); void help(); + void exit(); private: char buffer[SHELL_MAX_CMD_LEN]; @@ -88,11 +91,13 @@ private: size_t historyPosn; const char *prom; bool hideChars; + bool isClient; // Disable copy constructor and operator=(). Shell(const Shell &other) {} Shell &operator=(const Shell &) { return *this; } + bool beginShell(Stream &stream, size_t maxHistory, Terminal::Mode mode); void execute(); bool execute(const ShellArguments &argv); void executeBuiltin(const char *cmd); diff --git a/libraries/Terminal/Terminal.cpp b/libraries/Terminal/Terminal.cpp index cfc51e31..82da2b52 100644 --- a/libraries/Terminal/Terminal.cpp +++ b/libraries/Terminal/Terminal.cpp @@ -178,7 +178,7 @@ Terminal::~Terminal() * will be interpreted. This is useful if the underlying \a stream is a TCP * connection on port 23. The mode operates as a telnet server. * - * \sa end(), mode() + * \sa end(), stream(), mode() */ void Terminal::begin(Stream &stream, Mode mode) { @@ -200,6 +200,14 @@ void Terminal::end() _stream = 0; } +/** + * \fn Stream *Terminal::stream() const + * \brief Returns a pointer to the underlying Stream, or NULL if the + * stream has not been set with begin() yet. + * + * \sa begin() + */ + /** * \fn Terminal::Mode Terminal::mode() const * \brief Returns the mode this terminal is operating in, Serial or Telnet. diff --git a/libraries/Terminal/Terminal.h b/libraries/Terminal/Terminal.h index 2454f72f..b60a8f71 100644 --- a/libraries/Terminal/Terminal.h +++ b/libraries/Terminal/Terminal.h @@ -48,6 +48,7 @@ public: void begin(Stream &stream, Mode mode = Serial); void end(); + Stream *stream() const { return _stream; } Terminal::Mode mode() const { return (Terminal::Mode)mod; } virtual int available(); diff --git a/libraries/Terminal/examples/TelnetServer/TelnetServer.ino b/libraries/Terminal/examples/TelnetServer/TelnetServer.ino index 74ab2ee1..4c2ff080 100644 --- a/libraries/Terminal/examples/TelnetServer/TelnetServer.ino +++ b/libraries/Terminal/examples/TelnetServer/TelnetServer.ino @@ -29,13 +29,7 @@ void cmdLed(Shell &shell, int argc, const ShellArguments &argv) digitalWrite(ledPin, LOW); } -void cmdExit(Shell &shell, int argc, const ShellArguments &argv) -{ - client.stop(); -} - ShellCommand(led, "Turns the status LED on or off", cmdLed); -ShellCommand(exit, "Exit and log out", cmdExit); void setup() { @@ -72,7 +66,7 @@ void loop() client = server.available(); if (client) { haveClient = true; - shell.begin(client, 5, Terminal::Telnet); + shell.begin(client, 5); } } else if (!client.connected()) { // The current client has been disconnected. Shut down the shell.