diff options
author | Aleksander Morgado <aleksander@aleksander.es> | 2013-11-20 15:41:33 +0100 |
---|---|---|
committer | Aleksander Morgado <aleksander@aleksander.es> | 2014-02-13 13:40:01 +0100 |
commit | 6f235b9948cf1ff1d11a7a339d3d1ffc40171dcd (patch) | |
tree | ede65682bbc0684c6e07bd8293be3fa10595be9b /src/mm-port-serial-at.c | |
parent | 0d1602bf0f861eed0af68a51610a3a6d3e139d0b (diff) |
ports: rename 'MMAtSerialPort' to 'MMPortSerialAt'
Diffstat (limited to 'src/mm-port-serial-at.c')
-rw-r--r-- | src/mm-port-serial-at.c | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/src/mm-port-serial-at.c b/src/mm-port-serial-at.c new file mode 100644 index 00000000..a8f95133 --- /dev/null +++ b/src/mm-port-serial-at.c @@ -0,0 +1,649 @@ +/* -*- 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) 2008 - 2009 Novell, Inc. + * Copyright (C) 2009 Red Hat, Inc. + */ + +#define _GNU_SOURCE /* for strcasestr() */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#include "mm-port-serial-at.h" +#include "mm-log.h" + +G_DEFINE_TYPE (MMPortSerialAt, mm_port_serial_at, MM_TYPE_PORT_SERIAL) + +#define MM_PORT_SERIAL_AT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_PORT_SERIAL_AT, MMPortSerialAtPrivate)) + +enum { + PROP_0, + PROP_REMOVE_ECHO, + PROP_INIT_SEQUENCE_ENABLED, + PROP_INIT_SEQUENCE, + PROP_SEND_LF, + LAST_PROP +}; + +typedef struct { + /* Response parser data */ + MMPortSerialAtResponseParserFn response_parser_fn; + gpointer response_parser_user_data; + GDestroyNotify response_parser_notify; + + GSList *unsolicited_msg_handlers; + + MMPortSerialAtFlag flags; + + /* Properties */ + gboolean remove_echo; + guint init_sequence_enabled; + gchar **init_sequence; + gboolean send_lf; +} MMPortSerialAtPrivate; + +/*****************************************************************************/ + +gchar * +mm_port_serial_at_quote_string (const char *string) +{ + int len, i; + gchar *quoted, *pos; + + if (string == NULL) + len = 0; + else + len = strlen (string); + quoted = g_malloc (3 + 3 * len); /* worst case */ + + pos = quoted; + *pos++ = '"'; + for (i = 0 ; i < len; i++) { + if (string[i] < 0x20 || string[i] == '"' || string[i] == '\\') + pos += sprintf (pos, "\\%02X", string[i]); + else + *pos++ = string[i]; + } + *pos++ = '"'; + *pos++ = '\0'; + + return quoted; +} + +void +mm_port_serial_at_set_response_parser (MMPortSerialAt *self, + MMPortSerialAtResponseParserFn fn, + gpointer user_data, + GDestroyNotify notify) +{ + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + + g_return_if_fail (MM_IS_PORT_SERIAL_AT (self)); + + if (priv->response_parser_notify) + priv->response_parser_notify (priv->response_parser_user_data); + + priv->response_parser_fn = fn; + priv->response_parser_user_data = user_data; + priv->response_parser_notify = notify; +} + +void +mm_port_serial_at_remove_echo (GByteArray *response) +{ + guint i; + + if (response->len <= 2) + return; + + for (i = 0; i < (response->len - 1); i++) { + /* If there is any content before the first + * <CR><LF>, assume it's echo or garbage, and skip it */ + if (response->data[i] == '\r' && response->data[i + 1] == '\n') { + if (i > 0) + g_byte_array_remove_range (response, 0, i); + /* else, good, we're already started with <CR><LF> */ + break; + } + } +} + +static gboolean +parse_response (MMPortSerial *port, GByteArray *response, GError **error) +{ + MMPortSerialAt *self = MM_PORT_SERIAL_AT (port); + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + gboolean found; + GString *string; + + g_return_val_if_fail (priv->response_parser_fn != NULL, FALSE); + + /* Remove echo */ + if (priv->remove_echo) + mm_port_serial_at_remove_echo (response); + + /* Construct the string that AT-parsing functions expect */ + string = g_string_sized_new (response->len + 1); + g_string_append_len (string, (const char *) response->data, response->len); + + /* Parse it */ + found = priv->response_parser_fn (priv->response_parser_user_data, string, error); + + /* And copy it back into the response array after the parser has removed + * matches and cleaned it up. + */ + if (response->len) + g_byte_array_remove_range (response, 0, response->len); + g_byte_array_append (response, (const guint8 *) string->str, string->len); + g_string_free (string, TRUE); + return found; +} + +static gsize +handle_response (MMPortSerial *port, + GByteArray *response, + GError *error, + GCallback callback, + gpointer callback_data) +{ + MMPortSerialAt *self = MM_PORT_SERIAL_AT (port); + MMPortSerialAtResponseFn response_callback = (MMPortSerialAtResponseFn) callback; + GString *string; + + /* Convert to a string and call the callback */ + string = g_string_sized_new (response->len + 1); + g_string_append_len (string, (const char *) response->data, response->len); + response_callback (self, string, error, callback_data); + g_string_free (string, TRUE); + + return response->len; +} + +/*****************************************************************************/ + +typedef struct { + GRegex *regex; + MMPortSerialAtUnsolicitedMsgFn callback; + gboolean enable; + gpointer user_data; + GDestroyNotify notify; +} MMAtUnsolicitedMsgHandler; + +static gint +unsolicited_msg_handler_cmp (MMAtUnsolicitedMsgHandler *handler, + GRegex *regex) +{ + return g_strcmp0 (g_regex_get_pattern (handler->regex), + g_regex_get_pattern (regex)); +} + +void +mm_port_serial_at_add_unsolicited_msg_handler (MMPortSerialAt *self, + GRegex *regex, + MMPortSerialAtUnsolicitedMsgFn callback, + gpointer user_data, + GDestroyNotify notify) +{ + GSList *existing; + MMAtUnsolicitedMsgHandler *handler; + MMPortSerialAtPrivate *priv; + + g_return_if_fail (MM_IS_PORT_SERIAL_AT (self)); + g_return_if_fail (regex != NULL); + + priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + + existing = g_slist_find_custom (priv->unsolicited_msg_handlers, + regex, + (GCompareFunc)unsolicited_msg_handler_cmp); + if (existing) { + handler = existing->data; + /* We OVERWRITE any existing one, so if any context data existing, free it */ + if (handler->notify) + handler->notify (handler->user_data); + } else { + handler = g_slice_new (MMAtUnsolicitedMsgHandler); + priv->unsolicited_msg_handlers = g_slist_append (priv->unsolicited_msg_handlers, handler); + handler->regex = g_regex_ref (regex); + } + + handler->callback = callback; + handler->enable = TRUE; + handler->user_data = user_data; + handler->notify = notify; +} + +void +mm_port_serial_at_enable_unsolicited_msg_handler (MMPortSerialAt *self, + GRegex *regex, + gboolean enable) +{ + GSList *existing; + MMAtUnsolicitedMsgHandler *handler; + MMPortSerialAtPrivate *priv; + + g_return_if_fail (MM_IS_PORT_SERIAL_AT (self)); + g_return_if_fail (regex != NULL); + + priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + + existing = g_slist_find_custom (priv->unsolicited_msg_handlers, + regex, + (GCompareFunc)unsolicited_msg_handler_cmp); + if (existing) { + handler = existing->data; + handler->enable = enable; + } +} + +static gboolean +remove_eval_cb (const GMatchInfo *match_info, + GString *result, + gpointer user_data) +{ + int *result_len = (int *) user_data; + int start; + int end; + + if (g_match_info_fetch_pos (match_info, 0, &start, &end)) + *result_len -= (end - start); + + return FALSE; +} + +static void +parse_unsolicited (MMPortSerial *port, GByteArray *response) +{ + MMPortSerialAt *self = MM_PORT_SERIAL_AT (port); + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + GSList *iter; + + /* Remove echo */ + if (priv->remove_echo) + mm_port_serial_at_remove_echo (response); + + for (iter = priv->unsolicited_msg_handlers; iter; iter = iter->next) { + MMAtUnsolicitedMsgHandler *handler = (MMAtUnsolicitedMsgHandler *) iter->data; + GMatchInfo *match_info; + gboolean matches; + + if (!handler->enable) + continue; + + matches = g_regex_match_full (handler->regex, + (const char *) response->data, + response->len, + 0, 0, &match_info, NULL); + if (handler->callback) { + while (g_match_info_matches (match_info)) { + handler->callback (self, match_info, handler->user_data); + g_match_info_next (match_info, NULL); + } + } + + g_match_info_free (match_info); + + if (matches) { + /* Remove matches */ + char *str; + int result_len = response->len; + + str = g_regex_replace_eval (handler->regex, + (const char *) response->data, + response->len, + 0, 0, + remove_eval_cb, &result_len, NULL); + + g_byte_array_remove_range (response, 0, response->len); + g_byte_array_append (response, (const guint8 *) str, result_len); + g_free (str); + } + } +} + +/*****************************************************************************/ + +static GByteArray * +at_command_to_byte_array (const char *command, gboolean is_raw, gboolean send_lf) +{ + GByteArray *buf; + int cmdlen; + + g_return_val_if_fail (command != NULL, NULL); + + cmdlen = strlen (command); + buf = g_byte_array_sized_new (cmdlen + 4); + + if (!is_raw) { + /* Make sure there's an AT in the front */ + if (!g_str_has_prefix (command, "AT")) + g_byte_array_append (buf, (const guint8 *) "AT", 2); + } + + g_byte_array_append (buf, (const guint8 *) command, cmdlen); + + if (!is_raw) { + /* Make sure there's a trailing carriage return */ + if ((cmdlen == 0) || + (command[cmdlen - 1] != '\r' && (cmdlen == 1 || command[cmdlen - 2] != '\r'))) + g_byte_array_append (buf, (const guint8 *) "\r", 1); + if (send_lf) { + /* Make sure there's a trailing line-feed */ + if ((cmdlen == 0) || + (command[cmdlen - 1] != '\n' && (cmdlen == 1 || command[cmdlen - 2] != '\n'))) + g_byte_array_append (buf, (const guint8 *) "\n", 1); + } + } + + return buf; +} + +void +mm_port_serial_at_queue_command (MMPortSerialAt *self, + const char *command, + guint32 timeout_seconds, + gboolean is_raw, + GCancellable *cancellable, + MMPortSerialAtResponseFn callback, + gpointer user_data) +{ + GByteArray *buf; + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_PORT_SERIAL_AT (self)); + g_return_if_fail (command != NULL); + + buf = at_command_to_byte_array (command, is_raw, priv->send_lf); + g_return_if_fail (buf != NULL); + + mm_port_serial_queue_command (MM_PORT_SERIAL (self), + buf, + TRUE, + timeout_seconds, + cancellable, + (MMSerialResponseFn) callback, + user_data); +} + +void +mm_port_serial_at_queue_command_cached (MMPortSerialAt *self, + const char *command, + guint32 timeout_seconds, + gboolean is_raw, + GCancellable *cancellable, + MMPortSerialAtResponseFn callback, + gpointer user_data) +{ + GByteArray *buf; + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_PORT_SERIAL_AT (self)); + g_return_if_fail (command != NULL); + + buf = at_command_to_byte_array (command, is_raw, priv->send_lf); + g_return_if_fail (buf != NULL); + + mm_port_serial_queue_command_cached (MM_PORT_SERIAL (self), + buf, + TRUE, + timeout_seconds, + cancellable, + (MMSerialResponseFn) callback, + user_data); +} + +static void +debug_log (MMPortSerial *port, const char *prefix, const char *buf, gsize len) +{ + static GString *debug = NULL; + const char *s; + + if (!debug) + debug = g_string_sized_new (256); + + g_string_append (debug, prefix); + g_string_append (debug, " '"); + + s = buf; + while (len--) { + if (g_ascii_isprint (*s)) + g_string_append_c (debug, *s); + else if (*s == '\r') + g_string_append (debug, "<CR>"); + else if (*s == '\n') + g_string_append (debug, "<LF>"); + else + g_string_append_printf (debug, "\\%u", (guint8) (*s & 0xFF)); + + s++; + } + + g_string_append_c (debug, '\''); + mm_dbg ("(%s): %s", mm_port_get_device (MM_PORT (port)), debug->str); + g_string_truncate (debug, 0); +} + +void +mm_port_serial_at_set_flags (MMPortSerialAt *self, MMPortSerialAtFlag flags) +{ + g_return_if_fail (self != NULL); + g_return_if_fail (MM_IS_PORT_SERIAL_AT (self)); + g_return_if_fail (flags <= (MM_PORT_SERIAL_AT_FLAG_PRIMARY | + MM_PORT_SERIAL_AT_FLAG_SECONDARY | + MM_PORT_SERIAL_AT_FLAG_PPP | + MM_PORT_SERIAL_AT_FLAG_GPS_CONTROL)); + + MM_PORT_SERIAL_AT_GET_PRIVATE (self)->flags = flags; +} + +MMPortSerialAtFlag +mm_port_serial_at_get_flags (MMPortSerialAt *self) +{ + g_return_val_if_fail (self != NULL, MM_PORT_SERIAL_AT_FLAG_NONE); + g_return_val_if_fail (MM_IS_PORT_SERIAL_AT (self), MM_PORT_SERIAL_AT_FLAG_NONE); + + return MM_PORT_SERIAL_AT_GET_PRIVATE (self)->flags; +} + +/*****************************************************************************/ + +void +mm_port_serial_at_run_init_sequence (MMPortSerialAt *self) +{ + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + guint i; + + if (!priv->init_sequence) + return; + + mm_dbg ("(%s): running init sequence...", mm_port_get_device (MM_PORT (self))); + + /* Just queue the init commands, don't wait for reply */ + for (i = 0; priv->init_sequence[i]; i++) { + mm_port_serial_at_queue_command (self, + priv->init_sequence[i], + 3, + FALSE, + NULL, + NULL, + NULL); + } +} + +static void +config (MMPortSerial *self) +{ + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + + if (priv->init_sequence_enabled) + mm_port_serial_at_run_init_sequence (MM_PORT_SERIAL_AT (self)); +} + +/*****************************************************************************/ + +MMPortSerialAt * +mm_port_serial_at_new (const char *name) +{ + return MM_PORT_SERIAL_AT (g_object_new (MM_TYPE_PORT_SERIAL_AT, + MM_PORT_DEVICE, name, + MM_PORT_SUBSYS, MM_PORT_SUBSYS_TTY, + MM_PORT_TYPE, MM_PORT_TYPE_AT, + NULL)); +} + +static void +mm_port_serial_at_init (MMPortSerialAt *self) +{ + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + + /* By default, remove echo */ + priv->remove_echo = TRUE; + /* By default, run init sequence during first port opening */ + priv->init_sequence_enabled = TRUE; + + /* By default, don't send line feed */ + priv->send_lf = FALSE; +} + +static void +set_property (GObject *object, guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_REMOVE_ECHO: + priv->remove_echo = g_value_get_boolean (value); + break; + case PROP_INIT_SEQUENCE_ENABLED: + priv->init_sequence_enabled = g_value_get_boolean (value); + break; + case PROP_INIT_SEQUENCE: + g_strfreev (priv->init_sequence); + priv->init_sequence = g_value_dup_boxed (value); + break; + case PROP_SEND_LF: + priv->send_lf = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +get_property (GObject *object, guint prop_id, + GValue *value, GParamSpec *pspec) +{ + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (object); + + switch (prop_id) { + case PROP_REMOVE_ECHO: + g_value_set_boolean (value, priv->remove_echo); + break; + case PROP_INIT_SEQUENCE_ENABLED: + g_value_set_boolean (value, priv->init_sequence_enabled); + break; + case PROP_INIT_SEQUENCE: + g_value_set_boxed (value, priv->init_sequence); + break; + case PROP_SEND_LF: + g_value_set_boolean (value, priv->send_lf); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +finalize (GObject *object) +{ + MMPortSerialAt *self = MM_PORT_SERIAL_AT (object); + MMPortSerialAtPrivate *priv = MM_PORT_SERIAL_AT_GET_PRIVATE (self); + + while (priv->unsolicited_msg_handlers) { + MMAtUnsolicitedMsgHandler *handler = (MMAtUnsolicitedMsgHandler *) priv->unsolicited_msg_handlers->data; + + if (handler->notify) + handler->notify (handler->user_data); + + g_regex_unref (handler->regex); + g_slice_free (MMAtUnsolicitedMsgHandler, handler); + priv->unsolicited_msg_handlers = g_slist_delete_link (priv->unsolicited_msg_handlers, + priv->unsolicited_msg_handlers); + } + + if (priv->response_parser_notify) + priv->response_parser_notify (priv->response_parser_user_data); + + g_strfreev (priv->init_sequence); + + G_OBJECT_CLASS (mm_port_serial_at_parent_class)->finalize (object); +} + +static void +mm_port_serial_at_class_init (MMPortSerialAtClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + MMPortSerialClass *serial_class = MM_PORT_SERIAL_CLASS (klass); + + g_type_class_add_private (object_class, sizeof (MMPortSerialAtPrivate)); + + /* Virtual methods */ + object_class->set_property = set_property; + object_class->get_property = get_property; + object_class->finalize = finalize; + + serial_class->parse_unsolicited = parse_unsolicited; + serial_class->parse_response = parse_response; + serial_class->handle_response = handle_response; + serial_class->debug_log = debug_log; + serial_class->config = config; + + g_object_class_install_property + (object_class, PROP_REMOVE_ECHO, + g_param_spec_boolean (MM_PORT_SERIAL_AT_REMOVE_ECHO, + "Remove echo", + "Built-in echo removal should be applied", + TRUE, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_INIT_SEQUENCE_ENABLED, + g_param_spec_boolean (MM_PORT_SERIAL_AT_INIT_SEQUENCE_ENABLED, + "Init sequence enabled", + "Whether the initialization sequence should be run", + TRUE, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_INIT_SEQUENCE, + g_param_spec_boxed (MM_PORT_SERIAL_AT_INIT_SEQUENCE, + "Init sequence", + "Initialization sequence", + G_TYPE_STRV, + G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_SEND_LF, + g_param_spec_boolean (MM_PORT_SERIAL_AT_SEND_LF, + "Send LF", + "Send line-feed at the end of each AT command sent", + FALSE, + G_PARAM_READWRITE)); +} |