Shortening string within Arduino

I am writing a code that, among other things, will allow me to turn on and off up to 24 relays. I currently have it working, but feel there should be a much more efficient way to achieve my goals here.

Currently, the app sends a string to a HC-05, and then I use the following code:

    msg = Serial1.readString();
    if (msg == "relay1On"){
      digitalWrite(relay1, HIGH);}
    if (msg == "relay1Off"){
      digitalWrite(relay1, LOW);}

If I can guarantee that the only codes sent would be some form of "relay01On", "relay20Off" etc, how can I seperate it into two strings? if I am able to do this, I could cut 24 copies of that code down to a simple void:

void relayActivate(String string) {
   //some code here to seperate string into string1 and string 2
      if(string2 == "On")
      digitalWrite(string1, HIGH);
      if(string2 == "Off")
      digitalWrite(string1, LOW);
}

Would this work?

EDIT: No, digitalWrite() doens't accept Strings, must be uint8_t. For now, I'm just gonna do it the hard way I think.

How about sending just numbers ?

example:

101 turns ON relay 1
201 turns OFF relay 1

. . .
124 turns ON relay 24
224 turns OFF relay 24


A byte can be 0 to 256 so all you need is a byte.

While that could work, I am currently using 0-255 to control a potentiometer, so this would cause a conflict, as they are sent the same way.

Well you could send a byte that says here is a control, here is a analog etc.

example:

byte one = 123 (control), byte two = 224 turn OFF relay 24
byte one = 123 (control), byte two = 124 turn ON relay 24
byte one = 111 (analog), byte two (MSB) = 0xF3, byte three (LSB) = 0xFF
F = analog 15 = 0x3FF

after some research, I will change the incoming string to include a Separator (in this case, a comma) and then can use Serial.readStringUntil(',') to get the first string, then again to get the second string.

So if I send my strings as this "relay1,HIGH," and "relay1, LOW," I can then use this:

void relayActivate(String str1, String str2){
    digitalWrite(str1, str2);
}

And then in the code, I can do this:

if(Serial1.available()) {
   relayActivate(Serial1.readStringUntil(','), Serial1.readStringUntil(',');
}

I can probably just not use the void altogether, and have my loop look like this:

if(Serial1.available()){
   str1 = Serial1.readStringUntil(',');
   str2 = Serial1.readStringUntil(',');
  digitalWrite(str1, str2);
}

I avoid using STRINGs at all costs, noted to cause memory problems.

If this was me, I would come up with a character type exchange protocol.

Maybe send commands twice to prevent false relay operations.

Anyway, it’s bed time here, have fun !

:sleeping: :sleeping_bed:


Edit
digitalWrite(str1, str2); will not work, I’ll let Australia or England explain. :wink:

I figured out the hard way that digitalWrite doesn't take strings.
Hopefully someone else can help, but for now, I am going to do this the long way.

Yes, I know that I should come back to this and not use String. That will be the end goal, but for now, this will be functional.

String fns are your friend here


String receivedMsg;


// all String memory used in relayActivate is completely recovered on method return.
// no memory fragmentation here
bool relayActivate(String& string) {
  String msg = string; // note this makes a copy so original string can be used in error msg
  msg.toUpperCase(); // make all upper case
  if (msg.startsWith("RELAY")) {
    msg.replace("RELAY", "");
    int turnOn = LOW;
    if (msg.endsWith("ON")) {
      turnOn = HIGH;
      msg.replace("ON", "");
    } else if (msg.endsWith("OFF")) {
      turnOn = LOW;
      msg.replace("OFF", "");
    } else {
      Serial.print(string); Serial.println(" Error relay not On or Off.");
      return false;
    }
    // here have turnOn set and only number left
    int pinNo = msg.toInt();
    if (pinNo == 0) {
      Serial.print(string); Serial.println(" Error invalid pin.");
    } else {
      Serial.println(string);
      digitalWrite(pinNo, turnOn);
      return true;
    }
  } // else {
  Serial.print(string); Serial.println(" is an Invalid relay msg.");
  return false;
}

void setup() {
  Serial.begin(9600);
  for (int i = 10; i > 0; i--) {
    Serial.print(' '); Serial.print(i);
    delay(500);
  }
  Serial.println();

  receivedMsg.reserve(80);  // reserve space to prevent any memory fragmentation

  receivedMsg = "relay01on";
  relayActivate(receivedMsg);
  receivedMsg = "relay21off";
  relayActivate(receivedMsg);
  receivedMsg = "rela21off";
  relayActivate(receivedMsg);
  receivedMsg = "relayoff";
  relayActivate(receivedMsg);
  receivedMsg = "relay1out";
  relayActivate(receivedMsg);
  
}

void loop() {
}

Gives this output

relay01on
relay21off
rela21off is an Invalid relay msg.
relayoff Error invalid pin.
relayoff is an Invalid relay msg.
relay1out Error relay not On or Off.

Not necessary to avoid Strings, they are very safe on small memory AVR boards and using reserve( ) and String& avoids memory fragmentation.
See my tutorial on Taming Arduino Strings

But if you want to avoid Strings and still want to use the String type text functions like replace, substring, indexOf, endsWith etc then check out my SafeString library that uses fixed size char[ ..] underneath and protects against buffer overflows, null pointer, off by one indexing and missing '\0' terminators

1 Like

i use the following for debug/controling in almost all my code

a numeric value followed by a single letter command. there can be multiple "commands" on a single line. new "commands" can easily be added. human readable

// process single character commands from the PC
#define MAX_CHAR  10
char s [MAX_CHAR] = {};

int  analogPin = 0;

void
pcRead (void)
{

    static int  val = 0;

    if (Serial.available()) {
        int c = Serial.read ();

        switch (c)  {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            val = c - '0' + (10 * val);
            break;

        case 'A':
            analogPin = val;
            Serial.print   ("analogPin = ");
            Serial.println (val);
            val = 0;
            break;

        case 'D':
            debug ^= 1;
            break;

        case 'I':
            pinMode (val, INPUT);
            Serial.print   ("pinMode ");
            Serial.print   (val);
            Serial.println (" INPUT");
            val = 0;
            break;

        case 'O':
            pinMode (val, OUTPUT);
            Serial.print   ("pinMode ");
            Serial.print   (val);
            Serial.println (" OUTPUT");
            val = 0;
            break;

        case 'P':
            pinMode (val, INPUT_PULLUP);
            Serial.print   ("pinMode ");
            Serial.print   (val);
            Serial.println (" INPUT_PULLUP");
            val = 0;
            break;


        case 'a':
            Serial.print   ("analogRead: ");
            Serial.println (analogRead (val));
            val = 0;
            break;

        case 'c':
            digitalWrite (val, LOW);
            Serial.print   ("digitalWrite: LOW  ");
            Serial.println (val);
            val = 0;
            break;

        case 'p':
#if !defined(ARDUINO_ARCH_ESP32)
            analogWrite (analogPin, val);
            Serial.print   ("analogWrite: pin ");
            Serial.print   (analogPin);
            Serial.print   (", ");
            Serial.println (val);
            val = 0;
#endif
            break;

        case 'r':
            Serial.print   ("digitalRead: pin ");
            Serial.print   (val);
            Serial.print   (", ");
            Serial.println (digitalRead (val));
            val = 0;
            break;

        case 's':
            digitalWrite (val, HIGH);
            Serial.print   ("digitalWrite: HIGH ");
            Serial.println (val);
            val = 0;
            break;

        case 't':
            Serial.print   ("pinToggle ");
            Serial.println (val);
            digitalWrite (val, ! digitalRead (val));
            val = 0;
            break;

        case 'v':
            Serial.print ("\nversion: ");
            Serial.println (version);
            break;

        case '\n':          // ignore
            break;

        case '"':
            while ('\n' != Serial.read ())     // discard linefeed
                ;

            readString (s, MAX_CHAR-1);
            Serial.println (s);
            break;

        case '?':
            Serial.println ("\npcRead:\n");
            Serial.println ("    [0-9] append to #");
            Serial.println ("    A # - set analog pin #");
            Serial.println ("    D # - set debug to #");
            Serial.println ("    I # - set pin # to INPUT");
            Serial.println ("    O # - set pin # to OUTPUT");
            Serial.println ("    P # - set pin # to INPUT_PULLUP");
            Serial.println ("    a # - analogRead (pin #)");
            Serial.println ("    c # - digitalWrite (pin #, LOW)");
            Serial.println ("    p # -- analogWrite (analogPin, #)");
            Serial.println ("    r # - digitalRead (pin #)");
            Serial.println ("    s   - digitalWrite (pin #, HIGH)");
            Serial.println ("    t   -- toggle pin # output");
            Serial.println ("    v   - print version");
            Serial.println ("    \"   - read string");
            Serial.println ("    ?   - list of commands");
            break;

        default:
            Serial.print ("unknown char ");
            Serial.println (c,HEX);
            break;
        }
    }
}
1 Like

Thanks all for the advice, I think I prefer the way drmpf has laid it out, so I have marked that as the solution.

@drnpf: Have you done any timing comparisons using your library versus the standard string libraries that are part of the core?

Sorry no. In terms of statements executed, SafeString has more error checking but no malloc/reallocs