/* * Copyright (C) 2001-2004 by egnite Software GmbH. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ */ /* * $Id: chat.c 4610 2012-09-17 10:48:26Z haraldkipp $ */ #include #include #include #include #include #include #include #include uint8_t *chat_report; #ifdef NUTDEBUG_CHAT #include static FILE *__chat_trs; /*!< \brief Chat trace output stream. */ static uint8_t __chat_trf; /*!< \brief Chat trace flags. */ /*! * \brief Control chat tracing. * * \param stream Pointer to a previously opened stream or null to * disable trace output. * \param flags Flags to enable specific traces. */ void NutTraceChat(FILE * stream, uint8_t flags) { if (stream) __chat_trs = stream; if (__chat_trs) { __chat_trf = flags; fprintf(__chat_trs, "Chat trace flags=0x%02x\n", flags); } else __chat_trf = 0; } static INLINE void NutTracePrintf(const char *fmt, ...) { if (__chat_trf) { va_list ap; va_start(ap, fmt); vfprintf(__chat_trs, fmt, ap); va_end(ap); } } static INLINE void NutTracePutChar(int ch) { if (__chat_trf) { if (ch > 31 && ch < 127) { fputc(ch, __chat_trs); } else { fprintf(__chat_trs, "\\x%02x", ch); } } } #else #ifdef __GNUC__ #define NutTracePrintf(fmt,...) #else static INLINE void NutTracePrintf(const char *fmt, ...) {} #endif #define NutTracePutChar(ch) #endif /* * Special version of strchr, handling escaped characters. */ static char *strechr(const char *str, int c) { while (*str) { if (*str == '\\') { if (*++str) str++; } else if (*str == c) return (char *) str; else str++; } return 0; } /*! * \brief Wait for a specific string to appear. * * \param ci Pointer to a NUTCHAT structure, which must have been * created by NutChatCreate(). * \param str Expected string. May be empty if nothing is expected. * * \return 0 on success, 3 in case of a timeout error while waiting * for an expected string, or the index of an abort string * plus 4, if one has been received. */ int NutChatExpectString(NUTCHAT * ci, char *str) { char ch; uint8_t m; uint8_t i; char *cp = str; NutTracePrintf("Expect '%s', got '", str); while (*cp) { /* * Read the next character. Return on timeout. */ if (_read(ci->chat_fd, &ch, 1) != 1) { NutTracePrintf("' TIMEOUT\n"); return 3; } NutTracePutChar(ch); /* * If the character doesn't match the next expected one, * then restart from the beginning of the expected string. */ if (ch != *cp) { cp = str; } /* * If the character matched, advance the pointer into * the expected string. */ if (ch == *cp) { cp++; } /* * Check for abort strings. */ for (i = 0; i < ci->chat_aborts; i++) { m = ci->chat_abomat[i]; if (ch == ci->chat_abort[i][m]) { if (ci->chat_abort[i][++m] == 0) { NutTracePrintf("' ABORT\n"); return i + 4; } } else m = (ch == ci->chat_abort[i][0]); ci->chat_abomat[i] = m; } /* * Check for report strings. */ if (ci->chat_report_state > 0) { m = ci->chat_repmat; if (ci->chat_report_state == 2) { chat_report[m++] = ch; } else if (ch == ci->chat_report_search[m]) { chat_report[m++] = ch; if (ci->chat_report_search[m] == 0) { ci->chat_report_state = 2; } } else { m = (ch == ci->chat_report_search[0]); } ci->chat_repmat = m; } } /* * Read the remainder of the string before NutChatSendString clears it */ if (ci->chat_report_state == 2) { m = ci->chat_repmat; /* not needed... (but not nice to remove it) */ while (m < CHAT_MAX_REPORT_SIZE) { if (_read(ci->chat_fd, &ch, 1) != 1 || ch < ' ') { break; } chat_report[m++] = ch; NutTracePutChar(ch); } ci->chat_report_state = 0; /* Only find first occurence */ chat_report[m] = 0; } NutTracePrintf("'\n"); return 0; } /* * \return 0 on success or 2 in case of an I/O error. */ static int NutChatSendString(int fd, char *str) { int rc = 0; uint8_t eol = 1; uint8_t skip; char ch; NutTracePrintf("Send '%s'\n", str); /* Flush input buffer. */ _read(fd, 0, 0); while (*str && eol && rc == 0) { ch = *str++; skip = 0; if (ch == '^') { ch = *str++; ch &= 0x1f; } else if (ch == '\\') { ch = *str++; switch (ch) { case 'b': ch = '\b'; break; case 'c': eol = 0; skip = 1; break; case 'd': NutSleep(1000); skip = 1; break; case 'n': ch = '\n'; break; case 'N': ch = 0; break; case 'p': NutDelay(100); skip = 1; break; case 'r': ch = '\r'; break; case 's': ch = ' '; break; case 't': ch = '\t'; break; default: if (ch >= '0' && ch <= '7') { ch &= 0x07; if (*str >= '0' && *str <= '7') { ch <<= 3; ch |= *str++ & 0x07; if (*str >= '0' && *str <= '7') { ch <<= 3; ch |= *str++ & 0x07; } } } break; } } if (skip) skip = 0; else { NutDelay(10); if (_write(fd, &ch, 1) != 1) rc = 2; else _write(fd, 0, 0); } } if (eol && rc == 0 && _write(fd, "\r", 1) != 1) rc = 2; else _write(fd, 0, 0); return rc; } /* * \param ci Pointer to a NUTCHAT structure, which must have been * created by NutChatCreate(). * * \return 0 on success, 1 in case of invalid parameters, 2 in case * of an I/O error, 3 in case of a timeout error while waiting * for an expected string, or the index of an abort string plus * 4, if one has been received. */ int NutChatExpect(NUTCHAT * ci, char *str) { int rc = 0; char *reply; char *subexpect; /* * Process special keywords. */ if (strcmp(str, "ABORT") == 0) { ci->chat_arg = CHAT_ARG_ABORT; return 0; } if (strcmp(str, "TIMEOUT") == 0) { ci->chat_arg = CHAT_ARG_TIMEOUT; return 0; } if (strcmp(str, "REPORT") == 0) { ci->chat_repmat = 0; /* not needed ??? */ ci->chat_report_state = 1; ci->chat_arg = CHAT_ARG_REPORT; return 0; } /* * Process expected string. */ while (str) { if ((reply = strechr(str, '-')) != 0) { *reply++ = 0; if ((subexpect = strechr(reply, '-')) != 0) *subexpect++ = 0; } else subexpect = 0; if ((rc = NutChatExpectString(ci, str)) != 3 || reply == 0) break; if ((rc = NutChatSendString(ci->chat_fd, reply)) != 0) break; str = subexpect; } return rc; } /*! * \brief Process a chat send argument. * * \param ci Pointer to a NUTCHAT structure, which must have been * created by NutChatCreate(). * \param str String containing the chat send argument. * * \return 0 on success, 1 in case of invalid parameters, 2 in case * of an I/O error, 3 in case of a timeout error while waiting * for an expected string, or the index of an abort string plus * 4, if one has been received. */ int NutChatSend(NUTCHAT * ci, char *str) { char *cp; char ch; long lv; /* * Add a chat abort string. */ if (ci->chat_arg == CHAT_ARG_ABORT) { ci->chat_arg = CHAT_ARG_SEND; if (ci->chat_aborts >= CHAT_MAX_ABORTS) return 1; cp = malloc(strlen(str) + 1); ci->chat_abort[ci->chat_aborts++] = cp; while (*str) { ch = *str++; if (ch == '^') *cp = *str++ & 0x1f; else if (ch == '\\') { ch = *str++; switch (ch) { case 'b': *cp++ = '\b'; break; case 'n': *cp++ = '\n'; break; case 'r': *cp++ = '\r'; break; case 's': *cp++ = ' '; break; case 't': *cp++ = '\t'; break; default: if (ch >= '0' && ch <= '7') { ch &= 0x07; if (*str >= '0' && *str <= '7') { ch <<= 3; ch |= *str++ & 0x07; if (*str >= '0' && *str <= '7') { ch <<= 3; ch |= *str++ & 0x07; } } } if (ch) *cp++ = ch; break; } } else *cp++ = ch; } *cp = 0; return 0; } /* * Set chat timeout. */ if (ci->chat_arg == CHAT_ARG_TIMEOUT) { ci->chat_arg = CHAT_ARG_SEND; lv = atol(str) * 1000L; if (lv <= 0) lv = CHAT_DEFAULT_TIMEOUT * 1000L; _ioctl(ci->chat_fd, UART_SETREADTIMEOUT, &lv); return 0; } /* * Set report string */ if (ci->chat_arg == CHAT_ARG_REPORT) { ci->chat_arg = CHAT_ARG_SEND; chat_report = malloc(CHAT_MAX_REPORT_SIZE + 1); cp = malloc(strlen(str) + 1); ci->chat_report_search = cp; while (*str) *cp++ = *str++; /* Do it the easy way, not as smart and thorough as the abort string... */ *cp = 0; return 0; } /* * Send the argument string. */ return NutChatSendString(ci->chat_fd, str); } /*! * \brief Create a NUTCHAT structure. * * \return Pointer to a new NUTCHAT structure. */ NUTCHAT *NutChatCreate(int fd) { NUTCHAT *ci; if ((ci = malloc(sizeof(NUTCHAT))) != 0) { memset(ci, 0, sizeof(NUTCHAT)); ci->chat_fd = fd; } return ci; } /*! * \brief Destroy a previously created NUTCHAT structure. * * \param ci Pointer to a NUTCHAT structure, which must have been * created by NutChatCreate(). */ void NutChatDestroy(NUTCHAT * ci) { uint8_t i; if (ci) { for (i = 0; i < ci->chat_aborts; i++) free(ci->chat_abort[i]); free(ci); } } /*! * \brief Chat processor. * * \return 0 on success, 1 in case of invalid parameters, 2 in case * of an I/O error, 3 in case of a timeout error while waiting * for an expected string, or the index of an abort string plus * 4, if one has been received. */ static int NutChatProc(int fd, char *script) { int rc = 0; char sendflg = 0; NUTCHAT *ci; char *arg; uint32_t to; uint32_t irto; uint32_t iwto; /* * Initialize the chat info structure. */ if ((ci = NutChatCreate(fd)) == 0) return 2; /* * Save current and set default timeouts. */ _ioctl(fd, UART_GETREADTIMEOUT, &irto); _ioctl(fd, UART_GETWRITETIMEOUT, &iwto); to = 45000; _ioctl(fd, UART_SETREADTIMEOUT, &to); to = 5000; _ioctl(fd, UART_SETWRITETIMEOUT, &to); /* * This loop splits up the chat string into arguments and * alternating calls NutChatSend and NutChatExpect. */ while (*script && rc == 0) { /* * Skip leading spaces. */ if (*script == ' ' || *script == '\t' || *script == '\r' || *script == '\n') { script++; continue; } /* * Collect a quoted argument. */ if (*script == '"' || *script == '\'') { char quote = *script++; arg = script; while (*script != quote) { if (*script == 0) { rc = 1; break; } if (*script++ == '\\') { if (*script) ++script; } } } /* * Collect an argument upto the next space. */ else { arg = script; while (*script && *script != ' ' && *script != '\t' && *script != '\r' && *script != '\n') ++script; } if (*script) *script++ = 0; /* * Either send or expect the collected argument. */ if (rc == 0) { if (sendflg) rc = NutChatSend(ci, arg); else rc = NutChatExpect(ci, arg); sendflg = !sendflg; } } /* * Restore initial timeout values. */ _ioctl(fd, UART_SETREADTIMEOUT, &irto); _ioctl(fd, UART_SETWRITETIMEOUT, &iwto); /* * Release allocated memory. */ NutChatDestroy(ci); return rc; } /*! * \brief Execute a conversational exchange with a serial device. * * Its primary purpose is to establish a modem connection. * * \param fd Descriptor of a previously opened device. * \param script Pointer to a string containing the chat script. * * \return 0 on success, 1 in case of invalid parameters, 2 in case * of an I/O error, 3 in case of a timeout error while waiting * for an expected string, or the index of an abort string plus * 4, if one has been received. */ int NutChat(int fd, const char *script) { int rc = -1; char *buf; /* * Work with a local copy of the chat string. */ if ((buf = strdup(script)) != NULL) { rc = NutChatProc(fd, buf); free(buf); } return rc; } /*! * \brief Execute a conversational exchange with a serial device. * * Similar to NutChat() except that the chat string is located in * program memory. * * \return 0 on success, 1 in case of invalid parameters, 2 in case * of an I/O error, 3 in case of a timeout error while waiting * for an expected string, or the index of an abort string plus * 4, if one has been received. */ #ifdef __HARVARD_ARCH__ int NutChat_P(int fd, PGM_P script) { int rc = -1; char *buf; /* * Work with a local copy of the chat string. */ if ((buf = malloc(strlen_P(script) + 1)) != 0) { strcpy_P(buf, script); rc = NutChatProc(fd, buf); free(buf); } return rc; } #endif