aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--plugins/xmm/mm-broadband-modem-mbim-xmm.c39
-rw-r--r--plugins/xmm/mm-broadband-modem-xmm.c37
-rw-r--r--plugins/xmm/mm-modem-helpers-xmm.c122
-rw-r--r--plugins/xmm/mm-modem-helpers-xmm.h8
-rw-r--r--plugins/xmm/mm-shared-xmm.c638
-rw-r--r--plugins/xmm/mm-shared-xmm.h33
-rw-r--r--plugins/xmm/tests/test-modem-helpers-xmm.c57
7 files changed, 932 insertions, 2 deletions
diff --git a/plugins/xmm/mm-broadband-modem-mbim-xmm.c b/plugins/xmm/mm-broadband-modem-mbim-xmm.c
index 1f903c69..9001d401 100644
--- a/plugins/xmm/mm-broadband-modem-mbim-xmm.c
+++ b/plugins/xmm/mm-broadband-modem-mbim-xmm.c
@@ -24,14 +24,19 @@
#include "ModemManager.h"
#include "mm-log.h"
#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
#include "mm-broadband-modem-mbim-xmm.h"
#include "mm-shared-xmm.h"
-static void iface_modem_init (MMIfaceModem *iface);
-static void shared_xmm_init (MMSharedXmm *iface);
+static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+static void shared_xmm_init (MMSharedXmm *iface);
+
+static MMIfaceModemLocation *iface_modem_location_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimXmm, mm_broadband_modem_mbim_xmm, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init))
/*****************************************************************************/
@@ -82,11 +87,41 @@ iface_modem_init (MMIfaceModem *iface)
}
static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_xmm_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish;
+}
+
+static MMBroadbandModemClass *
+peek_parent_broadband_modem_class (MMSharedXmm *self)
+{
+ return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_mbim_xmm_parent_class);
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedXmm *self)
+{
+ return iface_modem_location_parent;
+}
+
+static void
shared_xmm_init (MMSharedXmm *iface)
{
+ iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
}
static void
mm_broadband_modem_mbim_xmm_class_init (MMBroadbandModemMbimXmmClass *klass)
{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = mm_shared_xmm_setup_ports;
}
diff --git a/plugins/xmm/mm-broadband-modem-xmm.c b/plugins/xmm/mm-broadband-modem-xmm.c
index 87625bfe..6f5d3831 100644
--- a/plugins/xmm/mm-broadband-modem-xmm.c
+++ b/plugins/xmm/mm-broadband-modem-xmm.c
@@ -24,16 +24,22 @@
#include "ModemManager.h"
#include "mm-log.h"
#include "mm-iface-modem.h"
+#include "mm-iface-modem-location.h"
#include "mm-broadband-modem-xmm.h"
#include "mm-shared-xmm.h"
+
static void iface_modem_init (MMIfaceModem *iface);
static void shared_xmm_init (MMSharedXmm *iface);
static void iface_modem_signal_init (MMIfaceModemSignal *iface);
+static void iface_modem_location_init (MMIfaceModemLocation *iface);
+
+static MMIfaceModemLocation *iface_modem_location_parent;
G_DEFINE_TYPE_EXTENDED (MMBroadbandModemXmm, mm_broadband_modem_xmm, MM_TYPE_BROADBAND_MODEM, 0,
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_SIGNAL, iface_modem_signal_init)
+ G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init)
G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_XMM, shared_xmm_init))
/*****************************************************************************/
@@ -88,6 +94,32 @@ iface_modem_init (MMIfaceModem *iface)
iface->reset_finish = mm_shared_xmm_reset_finish;
}
+
+static void
+iface_modem_location_init (MMIfaceModemLocation *iface)
+{
+ iface_modem_location_parent = g_type_interface_peek_parent (iface);
+
+ iface->load_capabilities = mm_shared_xmm_location_load_capabilities;
+ iface->load_capabilities_finish = mm_shared_xmm_location_load_capabilities_finish;
+ iface->enable_location_gathering = mm_shared_xmm_enable_location_gathering;
+ iface->enable_location_gathering_finish = mm_shared_xmm_enable_location_gathering_finish;
+ iface->disable_location_gathering = mm_shared_xmm_disable_location_gathering;
+ iface->disable_location_gathering_finish = mm_shared_xmm_disable_location_gathering_finish;
+}
+
+static MMBroadbandModemClass *
+peek_parent_broadband_modem_class (MMSharedXmm *self)
+{
+ return MM_BROADBAND_MODEM_CLASS (mm_broadband_modem_xmm_parent_class);
+}
+
+static MMIfaceModemLocation *
+peek_parent_location_interface (MMSharedXmm *self)
+{
+ return iface_modem_location_parent;
+}
+
static void
iface_modem_signal_init (MMIfaceModemSignal *iface)
{
@@ -100,9 +132,14 @@ iface_modem_signal_init (MMIfaceModemSignal *iface)
static void
shared_xmm_init (MMSharedXmm *iface)
{
+ iface->peek_parent_broadband_modem_class = peek_parent_broadband_modem_class;
+ iface->peek_parent_location_interface = peek_parent_location_interface;
}
static void
mm_broadband_modem_xmm_class_init (MMBroadbandModemXmmClass *klass)
{
+ MMBroadbandModemClass *broadband_modem_class = MM_BROADBAND_MODEM_CLASS (klass);
+
+ broadband_modem_class->setup_ports = mm_shared_xmm_setup_ports;
}
diff --git a/plugins/xmm/mm-modem-helpers-xmm.c b/plugins/xmm/mm-modem-helpers-xmm.c
index 1ef8f384..bcc24f6c 100644
--- a/plugins/xmm/mm-modem-helpers-xmm.c
+++ b/plugins/xmm/mm-modem-helpers-xmm.c
@@ -808,3 +808,125 @@ mm_xmm_xcesq_response_to_signal_info (const gchar *response,
return TRUE;
}
+
+/*****************************************************************************/
+/* AT+XLCSLSR=? response parser */
+
+static gboolean
+number_group_contains_value (const gchar *group,
+ const gchar *group_name,
+ guint value,
+ GError **error)
+{
+ GArray *aux;
+ guint i;
+ gboolean found;
+
+ aux = mm_parse_uint_list (group, NULL);
+ if (!aux) {
+ g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unsupported +XLCSLSR format: invalid %s field format", group_name);
+ return FALSE;
+ }
+
+ found = FALSE;
+ for (i = 0; i < aux->len; i++) {
+ guint value_i;
+
+ value_i = g_array_index (aux, guint, i);
+ if (value == value_i) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ g_array_unref (aux);
+ return found;
+}
+
+gboolean
+mm_xmm_parse_xlcslsr_test_response (const gchar *response,
+ gboolean *transport_protocol_invalid_supported,
+ gboolean *standalone_position_mode_supported,
+ gboolean *loc_response_type_nmea_supported,
+ gboolean *gnss_type_gps_glonass_supported,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gchar **groups = NULL;
+ GError *inner_error = NULL;
+
+ /*
+ * AT+XLCSLSR=?
+ * +XLCSLSR:(0-2),(0-3), ,(0,1), ,(0,1),(0 -7200),(0-255),(0-1),(0-2),(1-256),(0,1)
+ * transport_protocol: 2 (invalid)
+ * pos_mode: 3 (standalone)
+ * client_id: <empty>
+ * client_id_type: <empty>
+ * mlc_number: <empty>
+ * mlc_number_type: <empty>
+ * interval: 1 (seconds)
+ * service_type_id: <empty>
+ * pseudonym_indicator: <empty>
+ * loc_response_type: 1 (NMEA strings)
+ * nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG)
+ * gnss_type: 0 (GPS or GLONASS)
+ */
+ response = mm_strip_tag (response, "+XLCSLSR:");
+ groups = mm_split_string_groups (response);
+
+ /* We expect 12 groups */
+ if (g_strv_length (groups) < 12) {
+ inner_error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+ "Unsupported +XLCSLSR format: expected 12 fields");
+ goto out;
+ }
+
+ if (transport_protocol_invalid_supported) {
+ *transport_protocol_invalid_supported = number_group_contains_value (groups[0],
+ "transport protocol",
+ 2, /* invalid */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (standalone_position_mode_supported) {
+ *standalone_position_mode_supported = number_group_contains_value (groups[1],
+ "position mode",
+ 3, /* standalone */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (loc_response_type_nmea_supported) {
+ *loc_response_type_nmea_supported = number_group_contains_value (groups[9],
+ "location response type",
+ 1, /* NMEA */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ if (gnss_type_gps_glonass_supported) {
+ *gnss_type_gps_glonass_supported = number_group_contains_value (groups[11],
+ "gnss type",
+ 0, /* GPS/GLONASS */
+ &inner_error);
+ if (inner_error)
+ goto out;
+ }
+
+ ret = TRUE;
+
+ out:
+ g_strfreev (groups);
+
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return FALSE;
+ }
+
+ return ret;
+}
diff --git a/plugins/xmm/mm-modem-helpers-xmm.h b/plugins/xmm/mm-modem-helpers-xmm.h
index 1d65936e..9c77b14a 100644
--- a/plugins/xmm/mm-modem-helpers-xmm.h
+++ b/plugins/xmm/mm-modem-helpers-xmm.h
@@ -55,4 +55,12 @@ gboolean mm_xmm_xcesq_response_to_signal_info (const gchar *response,
MMSignal **out_lte,
GError **error);
+/* AT+XLCSLSR=? response parser */
+gboolean mm_xmm_parse_xlcslsr_test_response (const gchar *response,
+ gboolean *transport_protocol_invalid_supported,
+ gboolean *standalone_position_mode_supported,
+ gboolean *loc_response_type_nmea_supported,
+ gboolean *gnss_type_gps_glonass_supported,
+ GError **error);
+
#endif /* MM_MODEM_HELPERS_XMM_H */
diff --git a/plugins/xmm/mm-shared-xmm.c b/plugins/xmm/mm-shared-xmm.c
index 389889c6..a1d59a15 100644
--- a/plugins/xmm/mm-shared-xmm.c
+++ b/plugins/xmm/mm-shared-xmm.c
@@ -24,6 +24,7 @@
#include "mm-log.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-signal.h"
+#include "mm-iface-modem-location.h"
#include "mm-base-modem.h"
#include "mm-base-modem-at.h"
#include "mm-shared-xmm.h"
@@ -35,19 +36,40 @@
#define PRIVATE_TAG "shared-xmm-private-tag"
static GQuark private_quark;
+typedef enum {
+ GPS_ENGINE_STATE_OFF,
+ GPS_ENGINE_STATE_STANDALONE,
+} GpsEngineState;
+
typedef struct {
+ /* Broadband modem class support */
+ MMBroadbandModemClass *broadband_modem_class_parent;
+
+ /* Modem interface support */
GArray *supported_modes;
GArray *supported_bands;
MMModemMode allowed_modes;
+
+ /* Location interface support */
+ MMIfaceModemLocation *iface_modem_location_parent;
+ MMModemLocationSource supported_sources;
+ MMModemLocationSource enabled_sources;
+ GpsEngineState gps_engine_state;
+ MMPortSerialAt *gps_port;
+ GRegex *xlsrstop_regex;
+ GRegex *nmea_regex;
} Private;
static void
private_free (Private *priv)
{
+ g_clear_object (&priv->gps_port);
if (priv->supported_modes)
g_array_unref (priv->supported_modes);
if (priv->supported_bands)
g_array_unref (priv->supported_bands);
+ g_regex_unref (priv->xlsrstop_regex);
+ g_regex_unref (priv->nmea_regex);
g_slice_free (Private, priv);
}
@@ -62,6 +84,20 @@ get_private (MMSharedXmm *self)
priv = g_object_get_qdata (G_OBJECT (self), private_quark);
if (!priv) {
priv = g_slice_new0 (Private);
+ priv->gps_engine_state = GPS_ENGINE_STATE_OFF;
+
+ /* Setup regex for URCs */
+ priv->xlsrstop_regex = g_regex_new ("\\r\\n\\+XLSRSTOP:(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+ priv->nmea_regex = g_regex_new ("(?:\\r\\n)?(?:\\r\\n)?(\\$G.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL);
+
+ /* Setup parent class' MMBroadbandModemClass */
+ g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class);
+ priv->broadband_modem_class_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_broadband_modem_class (self);
+
+ /* Setup parent class' MMIfaceModemLocation */
+ g_assert (MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface);
+ priv->iface_modem_location_parent = MM_SHARED_XMM_GET_INTERFACE (self)->peek_parent_location_interface (self);
+
g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free);
}
@@ -754,6 +790,607 @@ mm_shared_xmm_signal_load_values (MMIfaceModemSignal *self,
}
/*****************************************************************************/
+/* Load capabilities (Location interface) */
+
+MMModemLocationSource
+mm_shared_xmm_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ GError *inner_error = NULL;
+ gssize value;
+
+ value = g_task_propagate_int (G_TASK (res), &inner_error);
+ if (inner_error) {
+ g_propagate_error (error, inner_error);
+ return MM_MODEM_LOCATION_SOURCE_NONE;
+ }
+ return (MMModemLocationSource)value;
+}
+
+static void
+xlcslsr_test_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+ gboolean transport_protocol_invalid_supported;
+ gboolean standalone_position_mode_supported;
+ gboolean loc_response_type_nmea_supported;
+ gboolean gnss_type_gps_glonass_supported;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ /* Recover parent sources */
+ sources = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ response = mm_base_modem_at_command_finish (self, res, &error);
+ if (!response ||
+ !mm_xmm_parse_xlcslsr_test_response (response,
+ &transport_protocol_invalid_supported,
+ &standalone_position_mode_supported,
+ &loc_response_type_nmea_supported,
+ &gnss_type_gps_glonass_supported,
+ &error)) {
+ mm_dbg ("XLCSLSR based GPS control unsupported: %s", error->message);
+ g_clear_error (&error);
+ } else if (!transport_protocol_invalid_supported ||
+ !standalone_position_mode_supported ||
+ !loc_response_type_nmea_supported ||
+ !gnss_type_gps_glonass_supported) {
+ mm_dbg ("XLCSLSR based GPS control unsupported: protocol %s, standalone %s, nmea %s, gps/glonass %s",
+ transport_protocol_invalid_supported ? "supported" : "unsupported",
+ standalone_position_mode_supported ? "supported" : "unsupported",
+ loc_response_type_nmea_supported ? "supported" : "unsupported",
+ gnss_type_gps_glonass_supported ? "supported" : "unsupported");
+ } else {
+ mm_dbg ("XLCSLSR based GPS control supported");
+ priv->supported_sources |= (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW);
+ sources |= priv->supported_sources;
+ }
+
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+}
+
+static void
+parent_load_capabilities_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource sources;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ sources = priv->iface_modem_location_parent->load_capabilities_finish (self, res, &error);
+ if (error) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If parent already supports GPS sources, we won't do anything else */
+ if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) {
+ mm_dbg ("No need to run XLCSLSR based location gathering");
+ g_task_return_int (task, sources);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Cache sources supported by the parent */
+ g_task_set_task_data (task, GUINT_TO_POINTER (sources), NULL);
+
+ mm_base_modem_at_command (
+ MM_BASE_MODEM (g_task_get_source_object (task)),
+ "+XLCSLSR=?",
+ 3,
+ TRUE, /* allow caching */
+ (GAsyncReadyCallback)xlcslsr_test_ready,
+ task);
+}
+
+void
+mm_shared_xmm_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ task = g_task_new (self, NULL, callback, user_data);
+
+ g_assert (priv->iface_modem_location_parent);
+ g_assert (priv->iface_modem_location_parent->load_capabilities);
+ g_assert (priv->iface_modem_location_parent->load_capabilities_finish);
+
+ priv->iface_modem_location_parent->load_capabilities (self,
+ (GAsyncReadyCallback)parent_load_capabilities_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* GPS engine state selection */
+
+static void
+nmea_received (MMPortSerialAt *port,
+ GMatchInfo *info,
+ MMSharedXmm *self)
+{
+ gchar *trace;
+
+ trace = g_match_info_fetch (info, 1);
+
+ /* Helper to debug GPS location related issues. Don't depend on a real GPS
+ * fix for debugging, just use some random values to update */
+#if 0
+ {
+ const gchar *prefix = NULL;
+ const gchar *lat = NULL;
+
+ /* lat N/S just to test which one is used */
+ if (g_str_has_prefix (trace, "$GPGGA")) {
+ prefix = "GPGGA";
+ lat = "S";
+ } else if (g_str_has_prefix (trace, "$GNGGA")) {
+ prefix = "GNGGA";
+ lat = "N";
+ }
+
+ if (prefix && lat) {
+ GString *str;
+ GDateTime *now;
+
+ mm_dbg ("GGA trace detected: '%s'", trace);
+ g_free (trace);
+
+ now = g_date_time_new_now_utc ();
+ str = g_string_new ("");
+ g_string_append_printf (str,
+ "$%s,%02u%02u%02u,4807.038,%s,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47",
+ prefix,
+ g_date_time_get_hour (now),
+ g_date_time_get_minute (now),
+ g_date_time_get_second (now),
+ lat);
+ mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), str->str);
+ g_string_free (str, TRUE);
+ g_date_time_unref (now);
+ return;
+ }
+ }
+#endif
+
+ mm_iface_modem_location_gps_update (MM_IFACE_MODEM_LOCATION (self), trace);
+ g_free (trace);
+}
+
+static gboolean
+gps_engine_state_select_finish (MMSharedXmm *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+xlcslsr_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GpsEngineState state;
+ const gchar *response;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ response = mm_base_modem_at_command_full_finish (self, res, &error);
+ if (!response) {
+ g_clear_object (&priv->gps_port);
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ state = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ g_assert (priv->gps_port);
+ mm_port_serial_at_add_unsolicited_msg_handler (priv->gps_port,
+ priv->nmea_regex,
+ (MMPortSerialAtUnsolicitedMsgFn)nmea_received,
+ self,
+ NULL);
+ priv->gps_engine_state = state;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+gps_engine_start (GTask *task)
+{
+ MMSharedXmm *self;
+ Private *priv;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (self);
+
+ /* Look for an AT port to use for GPS. Prefer secondary port if there is one,
+ * otherwise use primary */
+ g_assert (!priv->gps_port);
+ priv->gps_port = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self));
+ if (!priv->gps_port) {
+ priv->gps_port = mm_base_modem_get_port_primary (MM_BASE_MODEM (self));
+ if (!priv->gps_port) {
+ g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+ "No valid port found to control GPS");
+ g_object_unref (task);
+ return;
+ }
+ }
+
+ /*
+ * AT+XLCSLSR
+ * transport_protocol: 2 (invalid)
+ * pos_mode: 3 (standalone)
+ * client_id: <empty>
+ * client_id_type: <empty>
+ * mlc_number: <empty>
+ * mlc_number_type: <empty>
+ * interval: 1 (seconds)
+ * service_type_id: <empty>
+ * pseudonym_indicator: <empty>
+ * loc_response_type: 1 (NMEA strings)
+ * nmea_mask: 118 (01110110: GGA,GSA,GSV,RMC,VTG)
+ * gnss_type: 0 (GPS or GLONASS)
+ */
+ g_assert (priv->gps_port);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ priv->gps_port,
+ "AT+XLCSLSR=2,3,,,,,1,,,1,118,0",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)xlcslsr_ready,
+ task);
+}
+
+static void
+xlsrstop_ready (MMBaseModem *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GpsEngineState state;
+ GError *error = NULL;
+ Private *priv;
+
+ mm_base_modem_at_command_full_finish (self, res, &error);
+
+ priv = get_private (MM_SHARED_XMM (self));
+ state = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+ g_assert (priv->gps_port);
+ mm_port_serial_at_add_unsolicited_msg_handler (priv->gps_port, priv->nmea_regex, NULL, NULL, NULL);
+ g_clear_object (&priv->gps_port);
+ priv->gps_engine_state = GPS_ENGINE_STATE_OFF;
+
+ /* If already reached requested state, we're done */
+ if (state == priv->gps_engine_state) {
+ /* If we had an error when requesting this specific state, report it */
+ if (error)
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* Ignore errors if the stop operation was an intermediate one */
+ g_clear_error (&error);
+
+ /* Otherwise, start with new state */
+ gps_engine_start (task);
+}
+
+static void
+gps_engine_stop (GTask *task)
+{
+ MMSharedXmm *self;
+ Private *priv;
+
+ self = g_task_get_source_object (task);
+ priv = get_private (self);
+
+ g_assert (priv->gps_port);
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ priv->gps_port,
+ "+XLSRSTOP",
+ 3,
+ FALSE,
+ FALSE, /* raw */
+ NULL, /* cancellable */
+ (GAsyncReadyCallback)xlsrstop_ready,
+ task);
+}
+
+static void
+gps_engine_state_select (MMSharedXmm *self,
+ GpsEngineState state,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GTask *task;
+ Private *priv;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (state), NULL);
+
+ priv = get_private (self);
+
+ /* If already in the requested state, we're done */
+ if (state == priv->gps_engine_state) {
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* If states are different we always STOP first */
+ if (priv->gps_engine_state != GPS_ENGINE_STATE_OFF) {
+ gps_engine_stop (task);
+ return;
+ }
+
+ /* If GPS already stopped, go on to START right away */
+ g_assert (state != GPS_ENGINE_STATE_OFF);
+ gps_engine_start (task);
+}
+
+static GpsEngineState
+gps_engine_state_get_expected (MMModemLocationSource sources)
+{
+ /* If at lease one of GPS nmea/raw sources enabled, engine started */
+ if (sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW))
+ return GPS_ENGINE_STATE_STANDALONE;
+
+ /* If no GPS nmea/raw sources enabled, engine stopped */
+ return GPS_ENGINE_STATE_OFF;
+}
+
+/*****************************************************************************/
+/* Disable location gathering (Location interface) */
+
+gboolean
+mm_shared_xmm_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+disable_gps_engine_state_select_ready (MMSharedXmm *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource source;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ if (!gps_engine_state_select_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ source = GPOINTER_TO_UINT (g_task_get_task_data (task));
+ priv->enabled_sources &= ~source;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_disable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ if (!priv->iface_modem_location_parent->disable_location_gathering_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->iface_modem_location_parent);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent disable */
+ if (!(priv->supported_sources & source)) {
+ /* If disabling implemented by the parent, run it. */
+ if (priv->iface_modem_location_parent->disable_location_gathering &&
+ priv->iface_modem_location_parent->disable_location_gathering_finish) {
+ priv->iface_modem_location_parent->disable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_disable_location_gathering_ready,
+ task);
+ return;
+ }
+ /* Otherwise, we're done */
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW));
+
+ /* Update engine based on the expected sources */
+ gps_engine_state_select (MM_SHARED_XMM (self),
+ gps_engine_state_get_expected (priv->enabled_sources & ~source),
+ (GAsyncReadyCallback) disable_gps_engine_state_select_ready,
+ task);
+}
+
+/*****************************************************************************/
+/* Enable location gathering (Location interface) */
+
+gboolean
+mm_shared_xmm_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error)
+{
+ return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+enable_gps_engine_state_select_ready (MMSharedXmm *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ MMModemLocationSource source;
+ GError *error = NULL;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ if (!gps_engine_state_select_finish (self, res, &error)) {
+ g_task_return_error (task, error);
+ g_object_unref (task);
+ return;
+ }
+
+ source = GPOINTER_TO_UINT (g_task_get_task_data (task));
+ priv->enabled_sources |= source;
+
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+static void
+parent_enable_location_gathering_ready (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GTask *task)
+{
+ GError *error;
+ Private *priv;
+
+ priv = get_private (MM_SHARED_XMM (self));
+
+ g_assert (priv->iface_modem_location_parent);
+ if (!priv->iface_modem_location_parent->enable_location_gathering_finish (self, res, &error))
+ g_task_return_error (task, error);
+ else
+ g_task_return_boolean (task, TRUE);
+ g_object_unref (task);
+}
+
+void
+mm_shared_xmm_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ Private *priv;
+ GTask *task;
+
+ task = g_task_new (self, NULL, callback, user_data);
+ g_task_set_task_data (task, GUINT_TO_POINTER (source), NULL);
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->iface_modem_location_parent);
+ g_assert (priv->iface_modem_location_parent->enable_location_gathering);
+ g_assert (priv->iface_modem_location_parent->enable_location_gathering_finish);
+
+ /* Only consider request if it applies to one of the sources we are
+ * supporting, otherwise run parent enable */
+ if (!(priv->supported_sources & source)) {
+ priv->iface_modem_location_parent->enable_location_gathering (self,
+ source,
+ (GAsyncReadyCallback)parent_enable_location_gathering_ready,
+ task);
+ return;
+ }
+
+ /* We only expect GPS sources here */
+ g_assert (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW));
+
+ /* Update engine based on the expected sources */
+ gps_engine_state_select (MM_SHARED_XMM (self),
+ gps_engine_state_get_expected (priv->enabled_sources | source),
+ (GAsyncReadyCallback) enable_gps_engine_state_select_ready,
+ task);
+}
+
+/*****************************************************************************/
+
+void
+mm_shared_xmm_setup_ports (MMBroadbandModem *self)
+{
+ Private *priv;
+ MMPortSerialAt *ports[2];
+ guint i;
+
+ priv = get_private (MM_SHARED_XMM (self));
+ g_assert (priv->broadband_modem_class_parent);
+ g_assert (priv->broadband_modem_class_parent->setup_ports);
+
+ /* Parent setup first always */
+ priv->broadband_modem_class_parent->setup_ports (self);
+
+ ports[0] = mm_base_modem_peek_port_primary (MM_BASE_MODEM (self));
+ ports[1] = mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self));
+
+ /* Setup primary and secondary ports */
+ for (i = 0; i < G_N_ELEMENTS (ports); i++) {
+ if (!ports[i])
+ continue;
+
+ /* After running AT+XLSRSTOP we may get an unsolicited response
+ * reporting its status, we just ignore it. */
+ mm_port_serial_at_add_unsolicited_msg_handler (
+ ports[i],
+ priv->xlsrstop_regex,
+ NULL, NULL, NULL);
+
+
+
+ /* make sure GPS is stopped in case it was left enabled */
+ mm_base_modem_at_command_full (MM_BASE_MODEM (self),
+ ports[i],
+ "+XLSRSTOP",
+ 3, FALSE, FALSE, NULL, NULL, NULL);
+ }
+}
+
+/*****************************************************************************/
static void
shared_xmm_init (gpointer g_iface)
@@ -774,6 +1411,7 @@ mm_shared_xmm_get_type (void)
shared_xmm_type = g_type_register_static (G_TYPE_INTERFACE, "MMSharedXmm", &info, 0);
g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM);
+ g_type_interface_add_prerequisite (shared_xmm_type, MM_TYPE_IFACE_MODEM_LOCATION);
}
return shared_xmm_type;
diff --git a/plugins/xmm/mm-shared-xmm.h b/plugins/xmm/mm-shared-xmm.h
index d32cea14..1ff31ff1 100644
--- a/plugins/xmm/mm-shared-xmm.h
+++ b/plugins/xmm/mm-shared-xmm.h
@@ -22,8 +22,10 @@
#define _LIBMM_INSIDE_MM
#include <libmm-glib.h>
+#include "mm-broadband-modem.h"
#include "mm-iface-modem.h"
#include "mm-iface-modem-signal.h"
+#include "mm-iface-modem-location.h"
#define MM_TYPE_SHARED_XMM (mm_shared_xmm_get_type ())
#define MM_SHARED_XMM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_SHARED_XMM, MMSharedXmm))
@@ -34,10 +36,20 @@ typedef struct _MMSharedXmm MMSharedXmm;
struct _MMSharedXmm {
GTypeInterface g_iface;
+
+ /* Peek broadband modem class of the parent class of the object */
+ MMBroadbandModemClass * (* peek_parent_broadband_modem_class) (MMSharedXmm *self);
+
+ /* Peek location interface of the parent class of the object */
+ MMIfaceModemLocation * (* peek_parent_location_interface) (MMSharedXmm *self);
};
GType mm_shared_xmm_get_type (void);
+/* Shared XMM device setup */
+
+void mm_shared_xmm_setup_ports (MMBroadbandModem *self);
+
/* Shared XMM device management support */
void mm_shared_xmm_load_supported_modes (MMIfaceModem *self,
@@ -134,4 +146,25 @@ void mm_shared_xmm_signal_load_values (MMIfaceModemSign
GAsyncReadyCallback callback,
gpointer user_data);
+void mm_shared_xmm_location_load_capabilities (MMIfaceModemLocation *self,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+MMModemLocationSource mm_shared_xmm_location_load_capabilities_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_enable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_enable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+void mm_shared_xmm_disable_location_gathering (MMIfaceModemLocation *self,
+ MMModemLocationSource source,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gboolean mm_shared_xmm_disable_location_gathering_finish (MMIfaceModemLocation *self,
+ GAsyncResult *res,
+ GError **error);
+
#endif /* MM_SHARED_XMM_H */
diff --git a/plugins/xmm/tests/test-modem-helpers-xmm.c b/plugins/xmm/tests/test-modem-helpers-xmm.c
index 4c2bbf33..ef65b2f8 100644
--- a/plugins/xmm/tests/test-modem-helpers-xmm.c
+++ b/plugins/xmm/tests/test-modem-helpers-xmm.c
@@ -649,6 +649,61 @@ test_xcesq_response_to_signal (void)
}
/*****************************************************************************/
+/* AT+XLCSLSR=? response parser */
+
+typedef struct {
+ const gchar *response;
+ gboolean expected_transport_protocol_invalid_supported;
+ gboolean expected_standalone_position_mode_supported;
+ gboolean expected_loc_response_type_nmea_supported;
+ gboolean expected_gnss_type_gps_glonass_supported;
+} XlcslsrTest;
+
+static XlcslsrTest xlcslsr_tests[] = {
+ {
+ "+XLCSLSR:(0-2),(0-3), ,(0-1), ,(0-1),(0-7200),(0-255),(0-1),(0-2),(1-256),(0-1)",
+ TRUE, TRUE, TRUE, TRUE
+ },
+ {
+ "+XLCSLSR:(0,1,2),(0,1,2,3), ,(0,1), ,(0,1),(0-7200),(0-255),(0,1),(0,1,2),(1-256),(0,1)",
+ TRUE, TRUE, TRUE, TRUE
+ },
+ {
+ "+XLCSLSR:(0-1),(0-2), ,(0,1), ,(0,1),(0 -7200),(0-255),(0-1),(0),(1-256),(1)",
+ FALSE, FALSE, FALSE, FALSE
+ },
+};
+
+static void
+test_xlcslsr_test (void)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (xlcslsr_tests); i++) {
+ GError *error = NULL;
+ gboolean ret;
+ gboolean transport_protocol_invalid_supported;
+ gboolean standalone_position_mode_supported;
+ gboolean loc_response_type_nmea_supported;
+ gboolean gnss_type_gps_glonass_supported;
+
+ ret = mm_xmm_parse_xlcslsr_test_response (xlcslsr_tests[i].response,
+ &transport_protocol_invalid_supported,
+ &standalone_position_mode_supported,
+ &loc_response_type_nmea_supported,
+ &gnss_type_gps_glonass_supported,
+ &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ g_assert (transport_protocol_invalid_supported == xlcslsr_tests[i].expected_transport_protocol_invalid_supported);
+ g_assert (standalone_position_mode_supported == xlcslsr_tests[i].expected_standalone_position_mode_supported);
+ g_assert (loc_response_type_nmea_supported == xlcslsr_tests[i].expected_loc_response_type_nmea_supported);
+ g_assert (gnss_type_gps_glonass_supported == xlcslsr_tests[i].expected_gnss_type_gps_glonass_supported);
+ }
+}
+
+/*****************************************************************************/
void
_mm_log (const char *loc,
@@ -688,5 +743,7 @@ int main (int argc, char **argv)
g_test_add_func ("/MM/xmm/xcesq/query_response", test_xcesq_response);
g_test_add_func ("/MM/xmm/xcesq/query_response_to_signal", test_xcesq_response_to_signal);
+ g_test_add_func ("/MM/xmm/xlcslsr/test", test_xlcslsr_test);
+
return g_test_run ();
}