Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers ATCmdParser.cpp Source File

ATCmdParser.cpp

00001 /*
00002  * Copyright (c) 2017, Arm Limited and affiliates.
00003  * SPDX-License-Identifier: Apache-2.0
00004  *
00005  * Licensed under the Apache License, Version 2.0 (the "License");
00006  * you may not use this file except in compliance with the License.
00007  * You may obtain a copy of the License at
00008  *
00009  *     http://www.apache.org/licenses/LICENSE-2.0
00010  *
00011  * Unless required by applicable law or agreed to in writing, software
00012  * distributed under the License is distributed on an "AS IS" BASIS,
00013  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00014  * See the License for the specific language governing permissions and
00015  * limitations under the License.
00016  */
00017 
00018 #include "ATCmdParser.h"
00019 #include "mbed_poll.h"
00020 #include "mbed_debug.h"
00021 
00022 #ifdef LF
00023 #undef LF
00024 #define LF  10
00025 #else
00026 #define LF  10
00027 #endif
00028 
00029 #ifdef CR
00030 #undef CR
00031 #define CR  13
00032 #else
00033 #define CR  13
00034 #endif
00035 
00036 // getc/putc handling with timeouts
00037 int ATCmdParser::putc(char c)
00038 {
00039     pollfh fhs;
00040     fhs.fh = _fh;
00041     fhs.events = POLLOUT;
00042 
00043     int count = poll(&fhs, 1, _timeout);
00044     if (count > 0 && (fhs.revents & POLLOUT)) {
00045         return _fh->write(&c, 1) == 1 ? 0 : -1;
00046     } else {
00047         return -1;
00048     }
00049 }
00050 
00051 int ATCmdParser::getc()
00052 {
00053     pollfh fhs;
00054     fhs.fh = _fh;
00055     fhs.events = POLLIN;
00056 
00057     int count = poll(&fhs, 1, _timeout);
00058     if (count > 0 && (fhs.revents & POLLIN)) {
00059         unsigned char ch;
00060         return _fh->read(&ch, 1) == 1 ? ch : -1;
00061     } else {
00062         return -1;
00063     }
00064 }
00065 
00066 void ATCmdParser::flush()
00067 {
00068     while (_fh->readable()) {
00069         unsigned char ch;
00070         _fh->read(&ch, 1);
00071     }
00072 }
00073 
00074 
00075 // read/write handling with timeouts
00076 int ATCmdParser::write(const char *data, int size)
00077 {
00078     int i = 0;
00079     for ( ; i < size; i++) {
00080         if (putc(data[i]) < 0) {
00081             return -1;
00082         }
00083     }
00084     return i;
00085 }
00086 
00087 int ATCmdParser::read(char *data, int size)
00088 {
00089     int i = 0;
00090     for ( ; i < size; i++) {
00091         int c = getc();
00092         if (c < 0) {
00093             return -1;
00094         }
00095         data[i] = c;
00096     }
00097     return i;
00098 }
00099 
00100 
00101 // printf/scanf handling
00102 int ATCmdParser::vprintf(const char *format, va_list args)
00103 {
00104 
00105     if (vsprintf(_buffer, format, args) < 0) {
00106         return false;
00107     }
00108 
00109     int i = 0;
00110     for ( ; _buffer[i]; i++) {
00111         if (putc(_buffer[i]) < 0) {
00112             return -1;
00113         }
00114     }
00115     return i;
00116 }
00117 
00118 int ATCmdParser::vscanf(const char *format, va_list args)
00119 {
00120     // Since format is const, we need to copy it into our buffer to
00121     // add the line's null terminator and clobber value-matches with asterisks.
00122     //
00123     // We just use the beginning of the buffer to avoid unnecessary allocations.
00124     int i = 0;
00125     int offset = 0;
00126 
00127     while (format[i]) {
00128         if (format[i] == '%' && format[i+1] != '%' && format[i+1] != '*') {
00129             _buffer[offset++] = '%';
00130             _buffer[offset++] = '*';
00131             i++;
00132         } else {
00133             _buffer[offset++] = format[i++];
00134         }
00135     }
00136 
00137     // Scanf has very poor support for catching errors
00138     // fortunately, we can abuse the %n specifier to determine
00139     // if the entire string was matched.
00140     _buffer[offset++] = '%';
00141     _buffer[offset++] = 'n';
00142     _buffer[offset++] = 0;
00143 
00144     // To workaround scanf's lack of error reporting, we actually
00145     // make two passes. One checks the validity with the modified
00146     // format string that only stores the matched characters (%n).
00147     // The other reads in the actual matched values.
00148     //
00149     // We keep trying the match until we succeed or some other error
00150     // derails us.
00151     int j = 0;
00152 
00153     while (true) {
00154         // Ran out of space
00155         if (j+1 >= _buffer_size - offset) {
00156             return false;
00157         }
00158         // Recieve next character
00159         int c = getc();
00160         if (c < 0) {
00161             return -1;
00162         }
00163         _buffer[offset + j++] = c;
00164         _buffer[offset + j] = 0;
00165 
00166         // Check for match
00167         int count = -1;
00168         sscanf(_buffer+offset, _buffer, &count);
00169 
00170         // We only succeed if all characters in the response are matched
00171         if (count == j) {
00172             // Store the found results
00173             vsscanf(_buffer+offset, format, args);
00174             return j;
00175         }
00176     }
00177 }
00178 
00179 
00180 // Command parsing with line handling
00181 bool ATCmdParser::vsend(const char *command, va_list args)
00182 {
00183     // Create and send command
00184     if (vsprintf(_buffer, command, args) < 0) {
00185         return false;
00186     }
00187 
00188     for (int i = 0; _buffer[i]; i++) {
00189         if (putc(_buffer[i]) < 0) {
00190             return false;
00191         }
00192     }
00193 
00194     // Finish with newline
00195     for (size_t i = 0; _output_delimiter[i]; i++) {
00196         if (putc(_output_delimiter[i]) < 0) {
00197             return false;
00198         }
00199     }
00200 
00201     debug_if(_dbg_on, "AT> %s\n", _buffer);
00202     return true;
00203 }
00204 
00205 bool ATCmdParser::vrecv(const char *response, va_list args)
00206 {
00207 restart:
00208     _aborted = false;
00209     // Iterate through each line in the expected response
00210     while (response[0]) {
00211         // Since response is const, we need to copy it into our buffer to
00212         // add the line's null terminator and clobber value-matches with asterisks.
00213         //
00214         // We just use the beginning of the buffer to avoid unnecessary allocations.
00215         int i = 0;
00216         int offset = 0;
00217         bool whole_line_wanted = false;
00218 
00219         while (response[i]) {
00220             if (response[i] == '%' && response[i+1] != '%' && response[i+1] != '*') {
00221                 _buffer[offset++] = '%';
00222                 _buffer[offset++] = '*';
00223                 i++;
00224             } else {
00225                 _buffer[offset++] = response[i++];
00226                 // Find linebreaks, taking care not to be fooled if they're in a %[^\n] conversion specification
00227                 if (response[i - 1] == '\n' && !(i >= 3 && response[i-3] == '[' && response[i-2] == '^')) {
00228                     whole_line_wanted = true;
00229                     break;
00230                 }
00231             }
00232         }
00233 
00234         // Scanf has very poor support for catching errors
00235         // fortunately, we can abuse the %n specifier to determine
00236         // if the entire string was matched.
00237         _buffer[offset++] = '%';
00238         _buffer[offset++] = 'n';
00239         _buffer[offset++] = 0;
00240 
00241         debug_if(_dbg_on, "AT? %s\n", _buffer);
00242         // To workaround scanf's lack of error reporting, we actually
00243         // make two passes. One checks the validity with the modified
00244         // format string that only stores the matched characters (%n).
00245         // The other reads in the actual matched values.
00246         //
00247         // We keep trying the match until we succeed or some other error
00248         // derails us.
00249         int j = 0;
00250 
00251         while (true) {
00252             // Receive next character
00253             int c = getc();
00254             if (c < 0) {
00255                 debug_if(_dbg_on, "AT(Timeout)\n");
00256                 return false;
00257             }
00258             // Simplify newlines (borrowed from retarget.cpp)
00259             if ((c == CR && _in_prev != LF) ||
00260                 (c == LF && _in_prev != CR)) {
00261                 _in_prev = c;
00262                 c = '\n';
00263             } else if ((c == CR && _in_prev == LF) ||
00264                        (c == LF && _in_prev == CR)) {
00265                 _in_prev = c;
00266                 // onto next character
00267                 continue;
00268             } else {
00269                 _in_prev = c;
00270             }
00271             _buffer[offset + j++] = c;
00272             _buffer[offset + j] = 0;
00273 
00274             // Check for oob data
00275             for (struct oob *oob = _oobs; oob; oob = oob->next) {
00276                 if ((unsigned)j == oob->len && memcmp(
00277                         oob->prefix, _buffer+offset, oob->len) == 0) {
00278                     debug_if(_dbg_on, "AT! %s\n", oob->prefix);
00279                     oob->cb();
00280 
00281                     if (_aborted) {
00282                         debug_if(_dbg_on, "AT(Aborted)\n");
00283                         return false;
00284                     }
00285                     // oob may have corrupted non-reentrant buffer,
00286                     // so we need to set it up again
00287                     goto restart;
00288                 }
00289             }
00290 
00291             // Check for match
00292             int count = -1;
00293             if (whole_line_wanted && c != '\n') {
00294                 // Don't attempt scanning until we get delimiter if they included it in format
00295                 // This allows recv("Foo: %s\n") to work, and not match with just the first character of a string
00296                 // (scanf does not itself match whitespace in its format string, so \n is not significant to it)
00297             } else {
00298                 sscanf(_buffer+offset, _buffer, &count);
00299             }
00300 
00301             // We only succeed if all characters in the response are matched
00302             if (count == j) {
00303                 debug_if(_dbg_on, "AT= %s\n", _buffer+offset);
00304                 // Reuse the front end of the buffer
00305                 memcpy(_buffer, response, i);
00306                 _buffer[i] = 0;
00307 
00308                 // Store the found results
00309                 vsscanf(_buffer+offset, _buffer, args);
00310 
00311                 // Jump to next line and continue parsing
00312                 response += i;
00313                 break;
00314             }
00315 
00316             // Clear the buffer when we hit a newline or ran out of space
00317             // running out of space usually means we ran into binary data
00318             if (c == '\n' || j+1 >= _buffer_size - offset) {
00319                 debug_if(_dbg_on, "AT< %s", _buffer+offset);
00320                 j = 0;
00321             }
00322         }
00323     }
00324 
00325     return true;
00326 }
00327 
00328 // Mapping to vararg functions
00329 int ATCmdParser::printf(const char *format, ...)
00330 {
00331     va_list args;
00332     va_start(args, format);
00333     int res = vprintf(format, args);
00334     va_end(args);
00335     return res;
00336 }
00337 
00338 int ATCmdParser::scanf(const char *format, ...)
00339 {
00340     va_list args;
00341     va_start(args, format);
00342     int res = vscanf(format, args);
00343     va_end(args);
00344     return res;
00345 }
00346 
00347 bool ATCmdParser::send(const char *command, ...)
00348 {
00349     va_list args;
00350     va_start(args, command);
00351     bool res = vsend(command, args);
00352     va_end(args);
00353     return res;
00354 }
00355 
00356 bool ATCmdParser::recv(const char *response, ...)
00357 {
00358     va_list args;
00359     va_start(args, response);
00360     bool res = vrecv(response, args);
00361     va_end(args);
00362     return res;
00363 }
00364 
00365 // oob registration
00366 void ATCmdParser::oob(const char *prefix, Callback<void()> cb)
00367 {
00368     struct oob *oob = new struct oob;
00369     oob->len = strlen(prefix);
00370     oob->prefix = prefix;
00371     oob->cb = cb;
00372     oob->next = _oobs;
00373     _oobs = oob;
00374 }
00375 
00376 void ATCmdParser::abort()
00377 {
00378     _aborted = true;
00379 }
00380 
00381 bool ATCmdParser::process_oob()
00382 {
00383     if (!_fh->readable()) {
00384         return false;
00385     }
00386 
00387     int i = 0;
00388     while (true) {
00389         // Receive next character
00390         int c = getc();
00391         if (c < 0) {
00392             return false;
00393         }
00394         _buffer[i++] = c;
00395         _buffer[i] = 0;
00396 
00397         // Check for oob data
00398         struct oob *oob = _oobs;
00399         while (oob) {
00400             if (i == (int)oob->len && memcmp(
00401                     oob->prefix, _buffer, oob->len) == 0) {
00402                 debug_if(_dbg_on, "AT! %s\r\n", oob->prefix);
00403                 oob->cb();
00404                 return true;
00405             }
00406             oob = oob->next;
00407         }
00408         
00409         // Clear the buffer when we hit a newline or ran out of space
00410         // running out of space usually means we ran into binary data
00411         if (i+1 >= _buffer_size ||
00412             strcmp(&_buffer[i-_output_delim_size], _output_delimiter) == 0) {
00413 
00414             debug_if(_dbg_on, "AT< %s", _buffer);
00415             i = 0;
00416         }
00417     }
00418 }
00419 
00420