Blink light and stop by sending command to Serial

I have Arduino code that reads commands from the serial and changes colors of LED light based on a command. however, I want to have it change from staying one color to start blinking, and later via sending a different command to the Serial it should change color and stop blinking.
I believe to make it blink I need to throw it into a loop, but then I have difficulty how to take it out of the loop it should check if new command came in.

Below is my code which does not have a blink option.
Please take a look and help me out.
Thanks.

define Red 10
  #define Green 11 
  #define Blue 9
  
  String cmnd;
  char rcvdChar;
   
void setup() {
  // put your setup code here, to run once:
  pinMode(Red, OUTPUT);
  pinMode(Green, OUTPUT);
  pinMode(Blue, OUTPUT);
  Serial.begin(115200); 
}

void  readFromSerial(){
  while (Serial.available() > 0){
    rcvdChar = Serial.read();
    cmnd.concat(rcvdChar);
    delay(10);  //Check if need 
  }
}

void setColor(){
  if (cmnd.equals("red")== true){
    digitalWrite(Red, HIGH);
    digitalWrite(Green, LOW);
    digitalWrite(Blue, LOW);    
  }
  if (cmnd.equals("green")== true){
    digitalWrite(Red, LOW);
    digitalWrite(Green, HIGH);
    digitalWrite(Blue, LOW);    
  }
   if (cmnd.equals("off")== true){
    digitalWrite(Red, LOW);
    digitalWrite(Green, LOW);
    digitalWrite(Blue, LOW);    
  }

}

  
void loop() {
  // put your main code here, to run repeatedly:
  readFromSerial();
  setColor();

  cmnd = ""; 
}

This tutorial will help:

BlinkWithoutDelay

if you reset cmnd (cmnd = "";) immediately after setColor(), i doubt it will ever contain a complete string. but it does need to be reset but only if a valid word, and what about errors.

suggest Serial.readBytesUntil() and reading a complete line terminated with a linefeed.

You want the microcontroller do what most microcontroller-applications do.

Running multiple tasks so fast sequentially that the user-experience is the tasks are done in parallel.

This requires quit some learning that goes beyond a simple blink or receive some bytes.

Doing multiple things in sequential requieres on big loop where each task takes over "control" for a very short time does a single step and then hands over "control" to the next task.

Well it is not really taking "control" it is a jump in / jump out in each task.

This is done by calling each function inside the main-loop which is

void loop() {
}

itself

one task is checking if a new command is in the receive-buffer
with a very important difference:
the looping is done by void-loop and not inside your "receive-code"

one task does the blink etc.

non-blocking timing is done based on function millis()

So one part is learing what is a function

One part is learn to "loop" with repeated calling of functions

One part is learning to use non-blocking timing based on function millis()

Take a look into this tutorial:

Arduino Programming Course

It is easy to understand and has a good mixture between explaining important concepts and example-codes to get you going. So give it a try and report your opinion about this tutorial.

best regards Stefan

having loop() sequentially invoke multiple sub-functions would be more accurate. in this case, one sub-function monitors the serial interface while a 2nd sub-function flashes an LED

the term "task" is misleading because there is no operating systems doing context switching between tasks that independent of one another such as on an esp32. operating systems also include mechanisms for tasks to communicate with one another

of course, windows and linux are multi-tasking operating systems

<In an everyday meaning "task" could be used too.
But

is a good description which distinguishes from the informatic term "task" but the everyday analogon "something is functioning" is somehow a bit misleading too.

Maybe something like a sub-unit of code which does one thing and is build from two to n basic commands

This sub-unit should ideally do just one thing like:

  • checking the serial receivebuffer if a byte is there take it out of the buffer and add it to a variable

  • making an LED blink etc.

best regards Stefan

there are already well defined terms for these things in computer science. there's no need to come up with new ones or mis-use existing ones

There are a couple of things to consider here.

You need a better solution to handle Serial input... you should be checking for the end of input (such as a CR character), and you can't assume that the input can keep up with the Arduino. So you might get the the end of the serial buffer and not have read everything yet. You need to handle this, and allow the program to continue... and check the Serial buffer again for the rest of the message on the next loop.

You need to hold the previous command received while you are waiting for the next one to arrive.

To enable blinking use something like millis(s) to count the time to switch on/off... rather than blocking with something like delay(1000). If you have a variable that holds this information you can use when you decide which lights to turn on/off.

Here's an example...

#define Red   10
#define Green 11 
#define Blue  9
  
String command = "off";
char newCommand[10];
int idx = 0;
char rcvdChar;

float currTime, prevTime = 0;  
boolean blink = false;
   
void setup() 
{
  pinMode(Red,   OUTPUT);
  pinMode(Green, OUTPUT);
  pinMode(Blue,  OUTPUT);
  
  Serial.begin(9600); 

  prevTime = millis();
}

void readFromSerial()
{
  while (Serial.available() > 0)
  {
    char rcvdChar = Serial.read();

    if (rcvdChar == 0x0D) // End of input
    {
      newCommand[idx] = 0x00;
      
      command = newCommand;

      Serial.print("Command received: ");
      Serial.println(command);
      
      idx = 0;
    }
    else
    {
      newCommand[idx] = rcvdChar;
      idx++;
    }      
  }
}

void setLights()
{
  if (command == "off")
  {
    digitalWrite(Red,   LOW);
    digitalWrite(Green, LOW);
    digitalWrite(Blue,  LOW); 
    return;
  }
  
  if (command == "red")
  {
    digitalWrite(Red,   blink);
    digitalWrite(Green, LOW);
    digitalWrite(Blue,  LOW);  
    return;  
  }
  
  if (command == "green")
  {
    digitalWrite(Red,   LOW);
    digitalWrite(Green, blink);
    digitalWrite(Blue,  LOW);  
    return;  
  }

  if (command == "blue")
  {
    digitalWrite(Red,   LOW);
    digitalWrite(Green, LOW);
    digitalWrite(Blue,  blink);    
    return;
  }
}

void setBlink(int delay)
{
  currTime = millis();
  
  if (currTime > prevTime + delay)
  {
    prevTime = currTime;  
    
    blink = !blink;

    Serial.print("Blinking: ");
    Serial.println(blink);
  }   
}


  
void loop() 
{
  readFromSerial();

  setBlink(1000);  

  setLights();
}

There are a few things I want to comment on your code-example:

there are almost no explaining comments.

The code has no boundary-checking of the array newCommand.

This means if a too long string (more than 9 bytes) is send over the serial interface the wrong places in RAM get overwritten.

You are using a global defined variable of type String.
This could lead to eat up all RAM-memory over time and then crash the program

I have never seen befor using variable-type float for millis(). Does this reliably work over a long time?

Here is a version that uses the library SafeString which can be downloaded with the library-manager of the Arduino-IDE

#include <SafeString.h>

const byte maxRcvdBytes = 10;

// personal naming-convention suffix _SS indicates a S)afe-S)tring
createSafeString(newCommand_SS, (maxRcvdBytes) ); //+1 for the terminating zero 
createSafeString(Command_SS,    (maxRcvdBytes) );

const byte Red   = 10;
const byte Green = 11; 
const byte Blue  =  9;

const char endChar = 0x0D; // carriage return is hex-value 0D and is used as the terminating byte

int idx = 0;
char rcvdChar;

unsigned long currTime = 0; 
unsigned long prevTime = 0;
  
boolean blink = false;
   
void setup() {
  pinMode(Red,   OUTPUT);
  pinMode(Green, OUTPUT);
  pinMode(Blue,  OUTPUT);

  Command_SS = "off";
  newCommand_SS = "";
  Serial.begin(9600); 

  prevTime = millis();
}


void readFromSerial() {
  while (Serial.available() > 0)  {
    char rcvdChar = Serial.read();
    // for debuging purposes uncomment these lines 
    //to see each received character in the serial monitor
    /*
    Serial.print("reveived #");
    Serial.print(rcvdChar);
    Serial.println("#");
    */
    
    if ( (rcvdChar == endChar) ) { // End of input or max Command_SS-length reached
      
      Command_SS = newCommand_SS;
      newCommand_SS = "";

      Serial.print("Command_SS received: ");
      Serial.println(Command_SS);
      
      idx = 0;
    }
    else {
      newCommand_SS += rcvdChar; // add new received char to SafeString
      idx++;
    }      
  }
}

void setLights() {
  if (Command_SS == "off")
  {
    digitalWrite(Red,   LOW);
    digitalWrite(Green, LOW);
    digitalWrite(Blue,  LOW); 
    return;
  }
  
  if (Command_SS == "red")
  {
    digitalWrite(Red,   blink);
    digitalWrite(Green, LOW);
    digitalWrite(Blue,  LOW);  
    return;  
  }
  
  if (Command_SS == "green")
  {
    digitalWrite(Red,   LOW);
    digitalWrite(Green, blink);
    digitalWrite(Blue,  LOW);  
    return;  
  }

  if (Command_SS == "blue")
  {
    digitalWrite(Red,   LOW);
    digitalWrite(Green, LOW);
    digitalWrite(Blue,  blink);    
    return;
  }
}

void setBlink(int delay) {
  currTime = millis();
  
  if (currTime - prevTime >= delay)
  {
    prevTime = currTime;  

    // the attention-mark is the not-operator !true = false   !false = true
    blink = !blink; // invert the status of variable blink

    Serial.print("Blinking LED ");
    Serial.print(Command_SS);
    Serial.print(" ");
    Serial.println(blink);    
  }   
}


  
void loop() {
  readFromSerial();

  setBlink(1000);  

  setLights();
}

best regards Stefan

again, see readBytesUntil()

Your point has nothing to do with the OP.

True (although unlikely given the command set being used), it's a simple example in response to the OP. Your point has nothing to do with the OP.

Very unlikely is this case. Your point has nothing to do with the OP (where a String is also used)

Yep... that's a typo... but again your post has nothing to do with the OP.

So thanks for the critique... but how about looking at your own comments for perfection first: