-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New telnet feature - intermediate commit
- Loading branch information
Showing
4 changed files
with
316 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,313 @@ | ||
// telnet.h - munet | ||
|
||
#pragma once | ||
|
||
#include "ustd_platform.h" | ||
#include "ustd_array.h" | ||
#include "scheduler.h" | ||
#include <Arduino_JSON.h> | ||
|
||
#include "console.h" | ||
|
||
namespace ustd { | ||
|
||
/*! \brief munet Telnet Console Class | ||
The telnet console class implements a simple but effective telnet console shell that | ||
allows to communicate to the device via a TCP network connection. See \ref ustd::Console for a | ||
list of supported commands. | ||
This class is not instantiated directly but managed by the \ref TelnetServer class. | ||
*/ | ||
|
||
class TelnetConsole : public Console { | ||
public: | ||
Scheduler *pSched; | ||
int tID; | ||
WiFiClient client; | ||
bool connected; | ||
bool finished; | ||
char buffer[64]; | ||
|
||
TelnetConsole(WiFiClient client) | ||
: Console("telnet", null), client(client) { | ||
connected = client.connected(); | ||
finished = false; | ||
printer = &this->client; | ||
} | ||
|
||
const String getRemoteIP() { | ||
return client.remoteIP().toString(); | ||
} | ||
|
||
const uint16_t getRemotePort() { | ||
return client.remotePort(); | ||
} | ||
|
||
void begin(Scheduler *_pSched) { | ||
pSched = _pSched; | ||
tID = pSched->add([this]() { this->loop(); }, "telnet", 60000); // 60ms | ||
client.println("Welcome to the machine!"); | ||
prompt(); | ||
} | ||
|
||
void end() { | ||
client.stop(); | ||
pSched->remove(tID); | ||
} | ||
|
||
void loop() { | ||
connected = client.connected(); | ||
if (connected) { | ||
int icount = client.available(); | ||
while (icount > 0) { | ||
int iread = min(icount, (int)(sizeof(buffer) / sizeof(char)) - 1); | ||
int idone = client.read((uint8_t *)buffer, iread); | ||
if (idone) { | ||
const char *pStart = buffer; | ||
// our buffer is ALWAYS zero terminated! | ||
buffer[idone] = 0; | ||
for (char *pPtr = buffer; *pPtr; pPtr++) { | ||
switch (*pPtr) { | ||
case 9: // tab | ||
// treat like space | ||
*pPtr = 32; | ||
break; | ||
case 10: // line feed | ||
case 13: // enter | ||
// execute the command | ||
*pPtr = 0; | ||
DBGF("Executing %s\r\n", pStart); | ||
args += pStart; | ||
pStart = pPtr + 1; | ||
DBGF("Rest is now at pos:%i v:%i s:%s\r\n", (int)(pStart - buffer), (int)*pStart, pStart); | ||
execute(); | ||
prompt(); | ||
break; | ||
} | ||
} | ||
if (*pStart) { | ||
DBGF("Appending rest at pos:%i v:%i s:%s\r\n", (int)(pStart - buffer), (int)*pStart, pStart); | ||
args += pStart; | ||
} | ||
icount -= idone; | ||
} | ||
} | ||
} else { | ||
if (!finished) { | ||
DBGF("\rTelnet client disconnected\r\n"); | ||
client.stop(); | ||
finished = true; | ||
} | ||
} | ||
} | ||
}; | ||
|
||
/*! \brief munet Console Telnet Server Class | ||
The console telnet server class implements a network server listening on the configured port | ||
and managing \ref TelnetConsole instances for each incoming connection. | ||
## Sample of telnet server remote console: | ||
~~~{.cpp} | ||
#include "scheduler.h" | ||
#include "console.h" | ||
#include "telnet.h" | ||
ustd::Scheduler sched( 10, 16, 32 ); | ||
ustd::SerialConsole con; | ||
ustd::TelnetServer telnet; | ||
void apploop() {} | ||
void setup() { | ||
// initialize the serial interface | ||
Serial.begin( 115200 ); | ||
// extend console | ||
con.extend( "hurz", []( String cmd, String args ) { | ||
printer->println( "Der Wolf... Das Lamm.... Auf der grünen Wiese.... HURZ!" ); | ||
while ( args.length() ) { | ||
String arg = ustd::shift( args ); | ||
printer->println( arg + " HURZ!" ); | ||
} | ||
} ); | ||
con.begin( &sched ); | ||
int tID = sched.add( apploop, "main", 50000 ); | ||
} | ||
void loop() { | ||
sched.loop(); | ||
} | ||
~~~ | ||
*/ | ||
class TelnetServer { | ||
public: | ||
typedef struct { | ||
int id; | ||
char *command; | ||
T_COMMANDFN fn; | ||
} T_COMMAND; | ||
ustd::array<T_COMMAND> commands; | ||
int commandHandle = 0; | ||
Scheduler *pSched; | ||
int tID; | ||
WiFiServer server; | ||
bool connected = false; | ||
array<TelnetConsole *> consoles; | ||
uint8_t max_clients; | ||
|
||
int port; | ||
|
||
TelnetServer(uint16_t port = 23, uint8_t max_clients = 4) | ||
: server(port), consoles(max_clients), max_clients(max_clients) { | ||
} | ||
|
||
~TelnetServer() { | ||
for (unsigned int i = 0; i < consoles.length(); i++) { | ||
delete consoles[i]; | ||
} | ||
consoles.erase(); | ||
for (unsigned int i = 0; i < commands.length(); i++) { | ||
free(commands[i].command); | ||
} | ||
commands.erase(); | ||
} | ||
|
||
void addUserCommand(Console *pCon) { | ||
pCon->extend("users", [this](String cmd, String args, Print *printer) { | ||
printer->printf("ID IP Address Port\n"); | ||
printer->printf("---------------------------------\n"); | ||
for (unsigned int i = 0; i < consoles.length(); i++) { | ||
TelnetConsole *pCon = consoles[i]; | ||
if (pCon->finished) { | ||
printer->printf("%-3d Connection is terminated\n", i); | ||
} else { | ||
printer->printf("%-3d %-20s %-5d\n", i, pCon->getRemoteIP().c_str(), pCon->getRemotePort()); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
int extend(String command, T_COMMANDFN handler) { | ||
/*! Extend the console with a custom command | ||
* | ||
* @param command The name of the command | ||
* @param handler Callback of type void myCallback(String command, String | ||
* args) that is called, if a matching command is entered. | ||
* @return commandHandle on success (needed for unextend), or -1 | ||
* on error. | ||
*/ | ||
T_COMMAND cmd = {}; | ||
cmd.id = ++commandHandle; | ||
cmd.fn = handler; | ||
cmd.command = (char *)malloc(command.length() + 1); | ||
strcpy(cmd.command, command.c_str()); | ||
return commands.add(cmd) != -1 ? commandHandle : -1; | ||
} | ||
|
||
bool unextend(String command) { | ||
/*! Removes a custom command from the console | ||
* @param command The name of the command to remove. | ||
* @return true on success. | ||
*/ | ||
for (unsigned int i = 0; i < commands.length(); i++) { | ||
if (command == (const char *)commands[i].command) { | ||
for (unsigned int j = 0; j < consoles.length(); j++) { | ||
TelnetConsole *pCon = consoles[j]; | ||
pCon->unextend(commands[i].command); | ||
} | ||
return commands.erase(i); | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
bool unextend(int commandHandle) { | ||
/*! Removes a custom command from the console | ||
* @param commandHandle The commandHandle of the command to remove. | ||
* @return true on success. | ||
*/ | ||
for (unsigned int i = 0; i < commands.length(); i++) { | ||
if (commands[i].id == commandHandle) { | ||
for (unsigned int j = 0; j < consoles.length(); j++) { | ||
TelnetConsole *pCon = consoles[j]; | ||
pCon->unextend(commands[i].command); | ||
} | ||
return commands.erase(i); | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
void begin(Scheduler *_pSched, Console *pCon = nullptr) { | ||
pSched = _pSched; | ||
tID = pSched->add([this]() { this->loop(); }, "telnet", 60000); // 60ms | ||
pSched->subscribe(tID, "net/network", [this](String topic, String msg, String originator) { | ||
JSONVar jm = JSON.parse(msg); | ||
if (JSON.typeof(jm) == "object" && JSON.typeof(jm["state"]) == "string") { | ||
bool newconnected = !strcmp(jm["state"], "connected"); | ||
if (connected != newconnected) { | ||
connected = newconnected; | ||
if (connected) { | ||
DBG("Start listening..."); | ||
this->server.begin(); | ||
} else { | ||
DBG("Stop listening..."); | ||
// this->server.end(); | ||
} | ||
} | ||
} | ||
}); | ||
if (pCon) { | ||
addUserCommand(pCon); | ||
} | ||
} | ||
|
||
void loop() { | ||
// cleanup finished consoles | ||
for (unsigned int i = 0; i < consoles.length(); i++) { | ||
TelnetConsole *pCon = consoles[i]; | ||
if (pCon->finished) { | ||
DBGF("\rCleaning up console %i\r\n", i); | ||
pCon->end(); | ||
delete pCon; | ||
consoles.erase(i); | ||
--i; | ||
continue; | ||
} | ||
} | ||
// accept connections | ||
if (connected) { | ||
for (WiFiClient client = server.accept(); client; client = server.accept()) { | ||
if (consoles.length() < max_clients) { | ||
DBGF("\rNew telnet connection from %s:%u\r\n", client.remoteIP().toString().c_str(), | ||
client.remotePort()); | ||
TelnetConsole *pCon = new TelnetConsole(client); | ||
addUserCommand(pCon); | ||
pCon->extend("quit", [this, client](String cmd, String args, Print *printer) { | ||
printer->print("Have a nice day!\n"); | ||
printer->flush(); | ||
((WiFiClient *)printer)->stop(); | ||
}); | ||
for (unsigned int i = 0; i < commands.length(); i++) { | ||
pCon->extend(commands[i].command, commands[i].fn); | ||
} | ||
consoles.add(pCon); | ||
pCon->begin(pSched); | ||
} else { | ||
client.printf("Sorry - maximum connections limit reached. Bye!\n"); | ||
client.flush(); | ||
client.stop(); | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
|
||
} // namespace ustd |