手把手教你如何实现自动固件更新
——服务器篇
常席正 张博
常见的嵌入式设备的固件更新有两种方式:上位机工具更新和 HTTP 嵌入式网页更新。但两种方式都无法批
量更新,都需要用户手动操作,如果用户有大量模块需要更新固件,绝不可能像“把大象放进冰箱”那样三步就
可以解决问题,而且很多厂商的固件因为保密的问题是不开放给客户的。即使所有问题都不是问题,而用户更新
失败以及更新错误的固件,所造成设备变“砖”的风险也是设备开发者不得不考虑的。
下面给大家介绍一种嵌入式设备批量实现固件更新的方法----通过固件服务器自动更新。只需要有一台云服
务器,一些 HTML/PHP 和数据库方面的知识,然后再在模块里植入 HTTP 客户端固件更新的相关代码。之后用
户只需要将模块联网,固件更新就能自动完成。
本人思路如图 1 所示。
首先,模块需要给客户开放一个可选配置项,即是否允许模块自动进行固件更新操作。
然后,当模块自动进行固件更新被允许时,模块作为 HTTP Client 通过 HTTP POST 的方式将验证信息(一
般为模块的 MAC 地址)以 JSON 的格式发送至云服务器。
接着,服务器解析 JSON 以获取模块的 MAC 地址,跟数据库中预先保存的 MAC 地址列表进行对比验证。
如果验证通过,服务器将通过 HTTP POST 回复关于最新固件版本的必要信息(包括固件版本号、下载路径、文
件长度、文件校验 HASH 值)发给模块;如果验证通过,但服务器端出现异常,则服务器给模块报错;如果验证
不通过,服务器告诉模块“该设备未注册”。
最后,模块通过解析服务器回复的 JSON 格式的报文,获取最新固件版本的信息,并与自身固件版本作比对,
如果设备本身固件不是最新固件,则进一步完成下载最新固件并完成更新。
图 1 自动固件更新流程
通过以上流程描述可知,实现这套方案同时需要服务器端和嵌入式模块两部分的配合,这篇先给大家教一下
服务器端需要做哪些工作。
Web 服务器环境搭建和网站的建立
首先,需要拥有一个域名和一台云服务器。关于域名的解析、备案等大家可以自行了解。国内做云服务器的
厂商众多,易迈云、阿里云、腾讯云等等,我们要实现的这个功能非常简单,对服务器开销不大,根据实际需要
选一款即可。我用的是阿里云服务器 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 服务模块(绿色√),分别点击“Start”按钮启动 Apache Web 服
务器和 MySQL。
Web 服务器搭建完毕,域名也完成了解析和备案,就可以通过浏览器访问到用户的默认网站。接下来要做
的需要将自动固件更新服务器端的一整套代码,包括 HTML 和 PHP 文件替换原有的默认网页,服务器端环境和
网站就搭建完毕了。下面进行详细说明。
关系数据库搭建
固件的所有关键信息都是通过数据库保存的,PHP 文件只是用于对数据库进行必要操作脚本,因此完善、
清晰的数据库内容和结构对于后续的 PHP 代码编写非常重要。接下来我们需要先完成数据库的搭建。
点击 MySQL 模块同行的“Admin”按钮,进入数据库。
如下表 1 所示,新建一个数据库“fw_update”,排序规则为 utf8_general_ci。该数据库下新建 3 个数据表,
分别是“fw_list”、“device_list”以及“device_type”。
device_type:设备类型数据表,厂商的产品可以有多种型号(默认每种设备类型只有一种固件)。下设 2
个字段,包括一个自增长的主键 uId 和设备类型名称 uName。
fw_list:固件清单数据表,记录厂商上传至服务器的每一个固件的详细信息,包括 typeId(设备类型名称
编号)、uPath(固件下载路径)、uSize(固件大小)、uHash(固件哈希校验值,哈希的作用是文件在传输前和传
输后分别计算一个校验值,如果两个校验值相同,则说明文件传输没有发生错误)、版本号(分 3 段,v1.v2.v3)。
其中,typeId 字段需要设置为同 device_type 中的 uId 相关联。
device_list:设备列表,记录出厂模块的 MAC 地址和设备类型,typeId 字段也需要设置为同 device_type
中的 uId 相关联。
表 1 数据库结构
该数据库的理解是:每一个设备对应唯一的一个 MAC 地址,也对应一种设备类型,而每一种设备类型对应
有多个版本的同一类固件。这样,关系数据库就建好了,目前还是一个空的数据库,后续需要管理员通过固件上
传操作,将不同设备类型、不同版本的固件上传至服务器即可。
固件上传部分
固件上传网页如图 3 所示,管理员上传固件时需要选择设备类型、选择需要上传的固件以及填写固件版本号,
然后点击“确定”,上传的固件信息将会交给服务器脚本 PHP 去处理。
图 3 固件上传界面
HTML 代码如下:
01
02
03
04
远程自动固件更新系统
05
06
07
08 远程自动固件更新系统
09 固件上传
10
22
23
这里要说明的是固件是保存在服务器中的而不是数据库中,数据库中仅通过在“fw_list”数据表中的“uPath”
字段来保存其绝对路径。因此必要的操作是需要在网站所在目录里新建一个文件夹,例如“upload”,用于存放
前面 HTML 代码 POST 至服务器的固件。下面是通过 PHP 将 HTML 上传的固件信息保存至数据库的脚本代码。
01 0) { //检查文件是否有误
06 echo "";
07 } else {
08 $size=$_FILES["file"]["size"] / 1024; //计算固件大小,单位 KB
09 if (file_exists("upload/" . $_FILES["file"]["name"])) {
//检查 upload 文件夹中是否已有同名固件
10 echo "";
11 } else {
12 move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" .
$_FILES["file"]["name"]); //将上传至缓存的固件移动至 upload 文件夹中
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 rename("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 "Invalid file"; //若非.bin 文件,网页显示无效的文件
31 }
32 }
33 ?>
该段脚本主要代码注释如下:
表 2 固件上传脚本注释
当服务器接收到通过 POST 方式上传的文件时要对文件类型进行检查,bin 文件要求的文件类型必须为
“application/octet-stream”。若文件类型符合要求,并且检查文件无误后执行上传操作。系统先将文件上传至
PHP 的缓存即会产生一个“$_FILES["file"]["tmp_name"]”的临时文件,再通过“move_uploaded_file()” 将上
传的临时文件移动到指定位置,“file_exists()”用于检查是否有文件名称相同的文件。接下来的做法是用
“sha1_file()”计算出文件哈希值后,以该哈希值作为新的文件名通过“rename()”重新命名,这样做一来可以
在下载过程中给文件名称加密,二来使得数据库与服务器保持一致,易于区分。后续的几行代码用于把上传至服
务器的固件的相关字段插入数据库对应的数据表中。
服务器与模块之间实现自动固件更新的协议
接下来是实现自动固件更新的关键部分了。服务器与模块之间通过 HTTP 协议和 POST 方法这已经确定,服
务器和模块之间的信息交流还需要制订一些私有协议去完成具体的操作。就好比 2 个人说好了是用普通话交流,
但是还需要提前制订好交流的内容。