Parser for AT commands and similar protocols

Dependencies:   BufferedSerial

Dependents:   ESP8266 Final_Project_2 Final_Project CHEN_Final_Project

Files at this revision

API Documentation at this revision

Comitter:
geky
Date:
Mon Jul 20 21:28:39 2015 +0000
Parent:
8:91515b168c70
Child:
10:553f9ffaf657
Commit message:
Added direct printf/scanf methods as alternatives to the newline-based send/recv methods.

Changed in this revision

ATParser.cpp Show annotated file Show diff for this revision Revisions of this file
ATParser.h Show annotated file Show diff for this revision Revisions of this file
--- a/ATParser.cpp	Mon Jul 20 20:56:30 2015 +0000
+++ b/ATParser.cpp	Mon Jul 20 21:28:39 2015 +0000
@@ -60,10 +60,9 @@
 // read/write handling with timeouts
 int ATParser::write(const char *data, int size) {
     int i;
-    
     for (i = 0; i < size; i++) {
         if (putc(data[i]) < 0)
-            return i;
+            return -1;
     }
     
     return i;
@@ -71,12 +70,10 @@
 
 int ATParser::read(char *data, int size) {
     int i;
-    
     for (i = 0; i < size; i++) {
         int c = getc();
-        
         if (c < 0)
-            return i;
+            return -1;
             
         data[i] = c;
     }
@@ -85,10 +82,85 @@
 }
 
 
+// printf/scanf handling
+int ATParser::vprintf(const char *format, va_list args) {
+    if (vsprintf(_buffer, format, args) < 0)
+        return false;
+    
+    int i;
+    for (i = 0; _buffer[i]; i++) {
+        if (putc(_buffer[i]) < 0)
+            return -1;
+    }
+    
+    return i;
+}
+
+int ATParser::vscanf(const char *format, va_list args) {
+    // Since format is const, we need to copy it into our buffer to
+    // add the line's null terminator and clobber value-matches with asterisks.
+    //
+    // We just use the beginning of the buffer to avoid unnecessary allocations.
+    int i = 0;
+    int offset = 0;
+        
+    while (format[i]) {
+        if (format[i] == '%' && 
+            format[i+1] != '%' && 
+            format[i+1] != '*') {
+            _buffer[offset++] = '%';
+            _buffer[offset++] = '*';
+            i++;
+        } else {
+            _buffer[offset++] = format[i++];
+        }
+    }
+        
+    // Scanf has very poor support for catching errors
+    // fortunately, we can abuse the %n specifier to determine
+    // if the entire string was matched.
+    _buffer[offset++] = '%';
+    _buffer[offset++] = 'n';
+    _buffer[offset++] = 0;
+        
+    // To workaround scanf's lack of error reporting, we actually
+    // make two passes. One checks the validity with the modified
+    // format string that only stores the matched characters (%n). 
+    // The other reads in the actual matched values.
+    //
+    // We keep trying the match until we succeed or some other error
+    // derails us.
+    int j = 0;
+        
+    while (true) {
+        // Ran out of space
+        if (j+1 >= _buffer_size - offset)
+            return false;
+        
+        // Recieve next character
+        int c = getc();
+        if (c < 0)
+            return -1;
+                
+        _buffer[offset + j++] = c;
+        _buffer[offset + j] = 0;
+        
+        // Check for match
+        int count = -1;
+        sscanf(_buffer+offset, _buffer, &count);
+            
+        // We only succeed if all characters in the response are matched
+        if (count == j) {
+            // Store the found results
+            vsscanf(_buffer+offset, format, args);
+            return j;
+        }
+    }
+}
+
+
 // Command parsing with line handling
 bool ATParser::vsend(const char *command, va_list args) {
-    flush();
-    
     // Create and send command
     if (vsprintf(_buffer, command, args) < 0)
         return false;
@@ -105,7 +177,7 @@
     }
     
 #ifdef AT_ECHO
-    printf("AT> %s\r\n", _buffer);
+    ::printf("AT> %s\r\n", _buffer);
 #endif
         
     return true;
@@ -152,7 +224,11 @@
         // derails us.
         int j = 0;
         
-        while (j+1 < _buffer_size - offset) {
+        while (true) {
+            // Ran out of space
+            if (j+1 >= _buffer_size - offset)
+                return false;
+            
             // Recieve next character
             int c = getc();
             if (c < 0)
@@ -168,7 +244,7 @@
             // We only succeed if all characters in the response are matched
             if (count == j) {
 #ifdef AT_ECHO            
-                printf("AT= %s\r\n", _buffer+offset);
+                ::printf("AT= %s\r\n", _buffer+offset);
 #endif
                 // Reuse the front end of the buffer
                 memcpy(_buffer, response, i);
@@ -185,7 +261,7 @@
             // Clear the buffer when we hit a newline
             if (strcmp(&_buffer[offset + j-_delim_size], _delimiter) == 0) {
 #ifdef AT_ECHO            
-                printf("AT< %s", _buffer+offset);
+                ::printf("AT< %s", _buffer+offset);
 #endif
                 j = 0;
             }
@@ -197,6 +273,22 @@
 
 
 // Mapping to vararg functions
+int ATParser::printf(const char *format, ...) {
+    va_list args;
+    va_start(args, format);
+    int res = vprintf(format, args);
+    va_end(args);
+    return res;
+}
+
+int ATParser::scanf(const char *format, ...) {
+    va_list args;
+    va_start(args, format);
+    int res = vscanf(format, args);
+    va_end(args);
+    return res;
+}
+
 bool ATParser::send(const char *command, ...) {
     va_list args;
     va_start(args, command);
--- a/ATParser.h	Mon Jul 20 20:56:30 2015 +0000
+++ b/ATParser.h	Mon Jul 20 21:28:39 2015 +0000
@@ -25,22 +25,22 @@
 
  
 /**
- * Parser class for parsing AT commands
- *
- * Here are some examples:
- * @code
- * ATParser at = ATParser(serial, "\r\n");
- * int value;
- * char buffer[100];
- *
- * at.send("AT") && at.recv("OK");
- * at.send("AT+CWMODE=%d", 3) && at.recv("OK");
- * at.send("AT+CWMODE?") && at.recv("+CWMODE:%d\r\nOK", &value);
- * at.recv("+IPD,%d:", &value);
- * at.read(buffer, value);
- * at.recv("OK");
- * @endcode
- */
+* Parser class for parsing AT commands
+*
+* Here are some examples:
+* @code
+* ATParser at = ATParser(serial, "\r\n");
+* int value;
+* char buffer[100];
+*
+* at.send("AT") && at.recv("OK");
+* at.send("AT+CWMODE=%d", 3) && at.recv("OK");
+* at.send("AT+CWMODE?") && at.recv("+CWMODE:%d\r\nOK", &value);
+* at.recv("+IPD,%d:", &value);
+* at.read(buffer, value);
+* at.recv("OK");
+* @endcode
+*/
 class ATParser {
 private:
     // Serial information
@@ -101,7 +101,7 @@
     * Sends an AT command
     *
     * Sends a formatted command using printf style formatting
-    * @see printf
+    * @see ::printf
     *
     * @param command printf-like format string of command to send which 
     *                is appended with the specified delimiter
@@ -115,7 +115,7 @@
     * Recieve an AT response
     *
     * Recieves a formatted response using scanf style formatting
-    * @see scanf
+    * @see ::scanf
     *
     * Responses are parsed line at a time using the specified delimiter.
     * Any recieved data that does not match the response is ignored until
@@ -148,7 +148,7 @@
     *
     * @param data the array of bytes to write
     * @param size number of bytes to write
-    * @return number of bytes written
+    * @return number of bytes written or -1 on failure
     */
     int write(const char *data, int size);
     
@@ -157,11 +157,33 @@
     *
     * @param data the destination for the read bytes
     * @param size number of bytes to read
-    * @return number of bytes read
+    * @return number of bytes read or -1 on failure
     */
     int read(char *data, int size);
     
     /**
+    * Direct printf to underlying stream
+    * @see ::printf
+    *
+    * @param format format string to pass to printf
+    * @param ... arguments to printf
+    * @return number of bytes written or -1 on failure
+    */
+    int printf(const char *format, ...);
+    int vprintf(const char *format, va_list args);
+    
+    /**
+    * Direct scanf on underlying stream
+    * @see ::scanf
+    *
+    * @param format format string to pass to scanf
+    * @param ... arguments to scanf
+    * @return number of bytes read or -1 on failure
+    */
+    int scanf(const char *format, ...);
+    int vscanf(const char *format, va_list args);
+    
+    /**
     * Flushes the underlying stream
     */
     void flush();