/* -*- 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) 2019 Aleksander Morgado */ #include #include #include #define _LIBMM_INSIDE_MM #include #include "mm-log-object.h" #include "mm-iface-modem.h" #include "mm-iface-modem-voice.h" #include "mm-iface-modem-location.h" #include "mm-base-modem.h" #include "mm-base-modem-at.h" #include "mm-shared-simtech.h" #include "mm-modem-helpers-simtech.h" G_DEFINE_INTERFACE (MMSharedSimtech, mm_shared_simtech, MM_TYPE_IFACE_MODEM) /*****************************************************************************/ /* Private data context */ #define PRIVATE_TAG "shared-simtech-private-tag" static GQuark private_quark; typedef enum { FEATURE_SUPPORT_UNKNOWN, FEATURE_NOT_SUPPORTED, FEATURE_SUPPORTED, } FeatureSupport; typedef struct { /* location */ MMIfaceModemLocationInterface *iface_modem_location_parent; MMModemLocationSource supported_sources; MMModemLocationSource enabled_sources; FeatureSupport cgps_support; /* voice */ MMIfaceModemVoiceInterface *iface_modem_voice_parent; FeatureSupport cpcmfrm_support; FeatureSupport cpcmreg_support; FeatureSupport clcc_urc_support; GRegex *clcc_urc_regex; GRegex *voice_call_regex; GRegex *missed_call_regex; GRegex *cring_regex; GRegex *rxdtmf_regex; } Private; static void private_free (Private *ctx) { g_regex_unref (ctx->rxdtmf_regex); g_regex_unref (ctx->cring_regex); g_regex_unref (ctx->missed_call_regex); g_regex_unref (ctx->voice_call_regex); g_regex_unref (ctx->clcc_urc_regex); g_slice_free (Private, ctx); } static Private * get_private (MMSharedSimtech *self) { Private *priv; if (G_UNLIKELY (!private_quark)) private_quark = (g_quark_from_static_string (PRIVATE_TAG)); priv = g_object_get_qdata (G_OBJECT (self), private_quark); if (!priv) { priv = g_slice_new0 (Private); priv->supported_sources = MM_MODEM_LOCATION_SOURCE_NONE; priv->enabled_sources = MM_MODEM_LOCATION_SOURCE_NONE; priv->cgps_support = FEATURE_SUPPORT_UNKNOWN; priv->cpcmfrm_support = FEATURE_SUPPORT_UNKNOWN; priv->cpcmreg_support = FEATURE_SUPPORT_UNKNOWN; priv->clcc_urc_support = FEATURE_SUPPORT_UNKNOWN; priv->clcc_urc_regex = mm_simtech_get_clcc_urc_regex (); priv->voice_call_regex = mm_simtech_get_voice_call_urc_regex (); priv->missed_call_regex = mm_simtech_get_missed_call_urc_regex (); priv->cring_regex = mm_simtech_get_cring_urc_regex (); priv->rxdtmf_regex = mm_simtech_get_rxdtmf_urc_regex (); /* Setup parent class' MMIfaceModemLocation and MMIfaceModemVoice */ g_assert (MM_SHARED_SIMTECH_GET_IFACE (self)->peek_parent_location_interface); priv->iface_modem_location_parent = MM_SHARED_SIMTECH_GET_IFACE (self)->peek_parent_location_interface (self); g_assert (MM_SHARED_SIMTECH_GET_IFACE (self)->peek_parent_voice_interface); priv->iface_modem_voice_parent = MM_SHARED_SIMTECH_GET_IFACE (self)->peek_parent_voice_interface (self); g_object_set_qdata_full (G_OBJECT (self), private_quark, priv, (GDestroyNotify)private_free); } return priv; } /*****************************************************************************/ /* GPS trace received */ static void trace_received (MMPortSerialGps *port, const gchar *trace, MMIfaceModemLocation *self) { mm_iface_modem_location_gps_update (self, trace); } /*****************************************************************************/ /* Location capabilities loading (Location interface) */ MMModemLocationSource mm_shared_simtech_location_load_capabilities_finish (MMIfaceModemLocation *self, GAsyncResult *res, GError **error) { GError *inner_error = NULL; gssize aux; aux = 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) aux; } static void probe_gps_features (GTask *task); static void cgps_test_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { Private *priv; priv = get_private (MM_SHARED_SIMTECH (self)); if (!mm_base_modem_at_command_finish (self, res, NULL)) priv->cgps_support = FEATURE_NOT_SUPPORTED; else priv->cgps_support = FEATURE_SUPPORTED; probe_gps_features (task); } static void probe_gps_features (GTask *task) { MMSharedSimtech *self; MMModemLocationSource sources; Private *priv; self = MM_SHARED_SIMTECH (g_task_get_source_object (task)); priv = get_private (self); /* Need to check if CGPS supported... */ if (priv->cgps_support == FEATURE_SUPPORT_UNKNOWN) { mm_base_modem_at_command (MM_BASE_MODEM (self), "+CGPS=?", 3, TRUE, (GAsyncReadyCallback) cgps_test_ready, task); return; } /* All GPS features probed */ /* Recover parent sources */ sources = GPOINTER_TO_UINT (g_task_get_task_data (task)); if (priv->cgps_support == FEATURE_SUPPORTED) { mm_obj_dbg (self, "GPS commands supported: GPS capabilities enabled"); /* We only flag as supported by this implementation those sources not already * supported by the parent implementation */ if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_NMEA)) priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_NMEA; if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_RAW)) priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_RAW; if (!(sources & MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) priv->supported_sources |= MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED; sources |= priv->supported_sources; /* Add handler for the NMEA traces in the GPS data port */ mm_port_serial_gps_add_trace_handler (mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)), (MMPortSerialGpsTraceFn)trace_received, self, NULL); } else mm_obj_dbg (self, "no GPS command supported: no GPS capabilities"); g_task_return_int (task, (gssize) 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_SIMTECH (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; } /* Now our own check. If we don't have any GPS port, we're done */ if (!mm_base_modem_peek_port_gps (MM_BASE_MODEM (self))) { mm_obj_dbg (self, "no GPS data port found: no GPS capabilities"); 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); /* Probe all GPS features */ probe_gps_features (task); } void mm_shared_simtech_location_load_capabilities (MMIfaceModemLocation *self, GAsyncReadyCallback callback, gpointer user_data) { Private *priv; GTask *task; priv = get_private (MM_SHARED_SIMTECH (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); } /*****************************************************************************/ /* Disable location gathering (Location interface) */ gboolean mm_shared_simtech_disable_location_gathering_finish (MMIfaceModemLocation *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void disable_cgps_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { MMModemLocationSource source; Private *priv; GError *error = NULL; priv = get_private (MM_SHARED_SIMTECH (self)); mm_base_modem_at_command_finish (self, res, &error); /* Only use the GPS port in NMEA/RAW setups */ source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task)); if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { MMPortSerialGps *gps_port; /* Even if we get an error here, we try to close the GPS port */ gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); if (gps_port) mm_port_serial_close (MM_PORT_SERIAL (gps_port)); } if (error) g_task_return_error (task, error); else { 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 = NULL; Private *priv; priv = get_private (MM_SHARED_SIMTECH (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_simtech_disable_location_gathering (MMIfaceModemLocation *self, MMModemLocationSource source, GAsyncReadyCallback callback, gpointer user_data) { MMModemLocationSource enabled_sources; 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_SIMTECH (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 | MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)); /* Flag as disabled to see how many others we would have left enabled */ enabled_sources = priv->enabled_sources; enabled_sources &= ~source; /* If there are still GPS-related sources enabled, do nothing else */ if (enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW | MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { priv->enabled_sources &= ~source; g_task_return_boolean (task, TRUE); g_object_unref (task); return; } /* Stop GPS engine if all GPS-related sources are disabled */ g_assert (priv->cgps_support == FEATURE_SUPPORTED); mm_base_modem_at_command (MM_BASE_MODEM (self), "+CGPS=0", 10, FALSE, (GAsyncReadyCallback) disable_cgps_ready, task); } /*****************************************************************************/ /* Enable location gathering (Location interface) */ gboolean mm_shared_simtech_enable_location_gathering_finish (MMIfaceModemLocation *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void enable_cgps_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { MMModemLocationSource source; GError *error = NULL; Private *priv; priv = get_private (MM_SHARED_SIMTECH (self)); if (!mm_base_modem_at_command_finish (self, res, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } /* Only use the GPS port in NMEA/RAW setups */ source = (MMModemLocationSource) GPOINTER_TO_UINT (g_task_get_task_data (task)); if (source & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW)) { MMPortSerialGps *gps_port; gps_port = mm_base_modem_peek_port_gps (MM_BASE_MODEM (self)); if (!gps_port || !mm_port_serial_open (MM_PORT_SERIAL (gps_port), &error)) { if (error) g_task_return_error (task, error); else g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Couldn't open raw GPS serial port"); g_object_unref (task); return; } } 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 = NULL; Private *priv; priv = get_private (MM_SHARED_SIMTECH (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_simtech_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_SIMTECH (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 | MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)); /* If GPS already started, store new flag and we're done */ if (priv->enabled_sources & (MM_MODEM_LOCATION_SOURCE_GPS_NMEA | MM_MODEM_LOCATION_SOURCE_GPS_RAW | MM_MODEM_LOCATION_SOURCE_GPS_UNMANAGED)) { priv->enabled_sources |= source; g_task_return_boolean (task, TRUE); g_object_unref (task); return; } g_assert (priv->cgps_support == FEATURE_SUPPORTED); mm_base_modem_at_command (MM_BASE_MODEM (self), "+CGPS=1,1", 10, FALSE, (GAsyncReadyCallback) enable_cgps_ready, task); } /*****************************************************************************/ /* Common enable/disable voice unsolicited events */ typedef struct { gboolean enable; MMPortSerialAt *primary; MMPortSerialAt *secondary; gchar *clcc_command; gboolean clcc_primary_done; gboolean clcc_secondary_done; } VoiceUnsolicitedEventsContext; static void voice_unsolicited_events_context_free (VoiceUnsolicitedEventsContext *ctx) { g_clear_object (&ctx->secondary); g_clear_object (&ctx->primary); g_free (ctx->clcc_command); g_slice_free (VoiceUnsolicitedEventsContext, ctx); } static gboolean common_voice_enable_disable_unsolicited_events_finish (MMSharedSimtech *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void run_voice_enable_disable_unsolicited_events (GTask *task); static void clcc_command_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { VoiceUnsolicitedEventsContext *ctx; GError *error = NULL; ctx = g_task_get_task_data (task); if (!mm_base_modem_at_command_finish (self, res, &error)) { mm_obj_dbg (self, "couldn't %s +CLCC reporting: '%s'", ctx->enable ? "enable" : "disable", error->message); g_error_free (error); } /* Continue on next port */ run_voice_enable_disable_unsolicited_events (task); } static void run_voice_enable_disable_unsolicited_events (GTask *task) { MMSharedSimtech *self; Private *priv; VoiceUnsolicitedEventsContext *ctx; MMPortSerialAt *port = NULL; self = MM_SHARED_SIMTECH (g_task_get_source_object (task)); priv = get_private (self); ctx = g_task_get_task_data (task); /* If +CLCC URCs not supported, we're done */ if (priv->clcc_urc_support == FEATURE_NOT_SUPPORTED) { g_task_return_boolean (task, TRUE); g_object_unref (task); return; } if (!ctx->clcc_primary_done && ctx->primary) { mm_obj_dbg (self, "%s +CLCC extended list of current calls reporting in primary port...", ctx->enable ? "enabling" : "disabling"); ctx->clcc_primary_done = TRUE; port = ctx->primary; } else if (!ctx->clcc_secondary_done && ctx->secondary) { mm_obj_dbg (self, "%s +CLCC extended list of current calls reporting in secondary port...", ctx->enable ? "enabling" : "disabling"); ctx->clcc_secondary_done = TRUE; port = ctx->secondary; } if (port) { mm_base_modem_at_command_full (MM_BASE_MODEM (self), MM_IFACE_PORT_AT (port), ctx->clcc_command, 3, FALSE, FALSE, NULL, (GAsyncReadyCallback)clcc_command_ready, task); return; } /* Fully done now */ g_task_return_boolean (task, TRUE); g_object_unref (task); } static void common_voice_enable_disable_unsolicited_events (MMSharedSimtech *self, gboolean enable, GAsyncReadyCallback callback, gpointer user_data) { VoiceUnsolicitedEventsContext *ctx; GTask *task; task = g_task_new (self, NULL, callback, user_data); ctx = g_slice_new0 (VoiceUnsolicitedEventsContext); ctx->enable = enable; if (enable) ctx->clcc_command = g_strdup ("+CLCC=1"); else ctx->clcc_command = g_strdup ("+CLCC=0"); ctx->primary = mm_base_modem_get_port_primary (MM_BASE_MODEM (self)); ctx->secondary = mm_base_modem_get_port_secondary (MM_BASE_MODEM (self)); g_task_set_task_data (task, ctx, (GDestroyNotify) voice_unsolicited_events_context_free); run_voice_enable_disable_unsolicited_events (task); } /*****************************************************************************/ /* Disable unsolicited events (Voice interface) */ gboolean mm_shared_simtech_voice_disable_unsolicited_events_finish (MMIfaceModemVoice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void parent_voice_disable_unsolicited_events_ready (MMIfaceModemVoice *self, GAsyncResult *res, GTask *task) { GError *error = NULL; Private *priv; priv = get_private (MM_SHARED_SIMTECH (self)); if (!priv->iface_modem_voice_parent->disable_unsolicited_events_finish (self, res, &error)) { mm_obj_warn (self, "couldn't disable parent voice unsolicited events: %s", error->message); g_error_free (error); } g_task_return_boolean (task, TRUE); g_object_unref (task); } static void voice_disable_unsolicited_events_ready (MMSharedSimtech *self, GAsyncResult *res, GTask *task) { Private *priv; GError *error = NULL; if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) { mm_obj_warn (self, "couldn't disable Simtech-specific voice unsolicited events: %s", error->message); g_error_free (error); } priv = get_private (MM_SHARED_SIMTECH (self)); g_assert (priv->iface_modem_voice_parent); g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events); g_assert (priv->iface_modem_voice_parent->disable_unsolicited_events_finish); /* Chain up parent's disable */ priv->iface_modem_voice_parent->disable_unsolicited_events ( MM_IFACE_MODEM_VOICE (self), (GAsyncReadyCallback)parent_voice_disable_unsolicited_events_ready, task); } void mm_shared_simtech_voice_disable_unsolicited_events (MMIfaceModemVoice *self, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; task = g_task_new (self, NULL, callback, user_data); /* our own disabling first */ common_voice_enable_disable_unsolicited_events (MM_SHARED_SIMTECH (self), FALSE, (GAsyncReadyCallback) voice_disable_unsolicited_events_ready, task); } /*****************************************************************************/ /* Enable unsolicited events (Voice interface) */ gboolean mm_shared_simtech_voice_enable_unsolicited_events_finish (MMIfaceModemVoice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void voice_enable_unsolicited_events_ready (MMSharedSimtech *self, GAsyncResult *res, GTask *task) { GError *error = NULL; if (!common_voice_enable_disable_unsolicited_events_finish (self, res, &error)) { mm_obj_warn (self, "couldn't enable Simtech-specific voice unsolicited events: %s", error->message); g_error_free (error); } g_task_return_boolean (task, TRUE); g_object_unref (task); } static void parent_voice_enable_unsolicited_events_ready (MMIfaceModemVoice *self, GAsyncResult *res, GTask *task) { GError *error = NULL; Private *priv; priv = get_private (MM_SHARED_SIMTECH (self)); if (!priv->iface_modem_voice_parent->enable_unsolicited_events_finish (self, res, &error)) { mm_obj_warn (self, "couldn't enable parent voice unsolicited events: %s", error->message); g_error_free (error); } /* our own enabling next */ common_voice_enable_disable_unsolicited_events (MM_SHARED_SIMTECH (self), TRUE, (GAsyncReadyCallback) voice_enable_unsolicited_events_ready, task); } void mm_shared_simtech_voice_enable_unsolicited_events (MMIfaceModemVoice *self, GAsyncReadyCallback callback, gpointer user_data) { Private *priv; GTask *task; task = g_task_new (self, NULL, callback, user_data); priv = get_private (MM_SHARED_SIMTECH (self)); g_assert (priv->iface_modem_voice_parent); g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events); g_assert (priv->iface_modem_voice_parent->enable_unsolicited_events_finish); /* chain up parent's enable first */ priv->iface_modem_voice_parent->enable_unsolicited_events ( self, (GAsyncReadyCallback)parent_voice_enable_unsolicited_events_ready, task); } /*****************************************************************************/ /* Common setup/cleanup voice unsolicited events */ static void clcc_urc_received (MMPortSerialAt *port, GMatchInfo *match_info, MMSharedSimtech *self) { gchar *full; GError *error = NULL; GList *call_info_list = NULL; full = g_match_info_fetch (match_info, 0); if (!mm_simtech_parse_clcc_list (full, self, &call_info_list, &error)) { mm_obj_warn (self, "couldn't parse +CLCC list in URC: %s", error->message); g_error_free (error); } else mm_iface_modem_voice_report_all_calls (MM_IFACE_MODEM_VOICE (self), call_info_list); mm_simtech_call_info_list_free (call_info_list); g_free (full); } static void missed_call_urc_received (MMPortSerialAt *port, GMatchInfo *match_info, MMSharedSimtech *self) { GError *error = NULL; gchar *details = NULL; if (!mm_simtech_parse_missed_call_urc (match_info, &details, &error)) { mm_obj_warn (self, "couldn't parse missed call URC: %s", error->message); g_error_free (error); return; } mm_obj_dbg (self, "missed call reported: %s", details); g_free (details); } static void voice_call_urc_received (MMPortSerialAt *port, GMatchInfo *match_info, MMSharedSimtech *self) { GError *error = NULL; gboolean start_or_stop = FALSE; /* start = TRUE, stop = FALSE */ guint duration = 0; if (!mm_simtech_parse_voice_call_urc (match_info, &start_or_stop, &duration, &error)) { mm_obj_warn (self, "couldn't parse voice call URC: %s", error->message); g_error_free (error); return; } if (start_or_stop) { mm_obj_dbg (self, "voice call started"); return; } if (duration) { mm_obj_dbg (self, "voice call finished (duration: %us)", duration); return; } mm_obj_dbg (self, "voice call finished"); } static void cring_urc_received (MMPortSerialAt *port, GMatchInfo *info, MMSharedSimtech *self) { MMCallInfo call_info; g_autofree gchar *str = NULL; /* We could have "VOICE" or "DATA". Now consider only "VOICE" */ str = mm_get_string_unquoted_from_match_info (info, 1); mm_obj_dbg (self, "ringing (%s)", str); call_info.index = 0; call_info.direction = MM_CALL_DIRECTION_INCOMING; call_info.state = MM_CALL_STATE_RINGING_IN; call_info.number = NULL; mm_iface_modem_voice_report_call (MM_IFACE_MODEM_VOICE (self), &call_info); } static void rxdtmf_urc_received (MMPortSerialAt *port, GMatchInfo *match_info, MMSharedSimtech *self) { g_autofree gchar *dtmf = NULL; dtmf = g_match_info_fetch (match_info, 1); mm_obj_dbg (self, "received DTMF: %s", dtmf); /* call index unknown */ mm_iface_modem_voice_received_dtmf (MM_IFACE_MODEM_VOICE (self), 0, dtmf); } static void common_voice_setup_cleanup_unsolicited_events (MMSharedSimtech *self, gboolean enable) { Private *priv; MMPortSerialAt *ports[2]; guint i; priv = get_private (MM_SHARED_SIMTECH (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)); for (i = 0; i < G_N_ELEMENTS (ports); i++) { if (!ports[i]) continue; if (priv->clcc_urc_support == FEATURE_SUPPORTED) mm_port_serial_at_add_unsolicited_msg_handler (ports[i], priv->clcc_urc_regex, enable ? (MMPortSerialAtUnsolicitedMsgFn)clcc_urc_received : NULL, enable ? self : NULL, NULL); mm_port_serial_at_add_unsolicited_msg_handler (ports[i], priv->voice_call_regex, enable ? (MMPortSerialAtUnsolicitedMsgFn)voice_call_urc_received : NULL, enable ? self : NULL, NULL); mm_port_serial_at_add_unsolicited_msg_handler (ports[i], priv->missed_call_regex, enable ? (MMPortSerialAtUnsolicitedMsgFn)missed_call_urc_received : NULL, enable ? self : NULL, NULL); mm_port_serial_at_add_unsolicited_msg_handler (ports[i], priv->cring_regex, enable ? (MMPortSerialAtUnsolicitedMsgFn)cring_urc_received : NULL, enable ? self : NULL, NULL); mm_port_serial_at_add_unsolicited_msg_handler (ports[i], priv->rxdtmf_regex, enable ? (MMPortSerialAtUnsolicitedMsgFn)rxdtmf_urc_received : NULL, enable ? self : NULL, NULL); } } /*****************************************************************************/ /* Cleanup unsolicited events (Voice interface) */ gboolean mm_shared_simtech_voice_cleanup_unsolicited_events_finish (MMIfaceModemVoice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void parent_voice_cleanup_unsolicited_events_ready (MMIfaceModemVoice *self, GAsyncResult *res, GTask *task) { GError *error = NULL; Private *priv; priv = get_private (MM_SHARED_SIMTECH (self)); if (!priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish (self, res, &error)) { mm_obj_warn (self, "couldn't cleanup parent voice unsolicited events: %s", error->message); g_error_free (error); } g_task_return_boolean (task, TRUE); g_object_unref (task); } void mm_shared_simtech_voice_cleanup_unsolicited_events (MMIfaceModemVoice *self, GAsyncReadyCallback callback, gpointer user_data) { Private *priv; GTask *task; task = g_task_new (self, NULL, callback, user_data); priv = get_private (MM_SHARED_SIMTECH (self)); g_assert (priv->iface_modem_voice_parent); g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events); g_assert (priv->iface_modem_voice_parent->cleanup_unsolicited_events_finish); /* our own cleanup first */ common_voice_setup_cleanup_unsolicited_events (MM_SHARED_SIMTECH (self), FALSE); /* Chain up parent's cleanup */ priv->iface_modem_voice_parent->cleanup_unsolicited_events ( self, (GAsyncReadyCallback)parent_voice_cleanup_unsolicited_events_ready, task); } /*****************************************************************************/ /* Setup unsolicited events (Voice interface) */ gboolean mm_shared_simtech_voice_setup_unsolicited_events_finish (MMIfaceModemVoice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void parent_voice_setup_unsolicited_events_ready (MMIfaceModemVoice *self, GAsyncResult *res, GTask *task) { GError *error = NULL; Private *priv; priv = get_private (MM_SHARED_SIMTECH (self)); if (!priv->iface_modem_voice_parent->setup_unsolicited_events_finish (self, res, &error)) { mm_obj_warn (self, "couldn't setup parent voice unsolicited events: %s", error->message); g_error_free (error); } /* our own setup next */ common_voice_setup_cleanup_unsolicited_events (MM_SHARED_SIMTECH (self), TRUE); g_task_return_boolean (task, TRUE); g_object_unref (task); } void mm_shared_simtech_voice_setup_unsolicited_events (MMIfaceModemVoice *self, GAsyncReadyCallback callback, gpointer user_data) { Private *priv; GTask *task; task = g_task_new (self, NULL, callback, user_data); priv = get_private (MM_SHARED_SIMTECH (self)); g_assert (priv->iface_modem_voice_parent); g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events); g_assert (priv->iface_modem_voice_parent->setup_unsolicited_events_finish); /* chain up parent's setup first */ priv->iface_modem_voice_parent->setup_unsolicited_events ( self, (GAsyncReadyCallback)parent_voice_setup_unsolicited_events_ready, task); } /*****************************************************************************/ /* In-call audio channel setup/cleanup */ gboolean mm_shared_simtech_voice_setup_in_call_audio_channel_finish (MMIfaceModemVoice *self, GAsyncResult *res, MMPort **audio_port, /* optional */ MMCallAudioFormat **audio_format, /* optional */ GError **error) { Private *priv; priv = get_private (MM_SHARED_SIMTECH (self)); if (!g_task_propagate_boolean (G_TASK (res), error)) return FALSE; if (audio_format) { if (priv->cpcmreg_support == FEATURE_SUPPORTED) { const guint rate = priv->cpcmfrm_support == FEATURE_SUPPORTED ? 16000 : 8000; *audio_format = mm_call_audio_format_new (); mm_call_audio_format_set_encoding (*audio_format, "pcm"); mm_call_audio_format_set_resolution (*audio_format, "s16le"); mm_call_audio_format_set_rate (*audio_format, rate); } else *audio_format = NULL; } if (audio_port) { if (priv->cpcmreg_support == FEATURE_SUPPORTED) *audio_port = MM_PORT (mm_base_modem_get_port_audio (MM_BASE_MODEM (self))); else *audio_port = NULL; } return TRUE; } gboolean mm_shared_simtech_voice_cleanup_in_call_audio_channel_finish (MMIfaceModemVoice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void cpcmreg_set_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { GError *error = NULL; if (!mm_base_modem_at_command_finish (self, res, &error)) g_task_return_error (task, error); else g_task_return_boolean (task, TRUE); g_object_unref (task); } static gboolean call_audio_channel_set (gpointer user_data) { GTask *task; MMBaseModem *modem; gboolean setup; task = G_TASK (user_data); setup = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (task), "setup")); if (g_task_return_error_if_cancelled (task)) { g_object_unref (task); return G_SOURCE_REMOVE; } modem = g_task_get_source_object (task); mm_base_modem_at_command (modem, setup ? "+CPCMREG=1" : "+CPCMREG=0", 3, FALSE, (GAsyncReadyCallback) cpcmreg_set_ready, task); return G_SOURCE_REMOVE; } static void common_setup_cleanup_in_call_audio_channel (MMSharedSimtech *self, gboolean setup, GAsyncReadyCallback callback, gpointer user_data) { GTask *task; Private *priv; GCancellable *cancellable; priv = get_private (MM_SHARED_SIMTECH (self)); cancellable = mm_base_modem_peek_cancellable (MM_BASE_MODEM (self)); task = g_task_new (self, cancellable, callback, user_data); g_object_set_data (G_OBJECT (task), "setup", GINT_TO_POINTER (setup)); /* Do nothing if CPCMREG isn't supported */ if (priv->cpcmreg_support != FEATURE_SUPPORTED) { g_task_return_boolean (task, TRUE); g_object_unref (task); return; } /* Some models (like SIM7600) need to wait a bit before they can accept +CPCMREG */ g_timeout_add (500, (GSourceFunc)call_audio_channel_set, task); } void mm_shared_simtech_voice_setup_in_call_audio_channel (MMIfaceModemVoice *self, GAsyncReadyCallback callback, gpointer user_data) { common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), TRUE, callback, user_data); } void mm_shared_simtech_voice_cleanup_in_call_audio_channel (MMIfaceModemVoice *self, GAsyncReadyCallback callback, gpointer user_data) { common_setup_cleanup_in_call_audio_channel (MM_SHARED_SIMTECH (self), FALSE, callback, user_data); } /*****************************************************************************/ /* Check if Voice supported (Voice interface) */ gboolean mm_shared_simtech_voice_check_support_finish (MMIfaceModemVoice *self, GAsyncResult *res, GError **error) { return g_task_propagate_boolean (G_TASK (res), error); } static void cpcmfrm_set_ready (MMBaseModem *self, GAsyncResult *res, GTask *task) { GError *error = NULL; if (!mm_base_modem_at_command_finish (self, res, &error)) g_task_return_error (task, error); else { g_task_return_boolean (task, TRUE); mm_obj_dbg (self, "USB audio 16k sample rate turned on"); } g_object_unref (task); } static void cpcmfrm_format_check_and_set_ready (MMBroadbandModem *self, GAsyncResult *res, GTask *task) { Private *priv; priv = get_private (MM_SHARED_SIMTECH (self)); priv->cpcmfrm_support = (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); if (priv->cpcmfrm_support == FEATURE_SUPPORTED) { mm_base_modem_at_command (MM_BASE_MODEM (self), "+CPCMFRM=1", 3, FALSE, (GAsyncReadyCallback) cpcmfrm_set_ready, task); } else { g_task_return_boolean (task, TRUE); g_object_unref (task); } } static void cpcmreg_format_check_ready (MMBroadbandModem *self, GAsyncResult *res, GTask *task) { Private *priv; priv = get_private (MM_SHARED_SIMTECH (self)); priv->cpcmreg_support = (mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL) ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); mm_obj_dbg (self, "modem %s USB audio control", (priv->cpcmreg_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support"); /* If USB Audio not supported, we won't check and set its formats */ if (priv->cpcmreg_support == FEATURE_SUPPORTED) { mm_base_modem_at_command (MM_BASE_MODEM (self), "+CPCMFRM=?", 3, TRUE, (GAsyncReadyCallback) cpcmfrm_format_check_and_set_ready, task); } else { priv->cpcmfrm_support = FEATURE_NOT_SUPPORTED; g_task_return_boolean (task, TRUE); g_object_unref (task); } } static void clcc_format_check_ready (MMBroadbandModem *self, GAsyncResult *res, GTask *task) { Private *priv; GError *error = NULL; const gchar *response; gboolean clcc_urc_supported = FALSE; priv = get_private (MM_SHARED_SIMTECH (self)); response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL); if (response && !mm_simtech_parse_clcc_test (response, &clcc_urc_supported, &error)) { mm_obj_dbg (self, "failed checking CLCC URC support: %s", error->message); g_clear_error (&error); } priv->clcc_urc_support = (clcc_urc_supported ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED); mm_obj_dbg (self, "modem %s +CLCC URCs", (priv->clcc_urc_support == FEATURE_SUPPORTED) ? "supports" : "doesn't support"); /* If +CLCC URC supported we won't need polling in the parent */ g_object_set (self, MM_IFACE_MODEM_VOICE_PERIODIC_CALL_LIST_CHECK_DISABLED, (priv->clcc_urc_support == FEATURE_SUPPORTED), NULL); mm_base_modem_at_command (MM_BASE_MODEM (self), "+CPCMREG=?", 5, TRUE, (GAsyncReadyCallback) cpcmreg_format_check_ready, task); } static void parent_voice_check_support_ready (MMIfaceModemVoice *self, GAsyncResult *res, GTask *task) { Private *priv; GError *error = NULL; priv = get_private (MM_SHARED_SIMTECH (self)); if (!priv->iface_modem_voice_parent->check_support_finish (self, res, &error)) { g_task_return_error (task, error); g_object_unref (task); return; } /* voice is supported, check if +CLCC URCs are available */ mm_base_modem_at_command (MM_BASE_MODEM (self), "+CLCC=?", 3, TRUE, (GAsyncReadyCallback) clcc_format_check_ready, task); } void mm_shared_simtech_voice_check_support (MMIfaceModemVoice *self, GAsyncReadyCallback callback, gpointer user_data) { Private *priv; GTask *task; task = g_task_new (self, NULL, callback, user_data); priv = get_private (MM_SHARED_SIMTECH (self)); g_assert (priv->iface_modem_voice_parent); g_assert (priv->iface_modem_voice_parent->check_support); g_assert (priv->iface_modem_voice_parent->check_support_finish); /* chain up parent's setup first */ priv->iface_modem_voice_parent->check_support ( self, (GAsyncReadyCallback)parent_voice_check_support_ready, task); } /*****************************************************************************/ static void mm_shared_simtech_default_init (MMSharedSimtechInterface *iface) { }