aboutsummaryrefslogtreecommitdiff
path: root/src/mm-port-serial-at.c
diff options
context:
space:
mode:
authorAleksander Morgado <aleksander@aleksander.es>2013-11-20 15:41:33 +0100
committerAleksander Morgado <aleksander@aleksander.es>2014-02-13 13:40:01 +0100
commit6f235b9948cf1ff1d11a7a339d3d1ffc40171dcd (patch)
treeede65682bbc0684c6e07bd8293be3fa10595be9b /src/mm-port-serial-at.c
parent0d1602bf0f861eed0af68a51610a3a6d3e139d0b (diff)
ports: rename 'MMAtSerialPort' to 'MMPortSerialAt'
Diffstat (limited to 'src/mm-port-serial-at.c')
-rw-r--r--src/mm-port-serial-at.c649
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));
+}