mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
Merge pull request #20423 from derMihai/mir/at_urc
drivers/at: fix URC handling and add better testing
This commit is contained in:
commit
e6f03db4e2
589
drivers/at/at.c
589
drivers/at/at.c
@ -6,41 +6,9 @@
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/* A note on URCs (Unsolicited Result Codes), regardless of whether URC handling
|
||||
* is enabled or not.
|
||||
*
|
||||
* Some DCEs (Data Circuit-terminating Equipment, aka modem), like the LTE
|
||||
* modules from uBlox define a grace period where URCs are guaranteed NOT to be
|
||||
* sent as the time span between:
|
||||
* - the command EOL character reception AND command being internally accepted
|
||||
* - the EOL character of the last response line
|
||||
*
|
||||
* As follows, there is an indeterminate amount of time between:
|
||||
* - the command EOL character being sent
|
||||
* - the command EOL character reception AND command being internally accepted,
|
||||
* i.e. the begin of the grace period
|
||||
*
|
||||
* In other words, we can get a URC (or more?) just after issuing the command
|
||||
* and before the first line of response. The net effect is that such URCs will
|
||||
* appear to be the first line of response to the last issued command.
|
||||
*
|
||||
* The current solution is to skip characters that don't match the expected
|
||||
* response, at the expense of losing these URCs. Note, we may already lose URCs
|
||||
* when calling at_drain() just before any at_send_cmd(). Success partially
|
||||
* depends on whether command echoing is enabled or not:
|
||||
* 1. echo enabled: by observation, it seems that the grace period begins
|
||||
* BEFORE the echoed command. This has the advantage that we ALWAYS know what
|
||||
* the first line of response must look like and so if it doesn't, then it's a
|
||||
* URC. Thus, any procedure that calls at_send_cmd() will catch and discard
|
||||
* these URCs.
|
||||
* 2. echo disabled: commands that expect a response (e.g. at_send_cmd_get_resp_wait_ok())
|
||||
* will catch and discard any URC (or, if MODULE_AT_URC enabled, hand it over
|
||||
* to the URC callbacks). For the rest, it is the application's responsibility
|
||||
* to handle it.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
@ -49,7 +17,6 @@
|
||||
#include "isrpipe.h"
|
||||
#include "isrpipe/read_timeout.h"
|
||||
#include "periph/uart.h"
|
||||
#include "event/thread.h"
|
||||
|
||||
#define ENABLE_DEBUG 0
|
||||
#include "debug.h"
|
||||
@ -58,32 +25,19 @@
|
||||
#define AT_PRINT_INCOMING (0)
|
||||
#endif
|
||||
|
||||
#if defined(MODULE_AT_URC_ISR_LOWEST)
|
||||
#define AT_EVENT_PRIO EVENT_PRIO_LOWEST
|
||||
#elif defined(MODULE_AT_URC_ISR_MEDIUM)
|
||||
#define AT_EVENT_PRIO EVENT_PRIO_MEDIUM
|
||||
#elif defined(MODULE_AT_URC_ISR_HIGHEST)
|
||||
#define AT_EVENT_PRIO EVENT_PRIO_HIGHEST
|
||||
#endif
|
||||
|
||||
#if defined(MODULE_AT_URC)
|
||||
static int _check_urc(clist_node_t *node, void *arg);
|
||||
#endif
|
||||
|
||||
#if defined(MODULE_AT_URC_ISR)
|
||||
static void _event_process_urc(event_t *_event)
|
||||
{
|
||||
at_dev_t *dev = (at_dev_t *)container_of(_event, at_dev_t, event);
|
||||
at_process_urc(dev, 1000);
|
||||
}
|
||||
#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,
|
||||
static ssize_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 ssize_t read_line_or_echo(at_dev_t *dev, char const *cmd, char *resp_buf,
|
||||
size_t len, uint32_t timeout);
|
||||
|
||||
static inline bool starts_with(char const *str, char const *prefix)
|
||||
{
|
||||
@ -94,25 +48,17 @@ static void _isrpipe_write_one_wrapper(void *_dev, uint8_t data)
|
||||
{
|
||||
at_dev_t *dev = (at_dev_t *) _dev;
|
||||
isrpipe_write_one(&dev->isrpipe, data);
|
||||
#if defined(MODULE_AT_URC_ISR)
|
||||
if (data == AT_RECV_EOL_2[0] && !dev->awaiting_response) {
|
||||
event_post(AT_EVENT_PRIO, &dev->event);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int at_dev_init(at_dev_t *dev, at_dev_init_t const *init)
|
||||
{
|
||||
dev->uart = init->uart;
|
||||
assert(strlen(AT_RECV_EOL) >= 1);
|
||||
assert(init->rp_buf_size >= 16);
|
||||
|
||||
dev->uart = init->uart;
|
||||
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 *)init->rx_buf, init->rx_buf_size);
|
||||
|
||||
return uart_init(init->uart, init->baudrate, _isrpipe_write_one_wrapper, dev);
|
||||
@ -149,12 +95,7 @@ int at_parse_resp(at_dev_t *dev, char const *resp)
|
||||
|
||||
int at_expect_bytes(at_dev_t *dev, const char *bytes, uint32_t timeout)
|
||||
{
|
||||
int res = 0;
|
||||
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = true;
|
||||
#endif
|
||||
|
||||
ssize_t res = 0;
|
||||
while (*bytes) {
|
||||
char c;
|
||||
if ((res = isrpipe_read_timeout(&dev->isrpipe, (uint8_t *)&c, 1, timeout)) == 1) {
|
||||
@ -171,13 +112,8 @@ int at_expect_bytes(at_dev_t *dev, const char *bytes, uint32_t timeout)
|
||||
}
|
||||
}
|
||||
res = 0;
|
||||
|
||||
out:
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = false;
|
||||
#endif
|
||||
|
||||
return res;
|
||||
return (int)res;
|
||||
}
|
||||
|
||||
int at_wait_bytes(at_dev_t *dev, const char *bytes, uint32_t timeout)
|
||||
@ -197,27 +133,20 @@ void at_send_bytes(at_dev_t *dev, const char *bytes, size_t len)
|
||||
ssize_t at_recv_bytes(at_dev_t *dev, char *bytes, size_t len, uint32_t timeout)
|
||||
{
|
||||
char *resp_pos = bytes;
|
||||
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = true;
|
||||
#endif
|
||||
|
||||
while (len) {
|
||||
int read_res;
|
||||
if ((read_res = isrpipe_read_timeout(&dev->isrpipe, (uint8_t *)resp_pos,
|
||||
1, timeout)) == 1) {
|
||||
resp_pos += read_res;
|
||||
len -= read_res;
|
||||
ssize_t res;
|
||||
if ((res = isrpipe_read_timeout(&dev->isrpipe, (uint8_t *)resp_pos,
|
||||
1, timeout)) == 1) {
|
||||
if (AT_PRINT_INCOMING) {
|
||||
print(resp_pos, 1);
|
||||
}
|
||||
resp_pos += res;
|
||||
len -= res;
|
||||
}
|
||||
else if (read_res == -ETIMEDOUT) {
|
||||
else if (res == -ETIMEDOUT) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = false;
|
||||
#endif
|
||||
|
||||
return (resp_pos - bytes);
|
||||
}
|
||||
|
||||
@ -226,11 +155,7 @@ int at_recv_bytes_until_string(at_dev_t *dev, const char *string,
|
||||
{
|
||||
size_t len = 0;
|
||||
char *_string = (char *)string;
|
||||
int res = 0;
|
||||
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = true;
|
||||
#endif
|
||||
ssize_t res = 0;
|
||||
|
||||
while (*_string && len < *bytes_len) {
|
||||
char c;
|
||||
@ -249,12 +174,19 @@ int at_recv_bytes_until_string(at_dev_t *dev, const char *string,
|
||||
}
|
||||
}
|
||||
*bytes_len = len;
|
||||
return (int)res;
|
||||
}
|
||||
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = false;
|
||||
static int wait_echo(at_dev_t *dev, char const *command, uint32_t timeout)
|
||||
{
|
||||
ssize_t res;
|
||||
while ((res = read_line_or_echo(dev, command, dev->rp_buf, dev->rp_buf_size, timeout)) > 0) {
|
||||
/* keep reading until echo or some error happens. */
|
||||
#ifdef MODULE_AT_URC
|
||||
clist_foreach(&dev->urc_list, _check_urc, dev->rp_buf);
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
return (int)res;
|
||||
}
|
||||
|
||||
int at_send_cmd(at_dev_t *dev, const char *command, uint32_t timeout)
|
||||
@ -264,69 +196,139 @@ int at_send_cmd(at_dev_t *dev, const char *command, uint32_t timeout)
|
||||
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;
|
||||
}
|
||||
if (IS_ACTIVE(CONFIG_AT_SEND_SKIP_ECHO)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return wait_echo(dev, command, timeout);
|
||||
}
|
||||
|
||||
void at_drain(at_dev_t *dev)
|
||||
{
|
||||
uint8_t _tmp[16];
|
||||
int res;
|
||||
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = true;
|
||||
#endif
|
||||
ssize_t res;
|
||||
|
||||
do {
|
||||
/* consider no character within 10ms "drained" */
|
||||
res = isrpipe_read_timeout(&dev->isrpipe, _tmp, sizeof(_tmp), 10000U);
|
||||
} while (res > 0);
|
||||
}
|
||||
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = false;
|
||||
#endif
|
||||
static bool is_eol(char p)
|
||||
{
|
||||
return p == '\r' || p == '\n';
|
||||
}
|
||||
|
||||
static char *skip_leading_eol(char *line)
|
||||
{
|
||||
while (is_eol(*line)) {
|
||||
line++;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
static size_t trim_leading_eol(char *buf, size_t str_len)
|
||||
{
|
||||
char *p = skip_leading_eol(buf);
|
||||
if (p == buf) {
|
||||
/* not sure if memmove is a no-op in this case */
|
||||
return str_len;
|
||||
}
|
||||
size_t size_left = str_len - (size_t)(p - buf);
|
||||
/* +1 for the terminating \0 */
|
||||
memmove(buf, p, size_left + 1);
|
||||
return size_left;
|
||||
}
|
||||
|
||||
static size_t trim_trailing_eol(char *line, size_t str_len)
|
||||
{
|
||||
while (str_len && is_eol(line[str_len - 1])) {
|
||||
line[--str_len] = '\0';
|
||||
}
|
||||
return str_len;
|
||||
}
|
||||
|
||||
static size_t at_drain_n(at_dev_t *dev, size_t n)
|
||||
{
|
||||
unsigned char drain_buf[16];
|
||||
while (n > 0) {
|
||||
size_t const to_read = n > sizeof(drain_buf) ? sizeof(drain_buf) : n;
|
||||
ssize_t res = isrpipe_read_timeout(&dev->isrpipe, drain_buf, to_read, 10000U);
|
||||
if (res < 1) {
|
||||
break;
|
||||
}
|
||||
n -= res;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* @retval 0 if an echo was received and flushed
|
||||
* @retval length of the line if an URC was intercepted
|
||||
* @retval <0 on error */
|
||||
static ssize_t read_line_or_echo(at_dev_t *dev, char const *cmd, char *resp_buf,
|
||||
size_t len, uint32_t timeout)
|
||||
{
|
||||
size_t const cmd_len = strlen(cmd);
|
||||
if (cmd_len == 0 || len == 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (len == 1) {
|
||||
return -ENOBUFS;
|
||||
}
|
||||
/* We keep EOL in case the echoed command contains binary data and, by
|
||||
* chance, a EOL sequence. */
|
||||
ssize_t res = at_readline_skip_empty_stop_at_str(dev, resp_buf, len, true,
|
||||
cmd, timeout);
|
||||
bool overflow = false;
|
||||
if (res < 0) {
|
||||
if (res != -ENOBUFS) {
|
||||
return res;
|
||||
}
|
||||
/* fine to overflow when intercepting an echo */
|
||||
overflow = true;
|
||||
res = len - 1;
|
||||
}
|
||||
if ((unsigned)res > cmd_len) {
|
||||
/* definitely a URC */
|
||||
if (overflow) {
|
||||
/* URC didn't fit into the buffer so it's garbage */
|
||||
return -ENOBUFS;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
/* maybe a URC, but might also be:
|
||||
* 1. the command contained binary data and, by chance, a newline sequence
|
||||
* 2. the command overflowed the resp_buf */
|
||||
if (strncmp(cmd, resp_buf, res)) {
|
||||
/* no match, indeed a URC. */
|
||||
return trim_trailing_eol(resp_buf, res);
|
||||
}
|
||||
/* very good chance this is a echo, flush the rest */
|
||||
size_t const left_in_echo = cmd_len - res + AT_SEND_EOL_LEN;
|
||||
res = at_drain_n(dev, left_in_echo);
|
||||
if (res > 0) {
|
||||
res = -ETIMEDOUT;
|
||||
}
|
||||
|
||||
resp_buf[0] = '\0';
|
||||
return res;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
at_drain(dev);
|
||||
|
||||
res = at_send_cmd(dev, command, timeout);
|
||||
if (res) {
|
||||
goto out;
|
||||
}
|
||||
|
||||
res = at_readline_skip_empty(dev, resp_buf, len, false, timeout);
|
||||
|
||||
out:
|
||||
return res;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
ssize_t res;
|
||||
ssize_t res_ok;
|
||||
|
||||
at_drain(dev);
|
||||
|
||||
res = at_send_cmd(dev, command, timeout);
|
||||
ssize_t res = at_send_cmd(dev, command, timeout);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
return at_readline_skip_empty(dev, resp_buf, len, false, timeout);
|
||||
}
|
||||
|
||||
static ssize_t get_resp_with_prefix(at_dev_t *dev, const char *resp_prefix,
|
||||
char *resp_buf, size_t len, uint32_t timeout)
|
||||
{
|
||||
ssize_t res;
|
||||
/* URCs may occur right after the command has been sent and before the
|
||||
* expected response */
|
||||
while ((res = at_readline_skip_empty(dev, resp_buf, len, false, timeout)) >= 0) {
|
||||
@ -356,96 +358,89 @@ ssize_t at_send_cmd_get_resp_wait_ok(at_dev_t *dev, const char *command, const c
|
||||
}
|
||||
#endif
|
||||
}
|
||||
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;
|
||||
return res;
|
||||
}
|
||||
|
||||
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_resp_wait_ok(at_dev_t *dev, const char *command, const char *resp_prefix,
|
||||
char *resp_buf, size_t len, uint32_t timeout)
|
||||
{
|
||||
const char eol[] = AT_RECV_EOL_1 AT_RECV_EOL_2;
|
||||
assert(sizeof(eol) > 1);
|
||||
|
||||
ssize_t res;
|
||||
size_t bytes_left = len - 1;
|
||||
char *pos = resp_buf;
|
||||
|
||||
at_drain(dev);
|
||||
|
||||
res = at_send_cmd(dev, command, timeout);
|
||||
ssize_t res = at_send_cmd(dev, command, timeout);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
res = get_resp_with_prefix(dev, resp_prefix, resp_buf, len, timeout);
|
||||
if (res < 1) {
|
||||
/* error or OK (empty response) */
|
||||
return res;
|
||||
}
|
||||
/* got response, wait for OK */
|
||||
return at_wait_ok(dev, timeout);
|
||||
}
|
||||
|
||||
memset(resp_buf, '\0', len);
|
||||
#if IS_USED(MODULE_AT_URC)
|
||||
static char *next_line(char *p)
|
||||
{
|
||||
while (*p && *p != '\r' && *p != '\n') {
|
||||
p++;
|
||||
}
|
||||
return skip_leading_eol(p);
|
||||
}
|
||||
|
||||
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) {
|
||||
*pos++ = eol[sizeof(eol) - 2];
|
||||
bytes_left--;
|
||||
continue;
|
||||
}
|
||||
else if (res > 0) {
|
||||
size_t const res_len = res;
|
||||
bytes_left -= res_len;
|
||||
res = at_parse_resp(dev, pos);
|
||||
static void handle_urc_lines(at_dev_t *dev, char *resp_buf)
|
||||
{
|
||||
char *p = resp_buf;
|
||||
do {
|
||||
char *next = next_line(p);
|
||||
clist_foreach(&dev->urc_list, _check_urc, p);
|
||||
p = next;
|
||||
} while (*p);
|
||||
}
|
||||
#endif
|
||||
|
||||
switch (res) {
|
||||
case 0: /* OK */
|
||||
res = len - bytes_left;
|
||||
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;
|
||||
static ssize_t get_lines(at_dev_t *dev, char *resp_buf, size_t len, uint32_t timeout)
|
||||
{
|
||||
ssize_t res;
|
||||
char *pos = resp_buf;
|
||||
while ((res = at_readline_skip_empty(dev, pos, len, true, timeout)) > 0) {
|
||||
size_t const line_len = res;
|
||||
res = at_parse_resp(dev, pos);
|
||||
|
||||
len -= line_len;
|
||||
pos += line_len;
|
||||
|
||||
switch (res) {
|
||||
case 0: /* OK */
|
||||
return (size_t)(pos - resp_buf);
|
||||
case 1: /* response or URC */
|
||||
if (len == 0) {
|
||||
return -ENOBUFS;
|
||||
}
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
default: /* <0 */
|
||||
#if IS_USED(MODULE_AT_URC)
|
||||
/* DCE responded with an error. If we got some lines before that,
|
||||
* they must be URCs. */
|
||||
handle_urc_lines(dev, resp_buf);
|
||||
#endif
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return -ENOBUFS;
|
||||
return res;
|
||||
}
|
||||
|
||||
int at_send_cmd_wait_prompt(at_dev_t *dev, const char *command, uint32_t timeout)
|
||||
ssize_t at_send_cmd_get_lines(at_dev_t *dev, const char *command, char *resp_buf,
|
||||
size_t len, uint32_t timeout)
|
||||
{
|
||||
at_drain(dev);
|
||||
|
||||
int res = at_send_cmd(dev, command, timeout);
|
||||
ssize_t res = at_send_cmd(dev, command, timeout);
|
||||
if (res) {
|
||||
return res;
|
||||
}
|
||||
return get_lines(dev, resp_buf, len, timeout);
|
||||
}
|
||||
|
||||
static int wait_prompt(at_dev_t *dev, uint32_t timeout)
|
||||
{
|
||||
ssize_t res;
|
||||
do {
|
||||
res = at_readline_skip_empty_stop_at_str(dev, dev->rp_buf, dev->rp_buf_size,
|
||||
false, ">", timeout);
|
||||
@ -463,92 +458,86 @@ int at_send_cmd_wait_prompt(at_dev_t *dev, const char *command, uint32_t timeout
|
||||
#endif
|
||||
} while (res >= 0);
|
||||
|
||||
return res;
|
||||
return (int)res;
|
||||
}
|
||||
|
||||
int at_send_cmd_wait_prompt(at_dev_t *dev, const char *command, uint32_t timeout)
|
||||
{
|
||||
ssize_t res = at_send_cmd(dev, command, timeout);
|
||||
if (res) {
|
||||
return (int)res;
|
||||
}
|
||||
return wait_prompt(dev, timeout);
|
||||
}
|
||||
|
||||
int at_send_cmd_wait_ok(at_dev_t *dev, const char *command, uint32_t timeout)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = at_send_cmd_get_resp(dev, command, dev->rp_buf, dev->rp_buf_size, timeout);
|
||||
|
||||
while (res >= 0) {
|
||||
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
|
||||
res = at_readline_skip_empty(dev, dev->rp_buf, dev->rp_buf_size, false, timeout);
|
||||
int res = at_send_cmd(dev, command, timeout);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
}
|
||||
|
||||
return res;
|
||||
return at_wait_ok(dev, 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,
|
||||
static ssize_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 = 0;
|
||||
char *resp_pos = resp_buf;
|
||||
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = true;
|
||||
#endif
|
||||
|
||||
memset(resp_buf, 0, len);
|
||||
size_t substr_len = 0;
|
||||
if (len < 1) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (substr) {
|
||||
substr_len = strlen(substr);
|
||||
if (substr_len == 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
char *resp_pos = resp_buf;
|
||||
char const *substr_p = resp_buf;
|
||||
memset(resp_buf, 0, len);
|
||||
|
||||
while (len > 1) {
|
||||
int read_res;
|
||||
if ((read_res = isrpipe_read_timeout(&dev->isrpipe, (uint8_t *)resp_pos,
|
||||
1, timeout)) == 1) {
|
||||
if (AT_PRINT_INCOMING) {
|
||||
print(resp_pos, read_res);
|
||||
}
|
||||
if (sizeof(eol) > 2 && *resp_pos == eol[0]) {
|
||||
if (!keep_eol) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (*resp_pos == eol[sizeof(eol) - 2]) {
|
||||
*resp_pos = '\0';
|
||||
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 = isrpipe_read_timeout(&dev->isrpipe, (uint8_t *)resp_pos, 1, timeout);
|
||||
if (res < 1) {
|
||||
res = -ETIMEDOUT;
|
||||
break;
|
||||
}
|
||||
if (AT_PRINT_INCOMING) {
|
||||
print(resp_pos, 1);
|
||||
}
|
||||
|
||||
resp_pos++;
|
||||
len--;
|
||||
|
||||
if ((size_t)(resp_pos - resp_buf) >= strlen(AT_RECV_EOL)) {
|
||||
char *const eol_begin = resp_pos - strlen(AT_RECV_EOL);
|
||||
if (strcmp(eol_begin, AT_RECV_EOL) == 0) {
|
||||
if (keep_eol) {
|
||||
break;
|
||||
}
|
||||
*eol_begin = '\0';
|
||||
resp_pos -= strlen(AT_RECV_EOL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (substr && (size_t)(resp_pos - substr_p) >= substr_len) {
|
||||
if (strncmp(substr_p, substr, substr_len) == 0) {
|
||||
break;
|
||||
} else {
|
||||
substr_p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (len <= 1) {
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
#if IS_USED(MODULE_AT_URC_ISR)
|
||||
dev->awaiting_response = false;
|
||||
#endif
|
||||
|
||||
if (res < 0) {
|
||||
*resp_buf = '\0';
|
||||
} else {
|
||||
@ -567,11 +556,20 @@ 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);
|
||||
ssize_t res;
|
||||
if (len == 1) {
|
||||
/* Reading in a buffer of length 1 will forever return an empty line */
|
||||
return -ENOBUFS;
|
||||
}
|
||||
do {
|
||||
res = at_readline_stop_at_str(dev, resp_buf, len, keep_eol, substr, timeout);
|
||||
/* Trim any rogue EOL characters */
|
||||
if (res > 0) {
|
||||
res = trim_leading_eol(resp_buf, (size_t)res);
|
||||
} else if (res == -ENOBUFS) {
|
||||
res = trim_leading_eol(resp_buf, len - 1);
|
||||
}
|
||||
} while (res == 0);
|
||||
return res;
|
||||
|
||||
}
|
||||
@ -587,11 +585,11 @@ int at_wait_ok(at_dev_t *dev, uint32_t timeout)
|
||||
ssize_t res = at_readline_skip_empty(dev, dev->rp_buf, dev->rp_buf_size,
|
||||
false, timeout);
|
||||
if (res < 0) {
|
||||
return res;
|
||||
return (int)res;
|
||||
}
|
||||
res = at_parse_resp(dev, dev->rp_buf);
|
||||
if (res < 1) {
|
||||
return res;
|
||||
return (int)res;
|
||||
}
|
||||
#ifdef MODULE_AT_URC
|
||||
clist_foreach(&dev->urc_list, _check_urc, dev->rp_buf);
|
||||
@ -619,9 +617,10 @@ static int _check_urc(clist_node_t *node, void *arg)
|
||||
const char *buf = arg;
|
||||
at_urc_t *urc = container_of(node, at_urc_t, list_node);
|
||||
|
||||
DEBUG("Trying to match with %s\n", urc->code);
|
||||
DEBUG("Trying to match %s with %s\n", buf, urc->code);
|
||||
|
||||
if (starts_with(buf, urc->code)) {
|
||||
DEBUG("Matched %s\n", urc->code);
|
||||
urc->cb(urc->arg, buf);
|
||||
return 1;
|
||||
}
|
||||
@ -631,20 +630,22 @@ static int _check_urc(clist_node_t *node, void *arg)
|
||||
|
||||
void at_process_urc(at_dev_t *dev, uint32_t timeout)
|
||||
{
|
||||
char buf[AT_BUF_SIZE];
|
||||
|
||||
DEBUG("Processing URC (timeout=%" PRIu32 "us)\n", timeout);
|
||||
|
||||
ssize_t res;
|
||||
/* keep reading while received data are shorter than EOL */
|
||||
while ((res = at_readline(dev, buf, sizeof(buf), true, timeout)) <
|
||||
(ssize_t)sizeof(AT_RECV_EOL_1 AT_RECV_EOL_2) - 1) {
|
||||
if (res < 0) {
|
||||
return;
|
||||
}
|
||||
while (at_readline_skip_empty(dev, dev->rp_buf, dev->rp_buf_size, false, timeout) > 0) {
|
||||
clist_foreach(&dev->urc_list, _check_urc, dev->rp_buf);
|
||||
}
|
||||
}
|
||||
|
||||
void at_postprocess_urc(at_dev_t *dev, char *buf)
|
||||
{
|
||||
clist_foreach(&dev->urc_list, _check_urc, buf);
|
||||
}
|
||||
|
||||
void at_postprocess_urc_all(at_dev_t *dev, char *buf)
|
||||
{
|
||||
handle_urc_lines(dev, buf);
|
||||
}
|
||||
#endif
|
||||
|
||||
void at_dev_poweron(at_dev_t *dev)
|
||||
@ -656,3 +657,19 @@ void at_dev_poweroff(at_dev_t *dev)
|
||||
{
|
||||
uart_poweroff(dev->uart);
|
||||
}
|
||||
|
||||
#ifdef MODULE_EMBUNIT
|
||||
/* Exports for unit tests */
|
||||
__attribute__((alias("read_line_or_echo")))
|
||||
ssize_t _emb_read_line_or_echo(at_dev_t *dev, char const *cmd, char *resp_buf,
|
||||
size_t len, uint32_t timeout);
|
||||
__attribute__((alias("get_lines")))
|
||||
ssize_t _emb_get_lines(at_dev_t *dev, char *resp_buf, size_t len, uint32_t timeout);
|
||||
__attribute__((alias("get_resp_with_prefix")))
|
||||
ssize_t _emb_get_resp_with_prefix(at_dev_t *dev, const char *resp_prefix,
|
||||
char *resp_buf, size_t len, uint32_t timeout);
|
||||
__attribute__((alias("wait_echo")))
|
||||
int _emb_wait_echo(at_dev_t *dev, char const *command, uint32_t timeout);
|
||||
__attribute__((alias("wait_prompt")))
|
||||
int _emb_wait_prompt(at_dev_t *dev, uint32_t timeout);
|
||||
#endif
|
||||
|
@ -23,30 +23,71 @@
|
||||
* As a debugging aid, when compiled with `-DAT_PRINT_INCOMING=1`, every input
|
||||
* byte gets printed.
|
||||
*
|
||||
* ## Command echoing ##
|
||||
* Most DCEs (Data Circuit-terminating Equipment, aka modem) support command
|
||||
* echoing and enable it by default, and so does this driver.
|
||||
* If you disabled echoing on the DCE, you can compile this driver NOT to expect
|
||||
* echoing by defining CONFIG_AT_SEND_SKIP_ECHO.
|
||||
* Note, if the driver is NOT expecting command echoing but the DCE is echoing,
|
||||
* it should work just fine if and only if the EOL sequences of both DCE and
|
||||
* DTE (Data Terminal Equipmend - i.e. the device using this driver) match, i.e.
|
||||
* `AT_RECV_EOL_1 AT_RECV_EOL_2 == AT_SEND_EOL`.
|
||||
* In other words, if you are unsure about the echoing behavior of the DCE or
|
||||
* want to support both, set AT_SEND_EOL = AT_RECV_EOL_1 AT_RECV_EOL_2 and
|
||||
* define CONFIG_AT_SEND_SKIP_ECHO. This works because the URC (Unsolicited
|
||||
* Result Code) logic will intercept the echoes (see below).
|
||||
*
|
||||
* ## Unsolicited Result Codes (URC) ##
|
||||
* An unsolicited result code is a string message that is not triggered as a
|
||||
* information text response to a previous AT command and can be output at any
|
||||
* time to inform a specific event or status change.
|
||||
*
|
||||
* The module provides a basic URC handling by adding the `at_urc` module to the
|
||||
* application. This allows to @ref at_add_urc "register" and
|
||||
* @ref at_remove_urc "de-register" URC strings to check. Later,
|
||||
* @ref at_process_urc can be called to check if any of the registered URCs have
|
||||
* been detected. If a registered URC has been detected the correspondent
|
||||
* @ref at_urc_t::cb "callback function" is called. The mode of operation
|
||||
* requires that the user of the module processes periodically the URCs.
|
||||
* Some DCEs (Data Circuit-terminating Equipment, aka modem), like the LTE
|
||||
* modules from uBlox define a grace period where URCs are guaranteed NOT to be
|
||||
* sent as the time span between:
|
||||
* - the command EOL character reception AND command being internally accepted
|
||||
* - the EOL character of the last response line
|
||||
*
|
||||
* Alternatively, one of the `at_urc_isr_<priority>` modules can be included.
|
||||
* `priority` can be one of `low`, `medium` or `highest`, which correspond to
|
||||
* the priority of the thread that processes the URCs. For more information on
|
||||
* the priorities check the @ref sys_event module. This will extend the
|
||||
* functionality of `at_urc` by processing the URCs when the @ref AT_RECV_EOL_2
|
||||
* 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.
|
||||
* As follows, there is an indeterminate amount of time between:
|
||||
* - the command EOL character being sent
|
||||
* - the command EOL character reception AND command being internally accepted,
|
||||
* i.e. the begin of the grace period
|
||||
*
|
||||
* In other words, we can get a URC (or more?) just after issuing the command
|
||||
* and before the first line of response. The net effect is that such URCs will
|
||||
* appear to be the first line of response to the last issued command.
|
||||
*
|
||||
* Regardless of whether URC handling is enabled or not, URC interception
|
||||
* mechanics depend on command echoing:
|
||||
* 1. echo enabled: by observation, it seems that the grace period begins
|
||||
* BEFORE the echoed command. This has the advantage that we ALWAYS know what
|
||||
* the first line of response must look like and so if it doesn't, then it's a
|
||||
* URC. Thus, any procedure that calls at_send_cmd() internally will catch any
|
||||
* URC.
|
||||
* 2. echo disabled: commands that expect a particular type of response (e.g.
|
||||
* @ref at_send_cmd_get_resp_wait_ok() with a non-trivial prefix,
|
||||
* @ref at_send_cmd_wait_ok() etc.) will catch any URC. For the rest, it is
|
||||
* the application's responsibility to decide whether the received answer is
|
||||
* an URC or not and if yes, then @ref at_postprocess_urc() can be called with
|
||||
* the response as parameter.
|
||||
*
|
||||
* URC handling can be enabled by adding the `at_urc` module to the
|
||||
* application. This allows to @ref at_add_urc "register" and @ref at_remove_urc
|
||||
* "de-register" URC strings to check. Later, URCs can be processed in three
|
||||
* different ways:
|
||||
* - automatically, whenever any at_* method that intercepts URCs is called.
|
||||
* Such methods are marked in their docstring
|
||||
* - manually, by calling at_process_urc() periodically
|
||||
* - manually, by calling at_postprocess_urc() with an URC as parameter. The
|
||||
* URC is assumed to have been obtained from the device through methods that
|
||||
* do not automatically handle URCs (for example through @ref at_recv_bytes())
|
||||
* If a registered URC has been detected the correspondent @ref at_urc_t::cb
|
||||
* "callback function" is called.
|
||||
*
|
||||
* ## 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:
|
||||
* 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
|
||||
*
|
||||
@ -130,6 +171,11 @@ extern "C" {
|
||||
#define AT_RECV_EOL_2 "\n"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief convenience macro for the EOL sequence sent by the DCE
|
||||
*/
|
||||
#define AT_RECV_EOL AT_RECV_EOL_1 AT_RECV_EOL_2
|
||||
|
||||
/**
|
||||
* @brief default OK reply of an AT device.
|
||||
*/
|
||||
@ -143,29 +189,9 @@ extern "C" {
|
||||
#ifndef CONFIG_AT_RECV_ERROR
|
||||
#define CONFIG_AT_RECV_ERROR "ERROR"
|
||||
#endif
|
||||
|
||||
#if defined(MODULE_AT_URC) || DOXYGEN
|
||||
|
||||
/**
|
||||
* @brief Default buffer size used to process unsolicited result code data.
|
||||
* (as exponent of 2^n).
|
||||
*
|
||||
* As the buffer size ALWAYS needs to be power of two, this option
|
||||
* represents the exponent of 2^n, which will be used as the size of
|
||||
* the buffer.
|
||||
*/
|
||||
#ifndef CONFIG_AT_BUF_SIZE_EXP
|
||||
#define CONFIG_AT_BUF_SIZE_EXP (7U)
|
||||
#endif
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @brief Size of buffer used to process unsolicited result code data.
|
||||
*/
|
||||
#ifndef AT_BUF_SIZE
|
||||
#define AT_BUF_SIZE (1 << CONFIG_AT_BUF_SIZE_EXP)
|
||||
#endif
|
||||
|
||||
#if defined(MODULE_AT_URC) || DOXYGEN
|
||||
/**
|
||||
* @brief Unsolicited result code callback
|
||||
*
|
||||
@ -258,6 +284,8 @@ int at_dev_init(at_dev_t *dev, at_dev_init_t const *init);
|
||||
*
|
||||
* This function sends an AT command to the device and waits for "OK".
|
||||
*
|
||||
* URCs are automatically handled
|
||||
*
|
||||
* @param[in] dev device to operate on
|
||||
* @param[in] command command string to send
|
||||
* @param[in] timeout timeout (in usec)
|
||||
@ -275,6 +303,8 @@ 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
|
||||
*
|
||||
* URCs are automatically handled
|
||||
*
|
||||
* @param[in] dev device to operate on
|
||||
* @param[in] command command string to send
|
||||
* @param[in] timeout timeout (in usec)
|
||||
@ -290,7 +320,11 @@ int at_send_cmd_wait_prompt(at_dev_t *dev, const char *command, uint32_t timeout
|
||||
* @brief Send AT command, wait for response
|
||||
*
|
||||
* This function sends the supplied @p command, then waits and returns one
|
||||
* line of response.
|
||||
* line of response. The response is guaranteed null-terminated.
|
||||
*
|
||||
* Some URCs are automatically handled. The response returned can be an
|
||||
* URC. In that case, @ref at_postprocess_urc() can be called with the response
|
||||
* as parameter.
|
||||
*
|
||||
* A possible empty line will be skipped.
|
||||
*
|
||||
@ -301,6 +335,7 @@ int at_send_cmd_wait_prompt(at_dev_t *dev, const char *command, uint32_t timeout
|
||||
* @param[in] timeout timeout (in usec)
|
||||
*
|
||||
* @retval n length of response on success
|
||||
* @retval -ENOBUFS if the supplied buffer was to small.
|
||||
* @retval <0 on error
|
||||
*/
|
||||
ssize_t at_send_cmd_get_resp(at_dev_t *dev, const char *command, char *resp_buf,
|
||||
@ -310,10 +345,14 @@ ssize_t at_send_cmd_get_resp(at_dev_t *dev, const char *command, char *resp_buf,
|
||||
* @brief Send AT command, wait for response plus OK
|
||||
*
|
||||
* This function sends the supplied @p command, then waits and returns one
|
||||
* line of response.
|
||||
* line of response. The response is guaranteed null-terminated.
|
||||
*
|
||||
* A possible empty line will be skipped.
|
||||
*
|
||||
* URCs are automatically handled. If no prefix is provided, the response
|
||||
* may be an URC. In that case, @ref at_postprocess_urc() can be called with the
|
||||
* response as parameter.
|
||||
*
|
||||
* @param[in] dev device to operate on
|
||||
* @param[in] command command to send
|
||||
* @param[in] resp_prefix expected prefix in the response
|
||||
@ -324,6 +363,7 @@ ssize_t at_send_cmd_get_resp(at_dev_t *dev, const char *command, char *resp_buf,
|
||||
* @retval n length of response on success
|
||||
* @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 -ENOBUFS if the supplied buffer was to small.
|
||||
* @retval <0 other failures
|
||||
*/
|
||||
ssize_t at_send_cmd_get_resp_wait_ok(at_dev_t *dev, const char *command, const char *resp_prefix,
|
||||
@ -333,25 +373,30 @@ ssize_t at_send_cmd_get_resp_wait_ok(at_dev_t *dev, const char *command, const c
|
||||
* @brief Send AT command, wait for multiline response
|
||||
*
|
||||
* This function sends the supplied @p command, then returns all response
|
||||
* lines until the device sends "OK".
|
||||
* lines until the device sends "OK". The response is guaranteed null-terminated.
|
||||
*
|
||||
* Some URCs are automatically handled. The first m response lines can be
|
||||
* URCs. In that case, @ref at_postprocess_urc() can be called with each line
|
||||
* as parameter.
|
||||
*
|
||||
* If a line contains a DTE error response, this function stops and returns
|
||||
* accordingly.
|
||||
* accordingly. Any lines received prior to that are considered to be URCs and
|
||||
* thus handled.
|
||||
*
|
||||
* @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 resp_buf
|
||||
* @param[in] keep_eol true to keep the CR character in the response
|
||||
* @param[in] timeout timeout (in usec)
|
||||
*
|
||||
* @retval n length of response on success
|
||||
* @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 -ENOBUFS if the supplied buffer was to small.
|
||||
* @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);
|
||||
size_t len, uint32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Expect bytes from device
|
||||
@ -420,6 +465,8 @@ ssize_t at_recv_bytes(at_dev_t *dev, char *bytes, size_t len, uint32_t timeout);
|
||||
/**
|
||||
* @brief Send command to device
|
||||
*
|
||||
* Some URCs may be handled.
|
||||
*
|
||||
* @param[in] dev device to operate on
|
||||
* @param[in] command command to send
|
||||
* @param[in] timeout timeout (in usec)
|
||||
@ -448,13 +495,16 @@ int at_parse_resp(at_dev_t *dev, char const *resp);
|
||||
/**
|
||||
* @brief Read a line from device
|
||||
*
|
||||
* Stops at the first DCE EOL sequence. The response is guaranteed null-terminated.
|
||||
*
|
||||
* @param[in] dev device to operate on
|
||||
* @param[in] resp_buf buffer to store line
|
||||
* @param[in] len size of @p resp_buf
|
||||
* @param[in] keep_eol true to keep the CR character in the response
|
||||
* @param[in] keep_eol true to keep the trailing EOL sequence in the response
|
||||
* @param[in] timeout timeout (in usec)
|
||||
*
|
||||
* @retval n line length on success
|
||||
* @retval -ENOBUFS if the supplied buffer was to small.
|
||||
* @retval <0 on error
|
||||
*/
|
||||
ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, bool keep_eol, uint32_t timeout);
|
||||
@ -462,13 +512,17 @@ ssize_t at_readline(at_dev_t *dev, char *resp_buf, size_t len, bool keep_eol, ui
|
||||
/**
|
||||
* @brief Read a line from device, skipping a possibly empty line.
|
||||
*
|
||||
* Stops at the first DCE EOL sequence AFTER any non-EOL sequence. The response
|
||||
* is guaranteed null-terminated.
|
||||
*
|
||||
* @param[in] dev device to operate on
|
||||
* @param[in] resp_buf buffer to store line
|
||||
* @param[in] len size of @p resp_buf
|
||||
* @param[in] keep_eol true to keep the CR character in the response
|
||||
* @param[in] keep_eol true to keep the trailing EOL sequence in the response
|
||||
* @param[in] timeout timeout (in usec)
|
||||
*
|
||||
* @retval n line length on success
|
||||
* @retval -ENOBUFS if the supplied buffer was to small.
|
||||
* @retval <0 on error
|
||||
*/
|
||||
ssize_t at_readline_skip_empty(at_dev_t *dev, char *resp_buf, size_t len,
|
||||
@ -479,6 +533,8 @@ ssize_t at_readline_skip_empty(at_dev_t *dev, char *resp_buf, size_t len,
|
||||
*
|
||||
* Useful when crafting the command-response sequence by yourself.
|
||||
*
|
||||
* URCs are automatically handled
|
||||
*
|
||||
* @param[in] dev device to operate on
|
||||
* @param[in] timeout timeout (in usec)
|
||||
*
|
||||
@ -537,6 +593,27 @@ void at_remove_urc(at_dev_t *dev, at_urc_t *urc);
|
||||
* @param[in] timeout timeout (in usec)
|
||||
*/
|
||||
void at_process_urc(at_dev_t *dev, uint32_t timeout);
|
||||
|
||||
/**
|
||||
* @brief Process one URC from the provided buffer
|
||||
*
|
||||
* Useful if you e.g. called @ref at_send_cmd_get_lines() and the first lines
|
||||
* are URCs.
|
||||
*
|
||||
* @param[in] dev device to operate on
|
||||
* @param[in] buf buffer containing an URC
|
||||
*/
|
||||
void at_postprocess_urc(at_dev_t *dev, char *buf);
|
||||
/**
|
||||
* @brief Process all URCs from the provided buffer
|
||||
*
|
||||
* Useful if you e.g. called @ref at_recv_bytes(), found what you were interested
|
||||
* in, and there might be some URCs left in the buffer.
|
||||
*
|
||||
* @param[in] dev device to operate on
|
||||
* @param[in] buf buffer containing URCs
|
||||
*/
|
||||
void at_postprocess_urc_all(at_dev_t *dev, char *buf);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
@ -2,14 +2,25 @@ include ../Makefile.drivers_common
|
||||
|
||||
USEMODULE += shell
|
||||
USEMODULE += at
|
||||
USEMODULE += at_urc
|
||||
|
||||
# Enable if the DCE is sending only \n for EOL
|
||||
# CFLAGS += -DAT_RECV_EOL_1=""
|
||||
HANDLE_URC ?= 1
|
||||
ECHO_ON ?= 1
|
||||
SEND_EOL ?= "\\xd"
|
||||
RECV_EOL_1 ?= "\\xd"
|
||||
RECV_EOL_2 ?= "\\xa"
|
||||
|
||||
# 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
|
||||
|
||||
ifeq ($(HANDLE_URC), 1)
|
||||
USEMODULE += at_urc
|
||||
endif
|
||||
|
||||
ifeq ($(ECHO_ON), 0)
|
||||
CFLAGS += -DCONFIG_AT_SEND_SKIP_ECHO=1
|
||||
endif
|
||||
|
||||
CFLAGS += -DAT_RECV_EOL_1="\"$(RECV_EOL_1)\""
|
||||
CFLAGS += -DAT_RECV_EOL_2="\"$(RECV_EOL_2)\""
|
||||
CFLAGS += -DCONFIG_AT_SEND_EOL="\"$(SEND_EOL)\""
|
||||
|
||||
# we are printing from the event thread, we need more stack
|
||||
CFLAGS += -DEVENT_THREAD_MEDIUM_STACKSIZE=1024
|
||||
|
@ -119,7 +119,7 @@ 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) {
|
||||
10 * US_PER_SEC)) < 0) {
|
||||
puts("Error");
|
||||
return 1;
|
||||
}
|
||||
@ -378,7 +378,7 @@ static int emulate_dce(int argc, char **argv)
|
||||
}
|
||||
|
||||
res = at_send_cmd_get_lines(&at_dev, "AT+GETTWOLINES", resp_buf,
|
||||
sizeof(resp_buf), false, US_PER_SEC);
|
||||
sizeof(resp_buf), US_PER_SEC);
|
||||
if (res < 0) {
|
||||
printf("%u: Error AT+GETTWOLINES: %d\n", __LINE__, res);
|
||||
res = 1;
|
||||
|
70
tests/drivers/at/tests-with-config/test_all_configs.sh
Executable file
70
tests/drivers/at/tests-with-config/test_all_configs.sh
Executable file
@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
handle_urc=0
|
||||
echo=0
|
||||
rcv_eol_1=""
|
||||
rcv_eol_2=""
|
||||
send_eol=""
|
||||
|
||||
run_test() {
|
||||
echo "Running test with:"
|
||||
echo " URC handling = $handle_urc"
|
||||
echo " echo = $echo"
|
||||
echo " send EOL = $send_eol"
|
||||
echo " rcv EOL 1 = $rcv_eol_1"
|
||||
echo " rcv EOL 2 = $rcv_eol_2"
|
||||
|
||||
make -j --silent BOARD=native "HANDLE_URC=$handle_urc" "ECHO_ON=$echo" "SEND_EOL=\"$send_eol\"" "RECV_EOL_1=\"$rcv_eol_1\"" "RECV_EOL_2=\"$rcv_eol_2\"" tests-at
|
||||
|
||||
# take /dev/ttyS0 as serial interface. It is only required s.t. UART
|
||||
# initialization succeeds and it gets turned off right away.
|
||||
set +e
|
||||
if ! ./bin/native/tests_unittests.elf -c /dev/ttyS0 <<< "s\n";
|
||||
then
|
||||
echo "================================================================================"
|
||||
echo "Test failed! Generating compile-commands.json of the last build configuration..."
|
||||
echo "================================================================================"
|
||||
make -j --silent BOARD=native "HANDLE_URC=$handle_urc" "ECHO_ON=$echo" "SEND_EOL=\"$send_eol\"" "RECV_EOL_1=\"$rcv_eol_1\"" "RECV_EOL_2=\"$rcv_eol_2\"" compile-commands
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
||||
}
|
||||
|
||||
# set -x
|
||||
set -e
|
||||
|
||||
SCRIPT=$(readlink -f "$0")
|
||||
BASEDIR=$(dirname "$SCRIPT")/../../../unittests
|
||||
|
||||
cd "$BASEDIR"
|
||||
|
||||
for urc_i in 0 1; do
|
||||
handle_urc=$urc_i
|
||||
for echo_i in 0 1; do
|
||||
echo=$echo_i
|
||||
# 0xd == \r, 0xa == \n - I'm using this notation because I can't wrap my head around
|
||||
# how many parsers along the way try to interpret \r and \n. Worse, every time the
|
||||
# string is entering a new parser, one `\` is shaved off. So it looks like four
|
||||
# times is the right amount so that the final C string will be something like "\xd".
|
||||
for send_i in "\\\\xd" "\\\\xa" "\\\\xd\\\\xa"; do
|
||||
send_eol=$send_i
|
||||
|
||||
rcv_eol_1="\\\\xd"
|
||||
rcv_eol_2="\\\\xa"
|
||||
run_test
|
||||
|
||||
rcv_eol_1="\\\\xd"
|
||||
rcv_eol_2=""
|
||||
run_test
|
||||
|
||||
rcv_eol_1="\\\\xa"
|
||||
rcv_eol_2=""
|
||||
run_test
|
||||
done
|
||||
|
||||
done
|
||||
done
|
||||
|
||||
echo "================="
|
||||
echo "All tests passed!"
|
||||
echo "================="
|
1
tests/unittests/tests-at/Makefile
Normal file
1
tests/unittests/tests-at/Makefile
Normal file
@ -0,0 +1 @@
|
||||
include $(RIOTBASE)/Makefile.base
|
19
tests/unittests/tests-at/Makefile.include
Normal file
19
tests/unittests/tests-at/Makefile.include
Normal file
@ -0,0 +1,19 @@
|
||||
HANDLE_URC ?= 1
|
||||
ECHO_ON ?= 1
|
||||
SEND_EOL ?= "\\xd"
|
||||
RECV_EOL_1 ?= "\\xd"
|
||||
RECV_EOL_2 ?= "\\xa"
|
||||
|
||||
ifeq ($(HANDLE_URC), 1)
|
||||
USEMODULE += at_urc
|
||||
endif
|
||||
|
||||
ifeq ($(ECHO_ON), 0)
|
||||
CFLAGS += -DCONFIG_AT_SEND_SKIP_ECHO=1
|
||||
endif
|
||||
|
||||
CFLAGS += -DAT_RECV_EOL_1="\"$(RECV_EOL_1)\""
|
||||
CFLAGS += -DAT_RECV_EOL_2="\"$(RECV_EOL_2)\""
|
||||
CFLAGS += -DCONFIG_AT_SEND_EOL="\"$(SEND_EOL)\""
|
||||
|
||||
USEMODULE += at
|
692
tests/unittests/tests-at/tests-at.c
Normal file
692
tests/unittests/tests-at/tests-at.c
Normal file
@ -0,0 +1,692 @@
|
||||
/*
|
||||
* Copyright (C) 2024 ML!PA GmbH
|
||||
*
|
||||
* 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 "embUnit.h"
|
||||
|
||||
#include "at.h"
|
||||
#include "isrpipe/read_timeout.h"
|
||||
|
||||
#include "tests-at.h"
|
||||
|
||||
#define UNIT_TEST_LONG_URC "+UNITTEST_LONG_URC_VEEERY_LONG"
|
||||
#define UNIT_TEST_SHORT_URC "+U"
|
||||
#define LONG_COMMAND "AT+COMMAND_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"\
|
||||
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
|
||||
#define COMMAND_WITH_EOL "AT+COMMAND_"AT_RECV_EOL"BLABLA"AT_RECV_EOL"BLA"
|
||||
|
||||
at_dev_t at_dev;
|
||||
static char buf[256];
|
||||
static char rp_buf[256];
|
||||
#ifdef MODULE_AT_URC
|
||||
static unsigned urc_count = 0;
|
||||
|
||||
void unit_test_urc_long_handler(void *arg, char const *code)
|
||||
{
|
||||
TEST_ASSERT(strncmp(UNIT_TEST_LONG_URC, code, strlen(UNIT_TEST_LONG_URC)) == 0);
|
||||
unsigned *urc_count = (unsigned *)arg;
|
||||
*urc_count += 1;
|
||||
}
|
||||
|
||||
void unit_test_urc_short_handler(void *arg, char const *code)
|
||||
{
|
||||
TEST_ASSERT(strncmp(UNIT_TEST_SHORT_URC, code, strlen(UNIT_TEST_SHORT_URC)) == 0);
|
||||
unsigned *urc_count = (unsigned *)arg;
|
||||
*urc_count += 1;
|
||||
}
|
||||
|
||||
at_urc_t urc_long = {
|
||||
.arg = &urc_count,
|
||||
.code = UNIT_TEST_LONG_URC,
|
||||
.cb = unit_test_urc_long_handler
|
||||
};
|
||||
at_urc_t urc_short = {
|
||||
.arg = &urc_count,
|
||||
.code = UNIT_TEST_SHORT_URC,
|
||||
.cb = unit_test_urc_short_handler
|
||||
};
|
||||
#endif
|
||||
|
||||
static void set_up(void)
|
||||
{
|
||||
at_dev_init_t at_init_params = {
|
||||
.baudrate = 115200,
|
||||
.rp_buf = rp_buf,
|
||||
.rp_buf_size = sizeof(rp_buf),
|
||||
.rx_buf = buf,
|
||||
.rx_buf_size = sizeof(buf),
|
||||
.uart = UART_DEV(0),
|
||||
};
|
||||
int res = at_dev_init(&at_dev, &at_init_params);
|
||||
/* check the UART initialization return value and respond as needed */
|
||||
if (res == UART_NODEV) {
|
||||
TEST_FAIL("Invalid UART device given!");
|
||||
}
|
||||
if (res == UART_NOBAUD) {
|
||||
TEST_FAIL("Baudrate is not applicable!");
|
||||
}
|
||||
|
||||
/* we don't use the serial device, make sure it doesn't clobber our rx buffer */
|
||||
at_dev_poweroff(&at_dev);
|
||||
at_drain(&at_dev);
|
||||
|
||||
#ifdef MODULE_AT_URC
|
||||
at_add_urc(&at_dev, &urc_long);
|
||||
at_add_urc(&at_dev, &urc_short);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void tear_down(void)
|
||||
{
|
||||
#ifdef MODULE_AT_URC
|
||||
at_remove_urc(&at_dev, &urc_long);
|
||||
at_remove_urc(&at_dev, &urc_short);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void assert_urc_count(unsigned expected)
|
||||
{
|
||||
#ifdef MODULE_AT_URC
|
||||
TEST_ASSERT_EQUAL_INT(expected, urc_count);
|
||||
urc_count = 0;
|
||||
#endif
|
||||
(void)expected;
|
||||
}
|
||||
|
||||
int _emb_read_line_or_echo(at_dev_t *dev, char const *cmd, char *resp_buf,
|
||||
size_t len, uint32_t timeout);
|
||||
ssize_t _emb_get_lines(at_dev_t *dev, char *resp_buf, size_t len, uint32_t timeout);
|
||||
ssize_t _emb_get_resp_with_prefix(at_dev_t *dev, const char *resp_prefix,
|
||||
char *resp_buf, size_t len, uint32_t timeout);
|
||||
int _emb_wait_echo(at_dev_t *dev, char const *command, uint32_t timeout);
|
||||
int _emb_wait_prompt(at_dev_t *dev, uint32_t timeout);
|
||||
|
||||
static void inject_resp_str(at_dev_t *dev, char const *str)
|
||||
{
|
||||
isrpipe_write(&dev->isrpipe, (unsigned char const *)str, strlen(str));
|
||||
}
|
||||
|
||||
void test_readline_or_echo(void)
|
||||
{
|
||||
int res;
|
||||
char resp_buf[64];
|
||||
at_dev_t *dev = &at_dev;
|
||||
at_drain(dev);
|
||||
|
||||
res = _emb_read_line_or_echo(dev, "AT+COMMAND", resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == -ETIMEDOUT);
|
||||
|
||||
inject_resp_str(dev,
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL);
|
||||
res = _emb_read_line_or_echo(dev, "AT+COMMAND", resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = isrpipe_read_timeout(&dev->isrpipe, (unsigned char *)resp_buf, 1, 1000);
|
||||
TEST_ASSERT(res -ETIMEDOUT);
|
||||
|
||||
inject_resp_str(dev,
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
/* Reading in a buffer <= 1 should not read any characters from the RX */
|
||||
res = _emb_read_line_or_echo(dev, "AT+COMMAND", resp_buf, 0, 1000);
|
||||
TEST_ASSERT(res == -EINVAL);
|
||||
res = _emb_read_line_or_echo(dev, "AT+COMMAND", resp_buf, 1, 1000);
|
||||
TEST_ASSERT(res == -ENOBUFS);
|
||||
res = _emb_read_line_or_echo(dev, "AT+COMMAND", resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = at_readline_skip_empty(dev, resp_buf, sizeof(resp_buf), false, 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = strcmp("OK", resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
""
|
||||
CONFIG_AT_SEND_EOL);
|
||||
res = _emb_read_line_or_echo(dev, "", resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == -EINVAL);
|
||||
|
||||
/* here we should have a rogue CONFIG_AT_SEND_EOL left in the buffer from before */
|
||||
inject_resp_str(dev,
|
||||
LONG_COMMAND
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_read_line_or_echo(dev, LONG_COMMAND, resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = at_readline_skip_empty(dev, resp_buf, sizeof(resp_buf), false, 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = strcmp("OK", resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
"+R"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_read_line_or_echo(dev, "AT+COMMAND", resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = strcmp("+R", resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"+R"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_read_line_or_echo(dev, "AT+COMMAND", resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = strcmp("+R", resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
"+R"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_read_line_or_echo(dev, "AT+COMMAND", resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = strcmp("+R", resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
COMMAND_WITH_EOL
|
||||
CONFIG_AT_SEND_EOL);
|
||||
res = _emb_read_line_or_echo(dev, COMMAND_WITH_EOL, resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = isrpipe_read_timeout(&dev->isrpipe, (unsigned char *)resp_buf, 1, 1000);
|
||||
TEST_ASSERT(res -ETIMEDOUT);
|
||||
}
|
||||
|
||||
void test_wait_echo(void)
|
||||
{
|
||||
int res;
|
||||
char resp_buf[64];
|
||||
at_dev_t *dev = &at_dev;
|
||||
|
||||
at_drain(dev);
|
||||
|
||||
res = _emb_wait_echo(dev, "AT+COMMAND", 1000);
|
||||
TEST_ASSERT(res == -ETIMEDOUT);
|
||||
|
||||
inject_resp_str(dev, "AT+COMMAND" CONFIG_AT_SEND_EOL);
|
||||
res = _emb_wait_echo(dev, "AT+COMMAND", 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_LONG_URC
|
||||
AT_RECV_EOL
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL);
|
||||
res = _emb_wait_echo(dev, "AT+COMMAND", 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_LONG_URC
|
||||
AT_RECV_EOL
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_wait_echo(dev, "AT+COMMAND", 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = at_readline_skip_empty(dev, resp_buf, sizeof(resp_buf), false, 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = at_parse_resp(dev, resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
""
|
||||
CONFIG_AT_SEND_EOL);
|
||||
res = _emb_wait_echo(dev, "", 1000);
|
||||
TEST_ASSERT(res == -EINVAL);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_wait_echo(dev, "AT+COMMAND", 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = at_readline_skip_empty(dev, resp_buf, sizeof(resp_buf), false, 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = at_parse_resp(dev, resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_LONG_URC
|
||||
AT_RECV_EOL
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_wait_echo(dev, "AT+COMMAND", 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = at_readline_skip_empty(dev, resp_buf, sizeof(resp_buf), false, 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = at_parse_resp(dev, resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_LONG_URC
|
||||
AT_RECV_EOL
|
||||
COMMAND_WITH_EOL
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_wait_echo(dev, COMMAND_WITH_EOL, 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = at_readline_skip_empty(dev, resp_buf, sizeof(resp_buf), false, 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = at_parse_resp(dev, resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_LONG_URC
|
||||
AT_RECV_EOL
|
||||
COMMAND_WITH_EOL
|
||||
CONFIG_AT_SEND_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_wait_echo(dev, COMMAND_WITH_EOL, 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = at_readline_skip_empty(dev, resp_buf, sizeof(resp_buf), false, 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = at_parse_resp(dev, resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
assert_urc_count(9);
|
||||
}
|
||||
|
||||
void test_get_resp_with_prefix(void)
|
||||
{
|
||||
int res;
|
||||
char resp_buf[64];
|
||||
at_dev_t *dev = &at_dev;
|
||||
|
||||
at_drain(dev);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
"+RESPONSE: 123"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_resp_with_prefix(dev, "+RESPONSE: ", resp_buf, sizeof(resp_buf), 10000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = strcmp("123", resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_resp_with_prefix(dev, "+RESPONSE: ", resp_buf, sizeof(resp_buf), 10000);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL "+CME ERROR: 1"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_resp_with_prefix(dev, "+RESPONSE: ", resp_buf, sizeof(resp_buf), 10000);
|
||||
TEST_ASSERT(res == -AT_ERR_EXTENDED);
|
||||
res = strncmp("1", dev->rp_buf, 1);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
"ERROR"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_resp_with_prefix(dev, "+RESPONSE: ", resp_buf, sizeof(resp_buf), 10000);
|
||||
TEST_ASSERT(res == -1);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_resp_with_prefix(dev, "+RESPONSE: ", resp_buf, sizeof(resp_buf), 10000);
|
||||
TEST_ASSERT(res == -ETIMEDOUT);
|
||||
|
||||
inject_resp_str(dev,
|
||||
"trash"
|
||||
AT_RECV_EOL
|
||||
"+RESPONSE: 123"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_resp_with_prefix(dev, "+RESPONSE: ", resp_buf, sizeof(resp_buf), 10000);
|
||||
TEST_ASSERT(res > 0);
|
||||
res = strcmp("123", resp_buf);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
assert_urc_count(5);
|
||||
}
|
||||
|
||||
void test_read_lines(void)
|
||||
{
|
||||
int res;
|
||||
char resp_buf[62];
|
||||
char *p;
|
||||
at_dev_t *dev = &at_dev;
|
||||
|
||||
at_drain(dev);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
"+R1"
|
||||
AT_RECV_EOL
|
||||
"+R2"
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_lines(dev, resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
p = resp_buf;
|
||||
p = strstr(resp_buf, "+R1");
|
||||
TEST_ASSERT(p);
|
||||
p = strstr(resp_buf, "+R2");
|
||||
TEST_ASSERT(p);
|
||||
p = strstr(resp_buf, "OK");
|
||||
TEST_ASSERT(p);
|
||||
|
||||
/* inconsistent EOL */
|
||||
inject_resp_str(dev,
|
||||
"+R1"
|
||||
AT_RECV_EOL
|
||||
"+R2"
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_lines(dev, resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res > 0);
|
||||
p = resp_buf;
|
||||
p = strstr(resp_buf, "+R1");
|
||||
TEST_ASSERT(p);
|
||||
p = strstr(resp_buf, "+R2");
|
||||
TEST_ASSERT(p);
|
||||
p = strstr(resp_buf, "OK");
|
||||
TEST_ASSERT(p);
|
||||
|
||||
/* URCs should get handled here */
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
"ERROR"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_lines(dev, resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == -1);
|
||||
|
||||
/* URCs shouldn't get handled here. DCE answered neither OK nor error,
|
||||
* something went terribly wrong anyway, fine to just drop them. */
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_lines(dev, resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == -ETIMEDOUT);
|
||||
|
||||
/* overflow the input buffer */
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
"LONG_RESPONSE____________________________________________"
|
||||
AT_RECV_EOL
|
||||
"LONG_RESPONSE____________________________________________"
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_get_lines(dev, resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == -ENOBUFS);
|
||||
|
||||
assert_urc_count(2);
|
||||
}
|
||||
|
||||
void test_wait_prompt(void)
|
||||
{
|
||||
int res;
|
||||
char resp_buf[64];
|
||||
at_dev_t *dev = &at_dev;
|
||||
|
||||
at_drain(dev);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
">"
|
||||
"123");
|
||||
res = _emb_wait_prompt(dev, 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = isrpipe_read_timeout(&dev->isrpipe, (unsigned char *)resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == 3);
|
||||
res = strncmp("123", resp_buf, 3);
|
||||
|
||||
inject_resp_str(dev,
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
">"
|
||||
"456");
|
||||
res = _emb_wait_prompt(dev, 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = isrpipe_read_timeout(&dev->isrpipe, (unsigned char *)resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == 3);
|
||||
res = strncmp("456", resp_buf, 3);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
"ERROR"
|
||||
AT_RECV_EOL);
|
||||
res = _emb_wait_prompt(dev, 1000);
|
||||
TEST_ASSERT(res == -1);
|
||||
|
||||
inject_resp_str(dev,
|
||||
">"
|
||||
"123");
|
||||
res = _emb_wait_prompt(dev, 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
res = isrpipe_read_timeout(&dev->isrpipe, (unsigned char *)resp_buf, sizeof(resp_buf), 1000);
|
||||
TEST_ASSERT(res == 3);
|
||||
res = strncmp("123", resp_buf, 3);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
assert_urc_count(2);
|
||||
}
|
||||
|
||||
void test_wait_ok(void)
|
||||
{
|
||||
int res;
|
||||
unsigned urc_cnt = 5;
|
||||
at_dev_t *dev = &at_dev;
|
||||
|
||||
at_drain(dev);
|
||||
|
||||
inject_resp_str(dev,
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = at_wait_ok(dev, 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
"ERROR"
|
||||
AT_RECV_EOL);
|
||||
res = at_wait_ok(dev, 1000);
|
||||
TEST_ASSERT(res == -1);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
"+CME ERROR: 2"
|
||||
AT_RECV_EOL);
|
||||
res = at_wait_ok(dev, 1000);
|
||||
TEST_ASSERT(res == -AT_ERR_EXTENDED);
|
||||
|
||||
inject_resp_str(dev,
|
||||
"trash"
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = at_wait_ok(dev, 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
#ifdef CONFIG_AT_SEND_SKIP_ECHO
|
||||
if (strcmp(AT_RECV_EOL, CONFIG_AT_SEND_EOL) == 0) {
|
||||
/* Test echo handling when none expected */
|
||||
urc_cnt += 5;
|
||||
at_drain(dev);
|
||||
|
||||
inject_resp_str(dev,
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = at_wait_ok(dev, 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL
|
||||
"ERROR"
|
||||
AT_RECV_EOL);
|
||||
res = at_wait_ok(dev, 1000);
|
||||
TEST_ASSERT(res == -1);
|
||||
|
||||
inject_resp_str(dev,
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"+CME ERROR: 2"
|
||||
AT_RECV_EOL);
|
||||
res = at_wait_ok(dev, 1000);
|
||||
TEST_ASSERT(res == -AT_ERR_EXTENDED);
|
||||
|
||||
inject_resp_str(dev,
|
||||
"trash"
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
"AT+COMMAND"
|
||||
CONFIG_AT_SEND_EOL
|
||||
AT_RECV_EOL
|
||||
"OK"
|
||||
AT_RECV_EOL);
|
||||
res = at_wait_ok(dev, 1000);
|
||||
TEST_ASSERT(res == 0);
|
||||
}
|
||||
#endif /* CONFIG_AT_SEND_SKIP_ECHO */
|
||||
assert_urc_count(urc_cnt);
|
||||
}
|
||||
|
||||
#ifdef MODULE_AT_URC
|
||||
void test_process_urc(void)
|
||||
{
|
||||
at_dev_t *dev = &at_dev;
|
||||
at_drain(dev);
|
||||
|
||||
at_process_urc(dev, 1000);
|
||||
|
||||
inject_resp_str(dev,
|
||||
"trash"
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_SHORT_URC
|
||||
AT_RECV_EOL
|
||||
AT_RECV_EOL
|
||||
UNIT_TEST_LONG_URC
|
||||
AT_RECV_EOL);
|
||||
at_process_urc(dev, 1000);
|
||||
|
||||
assert_urc_count(2);
|
||||
}
|
||||
#endif /* MODULE_AT_URC */
|
||||
|
||||
void tests_at(void)
|
||||
{
|
||||
EMB_UNIT_TESTFIXTURES(fixtures) {
|
||||
new_TestFixture(test_readline_or_echo),
|
||||
#ifndef CONFIG_AT_SEND_SKIP_ECHO
|
||||
new_TestFixture(test_wait_echo),
|
||||
#endif
|
||||
new_TestFixture(test_get_resp_with_prefix),
|
||||
new_TestFixture(test_read_lines),
|
||||
new_TestFixture(test_wait_prompt),
|
||||
new_TestFixture(test_wait_ok),
|
||||
#ifdef MODULE_AT_URC
|
||||
new_TestFixture(test_process_urc),
|
||||
#endif
|
||||
};
|
||||
|
||||
EMB_UNIT_TESTCALLER(at_tests, set_up, tear_down, fixtures);
|
||||
|
||||
TESTS_RUN((Test *)&at_tests);
|
||||
}
|
36
tests/unittests/tests-at/tests-at.h
Normal file
36
tests/unittests/tests-at/tests-at.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2024 ML!PA GmbH
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup unittests
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief Unittests for the at module
|
||||
*
|
||||
* @author Mihai Renea <mihai.renea@ml-pa.com>
|
||||
*/
|
||||
#ifndef TESTS_AT_H
|
||||
#define TESTS_AT_H
|
||||
#include "embUnit/embUnit.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Tests entry point.
|
||||
*/
|
||||
void tests_at(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TESTS_AT_H */
|
||||
/** @} */
|
Loading…
Reference in New Issue
Block a user