视频监控—开发板上WIFI网卡的使用(3)-仿手机功能写WIFI程序
- 硬件平台:韦东山嵌入式Linxu开发板(S3C2440.v3),无线WIFI网卡(RT3070)
- 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统
- 参考资料:OV7740_CSP_DS_1.51 datasheet、S3C2440 datasheet
- 开发环境:Linux-4.13.0-41内核(虚拟机)、arm-linux-gcc-4.3.2工具链、linux-3.4.2内核(开发版根文件系统)
- 源码仓库:https://gitee.com/d_1254436976/Embedded-Linux-Phase-3
一、目的
仿照手机上的WIFI连接功能,在开发板上实现如下功能:
- 自动扫描WIFI热点
- 点击某个WIFI热点后会去连接它, 必要时让你输入密码
- 曾经连接过的WIFI热点会保留它的密码等信息, 以后会自动连接
此程序是在wpa_supplicant
库的基础上修改实现的,需配合wpa_supplicant
库使用,仅供学习参考。
二、代码编写
在wpa_supplicant库中有wpa_cli.c
这个文件,在此基础上进行修改。
1、控制逻辑
在while(1)
循环体内实现如下:
- 扫描附近的WIFI
- 把获得的结果,通过串口打印出来
- 等待3s,让用户输入需要连接的网络,若3秒内输入,则程序等待用户输入网络的ap值,否则跳到下一次循环
- 获取需要连接的网络的相关信息
- 根据连接网络的认证方式,进行不同的认证操作
- 存储已经连接网络的配置信息
2、具体实现
2.1 命令响应函数
通过观察wpa_cli.c
这个文件的源码,wpa_request()
函数可以根据输入的命令字符串进行相应的操作。所以对这个函数进行进一步的封装
/*!
* @brief 响应不同的命令
* cmd: "set_network 2 ssid "dswei",
* 则agrc = 4,
* agrv[0] = "set_network"
* agrv[1] = "2"
* agrv[2] = "ssid"
* agrv[3] = "\"dswei\""
* @return
*/
static int wpa_command(struct wpa_ctrl *ctrl, char *cmd)
{
int argc;
char buf[1024];
char *argv[CFG_MAXARGS];
strncpy(buf, cmd, 1024);
buf[1023] = '\0';
argc = parse_line(buf, argv);
return wpa_request(ctrl, argc, argv);
}
2.2 提取每行的有效信息
使用wpa_cli
命令时,对于不同的操作命令,都会得到多行的输出信息,需要根据输出信息提取每行需要的输出信息。
对于输出信息的存储,其存储在一个一维数组中,并不是按照多行存储在二维数组
- 在提取时需要判断是否为一行的尾部,并记录下此时行数
endcnt
,并且更新linestart
指针的位置,使其指向下一行的开头。 - 当记录的行数
endcnt
与需要提取的index
相同时,则进行字符串的拷贝工作。
/*!
* @brief 从buf中获取指定行的数据
* @return 0:成功,-1:失败
*/
static int get_line_from_buf(int index, char *buf, char *line)
{
int i = 0;
int j;
int len;
int endcnt = -1;
char *linestart = buf;
if (buf == NULL || line == NULL)
return -1;
while (1)
{
/* 一行尾部 */
if (buf[i] == '\n' || buf[i] == '\r' || buf[i] == '\0')
{
endcnt++;
/* 找到对应行 */
if (index == endcnt)
{
len = &buf[i] - linestart;
strncpy(line, linestart, len);
line[len] = '\0';
return 0;
}
else
{
/* 找到下一行开头的数组下标 */
for (j = i + 1; buf[j]; )
{
if (buf[j] == '\n' || buf[j] == '\r')
j++;
else
break;
}
if (!buf[j])
return -1;
/* 更新 */
linestart = &buf[j];
i = j;
}
}
if (!buf[i])
return -1;
i++;
}
}
2.3 采用select机制实现等待
通过标准输入来等待用户输入需要选择的网络ap值,采用了select机制,等待指定的时间输入字符串
/*!
* @brief 标准输入获取字符
* @return 0:成功,-1:失败
*/
static int StdinGetChar(int timeout)
{
/* 如果有数据就读取、处理、返回
* 如果没有数据, 立刻返回, 不等待
*/
/* select, poll 可以参数 UNIX环境高级编程 */
struct timeval tTV;
fd_set tFDs;
char c;
tTV.tv_sec = timeout;
tTV.tv_usec = 0;
FD_ZERO(&tFDs);
FD_SET(STDIN_FILENO, &tFDs); //STDIN_FILENO is 0
if (timeout == -1)
select(STDIN_FILENO+1, &tFDs, NULL, NULL, NULL);
else
select(STDIN_FILENO+1, &tFDs, NULL, NULL, &tTV);
if (FD_ISSET(STDIN_FILENO, &tFDs))
{
/* 处理数据 */
c = fgetc(stdin);
return c;
}
else
{
return 0;
}
}
/*!
* @brief 标准输入获取字符串
* @return 0:成功,-1:失败
*/
static int StdinGetString(char *buf, int timeout)
{
/* 如果获得了第1个字符,以后的字符没有时间限制 */
int c;
int i = 0;
c = StdinGetChar(timeout);
if (!c)
return -1;
if (c == '\n' || c == '\r')
return -1;
buf[i++] = c;
while ((c = StdinGetChar(-1)))
{
if (c == '\n' || c == '\r')
{
buf[i] = '\0';
return 0;
}
else
{
buf[i++] = c;
}
}
return 0;
}
2.4 提取有效网络信息
- 调用
wpa_command()
函数,获取到的信息与信息长度存储静态全局变量中s_ret_buf[2048]
和s_ret_len
。 - 调用
get_line_from_buf()
函数,把得到的指定行的信息存储在line[1024]
中 - 通过
sscanf()
函数,对该行的信息进行拆分,存储到对应的数组中
使用示例:
char line[1024];
char bssid[1024];
char freq[1024];
char signal[1024];
char flags[1024];
char ssid[1024];
get_line_from_buf(1, s_ret_buf, line);
sscanf(line, "%s %s %s %s %s ", bssid, freq, signal, flags, ssid);
2.5 组织与运行命令
- 对于具体的操作命令,需要通过
sprintf()
函数,把命令所需的各部分组织成一个字符串,存储在cmd[1024]
数组中; - 调用
wpa_command()
函数,把命令字符串传入到函数中,实现不同的操作。
使用示例:
/* 取出ap与ssid,组织成命令 */
sprintf(cmd, "set_network %d ssid \"%s\"", ap, ssid);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
3、修改的代码
此代码为部分截取,需要在wpa_cli.c
源码基础上,添加替换如下代码
static char s_ret_buf[2048]; /**< 存储解析后的数据 */
static size_t s_ret_len; /**< 存储解析后的数据的长度 */
static int _wpa_ctrl_command(struct wpa_ctrl *ctrl, char *cmd, int print)
{
int ret;
if (ctrl_conn == NULL) {
printf("Not connected to wpa_supplicant - command dropped.\n");
return -1;
}
s_ret_len = sizeof(s_ret_buf) - 1;
ret = wpa_ctrl_request(ctrl, cmd, os_strlen(cmd), s_ret_buf, &s_ret_len,
wpa_cli_msg_cb);
if (ret == -2) {
printf("'%s' command timed out.\n", cmd);
return -2;
} else if (ret < 0) {
printf("'%s' command failed.\n", cmd);
return -1;
}
s_ret_buf[s_ret_len] = '\0';
if (print) {
s_ret_buf[s_ret_len] = '\0';
printf("%s", s_ret_buf);
if (interactive && s_ret_len > 0 && s_ret_buf[s_ret_len - 1] != '\n')
printf("\n");
}
return 0;
}
/*!
* @brief 解析命令
* @return
*/
#define CFG_MAXARGS 10 /**< 最大参数长度 */
static int parse_line (char *line, char *argv[])
{
int nargs = 0;
while (nargs < CFG_MAXARGS) {
/* skip any white space */
while ((*line == ' ') || (*line == '\t')) {
++line;
}
if (*line == '\0') { /* end of line, no more args */
argv[nargs] = NULL;
return (nargs);
}
argv[nargs++] = line; /* begin of argument string */
/* find end of string */
while (*line && (*line != ' ') && (*line != '\t')) {
++line;
}
if (*line == '\0') { /* end of line, no more args */
argv[nargs] = NULL;
return (nargs);
}
*line++ = '\0'; /* terminate current arg */
}
return (nargs);
}
/*!
* @brief 自己编写的仿照手机的wifi功能
* cmd: "set_network 2 ssid "dswei",
* 则agrc = 4,
* agrv[0] = "set_network"
* agrv[1] = "2"
* agrv[2] = "ssid"
* agrv[3] = "\"dswei\""
* @return
*/
static int wpa_command(struct wpa_ctrl *ctrl, char *cmd)
{
int argc;
char buf[1024];
char *argv[CFG_MAXARGS];
strncpy(buf, cmd, 1024);
buf[1023] = '\0';
argc = parse_line(buf, argv);
return wpa_request(ctrl, argc, argv);
}
/*!
* @brief 从buf中获取指定行的数据
* @return 0:成功,-1:失败
*/
static int get_line_from_buf(int index, char *buf, char *line)
{
int i = 0;
int j;
int len;
int endcnt = -1;
char *linestart = buf;
if (buf == NULL || line == NULL)
return -1;
while (1)
{
/* 一行尾部 */
if (buf[i] == '\n' || buf[i] == '\r' || buf[i] == '\0')
{
endcnt++;
/* 找到对应行 */
if (index == endcnt)
{
len = &buf[i] - linestart;
strncpy(line, linestart, len);
line[len] = '\0';
return 0;
}
else
{
/* 找到下一行开头的数组下标 */
for (j = i + 1; buf[j]; )
{
if (buf[j] == '\n' || buf[j] == '\r')
j++;
else
break;
}
if (!buf[j])
return -1;
/* 更新 */
linestart = &buf[j];
i = j;
}
}
if (!buf[i])
return -1;
i++;
}
}
/*!
* @brief 标准输入初始化
* @return 0:成功,-1:失败
*/
static int StdinDevInit(void)
{
struct termios tTTYState;
//get the terminal state
tcgetattr(STDIN_FILENO, &tTTYState);
//turn off canonical mode
tTTYState.c_lflag &= ~ICANON;
//minimum of number input read.
tTTYState.c_cc[VMIN] = 1; /* 有一个数据时就立刻返回 */
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);
return 0;
}
/*!
* @brief 标准输入退出
* @return 0:成功,-1:失败
*/
static int StdinDevExit(void)
{
struct termios tTTYState;
//get the terminal state
tcgetattr(STDIN_FILENO, &tTTYState);
//turn on canonical mode
tTTYState.c_lflag |= ICANON;
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &tTTYState);
return 0;
}
/*!
* @brief 标准输入获取字符
* @return 0:成功,-1:失败
*/
static int StdinGetChar(int timeout)
{
/* 如果有数据就读取、处理、返回
* 如果没有数据, 立刻返回, 不等待
*/
/* select, poll 可以参数 UNIX环境高级编程 */
struct timeval tTV;
fd_set tFDs;
char c;
tTV.tv_sec = timeout;
tTV.tv_usec = 0;
FD_ZERO(&tFDs);
FD_SET(STDIN_FILENO, &tFDs); //STDIN_FILENO is 0
if (timeout == -1)
select(STDIN_FILENO+1, &tFDs, NULL, NULL, NULL);
else
select(STDIN_FILENO+1, &tFDs, NULL, NULL, &tTV);
if (FD_ISSET(STDIN_FILENO, &tFDs))
{
/* 处理数据 */
c = fgetc(stdin);
return c;
}
else
{
return 0;
}
}
/*!
* @brief 标准输入获取字符串
* @return 0:成功,-1:失败
*/
static int StdinGetString(char *buf, int timeout)
{
/* 如果获得了第1个字符,以后的字符没有时间限制 */
int c;
int i = 0;
c = StdinGetChar(timeout);
if (!c)
return -1;
if (c == '\n' || c == '\r')
return -1;
buf[i++] = c;
while ((c = StdinGetChar(-1)))
{
if (c == '\n' || c == '\r')
{
buf[i] = '\0';
return 0;
}
else
{
buf[i++] = c;
}
}
return 0;
}
/* wpa_cli status */
int main(int argc, char *argv[])
{
int ret = 0;
int i;
char line[1024];
char bssid[1024];
char freq[1024];
char signal[1024];
char flags[1024];
char ssid[1024];
char input[1024];
char cmd[1024];
char ret_buf_bak[1024];
int ap;
if (os_program_init())
return -1;
if (ctrl_ifname == NULL)
ctrl_ifname = wpa_cli_get_default_ifname();
if (wpa_cli_open_connection(ctrl_ifname, 0) < 0) {
fprintf(stderr, "Failed to connect to non-global "
"ctrl_ifname: %s error: %s\n",
ctrl_ifname, strerror(errno));
return -1;
}
StdinDevInit();
#if 0
ret = wpa_request(ctrl_conn, argc - optind,
&argv[optind]);
#endif
while (1)
{
/*!
* 查看当前是否已经连接了AP
*/
ret = wpa_command(ctrl_conn, "status");
if (ret)
continue;
/* 已经连接上某个AP */
if (strstr(s_ret_buf, "COMPLETED"))
{
/* 获取该AP的ssid */
get_line_from_buf(1, s_ret_buf, line);
sscanf(line + 5, "%s", ssid);
printf("\n********************** %s connected!\n", ssid);
/* 使用select_network会把其他的network禁止,需要重新开启 */
/* 运行命令 */
ret = wpa_command(ctrl_conn, "list_network");
if (ret)
continue;
strncpy(ret_buf_bak, s_ret_buf, 1024);
ret_buf_bak[1023] = '\0';
/* 取出每行的数据 */
for (i = 1; !get_line_from_buf(i, ret_buf_bak, line); i++)
{
/* 获取网络的ap */
sscanf(line, "%d", &ap);
/* 使能网络 */
sprintf(cmd, "enable_network %d", ap);
ret = wpa_command(ctrl_conn, cmd);
}
}
/*!
* 1、扫描AP: scan
*/
ret = wpa_command(ctrl_conn, "scan");
if (ret)
continue;
/*!
* 2、获得结果: scan_results
*/
ret = wpa_command(ctrl_conn, "scan_results");
if (ret)
continue;
/*!
* 3、打印出来
*/
/* 获取每行的数据 */
for (i = 1; !get_line_from_buf(i, s_ret_buf, line); i++)
{
/* 抽取每行数据的指定位 */
sscanf(line, "%s %s %s %s %s ", bssid, freq, signal, flags, ssid);
/* 打印数据 */
printf("%02d %-32s %s\n", i, ssid, flags);
}
printf("Q:Quit\n");
/*!
* 4、让用户选择某个AP
*/
printf("Select the AP to connect: ");
fflush(stdout);
/* 等待3秒让用户输入需要选择的网络 */
ret = StdinGetString(input, 3);
if (ret)
{
printf("\n");
continue;
}
if (input[0] == 'q' || input[0] == 'Q')
break;
/* 获取需要选择的网络的相关信息 */
sscanf(input, "%d", &ap);
get_line_from_buf(ap, s_ret_buf, line);
sscanf(line, "%s %s %s %s %s ", bssid, freq, signal, flags, ssid);
/* 创建一个network */
ret = wpa_command(ctrl_conn, "add_network");
if (ret)
continue;
sscanf(s_ret_buf, "%d", &ap);
/* 取出ap与ssid,组织成命令 */
sprintf(cmd, "set_network %d ssid \"%s\"", ap, ssid);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
/*!
* 5、让用户输入密码
*/
if (strstr(flags, "WPA"))
{
/* 网络为WPA */
printf("Enter password: ");
fflush(stdout);
/* 等待输入密码 */
ret = StdinGetString(input, -1);
if (ret)
continue;
/* 取出密码 */
sprintf(cmd, "set_network %d psk \"%s\"", ap, input);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
/* 选择使用网卡 */
sprintf(cmd, "select_network %d", ap);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
}
else if (strstr(flags, "WEP"))
{
/* 网络为WPA */
printf("Enter password: ");
fflush(stdout);
/* 等待输入密码 */
ret = StdinGetString(input, -1);
if (ret)
continue;
/* 设置网络的key_mgmt */
sprintf(cmd, "set_network %d key_mgmt NONE", ap);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
/* 取出密码 */
sprintf(cmd, "set_network %d wep_key0 \"%s\"", ap, input);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
/* 设置默认密码 */
sprintf(cmd, "set_network %d wep_tx_keyidx 0", ap);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
/* 选择使用网卡 */
sprintf(cmd, "select_network %d", ap);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
}
else
{
/* 网络为OPEN */
/* 设置网络的key_mgmt */
sprintf(cmd, "set_network %d key_mgmt NONE", ap);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
/* 选择使用网卡 */
sprintf(cmd, "select_network %d", ap);
/* 运行命令 */
ret = wpa_command(ctrl_conn, cmd);
if (ret)
continue;
}
/*!
* 把密码等信息保存起来: save_config
* 前提是配置文件里面有"update_config=1"
*/
ret = wpa_command(ctrl_conn, "save_config");
if (ret)
continue;
}
StdinDevExit();
os_free(ctrl_ifname);
wpa_cli_cleanup();
return ret;
}
三、编译与运行
1、编译
- 把修改后的
wpa_cli.c
文件上传到虚拟机中已经解压好的wpa_supplicant-2.0
库中,替换已有的文件。 - 执行
make
,后执行make install
,最终在tmp\usr\local\sbin
目录下得到wpa_cli
可执行文件 - 执行
cp wpa_cli mywpa_cli
,把mywpa_cli
可执行文件上传到开发板根文件系统的sbin
目录下
2、运行
在开发板根文件系统下,执行mywpa_cli
命令
一开始:程序会不断扫描附近的WIFI并打印出来。
当输入需要连接的网络的ap值后,程序会无限期等待输入完毕。若此时连接的网络需要认证,则等待输入密码。
等待几秒后,屏幕输出的信息中提示网络已连接成功,并会把配置信息存储到对应的配置文件中(前提:在配置文件中包含update_config=1
,程序才可以自动保存配置信息)