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,
49 PARSER_STATE_PDU_COMPLETE,
51 PARSER_STATE_PROMPT_COMPLETE
58 GAtResultFunc callback;
60 GDestroyNotify notify;
63 struct at_notify_node {
65 GAtNotifyFunc callback;
67 GDestroyNotify notify;
76 gint ref_count; /* Ref count */
77 guint next_cmd_id; /* Next command id */
78 guint next_notify_id; /* Next notify id */
79 guint read_watch; /* GSource read id, 0 if none */
80 guint write_watch; /* GSource write id, 0 if none */
81 GIOChannel *channel; /* channel */
82 GQueue *command_queue; /* Command queue */
83 guint cmd_bytes_written; /* bytes written from cmd */
84 GHashTable *notify_list; /* List of notification reg */
85 GAtDisconnectFunc user_disconnect; /* user disconnect func */
86 gpointer user_disconnect_data; /* user disconnect data */
87 struct ring_buffer *buf; /* Current read buffer */
88 guint read_so_far; /* Number of bytes processed */
89 gboolean disconnecting; /* Whether we're disconnecting */
90 enum chat_state state; /* Current chat state */
92 char *pdu_notify; /* Unsolicited Resp w/ PDU */
93 GSList *response_lines; /* char * lines of the response */
94 char *wakeup; /* command sent to wakeup modem */
95 gdouble inactivity_time; /* Period of inactivity */
96 guint wakeup_timeout; /* How long to wait for resp */
97 GTimer *wakeup_timer; /* Keep track of elapsed time */
100 static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
102 const struct at_notify_node *node = a;
103 guint id = GPOINTER_TO_UINT(b);
114 static void at_notify_node_destroy(struct at_notify_node *node)
117 node->notify(node->user_data);
122 static void at_notify_destroy(struct at_notify *notify)
124 g_slist_foreach(notify->nodes, (GFunc) at_notify_node_destroy, NULL);
128 static gint at_command_compare_by_id(gconstpointer a, gconstpointer b)
130 const struct at_command *command = a;
131 guint id = GPOINTER_TO_UINT(b);
133 if (command->id < id)
136 if (command->id > id)
142 static struct at_command *at_command_create(const char *cmd,
143 const char **prefix_list,
146 GDestroyNotify notify)
148 struct at_command *c;
150 char **prefixes = NULL;
153 int num_prefixes = 0;
156 while (prefix_list[num_prefixes])
159 prefixes = g_new(char *, num_prefixes + 1);
161 for (i = 0; i < num_prefixes; i++)
162 prefixes[i] = strdup(prefix_list[i]);
164 prefixes[num_prefixes] = NULL;
167 c = g_try_new0(struct at_command, 1);
173 c->cmd = g_try_new(char, len + 2);
180 memcpy(c->cmd, cmd, len);
182 /* If we have embedded '\r' then this is a command expecting a prompt
183 * from the modem. Embed Ctrl-Z at the very end automatically
185 if (strchr(cmd, '\r'))
190 c->cmd[len+1] = '\0';
192 c->prefixes = prefixes;
194 c->user_data = user_data;
200 static void at_command_destroy(struct at_command *cmd)
203 cmd->notify(cmd->user_data);
205 g_strfreev(cmd->prefixes);
210 static void g_at_chat_cleanup(GAtChat *chat)
212 struct at_command *c;
214 ring_buffer_free(chat->buf);
217 /* Cleanup pending commands */
218 while ((c = g_queue_pop_head(chat->command_queue)))
219 at_command_destroy(c);
221 g_queue_free(chat->command_queue);
222 chat->command_queue = NULL;
224 /* Cleanup any response lines we have pending */
225 g_slist_foreach(chat->response_lines, (GFunc)g_free, NULL);
226 g_slist_free(chat->response_lines);
227 chat->response_lines = NULL;
229 /* Cleanup registered notifications */
230 g_hash_table_destroy(chat->notify_list);
231 chat->notify_list = NULL;
233 if (chat->pdu_notify) {
234 g_free(chat->pdu_notify);
235 chat->pdu_notify = NULL;
239 g_free(chat->wakeup);
243 if (chat->wakeup_timer) {
244 g_timer_destroy(chat->wakeup_timer);
245 chat->wakeup_timer = 0;
249 static void read_watcher_destroy_notify(GAtChat *chat)
251 chat->read_watch = 0;
253 if (chat->disconnecting)
256 chat->channel = NULL;
258 g_at_chat_cleanup(chat);
260 if (chat->user_disconnect)
261 chat->user_disconnect(chat->user_disconnect_data);
264 static void write_watcher_destroy_notify(GAtChat *chat)
266 chat->write_watch = 0;
269 static void at_notify_call_callback(gpointer data, gpointer user_data)
271 struct at_notify_node *node = data;
272 GAtResult *result = user_data;
274 node->callback(result, node->user_data);
277 static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
280 struct at_notify *notify;
283 gboolean ret = FALSE;
286 g_hash_table_iter_init(&iter, chat->notify_list);
288 result.final_or_pdu = 0;
290 while (g_hash_table_iter_next(&iter, &key, &value)) {
294 if (!g_str_has_prefix(line, key))
298 chat->pdu_notify = line;
299 chat->state = PARSER_STATE_PDU;
304 result.lines = g_slist_prepend(NULL, line);
306 g_slist_foreach(notify->nodes, at_notify_call_callback,
312 g_slist_free(result.lines);
314 chat->state = PARSER_STATE_IDLE;
320 static void g_at_chat_finish_command(GAtChat *p, gboolean ok,
323 struct at_command *cmd = g_queue_pop_head(p->command_queue);
325 /* Cannot happen, but lets be paranoid */
332 p->response_lines = g_slist_reverse(p->response_lines);
334 result.final_or_pdu = final;
335 result.lines = p->response_lines;
337 cmd->callback(ok, &result, cmd->user_data);
340 g_slist_foreach(p->response_lines, (GFunc)g_free, NULL);
341 g_slist_free(p->response_lines);
342 p->response_lines = NULL;
346 at_command_destroy(cmd);
348 p->cmd_bytes_written = 0;
350 if (g_queue_peek_head(p->command_queue))
351 g_at_chat_wakeup_writer(p);
354 struct terminator_info {
355 const char *terminator;
360 static struct terminator_info terminator_table[] = {
362 { "ERROR", -1, FALSE },
363 { "NO DIALTONE", -1, FALSE },
364 { "BUSY", -1, FALSE },
365 { "NO CARRIER", -1, FALSE },
366 { "CONNECT", -1, TRUE },
367 { "NO ANSWER", -1, FALSE },
368 { "+CMS ERROR:", 11, FALSE },
369 { "+CME ERROR:", 11, FALSE },
370 { "+EXT ERROR:", 11, FALSE }
373 static gboolean g_at_chat_handle_command_response(GAtChat *p,
374 struct at_command *cmd,
378 int size = sizeof(terminator_table) / sizeof(struct terminator_info);
380 p->state = PARSER_STATE_IDLE;
382 for (i = 0; i < size; i++) {
383 struct terminator_info *info = &terminator_table[i];
385 if (info->len == -1 && !strcmp(line, info->terminator)) {
386 g_at_chat_finish_command(p, info->success, line);
391 !strncmp(line, info->terminator, info->len)) {
392 g_at_chat_finish_command(p, info->success, line);
400 for (i = 0; cmd->prefixes[i]; i++)
401 if (g_str_has_prefix(line, cmd->prefixes[i]))
408 p->response_lines = g_slist_prepend(p->response_lines,
414 static void have_line(GAtChat *p)
416 /* We're not going to copy terminal <CR><LF> */
417 unsigned int len = p->read_so_far - 2;
419 struct at_command *cmd;
421 /* If we have preceding <CR><LF> modify the len */
422 if ((p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) == 0)
425 /* Make sure we have terminal null */
426 str = g_try_new(char, len + 1);
429 ring_buffer_drain(p->buf, p->read_so_far);
433 if ((p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) == 0)
434 ring_buffer_drain(p->buf, 2);
435 ring_buffer_read(p->buf, str, len);
436 ring_buffer_drain(p->buf, 2);
440 /* Check for echo, this should not happen, but lets be paranoid */
441 if (!strncmp(str, "AT", 2) == TRUE)
444 cmd = g_queue_peek_head(p->command_queue);
446 if (cmd && p->cmd_bytes_written == strlen(cmd->cmd) &&
447 g_at_chat_handle_command_response(p, cmd, str))
450 if (g_at_chat_match_notify(p, str) == TRUE)
454 /* No matches & no commands active, ignore line */
456 p->state = PARSER_STATE_IDLE;
459 static void have_pdu(GAtChat *p)
461 unsigned int len = p->read_so_far - 2;
464 struct at_notify *notify;
469 pdu = g_try_new(char, len + 1);
472 ring_buffer_drain(p->buf, p->read_so_far);
476 ring_buffer_read(p->buf, pdu, len);
477 ring_buffer_drain(p->buf, 2);
481 result.lines = g_slist_prepend(NULL, p->pdu_notify);
482 result.final_or_pdu = pdu;
484 g_hash_table_iter_init(&iter, p->notify_list);
486 while (g_hash_table_iter_next(&iter, &key, &value)) {
490 if (!g_str_has_prefix(p->pdu_notify, prefix))
496 g_slist_foreach(notify->nodes, at_notify_call_callback,
500 g_slist_free(result.lines);
503 g_free(p->pdu_notify);
504 p->pdu_notify = NULL;
509 p->state = PARSER_STATE_IDLE;
512 static inline void parse_char(GAtChat *chat, char byte)
514 switch (chat->state) {
515 case PARSER_STATE_IDLE:
517 chat->state = PARSER_STATE_INITIAL_CR;
518 else if (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) {
520 chat->state = PARSER_STATE_PROMPT;
522 chat->state = PARSER_STATE_RESPONSE;
526 case PARSER_STATE_INITIAL_CR:
528 chat->state = PARSER_STATE_INITIAL_LF;
529 else if (byte != '\r' && /* Echo & no <CR><LF>?! */
530 (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
531 chat->state = PARSER_STATE_RESPONSE;
532 else if (byte != '\r')
533 chat->state = PARSER_STATE_IDLE;
536 case PARSER_STATE_INITIAL_LF:
538 chat->state = PARSER_STATE_TERMINATOR_CR;
539 else if (byte == '>')
540 chat->state = PARSER_STATE_PROMPT;
542 chat->state = PARSER_STATE_RESPONSE;
545 case PARSER_STATE_RESPONSE:
547 chat->state = PARSER_STATE_TERMINATOR_CR;
550 case PARSER_STATE_TERMINATOR_CR:
552 chat->state = PARSER_STATE_RESPONSE_COMPLETE;
554 chat->state = PARSER_STATE_IDLE;
557 case PARSER_STATE_PDU:
559 chat->state = PARSER_STATE_PDU_CR;
562 case PARSER_STATE_PDU_CR:
564 chat->state = PARSER_STATE_PDU_COMPLETE;
567 case PARSER_STATE_PROMPT:
569 chat->state = PARSER_STATE_PROMPT_COMPLETE;
571 chat->state = PARSER_STATE_RESPONSE;
573 case PARSER_STATE_RESPONSE_COMPLETE:
574 case PARSER_STATE_PDU_COMPLETE:
576 /* This really shouldn't happen */
582 static void new_bytes(GAtChat *p)
584 unsigned int len = ring_buffer_len(p->buf);
585 unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
586 unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
588 while (p->read_so_far < len) {
594 if (p->read_so_far == wrap) {
595 buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
599 if (p->state == PARSER_STATE_RESPONSE_COMPLETE) {
600 len -= p->read_so_far;
601 wrap -= p->read_so_far;
606 } else if (p->state == PARSER_STATE_PDU_COMPLETE) {
607 len -= p->read_so_far;
608 wrap -= p->read_so_far;
613 } else if (p->state == PARSER_STATE_INITIAL_CR) {
614 len -= p->read_so_far - 1;
615 wrap -= p->read_so_far - 1;
617 ring_buffer_drain(p->buf, p->read_so_far - 1);
620 } else if (p->state == PARSER_STATE_PROMPT_COMPLETE) {
621 len -= p->read_so_far;
622 wrap -= p->read_so_far;
624 g_at_chat_wakeup_writer(p);
626 ring_buffer_drain(p->buf, p->read_so_far);
632 if (p->state == PARSER_STATE_IDLE && p->read_so_far > 0) {
633 ring_buffer_drain(p->buf, p->read_so_far);
638 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
642 GAtChat *chat = data;
646 gsize total_read = 0;
648 if (cond & G_IO_NVAL)
651 /* Regardless of condition, try to read all the data available */
655 toread = ring_buffer_avail_no_wrap(chat->buf);
657 /* We're going to start overflowing the buffer
658 * this cannot happen under normal circumstances, so probably
659 * the channel is getting garbage, drop off
662 if (chat->state == PARSER_STATE_RESPONSE)
665 err = G_IO_ERROR_AGAIN;
669 buf = ring_buffer_write_ptr(chat->buf);
671 err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
673 total_read += rbytes;
676 ring_buffer_write_advance(chat->buf, rbytes);
678 } while (err == G_IO_ERROR_NONE && rbytes > 0);
683 if (cond & (G_IO_HUP | G_IO_ERR))
686 if (err == G_IO_ERROR_NONE && rbytes == 0)
689 if (err != G_IO_ERROR_NONE && err != G_IO_ERROR_AGAIN)
695 static gboolean wakeup_no_response(gpointer user)
697 GAtChat *chat = user;
699 g_at_chat_finish_command(chat, FALSE, NULL);
704 static gboolean can_write_data(GIOChannel *channel, GIOCondition cond,
707 GAtChat *chat = data;
708 struct at_command *cmd;
714 gboolean wakeup_first = FALSE;
715 #ifdef WRITE_SCHEDULER_DEBUG
719 if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
722 /* Grab the first command off the queue and write as
723 * much of it as we can
725 cmd = g_queue_peek_head(chat->command_queue);
727 /* For some reason command queue is empty, cancel write watcher */
731 len = strlen(cmd->cmd);
733 /* For some reason write watcher fired, but we've already
734 * written the entire command out to the io channel,
735 * cancel write watcher
737 if (chat->cmd_bytes_written >= len)
741 if (!chat->wakeup_timer) {
743 chat->wakeup_timer = g_timer_new();
745 } else if (g_timer_elapsed(chat->wakeup_timer, NULL) >
746 chat->inactivity_time)
750 if (chat->cmd_bytes_written == 0 && wakeup_first == TRUE) {
751 cmd = at_command_create(chat->wakeup, NULL, NULL, NULL, NULL);
756 g_queue_push_head(chat->command_queue, cmd);
758 len = strlen(chat->wakeup);
760 g_timeout_add(chat->wakeup_timeout, wakeup_no_response,
764 towrite = len - chat->cmd_bytes_written;
766 cr = strchr(cmd->cmd + chat->cmd_bytes_written, '\r');
769 towrite = cr - (cmd->cmd + chat->cmd_bytes_written) + 1;
771 #ifdef WRITE_SCHEDULER_DEBUG
778 err = g_io_channel_write(chat->channel,
779 cmd->cmd + chat->cmd_bytes_written,
780 #ifdef WRITE_SCHEDULER_DEBUG
787 if (err != G_IO_ERROR_NONE) {
788 g_at_chat_shutdown(chat);
792 chat->cmd_bytes_written += bytes_written;
794 if (bytes_written < towrite)
797 /* Full command submitted, update timer */
798 if (chat->wakeup_timer)
799 g_timer_start(chat->wakeup_timer);
804 static void g_at_chat_wakeup_writer(GAtChat *chat)
806 if (chat->write_watch != 0)
809 chat->write_watch = g_io_add_watch_full(chat->channel,
811 G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
812 can_write_data, chat,
813 (GDestroyNotify)write_watcher_destroy_notify);
816 GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
824 chat = g_try_new0(GAtChat, 1);
829 chat->next_cmd_id = 1;
830 chat->next_notify_id = 1;
833 chat->buf = ring_buffer_new(4096);
838 chat->command_queue = g_queue_new();
840 if (!chat->command_queue)
843 chat->notify_list = g_hash_table_new_full(g_str_hash, g_str_equal,
844 g_free, (GDestroyNotify)at_notify_destroy);
846 if (g_io_channel_set_encoding(channel, NULL, NULL) !=
850 io_flags = g_io_channel_get_flags(channel);
852 io_flags |= G_IO_FLAG_NONBLOCK;
854 if (g_io_channel_set_flags(channel, io_flags, NULL) !=
858 g_io_channel_set_close_on_unref(channel, TRUE);
860 chat->channel = channel;
861 chat->read_watch = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT,
862 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
864 (GDestroyNotify)read_watcher_destroy_notify);
870 ring_buffer_free(chat->buf);
872 if (chat->command_queue)
873 g_queue_free(chat->command_queue);
875 if (chat->notify_list)
876 g_hash_table_destroy(chat->notify_list);
882 GAtChat *g_at_chat_ref(GAtChat *chat)
887 g_atomic_int_inc(&chat->ref_count);
892 void g_at_chat_unref(GAtChat *chat)
899 is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
902 g_at_chat_shutdown(chat);
904 g_at_chat_cleanup(chat);
909 gboolean g_at_chat_shutdown(GAtChat *chat)
911 if (chat->channel == NULL)
914 chat->disconnecting = TRUE;
916 if (chat->read_watch)
917 g_source_remove(chat->read_watch);
919 if (chat->write_watch)
920 g_source_remove(chat->write_watch);
925 gboolean g_at_chat_set_disconnect_function(GAtChat *chat,
926 GAtDisconnectFunc disconnect, gpointer user_data)
931 chat->user_disconnect = disconnect;
932 chat->user_disconnect_data = user_data;
937 guint g_at_chat_send(GAtChat *chat, const char *cmd,
938 const char **prefix_list, GAtResultFunc func,
939 gpointer user_data, GDestroyNotify notify)
941 struct at_command *c;
943 if (chat == NULL || chat->command_queue == NULL)
946 c = at_command_create(cmd, prefix_list, func, user_data, notify);
951 c->id = chat->next_cmd_id++;
953 g_queue_push_tail(chat->command_queue, c);
955 if (g_queue_get_length(chat->command_queue) == 1)
956 g_at_chat_wakeup_writer(chat);
961 gboolean g_at_chat_cancel(GAtChat *chat, guint id)
965 if (chat == NULL || chat->command_queue == NULL)
968 l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
969 at_command_compare_by_id);
974 if (l == g_queue_peek_head(chat->command_queue)) {
975 struct at_command *c = l->data;
977 /* We can't actually remove it since it is most likely
978 * already in progress, just null out the callback
979 * so it won't be called
983 at_command_destroy(l->data);
984 g_queue_remove(chat->command_queue, l->data);
990 static struct at_notify *at_notify_create(GAtChat *chat, const char *prefix,
993 struct at_notify *notify;
996 key = g_strdup(prefix);
1001 notify = g_try_new0(struct at_notify, 1);
1010 g_hash_table_insert(chat->notify_list, key, notify);
1015 guint g_at_chat_register(GAtChat *chat, const char *prefix,
1016 GAtNotifyFunc func, gboolean expect_pdu,
1018 GDestroyNotify destroy_notify)
1020 struct at_notify *notify;
1021 struct at_notify_node *node;
1023 if (chat == NULL || chat->notify_list == NULL)
1029 if (prefix == NULL || strlen(prefix) == 0)
1032 notify = g_hash_table_lookup(chat->notify_list, prefix);
1035 notify = at_notify_create(chat, prefix, expect_pdu);
1037 if (!notify || notify->pdu != expect_pdu)
1040 node = g_try_new0(struct at_notify_node, 1);
1045 node->id = chat->next_notify_id++;
1046 node->callback = func;
1047 node->user_data = user_data;
1048 node->notify = destroy_notify;
1050 notify->nodes = g_slist_prepend(notify->nodes, node);
1055 gboolean g_at_chat_unregister(GAtChat *chat, guint id)
1057 GHashTableIter iter;
1058 struct at_notify *notify;
1060 gpointer key, value;
1063 if (chat == NULL || chat->notify_list == NULL)
1066 g_hash_table_iter_init(&iter, chat->notify_list);
1068 while (g_hash_table_iter_next(&iter, &key, &value)) {
1072 l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id),
1073 at_notify_node_compare_by_id);
1078 at_notify_node_destroy(l->data);
1079 notify->nodes = g_slist_remove(notify->nodes, l->data);
1081 if (notify->nodes == NULL)
1082 g_hash_table_iter_remove(&iter);
1090 gboolean g_at_chat_set_wakeup_command(GAtChat *chat, const char *cmd,
1091 unsigned int timeout, unsigned int msec)
1097 g_free(chat->wakeup);
1099 chat->wakeup = g_strdup(cmd);
1100 chat->inactivity_time = (gdouble)msec / 1000;
1101 chat->wakeup_timeout = timeout;