Help with Serial strings

Hi all,
i'm struggling to find the right commands to be used for my project that is a led light.
I have a burst of strings coming from the led controller over serial 232 that looks like the below:

a150(cr)
A150(cr)
a250(cr)
A250(cr)
u200(cr)
u300(cr)

my arduino should listen on the serial 232 and find the a150(cr) discarding all the others.
a150(cr) means the led should be off and can vary as below:
up to a159(cr) = led 10%, then from a1600 to a1699 = led from 11% to 99%, then a17100 = led 100%.

now, considering i can map later the value and make it as 0-255 for the pwm control, i'm stuck at the part for:

  1. Read the serial string
  2. Find the a150(cr) row and discard all the others
  3. Isolate the value after a1 that is the fixed part of the string so can be used as suffix.
  4. Be sure that any reading will stop at the carriage return otherwise i could have issue if the timeout expire later than i get the next string.

Any advice on which is the right commands to be used? I tried with Substring but i don't think is the right one.

Hope anybody can help

Your project will need to build test strings on-the-fly and test.

Here is a GPS sketch using no libraries. Note how the code assembles every character after LF into a buffer looking for "$GPRMC

/*
  GPS serial read and sentence detection
  Derrived from work by M. Ray Burnette on the PSoC code published on Hackster.io/RayBurne
  Compiler board: Nano
  Arduino 1.7.3 & 1..7.8 // 6/09/2015 & 1/17/2016
    Sketch uses 504 bytes (1%) of program storage space. Maximum is 30,720 bytes.
    Global variables use 16 bytes (0%) of dynamic memory, leaving 2,032 bytes for local variables. Maximum is 2,048 bytes
*/

#include <stdlib.h> // for function atoi()

// Global variables
uint8_t hour, minute, seconds;          //, year, month, day;
uint8_t day, month, year;

char    buffer[9];
char    nmea[120];

// double  atof(const char *str);       // elimited Limor's float implementation

boolean DayLightSavings;                // Pin 3.4
uint8_t GMToffset = 4;
uint8_t Flag = 1;
uint32_t  c = '\0';
int8_t  k   = 0 ;

boolean DSTime_Read(void) {
  return true;
}

void GLCD_WRITE( char *x ) {
}

void glcd_write(void) {
}

void glcd_clear(void) {
}

void glcd_tiny_draw_string(int x, int y, char* c) {
}

char UART_UartGetChar() {
  char i = '\0';
  return i;
}

void LED_Write( boolean PinState) {
}

boolean LED_Read(void) {
  return ~true;
}

void setup(void)
{
    DayLightSavings = DSTime_Read();        // Pin# Normally true (HIGH)

    if(! DayLightSavings) GMToffset += 1;
}

void loop(void)
    {
        if (Flag == 1)
        {
            GLCD_WRITE("Waiting on GPS") ;
            // Flag = 0;
        
        c = UART_UartGetChar();     // Get received character or null
        if (c)  
        {
            if(c == '$')            // $ start of NMEA sentences
            {
                for(k=0; k<5; k++)  // need 5 characters for sentence type
                {
                    LED_Write( ~LED_Read() );   // flicker LED for activity
                    do {
                        c = UART_UartGetChar();
                    }
                    while (! (c));
                    nmea[k] = c;    // G + P + R + M + C
                    // sprintf(buffer + k, "%c", c) ;  // only for debug
                }

                LED_Write( LOW );                   // LED off
                if (strstr(nmea, "GPRMC"))
                {
                    do {
                        do {
                            c = UART_UartGetChar();
                            LED_Write( ~LED_Read() ); // flicker LED
                        } while (!(c));
                        nmea[k] = c;
                        ++k;
                    } while ( !( c == '*' ) && k < 120) ;   // marker
                    LED_Write( LOW );                       // LED off
                    // Inspiration: Limor Fried's Arduino GPS lib
                    char *p = nmea;
                    p = strchr(p, ',') + 1;   // position after 1st comma
                    // float    timef  = atof(p);
                    // uint32_t  time  = timef;
                    uint32_t  time  = atoi(p);
                              hour  = time / 10000;
                            minute  = (time % 10000) / 100;
                           seconds  = (time % 100);
                    // output to GLCD
                    sprintf(buffer, "              %s","");  // clearbuffer
                    
                    // this corrects time to the West but not the date!!!
                    if( hour > GMToffset) {
                        hour = hour - GMToffset;
                    } else {
                        hour = (hour + 24) - GMToffset; }

                    // correct midnight to follow standard format
                    if (hour == 24) hour = 0;

                    if (hour < 10) {
                        if (DayLightSavings) {
                            sprintf(buffer, "EDT:  %d", hour);
                        } else {
                            sprintf(buffer, "EST:  %d", hour);
                        }
                    } else {
                        if (DayLightSavings) {
                            sprintf(buffer, "EDT: %d%d", hour);
                        } else {                       
                        sprintf(buffer, "EST: %d%d", hour); }
                    }
                    if (minute < 10) {
                        sprintf(buffer + 7, ":0%d", minute);
                    } else {
                        sprintf(buffer + 7, ":%d%d", minute); }
                    if (seconds < 10) {
                        sprintf(buffer + 10,":0%d%s", seconds);
                    } else {
                        sprintf(buffer + 10, ":%d%d%s", seconds); }
                    sprintf(buffer + 13, "%s", "\0");
                    if(Flag == 1)
                    {
                        Flag = 0;
                        glcd_clear() ;
                        delay(250) ;
                    }
                    // OUTPUT the TIME on GLCD
                    glcd_tiny_draw_string(0, 4, buffer);
                    glcd_write() ;
                    // Parse to integer date field
                    p = strchr(p, ',') +1;  // A/V?
                    p = strchr(p, ',') +1;  // lat
                    p = strchr(p, ',') +1;  // N/S?
                    p = strchr(p, ',') +1;  // lon
                    p = strchr(p, ',') +1;  // E/W?
                    p = strchr(p, ',') +1;  // speed
                    p = strchr(p, ',') +1;  // angle
                    p = strchr(p, ',') +1;  // move pass for date DDMMYY
                    // nmea date field looks like: 090914 (European)
                    uint32_t fulldate = atoi(p);
                    day     = (fulldate / 10000);
                    month   = (fulldate % 10000) / 100;
                    year    = (fulldate % 100);
                    sprintf(buffer, "              %s","");  // clearbuffer
                    if (day < 10) {
                        sprintf(buffer, "0%d", day);
                    } else {
                        sprintf(buffer, "%d%d", day); }
                    if (month < 10) {
                        sprintf(buffer + 2,"-0%d", month);
                    } else {
                        sprintf(buffer + 2,"-%d%d", month); }
                    sprintf(buffer + 5, "-20%d%d%s", year);
                    sprintf(buffer + 10,"%s",'\0');
                    // OUTPUT the DATE on GLCD
                    glcd_tiny_draw_string(0, 2, buffer);
                    glcd_write() ;
                }   // if (strstr(nmea, "GPRMC"))
            }       // if(c == '$')
        }           // if(c)
      }             // flag
    }               // loop()


/* [] END OF FILE */

Other useful code can be found in the sketch:

The QBF - "The Quick Brown Fox..." For Serial Diags - Community / Exhibition / Gallery - Arduino Forum

the easiest way if you want to use String is something like this

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

void loop() {
  if (Serial.available()) {
    String received = Serial.readStringUntil('\r'); // be sure it's CR that's sent and not LF
    received.trim();
    if (received.equals("a150")) {
      Serial.println("got a match");  // <=== this is where you would do your work
    } else {
      Serial.println("No cigar");
    }
  }
}

I don't understand what you mean. If you only filter for "a150\r" then you know the suffix after "a1" is always "50"

the better way to handle this would probably be in an async code structure not using the String class ➜ I would suggest to study Serial Input Basics to handle this

With serial input basics you could use
a variant that is only looking for a end-character (the carriage return) if "(cr)" has the meaning of carriage return

serial input basics shows how to receive serial data in a completely non-blocking way

if "a150(cr)" is the real only one starting with "a" you could use the "a" as the start-character
and use the "cr" as the end-character
whenever end-character "cr" is received without having received the start-character "a" the received bytes just get dropped.

Hi,
a150 is not the only one that start with “a”.
I have also “a250”.
Both of them end with (cr) then new line.
Both of them can vary as below:
from “a150” to “a159” , then from “a1610” to “a1699” then a17100.
I know it’s a strange way to control a led light but this is the master control and I can’t do anything with that.
At the end “a1” is the suffix. And I have to find a way to look for “a1” as prefix and “cr” as suffix.

Still unclear whichs strings you want to use and which you want to ignore

this would that you have simply TWO start-characters
look for "a" if an "a" os found then look out for "1" and only in case this sequence "a" followed by "1" is found
start storing the bytes until a "cr" is received as ending character.
in all other cases don't store the bytes

This is just a small modification to the serial input basics code-variant

receive with start and end-character

you should really read the serial input basics tutorial that is lined above

check post #3

const byte MAX_CHARS = 64;
char receivedChars[MAX_CHARS];
boolean newDataComplete = false;


void recvWithStartEndMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = 'a';
  char followUpMarker = '1';
  char endMarker = 0x13; // carriage return
  char rc;

  while (Serial.available() > 0 && newDataComplete == false) {
    rc = Serial.read();

    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= MAX_CHARS) {
          ndx = MAX_CHARS - 1;
        }
      }
      else {
        receivedChars[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        ndx = 0;
        newDataComplete = true;
      }
    }

    else if (rc == startMarker) { // startmarker received
      rc = Serial.read();         // fetch next byte
      if (rc == followUpMarker) {// check if next byte is valid
      recvInProgress = true;
    }
  }
}

// In the main-code call function recvWithStartEndMarkers() repeatedly

// check for 
// if(newDataComplete) {
  //take action with valid data

You can’t do that

You don’t know if there is something to read

Thanks for the help guys. Ok the variant you sent is clear that is looking for the one I need but, where the value in between is stored?
Example:
“a150” is equal to Led 0%.
“a151” is equal to Led 1%
…………
“a1610” is equal to Led 11%
means that I require the value after “a1” that will be mapped later to for the PWM use (0-255)

I though there was something more simple using String. Then convert it into INT

The price of using the advantage of beeing small (= using a cheap microcontroller) is a bit more complex coding compared to a windows PC or a RaspBerry Pi

using String on a microcontroller is a bad idea as String eats up al memory over time making your controller crash.

If you really want to have something similar to standard String use SafeString
almost the same comfort without the danger of program crashes

SafeString even has non-blocking receiving of strings over the serial interface.

You can install the SafeString-library with the library-manager
documentation is here
https://www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html

best regards Stefan

That’s an unfair generalization. You need to be careful on how you use it

Anyway seems like @delucaide is not interested in my post so I’ll leave @StefanL38 to provide guidance

J-M-L sorry, I’m interested in all solutions that could works. I’m not really confident with Arduino programming and I’m trying to learn it.

The example you suggested could works,
but, how can I extract the value I need?

pretty standad question give the duck a go

So if you want to test only the start by a1 you could change

Into

if (received.startsWith("a1")) {
      Serial.println("got a match");  // <=== this is where you would do your work
    } else {
      Serial.println("No cigar");
    }

Now you know that you got a1 as the first two chars so you need to transformthe substring starting at the third character toInt

I would suggest to look at the documentation for the String class to see if by any chance there would be a substring() and toInt() function you could use

It’s all in String() - Arduino Reference

Hello delucaide

Try to specify the software protocol more in detail.
Does the dataset received has a variable or fixed length?
Which letters and number and combination of those are use to transfer information.
How should this information be interpreted correctly?

Provide a data logging.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.