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
35 #include "ringbuffer.h"
38 /* #define WRITE_SCHEDULER_DEBUG 1 */
40 static const char *none_prefix[] = { NULL };
42 static void g_at_chat_wakeup_writer(GAtChat *chat);
43 static void debug_chat(GAtChat *chat, gboolean in, const char *str, gsize len);
50 GAtResultFunc callback;
51 GAtNotifyFunc listing;
53 GDestroyNotify notify;
56 struct at_notify_node {
58 GAtNotifyFunc callback;
60 GDestroyNotify notify;
69 gint ref_count; /* Ref count */
70 guint next_cmd_id; /* Next command id */
71 guint next_notify_id; /* Next notify id */
72 guint read_watch; /* GSource read id, 0 if none */
73 guint write_watch; /* GSource write id, 0 if none */
74 GIOChannel *channel; /* channel */
75 GQueue *command_queue; /* Command queue */
76 guint cmd_bytes_written; /* bytes written from cmd */
77 GHashTable *notify_list; /* List of notification reg */
78 GAtDisconnectFunc user_disconnect; /* user disconnect func */
79 gpointer user_disconnect_data; /* user disconnect data */
80 struct ring_buffer *buf; /* Current read buffer */
81 guint read_so_far; /* Number of bytes processed */
82 GAtDebugFunc debugf; /* debugging output function */
83 gpointer debug_data; /* Data to pass to debug func */
84 char *pdu_notify; /* Unsolicited Resp w/ PDU */
85 GSList *response_lines; /* char * lines of the response */
86 char *wakeup; /* command sent to wakeup modem */
88 gdouble inactivity_time; /* Period of inactivity */
89 guint wakeup_timeout; /* How long to wait for resp */
90 GTimer *wakeup_timer; /* Keep track of elapsed time */
92 gboolean destroyed; /* Re-entrancy guard */
93 GSList *terminator_list; /* Non-standard terminator */
96 struct terminator_info {
102 static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
104 const struct at_notify_node *node = a;
105 guint id = GPOINTER_TO_UINT(b);
116 static void at_notify_node_destroy(struct at_notify_node *node)
119 node->notify(node->user_data);
124 static void at_notify_destroy(struct at_notify *notify)
126 g_slist_foreach(notify->nodes, (GFunc) at_notify_node_destroy, NULL);
130 static gint at_command_compare_by_id(gconstpointer a, gconstpointer b)
132 const struct at_command *command = a;
133 guint id = GPOINTER_TO_UINT(b);
135 if (command->id < id)
138 if (command->id > id)
144 static struct at_command *at_command_create(const char *cmd,
145 const char **prefix_list,
147 GAtNotifyFunc listing,
150 GDestroyNotify notify,
153 struct at_command *c;
155 char **prefixes = NULL;
158 int num_prefixes = 0;
161 while (prefix_list[num_prefixes])
164 prefixes = g_new(char *, num_prefixes + 1);
166 for (i = 0; i < num_prefixes; i++)
167 prefixes[i] = strdup(prefix_list[i]);
169 prefixes[num_prefixes] = NULL;
172 c = g_try_new0(struct at_command, 1);
178 c->cmd = g_try_new(char, len + 2);
185 memcpy(c->cmd, cmd, len);
187 /* If we have embedded '\r' then this is a command expecting a prompt
188 * from the modem. Embed Ctrl-Z at the very end automatically
190 if (wakeup == FALSE) {
191 if (strchr(cmd, '\r'))
201 c->expect_pdu = expect_pdu;
202 c->prefixes = prefixes;
204 c->listing = listing;
205 c->user_data = user_data;
211 static void at_command_destroy(struct at_command *cmd)
214 cmd->notify(cmd->user_data);
216 g_strfreev(cmd->prefixes);
221 static void free_terminator(struct terminator_info *info)
223 g_free(info->terminator);
224 info->terminator = NULL;
229 static void g_at_chat_cleanup(GAtChat *chat)
231 struct at_command *c;
233 ring_buffer_free(chat->buf);
236 /* Cleanup pending commands */
237 while ((c = g_queue_pop_head(chat->command_queue)))
238 at_command_destroy(c);
240 g_queue_free(chat->command_queue);
241 chat->command_queue = NULL;
243 /* Cleanup any response lines we have pending */
244 g_slist_foreach(chat->response_lines, (GFunc)g_free, NULL);
245 g_slist_free(chat->response_lines);
246 chat->response_lines = NULL;
248 /* Cleanup registered notifications */
249 g_hash_table_destroy(chat->notify_list);
250 chat->notify_list = NULL;
252 if (chat->pdu_notify) {
253 g_free(chat->pdu_notify);
254 chat->pdu_notify = NULL;
258 g_free(chat->wakeup);
262 if (chat->wakeup_timer) {
263 g_timer_destroy(chat->wakeup_timer);
264 chat->wakeup_timer = 0;
267 if (chat->timeout_source) {
268 g_source_remove(chat->timeout_source);
269 chat->timeout_source = 0;
272 g_at_syntax_unref(chat->syntax);
275 chat->channel = NULL;
277 if (chat->terminator_list) {
278 g_slist_foreach(chat->terminator_list,
279 (GFunc)free_terminator, NULL);
280 g_slist_free(chat->terminator_list);
281 chat->terminator_list = NULL;
285 static void read_watcher_destroy_notify(GAtChat *chat)
287 g_at_chat_cleanup(chat);
288 chat->read_watch = 0;
290 if (chat->user_disconnect)
291 chat->user_disconnect(chat->user_disconnect_data);
297 static void write_watcher_destroy_notify(GAtChat *chat)
299 chat->write_watch = 0;
302 static void at_notify_call_callback(gpointer data, gpointer user_data)
304 struct at_notify_node *node = data;
305 GAtResult *result = user_data;
307 node->callback(result, node->user_data);
310 static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
313 struct at_notify *notify;
316 gboolean ret = FALSE;
319 g_hash_table_iter_init(&iter, chat->notify_list);
321 result.final_or_pdu = 0;
323 while (g_hash_table_iter_next(&iter, &key, &value)) {
327 if (!g_str_has_prefix(line, key))
331 chat->pdu_notify = line;
333 if (chat->syntax->set_hint)
334 chat->syntax->set_hint(chat->syntax,
335 G_AT_SYNTAX_EXPECT_PDU);
340 result.lines = g_slist_prepend(NULL, line);
342 g_slist_foreach(notify->nodes, at_notify_call_callback,
348 g_slist_free(result.lines);
355 static void g_at_chat_finish_command(GAtChat *p, gboolean ok,
358 struct at_command *cmd = g_queue_pop_head(p->command_queue);
359 GSList *response_lines;
361 /* Cannot happen, but lets be paranoid */
365 p->cmd_bytes_written = 0;
367 if (g_queue_peek_head(p->command_queue))
368 g_at_chat_wakeup_writer(p);
370 response_lines = p->response_lines;
371 p->response_lines = NULL;
376 response_lines = g_slist_reverse(response_lines);
378 result.final_or_pdu = final;
379 result.lines = response_lines;
381 cmd->callback(ok, &result, cmd->user_data);
384 g_slist_foreach(response_lines, (GFunc)g_free, NULL);
385 g_slist_free(response_lines);
388 at_command_destroy(cmd);
391 static struct terminator_info terminator_table[] = {
393 { "ERROR", -1, FALSE },
394 { "NO DIALTONE", -1, FALSE },
395 { "BUSY", -1, FALSE },
396 { "NO CARRIER", -1, FALSE },
397 { "CONNECT", -1, TRUE },
398 { "NO ANSWER", -1, FALSE },
399 { "+CMS ERROR:", 11, FALSE },
400 { "+CME ERROR:", 11, FALSE },
401 { "+EXT ERROR:", 11, FALSE }
404 void g_at_chat_add_terminator(GAtChat *chat, char *terminator,
405 int len, gboolean success)
407 struct terminator_info *info = g_new0(struct terminator_info, 1);
408 info->terminator = g_strdup(terminator);
410 info->success = success;
411 chat->terminator_list = g_slist_prepend(chat->terminator_list, info);
414 static gboolean check_terminator(struct terminator_info *info, char *line)
416 if (info->len == -1 && !strcmp(line, info->terminator))
419 if (info->len > 0 && !strncmp(line, info->terminator, info->len))
425 static gboolean g_at_chat_handle_command_response(GAtChat *p,
426 struct at_command *cmd,
430 int size = sizeof(terminator_table) / sizeof(struct terminator_info);
434 for (i = 0; i < size; i++) {
435 struct terminator_info *info = &terminator_table[i];
436 if (check_terminator(info, line)) {
437 g_at_chat_finish_command(p, info->success, line);
442 for (l = p->terminator_list; l; l = l->next) {
443 struct terminator_info *info = l->data;
444 if (check_terminator(info, line)) {
445 g_at_chat_finish_command(p, info->success, line);
453 for (i = 0; cmd->prefixes[i]; i++)
454 if (g_str_has_prefix(line, cmd->prefixes[i]))
461 if (cmd->listing && cmd->expect_pdu)
462 hint = G_AT_SYNTAX_EXPECT_PDU;
464 hint = G_AT_SYNTAX_EXPECT_MULTILINE;
466 if (p->syntax->set_hint)
467 p->syntax->set_hint(p->syntax, hint);
469 if (cmd->listing && cmd->expect_pdu) {
470 p->pdu_notify = line;
477 result.lines = g_slist_prepend(NULL, line);
478 result.final_or_pdu = NULL;
480 cmd->listing(&result, cmd->user_data);
482 g_slist_free(result.lines);
485 p->response_lines = g_slist_prepend(p->response_lines, line);
490 static void have_line(GAtChat *p, char *str)
492 /* We're not going to copy terminal <CR><LF> */
493 struct at_command *cmd;
498 /* Check for echo, this should not happen, but lets be paranoid */
499 if (!strncmp(str, "AT", 2) == TRUE)
502 cmd = g_queue_peek_head(p->command_queue);
504 if (cmd && p->cmd_bytes_written > 0) {
505 char c = cmd->cmd[p->cmd_bytes_written - 1];
507 /* We check that we have submitted a terminator, in which case
508 * a command might have failed or completed successfully
510 * In the generic case, \r is at the end of the command, so we
511 * know the entire command has been submitted. In the case of
512 * commands like CMGS, every \r or Ctrl-Z might result in a
513 * final response from the modem, so we check this as well.
515 if ((c == '\r' || c == 26) &&
516 g_at_chat_handle_command_response(p, cmd, str))
520 if (g_at_chat_match_notify(p, str) == TRUE)
524 /* No matches & no commands active, ignore line */
528 static void have_notify_pdu(GAtChat *p, char *pdu, GAtResult *result)
531 struct at_notify *notify;
535 g_hash_table_iter_init(&iter, p->notify_list);
537 while (g_hash_table_iter_next(&iter, &key, &value)) {
541 if (!g_str_has_prefix(p->pdu_notify, prefix))
547 g_slist_foreach(notify->nodes, at_notify_call_callback, result);
551 static void have_pdu(GAtChat *p, char *pdu)
553 struct at_command *cmd;
555 gboolean listing_pdu = FALSE;
560 result.lines = g_slist_prepend(NULL, p->pdu_notify);
561 result.final_or_pdu = pdu;
563 cmd = g_queue_peek_head(p->command_queue);
565 if (cmd && cmd->expect_pdu && p->cmd_bytes_written > 0) {
566 char c = cmd->cmd[p->cmd_bytes_written - 1];
573 cmd->listing(&result, cmd->user_data);
575 if (p->syntax->set_hint)
576 p->syntax->set_hint(p->syntax,
577 G_AT_SYNTAX_EXPECT_MULTILINE);
579 have_notify_pdu(p, pdu, &result);
581 g_slist_free(result.lines);
584 g_free(p->pdu_notify);
585 p->pdu_notify = NULL;
591 static char *extract_line(GAtChat *p)
593 unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
594 unsigned int pos = 0;
595 unsigned char *buf = ring_buffer_read_ptr(p->buf, pos);
600 while (pos < p->read_so_far) {
601 if (*buf == '\r' || *buf == '\n')
613 buf = ring_buffer_read_ptr(p->buf, pos);
616 line = g_try_new(char, line_length + 1);
619 ring_buffer_drain(p->buf, p->read_so_far);
623 ring_buffer_drain(p->buf, strip_front);
624 ring_buffer_read(p->buf, line, line_length);
625 ring_buffer_drain(p->buf, p->read_so_far - strip_front - line_length);
627 line[line_length] = '\0';
632 static void new_bytes(GAtChat *p)
634 unsigned int len = ring_buffer_len(p->buf);
635 unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
636 unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
638 GAtSyntaxResult result;
642 while (p->channel && (p->read_so_far < len)) {
643 gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
644 result = p->syntax->feed(p->syntax, (char *)buf, &rbytes);
647 p->read_so_far += rbytes;
649 if (p->read_so_far == wrap) {
650 buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
654 if (result == G_AT_SYNTAX_RESULT_UNSURE)
658 case G_AT_SYNTAX_RESULT_LINE:
659 case G_AT_SYNTAX_RESULT_MULTILINE:
660 have_line(p, extract_line(p));
663 case G_AT_SYNTAX_RESULT_PDU:
664 have_pdu(p, extract_line(p));
667 case G_AT_SYNTAX_RESULT_PROMPT:
668 g_at_chat_wakeup_writer(p);
669 ring_buffer_drain(p->buf, p->read_so_far);
673 ring_buffer_drain(p->buf, p->read_so_far);
677 len -= p->read_so_far;
678 wrap -= p->read_so_far;
682 /* We're overflowing the buffer, shutdown the socket */
683 if (p->buf && ring_buffer_avail(p->buf) == 0)
684 g_source_remove(p->read_watch);
689 static void debug_chat(GAtChat *chat, gboolean in, const char *str, gsize len)
691 char type = in ? '<' : '>';
692 gsize escaped = 2; /* Enough for '<', ' ' */
694 const char *esc = "<ESC>";
695 gsize esc_size = strlen(esc);
696 const char *ctrlz = "<CtrlZ>";
697 gsize ctrlz_size = strlen(ctrlz);
700 if (!chat->debugf || !len)
703 for (i = 0; i < len; i++) {
708 else if (c == '\r' || c == '\t' || c == '\n')
711 escaped += ctrlz_size;
718 escaped_str = g_malloc(escaped + 1);
719 escaped_str[0] = type;
720 escaped_str[1] = ' ';
721 escaped_str[2] = '\0';
722 escaped_str[escaped] = '\0';
724 for (escaped = 2, i = 0; i < len; i++) {
729 escaped_str[escaped++] = '\\';
730 escaped_str[escaped++] = 'r';
733 escaped_str[escaped++] = '\\';
734 escaped_str[escaped++] = 't';
737 escaped_str[escaped++] = '\\';
738 escaped_str[escaped++] = 'n';
741 strncpy(&escaped_str[escaped], ctrlz, ctrlz_size);
742 escaped += ctrlz_size;
745 strncpy(&escaped_str[escaped], esc, esc_size);
750 escaped_str[escaped++] = c;
752 escaped_str[escaped++] = '\\';
753 escaped_str[escaped++] = '0' + ((c >> 6) & 07);
754 escaped_str[escaped++] = '0' + ((c >> 3) & 07);
755 escaped_str[escaped++] = '0' + (c & 07);
760 chat->debugf(escaped_str, chat->debug_data);
764 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
768 GAtChat *chat = data;
772 gsize total_read = 0;
774 if (cond & G_IO_NVAL)
777 /* Regardless of condition, try to read all the data available */
781 toread = ring_buffer_avail_no_wrap(chat->buf);
786 buf = ring_buffer_write_ptr(chat->buf);
788 err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
789 debug_chat(chat, TRUE, (char *)buf, rbytes);
791 total_read += rbytes;
794 ring_buffer_write_advance(chat->buf, rbytes);
796 } while (err == G_IO_ERROR_NONE && rbytes > 0);
801 if (cond & (G_IO_HUP | G_IO_ERR))
804 if (err != G_IO_ERROR_NONE && err != G_IO_ERROR_AGAIN)
810 static void wakeup_cb(gboolean ok, GAtResult *result, gpointer user_data)
812 GAtChat *chat = user_data;
818 chat->debugf("Finally woke up the modem\n", chat->debug_data);
820 g_source_remove(chat->timeout_source);
821 chat->timeout_source = 0;
824 static gboolean wakeup_no_response(gpointer user)
826 GAtChat *chat = user;
827 struct at_command *cmd = g_queue_peek_head(chat->command_queue);
830 chat->debugf("Wakeup got no response\n", chat->debug_data);
832 g_at_chat_finish_command(chat, FALSE, NULL);
833 cmd = at_command_create(chat->wakeup, none_prefix, FALSE,
834 NULL, wakeup_cb, chat, NULL, TRUE);
837 chat->timeout_source = 0;
841 g_queue_push_head(chat->command_queue, cmd);
846 static gboolean can_write_data(GIOChannel *channel, GIOCondition cond,
849 GAtChat *chat = data;
850 struct at_command *cmd;
856 gboolean wakeup_first = FALSE;
857 #ifdef WRITE_SCHEDULER_DEBUG
861 if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
864 /* Grab the first command off the queue and write as
865 * much of it as we can
867 cmd = g_queue_peek_head(chat->command_queue);
869 /* For some reason command queue is empty, cancel write watcher */
873 len = strlen(cmd->cmd);
875 /* For some reason write watcher fired, but we've already
876 * written the entire command out to the io channel,
877 * cancel write watcher
879 if (chat->cmd_bytes_written >= len)
883 if (!chat->wakeup_timer) {
885 chat->wakeup_timer = g_timer_new();
887 } else if (g_timer_elapsed(chat->wakeup_timer, NULL) >
888 chat->inactivity_time)
892 if (chat->cmd_bytes_written == 0 && wakeup_first == TRUE) {
893 cmd = at_command_create(chat->wakeup, none_prefix, FALSE,
894 NULL, wakeup_cb, chat, NULL, TRUE);
899 g_queue_push_head(chat->command_queue, cmd);
901 len = strlen(chat->wakeup);
903 chat->timeout_source = g_timeout_add(chat->wakeup_timeout,
904 wakeup_no_response, chat);
907 towrite = len - chat->cmd_bytes_written;
909 cr = strchr(cmd->cmd + chat->cmd_bytes_written, '\r');
912 towrite = cr - (cmd->cmd + chat->cmd_bytes_written) + 1;
914 #ifdef WRITE_SCHEDULER_DEBUG
921 err = g_io_channel_write(chat->channel,
922 cmd->cmd + chat->cmd_bytes_written,
923 #ifdef WRITE_SCHEDULER_DEBUG
930 if (err != G_IO_ERROR_NONE) {
931 g_source_remove(chat->read_watch);
935 debug_chat(chat, FALSE, cmd->cmd + chat->cmd_bytes_written,
937 chat->cmd_bytes_written += bytes_written;
939 if (bytes_written < towrite)
942 /* Full command submitted, update timer */
943 if (chat->wakeup_timer)
944 g_timer_start(chat->wakeup_timer);
949 static void g_at_chat_wakeup_writer(GAtChat *chat)
951 if (chat->write_watch != 0)
954 chat->write_watch = g_io_add_watch_full(chat->channel,
956 G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
957 can_write_data, chat,
958 (GDestroyNotify)write_watcher_destroy_notify);
961 GAtChat *g_at_chat_new(GIOChannel *channel, GAtSyntax *syntax)
972 chat = g_try_new0(GAtChat, 1);
978 chat->next_cmd_id = 1;
979 chat->next_notify_id = 1;
982 chat->buf = ring_buffer_new(4096);
987 chat->command_queue = g_queue_new();
989 if (!chat->command_queue)
992 chat->notify_list = g_hash_table_new_full(g_str_hash, g_str_equal,
993 g_free, (GDestroyNotify)at_notify_destroy);
995 if (g_io_channel_set_encoding(channel, NULL, NULL) !=
999 io_flags = g_io_channel_get_flags(channel);
1001 io_flags |= G_IO_FLAG_NONBLOCK;
1003 if (g_io_channel_set_flags(channel, io_flags, NULL) !=
1007 g_io_channel_set_close_on_unref(channel, TRUE);
1009 chat->channel = channel;
1010 chat->read_watch = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT,
1011 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
1012 received_data, chat,
1013 (GDestroyNotify)read_watcher_destroy_notify);
1015 chat->syntax = g_at_syntax_ref(syntax);
1021 ring_buffer_free(chat->buf);
1023 if (chat->command_queue)
1024 g_queue_free(chat->command_queue);
1026 if (chat->notify_list)
1027 g_hash_table_destroy(chat->notify_list);
1033 GIOChannel *g_at_chat_get_channel(GAtChat *chat)
1038 return chat->channel;
1041 GAtChat *g_at_chat_ref(GAtChat *chat)
1046 g_atomic_int_inc(&chat->ref_count);
1051 void g_at_chat_unref(GAtChat *chat)
1058 is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
1060 if (is_zero == FALSE)
1063 g_at_chat_shutdown(chat);
1065 /* glib delays the destruction of the watcher until it exits, this
1066 * means we can't free the data just yet, even though we've been
1067 * destroyed already. We have to wait until the read_watcher
1068 * destroy function gets called
1070 if (chat->read_watch != 0)
1071 chat->destroyed = TRUE;
1076 gboolean g_at_chat_shutdown(GAtChat *chat)
1078 if (chat->channel == NULL)
1081 /* Don't trigger user disconnect on shutdown */
1082 chat->user_disconnect = NULL;
1083 chat->user_disconnect_data = NULL;
1085 if (chat->read_watch)
1086 g_source_remove(chat->read_watch);
1088 if (chat->write_watch)
1089 g_source_remove(chat->write_watch);
1094 gboolean g_at_chat_set_syntax(GAtChat *chat, GAtSyntax *syntax)
1099 g_at_syntax_unref(chat->syntax);
1101 chat->syntax = g_at_syntax_ref(syntax);
1106 gboolean g_at_chat_set_disconnect_function(GAtChat *chat,
1107 GAtDisconnectFunc disconnect, gpointer user_data)
1112 chat->user_disconnect = disconnect;
1113 chat->user_disconnect_data = user_data;
1118 gboolean g_at_chat_set_debug(GAtChat *chat, GAtDebugFunc func, gpointer user)
1123 chat->debugf = func;
1124 chat->debug_data = user;
1129 static guint send_common(GAtChat *chat, const char *cmd,
1130 const char **prefix_list,
1131 gboolean expect_pdu,
1132 GAtNotifyFunc listing, GAtResultFunc func,
1133 gpointer user_data, GDestroyNotify notify)
1135 struct at_command *c;
1137 if (chat == NULL || chat->command_queue == NULL)
1140 c = at_command_create(cmd, prefix_list, expect_pdu, listing, func,
1141 user_data, notify, FALSE);
1146 c->id = chat->next_cmd_id++;
1148 g_queue_push_tail(chat->command_queue, c);
1150 if (g_queue_get_length(chat->command_queue) == 1)
1151 g_at_chat_wakeup_writer(chat);
1156 guint g_at_chat_send(GAtChat *chat, const char *cmd,
1157 const char **prefix_list, GAtResultFunc func,
1158 gpointer user_data, GDestroyNotify notify)
1160 return send_common(chat, cmd, prefix_list, FALSE, NULL, func,
1164 guint g_at_chat_send_listing(GAtChat *chat, const char *cmd,
1165 const char **prefix_list,
1166 GAtNotifyFunc listing, GAtResultFunc func,
1167 gpointer user_data, GDestroyNotify notify)
1169 if (listing == NULL)
1172 return send_common(chat, cmd, prefix_list, FALSE, listing, func,
1176 guint g_at_chat_send_pdu_listing(GAtChat *chat, const char *cmd,
1177 const char **prefix_list,
1178 GAtNotifyFunc listing, GAtResultFunc func,
1179 gpointer user_data, GDestroyNotify notify)
1181 if (listing == NULL)
1184 return send_common(chat, cmd, prefix_list, TRUE, listing, func,
1188 gboolean g_at_chat_cancel(GAtChat *chat, guint id)
1192 if (chat == NULL || chat->command_queue == NULL)
1195 /* We use id 0 for wakeup commands */
1199 l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
1200 at_command_compare_by_id);
1205 if (l == g_queue_peek_head(chat->command_queue) &&
1206 chat->cmd_bytes_written > 0) {
1207 struct at_command *c = l->data;
1209 /* We can't actually remove it since it is most likely
1210 * already in progress, just null out the callback
1211 * so it won't be called
1215 at_command_destroy(l->data);
1216 g_queue_remove(chat->command_queue, l->data);
1222 gboolean g_at_chat_cancel_all(GAtChat *chat)
1225 struct at_command *c;
1227 if (chat == NULL || chat->command_queue == NULL)
1230 while ((c = g_queue_peek_nth(chat->command_queue, n)) != NULL) {
1236 if (n == 0 && chat->cmd_bytes_written > 0) {
1242 at_command_destroy(c);
1243 g_queue_remove(chat->command_queue, c);
1249 static struct at_notify *at_notify_create(GAtChat *chat, const char *prefix,
1252 struct at_notify *notify;
1255 key = g_strdup(prefix);
1260 notify = g_try_new0(struct at_notify, 1);
1269 g_hash_table_insert(chat->notify_list, key, notify);
1274 guint g_at_chat_register(GAtChat *chat, const char *prefix,
1275 GAtNotifyFunc func, gboolean expect_pdu,
1277 GDestroyNotify destroy_notify)
1279 struct at_notify *notify;
1280 struct at_notify_node *node;
1282 if (chat == NULL || chat->notify_list == NULL)
1288 if (prefix == NULL || strlen(prefix) == 0)
1291 notify = g_hash_table_lookup(chat->notify_list, prefix);
1294 notify = at_notify_create(chat, prefix, expect_pdu);
1296 if (!notify || notify->pdu != expect_pdu)
1299 node = g_try_new0(struct at_notify_node, 1);
1304 node->id = chat->next_notify_id++;
1305 node->callback = func;
1306 node->user_data = user_data;
1307 node->notify = destroy_notify;
1309 notify->nodes = g_slist_prepend(notify->nodes, node);
1314 gboolean g_at_chat_unregister(GAtChat *chat, guint id)
1316 GHashTableIter iter;
1317 struct at_notify *notify;
1319 gpointer key, value;
1322 if (chat == NULL || chat->notify_list == NULL)
1325 g_hash_table_iter_init(&iter, chat->notify_list);
1327 while (g_hash_table_iter_next(&iter, &key, &value)) {
1331 l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id),
1332 at_notify_node_compare_by_id);
1337 at_notify_node_destroy(l->data);
1338 notify->nodes = g_slist_remove(notify->nodes, l->data);
1340 if (notify->nodes == NULL)
1341 g_hash_table_iter_remove(&iter);
1349 gboolean g_at_chat_set_wakeup_command(GAtChat *chat, const char *cmd,
1350 unsigned int timeout, unsigned int msec)
1356 g_free(chat->wakeup);
1358 chat->wakeup = g_strdup(cmd);
1359 chat->inactivity_time = (gdouble)msec / 1000;
1360 chat->wakeup_timeout = timeout;