mirror of
https://gitee.com/moluo-tech/AT-Command
synced 2025-06-17 07:57:52 +00:00
581 lines
14 KiB
C
581 lines
14 KiB
C
/******************************************************************************
|
|
* @brief AT命令测试程序
|
|
* Change Logs:
|
|
* Date Author Notes
|
|
* 2022-04-01 Roger.luo 初版
|
|
******************************************************************************/
|
|
#include "at_chat.h"
|
|
#include "at_port.h"
|
|
#include "at_device.h"
|
|
#include <sys/poll.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
|
|
|
|
static at_obj_t *at_obj; //AT
|
|
static pthread_mutex_t at_lock; //互斥锁
|
|
|
|
typedef struct {
|
|
pthread_mutex_t completed; //完成信号量
|
|
} at_watch_t;
|
|
|
|
/**
|
|
* @brief 测试项
|
|
*/
|
|
typedef struct {
|
|
void (*handler)(void);
|
|
const char *brief;
|
|
} test_item_t;
|
|
|
|
/**
|
|
* @brief 上锁
|
|
*/
|
|
static void at_mutex_lock(void)
|
|
{
|
|
pthread_mutex_lock(&at_lock);
|
|
}
|
|
|
|
/**
|
|
* @brief 解锁
|
|
*/
|
|
static void at_mutex_unlock(void)
|
|
{
|
|
pthread_mutex_unlock(&at_lock);
|
|
}
|
|
|
|
/**
|
|
* @brief 命令异常处理
|
|
*/
|
|
static void at_error_handler(at_response_t *r)
|
|
{
|
|
printf("Error detected!\r\n");
|
|
}
|
|
/**
|
|
* @brief 打印输出
|
|
*/
|
|
static void at_debug(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
vprintf(fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
#if AT_URC_WARCH_EN
|
|
|
|
/**
|
|
* @brief 开/关机URC处理程序
|
|
*/
|
|
static int urc_power_handler(at_urc_info_t *ctx)
|
|
{
|
|
int status;
|
|
if (sscanf(ctx->urcbuf, "+POWER:%d",&status) == 1) {
|
|
printf("Device power %s event detected!\r\n", status ? "on" : "off");
|
|
}
|
|
return 0;
|
|
}
|
|
/**
|
|
* @brief socket数据包(+IPD,<socket id>,<data length>:.....)
|
|
*/
|
|
static int urc_socket_data_handler(at_urc_info_t *ctx)
|
|
{
|
|
int length, total_length, sockid, i;
|
|
char *data;
|
|
unsigned char check = 0;
|
|
if (sscanf(ctx->urcbuf, "+IPD,%d,%d:", &sockid, &length) == 2) { //解析出总数据长度
|
|
data = strchr(ctx->urcbuf, ':');
|
|
if (data == NULL)
|
|
return 0;
|
|
data++;
|
|
total_length = (data - ctx->urcbuf) + length; //计算头部长度
|
|
if (ctx->urclen < total_length) { //未接收全,返回剩余待接收数据
|
|
printf("Need to receive %d more bytes\r\n", total_length - ctx->urclen);
|
|
return total_length - ctx->urclen;
|
|
}
|
|
for (i = 1, check = 0; i < length; i++) {
|
|
check ^= data[i];
|
|
}
|
|
printf("%d bytes of data were received form socket %d, check %s!\r\n",
|
|
length, sockid, check == data[0] ? "ok": "error");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* @brief urc订阅表
|
|
*/
|
|
static const urc_item_t urc_table[] =
|
|
{
|
|
{.prefix = "+POWER:", .endmark = '\n', .handler = urc_power_handler},
|
|
{.prefix = "+IPD,", .endmark = ':', .handler = urc_socket_data_handler}
|
|
};
|
|
#endif
|
|
|
|
/**
|
|
* @brief 显示内存使用信息
|
|
*/
|
|
static void sample_show_memory(void)
|
|
{
|
|
#if AT_MEM_WATCH_EN
|
|
printf("max memory:%d, current memory:%d\r\n", at_max_used_memory(), at_cur_used_memory());
|
|
#else
|
|
printf("Unknown memory usage, please enable 'AT_MEM_WATCH_EN' macro first.\r\n");
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* @brief 单行命令
|
|
*/
|
|
static void sample_singlline(void)
|
|
{
|
|
at_attr_t attr;
|
|
at_attr_deinit(&attr);
|
|
// => AT+CSQ
|
|
// <= +CSQ:<rssi>,<ber>
|
|
// <= OK
|
|
at_send_singlline(at_obj, &attr, "AT+CSQ");
|
|
}
|
|
|
|
/**
|
|
* @brief 多行命令
|
|
*/
|
|
static void sample_multiline(void)
|
|
{
|
|
static const char *cmd_table[] = {
|
|
"AT+CPIN?",
|
|
"AT+CSQ",
|
|
"AT+CREG",
|
|
"AT+PARAM?",
|
|
NULL
|
|
};
|
|
at_send_multiline(at_obj, NULL, cmd_table);
|
|
}
|
|
|
|
/**
|
|
* @brief 可变参数命令
|
|
*/
|
|
static void sample_variable_param_cmd(void)
|
|
{
|
|
srand((unsigned)time(NULL));
|
|
at_exec_cmd(at_obj, NULL, "AT+PARAM=%d,%d",rand() % 100,rand() % 200);
|
|
at_exec_cmd(at_obj, NULL, "AT+PARAM?");
|
|
}
|
|
|
|
/**
|
|
* @brief 响应超级返重试
|
|
*/
|
|
static void sample_timeout_retry(void)
|
|
{
|
|
at_attr_t attr;
|
|
at_attr_deinit(&attr);
|
|
attr.retry = 3;
|
|
attr.timeout = 1000;
|
|
at_exec_cmd(at_obj, &attr, "AAA");
|
|
}
|
|
|
|
/**
|
|
* @brief 命令错误重试
|
|
*/
|
|
static void sample_error_retry(void)
|
|
{
|
|
at_attr_t attr;
|
|
at_attr_deinit(&attr);
|
|
attr.retry = 2;
|
|
at_exec_cmd(at_obj, &attr, "AT+CMD");
|
|
}
|
|
|
|
/**
|
|
* @brief 命令终止
|
|
*/
|
|
static void sample_abort_command(void)
|
|
{
|
|
at_exec_cmd(at_obj, NULL, "AT+CMD");
|
|
at_work_abort_all(at_obj);
|
|
}
|
|
|
|
/**
|
|
* @brief 版本读取响应程序
|
|
*/
|
|
static void read_ver_cb(at_response_t *r)
|
|
{
|
|
char verbuf[16];
|
|
if (r->code == AT_RESP_OK) {
|
|
sscanf(r->prefix, "+VER:%s\r\n", verbuf);
|
|
printf("Version info:%s\r\n", verbuf);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief 指定响应前缀
|
|
* => AT+VER
|
|
* <= +VER:Vxxxx
|
|
* <= OK
|
|
*/
|
|
static void sample_specific_prefix(void)
|
|
{
|
|
at_attr_t attr;
|
|
at_attr_deinit(&attr);
|
|
//匹配介于[+VER -> \r\n]之间的内容
|
|
attr.prefix = "+VER",
|
|
attr.suffix = "\r";
|
|
attr.cb = read_ver_cb;
|
|
at_exec_cmd(at_obj, &attr, "AT+VER");
|
|
}
|
|
|
|
/**
|
|
* @brief 自定义命令发送器
|
|
*/
|
|
static void custom_sender(at_env_t *env)
|
|
{
|
|
env->println(env, "AT+CSQ\r\n");
|
|
}
|
|
/**
|
|
* @brief 自定义命令
|
|
*/
|
|
static void sample_custom_cmd(void)
|
|
{
|
|
at_custom_cmd(at_obj, NULL, custom_sender);
|
|
}
|
|
|
|
/**
|
|
* @brief 发送缓冲区
|
|
*/
|
|
static void sample_send_buffer(void)
|
|
{
|
|
char buf[] = "AT+CSQ\r\n";
|
|
at_send_data(at_obj, NULL, buf, sizeof(buf));
|
|
}
|
|
|
|
/**
|
|
* @brief 查询CSQ
|
|
*/
|
|
static int at_work_query_csq(at_env_t *env)
|
|
{
|
|
char *p;
|
|
int rssi, ber;
|
|
switch (env->state)
|
|
{
|
|
case 0:
|
|
env->println(env, "AT+CSQ\r\n");
|
|
env->state++; //转到接收状态
|
|
env->recvclr(env); //清空接口
|
|
env->reset_timer(env); //重置计时器
|
|
break;
|
|
case 1:
|
|
if (env->contains(env, "OK")) {
|
|
//+CSQ:<rssi>,<ber>
|
|
if ((p = env->contains(env, "+CSQ")) != NULL &&
|
|
sscanf(p, "+CSQ:%d,%d", &rssi, &ber) == 2) {
|
|
printf("The CSQ read ok, rssi:%d, ber:%d\r\n", rssi, ber);
|
|
}
|
|
return true;
|
|
} else if (env->contains(env, "ERROR")) { //错误处理
|
|
printf("The CSQ read failed.\r\n");
|
|
return true;
|
|
} else if (env->is_timeout(env, 1000)) { //错误处理
|
|
printf("The CSQ read timeout.\r\n");
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief 自定义作业测试1
|
|
*/
|
|
static void sample_at_work_1(void)
|
|
{
|
|
at_do_work(at_obj, NULL, at_work_query_csq);
|
|
}
|
|
|
|
|
|
static unsigned char check_bin_data(unsigned char *buf, int len)
|
|
{
|
|
unsigned char check = 0;
|
|
while (len--) {
|
|
check ^= *buf++;
|
|
}
|
|
return check;
|
|
}
|
|
|
|
/**
|
|
* @brief 读取socket数制
|
|
*/
|
|
static int at_work_read_bin(at_env_t *env)
|
|
{
|
|
#define READ_STAT_START 0
|
|
#define READ_STAT_HEAD 1
|
|
#define READ_STAT_DATA 2
|
|
|
|
static char *start, *end;
|
|
static int total, sockid;
|
|
unsigned char buf[256];
|
|
//=> AT+BINDAT=<socket id>,<read size>
|
|
//
|
|
//<= +BINDAT:<socket id>,<real size>\r\n<raw data......>
|
|
//
|
|
//<= OK
|
|
switch (env->state)
|
|
{
|
|
case READ_STAT_START:
|
|
env->println(env, "AT+BINDAT=%d,%d\r\n", 1, sizeof(buf));
|
|
env->recvclr(env);
|
|
env->reset_timer(env);
|
|
env->state = READ_STAT_HEAD;
|
|
break;
|
|
case READ_STAT_HEAD:
|
|
if ((start = env->contains(env, "+BINDAT:")) != NULL) {
|
|
end = strchr(start, '\n');
|
|
if (end == NULL)
|
|
break;
|
|
end++;
|
|
if (sscanf(start, "+BINDAT:%d,%d" ,&sockid, &total) == 2) {
|
|
env->reset_timer(env);
|
|
env->state++;
|
|
printf("Next receive %d bytes data from socket %d\r\n", total, sockid);
|
|
}
|
|
} else if (env->contains(env, "ERROR")) {
|
|
env->finish(env, AT_RESP_ERROR);
|
|
return true;
|
|
} else if (env->is_timeout(env, 1000)) {
|
|
if (env->i++ > 3) {
|
|
/*产生发送异常事件 */
|
|
printf("recv error!!!\r\n");
|
|
return true;
|
|
}
|
|
env->state = READ_STAT_START; /*重新发送*/
|
|
}
|
|
break;
|
|
case READ_STAT_DATA:
|
|
//
|
|
//recvbuf...start....end....data ....
|
|
// |---- total ----|
|
|
//|---------- recvlen ---------------|
|
|
if (env->recvlen(env) >= total + (end - env->recvbuf(env))) { //数据接收完成
|
|
memcpy(buf, end, total);
|
|
printf("recv ok!!!\r\n");
|
|
if (total > 0 && buf[0] == check_bin_data(&buf[1], total - 1))
|
|
printf("check ok!!!\r\n");
|
|
return true;
|
|
} else if (env->is_timeout(env, 1000)){
|
|
printf("recv error!!!\r\n");
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @brief Read binary data via 'at work'
|
|
*/
|
|
static void sample_read_bin_data(void)
|
|
{
|
|
at_do_work(at_obj, NULL, at_work_read_bin);
|
|
}
|
|
|
|
/**
|
|
* @brief Capture unsolicited binary data
|
|
*/
|
|
static void sample_urc_bin_data(void)
|
|
{
|
|
int i;
|
|
unsigned char check; //数据校验码
|
|
int len;
|
|
char buf[256];
|
|
char head[32];
|
|
srand((unsigned)time(NULL));
|
|
len = rand() % sizeof(buf);
|
|
for (i = 1, check = 0; i < len; i++) {
|
|
|
|
buf[i] = rand() % 128;
|
|
check ^= buf[i]; //计算校验码
|
|
}
|
|
buf[0] = check;
|
|
//BCC+DATA
|
|
//+IPD,<socket id>,<data length>:.....
|
|
snprintf(head, sizeof(head),"+IPD,%d,%d:",rand() % 16, len);
|
|
at_device_emit_urc(head, strlen(head));
|
|
at_device_emit_urc(buf, len);
|
|
}
|
|
|
|
#if AT_WORK_CONTEXT_EN
|
|
/**
|
|
* @brief 发送命令(同步方式)
|
|
* @param respbuf 响应缓冲区
|
|
* @param bufsize 缓冲区大小
|
|
* @param timeout 超时时间
|
|
* @param cmd 命令
|
|
* @retval 命令执行状态
|
|
*/
|
|
static at_resp_code at_send_cmd_sync(char *respbuf, int bufsize, int timeout, const char *cmd, ...)
|
|
{
|
|
at_attr_t attr;
|
|
at_context_t ctx;
|
|
va_list args;
|
|
bool ret;
|
|
//属性初始化
|
|
at_attr_deinit(&attr);
|
|
attr.timeout = timeout;
|
|
attr.retry = 1;
|
|
//初始化context
|
|
at_context_init(&ctx, respbuf, bufsize);
|
|
//为工作项绑定context
|
|
at_context_attach(&attr, &ctx);
|
|
|
|
va_start(args, cmd);
|
|
ret = at_exec_vcmd(at_obj, &attr, cmd, args);
|
|
va_end(args);
|
|
|
|
if (!ret) {
|
|
return AT_RESP_ERROR;
|
|
}
|
|
|
|
//等待命令执行完毕
|
|
while (!at_work_is_finish(&ctx)) {
|
|
usleep(1000);
|
|
}
|
|
return at_work_get_result(&ctx);
|
|
}
|
|
|
|
/**
|
|
* @brief 基于上下文实现同步接口
|
|
*/
|
|
static void sample_at_sync(void)
|
|
{
|
|
int rssi, error_rate;
|
|
char csqbuf[64];
|
|
at_resp_code code;
|
|
code = at_send_cmd_sync(csqbuf, sizeof(csqbuf), 200, "AT+CSQ");
|
|
if (code == AT_RESP_OK) {
|
|
sscanf(csqbuf, "+CSQ:%d,%d", &rssi, &error_rate);
|
|
printf("The CSQ value is read successfully\r\n=>rssi:%d, error_rate:%d\r\n", rssi, error_rate);
|
|
} else if (code == AT_RESP_TIMEOUT) {
|
|
printf("The CSQ read timeout.\r\n");
|
|
} else {
|
|
printf("The CSQ read failed.\r\n");
|
|
}
|
|
}
|
|
|
|
#endif
|
|
/**
|
|
* @brief 测试用例
|
|
*/
|
|
static const test_item_t test_cases[] = {
|
|
{sample_show_memory, "Display memory information."},
|
|
{sample_singlline, "Testing singlline command."},
|
|
{sample_multiline, "Testing multiline commands."},
|
|
{sample_variable_param_cmd, "Testing variable parameter command."},
|
|
{sample_timeout_retry, "Testing response timeout retry."},
|
|
{sample_error_retry, "Testing response error retry."},
|
|
{sample_abort_command, "Testing command abort."},
|
|
{sample_specific_prefix, "Testing specific response prefix."},
|
|
{sample_custom_cmd, "Testing custom command."},
|
|
{sample_send_buffer, "Testing buffer send."},
|
|
{sample_at_work_1, "Testing 'at_do_work' 1."},
|
|
{sample_read_bin_data, "Testing read binary data via 'at work'."},
|
|
{sample_urc_bin_data, "Testing capture unsolicited binary data."},
|
|
#if AT_WORK_CONTEXT_EN
|
|
{sample_at_sync, "Testing at context interface."},
|
|
#endif
|
|
};
|
|
|
|
/**
|
|
* @brief 显示测试用例
|
|
*/
|
|
static void show_test_case(void)
|
|
{
|
|
int i;
|
|
printf("Please input test item:\r\n");
|
|
for (i = 0; i < sizeof(test_cases) / sizeof(test_cases[0]); i++)
|
|
printf("\t%d:%s\r\n", i + 1, test_cases[i].brief);
|
|
printf("*******************************************************\r\n");
|
|
}
|
|
|
|
/**
|
|
* @brief 显示测试用例
|
|
*/
|
|
static void input_process(void)
|
|
{
|
|
const test_item_t *item;
|
|
char buf[128];
|
|
int num;
|
|
|
|
fgets(buf, sizeof(buf), stdin);
|
|
num = atoi(buf);
|
|
if (num > 0 && num <= sizeof(test_cases) / sizeof(test_cases[0])) {
|
|
item = &test_cases[num - 1];
|
|
printf("Start '%s'\r\n", item->brief);
|
|
item->handler();
|
|
} else {
|
|
show_test_case();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief at适配器
|
|
*/
|
|
static const at_adapter_t at_adapter = {
|
|
.lock = at_mutex_lock,
|
|
.unlock = at_mutex_unlock,
|
|
.write = at_device_write,
|
|
.read = at_device_read,
|
|
.error = at_error_handler,
|
|
.debug = at_debug,
|
|
#if AT_URC_WARCH_EN
|
|
.urc_bufsize = 300,
|
|
#endif
|
|
.recv_bufsize = 300
|
|
};
|
|
|
|
/**
|
|
* @brief at通信处理线程
|
|
*/
|
|
static void *at_thread(void *args)
|
|
{
|
|
pthread_detach(pthread_self());
|
|
printf("at thread running...\r\n");
|
|
while(1) {
|
|
usleep(1000);
|
|
at_obj_process(at_obj);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
pthread_t tid;
|
|
at_obj = at_obj_create(&at_adapter);
|
|
if (at_obj == NULL) {
|
|
printf("at object create failed\r\n");
|
|
_exit(0);
|
|
}
|
|
at_obj_set_urc(at_obj, urc_table, sizeof(urc_table) / sizeof(urc_table[0]) );
|
|
at_device_init();
|
|
pthread_mutex_init(&at_lock, NULL);
|
|
pthread_create(&tid, NULL, at_thread, NULL);
|
|
printf("*******************************************************\r\n");
|
|
printf("This is an asynchronous AT command framework.\r\n");
|
|
printf("Author:roger.luo, Mail:morro_luo@163.com\r\n");
|
|
printf("*******************************************************\r\n\r\n");
|
|
//Open AT emulator device
|
|
at_device_open();
|
|
|
|
show_test_case();
|
|
while(1){
|
|
input_process();
|
|
usleep(10 * 1000);
|
|
}
|
|
}
|