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:
+13
-11
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
|
||||
+387
-9
@@ -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,7 +152,8 @@ private_free (Private *priv)
|
||||
static Private *
|
||||
get_private (MMSharedQmi *self)
|
||||
{
|
||||
Private *priv;
|
||||
g_autoptr(GError) error = NULL;
|
||||
Private *priv;
|
||||
|
||||
if (G_UNLIKELY (!private_quark))
|
||||
private_quark = g_quark_from_static_string (PRIVATE_TAG);
|
||||
@@ -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);
|
||||
@@ -5156,13 +5173,17 @@ stop_gps_engine (MMSharedQmi *self,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task;
|
||||
Private *priv;
|
||||
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, ×tamp, 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_object_unref (task);
|
||||
} 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);
|
||||
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
Reference in New Issue
Block a user