2026-09-02 16:10:57 +08:00

381 lines
8.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<!-- #ifdef APP -->
<scroll-view style="flex:1">
<!-- #endif -->
<view class="page">
<view class="card" v-if="!connected">
<text class="tipText">设备未连接,请先在"设备"页连接 CAIIC-MCM</text>
</view>
<template v-if="connected">
<!-- 当前设备信息 -->
<view class="card">
<view class="infoRow">
<text class="infoLabel">当前固件版本</text>
<text class="infoValue">{{ curVersion }}</text>
</view>
<view class="infoRow">
<text class="infoLabel">运行 Bank</text>
<text class="infoValue">{{ bankInfo }}</text>
</view>
</view>
<!-- 固件文件 -->
<view class="card">
<text class="sectionTitle">固件文件(Keil 产物 bin,≤224KB)</text>
<input class="input" v-model="filePath" placeholder="/sdcard/Download/embeddedSrc.bin" :disabled="upgrading" />
<view class="btnRow">
<button class="btn" size="mini" :disabled="upgrading" @tap="onLoadFile">读取文件</button>
</view>
<view class="infoRow" v-if="fileLoaded">
<text class="infoLabel">文件大小</text>
<text class="infoValue">{{ fileSize }} 字节</text>
</view>
<view class="infoRow" v-if="fileLoaded">
<text class="infoLabel">镜像 CRC32</text>
<text class="infoValue">0x{{ fileCrcHex }}</text>
</view>
</view>
<!-- 目标版本 -->
<view class="card">
<text class="sectionTitle">目标版本号(如 1.00.02)</text>
<input class="input" v-model="versionText" placeholder="1.00.02" :disabled="upgrading" />
</view>
<!-- 升级控制 -->
<view class="card">
<view class="progressBox">
<view class="progressBar" :style="'width:' + progress + '%'"></view>
</view>
<text class="progressText">{{ progress }}%</text>
<view class="btnRow">
<button class="btn" size="mini" v-if="!upgrading" :disabled="!fileLoaded" @tap="onStartOta">开始升级</button>
<button class="btn btnWarn" size="mini" v-if="upgrading" @tap="onAbortOta">中止升级</button>
</view>
<text class="warnText" v-if="upgrading">升级中请勿断开连接或退出应用</text>
</view>
<!-- 日志 -->
<view class="card" v-if="logs.length > 0">
<text class="sectionTitle">升级日志</text>
<text class="logText" v-for="(log, index) in logs" :key="index">{{ log }}</text>
</view>
</template>
</view>
<!-- #ifdef APP -->
</scroll-view>
<!-- #endif -->
</template>
<script setup lang="uts">
import { MyApiResult } from "@/uni_modules/android-ble"
import * as Ble from "../../src/BluetoothManager"
import * as McmCmd from "../../src/mcm/McmCommands.uts"
import * as Ota from "../../src/mcm/OtaManager.uts"
import { OtaCallbacks } from "../../src/mcm/OtaManager.uts"
import { crc32Ieee } from "../../src/mcm/Crc.uts"
// #ifdef APP-ANDROID
import File from 'java.io.File'
import FileInputStream from 'java.io.FileInputStream'
// #endif
const connected = ref<boolean>(false)
const curVersion = ref<string>("--")
const bankInfo = ref<string>("--")
const filePath = ref<string>("/sdcard/Download/embeddedSrc.bin")
const fileLoaded = ref<boolean>(false)
const fileSize = ref<number>(0)
const fileCrcHex = ref<string>("")
const versionText = ref<string>("")
const upgrading = ref<boolean>(false)
const progress = ref<number>(0)
const logs = ref<string[]>([])
let imageBytes = [] as number[]
function addLog(msg: string) {
logs.value.push(msg)
}
// ================= 文件读取(Android 本地文件) =================
function readFileBytes(path: string): number[] | null {
// #ifdef APP-ANDROID
try {
const f = new File(path)
if (!f.exists()) return null
const len: Int = f.length().toInt()
if (len <= 0) return null
const fis = new FileInputStream(f)
const buf = new ByteArray(len)
let readTotal: Int = 0
while (readTotal < len) {
const n = fis.read(buf, readTotal, len - readTotal)
if (n <= 0) break
readTotal += n
}
fis.close()
if (readTotal != len) return null
const out = [] as number[]
var i: Int = 0
while (i < len) {
out.push(buf[i].toInt() & 0xFF)
i++
}
return out
} catch (e) {
console.log("readFileBytes error", e)
return null
}
// #endif
// #ifndef APP-ANDROID
return null
// #endif
}
function onLoadFile() {
const bytes = readFileBytes(filePath.value.trim())
if (bytes == null) {
fileLoaded.value = false
uni.showToast({ title: '文件读取失败,请检查路径', icon: 'error' })
return
}
if (bytes.length > 0x38000) {
fileLoaded.value = false
uni.showToast({ title: '文件超过 224KB 上限', icon: 'error' })
return
}
imageBytes = bytes
fileSize.value = bytes.length
fileCrcHex.value = crc32Ieee(bytes).toString(16)
fileLoaded.value = true
addLog("已加载文件: " + bytes.length + " 字节, CRC32=0x" + fileCrcHex.value)
}
// ================= 版本号解析 "1.00.02" -> 0x010002 =================
function parseVersion(v: string): number | null {
const parts = v.trim().split(".")
if (parts.length != 3) return null
const a = parseInt(parts[0])
const b = parseInt(parts[1])
const c = parseInt(parts[2])
if (isNaN(a) || isNaN(b) || isNaN(c)) return null
if (a < 0 || a > 255 || b < 0 || b > 255 || c < 0 || c > 255) return null
return a * 65536 + b * 256 + c
}
// ================= OTA =================
async function onStartOta() {
const version = parseVersion(versionText.value)
if (version == null) {
uni.showToast({ title: '版本号格式错误(如 1.00.02)', icon: 'error' })
return
}
if (!Ble.isConnected()) {
uni.showToast({ title: '设备未连接', icon: 'error' })
return
}
upgrading.value = true
progress.value = 0
logs.value = []
addLog("开始升级,目标版本 " + versionText.value)
const cbs: OtaCallbacks = {
onProgress: (percent: number) => {
progress.value = percent
},
onState: (msg: string) => {
addLog(msg)
}
} as OtaCallbacks
const ok = await Ota.start(imageBytes, version, cbs)
upgrading.value = false
if (ok) {
uni.showToast({ title: '升级成功,设备将自动复位', icon: 'success' })
} else {
uni.showToast({ title: '升级失败', icon: 'error' })
}
}
function onAbortOta() {
Ota.abort()
addLog("用户请求中止 ...")
}
// ================= 设备信息 =================
async function loadDeviceInfo() {
const v = await McmCmd.getVersion()
curVersion.value = v != null ? v : "--"
const b = await McmCmd.getBankInfo()
if (b != null) {
bankInfo.value = b.indexOf("APP2") >= 0 ? "APP2" : (b.indexOf("APP1") >= 0 ? "APP1" : "--")
}
}
// ================= 蓝牙状态监听 =================
function onBleStatueListener(state: number) {
if (state == 10000 || state == 10001) {
connected.value = false
if (upgrading.value) {
addLog("连接断开,升级中止")
upgrading.value = false
}
}
}
let subscribed = false
function subscribe() {
if (subscribed) return
uni.$on("bleState", onBleStatueListener)
subscribed = true
}
function unsubscribe() {
if (!subscribed) return
uni.$off("bleState", onBleStatueListener)
subscribed = false
}
// ================= 生命周期 =================
onLoad((options: UTSJSONObject | null) => {
subscribe()
})
onShow(() => {
subscribe()
connected.value = Ble.isConnected()
if (connected.value) {
loadDeviceInfo()
}
})
onHide(() => {
unsubscribe()
})
onUnload(() => {
unsubscribe()
// 离开页面时若仍在升级则中止(升级期间不建议离开)
if (Ota.isRunning()) {
Ota.abort()
}
})
</script>
<style scoped>
.page{
display:flex;
flex-direction:column;
padding: 0 20rpx;
margin-bottom: 16px;
}
.card {
border-radius: 36rpx;
background-color: #ffffff;
padding: 24rpx;
margin-top: 20rpx;
box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.08);
display: flex;
flex-direction: column;
}
.tipText {
font-size: 26rpx;
color: #999999;
text-align: center;
padding: 20rpx 0;
}
.sectionTitle {
font-size: 26rpx;
color: #888888;
margin-bottom: 12rpx;
}
.input {
border: 1px solid #dddddd;
border-radius: 12rpx;
padding: 16rpx;
font-size: 26rpx;
}
.infoRow {
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 8rpx 0;
}
.infoLabel {
font-size: 26rpx;
color: #888888;
flex-shrink: 0;
}
.infoValue {
font-size: 24rpx;
color: #333333;
flex: 1;
text-align: right;
margin-left: 20rpx;
}
.btnRow {
display: flex;
flex-direction: row;
justify-content: flex-end;
margin-top: 16rpx;
}
.btn {
background-color: #3A6FA1;
color: #ffffff;
}
.btnWarn {
background-color: #dd524d;
}
.progressBox {
height: 24rpx;
border-radius: 12rpx;
background-color: #eeeeee;
overflow: hidden;
margin-top: 8rpx;
}
.progressBar {
height: 100%;
background-color: #3A6FA1;
}
.progressText {
font-size: 26rpx;
color: #3A6FA1;
text-align: center;
margin-top: 8rpx;
}
.warnText {
font-size: 24rpx;
color: #dd524d;
text-align: center;
margin-top: 12rpx;
}
.logText {
font-size: 22rpx;
color: #333333;
font-family: monospace;
line-height: 1.5;
}
</style>