From 9c9fad6da543d7c54adf27024f5f26bac9a83a45 Mon Sep 17 00:00:00 2001
From: Kaspar Schleiser <kaspar@schleiser.de>
Date: Tue, 13 Dec 2016 14:34:13 +0100
Subject: [PATCH] drivers: initial commit of generic AT parser module

---
 drivers/Makefile.dep |   5 ++
 drivers/at/Makefile  |   1 +
 drivers/at/at.c      | 209 +++++++++++++++++++++++++++++++++++++++++++
 drivers/include/at.h | 173 +++++++++++++++++++++++++++++++++++
 4 files changed, 388 insertions(+)
 create mode 100644 drivers/at/Makefile
 create mode 100644 drivers/at/at.c
 create mode 100644 drivers/include/at.h

diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep
index 4a690c0246..fd399515e6 100644
--- a/drivers/Makefile.dep
+++ b/drivers/Makefile.dep
@@ -21,6 +21,11 @@ ifneq (,$(filter apa102,$(USEMODULE)))
   FEATURES_REQUIRED += periph_gpio
 endif
 
+ifneq (,$(filter at,$(USEMODULE)))
+    USEMODULE += fmt
+    USEMODULE += xtimer
+endif
+
 ifneq (,$(filter at30tse75x,$(USEMODULE)))
   USEMODULE += xtimer
   FEATURES_REQUIRED += periph_i2c
diff --git a/drivers/at/Makefile b/drivers/at/Makefile
new file mode 100644
index 0000000000..48422e909a
--- /dev/null
+++ b/drivers/at/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/drivers/at/at.c b/drivers/at/at.c
new file mode 100644
index 0000000000..06df0261c9
--- /dev/null
+++ b/drivers/at/at.c
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+#include <errno.h>
+#include <string.h>
+
+#include "at.h"
+#include "fmt.h"
+#include "isrpipe.h"
+#include "periph/uart.h"
+#include "xtimer.h"
+
+#define ENABLE_DEBUG (0)
+#include "debug.h"
+
+#ifndef AT_PRINT_INCOMING
+#define AT_PRINT_INCOMING (0)
+#endif
+
+int at_dev_init(at_dev_t *dev, uart_t uart, uint32_t baudrate, char *buf, size_t bufsize)
+{
+    dev->uart = uart;
+    isrpipe_init(&dev->isrpipe, buf, bufsize);
+    uart_init(uart, baudrate, (uart_rx_cb_t) isrpipe_write_one,
+              &dev->isrpipe);
+
+    return 0;
+}
+
+int at_expect_bytes(at_dev_t *dev, const char *bytes, size_t len, uint32_t timeout)
+{
+    while (len) {
+        char c;
+        int res;
+        if ((res = isrpipe_read_timeout(&dev->isrpipe, &c, 1, timeout)) == 1) {
+            if (AT_PRINT_INCOMING) {
+                print(&c, 1);
+            }
+            if (!(c == *bytes++)) {
+                return -1;
+            }
+            len--;
+        }
+        else {
+            return res;
+        }
+    }
+
+    return 0;
+}
+
+int at_send_cmd(at_dev_t *dev, const char *command, uint32_t timeout)
+{
+    unsigned cmdlen = strlen(command);
+
+    uart_write(dev->uart, (const uint8_t *)command, cmdlen);
+    uart_write(dev->uart, (const uint8_t *)"\n", 1);
+
+    if (at_expect_bytes(dev, command, cmdlen, timeout)) {
+        return -1;
+    }
+
+    if (at_expect_bytes(dev, "\r\n", 2, timeout)) {
+        return -2;
+    }
+
+    return 0;
+}
+
+void at_drain(at_dev_t *dev)
+{
+    char _tmp[16];
+    int res;
+
+    do {
+        res = isrpipe_read_timeout(&dev->isrpipe, _tmp, sizeof(_tmp), 10000U);
+    } while (res > 0);
+}
+
+ssize_t at_send_cmd_get_resp(at_dev_t *dev, const char *command, char *resp_buf, size_t len, uint32_t timeout)
+{
+    ssize_t res = -1;
+
+    at_drain(dev);
+
+    res = at_send_cmd(dev, command, timeout);
+    if (res) {
+        goto out;
+    }
+
+    res = at_readline(dev, resp_buf, len, timeout);
+    if (res == 0) {
+        /* skip possible empty line */
+        res = at_readline(dev, resp_buf, len, timeout);
+    }
+
+out:
+    return res;
+}
+
+ssize_t at_send_cmd_get_lines(at_dev_t *dev, const char *command, char *resp_buf, size_t len, uint32_t timeout)
+{
+    ssize_t res = -1;
+    size_t bytes_left = len - 1;
+    char *pos = resp_buf;
+
+    memset(resp_buf, '\0', len);
+
+    at_drain(dev);
+
+    res = at_send_cmd(dev, command, timeout);
+    if (res) {
+        goto out;
+    }
+
+    while(1) {
+        res = at_readline(dev, pos, bytes_left, timeout);
+        if (res == 0) {
+            continue;
+        }
+        else if (res > 0) {
+            bytes_left -= res;
+            if ((res == 2) && (strncmp(pos, "OK", 2) == 0)) {
+                res = len - bytes_left;
+                break;
+            }
+            else if ((res == 5) && (strncmp(pos, "ERROR", 5) == 0)) {
+                return -1;
+            }
+            else if (strncmp(pos, "+CME ERROR:", 11) == 0) {
+                return -1;
+            }
+            else {
+                pos += res;
+                if (bytes_left) {
+                    *pos++ = '\n';
+                    bytes_left--;
+                }
+                else {
+                    return -1;
+                }
+            }
+        }
+        else {
+            break;
+        }
+    }
+
+out:
+    return res;
+}
+
+int at_send_cmd_wait_ok(at_dev_t *dev, const char *command, uint32_t timeout)
+{
+    int res;
+    char resp_buf[32];
+
+    res = at_send_cmd_get_resp(dev, command, resp_buf, sizeof(resp_buf), timeout);
+    if (res > 0) {
+        if (strcmp(resp_buf, "OK") == 0) {
+            res = 0;
+        }
+        else {
+            res = -1;
+        }
+    }
+
+    return res;
+}
+
+ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, uint32_t timeout)
+{
+    ssize_t res = -1;
+    char *resp_pos = resp_buf;
+
+    memset(resp_buf, 0, len);
+
+    while (len) {
+        int read_res;
+        if ((read_res = isrpipe_read_timeout(&dev->isrpipe, resp_pos, 1, timeout)) == 1) {
+            if (AT_PRINT_INCOMING) {
+                print(resp_pos, read_res);
+            }
+            if (*resp_pos == '\r') {
+                continue;
+            }
+            if (*resp_pos == '\n') {
+                *resp_pos = '\0';
+                res = resp_pos - resp_buf;
+                goto out;
+            }
+
+            resp_pos += read_res;
+            len -= read_res;
+        }
+        else if (read_res == -ETIMEDOUT) {
+            res = -ETIMEDOUT;
+            break;
+        }
+    }
+
+out:
+    return res;
+}
diff --git a/drivers/include/at.h b/drivers/include/at.h
new file mode 100644
index 0000000000..1637893ca3
--- /dev/null
+++ b/drivers/include/at.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2017 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup    drivers_at AT (Hayes) command set library
+ * @ingroup     drivers
+ * @brief       AT (Hayes) command set library
+ *
+ * This module provides functions to interact with devices using AT commands.
+ *
+ * Most functions compare the bytes echoed by the device with what they
+ * intended to send, and bail out if there's no match.
+ *
+ * Furthermore, the library tries to copy with difficulties regarding different
+ * line endings. It usually sends "<command><CR>", but expects
+ * "<command>\LF\CR" as echo.
+ *
+ * As a debugging aid, when compiled with "-DAT_PRINT_INCOMING=1", every input
+ * byte gets printed.
+ * @{
+ *
+ * @file
+ *
+ * @brief       AT (Hayes) library interface
+ * @author      Kaspar Schleiser <kaspar@schleiser.de>
+ */
+
+#ifndef AT_H
+#define AT_H
+
+#include <stdint.h>
+
+#include "isrpipe.h"
+#include "periph/uart.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief AT device structure
+ */
+typedef struct {
+    isrpipe_t isrpipe;      /**< isrpipe used for getting data from uart */
+    uart_t uart;            /**< UART device where the AT device is attached */
+} at_dev_t;
+
+
+/**
+ * @brief   Initialize AT device struct
+ *
+ * @param[in]   dev         struct to initialize
+ * @param[in]   uart        UART the device is connected to
+ * @param[in]   baudrate    baudrate of the device
+ * @param[in]   buf         input buffer
+ * @param[in]   bufsize     size of @p buf
+ *
+ * @returns     0 on success
+ * @returns     <0 otherwise
+ */
+int at_dev_init(at_dev_t *dev, uart_t uart, uint32_t baudrate, char *buf, size_t bufsize);
+
+/**
+ * @brief   simple command helper
+ *
+ * This function sends an AT command to the device and waits for "OK".
+ *
+ * @param[in]   dev     device to operate on
+ * @param[in]   command command string to send
+ * @param[in]   timeout timeout (in usec)
+ *
+ * @returns     0 when device answers "OK"
+ * @returns     <0 otherwise
+ */
+int at_send_cmd_wait_ok(at_dev_t *dev, const char *command, uint32_t timeout);
+
+/**
+ * @brief   send AT command, wait for response
+ *
+ * This function will send the supplied @p command, then wait and return one
+ * line of response.
+ *
+ * A possible empty line will be skipped.
+ *
+ * @param[in]   dev         device to operate on
+ * @param[in]   command     command to send
+ * @param[out]  resp_buf    buffer for storing response
+ * @param[in]   len         len of @p buffer
+ * @param[in]   timeout     timeout (in usec)
+ *
+ * @returns     lenght of response on success
+ * @returns     <0 on error
+ */
+ssize_t at_send_cmd_get_resp(at_dev_t *dev, const char *command, char *resp_buf, size_t len, uint32_t timeout);
+
+/**
+ * @brief   send AT command, wait for multiline response
+ *
+ * This function will send the supplied @p command, then return all response
+ * lines until the device sends "OK".
+ *
+ * If a line starts with "ERROR" or "+CME ERROR:", or the buffer is full, the
+ * function returns -1.
+ *
+ * @param[in]   dev         device to operate on
+ * @param[in]   command     command to send
+ * @param[out]  resp_buf    buffer for storing response
+ * @param[in]   len         len of @p buffer
+ * @param[in]   timeout     timeout (in usec)
+ *
+ * @returns     lenght of response on success
+ * @returns     <0 on error
+ */
+ssize_t at_send_cmd_get_lines(at_dev_t *dev, const char *command, char *resp_buf, size_t len, uint32_t timeout);
+
+/**
+ * @brief   Expect bytes from device
+ *
+ * @param[in]   dev     device to operate on
+ * @param[in]   bytes   buffer containing bytes to expect
+ * @param[in]   len     number of bytes to expect
+ * @param[in]   timeout timeout (in usec)
+ *
+ * @returns     0 on success
+ * @returns     <0 otherwise
+ */
+int at_expect_bytes(at_dev_t *dev, const char *bytes, size_t len, uint32_t timeout);
+
+/**
+ * @brief   send command to device
+ *
+ * @param[in]   dev     device to operate on
+ * @param[in]   command command to send
+ * @param[in]   timeout timeout (in usec)
+ *
+ * @returns     0 on success
+ * @returns     <0 otherwise
+ */
+int at_send_cmd(at_dev_t *dev, const char *command, uint32_t timeout);
+
+/**
+ * @brief   read a line from device
+ *
+ * @param[in]   dev         device to operate on
+ * @param[in]   resp_buf    buffer to store line
+ * @param[in]   len         size of @p buffer
+ * @param[in]   timeout     timeout (in usec)
+ *
+ * @returns     line length on success
+ * @returns     <0 on error
+ */
+ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, uint32_t timeout);
+
+/**
+ * @brief   drain device input buffer
+ *
+ * This function drains any possible bytes waiting in the device's input
+ * buffer.
+ *
+ * @param[in]   dev     device to operate on
+ */
+void at_drain(at_dev_t *dev);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* AT_H */
-- 
GitLab