Seems that launching AT+CSQF when not registered ends up timing out. AT+CSQF is meant to be the fast version to get the signal quality, and is expected to return instantly with the last cached signal quality value. But if we never got registered, there is no such cached value, so it probably waits to get the first one.
544 lines
18 KiB
C
544 lines
18 KiB
C
/* -*- 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) 2011 Ammonit Measurement GmbH
|
|
* Author: Aleksander Morgado <aleksander@lanedo.com>
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <ctype.h>
|
|
|
|
#include "mm-errors.h"
|
|
#include "mm-modem-helpers.h"
|
|
#include "mm-modem-iridium-gsm.h"
|
|
#include "mm-log.h"
|
|
|
|
/* NOTE:
|
|
* We are simulating here the Iridium modem as if it were a pure GSM device
|
|
* (even if it of course isn't). This is because the Iridium modems implement
|
|
* GSM-like AT commands and therefore we can base the Iridium plugin on the
|
|
* generic GSM implementation. So:
|
|
* - We report Access Technology as pure GSM.
|
|
* - We allow only 2G-related allowed modes.
|
|
*
|
|
*/
|
|
|
|
static void modem_init (MMModem *modem_class);
|
|
static void modem_gsm_network_init (MMModemGsmNetwork *gsm_network_class);
|
|
|
|
G_DEFINE_TYPE_EXTENDED (MMModemIridiumGsm, mm_modem_iridium_gsm, MM_TYPE_GENERIC_GSM, 0,
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)
|
|
G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_GSM_NETWORK, modem_gsm_network_init))
|
|
|
|
|
|
#define MM_MODEM_IRIDIUM_GSM_GET_PRIVATE(o) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_MODEM_IRIDIUM_GSM, MMModemIridiumGsmPrivate))
|
|
|
|
typedef struct {
|
|
/* Current allowed mode */
|
|
MMModemGsmAllowedMode allowed_mode;
|
|
} MMModemIridiumGsmPrivate;
|
|
|
|
MMModem *
|
|
mm_modem_iridium_gsm_new (const char *device,
|
|
const char *driver,
|
|
const char *plugin,
|
|
guint32 vendor,
|
|
guint32 product)
|
|
{
|
|
g_return_val_if_fail (device != NULL, NULL);
|
|
g_return_val_if_fail (driver != NULL, NULL);
|
|
g_return_val_if_fail (plugin != NULL, NULL);
|
|
|
|
return MM_MODEM (g_object_new (MM_TYPE_MODEM_IRIDIUM_GSM,
|
|
MM_MODEM_MASTER_DEVICE, device,
|
|
MM_MODEM_DRIVER, driver,
|
|
MM_MODEM_PLUGIN, plugin,
|
|
MM_MODEM_HW_VID, vendor,
|
|
MM_MODEM_HW_PID, product,
|
|
/* If up to 3 commands get timed out, mark
|
|
* the modem as removed */
|
|
MM_MODEM_BASE_MAX_TIMEOUTS, 3,
|
|
/* Allow up to 200s to setup the IP configuration
|
|
* (used by NM when launching pppd) */
|
|
MM_MODEM_IP_TIMEOUT, 200,
|
|
NULL));
|
|
}
|
|
|
|
static gboolean
|
|
grab_port (MMModem *modem,
|
|
const char *subsys,
|
|
const char *name,
|
|
MMPortType suggested_type,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
MMGenericGsm *gsm = MM_GENERIC_GSM (modem);
|
|
MMPortType ptype = MM_PORT_TYPE_IGNORED;
|
|
MMPort *port = NULL;
|
|
|
|
if (suggested_type == MM_PORT_TYPE_UNKNOWN) {
|
|
if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY))
|
|
ptype = MM_PORT_TYPE_PRIMARY;
|
|
else if (!mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_SECONDARY))
|
|
ptype = MM_PORT_TYPE_SECONDARY;
|
|
} else
|
|
ptype = suggested_type;
|
|
|
|
port = mm_generic_gsm_grab_port (gsm, subsys, name, ptype, error);
|
|
if (port && MM_IS_AT_SERIAL_PORT (port)) {
|
|
/* Set 9600 baudrate by default */
|
|
g_object_set (G_OBJECT (port),
|
|
MM_SERIAL_PORT_BAUD, 9600,
|
|
NULL);
|
|
}
|
|
|
|
return !!port;
|
|
}
|
|
|
|
static gboolean
|
|
after_atz_sleep_cb (gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
MMAtSerialPort *port;
|
|
|
|
port = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (info->modem), MM_PORT_TYPE_PRIMARY);
|
|
g_assert (port);
|
|
|
|
/* And send remaining initialization commands here, we do not care about the
|
|
* responses. Note we also don't need a power-up command!
|
|
*/
|
|
mm_at_serial_port_queue_command (port, "E0 V1", 10, NULL, NULL);
|
|
mm_at_serial_port_queue_command (port, "+CMEE=1", 10, NULL, NULL);
|
|
|
|
/* Bearer service type set to 9600bps (V.110), which behaves better than the
|
|
* default 9600bps (V.32). */
|
|
mm_at_serial_port_queue_command (port, "+CBST=71,0,1", 3, NULL, NULL);
|
|
|
|
mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), NULL, info);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
atz_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error) {
|
|
mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info);
|
|
} else {
|
|
/* Once ATZ reply is received, we need to wait a bit before going on,
|
|
* otherwise, the next commands given will receive garbage as reply
|
|
* (500ms should be enough) */
|
|
g_timeout_add (500, after_atz_sleep_cb, info);
|
|
}
|
|
}
|
|
|
|
static void
|
|
enable_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
if (error)
|
|
mm_generic_gsm_enable_complete (MM_GENERIC_GSM (info->modem), error, info);
|
|
else {
|
|
/* Just ATZ alone first */
|
|
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "Z", 3, atz_done, user_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
do_enable (MMGenericGsm *modem, MMModemFn callback, gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info;
|
|
MMAtSerialPort *primary;
|
|
|
|
primary = mm_generic_gsm_get_at_port (modem, MM_PORT_TYPE_PRIMARY);
|
|
g_assert (primary);
|
|
|
|
info = mm_callback_info_new (MM_MODEM (modem), callback, user_data);
|
|
mm_serial_port_flash (MM_SERIAL_PORT (primary), 100, FALSE, enable_flash_done, info);
|
|
}
|
|
|
|
static void
|
|
disconnect_flash_done (MMSerialPort *port, GError *error, gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error) {
|
|
/* Ignore "NO CARRIER" response when modem disconnects and any flash
|
|
* failures we might encounter. Other errors are hard errors.
|
|
*/
|
|
if ( !g_error_matches (error, MM_MODEM_CONNECT_ERROR, MM_MODEM_CONNECT_ERROR_NO_CARRIER)
|
|
&& !g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_FLASH_FAILED)) {
|
|
info->error = g_error_copy (error);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Send ATE0 after reopening, and continue */
|
|
mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "E0", 3, NULL, NULL);
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static gboolean
|
|
after_disconnect_sleep_cb (gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *)user_data;
|
|
MMAtSerialPort *primary;
|
|
GError *error = NULL;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return FALSE;
|
|
|
|
primary = mm_generic_gsm_get_at_port (MM_GENERIC_GSM (info->modem), MM_PORT_TYPE_PRIMARY);
|
|
g_assert (primary);
|
|
|
|
/* Propagate errors when reopening the port */
|
|
if (!mm_serial_port_open (MM_SERIAL_PORT (primary), &error)) {
|
|
info->error = g_error_copy (error);
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
mm_serial_port_flash (MM_SERIAL_PORT (primary), 1000, TRUE, disconnect_flash_done, info);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
do_disconnect (MMGenericGsm *gsm,
|
|
gint cid,
|
|
MMModemFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info;
|
|
MMAtSerialPort *primary;
|
|
|
|
info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data);
|
|
|
|
primary = mm_generic_gsm_get_at_port (gsm, MM_PORT_TYPE_PRIMARY);
|
|
g_assert (primary);
|
|
|
|
/* Close the serial port and wait some seconds before reopening it */
|
|
mm_serial_port_close (MM_SERIAL_PORT (primary));
|
|
mm_dbg ("Waiting some seconds before reopening the port...");
|
|
g_timeout_add_seconds (5, after_disconnect_sleep_cb, info);
|
|
}
|
|
|
|
static void
|
|
set_allowed_mode (MMGenericGsm *gsm,
|
|
MMModemGsmAllowedMode mode,
|
|
MMModemFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMModemIridiumGsmPrivate *priv = MM_MODEM_IRIDIUM_GSM_GET_PRIVATE (gsm);
|
|
MMCallbackInfo *info;
|
|
|
|
info = mm_callback_info_new (MM_MODEM (gsm), callback, user_data);
|
|
|
|
/* Allow only 2G-related allowed modes */
|
|
switch (mode) {
|
|
case MM_MODEM_GSM_ALLOWED_MODE_2G_PREFERRED:
|
|
case MM_MODEM_GSM_ALLOWED_MODE_2G_ONLY:
|
|
case MM_MODEM_GSM_ALLOWED_MODE_ANY:
|
|
priv->allowed_mode = mode;
|
|
break;
|
|
default:
|
|
info->error = g_error_new (MM_MODEM_ERROR,
|
|
MM_MODEM_ERROR_GENERAL,
|
|
"Cannot set desired allowed mode, "
|
|
"not supported");
|
|
break;
|
|
}
|
|
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
get_allowed_mode (MMGenericGsm *gsm,
|
|
MMModemUIntFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMModemIridiumGsmPrivate *priv = MM_MODEM_IRIDIUM_GSM_GET_PRIVATE (gsm);
|
|
MMCallbackInfo *info;
|
|
|
|
/* Just return cached value */
|
|
info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data);
|
|
mm_callback_info_set_result (info,
|
|
GUINT_TO_POINTER (priv->allowed_mode),
|
|
NULL);
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
get_access_technology (MMGenericGsm *gsm,
|
|
MMModemUIntFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info;
|
|
|
|
info = mm_callback_info_uint_new (MM_MODEM (gsm), callback, user_data);
|
|
mm_callback_info_set_result (info,
|
|
GUINT_TO_POINTER (MM_MODEM_GSM_ACCESS_TECH_GSM),
|
|
NULL);
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
get_csqf_done (MMAtSerialPort *port,
|
|
GString *response,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info = (MMCallbackInfo *) user_data;
|
|
gboolean parsed = FALSE;
|
|
|
|
/* If the modem has already been removed, return without
|
|
* scheduling callback */
|
|
if (mm_callback_info_check_modem_removed (info))
|
|
return;
|
|
|
|
if (error) {
|
|
info->error = g_error_copy (error);
|
|
goto done;
|
|
}
|
|
|
|
if (response && strstr (response->str, "+CSQ:")) {
|
|
/* Got valid reply */
|
|
const char *str;
|
|
int quality;
|
|
|
|
str = strstr (response->str, "+CSQ:") + 5;
|
|
|
|
/* Skip possible whitespaces after '+CSQF:' and before the response */
|
|
while (*str == ' ')
|
|
str++;
|
|
|
|
if (sscanf (str, "%d", &quality)) {
|
|
/* Normalize the quality. <rssi> is NOT given in dBs,
|
|
* given as a relative value between 0 and 5 */
|
|
quality = CLAMP (quality, 0, 5) * 100 / 5;
|
|
|
|
mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (info->modem), quality);
|
|
mm_callback_info_set_result (info, GUINT_TO_POINTER (quality), NULL);
|
|
parsed = TRUE;
|
|
}
|
|
}
|
|
|
|
if (!parsed && !info->error) {
|
|
info->error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
|
|
"Could not parse signal quality results");
|
|
}
|
|
|
|
done:
|
|
mm_callback_info_schedule (info);
|
|
}
|
|
|
|
static void
|
|
get_signal_quality (MMModemGsmNetwork *modem,
|
|
MMModemUIntFn callback,
|
|
gpointer user_data)
|
|
{
|
|
MMCallbackInfo *info;
|
|
MMAtSerialPort *port;
|
|
MMModemGsmNetwork *parent_iface;
|
|
|
|
port = mm_generic_gsm_get_best_at_port (MM_GENERIC_GSM (modem), NULL);
|
|
if (!port) {
|
|
/* Let the superclass handle it, will return cached value */
|
|
parent_iface = g_type_interface_peek_parent (MM_MODEM_GSM_NETWORK_GET_INTERFACE (modem));
|
|
parent_iface->get_signal_quality (modem, callback, user_data);
|
|
return;
|
|
}
|
|
|
|
info = mm_callback_info_uint_new (MM_MODEM (modem), callback, user_data);
|
|
|
|
/* If modem is not registered, don't even bother trying to get signal
|
|
* quality */
|
|
if (mm_modem_get_state (MM_MODEM (modem)) < MM_MODEM_STATE_REGISTERED) {
|
|
mm_dbg ("Not getting signal quality, not registered yet");
|
|
mm_generic_gsm_update_signal_quality (MM_GENERIC_GSM (info->modem), 0);
|
|
mm_callback_info_set_result (info, GUINT_TO_POINTER (0), NULL);
|
|
mm_callback_info_schedule (info);
|
|
return;
|
|
}
|
|
|
|
/* The iridium modem may have a huge delay to get signal quality if we pass
|
|
* AT+CSQ, so we'll default to use AT+CSQF, which is a fast version that
|
|
* returns right away the last signal quality value retrieved */
|
|
mm_at_serial_port_queue_command (port, "+CSQF", 3, get_csqf_done, info);
|
|
}
|
|
|
|
static void
|
|
get_sim_iccid (MMGenericGsm *modem,
|
|
MMModemStringFn callback,
|
|
gpointer callback_data)
|
|
{
|
|
/* There seems to be no way of getting an ICCID/IMSI subscriber ID within
|
|
* the Iridium AT command set, so we just skip this. */
|
|
callback (MM_MODEM (modem), "", NULL, callback_data);
|
|
}
|
|
|
|
static void
|
|
get_operator_name (MMGenericGsm *modem,
|
|
MMModemStringFn callback,
|
|
gpointer callback_data)
|
|
{
|
|
/* Only "IRIDIUM" operator name is assumed */
|
|
callback (MM_MODEM (modem), "IRIDIUM", NULL, callback_data);
|
|
}
|
|
|
|
static void
|
|
get_operator_code (MMGenericGsm *modem,
|
|
MMModemStringFn callback,
|
|
gpointer callback_data)
|
|
{
|
|
/* Only "90103" operator code is assumed */
|
|
callback (MM_MODEM (modem), "90103", NULL, callback_data);
|
|
}
|
|
|
|
static void
|
|
set_property (GObject *object,
|
|
guint prop_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
/* Do nothing... see set_property() in parent, which also does nothing */
|
|
}
|
|
|
|
static void
|
|
get_property (GObject *object,
|
|
guint prop_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (prop_id) {
|
|
case MM_GENERIC_GSM_PROP_FLOW_CONTROL_CMD:
|
|
/* Enable RTS/CTS flow control.
|
|
* Other available values:
|
|
* AT&K0: Disable flow control
|
|
* AT&K3: RTS/CTS
|
|
* AT&K4: XOFF/XON
|
|
* AT&K6: Both RTS/CTS and XOFF/XON
|
|
*/
|
|
g_value_set_string (value, "&K3");
|
|
break;
|
|
case MM_GENERIC_GSM_PROP_SMS_INDICATION_ENABLE_CMD:
|
|
/* AT+CNMO=<mode>,[<mt>[,<bm>[,<ds>[,<bfr>]]]]
|
|
* but <bm> can only be 0,
|
|
* and <ds> can only be either 0 or 1
|
|
*
|
|
* Note: Modem may return +CMS ERROR:322, which indicates Memory Full,
|
|
* not a big deal
|
|
*/
|
|
g_value_set_string (value, "+CNMI=2,1,0,0,1");
|
|
break;
|
|
case MM_GENERIC_GSM_PROP_SMS_STORAGE_LOCATION_CMD:
|
|
/* AT=CPMS=<mem1>[,<mem2>[,<mem3>]]
|
|
* Only "SM" is allowed in all 3 message storages
|
|
*
|
|
* Note: Modem may return +CMS ERROR:322, which indicates Memory Full,
|
|
* not a big deal
|
|
*/
|
|
g_value_set_string (value, "+CPMS=\"SM\",\"SM\",\"SM\"");
|
|
break;
|
|
case MM_GENERIC_GSM_PROP_PS_NETWORK_SUPPORTED:
|
|
/* We do not support PS network in Iridium, only CS */
|
|
g_value_set_boolean (value, FALSE);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
|
|
static void
|
|
modem_init (MMModem *modem_class)
|
|
{
|
|
modem_class->grab_port = grab_port;
|
|
}
|
|
|
|
static void
|
|
modem_gsm_network_init (MMModemGsmNetwork *network_class)
|
|
{
|
|
network_class->get_signal_quality = get_signal_quality;
|
|
}
|
|
|
|
static void
|
|
mm_modem_iridium_gsm_init (MMModemIridiumGsm *self)
|
|
{
|
|
MMModemIridiumGsmPrivate *priv = MM_MODEM_IRIDIUM_GSM_GET_PRIVATE (self);
|
|
|
|
/* Set defaults */
|
|
priv->allowed_mode = MM_MODEM_GSM_ALLOWED_MODE_ANY;
|
|
}
|
|
|
|
static void
|
|
mm_modem_iridium_gsm_class_init (MMModemIridiumGsmClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
MMGenericGsmClass *gsm_class = MM_GENERIC_GSM_CLASS (klass);
|
|
|
|
g_type_class_add_private (object_class, sizeof (MMModemIridiumGsmPrivate));
|
|
|
|
object_class->get_property = get_property;
|
|
object_class->set_property = set_property;
|
|
|
|
g_object_class_override_property (object_class,
|
|
MM_GENERIC_GSM_PROP_FLOW_CONTROL_CMD,
|
|
MM_GENERIC_GSM_FLOW_CONTROL_CMD);
|
|
|
|
g_object_class_override_property (object_class,
|
|
MM_GENERIC_GSM_PROP_SMS_INDICATION_ENABLE_CMD,
|
|
MM_GENERIC_GSM_SMS_INDICATION_ENABLE_CMD);
|
|
|
|
g_object_class_override_property (object_class,
|
|
MM_GENERIC_GSM_PROP_SMS_STORAGE_LOCATION_CMD,
|
|
MM_GENERIC_GSM_SMS_STORAGE_LOCATION_CMD);
|
|
|
|
g_object_class_override_property (object_class,
|
|
MM_GENERIC_GSM_PROP_PS_NETWORK_SUPPORTED,
|
|
MM_GENERIC_GSM_PS_NETWORK_SUPPORTED);
|
|
|
|
gsm_class->do_enable = do_enable;
|
|
gsm_class->do_disconnect = do_disconnect;
|
|
gsm_class->get_access_technology = get_access_technology;
|
|
gsm_class->set_allowed_mode = set_allowed_mode;
|
|
gsm_class->get_allowed_mode = get_allowed_mode;
|
|
gsm_class->get_sim_iccid = get_sim_iccid;
|
|
gsm_class->get_operator_name = get_operator_name;
|
|
gsm_class->get_operator_code = get_operator_code;
|
|
}
|
|
|