SoftwareSerial Not Reliable?

:slight_smile:

a7

That sketch did not compile for me, lots of non-String errors.
BUT not surprising you are having String memory problems. Lots of String copies all over the place.
Take a look at my Taming Arduino Strings tutorial for how to improve things.
There is a revised set of code that implements those suggestions.

//https://www.youtube.com/watch?v=4vKxGHGYOtI&t=103s
//#define SCROLL
#define DISPLAY_TEMPERATURES //display pool and air temperatures as well as time & date

#define USE_SOFTWARESERIAL

#ifdef USE_SOFTWARESERIAL
// SERIAL_IO redefines Arduino.h:54  #define SERIAL_IO  0x0
// that won't help
#define SERIAL_IO Serial1
#else
#define SERIAL_IO Serial
#endif

#define MAXLEN 60
#ifdef USE_SOFTWARESERIAL
#include <SoftwareSerial.h>
#endif
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display
//Gnd
//Vcc
//SDA(A4)
//SCL(A5)
//-------------Clock variables-----------
unsigned long refmillis, currenthour;
int Time[6]; //currenthour_10's,currenthour_1's,currentminute_10's,currentminute_1's
String str_currentTime = "", str_currentDate = "", str_pooltemp = "", str_airtemp = "", str_wakeupTime = "";
// add reserve( ) in setup for these globals
int buzzPin = 8, alarmdone = 0;
// make period and duration arrays to get compile to work
int period[] = {500, 594, 500, 594, 707, 594, 707, 594, 530, 500, 594};
int duration[] = {1000, 1000, 1000, 1000, 250, 500, 250, 500, 500, 1000, 1000};
//------------esp01 variables ----------------------------------------------------
#ifdef USE_SOFTWARESERIAL
SoftwareSerial SERIAL_IO(6, 7); // RX, TX
#endif
String AP = "*****"; // AP NAME
String PASS = "***********"; // AP PASSWORD
String API = "***********"; // Write API KEY
String HOST = "api.thingspeak.com";
String PORT = "80";
String str_time;
int toggle = 0;
int countTimeCommand = 0;
boolean found = false;
const unsigned long delaymillis = 120000UL; //120 sec
unsigned long startmillis;
//--------------LCD variables ---------------
int LCDcol = 0;
#ifdef SCROLL
const unsigned long delaydisplaymillis = 500UL;
#else
const unsigned long delaydisplaymillis = 6000UL;
#endif
unsigned long startdisplaymillis;
//--------------Big Display-------------------
// Define the bit patters for each of our custom chars. These
// are 5 bits wide and 8 dots deep
uint8_t custChar[8][8] = {
  {31, 31, 31, 0, 0, 0, 0, 0}, // Small top line - 0
  {0, 0, 0, 0, 0, 31, 31, 31}, // Small bottom line - 1
  {31, 0, 0, 0, 0, 0, 0, 31}, // Small lines top and bottom -2
  {0, 0, 7, 7, 7, 7, 0, 0}, // colon dot -3
  {31, 31, 31, 31, 31, 31, 15, 7}, // Left bottom chamfer full - 4
  {28, 30, 31, 31, 31, 31, 31, 31}, // Right top chamfer full -5
  {31, 31, 31, 31, 31, 31, 30, 28}, // Right bottom chamfer full -6
  {7, 15, 31, 31, 31, 31, 31, 31}, // Left top chamfer full -7
};
// Define our numbers 0 thru 9
// 254 is blank and 255 is the "Full Block"
uint8_t bigNums[10][6] = {
  {7, 0, 5, 4, 1, 6}, //0
  {0, 5, 254, 1, 255, 1}, //1
  {0, 2, 5, 7, 1, 1}, //2
  {0, 2, 5, 1, 1, 6}, //3
  {7, 1, 255, 254, 254, 255}, //4
  {7, 2, 0, 1, 1, 6}, //5
  {7, 2, 0, 4, 1, 6}, //6
  {0, 0, 5, 254, 7, 254}, //7
  {7, 2, 5, 4, 1, 6}, //8
  {7, 2, 5, 1, 1, 6}, //9
};

void setup() {
  // put your setup code here, to run once:
  // Only start Serial if using USE_SOFTWARESERIAL
#ifdef USE_SOFTWARESERIAL
  Serial.begin(115200);
  for (int i = 10; i > 0; i--) {
    delay(500);
    Serial.print(i); Serial.print(' ');
  }
  Serial.println(F("Started "));
#endif
  // not a problem is these are not correct String will just expand them first time they exceed this reserve
  str_currentTime.reserve(20); // padded to 16 in Clock( ) so 20 should be fine
  str_currentDate.reserve(20); // padded to 16 in getresults1 so 20 should be fine
  str_pooltemp.reserve(10);
  str_airtemp.reserve(10);
  str_wakeupTime.reserve(10);

  pinMode(buzzPin, OUTPUT);
  digitalWrite(buzzPin, LOW);
  lcd.init(); // initialize the lcd
  lcd.init();
  lcd.backlight();
  delay(500);
  lcd.home();
  lcd.clear();
  for (size_t cnt = 0; cnt < sizeof(custChar) / 8; cnt++)
  {
    lcd.createChar(cnt, custChar[cnt]);
  }
  initClock(0, 0, 0, &refmillis); //init Time to midnite
  SERIAL_IO.begin(9600);

  while (found == false)
  {
    sendCommand("AT", 5, "OK");
    sendCommand("AT+CWMODE=1", 5, "OK");
    sendCommand("AT + CWJAP = \"" + AP + "\",\"" + PASS + "\"", 50, "OK");
    sendCommand("AT+CIPMUX=1", 5, "OK");
  }
  startmillis = millis() - delaymillis;
  startdisplaymillis = millis() - delaydisplaymillis;
}

void loop()
{
  //--------------- Get info from Internet ---------
  if ((millis() - startmillis) >= delaymillis)
  {
    while (SERIAL_IO.available() > 0) SERIAL_IO.read(); //start with an MT softwareserial buffer before sending GET's
    if (toggle == 3)
    {
      getresults1(str_time, 1423291L, 1, str_wakeupTime, "", 0); //get wakupTime
      toggle = 0;
    }
    else if (toggle == 2)
    {
      getresults1(str_time, 1418147L, 1, str_airtemp, "AIR", 1); //get airtemp
      toggle = 3;
    }
    else if (toggle == 1)
    {
      getresults1(str_time, 1418147L, 4, str_pooltemp, "EAU", 1); //get pooltemp
      toggle = 2;
    }
    else if (toggle == 0)
    {
      getresults(str_time); //getTime & date
      parseResults(str_time, str_currentDate, &refmillis);
#ifdef DISPLAY_TEMPERATURES
      toggle = 1;
#endif
    }
#ifdef USE_SOFTWARESERIAL
    Serial.println(str_time);
#endif
    SERIAL_IO.println(F("\n\r"));
    startmillis = millis();
  }
  //-------------------- Run the Clock ----------------------------------------------
  Clock(str_currentTime, refmillis, &currenthour, Time); //update time of day Clock
  if (str_wakeupTime == str_currentTime.substring(0, 5)) //alarm if active
  {
    if (alarmdone == 0)
    {
      Miles(period, duration);
      alarmdone = 1;
    }
  }
  else
  {
    alarmdone = 0;
  }
  //----------------Take care of LCD display -----------------------------------------
  if (((int)currenthour > 21) || ((int)currenthour < 9)) //During sleeptime, only show currentTime on big display
    // if (0)
  {
    LCDbig(Time);
  }
  else //During waketime, show currentTime, currentDate, pooltemp, and airtemp on small display
  {
    LCDsmall(&startdisplaymillis, &LCDcol, str_currentTime, str_currentDate, str_pooltemp, str_airtemp, str_wakeupTime);
  }
} //void loop()

void initClock(unsigned long valhour, unsigned long valminute, unsigned long valsecond, unsigned long *Refmillis)
{
  *Refmillis = millis() - ((valhour * 3600L + valminute * 60L + valsecond)) * 1000L; //reference to 0h/0m/0s
}

//int Clock(String *currentTime, unsigned long Refmillis, unsigned long *Currenthour, int *TIme)
// nothing returned from this method
void Clock(String &currentTime, unsigned long Refmillis, unsigned long *Currenthour, int *TIme)
{
  unsigned long deltamillis, currentminute, currentsecond;
  int siz, i;
  deltamillis = (millis() - Refmillis) % ((unsigned long)(60L * 60L * 24L) * 1000L);
  *Currenthour = deltamillis / 3600000L;
  deltamillis -= *Currenthour * 3600000L;
  currentminute = deltamillis / 60000L;
  deltamillis -= currentminute * 60000L;
  currentsecond = deltamillis / 1000L;
  currentTime = "";// clear to start with
  twoDigits(currentTime, *Currenthour);
  currentTime += ":";
  twoDigits(currentTime, currentminute);
  currentTime += ":";
  twoDigits(currentTime, currentsecond);
  siz = currentTime.length();
  if (siz < 16) for (i = 0; i < 16 - siz; i++) currentTime += (" "); // padd it
  TIme[0] = *Currenthour / 10;
  TIme[1] = *Currenthour % 10;
  TIme[2] = currentminute / 10;
  TIme[3] = currentminute % 10;
  TIme[4] = currentsecond / 10;
  TIme[5] = currentsecond % 10;
}

void Miles(int *Period, int *Duration)
{
  int i, j;
  unsigned long tIme;
  for (j = 0; j < 2; j++)
  {
    for (i = 0; i < 11; i++) //song duration=19sec
    {
      tIme = 0ul;
      while (tIme < 1000ul * Duration[i])
      {
        digitalWrite(buzzPin, HIGH);
        delayMicroseconds(Period[i]);
        digitalWrite(buzzPin, LOW);
        delayMicroseconds(Period[i]);
        tIme += (2 * Period[i]);
      }
    }
    delay(1000);
  }
  digitalWrite(buzzPin, LOW);
}

void LCDbig(int *tIme)
{
  printBigNum(tIme[0], 0, 0);
  lcd.setCursor(3, 0);
  lcd.print(' ');
  lcd.setCursor(3, 1);
  lcd.print(' ');
  printBigNum(tIme[1], 4, 0);
  if (tIme[5] % 2 == 0) //blink the : every second
  {
    lcd.setCursor(7, 0);
    lcd.print((char)3); //:
    lcd.setCursor(7, 1);
    lcd.print((char)3); //:
  }
  else
  {
    lcd.setCursor(7, 0);
    lcd.print(' '); //blank
    lcd.setCursor(7, 1);
    lcd.print(' '); //blank
  }
  lcd.setCursor(8, 0);
  lcd.print(' ');
  lcd.setCursor(8, 1);
  lcd.print(' ');
  printBigNum(tIme[2], 9, 0);
  lcd.setCursor(12, 0);
  lcd.print(' ');
  lcd.setCursor(12, 1);
  lcd.print(' ');
  printBigNum(tIme[3], 13, 0);

}

// -----------------------------------------------------------------
// Print big number over 2 lines, 3 colums per half digit
// -----------------------------------------------------------------
void printBigNum(int number, int startCol, int startRow) {
  // Position cursor to requested position (each char takes 3 cols plus a space col)
  lcd.setCursor(startCol, startRow);
  // Each number split over two lines, 3 chars per line. Retrieve character
  // from the main array to make working with it here a bit easier.
  uint8_t thisNumber[6];
  for (int cnt = 0; cnt < 6; cnt++) {
    thisNumber[cnt] = bigNums[number][cnt];
  }
  // First line (top half) of digit
  for (int cnt = 0; cnt < 3; cnt++) {
    lcd.print((char)thisNumber[cnt]);
  }
  // Now position cursor to next line at same start column for digit
  lcd.setCursor(startCol, startRow + 1);
  // 2nd line (bottom half)
  for (int cnt = 3; cnt < 6; cnt++) {
    lcd.print((char)thisNumber[cnt]);
  }
}

void LCDsmall(unsigned long *Startdisplaymillis, int *lcdcol, String &STR_currentTime, String &STR_currentDate, String &STR_pooltemp, String &STR_airtemp, String &str_wakeupTIME)
{
  String Message, line2;
  int Msglength;
  lcd.setCursor(0, 0);
  if (str_wakeupTIME.length() > 0)
  {
    lcd.print(STR_currentTime.substring(0, 8) + " " + str_wakeupTIME);
  }
  else lcd.print(STR_currentTime); //Line0
  Message = "";
  Message += STR_currentDate; //16 characters
  Message += STR_pooltemp; //8 characters or 0
  Message += STR_airtemp; //8 characters or 0
  if (Message.length() == 24) Message += " "; //16 or 32 characters only  // <<<<<<<<<< odd
  Msglength = Message.length();
  Message += STR_currentDate; //needed for scrolling
  line2 = Message.substring(*lcdcol, *lcdcol + 16);
  lcd.setCursor(0, 1);
  lcd.print(line2); //Line2 scrolls or alternates
  if ((millis() - *Startdisplaymillis) >= delaydisplaymillis) //Line1
  {
#ifdef SCROLL
    *lcdcol += 1; //scroll
#else
    *lcdcol += 16; //alternate
#endif
    if (*lcdcol >= Msglength) *lcdcol = 0;
    *Startdisplaymillis = millis();
  }
}

void getresults(String &Str_time) //get time of day from Internet
{
  // this String getData will fail if you don't have at least 128 bytes of stack left afterwards
  String getData = "GET https://api.thingspeak.com/apps/thinghttp/send_request?api_key=5R0NJ8SL2DSEAGQA";
  char c = ' ';
  //String Str_time = "";
  Str_time = ""; // clear to start with
  int countcomma = 0;
  unsigned long waitTime, startTime;
  // this will fail if you don't have at least 128 bytes of stack after each temp String is created
  sendCommand("AT+CIPSTART=0,\"TCP\",\"" + HOST + "\"," + PORT, 15, "OK");
  // this will fail if you don't have at least 128 bytes of stack after each temp String is created
  sendCommand("AT+CIPSEND=0," + String(getData.length() + 4), 4, ">");
  SERIAL_IO.println(getData);  // no need for a String here
  SERIAL_IO.println(F("\n\r"));
  //Typical Response From esp-01
  //Recv 87 bytes
  //SEND OK
  //+IPD,0,32:11:23:32 AM, Friday 14, May 20210,CLOSED

  waitTime = 10000UL;
  startTime = millis();
  while ((c != ':') && ((millis() - startTime) <= waitTime)) //remove unwanted initial characters
  {
    if (SERIAL_IO.available() > 0)
    {
      c = SERIAL_IO.read();
    }
  }
  waitTime = 1000UL;
  startTime = millis();
  c = ' ';
  while ((countcomma != 3) && ((millis() - startTime) <= waitTime)) //get time,date characters up to 3 ','
  {
    if (SERIAL_IO.available() > 0)
    {
      c = SERIAL_IO.read();
      Str_time.concat(c);
      if (c == ',') countcomma++;
    }
  }
  // return Str_time;
}

void getresults1(String &str_res, long int channelID, int fieldno, String &str_out, const char* str_label, int temperatureresult)
{
  char c = ' ';
  String getData = "GET https://api.thingspeak.com/channels/";
  getData += String(channelID);
  getData += "/fields/";
  getData += String(fieldno);
  getData += ".json?results=3";
  String Str_time, Str_temp = "";// ,str_res
  str_res = ""; // clear to start with
  int i, countcolons = 0, siz, iter;
  unsigned long waitTime, startTime;
  sendCommand("AT+CIPSTART=0,\"TCP\",\"" + HOST + "\"," + PORT, 15, "OK"); // uses temp String memory but probably OK
  sendCommand("AT+CIPSEND=0," + String(getData.length() + 4), 4, ">");
  SERIAL_IO.println(getData);
  SERIAL_IO.println(F("\n\r"));
  waitTime = 10000UL;
  startTime = millis();
  c = ' ';
  while ((c != '[') && ((millis() - startTime) <= waitTime)) //remove until [ start of feed
  {
    if (SERIAL_IO.available() > 0)
    {
      c = SERIAL_IO.read();
    }
  }
  for (iter = 0; iter < 3; iter++)
  {
    waitTime = 1000UL;
    startTime = millis();
    c = ' ';
    countcolons = 0;
    while ((countcolons != 5) && ((millis() - startTime) <= waitTime)) //remove until field3
    {
      if (SERIAL_IO.available() > 0)
      {
        c = SERIAL_IO.read();
        if (c == ':') countcolons++;
      }
    }
    waitTime = 1000UL;
    startTime = millis();
    c = ' ';
    Str_time = "";
    while ((c != '}') && ((millis() - startTime) <= waitTime)) //get data = "xxxxx"} or null}
    {
      if (SERIAL_IO.available() > 0)
      {
        c = SERIAL_IO.read();
        Str_time.concat(c);
      }
    }
    if (Str_time.substring(0, 1) != "n") Str_temp = Str_time; //Copy if field is not null. The last (most recent) valid field will be used.
  } //for (iter=0;iter<3;iter++) next feed
  if (Str_temp.length() != 0)
  {
    siz = Str_temp.length();
    str_res = Str_temp.substring(1, siz - 2);
  }
  if (str_res.length() != 0)
  {
    if (temperatureresult)
    {
      str_out = str_label;
      str_out += " ";
      str_out += String(round(str_res.toFloat()));
      str_out += "C";
      siz = str_out.length();
      if (siz < 8) for (i = 0; i < 8 - siz; i++) str_out += " ";
    }
    else
    {
      if (str_res.toInt() < 0) str_out = "";  // 0 could indicate error in toInt()
      else {
        str_out = "";
        twoDigits(str_out, str_res.toInt() / 100);
        str_out += ":";
        twoDigits(str_out, str_res.toInt() % 100);
      }
    }
  }
  //return str_res;
}

void parseResults(String &Str_time, String &str_CurrentDate, unsigned long *refMillis)
{
  int i = 0, j = 0, len,  startindx = 0, hashcode, siz;  //newTime[3],
  // if newTime[3] not initialized here, then when used below may get garbage
  int newTime[3] = {0, 0, 0};
  String str_temp, parsechars = ":: , , ";
  //
  // typical String to parse is:  11:23:32^AM,^Friday^14,^May^20210,CLOSED
  //
  //      substring(startindx,i) pchar[j]  found    what
  //      -----------------------------------------------
  //                    0     2     :  0    11      hour
  //                    3     5     :  1    23      minute
  //                    6     8     ^  2    32      second
  //                    9     12    ^  3    AM,     AM/PM
  //                    13    19    ^  4    Friday  day name
  //                    20    22    ,  5    14      day date
  //                    23    23    ^  6    -       nothing
  //                    24    27    ^  7    May     month
  //                    28    33    ,  8    20210   year
  //                                ^  9

  len = Str_time.length();
  if ((len >= 10) && (len < MAXLEN)) //parsing the received string newTime values in [hours,minutes,seconds], Day, Date, Month, Year
  {
    while (i < len)
    {
      if (Str_time.charAt(i) == parsechars.charAt(j))
      {
        switch (j)
        {
          case 0: newTime[j] = (Str_time.substring(startindx, i)).toInt(); break; //get hour,minute,seconds
          case 1: newTime[j] = (Str_time.substring(startindx, i)).toInt(); break; //get hour,minute,seconds
          case 2: newTime[j] = (Str_time.substring(startindx, i)).toInt(); break; //get hour,minute,seconds
          case 3: str_temp = Str_time.substring(startindx, i - 1);
            if (str_temp.charAt(0) == 'P') //change to 24-hour format
            {
              if (newTime[0] != 12) newTime[0] += 12;
            }
            else
            {
              if (newTime[0] == 12) newTime[0] -= 12;
            }
            break;
          case 4: str_temp = Str_time.substring(startindx, i); //get day
            //get day of the week
            hashcode = codeString(str_temp);
            switch (hashcode)
            {
              case 13760: str_CurrentDate = "Lun "; break; //Mon
              case 13280: str_CurrentDate = "Mar "; break; //Tue
              case 13440: str_CurrentDate = "Mer "; break; //Wed
              case 13984: str_CurrentDate = "Jeu "; break; //Thu
              case 13088: str_CurrentDate = "Ven "; break; //Fri
              case 14208: str_CurrentDate = "Sam "; break; //Sat
              case 12864: str_CurrentDate = "Dim "; break; //Sun
              default: break; //do nothing
            }
            break;
          case 5: twoDigits(str_CurrentDate, (Str_time.substring(startindx, i)).toInt()); break; //get day date (1-31)
          case 6: break;
          case 7: str_temp = Str_time.substring(startindx, i); //get month
            //get Month
            hashcode = codeString(str_temp);
            switch (hashcode)
            {
              case 13760: str_CurrentDate += "/01/"; break; //Jan
              case 13376: str_CurrentDate += "/02/"; break; //Feb
              case 14016: str_CurrentDate += "/03/"; break; //Mar
              case 12864: str_CurrentDate += "/04/"; break; //Apr
              case 14304: str_CurrentDate += "/05/"; break; //May
              case 12992: str_CurrentDate += "/06/"; break; //Jun
              case 12928: str_CurrentDate += "/07/"; break; //Jul
              case 13600: str_CurrentDate += "/08/"; break; //Aug
              case 13824: str_CurrentDate += "/09/"; break; //Sep
              case 12672: str_CurrentDate += "/10/"; break; //Oct
              case 13120: str_CurrentDate += "/11/"; break; //Nov
              case 14240: str_CurrentDate += "/12/"; break; //Dec
              default: break; //do nothing
            }
            break;
          case 8: str_CurrentDate += Str_time.substring(startindx, i - 1); break; //get year (4 digits)
          default: break; //do nothibg
        } // switch(j)
        j++;
        startindx = i + 1;
      } //if (Str_time.charAt(i) == parsechars.charAt(j))
      i++;
    } //while (i<len)
    initClock(newTime[0], newTime[1], newTime[2], refMillis);  // <<<<<<< warning about newTime[ ] not being initialized
    siz = str_CurrentDate.length();
    if (siz < 16) for (i = 0; i < 16 - siz; i++) str_CurrentDate += " ";
  } //if ((len>=10)&&(len<MAXLEN))
}

int codeString(String &in)
{
  int res = 0;
  for (int i = 0; i < 3; i++) res = ((in[i] << 5) + res) ^ res;
  return res;
}
//String twoDigits(unsigned long val)
// add two digits to outString
void twoDigits(String &outString, unsigned long val)
{
  //String outString, str_val;
  String str_val(val);
  if (str_val.length() == 1) outString += "0";
  outString += str_val;
  //return (outString);
}

// readReplay should be char* NOT a char
//void sendCommand(String command, int maxTime, char readReplay) {
void sendCommand(String &command, int maxTime, const char* readReplay) {
  sendCommand(command.c_str(), maxTime, readReplay);
}
void sendCommand(const char* command, int maxTime, const char* readReplay) {
  found = false;
  while (countTimeCommand < (maxTime * 1))
  {
    SERIAL_IO.println(command);
    if (SERIAL_IO.find((uint8_t*)readReplay)) //ok
    {
      found = true;
      break;
    }
    countTimeCommand++;
  }
  countTimeCommand = 0;
}

Hi,
What model Arduino are you using?
I couldn't see in your posts, possibly UNO or Nano?

Have you though about porting to ESP32, it will do what you need without the limited memory restraints of UNO or Nano, and faster.

Thanks.. Tom.. :smiley: :+1: :coffee: :australia:

I second this suggestion. When I started to replace Strings with SafeString, which are based on static char[ ] arrays instead of dynamic heap allocation,
even without replacing ALL the String usage I get the following warning compiling on UNO

Global variables use 1777 bytes (86%) of dynamic memory, leaving 271 bytes for local variables. Maximum is 2048 bytes.

and that compile still uses a String for the getresults1 response from the ESP.
Also SafeString (like String on UNO) insists on leaving 128 bytes of free stack when it is allocated as a local variable.
So it is likely a local SafeString or String allocation could fail. Won't crash you sketch but won't work either.
You can sneak around the SafeString free stack check by creating a local SafeString wrapping a local char[ ] i.e.

  void method() {
  char charBuffer[50];
  cSFA(sfStr,charBuffer);  // no free stack check here so you can use the last 128 bytes if you are game

But bottom line is you are running short on RAM if you only have 2048bytes.

1 Like

@buffalobillyboy, let's make a list:

  • SoftwareSerial takes over the entire ATmega328P. If you use it, there is not much else you can do, maybe blink a led. Even then it is not 100% reliable. Use a Leonardo with a extra Serial port, or a MKR board or ESP32. If you want to improve this project, then the AltSoftSerial is less demanding.
  • Your arrays for display patterns could be make 'const'. They are 124 bytes together. You should put them in PROGMEM.
  • The ESP8266 for Wifi is okay, but now you have two boards. That is a lot more trouble. If you can do your project with a single ESP32 board, then you can also use String without problem. The Wifi is also more reliable, because you will have the latest updates instead of older firmware. The ESP8266 is also a good board, but you have to a use a normal ESP8266 board, such as a NodeMCU. If you want an extra Serial port on the ESP8266, then the SoftwareSerial for the ESP8266 is not so bad.
  • If you want to use that many String objects, then you should read about taming those things on the website of drmpf.
  • You wait for a response of the Serial data, sometimes 10 seconds. During that time you can not run the rest of the sketch.

Conclusion: With improvements the sketch can be more reliable. Running your sketch on a ESP32 (or ESP8266) board will make it more reliable. Tutorials for the ESP8266 and ESP32: https://randomnerdtutorials.com/.

thanx

Nano. Thanks for your suggestions

thanks! I compile with a nano board no problem. I wonder if it's not wiser to avoid using Strings at all and just use classical c strings in global memory and sending only the string addresses as arguments for the functions.

Regardless or CStrings or String and SafeStrings, as long as you keep the text in ram you will have memory pressure. Passing a cString pointer or passing a String as reference does not create a copy.
If you are short on RAM and have done all the suggested clean up and implemented tests to avoid memory errors then it’s time to consider a more capable processor

You can do that, but if you get the sizes wrong you code will crash, which makes debugging difficult.
You can improve your chances by passing both the string address AND its size and then using strlcpy and strlcat to work on them see strlcpy_strlcat.ino for an example sketch on how to use them.
This will protect you if you get the size wrong for the number of chars you are adding and not crash your sketch so you can still print debug msgs.
OR
You can use my SafeString library to create the globals which have a number of advantages
I) the SafeString carries around the char[ ] size to check there is enough space for the next add
ii) SafeString has similar functions to String so converting to SafeString is more straight forward
iii) SafeString will give you detailed error msgs when you try to push too much into them.
The downside is that SafeString objects take a bit more memory than just a char[ ], and you don't have a lot of spare memory. If SafeStrings work then great otherwise you are back to raw char[ ]'s
OR
using a board with more RAM (see the suggestion from @TomGeorge above)
Can you just move everything to the ESP8266? Or are you short of pins?

Here the previous sketch I posted, but partially converted to use SafeString.

// download SafeString V4.1.5+ library from the Arduino Library manager or from
// https://www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html
#include "SafeString.h"

//https://www.youtube.com/watch?v=4vKxGHGYOtI&t=103s
//#define SCROLL
#define DISPLAY_TEMPERATURES //display pool and air temperatures as well as time & date

#define USE_SOFTWARESERIAL

#ifdef USE_SOFTWARESERIAL
// SERIAL_IO redefines Arduino.h:54  #define SERIAL_IO  0x0
// that won't help
#define SERIAL_IO Serial1
#else
#define SERIAL_IO Serial
#endif

#define MAXLEN 60
#ifdef USE_SOFTWARESERIAL
#include <SoftwareSerial.h>
#endif
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // set the LCD address to 0x27 for a 16 chars and 2 line display
//Gnd
//Vcc
//SDA(A4)
//SCL(A5)
//-------------Clock variables-----------
unsigned long refmillis, currenthour;
int Time[6]; //currenthour_10's,currenthour_1's,currentminute_10's,currentminute_1's
//String str_currentTime = "", str_currentDate = "", str_pooltemp = "", str_airtemp = "", str_wakeupTime = "";
// not a problem is these are not correct SafeString will print a detailed error msg
cSF(str_currentTime, 20);
cSF(str_currentDate, 20);
cSF(str_pooltemp, 10);
cSF(str_airtemp, 10);
cSF(str_wakeupTime, 10);


// add reserve( ) in setup for these globals
int buzzPin = 8, alarmdone = 0;
// make period and duration arrays to get compile to work
int period[] = {500, 594, 500, 594, 707, 594, 707, 594, 530, 500, 594};
int duration[] = {1000, 1000, 1000, 1000, 250, 500, 250, 500, 500, 1000, 1000};
//------------esp01 variables ----------------------------------------------------
#ifdef USE_SOFTWARESERIAL
SoftwareSerial SERIAL_IO(6, 7); // RX, TX
#endif
// leave these as Strings because that is just convenient
String AP = "*****"; // AP NAME
String PASS = "***********"; // AP PASSWORD
String API = "***********"; // Write API KEY
String HOST = "api.thingspeak.com";
String PORT = "80";

cSF(str_time, 100); //looks like 87 is enough
int toggle = 0;
int countTimeCommand = 0;
boolean found = false;
const unsigned long delaymillis = 120000UL; //120 sec
unsigned long startmillis;
//--------------LCD variables ---------------
int LCDcol = 0;
#ifdef SCROLL
const unsigned long delaydisplaymillis = 500UL;
#else
const unsigned long delaydisplaymillis = 6000UL;
#endif
unsigned long startdisplaymillis;
//--------------Big Display-------------------
// Define the bit patters for each of our custom chars. These
// are 5 bits wide and 8 dots deep
uint8_t custChar[8][8] = {
  {31, 31, 31, 0, 0, 0, 0, 0}, // Small top line - 0
  {0, 0, 0, 0, 0, 31, 31, 31}, // Small bottom line - 1
  {31, 0, 0, 0, 0, 0, 0, 31}, // Small lines top and bottom -2
  {0, 0, 7, 7, 7, 7, 0, 0}, // colon dot -3
  {31, 31, 31, 31, 31, 31, 15, 7}, // Left bottom chamfer full - 4
  {28, 30, 31, 31, 31, 31, 31, 31}, // Right top chamfer full -5
  {31, 31, 31, 31, 31, 31, 30, 28}, // Right bottom chamfer full -6
  {7, 15, 31, 31, 31, 31, 31, 31}, // Left top chamfer full -7
};
// Define our numbers 0 thru 9
// 254 is blank and 255 is the "Full Block"
uint8_t bigNums[10][6] = {
  {7, 0, 5, 4, 1, 6}, //0
  {0, 5, 254, 1, 255, 1}, //1
  {0, 2, 5, 7, 1, 1}, //2
  {0, 2, 5, 1, 1, 6}, //3
  {7, 1, 255, 254, 254, 255}, //4
  {7, 2, 0, 1, 1, 6}, //5
  {7, 2, 0, 4, 1, 6}, //6
  {0, 0, 5, 254, 7, 254}, //7
  {7, 2, 5, 4, 1, 6}, //8
  {7, 2, 5, 1, 1, 6}, //9
};

void setup() {
  // put your setup code here, to run once:
  // Only start Serial if using USE_SOFTWARESERIAL
#ifdef USE_SOFTWARESERIAL
  Serial.begin(115200);
  for (int i = 10; i > 0; i--) {
    delay(500);
    Serial.print(i); Serial.print(' ');
  }
  Serial.println(F("Started "));
  SafeString::setOutput(Serial);   // <<<<<<<<<<   send error messages to Serial
#endif
  //  str_currentTime.reserve(20); // padded to 16 in Clock( ) so 20 should be fine
  //  str_currentDate.reserve(20); // padded to 16 in getresults1 so 20 should be fine
  //  str_pooltemp.reserve(10);
  //  str_airtemp.reserve(10);
  //  str_wakeupTime.reserve(10);

  pinMode(buzzPin, OUTPUT);
  digitalWrite(buzzPin, LOW);
  lcd.init(); // initialize the lcd
  lcd.init();
  lcd.backlight();
  delay(500);
  lcd.home();
  lcd.clear();
  for (size_t cnt = 0; cnt < sizeof(custChar) / 8; cnt++)
  {
    lcd.createChar(cnt, custChar[cnt]);
  }
  initClock(0, 0, 0, &refmillis); //init Time to midnite
  SERIAL_IO.begin(9600);

  while (found == false)
  {
    sendCommand("AT", 5, "OK");
    sendCommand("AT+CWMODE=1", 5, "OK");
    sendCommand("AT + CWJAP = \"" + AP + "\",\"" + PASS + "\"", 50, "OK");
    sendCommand("AT+CIPMUX=1", 5, "OK");
  }
  startmillis = millis() - delaymillis;
  startdisplaymillis = millis() - delaydisplaymillis;
}

void loop()
{
  if (SafeString::errorDetected()) {
    // one of the toInt() failed
    SafeString::Output.println("one if the toInt( ) failed");
  }
  //--------------- Get info from Internet ---------
  if ((millis() - startmillis) >= delaymillis)
  {
    while (SERIAL_IO.available() > 0) SERIAL_IO.read(); //start with an MT softwareserial buffer before sending GET's
    if (toggle == 3)
    {
      getresults1(str_time, 1423291L, 1, str_wakeupTime, "", 0); //get wakupTime
      toggle = 0;
    }
    else if (toggle == 2)
    {
      getresults1(str_time, 1418147L, 1, str_airtemp, "AIR", 1); //get airtemp
      toggle = 3;
    }
    else if (toggle == 1)
    {
      getresults1(str_time, 1418147L, 4, str_pooltemp, "EAU", 1); //get pooltemp
      toggle = 2;
    }
    else if (toggle == 0)
    {
      getresults(str_time); //getTime & date
      parseResults(str_time, str_currentDate, &refmillis);
#ifdef DISPLAY_TEMPERATURES
      toggle = 1;
#endif
    }
#ifdef USE_SOFTWARESERIAL
    Serial.println(str_time);
#endif
    SERIAL_IO.println(F("\n\r"));
    startmillis = millis();
  }
  //-------------------- Run the Clock ----------------------------------------------
  Clock(str_currentTime, refmillis, &currenthour, Time); //update time of day Clock
  //  if (str_wakeupTime == str_currentTime.substring(0, 5)) //alarm if active
  cSF(sfWake, 5);
  str_currentTime.substring(sfWake, 0, 5);
  if (str_wakeupTime == sfWake) //alarm if active
  {
    if (alarmdone == 0)
    {
      Miles(period, duration);
      alarmdone = 1;
    }
  }
  else
  {
    alarmdone = 0;
  }
  //----------------Take care of LCD display -----------------------------------------
  if (((int)currenthour > 21) || ((int)currenthour < 9)) //During sleeptime, only show currentTime on big display
    // if (0)
  {
    LCDbig(Time);
  }
  else //During waketime, show currentTime, currentDate, pooltemp, and airtemp on small display
  {
    LCDsmall(&startdisplaymillis, &LCDcol, str_currentTime, str_currentDate, str_pooltemp, str_airtemp, str_wakeupTime);
  }
} //void loop()

void initClock(unsigned long valhour, unsigned long valminute, unsigned long valsecond, unsigned long *Refmillis)
{
  *Refmillis = millis() - ((valhour * 3600L + valminute * 60L + valsecond)) * 1000L; //reference to 0h/0m/0s
}

//int Clock(String *currentTime, unsigned long Refmillis, unsigned long *Currenthour, int *TIme)
// nothing returned from this method
void Clock(SafeString &currentTime, unsigned long Refmillis, unsigned long *Currenthour, int *TIme)
{
  unsigned long deltamillis, currentminute, currentsecond;
  int siz, i;
  deltamillis = (millis() - Refmillis) % ((unsigned long)(60L * 60L * 24L) * 1000L);
  *Currenthour = deltamillis / 3600000L;
  deltamillis -= *Currenthour * 3600000L;
  currentminute = deltamillis / 60000L;
  deltamillis -= currentminute * 60000L;
  currentsecond = deltamillis / 1000L;
  currentTime = "";// clear to start with
  twoDigits(currentTime, *Currenthour);
  currentTime += ":";
  twoDigits(currentTime, currentminute);
  currentTime += ":";
  twoDigits(currentTime, currentsecond);
  siz = currentTime.length();
  if (siz < 16) for (i = 0; i < 16 - siz; i++) currentTime += (" "); // padd it
  TIme[0] = *Currenthour / 10;
  TIme[1] = *Currenthour % 10;
  TIme[2] = currentminute / 10;
  TIme[3] = currentminute % 10;
  TIme[4] = currentsecond / 10;
  TIme[5] = currentsecond % 10;
}

void Miles(int *Period, int *Duration)
{
  int i, j;
  unsigned long tIme;
  for (j = 0; j < 2; j++)
  {
    for (i = 0; i < 11; i++) //song duration=19sec
    {
      tIme = 0ul;
      while (tIme < 1000ul * Duration[i])
      {
        digitalWrite(buzzPin, HIGH);
        delayMicroseconds(Period[i]);
        digitalWrite(buzzPin, LOW);
        delayMicroseconds(Period[i]);
        tIme += (2 * Period[i]);
      }
    }
    delay(1000);
  }
  digitalWrite(buzzPin, LOW);
}

void LCDbig(int *tIme)
{
  printBigNum(tIme[0], 0, 0);
  lcd.setCursor(3, 0);
  lcd.print(' ');
  lcd.setCursor(3, 1);
  lcd.print(' ');
  printBigNum(tIme[1], 4, 0);
  if (tIme[5] % 2 == 0) //blink the : every second
  {
    lcd.setCursor(7, 0);
    lcd.print((char)3); //:
    lcd.setCursor(7, 1);
    lcd.print((char)3); //:
  }
  else
  {
    lcd.setCursor(7, 0);
    lcd.print(' '); //blank
    lcd.setCursor(7, 1);
    lcd.print(' '); //blank
  }
  lcd.setCursor(8, 0);
  lcd.print(' ');
  lcd.setCursor(8, 1);
  lcd.print(' ');
  printBigNum(tIme[2], 9, 0);
  lcd.setCursor(12, 0);
  lcd.print(' ');
  lcd.setCursor(12, 1);
  lcd.print(' ');
  printBigNum(tIme[3], 13, 0);

}

// -----------------------------------------------------------------
// Print big number over 2 lines, 3 colums per half digit
// -----------------------------------------------------------------
void printBigNum(int number, int startCol, int startRow) {
  // Position cursor to requested position (each char takes 3 cols plus a space col)
  lcd.setCursor(startCol, startRow);
  // Each number split over two lines, 3 chars per line. Retrieve character
  // from the main array to make working with it here a bit easier.
  uint8_t thisNumber[6];
  for (int cnt = 0; cnt < 6; cnt++) {
    thisNumber[cnt] = bigNums[number][cnt];
  }
  // First line (top half) of digit
  for (int cnt = 0; cnt < 3; cnt++) {
    lcd.print((char)thisNumber[cnt]);
  }
  // Now position cursor to next line at same start column for digit
  lcd.setCursor(startCol, startRow + 1);
  // 2nd line (bottom half)
  for (int cnt = 3; cnt < 6; cnt++) {
    lcd.print((char)thisNumber[cnt]);
  }
}

void LCDsmall(unsigned long *Startdisplaymillis, int *lcdcol, SafeString &STR_currentTime, SafeString &STR_currentDate, SafeString &STR_pooltemp, SafeString &STR_airtemp, SafeString &str_wakeupTIME)
{
  //  String Message, line2;
  cSF(Message, 24); // 24?? not a problem SafeString will tell us if this is too small
  cSF(line2, 24);
  int Msglength;
  lcd.setCursor(0, 0);
  if (str_wakeupTIME.length() > 0)
  {
    //    lcd.print(STR_currentTime.substring(0, 8) + " " + str_wakeupTIME);
    cSF(sfPrint, 20);
    STR_currentTime.substring(sfPrint, 0, 8);
    sfPrint += " ";
    sfPrint += str_wakeupTIME;
    lcd.print(sfPrint);
  }
  else lcd.print(STR_currentTime); //Line0
  Message = "";
  Message += STR_currentDate; //16 characters
  Message += STR_pooltemp; //8 characters or 0
  Message += STR_airtemp; //8 characters or 0
  if (Message.length() == 24) Message += " "; //16 or 32 characters only  // <<<<<<<<<< odd
  Msglength = Message.length();
  Message += STR_currentDate; //needed for scrolling
  //line2 = Message.substring(*lcdcol, *lcdcol + 16);
  Message.substring(line2, *lcdcol, *lcdcol + 16);
  lcd.setCursor(0, 1);
  lcd.print(line2); //Line2 scrolls or alternates
  if ((millis() - *Startdisplaymillis) >= delaydisplaymillis) //Line1
  {
#ifdef SCROLL
    *lcdcol += 1; //scroll
#else
    *lcdcol += 16; //alternate
#endif
    if (*lcdcol >= Msglength) *lcdcol = 0;
    *Startdisplaymillis = millis();
  }
}

void getresults(SafeString &Str_time) //get time of day from Internet
{
  cSF(getData, 100, "GET https://api.thingspeak.com/apps/thinghttp/send_request?api_key=5R0NJ8SL2DSEAGQA");
  char c = ' ';
  //String Str_time = "";
  Str_time = ""; // clear to start with
  int countcomma = 0;
  unsigned long waitTime, startTime;
  sendCommand("AT+CIPSTART=0,\"TCP\",\"" + HOST + "\"," + PORT, 15, "OK");
  sendCommand("AT+CIPSEND=0," + String(getData.length() + 4), 4, ">");
  SERIAL_IO.println(getData);  // no need for a String here
  SERIAL_IO.println(F("\n\r"));
  //Typical Response From esp-01
  //Recv 87 bytes
  //SEND OK
  //+IPD,0,32:11:23:32 AM, Friday 14, May 20210,CLOSED

  waitTime = 10000UL;
  startTime = millis();
  while ((c != ':') && ((millis() - startTime) <= waitTime)) //remove unwanted initial characters
  {
    if (SERIAL_IO.available() > 0)
    {
      c = SERIAL_IO.read();
    }
  }
  waitTime = 1000UL;
  startTime = millis();
  c = ' ';
  while ((countcomma != 3) && ((millis() - startTime) <= waitTime)) //get time,date characters up to 3 ','
  {
    if (SERIAL_IO.available() > 0)
    {
      c = SERIAL_IO.read();
      Str_time.concat(c);
      if (c == ',') countcomma++;
    }
  }
  // return Str_time;
}

void getresults1(SafeString &str_res, long int channelID, int fieldno, SafeString &str_out, const char* str_label, int temperatureresult)
{
  char c = ' ';
  cSF(getData, 80, "GET https://api.thingspeak.com/channels/");
  getData += channelID;
  getData += "/fields/";
  getData += fieldno;
  getData += ".json?results=3";
  String Str_time, Str_temp = "";// ,str_res
  str_res = ""; // clear to start with
  int i, countcolons = 0, siz, iter;
  unsigned long waitTime, startTime;
  sendCommand("AT+CIPSTART=0,\"TCP\",\"" + HOST + "\"," + PORT, 15, "OK");
  sendCommand("AT+CIPSEND=0," + String(getData.length() + 4), 4, ">");
  SERIAL_IO.println(getData);
  SERIAL_IO.println(F("\n\r"));
  waitTime = 10000UL;
  startTime = millis();
  c = ' ';
  while ((c != '[') && ((millis() - startTime) <= waitTime)) //remove until [ start of feed
  {
    if (SERIAL_IO.available() > 0)
    {
      c = SERIAL_IO.read();
    }
  }
  for (iter = 0; iter < 3; iter++)
  {
    waitTime = 1000UL;
    startTime = millis();
    c = ' ';
    countcolons = 0;
    while ((countcolons != 5) && ((millis() - startTime) <= waitTime)) //remove until field3
    {
      if (SERIAL_IO.available() > 0)
      {
        c = SERIAL_IO.read();
        if (c == ':') countcolons++;
      }
    }
    waitTime = 1000UL;
    startTime = millis();
    c = ' ';
    Str_time = "";
    while ((c != '}') && ((millis() - startTime) <= waitTime)) //get data = "xxxxx"} or null}
    {
      if (SERIAL_IO.available() > 0)
      {
        c = SERIAL_IO.read();
        Str_time.concat(c);
      }
    }
    if (Str_time.substring(0, 1) != "n") Str_temp = Str_time; //Copy if field is not null. The last (most recent) valid field will be used.
  } //for (iter=0;iter<3;iter++) next feed
  if (Str_temp.length() != 0)
  {
    siz = Str_temp.length();
    String tmpStr = Str_temp.substring(1, siz - 2);
    str_res = tmpStr.c_str();
  }
  if (str_res.length() != 0)
  {
    if (temperatureresult)
    {
      str_out = str_label;
      str_out += " ";
      float tempFloat = 0.0;
      str_res.toFloat(tempFloat);
      str_out += round(tempFloat);
      str_out += "C";
      siz = str_out.length();
      if (siz < 8) for (i = 0; i < 8 - siz; i++) str_out += " ";
    }
    else
    {
      int str_resInt = 0;
      str_res.toInt(str_resInt);
      //      if (str_res.toInt() < 0) str_out = "";  // 0 could indicate error in toInt()
      if (str_resInt < 0) str_out = "";  // 0 could indicate error in toInt()
      else {
        str_out = "";
        twoDigits(str_out, str_resInt / 100);
        str_out += ":";
        twoDigits(str_out, str_resInt % 100);
      }
    }
  }
  //return str_res;
}

void parseResults(SafeString &Str_time, SafeString &str_CurrentDate, unsigned long *refMillis)
{
  int i = 0, j = 0, len,  startindx = 0, hashcode, siz;  //newTime[3],
  // if newTime[3] not initialized here, then when used below may get garbage
  int newTime[3] = {0, 0, 0};
  //String str_temp,
  cSF(str_temp, 6);
  //String parsechars = ":: , , ";
  cSF(parsechars, 7, ":: , , ");
  //
  // typical String to parse is:  11:23:32^AM,^Friday^14,^May^20210,CLOSED
  //
  //      substring(startindx,i) pchar[j]  found    what
  //      -----------------------------------------------
  //                    0     2     :  0    11      hour
  //                    3     5     :  1    23      minute
  //                    6     8     ^  2    32      second
  //                    9     12    ^  3    AM,     AM/PM
  //                    13    19    ^  4    Friday  day name
  //                    20    22    ,  5    14      day date
  //                    23    23    ^  6    -       nothing
  //                    24    27    ^  7    May     month
  //                    28    33    ,  8    20210   year
  //                                ^  9

  len = Str_time.length();
  if ((len >= 10) && (len < MAXLEN)) //parsing the received string newTime values in [hours,minutes,seconds], Day, Date, Month, Year
  {
    while (i < len)
    {
      if (Str_time.charAt(i) == parsechars.charAt(j))
      {
        switch (j)
        {
          case 0:
            // newTime[j] = (Str_time.substring(startindx, i)).toInt(); break; //get hour,minute,seconds
            Str_time.substring(str_temp, startindx, i);
            str_temp.toInt(newTime[j]);
            break;
          case 1:
            //newTime[j] = (Str_time.substring(startindx, i)).toInt(); break; //get hour,minute,seconds
            Str_time.substring(str_temp, startindx, i);
            str_temp.toInt(newTime[j]);
            break;
          case 2:
            //newTime[j] = (Str_time.substring(startindx, i)).toInt(); break; //get hour,minute,seconds
            Str_time.substring(str_temp, startindx, i);
            str_temp.toInt(newTime[j]);
            break;
          case 3:
            //str_temp = Str_time.substring(startindx, i - 1);
            Str_time.substring(str_temp, startindx, i - 1);
            if (str_temp.charAt(0) == 'P') //change to 24-hour format
            {
              if (newTime[0] != 12) newTime[0] += 12;
            }
            else
            {
              if (newTime[0] == 12) newTime[0] -= 12;
            }
            break;
          case 4:
            // str_temp = Str_time.substring(startindx, i); //get day
            Str_time.substring(str_temp, startindx, i);
            //get day of the week
            hashcode = codeString(str_temp);
            switch (hashcode)
            {
              case 13760: str_CurrentDate = "Lun "; break; //Mon
              case 13280: str_CurrentDate = "Mar "; break; //Tue
              case 13440: str_CurrentDate = "Mer "; break; //Wed
              case 13984: str_CurrentDate = "Jeu "; break; //Thu
              case 13088: str_CurrentDate = "Ven "; break; //Fri
              case 14208: str_CurrentDate = "Sam "; break; //Sat
              case 12864: str_CurrentDate = "Dim "; break; //Sun
              default: break; //do nothing
            }
            break;
          case 5: {
              //twoDigits(str_CurrentDate, (Str_time.substring(startindx, i)).toInt()); break; //get day date (1-31)
              Str_time.substring(str_temp, startindx, i);
              int dayDate = 0;
              str_temp.toInt(dayDate);
              twoDigits(str_CurrentDate, dayDate);
            }
          case 6: break;
          case 7:
            //str_temp = Str_time.substring(startindx, i); //get month
            Str_time.substring(str_temp, startindx, i);
            //get Month
            hashcode = codeString(str_temp);
            switch (hashcode)
            {
              case 13760: str_CurrentDate += "/01/"; break; //Jan
              case 13376: str_CurrentDate += "/02/"; break; //Feb
              case 14016: str_CurrentDate += "/03/"; break; //Mar
              case 12864: str_CurrentDate += "/04/"; break; //Apr
              case 14304: str_CurrentDate += "/05/"; break; //May
              case 12992: str_CurrentDate += "/06/"; break; //Jun
              case 12928: str_CurrentDate += "/07/"; break; //Jul
              case 13600: str_CurrentDate += "/08/"; break; //Aug
              case 13824: str_CurrentDate += "/09/"; break; //Sep
              case 12672: str_CurrentDate += "/10/"; break; //Oct
              case 13120: str_CurrentDate += "/11/"; break; //Nov
              case 14240: str_CurrentDate += "/12/"; break; //Dec
              default: break; //do nothing
            }
            break;
          case 8: {
              //str_CurrentDate += Str_time.substring(startindx, i - 1); break; //get year (4 digits)
              Str_time.substring(str_temp, startindx, i);
              int yearDate = 0;
              str_temp.toInt(yearDate);
              twoDigits(str_CurrentDate, yearDate);
            }
          default: break; //do nothibg
        } // switch(j)
        j++;
        startindx = i + 1;
      } //if (Str_time.charAt(i) == parsechars.charAt(j))
      i++;
    } //while (i<len)
    initClock(newTime[0], newTime[1], newTime[2], refMillis);  // <<<<<<< warning about newTime[ ] not being initialized
    siz = str_CurrentDate.length();
    if (siz < 16) for (i = 0; i < 16 - siz; i++) str_CurrentDate += " ";
  } //if ((len>=10)&&(len<MAXLEN))
}

int codeString(SafeString &in)
{
  int res = 0;
  for (int i = 0; i < 3; i++) res = ((in[i] << 5) + res) ^ res;
  return res;
}
//String twoDigits(unsigned long val)
// add two digits to outString
void twoDigits(SafeString &outString, unsigned long val)
{
  //String outString, str_val;
  //String str_val(val);
  //if (str_val.length() == 1) outString += "0";
  if (val < 10) {
    outString += "0";
  }
  outString += val;
  //return (outString);
}

// readReplay should be char* NOT a char
//void sendCommand(String command, int maxTime, char readReplay) {
void sendCommand(SafeString &command, int maxTime, const char* readReplay) {
  sendCommand(command.c_str(), maxTime, readReplay);
}
void sendCommand(String &command, int maxTime, const char* readReplay) {
  sendCommand(command.c_str(), maxTime, readReplay);
}
void sendCommand(const char* command, int maxTime, const char* readReplay) {
  found = false;
  while (countTimeCommand < (maxTime * 1))
  {
    SERIAL_IO.println(command);
    if (SERIAL_IO.find((uint8_t*)readReplay)) //ok
    {
      found = true;
      break;
    }
    countTimeCommand++;
  }
  countTimeCommand = 0;
}

BUT
looks to me like you are very close to running out of RAM.
Try running this and check for SafeString Error msgs. A zero length SafeString indicates available memory is <128bytes.
See my previous post for how to get around that check.

[Mod edit]
Inappropriate comments removed.
@J-M-L please use more temperate language, appropriate for a technical support forum, and show some respect for other posters, even if you don't agree with them. Thank you.
[End of mod edit]

And as memory is low there is an incentive to be cautious and not add extra libraries requiring more ram than necessary.

The problem is with low memory you will not allow any more then you think you need. Hence the likely hood of buffer overflow is increased.
If you get buffer overflow you will most likely crash you sketch at that point or elsewhere.

Well that’s about design

You won’t crash if you test. You will have to handle edge cases and devise a strategy for when you can’t complete some task because memory was low.

If this happens too frequently and code has been optimized for memory then it’s time to get better hardware.

I think you have this around the wrong way, "You will crash when you test" if you do not allow enough space in the char[ ]s and use strcpy/strcat.

As I mentioned in by post #30, you can avoid crashes by using strlcpy and strlcat, BUT you need to carry around the char[ ] size yourself and pass it to every method the adds to that array.

Here is an example of a crash when the char[ ] was not sized correctly
This crash happened in testing. Which is my point really.

As was foretold in post #5, battle bas been joined.

Basically, it comes down to this: you need to manage your RAM Any Which Way You Can and opinions vary as to how that should be done.

No... you test BEFORE doing stupid things and thus don't crash. Of course it means you need to know the size of the buffers. Then the programmer of course needs to decide what happens in that case and how to recover.

I defer to you. You are a far better programmer than I will ever be. I test to find the stupid things I have coded.

LOL, you know what I mean as you do it in your library. You test for space available before concatenating etc...

Ohh.. Yes I agree, BUT all the solution code posted here on the forum just uses strcpy/strcat and does not include any such testing code so that was my expectation of any solutions offered using char[ ], hence the comment. The of' quoted Serial Basics being a case in point, although in that case the buffer is sized to avoid the need, but that is not made clear anywhere in the text leaving the reader with the impression that it is OK to just use strcpy without checks.