diff options
author | Aleksander Morgado <aleksander@lanedo.com> | 2011-12-01 13:11:56 +0100 |
---|---|---|
committer | Aleksander Morgado <aleksander@lanedo.com> | 2012-03-15 14:14:31 +0100 |
commit | 52db9b9035f1a39b3d38a6df8c5aea996b653c42 (patch) | |
tree | b6fb2786e3761529536eff62ef0ddaedc00a6927 | |
parent | 7b12da9169cca545af1d8295859160551ad13d7f (diff) |
base-modem-at: refactor AT sequence/command handling
Make a tight connection between the action of sending AT commands, either single
or in a sequence, with the MMBaseModem object owning the port. This direct
relation allows sending commands without specifying which port to use, so that
the modem object can get the best port at each time, and handling all that in a
single common place.
The original mm-at API has also been modified so that when a single command is
sent, a constant string is returned. We are allowed to return constant strings
in mm_base_modem_at_command_finish() because the string itself is owned by the
GSimpleAsyncResult, and hence, alive enough time. The GSimpleAsyncResult is
completely disposed only after the async call is fully completed.
Same reasoning behind the GVariant returned in the AT sequences; it should not
be owned by the caller, it's a transfer-none in introspection terms.
-rw-r--r-- | src/Makefile.am | 2 | ||||
-rw-r--r-- | src/mm-base-modem-at.c | 561 | ||||
-rw-r--r-- | src/mm-base-modem-at.h | 152 |
3 files changed, 715 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 17606ddb..eb0dd3da 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -112,6 +112,8 @@ modem_manager_SOURCES = \ mm-plugin-manager.h \ mm-sim.h \ mm-sim.c \ + mm-base-modem-at.h \ + mm-base-modem-at.c \ mm-base-modem.h \ mm-base-modem.c \ mm-iface-modem.h \ diff --git a/src/mm-base-modem-at.c b/src/mm-base-modem-at.c new file mode 100644 index 00000000..e5ee4f39 --- /dev/null +++ b/src/mm-base-modem-at.c @@ -0,0 +1,561 @@ +/* -*- 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 Aleksander Morgado <aleksander@gnu.org> + */ + +#include <glib.h> +#include <glib-object.h> + +#include <ModemManager.h> + +#include "mm-base-modem-at.h" +#include "mm-errors-types.h" + +static MMAtSerialPort * +base_modem_at_get_best_port (MMBaseModem *self, + GError **error) +{ + MMAtSerialPort *port; + + /* Decide which port to use */ + port = mm_base_modem_get_port_primary (self); + g_assert (port); + if (mm_port_get_connected (MM_PORT (port))) { + /* If primary port is connected, check if we can get the secondary + * port */ + port = mm_base_modem_get_port_secondary (self); + if (!port) { + /* If we don't have a secondary port, we need to halt the AT + * operation */ + g_set_error (error, + MM_CORE_ERROR, + MM_CORE_ERROR_CONNECTED, + "No AT port available to run command"); + } + } + + return port; +} + +static gboolean +abort_async_if_port_unusable (MMBaseModem *self, + MMAtSerialPort *port, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GError *error = NULL; + + /* Ensure we don't try to use a connected port */ + if (mm_port_get_connected (MM_PORT (port))) { + g_simple_async_report_error_in_idle ( + G_OBJECT (self), + callback, + user_data, + MM_CORE_ERROR, + MM_CORE_ERROR_CONNECTED, + "Cannot run sequence: port is connected"); + return FALSE; + } + + /* Ensure we have a port open during the sequence */ + if (!mm_serial_port_open (MM_SERIAL_PORT (port), &error)) { + g_simple_async_report_error_in_idle ( + G_OBJECT (self), + callback, + user_data, + MM_CORE_ERROR, + MM_CORE_ERROR_CONNECTED, + "Cannot run sequence: '%s'", + error->message); + g_error_free (error); + return FALSE; + } + + return TRUE; +} + +/*****************************************************************************/ +/* AT sequence handling */ + +typedef struct { + MMBaseModem *self; + MMAtSerialPort *port; + GCancellable *cancellable; + const MMBaseModemAtCommand *current; + const MMBaseModemAtCommand *sequence; + GSimpleAsyncResult *simple; + gpointer response_processor_context; + GDestroyNotify response_processor_context_free; + GVariant *result; +} AtSequenceContext; + +static void +at_sequence_context_free (AtSequenceContext *ctx) +{ + mm_serial_port_close (MM_SERIAL_PORT (ctx->port)); + g_object_unref (ctx->port); + + if (ctx->response_processor_context && + ctx->response_processor_context_free) + ctx->response_processor_context_free (ctx->response_processor_context); + if (ctx->self) + g_object_unref (ctx->self); + if (ctx->cancellable) + g_object_unref (ctx->cancellable); + if (ctx->result) + g_variant_unref (ctx->result); + if (ctx->simple) + g_object_unref (ctx->simple); + g_free (ctx); +} + +GVariant * +mm_base_modem_at_sequence_in_port_finish (MMBaseModem *self, + GAsyncResult *res, + gpointer *response_processor_context, + GError **error) +{ + AtSequenceContext *ctx; + + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), + error)) + return NULL; + + ctx = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); + if (response_processor_context) + /* transfer none, no need to free the context ourselves, if + * we gave a response_processor_context_free callback */ + *response_processor_context = ctx->response_processor_context; + + /* transfer-none! (so that we can ignore it) */ + return ctx->result; +} + +static void +at_sequence_parse_response (MMAtSerialPort *port, + GString *response, + GError *error, + AtSequenceContext *ctx) +{ + GVariant *result = NULL; + GError *result_error = NULL; + gboolean continue_sequence; + GSimpleAsyncResult *simple; + + /* Cancelled? */ + if (g_cancellable_is_cancelled (ctx->cancellable)) { + g_simple_async_result_set_error (ctx->simple, + MM_CORE_ERROR, + MM_CORE_ERROR_CANCELLED, + "AT sequence was cancelled"); + g_simple_async_result_complete (ctx->simple); + at_sequence_context_free (ctx); + return; + } + + if (!ctx->current->response_processor) + /* No need to process response, go on to next command */ + continue_sequence = TRUE; + else { + /* Response processor will tell us if we need to keep on the sequence */ + continue_sequence = !ctx->current->response_processor ( + ctx->self, + ctx->response_processor_context, + ctx->current->command, + response->str, + error, + &result, + &result_error); + /* Were we told to abort the sequence? */ + if (result_error) { + g_assert (result == NULL); + g_simple_async_result_take_error (ctx->simple, result_error); + g_simple_async_result_complete (ctx->simple); + at_sequence_context_free (ctx); + return; + } + } + + if (continue_sequence) { + g_assert (result == NULL); + ctx->current++; + if (ctx->current->command) { + /* Schedule the next command in the probing group */ + if (ctx->current->allow_cached) + mm_at_serial_port_queue_command_cached ( + ctx->port, + ctx->current->command, + ctx->current->timeout, + (MMAtSerialResponseFn)at_sequence_parse_response, + ctx); + else + mm_at_serial_port_queue_command ( + ctx->port, + ctx->current->command, + ctx->current->timeout, + (MMAtSerialResponseFn)at_sequence_parse_response, + ctx); + return; + } + + /* On last command, end. */ + } + + /* If we got a response, set it as result */ + if (result) + /* transfer-full */ + ctx->result = result; + + /* Set the whole context as result, in order to pass the response + * processor context during finish(). We do remove the simple async result + * from the context as well, so that we control its last unref. */ + simple = ctx->simple; + ctx->simple = NULL; + g_simple_async_result_set_op_res_gpointer ( + simple, + ctx, + (GDestroyNotify)at_sequence_context_free); + + /* And complete. The whole context is owned by the result, and it will + * be freed when completed. */ + g_simple_async_result_complete (simple); + g_object_unref (simple); +} + +void +mm_base_modem_at_sequence_in_port (MMBaseModem *self, + MMAtSerialPort *port, + const MMBaseModemAtCommand *sequence, + gpointer response_processor_context, + GDestroyNotify response_processor_context_free, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + AtSequenceContext *ctx; + + /* Ensure that we have an open port */ + if (!abort_async_if_port_unusable (self, port, callback, user_data)) + return; + + /* Setup context */ + ctx = g_new0 (AtSequenceContext, 1); + ctx->self = g_object_ref (self); + ctx->port = g_object_ref (port); + ctx->cancellable = (cancellable ? + g_object_ref (cancellable) : + NULL); + ctx->simple = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + mm_base_modem_at_sequence_in_port); + ctx->current = ctx->sequence = sequence; + ctx->response_processor_context = response_processor_context; + + /* Go on with the first one in the sequence */ + mm_at_serial_port_queue_command ( + ctx->port, + ctx->current->command, + ctx->current->timeout, + (MMAtSerialResponseFn)at_sequence_parse_response, + ctx); +} + +GVariant * +mm_base_modem_at_sequence_finish (MMBaseModem *self, + GAsyncResult *res, + gpointer *response_processor_context, + GError **error) +{ + return (mm_base_modem_at_sequence_in_port_finish ( + self, + res, + response_processor_context, + error)); +} + +void +mm_base_modem_at_sequence (MMBaseModem *self, + const MMBaseModemAtCommand *sequence, + gpointer response_processor_context, + GDestroyNotify response_processor_context_free, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMAtSerialPort *port; + GError *error = NULL; + + /* No port given, so we'll try to guess which is best */ + port = base_modem_at_get_best_port (self, &error); + if (!port) { + g_assert (error != NULL); + g_simple_async_report_take_gerror_in_idle (G_OBJECT (self), + callback, + user_data, + error); + return; + } + + mm_base_modem_at_sequence_in_port ( + self, + port, + sequence, + response_processor_context, + response_processor_context_free, + cancellable, + callback, + user_data); +} + +/*****************************************************************************/ +/* Response processor helpers */ + +gboolean +mm_base_modem_response_processor_string (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + const GError *error, + GVariant **result, + GError **result_error) +{ + if (error) { + *result_error = g_error_copy (error); + return FALSE; + } + + *result = g_variant_new_string (response); + return TRUE; +} + +gboolean +mm_base_modem_response_processor_no_result (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + const GError *error, + GVariant **result, + GError **result_error) +{ + if (error) { + *result_error = g_error_copy (error); + return FALSE; + } + + *result = NULL; + return TRUE; +} + +gboolean +mm_base_modem_response_processor_no_result_continue (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + const GError *error, + GVariant **result, + GError **result_error) +{ + if (error) + *result_error = g_error_copy (error); + + /* Return FALSE so that we keep on with the next steps in the sequence */ + return FALSE; +} + +/*****************************************************************************/ +/* Single AT command handling */ + +typedef struct { + MMBaseModem *self; + MMAtSerialPort *port; + GCancellable *cancellable; + GSimpleAsyncResult *result; +} AtCommandContext; + +static void +at_command_context_free (AtCommandContext *ctx) +{ + if (ctx->self) + g_object_unref (ctx->self); + if (ctx->cancellable) + g_object_unref (ctx->cancellable); + mm_serial_port_close (MM_SERIAL_PORT (ctx->port)); + g_object_unref (ctx->port); + g_object_unref (ctx->result); + g_free (ctx); +} + +const gchar * +mm_base_modem_at_command_in_port_finish (MMBaseModem *self, + GAsyncResult *res, + GError **error) +{ + if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error)) + return NULL; + + return g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (res)); +} + +static void +at_command_parse_response (MMAtSerialPort *port, + GString *response, + GError *error, + AtCommandContext *ctx) +{ + /* Cancelled? */ + if (g_cancellable_is_cancelled (ctx->cancellable)) + g_simple_async_result_set_error (ctx->result, + MM_CORE_ERROR, + MM_CORE_ERROR_CANCELLED, + "AT command was cancelled"); + + /* Error coming from the serial port? */ + else if (error) + g_simple_async_result_set_from_error (ctx->result, error); + + /* Valid string response */ + else if (response && response->str) + g_simple_async_result_set_op_res_gpointer (ctx->result, + g_strdup (response->str), + g_free); + + /* No response */ + else + g_simple_async_result_set_op_res_gpointer (ctx->result, NULL, NULL); + + g_simple_async_result_complete (ctx->result); + at_command_context_free (ctx); +} + +void +mm_base_modem_at_command_in_port (MMBaseModem *self, + MMAtSerialPort *port, + const gchar *command, + guint timeout, + gboolean allow_cached, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + AtCommandContext *ctx; + + /* Ensure that we have an open port */ + if (!abort_async_if_port_unusable (self, port, callback, user_data)) + return; + + ctx = g_new (AtCommandContext, 1); + ctx->self = g_object_ref (self); + ctx->port = g_object_ref (port); + ctx->cancellable = (cancellable ? + g_object_ref (cancellable) : + NULL); + ctx->result = g_simple_async_result_new (G_OBJECT (self), + callback, + user_data, + mm_base_modem_at_command_in_port); + + /* Go on with the command */ + if (allow_cached) + mm_at_serial_port_queue_command_cached ( + port, + command, + timeout, + (MMAtSerialResponseFn)at_command_parse_response, + ctx); + else + mm_at_serial_port_queue_command ( + port, + command, + timeout, + (MMAtSerialResponseFn)at_command_parse_response, + ctx); +} + +const gchar * +mm_base_modem_at_command_finish (MMBaseModem *self, + GAsyncResult *res, + GError **error) +{ + return mm_base_modem_at_command_in_port_finish (self, res, error); +} + +void +mm_base_modem_at_command (MMBaseModem *self, + const gchar *command, + guint timeout, + gboolean allow_cached, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + MMAtSerialPort *port; + GError *error = NULL; + + /* No port given, so we'll try to guess which is best */ + port = base_modem_at_get_best_port (self, &error); + if (!port) { + g_assert (error != NULL); + g_simple_async_report_take_gerror_in_idle (G_OBJECT (self), + callback, + user_data, + error); + return; + } + + mm_base_modem_at_command_in_port (self, + port, + command, + timeout, + allow_cached, + cancellable, + callback, + user_data); +} + +/*****************************************************************************/ +/* Single AT command handling, completely ignoring the response */ + +void +mm_base_modem_at_command_in_port_ignore_reply (MMBaseModem *self, + MMAtSerialPort *port, + const gchar *command, + guint timeout) +{ + /* Use the async method without callback, so that we ensure port + * gets opened and such, if needed */ + mm_base_modem_at_command_in_port (self, + port, + command, + timeout, + FALSE, + NULL, /* cancellable */ + NULL, /* callback */ + NULL); /* user_data */ +} + +void +mm_base_modem_at_command_ignore_reply (MMBaseModem *self, + const gchar *command, + guint timeout) +{ + MMAtSerialPort *port; + + /* No port given, so we'll try to guess which is best */ + port = base_modem_at_get_best_port (self, NULL); + if (!port) + /* No valid port, and we ignore replies, so just exit. */ + return; + + mm_base_modem_at_command_in_port_ignore_reply (self, port, command, timeout); +} diff --git a/src/mm-base-modem-at.h b/src/mm-base-modem-at.h new file mode 100644 index 00000000..0b39107c --- /dev/null +++ b/src/mm-base-modem-at.h @@ -0,0 +1,152 @@ +/* -*- 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 Aleksander Morgado <aleksander@gnu.org> + */ + +#ifndef MM_BASE_MODEM_AT_H +#define MM_BASE_MODEM_AT_H + +#include <gio/gio.h> + +#include "mm-base-modem.h" +#include "mm-at-serial-port.h" + +/* + * The expected result depends on the specific operation, so the GVariant + * created by the response processor needs to match the one expected in + * finish(). + * + * TRUE must be returned when the operation is to be considered successful, + * and a result may be given. + * + * FALSE must be returned when: + * - A GError is propagated into result_error, which will be treated as a + * critical error and therefore the operation will be aborted. + * - When no result_error is given, to instruct the operation to go on with + * the next scheduled command. + * + * This setup, therefore allows: + * - Running a single command and processing its result. + * - Running a set of N commands, providing a global result after all have + * been executed. + * - Running a set of N commands out of M (N<M), where the global result is + * obtained without having executed all configured commands. + */ +typedef gboolean (* MMBaseModemAtResponseProcessor) (MMBaseModem *self, + gpointer response_processor_context, + const gchar *command, + const gchar *response, + const GError *error, + GVariant **result, + GError **result_error); + +/* Struct to configure AT command operations */ +typedef struct { + /* The AT command */ + gchar *command; + /* Timeout of the command, in seconds */ + guint timeout; + /* Flag to allow cached replies */ + gboolean allow_cached; + /* The response processor */ + MMBaseModemAtResponseProcessor response_processor; +} MMBaseModemAtCommand; + +/* AT sequence handling */ +void mm_base_modem_at_sequence (MMBaseModem *self, + const MMBaseModemAtCommand *sequence, + gpointer response_processor_context, + GDestroyNotify response_processor_context_free, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GVariant *mm_base_modem_at_sequence_finish (MMBaseModem *self, + GAsyncResult *res, + gpointer *response_processor_context, + GError **error); + +void mm_base_modem_at_sequence_in_port (MMBaseModem *self, + MMAtSerialPort *port, + const MMBaseModemAtCommand *sequence, + gpointer response_processor_context, + GDestroyNotify response_processor_context_free, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +GVariant *mm_base_modem_at_sequence_in_port_finish (MMBaseModem *self, + GAsyncResult *res, + gpointer *response_processor_context, + GError **error); + +/* Common helper response processors */ + +/* Every string received as response, will be set as result */ +gboolean mm_base_modem_response_processor_string (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + const GError *error, + GVariant **result, + GError **result_error); +/* Just abort if error without result set, otherwise finish sequence */ +gboolean mm_base_modem_response_processor_no_result (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + const GError *error, + GVariant **result, + GError **result_error); +/* Just abort if error without result set, otherwise continue sequence */ +gboolean mm_base_modem_response_processor_no_result_continue (MMBaseModem *self, + gpointer none, + const gchar *command, + const gchar *response, + const GError *error, + GVariant **result, + GError **result_error); + + +/* Single AT command, returning the whole response string */ +void mm_base_modem_at_command (MMBaseModem *self, + const gchar *command, + guint timeout, + gboolean allow_cached, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +const gchar *mm_base_modem_at_command_finish (MMBaseModem *self, + GAsyncResult *res, + GError **error); + +void mm_base_modem_at_command_in_port (MMBaseModem *self, + MMAtSerialPort *port, + const gchar *command, + guint timeout, + gboolean allow_cached, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +const gchar *mm_base_modem_at_command_in_port_finish (MMBaseModem *self, + GAsyncResult *res, + GError **error); + +/* Fire and forget an AT command */ +void mm_base_modem_at_command_ignore_reply (MMBaseModem *self, + const gchar *command, + guint timeout); +void mm_base_modem_at_command_in_port_ignore_reply (MMBaseModem *self, + MMAtSerialPort *port, + const gchar *command, + guint timeout); + +#endif /* MM_BASE_MODEM_AT_H */ |