($100 paid request) Serial/SafeString/Digital Read/Write bug is ruining my life

! added loopTimer and changed output to DROP_IF_FULL which displays ~~ on output buffer overflow.
Still got missing input but no ~~ in output so output buffer not overflowing

10:34:16.856 -> loop uS Latency
10:34:16.856 -> 5sec max:4484 avg:2455
10:34:16.856 -> sofar max:4484 avg:2455 max - prt:2044

latency 2.5ms to 4.4ms
115200 baud => ~87us per byte so ~28bytes arrive per loop
a cmd is ~8bytes => 0.7ms per cmd so ~3.5msgs arrive every loop but only one is processed.

There are at least three solutions
i) change to an ESP32 which processes much faster
ii) reduce the Serial baud rate to say 38400 or 19200. You can use SafeStringStream.RxBufferOverflow() when injecting data to see how many bytes were dropped.
iii) add BufferedInput to cope with bursts of commands.

This code adds the bufferedInput with a 512byte buffer the will hold ~64cmds in a burst
Try that and see how it goes or try one of the other solutions.

// https://forum.arduino.cc/t/100-paid-request-serial-safestring-digital-read-write-bug-is-ruining-my-life/903396
#include "SafeStringReader.h"
#include "BufferedOutput.h"
#include "SafeStringStream.h"
#include "BufferedInput.h"

#include "loopTimer.h"

// serial messages / keys and pin definitions
// air valves
#define CANOPY_IN_AV "cainv"
#define CANOPY_IN_PIN 44 // NC
#define CANOPY_EX_AV "caexv"
#define CANOPY_EX_PIN 47 // OC
#define ROOT_IN_AV "rtinv"
#define ROOT_IN_PIN 43 // NR
#define ROOT_EX_AV "rtexv"
#define ROOT_EX_PIN 46 // OR
#define AMBIENT_IN_AV "aminv"
#define AMBIENT_IN_PIN 45 // NA
#define AMBIENT_EX_AV "amexv"
#define AMBIENT_EX_PIN 48 // OA
// water solenoids
#define PASS_RADIATOR_SOL "pards" // R1
#define PASS_RAD_PIN 39
#define ROOT_RADIATOR_SOL "rtrds" // R2
#define ROOT_RAD_PIN 40
#define CANOPY_SPRAY_SOL "casps" // IC
#define CANOPY_SPRAY_PIN 41
#define CORE_IN_SOL "coins" // IR
#define CORE_IN_PIN 38
#define RETURN_IN_SOL "reins" // RE
#define RETURN_IN_PIN 37
#define ROOT_DRAIN_IN_SOL "rtdrins" // RZ
#define ROOT_DRAIN_IN_PIN 36
#define MIST_1_SOL "mi1s" // M1
#define MIST_1_PIN 35
#define MIST_2_SOL "mi2s" // M2
#define MIST_2_PIN 34
#define DRAIN_SOL "drains" // DR
#define DRAIN_PIN 33
// water pump
#define PUMP "pump"
#define PUMP_PIN 42
// lock
#define LOCK "lock"
#define LOCK_PIN 27
#define EMPTY 26
bool locked = false;
// fan
#define FAN "fan"
#define IN1 5
#define IN2 4
#define PWM 6
// SENSORS
#define HUMIDITY "humid"
#define IN_TEMP "intemp"
#define ROOT_TEMP "rttemp"
#define EX_TEMP "extemp"
#define EX_TEMP_PIN A6
#define PRESSURE "press"
#define PRESSURE_PIN A14
const int pressure_offset = 0.469;
#define RETURN_SOLUTION "resol"
#define RETURN_SOLUTION_PIN 29
#define DOOR_CLOSED "door"
#define LEFT_DOOR_CLOSE_PIN 16
#define RIGHT_DOOR_CLOSE_PIN 15
#define TOUCH "touch"

millisDelay printMissedBytes;
unsigned long PRINT_MISSED_BYTES_MS = 5000;

// SafeStringStream
cSF(sfTestData, 220); // the test data SafeString, will be filled in setup()
cSF(rxBuf, 64); // the extra rxbuffer for the SafeStringStream to mimic the Uno hardware serial rx buffer
SafeStringStream sfStream(sfTestData, rxBuf); // set the SafeString to be read from and the SafeString providing the rx buffer

// create an safeReader instance of SafeStringReader class
// delimited by comma, CarrageReturn or NewLine
// the createSafeStringReader( ) macro creates both the SafeStringReader (safeReader) and the necessary SafeString that holds input chars until a delimiter is found
// args are (ReaderInstanceName, expectedMaxCmdLength, delimiters)
createSafeStringReader(safeReader, 10, ",\r\n"); // max cmd seems to be <= 8 including \n
// Buffered output instance
createBufferedOutput(output, 63, DROP_IF_FULL); //BLOCK_IF_FULL); // will see ~~ in output if this buffer overflows and would block
createBufferedInput(bufferedInput, 512); // handle bursts of input upto (512/8 =) 64cmds

void fillTestData() {
  SafeString::Output.println(">> fillTestData");
  sfTestData = F(
                 "mi1s:1\n"
                 "cainv:1\n"
                 "caexv:1\n"
                 "pump:1\n"
                 "rtinv:1\n"
                 "rtexv:1\n"
                 "rtexv:0\n"
                 "pump:0\n"
                 "mi1s:0\n"
                 "rtinv:0\n"
                 "pump:0\n"
                 "caexv:0\n"
                 "cainv:0\n"
                 "p\n"
                 "mi1s:1\n"
                 "cainv:1\n"
                 "caexv:1\n"
                 "pump:1\n"
                 "rtinv:1\n"
                 "rtexv:1\n"
                 "rtexv:0\n"
                 "pump:0\n"
                 "mi1s:0\n"
                 "rtinv:0\n"
                 "pump:0\n"
                 "caexv:0\n"
                 "cainv:0\n"
                 "p\n"
               ); // initialized the test data
}

bool running = true;
float pressure = 300;
void setup() {
  Serial.begin(115200);
  for (int i = 10; i > 0; i--) {
    Serial.print(i); Serial.print(' '); delay(500);
  }
  Serial.println();
  output.connect(Serial); // Connect output buffer to serial
  // enable error messages and SafeString.debug() output to be sent to Serial
  // SafeString::setOutput(Serial);  // just use SafeString::errorDetected() instead to start with
  bufferedInput.connect(sfStream);
  safeReader.connect(bufferedInput); // read the test data from the SafeStringStream
  safeReader.echoOn(); // echos the read data back to be re-read again
  fillTestData();
  Serial.print("Test Data size:"); Serial.println(sfTestData.length());
  Serial.println("Test Data:-");
  Serial.println(sfTestData);
  Serial.println("----------");
  sfStream.begin(sfTestData, 115200); // start releasing sfTestData at 38400 works 57600 does not nor does 115200);

  pinMode(CANOPY_IN_PIN, OUTPUT);
  pinMode(CANOPY_EX_PIN, OUTPUT);
  pinMode(ROOT_IN_PIN, OUTPUT);
  pinMode(ROOT_EX_PIN, OUTPUT);
  pinMode(AMBIENT_IN_PIN, OUTPUT);
  pinMode(AMBIENT_EX_PIN, OUTPUT);
  pinMode(PASS_RAD_PIN, OUTPUT);
  pinMode(ROOT_RAD_PIN, OUTPUT);
  pinMode(CANOPY_SPRAY_PIN, OUTPUT);
  pinMode(CORE_IN_PIN, OUTPUT);
  pinMode(RETURN_IN_PIN, OUTPUT);
  pinMode(ROOT_DRAIN_IN_PIN, OUTPUT);
  pinMode(MIST_1_PIN, OUTPUT);
  pinMode(MIST_2_PIN, OUTPUT);
  pinMode(DRAIN_PIN, OUTPUT);
  pinMode(PUMP_PIN, OUTPUT);
  pinMode(LOCK_PIN, OUTPUT);
  pinMode(EMPTY, OUTPUT);
  allRelaysOff();

  printMissedBytes.start(PRINT_MISSED_BYTES_MS);
}


void loop() {
  bufferedInput.nextByteIn(); // get next bytes available
  output.nextByteOut(); // Send next byte from serial buffer
  processSerial();
  if (SafeString::errorDetected()) {
    output.println("SafeString Error Detected");
  }
}

void processSerial() {
  if (printMissedBytes.justFinished()) {
    printMissedBytes.restart();
    output.print(">>"); output.println(sfStream.RxBufferOverflow());
  }
  loopTimer.check(output);
  if (safeReader.read()) {
    //  SafeString::Output.print(">");
    //  SafeString::Output.println(safeReader);
    size_t colonIdx = 0;
    colonIdx = safeReader.indexOf(':');
    if (colonIdx < safeReader.length()) { // Command needs data parsing
      // safeString parsing variables
      createSafeString(command, 9);
      createSafeString(data, 11);
      long dataLong;
      // Parse
      safeReader.substring(command, 0, colonIdx); // extract command before colon
      safeReader.substring(data, colonIdx + 1); // extract data after colon
      if (data.toLong(dataLong)) { // type long parse (fails with decimal)
        // SafeString::Output.print(F("Long data detected: "));
        // SafeString::Output.println(dataLong);
        // output.println(safeReader);
        if (command == CANOPY_IN_AV) { // canopy in air valve
          setRelay(CANOPY_IN_PIN, dataLong, CANOPY_IN_AV);
        } else if (command == CANOPY_EX_AV) { // canopy exhaust air valve
          setRelay(CANOPY_EX_PIN, dataLong, CANOPY_EX_AV);
        } else if (command == ROOT_IN_AV) { // root in air valve
          setRelay(ROOT_IN_PIN, dataLong, ROOT_IN_AV);
        } else if (command == ROOT_EX_AV) { // root exhaust air valve
          setRelay(ROOT_EX_PIN, dataLong, ROOT_EX_AV);
        } else if (command == AMBIENT_IN_AV) { // ambient in air valve
          setRelay(AMBIENT_IN_PIN, dataLong, AMBIENT_IN_AV);
        } else if (command == AMBIENT_EX_AV) { // ambient ehaust air valve
          setRelay(AMBIENT_EX_PIN, dataLong, AMBIENT_EX_AV);
        } else if (command == PASS_RADIATOR_SOL) { // pass through radiator solenoid
          setRelay(PASS_RAD_PIN, dataLong, PASS_RADIATOR_SOL);
        } else if (command == ROOT_RADIATOR_SOL) { // root radiator solenoid
          setRelay(ROOT_RAD_PIN, dataLong, ROOT_RADIATOR_SOL);
        } else if (command == CANOPY_SPRAY_SOL) { // canopy spray solenoid
          setRelay(CANOPY_SPRAY_PIN, dataLong, CANOPY_SPRAY_SOL);
        } else if (command == CORE_IN_SOL) { // core intake solution solenod
          setRelay(CORE_IN_PIN, dataLong, CORE_IN_SOL);
        } else if (command == RETURN_IN_SOL) { // return intake solenoid
          setRelay(RETURN_IN_PIN, dataLong, RETURN_IN_SOL);
        } else if (command == ROOT_DRAIN_IN_SOL) { // root drain intake solenoid
          setRelay(ROOT_DRAIN_IN_PIN, dataLong, ROOT_DRAIN_IN_SOL);
        } else if (command == MIST_1_SOL) { // mist 1 solenoid
          setRelay(MIST_1_PIN, dataLong, MIST_1_SOL);
        } else if (command == MIST_2_SOL) { // mist 2 solenoid
          setRelay(MIST_2_PIN, dataLong, MIST_2_SOL);
        } else if (command == DRAIN_SOL) { // drain solenoid
          setRelay(DRAIN_PIN, dataLong, DRAIN_SOL);
        } else if (command == PUMP) { // water pump
          setRelay(PUMP_PIN, dataLong, PUMP);
        } else {
        }
      } else {
      }
    } else { // else no data command
      readPressure();
    }
  }
}

// Send outgoing message with colon
// could pass SafeString & here but seems an overkill
void transmitSerial(const char*key, const char*data) {
  output.print(key); output.print(F(":"));
  output.println(data);
}

// set relay true = on false = off
void setRelay(int pin, bool setting, const char*reply_key) {
  digitalWrite(pin, !setting);
  cSF(sfData, 10);
  sfData = setting; //!digitalRead(pin); // note this does not work on ESP32 use setting instead
  transmitSerial(reply_key, sfData.c_str()); //String(!digitalRead(pin)));
}

// all relays off
void allRelaysOff() {
  digitalWrite(CANOPY_IN_PIN, HIGH);
  digitalWrite(CANOPY_EX_PIN, HIGH);
  digitalWrite(ROOT_IN_PIN, HIGH);
  digitalWrite(ROOT_EX_PIN, HIGH);
  digitalWrite(AMBIENT_IN_PIN, HIGH);
  digitalWrite(AMBIENT_EX_PIN, HIGH);
  digitalWrite(PASS_RAD_PIN, HIGH);
  digitalWrite(ROOT_RAD_PIN, HIGH);
  digitalWrite(CANOPY_SPRAY_PIN, HIGH);
  digitalWrite(CORE_IN_PIN, HIGH);
  digitalWrite(RETURN_IN_PIN, HIGH);
  digitalWrite(ROOT_DRAIN_IN_PIN, HIGH);
  digitalWrite(MIST_1_PIN, HIGH);
  digitalWrite(MIST_2_PIN, HIGH);
  digitalWrite(DRAIN_PIN, HIGH);
  digitalWrite(PUMP_PIN, HIGH);
  digitalWrite(LOCK_PIN, HIGH);
  digitalWrite(EMPTY, HIGH);
}

// read pressure
void readPressure() {
  static float voltage, pressure;
  // Calculations
  voltage = analogRead(PRESSURE_PIN) * 5.0 / 1024.0; // Calculate the voltage
  pressure = (voltage - pressure_offset) * 400.0; // Calculate water pressure
  cSF(sfData, 10);
  sfData = pressure;
  transmitSerial(PRESSURE, sfData.c_str());
}