※已刊登在“无线电”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地址,可以添加更多的验证字段,或者对设备验证的通讯进行加密,从而保验证通讯的安全;数据库管理需要更符合销售人员的使用习惯,方便录入销售数据和匹配设备型号;服务器上的固件上传需要更为完善与谨慎,建议添加更多的验证步骤,本身是方便设备更新固件而设置的服务器,试想如果上传了错误固件的话,也很“方便”的变成灾难了。
在下一篇“手把手教你如何实现自动固件更新 – 嵌入式篇”中,我将继续与大家分享在嵌入式系统中增加云更新固件的功能,请大家拭目以待。