第三阶段应用层——2.8 视频监控—开发板上WIFI网卡的使用(3)-仿手机功能写WIFI程序

视频监控—开发板上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)循环体内实现如下:

  1. 扫描附近的WIFI
  2. 把获得的结果,通过串口打印出来
  3. 等待3s,让用户输入需要连接的网络,若3秒内输入,则程序等待用户输入网络的ap值否则跳到下一次循环
  4. 获取需要连接的网络的相关信息
  5. 根据连接网络的认证方式,进行不同的认证操作
  6. 存储已经连接网络的配置信息

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命令时,对于不同的操作命令,都会得到多行的输出信息,需要根据输出信息提取每行需要的输出信息

对于输出信息的存储,其存储在一个一维数组中,并不是按照多行存储在二维数组

  1. 提取时需要判断是否为一行的尾部,并记录下此时行数endcnt,并且更新linestart指针的位置,使其指向下一行的开头
  2. 记录的行数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 提取有效网络信息

  1. 调用wpa_command()函数获取到的信息与信息长度存储静态全局变量中s_ret_buf[2048]s_ret_len
  2. 调用get_line_from_buf()函数,把得到的指定行的信息存储在line[1024]
  3. 通过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 组织与运行命令

  1. 对于具体的操作命令,需要通过sprintf()函数,把命令所需的各部分组织成一个字符串存储在cmd[1024]数组中;
  2. 调用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、编译

  1. 把修改后的wpa_cli.c文件上传到虚拟机中已经解压好的wpa_supplicant-2.0库中,替换已有的文件。
  2. 执行make,后执行make install,最终在tmp\usr\local\sbin目录下得到wpa_cli可执行文件
  3. 执行cp wpa_cli mywpa_cli,把mywpa_cli可执行文件上传到开发板根文件系统的sbin目录下

2、运行

在开发板根文件系统下,执行mywpa_cli命令

一开始:程序会不断扫描附近的WIFI并打印出来
在这里插入图片描述
输入需要连接的网络的ap值后,程序会无限期等待输入完毕。若此时连接的网络需要认证,则等待输入密码
在这里插入图片描述
等待几秒后,屏幕输出的信息中提示网络已连接成功,并会把配置信息存储到对应的配置文件中(前提:在配置文件中包含update_config=1,程序才可以自动保存配置信息
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_42813232/article/details/107822670