AT Client
AT Client 配置
当我们使用 AT 组件中的 AT Client 功能是需要在 rtconfig.h 中定义如下配置:
- #define RT_USING_AT
- #define AT_USING_CLIENT
- #define AT_CLIENT_NUM_MAX 1
- #define AT_USING_SOCKET
- #define AT_USING_CLI
- RT_USING_AT: 用于开启或关闭 AT 组件;
- AT_USING_CLIENT: 用于开启 AT Client 功能;
- AT_CLIENT_NUM_MAX: 最大同时支持的 AT 客户端数量
- AT_USING_SOCKET:用于 AT 客户端支持标准 BSD Socket API,开启 AT Socket 功能。
- AT_USING_CLI: 用于开启或关闭客户端命令行交互模式。
上面配置选项可以直接在rtconfig.h
文件中添加使用,也可以通过组件包管理工具 ENV 配置选项加入,ENV 中具体路径如下:
- RT-Thread Components --->
- Network --->
- AT commands --->
- [*] Enable AT commands
- [ ] Enable debug log output
- [ ] Enable AT commands server
- [*] Enable AT commands client
- (uart2) Client device name
- (512) The maximum length of client data accepted
- [*] Enable command-line interface for AT commands
- [ ] Enable print RAW format AT command communication data
添加配置完成之后可以使用命令行重新生成工程,或使用 scons 来进行编译生成。
AT Client 初始化
配置开启 AT Client 配置之后,需要在启动时对它进行初始化,开启 AT client 功能,如果程序中已经使用了组件自动初始化,则不再需要额外进行单独的初始化,否则需要在初始化任务中调用如下函数:
- int at_client_init(const char *dev_name, rt_size_t recv_bufsz);
AT Client 初始化函数,属于应用层函数,需要在使用 AT Client 功能或者使用 AT Client CLI 功能前调用。at_client_init 函数完成对 AT Client 设备初始化、AT Client 移植函数的初始化、AT Client 使用的信号量、互斥锁等资源初始化,并创建 at_client
线程用于 AT Client 中数据的接收的解析以及对 URC 数据的处理。
AT Client 数据收发方式
AT Client 主要功能是发送 AT 命令、接收数据并解析数据。下面是对 AT Client 数据接收和发送相关流程与函数介绍。
相关结构体定义:
- struct at_response
- {
- /* response buffer */
- char *buf;
- /* the maximum response buffer size */
- rt_size_t buf_size;
- /* the number of setting response lines
- * == 0: the response data will auto return when received 'OK' or 'ERROR'
- * != 0: the response data will return when received setting lines number data */
- rt_size_t line_num;
- /* the count of received response lines */
- rt_size_t line_counts;
- /* the maximum response time */
- rt_int32_t timeout;
- };
- typedef struct at_response *at_response_t;
AT 组件中,该结构体用于定义一个 AT Server 响应数据的控制块,用于存放或者限制 AT Server 返回数据的部分格式。其中 buf
用于存放接收到的响应数据,注意的是 buf 中存放的数据并不是原始响应数据,而是原始响应数据去除结束符("\r\n")的数据,buf 中每行数据以 '\0' 分割,方便按行获取数据。buf_size
为用户自定义本次响应最大支持的接收数据的长度,由用户根据自己命令返回值长度定义。line_num
为用户自定义的本次响应数据需要接收的行数,如果没有行数限定需求可以置为 0。 line_counts
用于记录本次响应数据总行数。timeout
为用户自定义的本次响应数据最大响应时间。该结构体中 buf_size
、line_num
、timeout
三个参数为限制条件,在结构体创建时设置,其他参数为存放数据参数,用于后面数据解析。
相关 API 接口介绍:
创建响应结构体
- at_response_t at_create_resp(rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout);
该函数用于创建自定义的响应数据接收结构,用于后面接收并解析发送命令响应数据。
参数 | 描述 |
---|---|
buf_size | 本次响应最大支持的接收数据的长度 |
line_num | 本次响应需要返回数据的行数,行数是以标准结束符划分 |
若为 0 ,则接收到 "OK" 或 "ERROR" 数据后结束本次响应接收 | |
若大于 0,接收完当前设置行号的数据后返回成功 | |
timeout | 本次响应数据最大响应时间,数据接收超时返回错误。 |
返回 | 描述 |
!= NULL | 成功,返回指向响应结构体的指针 |
= NULL | 失败,内存不足 |
删除响应结构体
- void at_delete_resp(at_response_t resp);
该函数用于删除创建的响应结构体对象,一般与 at_create_resp 创建函数成对出现。
参数 | 描述 |
---|---|
resp | 准备删除的响应结构体指针 |
返回 | 描述 |
无 | 无 |
设置响应结构体参数
- at_response_t at_resp_set_info(at_response_t resp, rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout);`
该函数用于设置已经创建的响应结构体信息,主要设置对响应数据的限制信息,一般用于创建结构体之后,发送 AT 命令之前。
参数 | 描述 |
---|---|
resp | 已经创建的响应结构体指针 |
buf_size | 本次响应最大支持的接收数据的长度 |
line_num | 本次响应需要返回数据的行数,行数是以标准结束符划分 |
若为 0 ,则接收到 "OK" 或 "ERROR" 数据后结束本次响应接收 | |
若大于 0,接收完当前设置行号的数据后返回成功 | |
timeout | 本次响应数据最大响应时间,数据接收超时返回错误。 |
返回 | 描述 |
!= NULL | 成功,返回指向响应结构体的指针 |
= NULL | 失败,内存不足 |
发送命令并接收响应
- rt_err_t at_exec_cmd(at_response_t resp, const char *cmd_expr, ...);`
该函数用于 AT Client 发送命令到 AT Server,并等待接收响应,其中 resp
是已经创建好的响应结构体的指针,AT 命令的使用匹配表达式的可变参输入,输入命令的结尾不需要添加命令结束符 。
参数 | 描述 |
---|---|
resp | 创建的响应结构体指针 |
cmd_expr | 自定义输入命令的表达式 |
… | 输入命令数据列表,为可变参数 |
返回 | 描述 |
>=0 | 成功 |
-1 | 失败 |
-2 | 失败,接收响应超时 |
可参考以下代码了解如何使用以上几个 AT 命令收发相关函数使用方式:
- /*
- * 程序清单:AT Client发送命令并接收响应例程
- */
- #include <rtthread.h>
- #include <at.h> /* AT 组件头文件 */
- int at_client_send(int argc, char **argv)
- {
- at_response_t resp = RT_NULL;
- if (argc != 2)
- {
- LOG_E("at_cli_send [command] - AT client send commands to AT server.");
- return -RT_ERROR;
- }
- /* 创建响应结构体,设置最大支持响应数据长度为 512 字节,响应数据行数无限制,超时时间为 5 秒 */
- resp = at_create_resp(512, 0, rt_tick_from_millisecond(5000));
- if (!resp)
- {
- LOG_E("No memory for response structure!");
- return -RT_ENOMEM;
- }
- /* 发送 AT 命令并接收 AT Server 响应数据,数据及信息存放在 resp 结构体中 */
- if (at_exec_cmd(resp, argv[1]) != RT_EOK)
- {
- LOG_E("AT client send commands failed, response error or timeout !");
- return -ET_ERROR;
- }
- /* 命令发送成功 */
- LOG_D("AT Client send commands to AT Server success!");
- /* 删除响应结构体 */
- at_delete_resp(resp);
- return RT_EOK;
- }
- #ifdef FINSH_USING_MSH
- #include <finsh.h>
- /* 输出 at_client_send 函数到 msh 中 */
- MSH_CMD_EXPORT(at_client_send, AT Client send commands to AT Server and get response data);
- #endif
发送和接收数据的实现原理比较简单,主要是对 AT Client 绑定的串口设备的读写操作,并设置相关行数和超时来限制响应数据,值得注意的是,正常情况下需要先创建 resp 响应结构体传入 at_exec_cmd 函数用于数据的接收,当 at_exec_cmd 函数传入 resp 为 NULL 时说明本次发送数据不考虑处理响应数据直接返回结果。
AT Client 数据解析方式
数据正常获取之后,需要对响应的数据进行解析处理,这也是 AT Client 重要的功能之一。 AT Client 中数据的解析提供自定义解析表达式的解析形式,其解析语法使用标准的 sscanf
解析语法。开发者可以通过自定义数据解析表达式回去响应数据中有用信息,前提是开发者需要提前查看相关手册了解 AT Client 连接的 AT Server 设备响应数据的基本格式。下面通过几个函数和例程简单 AT Client 数据解析方式。
相关 API 接口:
获取指定行号的响应数据
- const char *at_resp_get_line(at_response_t resp, rt_size_t resp_line);
该函数用于在 AT Server 响应数据中获取指定行号的一行数据。行号是以标准数据结束符来判断的,上述发送和接收函数 at_exec_cmd 已经对响应数据的数据和行号进行记录处理存放于 resp 响应结构体中,这里可以直接获取对应行号的数据信息。
参数 | 描述 |
---|---|
resp | 响应结构体指针 |
resp_line | 需要获取数据的行号 |
返回 | 描述 |
!= NULL | 成功,返回对应行号数据的指针 |
= NULL | 失败,输入行号错误 |
获取指定关键字的响应数据
- const char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword);
该函数用于在 AT Server 响应数据中通过关键字获取对应的一行数据。
参数 | 描述 |
---|---|
resp | 响应结构体指针 |
keyword | 关键字信息 |
返回 | 描述 |
!= NULL | 成功,返回对应行号数据的指针 |
= NULL | 失败,未找到关键字信息 |
解析指定行号的响应数据
- int at_resp_parse_line_args(at_response_t resp, rt_size_t resp_line, const char *resp_expr, ...);
该函数用于在 AT Server 响应数据中获取指定行号的一行数据, 并解析该行数据中的参数。
参数 | 描述 |
---|---|
resp | 响应结构体指针 |
resp_line | 需要解析数据的行号 |
resp_expr | 自定义的参数解析表达式 |
… | 解析参数列表,为可变参数 |
返回 | 描述 |
>0 | 成功,返回解析成功的参数个数 |
=0 | 失败,无匹参配数解析表达式的参数 |
-1 | 失败,参数解析错误 |
解析指定关键字行的响应数据
- int at_resp_parse_line_args_by_kw(at_response_t resp, const char *keyword, const char *resp_expr, ...);
该函数用于在 AT Server 响应数据中获取包含关键字的一行数据, 并解析该行数据中的参数。
参数 | 描述 |
---|---|
resp | 响应结构体指针 |
keyword | 关键字信息 |
resp_expr | 自定义的参数解析表达式 |
… | 解析参数列表,为可变参数 |
返回 | 描述 |
>0 | 成功,返回解析成功的参数个数 |
=0 | 失败,无匹参配数解析表达式的参数 |
-1 | 失败,参数解析错误 |
数据解析语法使用标准 sscanf
解析语法,语法的内容比较多,开发者可以自行搜索其解析语法,这里使用两个例程介绍简单使用方法。
串口配置信息解析示例
客户端发送的数据: AT+UART?
客户端获取的响应数据: UART=115200,8,1,0,0\r\n OK\r\n
解析伪代码如下:
- /* 创建服务器响应结构体,64 为用户自定义接收数据最大长度 */
- resp = at_create_resp(64, 0, rt_tick_from_millisecond(5000));
- /* 发送数据到服务器,并接收响应数据存放在 resp 结构体中 */
- at_exec_cmd(resp, "AT+UART?");
- /* 解析获取串口配置信息,1 表示解析响应数据第一行,'%*[^=]'表示忽略等号之前的数据 */
- at_resp_parse_line_args(resp, 1,"%*[^=]=%d,%d,%d,%d,%d", &baudrate, &databits,
- &stopbits, &parity, &control); //
- printf("baudrate=%d, databits=%d, stopbits=%d, parity=%d, control=%d\n",
- baudrate, databits, stopbits, parity, control);
- /* 删除服务器响应结构体 */
- at_delete_resp(resp);
IP和MAC地址解析示例
客户端发送的数据: AT+IPMAC?
服务器获取的响应数据:
- IP=192.168.1.10\r\n
- MAC=12:34:56:78:9a:bc\r\n
- OK\r\n
解析伪代码如下:
- /* 创建服务器响应结构体,128 为用户自定义接收数据最大长度 */
- resp = at_create_resp(128, 0, rt_tick_from_millisecond(5000));
- at_exec_cmd(resp, "AT+IPMAC?");
- /* 自定义解析表达式,解析当前行号数据中的信息 */
- at_resp_parse_line_args(resp, 1,"IP=%s", ip);
- at_resp_parse_line_args(resp, 2,"MAC=%s", mac);
- printf("IP=%s, MAC=%s\n", ip, mac);
- at_delete_resp(resp);
解析数据的关键在于解析表达式的正确定义,因为对于 AT 设备的响应数据,不同设备厂家不同命令的响应数据格式不唯一,所以只能提供自定义解析表达式的形式获取需要信息,at_resp_parse_line_args 解析参数函数的设计基于 sscanf
数据解析方式,开发者使用之前需要先了解基本的解析语法,再结合响应数据设计合适的解析语法。如果开发者不需要解析具体参数,可以直接使用 at_resp_get_line 函数获取一行的具体数据。
AT Client URC 数据处理
URC 数据的处理是 AT Client 另一个重要功能,URC 数据为服务器主动下发的数据,不能通过上述数据发送接收函数接收,并且对于不同设备 URC 数据格式和功能不一样,所以 URC 数据处理的方式也是需要用户自定义实现的。AT 组件中对 URC 数据的处理提供列表管理方式,用户可自定义添加 URC 数据和其执行函数到管理列表中,所以URC 数据的处理也是 AT Client 的主要移植工作。
相关结构体:
- struct at_urc
- {
- const char *cmd_prefix; // URC 数据前缀
- const char *cmd_suffix; // URC 数据后缀
- void (*func)(const char *data, rt_size_t size); // URC 数据执行函数
- };
- typedef struct at_urc *at_urc_t;
每种 URC 数据都有一个结构体控制块,用于定义判断 URC 数据的前缀和后缀,以及 URC 数据的执行函数。一段数据只有完全匹配 URC 的前缀和后缀才能定义为 URC 数据,获取到匹配的 URC 数据后会立刻执行 URC 数据执行函数。所以开发者添加一个 URC 数据需要自定义匹配的前缀、后缀和执行函数。
相关 API 接口
URC 数据列表初始化
- void at_set_urc_table(const struct at_urc *table, rt_size_t size);
该函数用于初始化开发者自定义的 URC 数据列表,主要在 AT Client 移植函数中使用。
参数 | 描述 |
---|---|
table | URC 数据结构体数组指针 |
size | URC 数据的个数 |
返回 | 描述 |
无 | 无 |
下面给出 AT Client 移植具体示例,该示例主要展示 at_client_port_init()
移植函数中 URC 数据的具体处理方式,开发者可直接应用到自己的移植文件中,或者自定义修改实现功能,完成 AT Client 的移植。
- static void urc_conn_func(const char *data, rt_size_t size)
- {
- /* WIFI 连接成功信息 */
- LOG_D("AT Server device WIFI connect success!");
- }
- static void urc_recv_func(const char *data, rt_size_t size)
- {
- /* 接收到服务器发送数据 */
- LOG_D("AT Client receive AT Server data!");
- }
- static void urc_func(const char *data, rt_size_t size)
- {
- /* 设备启动信息 */
- LOG_D("AT Server device startup!");
- }
- static struct at_urc urc_table[] = {
- {"WIFI CONNECTED", "\r\n", urc_conn_func},
- {"+RECV", ":", urc_recv_func},
- {"RDY", "\r\n", urc_func},
- };
- int at_client_port_init(void)
- {
- /* 添加多种 URC 数据至 URC 列表中,当接收到同时匹配 URC 前缀和后缀的数据,执行 URC 函数 */
- at_set_urc_table(urc_table, sizeof(urc_table) / sizeof(urc_table[0]));
- return RT_EOK;
- }
AT Client 其他 API 接口
发送指定长度数据
- rt_size_t at_client_send(const char *buf, rt_size_t size);
该函数用于通过 AT Client 设备发送指定长度数据到 AT Server 设备,多用于 AT Socket 功能。
参数 | 描述 |
---|---|
buf | 发送数据的指针 |
size | 发送数据的长度 |
返回 | 描述 |
>0 | 成功,返回发送成功的数据长度 |
<=0 | 失败 |
接收指定长度数据
- rt_size_t at_client_recv(char *buf, rt_size_t size);
该函数用于通过 AT Client 设备接收指定长度的数据,多用于 AT Socket 功能。该函数只能在 URC 回调处理函数中使用。
参数 | 描述 |
---|---|
buf | 接收数据的指针 |
size | 最大支持接收数据的长度 |
返回 | 描述 |
>0 | 成功,返回接收成功的数据长度 |
<=0 | 失败 |
设置接收数据的行结束符
- void at_set_end_sign(char ch);
该函数用于设置行结束符,用于判断客户端接收一行数据的结束, 多用于 AT Socket 功能
参数 | 描述 |
---|---|
ch | 行结束符 |
返回 | 描述 |
无 | 无 |
等待模块初始化完成
- int at_client_wait_connect(rt_uint32_t timeout);
该函数用于 AT 模块启动时循环发送 AT 命令,直到模块响应数据,说明模块启动成功。
参数 | 描述 |
---|---|
timeout | 等待超时时间 |
返回 | 描述 |
0 | 成功 |
<0 | 失败,超时时间内无数据返回 |
AT Client 多客户端支持
一般情况下,设备作为 AT Client 只连接一个 AT 模块(AT Server)可直接使用上述数据收发和命令解析的函数。少数情况,设备作为 AT Client 需要连接多个 AT 模块(AT Server),这种情况下设备的多客户端支持。
AT 组件提供对多客户端连接的支持,并且提供两套不同的函数接口:单客户端模式函数 和 多客户端模式函数。
单客户端模式函数:该类函数接口主要用于设备只连接一个 AT 模块情况,或者在设备连接多个 AT 模块时,用于第一个初始化的 AT 客户端中。
多客户端模式函数:该类函数接口主要用设备连接多个 AT 模块情况。
两种不同模式函数和在不同应用场景下的优缺点如下图:
单客户端模式函数定义与单连接模式函数相比,主要是对传入的客户端对象的定义不同,单客户端模式函数默认使用第一个初始化的 AT 客户端对象,多客户端模式函数可以传入用户自定义获取的客户端对象, 获取客户端对象的函数如下:
- at_client_t at_client_get(const char *dev_name);
该函数通过传入的设备名称获取该设备创建的 AT 客户端对象,用于多客户端连接时区分不同的客户端。
单客户端模式和多客户端模式函数接口定义区别如下几个函数:
单客户端模式函数 | 多客户端模式函数 |
---|---|
at_exec_cmd(…) | at_obj_exec_cmd(client, …) |
at_set_end_sign(…) | at_obj_set_end_sign(client, …) |
at_set_urc_table(…) | at_obj_set_urc_table(client, …) |
at_client_wait_connect(…) | at_client_obj_wait_connect(client, …) |
at_client_send(…) | at_client_obj_send(client, …) |
at_client_recv(…) | at_client_obj_recv(client, …) |
两种模式客户端数据收发和解析的方式基本相同,在函数使用流程上有所不同,如下所示:
- /* 单客户端模式函数使用方式 */
- at_response_t resp = RT_NULL;
- at_client_init("uart2", 512);
- resp = at_create_resp(256, 0, 5000);
- /* 使用单客户端模式函数发送命令 */
- at_exec_cmd(resp, "AT+CIFSR");
- at_delete_resp(resp);
- /* 多客户端模式函数使用方式 */
- at_response_t resp = RT_NULL;
- at_client_t client = RT_NULL;
- /* 初始化两个 AT 客户端 */
- at_client_init("uart2", 512);
- at_client_init("uart3", 512);
- /* 通过名称获取对应的 AT 客户端对象 */
- client = at_client_get("uart3");
- resp = at_create_resp(256, 0, 5000);
- /* 使用多客户端模式函数发送命令 */
- at_obj_exec_cmd(client, resp, "AT+CIFSR");
- at_delete_resp(resp);
其他函数使用的流程区别类似于上述 at_obj_exec_cmd()
函数,主要是先通过 at_client_get()
函数获取客户端对象,再通过传入的对象判断是哪个客户端,实现多客户端的支持。