AT Client

AT Client 配置

当我们使用 AT 组件中的 AT Client 功能是需要在 rtconfig.h 中定义如下配置:

  1. #define RT_USING_AT
  2. #define AT_USING_CLIENT
  3. #define AT_CLIENT_NUM_MAX 1
  4. #define AT_USING_SOCKET
  5. #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 中具体路径如下:
  1. RT-Thread Components --->
  2. Network --->
  3. AT commands --->
  4. [*] Enable AT commands
  5. [ ] Enable debug log output
  6. [ ] Enable AT commands server
  7. [*] Enable AT commands client
  8. (uart2) Client device name
  9. (512) The maximum length of client data accepted
  10. [*] Enable command-line interface for AT commands
  11. [ ] Enable print RAW format AT command communication data

添加配置完成之后可以使用命令行重新生成工程,或使用 scons 来进行编译生成。

AT Client 初始化

配置开启 AT Client 配置之后,需要在启动时对它进行初始化,开启 AT client 功能,如果程序中已经使用了组件自动初始化,则不再需要额外进行单独的初始化,否则需要在初始化任务中调用如下函数:

  1. 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 数据接收和发送相关流程与函数介绍。

相关结构体定义:

  1. struct at_response
  2. {
  3. /* response buffer */
  4. char *buf;
  5. /* the maximum response buffer size */
  6. rt_size_t buf_size;
  7. /* the number of setting response lines
  8. * == 0: the response data will auto return when received 'OK' or 'ERROR'
  9. * != 0: the response data will return when received setting lines number data */
  10. rt_size_t line_num;
  11. /* the count of received response lines */
  12. rt_size_t line_counts;
  13. /* the maximum response time */
  14. rt_int32_t timeout;
  15. };
  16. 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_sizeline_numtimeout 三个参数为限制条件,在结构体创建时设置,其他参数为存放数据参数,用于后面数据解析。

相关 API 接口介绍:

创建响应结构体

  1. 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 失败,内存不足

删除响应结构体

  1. void at_delete_resp(at_response_t resp);

该函数用于删除创建的响应结构体对象,一般与 at_create_resp 创建函数成对出现。

参数 描述
resp 准备删除的响应结构体指针
返回 描述

设置响应结构体参数

  1. 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 失败,内存不足

发送命令并接收响应

  1. 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 命令收发相关函数使用方式:

  1. /*
  2. * 程序清单:AT Client发送命令并接收响应例程
  3. */
  4.  
  5. #include <rtthread.h>
  6. #include <at.h> /* AT 组件头文件 */
  7.  
  8. int at_client_send(int argc, char **argv)
  9. {
  10. at_response_t resp = RT_NULL;
  11.  
  12. if (argc != 2)
  13. {
  14. LOG_E("at_cli_send [command] - AT client send commands to AT server.");
  15. return -RT_ERROR;
  16. }
  17.  
  18. /* 创建响应结构体,设置最大支持响应数据长度为 512 字节,响应数据行数无限制,超时时间为 5 秒 */
  19. resp = at_create_resp(512, 0, rt_tick_from_millisecond(5000));
  20. if (!resp)
  21. {
  22. LOG_E("No memory for response structure!");
  23. return -RT_ENOMEM;
  24. }
  25.  
  26. /* 发送 AT 命令并接收 AT Server 响应数据,数据及信息存放在 resp 结构体中 */
  27. if (at_exec_cmd(resp, argv[1]) != RT_EOK)
  28. {
  29. LOG_E("AT client send commands failed, response error or timeout !");
  30. return -ET_ERROR;
  31. }
  32.  
  33. /* 命令发送成功 */
  34. LOG_D("AT Client send commands to AT Server success!");
  35.  
  36. /* 删除响应结构体 */
  37. at_delete_resp(resp);
  38.  
  39. return RT_EOK;
  40. }
  41. #ifdef FINSH_USING_MSH
  42. #include <finsh.h>
  43. /* 输出 at_client_send 函数到 msh 中 */
  44. MSH_CMD_EXPORT(at_client_send, AT Client send commands to AT Server and get response data);
  45. #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 接口:

获取指定行号的响应数据

  1. 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 失败,输入行号错误

获取指定关键字的响应数据

  1. const char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword);

该函数用于在 AT Server 响应数据中通过关键字获取对应的一行数据。

参数 描述
resp 响应结构体指针
keyword 关键字信息
返回 描述
!= NULL 成功,返回对应行号数据的指针
= NULL 失败,未找到关键字信息

解析指定行号的响应数据

  1. 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 失败,参数解析错误

解析指定关键字行的响应数据

  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

解析伪代码如下:

  1. /* 创建服务器响应结构体,64 为用户自定义接收数据最大长度 */
  2. resp = at_create_resp(64, 0, rt_tick_from_millisecond(5000));
  3.  
  4. /* 发送数据到服务器,并接收响应数据存放在 resp 结构体中 */
  5. at_exec_cmd(resp, "AT+UART?");
  6.  
  7. /* 解析获取串口配置信息,1 表示解析响应数据第一行,'%*[^=]'表示忽略等号之前的数据 */
  8. at_resp_parse_line_args(resp, 1,"%*[^=]=%d,%d,%d,%d,%d", &baudrate, &databits,
  9. &stopbits, &parity, &control); //
  10. printf("baudrate=%d, databits=%d, stopbits=%d, parity=%d, control=%d\n",
  11. baudrate, databits, stopbits, parity, control);
  12.  
  13. /* 删除服务器响应结构体 */
  14. at_delete_resp(resp);

IP和MAC地址解析示例

客户端发送的数据:​ AT+IPMAC?

服务器获取的响应数据:

  1. IP=192.168.1.10\r\n
  2. MAC=12:34:56:78:9a:bc\r\n
  3. OK\r\n

解析伪代码如下:

  1. /* 创建服务器响应结构体,128 为用户自定义接收数据最大长度 */
  2. resp = at_create_resp(128, 0, rt_tick_from_millisecond(5000));
  3.  
  4. at_exec_cmd(resp, "AT+IPMAC?");
  5.  
  6. /* 自定义解析表达式,解析当前行号数据中的信息 */
  7. at_resp_parse_line_args(resp, 1,"IP=%s", ip);
  8. at_resp_parse_line_args(resp, 2,"MAC=%s", mac);
  9. printf("IP=%s, MAC=%s\n", ip, mac);
  10.  
  11. 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 的主要移植工作。

相关结构体:

  1. struct at_urc
  2. {
  3. const char *cmd_prefix; // URC 数据前缀
  4. const char *cmd_suffix; // URC 数据后缀
  5. void (*func)(const char *data, rt_size_t size); // URC 数据执行函数
  6. };
  7. typedef struct at_urc *at_urc_t;

每种 URC 数据都有一个结构体控制块,用于定义判断 URC 数据的前缀和后缀,以及 URC 数据的执行函数。一段数据只有完全匹配 URC 的前缀和后缀才能定义为 URC 数据,获取到匹配的 URC 数据后会立刻执行 URC 数据执行函数。所以开发者添加一个 URC 数据需要自定义匹配的前缀、后缀和执行函数。

相关 API 接口

URC 数据列表初始化

  1. 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 的移植。

  1. static void urc_conn_func(const char *data, rt_size_t size)
  2. {
  3. /* WIFI 连接成功信息 */
  4. LOG_D("AT Server device WIFI connect success!");
  5. }
  6.  
  7. static void urc_recv_func(const char *data, rt_size_t size)
  8. {
  9. /* 接收到服务器发送数据 */
  10. LOG_D("AT Client receive AT Server data!");
  11. }
  12.  
  13. static void urc_func(const char *data, rt_size_t size)
  14. {
  15. /* 设备启动信息 */
  16. LOG_D("AT Server device startup!");
  17. }
  18.  
  19. static struct at_urc urc_table[] = {
  20. {"WIFI CONNECTED", "\r\n", urc_conn_func},
  21. {"+RECV", ":", urc_recv_func},
  22. {"RDY", "\r\n", urc_func},
  23. };
  24.  
  25. int at_client_port_init(void)
  26. {
  27. /* 添加多种 URC 数据至 URC 列表中,当接收到同时匹配 URC 前缀和后缀的数据,执行 URC 函数 */
  28. at_set_urc_table(urc_table, sizeof(urc_table) / sizeof(urc_table[0]));
  29. return RT_EOK;
  30. }

AT Client 其他 API 接口

发送指定长度数据

  1. rt_size_t at_client_send(const char *buf, rt_size_t size);

该函数用于通过 AT Client 设备发送指定长度数据到 AT Server 设备,多用于 AT Socket 功能。

参数 描述
buf 发送数据的指针
size 发送数据的长度
返回 描述
>0 成功,返回发送成功的数据长度
<=0 失败

接收指定长度数据

  1. rt_size_t at_client_recv(char *buf, rt_size_t size);

该函数用于通过 AT Client 设备接收指定长度的数据,多用于 AT Socket 功能。该函数只能在 URC 回调处理函数中使用

参数 描述
buf 接收数据的指针
size 最大支持接收数据的长度
返回 描述
>0 成功,返回接收成功的数据长度
<=0 失败

设置接收数据的行结束符

  1. void at_set_end_sign(char ch);

该函数用于设置行结束符,用于判断客户端接收一行数据的结束, 多用于 AT Socket 功能

参数 描述
ch 行结束符
返回 描述

等待模块初始化完成

  1. 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 client 模式对比图

单客户端模式函数定义与单连接模式函数相比,主要是对传入的客户端对象的定义不同,单客户端模式函数默认使用第一个初始化的 AT 客户端对象,多客户端模式函数可以传入用户自定义获取的客户端对象, 获取客户端对象的函数如下:

  1. 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, …)

两种模式客户端数据收发和解析的方式基本相同,在函数使用流程上有所不同,如下所示:

  1. /* 单客户端模式函数使用方式 */
  2.  
  3. at_response_t resp = RT_NULL;
  4.  
  5. at_client_init("uart2", 512);
  6.  
  7. resp = at_create_resp(256, 0, 5000);
  8.  
  9. /* 使用单客户端模式函数发送命令 */
  10. at_exec_cmd(resp, "AT+CIFSR");
  11.  
  12. at_delete_resp(resp);
  1. /* 多客户端模式函数使用方式 */
  2.  
  3. at_response_t resp = RT_NULL;
  4. at_client_t client = RT_NULL;
  5.  
  6. /* 初始化两个 AT 客户端 */
  7. at_client_init("uart2", 512);
  8. at_client_init("uart3", 512);
  9.  
  10. /* 通过名称获取对应的 AT 客户端对象 */
  11. client = at_client_get("uart3");
  12.  
  13. resp = at_create_resp(256, 0, 5000);
  14.  
  15. /* 使用多客户端模式函数发送命令 */
  16. at_obj_exec_cmd(client, resp, "AT+CIFSR");
  17.  
  18. at_delete_resp(resp);

其他函数使用的流程区别类似于上述 at_obj_exec_cmd() 函数,主要是先通过 at_client_get() 函数获取客户端对象,再通过传入的对象判断是哪个客户端,实现多客户端的支持。