Using the SafeString library

This topic is open to questions about how to apply my SafeString library to particular cases.
(The SafeString library can be installed from the Arduino library manager)

If you want to comment on the pros and cons of have a SafeString library at all, please post to this topic
A Safe Alternative to using Strings in Arduino and try and keep this topic to problem solving using SafeStrings.

The primary reference for SafeStrings in the tutorial
But the SafeString library also includes the following extra classes
SafeStringReader, BufferedOutput, BufferedInput, SafeStringStream, millisDelay and loopTimer

SafeStringReader is covered briefly in the SafeString tutorial
SafeStringReader, BufferedOutput, BufferedInput and SafeStringStream are covered in the Arduino Text I/O for the Real World tutorial
millisDelay is covered in How to Code Timers and Delays in Arduino
loopTimer is covered in Multi-tasking in Arduino

If you have a problem applying SafeStrings to your sketch, you can post it here or you can contact me directly via Contact link (top right) on www.pfod.com.au

A Safe Alternative to using Strings in Arduino

Question: I'm stuck at redoing some Class definitions. Can createSafeString be used in Classes?

I'm trying:

class Loco { 
 public: 
   // Members 
   createSafeString(_niceName, 255); 
   createSafeString(_locoName, 255); 
   int _locoAddress; 
 . . .

But the compiler (arduino IDE) gives me:

libraries/SafeString/src/SafeString.h:209:94: error: expected identifier before 'sizeof'
#define createSafeString(name, size,...) char name ## _SAFEBUFFER[(size)+1]; SafeString name(sizeof(name ## _SAFEBUFFER),name ## _SAFEBUFFER, "" VA_ARGS , #name);
^
U.ino:98:3: note: in expansion of macro 'createSafeString'
createSafeString(_niceName, 255);
^
libraries/SafeString/src/SafeString.h:209:94: error: expected ',' or '...' before 'sizeof'
#define createSafeString(name, size,...) char name ## _SAFEBUFFER[(size)+1]; SafeString name(sizeof(name ## _SAFEBUFFER),name ## _SAFEBUFFER, "" VA_ARGS , #name);

No you cannot create SafeStrings as class members directly.

What you do is create normal char[]'s and then access/update them by wrapping them in a SafeStrings using
createSafeStringFromCharArray(sfString, charArrayName); OR cSFA(sfString, charArrayName) for short
within the class methods.

The resulting SafeString automatically knows how big the char[] is and how much space is available for use.

You need to do this each time you access the char[] to get the security and functionality of SafeString but the wrapping SafeString is created on the stack and its memory usage is completely recovered when you return from the class method call.

e.g.

#include <SafeString.h>
// install SafeString library from the Arduino Library manager
// tutorial at https://www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html

class Loco {
  private:
    // make these private to protect from external access
    char _niceName[12];
    char _locoName[12];
    int _locoAddress;

  public:
    // Members
    Loco(const char* niceName, const char* locoName, int locoAddress);
    void printData(Stream& out);
};

Loco::Loco(const char* niceName, const char* locoName, int locoAddress) {
  cSFA(sfNiceName, _niceName); //wrap in SafeStrings for processing
  cSFA(sfLocoName, _locoName);
  sfNiceName = niceName;
  sfLocoName = locoName;
  _locoAddress = locoAddress;
}
void Loco::printData(Stream &out) {
  out.print(" niceName:"); out.println((char*)_niceName);
  out.print(" locoName:"); out.println((char*)_locoName);
  out.print(" location:"); out.println(_locoAddress);
}

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  for (int i = 10; i > 0; i--) {
    Serial.print(i); Serial.print(' ');
    delay(500);
  }
  Serial.println();
  SafeString::setOutput(Serial); // enable error msgs

  // try and create a loco with names too long
  Serial.println(" construct locoBad");
  Loco locoBad("This nice name is too long", "and so is this loco name", 5);
  Serial.println(" the locoBad contains");
  locoBad.printData(Serial);
  Serial.println();
  Serial.println(" construct locoGood");
  Loco locoGood("fred", "35454", 5);
  Serial.println(" the locoGood contains");
  locoGood.printData(Serial);
}
void loop() {
}

The output is

 construct locoBad
Error: sfNiceName.concat() needs capacity of 26 for the first 26 chars of the input.
        Input arg was 'This nice name is too long'
        sfNiceName cap:11 len:0 ''
Error: sfLocoName.concat() needs capacity of 24 for the first 24 chars of the input.
        Input arg was 'and so is this loco name'
        sfLocoName cap:11 len:0 ''
 the locoBad contains
 niceName:
 locoName:
 location:5

 construct locoGood
 the locoGood contains
 niceName:fred
 locoName:35454
 location:5

You will only get the error msgs if you construct the Loco AFTER you begin Serial and call SafeString::setOutput(Serial);

Normally you would construct Loco at the top of your sketch outside setup(). When you do it in that location, SafeString still protect your code from buffer overrun, but there are no error msgs. The empty niceName etc is a strong hint the construction did not work and you can then move it into setup() (as shown above) to see what went wrong.

Using cSFA each time may seem like a nuance, but the rest of the code is made much simpler and safer using the SafeString
e.g. sfNiceName = niceName;
does all the bound checking and copying in one simple statement.

Two questions:

Question 1
I've added a SafeStringReader and Buffered output and it seems to be working.

Receiving input appears to add about 62ms versus 51 if I do not use SafeStringReader? Maybe this is expected?

As an aside the buffered out has dropped my loop time to 94ms from almost 15000... nice...

Sample code below which only times a loop. I've got a couple of statements that are commented in or out depending on if the buffer is used or not.

// *********************************************************************************

#include <millisDelay.h>
#include <SafeString.h>
#include <BufferedOutput.h>
#include <loopTimer.h>
#include <SafeStringReader.h>

createBufferedOutput(output, 66, DROP_UNTIL_EMPTY); // modes are DROP_UNTIL_EMPTY, DROP_IF_FULL or BLOCK_IF_FULL
// set up SafeString environment
createSafeString(msgStr, 30);


// args are (ReaderInstanceName, expectedMaxCmdLength, delimiters)
createSafeStringReader(sfReader, 50, " ,\r\n");
unsigned long currentMillis;

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

  // put your setup code here, to run once:
  SafeString::setOutput(Serial);
  output.connect(Serial);  // <<<<< connect the buffered output to Serial


  
  // Comment out to use buffer input 
  // sfReader.connect(Serial); // where SafeStringReader will read from not using buffer


  // comment these two lines out if not using buffered input
  output.connect(Serial);  // connect bufferedOut to Serial
  sfReader.connect(output); // where SafeStringReader will read from using buffer
  
  
  sfReader.echoOn(); // echo goes out via bufferedOut
  sfReader.setTimeout(100); // set 100mS == 0.1sec non-blocking timeout
}

void loop() {
  loopTimer.check(Serial);
  output.nextByteOut();


  currentMillis = millis();

  if (sfReader.read()) {
    output.print ("SafeString - Message Received\n");
  } // else ignore unrecognized command
}

Question 2:

Can you point me to the docs for parsing commands.

I want to parse out the values in this string delimited by "-":

"PiString-21-21-27-1-3-150-0"

I see reference to some SafeString Parsing commands but can't find examples of use.

Excellent work and tutorials... I've been able to figure most of it out and I'm a definite beginner,

thanks

Either using millisDelay or just millis, is this a good enough way to convert the following program:

 if ( (millis() - delayStart) >= 5)    
    digitalWrite(stepPinY, HIGH);
    
  if ( (millis() - delayStart) >= 5)  
    digitalWrite(stepPinY, LOW);
#include <SafeString.h>

const size_t readString_RESERVE = 100;

String shape_sequence;
unsigned long delayStart = 0;

const byte dirPinY = 2;
const byte stepPinY = 3;

void setup() {
  Serial.begin(19200);
  delay(500); // Necessary for clean serial monitor print

  shape_sequence.reserve(readString_RESERVE);

  pinMode(stepPinY, OUTPUT);
  pinMode(dirPinY, OUTPUT);
}

void loop() {
  digitalWrite(stepPinY, HIGH);
  delayMicroseconds(5);
  digitalWrite(stepPinY, LOW);
  delayMicroseconds(5);
}

If that's good enough, what about delay(500) in setup? Will I need to completely restructure a 'big program'?

@adamelli

A couple of points
i) delayMicroseconds(5) only delays for 5uS not 5mS millisDelay works in mS
ii) looks like you are driving a stepper motor. In that case talk a look at the detailed stepper example in
my Multi-tasking tutorial
iii) delays in setup() are not a problem

Here is some example code using millisDelays. You need just one timer and a boolean flag to keep track of high/low on next time out.

#include "SafeString.h"
#include "millisDelay.h"

// https://forum.arduino.cc/index.php?topic=723906.msg4929251#msg4929251

unsigned long delayStart = 0;

millisDelay stepDelay;
unsigned long STEP_DELAY_MS = 5; // 5mS
bool stepHigh = false;

const byte dirPinY = 2;
const byte stepPinY = 3;

void setup() {
  Serial.begin(19200);
  delay(500); // Necessary for clean serial monitor print
  // <<<<< delays in setup() not a problem

  pinMode(stepPinY, OUTPUT);
  pinMode(dirPinY, OUTPUT);
  stepDelay.start(STEP_DELAY_MS); // start stepping
  // call stepDelay.stop() to stop
}

void loop() {
  if (stepDelay.justFinished()) {
    if (stepHigh) { // was high
      digitalWrite(stepPinY, LOW);
      stepHigh = false; // LOW now
    } else { // was low
      digitalWrite(stepPinY, HIGH);
      stepHigh = true; // HIGH now
    }
  }
}

@dragynn
Question 1, let me look into that. SafeStringReader has alot of extra code to handle input buffer overflows, skip to next delimiter and timeout, but I would not have expected it to take an extra 11mS. but...

Question 2.

"PiString-21-21-27-1-3-150-0"

Check out the GPS parsing example which splits via ",*" and change the

char delims[] = ",*"; // fields delimited by , or *

to

char delims[] = "-"; // fields delimited by -

@dragynn
Question 1.
Only timing the SafeStringReader code I get these timings on an UNO
with input 12345678901234567890\n i.e. newline terminated, no timeout in SafeString reader

with echo on
1234567890
SafeString - Message Received
loop uS Latency
 5sec max:1008 avg:59
 sofar max:1008 avg:59 max - prt:1156

with echo off
SafeString - Message Received
loop uS Latency
 5sec max:612 avg:59
 sofar max:612 avg:59 max - prt:1064

That is ~1mS with echo on, due to extra processing in the bufferedOutput, and ~0.6mS with echo off
Both a long way from the 62mS you quoted. Note: the loopTimer is in uS so 62mS == 62000uS in the loopTimer output.

Here is my test code, with the timer only timing the SafeStringReader code

// *********************************************************************************
// https://forum.arduino.cc/index.php?topic=723906.msg4928577#msg4928577

#include <millisDelay.h>
#include <SafeString.h>
#include <BufferedOutput.h>
#include <loopTimer.h>
#include <SafeStringReader.h>

createBufferedOutput(output, 66, DROP_UNTIL_EMPTY); // modes are DROP_UNTIL_EMPTY, DROP_IF_FULL or BLOCK_IF_FULL
// set up SafeString environment
createSafeString(msgStr, 30);


// args are (ReaderInstanceName, expectedMaxCmdLength, delimiters)
createSafeStringReader(sfReader, 50, " ,\r\n");
unsigned long currentMillis;

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

  // put your setup code here, to run once:
  SafeString::setOutput(Serial);
  output.connect(Serial);  // <<<<< connect the buffered output to Serial



  // Comment out to use buffer input
  // sfReader.connect(Serial); // where SafeStringReader will read from not using buffer


  // comment these two lines out if not using buffered input
  output.connect(Serial);  // connect bufferedOut to Serial
  sfReader.connect(output); // where SafeStringReader will read from using buffer


  sfReader.echoOn(); // echo goes out via bufferedOut
  sfReader.setTimeout(100); // set 100mS == 0.1sec non-blocking timeout
}

bool read() {
  loopTimer.check(Serial);
  return sfReader.read();
}

void loop() {
  output.nextByteOut();

  currentMillis = millis();
  if (read()) {
    output.print ("SafeString - Message Received\n");
  } // else ignore unrecognized command
}

Thank you:
i) my mistake (about ms vs microseconds)
ii) lots of detail, will try to understand: Multi-tasking tutorial
iii) delays in setup() are fine - good to know

drmpf:
Here is some example code using millisDelays. You need just one timer and a boolean flag to keep track of high/low on next time out.

#include "SafeString.h"

#include "millisDelay.h"

unsigned long STEP_DELAY_US = 0.005; // 5 uS

void loop() {
 if (stepDelay.justFinished()) {
   if (stepHigh) { // was high
     digitalWrite(stepPinY, LOW);
     stepHigh = false; // LOW now
   } else { // was low
     digitalWrite(stepPinY, HIGH);
     stepHigh = true; // HIGH now
   }
 }
}

I was afraid of that. The larger Arduino Uno program I'm using calls function1 if a new string is received (from ESP32 running the Dashboard's GUI). Then it parses out each event to run function2 (using a switch statement), which calls sub-functions, "function3". Then each function3 has a unique equation, and each calls function4 (using that equation). From there, that function calls function5. That last function, five levels deep, includes this:

 digitalWrite(stepPinX, HIGH);
  delayMicroseconds(5);
  digitalWrite(stepPinX, LOW);
  delayMicroseconds(5);

It seems I need to radically restructure the 'big program'... right? I don't have the stepper motors with me now, but I will try to insert some serial prints to see if it works without a huge restructure. Thank you for your help.

So your problem is to use millisDelay you need to 'call' the lowest method from the top loop() code.
Not a problem.
just add another method
runStepper() that gets called every loop()
and change function5 to
stepDelay.start( )
That is the basis of multi-tasking. Call lots of lowest level code methods that do the work and have them just return if nothing to do. Use higher level methods to set/clear flags, timers etc to control the lowest level code runs.

void runStepper() {
  if (stepDelay.justFinished()) {
    if (stepHigh) { // was high
      digitalWrite(stepPinY, LOW);
      stepHigh = false; // LOW now
    } else { // was low
      digitalWrite(stepPinY, HIGH);
      stepHigh = true; // HIGH now
    }
  }
}
void function5() {
   stepDelay.start(... );  // runStepper will not start doing something
   ..
   stepDelay.stop();  // runStepper will just return without doing any thing now
}

void loop() {
   // call this every loop to do something if it needs to
   runStepper();  // does nothing if stepDelay not running
}

If you are doing just one step then have the runStepper() call the stepDelay.stop() itself after the one step is complete. (a bit of logic needed here)

@drmpf

Arg... unit error on my part... told ya... really new... ok. My timings look to be really close to yours. Excellent thanks...

My overall loop went from 94 to 140 when I added the read buffer so in my head I thought wow that’s a big delay increase... sorry for the confusion.

As for q2... I’ll have a look thanks...

I appreciate your fast responses...

Dragynn

drmpf:
@dragynn
Question 1, let me look into that. SafeStringReader has alot of extra code to handle input buffer overflows, skip to next delimiter and timeout, but I would not have expected it to take an extra 11mS. but...

Question 2.
Check out the GPS parsing example which splits via ",*" and change the

char delims[] = ",*"; // fields delimited by , or *

to

char delims[] = "-"; // fields delimited by -

OK, Question 2 follow up... I have it almost figured out but I am getting some weird results.
What is supposed to happen:
Input a line into the serial monitor like ArdString-21-21-20-27-1-3-150-1 and it will return PiStr-21-21-20-27-1-3-150-1. Change any of the numbers in the original string and it will return the numbers preceded by PiStr. if all numbers are the same nothing is returned.

#include <millisDelay.h>
#include <SafeString.h>
#include <BufferedOutput.h>
#include <loopTimer.h>
#include <SafeStringReader.h>

millisDelay printDelay;

createBufferedOutput(output, 66, DROP_UNTIL_EMPTY); // modes are DROP_UNTIL_EMPTY, DROP_IF_FULL or BLOCK_IF_FULL
// set up SafeString environment
// args are (ReaderInstanceName, expectedMaxCmdLength, delimiters)
createSafeStringReader(sfReader, 50, " ,\r\n");
createSafeString(msgStr, 30);
createSafeString(field, 10); // for the field strings. Should have capacity > largest field length
createSafeString(PiString, 50, " ,\r\n");
createSafeString(ArdString, 50, " ,\r\n");
createSafeString(PiStringOld, 50, " ,\r\n");

int8_t print2serial = 0;           //set to zero to diable serial printing of debug lines ,


const char delimiters[] = "-"; // just dash for delimiter, could also use ",;" if comma or semi-colon seperated fields
int nextIdx = 0; // index into inputLine
size_t fieldNumber = 0;
bool returnEmptyFields = true; // ,, returns an empty field

int8_t Drivers_tempC_var = 0;   //Drivers Area Temp
int8_t Passenger_tempC_var = 0; //Passenger Area Temp
int8_t CabAve_var = 20;         //Current Cabin Average
int8_t CabinSet_var = 27;       //Desired Temperature
int8_t SystemState_var = 1;     //System State Selection (Off/On/Auto) Value 1-3
int8_t DamperSel_var = 3;       //Damper Selection (Cabin/Defrost/Both)Value 1-3
int FanActual_var = 0;     //Fan Speed actual
int8_t Units_var = 0;              // Units of measure for system 0 = Celcius, 1 = Fereinheit

int8_t flag = 0;




void setup() {
  // put your setup code here, to run once:
  // SafeString::setOutput(Serial);
  output.connect(Serial);  // <<<<< connect the buffered output to Serial

  // sfReader.connect(Serial); // where SafeStringReader will read from

  sfReader.connect(output); // where SafeStringReader will read from
  sfReader.echoOn(); // echo goes out via bufferedOut
  sfReader.setTimeout(100); // set 100mS == 0.1sec non-blocking timeout

  Serial.begin(115200);


}

void loop() {
  // put your main code here, to run repeatedly:
  output.nextByteOut();
  ListenToPI();
}


void ListenToPI() {
  if (sfReader.read()) {
    if (sfReader.startsWith("ArdString-")) {
      DecodeArdString();
    }

  } // else no delimited command yet

}

void DecodeArdString() {

  flag = 0;
  while (nextIdx >= 0) { // still finding tokens
    nextIdx = sfReader.stoken(field, nextIdx, delimiters, returnEmptyFields); // steps over previous delimiter // returns -1 if none found
    field.debug(F("   sfReader.stoken => "));
    fieldNumber++;
    float f;
    if (field.toFloat(f)) {
      if ( print2serial == 1 ) { //print first type of debug line
        output.print(F("Field ")); output.print(fieldNumber); output.print(F(" : ")); output.println(f);
      }


      if ( fieldNumber == 2) {
        if (Drivers_tempC_var == f) {

        } else {
          Drivers_tempC_var = f;
          flag = 1;
        }

      }
      //String  Pi2Str = String(Passenger_tempC_var);
      if ( fieldNumber == 3) {
        if (Passenger_tempC_var == f) {

        } else {
          Passenger_tempC_var = f;
          flag = 1;
        }
      }
      //String  Pi3Str = String(CabAve_var);
      if ( fieldNumber == 4) {
        if (CabAve_var == f) {

        } else {
          CabAve_var = f;
          flag = 1;
        }
      }
      //String  Pi4Str = String(CabinSet_var);
      if ( fieldNumber == 5) {
        if (CabinSet_var == f) {

        } else {
          CabinSet_var = f;
          flag = 1;
        }
      }
      //String  Pi5Str = String(SystemState_var);
      if ( fieldNumber == 6) {
        if (SystemState_var == f) {

        } else {
          SystemState_var = f;
          flag = 1;
        }
      }
      //String  Pi6Str = String(DamperSel_var);
      if ( fieldNumber == 7) {
        if (DamperSel_var == f) {

        } else {
          DamperSel_var = f;
          flag = 1;
        }
      }
      //String  Pi7Str = String(FanActual_var);
      if ( fieldNumber == 8) {
        if (FanActual_var == f) {

        } else {
          FanActual_var = f;
          flag = 1;
        }
      }
      //String  Pi8Str = String(Units_var);
      if ( fieldNumber == 9) {
        if (Units_var == f) {

        } else {
          Units_var = f;
          flag = 1;
        }
      }

    } else {
     SafeString::Output.print(F("TR-   ,")); SafeString::Output.print(field); SafeString::Output.println(F(", is not a number"));
    }
  }
  nextIdx = 0;
  fieldNumber = 0;
  if (flag == 1) {
    // flag = 0;
    TalkToPI();
  }
}

void TalkToPI() {

  PiString.concat("PiStr-").concat(Drivers_tempC_var).concat("-").concat(Passenger_tempC_var).concat("-").concat(CabAve_var).concat("-").concat(CabinSet_var).concat("-").concat(SystemState_var).concat("-").concat(DamperSel_var).concat("-").concat(FanActual_var).concat("-").concat(Units_var).concat("\n");
  output.println(PiString);
}

The results:

ArdString-22-21-20-27-1-3-150-1
10:33:15.736 -> ,
10:33:15.736 -> PiStr-22-21-20-27-1-3-150-1
10:33:15.736 ->
10:33:19.983 -> ArdString-21-21-20-27-1-3-150-1
10:33:19.983 -> ,
10:33:19.983 -> PiStr-22-21-20-27-1-3-150-1
10:33:19.983 -> PiStr-21-21-20-27-
10:34:06.308 -> ArdString-21-21-20-27-1-3-150-1
10:34:12.947 -> ArdString-21-21-20-27-1-3-150-0
10:34:12.947 -> ,
10:34:12.947 -> PiStr-22-21-20-27-1-3-150-1
10:34:12.947 -> PiStr-21-21-20-27-

I don't know where the "," in the second and fifth (etc) line is coming from... it seems to be interrupting my PiStr which then never recovers. I also get an extra PiStr before my modified PiStr (so I am assuming it is in the buffer already?)

set print2serial = 1 and I get:

10:37:57.111 -> ArdString-21-21-20-27-1-3-150-1
10:37:57.111 -> Field 2 : 21.00
10:37:57.111 -> Field 3 : 21.00
10:37:57.111 -> Field 4 : 20.00
10:37:57.111 -> Field 5 : 27.00
10:37:57.111 -> Field 6 : 1.00
10:37:57.111 -> Field 7 : 3.00
10:37:57.111 -> Field 8 : 150.00
10:37:57.111 -> Field 9 : 1.00
10:37:57.111 -> ,
10:37:57.111 -> PiStr-21-21-20-27-1-3-150-1
10:37:57.111 ->
10:38:05.319 -> ArdString-22-21-20-27-1-3-150-1
10:38:05.319 -> Field 2 : 22.00
10:38:05.319 -> Field 3 : 21.00
10:38:05.319 -> Field 4 : 20.00
10:38:05.319 -> Field 5 : 27.00
10:38:05.319 -> Field 6 : 1.00
10:38:05.319 -> Field 7 : 3.00
10:38:05.319 -> Field 8 : 150.00
10:38:05.319 -> Field 9 : 1.00
10:38:05.319 -> ,
10:38:05.319 -> PiStr-21-21-20-27-1-3-150-1
10:38:05.319 -> PiStr-22-21-20-27-

So the changed line is being received and parsed but the previous PiStr is returned first.

I'm stumped,

Thanks

Dragynn

I've had a look and simplified the variable assign "if" statements to

//String Pi8Str = String(Units_var);
if ( fieldNumber == 9) {
if (Units_var != f) {
Units_var = f;
flag = 1;

I had a cumbersome "else" in there..

shouldn't affect the code... just makes it a bit cleaner.

thanks

Hi Try

createSafeString(PiString, 50);  // << start with empty SafeString
createSafeString(ArdString, 50);
createSafeString(PiStringOld, 50);

your previous code was initilaizing the SafeStrings with the string " ,\r\n"

For the SafeStringReader that text at the end is the delimiters which in your case could just be '\n' eg

createSafeStringReader(sfReader, 50, "\n");

Here add a trim() to remove any leading /trailing whitespace

  if (sfReader.read()) {
    sfReader.trim(); // clear up any leading trailing white space \r etc
    if (sfReader.startsWith("ArdString-")) {
      DecodeArdString();
    }

Here two points.
concat( ) adds to existing text so use

PiString = "PiStr-";  // << clears previous PiString and sets to PiStr-
PiString.concat(Drivers_tempC_var). ..   ,concat("\n"); // now concat to the current text i.e. to "PiStr-"
// finally
ouput.print(PiString);  // NOT println as that will add \r\n to the \n you already have above

Second you don't even need a SafeString. you can just print to the output directly

void TalkToPI() {
  output.print(F("PiStr-"));
  output.print(Drivers_tempC_var);
  output.print("-")
  // etc
  output.print('\n');  //  if you use println() you will get \r\n added
}

Excellent thanks!

I just figured out the

concat( ) adds to existing text...

Funny how computers do exactly what you tell (code) them to :o

I'll implement the changes, thanks...

Question regarding

Second you don't even need a SafeString. you can just print to the output directly...

I had implemented the SafeString to avoid loop delays.
I think what you are saying is because I have already implemented

createBufferedOutput(output, 66, DROP_UNTIL_EMPTY); // modes are DROP_UNTIL_EMPTY, DROP_IF_FULL or BLOCK_IF_FULL

it is already removing write delays??

I appreciate all your help!

This will end up being serial communication between a RasberryPi and The Mega...

The mega is doing all the "heat control" stuff and the Pi is the GUI via Python and QT designer...

I'd be happy to post the full combo but it's pretty big and the code I posted is the only serial communications

it is already removing write delays??

Yes, but there is no delay difference between
output.print("abc");
output.print("efg");
and
output.print("abcefg")
so collecting all the text into a single SafeString does not affect the delay
BUT..
If your output buffer fills up having a single SafeString will let you use length() to clearspace( ) in the output buffer for the whole output string. (dropping debug msgs) and the then protect() the output.

Thanks Much...

My buttons on the Pi side are now responding in a more consistent manner... looks like this particular problem is solved for me... again thanks !!!

Hi Mathew,

I would like to use SafeStrings for a ESP32 OTA-webserver.
I coded

createSafeString(HTML_Headline_SS,256); 

  HTML_Headline_SS = "Hi! ESP32";
  HTML_Headline_SS += __FILE__;
  HTML_Headline_SS += " compiled ";
  HTML_Headline_SS += __DATE__;
  HTML_Headline_SS += " ";
  HTML_Headline_SS += __TIME__;  

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", HTML_Headline_SS);
  });

and get an error-message

OTA-Bare-Minimum-Sketch-001:88:54: error: no matching function for call to 'AsyncWebServerRequest::send(int, const char [11], SafeString&)'
     request->send(200, "text/plain", HTML_Headline_SS);

What do I have to change to make it work? I guess the send-function expects an array of char.
Does this mean I have to copy the content of the SafeString into an array of char?
That would counter-act on the security of SafeStrings.
Passing a String to a function that expects an array of char is the most common thing about using strings.

So there should be an example-code that demonstrates that.
best regards Stefan

Usually you would try something like

 request->send(200, "text/plain", HTML_Headline_SS.c_str());

and if you need the length use

HTML_Headline_SS.length()

However I could not find an AsyncWebServerRequest.send( ) method that accepts a char*
So looks like you need to use

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", String(HTML_Headline_SS.c_str()));
  });

The temporary String(HTML_Headline_SS.c_str()) memory will be recovered completely once the block exits.
No ideal but..

Hi Mathew,

thank you very much for your answer.

  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", HTML_Headline_SS.c_str());
  });

works fine.
best regards Stefan