前言:想法产生
这个想法是在调试树莓派的时候发生的,因为树莓派作为嵌入式的这么一个平台,尤其在我们安装上ubuntu-mate这一类带桌面系统之后,我们仍需要连接一块屏幕来配置它的网络,很麻烦,所以就想着用ap热点什么的可以直接配网该多好。所有人思维应该是利用ap热点来控制WiFi的连接,当然这也是正常思维才对。但是树莓派这么一个小东西,它的底层简直太脆弱了,底层配置错误轻则无法联网,重则连机都开不了。而ap这么一个重量型工具,需要我们修改更多的底层来配合它完成联网,很无耐。所以在这之后就在想,我们可不可以在应用层通过串口传输账号密码、shell脚本控制来完成联网,我们就可以不用每次换场地联网都要连接一个邋遢的桌面。
一、功能概述
1、硬件连接:通过两个转串模块将pc和树莓派连接起来即可,最好在树莓派端不要连接其他同种类型芯片usb模块,因为我们会指定某一端口来作为接收串口数据端口。如果实在需要,那么下一篇文章我会讲解给Ubuntu增设串口别名的方法。
2、控制流程:Ubuntu端首先启动shell脚本,用于等待接收串口数据;windows端的串口助手,发送账号和密码(空格作为标识符,所以wifi账号和密码都不能出现空格),为了避免串口刚开始接收数据不稳定,所以我们需要手动发送两次数据,连接开始。
二、注意事项
由于大家在看教程的时候只看教程前面的资料,到了代码这里直接把代码一复制就完事不再往下看了,我也这样,所以我把注意事项写在这里。
1、此代码只是提供学习使用,并不完善,甚至显得很蹩脚,虽然完成日常事项已经足够,但我确实是用了最笨的实现方法,好多脚本读取文件问题我并没有深究,所以等会儿你会发现我会有三个文本文档,其实一个足够,欢迎大家完善后交流学习。
2、现在代码的报错机制还不完善,比如wifi不在区域内无报错,没有密码错误报错,但是如果一直没有连接上wifi,一定是这两种错误其中之一。所以为了能准确无误连接wifi,首先用手机测试一下有没有此wifi,密码对不对。
3、空格为账号和密码标识符、\r\n为结尾标识符,咱们账号密码不要出现空格和\r\n。
4、我给的文件里文本文档不要删
5、开机之前首先把硬件连接好
6、shell脚本没有循环,连接一次不管成功与否,程序都已执行完
现在就想到这些,后期再补
三、代码
提醒大家一下,最后有流程,按照流程来你会成功的
1、c++读取串口数据(serial1.cpp)
#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <cstring>
using namespace std;
int Error_time=0;//用于记录错误次数
int i,i1;
int rec_time=0;//用于记录接收次数
int acc_cri=1;//允许死循环标志位
int rr_num=0;//用于接收来自串口的数据
FILE* NAME1;//分别为用户名和密码
FILE* PASSWORD1;
FILE* ERROR1;
int main(int argc, char **argv)
{
int fd,flag,wr_num=0;
struct termios options, newstate;
speed_t baud_rate_i,baud_rate_o;
unsigned char buf[40]="Connecting...\r\n\r\n"; //向串口发送的数组
unsigned char buf1[100];
unsigned char buf2[20];
unsigned char buf3[20]="OK.....\r\n\r\n";
unsigned char error[40]="Error:case 1\r\n\r\n";
fd=open("/dev/ttyUSB0", O_RDWR|O_NONBLOCK|O_NOCTTY|O_NDELAY); //打开串口
if(fd==-1)
printf("can not open the COM1!\n");
else
printf("open COM1 ok!\n");
/*判断是否是终端设备
if(isatty(STDIN_FILENO) == 0)
printf("不是终端设备\n");
else
printf("是终端设备\n");
*/
if( fcntl(fd, F_SETFL, 0) <0 ) //改为阻塞模式
printf("fcntl failed\n");
else
printf("fcntl=%d\n", fcntl(fd, F_SETFL, 0));
tcgetattr(fd, &options);
//设置波特率
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
//获取波特率
tcgetattr(fd, &newstate);
baud_rate_i=cfgetispeed(&newstate);
baud_rate_o=cfgetospeed(&newstate);
//串口设置
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB;//设置无奇偶校验位,
options.c_cflag &= ~CSTOPB; //设置停止位1
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8; //设置数据位
options.c_cc[VTIME]=0;//阻塞模式的设置
options.c_cc[VMIN]=1;
//激活新配置
tcsetattr(fd, TCSANOW, &options);
//输出波特率
printf("输入波特率为%d,输出波特率为%d\n" , baud_rate_i, baud_rate_o);
//写入串口
wr_num=write(fd,buf3,sizeof(buf3));
while(acc_cri)
{
int WENJIAN_Flag=0;
rr_num=read(fd,buf1,sizeof(buf1));
//printf("%d\r\n",rr_num);
if(rr_num>1)
{
rec_time++;
NAME1 = fopen("NAME.txt", "w");//打开姓名和密码文件
PASSWORD1 = fopen("PASSWORD.txt", "w");
for(i=0;i<rr_num;i++)//循环读取并储存在数组
{
//printf("%c\r\n",buf1[i]);
printf("外:%d\r\n",WENJIAN_Flag);
if(32 == buf1[i])//接收到空格
{
//printf("iiiii\r\n");
WENJIAN_Flag+=1;
printf("内:%d\r\n",WENJIAN_Flag);
continue;
}
if(WENJIAN_Flag==0)fprintf(NAME1, "%c", buf1[i]);//未接收到空格的动作
else if(WENJIAN_Flag==1)//接收到空格的动作
{
fprintf(PASSWORD1, "%c", buf1[i]);
}
else //接收到两次空格
{
rec_time=3;
Error_time=1;
}
}
fclose(NAME1);//关闭
fclose(PASSWORD1);
if(rec_time>=2)
{
acc_cri=0;//接收2次程序结束
if(0==Error_time)
{
printf("程序正常结束储存完毕\r\n");
write(fd,buf,sizeof(buf));
}
if(1==Error_time)
{
printf("Error 1\r\n"); //接收到两次空格报错
write(fd,error,sizeof(error));
ERROR1 = fopen("ERROR.txt", "w");//用于储存错误
fprintf(ERROR1, "%c", buf2[0]);
fclose(ERROR1);
}
}
}
}
return 0;
}
2、shell 脚本连接无线网主体代码(autoaccwifi.sh)
#!/bin/bash
cd ~/test
./serial1
test="NAME.txt"
test1="ERROR.txt"
time=0;
flag=1;
#若文档为空,则在while循环内等待实际上这样的话,程序已经死了
while [ -e $test -a ! -s $test ]
do
sleep 1
echo "wait...$time"
let time++
#echo $time
done
if [ -e $test1 -a ! -s $test1 ];then
flag=0
fi
sed -i '1d' ERROR.txt
echo "已删除错误标志位"
echo $flag
echo "正在接收..."
#此处由于串口最初接收数据不稳定,延时3s接收
sleep 3
for name in `cat NAME.txt`
do
echo $name
done
for password in `cat PASSWORD.txt`
do
echo $password
done
echo "接收完毕"
#获取帐号和密码
if (( $flag==0 ));then
nmcli d wifi connect $name password $password
fi
#删除帐号和密码
sed -i '1d' NAME.txt
sed -i '1d' PASSWORD.txt
echo "已删除账户和密码"
#获取IP地址并以文本形式保存到本地
if (( $flag==0 ));then
IP=`ifconfig | grep "inet addr" | grep -v 127.0.0.1 | awk '{print}'`
echo ${IP}
sh -c "echo ${IP} > autogainip"
fi
./trx
sed -i '1d' autogainip
echo "已删除储存的ip"
3、c++发送当前ip到windows端(trx.cpp)
#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <cstring>
#include <fstream>
#include <cassert>
using namespace std;
int i;
int spa_time=0;//用于记录space
int trx_time=0;//用于记录
int rec_time=0;//用于记录接收次数
int acc_cri=1;//允许死循环标志位
int rr_num=0;//用于接收来自串口的数据
int main(int argc, char **argv)
{
int fd,flag,wr_num=0;
struct termios options, newstate;
speed_t baud_rate_i,baud_rate_o;
unsigned char buf[40]={}; //向串口发送的数组
unsigned char buf1[100];
fd=open("/dev/ttyUSB0", O_RDWR|O_NONBLOCK|O_NOCTTY|O_NDELAY); //打开串口
if(fd==-1)
printf("can not open the COM1!\n");
else
printf("open COM1 ok!\n");
/*判断是否是终端设备
if(isatty(STDIN_FILENO) == 0)
printf("不是终端设备\n");
else
printf("是终端设备\n");
*/
if( fcntl(fd, F_SETFL, 0) <0 ) //改为阻塞模式
printf("fcntl failed\n");
else
printf("fcntl=%d\n", fcntl(fd, F_SETFL, 0));
tcgetattr(fd, &options);
//设置波特率
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
//获取波特率
tcgetattr(fd, &newstate);
baud_rate_i=cfgetispeed(&newstate);
baud_rate_o=cfgetospeed(&newstate);
//串口设置
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB;//设置无奇偶校验位,N
options.c_cflag &= ~CSTOPB; //设置停止位1
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8; //设置数据位
options.c_cc[VTIME]=0;//阻塞模式的设置
options.c_cc[VMIN]=1;
//激活新配置
tcsetattr(fd, TCSANOW, &options);
//输出波特率
printf("输入波特率为%d,输出波特率为%d\n" , baud_rate_i, baud_rate_o);
while(acc_cri)
{
i=0;
//readTxt("autogainip");
ifstream infile;
string file="autogainip";
infile.open(file.data()); //将文件流对象与文件连接起来
assert(infile.is_open()); //若失败,则输出错误消息,并终止程序运行
char c;
infile >> noskipws;
while(acc_cri)
{
while (!infile.eof())
{
infile>>c;
if(c==32)spa_time++;
if(spa_time<2)
{
//cout<<c;
buf[i]=c;
//cout<<buf[i];
if(105 == buf[0])acc_cri=0;
i++;
}
//for(i=0;i<wr_num;i++)
}
}
wr_num=write(fd,buf,sizeof(buf));
//for(i=0;i<)
cout<<endl;
infile.close(); //关闭文件输入流
return 0;
}
}
四、配置流程
1、在home创建一个test文件夹
2、进入test,创建储存代码文件。shell脚本命名为autoaccwifi.sh,c++接收串口代码命名为serial1.cpp,c++发送代码命名为trx.cpp。其实命名是依据你自己的喜好来的,但实际上是牵一发动全身,你熟悉这个代码之后随意更改。
3、创建如图所示文本,拓展名不可随意更改,你熟悉了之后可以在代码中随意配置
4、编译c++代码,g++ serial1.cpp -o serial1和g++ trx.cpp -o trx
5、测试:硬件连接上,终端进入test,执行./autoaccwifi.sh;windows端串口助手发送两次账号和密码,账号和密码以空格为间距。
6、加入到开机选项。我是这样想的,既然我们打算是在应用层实现功能,那我们就不要去打扰底层了,所以开机选项我们这样添加。首先安装gnome:sudo apt install gnome-session-bin;然后启动:gnome-session-properties,添加autoaccwifi.sh路径即可。
学习更多嵌入式Linux、Qt以及嵌入式单片机知识关注公众号“爱玩嵌入式”: