※已刊登在“無線電”06月刊上手把手教你如何實現自動固件更新 – 服務器篇
作者:常席正,張博
常見的嵌入式設備的固件更新有兩種方式:上位機工具更新和HTTP嵌入式網頁更新但兩種方式都無法批量更新,都需要用戶手動操作,如果用戶有大量模塊需要更新固件,絕不可能像“把大象放進冰箱”那樣三步就可以解決問題,而且很多廠商的固件因為保密的問題是不開放給客戶的。即使所有問題都不是問題,而用戶更新失敗以及更新錯誤的固件,所造成設備變“磚”的風險也是設備開發者不得不考慮的。
下面給大家介紹一種嵌入式設備批量實現固件更新的方法—-通過固件服務器自動更新。只需要有一臺云服務器,一些HTML / PHP和數據庫方面的知識,然后再在模塊里植入HTTP客戶端固件更新的相關代碼。之后用戶只需要將模塊聯網,固件更新就能自動完成。
本人思路如圖1所示。
首先,模塊需要給客戶開放一個可選配置項,即是否允許模塊自動進行固件更新操作。
然后,當模塊自動進行固件更新被允許時,模塊作為HTTP客戶端通過HTTP POST的方式將驗證信息(一般為模塊的MAC地址)以JSON的格式發送至云服務器。
接著,服務器解析JSON以獲取模塊的MAC地址,跟數據庫中預先保存的MAC地址列表進行對比驗證。如果驗證通過,服務器將通過HTTP POST回復關于最新固件版本的必要信息(包括固件版本號,下載路徑,文件長度,文件校驗HASH值)發給模塊;如果驗證通過,但服務器端出現異常,則服務器給模塊報錯;如果驗證不通過,服務器告訴模塊“該設備未注冊”。
最后,模塊通過解析服務器回復的JSON格式的報文,獲取最新固件版本的信息,并與自身固件版本作比對,如果設備本身固件不是最新固件,則進一步完成下載最新固件并完成更新。
圖1自動固件更新流程
通過以上流程描述可知,實現這套方案同時需要服務器端和嵌入式模塊兩部分的配合,這篇先給大家教一下服務器端需要做哪些工作。
網站服務器環境搭建和網站的建立
首先,需要擁有一個域名和一臺云服務器。關于域名的解析,備案等大家可以自行了解。國內做云服務器的廠商眾多,易邁云,阿里云,騰訊云等等,我們要實現的這個功能非常簡單,對服務器開銷不大,根據實際需要選一款即可。我用的是阿里云服務器Windows Sever 2008標準版SP2 32位中文版,配置是1核2G內存2Mbps帶寬掛載40G的系統盤和40G的數據盤。
搭建服務器主要包括3個核心環境,即Web服務器軟件(常用的有IIS服務器和Apache等),PHP和數據庫(常用的有MySQL和SQL Server)。由于這3種核心的軟件更新很快,版本眾多,且相互之間有版本要求的限制,因此推薦使用建站集成軟件包XAMPP.XAMPP集成了Apache的+ PHP + MySQL的,簡單實用,配置靈活,非常利于快速實現一些簡單的服務器功能.XAMPP軟件界面如圖2所示。
圖2 XAMPP軟件界面
XAMPP軟件的安裝很簡單,需要提醒大家的是服務器需要提前安裝最新VC運行庫,否則會導致安裝失敗。安裝好后如圖2所示安裝的Apache和MySQL的服務模塊(綠色√),分別點擊“開始“按鈕啟動Apache Web服務器和MySQL。
網絡服務器搭建完畢,域名也完成了解析和備案,就可以通過瀏覽器訪問到用戶的默認網站。接下來要做的需要將自動固件更新服務器端的一整套代碼,包括HTML和PHP文件替換原有的默認網頁,服務器端環境和網站就搭建完畢了。下面進行詳細說明。
關系數據庫搭建
固件的所有關鍵信息都是通過數據庫保存的,PHP文件只是用于對數據庫進行必要操作腳本,因此完善,清晰的數據庫內容和結構對于后續的PHP代碼編寫非常重要。接下來我們需要先完成數據庫的搭建。
點擊MySQL的模塊同行的“管理”按鈕,進入數據庫。
如下表1所示,新建一個數據庫“fw_update”,排序規則為utf8_general_ci。該數據庫下新建3個數據表,分別是“fw_list”,“device_list”以及“DEVICE_TYPE”。
device_type:設備類型數據表,廠商的產品可以有多種型號(默認每種設備類型只有一種固件)。下設2個字段,包括一個自增長的主鍵uId和設備類型名稱uName。
fw_list:固件清單數據表,記錄廠商上傳至服務器的每一個固件的詳細信息,包括typeId(設備類型名稱編號),uPath(固件大?。?,uHash(固件哈希校驗值) ,哈希的作用是文件在傳輸前和傳輸后分別計算一個校驗值,如果兩個校驗值相同,則說明文件傳輸沒有發生錯誤),版本號(3分段,v1.v2.v3) 。其中,TYPEID字段需要設置為同DEVICE_TYPE中的的uId相關聯。
device_list:設備列表,記錄出廠模塊的MAC地址和設備類型,typeId字段也需要設置為同device_type中的uId相關聯。
表1數據庫結構
該數據庫的理解是:每一個設備對應唯一的一個MAC地址,也對應一種設備類型,而每一種設備類型對應有多個版本的同一類固件這樣,關系數據庫就建好了,目前還是一個空的數據庫,后續需要管理員通過固件上傳操作,將不同設備類型,不同版本的固件上傳至服務器即可。
固件上傳部分
固件上傳網頁如圖3所示,管理員上傳固件時需要選擇設備類型,選擇需要上傳的固件以及填寫固件版本號,然后點擊“確定”,上傳的固件信息將會交給服務器腳本PHP去處理。
圖3固件上傳界面
HTML代碼如下:
01 < html >
02 <頭>
03 < meta http-equiv = “Content-Type”content = “text / html ; charset = utf-8” />
04 < title >遠程自動固件更新系統</ title >
05 </ head >
06 <身體>
07 < BR > < BR >
08 < H1對齊= “中心” >遠程自動固件更新系統</ H1 > < BR >
09 < H2對齊= “中心” >固件上傳</ H2 > < BR >
10 < form method = “post”enctype = “multipart / form-data” > <! –用POST方式– >
11 < label for = “file” >設備類型:</ label >
12 < 選擇名稱= ‘類型’需要ID = ‘C選擇’ >
13 < option value = ” > </ option >
14 < option value = ‘1’ > W5500S2E-S1 </ option >
15 < option value = ‘2’ > W5500S2E-Z1 </ option >
16 </ select >;
17 < label for = “file” >選擇固件:</ label >
18 < input type = “file”name = “file”id = “file” /> <!-選擇固件– >版本:
19 < input type = “text”name = “v1”id = “v1”size = “ 1 ” >。< input type = “text”name = “v2”id = “v2”size = “ 1 ” >。< input type = “text”name = “v3”id = “v3”size = “ 1 ” >
20 < input type = “ submit ”name = “ submit ” value = “確定” /> <! –確認提交– >
21 </ form >
22 </ body >
23 </ html >
這里要說明的是固件是保存在服務器中的而不是數據庫中,數據庫中僅通過在“fw_list”數據表中的“UPATH”字段來保存其絕對路徑。因此必要的操作是需要在網站所在目錄里新建一個文件夾,例如“上傳”,用于存放前面的HTML代碼POST至服務器的固件。下面是通過PHP將HTML上傳的固件信息保存至數據庫的腳本代碼。
01 <?php
02 if($ _SERVER [‘REQUEST_METHOD’] ==“POST”)//是否是POST方式上傳至服務器
03 {
04 if($ _FILES [“file”] [“type”] ==“application / octet-stream”){//“application / octet-stream”代表.bin的文件類型
05 if($ _FILES [“file”] [“error”]> 0){//檢查文件是否有錯
06 echo“{window.alert(’提示:文件錯誤!’);}”;
07} else {
08 $ size = $ _ FILES [“file”] [“size”] / 1024; //計算固件大小,單位KB
09 if(file_exists(“upload /”。$ _FILES [“file”] [“name”])){
//檢查上傳文件夾中是否已有同名固件
10 echo“{window.alert(’提示:該文件已存在!’);}”;
11} else {
12 move_uploaded_file($ _ FILES [“file”] [“tmp_name”],“upload /”。$ _FILES [“file”] [“name”]); //將上傳至緩存的固件移動至上傳文件夾中
13 $ oldname = $ _ FILES [‘file’] [‘name’];
14 $ path =“http://W5500.com/fw_update/upload/”。$ oldname;
//記錄固件的路徑
15 $ sha1file = sha1_file($ path); //用SHA1算法計算固件的哈希值
16 $ newname = $ sha1file?!?。bin”;
17重命名(“D:/Website/W5500.com/fw_update/upload/$oldname”,“D:/Website/W5500.com/fw_update/upload/$newname”); //用固件哈希值來重命名該固件
18 $ path =“http://W5500.com/fw_update/upload/”。$ newname;
//重新獲取固件的路徑
19 if(isset($ _ POST [“v1”]))$ v1 = $ _ POST [“v1”];
//定義變量V1為POST過來的版本號的第1位
20 if(isset($ _ POST [“v2”]))$ v2 = $ _ POST [“v2”]; //版本號的第2位
21 if(isset($ _ POST [“v3”]))$ v3 = $ _ POST [“v3”]; //版本號的第3位
22 $ typeId = $ _ POST [“type”]; //定義變量TYPEID為POST過來的設備類型參數
23 $ link = mysql_connect(“localhost”,“root”,“xxxxxx”); //連接數據庫
24 mysql_select_db(“fw_update”,$ link); //選擇名為“fw_update”的數據庫
25 mysql_query(“INSERT INTO fw_list(typeId,uPath,uSize,uHash,v1,v2,v3)VALUES(’$ typeId’,’$ path’,’$ size’,’$ sha1file’,’$ v1’,’ $ V2′ , ‘$ V3’)“);
//將信息記錄到“fw_list”數據表中
26 mysql_close($ link); //斷開數據庫連接
27}
28}
29} else {
30 echo“無效文件”; //若非的.bin文件,網頁顯示無效的文件
31}
32}
33?>
該段腳本主要代碼注釋如下:
表2固件上傳腳本注釋
當服務器接收到通過POST方式上傳的文件時要對文件類型進行檢查,箱文件要求的文件類型必須為“應用程序/八位字節流”。若文件類型符合要求,并且檢查文件無誤后執行上傳操作。系統先將文件上傳至PHP的緩存即會產生一個“$ _FILES [”file“] [”tmp_name“]”的臨時文件,再通過“move_uploaded_file()”將上傳的臨時文件移動到指定位置,“file_exists( )”用于檢查是否有文件名稱相同的文件。接下來的做法是用‘sha1_file()’計算出文件哈希值后,以該哈希值作為新的文件名通過‘重命名()’重新命名,這樣做一來可以在下載過程中給文件名稱加密,二來使得數據庫與服務器保持一致,易于區分。后續的幾行代碼用于把上傳至服務器的固件的相關字段插入數據庫對應的數據表中。
服務器與模塊之間實現自動固件更新的協議
接下來是實現自動固件更新的關鍵部分了。服務器與模塊之間通過HTTP協議和。POST方法這已經確定,服務器和模塊之間的信息交流還需要制訂一些私有協議去完成具體的操作就好比2個人說好了是用普通話交流,但是還需要提前制訂好交流的內容。
圖4自動固件更新協議
如圖所示,當模塊的程序(下一期會講述)向服務器發出自己的MAC地址進行設備身份驗證后,服務器首先會查閱“device_list”數據表判斷是否存在該設備,如果不存在,則驗證不通過,服務器以JSON格式給客戶端回復:{“錯誤”:” 1” },如果存在,驗證通過,服務器將自動通過之前建立的數據表的關聯字段‘TYPEID’來查詢在‘fw_list’數據表中有無該種設備類型的固件,如果沒有,回復:{“錯誤”:” 2” },如果有固件,給客戶端回復最新的版本號,下載路徑,固件大小,文件散列校驗值等4個關鍵信息:{“版本”:” XXX”,”路徑”:” …”,”大小”:” XX”,”散列”:” …”},客戶端收到這4個關鍵信息后就可以自行從服務器下載固件并自行更新了。
要實現以上協議過程,服務器端的PHP腳本如下:
1 <?php
2 if($ _SERVER [‘REQUEST_METHOD’] ==“POST”)//是否是POST方式上傳至服務器
3 {
4 if(isset($ _ POST [“mac”]))$ mac = $ _ POST [“mac”];
5 //接收POST過來的數據,即JSON格式的MAC地址
6 $ obj = json_decode($ mac); //解析JSON
7 $ mac = $ obj-> MAC; //獲取具體的MAC地址
8 $link = mysql_connect(“localhost”,”root”,”xxxxxx”);
9 mysql_select_db(“fw_update”, $link);
10 $result = mysql_query(“SELECT * FROM device_list WHERE uMAC =’$mac'”);
11 //選取“device_list”數據表中“uMAC”字段值等于模塊發過來的MAC地址的字段
12 $count=mysql_num_rows($result); //對選取結果計數
13 $res=mysql_fetch_array($result); //將符合要求的第一項抽取出來
14 $typeID=$res[“typeId”]; //取出設備類型號
15 mysql_close($link);
16 if ($count==1) { //若數據庫中有1個相同的MAC,證明該設備已在數據庫中記錄
17 $link = mysql_connect(“localhost”,”root”,”xxxxxx”);
18 mysql_select_db(“fw_update”, $link);
19 $result=mysql_query(“SELECT v1,v2,v3,fw_list.* FROM `fw_list` WHERE
20 typeId=’$typeID’ ORDER BY v1 DESC, v2 DESC,v3 DESC limit 0,1”);
21 //選取“fw_list”數據表中屬于某個設備類型的最新版本的固件
22 $num=mysql_num_rows($result); //對選取結果計數
23 if ($num==1) {
24 //若符合要求的個數等于1,說明數據庫中存在該設備類型的固件
25 $array=mysql_fetch_array($result); //將符合要求的第一項抽取出來
26 $latest_rev=$array[“v1″].”.”.$array[“v2″].”.”.$array[“v3”];
27 //取出該固件的版本號
28 $path=$array[“uPath”]; //取出路徑
29 $size=$array[“uSize”]; //取出固件大小值
30 $hash=$array[“uHash”]; //取出固件哈希值
31 echo “{\”version\”:\”$latest_rev\”,\”path\”:\”$path\”,\”size\”:\”$size\”,\”hash\”:
32 \”$hash\”}”; //服務器將關鍵信息回復給模塊
33 } else {
34 echo “{\”error\”:\”2\”}”;
35 //數據庫中沒有符合要求的固件,服務器給模塊回復錯誤代碼2
36 }
37 mysql_close($link);
38 } else {
39 echo “{\”error\”:\”1\”}”;
40 //數據庫中沒有相同的MAC,證明該設備未在數據庫中記錄,服務器給模塊回復錯誤代碼1
41 }
42 }
43 ?>
代碼大致注釋如下:
第一部分,服務器接收模塊POST過來的MAC地址:{“MAC”:”00:08:DC:XX:XX:XX”},PHP腳本通過“json_decode();”函數解析出來,并用“$obj->MAC;”將MAC地址讀出來,這樣服務器就成功獲取到了模塊POST過來的MAC地址的值;
第二部分,接下來的幾個數據庫操作函數是為了在“device_list”中查詢是否有符合的MAC地址,并獲取該MAC地址對應的設備類型號。如果存在該設備,設備驗證通過,計數值等于1,進而查找是否存在符合該設備類型的固件,數據庫操作語句“$ result = mysql_query(”SELECT v1,v2,v3,fw_list。* FROM`fw_list` WHERE typeId =’$ typeID’ORDER BY v1 DESC,v2 DESC,v3 DESC limit 0,1“);”意為在“fw_list”中查找符合設備類型的最新版本固件。若存在,num值等于1,服務器將關鍵信息發給客戶端。
至此,自動固件更新服務器端的工作就告一段落,當然這里只是提供了一個思路和大概步驟,在設備驗證,數據庫管理,固件管理等各個方面都需要進行完善。比如設備驗證,當前只是驗證MAC地址,可以添加更多的驗證字段,或者對設備驗證的通訊進行加密,從而保驗證通訊的安全;數據庫管理需要更符合銷售人員的使用習慣,方便錄入銷售數據和匹配設備型號;服務器上的固件上傳需要更為完善與謹慎,建議添加更多的驗證步驟,本身是方便設備更新固件而設置的服務器,試想如果上傳了錯誤固件的話,也很“方便”的變成災難了。
在下一篇“手把手教你如何實現自動固件更新 – 嵌入式篇”中,我將繼續與大家分享在嵌入式系統中增加云更新固件的功能,請大家拭目以待。