Search byte array from Serial for a match for byte array

Hi,

I am communicating with a 3D Printer running Marlin. I am trying to determine when a command like G28 is finished processing.

Marlin sends out data letting you know while it is "echo:busy: processing". I want to know when it is done "ok P15 B3".

The issue I am having is I am using memcmp to compare the received data on the serial port to my byte array holding the "ok P15 B3" data.

The "echo:busy: processing" is sent out as one character string, but when the "ok P15 B3" is sent is is received with 91 characters and the part I care about is last. Like such:

echo:busy: processing
Active Extruder: 0
X:190.00 Y:155.00 Z:10.00 E:0.00 Count X:15200 Y:12400 Z:4000
ok P15 B3

What would be the proper way to go about this as I normally do not do this type of coding.

        bool ok_message = false;

        do{
          uint8_t nBytes = comportReceive(&pData[0], MSGBUFFERSIZE);
          if (nBytes > 0){
              if(!memcmp(pData, okData, sizeof(okData))){
                Serial.println("TRUE");
                Serial.write(pData, nBytes);
                Serial.println("");
                ok_message = true;
              }else{
                Serial.println("FALSE");
                Serial.println(nBytes);
                Serial.write(pData, nBytes);
                ok_message = false;
              }
          }
          Serial.println("(okData != pData)");
          delay(1000);
        }while(!ok_message);
// ------------------------------------------------------------------------------------------------------
// comportReceive
// ------------------------------------------------------------------------------------------------------
// pMsg = Message buffer
// nLen = Message buffer length
//
int comportReceive(byte* pMsg, int nLen)
{
    // Declarations
    int nBytes = 0;
    
    // Read serial data
    while(Serial2.available() > 0)
    {
        pMsg[nBytes++] = (byte)Serial2.read();
        if(nBytes >= nLen)
            break;

    }// end while
    
    return nBytes;
  
}// end comportReceive

Any help and guidance is appreciate as I have searched exhaustively for a while now. Is an option to only read the Serial port until I get the newline character "\n", and break up my Serial port reads that way? I would like to learn the best way to do such a coding task.

I am creating an ESP32 Bluetooth PS4 Controller interface for 3D Printers. I just want to make it safe, and so the user knows when the process is completed and prevents them from trying to tell Marlin to do something while it is busy. I have quite a bit of it done and plan on open-sourcing it on Github.

Thank you,
/retnel

There seems to be a natural end of message separator = a new line or may be CR + LF

I would suggest to study Serial Input Basics to handle this, and use strcmp() to compare the line you got with the message you expect (bytes are coming one by one - asynchronously - not in one go)

look this over

char s [80];

// -----------------------------------------------------------------------------
#define MaxTok  10
char *toks [MaxTok];
int   vals [MaxTok];

int
tokenize (
    char       *s,
    const char *sep )
{
    unsigned n = 0;
    toks [n] = strtok (s, sep);
    vals [n] = atoi (toks [n]);

    for (n = 1; (toks [n] = strtok (NULL, sep)); n++)
        vals [n] = atoi (toks [n]);

    return n;
}

// -----------------------------------------------------------------------------
void dispToks (
    char * toks [])
{
    char s [40];
    for (unsigned n = 0; toks [n]; n++)  {
        sprintf (s, " %6d  %s", vals [n], toks [n]);
        Serial.println (s);
    }
}

// -----------------------------------------------------------------------------
void dispCmds (
    char * toks [])
{
    char s [40];
    char c;
    int  val;

    for (unsigned n = 0; toks [n]; n++)  {
        sscanf (toks [n], "%c:%d", &c, &val);
        sprintf (s, " %6d %c  %s", val, c, toks [n]);
        Serial.println (s);
    }
}

// -----------------------------------------------------------------------------
void loop ()
{
    if (Serial.available ())  {
        char buf [90];
        int n = Serial. readBytesUntil ('\n', buf, sizeof(buf)-1);
        buf [n] = 0;      // terminate string

        if (strstr(buf, "busy") || strstr(buf, "Active") || strstr(buf, "ok")) {
            Serial.print   (" notice ");
            Serial.println (buf);
        }
        else  {
            tokenize (buf, " ");
            dispCmds (toks);
        }
    }
}

// -----------------------------------------------------------------------------
void setup ()
{
    Serial.begin (9600);
}

There is the memmem() function:
https://www.nongnu.org/avr-libc/user-manual/group__avr__string.html#ga1c22a39c9d936f18aa0764e331e3cddc

If you are using a char array, then strstr() would be more appropriate.

Definitely have a look at the Serial Input Basics link that @J-M-L posted, your comportReceive() function will likely never return complete lines of text because of the slowness of the serial communications.

@david_2018 @J-M-L @gcjr

I changed my comportReceive to:

// ------------------------------------------------------------------------------------------------------
// comportReceive
// ------------------------------------------------------------------------------------------------------
// pMsg = Message buffer
// nLen = Message buffer length
//
int comportReceive(byte* pMsg, int nLen)
{
    // Declarations
    int nBytes = 0;
    byte bSerailRead;

    // Read serial data
    while(Serial2.available() > 0)
    {
        bSerailRead  = (byte)Serial2.read();
        if(bSerailRead == '\n'){
          Serial.println("NEW_LINE");
          break;
        }
        pMsg[nBytes++] = bSerailRead;
        if(nBytes >= nLen)
            break;

    }// end while
    
    return nBytes;
  
}// end comportReceive

I kicks back only one line of bytes at a time now.

I changed the routine to use memmem() as @david_2018 suggested, but it still behaves as if okData not a match:

// Message buffer design
#define MSGBUFFERSIZE       255//80
byte pData[MSGBUFFERSIZE];
byte okData[] = "ok P15 B3";
byte busyData[] = "echo:busy: processing";

        do{
          uint8_t nBytes = comportReceive(&pData[0], MSGBUFFERSIZE);
          if (nBytes > 0){
              if(memmem(pData, nBytes, okData, sizeof(okData))){
                Serial.println("TRUE");
                Serial.write(pData, nBytes);
                Serial.println("");
                ok_message = true;
              }else{
                Serial.println("FALSE");
                Serial.println(nBytes);
                Serial.write(pData, nBytes);
                Serial.println("");
                ok_message = false;
              }
          }
          //Serial.println("(okData != pData)");
          //delay(1000);
        }while(!ok_message);

This is the Serial Monitor Data:

echo:busy: processing
NEW_LINE
FALSE
21
echo:busy: processing
NEW_LINE
FALSE
21
echo:busy: processing
NEW_LINE
FALSE
18
Active Extruder: 0
NEW_LINE
FALSE
61
X:190.00 Y:155.00 Z:10.00 E:0.00 Count X:15200 Y:12400 Z:4000
NEW_LINE
FALSE
9
ok P15 B3

the last part of FALSE, 9, and ok P15 B3

I will also give @gcjr example a try as I can see why it was shared, but shouldn't memmem() do the exact same thing with byte arrays? The both return pointers to the index of the start of the data you are searching for.

In the end I want it to work, but I also want to do it the right way so when I share it, it is useable and customizable.

LOL, typing responses always help my process my thoughts better. It just dawned on me that for memmem() to work I have to dot this:

if(memmem(pData, nBytes, okData, sizeof(okData)-1)){

Instead of this:

if(memmem(pData, nBytes, okData, sizeof(okData))){

This fall in the category if you do it right it will work, lol:

NEW_LINE
TRUE
ok P15 B3
ok P15 B3

YAY! Thank your for your help, :slight_smile:

/retnel

I will also be using the codey you shared to implement in a way I did not think about. Your loop code is what i should be doing (Thank you):

        if (strstr(buf, "busy") || strstr(buf, "Active") || strstr(buf, "ok")) {
            Serial.print   (" notice ");
            Serial.println (buf);
        }

Thank you!

have fun!

perhaps you missed readBytesUntil()

You are correct, my way is higher level and more draining unnecessary resource . Whenever possible do not reinvent the wheel. I will be changing it to your way. I already have it using memmem() as I a working with bytes to fin 'ok', 'busy', and 'Active' like in your example.

I had not gotten to the stage where I fine tune the serial communications. I was just at lets get it working then make it pretty. I will normally build up to a lower level code execution at times. If I am trying something out, it will be code that is not related to efficient at all. I do this to make sure my draft idea can actually work as desired, then I clean it up and make it efficient. When I do it this way, if my draft works and my better version does not, then I can look at my draft to figure it out. Not all code starts out pretty, lol.

I will also be giving this a try:
How to use ESP32 Dual Core with Arduino IDE

I think I am going to use one core to be my serial communication and interrupt handling. The goal is to use a global boolean that signals the main arduino code when it has data for it to update. I am thinking this will make the entire system faster. First time using these ESP32s for this type of project and having one core on a loop with a very short delay that handles all of the serial communications has to make life much easier , fluid, and reliable.

fun is a relative term, lol.

it is - but if you don't get any fun in doing this, don't do it :slight_smile:

i thought one core on the esp32 is dedicated to WiFi.

not "dedicated" - you could run other stuff there too as long as you don't lock the system for too long I suppose and yield often enough

curious what it would take to add additional tasks (?) to the 2nd processor as well as develop a Arduino-like program on the primary (?) processor.

can you use the Arduino IDE? do you need the existing code for the 2nd processor along with the OS?

i'm sure this is possible, but it seem outside the Arduino IDE.

the setup / loop() on the ESP32 is just a task amongst the others created in main

and the loop task does basically call setup and then loop for ever, patting the dog along the way

void loopTask(void *pvParameters) {
    setup();
    for(;;) {
        if(loopTaskWDTEnabled) esp_task_wdt_reset();
        loop();
        if (serialEventRun) serialEventRun();
    }
}

(simplified)

so your own setup can use in the same way xTaskCreate to spawn other tasks and you can decide on which core the task is run.

may be this previous conversation can help

(there is another one with more stuff, can't find it right now)

or read ESP32 Dual Core with Arduino IDE | Random Nerd Tutorials

thanks. appreciate the iinks

Thank you again! I think I understand it, but that can only be verified if I code it and it works as desired, lol.

Here is a sneak peek video of the Marlin PS4 Controller (Please excuse the mess):

Thank you again, @david_2018 , @gcjr , and @J-M-L

I really appreciate it!

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