diff options
-rw-r--r-- | plugins/xmm/mm-broadband-modem-mbim-xmm.c | 39 | ||||
-rw-r--r-- | plugins/xmm/mm-broadband-modem-xmm.c | 37 | ||||
-rw-r--r-- | plugins/xmm/mm-modem-helpers-xmm.c | 122 | ||||
-rw-r--r-- | plugins/xmm/mm-modem-helpers-xmm.h | 8 | ||||
-rw-r--r-- | plugins/xmm/mm-shared-xmm.c | 638 | ||||
-rw-r--r-- | plugins/xmm/mm-shared-xmm.h | 33 | ||||
-rw-r--r-- | plugins/xmm/tests/test-modem-helpers-xmm.c | 57 |
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 (); } |