-- version: 00.00.01 local posix = require('posix') local time = require('posix.time') local siganl = require('posix.signal') --local sem = require('posix.sem') local bit = require('bit') local cjson = require('cjson') local base64 = require('base64') local dyutils = require('dyutils') local lpack = require('lua_pack') local struct = require("struct") local band, bor, bxor, bnot = bit.band, bit.bor, bit.bxor, bit.bnot local lshift, rshift, rol = bit.lshift, bit.rshift, bit.rol local lpack = require("lua_pack") local bpack = string.pack local bunpack = string.unpack local TIMEOUT_MS = 1000 -- 注意:firmware_info.mode = 1 -- 升级模式: 并行升级 由lua脚本自行通过http post等方式推送升级文件给下挂设备,严禁调用send_command()\receive_data()等串行升级接口 -- 并行升级参数可在脚本中定义,也可在扩展参数中获取 local firmware_info = {} local ext_info = nil -- log Level local LOG_EMERG = 0 --/* system is unusable */ local LOG_ALERT = 1 --/* action must be taken immediately */ local LOG_CRIT = 2 --/* critical conditions */ local LOG_ERR = 3 --/* error conditions */ local LOG_WARNING = 4 --/* warning conditions */ local LOG_NOTICE = 5 --/* normal but significant condition */ local LOG_INFO = 6 --/* informational */ local LOG_DEBUG = 7 --/* debug-level messages */ local pipes_table = { ["command"] = {fd = -1, path = "/tmp/command"}, -- Lua → C ["response"] = {fd = -1, path = "/tmp/response"},-- C → Lua ["progress"] = {fd = -1, path = "/tmp/progress"},-- Lua → C ["log"] = {fd = -1, path = "/tmp/log"}, -- Lua → C } --local mysem, err = sem.open("/otasem") local function open_pipes() local flags = bor(posix.O_RDONLY, posix.O_NONBLOCK) pipes_table["command"].fd = posix.open(pipes_table["command"].path, posix.O_WRONLY) pipes_table["progress"].fd = posix.open(pipes_table["progress"].path, posix.O_WRONLY) pipes_table["log"].fd = posix.open(pipes_table["log"].path, posix.O_WRONLY) pipes_table["response"].fd = posix.open(pipes_table["response"].path, flags) end local update_log local function close_pipes(pipes) for name, pipe_info in pairs(pipes) do if pipe_info.fd ~= -1 then local ok, err = pcall(posix.close, pipe_info.fd) if ok then print(string.format("success closed fd=%d for pipe '%s'", pipe_info.fd, name)) pipe_info.fd = -1 else print(string.format("fail to close fd=%d for pipe '%s': %s", pipe_info.fd, name, err)) end else print(string.format("Pipe %s %s", name, "already closed (fd=-1)")) end end end local function CalculateXorCrc(arr_buff, len) local lastValue = 0 local shiftBitNum = {24, 16, 8} local fractionalData = bit.band(len, 0x3) local crc = 0 local int_len = math.modf(len/4) local k = 1 local i = 1 for k=0, int_len-1 do local sub = string.sub(arr_buff,k*4 + 1, k*4 + 4) _, t = bunpack(sub, "I") crc = bit.bxor(crc, t) end if (fractionalData ~= 0) then for i=1, fractionalData do lastValue = bit.bor(lastValue, bit.lshift(string.byte(string.sub(arr_buff,len-fractionalData+i,len-fractionalData+i)), shiftBitNum[i])) end crc = bit.bxor(crc, lastValue) end crc = tonumber(string.format("%u", crc)) return crc; end -- 接收C返回LUA的响应帧(带超时时间ms,返回0 此帧无应答 返回 1 正常解析数据) local function receive_data() local fd_Id = pipes_table["response"].fd if fd_Id == -1 then update_log(LOG_ERR, "fd_Id is -1 (already closed)") return -1, nil end local fds = { [fd_Id] = { events = { IN = true, -- 数据可读 HUP = true, -- 挂断事件 ERR = true -- 错误事件 } } } local poll = require 'posix.poll' local ret = poll.poll(fds, TIMEOUT_MS) if ret == 0 then update_log(LOG_INFO, "timeout, no data received") return 0, nil elseif ret == -1 then update_log(LOG_WARNING, "poll call failed, reason: " .. tostring(posix.errno())) return -1, nil else for fd in pairs(fds) do if fds[fd].revents.IN then local data, err = posix.read(fd, 1024) if data then --update_log(LOG_INFO, string.format("RX[%d]: %s", #data, string.toHex(data," "))) return 1, data else return -1, nil end elseif fds[fd].revents.HUP then update_log(LOG_ERR, "pipe closed (HUP)") posix.close(fd) -- 关闭 fd fd = -1 -- 标记为已关闭 pipes_table["response"].fd = -1 return -2, nil elseif fds[fd].revents.ERR then update_log(LOG_ERR, "pipe error (ERR)") posix.close(fd) fd = -1 pipes_table["response"].fd = -1 return -3, nil end end end end -- 发送命令/固件包 --(每包数据可指定超时时间) local function send_command(timeout_ms, data) --mysem:wait() local block_pkt = bpack("C 65535 then error("随机数范围必须在0-65535之间") end if min > max then min, max = max, min end return math.random(min, max) end -- 生成多个随机数 function RandomGenerator.multiRandom(count, min, max) local results = {} for i = 1, count do results[i] = RandomGenerator.random(min, max) end return results end local function exit_update_result(success, ver, err_code) -- local ver = "v1.2.3" -- 升级成功传入设备版本,若协议不支持版本获取,则为""即可 if success then send_update_success(ver) update_log(LOG_INFO, "Firmware upgrade completed success") else send_update_fail(err_code) update_log(LOG_INFO, "Firmware upgrade failed!!!") end posix.sleep(2) close_pipes(pipes_table) end -- 1. 此协议支持rs485方式升级,只支持定向方式升级BCU local function get_target_addr(obj) local addr = 0 -- 先关心单播方式升级(定向) addr = bit.bor(addr, bit.lshift(obj['spread'], 14)) -- 广播标志 addr = bit.bor(addr, bit.lshift(obj['L3_DevId'], 13)) -- (14 .. 13) BAU 设备ID addr = bit.bor(addr, bit.lshift(obj['L2_DevId'], 7)) -- (11 .. 7) BCU 设备ID addr = bit.bor(addr, obj['L1_DevId']) -- (6 .. 0) BMU 设备ID return addr end -- 2. 此协议支持tcp方式升级,支持升级BCU和BMU,只支持定向方式升级 local interface = nil -- 接口方式 rs485 socket -- 执行升级函数 function perform_upgrade(dev_no, addr, fri_str, ext_str) open_pipes() posix.sleep(1) local err_code = 0 local address = tonumber(addr) local currTime = os.time() update_log(LOG_INFO, string.format("dev_no: %s, addr: %d, fri_str: %s, ext_str: %s", dev_no, addr, fri_str, ext_str)) local obj = cjson.decode(fri_str) if not obj.mode or not obj.num or not obj.info then update_log(LOG_ERR, "Invalid firmware mode or num or info") return end firmware_info.mode = obj.mode -- 0:串行升级 1:并行升级(此参数不使用,固化0) firmware_info.num = obj.num -- 升级文件个数 firmware_info.info = obj.info for i, path in ipairs(firmware_info.info) do update_log(LOG_INFO, string.format("info[%d]: %s", i, path)) end ext_info = cjson.decode(ext_str) if not ext_info then update_log(LOG_ERR, "Invalid extend argv") return end for key, value in pairs(ext_info) do update_log(LOG_INFO, string.format("argv %s: %s %s", key, value, type(value))) end if ext_info.interface == nil then interface = "rs485" else interface = ext_info.interface end -- siganl.signal(siganl.SIGKILL, handler) -- siganl.signal(siganl.SIGHUP, handler) local success = true -- if ext_info['Dev_Type'] and ext_info['L3_DevId'] and ext_info['L2_DevId'] and ext_info['L1_DevId'] and ext_info['Reset'] then if ext_info['Dev_Type'] and ext_info['L3_DevId'] and ext_info['L2_DevId'] and ext_info['L1_DevId'] and ext_info['Reset'] and ext_info['version'] and ext_info['spread'] then --锦浪定制 -- what to do? else update_log(LOG_WARNING, "Invalid parameter!!!") err_code = INVA_PARAM -- 参数无效 success = false exit_update_result(success, "", err_code) end -- 协议帧框架的目的地址 local dest_addr = get_target_addr(ext_info) update_log(LOG_INFO, string.format("dest addr %02x", dest_addr)) local dev_addr if (obj['Dev_Type'] == 0) then dev_addr = 0x2081 -- BMU设备 elseif (obj['Dev_Type'] == 1) then dev_addr = 0x2080 -- BCU设备 end --update_log(LOG_INFO, string.format("switch device address %02x", dev_addr)) local mi = 0 local block_size = 192 local try_cnt = 5 local fail_cnt = 0 ------------------------------------------------------------------------- -- 1. 若需要版本校验,需在升级文件名里包含版本信息,解析升级文件名获取版本信息 -- /tmp/SeIbuV-Board205-16S-D0503-0x5024-23.60.1.6-20221021.bin -- local firmware_version = firmware_info.info[1]:match("SeIbuV.*%-(%d+%.%d+%.%d+%.%d+).*%.bin")--string.match(firmware,'[(Se)|(Auto)]lbuV(%d.%d.%d.%d).bin') -- /tmp/SeIbuV13.93.1.39-XN1226-0037-13pack-0X3343-1C-208-2401-1331-20260129.bin --[[ local firmware_version = firmware_info.info[1]:match("V(%d+%.%d+%.%d+%.%d+)") update_log(LOG_INFO, string.format("firmware_version: %s", firmware_version)) ]] local firmware_version = ext_info['version'] -- 锦浪定制,直接取版本 update_log(LOG_INFO, string.format("firmware_version: %s", firmware_version)) -- 2. 获取下挂设备版本信息 -- ShowVersion while (true) do local ver_flag = false while (true) do -- mi = 1000 local data = string.format("%s", "ShowVersion") local version_pkt = bpack("CCS", version_pkt, crc16) local mbap = bpack(">S>S>S", mi, 0x01, #_frame) -- 仿MODBUS_TCP MABP头 - 事务单元标识符 协议标识符 长度 local send_frame = bpack("AA", mbap, _frame) -- SEND send_command(500, send_frame) -- mi = mi + 1 local val, response = receive_data() if response then update_log(LOG_INFO, string.format("receive %d byte", #response)) fail_cnt = 0 local rev_frame = response -- 解析 local pv = string.match(rev_frame, 'pv%s=%s(%d+)') local mv = string.match(rev_frame, 'mv%s=%s(%d+)') local sv = string.match(rev_frame, 'sv%s=%s(%d+)') local cv = string.match(rev_frame, 'cv%s=%s(%d+)') local cur_version = string.format('%d.%d.%d.%d', pv, mv, sv, cv) update_log(LOG_INFO, string.format("VERSION: %s", cur_version)) if (cur_version == firmware_version) then -- 版本号与当前版本一致,不进行升级 update_log(LOG_NOTICE, "start firmware same version, no upgrade ") send_progress(100) local ver = cur_version -- 升级成功传入设备版本,若协议不支持版本获取,则为""即可 exit_update_result(success, ver, err_code) return 0 end ver_flag = true break else update_log(LOG_WARNING, "ShowVersion, Timeout waiting for device response") fail_cnt = fail_cnt + 1 if fail_cnt >= try_cnt then err_code = ACK_ERROR_OR_TIMEOUT success = false exit_update_result(success, "", err_code) return success end end end if ver_flag == true then -- 版本获取成功 break end end -- mi = mi + 1 -- TCP比较特殊,复位后需要重新建立连接,因此此处不再进行复位操作 --[[ -- 3. 对比,若版本一致,则返回success = true,否则进入升级流程 -- update_log(LOG_INFO, "firmware same version,no upgrade") update_log(LOG_INFO, string.format("Reset type: %s, value: %d", type(ext_info['Reset']), tonumber(ext_info['Reset']))) -- 补,~~复位~~,防止之前升级意外中断,导致本次不能升级问题 if tonumber(ext_info['Reset']) == 1 then local data = string.format("%s", "reset") local reset_pkt = bpack("CCS", reset_pkt, crc) local mbap = bpack(">S>S>S", mi, 0x01, #_frame) -- 仿MODBUS_TCP MABP头 - 事务单元标识符 协议标识符 长度 local send_frame = bpack("AA", mbap, _frame) -- SEND send_command(500, send_frame) local val, response = receive_data() if response then update_log(LOG_INFO, string.format("receive %d byte", #response)) else update_log(LOG_NOTICE, "Reset No response!!!") end end ]] time.nanosleep({tv_sec=5, tv_nsec=0}) update_log(LOG_INFO, "Starting firmware upgrade process...") indx = 0 -- 升级文件索引,升级第二个固件 indx = 1,以此类推(此变量必须被更新) -- 发包前操作 RandomGenerator.init() local jobid = RandomGenerator.random(1, 32768) --[[ -- 指令集::::::::::::::::::: 指令 发送字符串 回复解析 复位 “reset” 无回应(可以间隔 50ms 发送 2 次) 请求升级 “StartPrg” 回复: “Ready”表示准备好; 回复: “Busy”未准备好; 不回复或者其它: 连续发送 3 次直接进入下一步; 读取版本 “ShowVersion” 回复:pv = 3 mv = 4 sv = 1 cv = 0 Axx-0X5753-0-1-2023-12-20 multi HW:BCU-B50-501XX-BMU51-BFU00x” 结束升级 “EndPrg” 回复: “Success”正常结束;回复: “Failed”非正常结束;不回复或者其它:连续发送 3 次直接进入下一步 ]] --[[ -- 升级流程::::::::::::::::::: 1. 请求升级 2. 发送升级报文 3. 结束报文 4. 发送结束命令 5. 复位 6. 读取版本(验证版本) ]] -- 请求升级 StartPrg while (true) do -- mi = 1000 local data = string.format("%s", "StartPrg") local prg_pkt = bpack("CCS", prg_pkt, crc16) local mbap = bpack(">S>S>S", mi, 0x01, #_frame) -- 仿MODBUS_TCP MABP头 - 事务单元标识符 协议标识符 长度 local send_frame = bpack("AA", mbap, _frame) -- SEND send_command(500, send_frame) -- mi = mi + 1 local val, response = receive_data() if response then update_log(LOG_INFO, string.format("receive %d byte", #response)) local rev_frame = response -- 回复: “Ready”表示准备好; 回复: “Busy”未准备好; 不回复或者其它: 连续发送 3 次直接进入下一步; if string.find(rev_frame, "Ready") then fail_cnt = 0 update_log(LOG_NOTICE, "enter ota") break else update_log(LOG_ERR, "Enter ota failed!!!") fail_cnt = fail_cnt + 1 if (fail_cnt >= 3) then fail_cnt = 0 break end end else update_log(LOG_WARNING, "StartPrg, Timeout waiting for device response") fail_cnt = fail_cnt + 1 if fail_cnt >= 3 then fail_cnt = 0 break end end end local file = io.open(firmware_info.info[1], "r") local bin_buf = file:read("*a") local mod = math.fmod(#bin_buf, 4) -- 取余数 -- 升级文件按照 4 字节对齐,最后不足 4 字节的,后续补 0 if mod ~= 0 then local pad = 4 - mod for i=1, pad do -- bin_buf = bin_buf .. 0 bin_buf = bpack("AC", bin_buf, 0) end end -- 普通报文 send data file:seek("set") local offset = 0 local last_progress = 0 while (true) do if (offset >= #bin_buf) then break end local block_data = string.sub(bin_buf, offset+1, offset+block_size) if not block_data then update_log(LOG_WARNING, "read data failed!!!") break end local block_pkt = bpack("CCS", block_pkt, crc16) local mbap = bpack(">S>S>S", mi, 0x01, #_frame) -- 仿MODBUS_TCP MABP头 - 事务单元标识符 协议标识符 长度 local send_frame = bpack("AA", mbap, _frame) -- mi = mi + 1 -- 下一帧的位置偏移 offset = offset + #block_data while (true) do send_command(500, send_frame) local val, response = receive_data() if response then -- update_log(LOG_INFO, string.format("receive %d byte", #response)) local rev_frame = response local _,_,_,_,_,_,result = bunpack(rev_frame:sub(8), "CSCCCI") -- update_log(LOG_INFO, string.format("result type: %s [%d %d]", type(result), result, offset)) if (result == offset) then -- BMS回复下一个期望帧的偏移(当前升级的偏移位置+当前帧的长度) offset = offset + #block_data fail_cnt = 0 local progress = math.modf((offset * 100)/#bin_buf) -- 提示发送数据进度 if (progress ~= last_progress) then last_progress = progress send_progress(progress) -- update_log(LOG_INFO, string.format("Info Current send progress::::::::::::: %f", progress)) end break else -- 0xFFFFFFFF 升级过程失败(当前帧不是期望帧或者格式错误) update_log(LOG_WARNING, "result not match to offset!!!") fail_cnt = fail_cnt + 1 if (fail_cnt >= try_cnt) then if tonumber(ext_info['Reset']) == 1 then local data = string.format("%s", "reset") local reset_pkt = bpack("CS", reset_pkt, _crc) send_command(300, _send_frame) -- 无应答 end err_code = UPGRADE_PRO_FAILED success = false exit_update_result(success, "", err_code) return success end end else update_log(LOG_WARNING, "send data, Timeout waiting for device response") fail_cnt = fail_cnt + 1 if (fail_cnt >= try_cnt) then if tonumber(ext_info['Reset']) == 1 then -- 防止一次升级失败后面再不能升级的问题 local data = string.format("%s", "reset") local reset_pkt = bpack("CS", reset_pkt, _crc) send_command(300, _send_frame) end err_code = ACK_ERROR_OR_TIMEOUT success = false exit_update_result(success, "", err_code) return success end end end -- time.nanosleep({tv_sec=0, tv_nsec=10*1000*1000}) -- 每报数据间隔100ms end if true then -- 结束报文 发送偏移量为FFFFFFFF的报文,该报文携带有整个升级文件的长度以及CRC32的校验消息;BMS端接收到该报文后会进行整个升级文件的校验,然后返回校验结果 -- local file_check = dyutils.CalculateXorCrc(bin_buf, #bin_buf) local file_check = CalculateXorCrc(bin_buf, #bin_buf) local check_pkt = bpack("CCA>S", check_pkt, crc) local mbap = bpack(">S>S>S", mi, 0x01, #_frame) -- 仿MODBUS_TCP MABP头 - 事务单元标识符 协议标识符 长度 local send_frame = bpack("AA", mbap, _frame) while (true)do -- mi = mi + 1 send_command(500, send_frame) local val, response = receive_data() if response then update_log(LOG_INFO, string.format("receive %d byte", #response)) local rev_frame = response local _,_,_,_,_,_,result = bunpack(rev_frame:sub(8), "CSCCCI") update_log(LOG_INFO, string.format("result: %d", result)) if result == 0 then -- 0:全部升级正常结束 update_log(LOG_NOTICE, "Upgrade file CRC32 Check success") break else update_log(LOG_ERR, "Upgrade file CRC32 Check failed!!!") err_code = CHECK_FAILED success = false exit_update_result(success, "", err_code) return success end else update_log(LOG_ERR, "CRC32 Check, Timeout waiting for device response!!!") err_code = ACK_ERROR_OR_TIMEOUT success = false exit_update_result(success, "", err_code) return success end end end -- 发送结束命令 while (true) do -- mi = 1000 local data = string.format("%s", "EndPrg") local end_pkt = bpack("CCS", end_pkt, crc16) local mbap = bpack(">S>S>S", mi, 0x01, #_frame) -- 仿MODBUS_TCP MABP头 - 事务单元标识符 协议标识符 长度 local send_frame = bpack("AA", mbap, _frame) -- SEND send_command(500, send_frame) -- mi = mi + 1 local val, response = receive_data() if response then update_log(LOG_INFO, string.format("receive %d byte", #response)) local rev_frame = response -- 回复: “Success”正常结束;回复: “Failed”非正常结束;不回复或者其它:连续发送 3 次直接进入下一步 if string.find(rev_frame, "Success") then fail_cnt = 0 update_log(LOG_NOTICE, "success end") break elseif string.find(rev_frame, "Failed") then update_log(LOG_ERR, "Failed!!!") err_code = END_FAILED success = false exit_update_result(success, "", err_code) return success else update_log(LOG_WARNING, "other...") fail_cnt = fail_cnt + 1 if (fail_cnt >= 3) then fail_cnt = 0 break end end else update_log(LOG_WARNING, "EndPrg, Timeout waiting for device response") fail_cnt = fail_cnt + 1 if fail_cnt >= 3 then fail_cnt = 0 break end end end -- 复位 if tonumber(ext_info['Reset']) == 1 then local data = string.format("%s", "reset") local reset_pkt = bpack("CCS", reset_pkt, crc) local mbap = bpack(">S>S>S", mi, 0x01, #_frame) -- 仿MODBUS_TCP MABP头 - 事务单元标识符 协议标识符 长度 local send_frame = bpack("AA", mbap, _frame) -- SEND send_command(500, send_frame) local val, response = receive_data() if response then update_log(LOG_INFO, string.format("receive %d byte", #response)) else update_log(LOG_NOTICE, "Reset No response!!!") end end time.nanosleep({tv_sec=5, tv_nsec=0}) -- TCP比较特殊,复位后需要重新建立连接,因此此处不再执行获取版本命令,执行到此处不再校验版本号,默认升级成功 --[[ -- ShowVersion while (true) do local ver_flag = false while (true) do -- mi = 1000 local data = string.format("%s", "ShowVersion") local version_pkt = bpack("CCS", version_pkt, crc16) local mbap = bpack(">S>S>S", mi, 0x01, #_frame) -- 仿MODBUS_TCP MABP头 - 事务单元标识符 协议标识符 长度 local send_frame = bpack("AA", mbap, _frame) -- SEND send_command(500, send_frame) -- mi = mi + 1 local val, response = receive_data() if response then update_log(LOG_INFO, string.format("receive %d byte", #response)) fail_cnt = 0 local rev_frame = response -- 解析 local pv = string.match(rev_frame, 'pv%s=%s(%d+)') local mv = string.match(rev_frame, 'mv%s=%s(%d+)') local sv = string.match(rev_frame, 'sv%s=%s(%d+)') local cv = string.match(rev_frame, 'cv%s=%s(%d+)') local cur_version = string.format('%d.%d.%d.%d', pv, mv, sv, cv) update_log(LOG_INFO, string.format("VERSION: %s", cur_version)) if (cur_version == firmware_version) then -- 版本号与当前版本一致,升级成功 update_log(LOG_NOTICE, "end firmware same version,upgrade success") else update_log(LOG_ERR, "end firmware NOT same version,upgrade failed!!!") err_code = NOT_SAME_VER success = false exit_update_result(success, "", err_code) return success end ver_flag = true break else update_log(LOG_WARNING, "ShowVersion, Timeout waiting for device response") fail_cnt = fail_cnt + 1 if fail_cnt >= (try_cnt + 5) then err_code = ACK_ERROR_OR_TIMEOUT success = false exit_update_result(success, "", err_code) return success end end end if ver_flag == true then -- 版本获取成功 break end end ]] local ver = firmware_version -- 升级成功传入设备版本,若协议不支持版本获取,则为""即可 exit_update_result(success, ver, err_code) --mysem:close() return success end local SIGTERM = 15 -- 允许进程在退出前执行清理操作(如关闭文件、释放资源),替代 kill -9 posix.signal(posix.SIGTERM, function(sig) print(string.format("[signal %d] received the termination request, cleanup...", sig)) close_pipes(pipes_table) os.exit(128 + SIGTERM) end) local ok, err = pcall(perform_upgrade, arg[1], arg[2], arg[3], arg[4]) if not ok then print("Fatal error: " .. tostring(err)) -- print to stdout,提示lua语法错误/崩溃信息 ---语、运行时法错误错误、或主动调用的 error() close_pipes(pipes_table) os.exit(1) end os.exit(0)