Parsing Code Review

Hello,

I am new to Arduino, however i did take some c++ classes about 10 years back. So i have a general idea of how things should be. Just enough that I seem to over complicate almost everything :slight_smile: SOOO... i have written some code to retrieve serial input from my HC-05 BLE module. I decided to use start and end markers to make sure the "packets" are the only thing that gets interpreted. I have also implemented a system of delimiters (I use '|' between variable/value sets and ',' to separate the value from the variable) so that i can parse the data into a usable form. This is something i plan to incorporate into a door lock/unlock system but till i have the bugs worked out i am keeping it separate. Also i have only implemented parsing for the particular case of 'U' which will be the unlock command, followed by parsing the userID and password. This works as expected. I just cant help the feeling that there is probably a better way to implement this.

So what i want to know is, do you all think i am going about this the correct way? Or is there a better and/or easier way to handle this?

Any advice on any of the code is welcome as i am by no means a pro and always open to learning new things.

Without further ado:

const int numChars = 32;              // Buffer Size for data

const char startMarker = '<';         // Start marker for data transmissions
const char endMarker = '>';           // End marker for data transmissions
const char delimPipe = '|';
const char delimCom = ',';

char recievedChar[numChars];          // From serial chars recieved
char tempChars[numChars];             // Temp storage for strtok manipulation
char incomingChar[numChars] = {0};    // Incoming char for parsing

bool newData = false;                 // New Data needs parsed flag

struct ParseCommands {                // Place holder struct for user recieved commands

  int recievedUserID;
  int recievedPassword;

} parseVal;

void setup() {
  Serial.begin(38400);                     // Need to init serial to use, 38400 baud for BLE
}

void loop() {
  
  ReadSerialInput();                      // lets check for serial input, if available, retrieve it

  if (newData == true) {                  // If we have new data in the input stream/buffer
    strcpy(tempChars, recievedChar);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
            
    ParseSerialInput();                   // Let's parse the data we just got
    PrintSerialData();                    // Lets print to verify *********** TESTING ONLY ***********************
    newData = false;                      // Set false since we dealt with the new data
  }
}

// ******* NEW SERIAL FUNCTIONS  ****************** STILL TESTING ********
// planning on putting beginning and ending line char's to let arduino know when data is all here
// Then parse the data that came in, store it to the proper values for the rest of sketch to deal with
// Example Unlock string: <U|USR,1001|PWD,1234> should parse as command = u, recievedUser=1001, recievedPass=1234
// Example Settings String <S|4|USER,1002|PASS,2345> Set master to userid 1002

void ReadSerialInput() {                        // Lets get serial input based on begin and end markers
  //////initialize variables//////
  static bool recvInProgress = false;
  static byte ndx = 0;
  char rc;                    // Recieved char
  
  while (Serial.available() > 0 && newData == false) {  //While serial and no new data
    rc = Serial.read();           // Get one byte from serial

    if (recvInProgress == true) {  //Are we recieving Still?
      if (rc != endMarker) {      // Have we reached end marker?
        recievedChar[ndx] = rc;   // Store recieved character to array
        ndx++;                     // add to iterator
        if (ndx >= numChars) {    // If iterator > number chars available
          ndx = numChars - 1;     // Prevent buffer overflow
        }
      } else {
        recievedChar[ndx] = '\0';    // Terminates string
        recvInProgress == false;     // No longer recieving
        ndx = 0;                     // reeset iterator
        newData = true;              // New data to be parsed = true
      }
    } else if (rc == startMarker) { // If recieved char is the startmarker
      recvInProgress = true;        // set recieve flag true so we will stop ignoring data 
    }
  }
}

void ParseSerialInput() {                       // Lets parse our new data to find out what we need to do
  
  char * strtokIndx;                                                             // this is used by strtok() as an index
  strtokIndx = strtok(tempChars, delimPipe);                                     // get the first part - the string
  strcpy(incomingChar, strtokIndx);                                              // copy it to Command char
  
  switch (incomingChar[0]) {                                                     // First letter of stream does...

    case 'U': case 'u':                                                          // Unlock Cmmand
      strtokIndx = strtok(NULL, delimCom);                                       // this continues where the previous call left off
      strcpy(incomingChar, strtokIndx);                                          // copy it to incomingChar
      
      if ((incomingChar[0] == 'U') && (incomingChar[1] == 'S') && (incomingChar[2] == 'R')) { // Checks for USR as identifier

        strtokIndx = strtok(NULL, delimPipe);                                    // this continues where the previous call left off
        parseVal.recievedUserID = atoi(strtokIndx);                              // convert this part to an integer

        strtokIndx = strtok(NULL, delimCom);                                     // this continues where the previous call left off
        strcpy(incomingChar, strtokIndx);                                        // copy it to Command char

        if ((incomingChar[0] == 'P') && (incomingChar[1] == 'W') && (incomingChar[2] == 'D')) { // Checks for PWD as identifier

          strtokIndx = strtok(NULL, delimPipe);                                  // this continues where the previous call left off
          parseVal.recievedPassword = atoi(strtokIndx);                          // convert this part to an integer

        } else {
          Serial.println("ERROR!!");                                             // Still need to work out error flags
        }

      } else {

        Serial.println("ERROR!");                                                // Still need to work out error flags
      }


      break;

    case 'S':         // Settings command

      Serial.println("S presed"); //********** TESTING *******************

      break;

    case 'A':         // Add User Command

      break;

    case 'R':         // Remove User command

      break;

    case 'M':         // Change Master command

      break;

    default:          // Error - NOT VALID OPTION

      break;
  }
  
  for (byte i = 0; i <= numChars; i++) {      // Lets clear the input buffer to prep for next run
    incomingChar[i] = NULL;
  }

}

void PrintSerialData() {                    //******* TESTING OUTPUT*******************
  //Serial.println(incomingChar);
  Serial.println(parseVal.recievedUserID);
  Serial.println(parseVal.recievedPassword);


  parseVal.recievedUserID = NULL;
  parseVal.recievedPassword = NULL;
}

Is the repeated misspelling of "receive" (and its derivations) deliberate ?

cameornxt:
So what i want to know is, do you all think i am going about this the correct way? Or is there a better and/or easier way to handle this?

I have the impression that you have based you code one of the examples in Serial Input Basics (where the spelling is correct) and I can't understand why you have made what appear to be many unnecessary changes to it. My idea in writing the tutorial was that people would just copy and past the code with only essential changes - perhaps to change the end-marker or the size of the array receivedChars[]

It is much easier to help when it is not necessary to examine chunks of code that have already been tested.

Please provide some examples of the messages you will be receiving and (if it is not obvious) how they should be parsed.

...R

The spelling was my fault, i was reading on my phone then typing into the keyboard. Plus spelling has never been my forte. I did use one of your (@Robin2) examples as a basis.Also i started with some different code and was attempting to make changes without breaking my program. That is the reason i broke it into a separate sketch. Mainly to get it functioning, then integrate with my program.

An example of a "packet" for logging in would be as such (Sent from an android app via bluetooth): <U|USR,1001|PWD,1234>

Which i would like to perform actions based on. U would tell to unlock, USR,1001 would change a variable holding user input ID too 1001, and PWD,1234 would change variable for input password to 1234.

I don't have any control over the way the app sends the data, so i was trying to alter the examples to suit my needs.

cameornxt:
I don't have any control over the way the app sends the data, so i was trying to alter the examples to suit my needs.

I can't anticipate all of the packet types, but here is an example of collecting and parsing this packet:

<U|USR,1001|PWD,1234>

here:

const size_t MAX_MESSAGE_BUFFER_LENGTH = 32;

//tested using this data:   <U|USR,1001|PWD,1234>

template<class T> inline Print &operator << (Print &object, T argument)
{
  object.print(argument);
  return object;
}

void setup()
{
  Serial.begin(9600);
  if(Serial)
  {
    Serial << (F("let's go!\n"));
  }
}

void loop()
{
  if (const char* packet = checkForNewPacket(Serial, '<', '>'))  // checks for message on Serial with start marker of "<" and end marker of ">"
  {
    Serial << (F("\nthis just in...\n")) << (packet) << (F("\n\n"));
    Serial << packet[0] << "\n";
    if (packet[0] == 'U') {
      Serial << "Unlock Command Sent!\n";
    }
    char* ptr = strchr(packet, '|');  // pointer to the first pipe in the packet
    ptr++;  // move the pointer along the string by one
    //Serial << ptr << "\n";
    ptr += 4; // move the pointer four chars right
    int USR = atoi(ptr);
    Serial << "USR: " << USR << "\n";
    ptr = strchr(ptr, '|'); // move pointer to the 2nd pipe
    ptr++;
    //Serial << ptr << "\n";
    ptr += 4; // move the pointer four chars right
    char password[16] = "";
    strcpy(password, ptr);
    Serial << "Password: " << password;
  }
}

const char* checkForNewPacket(Stream& stream, const char startMarker, const char endMarker)
{
  static char incomingMessage[MAX_MESSAGE_BUFFER_LENGTH] = "";
  static unsigned char idx = 0;
  static bool gotStartMarker = false;
  if (stream.available())
  {
    if (!gotStartMarker)
    {
      if (stream.read() == startMarker)
      {
        gotStartMarker = true;
      }
    }
    else
    {
      incomingMessage[idx] = stream.read();
      if (incomingMessage[idx] == endMarker)
      {
        incomingMessage[idx] = '\0';
        idx = 0;
        gotStartMarker = false;
        if (!strlen(incomingMessage)) //startMarker followed immediatly by endMarker (i.e.no data)
        {
          //stream << (F("{\"error\":\"no data\"}\n"));  // you can return an error to sender here
          return nullptr;
        }
        //stream << (F("{\"success\":\"")) << (incomingMessage) << (F("\"}\n"));  // or return a success messaage
        return incomingMessage;
      }
      else
      {
        idx++;
        if (idx > MAX_MESSAGE_BUFFER_LENGTH - 1)
        {
          stream << (F("{\"error\":\"message exceeded ")) << (sizeof(incomingMessage) - 1) << (F(" chars\"}\n"));  // another error to sender here
          idx = 0;
          gotStartMarker = false;
          incomingMessage[idx] = '\0';
        }
      }
    }
  }
  return nullptr;
}

That seems easier to follow with clearer intentions than what i was trying to do. However there are 2 parts i don't understand.

  1. Pointers.... I have been unable to wrap my head around them. Is there a good place to read more into those as what i have found is typically along the lines of "A pointer points to an address space, that's all you need to know".

Why would i use a pointer as opposed to a new variable or the variable that we are planning on pointing to?

Also,What is the difference between the way * is used?

const char* checkForNewPacket(Stream& stream, const char startMarker, const char endMarker)

Here the * is used immediately following char

VS:

char * strtokIndx;

Here the * is used in the middle.

Some code seems to use it immediately preceding the pointers name. Do they all get interpreted the same or is there actually a difference?

  1. Template - never seen that used before. I'll have to read up on it. Any recommended reading on that?

learning pointers and pointer syntax will be very helpful for you if you are trying to parse strings, as you are. they are probably the most difficult part of learning C++. but, there is a magic moment once you understand, and it will all crystalize for you.

this thing:

*

is a bit of a bugger, and is hard for many to understand at first as well. It has several uses in pointers and pointer dereferencing (and Math!!!).

so, the short answer is that what it is and means depends on its context.

the parser/compiler looks as these two lines identically:

char* ptr;
char *ptr;

However, you may from time to time see this as a way to initialize three ints:

int someVal, someOtherVal, yetSomeFurtherVal;  // some ints

but pointers would have to be done like this:

int *someVal, *someOtherVal, *yetSomeFurtherVal; // some pointers to ints

so that is why you very often see the asterisk crowded towards the variable name.

regarding the function template, I'd disregard it for now, I only use that to make Serial printing faster to type. That will come later for you.

Thank you for explaining that in a way even i can understand! So just to make sure I'm understanding pointers correctly,

int *someValue = someOtherValue;

would initialize the pointer, pointing to someOtherValue.

int anotherValue = *someValue;

would make anotherValue equal to where *someValue is pointing in this case someOtherValue

while,

int anotherValue = &someValue;

would make anotherValue equal the actual memory address of someOtherValue

am i on the right track here?

cameornxt:
am i on the right track here?

your examples say not yet...

Pointers are actually quite simple; they consist of an address in memory and (usually) a type. That is it. they cannot hold any values, they are simply pointing to some address in memory and at that location there is (presumed) to be that specific type of data.

char* a;  // a is a pointer to what is expected to be a char**

**since a in this example is not initialized, it is actually pointing to some undefined location...

I think you may find this video helpful. If not keep looking at youtube examples until you understand.

Once you see how simple it is, it will blow your mind....

By all means learn about pointers and templates - learning is always good.

But I don't believe either is necessary to give effect to your requirement.

...R

@BulldogLowell - Thanks for the link! I think my biggest problem with understanding pointers had to do with, at least in my head, trying to lump pointers and references together. It makes much more sense after watching that video and the next video in the series (references). I will have to check out the rest of his videos, I'm sure there is plenty more to learn off just that one channel.

@Robin2 - Is there another method you would recommend for parsing then? As my original code based off your example uses a pointer, and bulldog's code makes a little heavier use of pointers. I don't need you to write me any code, just point me in the right direction and ill be happy to learn it for myself.

cameornxt:
@BulldogLowell - Thanks for the link! I think my biggest problem with understanding pointers had to do with, at least in my head, trying to lump pointers and references together. It makes much more sense after watching that video and the next video in the series (references). I will have to check out the rest of his videos, I'm sure there is plenty more to learn off just that one channel.

@Robin2 - Is there another method you would recommend for parsing then? As my original code based off your example uses a pointer,

You are quite right, it does use a pointer, but I just didn't make an issue of it.

I didn't study the code in your Original Post because you have made a lot of unnecessary changes to my example code. It would be a waste of my time trying to match what you have done back to my example to see if there are any substantive changes.

...R

so as it turns out, i wasn't understanding the way strtok() worked. After reviewing several different examples and finally understanding pointers, at least a little bit anyhow. Went back to the drawing board and here is what i have now.

const byte numChars = 32;              // Buffer Size for data

const char startMarker = '<';         // Start marker for data transmissions
const char endMarker = '>';           // End marker for data transmissions

char receivedChar[numChars];          // From serial chars received
char tempChars[numChars];             // Temp storage for strtok manipulation

bool newData = false;                 // New Data needs parsed flag


struct ParsedCommands {        // Place holder struct for user received commands

  char command[3];              // Holds parsed command value, first letter of packet plus 2 spaces for terminate
  char varType[numChars];       // Holds variable type eg. "USR" for userID or "PWD" for password        
  
  int userID;
  int password;

} parseVal;
//****************************************************

void setup() {
  Serial.begin(38400);                     // Need to init serial @ 38400 baud for HC-05

}

void loop() {
  
  ReadSerialInput();                      // lets check for serial input, if available, retrieve it
  
  if (newData == true) {                  // If we have new data in the input stream/buffer
    strcpy(tempChars, receivedChar);      // this temporary copy is necessary to protect the original data
                                          //   because strtok() used in parseData() replaces the commas with \0
    ParseSerialInput();                   // Let's parse the data we just got
    PrintSerialData();                    // Lets print to verify
    newData = false;                      // Set false since we dealt with the new data
  }
}

// ******* NEW SERIAL FUNCTIONS  ****************** STILL TESTING *************************************//
// planning on putting beginning and ending line char's to let arduino know when data is all here      //
// Then parse the data that came in, store it to the proper values for the rest of sketch to deal with //
// Example Unlock string: <U|USR,1001|PWD,1234> should parse as command = u, UserID=1001, password=1234//
// Example Settings String <S|4|USER,1002|PASS,2345> Set master to userid 1002                         //
//*****************************************************************************************************//

void ReadSerialInput() {                        // Lets get serial input based on begin and end markers

  static bool recvInProgress = false;
  static byte ndx = 0;
  char rc;                    // received char
  
  if (Serial.available() > 0 && newData == false) {  //While serial and no new data
    rc = Serial.read();           // Get one byte from serial
    if (recvInProgress == true) {  //Are we still recieving a previous message?
      
      if (rc != endMarker) {      // Have we reached end marker?
        
        receivedChar[ndx] = rc;   // Store received character to array
        ndx++;                    
        if (ndx >= numChars) {    // Prevent Buffer overflow
          
          ndx = numChars - 1; 
        }
      } else {
        
          receivedChar[ndx] = '\0';    // Terminates string
          recvInProgress == false;     // No longer recieving
          ndx = 0;                     // reset iterator
          newData = true;              // New data to be parsed
      }
    } else if (rc == startMarker) { 
      
      recvInProgress = true;        // set recieve flag true so we will stop ignoring data 
    }
  }
}

void ParseSerialInput() {             
  char * strtokIndx;                                                // this is used by strtok() as an index
  strtokIndx = strtok(tempChars, "|,");                             // get the first token
  strcpy(parseVal.command, strtokIndx);                             // copy it to Command char
  
  switch (parseVal.command[0]) {                                    // First letter of stream does...
    
    case 'U':                                                       // Unlock Command

      strtokIndx = strtok(NULL, "|,");                              // Get next Token      
      strcpy(parseVal.varType, strtokIndx);                         // Store variable type to verify message
      
      if (strcmp(parseVal.varType, "USR")==0) {
        
        strtokIndx = strtok(NULL, "|,");                            // this continues where the previous call left off
        parseVal.userID = atoi(strtokIndx);                         // Store parsed userID
        
      } else {
        Serial.println("ERROR: No USR");
      }
      
      strtokIndx = strtok(NULL, "|,");                                       
      strcpy(parseVal.varType, strtokIndx);                         // Store variable type to verify message
      
      if (strcmp(parseVal.varType, "PWD")==0){
        
        strtokIndx = strtok(NULL, "|,");                                   
        parseVal.password = atoi(strtokIndx);                       // Store parsed password
        
      } else {
        
        Serial.println("ERROR: No PWD");
      }

      break;

    case 'S':         // Settings command

      Serial.println("S presed"); //********** TESTING *******************

      break;

    case 'A':         // Add User Command

      break;

    case 'R':         // Remove User command

      break;

    case 'M':         // Change Master command

      break;

    default:          // Error - NOT VALID OPTION

      break;
  }

}

void PrintSerialData() {
  //Serial.println(incomingChar);
  Serial.println(parseVal.userID);
  Serial.println(parseVal.password);


  parseVal.userID = NULL;
  parseVal.password = NULL;
}

My packet gets parsed correctly. I removed a lot of redundant code. And i have learned a lot so far. Now i just have to go through the different packet types and get them all working.

Thanks for your help!

So i have discovered an issue with my program. When i first parse one packet, it is all parsed correctly, even printed to serial to confirm. However when i attempt to put in another packet, or even the same one, it all falls apart.

It looks like after the problem free initial run, i am managing to grab my start char which is '<' and in turn this adds that to my very first pass through strtok().

Input Packet: <U|USR,1001|PWD,1234>

First Pass output with added println's, everything is as expected:

User ID: 1001
Password: 1234
Master Password: 0
Setting; 0
Setting Type: 0
settingID: 0
Brightness: 0
Command: U

Then second pass, exact same packet, it falls apart:

User ID: 0
Password: 0
Master Password: 0
Setting; 0
Setting Type: 0
settingID: 0
Brightness: 0
Command: <U

What is making that happen?

char command[3];// Holds parsed command value, first letter of packet plus 2 spaces for terminate

"command" may be more than a single char?

you need more serial output in your code above.

the Serial Input Basics paradigm is a good start, but none of your functions take an argument or return a value so your loop() is blindly doddering along.

Try like this, which uses your strtok() method:

const size_t MAX_MESSAGE_BUFFER_LENGTH = 32;

//tested using this data:   <U|USR,1001|PWD,1234>

template<class T> inline Print &operator << (Print &object, T argument)
{
  object.print(argument);
  return object;
}

struct ParsedCommands {        // Place holder struct for user received commands
  char command;              // Holds parsed command value, first letter of packet plus 2 spaces for terminate
  char varType[8];       // Holds variable type eg. "USR" for userID or "PWD" for password

  int userID;
  int password;

} parseVal;

void setup()
{
  Serial.begin(38400);
  if (Serial)
  {
    Serial << (F("let's go!\n"));
  }
}

void loop()
{
  if (const char* packet = checkForNewPacket(Serial, '<', '>'))  // checks for message on Serial with start marker of "<" and end marker of ">"
  {
    Serial << (F("\nthis just in...\n")) << (packet) << (F("\n\n"));
    Serial << (F("Command: ")) << packet[0] << "\n";
    char mssg[MAX_MESSAGE_BUFFER_LENGTH] = "";
    strcpy(mssg, packet);
    char command = mssg[0];
    char * strtokIndx;                                                // this is used by strtok() as an index
    strtokIndx = strtok(mssg, "|,");                             // get the first token
    switch (command) {
      case 'U':
        strtokIndx = strtok(NULL, "|,");                              // Get next Token
        strcpy(parseVal.varType, strtokIndx);                         // Store variable type to verify message
        Serial << (F("varType: "));
        Serial << (parseVal.varType) << (F("\n"));
        if (strstr(parseVal.varType, "USR")) {
          strtokIndx = strtok(NULL, "|,");                            // this continues where the previous call left off
          parseVal.userID = atoi(strtokIndx);                         // Store parsed userID
          Serial << (F("userID: "));
          Serial << (parseVal.userID) << (F("\n"));
        } else {
          Serial << ("ERROR: No USR\n");
        }
        strtokIndx = strtok(NULL, "|,");
        strcpy(parseVal.varType, strtokIndx);                         // Store variable type to verify message
        if (strstr(parseVal.varType, "PWD")) {
          strtokIndx = strtok(NULL, "|,");
          parseVal.password = atoi(strtokIndx);                       // Store parsed password
          Serial << (F("password: "));
          Serial << (parseVal.password) << (F("\n"));
        } else {

          Serial << (F("ERROR: No PWD"));
        }

        break;
      case 'S':
        Serial << (F("S presed\n")); //********** TESTING *******************
        break;
    }

  }
}

const char* checkForNewPacket(Stream& stream, const char startMarker, const char endMarker)
{
  static char incomingMessage[MAX_MESSAGE_BUFFER_LENGTH] = "";
  static unsigned char idx = 0;
  static bool gotStartMarker = false;
  if (stream.available()) {
    if (!gotStartMarker) {
      if (stream.read() == startMarker) {
        gotStartMarker = true;
      }
    } else {
      incomingMessage[idx] = stream.read();
      if (incomingMessage[idx] == endMarker) {
        incomingMessage[idx] = '\0';
        idx = 0;
        gotStartMarker = false;
        if (!strlen(incomingMessage)) { //startMarker followed immediatly by endMarker (i.e.no data)
          //stream << (F("{\"error\":\"no data\"}\n"));  // you can return an error to sender here
          return nullptr;
        }
        //stream << (F("{\"success\":\"")) << (incomingMessage) << (F("\"}\n"));  // or return a success messaage
        return incomingMessage;
      } else {
        idx++;
        if (idx > MAX_MESSAGE_BUFFER_LENGTH - 1) {
          stream << (F("{\"error\":\"message exceeded ")) << (sizeof(incomingMessage) - 1) << (F(" chars\"}\n"));  // another error to sender here
          idx = 0;
          gotStartMarker = false;
          incomingMessage[idx] = '\0';
        }
      }
    }
  }
  return nullptr;
}

PS. User ID's and Passwords should be processed as strings, not integers.

The compiler threw an error, when i had one char for command, using strcpy(). Something about it expected a different data type, i can't recall for sure now. i changed it to 3 and it went away. i assumed strtok() needed some space to terminate the string i guess. i see your code works without this though so i will have to compare mine to yours to see where i went wrong.

I had added a bunch of serial output in right after i posted but i didn't get it narrowed down much, except that on subsequent passes i was somehow getting my start marker added to command. More probing to do there. I feel like maybe this has something to do with the error i was getting with a single char for command.

Definitely a good idea to be passing arguments and return values.

Is there a particular reason why you chose strstr() as opposed to strcmp()?

As for the ID's and Passwords, they will only ever be at the max 4 digits, so 9999. I have read a few places that string manipulation is best kept to a minimum on these. Therefore i chose to make them int as they are easily compared and manipulated and will accept the max requirements. Should i still just leave them strings?

Thanks for all the help, i definitely owe you a cold one!

cameornxt:
I had added a bunch of serial output in right after i posted but i didn't get it narrowed down much, except that on subsequent passes i was somehow getting my start marker added to command. More probing to do there. I feel like maybe this has something to do with the error i was getting with a single char for command.

Definitely a good idea to be passing arguments and return values.

It is exactly that use of anonymous functions a) which have no method of connecting their dependancies and b) rely on global variables that makes your code so difficult to debug. I couldn't find your error either, so don't feel bad.

Your code demonstrates exactly why using global variables is so onerous. The problem with global variables is that since every function has access to these, it becomes increasingly hard to figure out which functions actually read and write these variables.

To understand how the application works, you pretty much have to take into account every function which modifies the global state. That can be done, but as the application grows it will get harder to the point of being virtually impossible (or at least a complete waste of time, as you observed).

If you don't rely on global variables, you can pass state around between different functions as needed. That way you stand a much better chance of understanding what each function does, as you don't need to take the global state into account.

As you say: "Definitely a good idea to be passing arguments and return values." . There is a reason for that. It allows for you to compartmentalize your code and isolate your errors.

cameornxt:
Is there a particular reason why you chose strstr() as opposed to strcmp()?

No, tomayto-tomahto kind-of-thing.

cameornxt:
As for the ID's and Passwords, they will only ever be at the max 4 digits, so 9999. I have read a few places that string manipulation is best kept to a minimum on these. Therefore i chose to make them int as they are easily compared and manipulated and will accept the max requirements. Should i still just leave them strings?

your password isn't an integer, so don't treat it as such. It is a string of characters which happen to be numerals, much like a phone number.

C strings are the way to go. Practice making a function to handle the comparison... you already know about strstr() and strcmp().

I forgot to ask previously, but how does stream compare to serial. They seem to function in the same fashion. Is stream a "lower level" way to perform essentially the same tasks?

I will have to edit some code in my other sketch, that this is eventually destined for, to make that feasible, but they aren't drastic or massive changes in any way so that shouldn't be a problem.

I'll get a comparison function going and try to not to butcher anything too bad haha

never mind, i see what you did with stream there, i guess i didn't notice that before.

cameornxt:
never mind, i see what you did with stream there, i guess i didn't notice that before.

Yes, the function explicitly requires that you pass a Reference to a Stream Object. That makes it a) portable and b) very obvious as to your intent.

HardwareSerial inherits from Stream.

You already have all the tools to make your pass phrase functional. Try it.