Parser for AT commands and similar protocols

Dependencies:   BufferedSerial

Revision:
9:9bcb87c27208
Parent:
8:91515b168c70
Child:
10:553f9ffaf657
diff -r 91515b168c70 -r 9bcb87c27208 ATParser.cpp
--- 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);