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
36 #include "ringbuffer.h"
39 /* #define WRITE_SCHEDULER_DEBUG 1 */
41 static void g_at_chat_wakeup_writer(GAtChat *chat);
42 static void debug_chat(GAtChat *chat, gboolean in, const char *str, gsize len);
48 GAtResultFunc callback;
49 GAtNotifyFunc listing;
51 GDestroyNotify notify;
54 struct at_notify_node {
56 GAtNotifyFunc callback;
58 GDestroyNotify notify;
67 gint ref_count; /* Ref count */
68 guint next_cmd_id; /* Next command id */
69 guint next_notify_id; /* Next notify id */
70 guint read_watch; /* GSource read id, 0 if none */
71 guint write_watch; /* GSource write id, 0 if none */
72 GIOChannel *channel; /* channel */
73 GQueue *command_queue; /* Command queue */
74 guint cmd_bytes_written; /* bytes written from cmd */
75 GHashTable *notify_list; /* List of notification reg */
76 GAtDisconnectFunc user_disconnect; /* user disconnect func */
77 gpointer user_disconnect_data; /* user disconnect data */
78 struct ring_buffer *buf; /* Current read buffer */
79 guint read_so_far; /* Number of bytes processed */
80 gboolean disconnecting; /* Whether we're disconnecting */
81 GAtDebugFunc debugf; /* debugging output function */
82 gpointer debug_data; /* Data to pass to debug func */
83 char *pdu_notify; /* Unsolicited Resp w/ PDU */
84 GSList *response_lines; /* char * lines of the response */
85 char *wakeup; /* command sent to wakeup modem */
87 gdouble inactivity_time; /* Period of inactivity */
88 guint wakeup_timeout; /* How long to wait for resp */
89 GTimer *wakeup_timer; /* Keep track of elapsed time */
93 static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
95 const struct at_notify_node *node = a;
96 guint id = GPOINTER_TO_UINT(b);
107 static void at_notify_node_destroy(struct at_notify_node *node)
110 node->notify(node->user_data);
115 static void at_notify_destroy(struct at_notify *notify)
117 g_slist_foreach(notify->nodes, (GFunc) at_notify_node_destroy, NULL);
121 static gint at_command_compare_by_id(gconstpointer a, gconstpointer b)
123 const struct at_command *command = a;
124 guint id = GPOINTER_TO_UINT(b);
126 if (command->id < id)
129 if (command->id > id)
135 static struct at_command *at_command_create(const char *cmd,
136 const char **prefix_list,
137 GAtNotifyFunc listing,
140 GDestroyNotify notify)
142 struct at_command *c;
144 char **prefixes = NULL;
147 int num_prefixes = 0;
150 while (prefix_list[num_prefixes])
153 prefixes = g_new(char *, num_prefixes + 1);
155 for (i = 0; i < num_prefixes; i++)
156 prefixes[i] = strdup(prefix_list[i]);
158 prefixes[num_prefixes] = NULL;
161 c = g_try_new0(struct at_command, 1);
167 c->cmd = g_try_new(char, len + 2);
174 memcpy(c->cmd, cmd, len);
176 /* If we have embedded '\r' then this is a command expecting a prompt
177 * from the modem. Embed Ctrl-Z at the very end automatically
179 if (strchr(cmd, '\r'))
184 c->cmd[len+1] = '\0';
186 c->prefixes = prefixes;
188 c->listing = listing;
189 c->user_data = user_data;
195 static void at_command_destroy(struct at_command *cmd)
198 cmd->notify(cmd->user_data);
200 g_strfreev(cmd->prefixes);
205 static void g_at_chat_cleanup(GAtChat *chat)
207 struct at_command *c;
209 ring_buffer_free(chat->buf);
212 /* Cleanup pending commands */
213 while ((c = g_queue_pop_head(chat->command_queue)))
214 at_command_destroy(c);
216 g_queue_free(chat->command_queue);
217 chat->command_queue = NULL;
219 /* Cleanup any response lines we have pending */
220 g_slist_foreach(chat->response_lines, (GFunc)g_free, NULL);
221 g_slist_free(chat->response_lines);
222 chat->response_lines = NULL;
224 /* Cleanup registered notifications */
225 g_hash_table_destroy(chat->notify_list);
226 chat->notify_list = NULL;
228 if (chat->pdu_notify) {
229 g_free(chat->pdu_notify);
230 chat->pdu_notify = NULL;
234 g_free(chat->wakeup);
238 if (chat->wakeup_timer) {
239 g_timer_destroy(chat->wakeup_timer);
240 chat->wakeup_timer = 0;
243 g_at_syntax_unref(chat->syntax);
247 static void read_watcher_destroy_notify(GAtChat *chat)
249 chat->read_watch = 0;
251 if (chat->disconnecting)
254 chat->channel = NULL;
256 g_at_chat_cleanup(chat);
258 if (chat->user_disconnect)
259 chat->user_disconnect(chat->user_disconnect_data);
262 static void write_watcher_destroy_notify(GAtChat *chat)
264 chat->write_watch = 0;
267 static void at_notify_call_callback(gpointer data, gpointer user_data)
269 struct at_notify_node *node = data;
270 GAtResult *result = user_data;
272 node->callback(result, node->user_data);
275 static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
278 struct at_notify *notify;
281 gboolean ret = FALSE;
284 g_hash_table_iter_init(&iter, chat->notify_list);
286 result.final_or_pdu = 0;
288 while (g_hash_table_iter_next(&iter, &key, &value)) {
292 if (!g_str_has_prefix(line, key))
296 chat->pdu_notify = line;
298 if (chat->syntax->set_hint)
299 chat->syntax->set_hint(chat->syntax,
300 G_AT_SYNTAX_EXPECT_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);
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 for (i = 0; i < size; i++) {
381 struct terminator_info *info = &terminator_table[i];
383 if (info->len == -1 && !strcmp(line, info->terminator)) {
384 g_at_chat_finish_command(p, info->success, line);
389 !strncmp(line, info->terminator, info->len)) {
390 g_at_chat_finish_command(p, info->success, line);
398 for (i = 0; cmd->prefixes[i]; i++)
399 if (g_str_has_prefix(line, cmd->prefixes[i]))
406 if (p->syntax->set_hint)
407 p->syntax->set_hint(p->syntax, G_AT_SYNTAX_EXPECT_MULTILINE);
412 result.lines = g_slist_prepend(NULL, line);
413 result.final_or_pdu = NULL;
415 cmd->listing(&result, cmd->user_data);
417 g_slist_free(result.lines);
420 p->response_lines = g_slist_prepend(p->response_lines, line);
425 static void have_line(GAtChat *p, char *str)
427 /* We're not going to copy terminal <CR><LF> */
428 struct at_command *cmd;
433 /* Check for echo, this should not happen, but lets be paranoid */
434 if (!strncmp(str, "AT", 2) == TRUE)
437 cmd = g_queue_peek_head(p->command_queue);
439 if (cmd && p->cmd_bytes_written > 0) {
440 char c = cmd->cmd[p->cmd_bytes_written - 1];
442 /* We check that we have submitted a terminator, in which case
443 * a command might have failed or completed successfully
445 * In the generic case, \r is at the end of the command, so we
446 * know the entire command has been submitted. In the case of
447 * commands like CMGS, every \r or Ctrl-Z might result in a
448 * final response from the modem, so we check this as well.
450 if ((c == '\r' || c == 26) &&
451 g_at_chat_handle_command_response(p, cmd, str))
455 if (g_at_chat_match_notify(p, str) == TRUE)
459 /* No matches & no commands active, ignore line */
463 static void have_pdu(GAtChat *p, char *pdu)
466 struct at_notify *notify;
474 result.lines = g_slist_prepend(NULL, p->pdu_notify);
475 result.final_or_pdu = pdu;
477 g_hash_table_iter_init(&iter, p->notify_list);
479 while (g_hash_table_iter_next(&iter, &key, &value)) {
483 if (!g_str_has_prefix(p->pdu_notify, prefix))
489 g_slist_foreach(notify->nodes, at_notify_call_callback,
493 g_slist_free(result.lines);
496 g_free(p->pdu_notify);
497 p->pdu_notify = NULL;
503 static char *extract_line(GAtChat *p)
505 unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
506 unsigned int pos = 0;
507 unsigned char *buf = ring_buffer_read_ptr(p->buf, pos);
512 while (pos < p->read_so_far) {
513 if (*buf == '\r' || *buf == '\n')
525 buf = ring_buffer_read_ptr(p->buf, pos);
528 line = g_try_new(char, line_length + 1);
531 ring_buffer_drain(p->buf, p->read_so_far);
535 ring_buffer_drain(p->buf, strip_front);
536 ring_buffer_read(p->buf, line, line_length);
537 ring_buffer_drain(p->buf, p->read_so_far - strip_front - line_length);
539 line[line_length] = '\0';
544 static void new_bytes(GAtChat *p)
546 unsigned int len = ring_buffer_len(p->buf);
547 unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
548 unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
550 GAtSyntaxResult result;
552 while (p->read_so_far < len) {
553 gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
554 result = p->syntax->feed(p->syntax, (char *)buf, &rbytes);
557 p->read_so_far += rbytes;
559 if (p->read_so_far == wrap) {
560 buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
564 if (result == G_AT_SYNTAX_RESULT_UNSURE)
568 case G_AT_SYNTAX_RESULT_LINE:
569 case G_AT_SYNTAX_RESULT_MULTILINE:
570 have_line(p, extract_line(p));
573 case G_AT_SYNTAX_RESULT_PDU:
574 have_pdu(p, extract_line(p));
577 case G_AT_SYNTAX_RESULT_PROMPT:
578 g_at_chat_wakeup_writer(p);
579 ring_buffer_drain(p->buf, p->read_so_far);
583 ring_buffer_drain(p->buf, p->read_so_far);
587 len -= p->read_so_far;
588 wrap -= p->read_so_far;
592 /* We're overflowing the buffer, shutdown the socket */
593 if (ring_buffer_avail(p->buf) == 0)
594 g_at_chat_shutdown(p);
597 static void debug_chat(GAtChat *chat, gboolean in, const char *str, gsize len)
599 char type = in ? '<' : '>';
600 gsize escaped = 2; /* Enough for '<', ' ' */
602 const char *esc = "<ESC>";
603 gsize esc_size = strlen(esc);
604 const char *ctrlz = "<CtrlZ>";
605 gsize ctrlz_size = strlen(ctrlz);
608 if (!chat->debugf || !len)
611 for (i = 0; i < len; i++) {
616 else if (c == '\r' || c == '\t' || c == '\n')
619 escaped += ctrlz_size;
626 escaped_str = g_malloc(escaped + 1);
627 escaped_str[0] = type;
628 escaped_str[1] = ' ';
629 escaped_str[2] = '\0';
630 escaped_str[escaped] = '\0';
632 for (escaped = 2, i = 0; i < len; i++) {
637 escaped_str[escaped++] = '\\';
638 escaped_str[escaped++] = 'r';
641 escaped_str[escaped++] = '\\';
642 escaped_str[escaped++] = 't';
645 escaped_str[escaped++] = '\\';
646 escaped_str[escaped++] = 'n';
649 strncpy(&escaped_str[escaped], ctrlz, ctrlz_size);
650 escaped += ctrlz_size;
653 strncpy(&escaped_str[escaped], esc, esc_size);
658 escaped_str[escaped++] = c;
660 escaped_str[escaped++] = '\\';
661 escaped_str[escaped++] = '0' + ((c >> 6) & 07);
662 escaped_str[escaped++] = '0' + ((c >> 3) & 07);
663 escaped_str[escaped++] = '0' + (c & 07);
668 chat->debugf(escaped_str, chat->debug_data);
672 static gboolean received_data(GIOChannel *channel, GIOCondition cond,
676 GAtChat *chat = data;
680 gsize total_read = 0;
682 if (cond & G_IO_NVAL)
685 /* Regardless of condition, try to read all the data available */
689 toread = ring_buffer_avail_no_wrap(chat->buf);
694 buf = ring_buffer_write_ptr(chat->buf);
696 err = g_io_channel_read(channel, (char *) buf, toread, &rbytes);
697 debug_chat(chat, TRUE, (char *)buf, rbytes);
699 total_read += rbytes;
702 ring_buffer_write_advance(chat->buf, rbytes);
704 } while (err == G_IO_ERROR_NONE && rbytes > 0);
709 if (cond & (G_IO_HUP | G_IO_ERR))
712 if (err != G_IO_ERROR_NONE && err != G_IO_ERROR_AGAIN)
718 static gboolean wakeup_no_response(gpointer user)
720 GAtChat *chat = user;
721 struct at_command *cmd = g_queue_peek_head(chat->command_queue);
723 chat->timeout_source = 0;
725 /* Sometimes during startup the modem is still in the ready state
726 * and might acknowledge our 'wakeup' command. In that case don't
727 * timeout the wrong command
729 if (cmd == NULL || cmd->id != 0)
732 g_at_chat_finish_command(chat, FALSE, NULL);
737 static gboolean can_write_data(GIOChannel *channel, GIOCondition cond,
740 GAtChat *chat = data;
741 struct at_command *cmd;
747 gboolean wakeup_first = FALSE;
748 #ifdef WRITE_SCHEDULER_DEBUG
752 if (cond & (G_IO_NVAL | G_IO_HUP | G_IO_ERR))
755 /* Grab the first command off the queue and write as
756 * much of it as we can
758 cmd = g_queue_peek_head(chat->command_queue);
760 /* For some reason command queue is empty, cancel write watcher */
764 len = strlen(cmd->cmd);
766 /* For some reason write watcher fired, but we've already
767 * written the entire command out to the io channel,
768 * cancel write watcher
770 if (chat->cmd_bytes_written >= len)
774 if (!chat->wakeup_timer) {
776 chat->wakeup_timer = g_timer_new();
778 } else if (g_timer_elapsed(chat->wakeup_timer, NULL) >
779 chat->inactivity_time)
783 if (chat->cmd_bytes_written == 0 && wakeup_first == TRUE) {
784 cmd = at_command_create(chat->wakeup, NULL, NULL, NULL,
790 g_queue_push_head(chat->command_queue, cmd);
792 len = strlen(chat->wakeup);
794 chat->timeout_source = g_timeout_add(chat->wakeup_timeout,
795 wakeup_no_response, chat);
798 towrite = len - chat->cmd_bytes_written;
800 cr = strchr(cmd->cmd + chat->cmd_bytes_written, '\r');
803 towrite = cr - (cmd->cmd + chat->cmd_bytes_written) + 1;
805 #ifdef WRITE_SCHEDULER_DEBUG
812 err = g_io_channel_write(chat->channel,
813 cmd->cmd + chat->cmd_bytes_written,
814 #ifdef WRITE_SCHEDULER_DEBUG
821 if (err != G_IO_ERROR_NONE) {
822 g_at_chat_shutdown(chat);
826 debug_chat(chat, FALSE, cmd->cmd + chat->cmd_bytes_written,
828 chat->cmd_bytes_written += bytes_written;
830 if (bytes_written < towrite)
833 /* Full command submitted, update timer */
834 if (chat->wakeup_timer)
835 g_timer_start(chat->wakeup_timer);
840 static void g_at_chat_wakeup_writer(GAtChat *chat)
842 if (chat->write_watch != 0)
845 chat->write_watch = g_io_add_watch_full(chat->channel,
847 G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
848 can_write_data, chat,
849 (GDestroyNotify)write_watcher_destroy_notify);
852 GAtChat *g_at_chat_new(GIOChannel *channel, GAtSyntax *syntax)
863 chat = g_try_new0(GAtChat, 1);
869 chat->next_cmd_id = 1;
870 chat->next_notify_id = 1;
873 chat->buf = ring_buffer_new(4096);
878 chat->command_queue = g_queue_new();
880 if (!chat->command_queue)
883 chat->notify_list = g_hash_table_new_full(g_str_hash, g_str_equal,
884 g_free, (GDestroyNotify)at_notify_destroy);
886 if (g_io_channel_set_encoding(channel, NULL, NULL) !=
890 io_flags = g_io_channel_get_flags(channel);
892 io_flags |= G_IO_FLAG_NONBLOCK;
894 if (g_io_channel_set_flags(channel, io_flags, NULL) !=
898 g_io_channel_set_close_on_unref(channel, TRUE);
900 chat->channel = channel;
901 chat->read_watch = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT,
902 G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
904 (GDestroyNotify)read_watcher_destroy_notify);
906 chat->syntax = g_at_syntax_ref(syntax);
912 ring_buffer_free(chat->buf);
914 if (chat->command_queue)
915 g_queue_free(chat->command_queue);
917 if (chat->notify_list)
918 g_hash_table_destroy(chat->notify_list);
924 static int open_device(const char *device)
929 fd = open(device, O_RDWR | O_NOCTTY);
933 tcflush(fd, TCIOFLUSH);
935 /* Switch TTY to raw mode */
936 memset(&ti, 0, sizeof(ti));
939 tcsetattr(fd, TCSANOW, &ti);
944 GAtChat *g_at_chat_new_from_tty(const char *device, GAtSyntax *syntax)
949 fd = open_device(device);
953 channel = g_io_channel_unix_new(fd);
959 return g_at_chat_new(channel, syntax);
962 GAtChat *g_at_chat_ref(GAtChat *chat)
967 g_atomic_int_inc(&chat->ref_count);
972 void g_at_chat_unref(GAtChat *chat)
979 is_zero = g_atomic_int_dec_and_test(&chat->ref_count);
982 g_at_chat_shutdown(chat);
984 g_at_chat_cleanup(chat);
989 gboolean g_at_chat_shutdown(GAtChat *chat)
991 if (chat->channel == NULL)
994 if (chat->timeout_source) {
995 g_source_remove(chat->timeout_source);
996 chat->timeout_source = 0;
999 chat->disconnecting = TRUE;
1001 if (chat->read_watch)
1002 g_source_remove(chat->read_watch);
1004 if (chat->write_watch)
1005 g_source_remove(chat->write_watch);
1010 gboolean g_at_chat_set_disconnect_function(GAtChat *chat,
1011 GAtDisconnectFunc disconnect, gpointer user_data)
1016 chat->user_disconnect = disconnect;
1017 chat->user_disconnect_data = user_data;
1022 gboolean g_at_chat_set_debug(GAtChat *chat, GAtDebugFunc func, gpointer user)
1027 chat->debugf = func;
1028 chat->debug_data = user;
1033 static guint send_common(GAtChat *chat, const char *cmd,
1034 const char **prefix_list,
1035 GAtNotifyFunc listing, GAtResultFunc func,
1036 gpointer user_data, GDestroyNotify notify)
1038 struct at_command *c;
1040 if (chat == NULL || chat->command_queue == NULL)
1043 c = at_command_create(cmd, prefix_list, listing, func,
1049 c->id = chat->next_cmd_id++;
1051 g_queue_push_tail(chat->command_queue, c);
1053 if (g_queue_get_length(chat->command_queue) == 1)
1054 g_at_chat_wakeup_writer(chat);
1059 guint g_at_chat_send(GAtChat *chat, const char *cmd,
1060 const char **prefix_list, GAtResultFunc func,
1061 gpointer user_data, GDestroyNotify notify)
1063 return send_common(chat, cmd, prefix_list, NULL, func,
1067 guint g_at_chat_send_listing(GAtChat *chat, const char *cmd,
1068 const char **prefix_list,
1069 GAtNotifyFunc listing, GAtResultFunc func,
1070 gpointer user_data, GDestroyNotify notify)
1072 if (listing == NULL)
1075 return send_common(chat, cmd, prefix_list, listing, func,
1079 gboolean g_at_chat_cancel(GAtChat *chat, guint id)
1083 if (chat == NULL || chat->command_queue == NULL)
1086 l = g_queue_find_custom(chat->command_queue, GUINT_TO_POINTER(id),
1087 at_command_compare_by_id);
1092 if (l == g_queue_peek_head(chat->command_queue)) {
1093 struct at_command *c = l->data;
1095 /* We can't actually remove it since it is most likely
1096 * already in progress, just null out the callback
1097 * so it won't be called
1101 at_command_destroy(l->data);
1102 g_queue_remove(chat->command_queue, l->data);
1108 static struct at_notify *at_notify_create(GAtChat *chat, const char *prefix,
1111 struct at_notify *notify;
1114 key = g_strdup(prefix);
1119 notify = g_try_new0(struct at_notify, 1);
1128 g_hash_table_insert(chat->notify_list, key, notify);
1133 guint g_at_chat_register(GAtChat *chat, const char *prefix,
1134 GAtNotifyFunc func, gboolean expect_pdu,
1136 GDestroyNotify destroy_notify)
1138 struct at_notify *notify;
1139 struct at_notify_node *node;
1141 if (chat == NULL || chat->notify_list == NULL)
1147 if (prefix == NULL || strlen(prefix) == 0)
1150 notify = g_hash_table_lookup(chat->notify_list, prefix);
1153 notify = at_notify_create(chat, prefix, expect_pdu);
1155 if (!notify || notify->pdu != expect_pdu)
1158 node = g_try_new0(struct at_notify_node, 1);
1163 node->id = chat->next_notify_id++;
1164 node->callback = func;
1165 node->user_data = user_data;
1166 node->notify = destroy_notify;
1168 notify->nodes = g_slist_prepend(notify->nodes, node);
1173 gboolean g_at_chat_unregister(GAtChat *chat, guint id)
1175 GHashTableIter iter;
1176 struct at_notify *notify;
1178 gpointer key, value;
1181 if (chat == NULL || chat->notify_list == NULL)
1184 g_hash_table_iter_init(&iter, chat->notify_list);
1186 while (g_hash_table_iter_next(&iter, &key, &value)) {
1190 l = g_slist_find_custom(notify->nodes, GUINT_TO_POINTER(id),
1191 at_notify_node_compare_by_id);
1196 at_notify_node_destroy(l->data);
1197 notify->nodes = g_slist_remove(notify->nodes, l->data);
1199 if (notify->nodes == NULL)
1200 g_hash_table_iter_remove(&iter);
1208 gboolean g_at_chat_set_wakeup_command(GAtChat *chat, const char *cmd,
1209 unsigned int timeout, unsigned int msec)
1215 g_free(chat->wakeup);
1217 chat->wakeup = g_strdup(cmd);
1218 chat->inactivity_time = (gdouble)msec / 1000;
1219 chat->wakeup_timeout = timeout;