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