Go Down

Topic: more with char arrays and class functions (Read 985 times) previous topic - next topic

gfvalvo

#15
Sep 15, 2020, 12:40 pm Last Edit: Sep 15, 2020, 06:45 pm by gfvalvo
The unused array entries should be null but if I find that is not the case, I can fix that also.
That will only be true when for objects of your class that are instantiated as global (and perhaps static-local) variables. Ones that are instantiated dynamically or as automatic-local variables will have random data in the array. So, yes, you should fix it by explicitly initialing all elements of the array to 0.

However, the main point of my post was that you can specify the size of the data buffer character array with a parameter to the constructor (or even a begin() method) by using dynamic allocation. But, you need to mindful of memory management.
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

petedd

Got it.  Thanks.  I avoid dynamic allocation on microprocessors whenever I can.

gfvalvo

#17
Sep 15, 2020, 07:21 pm Last Edit: Sep 15, 2020, 07:49 pm by gfvalvo
I avoid dynamic allocation on microprocessors whenever I can.
In that case, it seems like the only robust way of achieving your stated goal of having "different sized char arrays, with their sizes defined by the constructor" is to use templates. Of course, that requires that the size must be known at compile-time. Also, objects with different-sized arrays created this way will be different data types.
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

petedd

If anyone is interested, here is my final demonstration code for capturing, parsing, and displaying multiple string message formats using Class-based OOP.   Here I demo it with one port and alternately capture one message string type and then the other.   If one were using a chip with multiple serial ports, the input of each string type could be assigned to a dedicated port and share the code.

THANK YOU TO ALL WHO HELPED ME GET ON TRACK HERE!!

Code: [Select]

/*
    Demonstration of serial input string handler using Classes to
    allow for capture and parsing of different formats or serial messages

    This is  based on the non-Class examples in the (classy nonetheless) tutorials by J Haskell found at:
      http://jhaskellsblog.blogspot.com/2011/05/serial-comm-fundamentals-on-arduino.html  and
      http://jhaskellsblog.blogspot.com/2011/06/parsing-quick-guide-to-strtok-there-are.html

    Here GPS and IMU strings are handled.  "Parsing" here is just the breaking out and printing of string fields.
    Further usage of the data could be done with additional coding to capture the values as for use later as intergers, floats, char*, etc

    The GPS module might be this one: http://www.sparkfun.com/products/465
    The IMU might be this one: http://www.sparkfun.com/products/9623

    Here are some test strings you can copy and paste into the Serial Monitor
    NOTE: Be sure to set the Serial Monitor to add either CR or both NL/CR

       $GPGGA,161229.487,3723.2475,N,12158.3416,W,1,07,1.0,9.0,M,,,,0000*18
       $GPGLL,3723.2475,N,12158.3416,W,161229.487,A*2C
       $GPGSA,A,3,07,02,26,27,09,04,15,,,,,,1.8,1.0,1.5*33

       !ANG:320,33,191
       !ANG:0,320,90
       !ANG:0,0,0

    While this example uses only one serial port, one could assign GPS input to one port and IMU input to another and add a member variable
    to the Messages class for the port to use (eg: byte m_inputPort;  where, for example, GPS input is found serial port 1 and IMU input is
    found on serial port 2) and then use different ports in the getString method based on the passing of m_inputPort.  By keeping all variables (
    except incomingbyte as member variables, the code is reentrant in that multiple strings can be captured, each from a separate port.

    The Messages Class is set-up here to accept one, two, or three field delimiters in the constructor for each instance.  Here we use two for
    the GPS and three for the IMU strings.

//   Copyright (c) 2020 by Peter Dubler
//              This program is free software: you can redistribute it and/or modify
//              it under the terms of the GNU General Public License as published by
//              the Free Software Foundation, either version 3 of the License, or
//              (at your option) any later version.
//     
//              This program is distributed in the hope that it will be useful,
//              but WITHOUT ANY WARRANTY; without even the implied warranty of
//              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//              GNU General Public License for more details.
//     
//              The GNU General Public License
//              may be found here: <http://www.gnu.org/licenses/>.

*/

class Messages {
  public:
    uint8_t    m_dataBufferSize;
  private:
    char       m_startChar;
    char       m_endChar;
    char       m_delimiters[3] {0, 0, 0};  // set default values to null character
    char*      m_dataBuffer;  // if you want to access the dataBuffer (as GPS.m_dataBuffer for example)with non-class functions, change this to public:
                              // m_dataBuffer is "seeded" with a maximum-sized (for the string to be captured) string literal in the constructor
                              //   this assures stability of the data fields.
    bool       m_storeString;
    uint8_t    m_dataBufferIndex;

  public:
    // here we will create overloads of the constructor specifications for one, two, or three field delimiters
    // single delimiter instance
    Messages(uint8_t DBS, char SC, char EC, char DL0, char* DB)
    {
      m_dataBufferSize = DBS;
      m_startChar = SC;
      m_endChar   = EC;
      m_delimiters[0] = DL0;
      m_dataBuffer = DB; // DB should be a string literal "abc..." the length of DBS
    }

    // two delimter instance
    Messages(uint8_t DBS, char SC, char EC, char DL0, char DL1, char* DB)
    {
      m_dataBufferSize = DBS;
      m_startChar = SC;
      m_endChar   = EC;
      m_delimiters[0] = DL0;
      m_delimiters[1] = DL1;
      m_dataBuffer = DB; // DB should be a string literal "abc..." the length of DBS
    }

    // three delimter instance
    Messages(uint8_t DBS, char SC, char EC, char DL0, char DL1, char DL2, char* DB)
    {
      m_dataBufferSize = DBS;
      m_startChar = SC;
      m_endChar   = EC;
      m_delimiters[0] = DL0;
      m_delimiters[1] = DL1;
      m_delimiters[2] = DL2;
      m_dataBuffer = DB; // DB should be a string literal "abc..." the length of DBS
    }

    bool getSerialString() {  // Captures serial input starting with m_startChar and ending with m_endChar, upto a length of m_dataBufferSize.
                              // Returns true when full string has been captured.
      //static byte dataBufferIndex = 0;
      while (Serial.available() > 0) {
        char incomingbyte = Serial.read();
        if (incomingbyte == m_startChar) {
          m_dataBufferIndex = 0;  //Initialize our dataBufferIndex variable
          m_storeString = true;
        }
        if (m_storeString) {
          //Let's check our index here, and abort if we're outside our buffer size
          if (m_dataBufferIndex == m_dataBufferSize) {
            //Oops, our index is pointing to an array element outside our buffer.
            m_dataBufferIndex = 0;
            m_storeString = false; //reset flag so that next time through method will check for m_startChar
            break;
          }
          if (incomingbyte == m_endChar) {
            m_dataBuffer[m_dataBufferIndex] = 0; //null terminate the C string
            m_storeString = false; //reset flag so that next time through method will check for m_startChar
            //Our data string is complete.  return true
            return true;
          }
          else {
            m_dataBuffer[m_dataBufferIndex++] = incomingbyte;
            m_dataBuffer[m_dataBufferIndex] = 0; //null terminate the C string
          }
        }
        else {
        }
      }
      //We've read in all the available Serial data, and don't have a valid string yet, so return false
      return false;
    }

    void parseString() {
      char* valPosition;
      //This initializes strtok with our string to tokenize
      valPosition = strtok(m_dataBuffer, m_delimiters);

      while (valPosition != NULL) {
        Serial.print(valPosition);  Serial.print("  ");
        //Here we pass in a NULL value, which tells strtok to continue working with the previous string
        valPosition = strtok(NULL, m_delimiters);
      }
      Serial.println('\n');//Serial.println();
    }

};

#define GPSbufferSeed "12345678901234567890123456789012345678901234567890123456789012345678901234567890"  // 80 character string literal
#define IMUbufferSeed "123456789012345678"  // 18 character string literal


//Invoke GPS and IMU instances
//Messages InstanceName(int DBS, char SC, char EC, char DL1,(DL2),(DL3), char * bufferSeed) )
Messages GPS{80, '$', '\r', ',', '$', GPSbufferSeed};
Messages IMU{18, '!', '\r', ':', ',', '!', IMUbufferSeed};



void setup() {
  Serial.begin(115200);
  Serial.println("Serial port activated");
  delay(200);

}
void loop() {  // Ask for, receive, parse, and display parsed GPS string, then IMU string, and repeat
  Serial.println("Waiting for GPS string");
  bool gotString = false;
  while (!gotString) {
    if (GPS.getSerialString()) {
      Serial.print("got GPS string: ");
      GPS.parseString();
      gotString = true;
    }
  }

  Serial.println("Waiting for IMU string");
  gotString = false;
  while (!gotString) {
    if (IMU.getSerialString()) {
      Serial.print("got IMU string: ");
      IMU.parseString();
      gotString = true;
    }
  }
}


gfvalvo

#19
Sep 16, 2020, 01:36 am Last Edit: Sep 16, 2020, 03:54 am by gfvalvo
I assume you've seen the following compiler warnings from your code (if not, turn up the warning level in the Arduino IDE):
Code: [Select]


C:\Users\GFV\AppData\Local\Temp\arduino_modified_sketch_756953\sketch_sep15a.ino:155:52: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]

 Messages GPS{80, '$', '\r', ',', '$', GPSbufferSeed};

                                                    ^

C:\Users\GFV\AppData\Local\Temp\arduino_modified_sketch_756953\sketch_sep15a.ino:156:57: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]

 Messages IMU{18, '!', '\r', ':', ',', '!', IMUbufferSeed};

                                                         ^


I believe it was mentioned in one of your other threads that writing to memory occupied by a string literal is a really bad thing. It still is.

Why not just do this:
Code: [Select]
char GPSbufferSeed[80];
char IMUbufferSeed[18];


//Invoke GPS and IMU instances
//Messages InstanceName(int DBS, char SC, char EC, char DL1,(DL2),(DL3), char * bufferSeed) )
Messages GPS{sizeof(GPSbufferSeed), '$', '\r', ',', '$', GPSbufferSeed};
Messages IMU{sizeof(IMUbufferSeed), '!', '\r', ':', ',', '!', IMUbufferSeed};


Either way, the code violates the OOP tenant of Encapsulation. Since the buffer is handled by the class, it should be encapsulated in the the class instead of being supplied externally.

Also, the code is hardwired to only work with the Serial object. Better to pass a reference to a Stream object into the constructor. That way the class could work with any object whose class inherits from Stream (HardwareSerial, usb_serial_class, SoftwareSerial, etc).
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

GoForSmoke

Thank you.  I'm not sure what you mean by "point it to a char array elsewhere".  How does one do that?  Would you have a code snippet you could share or insert an example into my Class code above?

Thanks again.
in pseudocode then?
it's been too long since I used C++

class Whatever
{
  char * textbuff;
  int      otherdata;
}

char str00[] = "12345678";
char str01[] = "1234567";
char str02[] = "123456";
char str03[] = "12345";
char str04[] = "1234";
char str05[] = "123";
char str06[] = "12";
char str07[] = "1";

Whatever textobject();

textobject.textbuff = str04;

The name of every array is a pointer to the 0 element of that array. A 2D array is a pointer to an array of pointers, etc.

I learned C first in the 80's. It fits small computers, was great on the Z80.
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

GoForSmoke

Thank you.  I'm not sure what you mean by "point it to a char array elsewhere".  How does one do that?  Would you have a code snippet you could share or insert an example into my Class code above?

Thanks again.
If you store your delimiters in flash, the elsewhere you point to uses PROGMEM to write and read. EEPROM has its own library but an address is an address if you get the street right, pointers are an address that the compiler treats according to data type in source but the 2 bytes in an AVR pointer are offset from 0 addresses no matter which memory type.


If you set a parser up in a serial stream, at 250000 baud you have 640 cycles between each char arriving to check delimiters. If the delimiters are < all other legal input chars that could happen in very few cycles, possibly 1.

You have a "free" 64-byte buffer as long as it's going to print on Serial. There is no need to assemble text in a buffer just to print it. Print each item in turn and try not to overfill the Serial output buffer as it is easy to print 64 more chars before the bits from the first clear the TX register.
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

PieterP

#22
Sep 16, 2020, 05:56 pm Last Edit: Sep 16, 2020, 05:58 pm by PieterP
Whatever textobject();
I know you intended it as pseudocode, but it's too close to C++ not to say anything about it :)

Whatever textobject(); is the declaration of a function textobject that takes no arguments and returns an object of type Whatever.
If you want to declare a variable textobject of type Whatever, you want Whatever textobject; (without the parentheses).

A 2D array is a pointer to an array of pointers, etc.
That's not true, a 2D array is not the same as an array of pointers. Multidimensional arrays are stored contiguously in memory. There are no pointers involved at all.

If you do want your multidimensional array of type T to decay to a pointer, you'll still end up with a pointer to T, not pointer to pointer to T. (This is not usually done, because you lose the stride or leading dimensions of your multidimensional array. Unlike one-dimensional arrays, multidimensional arrays don't decay to pointers implicitly.)

For example: https://godbolt.org/z/qj1cE6

An array of pointers behaves completely differently, is not necessarily laid out contiguously in memory, and requires an extra indirection: https://godbolt.org/z/e5n7qd

petedd

I assume you've seen the following compiler warnings from your code (if not, turn up the warning level in the Arduino IDE):


Thanks.  Great input.  I will look into using a stream object for input... one more thing to learn.

petedd

Either way, the code violates the OOP tenant of Encapsulation. Since the buffer is handled by the class, it should be encapsulated in the the class instead of being supplied externally.

Also, the code is hardwired to only work with the Serial object. Better to pass a reference to a Stream object into the constructor. That way the class could work with any object whose class inherits from Stream (HardwareSerial, usb_serial_class, SoftwareSerial, etc).

gfvalvo,

By the way, do you have a favorite book, reference, tutorial, etc. that might cover Encapsulation and Stream objects which you might recommend?  (I have lots that I am reading, but perhaps you know of something better).  Thanks again.

gfvalvo

By the way, do you have a favorite book, reference, tutorial, etc. that might cover Encapsulation and Stream objects which you might recommend?  (I have lots that I am reading, but perhaps you know of something better).
Nothing specific, different topics.

Encapsulation is a general OOP concept. As I'm sure you've found there is voluminous information and references for OOP out there. Perhaps too much :)

The Stream class is Arduino-specific. There's a chain of class Inheritance (another OOP concept):

Print: An abstract class that knows how to output numerical values, c-strings, String objects, etc as a serial of ASCII characters. However, it doesn't know how to actually "print" to any specific hardware.

Stream: An abstract class that inherits from Print. It adds the ability to input ASCII characters. But, it also doesn't know how to work with specific hardware.

HardwareSerial, usb_serial_class, SoftwareSerial, etc: Classes that inherit from Stream. They are concrete classes (you can instantiate objects with them) that know how to work with specific hardware to do ASCII input and outputs.

Also, some concrete classes (such as those for LCD displays) inherit directly from Print as they involve output only, no input.
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

petedd

The Stream class is Arduino-specific. There's a chain of class Inheritance (another OOP concept):

Print: An abstract class that knows how to output numerical values, c-strings, String objects, etc as a serial of ASCII characters. However, it doesn't know how to actually "print" to any specific hardware.

Stream: An abstract class that inherits from Print. It adds the ability to input ASCII characters. But, it also doesn't know how to work with specific hardware.

HardwareSerial, usb_serial_class, SoftwareSerial, etc: Classes that inherit from Stream. They are concrete classes (you can instantiate objects with them) that know how to work with specific hardware to do ASCII input and outputs.

Also, some concrete classes (such as those for LCD displays) inherit directly from Print as they involve output only, no input.

Thanks again.

1) I thought I was trying to encapsulate everything so having an external array which is not private breaks that.  Still looking for a way to create different-sized char arrays as private objects in my Messages Class.

2) This Streams concept sounds just like one layer in what we called a protocol stack back when I learned this over 40 years ago.  Pretty straightforward.

gfvalvo

Still looking for a way to create different-sized char arrays as private objects in my Messages Class.
As I said, if you don't want to use dynamic allocation, then templates may be the only robust way. Example below. I didn't implement your actual message class because I didn't want to deal with providing input to parse. But, it shows all the concepts I recommended.
Code: [Select]
template<size_t N>
class Messages {
  public:
    Messages(Stream &str) : charStream(str), m_dataBufferSize(N) {
    }
    void setBuffer(const char *inString);
    void printBuffer();
    const char *getBufferPointer();
    size_t getStringLen();
    size_t getBufferSize();

  private:
    Stream &charStream;
    size_t m_dataBufferSize;
    char m_dataBuffer[N + 1] = {'\0'}; // Make sure there's always a null character at end of buffer
};


template<size_t N>
void Messages<N>::setBuffer(const char *inString) {
  strncpy(m_dataBuffer, inString, m_dataBufferSize);
}

template<size_t N>
void Messages<N>::printBuffer() {
  charStream.print(m_dataBuffer);
}

template<size_t N>
size_t Messages<N>::getBufferSize() {
  return (m_dataBufferSize);
}

template<size_t N>
size_t Messages<N>::getStringLen() {
  return strlen(m_dataBuffer);
}

template<size_t N>
const char * Messages<N>::getBufferPointer() {
  return m_dataBuffer;
}


Messages<80> GPSbufferSeed(Serial);
Messages<18> IMUbufferSeed(Serial);

void setup() {
  const char *ptr;

  Serial.begin(115200);
  delay(1000);

  Serial.print("GPSbufferSeed Buffer Size = ");
  Serial.println(GPSbufferSeed.getBufferSize());
  Serial.print("GPSbufferSeed String Size = ");
  Serial.println(GPSbufferSeed.getStringLen());
  GPSbufferSeed.setBuffer("Hello World");
  Serial.print("GPSbufferSeed Buffer String = ");
  GPSbufferSeed.printBuffer();
  Serial.println();
  ptr = GPSbufferSeed.getBufferPointer();
  Serial.print("GPSbufferSeed Buffer String = ");
  Serial.println(ptr);
  Serial.print("GPSbufferSeed String Size = ");
  Serial.println(GPSbufferSeed.getStringLen());

  Serial.println();
  Serial.println();

  Serial.print("IMUbufferSeed Buffer Size = ");
  Serial.println(IMUbufferSeed.getBufferSize());
  Serial.print("IMUbufferSeed String Size = ");
  Serial.println(IMUbufferSeed.getStringLen());
  IMUbufferSeed.setBuffer("Test 123");
  Serial.print("IMUbufferSeed Buffer String = ");
  IMUbufferSeed.printBuffer();
  Serial.println();
  ptr = IMUbufferSeed.getBufferPointer();
  Serial.print("IMUbufferSeed Buffer String = ");
  Serial.println(ptr);
  Serial.print("IMUbufferSeed String Size = ");
  Serial.println(IMUbufferSeed.getStringLen());
}

void loop() {
}
No technical questions via PM. They will be ignored. Post your questions in the forum so that all may learn.

petedd

Code: [Select]
template<size_t N>
class Messages {
  public:
    Messages(Stream &str) : charStream(str), m_dataBufferSize(N) {
    }
    
...

template<size_t N>
void Messages<N>::printBuffer() {
  charStream.print(m_dataBuffer);
}


}

Thanks glvalvo!

These are two breakthroughs for me.  1) I have used templates before but was not aware I could use a template this way to specify a value instead of specifying a type.  This is brilliant and I can really see the value here.   2) Passing the stream specifier this way is also brilliant as it allows me to specify different i/o streams. 

Again, many thanks.
Pete

petedd

passing Stream and using template for sizing the buffers saved 78 bytes of program memory and the same amount of dynamic memory (RAM).   Thanks again gfvalvo, PieterP and others!

here is the version 3.0 code.

Code: [Select]

/*
    Demonstration of serial input string handler using Classes to
    allow for capture and parsing of different formats or serial messages

    This is  based on the non-Class examples in the (classy nonetheless) tutorials by J Haskell found at:
      http://jhaskellsblog.blogspot.com/2011/05/serial-comm-fundamentals-on-arduino.html  and
      http://jhaskellsblog.blogspot.com/2011/06/parsing-quick-guide-to-strtok-there-are.html

    Here GPS and IMU strings are handled.  "Parsing" here is just the breaking out and printing of string fields.
    Further usage of the data could be done with additional coding to capture the values as for use later as intergers, floats, char*, etc

    The GPS module might be this one: http://www.sparkfun.com/products/465
    The IMU might be this one: http://www.sparkfun.com/products/9623

    Here are some test strings you can copy and paste into the Serial Monitor
    NOTE: Be sure to set the Serial Monitor to add either CR or both NL/CR

       $GPGGA,161229.487,3723.2475,N,12158.3416,W,1,07,1.0,9.0,M,,,,0000*18
       $GPGLL,3723.2475,N,12158.3416,W,161229.487,A*2C
       $GPGSA,A,3,07,02,26,27,09,04,15,,,,,,1.8,1.0,1.5*33

       !ANG:320,33,191
       !ANG:0,320,90
       !ANG:0,0,0

    While this example uses only one serial port, one could assign GPS input to one port and IMU input to another by specifying different Streams for the ports
    to use By keeping all variables (except incomingbyte as member variables, the code is reentrant in that multiple strings can be captured, each from
    a separate port (Stream).

    The Messages Class is set-up here to accept one, two, or three field delimiters in the constructor for each instance.  Here we use two delimters for
    the GPS and three delimiters for the IMU strings.

  //   Copyright (c) 2020 by Peter Dubler
  //              This program is free software: you can redistribute it and/or modify
  //              it under the terms of the GNU General Public License as published by
  //              the Free Software Foundation, either version 3 of the License, or
  //              (at your option) any later version.
  //
  //              This program is distributed in the hope that it will be useful,
  //              but WITHOUT ANY WARRANTY; without even the implied warranty of
  //              MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  //              GNU General Public License for more details.
  //
  //              The GNU General Public License
  //              may be found here: <http://www.gnu.org/licenses/>.

                3.0 using Class templates - uses 78 fewer bytes of each program memory and dynamic memory (RAM) as compared to prior version

*/
template<size_t N>
class Messages {
  private:
    char       m_startChar;
    char       m_endChar;
    char       m_delimiters[3] {0, 0, 0};  // set default values to null character
    bool       m_storeString;
    uint8_t    m_dataBufferIndex;
    Stream     &charStream;
    size_t     m_dataBufferSize;
    char       m_dataBuffer[N + 1] = {'\0'}; // Make sure there's always a null character at end of buffer
  public:
    // here we will create overloads of the constructor specifications for one, two, or three field delimiters
    // single delimiter instance
    Messages(Stream &str, char SC, char EC, char DL0)
      : charStream(str), m_dataBufferSize(N),  m_startChar(SC), m_endChar(EC) {
      m_delimiters[0] = DL0;
    }

    // two delimiter instance
    Messages(Stream &str, char SC, char EC, char DL0, char DL1)
      : charStream(str), m_dataBufferSize(N),  m_startChar(SC), m_endChar(EC) {
      m_delimiters[0] = DL0;
      m_delimiters[1] = DL1;
    }

    // three delimiter instance
    Messages(Stream &str, char SC, char EC, char DL0, char DL1, char DL2)
      : charStream(str), m_dataBufferSize(N),  m_startChar(SC), m_endChar(EC) {
      m_delimiters[0] = DL0;
      m_delimiters[1] = DL1;
      m_delimiters[2] = DL2;
    }

    bool getString();
    void parseString();
};

template<size_t N>
bool Messages<N>::getString() {  // Captures serial input starting with m_startChar and ending with m_endChar, upto a length of m_dataBufferSize.
  // Returns true when full string has been captured.
  while (charStream.available() > 0) {
    char incomingbyte = charStream.read();
    if (incomingbyte == m_startChar) {
      m_dataBufferIndex = 0;  //Initialize our dataBufferIndex variable
      m_storeString = true;
    }
    if (m_storeString) {
      //Let's check our index here, and abort if we're outside our buffer size
      if (m_dataBufferIndex == m_dataBufferSize) {
        //Oops, our index is pointing to an array element outside our buffer.
        m_dataBufferIndex = 0;
        m_storeString = false; //reset flag so that next time through method will check for m_startChar
        break;
      }
      if (incomingbyte == m_endChar) {
        m_dataBuffer[m_dataBufferIndex] = 0; //null terminate the C string
        m_storeString = false; //reset flag so that next time through method will check for m_startChar
        //Our data string is complete.  return true
        return true;
      }
      else {
        m_dataBuffer[m_dataBufferIndex++] = incomingbyte;
        m_dataBuffer[m_dataBufferIndex] = 0; //null terminate the C string
      }
    }
  }
  //We've read in all the available Serial data, and don't have a valid string yet, so return false
  return false;
}

template<size_t N>
void Messages<N>::parseString() {
  char* valPosition;
  //This initializes strtok with our string to tokenize
  valPosition = strtok(m_dataBuffer, m_delimiters);

  while (valPosition != NULL) {
    charStream.print(valPosition);  charStream.print("  ");
    //Here we pass in a NULL value, which tells strtok to continue working with the previous string
    valPosition = strtok(NULL, m_delimiters);
  }
  charStream.println('\n');
}


// Messages constructors
//  <bufferSize> Stream,  SC,  EC , DL0, DL1, DL2
//        |         |     |    |     |    |    |
//        V         V     V    V     V    V    V
Messages<80> GPS(Serial, '$', '\r', ',', '$');
Messages<18> IMU(Serial, '!', '\r', ':', ',', '!');



void setup() {
  Serial.begin(115200);
  Serial.println("Serial port activated");
  delay(200);
}

void loop() {  // Ask for, receive, parse, and display parsed GPS string, then IMU string, and repeat
  Serial.println("Waiting for GPS string");
  bool gotString = false;
  while (!gotString) {
    if (GPS.getString()) {
      Serial.print("got GPS string: ");
      GPS.parseString();
      gotString = true;
    }
  }

  Serial.println("Waiting for IMU string");
  gotString = false;
  while (!gotString) {
    if (IMU.getString()) {
      Serial.print("got IMU string: ");
      IMU.parseString();
      gotString = true;
    }
  }
}

Go Up