本項目須要實現以嵌入式Web服務器爲核心的視頻監控系統。css
攝像頭採集的到的圖像通過壓縮後,傳到內置的web服務器中。用戶只須要經過瀏覽器就能夠觀看攝像頭採集到的數據。html
除了視頻監控功能外,本項目還能夠蒐集空氣溼度、空氣溫度、光照強度等信息。前端
該項目基本能夠看做B/S架構,由有三部分組成:java
(1)核心服務端:實際上是客戶端(嚴格意義上說,PC上的瀏覽器纔是客戶端)一臺普通的PC機,須要與前端數據中心在同一局域網中,而後經過瀏覽器對系統進行監控和設置。linux
(2)前端數據中心:FS4412開發平臺,能夠鏈接攝像頭、GPRS、zigBee、傳感器、WIFI等模塊。ios
(3)遠程控制終端:zigBee模塊、攝像頭等用來進行信息採集的設備。採集到的信息有前端數據中心進行處理。web
該遠程監控系統支持chrome瀏覽器和Firefox瀏覽器,但不支持IE瀏覽器。sql
(1)環境搭建chrome
(2)編譯源碼數據庫
(3)鏡像燒寫
(4)前端數據中心數據接收與處理模塊的調試
(5)理解Web服務器的搭建與配置
(6)理解CGI程序的編譯調試
僅需在VMware Workstation Pro打開打開虛擬機便可
編譯源碼的環境是在Linux下,首先應先熟悉Linux終端的一些操做。
cd 進入一個目錄 如cd /usr/local 或者是cd usr,用cd ..返回上一層。
利用tab來補全命令。
Mkdir 是一個用來在 Linux 系統下建立目錄的命令。是一層一層的建立,能夠建立後用cd打開而後建立。
cp命令用來將一個或多個源文件或者目錄複製到指定的目的文件或目錄。如cp/xx/xx ./是將文件拷貝到當前目錄下。利用這兩條指令以及cd指令拷貝源碼。
編譯源碼須要修改環境變量,BootLoader編譯,Linux內核編譯,主應用程序編譯,根文件系統鏡像製做。
①完成bootloader的編譯與SD bootloader的編譯
②Linux內核的編譯
下面是根據指導書進入Linux內核配置圖形界面。
下面是用make工具完成zImage內核二進制文件的截圖
等待片刻以後,linux內核編譯完成
③ 主應用程序編譯
直接用make工具便可
④ 根文件系統鏡像製做
在製做根文件時須要將以前編譯好的應用程序、驅動等項目拷貝到rootfs下。
①製做SD卡啓動盤
用讀卡器將SD卡插入電腦,虛擬機識別SD讀卡器,將uboot燒寫到sd卡,將SD卡插入開發板SD卡槽內,撥碼至1000,鏈接開發板。在win上設置串口調試工具,選擇Serial鏈接方式(若是不選擇的話直接open會致使亂碼,其中COMX由設備管理器看)。啓動開發板,在倒計時5s結束前,按任意鍵中止。
②安裝Fastboot
在安卓手機中fastboot是一種比recovery更底層的刷機模式。就是使用USB數據線鏈接手機的一種刷機模式。相對於某些系統(如ios)卡刷來講,線刷更可靠,安全。
遇到問題:fastboot驅動安裝的強制簽名問題
解決方案:
經過配置win10關閉驅動簽名(百度上輕鬆找到解決方案)
在計算機屬性下的系統屬性,查看高級,選擇環境變量,編譯系統變量的path項,在變量值最後添加D:\Fastboot.使用win+r,輸入cmd,輸入fastboot測試是否安裝成功。
在設備管理器中是否有Android,有則更新驅動,路徑選擇D:\Fastboot。
③燒寫到開發板的Flash
硬件鏈接後撥動至1000 ,設置串口工具後,啓動開發板,按任意鍵暫停,輸入sdfuse flashall。等燒寫結束,關閉開發板,撥至0110,啓動開發板,暫停。輸入fdisk -c 0
對SD卡分區,輸入fastboot,再執行flash-all.bat。
在cmd命令行中輸入fastboot flash BootLoader u-boot-fs4412.bin完成燒寫BootLoader。
在cmd命令行中輸入fastboot flash kernel zimage完成燒寫內核鏡像zImage。
在cmd命令行中輸入fastboot flash system system.img完成燒寫根文件系統鏡像。
①WIFI模塊的移植——內核配置
配置內核
編譯內核
②WIFI模塊——wpa_suppliant的移植
openssl補丁安裝
Openssl安裝
注意權限問題——最後應該輸入sudo make install
wpa_suppliant的移植——修改Makefile
使用make工具編譯
==遇到問題==:不知道如何測試,文檔中沒有明確說明如何操做
後咱們選擇使用網線直連,其中若是須要修改網卡IP,因爲是直連,輸入ifconfig eth0 192.168.x.x
③Web服務器的配置
==遇到問題==:
解決方法
按照指導書修改過compat.h
Make工具進行編譯
我本身有過Web編程的經驗,也瞭解早期的Web編程是靠CGI來完成的,用的語言也是五花八門——C/C++、perl、bash……
我選擇研究該項目的CGI源碼,也是由於本身對Web編程有一些瞭解,而對系統編程就徹底不懂,也搞不清什麼線程、鎖、同步的概念。
①登陸表單處理——login.cgi
咱們進入文件系統rootfs的www目錄下,這裏就是存放web服務器html,css,js和cgi程序的地方。咱們先看看index.html.
咱們直接看登陸的表單部分。
<form name="form1" method="post" action="cgi-bin/login.cgi"> <table width="100%" border="0" cellspacing="9" cellpadding="0"> <tbody> <tr> <td width="92">用戶賬號:</td> <td width="130"><label> <input name="username" type="text" id="username" value="user"></label></td> </tr> <tr> <td>登陸密碼:</td> <td> <label> <input name="password" type="password" id="password" value="123456"> </label> </td> </tr> <tr> <td height="25"></td> <td><input type="image" name="submit" style="width:97px;height:25px;" src="images/login/go.gif"></td> </tr> </tbody> </table> </form>
這個表單的數據會提交給login.cgi這個程序去處理。學過java servlet的同窗會發現,CGI程序和servlet比較接近。
咱們來分析一下login.cgi的源碼。
//login.c cgiFormStringNoNewlines("username", name, N); cgiFormStringNoNewlines("password", pw, N);
這兩條語句將username和password分別放到char數組name和pw中,接下來確定是要到數據庫裏面去查詢了。這裏的數據庫用的是sqlite,很是輕量級的一個數據庫。
//login.c if(sqlite3_open("/user.db", &db) != SQLITE_OK) { fprintf(cgiOut, "<BODY>"); fprintf(cgiOut, "<H1>%s</H1>", "Server is busy..."); fprintf(cgiOut, "<meta http-equiv=\"refresh\" content=\"1;url=../index.html\">"); return -1; } sprintf(sql, "select * from usr where name='%s' and password='%s'", name, pw); if(sqlite3_get_table(db, sql, &result, &row, &column, NULL) != SQLITE_OK) { fprintf(cgiOut, "<BODY>"); fprintf(cgiOut, "<H1>%s</H1>", "Server is busy..."); fprintf(cgiOut, "<meta http-equiv=\"refresh\" content=\"1;url=../index.html\">"); sqlite3_close(db); return -1; }
很是容易理解,sqlite3_open()
函數用來打開數據user.db,若是打開失敗就會看到網頁上有Server is busy...字樣。
char數組sql中存放了一條select語句,sqlite3_get_table()
函數就是用來查詢的,若是數據庫出現問題,依然在網頁上顯示有Server is busy...
若是在數據庫裏找不到對應的用戶,變量row的值就是0,網頁上顯示Name or password error。
//login.c if(row == 0) { fprintf(cgiOut, "<BODY>"); fprintf(cgiOut, "<H1>%s</H1>", "Name or password error"); fprintf(cgiOut, "<meta http-equiv=\"refresh\" content=\"1;url=../index.html\">"); sqlite3_close(db); return 0; }
查看user.db數據庫,只有一條記錄
代碼裏的cgiOut是什麼呢?,咱們在cgic.h頭文件的實現文件cgic.h中找到了答案。
//cgic.c cgiIn = stdin; cgiOut = stdout;
cgiOut就是標準輸出。登陸成功後,會跳轉到主頁面main.html。
②環境信息獲取——env_1_a9_info.cgi
main.html頁面是一個frameset框架,由left.html,top.html,right.html三個頁面構成。其中只有left.html導航欄具備實際的功能選項。
咱們先看看環境信息env1.html頁面中的cgi程序
<iframe frameborder="0" border=0 scrolling="no" src="cgi-bin/env_1_a9_info.cgi" width="100%" height="100%"></iframe>
這個iframe告訴咱們env_1_a9_info.cgi程序會處理當前的實時數據,並返回結果。分析一下env_1_a9_info.cgi的源碼,來看看這些數據是怎麼獲得的。
get_env()函數是用來獲取環境信息的。
//env_1_a9_info.c void get_env() { sqlite3 *db; char sql1[N] = {0}, sql2[N] = {0}; char **result1, **result2; int row1, colunm1, row2, colunm2; if (sqlite3_open("/smartfarm.db", &db) != SQLITE_OK) { fprintf(cgiOut, "<H2>sqlit smartfarm.db open err</H2>"); errflag = 1; return ; } sprintf(sql1, "select * from env where farm_no=1"); if (sqlite3_get_table(db, sql1, &result1, &row1, &colunm1, NULL) != SQLITE_OK) { fprintf(cgiOut, "<H2>sqlite3_get_table err</H2>"); errflag = 1; return ; } strncpy(temp_max, result1[colunm1 + 1], 9); strncpy(temp_min, result1[colunm1 + 2], 9); strncpy(hum_max, result1[colunm1 + 3], 9); strncpy(hum_min, result1[colunm1 + 4], 9); strncpy(light_max, result1[colunm1 +5], 9); strncpy(light_min, result1[colunm1 + 6], 9); ....... sqlite3_close(db); }
不難發現,這些數據都是從數據庫smartfarm.db中獲取的。
最高/最低氣溫,溼度,光照,都在env表中。
還有一張collect_env表,顯示的是實時信息,可是好像沒有用到。
//env_1_a9_info.c void get_env() { ........ sprintf(sql2, "select * from collect_env where farm_no=-1"); if (sqlite3_get_table(db, sql2, &result2, &row2, &colunm2, NULL) != SQLITE_OK) { errflag = 1; return ; } #if 0 strncpy(temp, result2[colunm2 + 1], 9); strncpy(hum, result2[colunm2 + 2], 9); strncpy(light, result2[colunm2 + 3], 9); #endif }
很明顯,temp,hum,light分別存放當前的溫度、溼度和光照,可是被註釋掉了。
④實時監控的圖像拍照處理——take_photo.cgi
實現監控的CGI程序是take_photo.cgi,咱們仍是直接分析它的源代碼。.
拋開cgi程序的fprintf不看,咱們直接看核心處理的部分。
//take_photo.c key_t key; int msgid; char buf[2] = {0}; if((key = ftok("/lib", 'a')) < 0) { perror("ftok"); exit(1); } if ((msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666)) < 0) { if (errno == EEXIST) { msgid = msgget(key, 0666); }else{ perror("msgget msgid"); return 0; } } struct message msgbuf; msgbuf.type = 1L; msgbuf.msg_type = 2L; cgiFormString("mode", buf, 2); if(buf[0] <= '0' || buf[0] > '9') goto err; msgbuf.text.camera = buf[0] - '0'; msgsnd (msgid, &msgbuf, sizeof (msgbuf) - sizeof (long), 0);
這是一段linux的進程間通訊,咱們須要關注如下三個系統調用:
- key_t ftok(const char *pathname, int proj_id)
- int msgget(key_t key, int msgflg)
- int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg)
這些調用的細節不去管它,只要知道該進程須要收發其餘進程的消息就能夠了。
咱們關注這段代碼,看看進程發送的消息msgbuf是什麼。
//take_photo.c struct message msgbuf; msgbuf.type = 1L; msgbuf.msg_type = 2L; cgiFormString("mode", buf, 2); if(buf[0] <= '0' || buf[0] > '9') goto err; msgbuf.text.camera = buf[0] - '0';
注意到這裏的cgiFormStirng函數,咱們的buf存的是表單中收集到的數字,下面是表單的內容。
<form id="take_photo" name="take_photo" method="post" action="cgi-bin/take_photo.cgi"> <td width="15%"><input type="radio" name="mode" id="mode_1" value="1" />1</td> <td width="15%"><input type="radio" name="mode" id="mode_2" value="3" />3</td> <td width="15%"><input type="radio" name="mode" id="mode_3" value="5" />5</td> <td width="15%"><input type="radio" name="mode" id="mode_4" value="7" />7</td> <td width="15%"><input type="radio" name="mode" id="mode_5" value="9" />9</td> <td width="25%"><input type="submit" name="take_photo" id="take_photo" value="圖像抓拍" /></td> </form>
結構體message在struct.h頭文件中有定義。
//struct.h union text { unsigned char led; unsigned char buzzer; unsigned char camera; unsigned char fan; unsigned char relay; unsigned char uart; char phone[24]; struct control_parameter parameter; }; struct message { long type; long msg_type; union text text; };
那麼誰會接收msgbuf這個消息呢?咱們能夠在項目源碼中找到答案。
看一下,項目源碼裏面pthread_client_request.c中的一個代碼片斷。
//pthread_client_request.c switch (msgbuf.msg_type) { ........ case CAMERA: printf("received camera request\n"); pthread_mutex_lock (&mutex_camera); dev_camera_mask = msgbuf.text.camera; pthread_cond_signal (&cond_camera); pthread_mutex_unlock (&mutex_camera); break; ........ default: #if DEBUG printf("pthread client request default break\n"); #endif break; }
也就是說會 有專門的線程用來處理攝像頭有關的消息。
咱們還記得以前take_photo.c有這樣一段代碼。
struct message msgbuf; msgbuf.type = 1L; msgbuf.msg_type = 2L;
1L、2L這樣的魔數是什麼意思呢
答案在項目源碼的global_variable.h頭文件中。
//global_variable.h /*message queue type */ #define REQUEST 1L /*message queue msg_type*/ #define BUZZER 1L #define CAMERA 2L #define FAN 3L #define LED 4L #define RELAY 5L #define SET_PARAMETER 6L #define SMS 7L //#define SQLITE 7L #define UART 8L #define MODIFY_PHONE_NUM 9L #define BEEP 10L
linux下一切都是文件,打開攝像頭也不例外。
//pthread_camera.c if ((dev_camera_fd=open (DEV_CAMERA, O_RDWR | O_NOCTTY | O_NDELAY)) < 0) { printf ("Cann't open file /tmp/webcam\n"); exit (-1); }
其中DEV_CAMERA這個常量的定義在頭文件global_variable.h中也能找到。
//global_variable.h #define DEV_GPRS "/dev/ttyUSB1" #define DEV_GPRS_2 "/dev/ttyUSB2" #define DEV_ZIGBEE "/dev/ttyUSB0" #define DEV_LED "/dev/led" #define DEV_BUZZER "/dev/buzzer" #define DEV_CAMERA "/tmp/webcam" #define SQLITE_OPEN "./smartfarm.db"
這個常量就是/tmp/webcam,就是攝像頭設備。
④照片展現——show_photo.cgi
在「歷史照片」這一功能中,調用的CGI程序是show_photo.cgi
咱們一樣仍是忽略掉CGI源碼中的fprintf部分。
#define DIRNAME "../pice" #define PHOTO_NUM_MAX 100 // 最多100張,0-99 int cgiMain() { ......... if((dir = opendir(DIRNAME)) == NULL) //打開圖片存放的目錄 { perror("fail to opendir"); exit(1); } while((dirp = readdir(dir)) != NULL) //讀文件夾裏文件的名字 { if(dirp->d_name[0] > '9' || dirp->d_name[0] < '0') //名字的第一個字母 continue; sprintf(photoname[syncmsg1.photo_num++], "%s", dirp->d_name); if(syncmsg1.photo_num >= PHOTO_NUM_MAX) //判斷是否超過最大值 { syncmsg1.photo_num = PHOTO_NUM_MAX - 1; break; } } ......... }
咱們從中能夠知道,歷史照片都是保存在/www/pice目錄下的,並且最多隻保存100張。
本來這個系統是經過WIFI來訪問並進行控制的,可是咱們的WIFI模塊出現了問題,如今只能用網線直連的方式控制系統。
咱們將手機熱點配置爲my_accent,密碼設爲012345678,在rootfs/etc中添加配置文件wpa-spk-tkip.conf。
# WPA-PSK/TKIP ctrl_interface=/var/run/wpa_supplicant network={ ssid="my_accent" key_mgmt=WPA-PSK proto=WPA pairwise=CCMP group=CCMP psk="012345678" }
更新文件系統之後從新燒寫鏡像,配置IP和網關後,執行命令wpa_supplicant -B -i wlan0 -c /etc/wpa-psk-tkip.conf
咱們能夠看到,wlan網卡依然沒有鏈接到手機熱點上。咱們暫時沒有解決這個問題。
咱們不管直接使用含有項目內容的文件系統,仍是本身從新編譯mjpeg-streamer都出現了一樣的問題。
咱們啓動開發板後,對mjpeg-streamer進行測試。
運行mjpg_streamer -i "/mjpg/input_uvc.so -y -d /dev/video0" -o "/mjpg/output_http.so -w 192.168.9.111:8080"
系統顯示因爲設備被佔用了,初始化失敗。咱們沒有找到解決的方法。
這些問題目前依然沒有找到解決方案。
本項目特點在於與物聯網內容相關聯,拓寬了學生的眼界,讓咱們更好的學習Linux指令、物聯網相關知識、嵌入式系統調試等不少本不屬於咱們專業的知識,對於三個代碼和嵌入式基礎薄弱的理科專業學生來講是一個大量接觸相關知識的好機會。課題特點的地方在於充分結合嵌入式系統調試,可讓咱們給硬件嵌入Linux內核以方便咱們進行硬件控制。