mbed-os5 only for TYBLE16
Dependents: TYBLE16_simple_data_logger TYBLE16_MP3_Air
Diff: platform/source/ATCmdParser.cpp
- Revision:
- 1:9db0e321a9f4
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/platform/source/ATCmdParser.cpp Tue Dec 31 06:02:27 2019 +0000 @@ -0,0 +1,396 @@ +/* Copyright (c) 2017 ARM Limited + * SPDX-License-Identifier: Apache-2.0 + * + * 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 "ATCmdParser.h" +#include "mbed_poll.h" +#include "mbed_debug.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef LF +#undef LF +#define LF 10 +#else +#define LF 10 +#endif + +#ifdef CR +#undef CR +#define CR 13 +#else +#define CR 13 +#endif + +namespace mbed { + +// getc/putc handling with timeouts +int ATCmdParser::putc(char c) +{ + pollfh fhs; + fhs.fh = _fh; + fhs.events = POLLOUT; + + int count = poll(&fhs, 1, _timeout); + if (count > 0 && (fhs.revents & POLLOUT)) { + return _fh->write(&c, 1) == 1 ? 0 : -1; + } else { + return -1; + } +} + +int ATCmdParser::getc() +{ + pollfh fhs; + fhs.fh = _fh; + fhs.events = POLLIN; + + int count = poll(&fhs, 1, _timeout); + if (count > 0 && (fhs.revents & POLLIN)) { + unsigned char ch; + return _fh->read(&ch, 1) == 1 ? ch : -1; + } else { + return -1; + } +} + +void ATCmdParser::flush() +{ + while (_fh->readable()) { + unsigned char ch; + _fh->read(&ch, 1); + } +} + + +// read/write handling with timeouts +int ATCmdParser::write(const char *data, int size) +{ + int i = 0; + for (; i < size; i++) { + if (putc(data[i]) < 0) { + return -1; + } + } + return i; +} + +int ATCmdParser::read(char *data, int size) +{ + int i = 0; + for (; i < size; i++) { + int c = getc(); + if (c < 0) { + return -1; + } + data[i] = c; + } + return i; +} + + +// printf/scanf handling +int ATCmdParser::vprintf(const char *format, std::va_list args) +{ + + if (vsprintf(_buffer, format, args) < 0) { + return false; + } + + int i = 0; + for (; _buffer[i]; i++) { + if (putc(_buffer[i]) < 0) { + return -1; + } + } + return i; +} + +// Command parsing with line handling +bool ATCmdParser::vsend(const char *command, std::va_list args) +{ + // Create and send command + if (vsprintf(_buffer, command, args) < 0) { + return false; + } + + for (int i = 0; _buffer[i]; i++) { + if (putc(_buffer[i]) < 0) { + return false; + } + } + + // Finish with newline + for (size_t i = 0; _output_delimiter[i]; i++) { + if (putc(_output_delimiter[i]) < 0) { + return false; + } + } + + debug_if(_dbg_on, "AT> %s\n", _buffer); + return true; +} + +int ATCmdParser::vrecvscanf(const char *response, std::va_list args, bool multiline) +{ +restart: + _aborted = false; + // Iterate through each line in the expected response + // response being NULL means we just want to check for OOBs + 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 unnecessary allocations. + int i = 0; + int offset = 0; + bool whole_line_wanted = false; + + while (response && response[i]) { + if (response[i] == '%' && response[i + 1] != '%' && response[i + 1] != '*') { + if ((offset + 2) > _buffer_size) { + return -1; + } + _buffer[offset++] = '%'; + _buffer[offset++] = '*'; + i++; + } else { + if ((offset + 1) > _buffer_size) { + return -1; + } + _buffer[offset++] = response[i++]; + // Find linebreaks, taking care not to be fooled if they're in a %[^\n] conversion specification + if (response[i - 1] == '\n' && !(i >= 3 && response[i - 3] == '[' && response[i - 2] == '^')) { + whole_line_wanted = true; + break; + } + } + } + + // Scanf has very poor support for catching errors + // fortunately, we can abuse the %n specifier to determine + // if the entire string was matched. + if ((offset + 3) > _buffer_size) { + return -1; + } + _buffer[offset++] = '%'; + _buffer[offset++] = 'n'; + _buffer[offset++] = 0; + + debug_if(_dbg_on, "AT? %s\n", _buffer); + // 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 -1; + } + + // If just peeking for OOBs, and at start of line, check + // readability + if (!response && j == 0 && !_fh->readable()) { + return -1; + } + + // Receive next character + int c = getc(); + if (c < 0) { + debug_if(_dbg_on, "AT(Timeout)\n"); + return -1; + } + + // Simplify newlines (borrowed from retarget.cpp) + if ((c == CR && _in_prev != LF) || + (c == LF && _in_prev != CR)) { + _in_prev = c; + c = '\n'; + } else if ((c == CR && _in_prev == LF) || + (c == LF && _in_prev == CR)) { + _in_prev = c; + // onto next character + continue; + } else { + _in_prev = c; + } + + if ((offset + j + 1) > _buffer_size) { + return -1; + } + _buffer[offset + j++] = c; + _buffer[offset + j] = 0; + + // Check for oob data + if (multiline) { + for (struct oob *oob = _oobs; oob; oob = oob->next) { + if ((unsigned)j == oob->len && memcmp( + oob->prefix, _buffer + offset, oob->len) == 0) { + debug_if(_dbg_on, "AT! %s\n", oob->prefix); + _oob_cb_count++; + oob->cb(); + + if (_aborted) { + debug_if(_dbg_on, "AT(Aborted)\n"); + return false; + } + // oob may have corrupted non-reentrant buffer, + // so we need to set it up again + goto restart; + } + } + } + + // Check for match + int count = -1; + if (whole_line_wanted && c != '\n') { + // Don't attempt scanning until we get delimiter if they included it in format + // This allows recv("Foo: %s\n") to work, and not match with just the first character of a string + } else if (response) { + sscanf(_buffer + offset, _buffer, &count); + } + + // We only succeed if all characters in the response are matched + if (count == j) { + debug_if(_dbg_on, "AT= %s\n", _buffer + offset); + // Reuse the front end of the buffer + memcpy(_buffer, response, i); + _buffer[i] = 0; + + // Store the found results + vsscanf(_buffer + offset, _buffer, args); + + if (!multiline) { + return j; + } + + // Jump to next line and continue parsing + response += i; + break; + } + + // Clear the buffer when we hit a newline or ran out of space + // running out of space usually means we ran into binary data + if (c == '\n' || j + 1 >= _buffer_size - offset) { + debug_if(_dbg_on, "AT< %s", _buffer + offset); + j = 0; + } + } + } + + return 1; +} + +int ATCmdParser::vscanf(const char *format, std::va_list args) +{ + return vrecvscanf(format, args, false); +} + +bool ATCmdParser::vrecv(const char *response, std::va_list args) +{ + return (vrecvscanf(response, args, true)) > 0 ? true : false; +} + +// Mapping to vararg functions +int ATCmdParser::printf(const char *format, ...) +{ + std::va_list args; + va_start(args, format); + int res = vprintf(format, args); + va_end(args); + return res; +} + +int ATCmdParser::scanf(const char *format, ...) +{ + std::va_list args; + va_start(args, format); + int res = vrecvscanf(format, args, false); + va_end(args); + return res; +} + +bool ATCmdParser::send(const char *command, ...) +{ + std::va_list args; + va_start(args, command); + bool res = vsend(command, args); + va_end(args); + return res; +} + +bool ATCmdParser::recv(const char *response, ...) +{ + std::va_list args; + va_start(args, response); + int res = vrecvscanf(response, args, true); + va_end(args); + return (res > 0) ? true : false; +} + +// oob registration +void ATCmdParser::oob(const char *prefix, Callback<void()> cb) +{ + struct oob *oob = new struct oob; + oob->len = strlen(prefix); + oob->prefix = prefix; + oob->cb = cb; + oob->next = _oobs; + _oobs = oob; +} + +void ATCmdParser::remove_oob(const char *prefix) +{ + struct oob *prev = NULL; + struct oob *oob = _oobs; + while (oob) { + if (memcmp(oob->prefix, prefix, strlen(prefix)) == 0) { + if (prev) { + prev->next = oob->next; + } else { + _oobs = oob->next; + } + delete oob; + return; + } + prev = oob; + oob = oob->next; + } +} + +void ATCmdParser::abort() +{ + _aborted = true; +} + +bool ATCmdParser::process_oob() +{ + int pre_count = _oob_cb_count; + static_cast<void>(recv(NULL)); + return _oob_cb_count != pre_count; +} + +}