Converting a serial menu to telnet

Hi,

I currently have a working "one character" serial menu system that processes one character at a time and is designed to be nonblocking, so other periodical tasks keep running.

My original code is:

if (Console0.available()) inbyte = Console0.read();

Then inbyte is processed and finally set to 0 upon completing the task.

I can enter a one character command at a time, or even stack a series of characters.
They get processed one after the other using several cycles.

I want to make a telnet version of this system.
The way ESPtelnet.h processes input is however different: it takes always a full line.
telnet.onInputReceived([](String str);

So my original idea is to take that string and, at each cycle, extract the left character to inbyte and store the shortened string for the next cycle, returning to telnet.onInputReceived once the string is empty.

That will probably work, but looks a bit clumsy and string processing is known to be problematic.
Have you got a better idea how to do it more elegantly?

With what telnet library?

on the code of the library there is
char c = client.read();

Have I got a way to use that as well?

What would have been the syntax then?

...the ESPtelnet library from Lennart Hennigs.

This library does not make great use of memory nor does it offer flexibility

The input is hardcoded to await for a full ‘\n’ terminated line


if (client && isConnected && client.available()) {    
    char c = client.read();
    if (c != '\n') {  // HARDCODED…
      if (c >= 32) {
        input += c; 
      }
    // EOL -> send input
    } else {
      if (on_input != NULL) on_input(input);
      input = "";
      }

So you need to modify the library

Thank you. Do you know a better library?

I try to write code that others can use/implement easily: if it implies modifying libraries that's not my aim.

Have you checked just using the basic features of your ESP?

Tank you for that interesting information.
I will dig into it.
:+1:

Telnet is a line based protocol. So modifying the library to be non line based won't work. Perhaps a better way to express your problem, is that the library is blocking? Then you need to make the library non-blocking. That's different.

or my TelnetStream library

1 Like

Hey that looks great!
it's exactly the way I have been working with serial !

It was just gorgeous!
install the library, initialize in setup and these macros:

// ***Serial Output Definitions***
#ifdef TELNET
//*** Aliases for serial communication***
#define Console0 Serial  // Menu in
#define Console1 TelnetStream  // Reports 
#define Console2 TelnetStream  // Menu out
#define Console3 TelnetStream  // Errors
#define Console4 Serial  // Boot messages
#else
#define Console0 Serial  // Reports 1
#define Console1 Serial1  // Reports 2
#define Console2 Serial  // Menu
#define Console3 Serial1  // Errors
#define Console4 Serial  // Boot messages
#endif

and everything switches automagically to Telnet!
Input and output alike!
:heart_eyes:
Wow, just Wow!

IMHO, there is however a huge problem:

The Stream flush() command should flush the stream buffer, not flush all Telnet connections...

currently

TelnetStream.flush(); 

disconnects you immediately.
:confused:

what networking library, what board?
I tested for esp8266 3.0.2 and the telnet client stays open after flush()

I am working with several esp32 models.
Using your own example slightly simplified (the TimeLib is not required), commenting out the
// TelnetStream.stop();
and adding
delay(500);
TelnetStream.println("Still there?");:


#include <WiFi.h>
#include <TelnetStream.h>

const long gmtOffset_sec = 3600;
const int daylightOffset_sec = 3600;
tm*        timeinfo;                 //localtime returns a pointer to a tm struct static int Second;
time_t     now;

const char ssid[] = "PawsAway";    // your network SSID (name)
const char pass[] = "Secret";          // your network password (use for WPA, or use as key for WEP)

void setup() {
  Serial.begin(115200);

  Serial.print("Attempting to connect to WPA SSID: ");
  Serial.println(ssid);
  WiFi.begin(ssid, pass);
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("Failed to connect.");
    while (1) {
      delay(10);
    }
  }
 
  configTime(gmtOffset_sec, daylightOffset_sec, "pool.ntp.org");
  time_t now = time(nullptr);
  while (now < 1629050852) {
    delay(100);
    now = time(nullptr);
  }
  timeinfo  = localtime(&now); 
  IPAddress ip = WiFi.localIP();
  Serial.println();
  Serial.println("Connected to WiFi network.");
  Serial.print("Connect with Telnet client to ");
  Serial.println(ip);

  TelnetStream.begin();
}

void loop() {
  switch (TelnetStream.read()) {
    case 'R':
    TelnetStream.stop();
    delay(100);
    ESP.restart();
      break;
    case 'C':
      TelnetStream.println("bye bye");
      TelnetStream.flush();
//      TelnetStream.stop();
      delay(500);
      TelnetStream.println("Still there?");
      break;
  }

  static unsigned long next;
  if (millis() - next > 5000) {
    next = millis();
    log();
  }
}

void log() {
  static int i = 0;

  char timeStr[20];
  strftime(timeStr, sizeof(timeStr), " %R %d%b ", timeinfo);

  TelnetStream.print(i++);
  TelnetStream.print(" ");
  TelnetStream.println (timeStr);
}

The bootlog on Serial:

Attempting to connect to WPA SSID: PawsAway

Connected to WiFi network.
Connect with Telnet client to 192.168.188.77

Here the Telnet protocol (Debian on WSL)


rin67630@My-Laptop:~$ telnet 192.168.188.77
Trying 192.168.188.77...
Connected to 192.168.188.77.
Escape character is '^]'.
7  20:25 15Aug
8  20:25 15Aug
9  20:25 15Aug
C
bye bye
Connection closed by foreign host.
rin67630@My-Laptop:~$

You see the problem?

Compiled the same program for an ESP8266 (WemosD1)
Just changed the first line to
#include <ESP8266WiFi.h>

and -miracle- it works there:

rin67630@My-Laptop:~$ telnet 192.168.188.51
Trying 192.168.188.51...
Connected to 192.168.188.51.
Escape character is '^]'.
3  20:48 15Aug
4  20:48 15Aug
C
bye bye
Still there?
5  20:48 15Aug
6  20:48 15Aug
7  20:48 15Aug
8  20:48 15Aug

Hmm, no it does not work on en ESP8266 neither.
It does not disconnect, but nor does it flush:

$ telnet 192.168.188.51
Trying 192.168.188.51...
Connected to 192.168.188.51.
Escape character is '^]'.
4  21:00 15Aug
CCCCCCCCCCCCCCCCCC5  21:00 15Aug

bye bye
Still there?
bye bye
Still there?
bye bye
Still there?
bye bye
Still there?
bye bye
Still there?
6  21:00 15Aug
bye bye
Still there?
bye bye

cu at the GitHub issue....
I have meanwhile found a workaround: I am reading the buffer until \n into a dummy variable.
That leaves the buffer empty too...

esp32 WiFi library WiFiClient doesn't work as it should. it has 2 things wrong at first site.

  1. flush() doesn't flush tx, but discards rx.
  2. the WiFiClient contains a buffer handler which is copied for copies of the object.
    the two copies handle the malloced memory and flush() crashes.

EDIT: point 2 is not right

But ```
TelnetStream.flush()
Does not work on an ESP8266 neither.
It should flush the inbound stream like Serial does.
It does not...

Personally I don't care to flush incomplete print lines, but imperatively need clean read conditions.
I will use my workaround...

It has not done this since version 1.0

Serial.flush()

Description

Waits for the transmission of outgoing serial data to complete. (Prior to Arduino 1.0, this instead removed any buffered incoming serial data.)