diff options
Diffstat (limited to 'src/mm-generic-gsm.c')
-rw-r--r-- | src/mm-generic-gsm.c | 233 |
1 files changed, 232 insertions, 1 deletions
diff --git a/src/mm-generic-gsm.c b/src/mm-generic-gsm.c index 2ffa8cfc..20e26c25 100644 --- a/src/mm-generic-gsm.c +++ b/src/mm-generic-gsm.c @@ -25,6 +25,7 @@ #include "mm-modem-gsm-card.h" #include "mm-modem-gsm-network.h" #include "mm-modem-gsm-sms.h" +#include "mm-modem-gsm-ussd.h" #include "mm-modem-simple.h" #include "mm-errors.h" #include "mm-callback-info.h" @@ -41,6 +42,7 @@ static void modem_init (MMModem *modem_class); static void modem_gsm_card_init (MMModemGsmCard *gsm_card_class); static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class); static void modem_gsm_sms_init (MMModemGsmSms *gsm_sms_class); +static void modem_gsm_ussd_init (MMModemGsmUssd *gsm_ussd_class); static void modem_simple_init (MMModemSimple *class); static void modem_location_init (MMModemLocation *class); @@ -50,6 +52,7 @@ G_DEFINE_TYPE_EXTENDED (MMGenericGsm, mm_generic_gsm, MM_TYPE_MODEM_BASE, 0, G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init) G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_SMS, modem_gsm_sms_init) G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_LOCATION, modem_location_init) + G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_USSD, modem_gsm_ussd_init) G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init)) #define MM_GENERIC_GSM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_GSM, MMGenericGsmPrivate)) @@ -110,6 +113,8 @@ typedef struct { guint32 loc_caps; gboolean loc_enabled; gboolean loc_signal; + + MMModemGsmUssdState ussd_state; } MMGenericGsmPrivate; static void get_registration_status (MMAtSerialPort *port, MMCallbackInfo *info); @@ -3603,7 +3608,7 @@ sms_send_done (MMAtSerialPort *port, } static void -sms_send (MMModemGsmSms *modem, +sms_send (MMModemGsmSms *modem, const char *number, const char *text, const char *smsc, @@ -3669,6 +3674,180 @@ mm_generic_gsm_get_best_at_port (MMGenericGsm *self, GError **error) } /*****************************************************************************/ +/* MMModemGsmUssd interface */ + +static void +ussd_update_state (MMGenericGsm *self, MMModemGsmUssdState new_state) +{ + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (self); + + if (new_state != priv->ussd_state) { + priv->ussd_state = new_state; + g_object_notify (G_OBJECT (self), MM_MODEM_GSM_USSD_STATE); + } +} + +static void +ussd_initiate_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + MMGenericGsmPrivate *priv; + 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 (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); + switch (status) { + case 0: /* no further action required */ + ussd_state = MM_MODEM_GSM_USSD_STATE_IDLE; + break; + case 1: /* Not an error but not yet implemented */ + info->error = g_error_new (MM_MODEM_ERROR, + MM_MODEM_ERROR_GENERAL, + "Further action required."); + ussd_state = MM_MODEM_GSM_USSD_STATE_USER_RESPONSE; + 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; + 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; + 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; + 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); + + if (reply) { + /* look for the reply data coding scheme */ + if (mm_options_debug ()) { + if ((start = strrchr (end, ',')) != NULL) + g_debug ("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); + } + +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); + } + mm_callback_info_schedule (info); + + if (info->modem) + ussd_update_state (MM_GENERIC_GSM (info->modem), ussd_state); +} + +static void +ussd_initiate (MMModemGsmUssd *modem, + const char *command, + MMModemStringFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + char *atc_command; + char *hex; + GByteArray *ussd_command = g_byte_array_new(); + MMGenericGsmPrivate *priv = MM_GENERIC_GSM_GET_PRIVATE (modem); + MMAtSerialPort *port; + + 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); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + /* encode to cur_charset */ + g_warn_if_fail (mm_modem_charset_byte_array_append (ussd_command, command, FALSE, priv->cur_charset)); + /* convert to hex representation */ + hex = utils_bin2hexstr (ussd_command->data, ussd_command->len); + g_byte_array_free (ussd_command, TRUE); + atc_command = g_strdup_printf ("+CUSD=1,\"%s\",15", hex); + g_free (hex); + + mm_at_serial_port_queue_command (port, atc_command, 10, ussd_initiate_done, info); + g_free (atc_command); + + ussd_update_state (MM_GENERIC_GSM (modem), MM_MODEM_GSM_USSD_STATE_ACTIVE); +} + +static void +ussd_cancel_done (MMAtSerialPort *port, + GString *response, + GError *error, + gpointer user_data) +{ + MMCallbackInfo *info = (MMCallbackInfo *) user_data; + + if (error) + info->error = g_error_copy (error); + + mm_callback_info_schedule (info); + + if (info->modem) + ussd_update_state (MM_GENERIC_GSM (info->modem), MM_MODEM_GSM_USSD_STATE_IDLE); +} + +static void +ussd_cancel (MMModemGsmUssd *modem, + MMModemFn callback, + gpointer user_data) +{ + MMCallbackInfo *info; + MMAtSerialPort *port; + + info = mm_callback_info_new (MM_MODEM (modem), callback, user_data); + + port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), &info->error); + if (!port) { + mm_callback_info_schedule (info); + return; + } + + mm_at_serial_port_queue_command (port, "+CUSD=2", 10, ussd_cancel_done, info); +} + +/*****************************************************************************/ /* MMModemSimple interface */ typedef enum { @@ -4309,6 +4488,13 @@ modem_gsm_sms_init (MMModemGsmSms *class) } static void +modem_gsm_ussd_init (MMModemGsmUssd *class) +{ + class->initiate = ussd_initiate; + class->cancel = ussd_cancel; +} + +static void modem_simple_init (MMModemSimple *class) { class->connect = simple_connect; @@ -4354,6 +4540,21 @@ mm_generic_gsm_init (MMGenericGsm *self) NULL, MM_MODEM_LOCATION_DBUS_INTERFACE); + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_USSD_STATE, + "State", + MM_MODEM_GSM_USSD_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_USSD_NETWORK_NOTIFICATION, + "NetworkNotification", + MM_MODEM_GSM_USSD_DBUS_INTERFACE); + + mm_properties_changed_signal_register_property (G_OBJECT (self), + MM_MODEM_GSM_USSD_NETWORK_REQUEST, + "NetworkRequest", + MM_MODEM_GSM_USSD_DBUS_INTERFACE); + g_signal_connect (self, "notify::" MM_MODEM_STATE, G_CALLBACK (modem_state_changed), NULL); } @@ -4377,6 +4578,9 @@ set_property (GObject *object, guint prop_id, case MM_GENERIC_GSM_PROP_LOC_ENABLED: case MM_GENERIC_GSM_PROP_LOC_SIGNAL: case MM_GENERIC_GSM_PROP_LOC_LOCATION: + case MM_GENERIC_GSM_PROP_USSD_STATE: + case MM_GENERIC_GSM_PROP_USSD_NETWORK_REQUEST: + case MM_GENERIC_GSM_PROP_USSD_NETWORK_NOTIFICATION: break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); @@ -4384,6 +4588,24 @@ set_property (GObject *object, guint prop_id, } } +static const char * +ussd_state_to_string (MMModemGsmUssdState ussd_state) +{ + switch (ussd_state) { + case MM_MODEM_GSM_USSD_STATE_IDLE: + return "idle"; + case MM_MODEM_GSM_USSD_STATE_ACTIVE: + return "active"; + case MM_MODEM_GSM_USSD_STATE_USER_RESPONSE: + return "user-response"; + default: + break; + } + + g_warning ("Unknown GSM USSD state %d", ussd_state); + return "unknown"; +} + static void get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) @@ -4457,6 +4679,15 @@ get_property (GObject *object, guint prop_id, locations = g_hash_table_new (g_direct_hash, g_direct_equal); g_value_take_boxed (value, locations); break; + case MM_GENERIC_GSM_PROP_USSD_STATE: + 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, ""); + break; + case MM_GENERIC_GSM_PROP_USSD_NETWORK_NOTIFICATION: + g_value_set_string (value, ""); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; |