• 欢迎大家分享资料!前往留言板评论即可!

Luat全系列模块支持免费OTA远程升级,并提供例程将该功能部署到自己服务器上

合宙 模组资料网 2年前 (2021-05-15) 374次浏览 0个评论 扫描二维码
文章目录[隐藏]

准备姿势, update升级流程

先看一眼链接哦, 对升级过程有个认识
http://oldask.openluat.com/article/93

**升级流程

模块读取自身信息–拼接字符串–get请求服务器–服务器收到请求–处理请求–如果版本号低于最新版–返回200–模块下载升级包–下载成功自动重启–升级成功
如果版本号大于等于最新版–返回404错误码–模块停止升级检查。

再次强调重点

  1. 服务器返回200, 设备将读取响应,作为升级文件
  2. 服务器返回3XX, 重定向到新的地址下载
  3. 服务器返回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()

喜欢 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址