Parser for AT commands and similar protocols

Dependencies:   BufferedSerial

ATParser.cpp

Committer:
geky
Date:
2015-07-17
Revision:
4:38acbd6f9d9e
Parent:
3:32915b9467d2
Child:
5:26bc9255b751

File content as of revision 4:38acbd6f9d9e:

/* Copyright (c) 2015 ARM Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @section DESCRIPTION
 *
 * Parser for the AT command syntax
 *
 */
 
#include "ATParser.h"
#include <cstdarg>

// This can be defined to assist in debugging
#define AT_ECHO 1


// getc/putc handling with timeouts
int ATParser::putc(char c) {
    Timer timer;
    timer.start();
    
    while (true) {
        if (_serial->writeable())
            return _serial->putc(c);
            
        if (timer.read_ms() > _timeout)
            return -1;
    }
}

int ATParser::getc() {
    Timer timer;
    timer.start();
    
    while (true) {
        if (_serial->readable())
            return _serial->getc();
            
        if (timer.read_ms() > _timeout)
            return -1;
    }
}

void ATParser::flush() {
    while (_serial->readable())
        _serial->getc();
}


// getline/putline handling with timeouts/bounds checking
bool ATParser::_putline(const char *line) {    
    for (int i = 0; line[i]; i++) {
        if (putc(line[i]) < 0)
            return false;
    }
    
    // Finish with newline
    for (int i = 0; _delimiter[i]; i++) {
        if (putc(_delimiter[i]) < 0)
            return false;
    }
    
#ifdef AT_ECHO
    printf("AT> %s\r\n", line);
#endif
    
    return true;
}

bool ATParser::_getline(char *line, int size) {
    int i = 0;
    
    while (i < size) {
        int c = getc();
        if (c < 0)
            return false;
            
        line[i++] = c;
        
        // Finish when we hit a newline
        if (memcmp(&line[i-_delim_size], _delimiter, _delim_size) == 0) {
            line[i-_delim_size] = 0;
#ifdef AT_ECHO            
            printf("AT< %s\r\n", line);
#endif
            return true;
        }
    }
    
    // Ran out of space
    return false;
}

 
bool ATParser::command(const char *command, const char *response, ...) {
    va_list args;
    va_start(args, response);
    
    flush();
    
    // Create and send command
    if (command) {
        if (vsprintf(_buffer, command, args) < 0 ||
            !_putline(_buffer)) {
            va_end(args);
            return false;
        }
    }
    
    // Iterate through each line in the expected response
    while (response && response[0]) {
        // Since response 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 unecessary allocations.
        int i = 0;
        int offset = 0;
        
        while (response[i]) {
            if (memcmp(&response[i-_delim_size], _delimiter, _delim_size) == 0) {
                i += _delim_size;
                break;
            } else if (response[i] == '%' && 
                       response[i+1] != '%' && 
                       response[i+1] != '*') {
                _buffer[offset++] = '%';
                _buffer[offset++] = '*';
                i++;
            } else {
                _buffer[offset++] = response[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.
        while (true) {
            // Recieve response
            if (!_getline(_buffer+offset, _buffer_size-offset)) {
                va_end(args);
                return false;
            }
            
            int count = -1;
            sscanf(_buffer+offset, _buffer, &count);
            
            // We only succeed if all characters in the response is matched
            if (count >= 0 && (_buffer+offset)[count] == 0) {
                // Reuse the front end of the buffer
                int j;
                for (j = 0; j < i; j++) {
                    _buffer[j] = response[j];
                }
                _buffer[j] = 0;
                
                // Store the found results
                vsscanf(_buffer+offset, _buffer, args);
                
                // Jump to next line and continue parsing
                response += i;
                break;
            }
        }
    }
 
    va_end(args);
    return true;
}