Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from
Feb 22, 2024

Conversation

derMihai
Copy link
Contributor

Parse +CME/+CMS responses and save error value

Contribution description

Motivation

Modems, especially mobile ones, can be very shaky. As such, in the scope of debugging, it is very helpful to know failure reasons.
Luckily, many modems can offer detailed error codes. Once this feature is enabled, they will answer with +CME ERROR: <error_code> (or +CMS ERROR: <error_code> for SMS-related commands), where <error_code> is either an integer error code or a string. This is much better than the standard ERROR response, which provides no further information.

Unfortunately, in the current state, the AT driver does not parse any errors. Generally, if the response is not OK then -1 is returned and that's it.

Contribution

This patch enables extended error parsing. If a +CME/+CMS answer is detected, most functions will return -AT_ERR_EXTENDED, and the <error_code> will be available in a buffer for further inspection.

Design considerations

I tried to keep the changes at a minimum, maintain API compatibility and offer a clean solution at the same time. At the end, I had to sacrifice on change minimalism. As follows, this is a big PR. Individual changes are annotated inline.

Anyway, some generalities:

  • <error_response> is retrievable from a buffer instead of parsing and returning it as an error code because, depending modem configuration, it might be a string. Let the user handle that.
  • the error buffer is passed in at driver initialization out of following reasons
    • to keep API as unchanged as possible (e.g. functions not expecting a response do not provide a response buffer)
    • in the future, I might improve the URC parsing (currently is very unreliable - see this issue). This buffer could be then used for URC inspection. Otherwise, either heap or large stack allocations will be required.

Testing procedure

See tests/drivers/at/tests-with-config/emulate_dce.py.

@github-actions github-actions bot added Area: tests Area: tests and testing framework Area: drivers Area: Device drivers labels Jan 23, 2024
@derMihai
Copy link
Contributor Author

derMihai commented Jan 23, 2024

I will add inline comments regarding design decisions in a few moments.
Done.

@@ -88,18 +96,54 @@ 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)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argument list was getting too long.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that really a problem?

Copy link
Contributor Author

@derMihai derMihai Feb 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a long argument list is less visible. Especially when it comes to similar-typed arguments - we have two buffers now.

drivers/at/at.c Outdated Show resolved Hide resolved
@@ -224,8 +268,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)) {
Copy link
Contributor Author

@derMihai derMihai Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. this was buggy. AT_RECV_EOL_1 AT_RECV_EOL_2 is returned no matter if the command echoing feature is enabled or not.
  2. some commands do not reply with a newline at all, most notably the prompt commands (on Ublox LTE modules, they return > directly instead of e.g. \r\n>). As most commands parse the response with at_readline_skip_empty(), this newline pruning is no longer needed here. The only exception is at_send_cmd_get_lines(), which adds empty lines to the response too. It is now patched that it skips the first empty line.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW. I created #20281 because it allows me to see exactly what the modem is returning for an AT command. For example:

2024-01-24 22:26:30,373 # send 1 AT+COPS?
2024-01-24 22:26:30,374 # UART_DEV(1) TX: AT+COPS?
> 2024-01-24 22:26:30,401 # Success: UART_DEV(1) RX: [AT+COPS?\r\r\n]
2024-01-24 22:26:30,403 # Success: UART_DEV(1) RX: [+COPS: 0\r\n]
2024-01-24 22:26:30,404 # Success: UART_DEV(1) RX: [\r\n]
2024-01-24 22:26:30,405 # Success: UART_DEV(1) RX: [OK\r\n]

drivers/include/at.h Outdated Show resolved Hide resolved
@@ -2,8 +2,11 @@ include ../Makefile.drivers_common

USEMODULE += shell
USEMODULE += at
USEMODULE += at_urc_isr_medium
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Async URC handling is broken. See this issue.

drivers/at/at.c Outdated Show resolved Hide resolved
@keestux
Copy link
Contributor

keestux commented Jan 24, 2024

The title of this PR does not really cover the actual redesign that is going on.

@keestux
Copy link
Contributor

keestux commented Jan 24, 2024

Funny note. This evening I started looking at the at driver (again, after more than two years). I was thinking to rewrite this driver, ha ha. Note, I wrote several Arduino libraries for Ublox modems, for example https://github.com/SodaqMoja/Sodaq_R4X. So, I do know bits and pieces about this stuff.

@derMihai
Copy link
Contributor Author

Funny note. This evening I started looking at the at driver (again, after more than two years). I was thinking to rewrite this driver, ha ha. Note, I wrote several Arduino libraries for Ublox modems, for example https://github.com/SodaqMoja/Sodaq_R4X. So, I do know bits and pieces about this stuff.

Looks good. Nice way of detecting echoes!
Right now we're using a layer on top of this driver that provides ergonomic command formatting and response argument parsing. Might make sense to include that here too.

@derMihai
Copy link
Contributor Author

derMihai commented Feb 1, 2024

The title of this PR does not really cover the actual redesign that is going on.

Right, so next step is to fix the URC handling (as a first step, synchronously only). I wonder if I should open a separate PR for that, or to add it here and change the name to something like drivers/at: redesign error reporting and URC handling.

drivers/at/at.c Outdated Show resolved Hide resolved
drivers/at/at.c Outdated Show resolved Hide resolved
drivers/at/at.c Outdated
res = at_parse_resp(dev, pos);

switch (res) {
case 0:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a short comment

drivers/at/at.c Show resolved Hide resolved
drivers/include/at.h Outdated Show resolved Hide resolved
@@ -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 *err_buf; /**< error buffer */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is an error buffer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See line 61.

@@ -88,18 +96,54 @@ 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that really a problem?

drivers/include/at.h Outdated Show resolved Hide resolved
drivers/include/at.h Outdated Show resolved Hide resolved
uint32_t baudrate; /**< UART device baudrate */
char *rx_buf; /**< UART rx buffer */
size_t rx_buf_size; /**< UART rx buffer size */
char *err_buf; /**< error buffer */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be NULL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to allow this?
There was no error reporting before, so if a user doesn't need this, they might not want to pay for the additional buffer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have a look at this next week, it makes sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, on a second thought, it cannot be easily done. The whole PR revolves around the idea that there is always a buffer that can be used for response analysis - either the response buffer, if the command returns a response to the user; or the error buffer otherwise.

However, for the integer error value case, this buffer can be very small, just to hold a response like +CME ERROR: 1234. I can document this.

tests/drivers/at/main.c Outdated Show resolved Hide resolved
drivers/at/at.c Outdated
size_t const cmx_prefix_len = strlen("+CMx ERROR: ");
if (strncmp("+CME ERROR: ", resp, cmx_prefix_len) &&
strncmp("+CMS ERROR: ", resp, cmx_prefix_len)) {
// A command may return either one or the other, we need not differentiate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment should mention that it is the "otherwise" condition.

In other words, it is NOT prefix "+CME ERROR: " AND it is NOT prefix "+CMS ERROR: ".

drivers/at/at.c Outdated Show resolved Hide resolved
drivers/at/at.c Outdated Show resolved Hide resolved
drivers/at/at.c Outdated Show resolved Hide resolved
@benpicco benpicco added the CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR label Feb 21, 2024
@riot-ci
Copy link

riot-ci commented Feb 21, 2024

Murdock results

✔️ PASSED

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

Success Failures Total Runtime
9997 0 9997 11m:00s

Artifacts

Copy link
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My concerns have been addressed, just some small comments.
CI is complaining about trailing whitespaces, you want to fix those.

drivers/at/at.c Outdated Show resolved Hide resolved
drivers/include/at.h Outdated Show resolved Hide resolved
*
* @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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
int at_dev_init(at_dev_t *dev, at_dev_init_t const *init);
int at_dev_init(at_dev_t *dev, const at_dev_init_t *init);

feels more idiomatic

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know about that. I use east const for consistency, e.g.

char const *const c;

is more consistent than

const char *const c;

in the sense that const applies to whatever is to the left

drivers/at/at.c Show resolved Hide resolved
Comment on lines +140 to +142
size_t resp_len = strlen(resp);
if (resp_len + 1 > dev->rp_buf_size) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
size_t resp_len = strlen(resp);
if (resp_len + 1 > dev->rp_buf_size) {
size_t resp_len = strlen(resp) + 1;
if (resp_len > dev->rp_buf_size) {

is a bit nicer to read

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It means response string length and I want to keep it consistent across the whole file, otherwise it might get confusing quickly.

drivers/at/at.c Outdated Show resolved Hide resolved
drivers/at/at.c Outdated Show resolved Hide resolved
drivers/include/at.h Outdated Show resolved Hide resolved
drivers/include/at.h Outdated Show resolved Hide resolved
Copy link
Contributor

@benpicco benpicco left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please squash

@benpicco benpicco added this pull request to the merge queue Feb 22, 2024
Merged via the queue into RIOT-OS:master with commit cb83a89 Feb 22, 2024
25 checks passed
@derMihai derMihai deleted the mir/at_cme branch February 23, 2024 07:18
@benpicco benpicco added the Process: API change Integration Process: PR contains or issue proposes an API change. Should be handled with care. label Apr 5, 2024
@MrKevinWeiss MrKevinWeiss added this to the Release 2024.04 milestone Apr 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: drivers Area: Device drivers Area: tests Area: tests and testing framework CI: ready for build If set, CI server will compile all applications for all available boards for the labeled PR Process: API change Integration Process: PR contains or issue proposes an API change. Should be handled with care.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants