在嵌入式市場中,爲了更快地提高開發進度,更多都會選用模塊應用開發,比如選用Ble、Wifi、Modem等模塊,這一類型的模塊一般都是AT指令通信,如何通用解析以及處理應答數據極其重要。
如下三條指令,分別爲查詢類型指令、執行類型指令、設置類型指令。常用的解析方法爲調用strstr\sscanf等接口去解析數據,當然其他複雜的源數據還需要特殊的解析,如果在一套數據解析就可以靈活處理這些類型指令數據會大大加快開發進度,本文將講述一種可以靈活解析數據的方法思路。
//查詢類型指令
AT+VERSION?
+ver:AppR01A01V01
OK
//執行類型指令
AT+ACCESSON
OK
+accesson:sucess
//設置類型指令
AT+MODE=1
OK
解析思路
採用回調的方式解析數據,將解析完成的源數據傳入回調中由開發者自行處理。當然也是兼容了查詢、設置、執行類型的指令數據,parser_target_t結構體中元素包含了begin_str(起始字符串,可爲NULL), end_str(結束字符串,可爲NULL),is_cont(是否持續解析),cb_func(匹配成功回調函數);
//目前參數result尚未用到,預留
typedef int (*parser_result_callback)(int result, void *pvoid);
/**
- @brief 外部傳入指定匹配字符串以及回調函數
- >> begin_str與end_str不能同時爲NULL
- >> begin_str = NULL, end_str != NULL, end_str 之前的數據都傳入回調
- >> begin_str != NULL, end_str = NULL, 匹配到begin_str就會回調
- >> begin_str != NULL, end_str != NULL, begin_str 與 end_str之間的數據傳入回調
*/
typedef struct {
const char *begin_str; /* 匹配起始字符串 */
const char *end_str; /* 匹配結束字符串 */
bool is_cont; /* 在一次數據循環中是否持續匹配,
true:連續匹配,false:單次匹配,匹配成功則退出 */
parser_result_callback cb_func; /* 匹配成功回調函數 */
}parser_target_t;
- begin_str與end_str不能同時爲NULL,既然要解析數據,所以必須要有解析對象,解析對象不能同時爲NULL;
- 當begin_str與end_str不爲NULL時,則解析到begin_str與end_str之間的數據然後傳入回調函數中;
static int ver_parser_callback(int result, void *pdata)
{
printf("version:%s\r\n", (char *)pdata);
return 0;
}
static parser_target_t ver_parser_target = {
.begin_str = "+ver:", .end_str = "\r\n", .is_cont = false, .cb_func = ver_parser_callback
};
解析對象:
AT+VERSION?
+ver:AppR01A01V01
OK
回調執行輸出:
version:AppR01A01V01
- 當begin_str不爲NULL,end_str爲NULL,則爲匹配到begin_str則成功回調,並且傳入數據爲NULL,針對這類型的的匹配機制,可把參數cb_func回調設置爲NULL,則無需回調直接返回操作成功;
//第一種解析方式
static int module_mode_cfg_status = 0;
static int mode_parser_callback(int result, void *pdata)
{
module_mode_cfg_status = 1;
return 0;
}
static parser_target_t mode_parser_target = {
.begin_str = "OK", .end_str = NULL, .is_cont = false, .cb_func = mode_parser_callback
};
//第二種解析方式
static parser_target_t mode_parser_target = {
.begin_str = "OK", .end_str = NULL, .is_cont = false, .cb_func = NULL
};
解析對象:
//匹配到OK則執行成功
AT+MODE=1
OK
- 當begin_str爲NULL,end_str不爲NULL,這時將接收到數據暫存緩存區知道匹配到end_str,則將數據傳入回調函數,這種考慮到可能一條指令中需要解析爲多個數據,則將整包數據傳入回調,由回調函數中處理解析;
static int info_parser_callback(int result, void *pdata)
{
printf("info:%s\r\n", (char *)pdata);
return 0;
}
static parser_target_t info_parser_target = {
.begin_str = NULL, .end_str = "OK", .is_cont = false, .cb_func = info_parser_callback
};
解析對象:
AT+INFO?
+ver:AppR01A01V01
+freemem:512k
OK
回調執行輸出:
info:
+ver:AppR01A01V01
+freemem:512k
註冊接口
//內部結構體
typedef struct {
int step;
int data_pos;
int data_count;
int buffer_size;
unsigned int time_to_clr;
unsigned int clr_tick;
bool is_exist;
char *p_buffer;
parser_target_t target;
parser_target_t *update_target;
}parser_item_t;
typedef parser_item_t* parser_handler_t;
#define parser_clr(parser_handler) do { \
parser_handler->data_pos = 0; \
parser_handler->step = 0; \
parser_handler->data_count = 0; \
if (parser_handler->buffer_size) { \
memset(parser_handler->p_buffer, 0, parser_handler->buffer_size); \
} \
} while (0)
/**
* @brief 創建指定解析器,用於提取對應區間數據
* @param p_target - 解析對象結構體指針
* parser_buffer_size - 截取區間數據緩存區大小
* time_to_clr - 超時無數據,則清除匹配狀態,恢復初始狀態,單位:ms
* @retval 返回 parser_handler_t格式句柄,創建失敗返回NULL
*/
parser_handler_t parser_target_create(parser_target_t *p_target, int parser_buffer_size, unsigned int time_to_clr)
{
parser_handler_t parser_handler = NULL;
if (p_target == NULL)
goto parser_end;
if ((p_target->begin_str == NULL) && (p_target->end_str == NULL))
goto parser_end;
parser_handler = (parser_handler_t)quec_malloc(sizeof(parser_item_t));
if (parser_handler == NULL)
goto parser_end;
if (parser_buffer_size == 0) { /* 單獨匹配功能 */
parser_handler->buffer_size = 0;
parser_handler->p_buffer = NULL;
} else {
parser_handler->buffer_size = parser_buffer_size;
parser_handler->p_buffer = (char *)user_malloc(parser_buffer_size + 1);
if (parser_handler->p_buffer == NULL) {
user_free(parser_handler);
parser_handler = NULL;
goto parser_end;
}
}
if (time_to_clr == 0) {
time_to_clr = 200; /* 默認超時200ms */
}
parser_handler->time_to_clr = time_to_clr;
parser_handler->clr_tick = getms();
parser_handler->is_exist = true;
parser_handler->target = *p_target;
parser_handler->update_target = NULL;
parser_clr(parser_handler);
parser_end:
return parser_handler;
}
parser_handler_t parser_target_create(parser_target_t *p_target, int parser_buffer_size, unsigned int time_to_clr)
- p_target : 解析目標對象;
- parser_buffer_size:接收匹配數據緩存區長度,滿足解析條件時纔將數據存取緩存區中,單位:字節;
- time_to_clr:設置無數據交互時間,當無數據交互超過該時間則清除解析狀態,單位:ms;
- 該註冊接口執行成功後返回解析句柄。
解析接口
bool is_timeout(unsigned int start_tick, unsigned int timeout)
{
if ((getms() - start_tick) >= timeout)
return true;
else
return false;
}
/******************************************************************************
** 匹配指定指定單個字符串
** 參數 :pos[] - 字符串組匹配字符索引位置,由外部傳入,初始化必須爲0
** data - 查找的源數據緩存區
** size - 源數據緩存區大小
** ackstr- 匹配的字符串
** 返回值:
** < 0, 未匹配到指定字符串
** >= 0,返回包含指針數組中的下標次序
*******************************************************************************/
int libcom_single_string_match(int *pos, char *data, int size, const char *ackstr)
{
int i = 0;
for (i = 0; i < size; i++) {
if (data[i] != ackstr[pos[0]]) {
pos[0] = 0; /* 匹配失敗則從當前字符開始重新匹配 */
}
if (data[i] == ackstr[pos[0]]) {
pos[0]++;
if (0 == ackstr[pos[0]]) { /* 匹配到完整的字符串 */
pos[0] = 0; /* 清除位置狀態 */
return 0;
}
}
}
return -1;
}
/**
* @brief 指定解析處理進程
* @param parser_handler - 解析句柄
* data - 傳入數據緩存區
* size - 數據緩存區大小
* @retval 0:解析成功, <0:未解析成功或等待解析
*/
int parser_target_process(parser_handler_t parser_handler, char *data, int size)
{
int i = 0;
int ret = -1;
if (parser_handler == NULL)
return -1;
if (parser_handler->is_exist == false)
return -1;
if (true == is_timeout(parser_handler->clr_tick, parser_handler->time_to_clr)) {
parser_clr(parser_handler);
}
if (size) {
parser_handler->clr_tick = getms();
}
if (parser_handler->update_target != NULL) {
os_enter_cirtical();
parser_handler->target = *(parser_handler->update_target);
user_free(parser_handler->update_target);
parser_handler->update_target = NULL;
parser_clr(parser_handler);
os_exit_cirtical();
}
for (i = 0; i < size; i++) {
if ((parser_handler->target.begin_str == NULL) || (parser_handler->step == 1)) { /* 接收匹配結束字符串前數據 */
if (parser_handler->buffer_size) {
parser_handler->p_buffer[parser_handler->data_count++] = data[i];
}
if (0 == libcom_single_string_match(&parser_handler->data_pos, &data[i], 1, parser_handler->target.end_str)) {
if (parser_handler->buffer_size) {
parser_handler->p_buffer[parser_handler->data_count - strlen(parser_handler->target.end_str)] = '\0';
}
if (parser_handler->target.cb_func != NULL) {
parser_handler->target.cb_func(DEV_RET_OK, (void *)parser_handler->p_buffer);
}
ret = 0;
parser_clr(parser_handler);
if (parser_handler->target.is_cont == false)
return 0; /* 成功結束 */
}
} else { /* 匹配起始字符串 */
if (0 == libcom_single_string_match(&parser_handler->data_pos, &data[i], 1, parser_handler->target.begin_str)) {
parser_clr(parser_handler);
if (parser_handler->target.end_str == NULL) { /* 結束字符串爲NULL,則直接回調結束 */
if (parser_handler->target.cb_func != NULL) {
parser_handler->target.cb_func(DEV_RET_OK, NULL);
}
ret = 0;
if (parser_handler->target.is_cont == false)
return 0; /* 成功結束 */
} else { /* 進入下一步操作 */
parser_handler->step = 1;
}
}
}
if ((parser_handler->buffer_size) && (parser_handler->data_count >= parser_handler->buffer_size)) {
parser_clr(parser_handler);
}
}
return ret;
}
int parser_target_process(parser_handler_t parser_handler, char *data, int size)
- parser_handler:解析句柄;
- data:傳入接收到的數據;
- size:數據大小,單位:字節;
- 該解析接口需要執行任務中調用,並傳入接收到的全部源數據。
註銷接口
/**
* @brief 解除指定解析器
* @param parser_handler - 解析句柄
* @retval 0:釋放成功,-1:釋放失敗.
*/
int parser_target_delete(parser_handler_t parser_handler)
{
if (parser_handler == NULL)
return -1;
parser_handler->data_pos = 0;
parser_handler->step = 0;
parser_handler->data_count = 0;
parser_handler->target.begin_str = NULL;
parser_handler->target.end_str = NULL;
parser_handler->target.cb_func = NULL;
if (parser_handler->buffer_size) {
user_free(parser_handler->p_buffer);
}
parser_handler->buffer_size = 0;
parser_handler->p_buffer = NULL;
parser_handler->is_exist = false;
parser_handler->update_target = NULL;
user_free(parser_handler);
parser_handler = NULL;
return 0;
}
int parser_target_delete(parser_handler_t parser_handler)
- parser_handler:解析句柄;
- 解析結束後可調用註銷接口釋放資源。
更新解析目標接口
/**
* @brief 更新解析對象參數
* @param parser_handler - 解析句柄
* p_target - 更新對象
* @retval 0:成功,<0:失敗.
*/
int parser_target_update_param(parser_handler_t parser_handler, parser_target_t *p_target)
{
if ((parser_handler == NULL) || (p_target == NULL))
return -EPERM;
os_enter_cirtical();
parser_handler->update_target = (parser_target_t*)quec_malloc(sizeof(parser_target_t));
if (parser_handler->update_target == NULL) {
os_exit_cirtical();
return -ENOMEM;
}
*(parser_handler->update_target) = *p_target;
os_exit_cirtical();
return 0;
}
int parser_target_update_param(parser_handler_t parser_handler, parser_target_t *p_target)
- parser_handler:解析句柄;
- p_target:更新後的解析對象;
- 創建解析對象句柄後,如中間需要匹配多條不同的解析對象,則調用該接口更新解析目標。
清除解析狀態接口
/**
* @brief 解析對象狀態恢復
* @param parser_handler - 解析句柄
* @retval 0:成功,<0:失敗.
*/
int parser_target_state_restore(parser_handler_t parser_handler)
{
if (parser_handler == NULL)
return -1;
os_enter_cirtical();
parser_clr(parser_handler);
os_exit_cirtical();
return 0;
}
int parser_target_state_restore(parser_handler_t parser_handler)
- parser_handler:解析句柄;
- 針對多種解析對象時,調用該接口清除解析狀態。
使用示例
parser_handler_t parser_target_create(parser_target_t *p_target, int parser_buffer_size, unsigned int time_to_clr);
int parser_target_delete(parser_handler_t parser_handler);
int parser_target_process(parser_handler_t parser_handler, char *data, int size);
int parser_target_update_param(parser_handler_t parser_handler, parser_target_t *p_target);
int parser_target_state_restore(parser_handler_t parser_handler);
int parser_callback(int result, void *pvoid)
{
printf("parser data = %s \r\n", (char *)pvoid);
return 0;
}
parser_handler_t parser_handler = NULL;
parser_target_t parser_target = { "begin", "end", false, parser_callback };
void parser_init(void)
{
parser_handler = parser_target_create(&parser_target, 64, 200);
if (parser_handler == NULL) {
printf("parser create failed.\r\n");
}
}
int read_data(char *buf, int size)
{
//add here...
return 0;
}
void parser_thread1(void)
{
int read_len = 0;
char read_buffer[64] = { 0 };
read_len = read_data(read_buffer, sizeof(read_buffer));
if (0 == parser_target_process(parser_handler, read_buffer, read_len )) {
printf("parser ok.\r\n");
parser_target_delete(parser_handler);
}
}
總結
這套解析接口可組合使用,視具體應用場景而定。