Library for Modtronix NZ32 STM32 boards, like the NZ32-SC151, NZ32-SB072, NZ32-SE411 and others
Diff: mx_cmd_buffer.h
- Revision:
- 4:43abdd8eda40
- Child:
- 7:709130701ac7
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mx_cmd_buffer.h Sun Aug 30 09:29:08 2015 +1000 @@ -0,0 +1,639 @@ +/** + * File: mx_ascii_cmd_buffer.h + * + * Author: Modtronix Engineering - www.modtronix.com + * + * Description: + * + * Software License Agreement: + * This software has been written or modified by Modtronix Engineering. The code + * may be modified and can be used free of charge for commercial and non commercial + * applications. If this is modified software, any license conditions from original + * software also apply. Any redistribution must include reference to 'Modtronix + * Engineering' and web link(www.modtronix.com) in the file header. + * + * THIS SOFTWARE IS PROVIDED IN AN 'AS IS' CONDITION. NO WARRANTIES, WHETHER EXPRESS, + * IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. THE + * COMPANY SHALL NOT, IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL OR + * CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER. + */ +#ifndef SRC_MX_CMD_BUFFER_H_ +#define SRC_MX_CMD_BUFFER_H_ + +// This file contains an ASCII command buffer. It is used to store ASCII commands. +// Each command ends with a ';' character. CR(0x0a='\r') and LF(0x0d='\n') are converted to ';' characters. +// A ASCII command is an ASCII Formatted String, with Escape Sequences (Control Characters). +// It uses 2 upper case characters to represent a single hex character. +// Strings must be enclosed within single quotation marks('). +// Lower case characters 'a' to 'z' are used to represent "Control characters". It has the following format: +// HH = Two upper case characters representing a single byte (hex value). +// XHH = Same as HH +// NDD = Decimal number. For example "N100" = decimal 100 t0='500' +// c = Lower case character 'a' to 'z' represents a "control character". +// s, p = 's' an 'p' control characters represent the start and stop of a message. +// ^^ = Two "escape characters" represents a single '^' character. +// ^x = Use this format to represent a "control character" that is not lower. Lower case characters do not have +// to be escaped. Currently not used, but reserved for future use! +// ' = Enclose string with single quotes. A double ' character will not end the string, but represents a +// single ' character. +// +// Some Examples: +// 30A8'abc' = 2 bytes (0x30, 0xA8), and 3 characters = 'abc' +// 50800C'Hello'0A'World' = 3 bytes, 5 characters('Hello'), 1 byte, 5 characters('World') +// 5588; + +#include "mx_buffer_base.h" +#include "mx_circular_buffer.h" + +//Defines for MXH_DEBUG - debugging for include file +#define DEBUG_ENABLE_MX_CMD_BUFFER 1 +#define DEBUG_ENABLE_INFO_MX_CMD_BUFFER 0 + +#if !defined(MXH_DEBUG) +#if (DEBUG_ENABLE_MX_CMD_BUFFER == 1) + #define MXH_DEBUG MX_DEBUG + #if (DEBUG_ENABLE_INFO_MX_CMD_BUFFER == 1) + #define MXH_DEBUG_INFO MX_DEBUG + #else + #define MXH_DEBUG_INFO(format, args...) ((void)0) + #endif +#else + #define MXH_DEBUG(format, args...) ((void)0) + #define MXH_DEBUG_INFO(format, args...) ((void)0) +#endif // #if (DEBUG_ENABLE_MX_CMD_BUFFER == 1) +#endif // #if !defined(MXH_DEBUG) + + +/** Templated Circular buffer class + */ +template<uint32_t BufferSize, uint8_t Commands = 16, typename CounterType = uint32_t> +class MxCmdBuffer : public MxBuffer { +public: + MxCmdBuffer() : _head(0), _tail(0), _headStartOfNxtCmd(0), _full(false), + _errBufFull(0), _dontSaveCurrentCommand(0), flags(0) + { + //flags.Val = 0; + } + + ~MxCmdBuffer() { + } + + /** Adds a byte to the current command in the buffer. This function checks for the "End of Command" character, + * and if found, increments the "command count" register indicating how many commands this buffer has. + * + * If the buffer becomes full before the "End of Command" character is reached, all bytes added for current + * command are removed. And, all following bytes added until the next "End of Command" character will be ignored. + * + * The checkBufferFullError() function can be used to check if this function caused an error, and command was + * not added to buffer. + * + * @param data Data to be pushed to the buffer + * + * @return true if character added to buffer, else false. Important to note that all characters added to + * current command CAN BE REMOVED if buffer gets full before "End of Command" added to buffer! + */ + bool put(const uint8_t& data) { + uint8_t c = data; + + //Check if buffer full! If so: + // - All data for this command added till now is lost + // - All future data added for this command is ignored, until next "End of command" character is received + if (isFull() && (_dontSaveCurrentCommand==false)) { + _dontSaveCurrentCommand = true; + _errBufFull = true; + MXH_DEBUG("\r\nBuffer full, cmd LOST!"); + + //Remove all character added for this command. Restore head to last "End of Command" pointer + if (cmdEndsBuf.isEmpty() == false) { + //Restore head to byte following loast "End of Command" pointer + _head = ((cmdEndsBuf.peekLastAdded()+1)%BufferSize); + } + //If no commands in buffer, set head=tail + else { + _head = _tail; + _full = false; + } + } + + //Check if "End of Command" byte. A command string is terminated with a ';', CR(0x0a='\r') or LF(0x0d='\n') character + if ((c == ';') || (c == 0x0d) || (c == 0x0a)) { + if (flags.bits.replaceCrLfWithEoc) { + c = ';'; //Change to "end of command" character + } + + if (_dontSaveCurrentCommand == true) { + _dontSaveCurrentCommand = false; + _headStartOfNxtCmd = _head; + return false; //Nothing added + } + else { + //If multiple "end of command" characters received, ignore them. + if (_headStartOfNxtCmd == _head) { + //Nothing to do, exit + MXH_DEBUG_INFO("\r\nMultiple EOC"); + return false; //Nothing added + } + + cmdEndsBuf.put(_head); //Add pointer to "end of command" character + //End of command character will be added to buffer below at current _head pointer + MXH_DEBUG_INFO("\r\nAdded Cmd, EOC=%d", _head); + + //Save start of next command = byte following current _head + _headStartOfNxtCmd = ((_head+1)%BufferSize); + MXH_DEBUG_INFO(" Nxt=%d", _headStartOfNxtCmd); + } + } + + if (_dontSaveCurrentCommand) { + return false; + } + + //Add byte to buffer + _pool[_head++] = c; + _head %= BufferSize; + if (_head == _tail) { + _full = true; + } + + return true; + } + + /** Adds given NULL terminated string to the buffer. This function checks for the "End of Command" character, + * and if found, increments the "command count" register indicating how many commands this buffer has. + * + * If the buffer becomes full before the "End of Command" character is reached, all bytes added for current + * command are removed. And, all following bytes added until the next "End of Command" character will be ignored. + * + * The checkBufferFullError() function can be used to check if this function caused an error, and command was + * not added to buffer. + * + * @param buf Source buffer containing array to add to buffer + * @param bufSize Size of array to add to buffer + * + * @return Returns true if something added to buffer, else false. Important to note that all characters added to + * current command CAN BE REMOVED if buffer gets full before "End of Command" added to buffer! + */ + bool put(const char* str) { + bool retVal = 0; + + //DO NOT DO this check here! This check MUST be done by put() function, because if buffer becomes full before + //"End of Command" character received, the put() function will remove all current command characters added! + //if (isFull() == true) { + // return false; + //} + + //Add whole string to buffer. DO NOT check isFull() in next line, MUST be checked in put() below!!!! + while((*str != 0) /*&& (isFull()==false)*/) { + retVal = retVal | put((uint8_t)(*str++)); + } + return retVal; + } + + + /** Adds given array to the buffer. This function checks for the "End of Command" character, + * and if found, increments the "command count" register indicating how many commands this buffer has. + * + * If the buffer becomes full before the "End of Command" character is reached, all bytes added for current + * command are removed. And, all following bytes added until the next "End of Command" character will be ignored. + * + * The checkBufferFullError() function can be used to check if this function caused an error, and command was + * not added to buffer. + + * @param buf Source buffer containing array to add to buffer + * @param bufSize Size of array to add to buffer + * + * @return Returns true if something added to buffer, else false. Important to note that all characters added to + * current command CAN BE REMOVED if buffer gets full before "End of Command" added to buffer! + */ + bool putArray(uint8_t* buf, uint16_t bufSize) { + bool retVal = 0; + int i; + + //DO NOT DO this check here! This check MUST be done by put() function, because if buffer becomes full before + //"End of Command" character received, the put() function will remove all current command characters added! + //if (getFree() < bufSize) { + // return 0; + //} + + for(i=0; i<bufSize; i++) { + retVal = retVal | put(buf[i]); + } + return retVal; + } + + /** Gets and object from the buffer. Ensure buffer is NOT empty before calling this function! + * + * @return Read data + */ + uint8_t get() { + if (!isEmpty()) { + uint8_t retData; + retData = _pool[_tail++]; + _tail %= BufferSize; + _full = false; + return retData; + } + return 0; + } + + /** Gets and object from the buffer. Returns true if OK, else false + * + * @param data Variable to put read data into + * @return True if the buffer is not empty and data contains a transaction, false otherwise + */ + bool getAndCheck(uint8_t& data) { + if (!isEmpty()) { + data = _pool[_tail++]; + _tail %= BufferSize; + _full = false; + return true; + } + return false; + } + + + /** Gets and object from the buffer, but do NOT remove it. Ensure buffer is NOT empty before calling + * this function! If buffer is empty, will return an undefined value. + * + * @return Read data + */ + uint8_t peek() { + return _pool[_tail]; + } + + /** Gets an object from the buffer at given offset, but do NOT remove it. Given offset is a value from + * 0 to n. Ensure buffer has as many objects as the offset requested! For example, if buffer has 5 objects + * available, given offset can be a value from 0 to 4. + * + * @param offset Offset of requested object. A value from 0-n, where (n+1) = available objects = getAvailable() + * @return Object at given offset + */ + uint8_t peekAt(CounterType offset) { + return _pool[(offset+_tail)%BufferSize]; + } + + /** Gets the last object added to the buffer, but do NOT remove it. + * + * @return Object at given offset + */ + uint8_t peekLastAdded() { + return _pool[(_head-1)%BufferSize]; + } + + /** Gets and array of given size, and write it to given buffer. + * Nothing is removed from buffer + * + * @param buf Destination buffer to array to + * @param bufSize Maximum size to write to destination buffer + * @param lenReq Requested length + * + * + * @return Number of bytes written to given buffer + */ + uint16_t peekArray(uint8_t* buf, uint16_t bufSize, CounterType lenReq) { + uint16_t lenWritten=0; + CounterType currTail; + currTail = _tail; + + //Some checks + if (isEmpty() || (bufSize==0) || (lenReq==0)) { + return 0; + } + + do { + buf[lenWritten++] = _pool[currTail]; + currTail = ((currTail+1)%BufferSize); + } while((lenWritten<bufSize) && (lenWritten<lenReq)); + + + return lenWritten; + } + + /** Check if the buffer has a complete command + * + * @return True if the buffer has a command, false if not + */ + bool hasCommand() { + return !cmdEndsBuf.isEmpty(); + } + + /** Get length of next command in buffer, excluding "end of command" character! For example, + * the command "r=100;" will return 5. + * @return Length of next command in buffer + */ + uint8_t getCommandLength() { + CounterType offsetEOC; + + if (cmdEndsBuf.isEmpty()) { + return 0; + } + + //Get offset of "end of command" character of current command in buffer + offsetEOC = cmdEndsBuf.peek(); + + return ((offsetEOC-_tail) % BufferSize); + } + + /** Get number of commands waiting in buffer + * @return Number of commands waiting in buffer + */ + uint8_t getCommandsAvailable() { + return cmdEndsBuf.getAvailable(); + } + + /** If current command has "name=value" format, the 'name' part is returned. + * Else, the whole command is returned(excluding a possible trailing '=' character). + * Returned string is NULL terminated by default. + * Nothing is removed from the buffer! + * + * If true is returned in the "isNameValue" parameter, this is a "name=value" command, with a name part of at + * lease 1 character long. The offset of the 'value' part will be the returned + * + * If false is returned in the "isNameValue" parameter, there is no 'value' part of at least 1 character. There could however + * still be a '=' character with no 'value' following it. Any possible trailing '=' character is removed string returned in 'buf'. + * + * @param buf Destination buffer to write string to + * + * @param bufSize Maximum size to write to destination buffer + * + * @param isNameValue Returns true if a '=' character was found -AND- at least 1 char following it. This indicates + * this is a "name=value" command, and has a value part(of at least 1 character) following the '='. + * First character of 'value' is value returned by this function + 1! + * + * @return Size in bytes of string returned in 'buf' parameter(is 'name' if isNameValue=true). + * If true is returned in 'isNameValue', 'buf' contains the 'name' part of a 'name=value' command. This returned offset points to '='. + * If false is returned in 'isNameValue', 'buf' contains the whole command string(excluding possible trailing '=' character). + */ + uint16_t getCommandName(uint8_t* buf, uint16_t bufSize, bool& isNameValue, bool nullTerminate = true) { + CounterType offsetEOC; + CounterType currTail; + uint16_t nameLen = 0; + isNameValue=false; + + //Some checks + if (cmdEndsBuf.isEmpty() || (bufSize==0) || (_pool[_tail]=='=')) { + return 0; + } + offsetEOC = cmdEndsBuf.peek(); + currTail = _tail; + + //Copy current command string to given buffer, until '=' reached + do { + buf[nameLen++] = _pool[currTail]; + currTail = ((currTail+1)%BufferSize); + //If next character is '=' + if(_pool[currTail] == '=') { + //Check at least 1 character following '=' + //Get pointer of character following '=', and ensure it is not "end of command"(offsetEOC). Meaning there is + //still at least 1 more character following '=' + if(((currTail+1)%BufferSize) != offsetEOC) { + isNameValue = true; + } + break; + } + } while((currTail!=offsetEOC) && (nameLen<bufSize)); + + + if (nullTerminate) { + if(nameLen<bufSize) { + buf[nameLen] = 0; + } + buf[bufSize-1] = 0; //Null terminate last position of buffer in case it filled up + } + + return nameLen; + } + + /** For commands with "name=value" format, returns the 'value' part. + * Returned string is NULL terminated by default. + * Nothing is removed from the buffer! + * + * To optimize this function, call getCommandName() before calling this function. The getCommandName() return value(+1) + * can be used as 'offset' parameter to this function. This will save this function from searching for '=' character. + * + * + * @param buf Destination buffer to write string to + * @param bufSize Maximum size to write to destination buffer + * @param offset Gives offset of value string if known. This can be value(+1) returned by + * getCommandName() function. Use -1 if unknown. + * + * @return Size in bytes of returned string + */ + uint16_t getCommandValue(uint8_t* buf, uint16_t bufSize, uint16_t offset = -1, bool nullTerminate = true) { + CounterType offsetEOC; + CounterType currTail; + uint16_t valueLen = 0; + bool foundEq=false; //'=' character found + + //Some checks + if (cmdEndsBuf.isEmpty() || (bufSize==0)) { + return 0; + } + offsetEOC = cmdEndsBuf.peek(); //offset of "end of command" character + + currTail = _tail; + + //If offset was given, it will point to first character of value string + if (offset != -1) { + //Check offset point somewhere inside current command! Where ((offsetEOC-_tail) % BufferSize) = command length + if (offset < ((offsetEOC-_tail)%BufferSize)) { + currTail = ((currTail + offset)%BufferSize); //Add offset to tail + + //If given offset was for first character of 'value', it will NOT point to '='. It will be next character. + if(_pool[currTail] != '=') { + //Points to character after '=', indicate '=' has already been found. + foundEq=true; + } + //ELSE, assume offset for for '=' character. It will be found in step below + } + else { + //currTail = _tail; //Ignore given offset, and search whole command for '=' + MXH_DEBUG("\r\ngetCommandValue() Offset error!"); + } + } + + do { + if (foundEq) { + buf[valueLen++] = _pool[currTail]; + } + else { + if(_pool[currTail] == '=') { + foundEq=true; + } + } + currTail = ((currTail+1)%BufferSize); + } while((currTail != offsetEOC) && (valueLen<bufSize)); + + if (nullTerminate) { + if(valueLen<bufSize) { + buf[valueLen] = 0; + } + buf[bufSize-1] = 0; //Null terminate last position of buffer in case it filled up + } + + return valueLen; + } + + /** Search current command for given character + * + * @param offset Returns offset of found character. If NULL, this parameter is not used. + * + * @return Returns true if found, else false + */ + bool search(uint8_t c, uint16_t* offsetFound) { + CounterType offsetEOF; + CounterType currTail; + + //Get command length + if (cmdEndsBuf.isEmpty()) { + return false; + } + offsetEOF = cmdEndsBuf.peek(); + currTail = _tail; + do { + if(_pool[currTail] == c) { + if (offsetFound!=NULL) { + *offsetFound = (uint16_t)currTail; + } + return true; + } + currTail = ((currTail+1)%BufferSize); + } while(currTail != offsetEOF); + +// CounterType i; +// CounterType cmdLen; +// cmdLen = (offsetEOF-_tail) % BufferSize; +// for (i=0; i<cmdLen; i++) { +// if(_pool[(i+_tail)%BufferSize] == c) { +// //if(peekAt(i)==c) { +// offsetFound = (uint16_t)i; +// return true; +// } +// } + return false; + } + + /** Check if the buffer is empty + * + * @return True if the buffer is empty, false if not + */ + bool isEmpty() { + return (_head == _tail) && !_full; + } + + /** Check if the buffer is full + * + * @return True if the buffer is full, false if not + */ + bool isFull() { + return _full; + } + + /** Get number of available bytes in buffer + * @return Number of available bytes in buffer + */ + CounterType getAvailable() { + CounterType avail; + if (isEmpty()) { + return 0; + } + avail = _head - _tail; + avail %= BufferSize; + return avail; + } + + + /** Get number of free bytes in buffer available for writing data to. + * @return Number of free bytes in buffer available for writing data to. + */ + CounterType getFree() { + CounterType free; + //Full + if (_full==true) { + return 0; + } + //Empty + if(_head == _tail) { + return BufferSize; + } + free = _tail - _head; + free %= BufferSize; + return free; + } + + /** Replaces any LF or CR characters with an "End of Command" character = ';' + */ + void enableReplaceCrLfWithEoc() { + flags.bits.replaceCrLfWithEoc = true; + } + + /** Do NOT replaces LF and CR characters with an "End of Command" character = ';' + */ + void disableReplaceCrLfWithEoc() { + flags.bits.replaceCrLfWithEoc = false; + } + + /** Check if an overwrite error occurred. It resets the error flag if it was set + * @return True if overwrite error occurred since last time this function was called, else false + */ + bool checkBufferFullError() { + bool retVal = _errBufFull; + _errBufFull = false; + return retVal; + } + + /** Reset the buffer + */ + void reset() { + _head = 0; + _headStartOfNxtCmd = 0; + _tail = 0; + _full = false; + _errBufFull = false; + _dontSaveCurrentCommand = false; + } + + /** Remove a command from buffer + */ + void removeCommand() { + if (cmdEndsBuf.isEmpty()) { + return; + } + + //MXH_DEBUG("\r\nRemoving Cmd"); + + //Set tail = "end of command" character + 1. This is first character of next command + _tail = (cmdEndsBuf.get()+1) % BufferSize; + _full = false; + } + +private: + uint8_t _pool[BufferSize]; + volatile CounterType _head; + volatile CounterType _tail; + //_head position of first character of next command = position of first byte after last "end of command" character was added + volatile CounterType _headStartOfNxtCmd; + volatile bool _full; + volatile bool _errBufFull; + volatile bool _dontSaveCurrentCommand; + + union Flags { + struct { + uint8_t replaceCrLfWithEoc : 1; //Replace CR and LF with "End of Command" character = ';' + } bits; + uint8_t Val; + //Union constructor. Used in initialization list of this class. + Flags(uint8_t v) : Val(v) {} + } flags; + +public: + //Buffer for storing "end of command" locations + MxCircularBuffer <uint16_t, Commands, CounterType> cmdEndsBuf; + //MxCircularBuffer <uint16_t, Commands, uint16_t> cmdEndsBuf; //Creates larger code +}; + + + + +#endif /* SRC_MX_CMD_BUFFER_H_ */