SoftwareSerial Not Reliable?

I'm a new old guy here. I've got a question concerning the SoftwareSerial library. I know its limitations (Clint Eastwood told me to be careful):

1.You have to tell it which Serialx pins to listen to. The library is not always running.
2.You have to be careful with Baudrates>9600.

Keeping this in mind, I still have a reliability problem when I use it.

My project is a bedroom clock using an LCD 16x2 display and a buzzer to wake me up with a little song. I display the Time, Date, pool water temperature, outdoor air temperature, and wakeup time during the day and Time only in BIG NUMBERS at night.

I am monitoring Internet values (ThingSpeak temperature values, and a current time website) via the ESP8266-01 module using AT commands. Sometimes the values are not correctly received and sometimes my Nano program seems to permanently lose contact with the ESP01. This may occur after 10 hours or more of correct execution of my program that normally reads these values every 2 minutes. After switching to the hardware serial interface, the problem is solved. I am satisfied with this configuration but am curious why I need to do this.

  1. Is the SoftwareSerial Library so unreliable?
  2. Am I consuming too much memory?
    Using SoftwareSerial: Program Memory 56%, RAM 62%
    Using hardware serial: Program Memory 51%, RAM 56%

I must also add that I have successfully used the SoftwareSerial Library for my 2 temperature sensors(Nano, ATMega328P 8 MHz) and my living room display console(Nano) which displays these measurements. (These 3 projects use less RAM than the bedroom clock). I would be happy to provide my code, schematic if anyone is interested. Thanks & Good Day!

Bill

Conventional wisdom says that software serial is unreliable above 38400 baud, although people seem to have got it working with small amounts of data at a higher speed than that.

If your program runs properly for ten hours, I suspect you have other problems and we'll need to see your code.

My uninformed guess though: String objects and heap fragmentation.

So here's the code. Thanks for your interest!:

---------------------------------------- Code Below ----------------------------------------------------

//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 = "Livebox-9F34"; // AP NAME
String PASS = "9956636C4937DD59F954772442"; // AP PASSWORD
String API = "FOLOGDFQAZPURKFO"; // 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:
#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)
{
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.available()>0) SERIAL.read(); //start with an MT softwareserial buffer before sending GET's
if (toggle==3)
{
str_time=getresults1(1423291L,1,&str_wakeupTime,"",0); //get wakupTime
toggle=0;
}
else if (toggle==2)
{
str_time=getresults1(1418147L,1,&str_airtemp,"AIR",1); //get airtemp
toggle=3;
}
else if (toggle==1)
{
str_time=getresults1(1418147L,4,&str_pooltemp,"EAU",1); //get pooltemp
toggle=2;
}
else if (toggle==0)
{
str_time=getresults(); //getTime & date
parseResults(str_time,&str_currentDate,&refmillis);
#ifdef DISPLAY_TEMPERATURES
toggle=1;
#endif
}
#ifdef USE_SOFTWARESERIAL
Serial.println(str_time);
#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();
}
}

String getresults() //get time of day from Internet
{
char c=' ';
String getData="GET https://api.thingspeak.com/apps/thinghttp/send_request?api_key=5R0NJ8SL2DSEAGQA";
String Str_time="";
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.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++;
}
}
return Str_time;
}

String getresults1(long int channelID,int fieldno,String *str_out,String 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="";
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.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(String Str_time,String *str_CurrentDate,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(String command, int maxTime, char readReplay) {
found=false;
while(countTimeCommand < (maxTime*1))
{
SERIAL.println(command);
if(SERIAL.find(readReplay))//ok
{
found = true;
break;
}
countTimeCommand++;
}
countTimeCommand = 0;
}

Can you please modify your post to ensure the source code is in a single box? It should look like this.

// Your example source code

You need three ' at the beginning and end in the edit window? When you click on this icon </> you get.

```
type or paste code here
```

This ensures we can get all the source code. Right now if you copy the code there may be invisible characters that prevent successful compilation and parts might be missing.

This is holy war territory unfortunately.

You have extensive use of String objects in your code. On an Arduino with little RAM, like a Nano, there is a risk that over time like oh, I don't know, ten hours, you can fragment your heap and get unexpected failures.

There are those who will tell you that Strings are fine and under some circumstances they can be. In your case though, pertaining to Strings, Mr. Eastwood's advice is particularly apposite. Use arrays of char instead.

[note: edited to remove your credentials... careful when posting...]


I'll Try.

//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;
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:
#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)
  {
    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.available()>0) SERIAL.read(); //start with an MT softwareserial buffer before sending GET's
    if (toggle==3)
    {
      str_time=getresults1(1423291L,1,&str_wakeupTime,"",0); //get wakupTime
      toggle=0;
    }        
    else if (toggle==2)
    {
      str_time=getresults1(1418147L,1,&str_airtemp,"AIR",1); //get airtemp
      toggle=3;      
    }
    else if (toggle==1)
    {
      str_time=getresults1(1418147L,4,&str_pooltemp,"EAU",1); //get pooltemp
      toggle=2;
    }
    else if (toggle==0)
    {
      str_time=getresults(); //getTime & date
      parseResults(str_time,&str_currentDate,&refmillis);
#ifdef DISPLAY_TEMPERATURES
      toggle=1;
#endif
    }
#ifdef USE_SOFTWARESERIAL
    Serial.println(str_time);
#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(); 
    }  
}

String getresults() //get time of day from Internet
{
  char c=' ';  
  String getData="GET https://api.thingspeak.com/apps/thinghttp/send_request?api_key=5R0NJ8SL2DSEAGQA";
  String Str_time="";
  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.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++;
    }
  }
  return Str_time;
}

String getresults1(long int channelID,int fieldno,String *str_out,String 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="";
  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.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(String Str_time,String *str_CurrentDate,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(String command, int maxTime, char readReplay[]) {
  found=false;
  while(countTimeCommand < (maxTime*1))
  {
    SERIAL.println(command);
    if(SERIAL.find(readReplay))//ok
    {
      found = true;
      break;
    }
    countTimeCommand++;
  }
  countTimeCommand = 0;
}
1 Like

Oh please show me a little example if you have the time! Thanx!

You have some examples in your existing code. You're passing a plain C string as the last parameter to SendCommand. The first parameter could be one too instead of a String object.

It isn't a C string because you're using the ability to add Strings to get AP and PASS into the command. in C string world you'll need to accumulate that stuff in a buffer using strcpy and strcat.

@buffalobillyboy, your topic has been moved to a more suitable location on the forum.

Introductory tutorials is for tutorials to help people, not to ask questions. Feel free to write a tutorial once you have mastered software serial :slight_smile:

Gotcha. This could be a useful exercise-Reduce the use of Strings in favor of traditional c strings in functions. I'll let ya know. Is there an Arduino tool that analyzes stack usage?

Thanx

Bear with me. I'm a rookie. But is there really anyone out there who has mastered SoftwareSerial?

There is a freemem function that will show you how much space you have between stack and heap. Adafruit has one, along with a useful article explaining how heap fragmentation impacts your program.

Another consideration is that with more memory, the risk of Strings blowing your code up is reduced, although it may just delay the inevitable. Fixing your code is going to need a lot of changes. You might consider just buying your way out of trouble with one of the Teensy range.

there is nothing to master about it. It works if you don't flood it, if you don't let stuff accumulate in the input buffer, if you don't send out too much data too fast... basically don't put pressure on it

1 Like

I like economy. I have time(retired). I think I'll go for the "changes". I don't like to flee "trouble" but explain the why of the "trouble". However, that Teensy looks like a hot little machine!! I'll have to keep it in mind for my next mega project which I haven't dreamed of yet.

Before embarking on the changes, I'd suggest that you grab freemem and prove to yourself that you have a leak. It would be irritating to discover that the problem lies elsewhere (though I doubt it).

Yup I tried freemem(). The compiler says 733 bytes RAM available but when I use freemem() inside the getresults1() function there's only 243 bytes available.

1 Like

Everywhere where you past String as an argument to a function, you pass a copy of the String object. If you pass a reference, your memory usage in function should go down.

Two examples
Change

void sendCommand(String command, int maxTime, char readReplay[])

to

void sendCommand(String &command, int maxTime, char readReplay[])

Change

int codeString(String in)

to

int codeString(String &in)

// second example corrected

The crucial thing with freemem is to see how it changes over time. If it's going down (and I bet it is) you will eventually move into an Unforgiven state.

Getting closer to BOOM! And inside the sendCommand() called from getresults1() down to 127 bytes free. I'll probably not watch this for 10 hours but I think those Strings inside functions need to be changed to regular c strings like I did for the last argument of sendCommand().

1 Like

Yes! Thanx!