3 * AT chat library with GLib integration
5 * Copyright (C) 2008-2009 Intel Corporation. All rights reserved.
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 as
9 * published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
32 #include "ringbuffer.h"
33 #include "gatresult.h"
36 /* #define WRITE_SCHEDULER_DEBUG 1 */
38 static void g_at_chat_wakeup_writer(GAtChat *chat);
41 PARSER_STATE_IDLE = 0,
42 PARSER_STATE_INITIAL_CR,
43 PARSER_STATE_INITIAL_LF,
44 PARSER_STATE_RESPONSE,
45 PARSER_STATE_TERMINATOR_CR,
46 PARSER_STATE_RESPONSE_COMPLETE,
47 PARSER_STATE_GUESS_MULTILINE_RESPONSE,
50 PARSER_STATE_PDU_COMPLETE,
52 PARSER_STATE_PROMPT_COMPLETE
59 GAtResultFunc callback;
61 GDestroyNotify notify;
64 struct at_notify_node {
66 GAtNotifyFunc callback;
68 GDestroyNotify notify;
77 gint ref_count; /* Ref count */
78 guint next_cmd_id; /* Next command id */
79 guint next_notify_id; /* Next notify id */
80 guint read_watch; /* GSource read id, 0 if none */
81 guint write_watch; /* GSource write id, 0 if none */
82 GIOChannel *channel; /* channel */
83 GQueue *command_queue; /* Command queue */
84 guint cmd_bytes_written; /* bytes written from cmd */
85 GHashTable *notify_list; /* List of notification reg */
86 GAtDisconnectFunc user_disconnect; /* user disconnect func */
87 gpointer user_disconnect_data; /* user disconnect data */
88 struct ring_buffer *buf; /* Current read buffer */
89 guint read_so_far; /* Number of bytes processed */
90 gboolean disconnecting; /* Whether we're disconnecting */
91 enum chat_state state; /* Current chat state */
93 char *pdu_notify; /* Unsolicited Resp w/ PDU */
94 GSList *response_lines; /* char * lines of the response */
95 char *wakeup; /* command sent to wakeup modem */
96 gdouble inactivity_time; /* Period of inactivity */
97 guint wakeup_timeout; /* How long to wait for resp */
98 GTimer *wakeup_timer; /* Keep track of elapsed time */
101 static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
103 const struct at_notify_node *node = a;
104 guint id = GPOINTER_TO_UINT(b);
115 static void at_notify_node_destroy(struct at_notify_node *node)
118 node->notify(node->user_data);
123 static void at_notify_destroy(struct at_notify *notify)
125 g_slist_foreach(notify->nodes, (GFunc) at_notify_node_destroy, NULL);
129 static gint at_command_compare_by_id(gconstpointer a, gconstpointer b)
131 const struct at_command *command = a;
132 guint id = GPOINTER_TO_UINT(b);
134 if (command->id < id)
137 if (command->id > id)
143 static struct at_command *at_command_create(const char *cmd,
144 const char **prefix_list,
147 GDestroyNotify notify)
149 struct at_command *c;
151 char **prefixes = NULL;
154 int num_prefixes = 0;
157 while (prefix_list[num_prefixes])
160 prefixes = g_new(char *, num_prefixes + 1);
162 for (i = 0; i < num_prefixes; i++)
163 prefixes[i] = strdup(prefix_list[i]);
165 prefixes[num_prefixes] = NULL;
168 c = g_try_new0(struct at_command, 1);
174 c->cmd = g_try_new(char, len + 2);
181 memcpy(c->cmd, cmd, len);
183 /* If we have embedded '\r' then this is a command expecting a prompt
184 * from the modem. Embed Ctrl-Z at the very end automatically
186 if (strchr(cmd, '\r'))
191 c->cmd[len+1] = '\0';
193 c->prefixes = prefixes;
195 c->user_data = user_data;
201 static void at_command_destroy(struct at_command *cmd)
204 cmd->notify(cmd->user_data);
206 g_strfreev(cmd->prefixes);
211 static void g_at_chat_cleanup(GAtChat *chat)
213 struct at_command *c;
215 ring_buffer_free(chat->buf);
218 /* Cleanup pending commands */
219 while ((c = g_queue_pop_head(chat->command_queue)))
220 at_command_destroy(c);
222 g_queue_free(chat->command_queue);
223 chat->command_queue = NULL;
225 /* Cleanup any response lines we have pending */
226 g_slist_foreach(chat->response_lines, (GFunc)g_free, NULL);
227 g_slist_free(chat->response_lines);
228 chat->response_lines = NULL;
230 /* Cleanup registered notifications */
231 g_hash_table_destroy(chat->notify_list);
232 chat->notify_list = NULL;
234 if (chat->pdu_notify) {
235 g_free(chat->pdu_notify);
236 chat->pdu_notify = NULL;
240 g_free(chat->wakeup);
244 if (chat->wakeup_timer) {
245 g_timer_destroy(chat->wakeup_timer);
246 chat->wakeup_timer = 0;
250 static void read_watcher_destroy_notify(GAtChat *chat)
252 chat->read_watch = 0;
254 if (chat->disconnecting)
257 chat->channel = NULL;
259 g_at_chat_cleanup(chat);
261 if (chat->user_disconnect)
262 chat->user_disconnect(chat->user_disconnect_data);
265 static void write_watcher_destroy_notify(GAtChat *chat)
267 chat->write_watch = 0;
270 static void at_notify_call_callback(gpointer data, gpointer user_data)
272 struct at_notify_node *node = data;
273 GAtResult *result = user_data;
275 node->callback(result, node->user_data);
278 static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
281 struct at_notify *notify;
284 gboolean ret = FALSE;
287 g_hash_table_iter_init(&iter, chat->notify_list);
289 result.final_or_pdu = 0;
291 while (g_hash_table_iter_next(&iter, &key, &value)) {
295 if (!g_str_has_prefix(line, key))
299 chat->pdu_notify = line;
300 chat->state = PARSER_STATE_PDU;
305 result.lines = g_slist_prepend(NULL, line);
307 g_slist_foreach(notify->nodes, at_notify_call_callback,
313 g_slist_free(result.lines);
315 chat->state = PARSER_STATE_IDLE;
321 static void g_at_chat_finish_command(GAtChat *p, gboolean ok,
324 struct at_command *cmd = g_queue_pop_head(p->command_queue);
326 /* Cannot happen, but lets be paranoid */
333 p->response_lines = g_slist_reverse(p->response_lines);
335 result.final_or_pdu = final;
336 result.lines = p->response_lines;
338 cmd->callback(ok, &result, cmd->user_data);
341 g_slist_foreach(p->response_lines, (GFunc)g_free, NULL);
342 g_slist_free(p->response_lines);
343 p->response_lines = NULL;
347 at_command_destroy(cmd);
349 p->cmd_bytes_written = 0;
351 if (g_queue_peek_head(p->command_queue))
352 g_at_chat_wakeup_writer(p);
355 struct terminator_info {
356 const char *terminator;
361 static struct terminator_info terminator_table[] = {
363 { "ERROR", -1, FALSE },
364 { "NO DIALTONE", -1, FALSE },
365 { "BUSY", -1, FALSE },
366 { "NO CARRIER", -1, FALSE },
367 { "CONNECT", -1, TRUE },
368 { "NO ANSWER", -1, FALSE },
369 { "+CMS ERROR:", 11, FALSE },
370 { "+CME ERROR:", 11, FALSE },
371 { "+EXT ERROR:", 11, FALSE }
374 static gboolean g_at_chat_handle_command_response(GAtChat *p,
375 struct at_command *cmd,
379 int size = sizeof(terminator_table) / sizeof(struct terminator_info);
381 p->state = PARSER_STATE_IDLE;
383 for (i = 0; i < size; i++) {
384 struct terminator_info *info = &terminator_table[i];
386 if (info->len == -1 && !strcmp(line, info->terminator)) {
387 g_at_chat_finish_command(p, info->success, line);
392 !strncmp(line, info->terminator, info->len)) {
393 g_at_chat_finish_command(p, info->success, line);
401 for (i = 0; cmd->prefixes[i]; i++)
402 if (g_str_has_prefix(line, cmd->prefixes[i]))
409 if (!(p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
410 p->state = PARSER_STATE_GUESS_MULTILINE_RESPONSE;
412 p->response_lines = g_slist_prepend(p->response_lines,
418 static void have_line(GAtChat *p)
420 /* We're not going to copy terminal <CR><LF> */
421 unsigned int len = p->read_so_far - 2;
423 struct at_command *cmd;
425 /* If we have preceding <CR><LF> modify the len */
426 if ((p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) == 0)
429 /* Make sure we have terminal null */
430 str = g_try_new(char, len + 1);
433 ring_buffer_drain(p->buf, p->read_so_far);
437 if ((p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) == 0)
438 ring_buffer_drain(p->buf, 2);
439 ring_buffer_read(p->buf, str, len);
440 ring_buffer_drain(p->buf, 2);
444 /* Check for echo, this should not happen, but lets be paranoid */
445 if (!strncmp(str, "AT", 2) == TRUE)
448 cmd = g_queue_peek_head(p->command_queue);
450 if (cmd && p->cmd_bytes_written == strlen(cmd->cmd) &&
451 g_at_chat_handle_command_response(p, cmd, str))
454 if (g_at_chat_match_notify(p, str) == TRUE)
458 /* No matches & no commands active, ignore line */
460 p->state = PARSER_STATE_IDLE;
463 static void have_pdu(GAtChat *p)
465 unsigned int len = p->read_so_far - 2;
468 struct at_notify *notify;
473 pdu = g_try_new(char, len + 1);
476 ring_buffer_drain(p->buf, p->read_so_far);
480 ring_buffer_read(p->buf, pdu, len);
481 ring_buffer_drain(p->buf, 2);
485 result.lines = g_slist_prepend(NULL, p->pdu_notify);
486 result.final_or_pdu = pdu;
488 g_hash_table_iter_init(&iter, p->notify_list);
490 while (g_hash_table_iter_next(&iter, &key, &value)) {
494 if (!g_str_has_prefix(p->pdu_notify, prefix))
500 g_slist_foreach(notify->nodes, at_notify_call_callback,
504 g_slist_free(result.lines);
507 g_free(p->pdu_notify);
508 p->pdu_notify = NULL;
513 p->state = PARSER_STATE_IDLE;
516 static inline void parse_char(GAtChat *chat, char byte)
518 switch (chat->state) {
519 case PARSER_STATE_IDLE:
521 chat->state = PARSER_STATE_INITIAL_CR;
522 else if (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) {
524 chat->state = PARSER_STATE_PROMPT;
526 chat->state = PARSER_STATE_RESPONSE;
530 case PARSER_STATE_INITIAL_CR:
532 chat->state = PARSER_STATE_INITIAL_LF;
533 else if (byte != '\r' && /* Echo & no <CR><LF>?! */
534 (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
535 chat->state = PARSER_STATE_RESPONSE;
536 else if (byte != '\r')
537 chat->state = PARSER_STATE_IDLE;
540 case PARSER_STATE_INITIAL_LF:
542 chat->state = PARSER_STATE_TERMINATOR_CR;
543 else if (byte == '>')
544 chat->state = PARSER_STATE_PROMPT;
546 chat->state = PARSER_STATE_RESPONSE;
549 case PARSER_STATE_RESPONSE:
551 chat->state = PARSER_STATE_TERMINATOR_CR;
554 case PARSER_STATE_TERMINATOR_CR:
556 chat->state = PARSER_STATE_RESPONSE_COMPLETE;
558 chat->state = PARSER_STATE_IDLE;
561 case PARSER_STATE_GUESS_MULTILINE_RESPONSE:
563 chat->state = PARSER_STATE_INITIAL_CR;
565 chat->state = PARSER_STATE_RESPONSE;
568 case PARSER_STATE_PDU:
570 chat->state = PARSER_STATE_PDU_CR;
573 case PARSER_STATE_PDU_CR:
575 chat->state = PARSER_STATE_PDU_COMPLETE;
578 case PARSER_STATE_PROMPT:
580 chat->state = PARSER_STATE_PROMPT_COMPLETE;
582 chat->state = PARSER_STATE_RESPONSE;
584 case PARSER_STATE_RESPONSE_COMPLETE:
585 case PARSER_STATE_PDU_COMPLETE:
587 /* This really shouldn't happen */
593 static void new_bytes(GAtChat *p)
595 unsigned int len = ring_buffer_len(p->buf);
596 unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
597 unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
599 while (p->read_so_far < len) {
605 if (p->read_so_far == wrap) {
606 buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
610 if (p->state == PARSER_STATE_RESPONSE_COMPLETE) {
611 len -= p->read_so_far;
612 wrap -= p->read_so_far;
617 } else if (p->state == PARSER_STATE_PDU_COMPLETE) {
618 len -= p->read_so_far;
619 wrap -= p->read_so_far;
624 } else if (p->state == PARSER_STATE_INITIAL_CR) {
625 len -= p->read_so_far - 1;
626 wrap -= p->read_so_far - 1;
628 ring_buffer_drain(p->buf, p->read_so_far - 1);
631 } else if (p->state == PARSER_STATE_PROMPT_COMPLETE) {
632 len -= p->read_so_far;
633 wrap -= p->read_so_far;
635 g_at_chat_wakeup_writer(p);
637 ring_buffer_drain(p->buf, p->read_so_far);
643 if (p->state == PARSER_STATE_IDLE && p->read_so_far > 0) {
644 ring_buffer_drain(p->buf, p->read_so_far);
649 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
653 GAtChat *chat = data;
657 gsize total_read = 0;
659 if (cond & G_IO_NVAL)
662 /* Regardless of condition, try to read all the data available */
666 toread = ring_buffer_avail_no_wrap(chat->buf);
668 /* We're going to start overflowing the buffer
669 * this cannot happen under normal circumstances, so probably
670 * the channel is getting garbage, drop off
673 if (chat->state == PARSER_STATE_RESPONSE)
676 err = G_IO_ERROR_AGAIN;
680 buf = ring_buffer_write_ptr(chat->buf);
682 err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
684 total_read += rbytes;
687 ring_buffer_write_advance(chat->buf, rbytes);
689 } while (err == G_IO_ERROR_NONE && rbytes > 0);
694 if (cond & (G_IO_HUP | G_IO_ERR))
697 if (err == G_IO_ERROR_NONE && rbytes == 0)
700 if (err != G_IO_ERROR_NONE && err != G_IO_ERROR_AGAIN)
706 static gboolean wakeup_no_response(gpointer user)
708 GAtChat *chat = user;
710 g_at_chat_finish_command(chat, FALSE, NULL);
715 static gboolean can_write_data(GIOChannel *channel, GIOCondition cond,
718 GAtChat *chat = data;
719 struct at_command *cmd;
725 gboolean wakeup_first = FALSE;
726 #ifdef WRITE_SCHEDULER_DEBUG
730 if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
733 /* Grab the first command off the queue and write as
734 * much of it as we can
736 cmd = g_queue_peek_head(chat->command_queue);
738 /* For some reason command queue is empty, cancel write watcher */
742 len = strlen(cmd->cmd);
744 /* For some reason write watcher fired, but we've already
745 * written the entire command out to the io channel,
746 * cancel write watcher
748 if (chat->cmd_bytes_written >= len)
752 if (!chat->wakeup_timer) {
754 chat->wakeup_timer = g_timer_new();
756 } else if (g_timer_elapsed(chat->wakeup_timer, NULL) >
757 chat->inactivity_time)
761 if (chat->cmd_bytes_written == 0 && wakeup_first == TRUE) {
762 cmd = at_command_create(chat->wakeup, NULL, NULL, NULL, NULL);
767 g_queue_push_head(chat->command_queue, cmd);
769 len = strlen(chat->wakeup);
771 g_timeout_add(chat->wakeup_timeout, wakeup_no_response,
775 towrite = len - chat->cmd_bytes_written;
777 cr = strchr(cmd->cmd + chat->cmd_bytes_written, '\r');
780 towrite = cr - (cmd->cmd + chat->cmd_bytes_written) + 1;
782 #ifdef WRITE_SCHEDULER_DEBUG
789 err = g_io_channel_write(chat->channel,
790 cmd->cmd + chat->cmd_bytes_written,
791 #ifdef WRITE_SCHEDULER_DEBUG
798 if (err != G_IO_ERROR_NONE) {
799 g_at_chat_shutdown(chat);
803 chat->cmd_bytes_written += bytes_written;
805 if (bytes_written < towrite)
808 /* Full command submitted, update timer */
809 if (chat->wakeup_timer)
810 g_timer_start(chat->wakeup_timer);
815 static void g_at_chat_wakeup_writer(GAtChat *chat)
817 if (chat->write_watch != 0)
820 chat->write_watch = g_io_add_watch_full(chat->channel,
822 G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
823 can_write_data, chat,
824 (GDestroyNotify)write_watcher_destroy_notify);
827 GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
835 chat = g_try_new0(GAtChat, 1);
840 chat->next_cmd_id = 1;
841 chat->next_notify_id = 1;
844 chat->buf = ring_buffer_new(4096);
849 chat->command_queue = g_queue_new();
851 if (!chat->command_queue)
854 chat->notify_list = g_hash_table_new_full(g_str_hash, g_str_equal,
855 g_free, (GDestroyNotify)at_notify_destroy);
857 if (g_io_channel_set_encoding(channel, NULL, NULL) !=
861 io_flags = g_io_channel_get_flags(channel);
863 io_flags |= G_IO_FLAG_NONBLOCK;
865 if (g_io_channel_set_flags(channel, io_flags, NULL) !=
869 g_io_channel_set_close_on_unref(channel, TRUE);
871 chat->channel = channel;
872 chat->read_watch = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT,
873 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
875 (GDestroyNotify)read_watcher_destroy_notify);
881 ring_buffer_free(chat->buf);
883 if (chat->command_queue)
884 g_queue_free(chat->command_queue);
886 if (chat->notify_list)
887 g_hash_table_destroy(chat->notify_list);
893 GAtChat *g_at_chat_ref(GAtChat *chat)
898 g_atomic_int_inc(&chat->ref_count);
903 void g_at_chat_unref(GAtChat *chat)
910 is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
913 g_at_chat_shutdown(chat);
915 g_at_chat_cleanup(chat);
920 gboolean g_at_chat_shutdown(GAtChat *chat)
922 if (chat->channel == NULL)
925 chat->disconnecting = TRUE;
927 if (chat->read_watch)
928 g_source_remove(chat->read_watch);
930 if (chat->write_watch)
931 g_source_remove(chat->write_watch);
936 gboolean g_at_chat_set_disconnect_function(GAtChat *chat,
937 GAtDisconnectFunc disconnect, gpointer user_data)
942 chat->user_disconnect = disconnect;
943 chat->user_disconnect_data = user_data;
948 guint g_at_chat_send(GAtChat *chat, const char *cmd,
949 const char **prefix_list, GAtResultFunc func,
950 gpointer user_data, GDestroyNotify notify)
952 struct at_command *c;
954 if (chat == NULL || chat->command_queue == NULL)
957 c = at_command_create(cmd, prefix_list, func, user_data, notify);
962 c->id = chat->next_cmd_id++;
964 g_queue_push_tail(chat->command_queue, c);
966 if (g_queue_get_length(chat->command_queue) == 1)
967 g_at_chat_wakeup_writer(chat);
972 gboolean g_at_chat_cancel(GAtChat *chat, guint id)
976 if (chat == NULL || chat->command_queue == NULL)
979 l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
980 at_command_compare_by_id);
985 if (l == g_queue_peek_head(chat->command_queue)) {
986 struct at_command *c = l->data;
988 /* We can't actually remove it since it is most likely
989 * already in progress, just null out the callback
990 * so it won't be called
994 at_command_destroy(l->data);
995 g_queue_remove(chat->command_queue, l->data);
1001 static struct at_notify *at_notify_create(GAtChat *chat, const char *prefix,
1004 struct at_notify *notify;
1007 key = g_strdup(prefix);
1012 notify = g_try_new0(struct at_notify, 1);
1021 g_hash_table_insert(chat->notify_list, key, notify);
1026 guint g_at_chat_register(GAtChat *chat, const char *prefix,
1027 GAtNotifyFunc func, gboolean expect_pdu,
1029 GDestroyNotify destroy_notify)
1031 struct at_notify *notify;
1032 struct at_notify_node *node;
1034 if (chat == NULL || chat->notify_list == NULL)
1040 if (prefix == NULL || strlen(prefix) == 0)
1043 notify = g_hash_table_lookup(chat->notify_list, prefix);
1046 notify = at_notify_create(chat, prefix, expect_pdu);
1048 if (!notify || notify->pdu != expect_pdu)
1051 node = g_try_new0(struct at_notify_node, 1);
1056 node->id = chat->next_notify_id++;
1057 node->callback = func;
1058 node->user_data = user_data;
1059 node->notify = destroy_notify;
1061 notify->nodes = g_slist_prepend(notify->nodes, node);
1066 gboolean g_at_chat_unregister(GAtChat *chat, guint id)
1068 GHashTableIter iter;
1069 struct at_notify *notify;
1071 gpointer key, value;
1074 if (chat == NULL || chat->notify_list == NULL)
1077 g_hash_table_iter_init(&iter, chat->notify_list);
1079 while (g_hash_table_iter_next(&iter, &key, &value)) {
1083 l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id),
1084 at_notify_node_compare_by_id);
1089 at_notify_node_destroy(l->data);
1090 notify->nodes = g_slist_remove(notify->nodes, l->data);
1092 if (notify->nodes == NULL)
1093 g_hash_table_iter_remove(&iter);
1101 gboolean g_at_chat_set_wakeup_command(GAtChat *chat, const char *cmd,
1102 unsigned int timeout, unsigned int msec)
1108 g_free(chat->wakeup);
1110 chat->wakeup = g_strdup(cmd);
1111 chat->inactivity_time = (gdouble)msec / 1000;
1112 chat->wakeup_timeout = timeout;