mirror of
https://gitlab.com/mobian1/eg25-manager.git
synced 2025-08-29 23:32:14 +02:00
gnss: add GNSS assistance support
Automatically fetch the GNSS assistance data from the Web to heavily reduce the time to acquire a GNSS lock by uploading the assistance data to the modem. This feature is optional and can be disabled in the configuration as people may prefer to not download the assistance data from the Quectel/Qualcomm servers. Also configure the GNSS engine to optimize the performance and power consumption.
This commit is contained in:
@@ -43,13 +43,33 @@ configure = [
|
|||||||
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" },
|
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" },
|
||||||
{ cmd = "QCFG", subcmd = "urc/delay", expect = "1" },
|
{ cmd = "QCFG", subcmd = "urc/delay", expect = "1" },
|
||||||
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
||||||
{ cmd = "QGPS", value = "1" },
|
|
||||||
{ cmd = "QSCLK", value = "1" },
|
{ cmd = "QSCLK", value = "1" },
|
||||||
|
# GNSS configuration:
|
||||||
|
# * Enable A-GPS data upload support (XTRA)
|
||||||
|
# * Disable On-Demand-Positioning (ODP) mode
|
||||||
|
# to avoid running the GNSS system in the background, even when not enabled.
|
||||||
|
# * Enable Dynamic Power Optimizations (DPO) mode to turn off GNSS RF radios
|
||||||
|
# when they are not in use.
|
||||||
|
# * Only enable GPS and GLONASS, disable other GNSS systems.
|
||||||
|
# A-GPS data upload doesn't work for Galileo anyway.
|
||||||
|
# * Avoid turning on GNSS support automatically when the modem boots.
|
||||||
|
{ cmd = "QGPSXTRA", expect = "1" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "gnssconfig", expect = "4" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "odpcontrol", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "dpoenable", expect = "1" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "gpsnmeatype", expect = "31" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "glonassnmeatype", expect = "7" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "autogps", expect = "0" },
|
||||||
]
|
]
|
||||||
suspend = [
|
suspend = [
|
||||||
{ cmd = "QGPSEND" },
|
|
||||||
]
|
]
|
||||||
resume = [
|
resume = [
|
||||||
{ cmd = "QGPS", value = "1" }
|
|
||||||
]
|
]
|
||||||
reset = [ { cmd = "CFUN", value = "1,1" } ]
|
reset = [ { cmd = "CFUN", value = "1,1" } ]
|
||||||
|
|
||||||
|
[gnss]
|
||||||
|
enabled = true
|
||||||
|
url = "https://xtrapath4.izatcloud.net"
|
||||||
|
file = "xtra2.bin"
|
||||||
|
@@ -43,13 +43,33 @@ configure = [
|
|||||||
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" },
|
{ cmd = "QCFG", subcmd = "urc/ri/other", expect = "\"off\",1" },
|
||||||
{ cmd = "QCFG", subcmd = "urc/delay", expect = "1" },
|
{ cmd = "QCFG", subcmd = "urc/delay", expect = "1" },
|
||||||
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
||||||
{ cmd = "QGPS", value = "1" },
|
|
||||||
{ cmd = "QSCLK", value = "1" },
|
{ cmd = "QSCLK", value = "1" },
|
||||||
|
# GNSS configuration:
|
||||||
|
# * Enable A-GPS data upload support (XTRA)
|
||||||
|
# * Disable On-Demand-Positioning (ODP) mode
|
||||||
|
# to avoid running the GNSS system in the background, even when not enabled.
|
||||||
|
# * Enable Dynamic Power Optimizations (DPO) mode to turn off GNSS RF radios
|
||||||
|
# when they are not in use.
|
||||||
|
# * Only enable GPS and GLONASS, disable other GNSS systems.
|
||||||
|
# A-GPS data upload doesn't work for Galileo anyway.
|
||||||
|
# * Avoid turning on GNSS support automatically when the modem boots.
|
||||||
|
{ cmd = "QGPSXTRA", expect = "1" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "gnssconfig", expect = "4" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "odpcontrol", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "dpoenable", expect = "1" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "gpsnmeatype", expect = "31" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "glonassnmeatype", expect = "7" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "autogps", expect = "0" },
|
||||||
]
|
]
|
||||||
suspend = [
|
suspend = [
|
||||||
{ cmd = "QGPSEND" },
|
|
||||||
]
|
]
|
||||||
resume = [
|
resume = [
|
||||||
{ cmd = "QGPS", value = "1" }
|
|
||||||
]
|
]
|
||||||
reset = [ { cmd = "CFUN", value = "1,1" } ]
|
reset = [ { cmd = "CFUN", value = "1,1" } ]
|
||||||
|
|
||||||
|
[gnss]
|
||||||
|
enabled = true
|
||||||
|
url = "https://xtrapath4.izatcloud.net"
|
||||||
|
file = "xtra2.bin"
|
||||||
|
@@ -37,13 +37,33 @@ configure = [
|
|||||||
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
|
{ cmd = "QCFG", subcmd = "ims", expect = "1" },
|
||||||
{ cmd = "QCFG", subcmd = "apready", expect = "1,0,500" },
|
{ cmd = "QCFG", subcmd = "apready", expect = "1,0,500" },
|
||||||
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
{ cmd = "QURCCFG", subcmd = "urcport", expect = "\"all\"" },
|
||||||
{ cmd = "QGPS", value = "1" },
|
|
||||||
{ cmd = "QSCLK", value = "1" },
|
{ cmd = "QSCLK", value = "1" },
|
||||||
|
# GNSS configuration:
|
||||||
|
# * Enable A-GPS data upload support (XTRA)
|
||||||
|
# * Disable On-Demand-Positioning (ODP) mode
|
||||||
|
# to avoid running the GNSS system in the background, even when not enabled.
|
||||||
|
# * Enable Dynamic Power Optimizations (DPO) mode to turn off GNSS RF radios
|
||||||
|
# when they are not in use.
|
||||||
|
# * Only enable GPS and GLONASS, disable other GNSS systems.
|
||||||
|
# A-GPS data upload doesn't work for Galileo anyway.
|
||||||
|
# * Avoid turning on GNSS support automatically when the modem boots.
|
||||||
|
{ cmd = "QGPSXTRA", expect = "1" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "gnssconfig", expect = "4" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "odpcontrol", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "dpoenable", expect = "1" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "gpsnmeatype", expect = "31" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "glonassnmeatype", expect = "7" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "galileonmeatype", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "beidounmeatype", expect = "0" },
|
||||||
|
{ cmd = "QGPSCFG", subcmd = "autogps", expect = "0" },
|
||||||
]
|
]
|
||||||
suspend = [
|
suspend = [
|
||||||
{ cmd = "QGPSEND" },
|
|
||||||
]
|
]
|
||||||
resume = [
|
resume = [
|
||||||
{ cmd = "QGPS", value = "1" }
|
|
||||||
]
|
]
|
||||||
reset = [ { cmd = "CFUN", value = "1,1" } ]
|
reset = [ { cmd = "CFUN", value = "1,1" } ]
|
||||||
|
|
||||||
|
[gnss]
|
||||||
|
enabled = true
|
||||||
|
url = "https://xtrapath4.izatcloud.net"
|
||||||
|
file = "xtra2.bin"
|
||||||
|
@@ -58,6 +58,7 @@ mgr_deps = [
|
|||||||
dependency('gudev-1.0'),
|
dependency('gudev-1.0'),
|
||||||
dependency('libgpiod'),
|
dependency('libgpiod'),
|
||||||
dependency('libusb-1.0'),
|
dependency('libusb-1.0'),
|
||||||
|
dependency('libcurl'),
|
||||||
mmglib_dep,
|
mmglib_dep,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
22
src/at.c
22
src/at.c
@@ -7,6 +7,7 @@
|
|||||||
#include "at.h"
|
#include "at.h"
|
||||||
#include "suspend.h"
|
#include "suspend.h"
|
||||||
#include "gpio.h"
|
#include "gpio.h"
|
||||||
|
#include "gnss.h"
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
@@ -215,20 +216,37 @@ static gboolean modem_response(gint fd,
|
|||||||
|
|
||||||
g_message("Response: [%s]", response);
|
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) {
|
if (strcmp(response, "RDY") == 0) {
|
||||||
suspend_inhibit(manager, TRUE, TRUE);
|
suspend_inhibit(manager, TRUE, TRUE);
|
||||||
manager->modem_state = EG25_STATE_STARTED;
|
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 */
|
||||||
else if (strstr(response, "ERROR"))
|
else if (strstr(response, "ERROR"))
|
||||||
retry_at_command(manager);
|
retry_at_command(manager);
|
||||||
else if (strstr(response, "OK")) {
|
/*
|
||||||
|
* 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)
|
if (manager->at_callback != NULL)
|
||||||
manager->at_callback(manager, response);
|
manager->at_callback(manager, response);
|
||||||
else
|
else
|
||||||
g_warning("AT command succesfull but no callback registered");
|
g_warning("AT command succesfull but no callback registered");
|
||||||
}
|
}
|
||||||
|
/* Not a recognized response, try running next command, just in case */
|
||||||
else
|
else
|
||||||
// Not a recognized response, try running next command, just in case
|
|
||||||
at_next_command(manager);
|
at_next_command(manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
478
src/gnss.c
Normal file
478
src/gnss.c
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Dylan Van Assche <me@dylanvanassche.be>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "gnss.h"
|
||||||
|
#include "manager.h"
|
||||||
|
#include "at.h"
|
||||||
|
|
||||||
|
#define BUFFER_SIZE 256
|
||||||
|
#define UPLOAD_DELAY 100000
|
||||||
|
#define RESCHEDULE_IN_SECS 30
|
||||||
|
|
||||||
|
static void gnss_step(struct EG25Manager *manager);
|
||||||
|
|
||||||
|
void gnss_upload_assistance_data(struct EG25Manager *manager)
|
||||||
|
{
|
||||||
|
if (!manager->gnss_assistance_enabled) {
|
||||||
|
g_message("GNSS assistance is disabled!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
manager->gnss_assistance_step = EG25_GNSS_STEP_FIRST;
|
||||||
|
gnss_step(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
void gnss_init(struct EG25Manager *manager, toml_table_t *config)
|
||||||
|
{
|
||||||
|
toml_datum_t enabled;
|
||||||
|
toml_datum_t url;
|
||||||
|
toml_datum_t file;
|
||||||
|
g_autoptr (GError) error = NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
enabled = toml_bool_in(config, "enabled");
|
||||||
|
manager->gnss_assistance_enabled = FALSE;
|
||||||
|
if (enabled.ok)
|
||||||
|
manager->gnss_assistance_enabled = enabled.u.b;
|
||||||
|
|
||||||
|
if (!manager->gnss_assistance_enabled) {
|
||||||
|
g_message("GNSS assistance is disabled!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
url = toml_string_in(config, "url");
|
||||||
|
if (url.ok)
|
||||||
|
manager->gnss_assistance_url = url.u.s;
|
||||||
|
else
|
||||||
|
g_error("GNSS assistance server URL is missing from config file");
|
||||||
|
file = toml_string_in(config, "file");
|
||||||
|
if (file.ok)
|
||||||
|
manager->gnss_assistance_file = file.u.s;
|
||||||
|
else
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
CURL *curl;
|
||||||
|
CURLcode response;
|
||||||
|
long status_code;
|
||||||
|
gchar *url = NULL;
|
||||||
|
FILE *tmp_file = NULL;
|
||||||
|
long int size;
|
||||||
|
|
||||||
|
/* Fetch assistance data with curl */
|
||||||
|
tmp_file = fdopen(manager->gnss_assistance_fd, "wb");
|
||||||
|
url = g_strconcat(manager->gnss_assistance_url, "/",
|
||||||
|
manager->gnss_assistance_file, NULL);
|
||||||
|
curl = curl_easy_init();
|
||||||
|
if (!curl)
|
||||||
|
g_error ("Unable to initialize curl");
|
||||||
|
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_FOLLOWLOCATION, 1L);
|
||||||
|
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
||||||
|
response = curl_easy_perform(curl);
|
||||||
|
if (response == CURLE_HTTP_RETURNED_ERROR) {
|
||||||
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
g_warning ("Unable to fetch GNSS assistance data from %s (HTTP %ld)",
|
||||||
|
url, status_code);
|
||||||
|
|
||||||
|
/* Restart upload on HTTP error status code */
|
||||||
|
g_message ("Rescheduling upload because of failure in %ds",
|
||||||
|
RESCHEDULE_IN_SECS);
|
||||||
|
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||||
|
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
|
||||||
|
G_SOURCE_FUNC(gnss_upload_assistance_data),
|
||||||
|
manager);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Get file size in bytes */
|
||||||
|
size = (long int)lseek(manager->gnss_assistance_fd, 0, SEEK_END);
|
||||||
|
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (size <= 0) {
|
||||||
|
g_warning ("GNSS assistance data contains 0 bytes,"
|
||||||
|
"check network connection.");
|
||||||
|
/*
|
||||||
|
* Restart upload when file does not contain any data,
|
||||||
|
* mostly because of no network connection.
|
||||||
|
*/
|
||||||
|
g_message ("Rescheduling upload because of failure in %ds",
|
||||||
|
RESCHEDULE_IN_SECS);
|
||||||
|
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||||
|
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
|
||||||
|
G_SOURCE_FUNC(gnss_upload_assistance_data),
|
||||||
|
manager);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_message("Fetching GNSS assistance data from %s was successfull", url);
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
g_free(url);
|
||||||
|
|
||||||
|
/* Go to the next step */
|
||||||
|
manager->gnss_assistance_step++;
|
||||||
|
gnss_step(manager);
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_assistance_data_upload_start(struct EG25Manager *manager,
|
||||||
|
const char *response)
|
||||||
|
{
|
||||||
|
gchar value[BUFFER_SIZE];
|
||||||
|
long int size;
|
||||||
|
|
||||||
|
/* Process AT response */
|
||||||
|
at_process_result(manager, response);
|
||||||
|
|
||||||
|
/* Get file size in bytes */
|
||||||
|
size = (long int)lseek(manager->gnss_assistance_fd, 0, SEEK_END);
|
||||||
|
lseek(manager->gnss_assistance_fd, 0, SEEK_SET);
|
||||||
|
|
||||||
|
/* Start upload */
|
||||||
|
g_snprintf(value, BUFFER_SIZE, "\"RAM:%s\",%ld\r\n",
|
||||||
|
manager->gnss_assistance_file, size);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
char buffer[2*BUFFER_SIZE];
|
||||||
|
gint len;
|
||||||
|
gboolean success = TRUE;
|
||||||
|
|
||||||
|
/* Copy downloaded XTRA assistance data to the modem over serial */
|
||||||
|
while((len = read(manager->gnss_assistance_fd, buffer, 2*BUFFER_SIZE)) > 0)
|
||||||
|
{
|
||||||
|
len = write(manager->at_fd, buffer, len);
|
||||||
|
if (len < 0) {
|
||||||
|
success = FALSE;
|
||||||
|
g_error("Writing GNSS assistance data failed: %d", len);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
usleep(UPLOAD_DELAY);
|
||||||
|
g_message("Uploaded %d bytes", len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear QFUPL AT command and process next */
|
||||||
|
at_next_command(manager);
|
||||||
|
|
||||||
|
/* Go to the next step if successful */
|
||||||
|
if (success) {
|
||||||
|
manager->gnss_assistance_step++;
|
||||||
|
gnss_step(manager);
|
||||||
|
}
|
||||||
|
/* Restart upload */
|
||||||
|
else {
|
||||||
|
g_message ("Rescheduling upload because of failure in %ds",
|
||||||
|
RESCHEDULE_IN_SECS);
|
||||||
|
manager->gnss_assistance_step = EG25_GNSS_STEP_LAST;
|
||||||
|
g_timeout_add_seconds(RESCHEDULE_IN_SECS,
|
||||||
|
G_SOURCE_FUNC(gnss_upload_assistance_data),
|
||||||
|
manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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\"\r\n");
|
||||||
|
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\"\r\n",
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
MMModemLocationSource sources;
|
||||||
|
gboolean signal_location;
|
||||||
|
g_autoptr (GError) error = NULL;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
sources = mm_modem_location_get_enabled(manager->mm_location);
|
||||||
|
signal_location = mm_modem_location_signals_location(manager->mm_location);
|
||||||
|
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 */
|
||||||
|
|
||||||
|
#ifdef HAVE_MMGLIB
|
||||||
|
case EG25_GNSS_STEP_MM_GNSS_DISABLE:
|
||||||
|
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_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;
|
||||||
|
|
||||||
|
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:
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
16
src/gnss.h
Normal file
16
src/gnss.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 Dylan Van Assche <me@dylanvanassche.be>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <time.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include "manager.h"
|
||||||
|
|
||||||
|
void gnss_init(struct EG25Manager *manager, toml_table_t *config);
|
||||||
|
void gnss_destroy(struct EG25Manager *manager);
|
||||||
|
void gnss_upload_assistance_data(struct EG25Manager *manager);
|
@@ -15,6 +15,7 @@
|
|||||||
#include "ofono-iface.h"
|
#include "ofono-iface.h"
|
||||||
#include "suspend.h"
|
#include "suspend.h"
|
||||||
#include "udev.h"
|
#include "udev.h"
|
||||||
|
#include "gnss.h"
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
@@ -342,6 +343,7 @@ int main(int argc, char *argv[])
|
|||||||
ofono_iface_init(&manager);
|
ofono_iface_init(&manager);
|
||||||
suspend_init(&manager, toml_table_in(toml_config, "suspend"));
|
suspend_init(&manager, toml_table_in(toml_config, "suspend"));
|
||||||
udev_init(&manager, toml_table_in(toml_config, "udev"));
|
udev_init(&manager, toml_table_in(toml_config, "udev"));
|
||||||
|
gnss_init(&manager, toml_table_in(toml_config, "gnss"));
|
||||||
|
|
||||||
g_idle_add(G_SOURCE_FUNC(modem_start), &manager);
|
g_idle_add(G_SOURCE_FUNC(modem_start), &manager);
|
||||||
|
|
||||||
|
@@ -16,6 +16,32 @@
|
|||||||
|
|
||||||
#include "toml.h"
|
#include "toml.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EG25_GNSS_STEP_FIRST = 0,
|
||||||
|
#ifdef HAVE_MMGLIB
|
||||||
|
EG25_GNSS_STEP_MM_GNSS_DISABLE,
|
||||||
|
#endif
|
||||||
|
EG25_GNSS_STEP_AT_GNSS_DISABLE,
|
||||||
|
EG25_GNSS_STEP_FETCH_ASSISTANCE_DATA,
|
||||||
|
EG25_GNSS_STEP_INIT_UPLOAD,
|
||||||
|
EG25_GNSS_STEP_UPLOAD,
|
||||||
|
EG25_GNSS_STEP_FINISH_UPLOAD,
|
||||||
|
#ifdef HAVE_MMGLIB
|
||||||
|
EG25_GNSS_STEP_MM_GNSS_ENABLE,
|
||||||
|
#endif
|
||||||
|
EG25_GNSS_STEP_AT_QGPS_ENABLE,
|
||||||
|
EG25_GNSS_STEP_LAST,
|
||||||
|
} EG25GNSSStep;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EG25_GNSS_SOURCE_NONE = 0,
|
||||||
|
EG25_GNSS_SOURCE_NMEA = 1 << 0,
|
||||||
|
EG25_GNSS_SOURCE_RAW = 1 << 1,
|
||||||
|
EG25_GNSS_SOURCE_UNMANAGED = 1 << 2,
|
||||||
|
EG25_GNSS_SOURCE_QGPS = 1 << 3,
|
||||||
|
} EG25GNSSSource;
|
||||||
|
|
||||||
|
|
||||||
enum EG25State {
|
enum EG25State {
|
||||||
EG25_STATE_INIT = 0,
|
EG25_STATE_INIT = 0,
|
||||||
EG25_STATE_POWERED, // Power-on sequence has been executed, but the modem isn't on yet
|
EG25_STATE_POWERED, // Power-on sequence has been executed, but the modem isn't on yet
|
||||||
@@ -52,11 +78,19 @@ struct EG25Manager {
|
|||||||
enum EG25State modem_state;
|
enum EG25State modem_state;
|
||||||
gchar *modem_usb_id;
|
gchar *modem_usb_id;
|
||||||
|
|
||||||
|
gboolean gnss_assistance_enabled;
|
||||||
|
EG25GNSSSource gnss_sources;
|
||||||
|
EG25GNSSStep gnss_assistance_step;
|
||||||
|
gint gnss_assistance_fd;
|
||||||
|
gchar *gnss_assistance_url;
|
||||||
|
gchar *gnss_assistance_file;
|
||||||
|
|
||||||
enum ModemIface modem_iface;
|
enum ModemIface modem_iface;
|
||||||
guint mm_watch;
|
guint mm_watch;
|
||||||
#ifdef HAVE_MMGLIB
|
#ifdef HAVE_MMGLIB
|
||||||
MMManager *mm_manager;
|
MMManager *mm_manager;
|
||||||
MMModem *mm_modem;
|
MMModem *mm_modem;
|
||||||
|
MMModemLocation *mm_location;
|
||||||
#endif
|
#endif
|
||||||
guint ofono_watch;
|
guint ofono_watch;
|
||||||
GDBOManager *ofono_manager;
|
GDBOManager *ofono_manager;
|
||||||
|
@@ -15,6 +15,7 @@ src = [
|
|||||||
'suspend.c', 'suspend.h',
|
'suspend.c', 'suspend.h',
|
||||||
'toml.c', 'toml.h',
|
'toml.c', 'toml.h',
|
||||||
'udev.c', 'udev.h',
|
'udev.c', 'udev.h',
|
||||||
|
'gnss.c', 'gnss.h',
|
||||||
gdbofono_headers,
|
gdbofono_headers,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@@ -32,7 +32,10 @@ static void add_modem(struct EG25Manager *manager, GDBusObject *object)
|
|||||||
|
|
||||||
g_assert(MM_IS_OBJECT (object));
|
g_assert(MM_IS_OBJECT (object));
|
||||||
manager->mm_modem = mm_object_get_modem(MM_OBJECT(object));
|
manager->mm_modem = mm_object_get_modem(MM_OBJECT(object));
|
||||||
g_assert(manager->mm_modem != NULL);
|
manager->mm_location = mm_object_get_modem_location(MM_OBJECT(object));
|
||||||
|
g_assert_nonnull(manager->mm_modem);
|
||||||
|
g_assert_nonnull(manager->mm_location);
|
||||||
|
|
||||||
|
|
||||||
if (manager->modem_state == EG25_STATE_RESUMING) {
|
if (manager->modem_state == EG25_STATE_RESUMING) {
|
||||||
if (manager->modem_recovery_timer) {
|
if (manager->modem_recovery_timer) {
|
||||||
|
Reference in New Issue
Block a user