/** * @file Message.h * @brief This file contains the Message base class which is can be inherited to * parse serial messages over any type of protocol * @version 1.0.0 * @author Quinn Henthorne. Contact: quinn.henthorne@gmail.com */ #pragma once #include template class Message{ public: /** * @brief Initialize the Message object */ virtual void Init(uint32_t baudRate); /** * @brief Prints the args array to the serial monitor */ virtual void PrintArgs() = 0; /** * @brief Update the Message object and parse any data that's available */ void Update(); /** * @brief Returns true if there is new data available * @return true if there is new data available */ bool IsNewData(); /** * @brief Clears the new data flag */ void ClearNewData(); /** * @brief Return a pointer to the args array * @return a pointer to the args array */ int32_t * GetArgs(); /** * @brief Returns the number of args that have been populated for the current message * @return the number of args that have been populated for the current message */ uint32_t GetArgsLength(); /** * @brief Returns the number of args that have been populated for the current message * @return the number of args that have been populated for the current message */ uint32_t GetPopulatedArgs(); protected: enum SerialState : uint8_t{ IDLE, NEW_DATA, DATA_RECIEVED, RECIEVE_IN_PROGRESS }; Message() = default; /** * @brief reads the serial data and stores it in the data array * @return the next character in the serial buffer */ virtual char getChar() = 0; /** * @brief returns the number of bytes available in the serial buffer * @return the number of bytes available in the serial buffer */ virtual uint32_t dataAvailable() = 0; /** * @brief Takes in any available serial data and reads it into the buffer if our start character is hit. * Also marks what state the Message object is in */ void readSerial(); void parseData(); SerialState state{IDLE}; char data[SERIAL_BUFFER_SIZE]; // an array to store the received data char temp_data[SERIAL_BUFFER_SIZE]; // an array that will be used with strtok() uint32_t ndx{0}; uint32_t populatedArgs{0}; // the number of args that have been populated for the current message int32_t args[MAX_ARGS]; const char startMarker = '!'; const char endMarker = ';'; }; template void Init(uint32_t baudRate){ Serial.println("You called Message::Init! This should NEVER happen!"); } template void Message::readSerial(){ char c; // read the incoming serial data: while (this->dataAvailable() > 0 && this->state != SerialState::DATA_RECIEVED) { // get the neext character in the serial buffer c = this->getChar(); // only execute this if the startMarker has been received // if the incoming character is the endMarker clean up and set the flags if (this->state == SerialState::RECIEVE_IN_PROGRESS) { if (c == endMarker) { data[ndx] = '\0'; // terminate the string ndx = 0; this->state = SerialState::DATA_RECIEVED; } // if the incoming character is not the endMarker else { // add it to the data array data[ndx] = c; ndx++; // increment the data array index // if the index is greater than the maximum data array size, // keep overwriting the last element until the endMarker is received. if (ndx >= SERIAL_BUFFER_SIZE) { ndx = SERIAL_BUFFER_SIZE - 1; } } } // if the incoming character is the startMarker, set the recvInProgress flag else if (c == startMarker) { this->state = SerialState::RECIEVE_IN_PROGRESS; } } } template void Message::parseData() { // split the data into its parts this->populatedArgs = 0; // reset the populated args counter char * indx; // this is used by strtok() as an index int i = 0; indx = strtok(temp_data, ","); // get the first part - the string while(indx != NULL){ this->args[i] = atoi(indx); populatedArgs++; i++; indx = strtok(NULL, ","); // this continues where the previous call left off } } template void Message::Update(){ readSerial(); if (this->state == SerialState::DATA_RECIEVED) { strcpy(temp_data, data); // this temporary copy is necessary to protect the original data // because strtok() used in parseData() replaces the commas with \0 parseData(); this->state = SerialState::NEW_DATA; } } template bool Message::IsNewData(){ return this->state == SerialState::NEW_DATA; } template void Message::ClearNewData(){ this->state = SerialState::IDLE; } template int32_t * Message::GetArgs(){ return args; } template uint32_t Message::GetArgsLength(){ return MAX_ARGS; } template uint32_t Message::GetPopulatedArgs(){ return populatedArgs; }