Merge request !1418 from 'feat/inject-time-position'

shared-qmi: handle inject time and inject position requests from the modem

https://gitlab.freedesktop.org/mobile-broadband/ModemManager/-/merge_requests/1418
This commit is contained in:
Dan Williams
2026-01-09 17:20:44 -06:00
7 changed files with 1163 additions and 20 deletions
+13 -11
View File
@@ -10,7 +10,7 @@ project(
'c_std=gnu89',
'warning_level=2',
],
meson_version: '>= 0.53.0',
meson_version: '>= 0.60.0',
)
meson_version = meson.version()
@@ -31,11 +31,13 @@ mm_includedir = get_option('includedir')
mm_libdir = get_option('libdir')
mm_sbindir = get_option('sbindir')
mm_sysconfdir = get_option('sysconfdir')
mm_sharedstatedir = get_option('sharedstatedir')
mm_pkgdatadir = mm_datadir / mm_name
mm_pkgincludedir = mm_includedir / mm_name
mm_pkglibdir = mm_libdir / mm_name
mm_pkgsysconfdir = mm_sysconfdir / mm_name
mm_pkgsharedstatedir = mm_sharedstatedir / mm_name
mm_glib_name = 'libmm-glib'
mm_glib_pkgincludedir = mm_includedir / mm_glib_name
@@ -168,21 +170,21 @@ glib_deps = declare_dependency(
# DBus system directory
dbus_dep = dependency('dbus-1')
dbus_interfaces_dir = dbus_dep.get_pkgconfig_variable('interfaces_dir', define_variable: ['datadir', mm_datadir])
dbus_system_bus_services_dir = dbus_dep.get_pkgconfig_variable('system_bus_services_dir', define_variable: ['datadir', mm_datadir])
dbus_interfaces_dir = dbus_dep.get_variable(pkgconfig: 'interfaces_dir', pkgconfig_define: ['datadir', mm_datadir])
dbus_system_bus_services_dir = dbus_dep.get_variable(pkgconfig: 'system_bus_services_dir', pkgconfig_define: ['datadir', mm_datadir])
dbus_policy_dir = get_option('dbus_policy_dir')
if dbus_policy_dir == ''
dbus_policy_dir = dbus_dep.get_pkgconfig_variable('sysconfdir', define_variable: ['sysconfdir', mm_sysconfdir]) / 'dbus-1/system.d'
dbus_policy_dir = dbus_dep.get_variable(pkgconfig: 'sysconfdir', pkgconfig_define: ['sysconfdir', mm_sysconfdir]) / 'dbus-1/system.d'
endif
enable_bash_completion = get_option('bash_completion')
if enable_bash_completion
bash_completion_dep = dependency('bash-completion')
bash_completion_completionsdir = bash_completion_dep.get_pkgconfig_variable(
'completionsdir',
bash_completion_completionsdir = bash_completion_dep.get_variable(
pkgconfig: 'completionsdir',
# bash-completion 2.10 changed the substitutions
define_variable: bash_completion_dep.version().version_compare('>= 2.10') ? ['datadir', mm_datadir] : ['prefix', mm_prefix],
pkgconfig_define: bash_completion_dep.version().version_compare('>= 2.10') ? ['datadir', mm_datadir] : ['prefix', mm_prefix],
)
endif
@@ -197,7 +199,7 @@ config_h.set('WITH_UDEV', enable_udev)
udev_udevdir = get_option('udevdir')
if udev_udevdir == ''
assert(enable_udev, 'udevdir must be explicitly given if udev support is disabled')
udev_udevdir = dependency('udev').get_pkgconfig_variable('udevdir')
udev_udevdir = dependency('udev').get_variable(pkgconfig: 'udevdir')
endif
udev_rulesdir = udev_udevdir / 'rules.d'
@@ -207,7 +209,7 @@ install_systemdunitdir = (systemd_systemdsystemunitdir != 'no')
if install_systemdunitdir and systemd_systemdsystemunitdir == ''
systemd_dep = dependency('systemd', not_found_message: 'systemd required but not found, please provide a valid systemd user unit dir or disable it')
systemd_systemdsystemunitdir = systemd_dep.get_pkgconfig_variable('systemdsystemunitdir', define_variable: ['root_prefix', mm_prefix])
systemd_systemdsystemunitdir = systemd_dep.get_variable(pkgconfig: 'systemdsystemunitdir', pkgconfig_define: ['root_prefix', mm_prefix])
endif
# Suspend/resume support
@@ -240,7 +242,7 @@ enable_polkit = (polkit_opt != 'no')
if enable_polkit
polkit_gobject_dep = dependency('polkit-gobject-1', version: '>= 0.97', not_found_message: 'PolicyKit development headers are required')
polkit_gobject_policydir = polkit_gobject_dep.get_pkgconfig_variable('policydir', define_variable: ['prefix', mm_prefix])
polkit_gobject_policydir = polkit_gobject_dep.get_variable(pkgconfig: 'policydir', pkgconfig_define: ['prefix', mm_prefix])
policy_conf = {'MM_DEFAULT_USER_POLICY': (polkit_opt == 'permissive' ? 'yes' : 'auth_self_keep')}
endif
@@ -297,7 +299,7 @@ if enable_qrtr
if qmi_glib_dep.type_name() == 'internal'
qmi_qrtr_supported = qmi_glib_proj.get_variable('enable_qrtr')
else
qmi_qrtr_supported = qmi_glib_dep.get_pkgconfig_variable('qmi_qrtr_supported')
qmi_qrtr_supported = qmi_glib_dep.get_variable(pkgconfig: 'qmi_qrtr_supported')
endif
assert(qmi_qrtr_supported.to_int().is_odd(), 'Couldn\'t find QRTR support in qmi-glib.')
qrtr_glib_dep = dependency('qrtr-glib', version: '>= 1.0.0')
+11
View File
@@ -46,6 +46,7 @@ sources = files(
'mm-cbm-part.c',
'mm-charsets.c',
'mm-error-helpers.c',
'mm-location-cache.c',
'mm-log.c',
'mm-log-object.c',
'mm-modem-helpers.c',
@@ -80,11 +81,16 @@ if enable_systemd_journal
private_deps += libsystemd_dep
endif
c_args = [
'-DPKGSTATEDIR="@0@"'.format(mm_pkgsharedstatedir),
]
libhelpers = static_library(
'helpers',
sources: sources + enums_sources,
include_directories: incs,
dependencies: deps + private_deps,
c_args: c_args,
)
libhelpers_dep = declare_dependency(
@@ -506,6 +512,11 @@ install_data(
install_dir: udev_rulesdir,
)
# empty directory for cached position
install_emptydir(
mm_pkgsharedstatedir,
)
if enable_tests
subdir('tests')
endif
+241
View File
@@ -0,0 +1,241 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2026 Andrey Skvortsov <andrej.skvortzov@gmail.com>
*/
#include <config.h>
#include <glib/gstdio.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-location-cache.h"
#include "mm-log-object.h"
#if !defined PKGSTATEDIR
# error PKGSTATEDIR is not defined
#endif
#define LOCATION_STATE_FILE "location.ini"
#define LOCATION_LAST_POS_GROUP "last_position"
#define LOCATION_LAST_POS_LATITUDE_KEY "latitude"
#define LOCATION_LAST_POS_LONGITUDE_KEY "longitude"
/*****************************************************************************/
static void log_object_iface_init (MMLogObjectInterface *iface);
G_DEFINE_TYPE_EXTENDED (MMLocationCache, mm_location_cache, G_TYPE_OBJECT, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_LOG_OBJECT, log_object_iface_init))
struct _MMLocationCachePrivate {
MMLocationGpsRaw *gps_raw;
gdouble last_latitude;
gdouble last_longitude;
gchar* filename;
};
/*****************************************************************************/
static gchar *
log_object_build_id (MMLogObject *_self)
{
return g_strdup ("location-cache");
}
gboolean
mm_location_cache_load_from_file (MMLocationCache *self,
const gchar *file,
GError **error)
{
g_autoptr(GKeyFile) key_file = g_key_file_new ();
gdouble lat;
gdouble lon;
self->priv->last_latitude = MM_LOCATION_LATITUDE_UNKNOWN;
self->priv->last_longitude = MM_LOCATION_LONGITUDE_UNKNOWN;
if (!g_key_file_load_from_file (key_file, file, G_KEY_FILE_NONE, error)) {
g_prefix_error (error, "Error loading cached location from %s: ", file);
return FALSE;
}
lat = g_key_file_get_double (key_file,
LOCATION_LAST_POS_GROUP,
LOCATION_LAST_POS_LATITUDE_KEY,
error);
if (*error) {
g_prefix_error (error, "Error finding latitude in file: ");
return FALSE;
}
lon = g_key_file_get_double (key_file,
LOCATION_LAST_POS_GROUP,
LOCATION_LAST_POS_LONGITUDE_KEY,
error);
if (*error) {
g_prefix_error (error, "Error finding longitude in file: ");
return FALSE;
}
self->priv->last_latitude = lat;
self->priv->last_longitude = lon;
return TRUE;
}
gboolean
mm_location_cache_load (MMLocationCache *self,
GError **error)
{
return mm_location_cache_load_from_file (self, self->priv->filename, error);
}
gboolean
mm_location_cache_save_to_file (MMLocationCache *self,
const gchar *file,
GError **error)
{
g_autoptr(GKeyFile) key_file = g_key_file_new ();
if ((self->priv->last_latitude == MM_LOCATION_LATITUDE_UNKNOWN) ||
(self->priv->last_longitude == MM_LOCATION_LONGITUDE_UNKNOWN)) {
return TRUE;
}
key_file = g_key_file_new ();
g_key_file_set_double (key_file,
LOCATION_LAST_POS_GROUP,
LOCATION_LAST_POS_LATITUDE_KEY,
self->priv->last_latitude);
g_key_file_set_double (key_file,
LOCATION_LAST_POS_GROUP,
LOCATION_LAST_POS_LONGITUDE_KEY,
self->priv->last_longitude);
if (!g_key_file_save_to_file (key_file, file, error)) {
g_prefix_error (error, "Error saving cached location to %s", file);
return FALSE;
}
return TRUE;
}
gboolean
mm_location_cache_save (MMLocationCache *self,
GError **error)
{
return mm_location_cache_save_to_file (self, self->priv->filename, error);
}
void
mm_location_cache_set_filename (MMLocationCache *self,
const gchar *file)
{
g_free (self->priv->filename);
self->priv->filename = g_strdup (file);
}
void
mm_location_cache_update_from_lat_lon (MMLocationCache *self,
const double lat,
const double lon)
{
if (lat != MM_LOCATION_LATITUDE_UNKNOWN &&
lon != MM_LOCATION_LONGITUDE_UNKNOWN) {
self->priv->last_latitude = lat;
self->priv->last_longitude = lon;
}
}
void
mm_location_cache_update_from_nmea (MMLocationCache *self,
const gchar *trace)
{
gdouble lat;
gdouble lon;
g_assert (self->priv->gps_raw != NULL);
if (mm_location_gps_raw_add_trace (self->priv->gps_raw, trace)) {
lat = mm_location_gps_raw_get_latitude (self->priv->gps_raw);
lon = mm_location_gps_raw_get_longitude (self->priv->gps_raw);
mm_location_cache_update_from_lat_lon (self, lat, lon);
}
}
void
mm_location_cache_get_lat_lon (MMLocationCache *self,
double *lat,
double *lon)
{
*lat = self->priv->last_latitude;
*lon = self->priv->last_longitude;
}
MMLocationCache *
mm_location_cache_new (void)
{
return MM_LOCATION_CACHE (g_object_new (MM_TYPE_LOCATION_CACHE, NULL));
}
static void
mm_location_cache_init (MMLocationCache *self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_LOCATION_CACHE, MMLocationCachePrivate);
self->priv->last_longitude = MM_LOCATION_LONGITUDE_UNKNOWN;
self->priv->last_latitude = MM_LOCATION_LATITUDE_UNKNOWN;
self->priv->gps_raw = mm_location_gps_raw_new ();
self->priv->filename = g_build_path (G_DIR_SEPARATOR_S, PKGSTATEDIR, LOCATION_STATE_FILE, NULL);
}
static void
finalize (GObject *object)
{
MMLocationCache *self = MM_LOCATION_CACHE (object);
g_object_unref (self->priv->gps_raw);
g_free (self->priv->filename);
G_OBJECT_CLASS (mm_location_cache_parent_class)->finalize (object);
}
static void
dispose (GObject *object)
{
g_autoptr(GError) error = NULL;
MMLocationCache *self = MM_LOCATION_CACHE (object);
if (!mm_location_cache_save (self, &error))
mm_obj_warn (self, "%s", error->message);
G_OBJECT_CLASS (mm_location_cache_parent_class)->dispose (object);
}
static void
log_object_iface_init (MMLogObjectInterface *iface)
{
iface->build_id = log_object_build_id;
}
static void
mm_location_cache_class_init (MMLocationCacheClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (object_class, sizeof (MMLocationCachePrivate));
object_class->finalize = finalize;
object_class->dispose = dispose;
}
+57
View File
@@ -0,0 +1,57 @@
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2026 Andrey Skvortsov <andrej.skvortzov@gmail.com>
*/
#ifndef MM_LOCATION_CACHE_H
#define MM_LOCATION_CACHE_H
#include <glib.h>
#include <glib-object.h>
#define MM_TYPE_LOCATION_CACHE (mm_location_cache_get_type ())
#define MM_LOCATION_CACHE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_LOCATION_CACHE, MMLocationCache))
#define MM_LOCATION_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), MM_TYPE_LOCATION_CACHE, MMLocationCacheClass))
#define MM_IS_LOCATION_CACHE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_LOCATION_CACHE))
#define MM_IS_LOCATION_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), MM_TYPE_LOCATION_CACHE))
#define MM_LOCATION_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MM_TYPE_LOCATION_CACHE, MMLocationCacheClass))
typedef struct _MMLocationCache MMLocationCache;
typedef struct _MMLocationCacheClass MMLocationCacheClass;
typedef struct _MMLocationCachePrivate MMLocationCachePrivate;
struct _MMLocationCache {
GObject parent;
MMLocationCachePrivate *priv;
};
struct _MMLocationCacheClass {
GObjectClass parent;
};
GType mm_location_cache_get_type (void);
G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMLocationCache, g_object_unref)
MMLocationCache *mm_location_cache_new (void);
gboolean mm_location_cache_load_from_file (MMLocationCache *self, const gchar *file, GError **error);
gboolean mm_location_cache_load (MMLocationCache *self, GError **error);
gboolean mm_location_cache_save_to_file (MMLocationCache *self, const gchar *file, GError **error);
gboolean mm_location_cache_save (MMLocationCache *self, GError **error);
void mm_location_cache_set_filename (MMLocationCache *self, const gchar *file);
void mm_location_cache_update_from_lat_lon (MMLocationCache *self, const double lat, const double lon);
void mm_location_cache_update_from_nmea (MMLocationCache *self, const gchar *nmea);
void mm_location_cache_get_lat_lon (MMLocationCache *self, double *lat, double *lon);
#endif /* MM_LOCATION_CACHE_H */
+384 -6
View File
@@ -30,6 +30,7 @@
#include "mm-iface-modem.h"
#include "mm-iface-modem-3gpp.h"
#include "mm-iface-modem-location.h"
#include "mm-location-cache.h"
#include "mm-sim-qmi.h"
#include "mm-shared-qmi.h"
#include "mm-modem-helpers-qmi.h"
@@ -91,6 +92,9 @@ typedef struct {
guint32 loc_assistance_data_max_file_size;
guint32 loc_assistance_data_max_part_size;
gulong loc_assistance_inject_time_req_indication_id;
gulong loc_assistance_inject_position_req_indication_id;
/* Carrier config helpers */
gboolean config_active_default;
GArray *config_list;
@@ -102,6 +106,9 @@ typedef struct {
gulong uim_slot_status_indication_id;
gulong uim_refresh_indication_id;
guint uim_refresh_start_timeout_id;
/* Cached last location from nmea */
MMLocationCache *location_cache;
} Private;
static void
@@ -119,6 +126,10 @@ private_free (Private *priv)
g_object_unref (priv->pds_client);
if (priv->loc_location_nmea_indication_id)
g_signal_handler_disconnect (priv->loc_client, priv->loc_location_nmea_indication_id);
if (priv->loc_assistance_inject_time_req_indication_id)
g_signal_handler_disconnect (priv->loc_client, priv->loc_assistance_inject_time_req_indication_id);
if (priv->loc_assistance_inject_position_req_indication_id)
g_signal_handler_disconnect (priv->loc_client, priv->loc_assistance_inject_position_req_indication_id);
if (priv->loc_client)
g_object_unref (priv->loc_client);
if (priv->uim_slot_status_indication_id)
@@ -131,6 +142,7 @@ private_free (Private *priv)
g_object_unref (priv->uim_client);
if (priv->uim_refresh_start_timeout_id)
g_source_remove (priv->uim_refresh_start_timeout_id);
g_clear_object (&priv->location_cache);
if (priv->feature_nas_ssp_acquisition_order_preference_array)
g_array_unref (priv->feature_nas_ssp_acquisition_order_preference_array);
g_strfreev (priv->loc_assistance_data_servers);
@@ -140,6 +152,7 @@ private_free (Private *priv)
static Private *
get_private (MMSharedQmi *self)
{
g_autoptr(GError) error = NULL;
Private *priv;
if (G_UNLIKELY (!private_quark))
@@ -155,6 +168,10 @@ get_private (MMSharedQmi *self)
priv->feature_nas_ssp_acquisition_order_preference = FEATURE_UNKNOWN;
priv->config_active_i = -1;
priv->location_cache = mm_location_cache_new ();
if (!mm_location_cache_load (priv->location_cache, &error))
mm_obj_warn (priv->location_cache, "%s", error->message);
/* Setup parent class' MMIfaceModemLocation */
g_assert (MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface);
priv->iface_modem_location_parent = MM_SHARED_QMI_GET_INTERFACE (self)->peek_parent_location_interface (self);
@@ -5158,11 +5175,15 @@ stop_gps_engine (MMSharedQmi *self,
{
GTask *task;
Private *priv;
g_autoptr(GError) error = NULL;
priv = get_private (self);
task = g_task_new (self, NULL, callback, user_data);
if (!mm_location_cache_save (priv->location_cache, &error))
mm_obj_warn (self, "%s", error->message);
if (priv->pds_client) {
QmiMessagePdsSetGpsServiceStateInput *input;
@@ -5209,7 +5230,9 @@ pds_location_event_report_indication_cb (QmiClientPds *clie
{
QmiPdsPositionSessionStatus session_status;
const gchar *nmea;
Private *priv;
priv = get_private (MM_SHARED_QMI (self));
if (qmi_indication_pds_event_report_output_get_position_session_status (
output,
&session_status,
@@ -5224,6 +5247,7 @@ pds_location_event_report_indication_cb (QmiClientPds *clie
NULL)) {
mm_obj_dbg (self, "[NMEA] %s", nmea);
mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea);
mm_location_cache_update_from_nmea (priv->location_cache, nmea);
}
}
@@ -5233,13 +5257,16 @@ loc_location_nmea_indication_cb (QmiClientLoc *client,
MMSharedQmi *self)
{
const gchar *nmea = NULL;
Private *priv;
priv = get_private (MM_SHARED_QMI (self));
qmi_indication_loc_nmea_output_get_nmea_string (output, &nmea, NULL);
if (!nmea)
return;
mm_obj_dbg (self, "[NMEA] %s", nmea);
mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), nmea);
mm_location_cache_update_from_nmea (priv->location_cache, nmea);
}
/*****************************************************************************/
@@ -5670,6 +5697,7 @@ loc_start_ready (QmiClientLoc *client,
{
QmiMessageLocRegisterEventsInput *input;
QmiMessageLocStartOutput *output;
guint64 event_mask;
GError *error = NULL;
output = qmi_client_loc_start_finish (client, res, &error);
@@ -5691,8 +5719,11 @@ loc_start_ready (QmiClientLoc *client,
qmi_message_loc_start_output_unref (output);
input = qmi_message_loc_register_events_input_new ();
event_mask = QMI_LOC_EVENT_REGISTRATION_FLAG_NMEA |
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_TIME_REQUEST |
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_POSITION_REQUEST;
qmi_message_loc_register_events_input_set_event_registration_mask (
input, QMI_LOC_EVENT_REGISTRATION_FLAG_NMEA, NULL);
input, event_mask, NULL);
qmi_client_loc_register_events (client,
input,
10,
@@ -6714,6 +6745,338 @@ loc_location_get_predicted_orbits_data_source_indication_timed_out (GTask *task)
return G_SOURCE_REMOVE;
}
/*****************************************************************************/
/* Location: Inject UTC time */
typedef struct {
guint timeout_id;
gulong indication_id;
QmiClientLoc *client;
} InjectTimeContext;
static void
inject_time_context_cleanup_action (InjectTimeContext *ctx)
{
if (ctx->timeout_id) {
g_source_remove (ctx->timeout_id);
ctx->timeout_id = 0;
}
if (ctx->indication_id) {
if (ctx->client)
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
ctx->indication_id = 0;
}
}
static void
inject_time_context_free (InjectTimeContext *ctx)
{
inject_time_context_cleanup_action (ctx);
g_clear_object (&ctx->client);
g_slice_free (InjectTimeContext, ctx);
}
static void
inject_time_finish_task (GTask *_task,
GError *error)
{
InjectTimeContext *ctx;
g_autoptr(GTask) task = _task;
ctx = g_task_get_task_data (task);
inject_time_context_cleanup_action (ctx);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
}
static gboolean
loc_location_inject_time_indication_timed_out (GTask *task)
{
inject_time_finish_task (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
"Operation timed out"));
return G_SOURCE_REMOVE;
}
static void
loc_location_inject_time_indication_cb (QmiClientLoc *client,
QmiIndicationLocInjectUtcTimeOutput *output,
GTask *task)
{
QmiLocIndicationStatus status;
GError *error = NULL;
if (!qmi_indication_loc_inject_utc_time_output_get_indication_status (output, &status, &error))
g_prefix_error (&error, "QMI operation failed: ");
else
mm_error_from_qmi_loc_indication_status (status, &error);
inject_time_finish_task (task, error);
}
static void
loc_location_inject_time_ready (QmiClientLoc *client,
GAsyncResult *res,
GTask *task)
{
g_autoptr(QmiMessageLocInjectUtcTimeOutput) output = NULL;
GError *error = NULL;
output = qmi_client_loc_inject_utc_time_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
inject_time_finish_task (task, error);
return;
}
if (!qmi_message_loc_inject_utc_time_output_get_result (output, &error)) {
inject_time_finish_task (task, error);
return;
}
}
static void
loc_location_inject_time_req_indication_cb (QmiClientLoc *client,
QmiIndicationLocInjectTimeRequestOutput *output,
MMSharedQmi *self)
{
g_autoptr(QmiMessageLocInjectUtcTimeInput) input = NULL;
guint64 utc_ms = 0;
GTask *task;
InjectTimeContext *ctx;
task = g_task_new (self, NULL, NULL, NULL);
ctx = g_slice_new0 (InjectTimeContext);
g_task_set_task_data (task, ctx, (GDestroyNotify)inject_time_context_free);
ctx->client = g_object_ref (client);
input = qmi_message_loc_inject_utc_time_input_new ();
utc_ms = g_get_real_time () / 1000;
qmi_message_loc_inject_utc_time_input_set_utc_time (input, utc_ms, NULL);
qmi_message_loc_inject_utc_time_input_set_time_uncertainty (input, 1000, NULL);
qmi_message_loc_inject_utc_time_input_set_time_source (input, QMI_LOC_INJECTED_TIME_SOURCE_UNKNOWN, NULL);
ctx->timeout_id = g_timeout_add_seconds (10,
(GSourceFunc) loc_location_inject_time_indication_timed_out,
task);
ctx->indication_id = g_signal_connect (client,
"inject-utc-time",
G_CALLBACK (loc_location_inject_time_indication_cb),
task);
qmi_client_loc_inject_utc_time (
QMI_CLIENT_LOC (ctx->client),
input,
10,
NULL, /* cancellable */
(GAsyncReadyCallback)loc_location_inject_time_ready,
task);
}
/*****************************************************************************/
/* Location: Inject position */
typedef struct {
guint timeout_id;
gulong indication_id;
QmiClientLoc *client;
} InjectPositionContext;
static void
inject_position_context_cleanup_action (InjectPositionContext *ctx)
{
if (ctx->timeout_id) {
g_source_remove (ctx->timeout_id);
ctx->timeout_id = 0;
}
if (ctx->indication_id) {
if (ctx->client)
g_signal_handler_disconnect (ctx->client, ctx->indication_id);
ctx->indication_id = 0;
}
}
static void
inject_position_context_free (InjectPositionContext *ctx)
{
inject_position_context_cleanup_action (ctx);
g_clear_object (&ctx->client);
g_slice_free (InjectPositionContext, ctx);
}
static void
inject_position_finish_task (GTask *_task,
GError *error)
{
InjectPositionContext *ctx;
g_autoptr(GTask) task = _task;
ctx = g_task_get_task_data (task);
inject_position_context_cleanup_action (ctx);
if (error)
g_task_return_error (task, error);
else
g_task_return_boolean (task, TRUE);
}
static gboolean
loc_location_inject_position_indication_timed_out (GTask *task)
{
inject_position_finish_task (task, g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_ABORTED,
"Operation timed out"));
return G_SOURCE_REMOVE;
}
static void
loc_location_inject_position_indication_cb (QmiClientLoc *client,
QmiIndicationLocInjectPositionOutput *output,
GTask *task)
{
QmiLocIndicationStatus status;
GError *error = NULL;
if (!qmi_indication_loc_inject_position_output_get_indication_status (output, &status, &error))
g_prefix_error (&error, "QMI operation failed: ");
else
mm_error_from_qmi_loc_indication_status (status, &error);
inject_position_finish_task (task, error);
}
static void
loc_location_inject_position_ready (QmiClientLoc *client,
GAsyncResult *res,
GTask *task)
{
g_autoptr(QmiMessageLocInjectPositionOutput) output = NULL;
GError *error = NULL;
output = qmi_client_loc_inject_position_finish (client, res, &error);
if (!output) {
g_prefix_error (&error, "QMI operation failed: ");
inject_position_finish_task (task, error);
return;
}
if (!qmi_message_loc_inject_position_output_get_result (output, &error)) {
inject_position_finish_task (task, error);
return;
}
}
static void
loc_location_inject_position_req_indication_cb (QmiClientLoc *client,
QmiIndicationLocInjectPositionRequestOutput *output,
MMSharedQmi *self)
{
g_autoptr(QmiMessageLocInjectPositionInput) input = NULL;
InjectPositionContext *ctx;
Private *priv;
GTask *task;
gdouble lat;
gdouble lon;
guint64 utc_ms = 0;
gdouble last_lat;
gdouble last_lon;
guint64 timestamp;
qmi_indication_loc_inject_position_request_output_get_latitude (output, &lat, NULL);
qmi_indication_loc_inject_position_request_output_get_longitude (output, &lon, NULL);
qmi_indication_loc_inject_position_request_output_get_utc_timestamp (output, &timestamp, NULL);
/* check whether modem already has position */
if ((lat != 0.0) && (lon != 0.0) && (timestamp != 0))
return;
priv = get_private (MM_SHARED_QMI (self));
mm_location_cache_get_lat_lon (priv->location_cache, &last_lat, &last_lon);
if ((last_lat == MM_LOCATION_LATITUDE_UNKNOWN) ||
(last_lon == MM_LOCATION_LONGITUDE_UNKNOWN))
return;
task = g_task_new (self, NULL, NULL, NULL);
ctx = g_slice_new0 (InjectPositionContext);
g_task_set_task_data (task, ctx, (GDestroyNotify)inject_position_context_free);
ctx->client = g_object_ref (client);
input = qmi_message_loc_inject_position_input_new ();
qmi_message_loc_inject_position_input_set_latitude (input, last_lat, NULL);
qmi_message_loc_inject_position_input_set_longitude (input, last_lon, NULL);
qmi_message_loc_inject_position_input_set_position_source (input, QMI_LOC_POSITION_SOURCE_OTHER, NULL);
// it's not recommended to advertise accuracy better than 1000 meters to the modem
qmi_message_loc_inject_position_input_set_horizontal_uncertainty_circular (input, 1000, NULL);
qmi_message_loc_inject_position_input_set_horizontal_confidence (input, 68, NULL);
utc_ms = g_get_real_time () / 1000;
qmi_message_loc_inject_position_input_set_utc_timestamp (input, utc_ms, NULL);
ctx->timeout_id = g_timeout_add_seconds (10,
(GSourceFunc) loc_location_inject_position_indication_timed_out,
task);
ctx->indication_id = g_signal_connect (client,
"inject-position",
G_CALLBACK (loc_location_inject_position_indication_cb),
task);
qmi_client_loc_inject_position (
QMI_CLIENT_LOC (ctx->client),
input,
10,
NULL, /* cancellable */
(GAsyncReadyCallback)loc_location_inject_position_ready,
task);
}
static void
loc_register_event_inject_req_ready (QmiClientLoc *client,
GAsyncResult *res,
GTask *task)
{
g_autoptr(QmiMessageLocRegisterEventsOutput) output = NULL;
g_autoptr(GError) error = NULL;
MMSharedQmi *self;
Private *priv;
output = qmi_client_loc_register_events_finish (client, res, &error);
if (!output) {
mm_obj_warn (client, "QMI operation failed: %s", error->message);
goto out;
}
if (!qmi_message_loc_register_events_output_get_result (output, &error)) {
mm_obj_warn (client, "Couldn't not register tracking events: %s", error->message);
goto out;
}
self = g_task_get_source_object (task);
priv = get_private (self);
g_assert (!priv->loc_assistance_inject_time_req_indication_id);
priv->loc_assistance_inject_time_req_indication_id =
g_signal_connect (client,
"inject-time-request",
G_CALLBACK (loc_location_inject_time_req_indication_cb),
self);
g_assert (!priv->loc_assistance_inject_position_req_indication_id);
priv->loc_assistance_inject_position_req_indication_id =
g_signal_connect (client,
"inject-position-request",
G_CALLBACK (loc_location_inject_position_req_indication_cb),
self);
out:
g_task_return_int (task, MM_MODEM_LOCATION_ASSISTANCE_DATA_TYPE_XTRA);
g_object_unref (task);
}
static void
loc_location_get_predicted_orbits_data_source_indication_cb (QmiClientLoc *client,
QmiIndicationLocGetPredictedOrbitsDataSourceOutput *output,
@@ -6772,13 +7135,28 @@ loc_location_get_predicted_orbits_data_source_indication_cb (QmiClientLoc
}
out:
if (error)
if (error) {
g_task_return_error (task, error);
else if (!supported)
g_task_return_int (task, MM_MODEM_LOCATION_ASSISTANCE_DATA_TYPE_NONE);
else
g_task_return_int (task, MM_MODEM_LOCATION_ASSISTANCE_DATA_TYPE_XTRA);
g_object_unref (task);
} else if (!supported) {
g_task_return_int (task, MM_MODEM_LOCATION_ASSISTANCE_DATA_TYPE_NONE);
g_object_unref (task);
} else {
g_autoptr(QmiMessageLocRegisterEventsInput) input = NULL;
guint64 event_mask;
event_mask = QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_TIME_REQUEST |
QMI_LOC_EVENT_REGISTRATION_FLAG_INJECT_POSITION_REQUEST;
input = qmi_message_loc_register_events_input_new ();
qmi_message_loc_register_events_input_set_event_registration_mask (
input, event_mask, NULL);
qmi_client_loc_register_events (client,
input,
10,
NULL,
(GAsyncReadyCallback) loc_register_event_inject_req_ready,
task);
}
}
static void
+1
View File
@@ -10,6 +10,7 @@ test_units = {
'charsets': libhelpers_dep,
'error-helpers': libhelpers_dep,
'kernel-device-helpers': libkerneldevice_dep,
'location-cache': libhelpers_dep,
'modem-helpers': libhelpers_dep,
'port-scheduler': libport_dep,
'sms-part-3gpp': libhelpers_dep,
+453
View File
@@ -0,0 +1,453 @@
/*****************************************************************************/
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details:
*
* Copyright (C) 2026 Andrey Skvortsov <andrej.skvortzov@gmail.com>
*/
#include <config.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <locale.h>
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
#include "mm-log-test.h"
#include "mm-location-cache.h"
/*****************************************************************************/
static const gdouble reset_lat = 0.1;
static const gdouble reset_lon = 0.2;
typedef struct {
const gchar *file_content;
const gdouble expect_lat;
const gdouble expect_lon;
const gboolean expect_success;
} LoadTestcase;
static const LoadTestcase load_testcases[] = {
{
.file_content = "[last_position]\n"
"latitude=48.137\n"
"longitude=11.575\n",
.expect_lat = 48.137,
.expect_lon = 11.575,
.expect_success = TRUE,
},
{
.file_content = "[last_position]\n"
"longitude=11.575\n"
"latitude=48.137\n",
.expect_lat = 48.137,
.expect_lon = 11.575,
.expect_success = TRUE,
},
{
.file_content = "[last_position]\n"
"latitude=-27.142\n"
"longitude=-109.314\n",
.expect_lat = -27.142,
.expect_lon = -109.314,
.expect_success = TRUE,
},
{
.file_content = "[last_position]\n"
"latitude=48.137\n",
.expect_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.expect_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_success = FALSE,
},
{
.file_content = "[last_position]\n"
"longitude=11.575\n",
.expect_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.expect_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_success = FALSE,
},
{
.file_content = "[last_position]\n",
.expect_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.expect_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_success = FALSE,
},
{
.file_content = "To be, or not to be, that is the question...\n",
.expect_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.expect_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_success = FALSE,
},
};
static void
test_location_cache_load (void)
{
g_autoptr(MMLocationCache) cache = NULL;
gdouble get_lat;
gdouble get_lon;
g_autofree gchar *filename = NULL;
g_autoptr(GFile) loc = NULL;
g_autoptr(GIOStream) stream = NULL;
g_autoptr(GError) err = NULL;
gulong i;
gboolean ret;
cache = mm_location_cache_new ();
g_assert_nonnull (cache);
loc = g_file_new_tmp (NULL, (GFileIOStream **)&stream, &err);
filename = g_file_get_path (loc);
g_assert_no_error (err);
g_assert_nonnull (filename);
mm_location_cache_set_filename (cache, filename);
for (i = 0; i < G_N_ELEMENTS (load_testcases); i++) {
g_autoptr(GError) error = NULL;
LoadTestcase test = load_testcases[i];
g_debug ("load test %ld", i);
g_file_replace_contents (loc,
test.file_content,
strlen (test.file_content),
NULL, /* etag */
FALSE, /* make backup */
G_FILE_CREATE_NONE, /* flags */
NULL, /* new etag */
NULL, /* cancellable */
&error);
g_assert_no_error (error);
/* reset coordinates */
mm_location_cache_update_from_lat_lon (cache, reset_lat, reset_lon);
ret = mm_location_cache_load_from_file (cache, filename, &error);
g_assert (ret == test.expect_success);
g_assert (test.expect_success ? error == NULL : error != NULL);
mm_location_cache_get_lat_lon (cache, &get_lat, &get_lon);
g_assert_cmpfloat_with_epsilon (test.expect_lat, get_lat, 0.01);
g_assert_cmpfloat_with_epsilon (test.expect_lon, get_lon, 0.01);
}
/* try to load missing file */
g_file_delete (loc, NULL, NULL);
mm_location_cache_update_from_lat_lon (cache, reset_lat, reset_lon);
ret = mm_location_cache_load_from_file (cache, filename, &err);
g_assert (err != NULL);
mm_location_cache_get_lat_lon (cache, &get_lat, &get_lon);
g_assert_cmpfloat_with_epsilon (MM_LOCATION_LATITUDE_UNKNOWN, get_lat, 0.01);
g_assert_cmpfloat_with_epsilon (MM_LOCATION_LONGITUDE_UNKNOWN, get_lon, 0.01);
}
/*****************************************************************************/
typedef struct {
const gchar *nmea;
const gdouble set_lat;
const gdouble set_lon;
const gdouble expect_lat;
const gdouble expect_lon;
} UpdateTestcase;
static const UpdateTestcase update_testcases[] = {
{
.nmea = NULL,
.set_lat = 48.137,
.set_lon = 11.575,
.expect_lat = 48.137,
.expect_lon = 11.575,
},
{
.nmea = NULL,
.set_lat = 48.137,
.set_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_lat = reset_lat,
.expect_lon = reset_lon,
},
{
.nmea = NULL,
.set_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.set_lon = 11.575,
.expect_lat = reset_lat,
.expect_lon = reset_lon,
},
/* only GGA NMEA sentences are supported by current implementation */
{
.nmea = "$GNGLL,4808.2200,N,01134.5000,E,012345.000,A,A*40",
.set_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.set_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_lat = reset_lat,
.expect_lon = reset_lon,
},
{
.nmea = "$GNRMC,012345.000,A,4808.2200,N,01134.5000,E,1.0,300.5,100817,3.6,E,A,V*6B",
.set_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.set_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_lat = reset_lat,
.expect_lon = reset_lon,
},
{
.nmea = "$GNGGA,012345.000,4808.2200,N,01134.5000,E,1,12,1.1,-14.1,M,32.0,M,,*5E",
.set_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.set_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_lat = 48.137,
.expect_lon = 11.575,
},
{
.nmea = NULL,
.set_lat = -27.142,
.set_lon = -109.314,
.expect_lat = -27.142,
.expect_lon = -109.314,
},
{
.nmea = "$GNGLL,2708.5200,S,10918.8400,W,012345.000,A,A*4E",
.set_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.set_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_lat = reset_lat,
.expect_lon = reset_lon,
},
{
.nmea = "$GNRMC,012345.000,A,2708.5200,S,10918.8400,W,1.0,300.5,100817,3.6,E,A,V*65",
.set_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.set_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_lat = reset_lat,
.expect_lon = reset_lon,
},
{
.nmea = "$GNGGA,012345.000,2708.5200,S,10918.8400,W,1,12,1.1,-14.1,M,32.0,M,,*50",
.set_lat = MM_LOCATION_LATITUDE_UNKNOWN,
.set_lon = MM_LOCATION_LONGITUDE_UNKNOWN,
.expect_lat = -27.142,
.expect_lon = -109.314,
},
};
static void
test_location_cache_update (void)
{
g_autoptr(MMLocationCache) cache = NULL;
gulong i;
cache = mm_location_cache_new ();
g_assert_nonnull (cache);
for (i = 0; i < G_N_ELEMENTS (update_testcases); i++) {
gdouble get_lat = 0;
gdouble get_lon = 0;
UpdateTestcase test = update_testcases[i];
g_debug ("update test %ld", i);
/* reset coordinates */
mm_location_cache_update_from_lat_lon (cache, reset_lat, reset_lon);
if (test.nmea)
mm_location_cache_update_from_nmea (cache, test.nmea);
else
mm_location_cache_update_from_lat_lon (cache, test.set_lat, test.set_lon);
mm_location_cache_get_lat_lon (cache, &get_lat, &get_lon);
g_assert_cmpfloat_with_epsilon (test.expect_lat, get_lat, 0.01);
g_assert_cmpfloat_with_epsilon (test.expect_lon, get_lon, 0.01);
}
}
/*****************************************************************************/
static gchar *
get_temp_filename (void)
{
g_autoptr(GError) error = NULL;
gchar *filename;
gint fd;
fd = g_file_open_tmp (NULL, &filename, &error);
g_assert_no_error (error);
g_assert_nonnull (filename);
g_close (fd, &error);
g_assert_no_error (error);
g_unlink (filename);
return filename;
}
static void
test_location_cache_save (void)
{
g_autoptr(MMLocationCache) cache = NULL;
g_autoptr(GError) error = NULL;
g_autofree gchar *filename = NULL;
g_autofree gchar *path = NULL;
gboolean ret;
cache = mm_location_cache_new ();
g_assert_nonnull (cache);
filename = get_temp_filename ();
// write empty cache
g_unlink (filename);
ret = mm_location_cache_save_to_file (cache, filename, &error);
g_assert_true (ret);
g_assert_no_error (error);
ret = g_file_test (filename, G_FILE_TEST_EXISTS);
g_assert_false (ret);
// write non-empty cache
g_unlink (filename);
mm_location_cache_update_from_lat_lon (cache, reset_lat, reset_lon);
ret = mm_location_cache_save_to_file (cache, filename, &error);
g_assert_true (ret);
g_assert_no_error (error);
ret = g_file_test (filename, G_FILE_TEST_EXISTS);
g_assert_true (ret);
g_unlink (filename);
// write to non-existing path
path = g_build_path (G_DIR_SEPARATOR_S, "missing", "X", "Y", "Z", "location.ini", NULL);
ret = mm_location_cache_save_to_file (cache, path, &error);
g_assert_false (ret);
g_assert (error != NULL);
}
/*****************************************************************************/
typedef struct {
const gdouble set_lat;
const gdouble set_lon;
} SaveLoadTestcase;
static const SaveLoadTestcase save_load_testcases[] = {
{
.set_lat = 48.137,
.set_lon = 11.575,
},
{
.set_lat = -27.142,
.set_lon = -109.314,
},
};
static void
test_location_cache_save_load (void)
{
g_autoptr(MMLocationCache) cache = NULL;
g_autoptr(GError) error = NULL;
g_autofree gchar *filename = NULL;
gboolean ret;
gulong i;
cache = mm_location_cache_new ();
g_assert_nonnull (cache);
filename = get_temp_filename ();
mm_location_cache_set_filename (cache, filename);
for (i = 0; i < G_N_ELEMENTS (save_load_testcases); i++) {
gdouble get_lat = 0;
gdouble get_lon = 0;
SaveLoadTestcase test = save_load_testcases[i];
g_debug ("save/load test %ld", i);
mm_location_cache_update_from_lat_lon (cache, test.set_lat, test.set_lon);
g_unlink (filename);
ret = mm_location_cache_save (cache, &error);
g_assert_true (ret);
g_assert_no_error (error);
ret = g_file_test (filename, G_FILE_TEST_EXISTS);
g_assert_true (ret);
mm_location_cache_update_from_lat_lon (cache, reset_lat, reset_lon);
ret = mm_location_cache_load (cache, &error);
g_assert_true (ret);
g_assert_no_error (error);
mm_location_cache_get_lat_lon (cache, &get_lat, &get_lon);
g_assert_cmpfloat_with_epsilon (test.set_lat, get_lat, 0.01);
g_assert_cmpfloat_with_epsilon (test.set_lon, get_lon, 0.01);
}
g_unlink (filename);
}
/*****************************************************************************/
static void
test_location_cache_create_dispose (void)
{
MMLocationCache *cache = NULL;
g_autoptr(GError) error = NULL;
g_autofree gchar *filename = NULL;
gboolean ret;
gdouble get_lat = 0;
gdouble get_lon = 0;
cache = mm_location_cache_new ();
g_assert_nonnull (cache);
// to check later, that MMLocationCache object is finalized
g_object_add_weak_pointer (G_OBJECT (cache), (gpointer *) &cache);
filename = get_temp_filename ();
mm_location_cache_set_filename (cache, filename);
mm_location_cache_update_from_lat_lon (cache, reset_lat, reset_lon);
// drop the last reference and save cached location
g_object_unref (cache);
g_assert_null (cache);
// check whether file is created
ret = g_file_test (filename, G_FILE_TEST_EXISTS);
g_assert_true (ret);
// check file content
cache = mm_location_cache_new ();
g_assert_nonnull (cache);
mm_location_cache_set_filename (cache, filename);
ret = mm_location_cache_load (cache, &error);
g_assert_true (ret);
g_assert_no_error (error);
mm_location_cache_get_lat_lon (cache, &get_lat, &get_lon);
g_assert_cmpfloat_with_epsilon (reset_lat, get_lat, 0.01);
g_assert_cmpfloat_with_epsilon (reset_lon, get_lon, 0.01);
g_object_unref (cache);
g_unlink (filename);
}
/*****************************************************************************/
int main (int argc, char **argv)
{
setlocale (LC_ALL, "");
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/MM/location-cache/update", test_location_cache_update);
g_test_add_func ("/MM/location-cache/load", test_location_cache_load);
g_test_add_func ("/MM/location-cache/save", test_location_cache_save);
g_test_add_func ("/MM/location-cache/save_load", test_location_cache_save_load);
g_test_add_func ("/MM/location-cache/create_dispose", test_location_cache_create_dispose);
return g_test_run ();
}