SoftwareSerial Not Reliable?

we agree it's not OK to not test if you are not sure it won't overflow (gee, lots of negative !)

I think we agree as well that even though a library would "not do the stupid stuff and crash" (because it was developed in the right way and is doing the test), the programer should still check for proper completion of the request and decide what to do if the ask failed.

So it's a matter of testing before, or testing after - but in any case it's about testing when necessary.

No I don't agree with that, I might be sure today, but a change in a global buffersize or global string or a change in the method call order / code path could change that tomorrow.
I would say you should test always where the operation is happening with the arguments you have there at hand. So, for example, by using strlcpy or strlcat

Thanx. Tell me please what you mean by "passing a String as reference".

that's what was suggested here (#17)

In your code you had examples like

Where you passed a String pointer String*. No copies there.
A String reference is declared as String & instead of String *
It is really a pointer like String * BUT in your code you can use it like String.
e.g.

int Clock(String &currentTime, unsigned long Refmillis, unsigned long *Currenthour, int *TIme) {
 . . . 
  currentTime = twoDigits(*Currenthour) + ":" + twoDigits(currentminute)+ ":" + twoDigits(currentsecond);
  siz = currentTime.length();

Note the currentTime->length() is not written as currentTime.length().

So using a String & (reference) the code looks you are using a normal String but works like a String*, i.e. updates the result, but without having to use -> everywhere.

Depending on how you use them, String objects can be The Good, The Bad and The Ugly. There are several ways to do apparently innocuous things that end up calling the copy constructor and if you do so enough, your horse is headed down the trail to Ugly town.

That call by reference helps with avoiding one of those constructors being called. It's just one more thing you need to know before you have a chance to use Strings without problems.

Anyway, I removed several of the String * arguments of my functions and accessed them in these functions as global variables. I also used long String manipulations outside the functions. My freeMemory() now shows 276 bytes instead of 127. The compiler shows 56% progmem 63% Ram instead of 56% and 62%. It's been running for several hours with no bugging even tho' I'm using SoftwareSerial. I'll let it run all night to see if there's a little surprise waiting for me in the morning. Yeah, those big Strings can be pretty violent inside of functions. I'll post my new code here.

//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
#define SERIAL Serial1
#else
#define SERIAL 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="";
int buzzPin=8,alarmdone=0;
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(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="";
String getData="";
int toggle=0;
int countTimeCommand=0; 
boolean found = false; 
const unsigned long delaymillis=12000UL; //120 sec
unsigned long startmillis;
extern char *__brkval;
//--------------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
const 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"
const 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:
#ifdef USE_SOFTWARESERIAL
  Serial.begin(115200);
#endif
  pinMode(buzzPin,OUTPUT);
  digitalWrite(buzzPin,LOW);
  lcd.init();                      // initialize the lcd
  lcd.init();
  lcd.backlight();
  delay(500);
  lcd.home();
  lcd.clear();
  for (int cnt = 0; cnt < sizeof(custChar) / 8; cnt++) 
  {
    lcd.createChar(cnt, custChar[cnt]);
  }  
  initClock(0, 0, 0, &refmillis); //init Time to midnite
  SERIAL.begin(9600);
  
  while (found==false)
  {
    sendCommand1("AT",5,"OK");
    sendCommand1("AT+CWMODE=1",5,"OK");
    sendCommand1(("AT+CWJAP=\""+ AP +"\",\""+ PASS +"\"").c_str(),50,"OK");
    sendCommand1("AT+CIPMUX=1",5,"OK");
  }
  startmillis=millis()-delaymillis; 
  startdisplaymillis=millis()-delaydisplaymillis; 
}

void loop() 
{
//--------------- Get info from Internet ---------  
  if ((millis()-startmillis)>=delaymillis)
  {
    while (SERIAL.available()>0) SERIAL.read(); //start with an MT softwareserial buffer before sending GET's
    if (toggle==3)
    {
      getData="GET https://api.thingspeak.com/channels/";
      getData+="1423291";
      getData+="/fields/";
      getData+="1";
      getData+=".json?results=3";
      sendCommand1(("AT+CIPSTART=0,\"TCP\",\""+ HOST +"\","+ PORT).c_str(),15,"OK");
      sendCommand1(("AT+CIPSEND=0," +String(getData.length()+4)).c_str(),4,">");
      getresults1(&str_wakeupTime,"",0); //get wakupTime
#ifdef USE_SOFTWARESERIAL
      Serial.println(str_wakeupTime);
#endif
      toggle=0;
    }        
    else if (toggle==2)
    {
      getData="GET https://api.thingspeak.com/channels/";
      getData+="1418147";
      getData+="/fields/";
      getData+="1";
      getData+=".json?results=3";
      sendCommand1(("AT+CIPSTART=0,\"TCP\",\""+ HOST +"\","+ PORT).c_str(),15,"OK");
      sendCommand1(("AT+CIPSEND=0," +String(getData.length()+4)).c_str(),4,">");
      getresults1(&str_airtemp,"AIR",1); //get airtemp
#ifdef USE_SOFTWARESERIAL
      Serial.println(str_airtemp);
#endif
      toggle=3;      
    }
    else if (toggle==1)
    {
      getData="GET https://api.thingspeak.com/channels/";
      getData+="1418147";
      getData+="/fields/";
      getData+="4";
      getData+=".json?results=3";
      sendCommand1(("AT+CIPSTART=0,\"TCP\",\""+ HOST +"\","+ PORT).c_str(),15,"OK");
      sendCommand1(("AT+CIPSEND=0," +String(getData.length()+4)).c_str(),4,">");
      getresults1(&str_pooltemp,"EAU",1); //get pooltemp
#ifdef USE_SOFTWARESERIAL
      Serial.println(str_pooltemp);
#endif
      toggle=2;
    }
    else if (toggle==0)
    {
      getData="GET https://api.thingspeak.com/apps/thinghttp/send_request?api_key=5R0NJ8SL2DSEAGQA";
      sendCommand1(("AT+CIPSTART=0,\"TCP\",\""+ HOST +"\","+ PORT).c_str(),15,"OK");
      sendCommand1(("AT+CIPSEND=0," +String(getData.length()+4)).c_str(),4,">");
      getresults(); //getTime & date
      parseResults(&refmillis);
#ifdef USE_SOFTWARESERIAL
      Serial.println(str_currentDate);
#endif
#ifdef DISPLAY_TEMPERATURES
      toggle=1;
#endif
    }
    SERIAL.println("\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)
{
  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 = twoDigits(*Currenthour) + ":" + twoDigits(currentminute)+ ":" + twoDigits(currentsecond);
  siz = currentTime->length();
  if (siz<16) for (i = 0; i < 16 - siz; i++) *currentTime += (" ");
  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;
  long long tIme;
  for (j=0;j<2;j++)
  {
    for (i=0;i<11;i++) //song duration=19sec
    {
      tIme=0L;
      while (tIme<(long long)1000*(long long)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
    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() //get time of day from Internet
{
  char c=' ';  
  int countcomma=0;
  unsigned long waitTime,startTime; 
  str_time="";
  SERIAL.println(getData);
  SERIAL.println("\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.available()>0)
    {
      c=SERIAL.read();
    }
  }
  waitTime=1000UL;
  startTime=millis();
  c=' ';
  while ((countcomma!=3)&&((millis()-startTime)<=waitTime)) //get time,date characters up to 3 ','
  {
    if (SERIAL.available()>0)
    {
      c=SERIAL.read();
      str_time.concat(c);
      if (c==',') countcomma++;
    }
  }
}

void getresults1(String *str_out,String str_label,int temperatureresult)
{
  char c=' ';  
  String Str_temp="",str_res="";
  int i,countcolons=0,siz,iter;
  unsigned long waitTime,startTime;  
  SERIAL.println(getData);
  SERIAL.println("\n\r");
  waitTime=10000UL;
  startTime=millis();
  c=' ';
  while ((c!='[')&&((millis()-startTime)<=waitTime)) //remove until [ start of feed
  {
    if (SERIAL.available()>0)
    {
      c=SERIAL.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.available()>0)
      {
        c=SERIAL.read();
        if (c==':') countcolons++;
      }
    }
    waitTime=1000UL;
    startTime=millis();
    c=' ';
    str_time="";
    while ((c!='}')&&((millis()-startTime)<=waitTime)) //get data = "xxxxx"} or null}
    {
      if (SERIAL.available()>0)
      {
        c=SERIAL.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+" "+String(round(str_res.toFloat()))+"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="";
      else *str_out=twoDigits(str_res.toInt()/100)+":"+twoDigits(str_res.toInt()%100);
    }
  }
  return str_res;
}

void parseResults(unsigned long *refMillis)
{
  int i=0,j=0,len,newTime[3],startindx=0,hashcode,siz;
  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: str_currentDate+=twoDigits((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);
    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)
{
  String outString, str_val;
  str_val = String(val);
  if (str_val.length() == 1)
    outString = "0" + str_val;
  else
    outString = str_val;
  return (outString);
}

void sendCommand1(char command[], int maxTime, char readReplay[]) {
  found=false;
#ifdef USE_SOFTWARESERIAL
  Serial.println(freeMemory());
#endif
  while(countTimeCommand < (maxTime*1))
  {
    SERIAL.println(command);
    if(SERIAL.find(readReplay))//ok
    {
      found = true;
      break;
    }
    countTimeCommand++;
  }
  countTimeCommand = 0;
}

int freeMemory() {
  char top;
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
}

(edited your post to remove your credentials..)

good cleaning up work !

you can probably save a bit more as you have duplicated strings like
getData="GET https://api.thingspeak.com/channels/";

In that case adding reserve(..) in the setup for these globals can avoid having them move on the heap and causing RAM fragmentation.

Taming Arduino Strings has a StringReserveCheck class that will tell you if your reserve was large enough to avoid moving it on the heap.
With your memory limitations you will want to set the minimum reserve that does not result in the String being re-allocated to a different location leaving a hole behind.

Thanx but I get the compiler message. " 'codeString' was not declared in this scope"
Ya probably meant " int codeString(String &in) " ????
anyway that works like that

Thanx but I get the compiler message. " 'codeString' was not declared in this scope"
Ya probably meant " int codeString(String &in) " ????
anyway that works like that

Yes, sorry for that; I've corrected the example.

Ok thanx everybody! I learned a lotta new tricks in only a coupla days.

This forum is really cool,
It's like goin' back to school!

I can now probably go back to using SoftwareSerial for my bedroom clock 'cuz of the free memory I've won. But I'll keep using the hardware serial. I just need to change a define in my code and i'm set for debugging my Nano program on the prototype breadboard. Murphy told me there's more bugs still lurking out there!

Murphy was right. New code execution hangs up after several days. Gotta do a powerOFF/powerON to get back on the tracks. Using hardware serial and a constant free memory of 285 now instead of a fluctuating (down to 109) with the original code. I can't accuse the SoftwareSerial anymore! Bug hunting time. Below my new code if anyone is interested. I might also have to call Columbo to find this killerbug.

//https://www.youtube.com/watch?v=4vKxGHGYOtI&t=103s
//https://www.youtube.com/watch?v=8ZsUcUAsL3I&list=PLLTeWs2J_s7WxsLrbEkSCaTmQ9ezVJ0G4&index=174&t=625s
//#define SCROLL
//#define USE_SOFTWARESERIAL
#ifdef USE_SOFTWARESERIAL
#define SERIAL Serial1
#else
#define SERIAL 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="";
int buzzPin=8,alarmdone=0;
const int period[]={500,594,500,594,707,594,707,594,530,500,594};
const int duration[]={1000,1000,1000,1000,250,500,250,500,500,1000,1000};
//------------esp01 variables ----------------------------------------------------
#ifdef USE_SOFTWARESERIAL
SoftwareSerial SERIAL(6, 7); // RX, TX
#endif
String str_time="";
char GetData[90];
char Command[50];
char Command1[]="AT+CIPSTART=0,\"TCP\",\"api.thingspeak.com\",80";
char tmpchar[4];
const String parsechars="::   ,  , ";
int toggle=0;
int countTimeCommand=0; 
boolean found = false; 
const unsigned long delaymillis=120000UL; //120 sec
unsigned long startmillis;
extern char *__brkval;
//--------------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
const 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"
const 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:
#ifdef USE_SOFTWARESERIAL
  Serial.begin(115200);
#endif
  pinMode(buzzPin,OUTPUT);
  digitalWrite(buzzPin,LOW);
  lcd.init();                      // initialize the lcd
  lcd.init();
  lcd.backlight();
  delay(500);
  lcd.home();
  lcd.clear();
  for (int cnt = 0; cnt < sizeof(custChar) / 8; cnt++) 
  {
    lcd.createChar(cnt, custChar[cnt]);
  }  
  initClock(0, 0, 0, &refmillis); //init Time to midnite
  SERIAL.begin(9600);

  /*------------- allocate max. size of global Strings ------*/
  str_time.reserve(45);
  str_currentTime.reserve(16);
  str_currentDate.reserve(16);
  str_pooltemp.reserve(8);
  str_airtemp.reserve(8);
  str_wakeupTime.reserve(5);
  /*----------------------------------------------------------*/
  
  while (found==false)
  {
    strcpy(Command,"AT");
    sendCommand(Command,5,"OK");
    strcpy(Command,"AT+CWMODE=1");
    sendCommand(Command,5,"OK");
    strcpy(Command,"AT+CWJAP=\"xxxxxxx\",\"xxxxxxxx\""); //AP & PW
    sendCommand(Command,50,"OK");
    strcpy(Command,"AT+CIPMUX=1");
    sendCommand(Command,5,"OK");
  }
  startmillis=millis()-delaymillis; 
  startdisplaymillis=millis()-delaydisplaymillis; 
}

void loop() 
{
  int len;
//--------------- Get info from Internet ---------  
  if ((millis()-startmillis)>=delaymillis)
  {
    while (SERIAL.available()>0) SERIAL.read(); //start with an MT softwareserial buffer before sending GET's
    if (toggle==3)
    {
      strcpy(GetData,"GET https://api.thingspeak.com/channels/1423291/fields/1.json?results=3");
      sendCommand(Command1,15,"OK"); //"AT+CIPSTART=0,\"TCP\",\"api.thingspeak.com\",80";      
      len=strlen(GetData);
      itoa(len+4,tmpchar,10);
      strcpy(Command,"AT+CIPSEND=0,");
      strcat(Command,tmpchar);
      sendCommand(Command,4,">"); 
      getresults1(&str_wakeupTime,"",0); //get wakupTime
#ifdef USE_SOFTWARESERIAL
      Serial.println(str_wakeupTime);
#endif
      toggle=0;
    }        
    else if (toggle==2)
    {
      strcpy(GetData,"GET https://api.thingspeak.com/channels/1418147/fields/1.json?results=3");
      sendCommand(Command1,15,"OK"); //"AT+CIPSTART=0,\"TCP\",\"api.thingspeak.com\",80";
      len=strlen(GetData);
      itoa(len+4,tmpchar,10);
      strcpy(Command,"AT+CIPSEND=0,");
      strcat(Command,tmpchar);
      sendCommand(Command,4,">"); 
      getresults1(&str_airtemp,"AIR",1); //get airtemp
#ifdef USE_SOFTWARESERIAL
      Serial.println(str_airtemp);
#endif
      toggle=3;      
    }
    else if (toggle==1)
    {
      strcpy(GetData,"GET https://api.thingspeak.com/channels/1418147/fields/4.json?results=3");     
      sendCommand(Command1,15,"OK"); //"AT+CIPSTART=0,\"TCP\",\"api.thingspeak.com\",80";     
      len=strlen(GetData);
      itoa(len+4,tmpchar,10);
      strcpy(Command,"AT+CIPSEND=0,");
      strcat(Command,tmpchar);
      sendCommand(Command,4,">");                 
      getresults1(&str_pooltemp,"EAU",1); //get pooltemp
#ifdef USE_SOFTWARESERIAL
      Serial.println(str_pooltemp);
#endif
      toggle=2;
    }
    else if (toggle==0)
    {
      strcpy(GetData,"GET https://api.thingspeak.com/apps/thinghttp/send_request?api_key=5R0NJ8SL2DSEAGQA");
      sendCommand(Command1,15,"OK");//"AT+CIPSTART=0,\"TCP\",\"api.thingspeak.com\",80";
      len=strlen(GetData);
      itoa(len+4,tmpchar,10);
      strcpy(Command,"AT+CIPSEND=0,");
      strcat(Command,tmpchar);
      sendCommand(Command,4,">");
      getresults(); //getTime & date
      parseResults(&refmillis);
#ifdef USE_SOFTWARESERIAL
      Serial.println(str_currentTime);
      Serial.println(str_currentDate);
#endif
      toggle=1;
    }
    SERIAL.println("\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(&LCDcol);
  }
} //void loop()

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

int Clock(String *currentTime, unsigned long *Refmillis, unsigned long *Currenthour, int *TIme)
{
  unsigned long deltamillis,currentminute,currentsecond;
  int siz, i;
  deltamillis = (millis() - *Refmillis) % ((unsigned long)(60UL * 60UL * 24UL) * 1000UL);
  *Currenthour = deltamillis / 3600000UL;
  deltamillis -= *Currenthour * 3600000UL;
  currentminute = deltamillis / 60000UL;
  deltamillis-= currentminute * 60000UL;
  currentsecond = deltamillis / 1000UL;
  *currentTime = twoDigits(*Currenthour) + ":" + twoDigits(currentminute)+ ":" + twoDigits(currentsecond);
  siz = currentTime->length();
  if (siz<16) for (i = 0; i < 16 - siz; i++) *currentTime += (" ");
  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;
  long long tIme;
  for (j=0;j<2;j++)
  {
    for (i=0;i<11;i++) //song duration=19sec
    {
      tIme=0L;
      while (tIme<(long long)1000*(long long)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(int *lcdcol)
{
    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
    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() //get time of day from Internet
{
  char c=' ';  
  int countcomma=0;
  unsigned long waitTime,startTime; 
  str_time="";
  SERIAL.println(GetData);
  SERIAL.println("\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.available()>0)
    {
      c=SERIAL.read();
    }
  }
  waitTime=1000UL;
  startTime=millis();
  c=' ';
  while ((countcomma!=3)&&((millis()-startTime)<=waitTime)) //get time,date characters up to 3 ','
  {
    if (SERIAL.available()>0)
    {
      c=SERIAL.read();
      str_time.concat(c);
      if (c==',') countcomma++;
    }
  }
}

void getresults1(String *str_out,String str_label,int temperatureresult)
{
  char c=' ';  
  String Str_temp="";
  int i,countcolons=0,siz,iter;
  unsigned long waitTime,startTime; 
  SERIAL.println(GetData);
  SERIAL.println("\n\r");
  waitTime=10000UL;
  startTime=millis();
  c=' ';
  while ((c!='[')&&((millis()-startTime)<=waitTime)) //remove until [ start of feed
  {
    if (SERIAL.available()>0)
    {
      c=SERIAL.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.available()>0)
      {
        c=SERIAL.read();
        if (c==':') countcolons++;
      }
    }
    waitTime=1000UL;
    startTime=millis();
    c=' ';
    str_time="";
    while ((c!='}')&&((millis()-startTime)<=waitTime)) //get data = "xxxxx"} or null}
    {
      if (SERIAL.available()>0)
      {
        c=SERIAL.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
  siz = Str_temp.length();
  if (siz!=0) 
  {
    Str_temp=Str_temp.substring(1,siz-2);
    if (temperatureresult)
    { 
      *str_out=str_label+" "+String(round(Str_temp.toFloat()))+"C";
      siz=str_out->length();
      if (siz<8) for (i=0;i<8-siz;i++) *str_out+=" ";
    }
    else
    {
      if (Str_temp.toInt()<0) *str_out="";
      else *str_out=twoDigits(Str_temp.toInt()/100)+":"+twoDigits(Str_temp.toInt()%100);
    }
  }
}

void parseResults(unsigned long *refMillis)
{
  int i=0,j=0,len,newTime[3],startindx=0,hashcode,siz;
  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: str_currentDate+=twoDigits((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);
    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)
{
  String outString, str_val;
  str_val = String(val);
  if (str_val.length() == 1)
    outString = "0" + str_val;
  else
    outString = str_val;
  return (outString);
}

void sendCommand(char command[], int maxTime, char readReplay[]) {
  found=false;
#ifdef USE_SOFTWARESERIAL
  Serial.println(freeMemory());
#endif
  while(countTimeCommand < (maxTime*1))
  {
    SERIAL.println(command);
    if(SERIAL.find(readReplay))//ok
    {
      found = true;
      break;
    }
    countTimeCommand++;
  }
  countTimeCommand = 0;
}

int freeMemory() {
  char top;
  return __brkval ? &top - __brkval : &top - __malloc_heap_start;
}

freeMemory() tells you what's the status at a given point in your code but not the dynamics in between. You still use lots of Strings dynamic instantiation and no testing if operations on those are successful (and no testing for buffer overflow either).

Just wondering tho': If my freeMemory() is constant, I mean constant since PWR ON at this instant in the execution, then even tho' another instant might have less freeMemory(), I would have a tendency to think it should be constant also. I wonder if something else is happening every coupla days. I added a check on the length of String str_time which I generate to limit it to the reserve value in the setup(). Is that what you mean by testing? I do:

    while ((c!='}')&&((millis()-startTime)<=waitTime)&&(str_time.length()<STRTIMEMAX)) 
    {
      if (SERIAL.available()>0)
      {
        c=SERIAL.read();
        str_time.concat(c);
      }
    }

it could mean allocation and deallocation were matched when you measured - so it's stable but you would not see when an allocation failed and you got a wrong String as a result for example - in which case since nothing got allocated, you would still see stable memory footprint.

just a thought, not saying this is what happens

Whenever someone brings a problem here saying that their code runs for a while and then fails, the first thing I look for in their code is String objects. In your case of course, it was the zeroth thing because that was my guess sight unseen.

IIRC, you have removed some Strings and improved matters. I suggest that you remove some more :wink:

You have so little memory to spare that it won't take much to cause a problem. Frankly, in that situation I wouldn't use Strings in my code for A Fistful of Dollars, but if you wish to Sully your program with them I don't think you'll find it easy to fix.

What happens if another String that I don't limit it (as I just showed in my code) grows to a size> than reserve value in the setup()?

Depends when/where it happens. BUT as Strings on AVR require that there be 128bytes free AFTER they change size. So you are fighting up hill on this one.