CmdMessenger.cpp
Go to the documentation of this file.
00001 /*
00002   CmdMessenger - library that provides command based messaging
00003   
00004         Permission is hereby granted, free of charge, to any person obtaining
00005         a copy of this software and associated documentation files (the
00006         "Software"), to deal in the Software without restriction, including
00007         without limitation the rights to use, copy, modify, merge, publish,
00008         distribute, sublicense, and/or sell copies of the Software, and to
00009         permit persons to whom the Software is furnished to do so, subject to
00010         the following conditions:
00011 
00012         The above copyright notice and this permission notice shall be
00013         included in all copies or substantial portions of the Software.
00014 
00015         THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00016         EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00017         MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
00018         NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
00019         LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
00020         OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
00021         WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00022 
00023     Initial Messenger Library - Thomas Ouellet Fredericks.
00024     CmdMessenger Version 1    - Neil Dudman.
00025     CmdMessenger Version 2    - Dreamcat4.
00026         CmdMessenger Version 3    - Thijs Elenbaas.
00027           3.6  - Fixes
00028                - Better compatibility between platforms
00029                    - Unit tests
00030           3.5  - Fixes, speed improvements for Teensy
00031           3.4  - Internal update
00032           3.3  - Fixed warnings
00033                - Some code optimization
00034           3.2  - Small fixes and sending long argument support
00035           3.1  - Added examples
00036           3.0  - Bugfixes on 2.2
00037                - Wait for acknowlegde
00038                    - Sending of common type arguments (float, int, char)
00039                - Multi-argument commands
00040            - Escaping of special characters
00041            - Sending of binary data of any type (uses escaping)
00042  */
00043 
00044 extern "C" {
00045 #include <stdlib.h>
00046 #include <stdarg.h>
00047 }
00048 #include <stdio.h>
00049 #include <CmdMessenger.h>
00050 
00051 #define _CMDMESSENGER_VERSION 3_6 // software version of this library
00052 
00053 // **** Initialization **** 
00054 
00058 CmdMessenger::CmdMessenger(Stream &ccomms, const char fld_separator, const char cmd_separator, const char esc_character)
00059 {
00060     init(ccomms,fld_separator,cmd_separator, esc_character);
00061 }
00062 
00066 void CmdMessenger::init(Stream &ccomms, const char fld_separator, const char cmd_separator, const char esc_character)
00067 {
00068     default_callback = NULL;
00069     comms            = &ccomms;
00070     print_newlines    = false;
00071     field_separator   = fld_separator;
00072     command_separator = cmd_separator;
00073     escape_character  = esc_character;
00074     bufferLength      = MESSENGERBUFFERSIZE;
00075     bufferLastIndex   = MESSENGERBUFFERSIZE -1;
00076     reset();
00077 
00078     default_callback  = NULL;
00079     for (int i = 0; i < MAXCALLBACKS; i++)
00080         callbackList[i] = NULL;
00081 
00082     pauseProcessing   = false;
00083 }
00084 
00088 void CmdMessenger::reset()
00089 {
00090     bufferIndex = 0;
00091     current     = NULL;
00092     last        = NULL;
00093     dumped      = true;
00094 }
00095 
00099 void CmdMessenger::printLfCr(bool addNewLine)
00100 {
00101     print_newlines = addNewLine;
00102 }
00103 
00107 void CmdMessenger::attach(messengerCallbackFunction newFunction)
00108 {
00109     default_callback = newFunction;
00110 }
00111 
00115 void CmdMessenger::attach(byte msgId, messengerCallbackFunction newFunction)
00116 {
00117     if (msgId >= 0 && msgId < MAXCALLBACKS)
00118         callbackList[msgId] = newFunction;
00119 }
00120 
00121 // **** Command processing ****
00122 
00126 void CmdMessenger::feedinSerialData()
00127 {
00128     while ( !pauseProcessing && comms->available() )
00129         {          
00130                 // The Stream class has a readBytes() function that reads many bytes at once. On Teensy 2.0 and 3.0, readBytes() is optimized. 
00131                 // Benchmarks about the incredible difference it makes: http://www.pjrc.com/teensy/benchmark_usb_serial_receive.html
00132 
00133                 size_t bytesAvailable = min(comms->available(),MAXSTREAMBUFFERSIZE);
00134                 comms->readBytes(streamBuffer, bytesAvailable); 
00135                 
00136                 // Process the bytes in the stream buffer, and handles dispatches callbacks, if commands are received
00137                 for (size_t byteNo = 0; byteNo < bytesAvailable ; byteNo++) 
00138                 {   
00139                     int messageState = processLine(streamBuffer[byteNo]);
00140 
00141                         // If waiting for acknowledge command
00142                         if ( messageState == kEndOfMessage ) 
00143                         {
00144                                 handleMessage();
00145                         }
00146                 }
00147         }
00148 }
00149 
00153 uint8_t CmdMessenger::processLine(char serialChar)
00154 {
00155     messageState = kProccesingMessage;
00156     //char serialChar = (char)serialByte;
00157     bool escaped = isEscaped(&serialChar,escape_character,&CmdlastChar);
00158         if((serialChar == command_separator) && !escaped) {
00159                 commandBuffer[bufferIndex]=0;
00160                 if(bufferIndex > 0) {
00161                         messageState = kEndOfMessage;
00162                         current = commandBuffer;
00163                         CmdlastChar='\0';
00164                 }
00165                 reset();
00166         } else {
00167                 commandBuffer[bufferIndex]=serialChar;
00168                 bufferIndex++;
00169                 if (bufferIndex >= bufferLastIndex) reset();
00170         }
00171     return messageState;
00172 }
00173 
00177 void CmdMessenger::handleMessage()
00178 {
00179     lastCommandId = readInt16Arg();
00180     // if command attached, we will call it
00181     if (lastCommandId >= 0 && lastCommandId < MAXCALLBACKS && ArgOk && callbackList[lastCommandId] != NULL)
00182         (*callbackList[lastCommandId])();
00183     else // If command not attached, call default callback (if attached)
00184         if (default_callback!=NULL) (*default_callback)();
00185 }
00186 
00190 bool CmdMessenger::blockedTillReply(unsigned long timeout, int ackCmdId)
00191 {
00192     unsigned long time  = millis();
00193     unsigned long start = time;
00194     bool receivedAck    = false;
00195     while( (time - start ) < timeout && !receivedAck) {
00196         time = millis();
00197         receivedAck = CheckForAck(ackCmdId);
00198     }
00199     return receivedAck;
00200 }
00201 
00205 bool CmdMessenger::CheckForAck(int AckCommand)
00206 {
00207     while (  comms->available() ) {
00208                 //Processes a byte and determines if an acknowlegde has come in
00209                 int messageState = processLine(comms->read());
00210                 if ( messageState == kEndOfMessage ) {
00211                         int id = readInt16Arg();
00212                         if (AckCommand==id && ArgOk) {
00213                                 return true;
00214                         } else {
00215                                 return false;
00216                         }
00217                 }
00218                 return false;
00219     }
00220     return false;
00221 }
00222 
00226 bool CmdMessenger::next()
00227 {
00228     char * temppointer= NULL;
00229     // Currently, cmd messenger only supports 1 char for the field seperator
00230     switch (messageState) {
00231     case kProccesingMessage:
00232         return false;
00233     case kEndOfMessage:
00234         temppointer = commandBuffer;
00235         messageState = kProcessingArguments;
00236     default:
00237         if (dumped)
00238             current = split_r(temppointer,field_separator,&last);
00239         if (current != NULL) {
00240             dumped = true;
00241             return true;
00242         }
00243     }
00244     return false;
00245 }
00246 
00250 bool CmdMessenger::available()
00251 {
00252     return next();
00253 }
00254 
00258 bool CmdMessenger::isArgOk ()
00259 {
00260         return ArgOk;
00261 }
00262 
00266 uint8_t CmdMessenger::CommandID()
00267 {
00268     return lastCommandId;
00269 }
00270 
00271 // ****  Command sending ****
00272 
00276 void CmdMessenger::sendCmdStart(int cmdId)
00277 {
00278     if (!startCommand) {
00279                 startCommand   = true;
00280                 pauseProcessing = true;
00281                 comms->print(cmdId);
00282         }
00283 }
00284 
00288 void CmdMessenger::sendCmdEscArg(char* arg)
00289 {
00290     if (startCommand) {
00291         comms->print(field_separator);
00292         printEsc(arg);
00293     }
00294 }
00295 
00300 void CmdMessenger::sendCmdfArg(char *fmt, ...)
00301 {
00302         const int maxMessageSize = 128;
00303     if (startCommand) {
00304         char msg[maxMessageSize];
00305         va_list args;
00306         va_start (args, fmt );
00307         vsnprintf(msg, maxMessageSize, fmt, args);
00308         va_end (args);
00309 
00310         comms->print(field_separator);
00311         comms->print(msg);
00312     }
00313 }
00314 
00319 void CmdMessenger::sendCmdSciArg (double arg, int n)
00320 {
00321 if (startCommand)
00322   {
00323         comms->print (field_separator);
00324         printSci (arg, n);
00325   }
00326 }
00327 
00331 bool CmdMessenger::sendCmdEnd(bool reqAc, int ackCmdId, int timeout)
00332 {
00333     bool ackReply = false;
00334     if (startCommand) {
00335         comms->print(command_separator);
00336         if(print_newlines)
00337             comms->println(); // should append BOTH \r\n
00338         if (reqAc) {
00339             ackReply = blockedTillReply(timeout, ackCmdId);
00340         }
00341     }
00342     pauseProcessing = false;
00343     startCommand   = false;
00344     return ackReply;
00345 }
00346 
00350 bool CmdMessenger::sendCmd (int cmdId, bool reqAc, int ackCmdId)
00351 {
00352     if (!startCommand) {
00353         sendCmdStart (cmdId);
00354         return sendCmdEnd (reqAc, ackCmdId, DEFAULT_TIMEOUT);
00355     }
00356     return false;
00357 }
00358 
00362 bool CmdMessenger::sendCmd (int cmdId)
00363 {
00364     if (!startCommand) {
00365         sendCmdStart (cmdId);
00366         return sendCmdEnd (false, 1, DEFAULT_TIMEOUT);
00367     }
00368     return false;
00369 }
00370 
00371 // **** Command receiving ****
00372 
00376 int CmdMessenger::findNext(char *str, char delim)
00377 {
00378     int pos = 0;
00379     bool escaped = false;
00380         bool EOL = false;
00381     ArglastChar = '\0';
00382     while (true) {
00383         escaped = isEscaped(str,escape_character,&ArglastChar);
00384                 EOL = (*str == '\0' && !escaped);
00385                 if (EOL) { 
00386                         return pos;
00387                 }
00388         if (*str==field_separator && !escaped) {
00389             return pos;
00390         } else {
00391             str++;
00392             pos++;
00393         }
00394     }
00395     return pos;
00396 }
00397 
00401 int16_t CmdMessenger::readInt16Arg()
00402 {
00403     if (next()) {
00404         dumped = true;
00405                 ArgOk  = true;
00406         return atoi(current);
00407     }
00408         ArgOk  = false;
00409     return 0;
00410 }
00411 
00415 int32_t CmdMessenger::readInt32Arg()
00416 {
00417     if (next()) {
00418         dumped = true;
00419                 ArgOk  = true;
00420         return atol(current);
00421     }
00422         ArgOk  = false;
00423     return 0L;
00424 }
00425 
00429 bool CmdMessenger::readBoolArg()
00430 {
00431         return (readInt16Arg()!=0)?true:false;   
00432 }
00433 
00437 char CmdMessenger::readCharArg()
00438 {
00439     if (next()) {
00440         dumped = true;
00441                 ArgOk  = true;
00442         return current[0];
00443     }
00444         ArgOk  = false;
00445     return 0;
00446 }
00447 
00451 float CmdMessenger::readFloatArg()
00452 {
00453     if (next()) {
00454         dumped = true;
00455                 ArgOk  = true;
00456         //return atof(current);
00457                 return strtod(current,NULL);
00458     }
00459         ArgOk  = false;
00460     return 0;
00461 }
00462 
00466 double CmdMessenger::readDoubleArg()
00467 {
00468     if (next()) {
00469         dumped = true;
00470                 ArgOk  = true;
00471         return strtod(current,NULL);
00472     }
00473         ArgOk  = false;
00474     return 0;
00475 }
00476 
00481 char* CmdMessenger::readStringArg()
00482 {
00483     if (next()) {
00484         dumped = true;
00485                 ArgOk  = true;
00486         return current;
00487     }
00488         ArgOk  = false;
00489     return '\0';
00490 }
00491 
00496 void CmdMessenger::copyStringArg(char *string, uint8_t size)
00497 {
00498     if (next()) {
00499         dumped = true;
00500                 ArgOk  = true;
00501         strlcpy(string,current,size);
00502     } else {
00503                 ArgOk  = false;
00504         if ( size ) string[0] = '\0';
00505     }
00506 }
00507 
00511 uint8_t CmdMessenger::compareStringArg(char *string)
00512 {
00513     if (next()) {
00514         if ( strcmp(string,current) == 0 ) {
00515             dumped = true;
00516                         ArgOk  = true;
00517             return 1;
00518         } else {
00519                         ArgOk  = false;
00520             return 0;
00521         }
00522     }
00523         return 0;
00524 }
00525 
00526 // **** Escaping tools ****
00527 
00532 void CmdMessenger::unescape(char *fromChar)
00533 {
00534     // Move unescaped characters right
00535     char *toChar = fromChar;
00536     while (*fromChar != '\0') {
00537         if (*fromChar==escape_character) {
00538             fromChar++;
00539         }
00540         *toChar++=*fromChar++;
00541     }
00542     // Pad string with \0 if string was shortened
00543     for (; toChar<fromChar; toChar++) {
00544         *toChar='\0';
00545     }
00546 }
00547 
00552 char* CmdMessenger::split_r(char *str, const char delim, char **nextp)
00553 {
00554     char *ret;
00555     // if input null, this is not the first call, use the nextp pointer instead
00556     if (str == NULL) {
00557         str = *nextp;
00558     }
00559     // Strip leading delimiters
00560     while (findNext(str, delim)==0 && *str) {
00561         str++;
00562     }
00563     // If this is a \0 char, return null
00564     if (*str == '\0') {
00565         return NULL;
00566     }
00567     // Set start of return pointer to this position
00568     ret = str;
00569     // Find next delimiter
00570     str += findNext(str, delim);
00571     // and exchange this for a a \0 char. This will terminate the char
00572     if (*str) {
00573         *str++ = '\0';
00574     }
00575     // Set the next pointer to this char
00576     *nextp = str;
00577     // return current pointer
00578     return ret;
00579 }
00580 
00584 bool CmdMessenger::isEscaped(char *currChar, const char escapeChar, char *lastChar)
00585 {
00586     bool escaped;
00587     escaped   = (*lastChar==escapeChar);
00588     *lastChar = *currChar;
00589 
00590     // special case: the escape char has been escaped:
00591     if (*lastChar == escape_character && escaped) {
00592         *lastChar = '\0';
00593     }
00594     return escaped;
00595 }
00596 
00600 void CmdMessenger::printEsc(char *str)
00601 {
00602     while (*str != '\0') {
00603         printEsc(*str++);
00604     }
00605 }
00606 
00610 void CmdMessenger::printEsc(char str)
00611 {
00612     if (str==field_separator || str==command_separator || str==escape_character || str=='\0') {
00613         comms->print(escape_character);
00614     }
00615     comms->print(str);
00616 }
00617 
00621 void CmdMessenger::printSci(double f, unsigned int digits)
00622 {
00623   // handle sign
00624   if (f < 0.0)
00625   {
00626     Serial.print('-');
00627     f = -f;
00628   } 
00629   
00630   // handle infinite values
00631   if (isinf(f))
00632   {
00633     Serial.print("INF");
00634     return;
00635   }
00636   // handle Not a Number
00637   if (isnan(f)) 
00638   {
00639     Serial.print("NaN");
00640     return;
00641   }
00642 
00643   // max digits
00644   if (digits > 6) digits = 6;
00645   long multiplier = pow(10, digits);     // fix int => long
00646 
00647   int exponent;
00648   if (abs(f) < 10.0) { 
00649         exponent = 0;
00650   }     else {
00651         exponent = int(log10(f));
00652   }
00653   float g = f / pow(10, exponent);
00654   if ((g < 1.0) && (g != 0.0))      
00655   {
00656     g *= 10;
00657     exponent--;
00658   }
00659  
00660   long whole = long(g);                     // single digit
00661   long part = long((g-whole)*multiplier+0.5);  // # digits
00662   // Check for rounding above .99:
00663   if (part == 100) {
00664           whole++;
00665           part = 0;
00666   }
00667   char format[16];
00668   sprintf(format, "%%ld.%%0%dldE%%+d", digits);
00669   char output[16];
00670   sprintf(output,format, whole, part, exponent);
00671   comms->print(output);
00672 }


ric_mc
Author(s): RoboTiCan
autogenerated on Thu Aug 27 2015 14:39:49