Serial communications with an ESP32 over a terminal emulator

I need some help with serial communication between an ESP32 and a computer when using a terminal emulator.

Yes, I have read the “Serial Input Basics”, and quite a few more web pages or Youtube clips.

Here’s what it’s about:

I am making a system where an ESP32 takes readings from a DHT22, and uses those readings in order to set or reset a relay connected to a heating system if below or above a previously set reference temperature.
This works as intended.

Temperature and humidity readings and relay status are then sent to a MQTT broker over WiFi.
This works as intended.

The system will be installed at a location 80 km from where I live, so I want the user to be able to change certain configuration parameters without the need for me to be present (e.g. IP addresses, or temperature cutoff point or whatever). Those parameters are stored in nvs (using the Preferences.h library) and read if and when required.
This works as intended.

The idea is that the parameters can be changed through a serial connection between a the ESP32 and a computer running PuTTY or similar software. The user doesn’t have the skills, and is not interested in acquiring them, to change program code, run the Arduino IDE or VS Code …, but he will be able to follow a written procedure and execute it correctly.

Now here’s my problem : when I run the example shown below from within the terminal in VS Code on my computer, I can enter a text string, and send it to the ESP32 with a single newline (Enter key). When I do the same thing through a connection over PuTTY, I need to hit Enter twice before I get the “I received …” message (see the code below). VS Code behaves in the same way on a Windows and on a Linux computer, same goes for PuTTY
Additionally : when I press backspace, the cursor in the terminal window doesn't go to the left in order to erase or overwrite what I entered previously - but that's less important at this point in time.

I have played with the configuration settings in PuTTY (specifically Terminal – Implicit CR with every LF and vice-versa), but that doesn’t seem to have any influence – guess I’m not looking at the right place.

Here is the program code :

main.cpp

#include <Arduino.h>
#include "_utilities.h"

/*-----------------------------------------------------------*/
void setup() 
{

  Serial.begin(9600);
  Serial.setTimeout(10000);     // system allows for up to 10 seconds time between
                                // keystrokes - how's that for user support !
  Serial.print("\n\n");
}                               // end setup()
/*-----------------------------------------------------------*/
void loop() 
{

  Serial.print("Enter some text (maximum 16 characters) : ");
  _SerialInput(readBuffer,16);
  Serial.print("\n\nI received : "); Serial.print(readBuffer); Serial.print("\n\n");

}                               // end loop()

/*-----------------------------------------------------------*/

_utilities.h

#include <Arduino.h>
char readBuffer[256]; 

void _SerialInput(char* buf, int maxLen);

_utilities.cpp

#include <Arduino.h>

void _SerialInput(char* buf, int maxLen)
{
  char c[1]; c[0] = 0;                // this will be used when reading the serial port

  int loopCounter = 0, bufIndex = 0;  // local variables
  bool inputFinished = false;

  for ( loopCounter = 0; loopCounter < maxLen; loopCounter++) 
    buf[loopCounter] = 0;             // fill buffer bytes up to maxLen with nulls

  while (!Serial.available())         // as long as there are no bytes available
    ;                                 // do nothing

  while (inputFinished == false)      // wait until we have received a signal that input is done
                                      //  the signal takes the form of a newline character (see below)
  {
    if (Serial.available())           // here they are, start reading and processing
    {
      (void) Serial.readBytes(c,1);   // read exactly one character from the serial input

      switch(c[0])                    // validation of just-entered character
      {

        // printable characters - acceptable
        case 32 ... 125:
          buf[bufIndex] = c[0];       // copy the just-read character to the readbuffer
          Serial.print(c[0]);         // show the character on the screen - that's what this function is about
          bufIndex++;                 // position index pointer ready for next read
          if (bufIndex > maxLen-1)    // but avoid overflow
            inputFinished = true;

          break;                      // thank you, next

        // some acceptable non-printable characters
        case 8:
          // backspace
          bufIndex--;                 // bring buffer index one position backwards
          if (bufIndex < 0) 
            bufIndex = 0;             // but avoid underflow
          break;

        case 10:                      // line feed
        case 13:                      // carriage return
          // this is where it ends: when the user enters newline, CR, LF ..., we consider
          //  that there's no more input
          // newline character is no part of the string that will be returned from this function

          buf[bufIndex] = 0;           // write NULL character at the current position
          (void) Serial.readBytes(c,1);// flush the serial inputbuffer in case of double-byte input
          inputFinished = true;        // signal that we're done
          break;

        // other non-printable character, just reject      
        default:
          // do nothing : don't copy input byte, don't update buffer index position
          break;
      }                                       // end switch(c[0])
    }                                         // end if(Serial.available())
  }                                           // end while(inputFinished == false)

  return;                       // and that's it
}

any creative ideas are welcome.

Thanks in advance

what is the purpose of the extra readBytes() when receiving a \r or \n? doesn't that require 2 \n?

the loop() function asks each time again for an input, right ?

imagine you enter 3 characters followed by a newline during the first iteration. this will show you "I received : " followed by the three characters

if you do the same thing during a second iteration, the caracters entered are preceded by an additional chr(10)

so, in order to make sure the serial readbuffer is completely empty when I leave the function, I execute an additional readbytes. I guess it won't hurt, readBytes returns a 0 when there's nothing left. Given the speed by which the user enters keystrokes, I don't care much about performance loss due to that additional readBytes.

is this an answer to your question ?

why? wouldn't the preceding linefeed be processed when the first string is read?

but your code recognizes a \r or \n as the end of a string. typically it either \n or \r\n. i suggest just ignoring \r and recognizing a \n as the end of line or ignoring either at the beginning of a line

the arduino IDE serial monitor has various options for terminating a line with \n, \r, both or neither. not sure what the options are for putty to translate the enter key

it's up to you, but there's no mystery

1 Like

I would suggest that if you

then you don't use PuTTY which is quite clumsy to use. Other, less intimidating, terminal emulators such as CoolTerm are available

1 Like

I use PuTTY because it's easy to find and install in Linux. But TeraTerm on Windows. I have a serial control app just like yours, it works with PuTTY without any configuration changes to PuTTY. If you structure your code so that the terminal emulator requires special configuration, you are setting yourself up for user support headaches. Make your app follow the very simple procedures for interacting with a terminal and you won't have to worry about which emulator you use.

1 Like

@gcjr @UKHeliBob UKHeliBob, @aarg Thanks for your replies and advice.

I tried Coolterm and Tera Term, and I decided to go for Coolterm, I like the line mode, it's really simple and straightforward - just what the doctor ordered.

Tera Term is for people that want every possible feature. It even has TEK4010 vector terminal emulation!