Files
eg25-manager/src/gnss.c
Arnaud Ferraris f978b775e0 src: fix issues reported by clang-tidy
Some of those are simply ignored as they're false positives.
2024-11-12 13:10:43 +01:00

513 lines
17 KiB
C

/*
* Copyright (C) 2021 Dylan Van Assche <me@dylanvanassche.be>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "gnss.h"
#include "at.h"
#include "config.h"
#include "manager.h"
#include <errno.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#define BUFFER_SIZE 256
#define UPLOAD_DELAY_US 25000
#define UPLOAD_TIMEOUT_S 10
#define RESCHEDULE_IN_SECS 30
static void gnss_step(struct EG25Manager *manager);
gboolean gnss_upload_assistance_data(struct EG25Manager *manager)
{
if (!manager->gnss_assistance_enabled) {
g_message("GNSS assistance is disabled!");
return FALSE;
}
if (manager->gnss_assistance_step < EG25_GNSS_STEP_LAST) {
g_warning("GNSS assistance data upload already in process (%d/%d)",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
return FALSE;
}
/* data upload isn't necessary to bring the modem onine, so we should wait
* until we've finished the rest of our configuration */
if (!manager->modem_iface || manager->modem_state < EG25_STATE_CONFIGURED ||
manager->modem_state > EG25_STATE_CONNECTED) {
g_message("Rescheduling upload since modem isn't online yet, in %ds", RESCHEDULE_IN_SECS);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
return TRUE;
}
#ifdef HAVE_MMGLIB
/* ModemManager's Location is only available after unlocking */
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER && !manager->mm_location) {
g_message("Rescheduling upload since Location interface is not available, in %ds",
RESCHEDULE_IN_SECS);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
return TRUE;
}
#endif
manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST;
gnss_step(manager);
return FALSE;
}
void gnss_init(struct EG25Manager *manager, toml_table_t *config[])
{
toml_table_t *gnss_config[EG25_CONFIG_COUNT];
g_autoptr(GError) error = NULL;
for (int i = 0; i < EG25_CONFIG_COUNT; i++)
gnss_config[i] = config[i] ? toml_table_in(config[i], "gnss") : NULL;
if (!gnss_config[EG25_CONFIG_SYS])
g_error("Default config file lacks the 'gnss' section!");
/*
* GNSS assistance is an optional feature, you can disable it
* if you want in the configuration file.
* In case the configuration is missing, we assume GNSS assistance
* to be disabled.
*/
config_get_bool(gnss_config, "enabled", &manager->gnss_assistance_enabled);
if (!manager->gnss_assistance_enabled) {
g_message("GNSS assistance is disabled!");
return;
}
if (!config_get_string(gnss_config, "url", &manager->gnss_assistance_url))
g_error("GNSS assistance server URL is missing from config file");
if (!config_get_string(gnss_config, "file", &manager->gnss_assistance_file))
g_error("GNSS assistance file name is missing from config file");
/* Create temporary file to store assistance data */
manager->gnss_assistance_fd = g_file_open_tmp(NULL, NULL, &error);
if (error != NULL)
g_error("Unable to create temporary file: %s", error->message);
/* Initialize state and schedule upload */
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
g_timeout_add_seconds(RESCHEDULE_IN_SECS, G_SOURCE_FUNC(gnss_upload_assistance_data), manager);
}
void gnss_destroy(struct EG25Manager *manager)
{
g_free(manager->gnss_assistance_url);
g_free(manager->gnss_assistance_file);
close(manager->gnss_assistance_fd);
}
/******************************************************************************/
#ifdef HAVE_MMGLIB
static void disable_mm_gnss(struct EG25Manager *manager)
{
MMModemLocationSource sources;
gboolean signals_location;
g_autoptr(GError) error = NULL;
sources = mm_modem_location_get_enabled(manager->mm_location);
signals_location = mm_modem_location_signals_location(manager->mm_location);
manager->gnss_sources = EG25_GNSS_SOURCE_NONE;
/* Save GNSS engine state */
if (sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA)
manager->gnss_sources |= EG25_GNSS_SOURCE_NMEA;
else
manager->gnss_sources &= ~EG25_GNSS_SOURCE_NMEA;
if (sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW)
manager->gnss_sources |= EG25_GNSS_SOURCE_RAW;
else
manager->gnss_sources &= ~EG25_GNSS_SOURCE_RAW;
if (sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)
manager->gnss_sources |= EG25_GNSS_SOURCE_UNMANAGED;
else
manager->gnss_sources &= ~EG25_GNSS_SOURCE_UNMANAGED;
/* Disable GNSS engine */
sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_RAW;
sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
sources &= ~MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
mm_modem_location_setup_sync(manager->mm_location, sources, signals_location, NULL, &error);
if (error != NULL) {
g_warning("Unable to disable GNSS engine through ModemManager: %s", error->message);
}
}
#endif
static void disable_at_gnss_cb(struct EG25Manager *manager, const char *response)
{
/* Clear QGPSEND AT command and process next */
at_next_command(manager);
/* Go to the next step */
manager->gnss_assistance_step++;
gnss_step(manager);
}
static void state_at_gnss_cb(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;
/* Parse GNSS engine status and disable it if needed */
if (strstr(response, "QGPS: 1")) {
manager->gnss_sources |= EG25_GNSS_SOURCE_QGPS;
g_free(at_cmd->value);
g_free(at_cmd->cmd);
at_cmd->expected = NULL;
at_cmd->subcmd = NULL;
at_cmd->value = NULL;
at_cmd->cmd = g_strdup("QGPSEND");
at_cmd->callback = disable_at_gnss_cb;
at_send_command(manager);
}
/* QGPS is already disabled, move on to next step */
else {
at_next_command(manager);
manager->gnss_sources &= ~EG25_GNSS_SOURCE_QGPS;
manager->gnss_assistance_step++;
gnss_step(manager);
}
}
static void state_at_gnss(struct EG25Manager *manager)
{
/* Asynchronously send AT command to query status of GNSS engine */
at_append_command(manager, "QGPS?", NULL, NULL, NULL, state_at_gnss_cb);
at_send_command(manager);
}
/******************************************************************************/
static void fetch_assistance_data(struct EG25Manager *manager)
{
CURLcode response;
curl_off_t downloaded;
CURL *curl = NULL;
g_autofree gchar *url = NULL;
FILE *tmp_file = NULL;
gchar errbuf[CURL_ERROR_SIZE];
errbuf[0] = 0;
/* Fetch assistance data with curl */
tmp_file = fdopen(manager->gnss_assistance_fd, "wb+");
if (tmp_file == NULL) {
g_critical("Unable to open file to save assistance data: %s", g_strerror(errno));
goto bail;
}
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
if (ftruncate(manager->gnss_assistance_fd, 0) < 0)
g_warning("Unable to truncate file, assistance data might be invalid!");
url = g_strconcat(manager->gnss_assistance_url, "/", manager->gnss_assistance_file, NULL);
curl = curl_easy_init();
if (!curl) {
g_critical("Unable to initialize curl");
goto bail;
}
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, NULL);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, tmp_file);
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errbuf);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
response = curl_easy_perform(curl);
if (response != CURLE_OK) {
g_warning("Unable to fetch GNSS assistance data from %s: %s",
url,
strlen(errbuf) ? errbuf : curl_easy_strerror(response));
goto bail;
}
response = curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, &downloaded);
if (response) {
g_critical("Unable to get number of downloaded bytes from curl");
goto bail;
} else if (downloaded <= 0) {
g_warning("Downloaded empty assistance data file");
goto bail;
}
g_message("Fetching GNSS assistance data from %s was successful", url);
/* Go to the next step */
manager->gnss_assistance_step++;
gnss_step(manager);
goto cleanup;
bail:
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
cleanup:
fflush(tmp_file);
fclose(tmp_file);
if (curl != NULL)
curl_easy_cleanup(curl);
}
/******************************************************************************/
static void init_assistance_data_upload_ready(struct EG25Manager *manager, const char *response)
{
/* Search for 'CONNECT' in response to start upload */
if (strstr(response, "CONNECT")) {
g_message("Modem ready for GNSS assistance data upload");
manager->gnss_assistance_step++;
gnss_step(manager);
} else if (strstr(response, "QFUPL")) {
/* Clear QFUPL AT command and process next */
at_next_command(manager);
manager->gnss_assistance_step++;
gnss_step(manager);
}
}
static void init_assistance_data_upload_start(struct EG25Manager *manager, const char *response)
{
gchar value[BUFFER_SIZE];
off_t size;
/* Process AT response */
at_process_result(manager, response);
/* Get file size in bytes */
size = lseek(manager->gnss_assistance_fd, 0, SEEK_END);
if (size == -1) {
g_critical("gnss: unable to read size of xtra data file: %s", g_strerror(errno));
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
return;
}
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
/* Start upload */
g_snprintf(value,
BUFFER_SIZE,
"\"RAM:%s\",%ld,%d",
manager->gnss_assistance_file,
size,
UPLOAD_TIMEOUT_S);
g_message("Initiate GNSS assistance data upload: %s", value);
at_append_command(manager, "QFUPL", NULL, value, NULL, init_assistance_data_upload_ready);
at_send_command(manager);
}
static void init_assistance_data_upload(struct EG25Manager *manager)
{
/*
* Delete all previous GNSS assistance data files in RAM
* and start uploading the latest one to RAM.
*/
at_append_command(manager, "QFDEL", NULL, "\"RAM:*\"\r\n", NULL, init_assistance_data_upload_start);
at_send_command(manager);
}
static void upload_assistance_data(struct EG25Manager *manager)
{
gint error;
off_t written_total = 0;
gint ret;
struct stat sb;
if (fstat(manager->gnss_assistance_fd, &sb) != 0) {
g_critical("gnss: unable to stat xtra data file: %s", g_strerror(errno));
/* Make sure the upload times out and the modem goes back to AT command mode */
sleep(UPLOAD_TIMEOUT_S + 1);
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
return;
}
do {
errno = 0;
/* Copy downloaded XTRA assistance data to the modem over serial */
ret = sendfile(manager->at_fd, manager->gnss_assistance_fd, &written_total, BUFFER_SIZE);
error = errno;
usleep(UPLOAD_DELAY_US);
} while ((!error && written_total < sb.st_size) || (ret == -1 && error == EAGAIN));
/* Go to the next step if successful */
if (!error) {
g_message("Successfully uploaded %ld bytes to the modem", written_total);
} else {
g_critical("Unable to upload xtra data: %s", g_strerror(error));
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
}
}
static void finish_assistance_data_upload_cb(struct EG25Manager *manager, const char *response)
{
/* Process response */
at_process_result(manager, response);
g_message("GNSS assistance data upload finished");
/* Go to the next step */
manager->gnss_assistance_step++;
gnss_step(manager);
}
static void finish_assistance_data_upload(struct EG25Manager *manager)
{
gchar value[BUFFER_SIZE];
GDateTime *datetime;
gchar *timestring;
/* Configure GNSS assistance clock to current system time (UTC) */
datetime = g_date_time_new_now_utc();
timestring = g_date_time_format(datetime, "0,\"%Y/%m/%d,%H:%M:%S\"");
g_message("Setting GNSS assistance UTC clock to: %s", timestring);
at_append_command(manager, "QGPSXTRATIME", NULL, timestring, NULL, at_process_result);
/* Configure GNSS engine to use uploaded GNSS assistance data */
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\"", manager->gnss_assistance_file);
g_message("Setting GNSS assistance file to: %s", value);
at_append_command(manager, "QGPSXTRADATA", NULL, value, NULL, finish_assistance_data_upload_cb);
at_send_command(manager);
}
/******************************************************************************/
#ifdef HAVE_MMGLIB
static void enable_mm_gnss(struct EG25Manager *manager)
{
g_autoptr(GError) error = NULL;
MMModemLocationSource sources = mm_modem_location_get_enabled(manager->mm_location);
gboolean signal_location = mm_modem_location_signals_location(manager->mm_location);
if (manager->gnss_sources & EG25_GNSS_SOURCE_UNMANAGED)
sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED;
if (manager->gnss_sources & EG25_GNSS_SOURCE_NMEA)
sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA;
if (manager->gnss_sources & EG25_GNSS_SOURCE_RAW)
sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW;
mm_modem_location_setup_sync(manager->mm_location, sources, signal_location, NULL, &error);
if (error != NULL)
g_warning("Unable to enable GNSS engine through ModemManager: %s", error->message);
}
#endif
static void enable_at_gnss_cb(struct EG25Manager *manager, const char *response)
{
manager->gnss_assistance_step++;
gnss_step(manager);
}
static void enable_at_gnss(struct EG25Manager *manager)
{
if (manager->gnss_sources & EG25_GNSS_SOURCE_QGPS) {
at_append_command(manager, "QGPS", NULL, "1", NULL, enable_at_gnss_cb);
at_send_command(manager);
return;
}
manager->gnss_assistance_step++;
gnss_step(manager);
}
/******************************************************************************/
void gnss_step(struct EG25Manager *manager)
{
switch (manager->gnss_assistance_step) {
case EG25_GNSS_STEP_FIRST:
manager->gnss_assistance_step++;
g_message("GNSS assistance upload started...");
/* fall-through */
case EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA:
g_message("GNSS assistance upload step (%d/%d): "
"fetching assistance data",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
fetch_assistance_data(manager);
break;
#ifdef HAVE_MMGLIB
case EG25_GNSS_STEP_MM_GNSS_DISABLE:
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
g_message("GNSS assistance upload step (%d/%d): "
"disabling GNSS engine through ModemManager",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
disable_mm_gnss(manager);
}
manager->gnss_assistance_step++;
/* fall-through */
#endif
case EG25_GNSS_STEP_AT_GNSS_DISABLE:
g_message("GNSS assistance upload step (%d/%d): "
"disabling GNSS engine through AT+QGPS",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
state_at_gnss(manager);
break;
case EG25_GNSS_STEP_INIT_UPLOAD:
g_message("GNSS assistance upload step (%d/%d): initiating upload",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
init_assistance_data_upload(manager);
break;
case EG25_GNSS_STEP_UPLOAD:
g_message("GNSS assistance upload step (%d/%d): "
"uploading assistance data",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
upload_assistance_data(manager);
break;
case EG25_GNSS_STEP_FINISH_UPLOAD:
g_message("GNSS assistance upload step (%d/%d): finishing upload",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
finish_assistance_data_upload(manager);
break;
#ifdef HAVE_MMGLIB
case EG25_GNSS_STEP_MM_GNSS_ENABLE:
if (manager->modem_iface == MODEM_IFACE_MODEMMANAGER) {
g_message("GNSS assistance upload step (%d/%d): "
"re-enabling GNSS through ModemManager",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
enable_mm_gnss(manager);
}
manager->gnss_assistance_step++;
/* fall-through */
#endif
case EG25_GNSS_STEP_AT_QGPS_ENABLE:
g_message("GNSS assistance upload step (%d/%d): "
"re-enabling GNSS through AT+QGPS",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
enable_at_gnss(manager);
break;
case EG25_GNSS_STEP_LAST:
g_message("GNSS assistance upload step (%d/%d): finished",
manager->gnss_assistance_step,
EG25_GNSS_STEP_LAST);
break;
}
}