1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2025-01-17 05:12:57 +01:00

drivers/at: parse +CME/+CMS responses and save error value

This commit is contained in:
Mihai Renea 2024-01-12 16:52:06 +01:00
parent d83ec632e3
commit c58b71b899
6 changed files with 429 additions and 162 deletions

View File

@ -41,6 +41,7 @@
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include "at.h"
@ -77,6 +78,18 @@ static void _event_process_urc(event_t *_event)
}
#endif
static ssize_t at_readline_skip_empty_stop_at_str(at_dev_t *dev, char *resp_buf,
size_t len, bool keep_eol,
char const *substr, uint32_t timeout);
static size_t at_readline_stop_at_str(at_dev_t *dev, char *resp_buf, size_t len,
bool keep_eol, char const *substr,
uint32_t timeout);
static inline bool starts_with(char const *str, char const *prefix)
{
return strncmp(str, prefix, strlen(prefix)) == 0;
}
static void _isrpipe_write_one_wrapper(void *_dev, uint8_t data)
{
at_dev_t *dev = (at_dev_t *) _dev;
@ -88,18 +101,50 @@ static void _isrpipe_write_one_wrapper(void *_dev, uint8_t data)
#endif
}
int at_dev_init(at_dev_t *dev, uart_t uart, uint32_t baudrate, char *buf, size_t bufsize)
int at_dev_init(at_dev_t *dev, at_dev_init_t const *init)
{
dev->uart = uart;
dev->uart = init->uart;
assert(init->rp_buf_size >= 16);
dev->rp_buf = init->rp_buf;
dev->rp_buf_size = init->rp_buf_size;
#if IS_USED(MODULE_AT_URC_ISR)
dev->awaiting_response = false;
dev->event.handler = _event_process_urc;
#endif
isrpipe_init(&dev->isrpipe, (uint8_t *)buf, bufsize);
isrpipe_init(&dev->isrpipe, (uint8_t *)init->rx_buf, init->rx_buf_size);
return uart_init(uart, baudrate, _isrpipe_write_one_wrapper, dev);
return uart_init(init->uart, init->baudrate, _isrpipe_write_one_wrapper, dev);
}
int at_parse_resp(at_dev_t *dev, char const *resp)
{
if (*resp == '\0') {
return 1;
}
if (starts_with(resp, CONFIG_AT_RECV_OK)) {
dev->rp_buf[0] = '\0';
return 0;
}
if (starts_with(resp, CONFIG_AT_RECV_ERROR)) {
return -1;
}
/* A specific command may return either CME or CMS, we need not differentiate */
if (!starts_with(resp, "+CME ERROR: ") &&
!starts_with(resp, "+CMS ERROR: ")) {
/* neither `OK` nor error, must be a response or URC */
return 1;
}
resp += strlen("+CMx ERROR: ");
size_t resp_len = strlen(resp);
if (resp_len + 1 > dev->rp_buf_size) {
return -ENOBUFS;
}
/* dev->rp_buf and resp may overlap */
memmove(dev->rp_buf, resp, resp_len + 1);
return -AT_ERR_EXTENDED;
}
int at_expect_bytes(at_dev_t *dev, const char *bytes, uint32_t timeout)
@ -224,8 +269,7 @@ int at_send_cmd(at_dev_t *dev, const char *command, uint32_t timeout)
return -1;
}
if (at_expect_bytes(dev, CONFIG_AT_SEND_EOL AT_RECV_EOL_1 AT_RECV_EOL_2,
timeout)) {
if (at_expect_bytes(dev, CONFIG_AT_SEND_EOL, timeout)) {
return -2;
}
}
@ -275,53 +319,64 @@ ssize_t at_send_cmd_get_resp_wait_ok(at_dev_t *dev, const char *command, const c
{
ssize_t res;
ssize_t res_ok;
char ok_buf[64];
at_drain(dev);
res = at_send_cmd(dev, command, timeout);
if (res) {
goto out;
return res;
}
/* URCs may occur right after the command has been sent and before the
* expected response */
do {
res = at_readline_skip_empty(dev, resp_buf, len, false, timeout);
while ((res = at_readline_skip_empty(dev, resp_buf, len, false, timeout)) >= 0) {
if (!resp_prefix || *resp_prefix == '\0') {
break;
}
/* Strip the expected prefix */
if (res > 0 && resp_prefix && *resp_prefix) {
size_t prefix_len = strlen(resp_prefix);
if (strncmp(resp_buf, resp_prefix, prefix_len) == 0) {
size_t remaining_len = strlen(resp_buf) - prefix_len;
/* The one extra byte in the copy is the terminating nul byte */
memmove(resp_buf, resp_buf + prefix_len, remaining_len + 1);
res -= prefix_len;
break;
}
size_t prefix_len = strlen(resp_prefix);
if (starts_with(resp_buf, resp_prefix)) {
size_t remaining_len = strlen(resp_buf) - prefix_len;
/* The one extra byte in the copy is the terminating nul byte */
memmove(resp_buf, resp_buf + prefix_len, remaining_len + 1);
res -= prefix_len;
break;
}
} while (res >= 0);
/* wait for OK */
if (res >= 0) {
res_ok = at_readline_skip_empty(dev, ok_buf, sizeof(ok_buf), false, timeout);
if (res_ok < 0) {
return -1;
res = at_parse_resp(dev, resp_buf);
if (res == 0) {
/* empty response */
return 0;
}
ssize_t len_ok = sizeof(CONFIG_AT_RECV_OK) - 1;
if ((len_ok != 0) && (strcmp(ok_buf, CONFIG_AT_RECV_OK) == 0)) {
if (res < 0) {
return res;
}
#if IS_USED(MODULE_AT_URC)
else {
/* Something else than OK */
res = -1;
clist_foreach(&dev->urc_list, _check_urc, resp_buf);
}
#endif
}
out:
return res;
if (res < 0) {
return res;
}
/* wait for OK */
res_ok = at_readline_skip_empty(dev, dev->rp_buf, dev->rp_buf_size, false, timeout);
if (res_ok < 0) {
return -1;
}
res_ok = at_parse_resp(dev, dev->rp_buf);
if (res_ok == 0) {
return res;
}
if (res_ok < 0) {
return res_ok;
}
/* Neither OK nor error, go figure... */
return -1;
}
ssize_t at_send_cmd_get_lines(at_dev_t *dev, const char *command,
char *resp_buf, size_t len, bool keep_eol, uint32_t timeout)
ssize_t at_send_cmd_get_lines(at_dev_t *dev, const char *command, char *resp_buf,
size_t len, bool keep_eol, uint32_t timeout)
{
const char eol[] = AT_RECV_EOL_1 AT_RECV_EOL_2;
assert(sizeof(eol) > 1);
@ -334,123 +389,113 @@ ssize_t at_send_cmd_get_lines(at_dev_t *dev, const char *command,
res = at_send_cmd(dev, command, timeout);
if (res) {
goto out;
return res;
}
memset(resp_buf, '\0', len);
while (1) {
res = at_readline(dev, pos, bytes_left, keep_eol, timeout);
bool first_line = true;
while (bytes_left) {
if (first_line) {
res = at_readline_skip_empty(dev, pos, bytes_left, keep_eol, timeout);
first_line = false;
} else {
/* keep subsequent empty lines, for whatever reason */
res = at_readline(dev, pos, bytes_left, keep_eol, timeout);
}
if (res == 0) {
if (bytes_left) {
*pos++ = eol[sizeof(eol) - 2];
bytes_left--;
}
*pos++ = eol[sizeof(eol) - 2];
bytes_left--;
continue;
}
else if (res > 0) {
bytes_left -= res;
size_t len_ok = sizeof(CONFIG_AT_RECV_OK) - 1;
size_t len_error = sizeof(CONFIG_AT_RECV_ERROR) - 1;
if (((size_t )res == (len_ok + keep_eol)) &&
(len_ok != 0) &&
(strncmp(pos, CONFIG_AT_RECV_OK, len_ok) == 0)) {
size_t const res_len = res;
bytes_left -= res_len;
res = at_parse_resp(dev, pos);
switch (res) {
case 0: /* OK */
res = len - bytes_left;
break;
}
else if (((size_t )res == (len_error + keep_eol)) &&
(len_error != 0) &&
(strncmp(pos, CONFIG_AT_RECV_ERROR, len_error) == 0)) {
return -1;
}
else if (strncmp(pos, "+CME ERROR:", 11) == 0) {
return -2;
}
else if (strncmp(pos, "+CMS ERROR:", 11) == 0) {
return -2;
}
else {
pos += res;
if (bytes_left) {
*pos++ = eol[sizeof(eol) - 2];
bytes_left--;
}
else {
return -1;
return res;
case 1: /* response or URC */
pos += res_len;
if (bytes_left == 0) {
return -ENOBUFS;
}
*pos++ = eol[sizeof(eol) - 2];
bytes_left--;
continue;
default: /* <0 */
return res;
}
}
else {
break;
return res;
}
}
out:
return res;
return -ENOBUFS;
}
int at_send_cmd_wait_prompt(at_dev_t *dev, const char *command, uint32_t timeout)
{
unsigned cmdlen = strlen(command);
at_drain(dev);
uart_write(dev->uart, (const uint8_t *)command, cmdlen);
uart_write(dev->uart, (const uint8_t *)CONFIG_AT_SEND_EOL, AT_SEND_EOL_LEN);
if (!IS_ACTIVE(CONFIG_AT_SEND_SKIP_ECHO)) {
if (at_wait_bytes(dev, command, timeout)) {
return -1;
}
if (at_expect_bytes(dev, CONFIG_AT_SEND_EOL, timeout)) {
return -2;
}
int res = at_send_cmd(dev, command, timeout);
if (res) {
return res;
}
return at_wait_bytes(dev, ">", timeout);
do {
res = at_readline_skip_empty_stop_at_str(dev, dev->rp_buf, dev->rp_buf_size,
false, ">", timeout);
if (res < 0) {
break;
}
if (strstr(dev->rp_buf, ">")) {
return 0;
}
res = at_parse_resp(dev, dev->rp_buf);
#ifdef MODULE_AT_URC
if (res == 1) {
clist_foreach(&dev->urc_list, _check_urc, dev->rp_buf);
}
#endif
} while (res >= 0);
return res;
}
int at_send_cmd_wait_ok(at_dev_t *dev, const char *command, uint32_t timeout)
{
int res;
char resp_buf[64];
res = at_send_cmd_get_resp(dev, command, resp_buf, sizeof(resp_buf),
timeout);
size_t const len_ok = sizeof(CONFIG_AT_RECV_OK) - 1;
size_t const len_err = sizeof(CONFIG_AT_RECV_ERROR) - 1;
size_t const len_cme_cms = sizeof("+CME ERROR:") - 1;
res = at_send_cmd_get_resp(dev, command, dev->rp_buf, dev->rp_buf_size, timeout);
while (res >= 0) {
if (strncmp(resp_buf, CONFIG_AT_RECV_OK, len_ok) == 0) {
return 0;
res = at_parse_resp(dev, dev->rp_buf);
if (res < 1) {
return res;
}
else if (strncmp(resp_buf, CONFIG_AT_RECV_ERROR, len_err) == 0) {
return -1;
}
else if (strncmp(resp_buf, "+CME ERROR:", len_cme_cms) == 0) {
return -2;
}
else if (strncmp(resp_buf, "+CMS ERROR:", len_cme_cms) == 0) {
return -2;
}
/* probably a sneaky URC */
#ifdef MODULE_AT_URC
clist_foreach(&dev->urc_list, _check_urc, resp_buf);
clist_foreach(&dev->urc_list, _check_urc, dev->rp_buf);
#endif
res = at_readline_skip_empty(dev, resp_buf, sizeof(resp_buf), false, timeout);
res = at_readline_skip_empty(dev, dev->rp_buf, dev->rp_buf_size, false, timeout);
}
return res;
}
ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, bool keep_eol, uint32_t timeout)
/* Used to detect a substring that may happen before the EOL. For example,
* Ublox LTE modules don't add EOL after the prompt character `>`. */
static size_t at_readline_stop_at_str(at_dev_t *dev, char *resp_buf, size_t len,
bool keep_eol, char const *substr,
uint32_t timeout)
{
const char eol[] = AT_RECV_EOL_1 AT_RECV_EOL_2;
assert(sizeof(eol) > 1);
ssize_t res = -1;
ssize_t res = 0;
char *resp_pos = resp_buf;
#if IS_USED(MODULE_AT_URC_ISR)
@ -458,8 +503,15 @@ ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, bool keep_eol, ui
#endif
memset(resp_buf, 0, len);
while (len) {
size_t substr_len = 0;
if (substr) {
substr_len = strlen(substr);
if (substr_len == 0) {
return -EINVAL;
}
}
char const *substr_p = resp_buf;
while (len > 1) {
int read_res;
if ((read_res = isrpipe_read_timeout(&dev->isrpipe, (uint8_t *)resp_pos,
1, timeout)) == 1) {
@ -473,12 +525,19 @@ ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, bool keep_eol, ui
}
if (*resp_pos == eol[sizeof(eol) - 2]) {
*resp_pos = '\0';
res = resp_pos - resp_buf;
goto out;
break;
}
resp_pos += read_res;
len -= read_res;
if (substr && (size_t)(resp_pos - resp_buf) >= substr_len) {
if (strncmp(substr_p, substr, substr_len) == 0) {
break;
} else {
substr_p++;
}
}
}
else if (read_res == -ETIMEDOUT) {
res = -ETIMEDOUT;
@ -486,28 +545,59 @@ ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, bool keep_eol, ui
}
}
out:
#if IS_USED(MODULE_AT_URC_ISR)
dev->awaiting_response = false;
#endif
if (res < 0) {
*resp_buf = '\0';
} else {
res = resp_pos - resp_buf;
}
return res;
}
ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, bool keep_eol,
uint32_t timeout)
{
return at_readline_stop_at_str(dev, resp_buf, len, keep_eol, NULL, timeout);
}
static ssize_t at_readline_skip_empty_stop_at_str(at_dev_t *dev, char *resp_buf,
size_t len, bool keep_eol,
char const *substr, uint32_t timeout)
{
ssize_t res = at_readline_stop_at_str(dev, resp_buf, len, keep_eol, substr, timeout);
if (res == 0) {
/* skip possible empty line */
res = at_readline_stop_at_str(dev, resp_buf, len, keep_eol, substr, timeout);
}
return res;
}
ssize_t at_readline_skip_empty(at_dev_t *dev, char *resp_buf, size_t len,
bool keep_eol, uint32_t timeout)
{
ssize_t res = at_readline(dev, resp_buf, len, keep_eol, timeout);
if (res == 0) {
/* skip possible empty line */
res = at_readline(dev, resp_buf, len, keep_eol, timeout);
}
return res;
return at_readline_skip_empty_stop_at_str(dev, resp_buf, len, keep_eol, NULL, timeout);
}
int at_wait_ok(at_dev_t *dev, uint32_t timeout)
{
while (1) {
ssize_t res = at_readline_skip_empty(dev, dev->rp_buf, dev->rp_buf_size,
false, timeout);
if (res < 0) {
return res;
}
res = at_parse_resp(dev, dev->rp_buf);
if (res < 1) {
return res;
}
#ifdef MODULE_AT_URC
clist_foreach(&dev->urc_list, _check_urc, dev->rp_buf);
#endif
}
}
#ifdef MODULE_AT_URC
void at_add_urc(at_dev_t *dev, at_urc_t *urc)
{
@ -531,7 +621,7 @@ static int _check_urc(clist_node_t *node, void *arg)
DEBUG("Trying to match with %s\n", urc->code);
if (strncmp(buf, urc->code, strlen(urc->code)) == 0) {
if (starts_with(buf, urc->code)) {
urc->cb(urc->arg, buf);
return 1;
}

View File

@ -17,10 +17,10 @@
* intended to send, and bail out if there's no match.
*
* Furthermore, the library tries to cope with difficulties regarding different
* line endings. It usually sends "<command><CR>", but expects
* "<command>\LF\CR" as echo.
* 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
* As a debugging aid, when compiled with `-DAT_PRINT_INCOMING=1`, every input
* byte gets printed.
*
* ## Unsolicited Result Codes (URC) ##
@ -44,6 +44,24 @@
* character is detected and there is no pending response. This works by posting
* an @ref sys_event "event" to an event thread that processes the URCs.
*
* ## Error reporting ##
* Most DCEs (Data Circuit-terminating Equipment, aka modem) can return extra error
* information instead of the rather opaque "ERROR" message. They have the form:
* - `+CMS ERROR: err_code>` for SMS-related commands
* - `+CME ERROR: <err_code>` for other commands
*
* For `+CME`, this behavior is usually off by default and can be toggled with:
* `AT+CMEE=<type>`
* where `<type>` may be:
* - 0 disable extended error reporting, return `ERROR`
* - 1 enable extended error reporting, with `<err_code>` integer
* - 2 enable extended error reporting, with `<err_code>` as string
* Check your DCE's manual for more information.
*
* Some of the API calls below support detailed error reporting. Whenever they
* detect extended error responses, -AT_ERR_EXTENDED is returned and `<err_code>`
* can be retrieved by calling @ref at_get_err_info().
*
* @{
*
* @file
@ -168,6 +186,9 @@ typedef struct {
#endif /* MODULE_AT_URC */
/** Error cause can be further investigated. */
#define AT_ERR_EXTENDED 200
/** Shortcut for getting send end of line length */
#define AT_SEND_EOL_LEN (sizeof(CONFIG_AT_SEND_EOL) - 1)
@ -177,6 +198,8 @@ typedef struct {
typedef struct {
isrpipe_t isrpipe; /**< isrpipe used for getting data from uart */
uart_t uart; /**< UART device where the AT device is attached */
char *rp_buf; /**< response parsing buffer */
size_t rp_buf_size; /**< response parsing buffer size */
#ifdef MODULE_AT_URC
clist_node_t urc_list; /**< list to keep track of all registered urc's */
#ifdef MODULE_AT_URC_ISR
@ -186,19 +209,49 @@ typedef struct {
#endif
} at_dev_t;
/**
* @brief AT device initialization parameters
*/
typedef struct {
uart_t uart; /**< UART device where the AT device is attached */
uint32_t baudrate; /**< UART device baudrate */
char *rx_buf; /**< UART rx buffer */
size_t rx_buf_size; /**< UART rx buffer size */
/**
* Response parsing buffer - used for classifying DCE responses and holding
* detailed error information. Must be at least 16 bytes.
* If you don't care about URCs (MODULE_AT_URC is undefined) this must only
* be large enough to hold responses like `OK`, `ERROR` or `+CME ERROR: <err_code>`.
* Otherwise adapt its size to the maximum length of the URCs you are expecting
* and actually care about. */
char *rp_buf;
size_t rp_buf_size; /**< response parsing buffer size */
} at_dev_init_t;
/**
* @brief Get extended error information of the last command sent.
*
* If a previous at_* method returned with -AT_ERR_EXTENDED, you can retrieve
* a pointer to the error string with this.
*
* @param[in] dev device to operate on
*
* @retval string containing the error value.
*/
static inline char const *at_get_err_info(at_dev_t *dev)
{
return dev->rp_buf;
}
/**
* @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
* @param[in] init init struct, may be destroyed after return
*
* @retval success code UART_OK on success
* @retval error code UART_NODEV or UART_NOBAUD otherwise
*/
int at_dev_init(at_dev_t *dev, uart_t uart, uint32_t baudrate, char *buf, size_t bufsize);
int at_dev_init(at_dev_t *dev, at_dev_init_t const *init);
/**
* @brief Simple command helper
@ -210,7 +263,9 @@ int at_dev_init(at_dev_t *dev, uart_t uart, uint32_t baudrate, char *buf, size_t
* @param[in] timeout timeout (in usec)
*
* @retval 0 when device answers "OK"
* @retval <0 otherwise
* @retval -AT_ERR_EXTENDED if failed and a error code can be retrieved with
* @ref at_get_err_info() (i.e. DCE answered with `CMx ERROR`)
* @retval <0 other failures
*/
int at_send_cmd_wait_ok(at_dev_t *dev, const char *command, uint32_t timeout);
@ -220,12 +275,14 @@ int at_send_cmd_wait_ok(at_dev_t *dev, const char *command, uint32_t timeout);
* This function sends the supplied @p command, then waits for the prompt (>)
* character and returns
*
* @param[in] dev device to operate on
* @param[in] command command string to send
* @param[in] timeout timeout (in usec)
* @param[in] dev device to operate on
* @param[in] command command string to send
* @param[in] timeout timeout (in usec)
*
* @retval 0 when prompt is received
* @retval <0 otherwise
* @retval 0 when prompt is received
* @retval -AT_ERR_EXTENDED if failed and a error code can be retrieved with
* @ref at_get_err_info() (i.e. DCE answered with `CMx ERROR`)
* @retval <0 other failures
*/
int at_send_cmd_wait_prompt(at_dev_t *dev, const char *command, uint32_t timeout);
@ -246,7 +303,8 @@ int at_send_cmd_wait_prompt(at_dev_t *dev, const char *command, uint32_t timeout
* @retval n length of response on success
* @retval <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);
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 response plus OK
@ -264,7 +322,9 @@ ssize_t at_send_cmd_get_resp(at_dev_t *dev, const char *command, char *resp_buf,
* @param[in] timeout timeout (in usec)
*
* @retval n length of response on success
* @retval <0 on error
* @retval -AT_ERR_EXTENDED if failed and a error code can be retrieved with
* @ref at_get_err_info() (i.e. DCE answered with `CMx ERROR`)
* @retval <0 other failures
*/
ssize_t at_send_cmd_get_resp_wait_ok(at_dev_t *dev, const char *command, const char *resp_prefix,
char *resp_buf, size_t len, uint32_t timeout);
@ -275,9 +335,8 @@ ssize_t at_send_cmd_get_resp_wait_ok(at_dev_t *dev, const char *command, const c
* This function sends the supplied @p command, then returns all response
* lines until the device sends "OK".
*
* If a line starts with "ERROR" or the buffer is full, the function returns -1.
* If a line starts with "+CME ERROR" or +CMS ERROR", the function returns -2.
* In this case resp_buf contains the error string.
* If a line contains a DTE error response, this function stops and returns
* accordingly.
*
* @param[in] dev device to operate on
* @param[in] command command to send
@ -287,8 +346,9 @@ ssize_t at_send_cmd_get_resp_wait_ok(at_dev_t *dev, const char *command, const c
* @param[in] timeout timeout (in usec)
*
* @retval n length of response on success
* @retval -1 on error
* @retval -2 on CMS or CME error
* @retval -AT_ERR_EXTENDED if failed and a error code can be retrieved with
* @ref at_get_err_info() (i.e. DCE answered with `CMx ERROR`)
* @retval <0 other failures
*/
ssize_t at_send_cmd_get_lines(at_dev_t *dev, const char *command, char *resp_buf,
size_t len, bool keep_eol, uint32_t timeout);
@ -369,6 +429,22 @@ ssize_t at_recv_bytes(at_dev_t *dev, char *bytes, size_t len, uint32_t timeout);
*/
int at_send_cmd(at_dev_t *dev, const char *command, uint32_t timeout);
/**
* @brief Parse a response from the device.
*
* This is always called automatically in functions that may return -AT_ERR_EXTENDED.
* However, if you read the response by other methods (e.g. with @ref at_recv_bytes()),
* you might want to call this on the response so that you don't have to parse it yourself.
*
* @retval 0 if the response is "OK"
* @retval -AT_ERR_EXTENDED if the response is `+CMx ERROR: <err>`, and `<err>`
* has been successfully copied to @p dev->rp_buf
* @retval -1 if the response is "ERROR", or `+CMx ERROR: <err>` but `<err>`
* could not be copied
* @retval 1 otherwise
*/
int at_parse_resp(at_dev_t *dev, char const *resp);
/**
* @brief Read a line from device
*
@ -398,6 +474,21 @@ ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, bool keep_eol, ui
ssize_t at_readline_skip_empty(at_dev_t *dev, char *resp_buf, size_t len,
bool keep_eol, uint32_t timeout);
/**
* @brief Wait for an OK response.
*
* Useful when crafting the command-response sequence by yourself.
*
* @param[in] dev device to operate on
* @param[in] timeout timeout (in usec)
*
* @retval 0 when device answers "OK"
* @retval -AT_ERR_EXTENDED if failed and a error code can be retrieved with
* @ref at_get_err_info() (i.e. DCE answered with `CMx ERROR`)
* @retval <0 other failures
*/
int at_wait_ok(at_dev_t *dev, uint32_t timeout);
/**
* @brief Drain device input buffer
*

View File

@ -2,7 +2,14 @@ include ../Makefile.drivers_common
USEMODULE += shell
USEMODULE += at
USEMODULE += at_urc_isr_medium
USEMODULE += at_urc
# Enable if the DCE is sending only \n for EOL
# CFLAGS += -DAT_RECV_EOL_1=""
# Enable this to test with echo off. Don't forget to disable echo in
# 'tests-with-config/emulated_dce.py' too!
# CFLAGS += -DCONFIG_AT_SEND_SKIP_ECHO=1
# we are printing from the event thread, we need more stack
CFLAGS += -DEVENT_THREAD_MEDIUM_STACKSIZE=1024

View File

@ -20,6 +20,7 @@
* @}
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -33,6 +34,7 @@
static at_dev_t at_dev;
static char buf[256];
static char resp[1024];
static char rp_buf[256];
static int init(int argc, char **argv)
{
@ -51,9 +53,15 @@ static int init(int argc, char **argv)
printf("Wrong UART device number - should be in range 0-%d.\n", UART_NUMOF - 1);
return 1;
}
int res = at_dev_init(&at_dev, UART_DEV(uart), baudrate, buf, sizeof(buf));
at_dev_init_t at_init_params = {
.baudrate = baudrate,
.rp_buf = rp_buf,
.rp_buf_size = sizeof(rp_buf),
.rx_buf = buf,
.rx_buf_size = sizeof(buf),
.uart = UART_DEV(uart),
};
int res = at_dev_init(&at_dev, &at_init_params);
/* check the UART initialization return value and respond as needed */
if (res == UART_NODEV) {
puts("Invalid UART device given!");
@ -110,7 +118,8 @@ static int send_lines(int argc, char **argv)
}
ssize_t len;
if ((len = at_send_cmd_get_lines(&at_dev, argv[1], resp, sizeof(resp), true, 10 * US_PER_SEC)) < 0) {
if ((len = at_send_cmd_get_lines(&at_dev, argv[1], resp, sizeof(resp),
true, 10 * US_PER_SEC)) < 0) {
puts("Error");
return 1;
}
@ -285,7 +294,7 @@ static int remove_urc(int argc, char **argv)
}
#endif
static int sneaky_urc(int argc, char **argv)
static int emulate_dce(int argc, char **argv)
{
(void)argc;
(void)argv;
@ -299,9 +308,21 @@ static int sneaky_urc(int argc, char **argv)
#endif
res = at_send_cmd_wait_ok(&at_dev, "AT+CFUN=1", US_PER_SEC);
if (res) {
puts("Error AT+CFUN=1");
printf("%u: Error AT+CFUN=1: %d\n", __LINE__, res);
res = 1;
goto exit;
}
res = at_send_cmd(&at_dev, "AT+CFUN=1", US_PER_SEC);
if (res) {
printf("%u: Error AT+CFUN=1: %d\n", __LINE__, res);
res = 1;
goto exit;
}
res = at_wait_ok(&at_dev, US_PER_SEC);
if (res) {
printf("%u: Error AT+CFUN=1: %d\n", __LINE__, res);
res = 1;
goto exit;
}
@ -310,39 +331,72 @@ static int sneaky_urc(int argc, char **argv)
"+CEREG:", resp_buf,
sizeof(resp_buf), US_PER_SEC);
if (res < 0) {
puts("Error AT+CEREG?");
printf("%u: Error AT+CEREG?: %d\n", __LINE__, res);
res = 1;
goto exit;
}
res = at_send_cmd_wait_prompt(&at_dev, "AT+USECMNG=0,0,\"cert\",128", US_PER_SEC);
if (res) {
puts("Error AT+USECMNG");
printf("%u: Error AT+USECMNG: %d\n", __LINE__, res);
res = 1;
goto exit;
}
res = at_send_cmd_wait_prompt(&at_dev, "AT+PROMPTERROR", US_PER_SEC);
if (res != -AT_ERR_EXTENDED) {
printf("%u: Error AT+PROMPTERROR: %d\n", __LINE__, res);
res = 1;
goto exit;
}
res = atol(at_get_err_info(&at_dev));
if (res != 1984) {
printf("%u: Error AT+PROMPTERROR: %d\n", __LINE__, res);
res = 1;
goto exit;
}
res = at_send_cmd_wait_ok(&at_dev, "AT+CFUN=8", US_PER_SEC);
if (res != -1) {
puts("Error AT+CFUN=8");
printf("%u: Error AT+CFUN=8: %d\n", __LINE__, res);
res = 1;
goto exit;
}
res = at_send_cmd_wait_ok(&at_dev, "AT+CFUN=9", US_PER_SEC);
if (res != -2) {
puts("Error AT+CFUN=9");
if (res != -AT_ERR_EXTENDED) {
printf("%u: Error AT+CFUN=9: %d\n", __LINE__, res);
res = 1;
goto exit;
}
res = atol(at_get_err_info(&at_dev));
if (res != 666) {
printf("%u: Error AT+CFUN=9: %d\n", __LINE__, res);
res = 1;
goto exit;
}
res = at_send_cmd_get_lines(&at_dev, "AT+GETTWOLINES", resp_buf,
sizeof(resp_buf), false, US_PER_SEC);
if (res < 0) {
printf("%u: Error AT+GETTWOLINES: %d\n", __LINE__, res);
res = 1;
goto exit;
}
if (strcmp(resp_buf, "first_line\nsecond_line\nOK")) {
printf("%u: Error AT+GETTWOLINES: response not matching\n", __LINE__);
res = 1;
goto exit;
}
res = 0;
exit:
#ifdef MODULE_AT_URC
at_remove_urc(&at_dev, &urc);
#endif
printf("%s finished with %d\n", __func__, res);
return res;
}
@ -357,7 +411,7 @@ static const shell_command_t shell_commands[] = {
{ "drain", "Drain AT device", drain },
{ "power_on", "Power on AT device", power_on },
{ "power_off", "Power off AT device", power_off },
{ "sneaky_urc", "Test sneaky URC interference", sneaky_urc},
{ "emulate_dce", "Test against the DCE emulation script.", emulate_dce},
#ifdef MODULE_AT_URC
{ "add_urc", "Register an URC", add_urc },
{ "remove_urc", "De-register an URC", remove_urc },

View File

@ -13,17 +13,27 @@
# 1. Adapt the `EOL_IN`, `EOL_OUT`, `ECHO_ON` variables below to match your use case
# 2. Run this script with the baud rate and the serial dev the device is connected
# to, e.g.:
# $ ./sneaky_urc 115200 /dev/ttyUSB0
# $ ./emulate_dce.py 115200 /dev/ttyUSB0
# 4. run the test (e.g. make term)
# 5. inside the test console:
# a) run the `init` command (e.g. init 0 115200)
# b) run `sneaky_urc` command
# b) run `emulate_dce` command
#
# If the command echoing is enabled, you will miss the URCs, but the commands
# should work (e.g. the URC should not interfere with response parsing).
#
# If command echoing is enabled AND `MODULE_AT_URC` is defined, you should see
# *some* of the URCs being parsed.
#
# The easiest way is to run this test on native, with an socat tty bridge:
# a) run `$ socat -d -d pty,raw,echo=0 pty,raw,echo=0`
# socat will display the pty endpoints it just created, e.g:
# 2024/01/23 10:24:56 socat[45360] N PTY is /dev/pts/9
# 2024/01/23 10:24:56 socat[45360] N PTY is /dev/pts/10
# b) pick one pty endpoint (e.g. /dev/pts/9) and run this script:
# `$ emulate_dce.py 115200 /dev/pts/9`
# c) pick the other endpoint and run the compiled binary
# ` $ ./bin/native/tests_at.elf -c /dev/pts/10 < tests-with-config/native_stdin`
import sys
import pexpect
@ -37,17 +47,20 @@ EOL_IN = '\r'
# EOL to send back to the device
EOL_OUT = '\r\n'
# Command echoing enabled
ECHO_ON = False
ECHO_ON = True
CFUN_CMD = "AT+CFUN=1" + EOL_IN
CFUN_ERR_CMD = "AT+CFUN=8" + EOL_IN
CFUN_CME_CMD = "AT+CFUN=9" + EOL_IN
CEREG_CMD = "AT+CEREG?" + EOL_IN
USECMNG_CMD = "AT+USECMNG=0,0,\"cert\",128" + EOL_IN
GETTWOLINES_CMD = "AT+GETTWOLINES" + EOL_IN
PROMPTERROR_CMD = "AT+PROMPTERROR" + EOL_IN
while True:
try:
idx = tty.expect_exact([CFUN_CMD, CFUN_ERR_CMD, CFUN_CME_CMD, CEREG_CMD, USECMNG_CMD])
idx = tty.expect_exact([CFUN_CMD, CFUN_ERR_CMD, CFUN_CME_CMD, CEREG_CMD, USECMNG_CMD,
GETTWOLINES_CMD, PROMPTERROR_CMD])
if idx == 0:
print(CFUN_CMD)
tty.send(EOL_OUT + "+CSCON: 1" + EOL_OUT)
@ -65,7 +78,7 @@ while True:
tty.send(EOL_OUT + "+CSCON: 1" + EOL_OUT)
if ECHO_ON:
tty.send(CFUN_CME_CMD)
tty.send(EOL_OUT + "+CME ERROR:" + EOL_OUT)
tty.send(EOL_OUT + "+CME ERROR: 666" + EOL_OUT)
elif idx == 3:
print(CEREG_CMD)
tty.send(EOL_OUT + "+CSCON: 1" + EOL_OUT)
@ -79,6 +92,16 @@ while True:
if ECHO_ON:
tty.send(USECMNG_CMD)
tty.send(">")
elif idx == 5:
print(GETTWOLINES_CMD)
if ECHO_ON:
tty.send(GETTWOLINES_CMD)
tty.send(EOL_OUT + "first_line" + EOL_OUT + "second_line" + EOL_OUT + "OK" + EOL_OUT)
elif idx == 6:
print(PROMPTERROR_CMD)
if ECHO_ON:
tty.send(PROMPTERROR_CMD)
tty.send(EOL_OUT + "+CME ERROR: 1984" + EOL_OUT)
except pexpect.EOF:
print("ERROR: EOF")
except pexpect.TIMEOUT:

View File

@ -0,0 +1,2 @@
init 0 115200
emulate_dce