Files
eg25-manager/src/at.c
ArenM 89b7dfda2f at: remove call to g_strstrip before sending commando
This log statement called g_strstrip before sending the command, which
caused it to fail because it doesn't send the newline required to run
it.
2021-10-04 23:26:16 -04:00

422 lines
13 KiB
C

/*
* Copyright (C) 2020 Arnaud Ferraris <arnaud.ferraris@gmail.com>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "at.h"
#include "suspend.h"
#include "gpio.h"
#include "gnss.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <glib-unix.h>
static GArray *configure_commands = NULL;
static GArray *suspend_commands = NULL;
static GArray *resume_commands = NULL;
static GArray *reset_commands = NULL;
static int configure_serial(const char *tty)
{
struct termios ttycfg;
int fd;
fd = open(tty, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd > 0) {
tcgetattr(fd, &ttycfg);
ttycfg.c_iflag &= ~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ISTRIP | IXON);
ttycfg.c_oflag = 0;
ttycfg.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
ttycfg.c_cflag &= ~(CSIZE | PARENB);
ttycfg.c_cflag |= CS8;
ttycfg.c_cc[VMIN] = 1;
ttycfg.c_cc[VTIME] = 0;
cfsetspeed(&ttycfg, B115200);
tcsetattr(fd, TCSANOW, &ttycfg);
}
return fd;
}
gboolean at_send_command(struct EG25Manager *manager)
{
char command[256];
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
int ret, len = 0, pos = 0;
if (at_cmd) {
/* Wake up the modem from soft sleep before sending an AT command */
gpio_sequence_wake(manager);
/* Send AT command */
if (at_cmd->subcmd == NULL && at_cmd->value == NULL && at_cmd->expected == NULL)
len = snprintf(command, sizeof(command), "AT+%s\r\n", at_cmd->cmd);
else if (at_cmd->subcmd == NULL && at_cmd->value == NULL)
len = snprintf(command, sizeof(command), "AT+%s?\r\n", at_cmd->cmd);
else if (at_cmd->subcmd == NULL && at_cmd->value)
len = snprintf(command, sizeof(command),"AT+%s=%s\r\n", at_cmd->cmd, at_cmd->value);
else if (at_cmd->subcmd && at_cmd->value == NULL)
len = snprintf(command, sizeof(command), "AT+%s=\"%s\"\r\n", at_cmd->cmd, at_cmd->subcmd);
else if (at_cmd->subcmd && at_cmd->value)
len = snprintf(command, sizeof(command), "AT+%s=\"%s\",%s\r\n", at_cmd->cmd, at_cmd->subcmd, at_cmd->value);
if (len < 0) {
g_warning("snprintf(3) failed");
at_next_command(manager);
return FALSE;
}
else if (len >= sizeof(command)) {
g_warning("AT command does not fit into buffer "
"(%d bytes required, %zu available)", len, sizeof(command));
at_next_command(manager);
return FALSE;
}
manager->at_callback = at_cmd->callback;
do {
ret = write(manager->at_fd, &command[pos], len);
if (ret < 0) {
switch (errno) {
case EAGAIN:
case EINTR:
/* Try again. */
break;
default:
g_warning("error sending AT command: %s", strerror(errno));
at_next_command(manager);
return FALSE;
}
}
else {
len -= ret;
pos += ret;
}
} while (len > 0);
g_message("Successfully sent command: %s", g_strstrip(command));
} else {
/* Allow the modem to enter soft sleep again when we sent the AT command*/
gpio_sequence_sleep(manager);
if (manager->modem_state < EG25_STATE_CONFIGURED) {
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
#ifdef HAVE_MMGLIB
MMModemState modem_state = mm_modem_get_state(manager->mm_modem);
if (manager->mm_modem && modem_state >= MM_MODEM_STATE_REGISTERED)
modem_update_state(manager, modem_state);
else
manager->modem_state = EG25_STATE_CONFIGURED;
#endif
} else {
manager->modem_state = EG25_STATE_CONFIGURED;
}
} else if (manager->modem_state == EG25_STATE_SUSPENDING) {
modem_suspend_post(manager);
} else if (manager->modem_state == EG25_STATE_RESETTING) {
manager->modem_state = EG25_STATE_POWERED;
}
}
return FALSE;
}
void at_next_command(struct EG25Manager *manager)
{
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
if (!at_cmd)
return;
g_free(at_cmd->cmd);
g_free(at_cmd->subcmd);
g_free(at_cmd->value);
g_free(at_cmd->expected);
g_free(at_cmd);
manager->at_cmds = g_list_remove(manager->at_cmds, at_cmd);
at_send_command(manager);
}
static void retry_at_command(struct EG25Manager *manager)
{
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
if (!at_cmd)
return;
at_cmd->retries++;
if (at_cmd->retries > 3) {
g_critical("Command %s retried %d times, aborting...", at_cmd->cmd, at_cmd->retries);
at_next_command(manager);
} else {
g_timeout_add(500, G_SOURCE_FUNC(at_send_command), manager);
}
}
void at_process_result(struct EG25Manager *manager,
const char *response)
{
struct AtCommand *at_cmd = manager->at_cmds ? g_list_nth_data(manager->at_cmds, 0) : NULL;
if (!at_cmd)
return;
if (at_cmd->expected && !strstr(response, at_cmd->expected)) {
g_free(at_cmd->value);
at_cmd->value = at_cmd->expected;
g_message("Got a different result than expected, changing value...");
g_message("Expected: [%s]\nResponse: [%s]", at_cmd->expected, response);
at_cmd->expected = NULL;
at_send_command(manager);
} else {
at_next_command(manager);
}
}
int at_append_command(struct EG25Manager *manager,
const char *cmd,
const char *subcmd,
const char *value,
const char *expected,
void (*callback)
(struct EG25Manager *manager,
const char *response))
{
struct AtCommand *at_cmd = calloc(1, sizeof(struct AtCommand));
if (!at_cmd)
return -1;
at_cmd->cmd = g_strdup(cmd);
if (subcmd)
at_cmd->subcmd = g_strdup(subcmd);
if (value)
at_cmd->value = g_strdup(value);
if (expected)
at_cmd->expected = g_strdup(expected);
if (callback)
at_cmd->callback = callback;
manager->at_cmds = g_list_append(manager->at_cmds, at_cmd);
return 0;
}
#define READ_BUFFER_SIZE 256
static gboolean modem_response(gint fd,
GIOCondition event,
gpointer data)
{
struct EG25Manager *manager = data;
char response[READ_BUFFER_SIZE*4+1];
char tmp[READ_BUFFER_SIZE];
ssize_t ret, pos = 0;
/*
* Several reads can be necessary to get the full response, so we loop
* until we read 0 chars with a reasonable delay between attempts
* (remember the transfer rate is 115200 here)
*/
do {
ret = read(fd, tmp, sizeof(tmp));
if (ret > 0) {
memcpy(&response[pos], tmp, ret);
pos += ret;
usleep(10000);
}
} while (ret > 0 && pos < (sizeof(response) - 1));
if (pos > 0) {
response[pos] = 0;
g_strstrip(response);
if (strlen(response) == 0)
return TRUE;
g_message("Response: [%s]", response);
/*
* When the modem is started, it outputs 'RDY' to indicate that
* it is ready to receive AT commands.
*/
if (strcmp(response, "RDY") == 0) {
suspend_inhibit(manager, TRUE, TRUE);
manager->modem_state = EG25_STATE_STARTED;
}
/*
* Search for 'QGPSURC: "xtradataexpire"' in response to detect
* if GNSS assistance data must be re-uploaded.
* If that's the case, check if no AT commands are being processed
* to avoid deadlocks and start upload.
*/
else if (strstr(response, "QGPSURC: \"xtradataexpire\"") && manager->at_cmds == NULL)
gnss_upload_assistance_data(manager);
/*
* AT command failed, retry.
* QCFG="fast/poweroff" configuration is only available in
* newer firmware versions, skip retrying that specific AT command.
*/
else if (strstr(response, "ERROR") && !strstr(response, "fast/poweroff"))
retry_at_command(manager);
/*
* Successfull AT responses contain 'OK', except for AT+QFUPL which also
* returns 'CONNECT' when the modem is ready to receive data over serial
*/
else if (strstr(response, "OK") || strstr(response, "CONNECT")) {
if (manager->at_callback != NULL)
manager->at_callback(manager, response);
else
g_warning("AT command succesfull but no callback registered");
}
/* Not a recognized response, try running next command, just in case */
else
at_next_command(manager);
}
return TRUE;
}
static void parse_commands_list(toml_array_t *array, GArray **cmds)
{
int len;
len = toml_array_nelem(array);
*cmds = g_array_new(FALSE, TRUE, sizeof(struct AtCommand));
g_array_set_size(*cmds, (guint)len);
for (int i = 0; i < len; i++) {
struct AtCommand *cmd = &g_array_index(*cmds, struct AtCommand, i);
toml_table_t *table = toml_table_at(array, i);
toml_datum_t value;
if (!table)
continue;
value = toml_string_in(table, "cmd");
if (value.ok) {
cmd->cmd = g_strdup(value.u.s);
free(value.u.s);
}
value = toml_string_in(table, "subcmd");
if (value.ok) {
cmd->subcmd = g_strdup(value.u.s);
free(value.u.s);
}
value = toml_string_in(table, "value");
if (value.ok) {
cmd->value = g_strdup(value.u.s);
free(value.u.s);
}
value = toml_string_in(table, "expect");
if (value.ok) {
cmd->expected = g_strdup(value.u.s);
free(value.u.s);
}
}
}
int at_init(struct EG25Manager *manager, toml_table_t *config)
{
toml_array_t *commands;
toml_datum_t uart_port;
uart_port = toml_string_in(config, "uart");
if (!uart_port.ok)
g_error("Configuration file lacks UART port definition");
manager->at_fd = configure_serial(uart_port.u.s);
if (manager->at_fd < 0) {
g_critical("Unable to configure %s", uart_port.u.s);
free(uart_port.u.s);
return 1;
}
free(uart_port.u.s);
manager->at_source = g_unix_fd_add(manager->at_fd, G_IO_IN, modem_response, manager);
commands = toml_array_in(config, "configure");
if (!commands)
g_error("Configuration file lacks initial AT commands list");
parse_commands_list(commands, &configure_commands);
commands = toml_array_in(config, "suspend");
if (!commands)
g_error("Configuration file lacks suspend AT commands list");
parse_commands_list(commands, &suspend_commands);
commands = toml_array_in(config, "resume");
if (!commands)
g_error("Configuration file lacks resume AT commands list");
parse_commands_list(commands, &resume_commands);
commands = toml_array_in(config, "reset");
if (!commands)
g_error("Configuration file lacks reset AT commands list");
parse_commands_list(commands, &reset_commands);
return 0;
}
void at_destroy(struct EG25Manager *manager)
{
g_source_remove(manager->at_source);
if (manager->at_fd > 0)
close(manager->at_fd);
g_array_free(configure_commands, TRUE);
g_array_free(suspend_commands, TRUE);
g_array_free(resume_commands, TRUE);
g_array_free(reset_commands, TRUE);
}
void at_sequence_configure(struct EG25Manager *manager)
{
for (guint i = 0; i < configure_commands->len; i++) {
struct AtCommand *cmd = &g_array_index(configure_commands, struct AtCommand, i);
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
}
at_send_command(manager);
}
void at_sequence_suspend(struct EG25Manager *manager)
{
for (guint i = 0; i < suspend_commands->len; i++) {
struct AtCommand *cmd = &g_array_index(suspend_commands, struct AtCommand, i);
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
}
at_send_command(manager);
}
void at_sequence_resume(struct EG25Manager *manager)
{
for (guint i = 0; i < resume_commands->len; i++) {
struct AtCommand *cmd = &g_array_index(resume_commands, struct AtCommand, i);
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
}
at_send_command(manager);
}
void at_sequence_reset(struct EG25Manager *manager)
{
for (guint i = 0; i < reset_commands->len; i++) {
struct AtCommand *cmd = &g_array_index(reset_commands, struct AtCommand, i);
at_append_command(manager, cmd->cmd, cmd->subcmd, cmd->value, cmd->expected, at_process_result);
}
at_send_command(manager);
}