mirror of git://git.sysmocom.de/ofono
Refactor GAtChat to accept user-provided parsers
Intended for really broken modems. A default 27.007 compliant parser is provided.
This commit is contained in:
parent
239ab461f7
commit
83820c88b2
|
@ -2,7 +2,7 @@
|
||||||
noinst_LTLIBRARIES = libgatchat.la
|
noinst_LTLIBRARIES = libgatchat.la
|
||||||
|
|
||||||
libgatchat_la_SOURCES = gatchat.h gatchat.c gatresult.h gatresult.c \
|
libgatchat_la_SOURCES = gatchat.h gatchat.c gatresult.h gatresult.c \
|
||||||
ringbuffer.h ringbuffer.c
|
ringbuffer.h ringbuffer.c gatsyntax.h gatsyntax.c
|
||||||
|
|
||||||
AM_CFLAGS = @GLIB_CFLAGS@
|
AM_CFLAGS = @GLIB_CFLAGS@
|
||||||
|
|
||||||
|
|
|
@ -33,30 +33,12 @@
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
||||||
#include "ringbuffer.h"
|
#include "ringbuffer.h"
|
||||||
#include "gatresult.h"
|
|
||||||
#include "gatchat.h"
|
#include "gatchat.h"
|
||||||
|
|
||||||
/* #define WRITE_SCHEDULER_DEBUG 1 */
|
/* #define WRITE_SCHEDULER_DEBUG 1 */
|
||||||
|
|
||||||
static void g_at_chat_wakeup_writer(GAtChat *chat);
|
static void g_at_chat_wakeup_writer(GAtChat *chat);
|
||||||
|
static void debug_chat(GAtChat *chat, gboolean in, const char *str, gsize len);
|
||||||
enum chat_state {
|
|
||||||
PARSER_STATE_IDLE = 0,
|
|
||||||
PARSER_STATE_INITIAL_CR,
|
|
||||||
PARSER_STATE_INITIAL_LF,
|
|
||||||
PARSER_STATE_RESPONSE,
|
|
||||||
PARSER_STATE_TERMINATOR_CR,
|
|
||||||
PARSER_STATE_RESPONSE_COMPLETE,
|
|
||||||
PARSER_STATE_GUESS_MULTILINE_RESPONSE,
|
|
||||||
PARSER_STATE_MULTILINE_RESPONSE,
|
|
||||||
PARSER_STATE_MULTILINE_TERMINATOR_CR,
|
|
||||||
PARSER_STATE_MULTILINE_COMPLETE,
|
|
||||||
PARSER_STATE_PDU,
|
|
||||||
PARSER_STATE_PDU_CR,
|
|
||||||
PARSER_STATE_PDU_COMPLETE,
|
|
||||||
PARSER_STATE_PROMPT,
|
|
||||||
PARSER_STATE_PROMPT_COMPLETE
|
|
||||||
};
|
|
||||||
|
|
||||||
struct at_command {
|
struct at_command {
|
||||||
char *cmd;
|
char *cmd;
|
||||||
|
@ -95,14 +77,13 @@ struct _GAtChat {
|
||||||
struct ring_buffer *buf; /* Current read buffer */
|
struct ring_buffer *buf; /* Current read buffer */
|
||||||
guint read_so_far; /* Number of bytes processed */
|
guint read_so_far; /* Number of bytes processed */
|
||||||
gboolean disconnecting; /* Whether we're disconnecting */
|
gboolean disconnecting; /* Whether we're disconnecting */
|
||||||
enum chat_state state; /* Current chat state */
|
|
||||||
int flags;
|
|
||||||
char *pdu_notify; /* Unsolicited Resp w/ PDU */
|
char *pdu_notify; /* Unsolicited Resp w/ PDU */
|
||||||
GSList *response_lines; /* char * lines of the response */
|
GSList *response_lines; /* char * lines of the response */
|
||||||
char *wakeup; /* command sent to wakeup modem */
|
char *wakeup; /* command sent to wakeup modem */
|
||||||
gdouble inactivity_time; /* Period of inactivity */
|
gdouble inactivity_time; /* Period of inactivity */
|
||||||
guint wakeup_timeout; /* How long to wait for resp */
|
guint wakeup_timeout; /* How long to wait for resp */
|
||||||
GTimer *wakeup_timer; /* Keep track of elapsed time */
|
GTimer *wakeup_timer; /* Keep track of elapsed time */
|
||||||
|
GAtSyntax *syntax;
|
||||||
};
|
};
|
||||||
|
|
||||||
static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
|
static gint at_notify_node_compare_by_id(gconstpointer a, gconstpointer b)
|
||||||
|
@ -254,6 +235,9 @@ static void g_at_chat_cleanup(GAtChat *chat)
|
||||||
g_timer_destroy(chat->wakeup_timer);
|
g_timer_destroy(chat->wakeup_timer);
|
||||||
chat->wakeup_timer = 0;
|
chat->wakeup_timer = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_at_syntax_unref(chat->syntax);
|
||||||
|
chat->syntax = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void read_watcher_destroy_notify(GAtChat *chat)
|
static void read_watcher_destroy_notify(GAtChat *chat)
|
||||||
|
@ -306,7 +290,10 @@ static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
|
||||||
|
|
||||||
if (notify->pdu) {
|
if (notify->pdu) {
|
||||||
chat->pdu_notify = line;
|
chat->pdu_notify = line;
|
||||||
chat->state = PARSER_STATE_PDU;
|
|
||||||
|
if (chat->syntax->set_hint)
|
||||||
|
chat->syntax->set_hint(chat->syntax,
|
||||||
|
G_AT_SYNTAX_EXPECT_PDU);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -321,7 +308,6 @@ static gboolean g_at_chat_match_notify(GAtChat *chat, char *line)
|
||||||
if (ret) {
|
if (ret) {
|
||||||
g_slist_free(result.lines);
|
g_slist_free(result.lines);
|
||||||
g_free(line);
|
g_free(line);
|
||||||
chat->state = PARSER_STATE_IDLE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -387,8 +373,6 @@ static gboolean g_at_chat_handle_command_response(GAtChat *p,
|
||||||
int i;
|
int i;
|
||||||
int size = sizeof(terminator_table) / sizeof(struct terminator_info);
|
int size = sizeof(terminator_table) / sizeof(struct terminator_info);
|
||||||
|
|
||||||
p->state = PARSER_STATE_IDLE;
|
|
||||||
|
|
||||||
for (i = 0; i < size; i++) {
|
for (i = 0; i < size; i++) {
|
||||||
struct terminator_info *info = &terminator_table[i];
|
struct terminator_info *info = &terminator_table[i];
|
||||||
|
|
||||||
|
@ -415,8 +399,8 @@ static gboolean g_at_chat_handle_command_response(GAtChat *p,
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (!(p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
|
if (p->syntax->set_hint)
|
||||||
p->state = PARSER_STATE_GUESS_MULTILINE_RESPONSE;
|
p->syntax->set_hint(p->syntax, G_AT_SYNTAX_EXPECT_MULTILINE);
|
||||||
|
|
||||||
if (cmd->listing) {
|
if (cmd->listing) {
|
||||||
GAtResult result;
|
GAtResult result;
|
||||||
|
@ -434,31 +418,13 @@ out:
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void have_line(GAtChat *p, gboolean strip_preceding)
|
static void have_line(GAtChat *p, char *str)
|
||||||
{
|
{
|
||||||
/* We're not going to copy terminal <CR><LF> */
|
/* We're not going to copy terminal <CR><LF> */
|
||||||
unsigned int len = p->read_so_far - 2;
|
|
||||||
char *str;
|
|
||||||
struct at_command *cmd;
|
struct at_command *cmd;
|
||||||
|
|
||||||
/* If we have preceding <CR><LF> modify the len */
|
if (!str)
|
||||||
if (strip_preceding)
|
|
||||||
len -= 2;
|
|
||||||
|
|
||||||
/* Make sure we have terminal null */
|
|
||||||
str = g_try_new(char, len + 1);
|
|
||||||
|
|
||||||
if (!str) {
|
|
||||||
ring_buffer_drain(p->buf, p->read_so_far);
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (strip_preceding)
|
|
||||||
ring_buffer_drain(p->buf, 2);
|
|
||||||
ring_buffer_read(p->buf, str, len);
|
|
||||||
ring_buffer_drain(p->buf, 2);
|
|
||||||
|
|
||||||
str[len] = '\0';
|
|
||||||
|
|
||||||
/* Check for echo, this should not happen, but lets be paranoid */
|
/* Check for echo, this should not happen, but lets be paranoid */
|
||||||
if (!strncmp(str, "AT", 2) == TRUE)
|
if (!strncmp(str, "AT", 2) == TRUE)
|
||||||
|
@ -488,30 +454,18 @@ static void have_line(GAtChat *p, gboolean strip_preceding)
|
||||||
done:
|
done:
|
||||||
/* No matches & no commands active, ignore line */
|
/* No matches & no commands active, ignore line */
|
||||||
g_free(str);
|
g_free(str);
|
||||||
p->state = PARSER_STATE_IDLE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void have_pdu(GAtChat *p)
|
static void have_pdu(GAtChat *p, char *pdu)
|
||||||
{
|
{
|
||||||
unsigned int len = p->read_so_far - 2;
|
|
||||||
char *pdu;
|
|
||||||
GHashTableIter iter;
|
GHashTableIter iter;
|
||||||
struct at_notify *notify;
|
struct at_notify *notify;
|
||||||
char *prefix;
|
char *prefix;
|
||||||
gpointer key, value;
|
gpointer key, value;
|
||||||
GAtResult result;
|
GAtResult result;
|
||||||
|
|
||||||
pdu = g_try_new(char, len + 1);
|
if (!pdu)
|
||||||
|
|
||||||
if (!pdu) {
|
|
||||||
ring_buffer_drain(p->buf, p->read_so_far);
|
|
||||||
goto out;
|
goto out;
|
||||||
}
|
|
||||||
|
|
||||||
ring_buffer_read(p->buf, pdu, len);
|
|
||||||
ring_buffer_drain(p->buf, 2);
|
|
||||||
|
|
||||||
pdu[len] = '\0';
|
|
||||||
|
|
||||||
result.lines = g_slist_prepend(NULL, p->pdu_notify);
|
result.lines = g_slist_prepend(NULL, p->pdu_notify);
|
||||||
result.final_or_pdu = pdu;
|
result.final_or_pdu = pdu;
|
||||||
|
@ -540,97 +494,47 @@ out:
|
||||||
|
|
||||||
if (pdu)
|
if (pdu)
|
||||||
g_free(pdu);
|
g_free(pdu);
|
||||||
|
|
||||||
p->state = PARSER_STATE_IDLE;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void parse_char(GAtChat *chat, char byte)
|
static char *extract_line(GAtChat *p)
|
||||||
{
|
{
|
||||||
switch (chat->state) {
|
unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
|
||||||
case PARSER_STATE_IDLE:
|
unsigned int pos = 0;
|
||||||
if (byte == '\r')
|
unsigned char *buf = ring_buffer_read_ptr(p->buf, pos);
|
||||||
chat->state = PARSER_STATE_INITIAL_CR;
|
int strip_front = 0;
|
||||||
else if (chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF) {
|
int line_length = 0;
|
||||||
if (byte == '>')
|
char *line;
|
||||||
chat->state = PARSER_STATE_PROMPT;
|
|
||||||
|
while (pos < p->read_so_far) {
|
||||||
|
if (*buf == '\r' || *buf == '\n')
|
||||||
|
if (!line_length)
|
||||||
|
strip_front += 1;
|
||||||
else
|
else
|
||||||
chat->state = PARSER_STATE_RESPONSE;
|
break;
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_INITIAL_CR:
|
|
||||||
if (byte == '\n')
|
|
||||||
chat->state = PARSER_STATE_INITIAL_LF;
|
|
||||||
else if (byte != '\r' && /* Echo & no <CR><LF>?! */
|
|
||||||
(chat->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF))
|
|
||||||
chat->state = PARSER_STATE_RESPONSE;
|
|
||||||
else if (byte != '\r')
|
|
||||||
chat->state = PARSER_STATE_IDLE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_INITIAL_LF:
|
|
||||||
if (byte == '\r')
|
|
||||||
chat->state = PARSER_STATE_TERMINATOR_CR;
|
|
||||||
else if (byte == '>')
|
|
||||||
chat->state = PARSER_STATE_PROMPT;
|
|
||||||
else
|
else
|
||||||
chat->state = PARSER_STATE_RESPONSE;
|
line_length += 1;
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_RESPONSE:
|
buf += 1;
|
||||||
if (byte == '\r')
|
pos += 1;
|
||||||
chat->state = PARSER_STATE_TERMINATOR_CR;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_TERMINATOR_CR:
|
if (pos == wrap)
|
||||||
if (byte == '\n')
|
buf = ring_buffer_read_ptr(p->buf, pos);
|
||||||
chat->state = PARSER_STATE_RESPONSE_COMPLETE;
|
|
||||||
else
|
|
||||||
chat->state = PARSER_STATE_IDLE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_GUESS_MULTILINE_RESPONSE:
|
|
||||||
if (byte == '\r')
|
|
||||||
chat->state = PARSER_STATE_INITIAL_CR;
|
|
||||||
else
|
|
||||||
chat->state = PARSER_STATE_MULTILINE_RESPONSE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_MULTILINE_RESPONSE:
|
|
||||||
if (byte == '\r')
|
|
||||||
chat->state = PARSER_STATE_MULTILINE_TERMINATOR_CR;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_MULTILINE_TERMINATOR_CR:
|
|
||||||
if (byte == '\n')
|
|
||||||
chat->state = PARSER_STATE_MULTILINE_COMPLETE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_PDU:
|
|
||||||
if (byte == '\r')
|
|
||||||
chat->state = PARSER_STATE_PDU_CR;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_PDU_CR:
|
|
||||||
if (byte == '\n')
|
|
||||||
chat->state = PARSER_STATE_PDU_COMPLETE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_PROMPT:
|
|
||||||
if (byte == ' ')
|
|
||||||
chat->state = PARSER_STATE_PROMPT_COMPLETE;
|
|
||||||
else
|
|
||||||
chat->state = PARSER_STATE_RESPONSE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case PARSER_STATE_RESPONSE_COMPLETE:
|
|
||||||
case PARSER_STATE_PDU_COMPLETE:
|
|
||||||
case PARSER_STATE_MULTILINE_COMPLETE:
|
|
||||||
default:
|
|
||||||
/* This really shouldn't happen */
|
|
||||||
assert(FALSE);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
line = g_try_new(char, line_length + 1);
|
||||||
|
|
||||||
|
if (!line) {
|
||||||
|
ring_buffer_drain(p->buf, p->read_so_far);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ring_buffer_drain(p->buf, strip_front);
|
||||||
|
ring_buffer_read(p->buf, line, line_length);
|
||||||
|
ring_buffer_drain(p->buf, p->read_so_far - strip_front - line_length);
|
||||||
|
|
||||||
|
line[line_length] = '\0';
|
||||||
|
|
||||||
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void new_bytes(GAtChat *p)
|
static void new_bytes(GAtChat *p)
|
||||||
|
@ -639,76 +543,45 @@ static void new_bytes(GAtChat *p)
|
||||||
unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
|
unsigned int wrap = ring_buffer_len_no_wrap(p->buf);
|
||||||
unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
|
unsigned char *buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
|
||||||
|
|
||||||
while (p->read_so_far < len) {
|
GAtSyntaxResult result;
|
||||||
parse_char(p, *buf);
|
|
||||||
|
|
||||||
buf += 1;
|
while (p->read_so_far < len) {
|
||||||
p->read_so_far += 1;
|
gsize rbytes = MIN(len - p->read_so_far, wrap - p->read_so_far);
|
||||||
|
result = p->syntax->feed(p->syntax, (char *)buf, &rbytes);
|
||||||
|
|
||||||
|
buf += rbytes;
|
||||||
|
p->read_so_far += rbytes;
|
||||||
|
|
||||||
if (p->read_so_far == wrap) {
|
if (p->read_so_far == wrap) {
|
||||||
buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
|
buf = ring_buffer_read_ptr(p->buf, p->read_so_far);
|
||||||
wrap = len;
|
wrap = len;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (p->state == PARSER_STATE_RESPONSE_COMPLETE) {
|
if (result == G_AT_SYNTAX_RESULT_UNSURE)
|
||||||
gboolean strip_preceding;
|
continue;
|
||||||
|
|
||||||
if (p->flags & G_AT_CHAT_FLAG_NO_LEADING_CRLF)
|
switch (result) {
|
||||||
strip_preceding = FALSE;
|
case G_AT_SYNTAX_RESULT_LINE:
|
||||||
else
|
case G_AT_SYNTAX_RESULT_MULTILINE:
|
||||||
strip_preceding = TRUE;
|
have_line(p, extract_line(p));
|
||||||
|
break;
|
||||||
|
|
||||||
len -= p->read_so_far;
|
case G_AT_SYNTAX_RESULT_PDU:
|
||||||
wrap -= p->read_so_far;
|
have_pdu(p, extract_line(p));
|
||||||
|
break;
|
||||||
have_line(p, strip_preceding);
|
|
||||||
|
|
||||||
p->read_so_far = 0;
|
|
||||||
} else if (p->state == PARSER_STATE_MULTILINE_COMPLETE) {
|
|
||||||
len -= p->read_so_far;
|
|
||||||
wrap -= p->read_so_far;
|
|
||||||
|
|
||||||
have_line(p, FALSE);
|
|
||||||
|
|
||||||
p->read_so_far = 0;
|
|
||||||
} else if (p->state == PARSER_STATE_PDU_COMPLETE) {
|
|
||||||
len -= p->read_so_far;
|
|
||||||
wrap -= p->read_so_far;
|
|
||||||
|
|
||||||
/* Some modems like the TI Calypso send a CMT style
|
|
||||||
* notification with an extra CRLF thrown in
|
|
||||||
*/
|
|
||||||
if ((p->flags & G_AT_CHAT_FLAG_EXTRA_PDU_CRLF) &&
|
|
||||||
p->read_so_far == 2) {
|
|
||||||
p->state = PARSER_STATE_PDU;
|
|
||||||
ring_buffer_drain(p->buf, p->read_so_far);
|
|
||||||
} else
|
|
||||||
have_pdu(p);
|
|
||||||
|
|
||||||
p->read_so_far = 0;
|
|
||||||
} else if (p->state == PARSER_STATE_INITIAL_CR) {
|
|
||||||
len -= p->read_so_far - 1;
|
|
||||||
wrap -= p->read_so_far - 1;
|
|
||||||
|
|
||||||
ring_buffer_drain(p->buf, p->read_so_far - 1);
|
|
||||||
|
|
||||||
p->read_so_far = 1;
|
|
||||||
} else if (p->state == PARSER_STATE_PROMPT_COMPLETE) {
|
|
||||||
len -= p->read_so_far;
|
|
||||||
wrap -= p->read_so_far;
|
|
||||||
|
|
||||||
|
case G_AT_SYNTAX_RESULT_PROMPT:
|
||||||
g_at_chat_wakeup_writer(p);
|
g_at_chat_wakeup_writer(p);
|
||||||
|
|
||||||
ring_buffer_drain(p->buf, p->read_so_far);
|
ring_buffer_drain(p->buf, p->read_so_far);
|
||||||
|
break;
|
||||||
|
|
||||||
p->read_so_far = 0;
|
default:
|
||||||
|
ring_buffer_drain(p->buf, p->read_so_far);
|
||||||
p->state = PARSER_STATE_IDLE;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (p->state == PARSER_STATE_IDLE && p->read_so_far > 0) {
|
len -= p->read_so_far;
|
||||||
ring_buffer_drain(p->buf, p->read_so_far);
|
wrap -= p->read_so_far;
|
||||||
p->read_so_far = 0;
|
p->read_so_far = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -736,13 +609,8 @@ static gboolean received_data(GIOChannel *channel, GIOCondition cond,
|
||||||
* this cannot happen under normal circumstances, so probably
|
* this cannot happen under normal circumstances, so probably
|
||||||
* the channel is getting garbage, drop off
|
* the channel is getting garbage, drop off
|
||||||
*/
|
*/
|
||||||
if (toread == 0) {
|
if (toread == 0)
|
||||||
if (chat->state == PARSER_STATE_RESPONSE)
|
return FALSE;
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
err = G_IO_ERROR_AGAIN;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = ring_buffer_write_ptr(chat->buf);
|
buf = ring_buffer_write_ptr(chat->buf);
|
||||||
|
|
||||||
|
@ -900,7 +768,7 @@ static void g_at_chat_wakeup_writer(GAtChat *chat)
|
||||||
(GDestroyNotify)write_watcher_destroy_notify);
|
(GDestroyNotify)write_watcher_destroy_notify);
|
||||||
}
|
}
|
||||||
|
|
||||||
GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
|
GAtChat *g_at_chat_new(GIOChannel *channel, GAtSyntax *syntax)
|
||||||
{
|
{
|
||||||
GAtChat *chat;
|
GAtChat *chat;
|
||||||
GIOFlags io_flags;
|
GIOFlags io_flags;
|
||||||
|
@ -908,6 +776,9 @@ GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
|
||||||
if (!channel)
|
if (!channel)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
if (!syntax)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
chat = g_try_new0(GAtChat, 1);
|
chat = g_try_new0(GAtChat, 1);
|
||||||
|
|
||||||
if (!chat)
|
if (!chat)
|
||||||
|
@ -916,7 +787,6 @@ GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
|
||||||
chat->ref_count = 1;
|
chat->ref_count = 1;
|
||||||
chat->next_cmd_id = 1;
|
chat->next_cmd_id = 1;
|
||||||
chat->next_notify_id = 1;
|
chat->next_notify_id = 1;
|
||||||
chat->flags = flags;
|
|
||||||
|
|
||||||
chat->buf = ring_buffer_new(4096);
|
chat->buf = ring_buffer_new(4096);
|
||||||
|
|
||||||
|
@ -951,6 +821,8 @@ GAtChat *g_at_chat_new(GIOChannel *channel, int flags)
|
||||||
received_data, chat,
|
received_data, chat,
|
||||||
(GDestroyNotify)read_watcher_destroy_notify);
|
(GDestroyNotify)read_watcher_destroy_notify);
|
||||||
|
|
||||||
|
chat->syntax = g_at_syntax_ref(syntax);
|
||||||
|
|
||||||
return chat;
|
return chat;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
|
@ -967,22 +839,6 @@ error:
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int g_at_chat_get_flags(GAtChat *chat)
|
|
||||||
{
|
|
||||||
if (chat == NULL)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return chat->flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
void g_at_chat_set_flags(GAtChat *chat, int flags)
|
|
||||||
{
|
|
||||||
if (chat == NULL)
|
|
||||||
return;
|
|
||||||
|
|
||||||
chat->flags = flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int open_device(const char *device)
|
static int open_device(const char *device)
|
||||||
{
|
{
|
||||||
struct termios ti;
|
struct termios ti;
|
||||||
|
@ -1003,7 +859,7 @@ static int open_device(const char *device)
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
GAtChat *g_at_chat_new_from_tty(const char *device, int flags)
|
GAtChat *g_at_chat_new_from_tty(const char *device, GAtSyntax *syntax)
|
||||||
{
|
{
|
||||||
GIOChannel *channel;
|
GIOChannel *channel;
|
||||||
int fd;
|
int fd;
|
||||||
|
@ -1018,7 +874,7 @@ GAtChat *g_at_chat_new_from_tty(const char *device, int flags)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
return g_at_chat_new(channel, flags);
|
return g_at_chat_new(channel, syntax);
|
||||||
}
|
}
|
||||||
|
|
||||||
GAtChat *g_at_chat_ref(GAtChat *chat)
|
GAtChat *g_at_chat_ref(GAtChat *chat)
|
||||||
|
|
|
@ -27,6 +27,7 @@ extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "gatresult.h"
|
#include "gatresult.h"
|
||||||
|
#include "gatsyntax.h"
|
||||||
|
|
||||||
struct _GAtChat;
|
struct _GAtChat;
|
||||||
|
|
||||||
|
@ -44,11 +45,8 @@ enum _GAtChatFlags {
|
||||||
|
|
||||||
typedef enum _GAtChatFlags GAtChatFlags;
|
typedef enum _GAtChatFlags GAtChatFlags;
|
||||||
|
|
||||||
GAtChat *g_at_chat_new(GIOChannel *channel, int flags);
|
GAtChat *g_at_chat_new(GIOChannel *channel, GAtSyntax *syntax);
|
||||||
GAtChat *g_at_chat_new_from_tty(const char *device, int flags);
|
GAtChat *g_at_chat_new_from_tty(const char *device, GAtSyntax *syntax);
|
||||||
|
|
||||||
int g_at_chat_get_flags(GAtChat *chat);
|
|
||||||
void g_at_chat_set_flags(GAtChat *chat, int flags);
|
|
||||||
|
|
||||||
GAtChat *g_at_chat_ref(GAtChat *chat);
|
GAtChat *g_at_chat_ref(GAtChat *chat);
|
||||||
void g_at_chat_unref(GAtChat *chat);
|
void g_at_chat_unref(GAtChat *chat);
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* AT chat library with GLib integration
|
||||||
|
*
|
||||||
|
* Copyright (C) 2008-2009 Intel Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include <config.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
|
||||||
|
#include "gatsyntax.h"
|
||||||
|
|
||||||
|
enum GSMV1_STATE_ {
|
||||||
|
GSMV1_STATE_IDLE = 0,
|
||||||
|
GSMV1_STATE_INITIAL_CR,
|
||||||
|
GSMV1_STATE_INITIAL_LF,
|
||||||
|
GSMV1_STATE_RESPONSE,
|
||||||
|
GSMV1_STATE_TERMINATOR_CR,
|
||||||
|
GSMV1_STATE_GUESS_MULTILINE_RESPONSE,
|
||||||
|
GSMV1_STATE_MULTILINE_RESPONSE,
|
||||||
|
GSMV1_STATE_MULTILINE_TERMINATOR_CR,
|
||||||
|
GSMV1_STATE_PDU,
|
||||||
|
GSMV1_STATE_PDU_CR,
|
||||||
|
GSMV1_STATE_PROMPT,
|
||||||
|
GSMV1_STATE_GARBAGE,
|
||||||
|
GSMV1_STATE_GARBAGE_CHECK_LF,
|
||||||
|
};
|
||||||
|
|
||||||
|
static void gsmv1_hint(GAtSyntax *syntax, GAtSyntaxExpectHint hint)
|
||||||
|
{
|
||||||
|
switch (hint) {
|
||||||
|
case G_AT_SYNTAX_EXPECT_PDU:
|
||||||
|
syntax->state = GSMV1_STATE_PDU;
|
||||||
|
break;
|
||||||
|
case G_AT_SYNTAX_EXPECT_MULTILINE:
|
||||||
|
syntax->state = GSMV1_STATE_GUESS_MULTILINE_RESPONSE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static GAtSyntaxResult gsmv1_feed(GAtSyntax *syntax,
|
||||||
|
const char *bytes, gsize *len)
|
||||||
|
{
|
||||||
|
gsize i = 0;
|
||||||
|
GAtSyntaxResult res = G_AT_SYNTAX_RESULT_UNSURE;
|
||||||
|
|
||||||
|
while (i < *len) {
|
||||||
|
char byte = bytes[i];
|
||||||
|
|
||||||
|
switch (syntax->state) {
|
||||||
|
case GSMV1_STATE_IDLE:
|
||||||
|
if (byte == '\r')
|
||||||
|
syntax->state = GSMV1_STATE_INITIAL_CR;
|
||||||
|
else
|
||||||
|
syntax->state = GSMV1_STATE_GARBAGE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSMV1_STATE_INITIAL_CR:
|
||||||
|
if (byte == '\n')
|
||||||
|
syntax->state = GSMV1_STATE_INITIAL_LF;
|
||||||
|
else
|
||||||
|
syntax->state = GSMV1_STATE_GARBAGE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSMV1_STATE_INITIAL_LF:
|
||||||
|
if (byte == '\r')
|
||||||
|
syntax->state = GSMV1_STATE_TERMINATOR_CR;
|
||||||
|
else if (byte == '>')
|
||||||
|
syntax->state = GSMV1_STATE_PROMPT;
|
||||||
|
else
|
||||||
|
syntax->state = GSMV1_STATE_RESPONSE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSMV1_STATE_RESPONSE:
|
||||||
|
if (byte == '\r')
|
||||||
|
syntax->state = GSMV1_STATE_TERMINATOR_CR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSMV1_STATE_TERMINATOR_CR:
|
||||||
|
syntax->state = GSMV1_STATE_IDLE;
|
||||||
|
|
||||||
|
if (byte == '\n') {
|
||||||
|
i += 1;
|
||||||
|
res = G_AT_SYNTAX_RESULT_LINE;
|
||||||
|
} else
|
||||||
|
res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
|
||||||
|
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
case GSMV1_STATE_GUESS_MULTILINE_RESPONSE:
|
||||||
|
if (byte == '\r')
|
||||||
|
syntax->state = GSMV1_STATE_INITIAL_CR;
|
||||||
|
else
|
||||||
|
syntax->state = GSMV1_STATE_MULTILINE_RESPONSE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSMV1_STATE_MULTILINE_RESPONSE:
|
||||||
|
if (byte == '\r')
|
||||||
|
syntax->state = GSMV1_STATE_MULTILINE_TERMINATOR_CR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSMV1_STATE_MULTILINE_TERMINATOR_CR:
|
||||||
|
syntax->state = GSMV1_STATE_IDLE;
|
||||||
|
|
||||||
|
if (byte == '\n') {
|
||||||
|
i += 1;
|
||||||
|
res = G_AT_SYNTAX_RESULT_MULTILINE;
|
||||||
|
} else
|
||||||
|
res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
|
||||||
|
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
case GSMV1_STATE_PDU:
|
||||||
|
if (byte == '\r')
|
||||||
|
syntax->state = GSMV1_STATE_PDU_CR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSMV1_STATE_PDU_CR:
|
||||||
|
syntax->state = GSMV1_STATE_IDLE;
|
||||||
|
|
||||||
|
if (byte == '\n') {
|
||||||
|
i += 1;
|
||||||
|
res = G_AT_SYNTAX_RESULT_PDU;
|
||||||
|
} else
|
||||||
|
res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
|
||||||
|
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
case GSMV1_STATE_PROMPT:
|
||||||
|
if (byte == ' ') {
|
||||||
|
syntax->state = GSMV1_STATE_IDLE;
|
||||||
|
i += 1;
|
||||||
|
res = G_AT_SYNTAX_RESULT_PROMPT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
syntax->state = GSMV1_STATE_RESPONSE;
|
||||||
|
return G_AT_SYNTAX_RESULT_UNSURE;
|
||||||
|
|
||||||
|
case GSMV1_STATE_GARBAGE:
|
||||||
|
if (byte == '\r')
|
||||||
|
syntax->state = GSMV1_STATE_GARBAGE_CHECK_LF;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GSMV1_STATE_GARBAGE_CHECK_LF:
|
||||||
|
syntax->state = GSMV1_STATE_IDLE;
|
||||||
|
res = G_AT_SYNTAX_RESULT_UNRECOGNIZED;
|
||||||
|
|
||||||
|
if (byte == '\n')
|
||||||
|
i += 1;
|
||||||
|
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
*len = i;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
GAtSyntax *g_at_syntax_new_full(GAtSyntaxFeedFunc feed,
|
||||||
|
GAtSyntaxSetHintFunc hint,
|
||||||
|
int initial_state)
|
||||||
|
{
|
||||||
|
GAtSyntax *syntax;
|
||||||
|
|
||||||
|
syntax = g_new0(GAtSyntax, 1);
|
||||||
|
|
||||||
|
syntax->feed = feed;
|
||||||
|
syntax->set_hint = hint;
|
||||||
|
syntax->state = initial_state;
|
||||||
|
syntax->ref_count = 1;
|
||||||
|
|
||||||
|
return syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GAtSyntax *g_at_syntax_new_gsmv1()
|
||||||
|
{
|
||||||
|
return g_at_syntax_new_full(gsmv1_feed, gsmv1_hint, GSMV1_STATE_IDLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
GAtSyntax *g_at_syntax_ref(GAtSyntax *syntax)
|
||||||
|
{
|
||||||
|
if (syntax == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
g_atomic_int_inc(&syntax->ref_count);
|
||||||
|
|
||||||
|
return syntax;
|
||||||
|
}
|
||||||
|
|
||||||
|
void g_at_syntax_unref(GAtSyntax *syntax)
|
||||||
|
{
|
||||||
|
gboolean is_zero;
|
||||||
|
|
||||||
|
if (syntax == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
is_zero = g_atomic_int_dec_and_test(&syntax->ref_count);
|
||||||
|
|
||||||
|
if (is_zero)
|
||||||
|
g_free(syntax);
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* AT chat library with GLib integration
|
||||||
|
*
|
||||||
|
* Copyright (C) 2008-2009 Intel Corporation. All rights reserved.
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License version 2 as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __GATSYNTAX_H
|
||||||
|
#define __GATSYNTAX_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum _GAtSyntaxExpectHint {
|
||||||
|
G_AT_SYNTAX_EXPECT_PDU,
|
||||||
|
G_AT_SYNTAX_EXPECT_MULTILINE,
|
||||||
|
G_AT_SYNTAX_EXPECT_PROMPT
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum _GAtSyntaxExpectHint GAtSyntaxExpectHint;
|
||||||
|
|
||||||
|
enum _GAtSyntaxResult {
|
||||||
|
G_AT_SYNTAX_RESULT_UNRECOGNIZED,
|
||||||
|
G_AT_SYNTAX_RESULT_UNSURE,
|
||||||
|
G_AT_SYNTAX_RESULT_LINE,
|
||||||
|
G_AT_SYNTAX_RESULT_MULTILINE,
|
||||||
|
G_AT_SYNTAX_RESULT_PDU,
|
||||||
|
G_AT_SYNTAX_RESULT_PROMPT,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum _GAtSyntaxResult GAtSyntaxResult;
|
||||||
|
|
||||||
|
typedef struct _GAtSyntax GAtSyntax;
|
||||||
|
|
||||||
|
typedef void (*GAtSyntaxSetHintFunc)(GAtSyntax *syntax,
|
||||||
|
GAtSyntaxExpectHint hint);
|
||||||
|
typedef GAtSyntaxResult (*GAtSyntaxFeedFunc)(GAtSyntax *syntax,
|
||||||
|
const char *bytes, gsize *len);
|
||||||
|
|
||||||
|
struct _GAtSyntax {
|
||||||
|
gint ref_count;
|
||||||
|
int state;
|
||||||
|
GAtSyntaxSetHintFunc set_hint;
|
||||||
|
GAtSyntaxFeedFunc feed;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
GAtSyntax *g_at_syntax_new_full(GAtSyntaxFeedFunc feed,
|
||||||
|
GAtSyntaxSetHintFunc hint,
|
||||||
|
int initial_state);
|
||||||
|
GAtSyntax *g_at_syntax_new_gsmv1();
|
||||||
|
|
||||||
|
GAtSyntax *g_at_syntax_ref(GAtSyntax *syntax);
|
||||||
|
void g_at_syntax_unref(GAtSyntax *syntax);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __GATSYNTAX_H */
|
Loading…
Reference in New Issue