1325 lines
38 KiB
C
1325 lines
38 KiB
C
/******************************************************************************
|
|
* @brief AT command communication management V2
|
|
*
|
|
* Copyright (c) 2020~2022, <morro_luo@163.com>
|
|
*
|
|
* SPDX-License-Identifier: Apathe-2.0
|
|
*
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2020-01-02 roger.luo Initial version
|
|
* 2021-01-20 roger.luo Add debugging interface, and at the same time
|
|
* solve the problem of uninitialized linked list
|
|
* leading to segment fault.
|
|
* 2021-03-03 roger.luo Fix the problem of frequent print receive timeout
|
|
* caused by not clearing the URC counter.
|
|
* 2022-10-01 roger.luo Redesign the entire AT framework and release V2.0.
|
|
* 2023-01-09 roger.luo Optimized the processing logic when multi-line
|
|
* command execution fails.
|
|
* 2023-01-10 roger.luo Resolve URC message containing ':' character
|
|
* causing parsing failure.
|
|
* 2023-02-23 roger.luo Added a temporary disable interface for URC to
|
|
* solve the problem of URC matching exception when
|
|
* receiving binary data from the modem.
|
|
* 2023-10-27 roger.luo Added the function of transparent data transmission.
|
|
******************************************************************************/
|
|
#include "at_chat.h"
|
|
#include "at_port.h"
|
|
#include "linux_list.h"
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stddef.h>
|
|
|
|
#define AT_DEBUG(ai, fmt, args...) \
|
|
do \
|
|
{ \
|
|
if (__get_adapter(ai)->debug) \
|
|
__get_adapter(ai)->debug(fmt, ##args); \
|
|
} while (0)
|
|
|
|
#if AT_LIST_WORK_COUNT < 2
|
|
#error "AT_LIST_WORK_COUNT cannot be less than 2"
|
|
#endif
|
|
|
|
//Used to identify the validity of a work item.
|
|
#define WORK_ITEM_TAG 0x2532
|
|
|
|
#define AT_IS_TIMEOUT(start, time) (at_get_ms() - (start) > (time))
|
|
|
|
/**AT work type (corresponding to different state machine polling handler.) */
|
|
typedef enum {
|
|
WORK_TYPE_GENERAL = 0, /* General work */
|
|
WORK_TYPE_SINGLLINE, /* Singlline command */
|
|
WORK_TYPE_MULTILINE, /* Multiline command */
|
|
WORK_TYPE_CMD, /* Standard command */
|
|
WORK_TYPE_CUSTOM, /* Custom command */
|
|
WORK_TYPE_BUF, /* Buffer */
|
|
WORK_TYPE_MAX
|
|
} work_type;
|
|
|
|
/**
|
|
* @brief AT command execution state.
|
|
*/
|
|
typedef enum {
|
|
AT_STAT_SEND = 0,
|
|
AT_STAT_RECV,
|
|
AT_STAT_RETRY,
|
|
} at_cmd_state;
|
|
|
|
/**
|
|
* @brief AT receive match mask
|
|
*/
|
|
#define MATCH_MASK_PREFIX 0x01
|
|
#define MATCH_MASK_SUFFIX 0x02
|
|
#define MATCH_MASK_ERROR 0x04
|
|
|
|
/**
|
|
* @brief AT work item object
|
|
*/
|
|
typedef struct {
|
|
struct list_head node; /* list node */
|
|
at_attr_t attr; /* AT attributes */
|
|
unsigned int magic : 16; /* AT magic*/
|
|
unsigned int state : 3; /* State of work */
|
|
unsigned int type : 3; /* Type of work */
|
|
unsigned int code : 3; /* Response code*/
|
|
unsigned int life : 6; /* Life cycle countdown(s)*/
|
|
unsigned int dirty : 1; /* Dirty flag*/
|
|
union {
|
|
const void *info;
|
|
at_work_t work; /* Custom work */
|
|
const char * singlline;
|
|
const char **multiline;
|
|
void (*sender)(at_env_t *env); /* Custom sender */
|
|
struct {
|
|
unsigned int bufsize;
|
|
char buf[0];
|
|
};
|
|
};
|
|
} work_item_t;
|
|
|
|
/**
|
|
* @brief AT Object infomation.
|
|
*/
|
|
typedef struct {
|
|
at_obj_t obj; /* Inherit at_obj*/
|
|
at_env_t env; /* Public work environment*/
|
|
work_item_t *cursor; /* Currently running work*/
|
|
struct list_head hlist, llist; /* High and low priority queue*/
|
|
struct list_head *clist; /* Queue currently in use*/
|
|
unsigned int timer; /* General purpose timer*/
|
|
unsigned int next_delay; /* Next cycle delay time*/
|
|
unsigned int delay_timer; /* Delay timer*/
|
|
char *recvbuf; /* Command response receive buffer*/
|
|
char *prefix; /* Point to prefix match*/
|
|
char *suffix; /* Point to suffix match*/
|
|
#if AT_URC_WARCH_EN
|
|
const urc_item_t *urc_tbl;
|
|
const urc_item_t *urc_item; /* The currently matched URC item*/
|
|
char *urcbuf;
|
|
unsigned int urc_timer;
|
|
unsigned short urc_bufsize;
|
|
unsigned short urc_cnt;
|
|
unsigned short urc_target; /* The target data length of the current URC frame*/
|
|
unsigned short urc_tbl_size;
|
|
unsigned short urc_disable_time;
|
|
#endif
|
|
unsigned short list_cnt;
|
|
unsigned short recv_bufsize;
|
|
unsigned short recv_cnt; /* Command response receives counter*/
|
|
unsigned short match_len; /* Response information matching length*/
|
|
unsigned char match_mask; /* Response information matching mask*/
|
|
unsigned urc_enable: 1;
|
|
unsigned urc_match : 1;
|
|
unsigned enable : 1; /* Enable the work */
|
|
unsigned disposing : 1;
|
|
unsigned err_occur : 1;
|
|
unsigned raw_trans : 1;
|
|
} at_info_t;
|
|
|
|
/**
|
|
* @brief Default command attributes.
|
|
*/
|
|
static const at_attr_t at_def_attr = {
|
|
.params = NULL,
|
|
.prefix = NULL,
|
|
.suffix = AT_DEF_RESP_OK,
|
|
.cb = NULL,
|
|
.timeout= AT_DEF_TIMEOUT,
|
|
.retry = AT_DEF_RETRY,
|
|
.priority = AT_PRIORITY_LOW
|
|
};
|
|
/*Private static function declarations------------------------------------*/
|
|
static void at_send_line(at_info_t *ai, const char *fmt, va_list args);
|
|
static void *at_core_malloc(unsigned int nbytes);
|
|
static void at_core_free(void *ptr);
|
|
|
|
#if AT_MEM_WATCH_EN
|
|
static unsigned int at_max_mem; /* Maximum memory used*/
|
|
static unsigned int at_cur_mem; /* Currently used memory*/
|
|
#endif
|
|
|
|
/**
|
|
* @brief at_obj_t * -> at_info_t *
|
|
*/
|
|
static inline at_info_t *obj_map(at_obj_t *at)
|
|
{
|
|
return (at_info_t *)at;
|
|
}
|
|
|
|
|
|
static inline const at_adapter_t *__get_adapter(at_info_t *ai)
|
|
{
|
|
return ai->obj.adap;
|
|
}
|
|
|
|
static inline void at_lock(at_info_t *ai)
|
|
{
|
|
if (__get_adapter(ai)->lock != NULL)
|
|
__get_adapter(ai)->lock();
|
|
}
|
|
|
|
static inline void at_unlock(at_info_t *ai)
|
|
{
|
|
if (__get_adapter(ai)->unlock != NULL)
|
|
__get_adapter(ai)->unlock();
|
|
}
|
|
|
|
static inline void send_data(at_info_t *at, const void *buf, unsigned int len)
|
|
{
|
|
__get_adapter(at)->write(buf, len);
|
|
}
|
|
|
|
/**
|
|
* @brief Send command with newline.
|
|
*/
|
|
static void send_cmdline(at_info_t *at, const char *cmd)
|
|
{
|
|
int len;
|
|
if (cmd == NULL)
|
|
return;
|
|
len = strlen(cmd);
|
|
__get_adapter(at)->write(cmd, len);
|
|
__get_adapter(at)->write("\r\n", 2);
|
|
AT_DEBUG(at,"->\r\n%s", cmd);
|
|
}
|
|
|
|
/**
|
|
* @brief Formatted print with newline.
|
|
*/
|
|
static void println(at_env_t *env, const char *cmd, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, cmd);
|
|
at_send_line(obj_map(env->obj), cmd, args);
|
|
va_end(args);
|
|
}
|
|
|
|
static unsigned int get_recv_count(at_env_t *env)
|
|
{
|
|
return obj_map(env->obj)->recv_cnt;
|
|
}
|
|
|
|
static char *get_recvbuf(at_env_t *env)
|
|
{
|
|
return obj_map(env->obj)->recvbuf;
|
|
}
|
|
|
|
static void recvbuf_clear(at_env_t *env)
|
|
{
|
|
obj_map(env->obj)->recvbuf[0] = '\0';
|
|
obj_map(env->obj)->recv_cnt = 0;
|
|
}
|
|
|
|
static char *find_substr(at_env_t *env, const char *str)
|
|
{
|
|
return strstr(obj_map(env->obj)->recvbuf, str);
|
|
}
|
|
|
|
/**
|
|
* @brief Indicates whether the current work was abort.
|
|
*/
|
|
static bool at_isabort(at_env_t *env)
|
|
{
|
|
at_info_t *ai = obj_map(env->obj);
|
|
if (ai->cursor == NULL)
|
|
return true;
|
|
return ai->cursor->state == AT_WORK_STAT_ABORT;
|
|
}
|
|
|
|
/**
|
|
* @brief Indication timeout
|
|
*/
|
|
static bool at_is_timeout(at_env_t *env, unsigned int ms)
|
|
{
|
|
return AT_IS_TIMEOUT(obj_map(env->obj)->timer, ms);
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief Reset the currently working timer.
|
|
*/
|
|
static void at_reset_timer(at_env_t *env)
|
|
{
|
|
obj_map(env->obj)->timer = at_get_ms();
|
|
}
|
|
/**
|
|
* @brief Set the next poll wait time for the current work.
|
|
*/
|
|
static void at_next_wait(struct at_env *env, unsigned int ms)
|
|
{
|
|
obj_map(env->obj)->next_delay = ms;
|
|
AT_DEBUG(obj_map(env->obj), "Next wait:%d\r\n", ms);
|
|
}
|
|
|
|
static void update_work_state(work_item_t *wi, at_work_state state, at_resp_code code)
|
|
{
|
|
wi->state = state;
|
|
wi->code = code;
|
|
#if AT_WORK_CONTEXT_EN
|
|
at_context_t *ctx = wi->attr.ctx;
|
|
if (ctx != NULL) {
|
|
ctx->code = (at_resp_code)wi->code;
|
|
ctx->work_state = (at_work_state)wi->state;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief End the currently running work.
|
|
*/
|
|
static void at_finish(struct at_env *env, at_resp_code code)
|
|
{
|
|
work_item_t *it = obj_map(env->obj)->cursor;
|
|
update_work_state(it, AT_WORK_STAT_FINISH, code);
|
|
}
|
|
|
|
/**
|
|
* @brief AT execution callback handler.
|
|
*/
|
|
static void do_at_callback(at_info_t *ai, work_item_t *wi, at_resp_code code)
|
|
{
|
|
at_response_t r;
|
|
AT_DEBUG(ai, "<-\r\n%s", ai->recvbuf);
|
|
//Exception notification
|
|
if ((code == AT_RESP_ERROR || code == AT_RESP_TIMEOUT) && __get_adapter(ai)->error != NULL) {
|
|
__get_adapter(ai)->error(&r);
|
|
ai->err_occur = 1;
|
|
AT_DEBUG(ai, "AT Respose :%s", code == AT_RESP_TIMEOUT ? "timeout" : "error");
|
|
} else {
|
|
ai->err_occur = 0;
|
|
}
|
|
#if AT_WORK_CONTEXT_EN
|
|
at_context_t *ctx = wi->attr.ctx;
|
|
if (ctx != NULL ) {
|
|
if (ctx->respbuf != NULL/* && ctx->bufsize */) {
|
|
ctx->resplen = ai->recv_cnt >= ctx->bufsize ? ctx->bufsize - 1 : ai->recv_cnt;
|
|
memcpy(ctx->respbuf, ai->recvbuf, ctx->resplen);
|
|
}
|
|
}
|
|
#endif
|
|
update_work_state(wi, AT_WORK_STAT_FINISH, code);
|
|
//Submit response data and status.
|
|
if (wi->attr.cb) {
|
|
r.obj = &ai->obj;
|
|
r.params = wi->attr.params;
|
|
r.recvbuf = ai->recvbuf;
|
|
r.recvcnt = ai->recv_cnt;
|
|
r.code = code;
|
|
r.prefix = ai->prefix != NULL ? ai->prefix : ai->recvbuf;
|
|
r.suffix = ai->suffix != NULL ? ai->suffix : ai->recvbuf;
|
|
wi->attr.cb(&r);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Create a basic work item.
|
|
*/
|
|
static work_item_t *work_item_create(int extend_size)
|
|
{
|
|
work_item_t *it;
|
|
it = at_core_malloc(sizeof(work_item_t) + extend_size);
|
|
if (it != NULL)
|
|
memset(it, 0, sizeof(work_item_t) + extend_size);
|
|
return it;
|
|
}
|
|
|
|
/**
|
|
* @brief Destroy work item.
|
|
* @param it Pointer to an item to destroy.
|
|
*/
|
|
static void work_item_destroy(work_item_t *it)
|
|
{
|
|
if (it != NULL) {
|
|
it->magic = 0;
|
|
at_core_free(it);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Destroys all work items in the specified queue.
|
|
*/
|
|
static void work_item_destroy_all(at_info_t *ai, struct list_head *head)
|
|
{
|
|
struct list_head *pos, *n;
|
|
work_item_t *it;
|
|
at_lock(ai);
|
|
list_for_each_safe(pos, n, head) {
|
|
it = list_entry(pos, work_item_t, node);
|
|
list_del(&it->node);
|
|
work_item_destroy(it);
|
|
}
|
|
at_unlock(ai);
|
|
}
|
|
|
|
/**
|
|
* @brief Work item recycling.
|
|
*/
|
|
static void work_item_recycle(at_info_t *ai, work_item_t *it)
|
|
{
|
|
at_lock(ai);
|
|
if (ai->list_cnt)
|
|
ai->list_cnt--;
|
|
|
|
list_del(&it->node);
|
|
work_item_destroy(it);
|
|
at_unlock(ai);
|
|
}
|
|
/**
|
|
* @brief Create and initialize a work item.
|
|
* @param type Type of work item.
|
|
* @param attr Item attributes
|
|
* @param info additional information.
|
|
* @param size the extended size.
|
|
*/
|
|
static work_item_t *create_work_item(at_info_t *ai, int type, const at_attr_t *attr, const void *info, int extend_size)
|
|
{
|
|
work_item_t *it = work_item_create(extend_size);
|
|
if (it == NULL) {
|
|
AT_DEBUG(ai, "Insufficient memory, list count:%d\r\n", ai->list_cnt);
|
|
return NULL;
|
|
}
|
|
if (ai->list_cnt > AT_LIST_WORK_COUNT) {
|
|
AT_DEBUG(ai, "Work queue full\r\n");
|
|
work_item_destroy(it);
|
|
return NULL;
|
|
}
|
|
if (attr == NULL)
|
|
attr = &at_def_attr;
|
|
|
|
if (type == WORK_TYPE_CMD || type == WORK_TYPE_BUF) {
|
|
memcpy(it->buf, info, extend_size);
|
|
it->bufsize = extend_size;
|
|
} else {
|
|
it->info = info;
|
|
}
|
|
it->magic = WORK_ITEM_TAG;
|
|
it->attr = *attr;
|
|
it->type = type;
|
|
it->state = AT_WORK_STAT_READY;
|
|
#if AT_WORK_CONTEXT_EN
|
|
if (attr->ctx) {
|
|
attr->ctx->code = AT_RESP_OK;
|
|
attr->ctx->work_state = AT_WORK_STAT_READY;
|
|
}
|
|
#endif
|
|
return it;
|
|
}
|
|
|
|
static work_item_t *sumit_work_item(at_info_t *ai, work_item_t *it)
|
|
{
|
|
if (it != NULL) {
|
|
at_lock(ai);
|
|
list_add_tail(&it->node, it->attr.priority == AT_PRIORITY_HIGH ? &ai->hlist : &ai->llist);
|
|
ai->list_cnt++; //Statistics
|
|
at_unlock(ai);
|
|
}
|
|
return it;
|
|
}
|
|
|
|
/**
|
|
* @brief Create work and put it in the queue
|
|
* @param type work of type
|
|
* @param attr AT attributes
|
|
* @param info Extra info
|
|
* @param size Extra info size
|
|
*/
|
|
static work_item_t *add_work_item(at_info_t *ai, int type, const at_attr_t *attr, const void *info, int extend_size)
|
|
{
|
|
work_item_t *it = create_work_item(ai, type, attr, info, extend_size);
|
|
if (it == NULL)
|
|
return NULL;
|
|
return sumit_work_item(ai, it);
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize matching info
|
|
*/
|
|
static void match_info_init(at_info_t *ai, at_attr_t *attr)
|
|
{
|
|
ai->prefix = ai->suffix = NULL;
|
|
ai->match_len = 0;
|
|
ai->match_mask = 0;
|
|
if (attr->prefix == NULL || strlen(attr->prefix) == 0)
|
|
ai->match_mask |= MATCH_MASK_PREFIX;
|
|
if (attr->suffix == NULL || strlen(attr->suffix) == 0)
|
|
ai->match_mask |= MATCH_MASK_SUFFIX;
|
|
}
|
|
|
|
/**
|
|
* @brief Custom work processing
|
|
*/
|
|
static int do_work_handler(at_info_t *ai)
|
|
{
|
|
work_item_t *i = ai->cursor;
|
|
if (ai->next_delay > 0) {
|
|
if (!AT_IS_TIMEOUT(ai->delay_timer, ai->next_delay))
|
|
return 0;
|
|
ai->next_delay = 0;
|
|
}
|
|
return ((int (*)(at_env_t * e)) i->work)(&ai->env);
|
|
}
|
|
|
|
/**
|
|
* @brief Generic commands processing
|
|
*/
|
|
static int do_cmd_handler(at_info_t *ai)
|
|
{
|
|
work_item_t *wi = ai->cursor;
|
|
at_env_t *env = &ai->env;
|
|
at_attr_t *attr = &wi->attr;
|
|
switch (env->state)
|
|
{
|
|
case AT_STAT_SEND:
|
|
if (wi->type == WORK_TYPE_CUSTOM && wi->sender != NULL) {
|
|
wi->sender(env);
|
|
} else if (wi->type == WORK_TYPE_BUF) {
|
|
__get_adapter(ai)->write(wi->buf, wi->bufsize);
|
|
} else if (wi->type == WORK_TYPE_SINGLLINE) {
|
|
send_cmdline(ai, wi->singlline);
|
|
} else {
|
|
send_cmdline(ai, wi->buf);
|
|
}
|
|
env->state = AT_STAT_RECV;
|
|
env->reset_timer(env);
|
|
env->recvclr(env);
|
|
match_info_init(ai, attr);
|
|
break;
|
|
case AT_STAT_RECV: /*Receive information and matching processing.*/
|
|
if (ai->match_len != ai->recv_cnt) {
|
|
ai->match_len = ai->recv_cnt;
|
|
//Matching response content prefix.
|
|
if ( !(ai->match_mask & MATCH_MASK_PREFIX) ) {
|
|
ai->prefix = strstr(ai->recvbuf, attr->prefix);
|
|
ai->match_mask |= ai->prefix ? MATCH_MASK_PREFIX : 0x00;
|
|
}
|
|
//Matching response content suffix.
|
|
if ( ai->match_mask & MATCH_MASK_PREFIX ) {
|
|
ai->suffix = strstr(ai->prefix ? ai->prefix : ai->recvbuf, attr->suffix);
|
|
ai->match_mask |= ai->suffix ? MATCH_MASK_SUFFIX : 0x00;
|
|
}
|
|
ai->match_mask |= strstr(ai->recvbuf, AT_DEF_RESP_ERR) ? MATCH_MASK_ERROR : 0x00;
|
|
}
|
|
if (ai->match_mask & MATCH_MASK_ERROR) {
|
|
AT_DEBUG(ai, "<-\r\n%s\r\n", ai->recvbuf);
|
|
if (env->i++ >= attr->retry) {
|
|
do_at_callback(ai, wi, AT_RESP_ERROR);
|
|
return true;
|
|
}
|
|
|
|
env->state = AT_STAT_RETRY; //If the command responds incorrectly, it will wait for a while and try again.
|
|
env->reset_timer(env);
|
|
}
|
|
if (ai->match_mask & MATCH_MASK_SUFFIX) {
|
|
do_at_callback(ai, wi, AT_RESP_OK);
|
|
return true;
|
|
} else if (env->is_timeout(env, attr->timeout)) {
|
|
AT_DEBUG(ai, "Command response timeout, retry:%d\r\n", env->i);
|
|
if (env->i++ >= attr->retry) {
|
|
do_at_callback(ai, wi, AT_RESP_TIMEOUT);
|
|
return true;
|
|
}
|
|
env->state = AT_STAT_SEND;
|
|
}
|
|
break;
|
|
case AT_STAT_RETRY:
|
|
if (env->is_timeout(env, 100))
|
|
env->state = AT_STAT_SEND; /*Go back to the send state*/
|
|
break;
|
|
default:
|
|
env->state = AT_STAT_SEND;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Multi-line command sending processing.
|
|
*/
|
|
static int send_multiline_handler(at_info_t *ai)
|
|
{
|
|
work_item_t *wi = ai->cursor;
|
|
at_env_t *env = &ai->env;
|
|
at_attr_t *attr = &wi->attr;
|
|
const char **cmds = wi->multiline;
|
|
|
|
switch (env->state)
|
|
{
|
|
case AT_STAT_SEND:
|
|
if (cmds[env->i] == NULL) { /* All commands are sent.*/
|
|
do_at_callback(ai, wi, env->params ? AT_RESP_OK : AT_RESP_ERROR);
|
|
return true;
|
|
}
|
|
send_cmdline(ai, cmds[env->i]);
|
|
env->recvclr(env);
|
|
env->reset_timer(env);
|
|
env->state = AT_STAT_RECV;
|
|
match_info_init(ai, attr);
|
|
break;
|
|
case AT_STAT_RECV:
|
|
if (find_substr(env, attr->suffix)) {
|
|
env->state = 0;
|
|
env->i++;
|
|
env->j = 0;
|
|
env->params = (void *)true; /*Mark execution status*/
|
|
AT_DEBUG(ai, "<-\r\n%s", ai->recvbuf);
|
|
} else if (find_substr(env, AT_DEF_RESP_ERR)) {
|
|
AT_DEBUG(ai, "<-\r\n%s", ai->recvbuf);
|
|
env->j++;
|
|
AT_DEBUG(ai, "CMD:'%s' failed to executed, retry:%d", cmds[env->i], env->j);
|
|
if (env->j >= attr->retry) {
|
|
env->state = 0;
|
|
env->j = 0;
|
|
env->i++;
|
|
} else {
|
|
env->state = AT_STAT_RETRY; //After the command responds incorrect, try again after a period of time.
|
|
env->reset_timer(env);
|
|
}
|
|
} else if (env->is_timeout(env, AT_DEF_TIMEOUT)) {
|
|
do_at_callback(ai, wi, AT_RESP_TIMEOUT);
|
|
return true;
|
|
}
|
|
break;
|
|
case AT_STAT_RETRY:
|
|
if (env->is_timeout(env, 100))
|
|
env->state = AT_STAT_SEND;/*Go back to the send state and resend.*/
|
|
break;
|
|
default:
|
|
env->state = AT_STAT_SEND;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Send command line
|
|
*/
|
|
static void at_send_line(at_info_t *ai, const char *fmt, va_list args)
|
|
{
|
|
int len;
|
|
char *cmdline;
|
|
cmdline = at_core_malloc(AT_MAX_CMD_LEN);
|
|
if (cmdline == NULL) {
|
|
AT_DEBUG(ai, "Malloc failed when send...\r\n");
|
|
return;
|
|
}
|
|
len = vsnprintf(cmdline, AT_MAX_CMD_LEN, fmt, args);
|
|
//Clear receive buffer.
|
|
ai->recv_cnt = 0;
|
|
ai->recvbuf[0] = '\0';
|
|
send_data(ai, cmdline, len);
|
|
send_data(ai, "\r\n", 2);
|
|
AT_DEBUG(ai,"->\r\n%s\r\n", cmdline);
|
|
|
|
at_core_free(cmdline);
|
|
}
|
|
|
|
#if AT_URC_WARCH_EN
|
|
|
|
/**
|
|
* @brief Set the AT urc table.
|
|
*/
|
|
void at_obj_set_urc(at_obj_t *at, const urc_item_t *tbl, int count)
|
|
{
|
|
at_info_t *ai = obj_map(at);
|
|
ai->urc_tbl = tbl;
|
|
ai->urc_tbl_size = count;
|
|
}
|
|
/**
|
|
* @brief Get the urc recv buffer count.
|
|
*/
|
|
int at_obj_get_urcbuf_count(at_obj_t *at)
|
|
{
|
|
return obj_map(at)->urc_cnt;
|
|
}
|
|
/**
|
|
* @brief Temporarily disables the URC matching program
|
|
* @param enable Enable/disable the URC matching handler
|
|
* @param timeout Disabled timeout(ms).
|
|
*/
|
|
void at_obj_urc_set_enable(at_obj_t *at, int enable, unsigned short timeout)
|
|
{
|
|
at_info_t *ai = obj_map(at);
|
|
ai->urc_enable = enable ? 1 : 0;
|
|
if (!enable) {
|
|
ai->urc_timer = at_get_ms();
|
|
ai->urc_disable_time = timeout;
|
|
//AT_DEBUG(ai, "Disable the URC, time:%d ms\r\n", timeout);
|
|
} else {
|
|
//AT_DEBUG(ai, "Enable the URC\r\n");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Find a URC handler based on URC receive buffer information.
|
|
*/
|
|
const urc_item_t *find_urc_item(at_info_t *ai, char *urc_buf, unsigned int size)
|
|
{
|
|
const urc_item_t *tbl = ai->urc_tbl;
|
|
int i;
|
|
for (i = 0; i < ai->urc_tbl_size && tbl; i++, tbl++) {
|
|
if (strstr(urc_buf, tbl->prefix))//It will need to be further optimized in the future.
|
|
return tbl;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void urc_reset(at_info_t *ai)
|
|
{
|
|
ai->urc_target = 0;
|
|
ai->urc_cnt = 0;
|
|
ai->urc_item = NULL;
|
|
ai->urc_match = 0;
|
|
ai->urc_timer = at_get_ms();
|
|
}
|
|
|
|
/**
|
|
* @brief URC(unsolicited code) handler entry.
|
|
* @param[in] urc - URC receive buffer
|
|
* @param[in] size - URC receive buffer length
|
|
* @return none
|
|
*/
|
|
static void urc_handler_entry(at_info_t *ai, urc_recv_status status, char *urc, unsigned int size)
|
|
{
|
|
int remain;
|
|
at_urc_info_t ctx = {status, urc, size};
|
|
if (ai->urc_target > 0)
|
|
AT_DEBUG(ai, "<=\r\n%.5s..\r\n", urc);
|
|
else
|
|
AT_DEBUG(ai, "<=\r\n%s\r\n", urc);
|
|
/* Send URC event notification. */
|
|
remain = ai->urc_item ? ai->urc_item->handler(&ctx) : 0;
|
|
if (remain == 0 && (ai->urc_item || ai->cursor == NULL)) {
|
|
|
|
urc_reset(ai);
|
|
} else {
|
|
AT_DEBUG(ai,"URC receives %d bytes remaining.\r\n", remain);
|
|
ai->urc_target = ai->urc_cnt + remain;
|
|
ai->urc_match = true;
|
|
}
|
|
}
|
|
|
|
static void urc_timeout_process(at_info_t *ai)
|
|
{
|
|
//Receive timeout processing, default (MAX_URC_RECV_TIMEOUT).
|
|
if (ai->urc_cnt > 0 && AT_IS_TIMEOUT(ai->urc_timer, AT_URC_TIMEOUT)) {
|
|
if (ai->urc_cnt > 2 && ai->urc_item != NULL) {
|
|
ai->urcbuf[ai->urc_cnt] = '\0';
|
|
AT_DEBUG(ai,"urc recv timeout=>%s\r\n", ai->urcbuf);
|
|
urc_handler_entry(ai, URC_RECV_TIMEOUT, ai->urcbuf, ai->urc_cnt);
|
|
}
|
|
urc_reset(ai);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief URC receive processing
|
|
* @param[in] buf - Receive buffer
|
|
* @return none
|
|
*/
|
|
static void urc_recv_process(at_info_t *ai, char *buf, unsigned int size)
|
|
{
|
|
char *urc_buf;
|
|
int ch;
|
|
if (ai->urcbuf == NULL)
|
|
return;
|
|
if (size == 0) {
|
|
urc_timeout_process(ai);
|
|
return;
|
|
}
|
|
if (!ai->urc_enable) {
|
|
if (!AT_IS_TIMEOUT(ai->urc_timer, ai->urc_disable_time))
|
|
return;
|
|
ai->urc_enable = 1;
|
|
AT_DEBUG(ai, "Enable the URC match handler\r\n");
|
|
}
|
|
ai->urc_timer = at_get_ms();
|
|
urc_buf = ai->urcbuf;
|
|
while (size--) {
|
|
ch = *buf++;
|
|
urc_buf[ai->urc_cnt++] = ch;
|
|
if (ai->urc_cnt >= ai->urc_bufsize) { /* Empty directly on overflow */
|
|
urc_reset(ai);
|
|
AT_DEBUG(ai, "Urc buffer full.\r\n");
|
|
continue;
|
|
}
|
|
if (ai->urc_match) {
|
|
if (ai->urc_cnt >= ai->urc_target){
|
|
urc_handler_entry(ai, URC_RECV_OK, urc_buf, ai->urc_cnt);
|
|
}
|
|
continue;
|
|
}
|
|
if (strchr(AT_URC_END_MARKS, ch) == NULL && ch != '\0') // Find the URC end mark.
|
|
continue;
|
|
urc_buf[ai->urc_cnt] = '\0';
|
|
if (ai->urc_item == NULL) { //Find the corresponding URC handler
|
|
ai->urc_item = find_urc_item(ai, urc_buf, ai->urc_cnt);
|
|
if (ai->urc_item == NULL && ch == '\n') {
|
|
if (ai->urc_cnt > 2 && ai->cursor == NULL) //Unrecognized URC message
|
|
AT_DEBUG(ai, "%s\r\n", urc_buf);
|
|
urc_reset(ai);
|
|
continue;
|
|
}
|
|
}
|
|
if (ai->urc_item != NULL && ch == ai->urc_item->endmark)
|
|
urc_handler_entry(ai, URC_RECV_OK, urc_buf, ai->urc_cnt);
|
|
}
|
|
|
|
}
|
|
#endif
|
|
/**
|
|
* @brief Command response data processing
|
|
* @return none
|
|
*/
|
|
static void resp_recv_process(at_info_t *ai, const char *buf, unsigned int size)
|
|
{
|
|
if (size == 0)
|
|
return;
|
|
if (ai->recv_cnt + size >= ai->recv_bufsize) //Receive overflow, clear directly.
|
|
ai->recv_cnt = 0;
|
|
|
|
memcpy(ai->recvbuf + ai->recv_cnt, buf, size);
|
|
ai->recv_cnt += size;
|
|
ai->recvbuf[ai->recv_cnt] = '\0';
|
|
}
|
|
|
|
static int (*const work_handler_table[WORK_TYPE_MAX])(at_info_t *) = {
|
|
[WORK_TYPE_GENERAL] = do_work_handler,
|
|
[WORK_TYPE_SINGLLINE] = do_cmd_handler,
|
|
[WORK_TYPE_MULTILINE] = send_multiline_handler,
|
|
[WORK_TYPE_CMD] = do_cmd_handler,
|
|
[WORK_TYPE_CUSTOM] = do_cmd_handler,
|
|
[WORK_TYPE_BUF] = do_cmd_handler,
|
|
};
|
|
|
|
/**
|
|
* @brief AT work processing.
|
|
*/
|
|
static void at_work_process(at_info_t *ai)
|
|
{
|
|
at_env_t *env = &ai->env;
|
|
if (ai->cursor == NULL) {
|
|
if (!list_empty(&ai->hlist))
|
|
ai->clist = &ai->hlist;
|
|
else if (!list_empty(&ai->llist))
|
|
ai->clist = &ai->llist;
|
|
else
|
|
return; //No work to do.
|
|
|
|
at_lock(ai);
|
|
ai->next_delay = 0;
|
|
env->obj = (struct at_obj *)ai;
|
|
env->i = 0;
|
|
env->j = 0;
|
|
env->state = 0;
|
|
ai->cursor = list_first_entry(ai->clist, work_item_t, node);
|
|
env->params = ai->cursor->attr.params;
|
|
env->recvclr(env);
|
|
env->reset_timer(env);
|
|
/*Enter running state*/
|
|
if (ai->cursor->state == AT_WORK_STAT_READY) {
|
|
update_work_state(ai->cursor, AT_WORK_STAT_RUN, (at_resp_code)ai->cursor->code);
|
|
}
|
|
at_unlock(ai);
|
|
}
|
|
/* When the job execution is complete, put it into the idle work queue */
|
|
if (ai->cursor->state >= AT_WORK_STAT_FINISH || work_handler_table[ai->cursor->type](ai)) {
|
|
//Marked the work as done.
|
|
if (ai->cursor->state == AT_WORK_STAT_RUN) {
|
|
update_work_state(ai->cursor, AT_WORK_STAT_FINISH, (at_resp_code)ai->cursor->code);
|
|
}
|
|
//Recycle Processed work item.
|
|
work_item_recycle(ai, ai->cursor);
|
|
ai->cursor = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Create an AT object
|
|
* @param adap AT interface adapter (AT object only saves its pointer, it must be a global resident object)
|
|
* @return Pointer to a new AT object
|
|
*/
|
|
at_obj_t *at_obj_create(const at_adapter_t *adap)
|
|
{
|
|
at_env_t *e;
|
|
at_info_t *ai = at_core_malloc(sizeof(at_info_t));
|
|
if (ai == NULL)
|
|
return NULL;
|
|
memset(ai, 0, sizeof(at_info_t));
|
|
ai->obj.adap = adap;
|
|
/* Initialize high and low priority queues*/
|
|
INIT_LIST_HEAD(&ai->hlist);
|
|
INIT_LIST_HEAD(&ai->llist);
|
|
//Allocate at least 32 bytes to the buffer
|
|
ai->recv_bufsize = adap->recv_bufsize < 32 ? 32 : adap->recv_bufsize;
|
|
ai->recvbuf = at_core_malloc(ai->recv_bufsize);
|
|
if (ai->recvbuf == NULL) {
|
|
at_obj_destroy(&ai->obj);
|
|
return NULL;
|
|
}
|
|
#if AT_URC_WARCH_EN
|
|
if (adap->urc_bufsize != 0) {
|
|
ai->urc_bufsize = adap->urc_bufsize < 32 ? 32 : adap->urc_bufsize;
|
|
ai->urcbuf = at_core_malloc(ai->urc_bufsize);
|
|
if (ai->urcbuf == NULL) {
|
|
at_obj_destroy(&ai->obj);
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif
|
|
e = &ai->env;
|
|
ai->recv_cnt = 0;
|
|
ai->urc_enable = 1;
|
|
ai->enable = 1;
|
|
//Initialization of public work environment.
|
|
e->is_timeout = at_is_timeout;
|
|
e->println = println;
|
|
e->recvbuf = get_recvbuf;
|
|
e->recvclr = recvbuf_clear;
|
|
e->recvlen = get_recv_count;
|
|
e->contains = find_substr;
|
|
e->disposing = at_isabort;
|
|
e->finish = at_finish;
|
|
e->reset_timer = at_reset_timer;
|
|
e->next_wait = at_next_wait;
|
|
return &ai->obj;
|
|
}
|
|
/**
|
|
* @brief Destroy a AT object.
|
|
*/
|
|
void at_obj_destroy(at_obj_t *obj)
|
|
{
|
|
at_info_t *ai = obj_map(obj);
|
|
|
|
if (obj == NULL)
|
|
return;
|
|
|
|
work_item_destroy_all(ai, &ai->hlist);
|
|
work_item_destroy_all(ai, &ai->llist);
|
|
|
|
if (ai->recvbuf != NULL)
|
|
at_core_free(ai->recvbuf);
|
|
#if AT_URC_WARCH_EN
|
|
if (ai->urcbuf != NULL)
|
|
at_core_free(ai->urcbuf);
|
|
#endif
|
|
at_core_free(ai);
|
|
|
|
}
|
|
|
|
/**
|
|
* @brief Indicates if the AT object is busy.
|
|
* @return true - command queue is not empty, busy state
|
|
*/
|
|
bool at_obj_busy(at_obj_t *at)
|
|
{
|
|
return !list_empty(&obj_map(at)->hlist) || !list_empty(&obj_map(at)->llist)
|
|
|| obj_map(at)->urc_cnt != 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Enable/Disable the AT work
|
|
*/
|
|
void at_obj_set_enable(at_obj_t *at, int enable)
|
|
{
|
|
obj_map(at)->enable = enable ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Set user data
|
|
*/
|
|
void at_obj_set_user_data(at_obj_t *at, void *user_data)
|
|
{
|
|
at->user_data = user_data;
|
|
}
|
|
|
|
/**
|
|
* @brief Get user data.
|
|
*/
|
|
void *at_obj_get_user_data(at_obj_t *at)
|
|
{
|
|
return at->user_data;
|
|
}
|
|
|
|
/**
|
|
* @brief Default attributes initialization.
|
|
* Default low priority, other reference AT_DEF_XXX definition.
|
|
* @param attr AT attributes
|
|
*/
|
|
void at_attr_deinit(at_attr_t *attr)
|
|
{
|
|
*attr = at_def_attr;
|
|
}
|
|
|
|
/**
|
|
* @brief Execute command (with variable argument list)
|
|
* @param attr AT attributes(NULL to use the default value)
|
|
* @param cmd Format the command.
|
|
* @param va Variable parameter list
|
|
* @return Indicates whether the asynchronous work was enqueued successfully
|
|
*/
|
|
bool at_exec_vcmd(at_obj_t *at, const at_attr_t *attr, const char *cmd, va_list va)
|
|
{
|
|
char *buf;
|
|
int len;
|
|
void *workid = NULL;
|
|
buf = at_core_malloc(AT_MAX_CMD_LEN);
|
|
if (buf == NULL) {
|
|
AT_DEBUG(obj_map(at), "No memory when execute vcmd...\r\n");
|
|
return NULL;
|
|
}
|
|
len = vsnprintf(buf, AT_MAX_CMD_LEN, cmd, va);
|
|
if (len > 0) {
|
|
workid = add_work_item(obj_map(at), WORK_TYPE_CMD, attr, buf, len + 1);
|
|
}
|
|
at_core_free(buf);
|
|
return workid != NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Execute one command
|
|
* @param attr AT attributes(NULL to use the default value)
|
|
* @param cmd Formatted arguments
|
|
* @param ... Variable argument list (same usage as printf)
|
|
* @retval Indicates whether the asynchronous work was enqueued successfully
|
|
*/
|
|
bool at_exec_cmd(at_obj_t *at, const at_attr_t *attr, const char *cmd, ...)
|
|
{
|
|
bool ret;
|
|
va_list args;
|
|
va_start(args, cmd);
|
|
ret = at_exec_vcmd(at, attr, cmd, args);
|
|
va_end(args);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Execute custom command
|
|
* @param attr AT attributes(NULL to use the default value)
|
|
* @param sender Command sending handler (such as sending any type of data through the env->obj->adap-write interface)
|
|
* @retval Indicates whether the asynchronous work was enqueued successfully
|
|
*/
|
|
bool at_custom_cmd(at_obj_t *at, const at_attr_t *attr, void (*sender)(at_env_t *env))
|
|
{
|
|
return add_work_item(obj_map(at), WORK_TYPE_CUSTOM, attr, (const void *)sender, 0) != NULL;
|
|
}
|
|
/**
|
|
* @brief Send (binary) data
|
|
* @param attr AT attributes(NULL to use the default value)
|
|
* @param databuf Binary data
|
|
* @param bufsize Binary data length
|
|
* @retval Indicates whether the asynchronous work was enqueued successfully
|
|
*/
|
|
bool at_send_data(at_obj_t *at, const at_attr_t *attr, const void *databuf, unsigned int bufsize)
|
|
{
|
|
return add_work_item(obj_map(at), WORK_TYPE_BUF, attr, databuf, bufsize) != NULL;
|
|
}
|
|
/**
|
|
* @brief Send a single-line command
|
|
* @param cb Response callback handler, Fill in NULL if not required
|
|
* @param timeout command execution timeout(ms)
|
|
* @param retry command retries( >= 0)
|
|
* @param singlline command
|
|
* @retval Indicates whether the asynchronous work was enqueued successfully.
|
|
* @note Only the address is saved, so the 'singlline' can not be a local variable which will be destroyed.
|
|
*/
|
|
bool at_send_singlline(at_obj_t *at, const at_attr_t *attr, const char *singlline)
|
|
{
|
|
return add_work_item(obj_map(at), WORK_TYPE_SINGLLINE, attr, singlline, 0) != NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Send multiline commands
|
|
* @param attr AT attributes(NULL to use the default value)
|
|
* @param multiline Command table, with the last item ending in NULL.
|
|
* @example :
|
|
* const char *multiline = {
|
|
* "AT+XXX",
|
|
* "AT+XXX",
|
|
* NULL
|
|
* };
|
|
* at_send_multiline(at_dev, NULL, multiline);
|
|
*
|
|
* @retval Indicates whether the asynchronous work was enqueued successfully.
|
|
* @note Only the address is saved, so the array can not be a local variable which will be destroyed.
|
|
*/
|
|
bool at_send_multiline(at_obj_t *at, const at_attr_t *attr, const char **multiline)
|
|
{
|
|
return add_work_item(obj_map(at), WORK_TYPE_MULTILINE, attr, multiline, 0) != NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Execute custom work.
|
|
* @param params User parameter.
|
|
* @param work AT custom polling work entry, specific usage ref@at_work_t
|
|
* @retval Indicates whether the asynchronous work was enqueued successfully.
|
|
*/
|
|
bool at_do_work(at_obj_t *at, void *params, at_work_t work)
|
|
{
|
|
at_attr_t attr;
|
|
at_attr_deinit(&attr);
|
|
attr.params = params;
|
|
return add_work_item(obj_map(at), WORK_TYPE_GENERAL, &attr, (const void *)work, 0) != NULL;
|
|
}
|
|
|
|
/**
|
|
* @brief Abort all AT work
|
|
*/
|
|
void at_work_abort_all(at_obj_t *at)
|
|
{
|
|
struct list_head *pos;
|
|
work_item_t *it;
|
|
at_info_t *ai = obj_map(at);
|
|
at_lock(ai);
|
|
list_for_each(pos, &ai->hlist) {
|
|
it = list_entry(pos, work_item_t, node);
|
|
update_work_state(it, AT_WORK_STAT_ABORT, AT_RESP_ABORT);
|
|
}
|
|
list_for_each(pos, &ai->llist) {
|
|
it = list_entry(pos, work_item_t, node);
|
|
update_work_state(it, AT_WORK_STAT_ABORT, AT_RESP_ABORT);
|
|
}
|
|
at_unlock(ai);
|
|
}
|
|
|
|
#if AT_MEM_WATCH_EN
|
|
|
|
static void *at_core_malloc(unsigned int nbytes)
|
|
{
|
|
if (nbytes + at_cur_mem > AT_MEM_LIMIT_SIZE) { //The maximum memory limit has been exceeded.
|
|
return NULL;
|
|
}
|
|
unsigned long *mem_info = (unsigned long *)at_malloc(nbytes + sizeof(unsigned long));
|
|
*mem_info = nbytes;
|
|
at_cur_mem += nbytes; //Statistics of current memory usage.
|
|
if (at_cur_mem > at_max_mem) //Record maximum memory usage.
|
|
at_max_mem = at_cur_mem;
|
|
return mem_info + 1;
|
|
}
|
|
|
|
static void at_core_free(void *ptr)
|
|
{
|
|
unsigned long *mem_info = (unsigned long *)ptr;
|
|
unsigned long nbyte;
|
|
if (ptr != NULL) {
|
|
mem_info--;
|
|
nbyte = *mem_info;
|
|
at_cur_mem -= nbyte;
|
|
at_free(mem_info);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Get the maximum memory usage.
|
|
*/
|
|
unsigned int at_max_used_memory(void)
|
|
{
|
|
return at_max_mem;
|
|
}
|
|
|
|
/**
|
|
* @brief Get current memory usage.
|
|
*/
|
|
unsigned int at_cur_used_memory(void)
|
|
{
|
|
return at_cur_mem;
|
|
}
|
|
|
|
#else
|
|
|
|
static void *at_core_malloc(unsigned int nbytes)
|
|
{
|
|
return at_malloc(nbytes);
|
|
}
|
|
|
|
static void at_core_free(void *ptr)
|
|
{
|
|
at_free(ptr);
|
|
}
|
|
|
|
#endif
|
|
|
|
#if AT_WORK_CONTEXT_EN
|
|
|
|
/**
|
|
* @brief void * -> work_item_t *
|
|
*/
|
|
static inline work_item_t *work_item_map(void *work_obj)
|
|
{
|
|
return (work_item_t *)work_obj;
|
|
}
|
|
|
|
/**
|
|
* @brief Indicates whether the AT work is valid.
|
|
*/
|
|
bool at_work_isvalid(void *work_item)
|
|
{
|
|
work_item_t *it = work_item_map(work_item);
|
|
return it != NULL && it->magic == WORK_ITEM_TAG;
|
|
}
|
|
|
|
/**
|
|
* @brief Initialize a work context
|
|
* @param ctx - Pointer to 'at_context_t'
|
|
* @param respbuf Command response buffer, fill in NULL if not required
|
|
* @param bufsize Buffer size (must be long enough to receive all responses)
|
|
*/
|
|
void at_context_init(at_context_t *ctx, void *respbuf, unsigned bufsize)
|
|
{
|
|
memset(ctx, 0, sizeof(at_context_t));
|
|
ctx->bufsize = bufsize;
|
|
ctx->respbuf = respbuf;
|
|
}
|
|
|
|
/**
|
|
* @brief Attached to the AT attribute.
|
|
* @param attr AT attributes
|
|
* @param ctx AT context
|
|
*/
|
|
void at_context_attach(at_attr_t *attr, at_context_t *ctx)
|
|
{
|
|
attr->ctx = ctx;
|
|
}
|
|
|
|
/**
|
|
* @brief Get work running state
|
|
* @param ctx AT context
|
|
* @return State of work.
|
|
*/
|
|
at_work_state at_work_get_state(at_context_t *ctx)
|
|
{
|
|
return ctx->work_state;
|
|
}
|
|
|
|
bool at_work_is_busy(at_context_t *ctx)
|
|
{
|
|
return ctx->work_state == AT_WORK_STAT_RUN || ctx->work_state == AT_WORK_STAT_READY;
|
|
}
|
|
/**
|
|
* @brief Indicate whether the work has been finished (then you can call `at_work_get_result` to query the result)
|
|
* @param ctx AT context
|
|
* @return true - the work finish
|
|
*/
|
|
bool at_work_is_finish(at_context_t *ctx)
|
|
{
|
|
return ctx->work_state > AT_WORK_STAT_RUN;
|
|
}
|
|
|
|
/**
|
|
* @brief Get work running result
|
|
* @param ctx AT context
|
|
*/
|
|
at_resp_code at_work_get_result(at_context_t *ctx)
|
|
{
|
|
return ctx->code;
|
|
}
|
|
|
|
#endif
|
|
|
|
#if AT_RAW_TRANSPARENT_EN
|
|
/**
|
|
* @brief Data transparent transmission processing.
|
|
*/
|
|
static void at_raw_trans_process(at_obj_t *obj)
|
|
{
|
|
unsigned char rbuf[32];
|
|
int size;
|
|
int i;
|
|
at_info_t *ai = obj_map(obj);
|
|
if (obj->raw_conf == NULL)
|
|
return;
|
|
size = obj->adap->read(rbuf, sizeof(rbuf));
|
|
if (size > 0 ){
|
|
obj->raw_conf->write(rbuf, size);
|
|
}
|
|
size = obj->raw_conf->read(rbuf, sizeof(rbuf));
|
|
if (size > 0) {
|
|
obj->adap->write(rbuf, size);
|
|
}
|
|
//Exit command detection
|
|
if (obj->raw_conf->exit_cmd != NULL) {
|
|
for (i = 0; i < size; i++) {
|
|
if (ai->recv_cnt >= ai->recv_bufsize)
|
|
ai->recv_cnt = 0;
|
|
ai->recvbuf[ai->recv_cnt] = rbuf[i];
|
|
if (rbuf[i] == '\r' || rbuf[i] == 'n') {
|
|
ai->recvbuf[ai->recv_cnt] = '\0';
|
|
ai->recv_cnt = 0;
|
|
if (strcasecmp(obj->raw_conf->exit_cmd, ai->recvbuf) != 0) {
|
|
continue;
|
|
}
|
|
if (obj->raw_conf->on_exit) {
|
|
obj->raw_conf->on_exit();
|
|
}
|
|
} else {
|
|
ai->recv_cnt++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Enter transparent transmission mode.
|
|
* @param conf The configuration for transparent transmission mode.
|
|
*/
|
|
void at_raw_transport_enter(at_obj_t *obj, const at_raw_trans_conf_t *conf)
|
|
{
|
|
at_info_t *ai = obj_map(obj);
|
|
obj->raw_conf = conf;
|
|
ai->raw_trans = 1;
|
|
ai->recv_cnt = 0;
|
|
}
|
|
|
|
/**
|
|
* @brief Exit transparent transmission mode.
|
|
*/
|
|
void at_raw_transport_exit(at_obj_t *obj)
|
|
{
|
|
at_info_t *ai = obj_map(obj);
|
|
ai->raw_trans = 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* @brief AT work polling processing.
|
|
*/
|
|
void at_obj_process(at_obj_t *at)
|
|
{
|
|
char rbuf[64];
|
|
int read_size;
|
|
register at_info_t *ai = obj_map(at);
|
|
#if AT_RAW_TRANSPARENT_EN
|
|
if (ai->raw_trans) {
|
|
at_raw_trans_process(at);
|
|
return;
|
|
}
|
|
#endif
|
|
read_size = __get_adapter(ai)->read(rbuf, sizeof(rbuf));
|
|
#if AT_URC_WARCH_EN
|
|
urc_recv_process(ai, rbuf, read_size);
|
|
#endif
|
|
resp_recv_process(ai, rbuf, read_size);
|
|
at_work_process(ai);
|
|
}
|
|
|