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">Not connected. Please connect a CAIIC-MCM device on the Device tab first</text>
</view>
<template v-if="connected">
<!-- 当前设备信息 -->
<view class="card">
<view class="infoRow">
<text class="infoLabel">Current Firmware</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">Firmware file (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">Load File</button>
</view>
<view class="infoRow" v-if="fileLoaded">
<text class="infoLabel">File Size</text>
<text class="infoValue">{{ fileSize }} bytes</text>
</view>
<view class="infoRow" v-if="fileLoaded">
<text class="infoLabel">Image CRC32</text>
<text class="infoValue">0x{{ fileCrcHex }}</text>
</view>
</view>
<!-- 目标版本 -->
<view class="card">
<text class="sectionTitle">Target version (e.g. 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">Start Upgrade</button>
<button class="btn btnWarn" size="mini" v-if="upgrading" @tap="onAbortOta">Abort</button>
</view>
<text class="warnText" v-if="upgrading">Do not disconnect or exit during upgrade</text>
</view>
<!-- 日志 -->
<view class="card" v-if="logs.length > 0">
<text class="sectionTitle">Upgrade Log</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: 'Failed to read file, check the path', icon: 'error' })
return
}
if (bytes.length > 0x38000) {
fileLoaded.value = false
uni.showToast({ title: 'File exceeds the 224KB limit', icon: 'error' })
return
}
imageBytes = bytes
fileSize.value = bytes.length
fileCrcHex.value = crc32Ieee(bytes).toString(16)
fileLoaded.value = true
addLog("File loaded: " + bytes.length + " bytes, 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: 'Invalid version format (e.g. 1.00.02)', icon: 'error' })
return
}
if (!Ble.isConnected()) {
uni.showToast({ title: 'Device not connected', icon: 'error' })
return
}
upgrading.value = true
progress.value = 0
logs.value = []
addLog("Upgrade started, target version " + 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: 'Upgrade succeeded, device will reboot', icon: 'success' })
} else {
uni.showToast({ title: 'Upgrade failed', icon: 'error' })
}
}
function onAbortOta() {
Ota.abort()
addLog("Abort requested...")
}
// ================= 设备信息 =================
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("Disconnected, upgrade aborted")
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>