diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/mm-generic-gsm.c | 243 | ||||
-rw-r--r-- | src/mm-generic-gsm.h | 2 |
2 files changed, 184 insertions, 61 deletions
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index bc718d12..d814c57c 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -120,7 +120,11 @@ typedef struct { gboolean loc_enabled; gboolean loc_signal; + gboolean ussd_enabled; + MMCallbackInfo *pending_ussd_info; MMModemGsmUssdState ussd_state; + char *ussd_network_request; + char *ussd_network_notification; /* SMS */ GHashTable *sms_present; @@ -174,6 +178,10 @@ static void cmti_received (MMAtSerialPort *port, GMatchInfo *info, gpointer user_data); +static void cusd_received (MMAtSerialPort *port, + GMatchInfo *info, + gpointer user_data); + #define GS_HASH_TAG "get-sms" static GValue *simple_string_value (const char *str); static GValue *simple_uint_value (guint32 i); @@ -844,6 +852,10 @@ mm_generic_gsm_grab_port (MMGenericGsm *self, mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, cmti_received, self, NULL); g_regex_unref (regex); + regex = g_regex_new ("\\r\\n\\+CUSD:\\s*(.*)\\r\\n", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, NULL); + mm_at_serial_port_add_unsolicited_msg_handler (MM_AT_SERIAL_PORT (port), regex, cusd_received, self, NULL); + g_regex_unref (regex); + if (ptype == MM_PORT_TYPE_PRIMARY) { priv->primary = MM_AT_SERIAL_PORT (port); if (!priv->data) { @@ -1412,6 +1424,21 @@ cind_cb (MMAtSerialPort *port, } } +static void +cusd_enable_cb (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + if (error) { + mm_warn ("(%s): failed to enable USSD notifications.", + mm_port_get_device (MM_PORT (port))); + return; + } + + MM_GENERIC_GSM_GET_PRIVATE (user_data)->ussd_enabled = TRUE; +} + void mm_generic_gsm_enable_complete (MMGenericGsm *self, GError *error, @@ -1462,6 +1489,9 @@ mm_generic_gsm_enable_complete (MMGenericGsm *self, mm_at_serial_port_queue_command (priv->primary, cmd, 3, NULL, NULL); g_free (cmd); + /* Enable USSD notifications */ + mm_at_serial_port_queue_command (priv->primary, "+CUSD=1", 3, cusd_enable_cb, self); + mm_at_serial_port_queue_command (priv->primary, "+CIND=?", 3, cind_cb, self); /* Try one more time to get the SIM ID */ @@ -1689,6 +1719,11 @@ disable_flash_done (MMSerialPort *port, mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CREG=0", 3, NULL, NULL); mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CGREG=0", 3, NULL, NULL); + if (priv->ussd_enabled) { + mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CUSD=0", 3, NULL, NULL); + priv->ussd_enabled = FALSE; + } + if (priv->cmer_enabled) { mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "+CMER=0", 3, NULL, NULL); @@ -1732,6 +1767,8 @@ disable (MMModem *modem, mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (modem)); + mm_generic_gsm_ussd_cleanup (MM_GENERIC_GSM (modem)); + if (priv->poll_id) { g_source_remove (priv->poll_id); priv->poll_id = 0; @@ -4676,93 +4713,171 @@ ussd_update_state (MMGenericGsm *self, MMModemGsmUssdState new_state) } } +void +mm_generic_gsm_ussd_cleanup (MMGenericGsm *self) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (priv->pending_ussd_info) { + /* And schedule the callback */ + g_clear_error (&priv->pending_ussd_info->error); + priv->pending_ussd_info->error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "USSD session terminated without reply."); + mm_callback_info_schedule (priv->pending_ussd_info); + priv->pending_ussd_info = NULL; + } + + ussd_update_state (self, MM_MODEM_GSM_USSD_STATE_IDLE); + + g_free (priv->ussd_network_request); + priv->ussd_network_request = NULL; + g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_REQUEST); + + g_free (priv->ussd_network_notification); + priv->ussd_network_notification = NULL; + g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION); +} + +static char * +decode_ussd_response (const char *reply, MMModemCharset cur_charset) +{ + char **items, **iter, *p; + char *str = NULL; + gint encoding = -1; + + /* Look for the first ',' */ + p = strchr (reply, ','); + if (p == NULL) + return NULL; + + items = g_strsplit_set (p + 1, " ,", -1); + for (iter = items; iter && *iter; iter++) { + if (*iter[0] == '\0') + continue; + if (str == NULL) + str = *iter; + else if (encoding == -1) { + encoding = atoi (*iter); + mm_dbg ("USSD data coding scheme %d", encoding); + break; /* All done */ + } + } + + /* Strip quotes */ + if (str[0] == '"') + str++; + p = strchr (str, '"'); + if (p) + *p = '\0'; + + /* FIXME: actually use the given encoding scheme */ + return mm_modem_charset_hex_to_utf8 (str, cur_charset); +} + static void -ussd_send_done (MMAtSerialPort *port, - GString *response, - GError *error, - gpointer user_data) +cusd_received (MMAtSerialPort *port, + GMatchInfo *info, + gpointer user_data) { - MMCallbackInfo *info = (MMCallbackInfo *) user_data; - MMGenericGsmPrivate *priv; + MMGenericGsm *self = MM_GENERIC_GSM (user_data); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + GError *error = NULL; gint status; - gboolean parsed = FALSE; MMModemGsmUssdState ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; - const char *str, *start = NULL, *end = NULL; char *reply = NULL, *converted; - /* If the modem has already been removed, return without - * scheduling callback */ - if (mm_callback_info_check_modem_removed (info)) + reply = g_match_info_fetch (info, 1); + if (!reply || !isdigit (*reply)) { + mm_warn ("Recieved invalid USSD response: '%s'", reply ? reply : "(none)"); + g_free (reply); return; - - if (error) { - info->error = g_error_copy (error); - goto done; } - priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); - ussd_state = priv->ussd_state; - - str = mm_strip_tag (response->str, "+CUSD:"); - if (!str || !isdigit (*str)) - goto done; - - status = g_ascii_digit_value (*str); + status = g_ascii_digit_value (*reply); switch (status) { case 0: /* no further action required */ - ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + converted = decode_ussd_response (reply, priv->cur_charset); + if (priv->pending_ussd_info) { + /* Response to the user's request */ + mm_callback_info_set_result (priv->pending_ussd_info, converted, g_free); + } else { + /* Network-initiated USSD-Notify */ + g_free (priv->ussd_network_notification); + priv->ussd_network_notification = converted; + g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION); + } break; case 1: /* further action required */ ussd_state = MM_MODEM_GSM_USSD_STATE_USER_RESPONSE; + converted = decode_ussd_response (reply, priv->cur_charset); + if (priv->pending_ussd_info) { + mm_callback_info_set_result (priv->pending_ussd_info, converted, g_free); + } else { + /* Network-initiated USSD-Request */ + g_free (priv->ussd_network_request); + priv->ussd_network_request = converted; + g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_NETWORK_REQUEST); + } break; case 2: - info->error = g_error_new (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "USSD terminated by network."); - ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "USSD terminated by network."); break; case 4: - info->error = g_error_new (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Operiation not supported."); - ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + error = g_error_new_literal (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Operation not supported."); break; default: - info->error = g_error_new (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Unknown USSD reply %d", status); - ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Unhandled USSD reply %d", status); break; } - if (info->error) - goto done; - /* look for the reply */ - if ((start = strchr (str, '"')) && (end = strrchr (str, '"')) && (start != end)) - reply = g_strndup (start + 1, end - start -1); + ussd_update_state (self, ussd_state); - if (reply) { - /* look for the reply data coding scheme */ - if ((start = strrchr (end, ',')) != NULL) - mm_dbg ("USSD data coding scheme %d", atoi (start + 1)); - - converted = mm_modem_charset_hex_to_utf8 (reply, priv->cur_charset); - mm_callback_info_set_result (info, converted, g_free); - parsed = TRUE; - g_free (reply); + if (priv->pending_ussd_info) { + if (error) + priv->pending_ussd_info->error = g_error_copy (error); + mm_callback_info_schedule (priv->pending_ussd_info); + priv->pending_ussd_info = NULL; } -done: - if (!parsed && !info->error) { - info->error = g_error_new (MM_MODEM_ERROR, - MM_MODEM_ERROR_GENERAL, - "Could not parse USSD reply '%s'", - response->str); + g_clear_error (&error); + g_free (reply); +} + +static void +ussd_send_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + + /* If the modem has already been removed, return without + * scheduling callback */ + if (mm_callback_info_check_modem_removed (info)) + return; + + priv = MM_GENERIC_GSM_GET_PRIVATE (info->modem); + + if (error) { + /* Some immediate error happened when sending the USSD request */ + info->error = g_error_copy (error); + priv->pending_ussd_info = NULL; + mm_callback_info_schedule (info); + + ussd_update_state (MM_GENERIC_GSM (info->modem), MM_MODEM_GSM_USSD_STATE_IDLE); } - mm_callback_info_schedule (info); - if (info->modem) - ussd_update_state (MM_GENERIC_GSM (info->modem), ussd_state); + /* Otherwise if no error wait for the response to show up via the + * unsolicited response code. + */ } static void @@ -4779,6 +4894,8 @@ ussd_send (MMModemGsmUssd *modem, MMAtSerialPort *port; gboolean success; + g_warn_if_fail (priv->pending_ussd_info == NULL); + info = mm_callback_info_string_new (MM_MODEM (modem), callback, user_data); port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); @@ -4787,6 +4904,9 @@ ussd_send (MMModemGsmUssd *modem, return; } + /* Cache the callback info since the response is an unsolicited one */ + priv->pending_ussd_info = info; + /* encode to cur_charset */ success = mm_modem_charset_byte_array_append (ussd_command, command, FALSE, priv->cur_charset); g_warn_if_fail (success == TRUE); @@ -5737,10 +5857,10 @@ get_property (GObject *object, guint prop_id, g_value_set_string (value, ussd_state_to_string (priv->ussd_state)); break; case MM_GENERIC_GSM_PROP_USSD_NETWORK_REQUEST: - g_value_set_string (value, ""); + g_value_set_string (value, priv->ussd_network_request); break; case MM_GENERIC_GSM_PROP_USSD_NETWORK_NOTIFICATION: - g_value_set_string (value, ""); + g_value_set_string (value, priv->ussd_network_notification); break; case MM_GENERIC_GSM_PROP_FLOW_CONTROL_CMD: /* By default, try to set XOFF/XON flow control */ @@ -5769,6 +5889,7 @@ finalize (GObject *object) MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (object); mm_generic_gsm_pending_registration_stop (MM_GENERIC_GSM (object)); + mm_generic_gsm_ussd_cleanup (MM_GENERIC_GSM (object)); if (priv->pin_check_timeout) { g_source_remove (priv->pin_check_timeout); diff --git a/src/mm-generic-gsm.h b/src/mm-generic-gsm.h index 5afab3c8..57b65bf8 100644 --- a/src/mm-generic-gsm.h +++ b/src/mm-generic-gsm.h @@ -159,6 +159,8 @@ MMModem *mm_generic_gsm_new (const char *device, void mm_generic_gsm_pending_registration_stop (MMGenericGsm *modem); +void mm_generic_gsm_ussd_cleanup (MMGenericGsm *modem); + gint mm_generic_gsm_get_cid (MMGenericGsm *modem); void mm_generic_gsm_set_reg_status (MMGenericGsm *modem, |