Parser for AT commands and similar protocols

Dependencies:   BufferedSerial

Fork of ATParser by NetworkSocketAPI

Committer:
Raffaello
Date:
Sat May 27 13:07:44 2017 +0000
Revision:
17:f395d107f15e
Parent:
16:8f98e042b9b4
Added code guard

Who changed what in which revision?

UserRevisionLine numberNew contents of line
Christopher Haster 12:7d3c3f7ce928 1 /* Copyright (c) 2015 ARM Limited
Christopher Haster 12:7d3c3f7ce928 2 *
Christopher Haster 12:7d3c3f7ce928 3 * Licensed under the Apache License, Version 2.0 (the "License");
Christopher Haster 12:7d3c3f7ce928 4 * you may not use this file except in compliance with the License.
Christopher Haster 12:7d3c3f7ce928 5 * You may obtain a copy of the License at
Christopher Haster 12:7d3c3f7ce928 6 *
Christopher Haster 12:7d3c3f7ce928 7 * http://www.apache.org/licenses/LICENSE-2.0
Christopher Haster 12:7d3c3f7ce928 8 *
Christopher Haster 12:7d3c3f7ce928 9 * Unless required by applicable law or agreed to in writing, software
Christopher Haster 12:7d3c3f7ce928 10 * distributed under the License is distributed on an "AS IS" BASIS,
Christopher Haster 12:7d3c3f7ce928 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Christopher Haster 12:7d3c3f7ce928 12 * See the License for the specific language governing permissions and
Christopher Haster 12:7d3c3f7ce928 13 * limitations under the License.
Christopher Haster 12:7d3c3f7ce928 14 *
Christopher Haster 12:7d3c3f7ce928 15 * @section DESCRIPTION
Christopher Haster 12:7d3c3f7ce928 16 *
Christopher Haster 12:7d3c3f7ce928 17 * Parser for the AT command syntax
Christopher Haster 12:7d3c3f7ce928 18 *
Christopher Haster 12:7d3c3f7ce928 19 */
Christopher Haster 12:7d3c3f7ce928 20
Christopher Haster 12:7d3c3f7ce928 21 #include "ATParser.h"
Christopher Haster 12:7d3c3f7ce928 22 #include "mbed_debug.h"
Christopher Haster 12:7d3c3f7ce928 23
Christopher Haster 12:7d3c3f7ce928 24
Christopher Haster 12:7d3c3f7ce928 25 // getc/putc handling with timeouts
Christopher Haster 12:7d3c3f7ce928 26 int ATParser::putc(char c)
Christopher Haster 12:7d3c3f7ce928 27 {
Christopher Haster 12:7d3c3f7ce928 28 Timer timer;
Christopher Haster 12:7d3c3f7ce928 29 timer.start();
Christopher Haster 12:7d3c3f7ce928 30
Christopher Haster 12:7d3c3f7ce928 31 while (true) {
Christopher Haster 12:7d3c3f7ce928 32 if (_serial->writeable()) {
Christopher Haster 12:7d3c3f7ce928 33 return _serial->putc(c);
Christopher Haster 12:7d3c3f7ce928 34 }
Christopher Haster 12:7d3c3f7ce928 35 if (timer.read_ms() > _timeout) {
Christopher Haster 12:7d3c3f7ce928 36 return -1;
Christopher Haster 12:7d3c3f7ce928 37 }
Christopher Haster 12:7d3c3f7ce928 38 }
Christopher Haster 12:7d3c3f7ce928 39 }
Christopher Haster 12:7d3c3f7ce928 40
Christopher Haster 12:7d3c3f7ce928 41 int ATParser::getc()
Christopher Haster 12:7d3c3f7ce928 42 {
Christopher Haster 12:7d3c3f7ce928 43 Timer timer;
Christopher Haster 12:7d3c3f7ce928 44 timer.start();
Christopher Haster 12:7d3c3f7ce928 45
Christopher Haster 12:7d3c3f7ce928 46 while (true) {
Christopher Haster 12:7d3c3f7ce928 47 if (_serial->readable()) {
Christopher Haster 12:7d3c3f7ce928 48 return _serial->getc();
Christopher Haster 12:7d3c3f7ce928 49 }
Christopher Haster 12:7d3c3f7ce928 50 if (timer.read_ms() > _timeout) {
Christopher Haster 12:7d3c3f7ce928 51 return -1;
Christopher Haster 12:7d3c3f7ce928 52 }
Christopher Haster 12:7d3c3f7ce928 53 }
Christopher Haster 12:7d3c3f7ce928 54 }
Christopher Haster 12:7d3c3f7ce928 55
Christopher Haster 12:7d3c3f7ce928 56 void ATParser::flush()
Christopher Haster 12:7d3c3f7ce928 57 {
Christopher Haster 12:7d3c3f7ce928 58 while (_serial->readable()) {
Christopher Haster 12:7d3c3f7ce928 59 _serial->getc();
Christopher Haster 12:7d3c3f7ce928 60 }
Christopher Haster 12:7d3c3f7ce928 61 }
Christopher Haster 12:7d3c3f7ce928 62
Christopher Haster 12:7d3c3f7ce928 63
Christopher Haster 12:7d3c3f7ce928 64 // read/write handling with timeouts
Christopher Haster 12:7d3c3f7ce928 65 int ATParser::write(const char *data, int size)
Christopher Haster 12:7d3c3f7ce928 66 {
Christopher Haster 12:7d3c3f7ce928 67 int i = 0;
Christopher Haster 12:7d3c3f7ce928 68 for ( ; i < size; i++) {
Christopher Haster 12:7d3c3f7ce928 69 if (putc(data[i]) < 0) {
Christopher Haster 12:7d3c3f7ce928 70 return -1;
Christopher Haster 12:7d3c3f7ce928 71 }
Christopher Haster 12:7d3c3f7ce928 72 }
Christopher Haster 12:7d3c3f7ce928 73 return i;
Christopher Haster 12:7d3c3f7ce928 74 }
Christopher Haster 12:7d3c3f7ce928 75
Christopher Haster 12:7d3c3f7ce928 76 int ATParser::read(char *data, int size)
Christopher Haster 12:7d3c3f7ce928 77 {
Christopher Haster 12:7d3c3f7ce928 78 int i = 0;
Christopher Haster 12:7d3c3f7ce928 79 for ( ; i < size; i++) {
Christopher Haster 12:7d3c3f7ce928 80 int c = getc();
Christopher Haster 12:7d3c3f7ce928 81 if (c < 0) {
Christopher Haster 12:7d3c3f7ce928 82 return -1;
Christopher Haster 12:7d3c3f7ce928 83 }
Christopher Haster 12:7d3c3f7ce928 84 data[i] = c;
Christopher Haster 12:7d3c3f7ce928 85 }
Christopher Haster 12:7d3c3f7ce928 86 return i;
Christopher Haster 12:7d3c3f7ce928 87 }
Christopher Haster 12:7d3c3f7ce928 88
Christopher Haster 12:7d3c3f7ce928 89
Christopher Haster 12:7d3c3f7ce928 90 // printf/scanf handling
Christopher Haster 12:7d3c3f7ce928 91 int ATParser::vprintf(const char *format, va_list args)
Christopher Haster 12:7d3c3f7ce928 92 {
Christopher Haster 12:7d3c3f7ce928 93 if (vsprintf(_buffer, format, args) < 0) {
Christopher Haster 12:7d3c3f7ce928 94 return false;
Christopher Haster 12:7d3c3f7ce928 95 }
Christopher Haster 12:7d3c3f7ce928 96 int i = 0;
Christopher Haster 12:7d3c3f7ce928 97 for ( ; _buffer[i]; i++) {
Christopher Haster 12:7d3c3f7ce928 98 if (putc(_buffer[i]) < 0) {
Christopher Haster 12:7d3c3f7ce928 99 return -1;
Christopher Haster 12:7d3c3f7ce928 100 }
Christopher Haster 12:7d3c3f7ce928 101 }
Christopher Haster 12:7d3c3f7ce928 102 return i;
Christopher Haster 12:7d3c3f7ce928 103 }
Christopher Haster 12:7d3c3f7ce928 104
Christopher Haster 12:7d3c3f7ce928 105 int ATParser::vscanf(const char *format, va_list args)
Christopher Haster 12:7d3c3f7ce928 106 {
Christopher Haster 12:7d3c3f7ce928 107 // Since format is const, we need to copy it into our buffer to
Christopher Haster 12:7d3c3f7ce928 108 // add the line's null terminator and clobber value-matches with asterisks.
Christopher Haster 12:7d3c3f7ce928 109 //
Christopher Haster 12:7d3c3f7ce928 110 // We just use the beginning of the buffer to avoid unnecessary allocations.
Christopher Haster 12:7d3c3f7ce928 111 int i = 0;
Christopher Haster 12:7d3c3f7ce928 112 int offset = 0;
Christopher Haster 12:7d3c3f7ce928 113
Christopher Haster 12:7d3c3f7ce928 114 while (format[i]) {
Christopher Haster 12:7d3c3f7ce928 115 if (format[i] == '%' && format[i+1] != '%' && format[i+1] != '*') {
Christopher Haster 12:7d3c3f7ce928 116 _buffer[offset++] = '%';
Christopher Haster 12:7d3c3f7ce928 117 _buffer[offset++] = '*';
Christopher Haster 12:7d3c3f7ce928 118 i++;
Christopher Haster 12:7d3c3f7ce928 119 } else {
Christopher Haster 12:7d3c3f7ce928 120 _buffer[offset++] = format[i++];
Christopher Haster 12:7d3c3f7ce928 121 }
Christopher Haster 12:7d3c3f7ce928 122 }
Christopher Haster 12:7d3c3f7ce928 123
Christopher Haster 12:7d3c3f7ce928 124 // Scanf has very poor support for catching errors
Christopher Haster 12:7d3c3f7ce928 125 // fortunately, we can abuse the %n specifier to determine
Christopher Haster 12:7d3c3f7ce928 126 // if the entire string was matched.
Christopher Haster 12:7d3c3f7ce928 127 _buffer[offset++] = '%';
Christopher Haster 12:7d3c3f7ce928 128 _buffer[offset++] = 'n';
Christopher Haster 12:7d3c3f7ce928 129 _buffer[offset++] = 0;
Christopher Haster 12:7d3c3f7ce928 130
Christopher Haster 12:7d3c3f7ce928 131 // To workaround scanf's lack of error reporting, we actually
Christopher Haster 12:7d3c3f7ce928 132 // make two passes. One checks the validity with the modified
Christopher Haster 12:7d3c3f7ce928 133 // format string that only stores the matched characters (%n).
Christopher Haster 12:7d3c3f7ce928 134 // The other reads in the actual matched values.
Christopher Haster 12:7d3c3f7ce928 135 //
Christopher Haster 12:7d3c3f7ce928 136 // We keep trying the match until we succeed or some other error
Christopher Haster 12:7d3c3f7ce928 137 // derails us.
Christopher Haster 12:7d3c3f7ce928 138 int j = 0;
Christopher Haster 12:7d3c3f7ce928 139
Christopher Haster 12:7d3c3f7ce928 140 while (true) {
Christopher Haster 12:7d3c3f7ce928 141 // Ran out of space
Christopher Haster 12:7d3c3f7ce928 142 if (j+1 >= _buffer_size - offset) {
Christopher Haster 12:7d3c3f7ce928 143 return false;
Christopher Haster 12:7d3c3f7ce928 144 }
Christopher Haster 12:7d3c3f7ce928 145 // Recieve next character
Christopher Haster 12:7d3c3f7ce928 146 int c = getc();
Christopher Haster 12:7d3c3f7ce928 147 if (c < 0) {
Christopher Haster 12:7d3c3f7ce928 148 return -1;
Christopher Haster 12:7d3c3f7ce928 149 }
Christopher Haster 12:7d3c3f7ce928 150 _buffer[offset + j++] = c;
Christopher Haster 12:7d3c3f7ce928 151 _buffer[offset + j] = 0;
Christopher Haster 12:7d3c3f7ce928 152
Christopher Haster 12:7d3c3f7ce928 153 // Check for match
Christopher Haster 12:7d3c3f7ce928 154 int count = -1;
Christopher Haster 12:7d3c3f7ce928 155 sscanf(_buffer+offset, _buffer, &count);
Christopher Haster 12:7d3c3f7ce928 156
Christopher Haster 12:7d3c3f7ce928 157 // We only succeed if all characters in the response are matched
Christopher Haster 12:7d3c3f7ce928 158 if (count == j) {
Christopher Haster 12:7d3c3f7ce928 159 // Store the found results
Christopher Haster 12:7d3c3f7ce928 160 vsscanf(_buffer+offset, format, args);
Christopher Haster 12:7d3c3f7ce928 161 return j;
Christopher Haster 12:7d3c3f7ce928 162 }
Christopher Haster 12:7d3c3f7ce928 163 }
Christopher Haster 12:7d3c3f7ce928 164 }
Christopher Haster 12:7d3c3f7ce928 165
Christopher Haster 12:7d3c3f7ce928 166
Christopher Haster 12:7d3c3f7ce928 167 // Command parsing with line handling
Christopher Haster 12:7d3c3f7ce928 168 bool ATParser::vsend(const char *command, va_list args)
Christopher Haster 12:7d3c3f7ce928 169 {
Christopher Haster 12:7d3c3f7ce928 170 // Create and send command
Christopher Haster 12:7d3c3f7ce928 171 if (vsprintf(_buffer, command, args) < 0) {
Christopher Haster 12:7d3c3f7ce928 172 return false;
Christopher Haster 12:7d3c3f7ce928 173 }
Christopher Haster 12:7d3c3f7ce928 174 for (int i = 0; _buffer[i]; i++) {
Christopher Haster 12:7d3c3f7ce928 175 if (putc(_buffer[i]) < 0) {
Christopher Haster 12:7d3c3f7ce928 176 return false;
Christopher Haster 12:7d3c3f7ce928 177 }
Christopher Haster 12:7d3c3f7ce928 178 }
Christopher Haster 12:7d3c3f7ce928 179
Christopher Haster 12:7d3c3f7ce928 180 // Finish with newline
Raffaello 16:8f98e042b9b4 181 for (int i = 0; _delimiter_send[i]; i++) {
Raffaello 16:8f98e042b9b4 182 if (putc(_delimiter_send[i]) < 0) {
Christopher Haster 12:7d3c3f7ce928 183 return false;
Christopher Haster 12:7d3c3f7ce928 184 }
Christopher Haster 12:7d3c3f7ce928 185 }
Christopher Haster 12:7d3c3f7ce928 186
Christopher Haster 12:7d3c3f7ce928 187 debug_if(dbg_on, "AT> %s\r\n", _buffer);
Christopher Haster 12:7d3c3f7ce928 188 return true;
Christopher Haster 12:7d3c3f7ce928 189 }
Christopher Haster 12:7d3c3f7ce928 190
Christopher Haster 12:7d3c3f7ce928 191 bool ATParser::vrecv(const char *response, va_list args)
Christopher Haster 12:7d3c3f7ce928 192 {
Christopher Haster 12:7d3c3f7ce928 193 // Iterate through each line in the expected response
Christopher Haster 12:7d3c3f7ce928 194 while (response[0]) {
Christopher Haster 12:7d3c3f7ce928 195 // Since response is const, we need to copy it into our buffer to
Christopher Haster 12:7d3c3f7ce928 196 // add the line's null terminator and clobber value-matches with asterisks.
Christopher Haster 12:7d3c3f7ce928 197 //
Christopher Haster 12:7d3c3f7ce928 198 // We just use the beginning of the buffer to avoid unnecessary allocations.
Christopher Haster 12:7d3c3f7ce928 199 int i = 0;
Christopher Haster 12:7d3c3f7ce928 200 int offset = 0;
Christopher Haster 12:7d3c3f7ce928 201
Christopher Haster 12:7d3c3f7ce928 202 while (response[i]) {
Raffaello 16:8f98e042b9b4 203 if (memcmp(&response[i+1-_delim_size_close], _delimiter_close, _delim_size_close) == 0) {
Christopher Haster 12:7d3c3f7ce928 204 i++;
Christopher Haster 12:7d3c3f7ce928 205 break;
Christopher Haster 12:7d3c3f7ce928 206 } else if (response[i] == '%' && response[i+1] != '%' && response[i+1] != '*') {
Christopher Haster 12:7d3c3f7ce928 207 _buffer[offset++] = '%';
Christopher Haster 12:7d3c3f7ce928 208 _buffer[offset++] = '*';
Christopher Haster 12:7d3c3f7ce928 209 i++;
Christopher Haster 12:7d3c3f7ce928 210 } else {
Christopher Haster 12:7d3c3f7ce928 211 _buffer[offset++] = response[i++];
Christopher Haster 12:7d3c3f7ce928 212 }
Christopher Haster 12:7d3c3f7ce928 213 }
Christopher Haster 12:7d3c3f7ce928 214
Christopher Haster 12:7d3c3f7ce928 215 // Scanf has very poor support for catching errors
Christopher Haster 12:7d3c3f7ce928 216 // fortunately, we can abuse the %n specifier to determine
Christopher Haster 12:7d3c3f7ce928 217 // if the entire string was matched.
Christopher Haster 12:7d3c3f7ce928 218 _buffer[offset++] = '%';
Christopher Haster 12:7d3c3f7ce928 219 _buffer[offset++] = 'n';
Christopher Haster 12:7d3c3f7ce928 220 _buffer[offset++] = 0;
Christopher Haster 12:7d3c3f7ce928 221
Christopher Haster 12:7d3c3f7ce928 222 // To workaround scanf's lack of error reporting, we actually
Christopher Haster 12:7d3c3f7ce928 223 // make two passes. One checks the validity with the modified
Christopher Haster 12:7d3c3f7ce928 224 // format string that only stores the matched characters (%n).
Christopher Haster 12:7d3c3f7ce928 225 // The other reads in the actual matched values.
Christopher Haster 12:7d3c3f7ce928 226 //
Christopher Haster 12:7d3c3f7ce928 227 // We keep trying the match until we succeed or some other error
Christopher Haster 12:7d3c3f7ce928 228 // derails us.
Christopher Haster 12:7d3c3f7ce928 229 int j = 0;
Raffaello 16:8f98e042b9b4 230 _delim_check = 0;
Christopher Haster 12:7d3c3f7ce928 231
Christopher Haster 12:7d3c3f7ce928 232 while (true) {
Christopher Haster 12:7d3c3f7ce928 233 // Recieve next character
Christopher Haster 12:7d3c3f7ce928 234 int c = getc();
Christopher Haster 12:7d3c3f7ce928 235 if (c < 0) {
Christopher Haster 12:7d3c3f7ce928 236 return false;
Christopher Haster 12:7d3c3f7ce928 237 }
Christopher Haster 12:7d3c3f7ce928 238 _buffer[offset + j++] = c;
Christopher Haster 12:7d3c3f7ce928 239 _buffer[offset + j] = 0;
Christopher Haster 12:7d3c3f7ce928 240
Christopher Haster 12:7d3c3f7ce928 241 // Check for match
Christopher Haster 12:7d3c3f7ce928 242 int count = -1;
Christopher Haster 12:7d3c3f7ce928 243 sscanf(_buffer+offset, _buffer, &count);
Christopher Haster 12:7d3c3f7ce928 244
Christopher Haster 12:7d3c3f7ce928 245 // We only succeed if all characters in the response are matched
Raffaello 16:8f98e042b9b4 246 if (count != -1)
Raffaello 16:8f98e042b9b4 247 {
Raffaello 16:8f98e042b9b4 248 if (j > count) {
Raffaello 16:8f98e042b9b4 249
Raffaello 16:8f98e042b9b4 250 if(c == _delimiter_close[_delim_check]) {
Raffaello 16:8f98e042b9b4 251
Raffaello 16:8f98e042b9b4 252 _delim_check++;
Raffaello 16:8f98e042b9b4 253
Raffaello 16:8f98e042b9b4 254 if(_delim_check == _delim_size_close) {
Raffaello 16:8f98e042b9b4 255
Raffaello 16:8f98e042b9b4 256 debug_if(dbg_on, "AT= %s\r\n", _buffer+offset);
Raffaello 16:8f98e042b9b4 257 // Reuse the front end of the buffer
Raffaello 16:8f98e042b9b4 258 memcpy(_buffer, response, i);
Raffaello 16:8f98e042b9b4 259 _buffer[i] = 0;
Christopher Haster 12:7d3c3f7ce928 260
Raffaello 16:8f98e042b9b4 261 // Store the found results
Raffaello 16:8f98e042b9b4 262 vsscanf(_buffer+offset, _buffer, args);
Christopher Haster 12:7d3c3f7ce928 263
Raffaello 16:8f98e042b9b4 264 // Jump to next line and continue parsing
Raffaello 16:8f98e042b9b4 265 response += i;
Raffaello 16:8f98e042b9b4 266 break;
Raffaello 16:8f98e042b9b4 267 }
Raffaello 16:8f98e042b9b4 268 } else {
Raffaello 16:8f98e042b9b4 269 _delim_check = 0;
Raffaello 16:8f98e042b9b4 270 }
Raffaello 16:8f98e042b9b4 271 }
Christopher Haster 12:7d3c3f7ce928 272 }
Christopher Haster 12:7d3c3f7ce928 273
Christopher Haster 13:46a18ad08efc 274 // Clear the buffer when we hit a newline or ran out of space
Christopher Haster 13:46a18ad08efc 275 // running out of space usually means we ran into binary data
Christopher Haster 13:46a18ad08efc 276 if (j+1 >= _buffer_size - offset ||
Raffaello 16:8f98e042b9b4 277 strcmp(&_buffer[offset + j-_delim_size_close], _delimiter_close) == 0) {
Christopher Haster 13:46a18ad08efc 278
Christopher Haster 12:7d3c3f7ce928 279 debug_if(dbg_on, "AT< %s", _buffer+offset);
Christopher Haster 12:7d3c3f7ce928 280 j = 0;
Christopher Haster 12:7d3c3f7ce928 281 }
Christopher Haster 12:7d3c3f7ce928 282 }
Christopher Haster 12:7d3c3f7ce928 283 }
Christopher Haster 12:7d3c3f7ce928 284
Christopher Haster 12:7d3c3f7ce928 285 return true;
Christopher Haster 12:7d3c3f7ce928 286 }
Christopher Haster 12:7d3c3f7ce928 287
Christopher Haster 12:7d3c3f7ce928 288
Christopher Haster 12:7d3c3f7ce928 289 // Mapping to vararg functions
Christopher Haster 12:7d3c3f7ce928 290 int ATParser::printf(const char *format, ...)
Christopher Haster 12:7d3c3f7ce928 291 {
Christopher Haster 12:7d3c3f7ce928 292 va_list args;
Christopher Haster 12:7d3c3f7ce928 293 va_start(args, format);
Christopher Haster 12:7d3c3f7ce928 294 int res = vprintf(format, args);
Christopher Haster 12:7d3c3f7ce928 295 va_end(args);
Christopher Haster 12:7d3c3f7ce928 296 return res;
Christopher Haster 12:7d3c3f7ce928 297 }
Christopher Haster 12:7d3c3f7ce928 298
Christopher Haster 12:7d3c3f7ce928 299 int ATParser::scanf(const char *format, ...)
Christopher Haster 12:7d3c3f7ce928 300 {
Christopher Haster 12:7d3c3f7ce928 301 va_list args;
Christopher Haster 12:7d3c3f7ce928 302 va_start(args, format);
Christopher Haster 12:7d3c3f7ce928 303 int res = vscanf(format, args);
Christopher Haster 12:7d3c3f7ce928 304 va_end(args);
Christopher Haster 12:7d3c3f7ce928 305 return res;
Christopher Haster 12:7d3c3f7ce928 306 }
Christopher Haster 12:7d3c3f7ce928 307
Christopher Haster 12:7d3c3f7ce928 308 bool ATParser::send(const char *command, ...)
Christopher Haster 12:7d3c3f7ce928 309 {
Christopher Haster 12:7d3c3f7ce928 310 va_list args;
Christopher Haster 12:7d3c3f7ce928 311 va_start(args, command);
Christopher Haster 12:7d3c3f7ce928 312 bool res = vsend(command, args);
Christopher Haster 12:7d3c3f7ce928 313 va_end(args);
Christopher Haster 12:7d3c3f7ce928 314 return res;
Christopher Haster 12:7d3c3f7ce928 315 }
Christopher Haster 12:7d3c3f7ce928 316
Christopher Haster 12:7d3c3f7ce928 317 bool ATParser::recv(const char *response, ...)
Christopher Haster 12:7d3c3f7ce928 318 {
Christopher Haster 12:7d3c3f7ce928 319 va_list args;
Christopher Haster 12:7d3c3f7ce928 320 va_start(args, response);
Christopher Haster 12:7d3c3f7ce928 321 bool res = vrecv(response, args);
Christopher Haster 12:7d3c3f7ce928 322 va_end(args);
Christopher Haster 12:7d3c3f7ce928 323 return res;
Christopher Haster 12:7d3c3f7ce928 324 }