Parser for AT commands and similar protocols

Dependencies:   BufferedSerial

Committer:
geky
Date:
Thu Jul 16 20:42:44 2015 +0000
Revision:
1:66a14afe650a
Parent:
0:c741e144517c
Child:
2:4d68f546861c
Worked around scanf's lack of error reporting; ; This required a rather roundabout method of performing two scanf passes, one with clobbered matches %n, and the other with the actual value matches.; ; Also the parser now scans multiple lines.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
geky 0:c741e144517c 1 /* Copyright (c) 2015 ARM Limited
geky 0:c741e144517c 2 *
geky 0:c741e144517c 3 * Licensed under the Apache License, Version 2.0 (the "License");
geky 0:c741e144517c 4 * you may not use this file except in compliance with the License.
geky 0:c741e144517c 5 * You may obtain a copy of the License at
geky 0:c741e144517c 6 *
geky 0:c741e144517c 7 * http://www.apache.org/licenses/LICENSE-2.0
geky 0:c741e144517c 8 *
geky 0:c741e144517c 9 * Unless required by applicable law or agreed to in writing, software
geky 0:c741e144517c 10 * distributed under the License is distributed on an "AS IS" BASIS,
geky 0:c741e144517c 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
geky 0:c741e144517c 12 * See the License for the specific language governing permissions and
geky 0:c741e144517c 13 * limitations under the License.
geky 0:c741e144517c 14 *
geky 0:c741e144517c 15 * @section DESCRIPTION
geky 0:c741e144517c 16 *
geky 0:c741e144517c 17 * Parser for the AT command syntax
geky 0:c741e144517c 18 *
geky 0:c741e144517c 19 */
geky 0:c741e144517c 20
geky 0:c741e144517c 21 #include "ATParser.h"
geky 0:c741e144517c 22 #include <cstdarg>
geky 0:c741e144517c 23
geky 0:c741e144517c 24 // This can be defined to assist in debugging
geky 1:66a14afe650a 25 //#define AT_ECHO 1
geky 0:c741e144517c 26
geky 0:c741e144517c 27
geky 0:c741e144517c 28 // getc/putc handling with timeouts
geky 0:c741e144517c 29 int ATParser::_putc(char c) {
geky 0:c741e144517c 30 Timer timer;
geky 0:c741e144517c 31 timer.start();
geky 0:c741e144517c 32
geky 0:c741e144517c 33 while (true) {
geky 0:c741e144517c 34 if (_serial->writeable())
geky 0:c741e144517c 35 return _serial->putc(c);
geky 0:c741e144517c 36
geky 0:c741e144517c 37 if (timer.read_ms() > _timeout)
geky 0:c741e144517c 38 return -1;
geky 0:c741e144517c 39 }
geky 0:c741e144517c 40 }
geky 0:c741e144517c 41
geky 0:c741e144517c 42 int ATParser::_getc() {
geky 0:c741e144517c 43 Timer timer;
geky 0:c741e144517c 44 timer.start();
geky 0:c741e144517c 45
geky 0:c741e144517c 46 while (true) {
geky 0:c741e144517c 47 if (_serial->readable())
geky 0:c741e144517c 48 return _serial->getc();
geky 0:c741e144517c 49
geky 0:c741e144517c 50 if (timer.read_ms() > _timeout)
geky 0:c741e144517c 51 return -1;
geky 0:c741e144517c 52 }
geky 0:c741e144517c 53 }
geky 0:c741e144517c 54
geky 0:c741e144517c 55 void ATParser::_flush() {
geky 0:c741e144517c 56 while (_serial->readable())
geky 0:c741e144517c 57 _serial->getc();
geky 0:c741e144517c 58 }
geky 0:c741e144517c 59
geky 1:66a14afe650a 60
geky 0:c741e144517c 61 // getline/putline handling with timeouts/bounds checking
geky 1:66a14afe650a 62 bool ATParser::_putline(const char *line) {
geky 0:c741e144517c 63 for (int i = 0; line[i]; i++) {
geky 0:c741e144517c 64 if (_putc(line[i]) < 0)
geky 0:c741e144517c 65 return false;
geky 0:c741e144517c 66 }
geky 0:c741e144517c 67
geky 0:c741e144517c 68 // Finish with newline
geky 1:66a14afe650a 69 if (_putc('\r') < 0 || _putc('\n') < 0)
geky 0:c741e144517c 70 return false;
geky 0:c741e144517c 71
geky 0:c741e144517c 72 #ifdef AT_ECHO
geky 0:c741e144517c 73 printf("AT> %s\r\n", line);
geky 0:c741e144517c 74 #endif
geky 1:66a14afe650a 75
geky 0:c741e144517c 76 return true;
geky 0:c741e144517c 77 }
geky 0:c741e144517c 78
geky 1:66a14afe650a 79 bool ATParser::_getline(char *line, int size) {
geky 1:66a14afe650a 80 int i = 0;
geky 1:66a14afe650a 81
geky 1:66a14afe650a 82 while (i < size) {
geky 0:c741e144517c 83 int c = _getc();
geky 0:c741e144517c 84
geky 1:66a14afe650a 85 if (c < 0)
geky 1:66a14afe650a 86 return false;
geky 0:c741e144517c 87
geky 1:66a14afe650a 88 // Finish when we hit a newline
geky 1:66a14afe650a 89 if (c == '\r' || c == '\n') {
geky 1:66a14afe650a 90 // Only handle newlines on \n
geky 1:66a14afe650a 91 if (c != '\n')
geky 1:66a14afe650a 92 continue;
geky 0:c741e144517c 93
geky 1:66a14afe650a 94 line[i++] = 0;
geky 0:c741e144517c 95 #ifdef AT_ECHO
geky 0:c741e144517c 96 printf("AT< %s\r\n", line);
geky 0:c741e144517c 97 #endif
geky 0:c741e144517c 98 return true;
geky 0:c741e144517c 99 }
geky 0:c741e144517c 100
geky 1:66a14afe650a 101 line[i++] = c;
geky 0:c741e144517c 102 }
geky 0:c741e144517c 103
geky 0:c741e144517c 104 // Ran out of space
geky 0:c741e144517c 105 return false;
geky 1:66a14afe650a 106 }
geky 0:c741e144517c 107
geky 0:c741e144517c 108
geky 0:c741e144517c 109 bool ATParser::command(const char *command, const char *response, ...) {
geky 0:c741e144517c 110 va_list args;
geky 0:c741e144517c 111 va_start(args, response);
geky 0:c741e144517c 112
geky 0:c741e144517c 113 _flush();
geky 0:c741e144517c 114
geky 0:c741e144517c 115 // Create and send command
geky 0:c741e144517c 116 if (vsprintf(_buffer, command, args) < 0 ||
geky 0:c741e144517c 117 !_putline(_buffer)) {
geky 0:c741e144517c 118 va_end(args);
geky 0:c741e144517c 119 return false;
geky 0:c741e144517c 120 }
geky 0:c741e144517c 121
geky 1:66a14afe650a 122 // Iterate through each line in the expected response
geky 1:66a14afe650a 123 while (response && response[0]) {
geky 1:66a14afe650a 124 // Since response is const, we need to copy it into our buffer to
geky 1:66a14afe650a 125 // add the line's null terminator and clobber value matches with asterisks.
geky 1:66a14afe650a 126 //
geky 1:66a14afe650a 127 // We just use the beginning of the buffer to avoid unecessary allocations.
geky 1:66a14afe650a 128 int i = 0;
geky 1:66a14afe650a 129 int offset = 0;
geky 1:66a14afe650a 130
geky 1:66a14afe650a 131 while (response[i]) {
geky 1:66a14afe650a 132 // Only handle newlines on \n
geky 1:66a14afe650a 133 if (response[i] == '\n') {
geky 1:66a14afe650a 134 i++;
geky 1:66a14afe650a 135 break;
geky 1:66a14afe650a 136 } else if (response[i] == '\r') {
geky 1:66a14afe650a 137 i++;
geky 1:66a14afe650a 138 } else if (response[i] == '%' &&
geky 1:66a14afe650a 139 response[i+1] != '%' &&
geky 1:66a14afe650a 140 response[i+1] != '*') {
geky 1:66a14afe650a 141 i++;
geky 1:66a14afe650a 142
geky 1:66a14afe650a 143 _buffer[offset++] = '%';
geky 1:66a14afe650a 144 _buffer[offset++] = '*';
geky 1:66a14afe650a 145 } else {
geky 1:66a14afe650a 146 _buffer[offset++] = response[i++];
geky 1:66a14afe650a 147 }
geky 1:66a14afe650a 148 }
geky 1:66a14afe650a 149
geky 1:66a14afe650a 150 // Scanf has very poor support for catching errors
geky 1:66a14afe650a 151 // fortunately, we can abuse the %n specifier to determine
geky 1:66a14afe650a 152 // if the entire string was matched.
geky 1:66a14afe650a 153 _buffer[offset++] = '%';
geky 1:66a14afe650a 154 _buffer[offset++] = 'n';
geky 1:66a14afe650a 155 _buffer[offset++] = 0;
geky 1:66a14afe650a 156
geky 1:66a14afe650a 157 // To workaround scanf's lack of error reporting, we actually
geky 1:66a14afe650a 158 // make two passes. One checks the validity with the modified
geky 1:66a14afe650a 159 // format string that only stores the matched characters (%n).
geky 1:66a14afe650a 160 // The other reads in the actual matched values.
geky 1:66a14afe650a 161 //
geky 1:66a14afe650a 162 // We keep trying the match until we succeed or some other error
geky 1:66a14afe650a 163 // derails us.
geky 1:66a14afe650a 164 while (true) {
geky 1:66a14afe650a 165 // Recieve response
geky 1:66a14afe650a 166 if (!_getline(_buffer+offset, _buffer_size-offset)) {
geky 1:66a14afe650a 167 va_end(args);
geky 1:66a14afe650a 168 return false;
geky 1:66a14afe650a 169 }
geky 1:66a14afe650a 170
geky 1:66a14afe650a 171 int count;
geky 1:66a14afe650a 172 sscanf(_buffer+offset, _buffer, &count);
geky 1:66a14afe650a 173
geky 1:66a14afe650a 174 // We only succeed if all characters in the response is matched
geky 1:66a14afe650a 175 if ((_buffer+offset)[count] == 0) {
geky 1:66a14afe650a 176 // Reuse the front end of the buffer
geky 1:66a14afe650a 177 int j;
geky 1:66a14afe650a 178 for (j = 0; j < i; j++) {
geky 1:66a14afe650a 179 _buffer[j] = response[j];
geky 1:66a14afe650a 180 }
geky 1:66a14afe650a 181 _buffer[j] = 0;
geky 1:66a14afe650a 182
geky 1:66a14afe650a 183 // Store the found results
geky 1:66a14afe650a 184 vsscanf(_buffer+offset, _buffer, args);
geky 1:66a14afe650a 185
geky 1:66a14afe650a 186 // Jump to next line and continue parsing
geky 1:66a14afe650a 187 response += i;
geky 1:66a14afe650a 188 break;
geky 1:66a14afe650a 189 }
geky 1:66a14afe650a 190 }
geky 0:c741e144517c 191 }
geky 0:c741e144517c 192
geky 0:c741e144517c 193 va_end(args);
geky 0:c741e144517c 194 return true;
geky 0:c741e144517c 195 }