前言:想法產生
這個想法是在調試樹莓派的時候發生的,因爲樹莓派作爲嵌入式的這麼一個平臺,尤其在我們安裝上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路徑即可。