准备姿势, update升级流程
先看一眼链接哦, 对升级过程有个认识
http://oldask.openluat.com/article/93
**升级流程
模块读取自身信息–拼接字符串–get请求服务器–服务器收到请求–处理请求–如果版本号低于最新版–返回200–模块下载升级包–下载成功自动重启–升级成功
如果版本号大于等于最新版–返回404错误码–模块停止升级检查。
再次强调重点
- 服务器返回200, 设备将读取响应,作为升级文件
- 服务器返回3XX, 重定向到新的地址下载
- 服务器返回4XX, 设备无需升级
设备端代码
require "update"
update.request(nil,"iot.nutz.cn/api/site/firmware_upgrade")
// 域名 + 升级URI, 用ip也是可以的
// 仅支持http协议,除非自行扩展update.lua
4G模块的升级报错代码,请查阅 http://oldask.openluat.com/article/90
划重点: 要用外网服务器!!! 局域网可访问不了!!!
服务器端
设备会发什么上来:
1. URI: /luat/update 自定义即可,服务器与设备要一致
2. 参数5个,均通过URL传递
- project_key 产品密钥,自行定义,可以不管
- imei 设备识别号
- firmware_name 当前固件名称,相当于Lod版本
- version 用户自定义版本号
- need_oss_url 是否需要oss路径(仅供实现了CDN之后使用),可以无视
服务器端接收到这些参数后,根据业务逻辑决定返回的内容
例如, 代码这样写,用的lod是Luat_V0017_ASR1802
PRODUCT_KEY = "sadfsaqwerOKMGUFI"
PROJECT = "ALIYUN"
VERSION = "2.0.0"
那么, URL参数会是这样
project_key=sadfsaqwerOKMGUFI
imei=86902342332452
firmware_name=ALIYUN_Luat_V0007_ASR1802
version=2.0.0
need_oss_url=0
另有完整服务器端实现,请查阅 http://oldask.openluat.com/article/878
代码示例
伪代码版
// 根据imei查出设备
var dev = sql("select * from t_dev where imie=" + imei);
if (dev == null) {
// 没有这个设备,不准升级, or 自动插入记录
return resp(403);
}
// 根据设备查出升级计划
var updatePlan = sql("select * from t_update_plan where dev_id=" + dev.id)
if (updatePlan == null) {
// 没有对应的升级计划,不准升级
return resp(403);
}
// 固件版本是否对应
if (firmware_name != updatePlan.firmware_name) {
// 固件版本不对应,无法升级
return resp(404); // 不是返回200就行
}
// 软件版本号是否一致
if (version == updatePlan.version) {
// 软件版本一样,无需升级
return resp(404);
}
// 需要升级,写入升级文件
resp.write(updatePlan.file)
// 结束.
java版(使用nutz实现)
@IocBean
@At("/luat")
public class LuatUpdateModule {
@Fail("http:500")
@Ok("void")
@At("/update")
public void update(String project_key, String imei, String firmware_name, String version, int need_oss_url, HttpServletResponse resp) throws FileNotFoundException, IOException {
// TODO 根据 imei 查出设备
// TODO 根据设备查出升级计划
String expectVersion = "2.0.1";
// 判断版本号
if (expectVersion.equalsIgnoreCase(version)) {
resp.setStatus(404); // 不需要升级
return;
}
try (FileInputStream ins = new FileInputStream("/data/luat/update/" + expectVersion + "/update.bin")) {
Streams.writeAndClose(resp.getOutputStream(), ins);
}
}
}
php例子,由罗耀锋提供
public function actionUpgrade() {
imei = UtilsApp::getParamterByParamterName("imei");ver = UtilsApp::getParamterByParamterName("version");
updates = CardUpgrade::find()->all(); foreach (updates as update) {verInt = intval(ver);updateVer = intval(update->ver); if (updateVer>verInt) { Yii::app->getResponse()->sendFile(Yii::app->basePath . '/web/' .update->filepath, 'update.bin');
return;
}
}
Yii::$app->response->statusCode=500;
}
基于UDP的升级检查服务 python版
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
device_upgrade_server.py
python3, ubuntu16.04, mysql.
简单的升级服务,无数据校验等,仅供参考。
升级的配置存在mysql,是由另外的网页程序实现的。
需要安装sqlalchemy和gevent
pip3 install sqlalchemy
pip3 install gevent
mysql的python接口
apt-get install python3-mysql.connector
"""
from gevent.server import DatagramServer # 使用gevent的udp服务器。也可以直接用socket收。
from gevent import monkey
monkey.patch_all()
import gevent
import os
import sys
import time
import datetime
import re
import traceback
import logging
logging.basicConfig(level = logging.DEBUG)
dbg = logging.debug
def print_exception_info(): # 打印trace信息
#dbg(type(sys.exc_info()[1]))
#dbg(sys.exc_info()[1])
traceback.print_exc()
#exc_type, exc_obj, exc_tb = sys.exc_info()
#fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
#dbg(exc_type, fname, exc_tb.tb_lineno)
import sqlalchemy
from sqlalchemy import *
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
# mysql数据库配置
DB = 'mysql+mysqlconnector://name:[email protected]:3306/device_framework_upgrade'
dbengine = create_engine(DB, echo = False, pool_size = 512, max_overflow = 0, pool_timeout = 0, pool_recycle = 5)
Session = sessionmaker(bind = dbengine)
'''
device's data look like "862991443753386,A9335_V1723_B3633_WD1,1.1.8" and "Get6,12"
数据格式
1.recv [imei,project_name,version]
2.send [LUAUPDATE,upgrade_id,package_count,last_package_size]
3.recv [Getn,upgrade_id]
4.send [data]
...
'''
PACKAGE_SIZE = 1022
start_cmd_pattern = re.compile("^(\d{15}),(\w+),(\d+\.\d+\.\d+)")
get_cmd_pattern = re.compile("^Get(\d+),(\d+)")
class SingleUpgradeServer(DatagramServer):
def __init__(self, *args, **kwargs):
DatagramServer.__init__(self, *args, **kwargs)
def handle(self, data_org, address): # 接收数据
try:
dbg('=================================================')
dbg(('=== %s %s: got data_org') % (time.ctime(), address[0]))
dbg('=================================================')
# 连接数据库
dbsession = Session()
data = data_org.decode('ascii') # ascii解码
dbg(data)
function = None
reply = bytearray()
# 匹配命令
match = start_cmd_pattern.match(data)
if match:
function = 0
else:
match = get_cmd_pattern.match(data)
if match:
function = 1
dbg('function = {}'.format(function))
if function == 0: # match start_cmd_pattern
imei, project_name, version = match.groups()
# 在数据库中根据项目名查找固件
upgrade = dbsession.execute(text("select * from device_framework_upgrade.single_upgrade where project_name = :project_name and status = 1"), {"project_name":project_name}).fetchone()
if upgrade is None:
reply = "no upgrade".encode('ascii')
dbg("no upgrade")
# 检查imei是否在升级范围内
in_range = False
imei_ranges = dbsession.execute(text("select * from device_framework_upgrade.single_upgrade_imei_range where upgrade_id = :upgrade_id"), {"upgrade_id":upgrade.id}).fetchall()
for imei_range in imei_ranges:
if imei_range.starting_imei <= imei and imei <= imei_range.ending_imei:
in_range = True
break
if not in_range:
reply = "imei range error".encode('ascii')
dbg("range error")
# 检查设备版本号是否最新
v1, v2, v3 = upgrade["version"].split('.')
server_version = (int(v1) << 16) + (int(v2) << 8) + int(v3)
v1, v2, v3 = version.split('.')
device_version = (int(v1) << 16) + (int(v2) << 8) + int(v3)
if device_version >= server_version:
reply = "version error".encode('ascii')
dbg("version error")
else:
# 读取文件信息
file_path = upgrade['file_path']
dbg(file_path)
file_size = os.path.getsize(file_path)
last_package_size = file_size % PACKAGE_SIZE
package_count = int(file_size / PACKAGE_SIZE)
if last_package_size != 0:
package_count += 1
dbg((file_size, package_count, last_package_size))
reply = ("LUAUPDATE,%d,%d,%d" % (upgrade["id"], package_count, last_package_size)).encode('ascii')
elif function == 1: # match get_cmd_pattern
index, upgrade_id = match.groups()
dbg("get")
# 查找文件
upgrade = dbsession.execute(text("select * from device_framework_upgrade.single_upgrade where id = :id and status = 1"), {"id":int(upgrade_id)}).fetchone()
file_path = upgrade['file_path']
dbg("file is %s" % file_path)
# 读取文件数据
fd = open(file_path, 'rb')
fd.seek((int(index) - 1) * PACKAGE_SIZE)
reply = bytearray([int(int(index) / 256), int(index) % 256]) + fd.read(PACKAGE_SIZE)
else:
reply = "unknown function".encode('ascii')
except:
dbg("error")
print_exception_info()
reply = "error all".encode('ascii')
finally:
# 回复数据给设备
self.socket.sendto(reply, address)
if dbsession:
dbsession.close()
if __name__ == '__main__':
dbg('device_upgrade_server.py receiving datagrams on %s:%d' % ('', 2234))
try:
# 启动udp服务器
SingleUpgradeServer('%s:%d' % ('', 2234)).serve_forever()
except:
print_exception_info()