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:
parent
d83ec632e3
commit
c58b71b899
330
drivers/at/at.c
330
drivers/at/at.c
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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
|
||||
|
@ -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 },
|
||||
|
@ -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:
|
2
tests/drivers/at/tests-with-config/native_stdin
Normal file
2
tests/drivers/at/tests-with-config/native_stdin
Normal file
@ -0,0 +1,2 @@
|
||||
init 0 115200
|
||||
emulate_dce
|
Loading…
Reference in New Issue
Block a user